【KubernetesのPersistentVolume】その３- StatefulSet

## 目次
- [Kubernetesハンズオン目次](https://qiita.com/Esfahan/items/4fcf51622fed20dffb88)

## StatefulSetとは
DBなどのステートフルなアプリケーションのためのリソース。
つまり、Podを再度作成しても、同じPod名、host名で作成される。

### Pod起動、停止、削除時の挙動
- 新たにPodが起動されるたびに、以下のように末尾に連番で番号が付与されていく。
    - `{Pod name}-0, {Pod name}-1, {Pod name}-2・・・`
- Pod起動時は、ナンバリングが小さいものから起動する
- Pod削除時は、ナンバリングが大きいものから停止、削除される

### Podのスケーリング時
Podのスケーリング時には、StatefulSetのPodが全てReadyになっている必要がある。

```shell-session
$ sudo kubectl get sts
NAME                  READY   AGE
sample-statefuleset   2/2     26m
```

### Podのローリングアップデート
Podの停止、削除と同様、番号の大きなPodから実施される。

### Volumeについて
- Podが起動すると、前回と同じPersistentVolumeClaimsが使用される
- Podが削除されても、使用していたPersistentVolumeClaimsは削除されない

## 担保されること
- Stable, unique network identifiers.(固定のネットワーク識別子)
- Stable, persistent storage.(固定の永続ストレージ)
- Ordered, graceful deployment and scaling.(順番を担保したデプロイとスケーリング)
- Ordered, automated rolling updates.(順番を担保した削除と終了)

参照：[Using StatefulSets](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#using-statefulsets)

## Node、Podの障害時の挙動について
StatefulSetではPodのSTATUSがUnknownになってもPodを自動起動しない。
どういうことなのか、まずはkubleteによるPodの監視方法について理解する必要がある。

### kubeletによるPodの監視方法について
各Nodeのkubeletが、masterに定期的にPodの状態を報告することでPodの状況を監視している。
つまり、Nodeの障害などでkubeletが落ちると、masterはPodの状態が確認できなくなる。
Deploymentなどでは、このようなNodeの障害が起きた場合、別のNodeに新たなPodを自動起動することでリカバリをする。

しかし、kubeletが落ちているが、Podは落ちていないという状況もあり得る。
この状況で別Nodeに新たにPodを起動すると、Podの２重起動という状況になる。
Volumeをマウントしている場合、複数のPodからの書き込みが発生することになる。
この状況は、アプリケーションによってはデータが壊れる可能性がある。

### StatefulSetでkubeletに障害が起きた場合の挙動
StatefulSetではこれを防ぐために、kubeletが落ちても新たにPodの自動起動をしない。
言い換えると、Podを手動で削除しない限り、新たなPodは起動されない。

### Podを自動で起動してほしい場合
Deploymentなど、StatefulSet以外のリソースを使う必要がある。

以下の記事がわかりやすい。
参考：[KubernetesのStatefulSetを疑ってみたが濡れ衣だった](https://qiita.com/ozota/items/30ac2477baae825700e9)


## ハンズオン
### StatefulSetの定義
以下の例ではNFSにmountしている。NFSにmountする方法は以下を参照。
参考：[【KubernetesのPersistentVolume】その２- Dynamic Provisionerを使ってNFSサーバーをPodにmountする](https://qiita.com/Esfahan/items/68e2d97545091cb6d0ac)

```yaml:statefulset.yaml
kind: StatefulSet
apiVersion: apps/v1beta1
metadata:
  name: sample-statefuleset
spec:
  serviceName: statefuleset-service
  replicas: 2
  # これを定義することでRollingUpdateされる
  updateStrategy:
    # RollingUpdate or OnDelete。Default: RollingUpdate
    # OnDeleteだとPodは自動的に更新されず、手動でPodを削除すると、自動的に新しいPodが起動される。
    type: RollingUpdate
    ##############################################################
    # partitionを設定すると、指定した番号以上のPodのみが更新される。
    # 以下の例だと、{Pod名}-1以上のPodが更新され、{Pod名}-0は更新されない。
    ##############################################################
    #rollingUpdate:
    #  partition: 1
  # Podの定義
  template:
    metadata:
      labels:
        app: sample-statefuleset
    spec:
      containers:
      - name: sample-container
        image: gcr.io/google-samples/hello-app:1.0
        volumeMounts:
        - name: sample-pvc
          mountPath: /var/data
  # 以下のTemplateでPersistentVolumeClaimを作成する
  volumeClaimTemplates:
  - metadata:
      name: sample-pvc
    spec:
      resources:
        requests:
          storage: 1Gi
      accessModes:
      - ReadWriteOnce
      # storageClassNameを省略するとHostPathでホストOS上にmountされる
      storageClassName: nfs
```

### 作成

```shell-session
$ sudo kubectl apply -f statefulset.yaml
```

### 確認
Podが２つ起動している。

```shell-session
$ sudo kubectl get pod
sample-statefuleset-0                     1/1     Running   0          25s
sample-statefuleset-1                     1/1     Running   0          16s
```

statefulsetが作成されている。

```shell-session
$ sudo kubectl get statefulset
NAME                  READY   AGE
sample-statefuleset   2/2     96s
```

PersistentVolumeClaimが作成されている。

```shell-session
$ sudo kubectl get pvc
sample-pvc-sample-statefuleset-0   Bound    pvc-092e4166-cc92-460b-b9b7-5361dffdebe5   1Gi        RWO            nfs            4m
sample-pvc-sample-statefuleset-1   Bound    pvc-6ae2f4ca-ce4d-4a67-8c0b-a9673d63002f   1Gi        RWO            nfs            3m55s
```

NFSのPersistentVolumeが作成されている。

```shell-session
$ sudo kubectl get pv
pvc-092e4166-cc92-460b-b9b7-5361dffdebe5   1Gi        RWO            Retain           Bound    default/sample-pvc-sample-statefuleset-0   nfs                     4m12s
pvc-6ae2f4ca-ce4d-4a67-8c0b-a9673d63002f   1Gi        RWO            Retain           Bound    default/sample-pvc-sample-statefuleset-1   nfs                     4m7s
```

## Podを削除してもmount領域が変わっていないか確認
Podを削除して再度Podを起動しても、同じ領域がmountされることを確認する。

### Pod削除前に確認
NFSサーバーのmount領域を確認。以下のディレクトリが作成されていることを確認。

```shell-session
$ ls -1 /var/share/nfs
default-sample-pvc-sample-statefuleset-0-pvc-092e4166-cc92-460b-b9b7-5361dffdebe5
default-sample-pvc-sample-statefuleset-1-pvc-6ae2f4ca-ce4d-4a67-8c0b-a9673d63002f
```

sample-statefuleset-0用のテストファイルを作成しておく。

```shell-session
$ touch /var/share/nfs/default-sample-pvc-sample-statefuleset-0-pvc-092e4166-cc92-460b-b9b7-5361dffdebe5/sample001.txt
```
sample-statefuleset-1用のテストファイルを作成しておく。

```shell-session
$ touch /var/share/nfs/default-sample-pvc-sample-statefuleset-1-pvc-6ae2f4ca-ce4d-4a67-8c0b-a9673d63002f/sample002.txt
```

#### 作業前にPodから参照できることを確認
sample-statefuleset-0用のテストファイルが見えることを確認。

```shell-session
$ sudo kubectl exec -it sample-statefuleset-0 ls /var/data
sample001.txt
```

sample-statefuleset-1用のテストファイルが見えることを確認。

```shell-session
$ sudo kubectl exec -it sample-statefuleset-1 ls /var/data
sample002.txt
```


### Podを削除

```shell-session
$ sudo kubectl delete -f statefulset.yaml
statefulset.apps "sample-statefuleset" deleted
```

### Podを再作成

```shell-session
$ sudo kubectl apply -f statefulset.yaml
statefulset.apps/sample-statefuleset created
```

### 作業後のNFSのmount領域を確認
NFSサーバーのmount領域を確認。Pod削除前のディレクトリが残っていることを確認。
新たにディレクトリが生成されていないことを確認。

```shell-session
[NFSサーバー]$ ls -1 /var/share/nfs
default-sample-pvc-sample-statefuleset-0-pvc-092e4166-cc92-460b-b9b7-5361dffdebe5
default-sample-pvc-sample-statefuleset-1-pvc-6ae2f4ca-ce4d-4a67-8c0b-a9673d63002f
```

sample-statefuleset-0用のテストファイルがあることを確認

```shell-session
[NFSサーバー]$ ls -1 /var/share/nfs/default-sample-pvc-sample-statefuleset-0-pvc-092e4166-cc92-460b-b9b7-5361dffdebe5
sample001.txt
```

sample-statefuleset-1用のテストファイルがあることを確認

```shell-session
[NFSサーバー]$ ls -1 /var/share/nfs/default-sample-pvc-sample-statefuleset-1-pvc-6ae2f4ca-ce4d-4a67-8c0b-a9673d63002f
sample002.txt
```

#### 作業後もPodから参照できることを確認
sample-statefuleset-0用のテストファイルが見えることを確認。

```shell-session
$ sudo kubectl exec -it sample-statefuleset-0 ls /var/data
sample001.txt
```

sample-statefuleset-1用のテストファイルが見えることを確認。

```shell-session
$ sudo kubectl exec -it sample-statefuleset-1 ls /var/data
sample002.txt
```

## Rolling Update
Rolling Updateをしてみる。

### Podのイメージを更新
Podのコンテナイメージのversionを変更してapplyする。
参考：[KubernetesでPodをローリングアップデートをする](https://qiita.com/Esfahan/items/f9c246e3c60fe8490af3)

```diff:statefulset.yaml
kind: StatefulSet
apiVersion: apps/v1beta1
metadata:
  name: sample-statefuleset
spec:
  # 省略
  template:
    # 省略
    spec:
      containers:
      - name: sample-container
-       image: gcr.io/google-samples/hello-app:1.0
+       image: gcr.io/google-samples/hello-app:2.0
        volumeMounts:
        - name: sample-pvc
          mountPath: /var/data
  # 省略
```

### Rolling Update実行

```shell-session
$ sudo kubectl apply -f statefulset.yaml
statefulset.apps/sample-statefuleset configured
```

### Rolling Update開始
状況を確認。
ナンバリングが大きいものからアップデートされていくことが分かる。

```sh
$ sudo kubectl get pod -w
NAME                                      READY   STATUS    RESTARTS   AGE
# 旧Pod
sample-statefuleset-0                     1/1     Running   0          73m
sample-statefuleset-1                     1/1     Running   0          73m
sample-statefuleset-2                     1/1     Running   0          61m
# 旧Pod2が破棄される。（ナンバリングが大きいものから破棄される）
sample-statefuleset-2                     1/1     Terminating   0          78m
sample-statefuleset-2                     0/1     Terminating   0          78m
sample-statefuleset-2                     0/1     Terminating   0          78m
sample-statefuleset-2                     0/1     Terminating   0          78m
sample-statefuleset-2                     0/1     Pending       0          0s
sample-statefuleset-2                     0/1     Pending       0          0s
sample-statefuleset-2                     0/1     ContainerCreating   0          0s
# 新Pod2が旧Podと同じ名前で起動した。
sample-statefuleset-2                     1/1     Running             0          4s
# 旧Pod1が破棄される。
sample-statefuleset-1                     1/1     Terminating         0          90m
sample-statefuleset-1                     0/1     Terminating         0          90m
sample-statefuleset-1                     0/1     Terminating         0          90m
sample-statefuleset-1                     0/1     Terminating         0          90m
sample-statefuleset-1                     0/1     Pending             0          0s
sample-statefuleset-1                     0/1     Pending             0          0s
sample-statefuleset-1                     0/1     ContainerCreating   0          0s
# 新Pod1が旧Podと同じ名前で起動した。
sample-statefuleset-1                     1/1     Running             0          4s
# 旧Pod0が破棄される。
sample-statefuleset-0                     1/1     Terminating         0          91m
sample-statefuleset-0                     0/1     Terminating         0          91m
sample-statefuleset-0                     0/1     Terminating         0          91m
sample-statefuleset-0                     0/1     Terminating         0          91m
sample-statefuleset-0                     0/1     Pending             0          0s
sample-statefuleset-0                     0/1     Pending             0          0s
sample-statefuleset-0                     0/1     ContainerCreating   0          0s
# 新Pod0が旧Podと同じ名前で起動した。
sample-statefuleset-0                     1/1     Running             0          5s
```


## 参考
- [StatefulSets](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/)
- [Kubernetes勉強会第1回 〜Secrets、StatefulSet、DaemonSet、API server への接続方法〜](http://shepherdmaster.hateblo.jp/entry/2018/03/29/210905)
- [StatefulSetっていつ使うの？PersistentVolumesでいいんじゃないの？](https://scrapbox.io/blackawa/StatefulSet%E3%81%A3%E3%81%A6%E3%81%84%E3%81%A4%E4%BD%BF%E3%81%86%E3%81%AE%EF%BC%9FPersistentVolumes%E3%81%A7%E3%81%84%E3%81%84%E3%82%93%E3%81%98%E3%82%83%E3%81%AA%E3%81%84%E3%81%AE%EF%BC%9F)
- [KubernetesでStatefulSetのRolling Updateを試してみた](https://qiita.com/k-hal/items/db2b8f78b5ef371ad084)
- [Kubernetes道場 13日目 - StatefulSet / DaemonSetについて](https://cstoku.dev/posts/2018/k8sdojo-13/)