3. 节点亲和性与 Pod 调度:nodeAffinity 的硬约束与软约束
节点亲和性(Node Affinity)是 Kubernetes 调度器将 Pod 绑定到特定节点集合的核心策略。与简单的 nodeSelector 相比,nodeAffinity 提供了更丰富的匹配表达式(In、NotIn、Exists、DoesNotExist、Gt、Lt)以及两种调度语义:硬约束(必须满足)和软约束(尽量满足)。
3.1 硬约束 vs 软约束:核心区别
| 维度 | requiredDuringSchedulingIgnoredDuringExecution(硬约束) | preferredDuringSchedulingIgnoredDuringExecution(软约束) |
|---|---|---|
| 调度语义 | Pod 必须被调度到满足条件的节点上;若不存在符合条件的节点,Pod 将一直处于 Pending 状态。 |
调度器会优先选择满足条件的节点;若没有节点满足,Pod 仍会被调度到其他可用节点上。 |
| 权重 | 无权重概念,条件为布尔逻辑。 | 每个规则可设置 weight(1-100),调度器按加权和排序节点。 |
| 执行阶段 | 仅在 Pod 调度时检查(Scheduling)。运行期间节点标签变化不影响已运行 Pod。 | 仅在 Pod 调度时计算偏好。运行期间节点标签变化不影响已运行 Pod。 |
| 典型场景 | GPU 节点、合规区域、专用硬件池。 | 可用区均衡、性能优先、成本优化。 |
关键理解: 两种策略的
IgnoredDuringExecution 后缀意味着:节点在 Pod 运行后变更标签,不会驱逐已运行的 Pod。这是 Kubernetes 设计上对运行工作负载的保护。
3.2 调度流程与决策树
下图展示了调度器在处理 nodeAffinity 时的完整决策路径:
3.3 实战配置示例
3.3.1 硬约束:强制调度到 GPU 节点
以下配置确保 Pod 只能运行在带有 gpu-type=nvidia-a100 标签的节点上:
apiVersion: v1
kind: Pod
metadata:
name: gpu-inference-pod
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: gpu-type
operator: In
values:
- nvidia-a100
containers:
- name: inference
image: nvidia/cuda:12.0-runtime
resources:
limits:
nvidia.com/gpu: 1
避坑提示: 当使用
In 操作符时,values 列表至少包含一个值。若节点标签值可能为空,考虑使用 Exists 操作符仅检查 key 是否存在。
3.3.2 软约束:优先选择低延迟区域
以下配置优先将 Pod 调度到 zone=low-latency 的节点,权重为 80;若不可用,则回退到其他区域:
apiVersion: v1
kind: Pod
metadata:
name: latency-sensitive-app
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
preference:
matchExpressions:
- key: zone
operator: In
values:
- low-latency
- weight: 20
preference:
matchExpressions:
- key: zone
operator: In
values:
- standard
containers:
- name: app
image: nginx:alpine
3.4 多条件组合与操作符详解
| 操作符 | 语义 | 示例 |
|---|---|---|
In |
标签值在给定集合中 | key: region, values: [us-east-1, us-west-2] |
NotIn |
标签值不在给定集合中 | key: disk-type, values: [hdd] |
Exists |
节点存在该标签键(忽略值) | key: nvidia.com/gpu, operator: Exists |
DoesNotExist |
节点不存在该标签键 | key: spot-instance, operator: DoesNotExist |
Gt / Lt |
标签值大于/小于给定数值(值必须为整数) | key: memory-gb, operator: Gt, values: [64] |
3.5 硬约束与软约束联合使用
生产环境中,硬约束和软约束通常组合使用,形成“必须满足基础条件 + 尽量优化”的策略:
apiVersion: v1
kind: Pod
metadata:
name: hybrid-affinity-pod
spec:
affinity:
nodeAffinity:
# 硬约束:必须运行在 ARM 架构节点上
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/arch
operator: In
values:
- arm64
# 软约束:优先选择高内存节点
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 70
preference:
matchExpressions:
- key: memory-size
operator: In
values:
- large
- xlarge
- weight: 30
preference:
matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- zone-a
containers:
- name: app
image: arm64v8/nginx
调度器行为: 调度器首先过滤出所有满足硬约束的节点(ARM64),然后在这些节点上根据软约束权重计算得分(高内存 +70,zone-a +30),最终选择总分最高的节点。若没有节点满足硬约束,Pod 将保持 Pending。
3.6 调试与验证命令
使用 kubectl debug 和调度器事件排查亲和性调度问题:
# 查看 Pod 调度事件
kubectl describe pod <pod-name> | grep -A 10 Events
# 使用临时调试容器检查节点标签
kubectl debug node/<node-name> -it --image=busybox -- sh
# 在调试容器内执行:cat /etc/hostname 或 kubectl get nodes --show-labels
# 模拟调度(dry-run)
kubectl create -f pod.yaml --dry-run=server -o json | jq '.spec.nodeName'
# 查看调度器日志(需有权限)
kubectl logs -n kube-system -l component=kube-scheduler --tail=50 | grep <pod-name>
3.7 常见问题与排错
- Pod 一直 Pending: 检查
kubectl describe pod中 Events 是否提示0/3 nodes are available: 3 node(s) didn't match node selector。确认节点标签是否存在且值匹配。 - 软约束未生效: 软约束是尽力而为,若所有节点都不满足,Pod 仍会被调度。检查
weight值是否过低,或与其他调度策略(如 Pod 反亲和性)冲突。 - 节点标签变更后 Pod 未迁移: 这是预期行为。两种策略的
IgnoredDuringExecution后缀明确表示运行时不重新调度。如需响应式迁移,需结合descheduler或自定义控制器。 - 多条件逻辑混淆:
nodeSelectorTerms下多个matchExpressions是 OR 关系;单个matchExpressions内多个表达式是 AND 关系。