kubernetes
affinity

Kubernetesのnode affinity, pod affinityについて

前置き

説明が合っているか自信がないので、正確な情報は公式ページをご覧ください

node affinity

taints/tolerations(参考)は特定のnodeにpodをscheduleされるのを避けるための仕組み。
node affinityという仕組みは、podを特定のnode集合へscheduleするための仕組みだ。
node affinityの前身はnode selectorだった。node affinitynode selectorよりももっとパワフル。
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で実装されていなく、今後実装される予定。

また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).

pod affinity

node selectornode affinityはpodとnodeの影響を与えるものだった。
pod affinityはpod間に影響を与える仕組み。
たとえばfrontend podとbackend podがあった場合それらのpodが近いほうがlatencyが減らせるので、それぞれのpodが近いほうがいい。
pod affinityを使えば同じnode(もしくは同じラック、もしくは同じデータセンター)にscheduleすることができる。

以下のような定義。

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されている場合は、上記と同じ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

次に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

pod anti-affinity

pod anti-affinityを使うと、pod affinityと反対のことができる。
つまりpodを出来る限り異なるnodeにscheduleすることができる。

またこのようにpod affinitypod anti-affinityを合わせて指定することもできる。

参考