Kubernetes の Taint/Toleration について

はじめに

Kubernetes(以下 k8s)には、ノードが特定の Pod を排除する機能である Taint というものがある。一方で Toleration は Pod に適用され、一致する Taint が付与されているノードにスケジュールされることを許可するものである。このように基本的には Taint と Toleration は対になって機能する。

また、ユースケースとしてノードを特定のワークロード専用として使用した場合ノードのメンテナンスのため Pod を排除する場合などに使用される。

本記事では Taint, Toleration それぞれの設定方法やその内容について実際に動かしながら見ていこうと思う。

使用するクラスタの準備

クラスタの構築には、kind を使用した。Taint, Toleration を試すには複数ノードが必要になるが、手元でサクッと用意できるため kind を選択した。

kind を使ったマルチノードクラスタの構築、最新バージョンの k8s での構築方法については別の記事を書いているのでそちらを参照いただければと思う。

kind を使ってローカルにマルチノードな Kubernetes クラスタを構築する
k8s クラスタをローカルに作成できる kind を触ってみた
kind を使ってローカルにマルチノードな Kubernetes クラスタを構築する favicon egashira.dev
kind を使ってローカルにマルチノードな Kubernetes クラスタを構築する
kind で特定の Kubernetes のバージョンを指定してクラスタを構築する
最新のバージョンを使用したり、特定のバージョンでクラスタを構築したりする際に調べた
kind で特定の Kubernetes のバージョンを指定してクラスタを構築する favicon egashira.dev
kind で特定の Kubernetes のバージョンを指定してクラスタを構築する

まず、クラスタの設定を定義したファイルを作成する。

config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
    image: kindest/node:v1.26.0@sha256:691e24bd2417609db7e589e1a479b902d2e209892a10ce375fab60a8407c7352
  - role: worker
    image: kindest/node:v1.26.0@sha256:691e24bd2417609db7e589e1a479b902d2e209892a10ce375fab60a8407c7352
  - role: worker
    image: kindest/node:v1.26.0@sha256:691e24bd2417609db7e589e1a479b902d2e209892a10ce375fab60a8407c7352

そして、下記のコマンドを使うと定義した内容でクラスタが作成される。

$ kind create cluster --name=alice --config=config.yaml
$ kubectl get nodes -o wide
NAME                  STATUS   ROLES           AGE   VERSION   INTERNAL-IP   EXTERNAL-IP   OS-IMAGE       KERNEL-VERSION      CONTAINER-RUNTIME
alice-control-plane   Ready    control-plane   11m   v1.24.0   172.18.0.4    <none>        Ubuntu 21.10   5.10.124-linuxkit   containerd://1.6.4
alice-worker          Ready    <none>          11m   v1.24.0   172.18.0.3    <none>        Ubuntu 21.10   5.10.124-linuxkit   containerd://1.6.4
alice-worker2         Ready    <none>          11m   v1.24.0   172.18.0.2    <none>        Ubuntu 21.10   5.10.124-linuxkit   containerd://1.6.4

Taint

ノードに Taint を設定する

Taint の設定には、kubectl taint コマンドを使用する(もちろんマニフェストファイルを kubectl apply -f しても良い)。確認のため、複数の Taint を付与してみた。

$ kubectl taint nodes alice-worker key1=xxx:NoSchedule
$ kubectl taint nodes alice-worker key2=yyy:NoExecute
$ kubectl taint nodes alice-worker2 key1=zzz:NoSchedule

Taint が付与されたか確認してみる。余談だが、以下の記事にいい感じのコマンドが紹介されていたので真似してみた。

kubectl で Taint を一覧するコマンド例 - kakakakakku blog
Kubernetes でノードの Taint を確認するときに「一覧する」コマンドがなくて困るときがある.--show-labels オプションのように --show-taints オプションがあったら良いのに!例えば kubectl describe node xxx | grep Taints コマンドを実行すれば「ノードごとに」確認することはできるけど,ノードが多いと面倒だったりする.今回は個人的に使っているコマンド例をまとめる.もっとイイ Tips があったら教えて欲しいのだ💡 コマンド例 最初にコマンド例をまとめておく.実際に調べると -o custom-columns を使うコマン…
kubectl で Taint を一覧するコマンド例 - kakakakakku blog favicon kakakakakku.hatenablog.com
kubectl で Taint を一覧するコマンド例 - kakakakakku blog
$ kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints
NAME                  TAINTS
alice-control-plane   [map[effect:NoSchedule key:node-role.kubernetes.io/control-plane]]
alice-worker          [map[effect:NoSchedule key:key1 value:xxx] [effect:NoExecute key:key2 value:yyy]]
alice-worker2         [map[effect:NoSchedule key:key1 value:zzz]]

key が key1 で value が xxx の Taint が削除されたことが確認できる。

Taint を解除する

Taint を解除するには kubectl taint コマンドに - を付けるだけで可能。

$ kubectl taint node alice-worker key1=xxx:NoSchedule-

$ kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints
NAME                  TAINTS
alice-control-plane   [map[effect:NoSchedule key:node-role.kubernetes.io/control-plane]]
alice-worker          [map[effect:NoExecute key:key2 value:yyy]] # key1=xxx:NoSchedule が消えている
alice-worker2         [map[effect:NoSchedule key:key1 value:zzz]]

Pod を作成する

上記で Taint を使ってノードに汚れを付けることができたので、一度 Pod を作成して挙動を確認する。

$ kubectl run nginx --image nginx:1.23.2

$ kubectl get po -w
NAME    READY   STATUS    RESTARTS   AGE
nginx   0/1     Pending   0          1m16s
^C

-w オプションを使って作成した Pod の状況を確認するもいくら時間が経過してもステータスは Pending のまま止まってしまう。

$ kubectl describe po nginx | grep -i events: -A5
Events:
  Type     Reason            Age    From               Message
  ----     ------            ----   ----               -------
  Warning  FailedScheduling  6m56s  default-scheduler  0/3 nodes are available: 1 node(s) had untolerated taint {key1: zzz}, 1 node(s) had untolerated taint {key2: yyy}, 1 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: }. preemption: 0/3 nodes are available: 3 Preemption is not helpful for scheduling..

Pod が立ち上がらない原因としては、存在するノード全てに Taint が設定されているが、それを許容する Toleration が設定されていないためスケジューリングができない状態になっているため。

Toleration

Toleration を付与して Taint を許容する

Pod が Taint を許容するために Toleration を付与する。設定項目の詳細については後述する。

$ cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  labels:
    run: busybox
  name: busybox
spec:
  containers:
  - image: busybox:1.36.0
    name: busybox
    command:
    - sleep
    - "1d"
  restartPolicy: Always
  tolerations:
  - key: "key1"
    value: "zzz"
    effect: "NoSchedule"
    operator: "Equal"
EOF

Pod の状況を確認する。

$ kubectl get po -o wide
NAME      READY   STATUS    RESTARTS   AGE     IP           NODE            NOMINATED NODE   READINESS GATES
busybox   1/1     Running   0          63s     10.244.2.3   alice-worker2   <none>           <none>
nginx     0/1     Pending   0          4m15s   <none>       <none>          <none>           <none>

Running になり、alice-worker2 ノードで Pod が立ち上がっているのが分かる。これは alice-worker2 ノードの Taint と一致するものを busybox Pod の .spec.tolerations[0] に設定することによって Taint を許容し、立ち上げることができている。

ここで気になるのが、.spec.tolerations に設定する key, value 以外の項目ではないだろうか。順を追って解説する。

effect の種類

種類説明
NoScheduleTaint と Toleration が一致したあるいは存在した場合にのみスケジューリングする。
PreferNoScheduleTaint と一致するあるいは存在するノードを探し、スケジューリングを試みる。ただし、見つからない場合は許容できないノードで実行する。
NoExecuteTaint が付与された時にマッチする Toleration がない Pod はノードから排除される。つまり既存の Pod にも影響が出る。

NoSchedule については上記で確認できたので、 PreferNoSchedule, NoExecute について簡単に確認する。

PreferNoSchedule

試すにあたり既存の Taint を NoExecute から PreferNoSchedule 付け替える。

$ kubectl taint node alice-worker key2=yyy:NoExecute-
$ kubectl taint node alice-worker key2=yyy:PreferNoSchedule

$ kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints
NAME                  TAINTS
alice-control-plane   [map[effect:NoSchedule key:node-role.kubernetes.io/control-plane]]
alice-worker          [map[effect:PreferNoSchedule key:key2 value:yyy]]
alice-worker2         [map[effect:NoSchedule key:key1 value:zzz]]

しばらくして Pod 状況を確認すると、以前は Pending で立ち上がらなかった nginx Pod が alice-worker ノードで起動している。

$ kubectl get po -o wide
NAME      READY   STATUS    RESTARTS   AGE     IP           NODE            NOMINATED NODE   READINESS GATES
busybox   1/1     Running   0          6h13m   10.244.1.2   alice-worker2   <none>           <none>
nginx     1/1     Running   0          6h14m   10.244.2.3   alice-worker    <none>           <none>

NoExecute

繰り返しになるが、先程付け替えた Taint を元に戻す。

$ kubectl taint node alice-worker key2=yyy:PreferNoSchedule-
$ kubectl taint node alice-worker key2=yyy:NoExecute

$ kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints
NAME                  TAINTS
alice-control-plane   [map[effect:NoSchedule key:node-role.kubernetes.io/control-plane]]
alice-worker          [map[effect:NoExecute key:key2 value:yyy]]
alice-worker2         [map[effect:NoSchedule key:key1 value:zzz]]

すると nginx Pod が消えている。Taint を満たすノードが見つからず、起動中であったにもかかわらず排除されたのが分かる。

$ kubectl get po -o wide
NAME      READY   STATUS    RESTARTS   AGE     IP           NODE            NOMINATED NODE   READINESS GATES
busybox   1/1     Running   0          6h19m   10.244.1.2   alice-worker2   <none>           <none>

operator の種類

operator の種類説明
Equal (default)key, value, effect が同じ Taint を持つノードにスケジューリング
operator を指定しない場合 Equal になる。
Existskey, effect が同じ Taint を持つノードにスケジューリング(value は省略可能)

Exists

Equal については上記で動作を確認できたので Exists についてのみ試してみる。

$ kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints
NAME                  TAINTS
alice-control-plane   [map[effect:NoSchedule key:node-role.kubernetes.io/control-plane]]
alice-worker          [map[effect:NoExecute key:key2 value:yyy]]
alice-worker2         [map[effect:NoSchedule key:key1 value:zzz]]
$ cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  labels:
    run: operator-exists
  name: operator-exists
spec:
  containers:
  - command:
    - sleep
    - "999"
    image: busybox:1.26.0
    name: operator-exists
  restartPolicy: Always
  tolerations:
  - key: "key1"
    effect: "NoSchedule"
    operator: "Exists"
EOF

Pod の一覧を確認する。

$ kubectl get po -o wide
NAME              READY   STATUS    RESTARTS   AGE     IP           NODE            NOMINATED NODE   READINESS GATES
busybox           1/1     Running   0          7h12m   10.244.1.2   alice-worker2   <none>           <none>
operator-exists   1/1     Running   0          13s     10.244.1.3   alice-worker2   <none>           <none>

ちゃんと指定した通り、key: key1, effect: NoSchedule の Taint がある alice-worker2 ノードで Pod が実行されているのが分かる。

まとめ

Tain/Toleration について触ってみた。実際に動かしてみることで設定を変えるとこう動作するのか!と挙動を確認できたと思う。

参考

TaintとToleration
Nodeアフィニティは Podの属性であり、あるNode群を引きつけます(優先条件または必須条件)。反対に taint はNodeがある種のPodを排除できるようにします。 toleration はPodに適用され、一致するtaintが付与されたNodeへPodがスケジューリングされることを認めるものです。ただしそのNodeへ必ずスケジューリングされるとは限りません。 taintとtolerationは組になって機能し、Podが不適切なNodeへスケジューリングされないことを保証します。taintはNodeに一つまたは複数個付与することができます。これはそのNodeがtaintを許容しないPodを受け入れるべきではないことを示します。 コンセプト Nodeにtaintを付与するにはkubectl taintコマンドを使用します。 例えば、次のコマンドは kubectl taint nodes node1 key1=value1:NoSchedule node1にtaintを設定します。このtaintのキーはkey1、値はvalue1、taintの効果はNoScheduleです。 これはnode1にはPodに合致するtolerationがなければスケジューリングされないことを意味します。 上記のコマンドで付与したtaintを外すには、下記のコマンドを使います。 kubectl taint nodes node1 key1=value1:NoSchedule- PodのtolerationはPodSpecの中に指定します。下記のtolerationはどちらも、上記のkubectl taintコマンドで追加したtaintと合致するため、どちらのtolerationが設定されたPodもnode1へスケジューリングされることができます。 tolerations: - key: "key1" operator: "Equal" value: "value1" effect: "NoSchedule" tolerations: - key: "key1" operator: "Exists" effect: "NoSchedule" tolerationを設定したPodの例を示します。 pods/pod-with-toleration.yaml apiVersion: v1 kind: Pod metadata: name: nginx labels: env: test spec: containers: - name: nginx image: nginx imagePullPolicy: IfNotPresent tolerations: - key: "example-key" operator: "Exists" effect: "NoSchedule" operatorのデフォルトはEqualです。
TaintとToleration favicon kubernetes.io
TaintとToleration