この記事はなむゆの個人ブログにもマルチポストしてあります。
https://namyusql.hatenablog.com/entry/2022-02-14-kubernetes-nodeaffinity
はじめに結論
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 のエラーが起きたときに疑うべき要因の一つになるかもなと思った回でした。