#はじめに
今回はPodデプロイ時のスケジューリングの動作について確認したいと思います。Podをどのノードにデプロイするかを制御するためには、以下の方法があります。それぞれの設定方法と動作について確認していきます。
- nodeSelector
- Node Affinity
- node Anti-Affinity
- Inter-Pod Affinity
- Inter-Pod Anti-Affinity
#nodeSelector
nodeSelectorでデプロイするノードを制御するには、ノードに付与されているラベルを利用します。
##ラベルの確認と設定
まずは各ノードにデフォルトで設定されているラベルを確認します。
$ kubectl get nodes --show-labels
NAME STATUS ROLES AGE VERSION LABELS
k8s-master Ready master 104d v1.17.3 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-master,kubernetes.io/os=linux,node-role.kubernetes.io/master=
k8s-worker01 Ready <none> 104d v1.17.3 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-worker01,kubernetes.io/os=linux
k8s-worker02 Ready <none> 104d v1.17.3 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-worker02,kubernetes.io/os=linux
上記コマンドの他に、kubectl describeコマンドなどでも確認できます。
各Nodeにラベルを設定します。ここでは、「disktype」ラベルを設定して、k8s-worker01は「ssd」、k8s-worker02は「hdd」とします。
実際はどちらも同じディスクですけどね。検証ですので。
$ kubectl label node k8s-worker01 disktype=ssd
node/k8s-worker01 labeled
$ kubectl label node k8s-worker02 disktype=hdd
node/k8s-worker02 labeled
設定したラベルを確認します。
$ kubectl get nodes --show-labels
NAME STATUS ROLES AGE VERSION LABELS
k8s-master Ready master 104d v1.17.3 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-master,kubernetes.io/os=linux,node-role.kubernetes.io/master=
k8s-worker01 Ready <none> 104d v1.17.3 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,disktype=ssd,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-worker01,kubernetes.io/os=linux
k8s-worker02 Ready <none> 104d v1.17.3 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,disktype=hdd,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-worker02,kubernetes.io/os=linux
これだとちょっと見づらいので、disktypeラベルのみ確認します。
$ kubectl get node -L disktype
NAME STATUS ROLES AGE VERSION DISKTYPE
k8s-master Ready master 104d v1.17.3
k8s-worker01 Ready <none> 104d v1.17.3 ssd
k8s-worker02 Ready <none> 104d v1.17.3 hdd
##nodeSelecterの動作確認
該当のPodのマニフェストに、nodeSelectorのセクションを追加し、「disktype: ssd」のNodeにデプロイするようにします。今回はDeploymentにしています。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-dep
spec:
replicas: 4
selector:
matchLabels:
app: app1
template:
metadata:
labels:
app: app1
spec:
containers:
- name: nginx
image: nginx:latest
nodeSelector:
disktype: ssd
applyして、PodがデプロイされているNodeを確認します。
$ kubectl apply -f nginx-dep.yaml
deployment.apps/nginx-dep created
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-dep-8f97c98cd-b4z88 1/1 Running 0 21s 192.168.79.118 k8s-worker01 <none> <none>
nginx-dep-8f97c98cd-bfg46 1/1 Running 0 21s 192.168.79.110 k8s-worker01 <none> <none>
nginx-dep-8f97c98cd-jm22p 1/1 Running 0 21s 192.168.79.126 k8s-worker01 <none> <none>
nginx-dep-8f97c98cd-vk8nj 1/1 Running 0 21s 192.168.79.117 k8s-worker01 <none> <none>
4つのPodがすべてssdのNode(=k8s-worker01)にデプロイされていますね。
#Node Affinity
Node AffinityもラベルでデプロイするNodeを制御しますが、nodeSelectorよりも細かな設定ができます。
##設定
spec.affinityフィールドに設定を記載します。必須のスケジューリングポリシー(requiredDuringSchedulingIgnoredDuringExecution)と優先的に考慮されるスケジューリングポリシー(preferredDuringSchedulingIgnoredDuringExecution)があり、必須のポリシーを満たしたNodeのうち、優先的なポリシーに基づいてデプロイされるNodeが決定します。
apiVersion: v1
kind: Pod
metadata:
name: with-node-affinity
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution: #必須のスケジューリングポリシー
nodeSelectorTerms:
- matchExpressions:
- key: disktype #ラベル名
operator: In #In、NotIn、Exists、DoesNotExist、Gt、Ltを指定可能
values: #複数設定可能
- hdd
preferredDuringSchedulingIgnoredDuringExecution: #優先的に考慮されるスケジューリングポリシー
- weight: 1 #1~100で指定。条件を満たすノードのWeight値を合計し、一番スコアが高いノードが優先される
preference:
matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- k8s-worker01
containers:
- name: nginx-na
image: nginx:latest
今回は以下の図のように必須と優先的なスケジューリングポリシーを別ノードにするようにしています。
###operatorフィールドについて
以下の6つを指定できます。
operator | 概要 |
---|---|
In | keyで指定したラベルが指定したvaluesのいづれか一つ以上に一致する。 |
NotIn | keyで指定したラベルが指定したvaluesのいづれにも一致しない。 |
Exists | keyで指定したラベルが存在する。valuesは指定しません。 |
DoesNotExist | keyで指定したラベルが存在しない。valuesは指定しません。 |
Gt | keyで指定したラベルの値(values)よりも大きい。valuesには整数値を指定します。 |
Lt | keyで指定したラベルの値(values)よりも小さい。valuesには整数値を指定します。 |
##Node Affinityの動作確認
applyして動作を確認します。
$ kubectl apply -f nodeAffinity.yaml
pod/with-node-affinity created
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
with-node-affinity 1/1 Running 0 11s 192.168.69.210 k8s-worker02 <none> <none>
想定通りk8s-worker02にデプロイされていますね。
requiredDuringSchedulingIgnoredDuringExecutionは満たさないといけませんが、preferredDuringSchedulingIgnoredDuringExecutionは必ずしも満たす必要はありません。
なお、requiredDuringSchedulingIgnoredDuringExecutionを満たすNodeがない場合、preferredDuringSchedulingIgnoredDuringExecutionを満たすNodeがあってもスケジューリングされません。
Podを削除して、k8s-worker02をスケジューリング対象から外してから、再度applyしてみます。
$ kubectl get node
NAME STATUS ROLES AGE VERSION
k8s-master Ready master 105d v1.17.3
k8s-worker01 Ready <none> 105d v1.17.3
k8s-worker02 Ready,SchedulingDisabled <none> 105d v1.17.3
$ kubectl apply -f nodeAffinity.yaml
pod/with-node-affinity created
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
with-node-affinity 0/1 Pending 0 5s
$ kubectl describe pod with-node-affinity
Name: with-node-affinity
・・・
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 66s (x2 over 66s) default-scheduler 0/3 nodes are available: 1 node(s) were unschedulable, 2 node(s) didn't match node selector.
スケジューリングされず、Pendingで止まってますね。
#Node Anti-Affinity
Node Anti-Affinityは、Node Affinityの条件(operator)を否定形にします。
以下の例では、「disktypeがhdd"ではない"Node」(=k8s-worker01)にデプロイされます。
apiVersion: v1
kind: Pod
metadata:
name: node-anti-affinity
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disktype
operator: NotIn
values:
- hdd
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- k8s-worker01
containers:
- name: nginx-na
image: nginx:latest
$ kubectl apply -f nodeAntiAffinity.yaml
pod/node-anti-affinity created
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
node-anti-affinity 1/1 Running 0 31s 192.168.79.116 k8s-worker01 <none> <none>
#Inter-Pod Affinity
Inter-Pod Affinityは既にデプロイされているPodのラベルをキーにして、新規にデプロイされるPodのスケジューリングを決定します。
例えば、あるサービスを構成するPodは同じノードにデプロイすることで、レイテンシーを極力小さくするなどの利用方法が考えられますね。
##設定
ここでは、既に以下のPodがデプロイされています。
$ kubectl get pod -o wide -L env
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES ENV
redis-prd 1/1 Running 0 20s 192.168.79.111 k8s-worker01 <none> <none> prd
redis-stg 1/1 Running 0 20s 192.168.69.225 k8s-worker02 <none> <none> stg
Production環境に新たにnginxをデプロイします。このとき、既にデプロイされているredisと同じノードにデプロイさせたいと思います。
マニフェストはNode Affinityと似ていますが、topologyKeyが追加となっています。ここではhostnameを指定していますので、prdラベルを持つPodと同じhostnameのNodeにデプロイされます。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-prd
spec:
selector:
matchLabels:
env: prd
replicas: 3
template:
metadata:
labels:
env: prd
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: env
operator: In
values:
- prd
topologyKey: "kubernetes.io/hostname" #スケジューリング対象の範囲を指定
containers:
- name: nginx-prd
image: nginx:latest
##Inter-Pod Affinityの動作確認
このマニフェストをapplyします。
$ kubectl apply -f interPodAffinity.yaml
deployment.apps/nginx-prd created
$ kubectl get pod -o wide -L env
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES ENV
nginx-prd-7d498d98ff-djzvz 1/1 Running 0 16s 192.168.79.120 k8s-worker01 <none> <none> prd
nginx-prd-7d498d98ff-gtbzj 1/1 Running 0 16s 192.168.79.122 k8s-worker01 <none> <none> prd
nginx-prd-7d498d98ff-vtmk4 1/1 Running 0 16s 192.168.79.127 k8s-worker01 <none> <none> prd
redis-prd 1/1 Running 0 10m 192.168.79.111 k8s-worker01 <none> <none> prd
redis-stg 1/1 Running 0 10m 192.168.69.225 k8s-worker02 <none> <none> stg
3つのPod全てがredis-prdと同じNodeにデプロイされていますね。
なお、今回はrequiredDuringSchedulingIgnoredDuringExecutionのみを指定しましたが、Node Affinityと同様にpreferredDuringSchedulingIgnoredDuringExecutionも指定することができます。
#Inter-Pod Anti-affinity
Inter-Pod Anti-affinityは、特定のPodがデプロイされていないノード上に新たなPodをスケジューリングするポリシーです。
##設定
Node Anti-Affinityとは異なり、operatorを否定形にするのではなく、spec.affinityフィールドにpodAntiAffinityをマニフェストで指定できます。
以下の例は、inter-Pod Affinityのマニフェストの「podAffinity」を「podAntiAffinity」に変更しています。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-prd
spec:
selector:
matchLabels:
env: prd
replicas: 3
template:
metadata:
labels:
env: prd
spec:
affinity:
podAntiAffinity: #podAffinityから変更
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: env
operator: In
values:
- prd
topologyKey: "kubernetes.io/hostname"
containers:
- name: nginx-prd
image: nginx:latest
##Inter-Pod Anti-affinityの動作確認
このマニフェストをapplyして動作を確認します。初期状態として、redisのみがデプロイされています。
$ kubectl get pod -o wide -L env
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES ENV
redis-prd 1/1 Running 1 22h 192.168.79.121 k8s-worker01 <none> <none> prd
redis-stg 1/1 Running 1 22h 192.168.69.229 k8s-worker02 <none> <none> stg
$ kubectl apply -f interPodAntiAffinity.yaml
deployment.apps/nginx-prd created
$ kubectl get pod -o wide -L env
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES ENV
nginx-prd-7df77bb75f-69gcw 0/1 Pending 0 54s <none> <none> <none> <none> prd
nginx-prd-7df77bb75f-7gvq6 0/1 Pending 0 54s <none> <none> <none> <none> prd
nginx-prd-7df77bb75f-xqj5v 1/1 Running 0 54s 192.168.69.221 k8s-worker02 <none> <none> prd
redis-prd 1/1 Running 1 22h 192.168.79.121 k8s-worker01 <none> <none> prd
redis-stg 1/1 Running 1 22h 192.168.69.229 k8s-worker02 <none> <none> stg
replica数3のうち、1つだけPodがRunningになって、残りの2つはPendingで止まってますね。
詳細を確認します。
$ kubectl describe replicasets.apps nginx-prd-7df77bb75f
Name: nginx-prd-7df77bb75f
・・・
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 84s replicaset-controller Created pod: nginx-prd-7df77bb75f-xqj5v
Normal SuccessfulCreate 83s replicaset-controller Created pod: nginx-prd-7df77bb75f-69gcw
Normal SuccessfulCreate 83s replicaset-controller Created pod: nginx-prd-7df77bb75f-7gvq6
$ kubectl describe pod nginx-prd-7df77bb75f-69gcw
Name: nginx-prd-7df77bb75f-69gcw
・・・
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 35s (x2 over 101s) default-scheduler 0/3 nodes are available: 1 node(s) had taints that the pod didn't tolerate, 2 node(s) didn't match pod affinity/anti-affinity.
ReplicaSetまでは正常ですが、PodのデプロイでFailedSchedulingになっています。
あまり考えずに、「podAffinity」を「podAntiAffinity」に変更しましたが、よくよく考えると「prdラベルのPodがないノードにprdラベルのPodをデプロイする」という設定なので、1つ目のnginx-prdがデプロイされた時点で、全てのノード(2ノード)にprdラベルのPodが存在することになりますね。なので、2つ目、3つ目のPodがPendingで止まるのは正常な動作です。
#まとめ
今回は5つのスケジューリングの制御方法について確認しました。環境や要件に応じて、色々な制御ができそうですね。
また、Node Affinity/Node Anti-Affinity/Inter-Pod Affinity/Inter-Pod Anti-Affinityの4つは組み合わせて利用することもできますので、さらに細かく制御することができます。
まあ、細かくすればするほど設計が大変になりそうではありますが。。。