Kubernetes: CSI を使ったEphemeral Volumeの動作検証

# はじめに
本検証では、CSI を使った Ephemeral Volume として Kubernetes v1.16 で Beta となったCSI Ephemeral Volume と、Kubernetes v1.21 で Beta となった Generic Ephemeral Volume について検証を行い、その違いを示します。

# Ephemeral Volume とは
`Ephemeral` とは「儚い、短命な」という意味です。これに対して `Persistent` は「永続的な」という意味です。つまり、`Ephemeral Volume` とはデータの永続化を目的としたものでなく、一時的なデータの保存を目的としたボリュームになります。
Kubernetes においては、Pod のライフタイムに従い、Podと共に作成/削除されるボリュームを指します。
Kubernetes では、以下の種類のEphemeral Volumeをサポートしています([こちら](https://kubernetes.io/docs/concepts/storage/ephemeral-volumes/)を参考)。

- emptyDir
  - kubelet のベースディレクトリ又はRAM(メモリ)から空の領域をPodへボリュームとして割り当てます
  - データはkubeletが稼働しているノード上に格納されます
- configMap, downwardAPI, secret
  -  KubernetesのリソースをPodへボリュームとして割り当てます
- CSI Ephemeral Volume (※)
  - 特定のCSI Driver を通じ作成されるボリュームをPodへ割り当てます
  - データはCSI Driverのターゲットとなるストレージに格納されます
- Generic Ephemeral Volume 
  - 通常のPVC/PVの仕組み(Dynamic Provisionig)を使い作成されるボリュームをPodへ割り当てます
  - データはCSI Driverのターゲットとなるストレージに格納されます

上記に示すように、CSI Ephemeral Volume と Generic Ephemeral Volume は、emptyDir と異なり、ノードのメディアやRAM上の保存するのではなく、CSI Driverのターゲットとなるストレージに保存されます。
そのため、ノードのメディア(SSD/HDD)やRAMの容量を超えるサイズのデータを保存できます。
CSI Ephemeral Volume と Generic Ephemeral Volume の違いについては、以下の動作検証を通じ紹介します。

※ CSI Ephemeral Volumeは、以前 CSI Inline Volume と呼ばれていたこともあり、各種 CSI Driver のドキュメントでは Inline Volume と記載されていることがあります。

# 動作検証

CSI Ephemeral Volume と Generic Ephemeral Volume の動作検証を行います。

## 検証環境

- minikube v1.19.0
  - Kubernetes v1.21.0
- CSI Driver: csi-driver-host-path

## 動作検証

### 事前準備 (csi-driver-host-path のセットアップ)

minikubeを使いKubernetes v1.21.0を`--feature-gates=CSIVolumeHealth=true ` を指定し立ち上げます。 

```
$ minikube start --vm-driver=virtualbox --kubernetes-version=1.21.0 
```

minikubeによりKubernetes v1.21.0が起動したことを確認します。
次に、Volume Health Monitorに対応しているcsi-driver-host-pathをセットアップします。 

```
$ git clone git@github.com:kubernetes-csi/csi-driver-host-path.git
$ cd csi-driver-host-path/
$ deploy/kubernetes-latest/deploy.sh
...
error: unable to recognize "/Users/ysakashi/code/golang/src/github.com/kubernetes-csi/csi-driver-host-path/deploy/kubernetes-latest/snapshotter/csi-hostpath-snapshotclass.yaml": no matches for kind "VolumenapshotClass" in version "snapshot.storage.k8s.io/v1"
```

VolumenapshotClassでエラーが出ますが、今回はVolumenapshot関連のリソースをデプロイしていないため、無視します。
csi-driver-host-pathのPodが起動していることを確認します。

```
$ kubectl get pod 
NAME                   READY   STATUS    RESTARTS   AGE
csi-hostpathplugin-0   8/8     Running   0          30m
...
```

次に、StorageClassをセットアップします。

```
$ kubectl apply -f examples/csi-storageclass.yaml 
storageclass.storage.k8s.io/csi-hostpath-sc created 

$ kubectl get sc
NAME                 PROVISIONER                RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
csi-hostpath-sc      hostpath.csi.k8s.io        Delete          Immediate           true                   32m
standard (default)   k8s.io/minikube-hostpath   Delete          Immediate           false                  46m
```

## CSI Ephemeral Volume の動作検証

まず、CSI Ephemeral Volume の動作検証を行います。
CSI Ephemeral Volume は、これをサポートしている CSI Driver でしか利用できません。
既に使われている CSI Driver がサポートしているかは、csidrivers リソースの Mode を確認してください。

```
$ kubectl get csidrivers.storage.k8s.io 
NAME                  ATTACHREQUIRED   PODINFOONMOUNT   STORAGECAPACITY   TOKENREQUESTS   REQUIRESREPUBLISH   MODES                  AGE
hostpath.csi.k8s.io   true             true             false             <unset>         false               Persistent,Ephemeral   49m
```

Mode に`Ephemeral`がない(`Persistent`のみの)CSI Driver は CSI Ephemeral Volume は利用できません。
また、[Kubernetes CSI Developer Document/Drivers](https://kubernetes-csi.github.io/docs/drivers.html) に記載の表の"Persistence (Beyond Pod Lifetime)" の列でも `Ephemeral` をサポートしているか否かを確認できます。

以下に CSI Ephemeral Volume を使ったManifest(`inline.yaml`)を示します。

- inline.yaml

```YAML
kind: Pod
apiVersion: v1
metadata:
  name: my-csi-app
spec:
  containers:
    - name: my-frontend
      image: busybox
      volumeMounts:
      - mountPath: "/data"
        name: my-csi-inline-vol
      command: [ "sleep", "1000000" ]
  volumes:
    - name: my-csi-inline-vol
      csi:
        driver: hostpath.csi.k8s.io

```

spec.volumes.csi.driver に CSI Driver 名を記載します。
これにより、Pod の作成/削除時に、指定された CSI Driver へ Volume の作成/削除のリクエストが飛びます。

inline.yaml をデプロイします。

```
$ kubectl apply -f inline.yaml 
pod/my-csi-app created

$ kubectl get pod,pvc,pv
NAME                       READY   STATUS    RESTARTS   AGE
...
pod/my-csi-app             1/1     Running   0          28s
```

csi-driver-host-path の CSI Ephemeral Volume では、PVC/PV は作成されません。
`kubectl describe` コマンドで確認します。

```
$ kubectl describe pod my-csi-app 
...
Volume:
  my-csi-inline-vol:
    Type:              CSI (a Container Storage Interface (CSI) volume source)
    Driver:            hostpath.csi.k8s.io
    FSType:            
    ReadOnly:          false
    VolumeAttributes:  <none>
...
```

上記のように、`my-csi-inline-vol` として Type: CSI の Volume がマウントされているのが確認できます。
Pod のコンテナに入り確認すると `/data` にマウントされているのがわかります。

```
$ kubectl exec -ti my-csi-app -- mount |grep '/data'
tmpfs on /data type tmpfs (rw,relatime,size=5357132k)
```

csi-driver-host-path では、tmpfs を割り当てる実装となっていますが、外部ストレージを利用する CSI Driver の場合は、外部ストレージ上に Volume が作成されコンテナにマウントされます。
Pod を削除します。

```
$ kubectl delete pod my-csi-app 
pod "my-csi-app" deleted
```

Pod の削除と共に、Volume も削除されます。
csi-driver-host-path では mock device で実際に Volume が削除されたかを確認出来ないため、ここでは割愛します。

## Generic Ephemeral Volume の動作検証

次に、Generic Ephemeral Volume の動作検証を行います。
Generic Ephemeral Volume は Dynamic Provisioningをサポートしている CSI Driver で利用できます。
言い換えると、Generic Ephemeral Volume は、CSI Ephemeral Volumeとは異なり、csidrivers リソースの Mode が `Persistent`のみのCSI Driver でも利用できます。
また、[Kubernetes CSI Developer Document/Drivers](https://kubernetes-csi.github.io/docs/drivers.html) の "Dynamic Provisioning" の列でも確認できます。

以下に、Generic Ephemeral Volume を使ったManifest(`generic.yaml`)を示します。

- generic.yaml
  
```YAML
kind: Pod
apiVersion: v1
metadata:
  name: my-app
spec:
  containers:
    - name: my-frontend
      image: busybox
      volumeMounts:
      - mountPath: "/data"
        name: generic-volume
      command: [ "sleep", "1000000" ]
  volumes:
    - name: generic-volume
      ephemeral:
        volumeClaimTemplate:
          spec:
            accessModes: [ "ReadWriteOnce" ]
            storageClassName: csi-hostpath-sc
            resources:
              requests:
                storage: 1Gi
```

spec.volumes.ephemeral.volumeClaimTemplate に、PVC のテンプレートとなる内容を記載します。これにより Pod の作成/削除時に、volumeClaimTemplate に従い PVC が作成/削除されます。

generic.yaml をデプロイします。

```
$ kubectl apply -f generic.yaml 
pod/my-app created

$ kubectl get pod,pvc,pv
NAME                       READY   STATUS    RESTARTS   AGE
...
pod/my-app                 1/1     Running   0          16s

NAME                                          STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS      AGE
persistentvolumeclaim/my-app-generic-volume   Bound    pvc-57a8431b-9afd-4c53-addc-34615c95e03d   1Gi        RWO            csi-hostpath-sc   16s

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                           STORAGECLASS      REASON   AGE
persistentvolume/pvc-57a8431b-9afd-4c53-addc-34615c95e03d   1Gi        RWO            Delete           Bound    default/my-app-generic-volume   csi-hostpath-sc            16s
```

Podの作成と共にPVCが作成され、Dynamic Provisioning により PVC に Bound される PV が自動生成後、Pod にマウントされます。
この PVC/PV、Podへのマウントの処理は、通常のPersistent Volume と同じです。
次に、Pod を削除します。

```
$ kubectl delete pod my-app 
pod "my-app" deleted

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

Pod が削除されると、併せて PVC/PV も削除されます。
通常のPersistent Volume だと、Podが削除されてもPVC/PV は削除されませんが、Generic Ephemeral Volume では Pod の削除と連動し削除されます。

# 感想
今回は永続ボリューム(`Persistent Volume`)ではなく、短命なボリューム(`Ephemeral Volume`) について、動作検証を行いました。
検証により動作はわかったものの、次の2点が気になる人も多いのではないでしょうか。

1. CSI Ephemeral Volume, Generic Ephemeral Volume のユースケース
2. CSI Ephemeral Volume と Generic Ephemeral Volume のどちらを使うべきか?

1点目のユースケースを考えるにあたり、ポイントはノードのリソースを使う emptyDir よりも大きなデータを扱うシーンではないでしょうか。
さらに、Ephemeral Volumeのため、Podのライフサイクルに併せて使われる一時的なデータの保存領域が必要なユースケースが対象となります。
一例としては、以下のようなユースケースにマッチするのではないでしょうか

- 画像処理や音声処理のように、中間データを出力するアプリケーション
  - 入力データと最終的な出力データは永続ボリュームに保存するが、処理中にのみ利用する中間データの格納場所としてEphemeral Volumeを活用

その他のユースケースとしては、Ephemeral Volume に格納するデータは小さいが、1台のノードに多数のPodを詰め込みたい場合も活用できます。
emptyDir はノードのリソース(メディアやRAM)を使うため、これらのリソースがネックになりノードあたりに配置できるPod数が少なくなってしまうことがあります。
このような場合に CSI Ephemeral Volume や Generic Ephemeral Volumeは有効な回避方法のひとつではないでしょうか。

2点目のCSI Ephemeral Volume と Generic Ephemeral Volume のどちらを使うべきかについては、特段の理由が無い限りは、Kubernetes v1.21でBetaとなった Generic Ephemeral Volume を利用するのが、個人的なお勧めです。理由としては以下です。

- CSI Ephemeral VolumeをサポートしているCSI Driver は限定的(数が少ない)
- CSI Ephemeral Volumeの仕様はCSI Driver毎に独自仕様(今回の動作検証では利用しなかったspec.volumes.csi.volumeAttributesで指定)
- Generic Ephemeral Volumeの方が後発であり、今後CSI Driverをサポートするベンダーは`Persistent`と`Ephemeral`を区別せず楽に実装できるGeneric Ephemeral Volumeを選択すると予測
- Generic Ephemeral Volumeは、永続ボリューム(`Persistent`)でサポートしている機能(Volume ExpansionやVolume Snapshotなど)が利用可能

CSI を使った Ephemeral Volume は、ステートフルアプリケーション以外でもKubernetes でストレージを使ったユースケースが広がるポテンシャルをもっているため、ユースケースの広がりに期待します。

# 参考情報

- https://kubernetes.io/docs/concepts/storage/ephemeral-volumes/
- https://github.com/kubernetes-csi/csi-driver-host-path/blob/master/docs/example-ephemeral.md