Langchain系列文章目录

01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南
02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖
03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南
04-玩转 LangChain:从文档加载到高效问答系统构建的全程实战
05-玩转 LangChain:深度评估问答系统的三种高效方法(示例生成、手动评估与LLM辅助评估)
06-从 0 到 1 掌握 LangChain Agents:自定义工具 + LLM 打造智能工作流!
07-【深度解析】从GPT-1到GPT-4:ChatGPT背后的核心原理全揭秘
08-【万字长文】MCP深度解析:打通AI与世界的“USB-C”,模型上下文协议原理、实践与未来

Python系列文章目录

PyTorch系列文章目录

机器学习系列文章目录

深度学习系列文章目录

Java系列文章目录

JavaScript系列文章目录

Python系列文章目录

Go语言系列文章目录

Docker系列文章目录

01-【Docker-Day 1】告别部署噩梦:为什么说 Docker 是每个开发者的必备技能?
02-【Docker-Day 2】从零开始:手把手教你在 Windows、macOS 和 Linux 上安装 Docker
03-【Docker-Day 3】深入浅出:彻底搞懂 Docker 的三大核心基石——镜像、容器与仓库
04-【Docker-Day 4】从创建到删除:一文精通 Docker 容器核心操作命令
05-【Docker-Day 5】玩转 Docker 镜像:search, pull, tag, rmi 四大金刚命令详解
06-【Docker-Day 6】从零到一:精通 Dockerfile 核心指令 (FROM, WORKDIR, COPY, RUN)
07-【Docker-Day 7】揭秘 Dockerfile 启动指令:CMD、ENTRYPOINT、ENV、ARG 与 EXPOSE 详解
08-【Docker-Day 8】高手进阶:构建更小、更快、更安全的 Docker 镜像
09-【Docker-Day 9】实战终极指南:手把手教你将 Node.js 应用容器化
10-【Docker-Day 10】容器的“持久化”记忆:深入解析 Docker 数据卷 (Volume)
11-【Docker-Day 11】Docker 绑定挂载 (Bind Mount) 实战:本地代码如何与容器实时同步?
12-【Docker-Day 12】揭秘容器网络:深入理解 Docker Bridge 模式与端口映射
13-【Docker-Day 13】超越默认Bridge:精通Docker Host、None与自定义网络模式
14-【Docker-Day 14】Docker Compose深度解析
15-【Docker-Day 15】一键部署 WordPress!Docker Compose 实战终极指南
16-【Docker-Day 16】告别单机时代:为什么 Docker Compose 不够用,而你需要 Kubernetes?
17-【Docker-Day 17】K8s 架构全解析:深入理解 Kubernetes 的大脑 (Master) 与四肢 (Node)
18-【Docker-Day 18】告别选择困难症:一文掌握 Minikube、kind、k3d,轻松搭建你的第一个 K8s 集群
19-【Docker-Day 19】万物皆 YAML:掌握 Kubernetes 声明式 API 的艺术
20-【Docker-Day 20】揭秘 Kubernetes 的原子单位:深入理解 Pod
21-【Docker-Day 21】Pod的守护神:ReplicaSet与ReplicationController,轻松实现应用高可用
22-【K8s-Day 22】深入解析 Kubernetes Deployment:现代应用部署的基石与滚动更新的艺术
23-【K8s-Day 23】从 Pod 的“失联”到 Service 的“牵线”:深入理解 ClusterIP 核心原理
24-【Docker-Day 24】K8s网络解密:深入NodePort与LoadBalancer,让你的应用走出集群
25-【Docker-Day 25】深入理解 Kubernetes Namespace:实现多租户与环境隔离的利器
26-【Docker-Day 26】K8s实战演练:从零开始部署一个完整的前后端分离Web应用
27-【K8s-Day 27】应用的“体检医生”:深入解析 Kubernetes 健康检查探针 (Probe)
28-【Docker-Day 28】K8s 核心配置管理:解密 ConfigMap,告别硬编码!
29-【Docker-Day 29】K8s 安全第一课:揭秘敏感信息管理器 Secret
30-【Docker-Day 30】解密 K8s 的“硬盘”:深入理解 PersistentVolume (PV) 与 PersistentVolumeClaim (PVC)
31-【Docker-Day 31】告别手动创建 PV!一文搞懂 Kubernetes StorageClass 工作原理与实战
32-【K8s-Day 32】StatefulSet 深度解析:为你的数据库和有状态应用保驾护航
33-【Docker-Day 33】掌握 K8s 任务调度:DaemonSet、Job、CronJob 实战指南
34-【Docker-Day 34】Kubernetes Ingress 详解:从小白到精通的 K8s 流量路由指南
35-【Docker-Day 35】实战部署 Nginx Ingress Controller:集群流量入口的终极指南
36-【Docker-Day 36】K8s网络解密:CNI接口如何为Pod分配IP地址?
37-【Docker-Day 37】K8s 网络“防火墙”:NetworkPolicy 深度解析与实战
38-【Docker-Day 38】Kubernetes 核心调度:深入解析资源请求 (Requests) 与限制 (Limits) 的奥秘
39-【Docker-Day 39】揭秘 Kubernetes 高级调度:从 nodeSelector 到亲和性与污点的实战指南



摘要

在上一篇文章(Day 38)中,我们学习了 Kubernetes 调度的基础——如何通过 requestslimits 声明资源需求,让调度器为 Pod 找到一个“够住”的节点。然而,在复杂的生产环境中,仅仅“够住”是远远不够的。我们常常需要更精细化的控制:如何让需要 GPU 的 Pod 只在配备 GPU 的节点上运行?如何让前端和后端 Pod 部署在同一个可用区以降低延迟?如何将特定节点预留给关键任务,防止其他应用占用?本文将深入探讨 Kubernetes 的高级调度策略,包括 nodeSelector、亲和性与反亲和性(Affinity & Anti-Affinity)、以及污点与容忍(Taints & Tolerations)。掌握这些“调度艺术”,你将能够像指挥家一样,精准地控制每一个 Pod 的落点,构建更加健壮、高效和高可用的系统。

一、回顾与前言:为何需要高级调度?

Kubernetes 调度器(kube-scheduler)的核心职责是为新创建的 Pod 寻找到一个最合适的 Node。在默认情况下,它的决策过程主要基于两点:

  1. 节点过滤(Filtering):筛选出所有满足 Pod 资源请求(requests)的节点。
  2. 节点打分(Scoring):对通过过滤的节点进行打分,选择分数最高的节点。

这个过程虽然高效,但它忽略了应用的业务特性和高可用性需求。例如:

  • 硬件依赖性:某个机器学习任务需要运行在具有 NVIDIA Tesla V100 GPU 的节点上。
  • 性能优化:一个 Web 应用的前端 Pod 最好和它的缓存 Pod 部署在同一个节点上,以利用本地网络通信,减少延迟。
  • 高可用性:为了防止单点故障,一个服务的多个副本应该分散到不同的物理主机或可用区。
  • 资源隔离:我们希望将某些节点专门用于数据库或监控服务,不希望其他普通应用被调度上去。

为了解决这些问题,Kubernetes 提供了丰富的“干预”手段,允许我们通过声明式配置来影响调度器的决策。这些手段就是我们今天要探讨的高级调度策略。

二、定向调度:最简单的节点选择

在深入复杂策略之前,我们先从最简单、最直接的节点选择方法开始。

2.1 nodeName:简单粗暴的“指派”

nodeName 是 Pod spec 中的一个字段,一旦设置,它将完全绕过调度器,直接将 Pod 指派到指定名称的 Node 上。

(1) 使用方法
apiVersion: v1
kind: Pod
metadata:
  name: nginx-on-specific-node
spec:
  # 直接指定节点名称
  nodeName: worker-node-01 
  containers:
  - name: nginx
    image: nginx
(2) 优缺点分析
  • 优点:极其简单、直接。
  • 缺点
    • 僵化:完全写死,失去了灵活性。
    • 绕过调度器:调度器的资源检查、健康检查等流程都被跳过。
    • 无故障转移:如果 worker-node-01 宕机或资源不足,Pod 将永远处于 Pending 状态,无法被重新调度。

结论nodeName 极少在生产环境的动态工作负载(如 Deployment)中使用,通常只用于某些特定的、静态的系统级 Pod 或调试场景。

2.2 nodeSelector:基于标签的“筛选”

nodeSelector 是一种比 nodeName 灵活得多的方式。它允许 Pod 通过匹配 Node 的标签(Label)来选择目标节点。

(1) 工作流程
  1. 为 Node 打标签:管理员首先使用 kubectl label 命令为 Node 添加自定义标签。
  2. 在 Pod 中指定 nodeSelector:在 Pod 的 spec 中定义 nodeSelector 字段,列出期望的节点标签。
  3. 调度器匹配:调度器在过滤阶段,只会考虑那些拥有所有指定标签的 Node。
(2) 实战演练

假设我们有一个节点配备了高速 SSD 硬盘,我们希望将一个对 I/O 敏感的应用部署上去。

步骤 1:为节点打上标签

# 假设我们的节点名为 minikube
# 为 minikube 节点添加一个标签 disktype=ssd
kubectl label nodes minikube disktype=ssd

# 查看标签
kubectl get nodes minikube --show-labels

步骤 2:创建使用 nodeSelector 的 Pod

apiVersion: v1
kind: Pod
metadata:
  name: data-intensive-app
spec:
  # 调度器只会将此 Pod 调度到具有 disktype=ssd 标签的节点上
  nodeSelector:
    disktype: ssd
  containers:
  - name: app
    image: busybox
    command: ["sh", "-c", "sleep 3600"]

步骤 3:验证调度结果

# 创建 Pod
kubectl apply -f data-intensive-app.yaml

# 查看 Pod 所在的节点,-o wide 参数可以显示更多信息
kubectl get pod data-intensive-app -o wide
# NAME                 READY   STATUS    RESTARTS   AGE   IP           NODE       NOMINATED NODE   READINESS GATES
# data-intensive-app   1/1     Running   0          15s   172.17.0.2   minikube   <none>           <none>

可以看到,Pod 成功地被调度到了我们带有 disktype=ssd 标签的 minikube 节点上。

(3) 局限性

nodeSelector 只能进行简单的“与”(AND)匹配,即节点必须同时满足 nodeSelector 中列出的所有标签。它无法实现“或”(OR)逻辑(如“运行在 A 区或 B 区的节点上”),也无法实现“软偏好”(如“优先运行在 SSD 节点上,如果没有,普通节点也行”)。为了满足这些更复杂的需求,节点亲和性应运而生。

三、智能调度:亲和性与反亲和性

亲和性(Affinity)和反亲和性(Anti-Affinity)是 nodeSelector 的全面升级版,提供了更强大、更灵活的调度控制能力。它们主要分为两类:

  • 节点亲和性(Node Affinity):处理 Pod 与 Node 之间的关系,是 nodeSelector 的替代品。
  • Pod 亲和性/反亲和性(Pod Affinity/Anti-Affinity):处理 Pod 与其他 Pod 之间的关系。

3.1 节点亲和性 (Node Affinity)

节点亲和性允许你定义更复杂的节点选择规则。它有两种类型:

  • requiredDuringSchedulingIgnoredDuringExecution硬亲和性。调度时必须满足规则,否则 Pod 无法被调度。IgnoredDuringExecution 意味着 Pod 启动后,即使节点的标签发生变化,不再满足亲和性规则,Pod 也会继续在该节点上运行。
  • preferredDuringSchedulingIgnoredDuringExecution软亲和性。调度器会优先选择满足规则的节点,但如果没有满足条件的节点,Pod 依然可以被调度到其他节点上。每个偏好项可以设置一个 weight(权重,1-100),调度器会选择得分最高的节点。
(1) 硬亲和性 (required...) 示例

假设我们需要 Pod 必须运行在 zone-azone-b 的节点上,并且该节点必须是 amd64 架构。这是 nodeSelector 无法实现的“OR”逻辑。

apiVersion: v1
kind: Pod
metadata:
  name: with-hard-node-affinity
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: topology.kubernetes.io/zone
            operator: In  # 操作符 In 表示 key 的值在 value 列表中即可
            values:
            - zone-a
            - zone-b
        - matchExpressions:
          - key: kubernetes.io/arch
            operator: In
            values:
            - amd64
  containers:
  - name: app
    image: busybox
    command: ["sh", "-c", "sleep 3600"]

可用操作符In, NotIn, Exists (标签存在即可), DoesNotExist, Gt (大于), Lt (小于)。

(2) 软亲和性 (preferred...) 示例

我们希望应用优先部署在带有 disktype=ssd 标签的节点上,如果没有,也可以接受其他节点。

apiVersion: v1
kind: Pod
metadata:
  name: with-soft-node-affinity
spec:
  affinity:
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100  # 权重,分数越高越优先
        preference:
          matchExpressions:
          - key: disktype
            operator: In
            values:
            - ssd
  containers:
  - name: app
    image: busybox
    command: ["sh", "-c", "sleep 3600"]

你可以同时定义多个 preferred 规则,调度器会累加所有满足条件的规则的权重,选择总分最高的节点。

3.2 Pod 亲和性与反亲和性

这类亲和性规则基于节点上已运行的 Pod 的标签来决定新 Pod 的调度位置,而不是基于节点本身的标签。

核心概念:topologyKey
topologyKey 是实现 Pod 亲和性的关键。它定义了一个“域”或“拓扑边界”,例如:

  • kubernetes.io/hostname:将“域”限定为单个 Node。
  • topology.kubernetes.io/zone:将“域”限定为同一个可用区。
  • topology.kubernetes.io/region:将“域”限定为同一个区域。
(1) Pod 亲和性 (podAffinity)

场景:将 Web 前端 Pod (app=frontend) 与其依赖的 API 后端 Pod (app=backend) 部署在同一个可用区,以降低网络延迟。

apiVersion: v1
kind: Pod
metadata:
  name: frontend-pod
spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: app
            operator: In
            values:
            - backend
        # 拓扑域:可用区
        topologyKey: topology.kubernetes.io/zone
  containers:
  - name: nginx
    image: nginx

解释:调度器会寻找一个节点,该节点所在的可用区 (topologyKey: topology.kubernetes.io/zone) 中,已经存在一个带有 app=backend 标签的 Pod。

(2) Pod 反亲和性 (podAntiAffinity)

场景:为了实现高可用,一个 Redis 服务(app=redis)的多个副本应该分散在不同的物理节点上。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-ha
spec:
  replicas: 3
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - redis
            # 拓扑域:节点主机名
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: redis
        image: redis

解释:当调度一个新的 Redis Pod 时,调度器会排除那些已经运行了 app=redis Pod 的节点 (topologyKey: "kubernetes.io/hostname")。这确保了 3 个副本会被调度到 3 个不同的节点上(如果集群节点数足够)。

注意:Pod 亲和性与反亲和性同样有 required...(硬性)和 preferred...(软性)两种形式,用法与节点亲和性类似。

四、反向调度:污点与容忍

如果说亲和性是 Pod 对节点的“吸引力”,那么污点(Taints)就是节点对 Pod 的“排斥力”。

  • 污点 (Taint):应用于 Node,使得默认情况下没有 Pod 能被调度到这个 Node 上。
  • 容忍 (Toleration):应用于 Pod,表示该 Pod “容忍”节点的某个污点,从而可以被调度到这个带有污点的 Node 上。

这个机制就像给节点贴上“禁止入内”的牌子,而只有持有“特殊通行证”(容忍)的 Pod 才能进入。

graph TD
    subgraph Kubernetes Scheduler
        P1(Pod 1 <br> No Toleration)
        P2(Pod 2 <br> tolerates=gpu:NoSchedule)
    end

    subgraph Nodes
        NodeA[Node A <br> (No Taints)]
        NodeB[Node B <br> <strong>Taint: gpu=true:NoSchedule</strong>]
    end

    P1 --✔ Can Schedule--> NodeA
    P1 --❌ Cannot Schedule (Repelled by Taint)--> NodeB
    P2 --✔ Can Schedule--> NodeA
    P2 --✔ Can Schedule (Matches Toleration)--> NodeB

    style NodeB fill:#f9d7d7,stroke:#c00,stroke-width:2px

4.1 污点的三种效果 (Effect)

在给节点添加污点时,需要指定一个效果:

  1. NoSchedule(常用) 新的 Pod 不会被调度到该节点上,但节点上已经存在的 Pod 不受影响。
  2. PreferNoScheduleNoSchedule 的软性版本。调度器会尽量避免将 Pod 调度到该节点,但如果没有其他可用节点,仍然可以调度上来。
  3. NoExecute(影响大) 不仅新的 Pod 不会被调度上来,节点上已经存在的、不容忍该污点的 Pod 也会被驱逐(Evicted)。

4.2 如何使用污点与容忍

(1) 管理节点的污点
# 1. 给 node1 添加一个污点,key=special, value=true, effect=NoSchedule
kubectl taint nodes node1 special=true:NoSchedule

# 2. 查看节点的污点
kubectl describe node node1 | grep Taints

# 3. 移除污点(在 key 和 effect 后加上 -)
kubectl taint nodes node1 special:NoSchedule-
(2) 为 Pod 添加容忍

在 Pod 的 spec 中添加 tolerations 字段。

apiVersion: v1
kind: Pod
metadata:
  name: special-task-pod
spec:
  containers:
  - name: app
    image: busybox
    command: ["sh", "-c", "sleep 3600"]
  # 这个 Pod 可以容忍 key=special, effect=NoSchedule 的污点
  tolerations:
  - key: "special"
    operator: "Exists" # Exists 操作符表示只要 key 存在即可,无论 value 是什么
    effect: "NoSchedule"

这个 Pod 现在就可以被调度到我们之前打上 special=true:NoSchedule 污点的 node1 上了。

4.3 常见应用场景

(1) 专用节点

这是最常见的用法。例如,为集群中的 GPU 节点打上污点,确保只有需要 GPU 的 Pod (带有相应容忍) 才能使用这些昂贵的资源。

# 1. 标记 GPU 节点
kubectl taint nodes gpu-node-1 gpu=true:NoSchedule

# 2. 在需要 GPU 的 Pod 中添加容忍
tolerations:
- key: "gpu"
  operator: "Equal"
  value: "true"
  effect: "NoSchedule"
(2) 基于节点状况的驱逐

Kubernetes 自身就广泛使用污点和容忍来处理节点故障。例如:

  • 当一个节点失联时,kube-controller-manager 会为该节点添加 node.kubernetes.io/unreachable 污点,并带有 NoExecute 效果。
  • 当一个节点磁盘空间不足时,会被添加 node.kubernetes.io/disk-pressure 污点,效果也是 NoExecute

默认情况下,Deployment 创建的 Pod 会自动带上对这些基础污点的容忍,并设置一个 tolerationSeconds,表示在驱逐前等待的时间。

(3) Master 节点的隔离

你是否想过为什么我们部署的应用不会跑到 Master 节点上?原因就是 Master 节点在初始化时就被打上了污点,例如:node-role.kubernetes.io/master:NoSchedulenode-role.kubernetes.io/control-plane:NoSchedule。这可以防止业务 Pod 干扰核心组件的运行。

五、策略组合与最佳实践

这些高级调度策略不是孤立的,而是可以组合使用,以实现更复杂的调度逻辑。

场景示例:我们有一个 AI 训练集群,其中包含两种 GPU 节点(V100 和 A100)。我们希望:

  1. GPU 节点只用于 AI 任务(使用污点和容忍)。
  2. 默认的 AI 任务优先使用 A100 节点(使用软性节点亲和性)。
  3. 一个高优先级的 AI 任务必须使用 V100 节点(使用硬性节点亲和性)。
  4. 为了高可用,同一个训练任务的多个 Worker 实例必须分散在不同机器上(使用 Pod 反亲和性)。

通过组合这些策略,我们可以精确地满足所有需求。

策略对比总结表

特性 nodeSelector 节点亲和性 (Node Affinity) Pod 亲和性/反亲和性 (Pod Affinity/Anti-Affinity) 污点与容忍 (Taints & Tolerations)
作用对象 Pod -> Node Pod -> Node Pod -> Pod Node -> Pod
作用方式 Pod 主动选择符合标签的节点。 Pod 主动选择符合规则的节点。 Pod 主动选择与其它 Pod 亲近或远离。 节点被动排斥不符合容忍的 Pod。
规则类型 硬性要求 (必须满足所有标签) 硬性要求 (required) 和软性偏好 (preferred) 硬性要求 (required) 和软性偏好 (preferred) 硬性 (NoSchedule, NoExecute) 和软性 (PreferNoSchedule)
规则复杂度 简单键值对 (AND 关系) 复杂的表达式 (In, NotIn, Exists, Gt, Lt 等) 复杂的表达式,基于 Pod 标签和拓扑域 简单的键值+Effect
核心场景 简单的节点定向,如选择特定环境的节点。 复杂的节点选择,如选择特定硬件或区域的节点。 应用协同部署(性能优化),应用分散部署(高可用)。 为节点设置专用性(如 GPU 节点),节点维护与隔离。

六、总结

本文深入探讨了 Kubernetes 的高级调度策略,它们是优化应用部署、实现高可用和提升资源利用率的关键工具。

  • nodeSelectornodeName:提供了最基础的节点定向能力,nodeSelector 基于标签,是简单场景下的快速选择。
  • 节点亲和性 (Node Affinity):是 nodeSelector 的超集,通过 requiredpreferred 规则,提供了强大的、富有弹性的节点选择逻辑,能够处理复杂的“与/或”关系和偏好设置。
  • Pod 亲和性与反亲和性 (Pod Affinity/Anti-Affinity):将调度决策从节点属性扩展到了 Pod 间的关系,通过 topologyKey 定义作用域,是实现应用协同部署(降低延迟)和分散部署(提高可用性)的核心武器。
  • 污点与容忍 (Taints & Tolerations):提供了一种反向的调度控制机制,由节点来“排斥”Pod,非常适合用于创建专用节点池(如 GPU 节点、监控节点)和处理节点异常状态。

从简单的资源请求到复杂的亲和性与污点组合,Kubernetes 的调度系统为我们提供了从粗到细、从吸引到排斥的全方位控制能力。熟练运用这些“调度艺术”,是每一位 Kubernetes 工程师从入门走向精通的必经之路。


Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐