在 Kubernetes 中,调度器(Scheduler)是集群的 “大脑”,负责将 Pod 合理分配到集群节点上;而亲和性(Affinity)则是精细控制 Pod 调度位置的核心工具。本文将结合我在离线单节点环境下的完整实战,从原理到代码,带你彻底掌握调度器与亲和性的配置、排障与最佳实践。


🎯 一、Kubernetes 调度器核心原理

1.1 调度器的工作流程

Kubernetes 默认调度器 kube-scheduler 的核心任务是为每个待调度 Pod 选择最优节点,分为以下阶段:

  1. 过滤(Filtering):通过一系列 “预选策略” 排除不满足条件的节点(如资源不足、节点污点)。
  2. 打分(Scoring):对通过过滤的节点按 “优选策略” 打分,得分最高的节点被选中。
  3. 绑定(Binding):将 Pod 与选中的节点绑定,完成调度。

1.2 自定义调度器的价值

默认调度器满足大多数场景,但在离线、单节点或特殊业务需求下,自定义调度器可以:

  • 绕过 DNS 依赖,直接通过 API 绑定 Pod。
  • 实现更灵活的节点选择逻辑(如固定调度到特定节点)。
  • 适配无 jq、无网络的极端离线环境。

1.3 无依赖自定义调度器实战

在离线环境中,我们可以用纯 Shell 脚本实现一个轻量自定义调度器,核心逻辑如下:

#!/bin/bash
SERVER='localhost:8001'
while true; do
    # 获取待调度 Pod(schedulerName=my-scheduler 且未分配节点)
    PODS=$(kubectl --server $SERVER get pods -o custom-columns=NAME:.metadata.name,SCHEDULER:.spec.schedulerName,NODE:.spec.nodeName --no-headers | awk '$2 == "my-scheduler" && $3 == "<none>" {print $1}')
    
    for PODNAME in $PODS; do
        # 获取节点列表并随机选择
        NODES=$(kubectl --server $SERVER get nodes -o custom-columns=NAME:.metadata.name --no-headers)
        CHOSEN=$(echo "$NODES" | shuf -n 1)
        
        # 绑定 Pod 到节点
        curl --header "Content-Type:application/json" \
             --request POST \
             --data '{"apiVersion":"v1","kind":"Binding","metadata":{"name":"'$PODNAME'"},"target":{"apiVersion":"v1","kind":"Node","name":"'$CHOSEN'"}}' \
             http://$SERVER/api/v1/namespaces/default/pods/$PODNAME/binding/
        
        echo "$(date +'%Y-%m-%d %H:%M:%S') - Assigned $PODNAME to $CHOSEN"
    done
    sleep 1
done

💡 关键优化:用 kubectl custom-columns + awk 替代 jq,纯原生工具适配离线环境;单节点环境下自动绑定到唯一节点 172-16-2-47


📋 二、亲和性与反亲和性深度解析

2.1 亲和性的设计目标

亲和性(Affinity)解决了 nodeSelector 功能单一的问题,支持:

  • 软约束 / 硬约束:区分 “必须满足” 和 “尽量满足” 的调度规则。
  • Pod 级亲和性:基于已有 Pod 的标签进行调度(如 “和 A Pod 同节点” 或 “不和 B Pod 同节点”)。
  • 拓扑域控制:按节点、机房等拓扑维度进行调度。

2.2 核心类型与对比

类型硬约束(Required)软约束(Preferred)
节点亲和性(NodeAffinity)必须调度到匹配节点,否则 Pod Pending尽量调度到匹配节点,无匹配则忽略
Pod 亲和性(PodAffinity)必须和指定 Pod 同拓扑域,否则 Pod Pending尽量和指定 Pod 同拓扑域,无匹配则忽略
Pod 反亲和性(PodAntiAffinity)必须不和指定 Pod 同拓扑域,否则 Pod Pending尽量不和指定 Pod 同拓扑域,无匹配则忽略

2.3 硬反亲和性单节点实战

在单节点环境下,硬反亲和性的行为需要特别注意:

affinity:                 
  podAntiAffinity:        
    requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
            - key: app
              operator: In
              values:
                - pod-2
        topologyKey: kubernetes.io/hostname

⚠️ 单节点特性:如果集群中存在 app=pod-2 的 Pod,当前 Pod 会永久 Pending(无其他节点可选);若不存在则正常调度。


🛠️ 三、常见问题与排障指南

3.1 YAML 语法错误

现象:部署时提示 error converting YAML to JSON: yaml: line X: did not find expected '-'原因:列表项缺少 - 前缀或缩进错误。解决

  1. 确保 requiredDuringSchedulingIgnoredDuringExecution 等列表项前有 -
  2. 统一使用 2 个空格缩进,避免 Tab 与空格混用。

3.2 自定义调度器无法绑定 Pod

现象:调度器日志显示 curl 失败,Pod 一直处于 Pending原因:未启动 kubectl proxy 或 API 权限不足。解决

# 启动 API 代理
kubectl proxy --port=8001 &

# 验证 API 连通性
curl http://localhost:8001/version

3.3 亲和性规则不生效

现象:Pod 未按亲和性规则调度。原因:标签不匹配、拓扑域错误或硬约束无法满足。解决

# 检查节点标签
kubectl get nodes --show-labels

# 检查 Pod 标签
kubectl get pods --show-labels

# 查看调度事件
kubectl describe pod <pod-name> | grep "Events" -A 10

📊 四、生产环境最佳实践

4.1 调度器选型

  • 默认调度器:满足 90% 以上场景,无需额外维护。
  • 自定义调度器:仅在特殊需求下使用(如离线环境、固定节点调度)。
  • 第三方调度器:如 Volcano、Scheduler-Plugins,适合复杂业务场景。

4.2 亲和性配置原则

  1. 优先软约束:除非必须,否则用 preferredDuringSchedulingIgnoredDuringExecution 避免 Pod Pending。
  2. 拓扑域选择:多可用区场景下用 topologyKey: topology.kubernetes.io/zone 实现跨机房容灾。
  3. 标签规范:统一标签命名(如 approle),确保亲和性规则可维护。

4.3 离线环境适配

  • 镜像本地化:将所有镜像提前导入节点,避免拉取失败。
  • 工具无依赖:用 kubectlawkshuf 替代 jq 等第三方工具。
  • 单节点验证:在单节点环境下测试硬约束,确保极端场景下的稳定性。

🎓 五、总结与思考

通过本次离线单节点环境的实战,我们不仅掌握了调度器与亲和性的核心原理,更深入理解了 Kubernetes 调度系统的灵活性与复杂性。在实际生产中,我们需要:

  1. 平衡灵活性与稳定性:自定义调度器带来灵活性,但增加了维护成本;默认调度器稳定,但功能有限。
  2. 精细化调度控制:亲和性是一把双刃剑,合理使用可以提升资源利用率,滥用则会导致调度失败。
  3. 离线场景预案:在无网络、无依赖的环境下,原生工具与 Shell 脚本是最可靠的选择。

掌握调度器与亲和性,不仅能让你在 Kubernetes 运维中更游刃有余,更能为大规模集群的资源优化与容灾设计打下坚实基础。

更多推荐