KubernetesのNode Affinity, Inter-Pod Affinityについて


Node Affinity

taints/tolerations(参考)は特定のNodeにPodをscheduleされるのを避けるための仕組み。

それに対してNode Affinityという仕組みは、Podを特定のNode集合へscheduleするための仕組みだ。

特定のNodeにPodをscheduleするための仕組みとしてはnode selectorがある。Node Affinitynode selectorよりももっとパワフル。

Node AffinityはNodeのlabelとして設定する。

そして(node selectorはpodにselectorを設定したが、同様に)Node Affinity ruleはpodに設定する。

podのNode Affinity ruleにnodeのlabelのNode Affinityがマッチした場合、そのnodeへpodがscheduleされる。

まずnodeのlabelに設定されているNode Affinityを試しに見てみよう。

以下はGKEのnodeに設定されているlabelだ。

$ kubectl describe node gke-kubia-default-pool-db274c5a-mjnf

Name: gke-kubia-default-pool-db274c5a-mjnf
Role:
Labels: beta.kubernetes.io/arch=amd64
beta.kubernetes.io/fluentd-ds-ready=true
beta.kubernetes.io/instance-type=f1-micro
beta.kubernetes.io/os=linux
cloud.google.com/gke-nodepool=default-pool
failure-domain.beta.kubernetes.io/region=europe-west1
failure-domain.beta.kubernetes.io/zone=europe-west1-d
kubernetes.io/hostname=gke-kubia-default-pool-db274c5a-mjnf

沢山labelが設定されているがaffiniyとして重要なのが下の3つのlabel。詳細はあとで説明するが、この3つが意味するのは以下だ。



  • failure-domain.beta.kubernetes.io/region はnodeが位置するregionを指す


  • failure-domain.beta.kubernetes.io/zone はnodeが属するavailability zoneを指す


  • kubernetes.io/hostname はnodeのhostnameを指す

podには以下のようにnodeAffinityを設定する。

apiVersion: v1

kind: Pod
metadata:
name: node-affinity-pod
spec:
containers:
- name: main
image: alpine
command: ["sleep", "9999999"]
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: failure-domain.beta.kubernetes.io/zone
operator: In
values:
- "europe-west1-d"
- "asia-northeast1-a"

matchExpressionsを使って柔軟に条件を設定することができる。

requiredDuringSchedulingIgnoredDuringExecutionは2つの意味に分解できる。

前半のrequiredDuringSchedulingはnodeをscheduleする際、このaffinity ruleに合致するnodeにしかscheduleされないことを意味する。

後半のIgnoredDuringExecutionはこのaffinity ruleはnodeで実行中のpodには影響を与えないことを意味する。

preferredDuringSchedulingIgnoredDuringExecutionというのもある。

前半のpreferredDuringSchedulingはnodeをscheduleする際、このaffinity ruleに合致するnodeに優先的にscheduleするが合致するnodeがなければ他のnodeへscheduleすることを意味する。

またIgnoredDuringExecutionとは対象的なRequiredDuringExecutionもある。これはnod上で実行中のpodに影響を与えることを意味するが、まだKubernetesで実装されていなく、今後実装される予定。

operator: Inという設定が上記でされているが、operatorにはIn以外にもNotInExistsDoesNotExistsGtLtが使用できる

またNode Affinityの設定は複数定義することができ、それぞれにweightを設定することができる。


試してみる


node-affinity-pod.yaml

apiVersion: v1

kind: Pod
metadata:
name: node-affinity-pod
spec:
containers:
- name: main
image: alpine
command: ["sleep", "9999999"]
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- gke-wdpress-default-pool-ee3b6592-6n3t

$ kubectl create -f node-affinity-pod.yaml

pod "node-affinity-pod" created

nodeAffinityで指定したノードに配置されていることが分かる

$ kubectl get pod -o wide

NAME READY STATUS RESTARTS AGE IP NODE
node-affinity-pod 1/1 Running 0 3s 10.24.8.10 gke-wdpress-default-pool-ee3b6592-6n3t

ちなみにaffinity定義にマッチしない場合、podを作成してもPending状態になる。

$ kubectl get pod -o wide

NAME READY STATUS RESTARTS AGE IP NODE
node-affinity-pod 0/1 Pending 0 2s <none> <none>

# Eventsの欄に「No nodes are available that match all of the predicates: MatchNodeSelector (5).」と出ているのが分かる。
$ kubectl describe pod node-affinity-pod | tail
SecretName: default-token-2hc7t
Optional: false
QoS Class: Burstable
Node-Selectors: <none>
Tolerations: node.alpha.kubernetes.io/notReady:NoExecute for 300s
node.alpha.kubernetes.io/unreachable:NoExecute for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 2s (x4 over 5s) default-scheduler No nodes are available that match all of the predicates: MatchNodeSelector (5).


Node Anti-Affinity

Nod Anti-Affinityは Nod Affinityの反対で、Podを特定のNode以外へスケジューリングすることができます。

厳密にはNod Anti-Affinityは存在せず、Node Affinityのspec.affinity.nodeAffinityの条件部分に否定形のオペレータを指定することで実現する。


Inter-Pod Affinity

Node SelectorNode Affinityはpodとnodeの影響を与えるものだった。

Inter-Pod Affinityはpod間に影響を与える仕組み。

Inter-Pod Affinityを使うと、特定のPodが実行されているドメイン(Node、ラック、ゾーン、データセンター、etc)上へPodをschedulingさせることができる。

たとえばfrontend podとbackend podがあった場合それらのpodが近いほうがlatencyが減らせるので、それぞれのpodが近いほうがいい。そういうときにInter-Pod Affinityが使える。

以下のような定義。

apiVersion: extensions/v1beta1

kind: Deployment
metadata:
name: frontend
spec:
replicas: 5
template:
...
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: rack
labelSelector:
matchLabels:
app: backend

topologyKey: rackだが、こうすると同じrackにあるnodeにpodがscheduleされる。

上記でいうと、app: backendというlabelをもったpodがすでにrack=aというlabelを持ったnodeにscheduleされていたら、このfrontend podはrack=aというlabelをもったnodeにscheduleされる。

つまり、例えばまず2つのnodeにそれぞれrack=a, rack=bのようにLabelをつけたとする。

すると、app=backendというlabelをもつpodが先にrack=aのnodeにscheduleされている場合は、上記と同じInter-Pod Affinity ruleをもつ他のpodはrack=aのnodeにscheduleされる。


試してみる

nodeを5つ作る

$ kubectl get node

NAME STATUS ROLES AGE VERSION
gke-wdpress-default-pool-ee3b6592-6n3t Ready <none> 59m v1.8.8-gke.0
gke-wdpress-default-pool-ee3b6592-mj3x Ready <none> 59m v1.8.8-gke.0
gke-wdpress-default-pool-ee3b6592-pbrq Ready <none> 59m v1.8.8-gke.0
gke-wdpress-default-pool-ee3b6592-q033 Ready <none> 59m v1.8.8-gke.0
gke-wdpress-default-pool-ee3b6592-th89 Ready <none> 59m v1.8.8-gke.0

app: backend labelを設定したbackend-pod.yamlを作り、


backend-pod.yaml

apiVersion: v1

kind: Pod
metadata:
name: backend-pod
labels:
app: backend
spec:
containers:
- name: main
image: alpine
command: ["sleep", "9999999"]

backend-podを作成する。

$ kubectl create -f backend-pod.yaml

pod "backend-pod" created

$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE
backend-pod 1/1 Running 0 4s 10.24.5.16 gke-wdpress-default-pool-ee3b6592-mj3x

次にInter-Pod Affinityを設定したpodを作成する


pod-affinity-pod.yaml

apiVersion: v1

kind: Pod
metadata:
name: pod-affinity-pod1
spec:
containers:
- name: main
image: alpine
command: ["sleep", "9999999"]
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: "kubernetes.io/hostname"
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- backend

そしてpod-affinity-pod1を作成

$ kubectl create -f  pod-affinity-pod.yaml

pod "pod-affinity-pod1" created

同じノードに配置されていることが分かる(topologyKey: "kubernetes.io/hostname"なので同じホスト(=ノード)に配置されるということ)

$ kubectl get pod -o wide

NAME READY STATUS RESTARTS AGE IP NODE
backend-pod 1/1 Running 0 56s 10.24.5.16 gke-wdpress-default-pool-ee3b6592-mj3x
pod-affinity-pod1 1/1 Running 0 4s 10.24.5.17 gke-wdpress-default-pool-ee3b6592-mj3x

pod-affinity-podを増やして見ても同じノードに配置されることが分かる。

$ kubectl get pod -o wide

NAME READY STATUS RESTARTS AGE IP NODE
backend-pod 1/1 Running 0 1m 10.24.5.16 gke-wdpress-default-pool-ee3b6592-mj3x
pod-affinity-pod1 1/1 Running 0 19s 10.24.5.17 gke-wdpress-default-pool-ee3b6592-mj3x
pod-affinity-pod2 1/1 Running 0 8s 10.24.5.18 gke-wdpress-default-pool-ee3b6592-mj3x
pod-affinity-pod3 1/1 Running 0 2s 10.24.5.19 gke-wdpress-default-pool-ee3b6592-mj3x


Inter-Pod Anti-Affinity

Inter-Pod Anti-AffinityはInter-Pod Affinityと反対で、特定のPodがいないドメイン(Node、ラック、ゾーン、データセンター、etc)上へPodをschedulingさせることができる。

またこのようにInter-Pod AffinityInter-Pod Anti-Affinityを合わせて指定することもできる。


参考