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 | 引き寄せ | Pod | Pod が特定のノードを選ぶ |
| 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:NoSchedulevalue は省略でき、そのときは key と effect だけで taint が成立します。
# value のない taint
k taint node node01 dedicated:NoScheduleEffect の 3 種類: NoSchedule / PreferNoSchedule / NoExecute #
taint の強さは effect が決めます。3 種類あり、何を防ぐかが互いに異なります。
| effect | 新しい Pod のスケジュール | すでに動いている Pod |
|---|---|---|
NoSchedule | toleration がなければ 配置を拒否 | 触れない |
PreferNoSchedule | 可能なら避けるが 強制ではない | 触れない |
NoExecute | toleration がなければ配置を拒否 | toleration がなければ 即座に追い出す (evict) |
NoSchedule は最もよく使われる effect で、これから入ってくる Pod だけを防ぎます。PreferNoSchedule は弱いバージョンなので、ほかのノードがなければ結局配置されます。NoExecute は最も強いです。新しい Pod を防ぐことに加え、すでにそのノードで動いていた toleration のない Pod まで追い出します。 ノードを空けなければならないとき、強く使う effect です。
# NoExecute: すでに動いている toleration のない Pod まで追い出す
k taint node node01 maintenance=true:NoExecuteToleration の付け方 #
Pod が taint に耐えるには spec.tolerations に toleration を書きます。taint の key、value、effect と合っている必要があります。
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: nginxoperator は 2 種類です。
| operator | 意味 |
|---|---|
Equal | key、value、effect がすべて一致する taint に耐える (value 必要) |
Exists | key (と 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 億未満 |
globalDefault | true なら priorityClassName のない Pod のデフォルト優先度。クラスターに 1 つだけ 置ける |
preemptionPolicy | PreemptLowerPriority (デフォルト) なら 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 falsesystem-node-critical と system-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 は リソースを巡って競合するとき、優先度の順序を強制する仕組み です。
preemptionPolicy を Never にすると動作が変わります。この Pod はスケジューリングのキューでは高い優先度で前に位置しますが、ほかの Pod を追い出しはしません。 空きが出るまで待ちつつ他者を追い出さないので、バッチ作業のようなワークロードに適しています。
# 列の前には立つが追い出しはしない
preemptionPolicy: NeverPreemption と 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 を削除しないとワークロードが載りません。 tolerationSecondsはNoExecuteでのみ意味があり、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 を扱います。リソース設定がスケジューリングと追い出しの両方にどうつながるかも一緒に整理します。