特定のノードへの Kubernetes Pod のスケジューリング

KubernetesBeginner
オンラインで実践に進む

はじめに

通常、スケジューラーは実行可能なノードを任意に選択します。ラベルとアフィニティはワークロードの配置に関する希望を表し、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 を使い、配置の意図とノードへの受け入れ条件をそれぞれ独立して表現しました。