【Kubernetes】PersistentVolume/PersistentVolumeClaim is 何

## そもそもVolumeとは
コンテナはいわゆる[^1]揮発性であり、かつコンテナは基本的に作って壊してを繰り返す、再利用をすることが一般的です。

そこでVolumeと呼ばれる外部ストレージ(物理機器で言えばHDDのようなもの)を使用してコンテナにマウントすることでデータを永続化することができます。


## 前提
- 本記事で紹介している検証環境はkindを使用したローカルのクラスター環境で検証しております。
- Windows10 Pro 64bitのWSL2で動作検証をしております。

PersistentVolumeについて解説する前に、Kubernetesで使用できるボリュームには以下の種類があるため、順を追って解説いたします。
- Podのディスクを使用する方法 (emptyDir)
- Nodeのディスクを使用する方法 (hostPath)
- 外部ストレージを利用する方法 (PersistentVolume)


### Podのディスクを利用する方法 (emptyDir)
文字通り、Pod内のディスク領域を使用する方法です。
ただし、これは一時的な利用領域となるとため、Podが削除されると保存されたデータも一緒に削除されてしまうため、Volumeとは言いつつもデータの永続化ができません。

マニフェストファイルで定義する場合、volumesセクションに`emptyDir: {}`という空のディレクトリを作成し、一時的なストレージをPod内に作成します。
`emptyDir`ボリュームを作成し、それをコンテナの`/cache`ディレクトリにマウントします。

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: test-emptydir
spec:
  containers:
  - image: nginx:latest
    name: test-emptydir
    volumeMounts:
    - mountPath: /cache
      name: cache-volume
  volumes:
  - name: cache-volume
    emptyDir: {}
```
実際に確認してみましょう。
適当なディレクトリにemptyDir.yamlを作成し、以下コマンドでapplyします。

```bash
kubectl apply -f emptyDir.yaml
pod/test-emptydir created

# コンテナが起動するのを確認します
kubectl get pod -w -o wide
NAME            READY   STATUS              RESTARTS   AGE   IP       NODE             NOMINATED NODE   READINESS GATES
test-emptydir   1/1     Running             0          97s   10.1.8.43   docker-desktop   <none>           <none>

```
コンテナに接続し、ボリュームを確認してみます。
```bash
kubectl exec -it test-emptydir -- /bin/bash

# dfコマンドでマウントされたディレクトリの情報を確認します、-kで1024(K)バイト単位で表示します
df -k

root@test-emptydir:/# df -k
Filesystem      1K-blocks     Used Available Use% Mounted on
overlay        1055762868 14390924 987668472   2% /
tmpfs               65536        0     65536   0% /dev
tmpfs             2007700        0   2007700   0% /sys/fs/cgroup
/dev/sde       1055762868 14390924 987668472   2% /cache
shm                 65536        0     65536   0% /dev/shm
tmpfs             4015400       12   4015388   1% /run/secrets/kubernetes.io/serviceaccount
tmpfs             2007700        0   2007700   0% /proc/acpi
tmpfs             2007700        0   2007700   0% /sys/firmware
root@test-emptydir:/#
```

`/cache`が`dev/sde`にマウントされていることが確認できます。
実際に`/cache`に適当なファイルを作成し、一時的なファイルであることを確認していきます。

以下コマンドで`/cache`にtestfile.txtを作成します。
```bash
root@test-emptydir:/cache# echo "Hello, emptyDir!" > /cache/testfile.txt
root@test-emptydir:/cache# ls -l
total 4
-rw-r--r-- 1 root root 17 Mar 23 05:23 testfile.txt

root@test-emptydir:/cache# cat testfile.txt 
Hello, emptyDir!
```
exitでPodを削除して、再作成して`/cache`の中身がないことを確認します。
```bash
kubectl delete pod test-emptydir
kubectl apply -f emptyDir.yaml
kubectl exec -it test-emptydir -- /bin/bash
root@test-emptydir:/# cd /cache/
root@test-emptydir:/cache# ls -l
total 0
```

何もなくなっていることが確認できました。

### Nodeのディスクを利用する方法 (hostPath)
Kubernetesの[^2]Nodeのディスク領域を使用する方法です。
Nodeのディスク領域をPodにマウントすることで、Nodeのディスクにデータが残るため、データは永続化されます。
ただし、特定のNode上のディレクトリにマウントするため、Podが別のNodeにスケジュールされるとデータにアクセスできなくなります。
スケジュールされるとは、Kubernetesのコントロールプレーンに存在する`kube-scheduler`がPodが検出されると、どのNodeに対してPodを割り当てるかをスケジューリングすることを言います。

https://sysdig.jp/learn-cloud-native/what-is-a-kubernetes-node/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/379725/417fdaf5-74b4-4f2c-9976-9f0f7a91d108.png)

上記サイトの図を拝借しながら処理の流れを以下に記載します。
1. ユーザが`kubectl apply`コマンドでPodの作成をリクエストすると、そのリクエストが`kube-apiserver`に送られます
1. `kube-apiserver`はリクエストを検証し、`etcd`にPodの情報を保存します
1. `kube-scheduler`は`kube-apiserver`を監視し、`Pending`状態のPodを検知します
1. `kube-scheduler`はNodeの空き状況を確認し、適切なNodeに割り当てます
1. `kube-scheduler`は選択したNodeを`kube-apiserver`に通知します
1. `kube-apiserver`は`etcd`に上記の情報を保存し、対象の`kubelet`(Node上のエージェント)にPodの作成を指示します
1. `kubelet`はマニフェストファイルの仕様に従ってコンテナを起動します
1. `kubelet`はPodのステータスを`Running`に更新し、`kube-apiserver`を通じて`etcd`に反映します

```mermaid
sequenceDiagram
    participant User
    participant kube-apiserver
    participant etcd
    participant kube-scheduler
    participant kubelet

    User->>kube-apiserver: kubectl apply -f pod.yaml
    kube-apiserver->>etcd: Pod情報を保存（Pending状態）
    kube-scheduler->>kube-apiserver: WatchでPending Podを検知
    kube-scheduler->>kube-scheduler: スケジューリング決定
    kube-scheduler->>kube-apiserver: Nodeを割り当て
    kube-apiserver->>etcd: Node情報を更新
    kube-apiserver->>kubelet: Pod作成を指示
    kubelet->>kube-apiserver: PodのステータスをRunningに更新
```


「Node上のディレクトリ」であることから、同じノード上のPod間ではデータを共有することはできますが、異なるNode間でデータを共有することはできません。

実際に`hostPath`の動作確認をしてみます。
以下マニフェストファイルを使用してNodeの特定のディレクトリ (`/mnt/data`)がPod内の`/host-data`にマウントします。

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: test-hostpath
spec:
  containers:
  - image: nginx:latest
    name: test-hostpath
    volumeMounts:
    - mountPath: /host-data
      name: hostpath-volume
  volumes:
  - name: hostpath-volume
    hostPath:
      path: /mnt/data 
      type: DirectoryOrCreate # ディレクトリが存在しなければ作成
```

以下コマンドで`apply`を実行します。
```bash
kubectl apply -f hostPath.yaml
pod/test-hostpath created

# コンテナが起動するのを確認します
kubectl get pod -w -o wide
NAME            READY   STATUS    RESTARTS   AGE   IP           NODE                            NOMINATED NODE   READINESS GATES
test-hostpath   1/1     Running   0          4s    10.244.0.6   sandbox-cluster-control-plane   <none>           <none>

```

コンテナに接続し、ボリュームを確認してみます。
```bash
kubectl exec -it test-hostpath -- /bin/bash

# dfコマンドでマウントされたディレクトリの情報を確認します、-kで1024(K)バイト単位で表示します
df -k
Filesystem      1K-blocks     Used Available Use% Mounted on
overlay        1055762868 14643780 987415616   2% /
tmpfs               65536        0     65536   0% /dev
tmpfs             2007700        0   2007700   0% /sys/fs/cgroup
overlay        1055762868 14643780 987415616   2% /host-data
/dev/sde       1055762868 14643780 987415616   2% /etc/hosts
shm                 65536        0     65536   0% /dev/shm
tmpfs             4015400       12   4015388   1% /run/secrets/kubernetes.io/serviceaccount
tmpfs             2007700        0   2007700   0% /proc/acpi
tmpfs             2007700        0   2007700   0% /sys/firmware

```
Podの`host-data`ディレクトリが`overlay`にマウントされていますね。

この`overlay`についてはこれ一つで一つの記事ができてしまいそうなので、非常にわかりやすく解説をしている記事を載せておきます。

https://tech-lab.sios.jp/archives/21103#OverlayFStoha

Podの`host-data`ディレクトリがNodeに作成した`mnt/data`にマウントされており、データの共有ができているかを確認します。

以下コマンドでNodeの名前を確認します。
```bash
kubectl get nodes

NAME                            STATUS   ROLES           AGE     VERSION
sandbox-cluster-control-plane   Ready    control-plane   3d22h   v1.27.3
```
KindのノードはDockerコンテナとして動作しているため、ノードコンテナ内のパスを`hostPath`と指定しています。
そのためKindノードへの接続の仕方は以下のコマンドを使用します。
```bash
docker container exec -it sandbox-cluster-control-plane bash
root@sandbox-cluster-control-plane:/#
```
上記でノード内に接続することができました。
マニフェストファイルで作成した`/mnt/data`が存在するか確認してみます。
```bash
ls -l /mnt/data/
total 0
```
ノード内にマニフェストで指定したディレクトリが存在することが確認できます。


`/mnt/data`内に適当なファイルを作成してみます。
```bash
root@sandbox-cluster-control-plane:/mnt/data# echo > "test" /mnt/data/
root@sandbox-cluster-control-plane:/mnt/data# ls -l
total 4
-rw-r--r-- 1 root root 11 Mar 26 21:53 test
```

今度はPod側から`test`ファイルが共有されているかを確認します。
```bash
kubectl exec -it test-hostpath -- /bin/bash
root@test-hostpath:/# cd /host-data/
root@test-hostpath:/host-data# ls -l
total 4
-rw-r--r-- 1 root root 11 Mar 26 21:53 test
```
Pod側からも`test`ファイルが存在することが確認できます。


永続性の確認をしてみます。
```bash
kubectl delete pod test-hostpath # 一度Podを削除します
pod "test-hostpath" deleted

kubectl apply -f hostPath.yaml # Podを再作成します 
pod/test-hostpath created

kubectl get pod -o wide # Podの情報を確認します
NAME            READY   STATUS    RESTARTS   AGE   IP           NODE                            NOMINATED NODE   READINESS 
GATES
test-hostpath   1/1     Running   0          33s   10.244.0.6   sandbox-cluster-control-plane   <none>           <none>    

kubectl exec -it test-hostpath -- /bin/bash # Podに接続します
root@test-hostpath:/# cd host-data/

root@test-hostpath:/host-data# ls -l # testファイルの存在確認をします
total 4
-rw-r--r-- 1 root root 11 Mar 26 21:53 test
```
Podの再作成で再度ノードに存在するファイルを確認した結果、ちゃんと永続化されていることが確認できました。
Kindクラスターの場合デフォルト構成では単一のコントロールプレーンノードしか存在しないため、すべてのPodがそのノードにスケジュールされるため、ノード上にファイルが存在すれば、そのファイルを確認することが可能です。

:::note info
AWS EKSクラスターでFargateプロファイルでサーバーレスにPodを実行する場合、
ノードという概念がなくなります。
どのFargateプロファイルでPodを実行するか？を定義することになります。
FargateプロファイルにはNamespaceを定義することができ、Pod側はどのNamespaceで実行するか？をマニフェストファイルに記述することで、マッチするFargateプロファイルにプロビジョニングを行います。
:::


### 外部ストレージを利用する方法
ここから本題のPersistentVolumeについて解説します。
これまで解説してきた`emptyDir`と`hostPath`についてはPodの中にストレージ領域を作成する方法とノード上のディレクトリを作成してファイルを共有する方法でした。

PersistentVolume (PV)は外部の永続ボリュームと連携して永続化領域を確保するボリュームです。
AWSを例にすると外部の永続ボリュームとしてEFSと連携します。
以下図を見てただけるとわかりやすいかと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/379725/304ecf68-0014-4523-af2b-db09749e6f76.png)


ただし、このPVを使用するためにはPersistentVolumeClaim (PVC)が必要になります。
PersistentVolumeClaim (PVC)とはコンテナとPVを紐づける役割を担っています。
Claim = 要求する といった意味になりますが、PVCはPodとPVの仲介役となり、Podが必要としているPVをPVCが要求し、PodとPVを紐づけます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/379725/19ce731d-1f4f-4d70-8cb6-805e5829767f.png)

PVとPVCは1:1の関係である必要があります。
Kubernetesクラスター内にはNamespaceを作って境界を分けることがありますが、NamespaceごとにPVとPVCをapplyしてEFSと連携する必要があります。
EFSにおいてもNamespaceごとのEFSが必要となります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/379725/a65028d5-7ffa-47ab-80c7-14fd808dab32.png)



マニフェストファイルを例に解説してみます。

sc.yaml
```yaml
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: aws-efs-sc
provisioner: efs.csi.aws.com
```

pv.yaml
```yaml
apiVersion: v1
kind: PersistentVolume
metadata:
    name: aws-pv-share
spec:capacity:
        storage: 5Gi
	volumeMode: Filesystem
	accessModes:
        - ReadWriteMany
    persistentVolumeReclaimPolicy: Retain
    storageClassName: aws-efs-sc
	csi:
        driver: efs.csi.aws.com
        volumeHandle: fs-xxxxxx # EFSのDNS名のファイルシステムのID
        volumeAttributes:
            path: /01 # EFS内のマウントパス
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
    name: aws-pvc-share
    namespace: aws-01
spec:
    accessModes:
        - ReadWriteMany
	storageClassName: aws-efs-sc
	resources:
        requests:
            storage: 5Gi
```
PV作成に当たり、どの`StorageClass`を使用するかを宣言する必要があります。
以下の場合、AWSのEFSを使用することを宣言しています。
```yaml
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: aws-efs-sc
provisioner: efs.csi.aws.com
```
`provisioner: efs.csi.aws.com`の記載により、どのストレージと連携するか？を示しています。
この場合、Kubernetesがストレージリソースを作成する際に、外部のプロビジョニングシステム (AWS CSI ドライバー)を呼び出すことを宣言しています。

### Amazon EFS CSIドライバーとは
CSIとは`Container Storage Interface`の略です。
`Container Storage Interface`という名前からKubernetesとEFSが連携する際の橋渡しをする役目をします。
このEFS CSIドライバーはEKSクラスターのアドオンとして提供されています。
以下AWS公式にEFS CSIドライバーに関する解説がありますが、EKSクラスターがCSIドライバーをアドオンとして実装することで、KubernetesがEFSストレージを利用可能となります。

https://aws.amazon.com/jp/blogs/news/amazon-efs-csi-dynamic-provisioning/


### EFS CSIドライバーを使用する前提条件
- CSIドライバーを使用するためのポリシーがアタッチされたIAMロールをServiceAccountに関連付け
- CSIドライバーのインストール

IAMロールをServiceAccountに関連付けするには以下コマンドを実行します。

:::note warn
以下のコマンドを実行する上での前提条件はeksctl, kubectlコマンドが実行できることです。
eksctl, kubectlのインストールについては以下AWS公式に手順があります。

https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/install-kubectl.html

:::

```bash
## IAMポリシードキュメントのダウンロード 
curl -S https://raw.githubusercontent.com/kubernetes-sigs/aws-efs-csi-driver/v1.2.0/docs/iam-policy-example.json -o iam-policy.json

## IAMポリシーの作成 
aws iam create-policy \
--policy-name EFSCSIControllerIAMPolicy \ 
--policy-document file://iam-policy.json

## Kubernetesサービスアカウントの作成 
eksctl create iamserviceaccount \
--cluster=<cluster> \ # EKSクラスターの名前を指定
--region <AWS Region> \ 
--namespace=kube-system \ # Namespaceの指定 -> CSIドライバーがインストールされるNamespaceと一致させる必要がある
--name=efs-csi-controller-sa \ # ServiceAccountの名前を指定
--override-existing-serviceaccounts \
--attach-policy-arn=arn:aws:iam::<AWS account ID>:policy/EFSCSIControllerIAMPolicy \
--approve
```

helmを使用してEFS CSIドライバーをインストール

:::note warn
helmを使用する場合にはhelmをインストールしている必要があります。
以下AWS公式に手順があります。

https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/helm.html

:::

```bash
helm repo add aws-efs-csi-driver https://kubernetes-sigs.github.io/aws-efs-csi-driver
helm repo update
helm upgrade -i aws-efs-csi-driver aws-efs-csi-driver/aws-efs-csi-driver \
  --namespace kube-system \ # ServiceAccountを作成したNamespaceと一致させる必要がある
  --set image.repository=602401143452.dkr.ecr.us-west-2.amazonaws.com/eks/aws-efs-csi-driver \
  --set serviceAccount.controller.create=false \
  --set serviceAccount.controller.name=efs-csi-controller-sa
```


ここまで`StorageClass`からAWS CSIドライバーまで話までしました。

## PersistentVolume
次にPVです。
以下がPVを作成するマニフェストとなります。

```yaml
apiVersion: v1
kind: PersistentVolume
metadata:
    name: aws-pv-share
spec:
    capacity:
        storage: 5Gi
	volumeMode: Filesystem
	accessModes:
        - ReadWriteMany
	persistentVolumeReclaimPolicy: Retain
	storageClassName: aws-efs-sc
	csi:
        driver: efs.csi.aws.com
        volumeHandle: fs-xxxxxx # EFSのDNS名のファイルシステムのID
        volumeAttributes:
            path: /01 # EFS内のマウントパス
```
### spec.capacity
`spec`セクションでボリュームの容量を5GiBを宣言しています。
しかし、EFSの容量制限はないですが、必須の記載項目となっていますので形式的に記載しています。

### spec.accessModes
`accessModes`はボリュームのマウント方法をKubernetesに伝えます。
`ReadWriteMany`は「同時に複数のPodから同時に読み込み/書き込みが可能」を意味します。
例えばPodAとPodBが同じEFSの`/data`に同時にファイルを書き込みすることができます。

### spec.persistentVolumeReclaimPolicy
`Retain`はPVのライフサイクルポリシーを指定するもので、PVCが削除されたとしてもPVとEFS(およびストレージ内のデータ)は保持する、という指定です。



## PersistentVolumeClaim
以下はPVCの内容ですが、PVCはどのようなストレージを要求するか、を宣言しPVをバインドします。

`storageClassName: aws-efs-sc`により、StorageClassで定義している外部ストレージを使用することを宣言しています。

```yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
    name: aws-pvc-share
    namespace: aws-01
spec:
    accessModes:
        - ReadWriteMany
	storageClassName: aws-efs-sc
	resources:
        requests:
            storage: 5Gi
```
再掲しますが、StorageClassでは`provisioner: efs.csi.aws.com`とすることで、AWSのEFSと連携することを宣言しています。
```yaml
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: aws-efs-sc
provisioner: efs.csi.aws.com
```
PV, PVCとEFSを連携した動作確認をしたいところですが、そのためにはEKSクラスターの構築が必要になってくるため、AWS環境を利用した動作確認は割愛します。
ローカルクラスターで代替できる方法で動作確認を行っていきたいと思います。

EFSの代替としてローカルクラスター上のノードをストレージとしてマウントします。

1.クラスターのノードに接続し、適当なファイルを作成します。
```bash
docker container exec -it sandbox-cluster-control-plane bash

root@sandbox-cluster-control-plane:/#
root@sandbox-cluster-control-plane:~# mkdir -p /mnt/data
root@sandbox-cluster-control-plane:~# echo "Hello from Node!" > /mnt/data/test.txt
root@sandbox-cluster-control-plane:~# ls -l /mnt/data/test.txt 
-rw-r--r-- 1 root root 17 Apr  2 22:09 /mnt/data/test.txt
root@sandbox-cluster-control-plane:~# cat /mnt/data/test.txt 
Hello from Node!
root@sandbox-cluster-control-plane:~# exit
exit
```

2.以下のPVとPVCのマニフェストファイルを作成します。

```yaml
# pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: node-pv
spec:
  storageClassName: node-sc  # 明示的に指定
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  hostPath:
    path: /mnt/data  # ノード内の絶対パス
    type: Directory
```
```yaml
# pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: node-pvc
spec:
  storageClassName: node-sc  # PVと同じストレージクラス名
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
```
PVとPVCのマニフェストファイルにおいて`storageClassName: node-sc`と明示的に記載することで、PVとPVCをマッチングさせています。

:::note warn
PVとPVCに記載するstorageClassNameは完全一致させる必要があります。
:::

ローカルクラスターの場合、外部ストレージを使用しないことから、`StorageClass`のマニフェストファイルは不要となります(hostPathを使用してクラスターノードを使用するためです)

3.applyしてきます。
 ```bash
kubectl apply -f pv.yaml
persistentvolume/node-pv created

kubectl get pv
NAME      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
node-pv   1Gi        RWO            Retain           Available           node-sc        <unset>                          13s

kubectl apply -f pvc.yaml 
persistentvolumeclaim/node-pvc created

kubectl get pvc
NAME       STATUS   VOLUME    CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
node-pvc   Bound    node-pv   1Gi        RWO            node-sc        <unset>                 9s
 ```
PVのSTATUSが`Available`となっているのは利用可能な状態を表しています。
PVCのSTATUSが`Bound`となっているのは利用可能なPVとバインド済みであることを表しています。
PVC作成後に再度`kubectl get pv`をしてSTATUSが`Bound`状態になっていることを確認します。

```bash
kubectl get pv
NAME                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM              STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
persistentvolume/node-pv   1Gi        RWO            Retain           Bound    default/node-pvc   node-sc        <unset>                          22m

```
これでPVとPVCが問題なくバインドされていることが確認できます。

PVとPVCの準備が整いましたのでPodと連携していきましょう。
以下のPodのマニフェストファイルを使用してapplyしていきます。
```yaml
# pod-test.yaml
apiVersion: v1
kind: Pod
metadata:
  name: test-pod
spec:
  containers:
  - name: nginx
    image: nginx:latest
    volumeMounts:
    - name: node-storage # volumesで定義したnameと一致させる
      mountPath: /usr/share/nginx/html # PVCにマウントされるディレクトリ
  volumes:
  - name: node-storage
    persistentVolumeClaim:
      claimName: node-pvc
```
PodはPVCを介してPVと連携することから、どのPVCを指定するかを記載する必要があります。
`volumes.persistentVolumeClaim`がその箇所です。
PVCと連携する際にPod内のどのディレクトリにマウントするのか？を記載しているのが、`volumeMounts`の箇所になります。

連携の全体像は以下となります。
```mermaid
graph LR
  A[Pod] -->|volumeMounts| B[volumes]
  B -->|persistentVolumeClaim| C[PVC]
  C -->|storageClassName| D[PV]
  D -->|hostPath/CSI| E[実際のストレージ]
```

ではPodをapplyしていきます。
```bash
kubectl apply -f pod-test.yaml
kubectl get pod
NAME       READY   STATUS    RESTARTS   AGE
test-pod   1/1     Running   0          9s
```
Podに接続してmountPath内にtest.txtが存在しているかを確認してみます。
```bash
kubectl exec -it test-pod -- /bin/bash
root@test-pod:~# cat /usr/share/nginx/html/test.txt 
Hello from Node!
```
クラスターのノードに作成したtest.txtが存在することが確認できました。

以上でPersistentVolume/PersistentVolumeClaimに関する解説を終わりたいと思います。

## PVをEFSと連携して使用するための補足

:::note info
EFSと連携して使用する場合にはEFSにポート2049を許可するインバウンドルールが必要となります。
:::




## 参考
https://kubernetes.io/ja/docs/concepts/storage/volumes/
https://www.ios-net.co.jp/blog/20230628-1190/

[^1]: コンテナが停止したり、再起動された際にコンテナ内に保存されたデータは消えてしまうこと。
[^2]: Nodeとはアプリケーションを実行している仮想的なまたは物理マシンのことです。