この記事は Kubernetes道場 Advent Calendar 2018 18日目の記事です。

今回はAffinity / Anti-Affinity / Taint / Tolerationについて。

Affinity / Anti-Affinity

AffinityはPodのスケジュールについての条件を指定をする機能だ。Affinityについては以下の3つの種類がある。

  • Node Affinity
  • Pod Affinity
  • Pod Anti-Affinity

一つずつ見ていこう。

Node Affinity

Node AffinityはNodeにあるLabelを元にPodのスケジュールを行う。 前回にこの機能に近い nodeSelector があったが、これよりも柔軟な指定をすることができる。

条件の指定方法には2種類あり、

  • matchExpressions : NodeのLabelに対して集合ベースの比較
  • matchFields : Nodeオブジェクトのフィールドに対して集合ベースの比較

matchExpressions については以前と同様の指定で行えるので割愛するが、 matchFields については初めて扱うので少し見ていこう。

matchFields

matchFields はオブジェクトのフィールドに対して集合ベースの比較を行う。

フィールドについては matchExpressions と同様のものだが、 key にはオブジェクトのフィールド名を指定する。

以下が指定例だ。

1
2
3
4
5
key: metadata.name
operator: In
values:
- hoge
- fuga

この matchFields をサポートしているフィールドはオブジェクトによって異なる。 metadata.namemetadata.namespace は全てのオブジェクトでサポートされている。

サポートされていないフィールドを key に指定するとエラーとなる。

Pod Affinity / Anti-Affinity

Pod AffinityとPod Anti-Affinityは既にスケジュールされているPodのラベルを元に、自身のスケジュールを行う。

Pod Affinityは条件にマッチした場合はスケジュールされ、Pod Anti-Affinityは条件にマッチした場合はスケジュール対象から除外される。

条件の指定方法は以下の3つのフィールドを使って行う。

  • labelSelector : PodのLabelに対してのLabelSelectorを指定する。 matchLabelsmatchExpressions が指定可能だ。
  • namespaces : LabelSelectorの対象にするNamespaceをリストで指定する。指定しなかったり空リストにした場合、現在スケジュールしようとしているPodのNamespaceが対象になる。
  • topologyKey : NodeのLabelのKeyを指定する。このフィールドは必須である。

さて、 labelSelectornamespaces についてはつかめると思うが、 topologyKey が難しいところだ。詳しく見ていこう。

Topology Key

Topology Keyはトポロジードメインを示すのに使用する。簡単に言うとNodeのLabelを使った範囲の指定だ。

ここに16台のNodeがあったとしよう。

これらのNodeには以下の3つのKeyのLabelがついている。

  • hostname
  • zone
  • type

ここでTopology Keyを指定するのだが、Topology Keyに指定するのはNodeのLabelのKeyを指定する。

Topology Keyに type を指定すると以下の4つの範囲がトポロジーとして扱われる。

別の例を見よう。Topology Keyに zone を指定すると以下の2つの範囲がトポロジーとして扱われる。

ここまででだいたい感覚がつかめてきたと思うが、Topology Keyで指定されたKeyに対応するValueで一致したものが同一のトポロジーとして扱われる。

最後の例としてNode毎にトポロジーを切りたい場合には hostname を指定すればよい。

Pod Affinity / Anti-Affinityのスケジュールの詳細

さて、それではトポロジーの概念も抑えたことなのでPod Affinity / Anti-Affinityのスケジュールについてもう少し詳しく見ていく。

Pod AffinityはLabelSelectorで指定したPodとトポロジー内で同じ場所にスケジュールされるようにする設定だ。

現在クラスタには以下のような感じでPodが動いているとしよう。

そして以下の設定を下Podをスケジュールすることを考える。

1
2
3
4
labelSelector:
  matchLabels:
    app: memcached
topologyKey: type

app=memcached とマッチするようなPodと同一トポロジーにスケジュールさせる設定だ。

これを図で確認すると以下のようになる。

また、この条件をPod Anti-Affinityに設定すると逆の意味になり、同一のトポロジーにスケジュールしないようになる。

実際には以下のような図でスケジュールしようとする。

このようにPod Affinity / Anti-Affinityはトポロジーの範囲・場所と、現在スケジュールされているPodが重要になってくる。

条件の必須要件と推奨要件

AffinityとAnti-Affinityにはルールを必須にするのと、推奨にする2つの要件を指定することができる。以下の2つがそのフィールドだ。

  • requiredDuringSchedulingIgnoredDuringExecution : 必須要件の指定
  • preferredDuringSchedulingIgnoredDuringExecution : 推奨要件の指定

フィールド名が無駄に長いが、要は実行しているPodを除いてスケジュールしているときの要件ということだ。

requiredDuringSchedulingIgnoredDuringExecution については簡単で、条件が満たされなかった際はスケジュールされない。

preferredDuringSchedulingIgnoredDuringExecution は少し複雑だ。

個々に指定したルールに対してWeightをする。このWeightは1から100の間で指定する。 そして各ルールをNode毎に評価し、ルールにマッチした際はWeightの値を加算していく。 最後に加算した結果が一番大きいNodeがスケジュール対象となる。

要はWeightを大きくしたルールが優先されて評価され、スケジュールされるわけだ。

Affinity / Anti-Affinityの例

MinikubeでAffinity / Anti-Affinityのテストはできるが、Nodeが1個しかないので少し退屈なものになってしまう。

もしMulti-Nodeなクラスタを使用できる方はそれを使って検証してみるといいだろう。

今回はMinikubeでAffinity / Anti-Affinityを指定して、スケジュールがされるか、されないかを見ていこう。

Node Affinityの例

Node Affinityを使ってみよう。まず、NodeについているLabelを確認するため、以下のコマンドを実行する。

1
2
3
$ kubectl get node --show-labels
NAME       STATUS   ROLES    AGE   VERSION   LABELS
minikube   Ready    <none>   27h   v1.13.0   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=minikube

ここで任意のLabelをNodeにつけてもいいが、手間なので今回は kubernetes.io/hostname=minikube というこのLabelを利用して設定を書いてみよう。

まず、先程のLabelと一致するようなNode Affinityを指定するようなManifestを作成した。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
apiVersion: v1
kind: Pod
metadata:
  name: nginx-node-affinity
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/hostname
            operator: In
            values:
            - minikube
  containers:
  - name: nginx
    image: nginx:alpine

これを適用して取得してみる。

1
2
3
4
5
$ kubectl apply -f nginx-node-affinity.yaml
pod/nginx-node-affinity created
$ kubectl get -f nginx-node-affinity.yaml
NAME                  READY   STATUS    RESTARTS   AGE
nginx-node-affinity   1/1     Running   0          70s

スケジュールされて実行できているようだ。

それでは条件が満たされないように、 operatorNotIn に変更したManifestを作成してみる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
apiVersion: v1
kind: Pod
metadata:
  name: nginx-node-affinity-nosched
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/hostname
            operator: NotIn
            values:
            - minikube
  containers:
  - name: nginx
    image: nginx:alpine

これを適用して取得してみる。

1
2
3
4
5
$ kubectl apply -f nginx-node-affinity-nosched.yaml
pod/nginx-node-affinity-nosched created
$ kubectl get -f nginx-node-affinity-nosched.yaml
NAME                          READY   STATUS    RESTARTS   AGE
nginx-node-affinity-nosched   0/1     Pending   0          5s

Pending のままだ。 kubectl describe で確認してみよう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
$ kubectl describe -f nginx-node-affinity-nosched.yaml
Name:               nginx-node-affinity-nosched
Namespace:          default
Priority:           0
PriorityClassName:  <none>
Node:               <none>
Labels:             <none>
Annotations:        kubectl.kubernetes.io/last-applied-configuration:
                      {"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{},"name":"nginx-node-affinity-nosched","namespace":"default"},"spec":{"affinity...
Status:             Pending
IP:
Containers:
  nginx:
    Image:        nginx:alpine
    Port:         <none>
    Host Port:    <none>
    Environment:  <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-qb4fq (ro)
Conditions:
  Type           Status
  PodScheduled   False
Volumes:
  default-token-qb4fq:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-qb4fq
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:
  Type     Reason            Age               From               Message
  ----     ------            ----              ----               -------
  Warning  FailedScheduling  3s (x9 over 70s)  default-scheduler  0/1 nodes are available: 1 node(s) didn't match node selector.

スケジュールに失敗しているのがわかる。

では、requiredDuringSchedulingIgnoredDuringExecutionpreferredDuringSchedulingIgnoredDuringExecution に変えたManifestを作成してみる。

preferredDuringSchedulingIgnoredDuringExecution を指定するのでWeightを指定すること。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
apiVersion: v1
kind: Pod
metadata:
  name: nginx-node-affinity-preferred
spec:
  affinity:
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        preference:
          matchExpressions:
          - key: kubernetes.io/hostname
            operator: NotIn
            values:
            - minikube
  containers:
  - name: nginx
    image: nginx:alpine

これを適用して取得してみる。

1
2
3
4
5
$ kubectl apply -f nginx-node-affinity-preferred.yaml
pod/nginx-node-affinity-preferred created
$ kubectl get -f nginx-node-affinity-preferred.yaml
NAME                            READY   STATUS    RESTARTS   AGE
nginx-node-affinity-preferred   1/1     Running   0          44s

スケジュールされて実行されているのがわかる。これは推奨要件の指定なので、条件が満たされなかった場合は通常通りスケジュールされるだけだ。

Pod Affinity / Anti-Affinityの例

Pod Affinity / Anti-Affinityを使ってみよう。先に以下のManifestを適用してmemcachedのPodを作成しておこう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
apiVersion: v1
kind: Pod
metadata:
  name: memcached
  labels:
    app: memcached
spec:
  containers:
  - name: memcached
    image: memcached:alpine
1
2
$ kubectl apply -f memcached.yaml
pod/memcached created

それではPod Affinityを設定したPodのManifestを作成してみる。今回はTopology Keyを kubernetes.io/hostname として、 app=memcached のPodと同じトポロジーに配置されるようにした。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod-affinity
spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: app
            operator: In
            values:
            - memcached
        topologyKey: kubernetes.io/hostname
  containers:
  - name: nginx
    image: nginx:alpine

これを適用してみよう。

1
2
3
4
5
kubectl apply -f nginx-pod-affinity.yaml
pod/nginx-pod-affinity created
bash-3.2$ kubectl get -f nginx-pod-affinity.yaml
NAME                 READY   STATUS    RESTARTS   AGE
nginx-pod-affinity   1/1     Running   0          6s

スケジュールされた。 app=memcached にマッチするPod(memcached Pod)がトポロジー(minikube)に存在するためスケジュールされたわけだ。

否定形や preferredDuringSchedulingIgnoredDuringExecution はだいたい同じなので、Pod Anti-Affinityを試してみよう。

フィールド名の podAffinitypodAntiAffinity に変更するだけだ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod-anti-affinity
spec:
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: app
            operator: In
            values:
            - memcached
        topologyKey: kubernetes.io/hostname
  containers:
  - name: nginx
    image: nginx:alpine

適用してみよう。

1
2
3
4
5
$ kubectl apply -f nginx-pod-anti-affinity.yaml
pod/nginx-pod-anti-affinity created
bash-3.2$ kubectl get -f nginx-pod-anti-affinity.yaml
NAME                      READY   STATUS    RESTARTS   AGE
nginx-pod-anti-affinity   0/1     Pending   0          8s

Pending のままなので kubectl describe で確認してみよう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
$ kubectl describe -f nginx-pod-anti-affinity.yaml
Name:               nginx-pod-anti-affinity
Namespace:          default
Priority:           0
PriorityClassName:  <none>
Node:               <none>
Labels:             <none>
Annotations:        kubectl.kubernetes.io/last-applied-configuration:
                      {"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{},"name":"nginx-pod-anti-affinity","namespace":"default"},"spec":{"affinity":{"...
Status:             Pending
IP:
Containers:
  nginx:
    Image:        nginx:alpine
    Port:         <none>
    Host Port:    <none>
    Environment:  <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-qb4fq (ro)
Conditions:
  Type           Status
  PodScheduled   False
Volumes:
  default-token-qb4fq:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-qb4fq
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:
  Type     Reason            Age               From               Message
  ----     ------            ----              ----               -------
  Warning  FailedScheduling  1s (x7 over 47s)  default-scheduler  0/1 nodes are available: 1 node(s) didn't match pod affinity/anti-affinity, 1 node(s) didn't match pod anti-affinity rules.

スケジュールできていないことがわかる。app=memcached にマッチするPod(memcached Pod)がスケジュールされていないトポロジーがなかったためにスケジュールできなかったわけだ。

このようにAffinity / Anti-Affinityを使って柔軟なPodのスケジュールを行うことができる。

Taint / Toleration

TaintはNode Affinityと逆の機能でNodeへPodをスケジュールさせないための機能だ。

Taintの方からまず見ていこう。

Taint

Taintは直訳だと汚点やよごれ、などの意味を持つ。 NodeにTaintをつけることでその条件が受け入れられないPodはそのNodeにスケジュールされない。

Taintは kubectl taint コマンドで追加する。

表記については以下の通りだ。

1
<Key>=<Value>:<Effect>

KeyValue についてはLabelと同様に任意の値をつける。

Effect については以下の3つの値を指定できる。

  • NoSchedule : このTaintが許容できない場合はそのNodeにスケジュールできない
  • PreferNoSchedule : このTaintが許容できない場合はそのNode以外にスケジュールを試みる
  • NoExecute : このTaintが許容できない場合はそのNode以外にスケジュールされ、実行されているPodも追い出される。

Taintの作成

作成する際は以下の2種類がある。

  • key=value:NoSchedule : Key / Value / Effectを指定
  • key=:NoSchedule : Key / Effectを指定(Valueは空文字扱い)

Taintの削除

作成する際は以下の2種類がある。

  • key:NoSchedule- : Key / Effectを指定。KeyとEffectが一致するValueを全て削除
  • key- : Keyを指定。Keyが一致するValueとEffectを全て削除

Taintを試す

それではTaintをNodeに追加してみる。 kubectl taint を実行する。

1
2
$ kubectl taint node minikube test-taint=hoge:NoSchedule
node/minikube tainted

Taintを確認してみよう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$ kubectl describe node/minikube
Name:               minikube
Roles:              <none>
Labels:             beta.kubernetes.io/arch=amd64
                    beta.kubernetes.io/os=linux
                    kubernetes.io/hostname=minikube
Annotations:        node.alpha.kubernetes.io/ttl: 0
                    volumes.kubernetes.io/controller-managed-attach-detach: true
CreationTimestamp:  Mon, 24 Dec 2018 03:22:13 +0900
Taints:             test-taint=hoge:NoSchedule
Unschedulable:      false
~~~

Taintが追加されたことがわかる。(出力結果の下を省略している)

さて、Manifestを適用してPodを作成し取得してみよう。

1
2
3
4
5
6
7
8
9
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx:alpine
  terminationGracePeriodSeconds: 0
1
2
3
4
5
$ kubectl apply -f nginx.yaml
pod/nginx created
$ kubectl get -f nginx.yaml
NAME    READY   STATUS    RESTARTS   AGE
nginx   0/1     Pending   0          34s

Pending のままだ。 kubectl describe で確認してみよう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
$ kubectl describe -f nginx.yaml
Name:               nginx
Namespace:          default
Priority:           0
PriorityClassName:  <none>
Node:               <none>
Labels:             <none>
Annotations:        kubectl.kubernetes.io/last-applied-configuration:
                      {"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{},"name":"nginx","namespace":"default"},"spec":{"containers":[{"image":"nginx:a...
Status:             Pending
IP:
Containers:
  nginx:
    Image:        nginx:alpine
    Port:         <none>
    Host Port:    <none>
    Environment:  <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-fqjxc (ro)
Conditions:
  Type           Status
  PodScheduled   False
Volumes:
  default-token-fqjxc:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-fqjxc
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:
  Type     Reason            Age                From               Message
  ----     ------            ----               ----               -------
  Warning  FailedScheduling  52s (x2 over 52s)  default-scheduler  0/1 nodes are available: 1 node(s) had taints that the pod didn't tolerate.

Toleration がない、ということでスケジュールされてないことがわかる。 NoSchedule から PreferNoSchedule に変更して、Taintを確認しよう。

1
2
3
4
$ kubectl taint node minikube test-taint=hoge:PreferNoSchedule
node/minikube tainted
$ kubectl taint node minikube test-taint:NoSchedule-
node/minikube untainted

Podの方を確認してみよう。

1
2
3
$ kubectl get po
NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          89s

スケジュールされたことがわかる。

それではTaintを PreferNoScheduleNoExecute に変更して、Taintを確認しよう。

1
2
3
4
$ kubectl taint node minikube test-taint=hoge:NoExecute
node/minikube tainted
$ kubectl taint node minikube test-taint:PreferNoSchedule-
node/minikube untainted

再度Podの方を確認してみよう。

1
2
$ kubectl get po
No resources found.

実行されていたPodが削除されていることがわかる。

このようにPodをNodeにスケジュールさせない / 実行させない仕組みがTaintだ。

Toleration

Tolerationは直訳だと容認や寛容、などの意味を持つ。 PodがNodeについているTaintを容認するかを指定する機能だ。

toleration にマッチしたTaintを容認し、そのNodeにスケジュールできるようになる。

このTolerationはPodの spec.tolerations にリストで指定する。指定するフィールドは以下のものだ。

  • key : TaintのKeyを指定
  • operator : Valueに比較の種類を指定。 EqualExists が指定できる。デフォルトは Equal
  • value : TaintのValueを指定。 operatorExists の場合、Valueを省略できる
  • effect : TaintのEffectを指定。指定しなかった場合、全てのEffectにマッチする
  • tolerationSeconds : NoExecuteを指定した際、追い出されるまでの時間を秒数で指定する。指定しなかった場合、追い出されないないようになる。0や負の数を指定した場合、即時に追い出される

以下が指定例だ。

1
2
3
4
tolerations:
- key: test-taint
  value: hoge
  effect: NoSchedule

Tolerationを試す

Tolerationを試してみよう。まず、NodeにTaintを追加する。

1
2
$ kubectl taint node minikube test-taint=hoge:NoSchedule
node/minikube tainted

それでは以下のManifestを適用してPodを作成し、取得してみる。

1
2
3
4
5
6
7
8
9
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx:alpine
  terminationGracePeriodSeconds: 0
1
2
3
$ kubectl get po
NAME    READY   STATUS    RESTARTS   AGE
nginx   0/1     Pending   0          4s

先ほどと同様にPendingのままだ。それでは tolerations を設定してみよう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
apiVersion: v1
kind: Pod
metadata:
  name: nginx-toleration
spec:
  containers:
  - name: nginx
    image: nginx:alpine
  terminationGracePeriodSeconds: 0
  tolerations:
  - key: test-taint
    value: hoge
    effect: NoSchedule

test-taint=hoge:NoSchedule というTaintは容認するように設定を行った。それでは適用して取得してみよう。

1
2
3
4
5
$ kubectl apply -f nginx-toleration.yaml
pod/nginx-toleration created
$ kubectl get -f nginx-toleration.yaml
NAME               READY   STATUS    RESTARTS   AGE
nginx-toleration   1/1     Running   0          5s

このようにTaint(汚点)をToleration(容認)してスケジュールされた。

TaintとTolerationは単語の意味合いで覚えるのがいいのかなと思う。


というわけで今回はここまで。

次回はAuthn / Authz / ServiceAccountについて見ていこう。

それでは。