Certified Kubernetes Administrator (CKA) #14 Scheduling 2: Taints/tolerations、Priority/PriorityClass、preemption

#13 Scheduling 1 では nodeSelector と affinity で Pod がノードを選ぶ メカニズムを扱いました。今回は方向が逆です。ノードが Pod を押し出す taint、そしてリソースが足りないとき 誰を先に生かすか を決める PriorityClass と preemption を扱います。

affinity が Pod の引き寄せなら、taint はノードの押し出しです。両者は正反対の方向のツールなので、組み合わせることでノード配置を両側から制御できます。ここに優先度が加わると、ノードがいっぱいのときスケジューラーが低い優先度の Pod を追い出して高い優先度の Pod を入れる preemption まで起こります。運用者の観点から YAML と kubectl で順に手に覚えさせます。

Taint と Toleration: ノードが拒否し、Pod が受け入れる #

taint と toleration は一対で動作します。taint はノードに掛ける拒否の印 であり、toleration はその拒否に耐えるという Pod の宣言 です。ノードに taint が掛かっていると、その taint に耐える toleration を持つ Pod だけがそのノードに入れます。

ノード:  「私は key=value:NoSchedule という拒否の印を掛けた」
Pod A: toleration なし        → このノードに入れない
Pod B: 同じ taint に耐える    → このノードに入れる

ここで核心は toleration が許可ではなく免除である という点です。toleration を持つ Pod が必ずそのノードへ行くのではなく、そのノードの拒否から免除されるだけです。実際にそのノードへ Pod を送るには、#13 の nodeAffinity や nodeSelector を一緒に使う必要があります。この違いが試験でよく混同される箇所です。

taint と affinity の役割は次のように分かれます。

ツール方向主体効果
nodeAffinity / nodeSelector引き寄せPodPod が特定のノードを選ぶ
taint / toleration押し出しノードノードが Pod を拒否し、toleration がその拒否を免除する

Taint の掛け方 #

taint は kubectl taint node で掛けます。形式は key=value:effect です。

# ノードに taint を掛ける (key=value:effect)
k taint node node01 gpu=true:NoSchedule

# taint の削除 (末尾に - を付ける)
k taint node node01 gpu=true:NoSchedule-

# ノードに掛かった taint を確認
k describe node node01 | grep -i taint
# Taints:  gpu=true:NoSchedule

value は省略でき、そのときは key と effect だけで taint が成立します。

# value のない taint
k taint node node01 dedicated:NoSchedule

Effect の 3 種類: NoSchedule / PreferNoSchedule / NoExecute #

taint の強さは effect が決めます。3 種類あり、何を防ぐかが互いに異なります。

effect新しい Pod のスケジュールすでに動いている Pod
NoScheduletoleration がなければ 配置を拒否触れない
PreferNoSchedule可能なら避けるが 強制ではない触れない
NoExecutetoleration がなければ配置を拒否toleration がなければ 即座に追い出す (evict)

NoSchedule は最もよく使われる effect で、これから入ってくる Pod だけを防ぎます。PreferNoSchedule は弱いバージョンなので、ほかのノードがなければ結局配置されます。NoExecute は最も強いです。新しい Pod を防ぐことに加え、すでにそのノードで動いていた toleration のない Pod まで追い出します。 ノードを空けなければならないとき、強く使う effect です。

# NoExecute: すでに動いている toleration のない Pod まで追い出す
k taint node node01 maintenance=true:NoExecute

Toleration の付け方 #

Pod が taint に耐えるには spec.tolerations に toleration を書きます。taint の keyvalueeffect と合っている必要があります。

apiVersion: v1
kind: Pod
metadata:
  name: gpu-pod
spec:
  tolerations:
  - key: "gpu"
    operator: "Equal"     # key=value が同じ taint に耐える
    value: "true"
    effect: "NoSchedule"
  containers:
  - name: app
    image: nginx

operator は 2 種類です。

operator意味
Equalkeyvalueeffect がすべて一致する taint に耐える (value 必要)
Existskey (と effect) だけ一致すれば value に関係なく耐える (value 省略)

Exists は value を参照しないので、特定の key を持つすべての taint を一度に許容させるときに便利です。key まで省略すると すべての taint に耐える toleration になり、通常は DaemonSet のようにどのノードにでも配置する必要があるワークロードで使います。

# すべての taint に耐える (key 省略 + Exists)
tolerations:
- operator: "Exists"

NoExecute と tolerationSeconds #

NoExecute taint には tolerationSeconds を一緒に使えます。この値を与えると、toleration を持つ Pod でも 無限に残らず、指定した秒数だけ留まった後に追い出されます

tolerations:
- key: "maintenance"
  operator: "Exists"
  effect: "NoExecute"
  tolerationSeconds: 300   # 300 秒だけ耐えてその後に追い出す

この動作はノード障害への対応にそのまま現れます。ノードが NotReady になると、control plane が node.kubernetes.io/not-ready:NoExecute taint を自動で掛けます。すべての Pod にはデフォルトでこの taint に対する tolerationSeconds: 300 toleration が自動で注入されているので、ノードが一時的に切れて戻れば Pod は生き残り、5 分を超えるとほかのノードへ移されます。tolerationSeconds は、いわば 障害検知と再配置の間の猶予時間 です。

Control plane ノードのデフォルト taint #

kubeadm で立てたクラスターの control plane ノードにはデフォルト taint が掛かっています。そのため一般のワークロード Pod が control plane に載りません。

k describe node controlplane | grep -i taint
# Taints:  node-role.kubernetes.io/control-plane:NoSchedule

この taint のおかげで control plane コンポーネントがユーザーワークロードとリソースを奪い合いません。単一ノードクラスターのように control plane にも Pod を立てる必要がある状況なら、この taint を削除すればよいです。

# control plane ノードのデフォルト taint を削除 (末尾に - )
k taint node controlplane node-role.kubernetes.io/control-plane:NoSchedule-

逆に kubeadm が control plane コンポーネント (kube-apiserver など) の Pod を control plane に立てられる理由も taint です。それらの static Pod は、この taint に耐える toleration を持っています。

Priority と PriorityClass #

ここまでは どこに 配置するかを扱いました。PriorityClass は別の次元です。リソースが足りず全員を立てられないとき、誰を先に生かすか を決めます。

PriorityClass はクラスター範囲 (non-namespaced) のリソースで、整数の優先度値を定義します。Pod は spec.priorityClassName でこのクラスを参照し、その値が Pod の優先度になります。値が大きいほど優先度が高いです。

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000000
globalDefault: false
preemptionPolicy: PreemptLowerPriority
description: "決済など重要なワークロード用の優先度"
フィールド意味
value優先度の整数。大きいほど高い。ユーザー定義は通常 10 億未満
globalDefaulttrue なら priorityClassName のない Pod のデフォルト優先度。クラスターに 1 つだけ 置ける
preemptionPolicyPreemptLowerPriority (デフォルト) なら preemption を実行。Never なら列の前に立つが 追い出しはしない

Pod 側では名前で参照します。

apiVersion: v1
kind: Pod
metadata:
  name: payment
spec:
  priorityClassName: high-priority
  containers:
  - name: app
    image: nginx
# PriorityClass の一覧と値を確認
k get priorityclass
# NAME                      VALUE        GLOBAL-DEFAULT
# high-priority             1000000      false
# system-cluster-critical   2000000000   false
# system-node-critical      2000001000   false

system-node-criticalsystem-cluster-critical は Kubernetes があらかじめ作っておくシステム PriorityClass です。kube-proxy や CNI のようなノード必須コンポーネントがこの値を使うことで、リソースが足りなくても最後まで生き残ります。

Preemption: 低い優先度の Pod を追い出す #

優先度が実際に力を発揮する瞬間が preemption です。高い優先度の Pod が Pending なのにクラスターに空きがないと、スケジューラーは より低い優先度の Pod を追い出して (evict) 空きを作り、その場所に高い優先度の Pod を入れます。

1. high-priority Pod が Pending。どのノードにも空きがない
2. スケジューラー: 「このノードの low-priority Pod を空ければ空きができる」
3. low-priority Pod が追い出される (graceful termination)
4. high-priority Pod がその場所に配置される

追い出された低い優先度の Pod は、ほかのノードに空きがあればそこへ移り、なければ Pending のまま残ります。つまり preemption は リソースを巡って競合するとき、優先度の順序を強制する仕組み です。

preemptionPolicyNever にすると動作が変わります。この Pod はスケジューリングのキューでは高い優先度で前に位置しますが、ほかの Pod を追い出しはしません。 空きが出るまで待ちつつ他者を追い出さないので、バッチ作業のようなワークロードに適しています。

# 列の前には立つが追い出しはしない
preemptionPolicy: Never

Preemption と PodDisruptionBudget #

preemption は graceful に進み、追い出し対象の Pod に terminationGracePeriod を与えます。ただし PodDisruptionBudget (PDB) は preemption を 完全には防げません。 スケジューラーは PDB をできる限り尊重しようと試みますが、高い優先度の Pod を立てるほかの方法がなければ、PDB に違反してでも追い出せます。PDB は #15 以降のリソース管理の流れで再び扱います。

Affinity と何が違うのか #

#13 の affinity と今回の記事のツールは、よく同じ場で比較されます。3 つを 1 段落でまとめると次のとおりです。affinity は Pod がノードを引き寄せる選好であり、taint/toleration はノードが Pod を押し出す拒否であり、PriorityClass/preemption はリソースが足りないときの生存順序です。 前の 2 つは「どのノードへ行けるか」という配置の問題であり、最後の 1 つは「空きが足りないとき誰が残るか」という競合の問題です。実務では 3 つを一緒に使います。taint で GPU ノードを一般の Pod から守り、nodeAffinity で GPU ワークロードをそのノードへ誘導し、PriorityClass でその中でも重要な作業を先に生かす、という形です。

試験ポイント #

  • toleration は許可ではなく免除 です。toleration があってもそのノードへ行く保証はなく、ノードへ送るには affinity や nodeSelector を一緒に使います。
  • effect の 3 種類を区別しましょう。NoSchedule (新しい Pod だけ)、PreferNoSchedule (弱い回避)、NoExecute (すでに動いている Pod まで追い出す)。
  • taint は k taint node <ノード> key=value:effect、削除は末尾に - を付けます。
  • control plane のデフォルト taint は node-role.kubernetes.io/control-plane:NoSchedule です。単一ノードならこの taint を削除しないとワークロードが載りません。
  • tolerationSecondsNoExecute でのみ意味があり、not-ready/unreachable の自動 taint のデフォルト猶予は 300 秒です。
  • PriorityClass の value は大きいほど高く、globalDefault: true はクラスターに 1 つだけ置きます。
  • preemption は高い優先度の Pod が Pending のとき低い優先度の Pod を追い出します。preemptionPolicy: Never なら列の前に立つが追い出しはしません。

まとめ #

この記事で押さえたこと:

  • taint はノードの拒否、toleration はその拒否の免除 という一対の動作、そして toleration が配置を保証しないという核心
  • effect の 3 種類 (NoSchedule/PreferNoSchedule/NoExecute) と NoExecute の追い出し、tolerationSeconds の猶予の意味
  • control plane ノードのデフォルト taint とその削除、そして not-ready 自動 taint の動作
  • PriorityClass (value/globalDefault/preemptionPolicy) と preemption でリソース競合の生存順序を強制する方法
  • affinity (引き寄せ)・taint (押し出し)・priority (生存順序) の役割の違い

スケジューリングを押さえたら、次はその配置の前提となるリソースそのものを扱います。

次へ — リソース管理 #

Pod をどのノードに置くか決めたら、次の問題はそのノードのリソースを どう分けて使うか です。リソースを少なく取るとノードが過密になり、多く取るとノードが空いていてもほかの Pod が入れません。

#15 リソース管理: requests/limits、QoS、LimitRange、ResourceQuota では、コンテナが要求し制限する CPU とメモリ (requests/limits)、それに応じて決まる QoS class (Guaranteed/Burstable/BestEffort)、ネームスペース単位でデフォルトと上限を強制する LimitRange と ResourceQuota を扱います。リソース設定がスケジューリングと追い出しの両方にどうつながるかも一緒に整理します。

X