kubernates 1.17。
$ kubectl version
Client Version: version.Info{Major:"1", Minor:"17", GitVersion:"v1.17.2", GitCommit:"59603c6e503c87169aea6106f57b9f242f64df89", GitTreeState:"clean", BuildDate:"2020-01-18T23:30:10Z", GoVersion:"go1.13.5", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"17", GitVersion:"v1.17.3", GitCommit:"06ad960bfd03b39c8310aaf92d1e7c12ce618213", GitTreeState:"clean", BuildDate:"2020-02-11T18:07:13Z", GoVersion:"go1.13.6", Compiler:"gc", Platform:"linux/amd64"}
Persistent Volumes の accessModes
Persistent Volumes(以下 PV) には accessModes という設定項目がある。
参考 Persistent Volumes
ReadWriteOnce – the volume can be mounted as read-write by a single node
ReadOnlyMany – the volume can be mounted read-only by many nodes
ReadWriteMany – the volume can be mounted as read-write by many nodes
そしてこれは「Pod から見て PV がどう見えるか?」ではなくて「PV をどの単位で公開するか?」という視点で書かれている。つまり ReadWriteOnce を使ったからといって、単一 Pod だけのアクセスを許すようになるわけではなく、公開範囲の複数の Pod が PV を利用できる。
k8s の Issue でも次のようなコメントがあった。
参考 Multiple pods able to write to PV with accessMode of ReadWriteOnce
AccessModes as defined today, only describe node attach (not pod mount) semantics, and doesn’t enforce anything
accessModes はノードにアタッチするときの話で、Pod のマウントの範囲とは関係ないよ、ということが書いてある。前述の accessModes の設定をよく見ると、「…by a single node/many nodes」と書いてあって Pod については触れられていないことに気づいたでしょうか。私は気づきませんでした。つまり、これらは PV レベルでの障害を発生させないための制約であって、Pod からのアクセス制御をする仕組みではない。
こういった違いなら今までもあった。たとえばブロックストレージは、ファイルシステムがブロック内にスーパーブロック作ったりデータブロック作ったりという管理をしながら利用している。これがもしも他のノードからもマウントされてフォーマットされると、あるファイルシステムが書いたブロックを他のファイルシステムが壊しゃったよトホホ…という状態になる。なので、そういった障害が起きないように、複数ノードから共有ストレージを使うときは NFS を立てて I/O 要求を一元管理/排他制御し、ローカルのファイルシステムに書き込んだりしていた。こういう、低レイヤの話が accessModes の制限事項だと思われる。
検証する
複数の Pod から書き込みができることを確認する。
Pod 内の複数コンテナから書き込む
まず accessModes が ReadWriteOnly な pv/pvc を用意する。
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
sample-vol 1Gi RWO Recycle Bound default/sample-pvc 57s
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
sample-pvc Bound sample-vol 1Gi RWO 14s
つぎに Pod 内の複数コンテナからアクセスできることを確認する。
apiVersion: v1
kind: Pod
metadata:
name: sample-pod
spec:
containers:
- name: busy1
image: busybox:musl
tty: true
volumeMounts:
- mountPath: /data1
name: vol
- name: busy2
image: busybox:musl
tty: true
volumeMounts:
- mountPath: /data2
name: vol
volumes:
- name: vol
persistentVolumeClaim:
claimName: sample-pvc
起動できるし、書き込みもできることがわかる。
$ kubectl create -f pod.yaml
pod/sample-pod created
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
sample-pod 2/2 Running 0 3s
$ kubectl exec -it sample-pod -c busy1 -- touch /data1/busy1
$ kubectl exec -it sample-pod -c busy2 -- touch /data2/busy2
$ kubectl exec -it sample-pod -c busy1 -- ls /data1
busy1 busy2
ReplicaSet の複数 Pod から書き込む
Deployment で replicas
で 2 以上を設定した場合、複数のコンテナ が pv/pvc を共有する。このときも同様にアクセスできることを確認する。
deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: busybox-deployment
labels:
app: busy
spec:
replicas: 3
selector:
matchLabels:
app: busy
template:
metadata:
labels:
app: busy
spec:
containers:
- name: busy
image: busybox:musl
tty: true
volumeMounts:
- mountPath: /data2
name: vol
volumes:
- name: vol
persistentVolumeClaim:
claimName: sample-pvc
起動できるし、書き込みできるのがわかる。
$ kubectl apply -f pod.yaml
pod/sample-pod created
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
busybox-deployment-78887649-2g46b 1/1 Running 0 2m20s 192.168.130.1 ip-172-30-0-112 <none> <none>
busybox-deployment-78887649-cv7zv 1/1 Running 0 2m20s 192.168.130.63 ip-172-30-0-112 <none> <none>
busybox-deployment-78887649-s2r4c 1/1 Running 0 2m20s 192.168.165.140 ip-172-30-0-246 <none> <none>
$ kubectl exec -it sample-pod busybox-deployment-78887649-2g46b -- touch /data2/2g46b
$ kubectl exec -it busybox-deployment-78887649-cv7zv -- touch /data2/cv7zv
$ kubectl exec -it busybox-deployment-78887649-s2r4c -- ls /data2
2g46b cv7zv
※ なぜか Pod とPV がノードをまたいでますが、これは accessModes が効くのは Pod のスケジューリングのタイミングだから、だと思います。まだマージされてませんが、fix: adding csi link and correct some words used to clarify access mode.というドキュメントの修正PR があり、そこに書いてました。
今回の NFS は ReadWriteMany に対応してるので全ノードから同じデータにアクセスできましたが、 hostPath などの ReadWriteMany に対応していない PV は、データがノードまたいで見えることはありませんでした。さすがに。
replicas = 1 なら…?
じゃあ replicas=1 なら問題ないかというと、それもダメ。なぜなら、オートヒーリングやローリングアップデート時に瞬間的に同じ Pod が立ち上がる可能性があるから。
今回は docker stop で無理やり Pod を止めてみたところ、次のように複数 Pod が起動された。
$ kubectl get pods -o wide -w
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
busybox-deployment-78887649-9lhpr 1/1 Running 0 3m11s 192.168.130.15 ip-172-30-0-112 <none> <none>
busybox-deployment-78887649-9lhpr 0/1 Error 0 3m23s 192.168.130.15 ip-172-30-0-112 <none> <none>
busybox-deployment-78887649-9lhpr 1/1 Running 1 3m24s 192.168.130.15 ip-172-30-0-112 <none> <none>
Deployment での ReadWriteOnly の利用
若干わき道にそれるが、Deployment で ReadWriteOnly を使う場合、ホスト障害が発生するとダウンタイムが発生する。前の Pod がノード単位の PV をつかんでいるので、別の ノードに Pod をスケジュールできないため。というかそもそもの話として、 Deployment はステートレスなコンテナを扱うために設計されてるらしい。
Deployment はステートレス アプリケーション用に設計されているため、同じ永続ボリューム要求が Deployment のすべてのレプリカで共有されます。作成されたレプリカポッドはそれぞれ同一なので、この設定では ReadOnlyMany モードと ReadWriteMany モードのボリュームだけが動作します。
引用元: Google Cloud 永続ディスクを使用した永続ボリューム
検証のまとめ
いろいろやったが、** accessModes は Pod からの書き込みを絞る仕組みではない**だろう、という確からしさが高まった。ただし「このパラメータって Pod に対する設定っぽいよね」という Issue もあるようなので、今後変わるかもしれない。自分も紛らわしいと思う。
参考 Issue with k8s.io/docs/concepts/storage/persistent-volumes/
アプリケーションレベルでの排他
PV 側で accessModes が適切に扱われていれば、PV レベルでの整合性は担保できる。しかしそれはあくまで PV の話。上述したように複数の Pod から書き込みができる以上は、アプリケーションレベルでの整合性が保てるわけではない。PV が同じ設定で同じ場所にマウントされていて Pod で同じアプリケーションが動いているので、意図せずデータを上書きする可能性がある。これは accessModes が ReadWriteMany の場合でも同じことだと思う。
ということでアプリケーションレベルでの排他を考えると、 Pod 単位で PV を占有したいことがある。こんなときに使えるのが StatefulSet らしい。
Google Cloud 永続ディスクを使用した永続ボリューム
Why StatefulSets? Can’t a stateless Pod use persistent volumes?
StatefulSet については今後勉強します。