2019年5月31日金曜日

k8s の StatefulSet を試してみた

概要

StatefulSet は Pod 名が常に同じ名前で起動し続ける機能です
これと Headless Service を組み合わせることで Pod の IP アドレスが変わっても名前を使って常に同じ Pod を参照し続けることができます
例えばクラスタ環境を構築する場合などに使います
今回は nginx コンテナを使って StatefulSet と Headless Service の挙動を確認してみました

環境

  • macOS 10.14.5
  • minikube v0.28.2

StatefulSet の yml ファイル

今回は nginx のコンテナを StatefulSet を使ってデプロイしてみます

  • vim nginx_sts.yml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  selector:
    matchLabels:
      app: nginx
  serviceName: "nginx"
  replicas: 3
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /tmp
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi

StatefulSet で構築した Pods には replicas で指定した分の Pods が作成されます
また Pods には 0 から順番にインデックス番号が振られ Pods が再作成されても同じ番号が振られます
volumeClaimTemplates を定義するのもポイントで StatefulSet の場合はデフォルトで PersistentVolumeClaim ありの状態で Pods が上がってくるのが基本です
今回は /tmp にマウントしています
DocumentRoot が /usr/share/nginx/html なのでここをマウントしてもいいのですが index.html が上書きでなくなってしまいます
PersistentVolume に cp してもいいのですが今回はテストなのでマウントポイントを変更することで対応しています

matchLabelslabelsapp: nginx は必ず同じラベルを振るようにしてください
Pods に振った app: nginx というラベルに対して StatefulSet のルールを適用するためです

これで StatefulSet をデプロイしましょう
replicas で指定した数になるまで Pods が作成されます

  • kubectl apply -f nginx_sts.yml

Service の yml ファイル

Service もポイントで Headless Service という仕組みを使うことで Pods 同士を名前でアクセスできるようにします

  • vim nginx_svc.yml
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx

clusterIP: None にするのがポイントです
これで Service をデプロイしましょう

  • kubectl apply -f nginx_svc.yml

今回作成したリソースは以下の通りです

  • kubectl get sts,pv,pvc,po,svc
NAME                   DESIRED   CURRENT   AGE
statefulset.apps/web   3         3         32m

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM               STORAGECLASS   REASON   AGE
persistentvolume/pvc-160b82d3-81a9-11e9-b396-08002719d78a   1Gi        RWO            Delete           Bound    default/www-web-0   standard                32m
persistentvolume/pvc-21951e03-81a9-11e9-b396-08002719d78a   1Gi        RWO            Delete           Bound    default/www-web-1   standard                32m
persistentvolume/pvc-28dd3ed2-81a9-11e9-b396-08002719d78a   1Gi        RWO            Delete           Bound    default/www-web-2   standard                32m

NAME                              STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/www-web-0   Bound    pvc-160b82d3-81a9-11e9-b396-08002719d78a   1Gi        RWO            standard       32m
persistentvolumeclaim/www-web-1   Bound    pvc-21951e03-81a9-11e9-b396-08002719d78a   1Gi        RWO            standard       32m
persistentvolumeclaim/www-web-2   Bound    pvc-28dd3ed2-81a9-11e9-b396-08002719d78a   1Gi        RWO            standard       32m

NAME        READY   STATUS    RESTARTS   AGE
pod/web-0   1/1     Running   0          32m
pod/web-1   1/1     Running   0          32m
pod/web-2   1/1     Running   0          32m

NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   120d
service/nginx        ClusterIP   None         <none>        80/TCP    2m

ちなみに PersistentVolume は standard という StorageClass を使っており standard は何かというとホストマシンの特定のパスをマウントする StorageClass になります

  • kubectl describe pv pvc-160b82d3-81a9-11e9-b396-08002719d78a
Source:
    Type:          HostPath (bare host directory volume)
    Path:          /tmp/hostpath-provisioner/pvc-160b82d3-81a9-11e9-b396-08002719d78a

でこのホストは何かと言うと minikube で起動している Virtualbox の VM になります
実際に ssh して確認すると該当のパスが存在するのが確認できます

  • minikube ssh
  • ls /tmp/hostpath-provisioner/pvc-160b82d3-81a9-11e9-b396-08002719d78a/

Pods を再作成しても同じ状態で上がってくることを確認する

StatefulSet の機能を確認していきます
冒頭でも少し触れましたが StatefulSet 経由で作成した Pod にはインデックス番号が振られます
そして一度振られた番号は何度 Pod を再作成しても同じ状態で起動します
その挙動を確認してみます

まず Headless Service が有効になっているか確認します
各 Pod が DNS を使って名前でアクセスできれば OK です
nslookup が nginx コンテナでは使えないので確認用の Pod を作成します

  • kubectl run -i --tty --image busybox:1.28 dns-test --restart=Never --rm
/ # nslookup web-0.nginx
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-0.nginx
Address 1: 172.17.0.7 web-0.nginx.default.svc.cluster.local
/ # nslookup web-1.nginx
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-1.nginx
Address 1: 172.17.0.8 web-1.nginx.default.svc.cluster.local
/ # nslookup web-2.nginx
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-2.nginx
Address 1: 172.17.0.9 web-2.nginx.default.svc.cluster.local

こんな感じで StatefulSet 経由で作成した各 Pod が Headless Service のおかげで名前でアクセスできるようになっています

ではこの状態で Pod を削除してみましょう

  • kubectl delete pod -l app=nginx
pod "web-0" deleted
pod "web-1" deleted
pod "web-2" deleted

しばらくすると StatefulSet が再度 Pod を作成してくれます
Running になったら再度 nslookup コマンドを実行してみましょう
先ほどと同じ名前で IP が引けることが確認できると思います
Pod を再作成すると IP が変わることがありますが名前が常に同じ状態なので名前さえわかっていれば常に同じ Pod にアクセスすることができるようになります
これが StatefulSet のメイン機能になります

nginx にアクセスするにはどうすればいいか

Pod に直接 exec すればいいのですがそれだと面倒な場合が多いです
一番てっとり早いのはもう一つ公開用の Service をデプロイするのが簡単です

  • vim nginx_svc_ext.yml
apiVersion: v1
kind: Service
metadata:
  name: nginx-ext
  labels:
    app: nginx
spec:
  type: NodePort
  selector:
    app: nginx
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  • kubectl apply -f nginx_svc_ext.yml
  • kubectl get svc
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP   10.96.0.1        <none>        443/TCP        120d
nginx        ClusterIP   None             <none>        80/TCP         2m
nginx-ext    NodePort    10.102.236.113   <none>        80:31481/TCP   1m

ちゃんと各 Pod にアクセスできているか確認するために index.html を書き換えます

  • for i in 0 2; do kubectl exec web-$i -- sh -c 'echo $(hostname) > /usr/share/nginx/html/index.html'; done

あとは minikube 経由でアクセスすれば nginx が見えると思います

  • curl $(minikube ip):31481

ちゃんと Headless Service もあるので Pods 内で名前解決も引き続きできると思います

後処理

  • kubectl delete sts web
  • kubectl delete pvc www-web-0 www-web-1 www-web-2
  • kubectl delete svc nginx nginx-ext

最後に

minikube で StatefulSet を試してみました
ポイントは

  • PersistentVolumeClaim と組み合わせて使う
  • Headless Service を使うことで Pod を名前で引けるようにする
  • 常に同じ Pod 名で起動するので名前で引ければ常に同じ Pod にアクセスすることができる

になるかなと思います
今回のサンプルの場合は PersistentVolumeClaim は必須ではないかなと思います
が、Pod が削除された場合に同じ Pod 名で起動するけどデータがないというのは少しステートフルとは違うかなと思うので基本は PersistentVolumeClaim と組み合わせて使うのが良いと思います

よく Deployment と比較されますが Deployment は Pod 名が固定にはならないので、そこがステートレスと言われているポイントなのかなと思います

参考サイト

0 件のコメント:

コメントを投稿