Node Affinity
taints/tolerations(参考)は特定のNodeにPodをscheduleされるのを避けるための仕組み。
それに対してNode Affinity
という仕組みは、Podを特定のNode集合へscheduleするための仕組みだ。
特定のNodeにPodをscheduleするための仕組みとしてはnode selector
がある。Node Affinity
はnode 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
以外にもNotIn
、Exists
、DoesNotExists
、Gt
、Lt
が使用できる
またNode Affinityの設定は複数定義することができ、それぞれにweightを設定することができる。
試してみる
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 Selector
と Node 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を作り、
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を作成する
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 Affinity
とInter-Pod Anti-Affinity
を合わせて指定することもできる。
参考
- Kubernetes完全ガイド(最高に分かりやすく書かれているのでぜひ買いましょう)
- Kubernetes in Action
- Affinity and anti-affinity