EKSでPersistentVolumeに紐付くEBSのサイズ拡張をゼロダウンタイムで行う

# はじめに
PersistenVolumeClaim(PVC)を使っているPodに対してストレージサイズ拡張に伴う再起動が必要ない、すなわちゼロダウンタイムで拡張を行える方法をDeploymentとStatefulSetの2パターンで説明します。

環境はEKS、外部ストレージはEBSを前提としています。

# 手順
kubeletの設定、StorageClassの設定、PVCの拡張、の順に説明していきます。
## kubeletの設定
Cloudformationのワーカーノード起動時の設定に「BootstrapArguments」の項目があるので、そこにkubeletの設定を加えます。

```
--kubelet-extra-args "--feature-gates=ExpandInUsePersistentVolumes=true"
```

ワーカーノードの設定はあらかじめ入れておかないと意味がないことに注意しましょう。例えばワーカーノード作成後にSSHして設定を変えたとしても、自動復旧で立ち上がった新たなワーカーノードにはその設定が反映されません。


## StorageClassの設定
EKSにはあらかじめ`gp2`というStorageClassが用意されています。
デフォルトではストレージ拡張のためのパラメータ`allowVolumeExpansion`が`false`になっているので変更する必要があります。

```console
## "allowVolumeExpansion: true"を追記する。
$ kubectl edit storageclass gp2

## 変更内容を確認する。
$ kubectl get storageclass gp2 -o yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"storage.k8s.io/v1","kind":"StorageClass","metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"},"name":"gp2"},"parameters":{"fsType":"ext4","type":"g    p2"},"provisioner":"kubernetes.io/aws-ebs"}
    storageclass.kubernetes.io/is-default-class: "true"
  creationTimestamp: 2019-08-13T03:34:59Z
  name: gp2
  resourceVersion: "70681"
  selfLink: /apis/storage.k8s.io/v1/storageclasses/gp2
  uid: 53ddc7e2-bd7b-11e9-a365-0a4781fb1e78
parameters:
  fsType: ext4
  type: gp2
provisioner: kubernetes.io/aws-ebs
reclaimPolicy: Delete
volumeBindingMode: Immediate
allowVolumeExpansion: true
```

## PVCの拡張
DeploymentとStatefulSetの2パターン説明します。両方ともDynamic ProvisioningでPVを動的に生成することを前提とします。

拡張を行う際には下記2点に注意しましょう。

* AWSでは1つのボリュームの拡張変更を行えるのは6時間に1度だけ
* ボリュームサイズを元の値より小さくすることはできない


### Deploymentの場合
手順は次のようになります。

1. PVCのマニフェストファイル`pvc.yaml`の`spec.resources.requests.storage`の値を変更する。
2. `$ kubectl apply -f pvc.yaml`で変更を反映させる。



実験として、あるPVCを8Giから9Giに変更してみます。この作業を行うと自動的にEBSのリサイズが始まります。下の画像を見るとボリュームの状態が`in-use`となっていることが分かります。
<img width="1436" alt="スクリーンショット 2019-08-14 19.07.12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/f4abaf9e-1d4b-64dd-e50c-9fdba0e45c74.png">

このときPVCを確認してもサイズは8Giのままです。

```console
$ kubectl get pvc
NAME             STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
sample           Bound     pvc-1a94f083-bdd3-11e9-adc6-0ed3a87e16f4   8Gi        RWO            gp2            20h
```

しばらく経つと状態が`in-use`になりました。
<img width="1439" alt="スクリーンショット 2019-08-14 19.24.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/00b818f1-9dbe-49f9-3c9e-323000874e34.png">

PVCを確認してみます。サイズが9Giに変わっていることが分かります。

```console
$ kubectl get pvc
NAME             STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
sample           Bound     pvc-1a94f083-bdd3-11e9-adc6-0ed3a87e16f4   9Gi        RWO            gp2            20h
```

### StatefulSetの場合
PVCのリソーススペックはStatefulSetのマニフェストにおける`spec.volumeClaimTemplates`で定義しているものとします。
このもとで、手順は次のようになります。

1. `$ kubectl edit pvc <PVC>`で`spec.resources.requests.storage`の値を変更する。
2. `$ kubectl delete statefulset <STATEFULSET> --cascade=false`でPodを削除せずStatefulSetのみ削除する。
3. 1で変更した`spec.resources.requests.storage`の値を、StatefulSetのマニフェストファイル`statefulset.yaml`の`spec.volumeClaimTemplates[].spec.resources.requests.storage`に記述する。
4. `$ kubectl apply -f statefulset.yaml`で変更を反映させる。

StatefulSetを途中で削除しています。これは、PVCのマニフェストで定義されている内容とStatefulSetのマニフェストに相違がないようにするためです。

直接StatefulSetの`spec.volumeClaimTemplates[].spec.resources.requests.storage`に変更を加えて`kubectl apply`しても下記のエラーが発生します。

```console
$ kubectl apply -f statefulset.yaml
* spec: Forbidden: updates to statefulset spec for fields other than 'replicas', 'template', and 'updateStrategy' are forbidden
```


# まとめ
EKSにデプロイしたDeployment、StatefulSetにアタッチされているEBSのボリューム拡張をゼロダウンタイムで行う方法を説明しました。
StatefulSetは冪等性を保たせるために無理やりな方法を取っていますが、今の所他の方法がなさそうです。