はじめに
Kubernetes v1.22にてPVC/PVの新しいAccessModeとして登場したReadWriteOncePod (RWOP)
について、これまでのAccessModeのReadWriteOnce (RWO)
との違いを動作検証を交えながら紹介します。
AccessModeとは
PVC/PVにて使用するオプションで、これによりマウントする際のアクセス権が指定されます。
Kubernetes v1.21まではReadWriteOnce (RWO)
, ReadOnlyMany (ROX)
, ReadWriteMany (RWX)
の3つがありました。
このAccessModeに、Kubernetes v1.22 ではアルファ機能としてReadWriteOncePod (RWOP)
が新たに追加されました。
ReadWriteOnceとReadWriteOncePodの違い
ReadWriteOnce
とReadWriteOncePod
は、一見似ているのですが、対象がNodeかPodなのかが異なります。
- ReadWriteOnce
- 1つのNodeからRead/Writeでマウントできる
- ReadWriteOncePod
- 1つのPodからRead/Writeでマウントできる
言い換えると、ReadWriteOnce
は、同じNode上のPodであればRead/Writeでマウントできます。
以下に図で示します。
ReadWriteOnce
はNode2のようなことが可能となります。
それに対し、新たに追加されたReadWriteOncePod
は、1PodからのみRead/Writeできるように制限されます。
ReadWriteOnce
又はReadWriteOncePod
を利用するケースの多くは、ブロックストレージを利用する場合ではないでしょうか。
ブロックストレージは、ロックのメカニズムを持っていないため、1台のホスト(Node)からのみWriteされるように構成を組む必要があります。
そのため、ストレージ視点だと場合、Writeを発行するNodeは1台と保証するAccessModeであるReadWriteOnce
が必要となります。
しかし、上記のように同じNode上のPodからは同時にWriteできてしまうため、注意が必要でした。
(StatefulSetのVolumeClaimTemplateを利用している場合は1Pod/PVC/PVとなるため、本構成にはなりません)
このReadWriteOnce
は、ストレージ視点では正しいかと思うのですが、Pod視点で見た場合には、少々不自然な印象でした。
Kubernetesでは、インフラを抽象化したモデルで操作するのを特徴としているにも関わらず、ストレージのマウント構成が滲みでてしまっています。
ユーザは、同じReadWriteOnce
のPVC/PVを同じNodeでマウントしないように注意しなければならず、インフラを意識せざるを得ませんでした。
そこで、登場したのがReadWriteOncePod
です。ReadWriteOncePod
は、Pod視点で1Pod/PVC/PVとなるように設計されたAccess Modeになります。
動作検証
本検証では、上記で説明したReadWriteOnce
とReadWriteOncePod
の違いを検証します。
検証環境の構築
minikube v1.22.0 を使い、2node構成のKubernetes v1.22.0をセットアップします。
また、ReadWriteOncePod
はアルファ機能のため、feature-gatesで有効化します。
$ minikube start --driver=virtualbox --kubernetes-version=1.22.0 --nodes 2 --feature-gates='ReadWriteOncePod=true'
上記コマンドで、構築したKubernetesの構成は以下となっています。
$ kubectl get node,sc
NAME STATUS ROLES AGE VERSION
node/minikube Ready control-plane,master 45m v1.22.0
node/minikube-m02 Ready <none> 43m v1.22.0
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
storageclass.storage.k8s.io/standard (default) k8s.io/minikube-hostpath Delete Immediate false 45m
ReadWriteOnceの検証
まずは、ReadWriteOnce
を検証します。
PVCのManifest(RWO-PVC1.yaml
, RWO-PVC2.yaml
)を用意します。
RWO-PVC1.yaml
とRWO-PVC2.yaml
はNameのみ異なっています。
- RWO-PVC1.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc1
spec:
storageClassName: standard
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
- RWO-PVC2.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc2
spec:
storageClassName: standard
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
上記PVCのManifestをデプロイします。
$ kubectl apply -f RWO-PVC1.yaml -f RWO-PVC2.yaml
persistentvolumeclaim/pvc1 created
persistentvolumeclaim/pvc2 created
つづいて、pvc1をマウントするPod(pod1
)とpvc2を同じNodeでマウントするPod(pod2
, pod3
)を立ち上げます。
以下にManifestを示します。
NodeSelectorを使いデプロイするNodeを指定しています。
- pod1.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
run: pod1
name: pod1
spec:
nodeSelector:
kubernetes.io/hostname: minikube
containers:
- image: ubuntu:18.04
name: pod1
command:
- sleep
- infinity
volumeMounts:
- mountPath: /mnt/data
name: myvolume
volumes:
- name: myvolume
persistentVolumeClaim:
claimName: pvc1
- pod2.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
run: pod2
name: pod2
spec:
nodeSelector:
kubernetes.io/hostname: minikube-m02
containers:
- image: ubuntu:18.04
name: pod2
command:
- sleep
- infinity
volumeMounts:
- mountPath: /mnt/data
name: myvolume
volumes:
- name: myvolume
persistentVolumeClaim:
claimName: pvc2
- pod3.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
run: pod3
name: pod3
spec:
nodeSelector:
kubernetes.io/hostname: minikube-m02
containers:
- image: ubuntu:18.04
name: pod3
command:
- sleep
- infinity
volumeMounts:
- mountPath: /mnt/data
name: myvolume
volumes:
- name: myvolume
persistentVolumeClaim:
claimName: pvc2
pod2.yamlとpod3.yamlは同一Node上で同じPVCをマウントするため、Pod名やコンテナ名しか違いはありません。
diff -u pod2.yaml pod3.yaml
--- pod2.yaml 2021-08-31 15:36:55.000000000 +0900
+++ pod3.yaml 2021-08-31 15:37:18.000000000 +0900
@@ -2,14 +2,14 @@
kind: Pod
metadata:
labels:
- run: pod2
- name: pod2
+ run: pod3
+ name: pod3
spec:
nodeSelector:
kubernetes.io/hostname: minikube-m02
containers:
- image: ubuntu:18.04
- name: pod2
+ name: pod3
command:
- sleep
- infinity
PodのManifestをデプロイします。
$ kubectl apply -f pod1.yaml -f pod2.yaml -f pod3.yaml
pod/pod1 created
pod/pod2 created
pod/pod3 created
Pod,PVC,PVを確認します。
$ kubectl get pod,pvc,pv -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/pod1 1/1 Running 0 15s 10.244.0.4 minikube <none> <none>
pod/pod2 1/1 Running 0 15s 10.244.1.5 minikube-m02 <none> <none>
pod/pod3 1/1 Running 0 15s 10.244.1.6 minikube-m02 <none> <none>
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE VOLUMEMODE
persistentvolumeclaim/pvc1 Bound pvc-ffd4f1aa-eb86-4ac4-8355-56e57a74a43a 1Gi RWO standard 22m Filesystem
persistentvolumeclaim/pvc2 Bound pvc-a3bc28e8-752c-4457-b788-6a4f057f9fbe 1Gi RWO standard 22m Filesystem
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE VOLUMEMODE
persistentvolume/pvc-a3bc28e8-752c-4457-b788-6a4f057f9fbe 1Gi RWO Delete Bound default/pvc2 standard 22m Filesystem
persistentvolume/pvc-ffd4f1aa-eb86-4ac4-8355-56e57a74a43a 1Gi RWO Delete Bound default/pvc1 standard 22m Filesystem
Node(minikube-m02
)上に1つのPVC(pvc2
)をマウントする2つのPod(pod2
, pod3
)が出来ています。
次に、pod2
からマウントしたVolume上のファイルに書き込み(Write)をしてみます。
$ kubectl exec -ti pod2 -- sh -c 'echo "Hello" >/mnt/data/hoge'
続いて、pod3
から同じファイル(/mnt/data/hoge
)に対して追加で書き込みを行います。
$ kubectl exec -ti pod3 -- sh -c 'echo "Hoge" >>/mnt/data/hoge'
次に、pod2
から、文字列を書き込んだファイル(/mnt/data/hoge
)の中身を確認してみます。
$ kubectl exec -ti pod2 -- cat /mnt/data/hoge
Hello
Hoge
同じVolumeへ書き込みを行なっているため、pod2
とpod3
から書き込んだ文字列が格納されています。
ストレージからすれば、Node上で動作するドライバ(SCSIなど)からWriteされた順でデータを格納するため、障害にはなりませんが、Pod上で動作するアプリからみたら、同一ファイルをロックなしにデータを書き込むため、データ破損のリスクがある危険な構成です。
Pod,PVC,PVを削除します。
$ kubectl delete pod --all
pod "pod1" deleted
pod "pod2" deleted
pod "pod3" deleted
$ kubectl delete pvc --all
persistentvolumeclaim "pvc1" deleted
persistentvolumeclaim "pvc2" deleted
ReadWriteOncePodの検証
続いて、Kubernetes v1.22で新しくアルファ機能として登場したReadWriteOncePod
を試します。
まず、ReadWriteOncePod
のAccessModeを指定したPVC(pvc3
)を作成します。
Manifestは以下です。
- RWOP-PVC3.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc3
spec:
storageClassName: standard
accessModes:
- ReadWriteOncePod
resources:
requests:
storage: 1Gi
Manifestをデプロイします。
$ kubectl apply -f RWOP-PVC3.yaml
persistentvolumeclaim/pvc3 created
PVC/PVを確認します。
$ kubectl get pvc,pv
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/pvc3 Bound pvc-611e7784-e5d7-4b20-b994-445fa1a22083 1Gi RWOP standard 7s
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
persistentvolume/pvc-611e7784-e5d7-4b20-b994-445fa1a22083 1Gi RWOP Delete Bound default/pvc3 standard 7s
ReadWriteOncePod (RWOP)
でPVC/PVが作成されていることが確認できます。
次に、作成したPVC/PVをマウントするPod(pod4
)を作成します。
Manifestは以下です。
- pod4.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
run: pod4
name: pod4
spec:
nodeSelector:
kubernetes.io/hostname: minikube
containers:
- image: ubuntu:18.04
name: pod4
command:
- sleep
- infinity
volumeMounts:
- mountPath: /mnt/data
name: myvolume
volumes:
- name: myvolume
persistentVolumeClaim:
claimName: pvc3
Manifestをデプロイし、Pod,PVC,PVを確認します。
$ kubectl get pod,pvc,pv -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/pod4 1/1 Running 0 21s 10.244.0.6 minikube <none> <none>
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE VOLUMEMODE
persistentvolumeclaim/pvc3 Bound pvc-611e7784-e5d7-4b20-b994-445fa1a22083 1Gi RWOP standard 103s Filesystem
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE VOLUMEMODE
persistentvolume/pvc-611e7784-e5d7-4b20-b994-445fa1a22083 1Gi RWOP Delete Bound default/pvc3 standard 103s Filesystem
ReadWriteOncePod (RWOP)
のAccessModeのPVCをマウントしたPod(pod4
)が立ち上がりました。
続いて、同じNode(minikube
)上に同じPVC(pvc3
)をマウントするPod(pod5
)を作成してみます。
Manifestは以下です。
- pod5.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
run: pod5
name: pod5
spec:
nodeSelector:
kubernetes.io/hostname: minikube
containers:
- image: ubuntu:18.04
name: pod5
command:
- sleep
- infinity
volumeMounts:
- mountPath: /mnt/data
name: myvolume
volumes:
- name: myvolume
persistentVolumeClaim:
claimName: pvc3
なお、先にデプロイしたpod4.yamlとpod5.yamlの差分は名前のみです。
$ diff -u pod4.yaml pod5.yaml
--- pod4.yaml 2021-08-31 16:08:51.000000000 +0900
+++ pod5.yaml 2021-08-31 16:13:22.000000000 +0900
@@ -2,14 +2,14 @@
kind: Pod
metadata:
labels:
- run: pod4
- name: pod4
+ run: pod5
+ name: pod5
spec:
nodeSelector:
kubernetes.io/hostname: minikube
containers:
- image: ubuntu:18.04
- name: pod4
+ name: pod5
command:
- sleep
- infinity
pod5.yamlのManifestをデプロイします。
$ kubectl apply -f pod5.yaml
pod/pod5 created
Podの状態を確認します。
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod4 1/1 Running 0 5m52s 10.244.0.6 minikube <none> <none>
pod5 0/1 Pending 0 30s <none> <none> <none> <none>
pod5はPending
となっています。
詳細を確認します。
$ kubectl describe pod pod5
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 3s (x2 over 87s) default-scheduler 0/2 nodes are available: 2 node has pod using PersistentVolumeClaim with the same name and ReadWriteOncePod access mode.
同じPVCの名前を持つPodが既に存在するため、マウントできるNodeがなく、スケジューリングできずにPending
となっていることが確認できます。
ReadWriteOncePod
が正しく動作していることが確認できました。
感想
本レポートでは、Kubernetes v1.22で新たに登場したAccessMode(ReadWiteOncePod
)を試しました。
これまで、ReadWriteOnce
がNode単位の制御であったため、データ破損のリスクのある構成を取ることが出来てしまいました。
多くのユーザにとっては、望んでいない構成かと思います。
今回追加されたReadWiteOncePod
により、より安全に1Pod/PVC/PVを保証するAccessModeが出来たことで、ブロックストレージを利用する多くのPVC/PVは、今後ReadWiteOnce
の代わりにReadWiteOncePod
を利用するケースが増えてくるかと推察します。
ReadWriteOnce
とReadWriteOncePod
の違いを抑え、安全にPVC/PVを利用すると良いでしょう。