PersistentVolume と Pod の NodeAffinity はどっちが優先されるの
この記事は cloud.config tech blog にもマルチポストしています。
はじめに結論
Q PersistentVolume と Pod、どちらでも NodeAffinity が設定できますが、これらで互いに矛盾するような設定をすると、どのように Pod がスケジューリングされるのでしょうか?
A node affinity conflict
のエラーであるとしてスケジューリングされなくなります。
説明
Kubernetes には NodeAffinity という概念があります。これは、その Pod がどの Node に配置されるか(スケジューリングされるか)をコントロールするために使われるものです。
Node 毎に label を設定し、その label が付けられた Node を対象とした NodeAffinity を Pod に設定してやることで、その Pod が展開される先の Node を指定したり、優先順位付けを行ってやることができます。
この NodeAffinity は、アプリケーションの本体である Pod に対して直接設定してやることもできますが、他にも設定できるリソースがあります。
PersistentVolume がその一つです。
PersistentVolume は Pod で作成されたデータを永続化するための概念で、Node として動作しているマシン内や外部のファイルシステム等にそのデータを保持する場所を定義し、Pod 内の特定のディレクトリと繋ぎ合わせます。
この中で特に、Node 内部にデータを保存する場合、どの Node に対してその Pod を展開するかが重要になってきます。
なぜなら、複数の Node がある環境の場合、Pod が配置された Node のマシン上にデータを保存すると言っても Pod がどの Node に配置されるか分からないため、Pod から作成されたデータがどこの Node に保存されるかも分からなくなってしまうためです。
そのため、PersistentVolume というリソースでも NodeAffinity を指定することができ、この PersistentVolume を使用することが指定されている Pod は使用している PersistentVolume の NodeAffinity に従って Node に配置されます。
ということで、NodeAffinity は Pod それそのものと、PersistentVolume の両方で設定することができます。
では、これらで互いに矛盾するような NodeAffinity を設定すると、どちらが優先されるのかというのが今回の話の趣旨になります。
検証方法
まず最初に、複数の Node がある Kubernetes クラスターを用意します。
スクラッチで用意してもいいのですが、自分の場合今回は Azure に頼って AKS で簡単に二つ Node がある環境を用意しています。
次に、作成した二つの Node にそれぞれ別の label を設定します。
kubectl label node <ひとつめのNodeの名前> nodeName=A kubectl label node <ふたつめのNodeの名前> nodeName=B
続いて、各種マニフェストファイルを用意します。
0_pv.yaml
apiVersion: v1 kind: PersistentVolume metadata: name: local-pv spec: capacity: storage: 1Gi volumeMode: Filesystem accessModes: - ReadWriteMany persistentVolumeReclaimPolicy: Retain storageClassName: local local: path: / nodeAffinity: required: nodeSelectorTerms: - matchExpressions: - key: nodeName operator: In values: - A
1_pvc.yaml
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: local-pvc spec: accessModes: - ReadWriteMany volumeMode: Filesystem resources: requests: storage: 1Gi storageClassName: local
2_deployment.yaml
apiVersion: apps/v1 kind: Deployment metadata: creationTimestamp: null labels: app: prefer-b-deployment name: prefer-b-deployment spec: replicas: 10 selector: matchLabels: app: prefer-b-deployment strategy: {} template: metadata: creationTimestamp: null labels: app: prefer-b-deployment spec: containers: - command: - /bin/sh - -c - sleep 1000 image: busybox name: busybox resources: requests: cpu: 50m memory: 50Mi limits: cpu: 50m memory: 50Mi volumeMounts: - mountPath: "/path" name: mypd volumes: - name: mypd persistentVolumeClaim: claimName: local-pvc affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: nodeName operator: In values: - B status: {}
ここで確認しておきたいのは、0_pv.yaml と 2_deployment.yaml に設定した nodeAffinity の値です。
両方にそれぞれrequired
、 requiredDuringSchedulingIgnoredDuringExecution
として Node を指定するパラメータを設定しています。
Node にはそれぞれ nodeName として A,B という label を割り振っているので、PersistentVolume と Pod では別の Node に割り当てられようとしています。
requiredDuringSchedulingIgnoredDuringExecution
の別のパラメータとしては preferredDuringSchedulingIgnoredDuringExecution
というものがあるのですが、こちらはrequired
ではなくpreffered
なので、required 程優先はされません。
そのため今回はpreferredDuringSchedulingIgnoredDuringExecution
の方を使用しています。
続いて、マニフェストを展開します。
kubectl apply -f 0_pv.yaml kubectl apply -f 1_pvc.yaml kubectl apply -f 2_deployment.yaml
マニフェストを展開したら、しばらくして pod の様子を確認します。
kubectl get pod
結果はどうでしょうか。例としては以下のように、全部の Pod が Pending の状態になっているはずです。
NAME READY STATUS RESTARTS AGE prefer-b-deployment-7479ffc7f8-2hpdc 0/1 Pending 0 9h prefer-b-deployment-7479ffc7f8-5cgqq 0/1 Pending 0 9h prefer-b-deployment-7479ffc7f8-5fp6m 0/1 Pending 0 9h prefer-b-deployment-7479ffc7f8-9bllh 0/1 Pending 0 9h prefer-b-deployment-7479ffc7f8-gxnrp 0/1 Pending 0 9h prefer-b-deployment-7479ffc7f8-r5p8g 0/1 Pending 0 9h prefer-b-deployment-7479ffc7f8-rmnkd 0/1 Pending 0 9h prefer-b-deployment-7479ffc7f8-tjxdz 0/1 Pending 0 9h prefer-b-deployment-7479ffc7f8-tqbff 0/1 Pending 0 9h prefer-b-deployment-7479ffc7f8-xvs7c 0/1 Pending 0 9h
個々の Pod の状態はどのようになっているのか確認しましょう。
このうちの一個の Pod の名前を指定して、様子を見てみましょう。
kubectl describe pod prefer-b-deployment-7479ffc7f8-2hpdc
出力は分量が多いので省略しますが、最後の Event の部分が以下のようになっているはずです。
Events: Type Reason Age From Message ---- ------ ---- ---- ------- Warning FailedScheduling 62m default-scheduler 0/2 nodes are available: 1 node(s) didn't match Pod's node affinity/selector, 1 node(s) had volume node affinity conflict.
0/2 nodes are available: 1 node(s) didn't match Pod's node affinity/selector, 1 node(s) had volume node affinity conflict.
とあるとおり、Pod の NodeAffinity と PersistentVolume の NodeAffinity との間で Conflict が起きているためスケジューリングできる Node がないとしてエラーとなることが確認できます。
おわりに
Pod のスケジューリングの仕組みは組み合わせによってはこのようなエラーが出ることもあるので、node の conflict のエラーが起きたときに疑うべき要因の一つになるかもなと思った回でした。