Auto remove PVCs created by StatefulSetについて

## はじめに

このページはKubernetes v1.23でアルファ機能となった`Auto remove PVCs created by StatefulSet`に関するまとめになります。

`Kubernetes 1.23: SIG-Apps の変更内容`を確認する中で、面白いなと思ったので記事にしてみました。

##  Auto remove PVCs created by StatefulSetについて
`Auto remove PVCs created by StatefulSet`はStatefulSet削除に`volumeClaimTemplates`で作成されてPVCを削除する機能になります。

### 機能追加の背景

StatefulSetでは`volumeClaimTemplates`を使用することにPersistentVolumeClaim(PVC)が作成され、Storage ClassがPVCを元にPersistentVolume(PV)を作成することにより、利用者はPVCとPVのリソースを明示的に作成することなくVolumeを使用することができます。

利用者は以下の例示するようなyamlを作成する必要がなくなったことが`volumeClaimTemplates`のメリットになります。

- PersistentVolumeClaim(PVC)の例

```yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: myclaim
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  resources:
    requests:
      storage: 8Gi
  storageClassName: slow
  selector:
    matchLabels:
      release: "stable"
    matchExpressions:
      - {key: environment, operator: In, values: [dev]}
```

- PersistentVolume(PV)の例

```yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv0003
spec:
  capacity:
    storage: 5Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  storageClassName: slow
  mountOptions:
    - hard
    - nfsvers=4.1
  nfs:
    path: /tmp
    server: 172.17.0.2
```

StatefulSetを削除した際に`volumeClaimTemplates`によって作成されたPVCは削除されないため、利用者は必要に応じてPVCを手動で削除する必要があります。

これはPodのライフサイクルとデータのライフサイクルが異なるために、データの削除については意図してStatefulSetから分離されているためです。あくまでStatefulSetはPodのライフサイクルを管理する機能であって、データのライフサイクルを管理するためのものではないことに気をつけてください。

ただ、こちらの[issue](https://github.com/kubernetes/kubernetes/issues/55045)にあるようにStatefulSetを削除した際に`volumeClaimTemplates`によって作成されたPVCが削除されて欲しいという声も多かったため、機能として追加されることになりました。

### 機能の利用方法

以下のようにStatefulSetの`spec`に`persistentVolumeClaimRetentionPolicy`が追加されており、`Delete`を指定することで該当機能が有効になります。

```yaml
apiVersion: apps/v1
kind: StatefulSet
  :
spec:
  persistentVolumeClaimRetentionPolicy:
    whenDeleted: Retain　　# デフォルトは`Retain`
    whenScaled: Delete　　# デフォルトは`Retain`
  :
```

- `whenDeleted`
StatefulSetが削除された時に`Delete`の場合は、PVCを削除する機能になります。

- `whenScaled`
StatefulSetのレプリカ数が減った時に`Delete`の場合は、PVCを削除する機能になります。


### 実際の動作結果

- クラスタの作成

今回の例ではminikubeを使用します。

```
$ minikube start --kubernetes-version v1.23.0 --feature-gates=StatefulSetAutoDeletePVC=true
```
アルファ機能なので、feature-gatesの`StatefulSetAutoDeletePVC`を有効化する必要があります。

- Manifestの作成

以下のように`persistentVolumeClaimRetentionPolicy`で`Delete`を設定したManifestを用意します。

```yaml
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: k8s.gcr.io/nginx-slim:0.8
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  persistentVolumeClaimRetentionPolicy:
    whenDeleted: Delete
    whenScaled: Delete
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "standard"
      resources:
        requests:
          storage: 1G
```

- Manifestの適用

```bash
$ kubectl apply -f StatefulSet.yaml    
statefulset.apps/web created

$ kubectl get sts,po,pvc,pv
NAME                   READY   AGE
statefulset.apps/web   3/3     44s

NAME        READY   STATUS    RESTARTS   AGE
pod/web-0   1/1     Running   0          44s
pod/web-1   1/1     Running   0          32s
pod/web-2   1/1     Running   0          29s

NAME                              STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/www-web-0   Bound    pvc-81240f89-c62e-4536-be78-3d27f292a770   1Gi        RWO            standard       44s
persistentvolumeclaim/www-web-1   Bound    pvc-e3221c3d-cf3e-4c0f-8ce6-96d20d126876   1Gi        RWO            standard       32s
persistentvolumeclaim/www-web-2   Bound    pvc-200dc5d9-dd8d-4037-b3bb-fc1a13eb34e5   1Gi        RWO            standard       29s

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM               STORAGECLASS   REASON   AGE
persistentvolume/pvc-200dc5d9-dd8d-4037-b3bb-fc1a13eb34e5   1Gi        RWO            Delete           Bound    default/www-web-2   standard                29s
persistentvolume/pvc-81240f89-c62e-4536-be78-3d27f292a770   1Gi        RWO            Delete           Bound    default/www-web-0   standard                39s
persistentvolume/pvc-e3221c3d-cf3e-4c0f-8ce6-96d20d126876   1Gi        RWO            Delete           Bound    default/www-web-1   standard                32s
```

StatefulSetでは`volumeClaimTemplates`とStorage Classの機能により、PVまで作成されていることが確認できます。

- Manifestの削除

```bash
$ k delete -f StatefulSet.yaml
statefulset.apps "web" deleted

$ kubectl get sts,po,pvc,pv   
No resources found
```

StatefulSetでは`volumeClaimTemplates`で作成されたPVCがStatefulSetの削除後に自動で削除されることが確認できます。

ここで一点気をつけて欲しいのが、PVが削除されたのはあくまでPVの`Reclaim Policy`に基づいたものであり、` Auto remove PVCs created by StatefulSet`とは直接関係ないということです。

### `whenDeleted`と`whenScaled`の使い分け

`whenDeleted`と`whenScaled`に対して、`Retain`と`Delete`が設定できるため、`Auto remove PVCs created by StatefulSet`では4つのパターンが存在します。それぞれの使い分け方について説明します。

1. `whenDeleted`=`Retain` & `whenScaled`=`Retain`(未指定時の動作)
k8s v1.22以前と同じ動作になります。StatefulSetからデータのライフサイクルが分離されているため、StatefulSetの削除によりPVCが削除されることはありません。

2.  `whenDeleted`=`Delete` & `whenScaled`=`Retain`
StatefulSet削除時にPVCが削除されますが、レプリカ数が減少した時にPVCが削除されることはありません。再度スケールする際に、PVの作成コストが減らせるというメリットがあります。

3.  `whenDeleted`=`Delete` & `whenScaled`=`Delete`
StatefulSet削除時とレプリカ数が減少した時にPVCを削除します。そのためPod数以上にPVCが作成されることがありません。Podの削除以降に、該当のデータを使用する必要がない場合に使用します。

4.  `whenDeleted`=`Retain` & `whenScaled`=`Delete`
StatefulSet削除にPVCは削除せず、レプリカ数が減少した時にPVCを削除します。これはスケールアップ時のデータについては保持する必要がないが、起動時のデータは保持しておきたい場合などに使います。最低限3個のPVは必要だが、それ以降にスケールアップしたときに作成されたPVは不要な場合などに有用です。
ただし、最低3個のPVを保持したい場合でも、`whenScaled`は有効化されてるのでレプリカ数が1になればPVCも1個になります。HPAと併用する場合はminReplicasを3にするなど、この機能とは別のところでレプリカ数に対するガードをかけないとPVが消える可能性がある点に注意してください。

### 気をつける点
`Auto remove PVCs created by StatefulSet`はStatefulSetに対してデータのライフサイクルに関する権限の譲渡する機能になります。そのため、StatefulSetの削除による誤ったデータ削除のリスクがあることに気をつけてください。

個人的には以下の3つくらいのパターンは起こっても不思議はないなと思っています。

- Manifest更新時に削除してしまう。
  手動で作業時に`apply`を使う場面で、一度きれいにしておきたくて`delete`をする。商用環境でやることはなかなかないと思いますが、テスト中くらいなら普通にやるケースはある気がしてます。

- `kubectl apply --prune`で誤って削除してしまう
 `prune`の機能は非常に便利なんですが、誤った利用による事故も多いのでこれはこの機能限らず気をつけてほうがいいと思います。個人的には商用環境での手動での`prune`の利用は禁止してもいいんじゃないかなくらいに、使いたくないです。

- CDツールによる意図しない`delete`の実行
通常はapplyを使用してると思うのですが、`force sync`みたいな機能では中で`delete`実装されてるケースもあるのかなという不安があります。Deploymentの場合は意図せず`delete`が走っても一時的にPodがなくなるだけなんですが、今回の機能を利用しているとデータが削除される可能性があるので大変心配してます。この機能を有効化する際には必ずお使いのCDツールの挙動を把握するようにしてください。
 
### 所感
データを誤って削除するリスクは怖いものの、便利な機能かなと思います。手元でテストした後にPVCを手動で消すのは正直、めんどくさいのでこの機能があるとありがたいです。

## 参考
`Auto remove PVCs created by StatefulSet`に関連する情報は以下を参考にしました。

- [公式ドキュメント：PersistentVolumeClaim retention](https://github.com/kubernetes/kubernetes/pull/99728)
- [KEP-1847: Auto delete PVCs created by StatefulSet](https://github.com/kubernetes/enhancements/tree/master/keps/sig-apps/1847-autoremove-statefulset-pvcs)
- 機能追加時の[PR](https://github.com/kubernetes/kubernetes/pull/99728)