投稿時はSRE船のPlatform Orchestrationヨット(a.k.a みちびき)で内定者インターンをしているpokoです。
私はkubernetesとお友達になりたいので、私は正座しながらKubernetes ドキュメントを読む時間を毎週設けています。
なんの記事?
記事投稿時、kubernetesのバージョンは1.30です
kubernetesでPodをスケジュールする際に利用できるTaint and Tolerationsの仕組みと挙動を説明しています。
最終的には「ドキュメントをお読みください。」に行き着くのですが、少し噛み砕いた説明を意識しています。
あなたはこの記事を読むべき人?
app=hoge:NoSchedule
というTaintがついたノードに、以下のTolerationがついたPodはスケジュールされる?されない?上記のTaintが付けられた時、すでにスケジュール済みのPodはどうなる?
tolerations: - key: "app" operator: "Equal" value: "hoge" effect: "NoSchedule"
答え
スケジュールされます 🙆
削除されません 🙅
今まで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の書式
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-ready
やnode.kubernetes.io/unschedulable
とか
- 例えば、
- Node Controllerが自動で付与するケース
所感ですが、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を評価してるところを追うだけ。
- func Setup()
- func New()
- func (sched *Scheduler) applyDefaultHandlers()
- func (sched *Scheduler) schedulePod()
- func (sched *Scheduler) findNodesThatFitPod()
- func (sched *Scheduler) findNodesThatPassFilters()
- PluginsによってフィルタされたNodeを返す
- PluginsにはTaints, TolerationやnodeAffinityなどがある
- func (f *frameworkImpl) RunFilterPluginsWithNominatedPods
- func (f *frameworkImpl) RunFilterPlugins()
- func (f *frameworkImpl) runFilterPlugin()
- func (pl *TaintToleration) Filter() - taint, tolerationのフィルタする本体。
- スケジュールしたいPodのTolerationsとNodeのTaintsをフィルタして許容されていなければエラーを返している
- func FindMatchingUntoleratedTaint()
- func TolerationsTolerateTaint()
- func (t *Toleration) ToleratesTaint() - 俺が評価してるぜ 😎
- func (pl *TaintToleration) Filter() - taint, tolerationのフィルタする本体。
- func New()
感想
今までkubernetesのソースコードを読んだことがなく、taints, tolerationsの評価をしている場所にのみ着目してコードリーディングにチャレンジしました。
いざ読んでみると時間はかかりましたが、評価する関数にたどり着いたので「自分はkubenetesのソースコードが読めるんだ!」と思い自信が少しつきました。(小さく始めてよかった😄)
普段から記事を書く機会は多くないのですが、今回アウトプットとして記事を書くことでかなり頭が整理されたと感じました。
今回はkubernetesの一部を知ることができましたが、kube-schedulerなんもわからん... いつか完全に理解したいですね。