freeeの開発情報ポータルサイト

KubernetesのTaints, Tolerations と友達になろう!

投稿時はSRE船のPlatform Orchestrationヨット(a.k.a みちびき)で内定者インターンをしているpokoです。
私はkubernetesとお友達になりたいので、私は正座しながらKubernetes ドキュメントを読む時間を毎週設けています。

なんの記事?

記事投稿時、kubernetesのバージョンは1.30です

kubernetesでPodをスケジュールする際に利用できるTaint and Tolerationsの仕組みと挙動を説明しています。
最終的には「ドキュメントをお読みください。」に行き着くのですが、少し噛み砕いた説明を意識しています。

あなたはこの記事を読むべき人?

  1. app=hoge:NoScheduleというTaintがついたノードに、以下のTolerationがついたPodはスケジュールされる?されない?

  2. 上記のTaintが付けられた時、すでにスケジュール済みのPodはどうなる?

tolerations:
  - key: "app"
    operator: "Equal"
    value: "hoge"
    effect: "NoSchedule"

答え

  1. スケジュールされます 🙆

  2. 削除されません 🙅

今までtaints, tolerationsを見て見ぬふりをしてきたあなた!この機会に学びましょう 😆

3行サマリー

  • Taints, Tolerationsはセットで利用される、Podのスケジュールをコントロールするkube-schedulerの機能

  • Taintsはノードに0個以上つけることができ、既にノードで実行されているPodを退去させるかはTaintEffectによって決まる

  • TolerationsはPodに0個以上つけることができ、一致するTaintがあるノードにスケジュールすることを許容します

座学編

Taints

言葉の意味 google翻訳: 汚点

deepL: 染み

ChatGPT: 汚染または腐敗 - (この中では)優勝

  • Node Affinityの反対とも言える

  • ノードに付与するものでPodを退去させるもの

    • 許可するにはTolerationが必要
  • Nodeに複数のTaintをつけることができる

Taintをくっつける

Taintの追加

kubectl taint nodes node1 key1=value1:NoSchedule

Taintの削除 (後ろにハイフン-が付きます)

kubectl taint nodes node1 key1=value1:NoSchedule-
Taintの書式

valueはoptionalなので、KeyとTaintEffectさえあればつけることができる。つまり、kubectl taint nodes node1 key1:NoScheduleのようなことが可能。

https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/api/core/v1/types.go#L3538

type Taint struct {
    // Required. The taint key to be applied to a node.
    Key string `json:"key" protobuf:"bytes,1,opt,name=key"`

    // +optional
    Value string `json:"value,omitempty" protobuf:"bytes,2,opt,name=value"`
 
    // Required. The effect of the taint on pods
    // Valid effects are NoSchedule, PreferNoSchedule and NoExecute.
    Effect TaintEffect `json:"effect" protobuf:"bytes,3,opt,name=effect,casttype=TaintEffect"`
 
    // TimeAdded represents the time at which the taint was added.
    // It is only written for NoExecute taints.
    // +optional
    TimeAdded *metav1.Time `json:"timeAdded,omitempty" protobuf:"bytes,4,opt,name=timeAdded"`
}
TaintEffectごとの挙動

NoSchedule / PreferNoSchedule / NoExecute

この3つを設定可能。

NoScheduleとPreferNoScheduleではどちらも、既にノードにスケジュール済みのPodは退去されず、新しくスケジュールされるPodについての挙動を指定する。

NoScheduleはtaintを許容しない限りPodがスケジュールされることはない。

PreferNoScheduleはtaintを許容しないPodがノードにスケジュールされることを回避しようとするが、保証はされない。

NoExecuteはNoScheduleに加え、既にスケジュール済みのPodを退去させる。

Tolerations

言葉の意味 google翻訳: 寛容

deepL: 黙認

ChatGPT: 容認または寛容 - 多分これが良さそう

Podに設定するもので複数指定可能 .spec.tolerations

Taintと完全に一致するとノードへのスケジュールが可能となる。(Key, Valueが同じでもTaintEffectが違うと一致しない

Tolerationを指定する
# .spec
tolerations:
  - key: "example-key"
    operator: "Exists"
    effect: "NoSchedule"
Tolerationsの書式

https://github.com/kubernetes/kubernetes/blob/7f23ebedc44565c0180898805011e61eb3f1226d/staging/src/k8s.io/api/core/v1/types.go#L3580

type Toleration struct {
    // +optional
    Key string `json:"key,omitempty" protobuf:"bytes,1,opt,name=key"`

    // Valid operators are Exists and Equal. Defaults to Equal.
    // +optional
    Operator TolerationOperator `json:"operator,omitempty" protobuf:"bytes,2,opt,name=operator,casttype=TolerationOperator"`

    // +optional
    Value string `json:"value,omitempty" protobuf:"bytes,3,opt,name=value"`

    // When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute.
    // +optional
    Effect TaintEffect `json:"effect,omitempty" protobuf:"bytes,4,opt,name=effect,casttype=TaintEffect"`
 
    // TolerationSeconds represents the period of time the toleration (which must be
    // of effect NoExecute, otherwise this field is ignored) tolerates the taint.  Zero and
    // negative values will be treated as 0 (evict immediately) by the system.
    // +optional
    TolerationSeconds *int64 `json:"tolerationSeconds,omitempty" protobuf:"varint,5,opt,name=tolerationSeconds"`
}
TolerationSecondsの挙動

TolerationSecondsはTaintEffectがNoExecuteの場合のみ指定可能。

一致するTaintがノードに付けられた場合、指定秒数待ってから退去が始まる。

時間が経過する前にTaintが削除されると、退去は行われない。

ユースケース
  • Dedicated Nodes

    • 特定のPodにノードを占有させたい場合
  • Nodes with Special Hardware

    • GPUを必要とするPodがあるケース
  • Taint based Evictions

    • Node Controllerが自動で付与するケース
      • 例えば、node.kubernetes.io/not-readynode.kubernetes.io/unschedulableとか

所感ですが、Taint/Tolerationのみでスケジュールを制御するケースはあまりなさそう。

TolerationのついたPodが絶対的に同じTaintのついたノードにスケジュールされるとは保証できないため。

実践編

事前準備

ここに素敵なクラスタがあります。この時点ではTaintはついていません。

k get node
NAME                 STATUS     ROLES           AGE   VERSION
kind-control-plane   Ready      control-plane   60s   v1.30.0
kind-worker          NotReady   <none>          21s   v1.30.0
kind-worker2         NotReady   <none>          21s   v1.30.0

Podのサンプル

# inflate.yaml
apiVersion: /v1
kind: Pod
metadata:
  name: inflate
spec:
  containers:
    - name: inflate
      image: public.ecr.aws/eks-distro/kubernetes/pause:3.2
  # tolerations:
  # 色々書いていく

ケース1: TaintがついたNodeにPodをスケジュールしてみる

1: EffectがNoScheduleのTaintをつける

$ k taint nodes kind-worker dont-come:NoSchedule
$ k taint nodes kind-worker2 dont-come:NoSchedule

2: Podのスケジューリングを試みる。(まだTolerationを設定していないのでPendingのままになる)

$ ka inflate.yaml   # ka='kubectl apply --recursive -f'
pod/inflate created

$ kgpo   # kgpo='kubectl get pod'
NAME      READY   STATUS    RESTARTS   AGE
inflate   0/1     Pending   0          26s

3: PodにTolerationをつけてみる

  tolerations:
    - key: "dont-come"
      effect: "NoSchedule"

4: Podのスケジューリングにリトライ。どちらのノードもTolerationと一致するので用意した2つのノードにスケジュール可能.

$ ka inflate.yaml
pod/inflate configured

# 今回はkind-workerにスケジュールされた
$ kgpo -o=jsonpath='{.items[0].spec.nodeName}'
kind-worker or kind-worker2

5: Taintを削除する

$ k taint nodes kind-worker dont-come:NoSchedule-
$ k taint nodes kind-worker2 dont-come:NoSchedule-

TolerationはTaintがついたNodeへのスケジュールを許容するだけでTaintのないNodeへもスケジュールはできる。

ケース2: NoExecute Effectがついている場合の動きを観察する

1: 適当なPodを配置しておく

$ cat <<EOF |kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: hoge
spec:
  containers:
    - name: hoge
      image: public.ecr.aws/eks-distro/kubernetes/pause:3.2
EOF

pod/hoge created

$ kgpo
hoge   1/1     Running   0          38s

2: EffectがNoExecuteのTaintをつける

$ k taint nodes kind-worker get-out:NoExecute
$ k taint nodes kind-worker2 get-out:NoExecute

3: 関係のないPodが退去したことを確認する

$ kgpo
No resources found in default namespace.

4: PodがスケジュールされるようにTolerationをつける

  tolerations:
    - key: "get-out"
      effect: "NoExecute"

5: Podを再度applyする。ステップ1でapplyした適当なPodを再度applyしてもPendingになりスケジュールはされません。

$ ka inflate.yaml
pod/inflate created

$ kgpo
inflate   1/1     Running   0          43s

簡単ですが、実践編はここで終わりです。

さらにその先へ(行きたい)

kube-schedulerのコードから学ぶ taint, tolerationを評価してるところを追うだけ。

感想

今までkubernetesのソースコードを読んだことがなく、taints, tolerationsの評価をしている場所にのみ着目してコードリーディングにチャレンジしました。
いざ読んでみると時間はかかりましたが、評価する関数にたどり着いたので「自分はkubenetesのソースコードが読めるんだ!」と思い自信が少しつきました。(小さく始めてよかった😄)

普段から記事を書く機会は多くないのですが、今回アウトプットとして記事を書くことでかなり頭が整理されたと感じました。
今回はkubernetesの一部を知ることができましたが、kube-schedulerなんもわからん... いつか完全に理解したいですね。