在指定节点上调度 Kubernetes Pod

KubernetesBeginner
立即练习

简介

调度器通常会从所有符合条件的节点中任选一个。标签和亲和性用于表达工作负载的偏好,而污点则允许节点拒绝缺少匹配容忍度的 Pod。

你将逐步为预先准备好的节点应用这些控制机制,并通过 Pod 事件区分强制调度规则和软性偏好。

可以用下面的思路理解这些机制:标签描述节点,选择器和亲和性表达 Pod 的需求,污点表达节点拒绝的对象,而容忍度则允许 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 选项会为每个标签键添加一列。在节点行中看到 local 和 fast,说明调度器能够评估后续步骤中使用的规则。

NAME         STATUS   ROLES           ...   VERSION   WORKLOAD-ZONE   DISK-CLASS
labex-v135   Ready    control-plane   ...   v1.35.5   local           fast

这也确认升级后的环境正在运行 Kubernetes v1.35.5。刚刚添加的标签提供了后面两列自定义信息。

使用 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,等待其就绪,并查看 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 更灵活。必需部分会排除不匹配的节点;首选部分则会为匹配的节点增加权重,但不会把这一偏好变成强制条件。

较长的字段名体现了规则的生效时机:规则只在调度期间评估,Pod 运行后不再据此执行驱逐操作。如果标签之后发生变化,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。由于这是一个只有一个节点的集群,必需的 workload-zone=local 规则决定了节点是否可用;首选的高速磁盘规则则展示了评分语法,而无需专门增加第二个节点进行比较。

使用污点和容忍度

在本步骤中,为节点添加污点,观察没有容忍度的 Pod 保持 Pending 状态,然后创建一个允许调度的 Pod。

该污点的键为 practice-reserved,值为 true,效果为 NoSchedule。这种效果会阻止没有匹配容忍度的新 Pod 调度到该节点,但不会驱逐已经在节点上运行的 Pod。

kubectl taint node NODE key=value:effect 会将这条规则附加到节点上。下面的 kubectl run 会创建一个没有容忍度的独立 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 可以让实验继续执行。调度事件中应提到未被容忍的污点,从而说明 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,但找不到允许它使用的节点。

创建一个匹配的容忍度。容忍度允许 Pod 越过污点进行调度,但它本身并不能选择节点。

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

这个 Pod 同时配置了 nodeSelector,因此一个规则负责选择节点,另一个规则负责允许 Pod 调度到该节点。删除有意创建的阻塞 Pod 后,命名空间会恢复到干净、健康的状态。

pod/tolerated-pod condition met 表明添加匹配的容忍度改变了调度结果。之前的 Pod 仍会继续运行,因为 NoSchedule 只影响新的放置操作,不会驱逐已经运行的 Pod。

总结

你使用标签、nodeSelector、必需和首选亲和性、调度器事件、污点以及容忍度,分别表达了工作负载的放置意图和节点的接纳条件。