EKSでNFSやEFSを静的マウントする際にStorageClassやcsi-driverが必須だったか検証

# はじめに

EKSのPodでNFSやEFSを静的マウントする際に、StorageClassやcsi-driverが必要だったかうる覚えだったので実際に検証を行い、その結果をまとめました。

# 結論

- NFS: 静的マウントの場合、StorageClassとcsi-driverは無くてもできる
- EFS: 静的マウントかつサブネットに紐づくマウントターゲットの場合、StorageClassとcsi-driverは無くてもできる

ただし、障害時の挙動や運用面を考慮すると、efs-csi-driverは使った方がいい。

# 検証環境

- EKS v1.31
- EKS managed workerタイプでEC2を2台
- addon
  - coredns
  - eks-pod-identity-agent
  - kube-proxy
  - vpc-cni

# NFSマウントの検証

## 事前準備

1. EC2でNFSサーバーを作成
2. 以下のuserdataでNFSサーバーをセットアップ

```sh
yum update -y
yum install -y nfs-utils
mkdir -p /mnt/nfs_share
chmod 777 /mnt/nfs_share
echo "/mnt/nfs_share *(rw,sync,no_root_squash)" >> /etc/exports
systemctl enable nfs-server
systemctl start nfs-server
exportfs -a
```

3. NFS起動確認と確認用ファイルの作成

```sh
sudo systemctl status nfs
echo nfs > /mnt/nfs_share/index.html
```

## 動作検証

1. 以下のマニフェストでPV、PVC、Podを作成

```yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  nfs:
    path: /mnt/nfs_share
    server: 10.0.1.172  # NFS EC2のIPアドレス
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 5Gi
  volumeName: nfs
---
apiVersion: v1
kind: Pod
metadata:
  name: nfs-nginx
  labels:
    type: nfs
spec:
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        - labelSelector:
            matchExpressions:
              - key: type
                operator: In
                values:
                  - nfs
          topologyKey: "kubernetes.io/hostname"
  containers:
  - name: nfs-nginx
    image: nginx
    volumeMounts:
    - mountPath: /usr/share/nginx/html
      name: nginx-volume
  volumes:
  - name: nginx-volume
    persistentVolumeClaim:
      claimName: nfs
---
apiVersion: v1
kind: Pod
metadata:
  name: nfs-nginx-2
  labels:
    type: nfs
spec:
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        - labelSelector:
            matchExpressions:
              - key: type
                operator: In
                values:
                  - nfs
          topologyKey: "kubernetes.io/hostname"
  containers:
  - name: nfs-nginx-2
    image: nginx
    volumeMounts:
    - mountPath: /usr/share/nginx/html
      name: nginx-volume
  volumes:
  - name: nginx-volume
    persistentVolumeClaim:
      claimName: nfs
```

2. リソースの状態確認

```
$ kubectl get pod,pv,pvc
NAME              READY   STATUS    RESTARTS   AGE
pod/nfs-nginx     1/1     Running   0          9s
pod/nfs-nginx-2   1/1     Running   0          9s

NAME                   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM         STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
persistentvolume/nfs   5Gi        RWO            Retain           Bound    default/nfs                  <unset>                          9s

NAME                        STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
persistentvolumeclaim/nfs   Bound    nfs      5Gi        RWO                           <unset>                 9s
```

3. マウントしたファイルの確認と修正

```
$ kubectl exec -it nfs-nginx -- cat /usr/share/nginx/html/index.html
nfs
$ kubectl exec -it nfs-nginx-2 -- cat /usr/share/nginx/html/index.html
nfs
$ kubectl exec -it nfs-nginx -- bash -c "echo NFS > /usr/share/nginx/html/index.html"
$ kubectl exec -it nfs-nginx -- cat /usr/share/nginx/html/index.html
NFS
$ kubectl exec -it nfs-nginx-2 -- cat /usr/share/nginx/html/index.html
NFS
```

# EFSマウントの検証

## 事前準備

1. EFSファイルシステムを作成（マウントターゲットはサブネットごとに作成）
2. NFSのEC2でマウントと確認用ファイルの作成

```sh
sudo su -
mkdir -p /mnt/efs_share
chmod 777 /mnt/efs_share
mount -t nfs fs-08b7e137f5e8809a0.efs.ap-northeast-2.amazonaws.com:/ /mnt/efs_share
echo efs > /mnt/efs_share/index.html
cat /mnt/efs_share/index.html
umount /mnt/efs_share
```

## 動作検証

1. 以下のマニフェストでPV、PVC、Podを作成

```yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: efs
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  nfs:
    path: /
    server: fs-08b7e137f5e8809a0.efs.ap-northeast-2.amazonaws.com
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: efs
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 5Gi
  volumeName: efs
---
apiVersion: v1
kind: Pod
metadata:
  name: efs-nginx
  labels:
    type: efs
spec:
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        - labelSelector:
            matchExpressions:
              - key: type
                operator: In
                values:
                  - efs
          topologyKey: "kubernetes.io/hostname"
  containers:
  - name: efs-nginx
    image: nginx
    volumeMounts:
    - mountPath: /usr/share/nginx/html
      name: nginx-volume
  volumes:
  - name: nginx-volume
    persistentVolumeClaim:
      claimName: efs
---
apiVersion: v1
kind: Pod
metadata:
  name: efs-nginx-2
  labels:
    type: efs
spec:
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        - labelSelector:
            matchExpressions:
              - key: type
                operator: In
                values:
                  - efs
          topologyKey: "kubernetes.io/hostname"
  containers:
  - name: efs-nginx-2
    image: nginx
    volumeMounts:
    - mountPath: /usr/share/nginx/html
      name: nginx-volume
  volumes:
  - name: nginx-volume
    persistentVolumeClaim:
      claimName: efs
```

2. リソースの状態確認

```
$ kubectl get pod,pv,pvc
NAME              READY   STATUS    RESTARTS   AGE
pod/efs-nginx     1/1     Running   0          9s
pod/efs-nginx-2   1/1     Running   0          9s

NAME                   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM         STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
persistentvolume/efs   5Gi        RWO            Retain           Bound    default/efs                  <unset>                          10s

NAME                        STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
persistentvolumeclaim/efs   Bound    efs      5Gi        RWO                           <unset>                 10s
```

3. マウントしたファイルの確認と修正

```
$ kubectl exec -it efs-nginx -- cat /usr/share/nginx/html/index.html
efs
$ kubectl exec -it efs-nginx-2 -- cat /usr/share/nginx/html/index.html
efs
$ kubectl exec -it efs-nginx -- bash -c "echo EFS > /usr/share/nginx/html/index.html"
$ kubectl exec -it efs-nginx -- cat /usr/share/nginx/html/index.html
EFS
$ kubectl exec -it efs-nginx-2 -- cat /usr/share/nginx/html/index.html
EFS
```

# 障害時の挙動

## セキュリティグループの変更

NFS/EFSともにセキュリティグループのインバウンド許可を削除すると：
- Podは Running 状態を維持
- Podからボリュームが見えなくなる
- インバウンドを元に戻すとPodからボリュームが再び見えるようになる

※ nfs-csi-driver および efs-csi-driver を使用した場合も同様の挙動

## NFSのEC2を停止

- Podは Running 状態を維持
- Podからボリュームが見えなくなる
- EC2を起動するとPodからボリュームが再び見えるようになる

※ nfs-csi-driver を使用した場合も同様の挙動

## EFSマウントターゲットの消失

1. Podがマウントしている状態でEFSのマウントターゲットを1つ削除
2. 削除したマウントターゲットと同じAZのノード上で動くPodでEFSが見えなくなる

```
$ kubectl exec -it efs-nginx -- cat /usr/share/nginx/html/index.html
efs
$ kubectl exec -it efs-nginx-2 -- cat /usr/share/nginx/html/index.html
# このまま応答なし
```

3. マウントターゲットを再作成しても状況は変わらない
4. Podを削除しようとしても削除できない
5. ノードを削除すればPodも消える

※ efs-csi-driver を使用した場合：
- マウントターゲットを再作成すれば時間はかかるが再接続できるようになる

# まとめ

静的マウントの場合、NFSとEFSともにStorageClassやcsi-driverなしでもマウント可能です。ただし、障害時の挙動や運用面を考慮すると、csi-driverを使用した方が良さそうです。特にEFSの場合、マウントターゲットの障害時にefs-csi-driverを使用することで、より柔軟な対応が可能になります。