0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Kubernetes】PersistentVolume/PersistentVolumeClaim is 何

Posted at

そもそも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ディレクトリにマウントします。

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します。

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>

コンテナに接続し、ボリュームを確認してみます。

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:/#

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

以下コマンドで/cacheにtestfile.txtを作成します。

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の中身がないことを確認します。

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

image.png

上記サイトの図を拝借しながら処理の流れを以下に記載します。

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

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

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

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を実行します。

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>

コンテナに接続し、ボリュームを確認してみます。

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についてはこれ一つで一つの記事ができてしまいそうなので、非常にわかりやすく解説をしている記事を載せておきます。

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

以下コマンドでNodeの名前を確認します。

kubectl get nodes

NAME                            STATUS   ROLES           AGE     VERSION
sandbox-cluster-control-plane   Ready    control-plane   3d22h   v1.27.3

KindのノードはDockerコンテナとして動作しているため、ノードコンテナ内のパスをhostPathと指定しています。
そのためKindノードへの接続の仕方は以下のコマンドを使用します。

docker container exec -it sandbox-cluster-control-plane bash
root@sandbox-cluster-control-plane:/#

上記でノード内に接続することができました。
マニフェストファイルで作成した/mnt/dataが存在するか確認してみます。

ls -l /mnt/data/
total 0

ノード内にマニフェストで指定したディレクトリが存在することが確認できます。

/mnt/data内に適当なファイルを作成してみます。

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ファイルが共有されているかを確認します。

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ファイルが存在することが確認できます。

永続性の確認をしてみます。

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がそのノードにスケジュールされるため、ノード上にファイルが存在すれば、そのファイルを確認することが可能です。

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

外部ストレージを利用する方法

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

PersistentVolume (PV)は外部の永続ボリュームと連携して永続化領域を確保するボリュームです。
AWSを例にすると外部の永続ボリュームとしてEFSと連携します。
以下図を見てただけるとわかりやすいかと思います。
image.png

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

image.png

PVとPVCは1:1の関係である必要があります。
Kubernetesクラスター内にはNamespaceを作って境界を分けることがありますが、NamespaceごとにPVとPVCをapplyしてEFSと連携する必要があります。
EFSにおいてもNamespaceごとのEFSが必要となります。
image.png

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

sc.yaml

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

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内のマウントパス
---
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を使用することを宣言しています。

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ストレージを利用可能となります。

EFS CSIドライバーを使用する前提条件

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

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

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

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

## 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ドライバーをインストール

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

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

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を作成するマニフェストとなります。

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で定義している外部ストレージを使用することを宣言しています。

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と連携することを宣言しています。

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

PV, PVCとEFSを連携した動作確認をしたいところですが、そのためにはEKSクラスターの構築が必要になってくるため、AWS環境を利用した動作確認は割愛します。
ローカルクラスターで代替できる方法で動作確認を行っていきたいと思います。

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

1.クラスターのノードに接続し、適当なファイルを作成します。

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のマニフェストファイルを作成します。

# 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
# 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をマッチングさせています。

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

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

3.applyしてきます。

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状態になっていることを確認します。

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していきます。

# 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の箇所になります。

連携の全体像は以下となります。

ではPodをapplyしていきます。

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が存在しているかを確認してみます。

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と連携して使用するための補足

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

参考

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

  1. コンテナが停止したり、再起動された際にコンテナ内に保存されたデータは消えてしまうこと。

  2. Nodeとはアプリケーションを実行している仮想的なまたは物理マシンのことです。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?