はじめに
通常、スケジューラーは実行可能なノードを任意に選択します。ラベルとアフィニティはワークロードの配置に関する希望を表し、taint は一致する toleration を持たない Pod をノードから退けます。
この実験では、準備済みのノードに対してこれらの制御機能を順番に適用し、Pod イベントを使って、必ず守られるスケジューリングルールと、優先されるだけの設定を見分けます。
次のように考えると分かりやすくなります。ラベルはノードの属性を表し、セレクターとアフィニティは Pod が求める条件を表します。taint はノードが拒否する条件を表し、toleration は一致する拒否ルールを Pod が通過できるようにします。これらの仕組みはいずれも、すでに実行中の Pod を直接「移動」させるものではありません。
配置用のラベルをノードに付ける
このステップでは、説明用のラベルを追加し、それらを一覧の列として表示します。
ここで付けるラベルは、アプリケーションの識別情報ではなく、ワークロードの配置を表すものです。workload-zone=local は場所を、disk-class=fast はノードの性能を表します。Kubernetes はラベルの業務上の意味を検証しないため、その意味はスケジューリングルールによって定義されます。
cd /home/labex/project/pod-scheduling
kubectl create namespace practice-scheduling
kubectl label node labex-v135 workload-zone=local disk-class=fast
kubectl get nodes -L workload-zone,disk-class
ラベルはセレクターやアフィニティが利用する属性であり、既存の Pod を自動的に移動させるものではありません。ただし、コントローラーが置き換え用の Pod を作成する場合は別です。
-L オプションを使うと、ラベルキーごとに 1 列が追加されます。ノードの行に local と fast が表示されれば、後のステップで使うルールをスケジューラーが評価できることを確認できます。
NAME STATUS ROLES ... VERSION WORKLOAD-ZONE DISK-CLASS
labex-v135 Ready control-plane ... v1.35.5 local fast
これにより、更新済みの環境で Kubernetes v1.35.5 が動作していることも確認できます。追加された 2 つの列は、先ほど設定したラベルに基づいています。
nodeSelector でノードを必須指定する
このステップでは、最も単純な完全一致の配置ルールを持つ Pod を作成します。
nodeSelector は、必須となる等価一致条件のマップです。Pod は、すべてのキーが要求された値を持つノードでのみ実行可能になります。単純な必須配置には適した方法です。
cd /home/labex/project/pod-scheduling
cat <<'EOF' > selected-pod.yaml
apiVersion: v1
kind: Pod
metadata: {name: selected-pod, namespace: practice-scheduling}
spec:
nodeSelector: {disk-class: fast}
containers:
- {name: worker, image: busybox:1.36, imagePullPolicy: IfNotPresent, command: ["sh", "-c", "while true; do sleep 30; done"]}
EOF
Pod を適用し、Ready になるまで待ってから、NODE 列で割り当て先のノードを確認します。
kubectl apply -f selected-pod.yaml
kubectl wait --for=condition=Ready pod/selected-pod -n practice-scheduling --timeout=120s
kubectl get pod selected-pod -n practice-scheduling -o wide
nodeSelector は必須条件です。要求されたすべてのラベルを持つノードが存在しない場合、Pod は Pending のままになります。
選択されたノードは labex-v135 になるはずです。スケジューリングが成功したことは、ノードの disk-class=fast が Pod の要求に一致したことを示します。
pod/selected-pod condition met
NAME READY STATUS ... NODE
selected-pod 1/1 Running ... labex-v135
NODE 列はスケジューリングの結果であり、マニフェスト内の nodeSelector はそのノードが選択可能だった理由です。
必須アフィニティと優先アフィニティを比較する
このステップでは、ローカルゾーンを必須とし、高速ディスクを優先する Pod を作成します。
ノードアフィニティは nodeSelector よりも柔軟な条件を表現できます。必須セクションは一致しないノードを候補から除外し、優先セクションは一致するノードに重みを付けますが、その条件を必須にはしません。
長いフィールド名は、ルールが評価されるタイミングを表しています。このルールはスケジューリング時に評価され、実行中は無視されます。後からラベルが変更されても、このアフィニティルールだけを理由に、Kubernetes が実行中の Pod を追い出すことはありません。
cd /home/labex/project/pod-scheduling
cat <<'EOF' > affinity-pod.yaml
apiVersion: v1
kind: Pod
metadata: {name: affinity-pod, namespace: practice-scheduling}
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions: [{key: workload-zone, operator: In, values: [local]}]
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions: [{key: disk-class, operator: In, values: [fast]}]
containers:
- {name: worker, image: busybox:1.36, imagePullPolicy: IfNotPresent, command: ["sh", "-c", "while true; do sleep 30; done"]}
EOF
Pod を適用し、スケジューリングが完了するまで待ちます。
kubectl apply -f affinity-pod.yaml
kubectl wait --for=condition=Ready pod/affinity-pod -n practice-scheduling --timeout=120s
kubectl get pod affinity-pod -n practice-scheduling -o wide
必須ルールは実行可能なノードを絞り込み、優先ルールは残った候補にスコアを付けます。IgnoredDuringExecution は、後からラベルが変更されても実行中の Pod を追い出さないことを意味します。
結果の表にも labex-v135 が表示されます。この 1 ノード構成のクラスタでは、必須条件である workload-zone=local が実行可能性を決めています。一方、高速ディスクを優先するルールは、比較用に別のノードを追加しなくても、スコアリングの構文を確認できるようになっています。
taint と toleration を使う
このステップでは、ノードに taint を設定し、toleration を持たない Pod が Pending のままになることを確認した後、許可された Pod を作成します。
この taint のキーは practice-reserved、値は true、効果は NoSchedule です。この効果により、一致する toleration を持たない新しい Pod は配置できなくなりますが、そのノードですでに実行中の Pod は追い出されません。
kubectl taint node NODE key=value:effect は、そのルールをノードに付加します。次の kubectl run は toleration を持たない単独の Pod を作成します。また、PodScheduled 条件を待つことで、スケジューラーに 10 秒間の制限内で想定される失敗を確認させます。
cd /home/labex/project/pod-scheduling
kubectl taint node labex-v135 practice-reserved=true:NoSchedule
kubectl run blocked-pod -n practice-scheduling --image=busybox:1.36 --image-pull-policy=IfNotPresent --restart=Never -- sh -c 'sleep 3600'
kubectl wait --for=condition=PodScheduled pod/blocked-pod -n practice-scheduling --timeout=10s || true
kubectl events -n practice-scheduling --for pod/blocked-pod
待機がタイムアウトするのは想定された動作です。|| true によって、レッスンを続行できます。スケジューリングイベントには toleration のない taint に関する情報が表示され、Pod が Pending になった理由を確認できます。
error: timed out waiting for the condition on pods/blocked-pod
LAST SEEN TYPE REASON OBJECT MESSAGE
... Warning FailedScheduling Pod/blocked-pod 0/1 nodes are available: 1 node(s) had untolerated taint(s)...
ここでは、Pending が正しい実験結果です。スケジューラーは Pod を評価しましたが、使用を許可できるノードを見つけられませんでした。
一致する toleration を作成します。toleration は taint のあるノードへのスケジューリングを許可しますが、それだけでノードを選択するわけではありません。
cat <<'EOF' > tolerated-pod.yaml
apiVersion: v1
kind: Pod
metadata: {name: tolerated-pod, namespace: practice-scheduling}
spec:
nodeSelector: {workload-zone: local}
tolerations:
- {key: practice-reserved, operator: Equal, value: "true", effect: NoSchedule}
containers:
- {name: worker, image: busybox:1.36, imagePullPolicy: IfNotPresent, command: ["sh", "-c", "while true; do sleep 30; done"]}
EOF
kubectl apply -f tolerated-pod.yaml
kubectl wait --for=condition=Ready pod/tolerated-pod -n practice-scheduling --timeout=120s
kubectl delete pod blocked-pod -n practice-scheduling
この toleration 付き Pod には nodeSelector も設定されています。一方のルールがノードを選択し、もう一方のルールがそのノードへの配置を許可します。意図的にブロックした Pod を削除することで、名前空間を正常でクリーンな状態に戻せます。
pod/tolerated-pod condition met と表示されれば、一致する toleration の追加によって結果が変わったことを確認できます。先ほどの Pod は引き続き実行されます。NoSchedule は新しい配置に影響しますが、すでに実行中の Pod を追い出すことはないためです。
まとめ
ラベル、nodeSelector、必須および優先アフィニティ、スケジューラーイベント、taint、toleration を使い、配置の意図とノードへの受け入れ条件をそれぞれ独立して表現しました。


