ローリングコンバットピッチなう!

AIとか仮想化とかペーパークラフトとか

microk8sでservice,deployment,replicasetを定義して、podを複数立ち上げる

概要

下記のエントリーの続きです。

rc30-popo.hatenablog.com

既に半年経ちましたが、前回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でも起動状況を確認できます。
f:id:RC30-popo:20211225230124p:plain


コマンドで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