【Kubernetes】StatefulSetについて理解する

スポンサーリンク

はじめに

KubernetesのStatefulSetについて、どんなものなのか動かしながら理解していきたいと思います。

StatefulSetとは

StatefulSetとは、データベースなどステートフルなPodを管理するオブジェクトになります。

ざっくり特徴としては、下記のものが挙げられます。

  • 各Podは一意なインデックスを含むホスト名がつけられ、再起動した場合でも同じホスト名のPodが起動される(pod-0, pod-1, pod2など)
  • 各Podに対して一意なPVCが割り当てられ、Podが削除されてもPVCは削除されない
  • 作成されるPodはインデックスが小さい順から作成され、前のPodがReadyになるまで次のPodは作成開始されない
  • 削除されるPodはインデックスが大きい順から削除され、前のPodが削除されるまで次のPodは削除されない
  • Headless Serviceを作成する必要がある

Headless Service

Headless Serviceとは、ClusterIPタイプのServiceでClusterIPの値が"None"に設定されているServiceのことです。

StatefulSetのように各Podに一意な役割があり、負荷分散のためのIPアドレスが不要な場合に使われます。

apiVersion: v1
kind: Service
metadata:
  name: headless-svc
  labels:
    app: sts-app
spec:
  ports:
  - port: 80
  clusterIP: None # clusterIPをNoneにすることでHeadless Serviceになる
  selector:
    app: sts-app

試してみる

実際にStatefulSetを操作しながら、特徴を理解していきます。

作成するファイルは以下の通りです。

.
├── service.yml
└── statefulset.yml

service.ymlstatefulset.ymlの内容は下記の通りです。

apiVersion: v1
kind: Service
metadata:
  name: myapp-svc
  labels:
    app: myapp
spec:
  ports:
  - port: 80
  clusterIP: None
  selector:
    app: myapp
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: myapp
spec:
  serviceName: "myapp-svc"
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: web
        image: nginx:1.22
        ports:
        - containerPort: 80
        volumeMounts:
        - name: html
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: html
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi

作成

まずは、StatefulSetを作成してみます。

kubectl apply -f statefulset.yml

作成される状況を確認すると、インデックスが小さいPodから作成され、前のPodが作成されてから次のPodが作成開始されているのがわかります。

❯ kubectl get pod -w
NAME      READY   STATUS              RESTARTS   AGE
myapp-0   0/1     ContainerCreating   0          2s   # 1つ目のPod起動開始
myapp-0   1/1     Running             0          5s
myapp-1   0/1     Pending             0          0s   # 2つ目のPod起動開始
myapp-1   0/1     ContainerCreating   0          0s
myapp-1   1/1     Running             0          3s
myapp-2   0/1     Pending             0          0s   # 3つ目のPod起動開始
myapp-2   0/1     ContainerCreating   0          0s
myapp-2   1/1     Running             0          4s

Headless Serviceも作成してみます。

kubectl apply -f service.yml

下記のコマンドで確認すると、ClusterIPが"None"になっていることが確認できます。

❯ kubectl get svc
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
myapp-svc    ClusterIP   None         <none>        80/TCP    5s

名前

名前についてもう少し確認してみます。

各Podは一意な名前がついています。

❯ kubectl get pod
NAME      READY   STATUS    RESTARTS   AGE
myapp-0   1/1     Running   0          9m47s
myapp-1   1/1     Running   0          9m42s
myapp-2   1/1     Running   0          9m39s

Podからホスト名を確認してみても、同じ名前が付けられていることが確認できます。

❯ kubectl exec myapp-1 -- hostname
myapp-1

例え、あるPodを削除したとしても同じ名前のPodが削除され、ホスト名も同じになっています。

❯ kubectl delete pod myapp-1
pod "myapp-1" deleted

❯ kubectl get pod
NAME      READY   STATUS    RESTARTS   AGE
myapp-0   1/1     Running   0          10m
myapp-1   1/1     Running   0          13s
myapp-2   1/1     Running   0          10m

❯ kubectl exec myapp-1 -- hostname
myapp-1

マウントするストレージ

次はマウントされているストレージ(PVC)について確認してみます。

PVCを確認すると、volumeClaimTemplatesから作成された各Podに紐づくPVCが確認できます。

❯ kubectl get pvc
NAME           STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
html-myapp-0   Bound    pvc-aca73200-b48b-4617-8dd6-40832a03a812   1Gi        RWO            hostpath       3m39s
html-myapp-1   Bound    pvc-c7444acd-52ce-4363-9217-0e299f4c822a   1Gi        RWO            hostpath       3m33s
html-myapp-2   Bound    pvc-84c45fcc-c6ca-48ee-90ea-3a281be87b00   1Gi        RWO            hostpath       3m28s

例え、Podを削除したとしても、紐づいていたPVCは削除されずに、再起動した同じ名前のPodに再度マウントされているのが確認できます。

❯ kubectl describe pod myapp-2 | grep -A 4 Volumes:
Volumes:
  html:
    Type:       PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
    ClaimName:  html-myapp-2
    ReadOnly:   false

❯ kubectl delete pod myapp-2
pod "myapp-2" deleted

❯ kubectl get pvc
NAME           STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
html-myapp-0   Bound    pvc-aca73200-b48b-4617-8dd6-40832a03a812   1Gi        RWO            hostpath       46m
html-myapp-1   Bound    pvc-c7444acd-52ce-4363-9217-0e299f4c822a   1Gi        RWO            hostpath       46m
html-myapp-2   Bound    pvc-84c45fcc-c6ca-48ee-90ea-3a281be87b00   1Gi        RWO            hostpath       46m

❯ kubectl describe pod myapp-2 | grep -A 4 Volumes:
Volumes:
  html:
    Type:       PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
    ClaimName:  html-myapp-2
    ReadOnly:   false

Podの更新

Podに更新があった場合の動きについて確認してみます。

下記コマンドでイメージの更新を行ってみます。

kubectl patch statefulset myapp -p '{"spec":{"template":{"spec":{"containers":[{"name":"web","image":"nginx:1.23"}]}}}}'

インデックスの大きいPodから停止され更新されているのが確認できます。

❯ kubectl get pod -w
NAME      READY   STATUS              RESTARTS   AGE
myapp-0   1/1     Running             0          44s
myapp-1   1/1     Running             0          37s
myapp-2   1/1     Terminating         0          41s # インデックスが大きいPodから停止
myapp-2   0/1     Pending             0          0s
myapp-2   0/1     ContainerCreating   0          0s
myapp-2   1/1     Running             0          1s  # Podの更新が完了
myapp-1   1/1     Terminating         0          39s # 次のPodが停止
myapp-1   0/1     Pending             0          0s
myapp-1   0/1     ContainerCreating   0          0s
myapp-1   1/1     Running             0          3s  # Podの更新が完了
myapp-0   1/1     Terminating         0          50s # 次のPodが停止
myapp-0   0/1     Pending             0          0s
myapp-0   0/1     ContainerCreating   0          0s
myapp-0   1/1     Running             0          2s  # Podの更新が完了

スケール

スケールアウト、スケールインの場合の動きを見てみます。

まずはスケールアウトしてみます。

kubectl scale --replicas=5 statefulset myapp

インデックスの続きからPodが作成されているのが確認できます。

❯ kubectl get pod -w
NAME      READY   STATUS              RESTARTS   AGE
myapp-0   1/1     Running             0          9m28s
myapp-1   1/1     Running             0          9m32s
myapp-2   1/1     Running             0          9m34s
myapp-3   0/1     Pending             0          1s
myapp-3   0/1     ContainerCreating   0          2s
myapp-3   1/1     Running             0          4s
myapp-4   0/1     Pending             0          1s
myapp-4   0/1     ContainerCreating   0          2s
myapp-4   1/1     Running             0          4s

次にスケールインしてみます。

kubectl scale --replicas=3 statefulset myapp

インデックスの大きいPodから終了して、スケールインしています。

❯ kubectl get pod -w
NAME      READY   STATUS        RESTARTS   AGE
myapp-0   1/1     Running       0          10m
myapp-1   1/1     Running       0          10m
myapp-2   1/1     Running       0          10m
myapp-3   1/1     Running       0          76s
myapp-4   1/1     Terminating   0          72s
myapp-4   0/1     Terminating   0          72s
myapp-3   1/1     Terminating   0          76s
myapp-3   0/1     Terminating   0          77s

削除

最後に削除について確認してみます。

StatefulSetを削除してみます。

kubectl delete statefulset myapp

インデックスが大きいPodから削除されるはずですが、タイミングが早すぎるのかkubectlの出力的には小さいものから削除されているように見えてしまっています。

❯ kubectl get pod -w
NAME      READY   STATUS        RESTARTS   AGE
myapp-0   1/1     Terminating   0          21m
myapp-1   1/1     Terminating   0          21m
myapp-2   1/1     Terminating   0          21m
myapp-0   0/1     Terminating   0          21m
myapp-0   0/1     Terminating   0          21m
myapp-1   0/1     Terminating   0          21m
myapp-1   0/1     Terminating   0          21m
myapp-2   0/1     Terminating   0          21m
myapp-2   0/1     Terminating   0          21m

試しにレプリカを10にして、削除してみましたが、出力はバラバラになっていました。

❯ kubectl get pod -w
NAME      READY   STATUS        RESTARTS   AGE
myapp-0   1/1     Terminating   0          43s
myapp-1   1/1     Terminating   0          41s
myapp-2   1/1     Terminating   0          40s
myapp-3   1/1     Terminating   0          25s
myapp-4   1/1     Terminating   0          24s
myapp-5   1/1     Terminating   0          22s
myapp-6   1/1     Terminating   0          21s
myapp-7   1/1     Terminating   0          17s
myapp-8   1/1     Terminating   0          15s
myapp-9   1/1     Terminating   0          13s
myapp-4   0/1     Terminating   0          25s
myapp-4   0/1     Terminating   0          25s
myapp-3   0/1     Terminating   0          26s
myapp-3   0/1     Terminating   0          26s
myapp-8   0/1     Terminating   0          16s
myapp-8   0/1     Terminating   0          16s
myapp-9   0/1     Terminating   0          15s
myapp-9   0/1     Terminating   0          15s
myapp-2   0/1     Terminating   0          42s
myapp-2   0/1     Terminating   0          43s
myapp-7   0/1     Terminating   0          20s
myapp-7   0/1     Terminating   0          20s
myapp-5   0/1     Terminating   0          26s
myapp-5   0/1     Terminating   0          26s
myapp-0   0/1     Terminating   0          48s
myapp-0   0/1     Terminating   0          48s
myapp-1   0/1     Terminating   0          47s
myapp-1   0/1     Terminating   0          47s
myapp-6   0/1     Terminating   0          27s
myapp-6   0/1     Terminating   0          27s

公式のドキュメントでも、インデックスの逆順から削除されると文章では書いてあるのですが、出力例は逆順にはなっていないので、kubectlの出力の問題なのかもしれません。
https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/#cascading-delete

また、StatefulSetを削除したとしても、Headless ServiceやPVCは削除されずに残っています。

❯ kubectl get svc
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
myapp-svc    ClusterIP   None         <none>        80/TCP    57m

❯ kubectl get pvc
NAME           STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
html-myapp-0   Bound    pvc-aca73200-b48b-4617-8dd6-40832a03a812   1Gi        RWO            hostpath       62m
html-myapp-1   Bound    pvc-c7444acd-52ce-4363-9217-0e299f4c822a   1Gi        RWO            hostpath       62m
html-myapp-2   Bound    pvc-84c45fcc-c6ca-48ee-90ea-3a281be87b00   1Gi        RWO            hostpath       62m

参考

タイトルとURLをコピーしました