概要
下記のエントリーの続きです。
既に半年経ちましたが、前回microk8sをubuntu(実際はVMです)にインストールし、python + flaskで作成したhello worldアプリ(以下、flask-helloアプリ)を含むコンテナを単独のpodとして起動しました。
最近、少し真面目にkubernetesの勉強を始め、kubernetesの公式サイトやその他の情報ソースを見ながら下記の流れでWebアプリのkubernetesへの配備と特にロードバランシングの基礎を確認中です。
①podが複数起動する様にdeploymentとreplicasetを定義、podが落ちても自動的に一定数のpodが起動し続ける様にする。
②serviceを定義し、replicasetで定義されたpod群をservice nameで(DNSを使い)検索可能とすると共に、serviceへのアクセスを自動的に各podにロードバランスされる様にする。
③serviceをノードの外部に公開し、External IPでアクセス可能とする。その際も自動的に各podにロードバランスさせる。
④外部のクライアントからpod上のWebアプリへのアクセス時に、同一クライアントからは同一podにアクセスする様にセッション維持(sticky session)を行う。
今回は①及び②までです。(③④はこれから実験)
ちなみにdeploymentそのものの解説はこちらの記事が自分には判りやすかったです。
qiita.com
事前準備
flask-helloアプリの改造
前回エントリーで使用したサンプルアプリは単純に"Hello World!!"という文字列をhttpアクセスに対して応答するだけでしたが、今回複数のpodを起動するにあたり、どのpodにアクセスしたかが判る様に、ホスト名とIPアドレスも一緒に返す様にします。
ソースは以下の通りです。
#!/usr/bin/env python3 # -*- coding: utf-8 -*- from flask import Flask import socket PORT=5000 my_host = socket.gethostname() my_ip = socket.gethostbyname(my_host) app = Flask(__name__) @app.route('/') def hello(): global myhost,my_ip mes = 'Hello World!!' + ',Hostname='+my_host+',IP='+my_ip+'\n' return mes if __name__ == '__main__': app.run(host='0.0.0.0',port=PORT)
アプリ起動時にmy_hostとmy_ipにホスト名とIPアドレスを取得し、これらの値をHTTPアクセスに対して返信しています。
ソースを修正後、dockerイメージをビルドしなおし、microk8sに再登録します。
イメージのビルドと登録手順は前回エントリーを参照願います。
microk8sでdnsサービスを有効化
serviceの検索にmicrok8s内でdns機能が必要になります。下記コマンドでdnsがenableか否か確認し、disableの場合はenableに変更します。
$ sudo microk8s.status microk8s is running high-availability: no datastore master nodes: 127.0.0.1:19001 datastore standby nodes: none addons: enabled: dashboard # The Kubernetes dashboard ha-cluster # Configure high availability on the current node metrics-server # K8s Metrics Server for API access to service metrics disabled: ambassador # Ambassador API Gateway and Ingress cilium # SDN, fast with full network policy dns # CoreDNS fluentd # Elasticsearch-Fluentd-Kibana logging and monitoring gpu # Automatic enablement of Nvidia CUDA helm # Helm 2 - the package manager for Kubernetes helm3 # Helm 3 - Kubernetes package manager host-access # Allow Pods connecting to Host services smoothly ingress # Ingress controller for external access istio # Core Istio service mesh services jaeger # Kubernetes Jaeger operator with its simple config keda # Kubernetes-based Event Driven Autoscaling knative # The Knative framework on Kubernetes. kubeflow # Kubeflow for easy ML deployments linkerd # Linkerd is a service mesh for Kubernetes and other frameworks metallb # Loadbalancer for your Kubernetes cluster multus # Multus CNI enables attaching multiple network interfaces to pods portainer # Portainer UI for your Kubernetes cluster prometheus # Prometheus operator for monitoring and logging rbac # Role-Based Access Control for authorisation registry # Private image registry exposed on localhost:32000 storage # Storage class; allocates storage from host directory traefik # traefik Ingress controller for external access
上記ではdisabled:のリストにdnsが含まれるため、下記コマンドでenableにします。
$ sudo microk8s.enable dns Enabling DNS Applying manifest serviceaccount/coredns created configmap/coredns created deployment.apps/coredns created service/kube-dns created clusterrole.rbac.authorization.k8s.io/coredns created clusterrolebinding.rbac.authorization.k8s.io/coredns created Restarting kubelet DNS is enabled
念のためstatusを再度確認します。
$ sudo microk8s.status microk8s is running high-availability: no datastore master nodes: 127.0.0.1:19001 datastore standby nodes: none addons: enabled: dashboard # The Kubernetes dashboard dns # CoreDNS ha-cluster # Configure high availability on the current node metrics-server # K8s Metrics Server for API access to service metrics disabled: ambassador # Ambassador API Gateway and Ingress cilium # SDN, fast with full network policy fluentd # Elasticsearch-Fluentd-Kibana logging and monitoring gpu # Automatic enablement of Nvidia CUDA helm # Helm 2 - the package manager for Kubernetes helm3 # Helm 3 - Kubernetes package manager host-access # Allow Pods connecting to Host services smoothly ingress # Ingress controller for external access istio # Core Istio service mesh services jaeger # Kubernetes Jaeger operator with its simple config keda # Kubernetes-based Event Driven Autoscaling knative # The Knative framework on Kubernetes. kubeflow # Kubeflow for easy ML deployments linkerd # Linkerd is a service mesh for Kubernetes and other frameworks metallb # Loadbalancer for your Kubernetes cluster multus # Multus CNI enables attaching multiple network interfaces to pods portainer # Portainer UI for your Kubernetes cluster prometheus # Prometheus operator for monitoring and logging rbac # Role-Based Access Control for authorisation registry # Private image registry exposed on localhost:32000 storage # Storage class; allocates storage from host directory traefik # traefik Ingress controller for external access
enabledにdnsが含まれています。また下記のコマンドで、enableにしたDNSサーバー機能のIPアドレスを確認できます。
$ sudo microk8s kubectl get services --namespace=kube-system NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE metrics-server ClusterIP 10.152.183.144 <none> 443/TCP 210d kubernetes-dashboard ClusterIP 10.152.183.59 <none> 443/TCP 210d dashboard-metrics-scraper ClusterIP 10.152.183.209 <none> 8000/TCP 210d kube-dns ClusterIP 10.152.183.10 <none> 53/UDP,53/TCP,9153/TCP 53s
kube-dnsがClusterIP 10.152.183.10で配備されています。10.x.x.xはmicrok8sのクラスターネットワークです。
deployment,replicasetを定義する
まずはflask-helloアプリを複数自動起動するためのdeployment及びreplicasetを定義します。
deploymentはコンテナ群の起動の起点になるワークロードで、deploymentがreplicasetを起動し、replicasetがpodを監視して必要数のpodを常に立ち上げる形です。
従って、deploymentの定義がreplicasetの定義を参照し、replicasetの定義がpodの定義を参照する形で定義ファイルを作成します。
以下はdeployment,replicaset,pod(flask-helloアプリ)の3つの定義を含むyamlファイルの例です。replica2-flaskhello.yamlというファイル名で作成しました。
podのレプリカは2つ起動する様しています。
前回エントリーと異なるのは、podのspecからhostPort(ホストマシンのportへの紐付け)定義を削除したことです。
従って、本定義で起動したpodにホストマシンのIPアドレスでアクセスすることはできません。
apiVersion: apps/v1 kind: Deployment metadata: name: flask-hello-deploy spec: selector: matchLabels: app: flask-hello replicas: 2 template: metadata: name: flask-hello labels: app: flask-hello spec: containers: - name: hello-flask image: myubuntu:flask-hello imagePullPolicy: Never ports: - containerPort: 5000
この定義を使ってdeployment,replicaset,podを起動してみます。
まず起動前の状態確認。
$ sudo microk8s kubectl get pods,services,deployments NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.152.183.1 <none> 443/TCP 217d
起動し、起動したリソース(ワークロード)を確認。
$ sudo microk8s kubectl create -f replica2-flaskhello.yaml deployment.apps/flask-hello-deploy created $ sudo microk8s kubectl get pods,services,deployments NAME READY STATUS RESTARTS AGE pod/flask-hello-deploy-5465d8d8b-g7pwx 1/1 Running 0 23s pod/flask-hello-deploy-5465d8d8b-z2r76 1/1 Running 0 23s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.152.183.1 <none> 443/TCP 217d NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/flask-hello-deploy 2/2 2 2 23s
microk8sのdashboardでも起動状況を確認できます。
コマンドでpodの状況の詳細を確認します。
$ sudo microk8s kubectl get pods --output=wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES flask-hello-deploy-5465d8d8b-g7pwx 1/1 Running 0 7m15s 10.1.3.218 ubuntu01 <none> <none> flask-hello-deploy-5465d8d8b-z2r76 1/1 Running 0 7m15s 10.1.3.214 ubuntu01 <none> <none> toy@ubuntu01:~/work$
2つのpodがClusterIP 10.1.3.218と10.1.3.214で起動していることが判ります。
実はこれらのIPアドレスにはホスト上から仮想IFにrouteが張られています。
$ ip route show default via 192.168.11.1 dev ens3 proto static 10.1.3.213 dev calid0e455418ee scope link 10.1.3.214 dev caliea59ba834f1 scope link 10.1.3.215 dev cali0d6e69d44d8 scope link 10.1.3.216 dev calica6bd0d5a5b scope link 10.1.3.217 dev cali7d67f0238a8 scope link 10.1.3.218 dev cali846f3dd338c scope link 10.1.3.220 dev cali0cf6c347e90 scope link 172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 linkdown 192.168.11.0/24 dev ens3 proto kernel scope link src 192.168.11.252
というわけでホストからcurlコマンドでpodにアクセスしてみます。
$ curl http://10.1.3.218:5000 Hello World!!,Hostname=flask-hello-deploy-5465d8d8b-g7pwx,IP=10.1.3.218 $ curl http://10.1.3.214:5000 Hello World!!,Hostname=flask-hello-deploy-5465d8d8b-z2r76,IP=10.1.3.214
こんな感じで、それぞれのpodが別のホスト名、IPアドレスを返してきます。ホスト名,IPアドレスはkubectlコマンドで確認したNAME,IPと同じです。
ちなみにこれらのpodのうち1つを、dashboard上で「削除」すると、直ぐに新しいpodが起動されてpod数はreplicasetで定義した数(今回のサンプルでは2)に戻ります。
逆にコンテナを完全に止めるために先にdeploymentを削除する必要があります。
$ sudo microk8s kubectl delete deployment.apps/flask-hello-deploy deployment.apps "flask-hello-deploy" deleted
serviceを定義する
podのIPを毎回調べて個別アクセスは面倒なので、serviceを定義し、service経由でpodにアクセス可能とします。
service定義は下記の様にserviceが提供するportと、それを紐付けるpod側のtargetPortを記述し、selectorにpodのlabelを定義します。
以下の定義ファイル(svc-flaskhello.yaml)では一本の定義ファイルでserviceとdeployment,replicas,podを纏めて起動できる様に同じファイルの後半にdeployment以下の定義を記述しています。
定義ファイルを分離して別々に起動することも可能です。
apiVersion: v1 kind: Service metadata: name: flask-hello spec: ports: - protocol: TCP port: 5000 targetPort: 5000 selector: app: flask-hello --- apiVersion: apps/v1 kind: Deployment metadata: name: flask-hello-deploy spec: selector: matchLabels: app: flask-hello replicas: 2 template: metadata: name: flask-hello labels: app: flask-hello spec: containers: - name: hello-flask image: myubuntu:flask-hello imagePullPolicy: Never ports: - containerPort: 5000
起動してみます。(合わせて、立ち上げた各ワークロードの状態を確認しています。)
$ sudo microk8s kubectl create -f svc-flaskhello.yaml service/flask-hello created deployment.apps/flask-hello-deploy created $ sudo microk8s kubectl get pods,services,deployments NAME READY STATUS RESTARTS AGE pod/flask-hello-deploy-5465d8d8b-25tgf 1/1 Running 0 18s pod/flask-hello-deploy-5465d8d8b-nm9pn 1/1 Running 0 18s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.152.183.1 <none> 443/TCP 217d service/flask-hello ClusterIP 10.152.183.179 <none> 5000/TCP 19s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/flask-hello-deploy 2/2 2 2 19s $ sudo microk8s kubectl get pods --output=wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES flask-hello-deploy-5465d8d8b-25tgf 1/1 Running 0 4m41s 10.1.3.225 ubuntu01 <none> <none> flask-hello-deploy-5465d8d8b-nm9pn 1/1 Running 0 4m41s 10.1.3.224 ubuntu01 <none> <none>
立ち上がったserviceを見るとservice/flash-helloにClusterIP10.152.183.179が付与されています。
このIPにcurlで数回アクセスしてみます。
$ curl http://10.152.183.179:5000 Hello World!!,Hostname=flask-hello-deploy-5465d8d8b-nm9pn,IP=10.1.3.224 $ curl http://10.152.183.179:5000 Hello World!!,Hostname=flask-hello-deploy-5465d8d8b-25tgf,IP=10.1.3.225 $ curl http://10.152.183.179:5000 Hello World!!,Hostname=flask-hello-deploy-5465d8d8b-25tgf,IP=10.1.3.225 $ curl http://10.152.183.179:5000 Hello World!!,Hostname=flask-hello-deploy-5465d8d8b-nm9pn,IP=10.1.3.224 $ curl http://10.152.183.179:5000 Hello World!!,Hostname=flask-hello-deploy-5465d8d8b-25tgf,IP=10.1.3.225
serviceのIPを使いflask-helloアプリにアクセス出来ています。アプリが返すHostnameとIPからアクセス先がランダムに選択されていることが判ります。
最後にservice名を使ってDNSでserviceのIPを検索してみます。
service名は<サービス名>.default..svc.cluster.localとなっています。(defaultのところはnamespaceを変更すると変わるはずですが試していません。)
nslookupコマンドでDNSサーバーアドレスを先ほど確認した10.152.183.10にしてホストから検索をかけてみます。
$ nslookup flask-hello.default.svc.cluster.local 10.152.183.10 Server: 10.152.183.10 Address: 10.152.183.10#53 Name: flask-hello.default.svc.cluster.local Address: 10.152.183.179
先ほどcurlでアクセスしたのと同じserviceのIPが引けています。このDNSはClusterネットワーク内の各podからも同じ様に検索可能なはずです。
ということで今回はここまでです。
次回は外部IPを利用したserviceの公開にチャレンジ予定です。(いつになるやら...)
【追記】最後に後始末
実験終わったらお片づけします。
$ sudo microk8s kubectl delete service/flask-hello service "flask-hello" deleted $ sudo microk8s kubectl delete deployment/flask-hello-deploy deployment.apps "flask-hello-deploy" deleted $ sudo microk8s kubectl get pods,services,deploymentsNAME READY STATUS RESTARTS AGE pod/flask-hello-deploy-5465d8d8b-25tgf 1/1 Terminating 0 16m pod/flask-hello-deploy-5465d8d8b-nm9pn 1/1 Terminating 0 16m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.152.183.1 <none> 443/TCP 217d $ sudo microk8s kubectl get pods,services,deployments NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.152.183.1 <none> 443/TCP 217d