AWS EBS CSI Driver 8.0 以降は gp3 がデフォルトになるのでアップグレードには注意が必要

AWSが提供するKubernetesのマネージドサービスであるEKSでは、デフォルトのストレージクラスとしてgp2が定義されていますが、EBS CSI driver をインストールすると、ボリュームのスナップショットやリサイズなどが行えるようになります。
https://github.com/kubernetes-sigs/aws-ebs-csi-driver

### AWS EBS CSI Driver 8.0 以降から `gp3` がデフォルトに

Amazon EBS では、汎用ボリュームとして `gp2` と `gp3` が選べます。
`gp2` に比べて `gp3` ではコストが安価になり、小容量でもIOPSやスループットを引き上げることができるようになりました。

詳しくはこの辺りを参考にしてください。

 * [新機能 – キャパシティに関わらずパフォーマンスをプロビジョニングできる Amazon EBS gp3 ボリューム](https://aws.amazon.com/jp/blogs/news/new-amazon-ebs-gp3-volume-lets-you-provision-performance-separate-from-capacity-and-offers-20-lower-price/)
 * [Amazon EBS ボリュームを gp2 から gp3 へ切り替えコストを削減する](https://blog.serverworks.co.jp/reinvent2020-gp3-pricing)

それに伴って、AWS EBS CSI Driver 8.0 以降からボリュームタイプのデフォルトが `gp2` から `gp3` に変更されています。
そのため、ボリュームタイプを指定せずにドライバーのバージョンをアップグレードしてしまうとエラーが発生してしまいます。

また、ややこしい事にアップグレード時にエラーは発生しないのですが、Podが再起動して別のノードに移動したときなど、割り当てたボリュームの付け替えが発生した際に下記のようなエラーになりPodの起動がうまくいかなくなります。

> Warning  FailedAttachVolume  71s attachdetach-controller  Multi-Attach error for volume "pvc-xxxx-xxxx-xxxx-xxx-xxxxxxxxx" Volume is already exclusively attached to one node and can't be attached to another


自分も開発環境のKubernetesでドライバーをアップグレードした際、後になってエラーが出て原因特定まで時間がかかりました。。。

### 再現実験

テスト用にほぼデフォルトの状態のEKSを作成して、エラーの再現をしてみます。

まずはデフォルトが`gp2`の古いバージョンのドライバーをインストール。
Helm で EBS CSI Driver 7.1 をインストール

```shell
$ helm upgrade --install \
       aws-ebs-csi-driver \
       https://github.com/kubernetes-sigs/aws-ebs-csi-driver/releases/download/v0.7.1/helm-chart.tgz
```

CSIドライバーの Storage Class の作成

```yaml:storage.yaml
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: ebs
provisioner: ebs.csi.aws.com
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer
```

```shell
$ kubectl apply -f storageclass.yaml
```

テスト用のPersistent Volume Claimの作成

```yaml:pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: test-pvc
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: ebs
  resources:
    requests:
      storage: 10Gi
```

```shell
$ kubectl apply -f pvc.yaml
```

テスト用のDeployment の作成

```yaml:deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-app
spec:
  selector:
    matchLabels:
      app: test-app
  template:
    metadata:
      labels:
        app: test-app
    spec:
      containers:
      - name: test-app
        image: centos
        command: ["/bin/sh"]
        args: ["-c", "while true; do echo $(date -u) >> /data/out.txt; sleep 5; done"]
        volumeMounts:
        - name: test-data
          mountPath: /data
      volumes:
      - name: test-data
        persistentVolumeClaim:
          claimName: test-pvc
```

```shell
kubectl --kubeconfig ./kubeconfig apply -f deployment.yaml
```

EBSのボリュームタイプが`gp2`のボリュームが付与されたテスト用のPodが作成されました。 

次に、AWS EBS CSI Driver 最新版 (v9.8) にアップグレード。
ボリュームタイプは指定しなかったのでデフォルトの gp3 になっています。

```shell
$ helm repo add aws-ebs-csi-driver https://kubernetes-sigs.github.io/aws-ebs-csi-driver
$ helm upgrade aws-ebs-csi-driver aws-ebs-csi-driver/aws-ebs-csi-driver
```
（7.1と9.8でインストールが異なるのは、8.0あたりからHelmレポジトリが提供されるようになったからです）


この状態でテスト用Podが動いているノードを強制的に停止して、別ノードにテスト用Podを移動させることでボリュームの付け替えを発生させます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46847/7568ab9f-b834-27d5-c45b-ab5c46bd71da.png)


やはり予想通り、ボリュームタイプが異なるとボリュームのアタッチに失敗して起動しません。


```shell
$ kubectl get pods
NAME                        READY   STATUS              RESTARTS   AGE
test-app-7886db5c8d-sqrzz   0/1     ContainerCreating   0          4m41s
```

エラーメッセージは以下の通りです。

> Warning  FailedAttachVolume  3m38s attachdetach-controller
  Multi-Attach error for volume "pvc-xxxxx-xxxxx-xxxx-xxxxxxx" Volume is already exclusively at  tached to one node and can't be attached to another                                                              Warning  FailedMount 95s kubelet, ip-xxx-xxx-xxx-xxx.ap-northeast-1.compute.internal  Unable to attach or mount volumes: unmounted volumes=[test-data], unattached volumes=[test-data default-tken-qmjpg[]: timed out waiting for the conditio

以前、開発環境でエラーを発生させてしまった時は、ドライバーのボリュームタイプのパラメーターを`gp2`に設定して解決させましたが、今回はEBSのボリュームタイプの方を`gp3`に変更した場合の挙動を確認してみます。

AWSのコンソール画面からボリュームタイプを`gp3`に変更します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46847/8537ff4f-bb84-d5be-1be1-9eb080f8db72.png)

予想としては`gp3`への変換が完了したタイミングでドライバーが正しくボリュームを認識してテスト用Podが正常起動すると思っていたのですが、実際は変換途中でドライバー側からは`gp3`のボリュームとして正しく認識していて、変換途中の段階でテスト用Podは正常起動しました。
もしかするとコンソール画面で変更処理を実行したタイミングで`gp3`として認識されるのかもしれません。

下記のように`gp3`へ変換している途中ですが、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46847/f22db794-2e21-f89a-ee11-62b59642392f.png)

テスト用のPodは正常に起動していました。

```shell
kubectl get pods
NAME                        READY   STATUS    RESTARTS   AGE
test-app-7886db5c8d-tfrsb   1/1     Running   0          4m9s
```

この挙動はちょっと意外でした。

### まとめ

アップグレード方法としてはストレージタイプのパラメーターを明示的に指定する場合は以下のようになると思います。

```shell
$ helm upgrade --set type=gp2 \
       aws-ebs-csi-driver \
       aws-ebs-csi-driver/aws-ebs-csi-driver
```

割り当てられているEBSボリュームがそれほど多くなく、ある程度のダウンタイムが発生しても許容してくれるようなシステムの場合、EBSのストレージタイプを`gp2`から`gp3`に変更する方法を検討してもいいかもしれません。
今回の実験でも`gp3`として認識するのは変換が完了する前なので、`gp3`への変換中にノードが停止するような事になっても、問題なく切り替わる可能性は高いと思います。

注意することがあるとすれば、EKSではスポットインスタンスを使うことが多いと思いますが、gp3では旧世代のインスタンスには未対応なため、対象のインスタンスタイプにそれらが含まれていないようにする必要があります。

また、ドキュメントを見る限り、`gp2`と`gp3`の両方を扱うことは無理っぽいのでどちらかに統一する必要があるが悩ましいところです。