Pod调度与节点选择

Kubernetes 调度器(kube-scheduler)是控制平面的核心组件,负责将未调度的 Pod 分配到合适的节点上运行。调度过程并非随机——它经过预选 → 优选 → 终选三个阶段,确保 Pod 既“能跑”又“跑得好”。本章从调度流程入手,逐步讲解 NodeSelector、亲和性/反亲和性、污点与容忍等调度策略。


1. Pod 创建流程与调度机制

Pod 创建到运行的完整流程

当用户通过 kubectl apply 或 API 创建一个 Pod 时,整个生命周期经历以下阶段:

textkubectl apply → API Server 收到请求
    │
    ├── 1. 认证(Authentication):确认请求者身份
    ├── 2. 授权(Authorization):RBAC 检查是否有创建 Pod 的权限
    ├── 3. 准入控制(Admission Controllers):如 NamespaceExists、LimitRanger、PodSecurityPolicy 等
    │
    ▼
API Server 将 Pod 对象写入 etcd(状态: Pending)
    │
    ▼
kube-scheduler Watch 到未调度的 Pod(spec.nodeName 为空)
    │
    ├── 预选(Filtering):过滤出所有可运行该 Pod 的节点
    ├── 优选(Scoring):对可行节点打分排序
    ├── 终选(Binding):将 Pod 绑定到得分最高的节点
    │
    ▼
API Server 更新 Pod 的 spec.nodeName → 写入 etcd
    │
    ▼
目标节点 kubelet Watch 到有 Pod 分配给自己
    │
    ├── 拉取镜像 → 创建容器 → 启动应用
    │
    ▼
Pod 状态变为 Running
### 调度器核心流程:预选 → 优选 → 终选

kube-scheduler 的调度决策分为三个阶段:

预选(Filtering / Predicates)

遍历所有节点,硬性排除不符合条件的节点。任何一个预选条件不满足,该节点就被直接淘汰。

常见的预选策略:

预选策略 说明
PodFitsResources 节点剩余 CPU/内存/GPU 是否满足 Pod 的 resources.requests
PodFitsHostPorts Pod 需要的 HostPort 是否已被占用
HostName 如果 Pod 指定了 spec.nodeName,只保留该节点
NodeSelectorFit 节点标签是否匹配 Pod 的 nodeSelector
NodeAffinity 节点是否满足 requiredDuringSchedulingIgnoredDuringExecution 硬亲和规则
TaintToleration 节点上的污点是否被 Pod 的容忍匹配
PodToleratesNodeTaints 同上,检查 Pod 能否容忍节点污点
CheckNodeUnschedulable 节点是否被标记为 unschedulablekubectl cordon
PodFitsVolume Pod 声明的 PV/StorageClass 能否在该节点挂载

预选结束后,剩下的节点称为可行节点(Feasible Nodes)

优选(Scoring / Priorities)

对所有可行节点打分排序,选出最"合适"的节点。每个优选策略独立打分,最终加权求和。

常见优选策略:

优选策略 权重 说明
NodeResourcesFit 1 节点资源越富裕得分越高(倾向分配到资源充足的节点,或最均衡的节点)
ImageLocality 1 节点上已有 Pod 需要的镜像越多,得分越高
InterPodAffinity 1 节点是否满足 Pod 的 preferredDuringScheduling 软亲和/反亲和规则
NodeAffinity 1 节点是否满足 preferredDuringSchedulingIgnoredDuringExecution 软亲和规则
TaintToleration 1 节点有 PreferNoSchedule 污点但 Pod 无容忍时扣分
SelectorSpreadPriority 1 尽量将同一 RC/RS/Deployment 的 Pod 分散到不同节点
EvenPodsSpread 1 满足 topologySpreadConstraints 的均匀分布约束

终选(Binding)

得分最高的节点成为目标节点。如果多个节点得分相同,则随机选一个(Round Robin)。

终选步骤:

  1. 调度器向 API Server 发送 Binding 请求,将 Pod 的 spec.nodeName 设为目标节点名
  2. API Server 更新 etcd 中的 Pod 对象
  3. 目标节点的 kubelet Watch 到变化,开始拉取镜像、创建容器

注意:如果调度失败(没有可行节点,或优选阶段所有节点得分太低),Pod 保持在 Pending 状态。调度器会按照 backoff 策略定期重试(podInitialBackoffSeconds=1spodMaxBackoffSeconds=10s)。

Scheduling Framework(v1.18+)

从 v1.18 开始,kube-scheduler 引入了 Scheduling Framework,将调度流程拆解为可扩展的插件扩展点:

textQueueSort → PreFilter → Filter → PostFilter → PreScore → Score → NormalizeScore → Reserve → Permit → PreBind → Bind → PostBind
扩展点 作用
QueueSort 决定 Pod 从调度队列取出的优先级顺序
PreFilter 预处理 Pod 信息,为 Filter 阶段做准备
Filter 对应"预选",排除不满足条件的节点
PostFilter Filter 后若无可行节点,执行补救逻辑(如抢占)
PreScore 为 Score 阶段做预处理
Score 对应"优选",对可行节点打分
Reserve 预留资源(防止绑定竞争)
Permit 批准或拒绝绑定(可延迟等待)
Bind 对应"终选",将 Pod 绑定到节点
PostBind 绑定成功后的回调清理

开发者可以通过编写自定义插件注册到上述任何扩展点,实现自定义调度逻辑,无需修改调度器源码。


2. 一、NodeSelector

1.1 介绍

nodeSelector 是最简单的节点选择机制——在 Pod 的 spec 中指定一组键值对,Kubernetes 只会将 Pod 调度到标签完全匹配的节点上。

它本质上是一个硬约束:如果集群中没有节点满足 nodeSelector 中所有键值对,Pod 会一直处于 Pending 状态。

适用场景

  • Pod 需要特定硬件(如 GPU 节点、SSD 磁盘节点)
  • Pod 需要运行在指定区域/机房
  • 简单的环境区分(生产环境 vs 测试环境)

局限

  • 只支持精确匹配(label 的 key 和 value 必须完全一致),不支持 In、NotIn、Exists 等运算符
  • 是硬约束,没有"尽量满足"的软策略
  • 如果需要更复杂的调度逻辑,应使用 nodeAffinity

1.2 实践

给节点打标签

bash# 给 node-01 打上磁盘类型标签
kubectl label nodes node-01 disktype=ssd

# 给 node-02 打上 GPU 标签
kubectl label nodes node-02 accelerator=nvidia-tesla-p100

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

Pod 中使用 nodeSelector

yamlapiVersion: v1
kind: Pod
metadata:
  name: nginx-ssd
spec:
  containers:
  - name: nginx
    image: nginx:1.25
  nodeSelector:
    disktype: ssd   # Pod 只会被调度到有 disktype=ssd 标签的节点

多标签组合

yamlapiVersion: v1
kind: Pod
metadata:
  name: gpu-app
spec:
  containers:
  - name: cuda-test
    image: "registry.k8s.io/cuda-vector-add:v0.1"
    resources:
      limits:
        nvidia.com/gpu: 1
  nodeSelector:
    accelerator: nvidia-tesla-p100  # 必须 GPU 节点
    disktype: ssd                    # 且必须是 SSD

nodeSelector 中的所有键值对都必须匹配,是 AND 逻辑。Pod 只会调度到同时拥有 accelerator=nvidia-tesla-p100 disktype=ssd 标签的节点。

验证调度结果

bash# 查看 Pod 被调度到了哪个节点
kubectl get pod nginx-ssd -o wide

# 查看调度事件
kubectl describe pod nginx-ssd | grep -A5 Events

如果集群中没有匹配标签的节点,事件会显示:

text0/3 nodes are available: 3 node(s) didn't match node selector.

3. 二、亲和性与反亲和性

2.1 总体介绍

亲和性(Affinity)和反亲和性(Anti-Affinity)是 nodeSelector 的增强版,提供了更丰富的选择语义

维度 nodeSelector Affinity/Anti-Affinity
匹配运算符 仅精确匹配 In、NotIn、Exists、DoesNotExist、Gt、Lt
策略类型 仅硬约束 硬约束(required)+ 软约束(preferred)
选择维度 仅节点标签 节点维度(nodeAffinity)+ Pod维度(podAffinity)
拓扑域 不支持 支持 topologyKey

亲和性分两个维度:

  • nodeAffinity:基于节点标签选择节点(类似增强版 nodeSelector)
  • podAffinity / podAntiAffinity:基于已运行 Pod 的标签选择节点(让 Pod "亲近"或"远离"特定 Pod)

策略类型对比

类型 策略字段 行为
硬策略 requiredDuringSchedulingIgnoredDuringExecution 调度时必须满足,否则 Pod 不会被调度。运行后如果条件不再满足,不会驱逐 Pod(IgnoredDuringExecution)
软策略 preferredDuringSchedulingIgnoredDuringExecution 调度时尽量满足,找不到满足条件的节点也能调度。权重 1-100,影响优选阶段的打分

IgnoredDuringExecution 的含义:这些规则只在调度时生效,一旦 Pod 已经运行在节点上,即使后续节点标签变化导致条件不再满足,也不会驱逐 Pod。requiredDuringSchedulingIgnoredDuringExecution 中的 "IgnoredDuringExecution" 就是强调这一点。


2.2 节点维度——nodeAffinity

nodeAffinity 让 Pod 基于节点本身的标签来表达调度偏好,是 nodeSelector 的超集。

2.2.1 节点亲和(硬策略 + 软策略)

硬策略:requiredDuringSchedulingIgnoredDuringExecution

Pod 必须调度到满足条件的节点,否则 Pending:

yamlapiVersion: v1
kind: Pod
metadata:
  name: affinity-required
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: zone          # 节点标签的 key
            operator: In       # 运算符
            values:            # 标签值列表
            - us-east-1a
            - us-east-1b
  containers:
  - name: nginx
    image: nginx:1.25

软策略:preferredDuringSchedulingIgnoredDuringExecution

Pod 倾向调度到满足条件的节点,但如果没有满足条件的节点也能调度:

yamlapiVersion: v1
kind: Pod
metadata:
  name: affinity-preferred
spec:
  affinity:
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 80             # 权重 1-100,值越大倾向越强
        preference:
          matchExpressions:
          - key: zone
            operator: In
            values:
            - us-east-1a       # 倾向调度到 us-east-1a 区域
      - weight: 20
        preference:
          matchExpressions:
          - key: disktype
            operator: In
            values:
            - ssd              # 倾向 SSD 节点,但权重较低
  containers:
  - name: nginx
    image: nginx:1.25

运算符说明

运算符 含义 示例
In 标签值在给定列表中 zone In [us-east-1a, us-east-1b] → zone 为这两个值之一
NotIn 标签值不在给定列表中 zone NotIn [us-east-1c] → zone 不是 us-east-1c
Exists 标签 key 存在(不管值) gpu Exists → 节点有 gpu 这个标签
DoesNotExist 标签 key 不存在 gpu DoesNotExist → 节点没有 gpu 标签
Gt 标签值大于给定数字 storage Gt 500 → storage 标签值 > 500
Lt 标签值小于给定数字 storage Lt 100 → storage 标签值 < 100

GtLt 仅适用于数值型标签值,且需要开启 TaintTolerationComparisonOperators 特性门控。

硬策略 + 软策略组合使用

yamlapiVersion: v1
kind: Pod
metadata:
  name: affinity-combo
spec:
  affinity:
    nodeAffinity:
      # 硬策略:必须跑在 us-east 区域
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: zone
            operator: In
            values:
            - us-east-1a
            - us-east-1b
      # 软策略:倾向 SSD 节点
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 50
        preference:
          matchExpressions:
          - key: disktype
            operator: In
            values:
            - ssd
  containers:
  - name: nginx
    image: nginx:1.25

调度逻辑:先在 us-east-1a 和 us-east-1b 的节点中筛选(硬策略),然后在这些节点中对 SSD 节点加分(软策略)。

2.2.2 节点反亲和

节点反亲和就是用 NotInDoesNotExist 运算符表达"不要调度到某类节点":

yamlapiVersion: v1
kind: Pod
metadata:
  name: avoid-gpu-node
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: accelerator
            operator: DoesNotExist   # 不要调度到有 GPU 标签的节点
  containers:
  - name: nginx
    image: nginx:1.25

软策略反亲和——倾向避开某类节点但不是硬性要求:

yamlpreferredDuringSchedulingIgnoredDuringExecution:
- weight: 50
  preference:
    matchExpressions:
    - key: zone
      operator: NotIn
      values:
      - us-east-1c    # 倾向避开 us-east-1c,但实在没节点也能去

nodeAffinity 反亲和 vs nodeSelector:nodeSelector 只能"选择"节点,无法表达"排除"语义。nodeAffinity 用 NotIn/DoesNotExist 实现排除,这是 nodeSelector 无法做到的。


2.3 Pod维度——podAffinity

podAffinity 基于已运行 Pod 的标签来决定新 Pod 的调度位置,实现"让相关的 Pod 跑在一起"或"让冲突的 Pod 跑开"。

2.3.1 Pod 亲和

##### (1)Pod 亲和调度——硬策略

让新 Pod 必须调度到与指定 Pod 相同拓扑域的节点:

yamlapiVersion: v1
kind: Pod
metadata:
  name: web-server
  labels:
    app: web
spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: app
            operator: In
            values:
            - cache        # 查找标签 app=cache 的 Pod
        topologyKey: topology.kubernetes.io/zone  # 在同一 zone 内亲和
  containers:
  - name: nginx
    image: nginx:1.25

调度逻辑:

  1. 找到集群中所有标签含 app=cache 的 Pod
  2. 看这些 Pod 所在节点的 topology.kubernetes.io/zone 标签值(如 us-east-1a
  3. 新 Pod 必须调度到 topology.kubernetes.io/zone=us-east-1a 的节点上

如果集群中没有 app=cache 的 Pod 正在运行,这个 Pod 会 Pending——因为找不到参考 Pod,无法确定拓扑域。

##### (2)Pod 亲和调度——软策略

让新 Pod 倾向调度到与指定 Pod 相同拓扑域的节点:

yamlapiVersion: v1
kind: Pod
metadata:
  name: web-server-soft
  labels:
    app: web
spec:
  affinity:
    podAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 80               # 权重较高,强倾向
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: app
              operator: In
              values:
              - cache
          topologyKey: topology.kubernetes.io/zone
  containers:
  - name: nginx
    image: nginx:1.25

2.3.2 Pod 反亲和

Pod 反亲和让新 Pod 远离指定 Pod 所在的拓扑域,常用于:

  • 同一 Deployment 的副本分散到不同节点/区域(避免单点故障)
  • 互相干扰的服务不在同一节点运行

硬策略反亲和——同一 Deployment 的 Pod 绝不在同一节点:

yamlapiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - web           # 匹配自身 Deployment 的 Pod
            topologyKey: kubernetes.io/hostname  # 不同节点(hostname 不同)
      containers:
      - name: nginx
        image: nginx:1.25

topologyKey: kubernetes.io/hostname 的含义:让同一 Deployment 的 Pod 不在同一 hostname(即不同节点)上。每个节点只有一个 hostname,所以这个规则确保每个节点最多一个该 Deployment 的 Pod。

软策略反亲和——尽量分散但不是硬性要求:

yamlpodAntiAffinity:
  preferredDuringSchedulingIgnoredDuringExecution:
  - weight: 100
    podAffinityTerm:
      labelSelector:
        matchExpressions:
        - key: app
          operator: In
          values:
          - web
      topologyKey: topology.kubernetes.io/zone  # 尽量不在同一 zone

2.3.3 拓扑域

##### (1)什么是拓扑域

拓扑域(Topology Domain)是 Kubernetes 中对集群节点进行逻辑分组的概念。同一拓扑域内的节点共享某个拓扑标签的值。

举例:

text节点 node-01: topology.kubernetes.io/zone=us-east-1a, kubernetes.io/hostname=node-01
节点 node-02: topology.kubernetes.io/zone=us-east-1a, kubernetes.io/hostname=node-02
节点 node-03: topology.kubernetes.io/zone=us-east-1b, kubernetes.io/hostname=node-03
节点 node-04: topology.kubernetes.io/zone=us-east-1b, kubernetes.io/hostname=node-04
- 以 `kubernetes.io/hostname` 为拓扑键 → 每个节点是一个独立拓扑域(4个域)
- 以 `topology.kubernetes.io/zone` 为拓扑键 → node-01/02 同域,node-03/04 同域(2个域)

拓扑域是 podAffinity/podAntiAffinity 的核心概念——亲和/反亲和不是在"节点"层面生效,而是在"拓扑域"层面生效。

##### (2)为何要用 topologyKey

podAffinity/podAntiAffinity 的匹配逻辑:

  1. 先通过 labelSelector 找到目标 Pod
  2. 找到这些 Pod 所在节点上的 topologyKey 标签值
  3. 新 Pod 被调度到(亲和)或避开(反亲和)相同 topologyKey 标签值的节点

如果不用 topologyKey,podAffinity 就只能以节点为单位做调度,无法表达"同一区域"、"同一机房"这类更大范围的亲和/反亲和需求。

topologyKey 的粒度决定调度粒度

topologyKey 拓扑域粒度 亲和效果
kubernetes.io/hostname 单节点 Pod 分散到不同节点
topology.kubernetes.io/zone 可用区 Pod 分散到不同可用区
topology.kubernetes.io/region 地域 Pod 分散到不同地域

##### (3)如何表示机器归属的拓扑域

通过给节点打标签来表示机器的拓扑域归属:

bash# 标记节点所在可用区
kubectl label nodes node-01 topology.kubernetes.io/zone=us-east-1a
kubectl label nodes node-02 topology.kubernetes.io/zone=us-east-1a
kubectl label nodes node-03 topology.kubernetes.io/zone=us-east-1b

# 标记节点所在地域
kubectl label nodes node-01 topology.kubernetes.io/region=us-east
kubectl label nodes node-02 topology.kubernetes.io/region=us-east

# 标记节点所属机房
kubectl label nodes node-01 dc=shanghai-dc1
kubectl label nodes node-02 dc=shanghai-dc1
kubectl label nodes node-03 dc=beijing-dc1

##### (4)创建新 Pod 时如何指定拓扑域

在 podAffinityTerm 中通过 topologyKey 字段指定:

yamlpodAffinity:
  requiredDuringSchedulingIgnoredDuringExecution:
  - labelSelector:
      matchExpressions:
      - key: app
        operator: In
        values:
        - backend
    topologyKey: topology.kubernetes.io/zone   # 以 zone 为拓扑域

规则topologyKey 不能为空。如果指定了 topologyKey,所有可行节点都必须有该标签,否则调度会排除无此标签的节点。

##### (5)拓扑域示例

场景:Web 服务和 Cache 服务需要跑在同一可用区以减少延迟

yamlapiVersion: v1
kind: Pod
metadata:
  name: web-with-cache
  labels:
    app: web
spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: app
            operator: In
            values:
            - cache
        topologyKey: topology.kubernetes.io/zone
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: app
              operator: In
              values:
              - web
          topologyKey: kubernetes.io/hostname
  containers:
  - name: nginx
    image: nginx:1.25

解读:

  • 硬亲和:必须和 app=cache 的 Pod 在同一可用区
  • 软反亲和:尽量不和 app=web 的 Pod 在同一节点(分散副本)

##### (6)默认创建好的拓扑域标签

Kubernetes 节点默认拥有以下拓扑标签:

标签 说明
kubernetes.io/hostname 节点的主机名,每节点唯一
topology.kubernetes.io/zone 节点所在可用区(如 us-east-1a)
topology.kubernetes.io/region 节点所在地域(如 us-east)
topology.kubernetes.io/arch 节点 CPU 架构(如 amd64、arm64)
topology.kubernetes.io/os 节点操作系统(如 linux)

云厂商集群(EKS、GKE、AKS)会自动根据云平台 API 为节点填充 zone 和 region 标签。自建集群需要手动打标签或通过 cloud-controller-manager 自动设置。

##### (7)总结

  • topologyKey 决定了 podAffinity/podAntiAffinity 的生效粒度
  • 粒度越细(hostname),调度越分散;粒度越粗(zone/region),调度越集中
  • 硬亲和使用 required,软亲和使用 preferred(带 weight)
  • 硬策略可能导致 Pod Pending(没有满足条件的拓扑域时),软策略不会
  • 生产环境推荐:反亲和用硬策略保证副本分散到不同节点,亲和用软策略倾向同区域但不强制

4. 三、污点与容忍

3.1 污点与容忍介绍

污点(Taint) 打在节点上,排斥不容忍该污点的 Pod——让节点"嫌弃"不合适的 Pod。

容忍(Toleration) 定义在 Pod 上,允许 Pod 接受节点的污点——让 Pod"接受"被嫌弃。

两者配合使用,实现节点级准入控制

text节点打污点 → 不容忍的 Pod 被排斥 → 定义了容忍的 Pod 可以调度

核心区别

机制 作用方向 效果
nodeSelector / nodeAffinity Pod → Node(Pod 选择节点) 主动吸引
Taint / Toleration Node → Pod(节点排斥 Pod) 主动排斥

典型场景

  • 专用节点:GPU 节点只允许需要 GPU 的 Pod
  • 故障隔离:节点磁盘故障,标记污点驱逐 Pod
  • 维护模式:节点正在维护,不让新 Pod 调度进来
  • Control Plane 专用:master 节点默认有污点,阻止普通 Pod

污点的三种效果

效果 说明
NoSchedule 禁止调度——新的不容忍 Pod 不会被调度到此节点,已运行的 Pod 不受影响
PreferNoSchedule 尽量不调度——软约束,调度器尽量避开,但实在没节点也能调度
NoExecute 禁止调度 + 驱逐运行——新 Pod 不调度,且已运行的不容忍 Pod 会被驱逐

Kubernetes 控制平面节点默认带有污点 node-role.kubernetes.io/control-plane:NoSchedule,阻止普通业务 Pod 调度到 master。

3.2 为节点打上污点

打污点

bash# 为 node-01 打上专用节点污点(key=dedicated, value=special-user, effect=NoSchedule)
kubectl taint nodes node-01 dedicated=special-user:NoSchedule

# 打 GPU 专用污点
kubectl taint nodes gpu-node nvidia.com/gpu=true:NoSchedule

# 打维护模式污点(NoExecute 效果,会驱逐已运行的 Pod)
kubectl taint nodes node-03 maint=ongoing:NoExecute

# 打软排斥污点(尽量不调度)
kubectl taint nodes node-04 test-only=true:PreferNoSchedule

# 按标签选择节点打污点
kubectl taint node -l role=infra dedicated=infra:NoSchedule

查看污点

bash# 查看节点上的污点
kubectl describe node node-01 | grep Taints

# 查看所有节点的污点
kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints

删除污点

bash# 删除指定 key+effect 的污点(在 key:effect 后加 -)
kubectl taint nodes node-01 dedicated=special-user:NoSchedule-

# 删除指定 key 的所有污点(不管 effect)
kubectl taint nodes node-01 dedicated-

# 删除指定 effect 的污点(保留其他 effect)
kubectl taint nodes node-01 dedicated:NoSchedule-

3.3 为 Pod 定义容忍

基本容忍

容忍需要与节点污点匹配才能生效。匹配规则取决于 operator

operator 匹配条件 说明
Equal(默认) key=value+effect 完全匹配 key、value、effect 三者必须与污点一致
Exists key+effect 匹配(忽略 value) 只要 key 和 effect 匹配,不管污点的 value 是什么

精确匹配(Equal)

yamlapiVersion: v1
kind: Pod
metadata:
  name: gpu-pod
spec:
  tolerations:
  - key: "nvidia.com/gpu"
    operator: "Equal"
    value: "true"
    effect: "NoSchedule"
  containers:
  - name: cuda-app
    image: cuda-vector-add:v0.1

Exists 匹配——容忍某 key 的所有污点(不管值):

yamltolerations:
- key: "nvidia.com/gpu"
  operator: "Exists"
  effect: "NoSchedule"

容忍所有污点——key 和 effect 都为空:

yamltolerations:
- operator: "Exists"   # key 为空 + operator=Exists → 匹配所有污点

这会让 Pod 可以调度到任何有污点的节点,慎用。

多容忍组合

yamlapiVersion: v1
kind: Pod
metadata:
  name: multi-tolerate-pod
spec:
  tolerations:
  - key: "dedicated"
    operator: "Equal"
    value: "special-user"
    effect: "NoSchedule"
  - key: "nvidia.com/gpu"
    operator: "Exists"
    effect: "NoSchedule"
  - key: "maint"
    operator: "Equal"
    value: "ongoing"
    effect: "NoExecute"
  containers:
  - name: nginx
    image: nginx:1.25

Pod 的 tolerations 是累加型的——只要有一条容忍匹配了节点的某个污点,该污点就不会排斥这个 Pod。不需要一条容忍匹配所有污点。

常见容忍场景

容忍 master 节点污点(让业务 Pod 可以调度到 control plane):

yamltolerations:
- key: "node-role.kubernetes.io/control-plane"
  operator: "Exists"
  effect: "NoSchedule"

容忍所有 NoSchedule 污点(用于 DaemonSet,确保每个节点都运行):

yamltolerations:
- operator: "Exists"
  effect: "NoSchedule"

DaemonSet 容忍所有污点的完整配置:

yamlapiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-monitor
spec:
  selector:
    matchLabels:
      app: monitor
  template:
    metadata:
      labels:
        app: monitor
    spec:
      tolerations:
      - operator: "Exists"    # 容忍所有 NoSchedule 污点
        effect: "NoSchedule"
      - operator: "Exists"    # 容忍所有 NoExecute 污点
        effect: "NoExecute"
      containers:
      - name: monitor
        image: prometheus-node-exporter:latest

3.4 定义 Pod 驱逐时间

tolerationSeconds 字段控制 Pod 在 NoExecute 污点出现后还能留在节点上的时间

场景说明

当节点突然被打上 NoExecute 污点时:

  • 没有容忍:Pod 立即被驱逐
  • 有容忍但无 tolerationSeconds:Pod 永远不会被驱逐
  • 有容忍且设置 tolerationSeconds:Pod 在 tolerationSeconds 后被驱逐

示例:Pod 可以在故障节点上停留 3600 秒

yamlapiVersion: v1
kind: Pod
metadata:
  name: resilient-app
spec:
  tolerations:
  - key: "node.kubernetes.io/not-ready"
    operator: "Exists"
    effect: "NoExecute"
    tolerationSeconds: 3600    # 节点 NotReady 后,Pod 还能停留 1 小时
  - key: "node.kubernetes.io/unreachable"
    operator: "Exists"
    effect: "NoExecute"
    tolerationSeconds: 3600    # 节点 Unreachable 后,Pod 还能停留 1 小时
  containers:
  - name: nginx
    image: nginx:1.25

默认 tolerationSeconds

Kubernetes 为 Pod 自动添加两条默认容忍(通过 Admission Controller):

yaml# 默认容忍——节点 NotReady 后等待 300 秒(5 分钟)
tolerations:
- key: "node.kubernetes.io/not-ready"
  operator: "Exists"
  effect: "NoExecute"
  tolerationSeconds: 300

# 默认容忍——节点 Unreachable 后等待 300 秒(5 分钟)
tolerations:
- key: "node.kubernetes.io/unreachable"
  operator: "Exists"
  effect: "NoExecute"
  tolerationSeconds: 300

这意味着默认情况下,节点 NotReady/Unreachable 5 分钟后,Pod 会被驱逐。如果你的 Pod 需要更长容忍时间,可以显式声明更大的 tolerationSeconds。

Kubernetes 自动添加的污点

自动污点 触发条件
node.kubernetes.io/not-ready 节点 NodeReady 条件为 False
node.kubernetes.io/unreachable 节点从 NodeController 不可达
node.kubernetes.io/memory-pressure 节点内存压力
node.kubernetes.io/disk-pressure 节点磁盘压力
node.kubernetes.io/pid-pressure 节点 PID 资源不足
node.kubernetes.io/network-unavailable 节点网络未配置
node.kubernetes.io/unschedulable 节点被 cordon 标记为不可调度

这些污点由 kubelet 和 node-controller 自动管理,无需手动添加。

驱逐时间实战:维护场景

bash# 1. 给节点打维护污点(NoExecute 驱逐已运行 Pod)
kubectl taint nodes node-03 maint=planned:NoExecute

# 2. 没有 tolerationSeconds 的容忍 → Pod 永不驱逐
# 3. tolerationSeconds=0 的容忍 → Pod 立即驱逐
# 4. tolerationSeconds=3600 → Pod 还能运行 1 小时再被驱逐
yaml# 立即驱逐的容忍(tolerationSeconds=0)
tolerations:
- key: "maint"
  operator: "Equal"
  value: "planned"
  effect: "NoExecute"
  tolerationSeconds: 0    # 等于 0 → 立即驱逐

# 优雅容忍(给 Pod 1 小时时间完成工作)
tolerations:
- key: "maint"
  operator: "Equal"
  value: "planned"
  effect: "NoExecute"
  tolerationSeconds: 3600  # 1 小时后驱逐

5. 四、Pod 均匀调度——topologySpreadConstraints

podAntiAffinity 能让同一 Deployment 的副本分散到不同节点,但它有一个根本缺陷:它是"尽力分散",不是"均匀分布"。当副本数多于拓扑域数时,podAntiAffinity 无法控制每个域内放几个 Pod;它只管"不在同一域",不管"域间差多少"。

topologySpreadConstraints 正是为解决这个问题而引入的——它通过 maxSkew(最大倾斜度) 精确控制 Pod 在各拓扑域之间的分布差异上限,实现真正的均匀调度。

4.1 API 字段详解

yamlapiVersion: v1
kind: Pod
metadata:
  name: example-pod
spec:
  topologySpreadConstraints:
  - maxSkew: <integer>              # 必填:最大允许倾斜度,默认 1,不允许 0
    topologyKey: <string>            # 必填:拓扑域标签键
    whenUnsatisfiable: <string>      # 必填:DoNotSchedule 或 ScheduleAnyway
    labelSelector:                   # 必填:选择哪些 Pod 参与分布计算
      matchLabels:
        app: foo
    minDomains: <integer>            # 可选 (v1.25+):最小拓扑域数
    matchLabelKeys:                  # 可选 (v1.27+ beta):从 Pod 自身标签取值合并到 labelSelector
      - pod-template-hash
    nodeAffinityPolicy: Honor|Ignore # 可选 (v1.26+ beta):是否考虑 nodeAffinity
    nodeTaintsPolicy: Honor|Ignore   # 可选 (v1.26+ beta):是否考虑节点污点

各字段说明:

字段 说明
maxSkew 最大允许的倾斜度。任意两个拓扑域之间匹配 Pod 数量的最大差值不能超过此值。默认 1,不允许设 0
topologyKey 节点标签键,具有相同标签值的节点属于同一拓扑域。调度器会尝试在每个域内放入均衡数量的 Pod
whenUnsatisfiable 无法满足分布约束时的行为:DoNotSchedule(硬约束,Pod 保持 Pending)或 ScheduleAnyway(软约束,尽量均匀但仍会调度)
labelSelector 选择参与倾斜度计算的 Pod 组。通常设为与自身 Deployment 的 podLabels 相同
minDomains 预期的最小拓扑域数量。当实际域数 < minDomains 时,全局最小值视为 0,使所有域都被认为严重不均衡
matchLabelKeys 从 Pod 自身标签取值并合并到 labelSelector。典型用法:pod-template-hash,确保滚动更新时新旧版本各自均匀分布
nodeAffinityPolicy Honor(默认):计算倾斜度时只考虑满足 Pod nodeAffinity/nodeSelector 的节点;Ignore:忽略 nodeAffinity,在所有节点上计算
nodeTaintsPolicy Honor(默认):计算倾斜度时只考虑 Pod 可容忍污点的节点;Ignore:忽略污点,在所有节点上计算

4.2 Skew 计算算法(核心原理)

Skew(倾斜度)的计算是 topologySpreadConstraints 的核心逻辑。理解它,才能正确配置 maxSkew。

计算公式

text全局最小值 (global minimum) = 所有合格拓扑域中匹配 Pod 数量的最小值

某域的 ActualSkew = 该域匹配 Pod 数量 - 全局最小值

约束判断:ActualSkew ≤ maxSkew → 该域可接受新 Pod
         ActualSkew > maxSkew → 该域拒绝新 Pod (DoNotSchedule)

调度器源码中的判断逻辑(filtering.go):

textskew = matchNum + selfMatchNum - minMatchNum
if skew > maxSkew → 拒绝调度到该域

其中 selfMatchNum = 新 Pod 自身是否匹配 labelSelector(匹配为 1,不匹配为 0),minMatchNum 取决于 minDomains:

  • 当合格域数量 ≥ minDomains 时,minMatchNum = 所有域中最小的 Pod 数
  • 当合格域数量 < minDomains 时,minMatchNum = 0(全局最小视为 0,触发强烈均衡)

示例推演

场景 1:三可用区集群,分布 2/2/1,maxSkew=1

textzone-a: 2 个匹配 Pod → ActualSkew = 2 - 1 = 1 ✓ (≤ maxSkew)
zone-b: 2 个匹配 Pod → ActualSkew = 2 - 1 = 1 ✓ (≤ maxSkew)
zone-c: 1 个匹配 Pod → ActualSkew = 1 - 1 = 0 ✓ (全局最小域)

新 Pod 只能调度到 zone-c(调度到 zone-a/b 会使 skew 变为 2,超过 maxSkew=1)。

场景 2:三可用区集群,分布 3/1/0,maxSkew=1

textzone-a: 3 → ActualSkew = 3 - 0 = 3 ✗
zone-b: 1 → ActualSkew = 1 - 0 = 1 ✗
zone-c: 0 → ActualSkew = 0 - 0 = 0 ✓ (全局最小域)

如果 whenUnsatisfiable: DoNotSchedule,Pod 只能调度到 zone-c。 如果 whenUnsatisfiable: ScheduleAnyway,Pod 优先调度到 zone-c,但在 zone-c 资源不足时也可调度到其他域。

场景 3:minDomains 的效果

假设设置了 minDomains: 3,但 zone-c 宕机,只剩 2 个域(分布 3/1):

- 合格域数量(2) < minDomains(3) → **全局最小视为 0**
  • zone-a 的 skew = 3 - 0 = 3,zone-b 的 skew = 1 - 0 = 1
  • 即使 maxSkew=1,两个域都"严重不均衡",新 Pod 被迫 Pending(DoNotSchedule)

minDomains 的设计意图:当拓扑域减少(如可用区故障)时,提醒调度器"本来应该有 3 个域",触发更强的均衡约束。

ScheduleAnyway 的打分逻辑

whenUnsatisfiable: ScheduleAnyway 时,调度器不会拒绝任何节点,但会通过打分优先选择减少倾斜度的域。打分公式(scoring.go):

textscore = matchCount × topologyNormalizingWeight + (maxSkew - 1)

其中 topologyNormalizingWeight = log(domainCount + 2),用于避免大域(zone 级)的 Pod 数量稀释小域(hostname 级)的权重。匹配 Pod 越少的域得分越低(因为 matchCount 小),反而更受青睐——调度器倾向把 Pod 放到"最缺 Pod 的域"。

4.3 单约束 vs 多约束分布策略

单约束:仅按可用区均匀分布

最简单的用法——确保 Pod 在各可用区之间均匀分布:

yamlapiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
spec:
  replicas: 6
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      topologySpreadConstraints:
      - maxSkew: 1
        topologyKey: topology.kubernetes.io/zone
        whenUnsatisfiable: DoNotSchedule
        labelSelector:
          matchLabels:
            app: web
      containers:
      - name: nginx
        image: nginx:1.25

效果:6 个副本在 3 个 zone 中分布为 2/2/2,任何 zone 不会超过 3 个副本。

多约束:Zone + Hostname 混用(生产推荐)

单约束只保证跨 zone 均匀,但不保证zone 内跨节点均匀。比如 2/2/2 的分布可能变成 zone-a 内 2 个副本全挤在同一节点。

生产环境推荐双约束策略——同时约束 zone 级和 hostname 级:

yamlapiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
spec:
  replicas: 6
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      topologySpreadConstraints:
      # 第一约束:跨可用区均匀分布(硬约束)
      - maxSkew: 1
        topologyKey: topology.kubernetes.io/zone
        whenUnsatisfiable: DoNotSchedule
        labelSelector:
          matchLabels:
            app: web
      # 第二约束:跨节点均匀分布(硬约束)
      - maxSkew: 1
        topologyKey: kubernetes.io/hostname
        whenUnsatisfiable: DoNotSchedule
        labelSelector:
          matchLabels:
            app: web
      containers:
      - name: nginx
        image: nginx:1.25

多约束求交集:调度器寻找同时满足所有约束的节点。以上双约束的效果:

  • zone 约束保证每个可用区不超过 2 个副本
  • hostname 约束保证每个节点不超过 1 个副本
  • 结果:6 个副本在 3 个 zone × 2 节点/zone 中分布为 zone-a[node1:1, node2:1]、zone-b[node3:1, node4:1]、zone-c[node5:1, node6:1]

重要:当定义多个 topologySpreadConstraints 时,调度器要求节点必须同时拥有所有 topologyKey 标签。缺少任何一个 topologyKey 标签的节点会被跳过,上面已有的 Pod 也不计入倾斜度计算。因此,所有节点必须同时标注 topology.kubernetes.io/zonekubernetes.io/hostname(hostname 默认存在,zone 需确保标注)。

灵活策略:zone 硬约束 + hostname 软约束

当节点数量不足以满足 hostname 级硬约束时(比如 zone 内只有 1 个节点),硬约束会导致 Pod Pending。更实用的做法:

yamltopologySpreadConstraints:
# 跨可用区:硬约束,必须均匀
- maxSkew: 1
  topologyKey: topology.kubernetes.io/zone
  whenUnsatisfiable: DoNotSchedule
  labelSelector:
    matchLabels:
      app: web
# 跨节点:软约束,尽量均匀但不阻塞调度
- maxSkew: 1
  topologyKey: kubernetes.io/hostname
  whenUnsatisfiable: ScheduleAnyway
  labelSelector:
    matchLabels:
      app: web

这样即使 zone 内只有 1 个节点,Pod 也不会 Pending,只是"尽量分散"。

4.4 与 podAntiAffinity 的对比

维度 podAntiAffinity topologySpreadConstraints
核心语义 "不要和某 Pod 在同一拓扑域" "各拓扑域的 Pod 数量差值不超过 maxSkew"
均衡效果 尽力分散,无法控制精确分布 精确控制各域 Pod 数量差异
域数 > Pod 数 每个 Pod 去不同域,效果很好 每个 Pod 去不同域,效果等同
域数 < Pod 数 无法控制域间差异(可能 3:1:0) maxSkew 限制最大差异(如 2:1:1)
硬约束风险 可能导致 Pod Pending 同样可能,但可通过 ScheduleAnyway 缓解
跨多拓扑域 需多条反亲和规则,配置冗余 多条约束求交集,配置清晰

结论:需要"均匀分布"时,优先使用 topologySpreadConstraints;需要"绝对不能在同一域"时,仍可使用 podAntiAffinity 硬策略。两者可以组合使用——topologySpreadConstraints 控制宏观均匀,podAntiAffinity 控制微观排斥。

4.5 默认约束

如果不定义任何 topologySpreadConstraints,kube-scheduler 会使用以下默认约束(ScheduleAnyway 软约束):

yamldefaultConstraints:
- maxSkew: 3
  topologyKey: "kubernetes.io/hostname"
  whenUnsatisfiable: ScheduleAnyway
- maxSkew: 5
  topologyKey: "topology.kubernetes.io/zone"
  whenUnsatisfiable: ScheduleAnyway

默认约束是软约束,不会阻塞调度,只是倾向均匀。如果节点没有 zone 标签,hostname 级约束仍然生效。

注意:如果你的节点不都具备 kubernetes.io/hostnametopology.kubernetes.io/zone 标签,应自定义约束而非使用默认值。


6. 五、Descheduler——故障恢复与重平衡

5.1 为什么需要 Descheduler

kube-scheduler 是一个一次性决策系统——它只在 Pod 创建时做出调度决定,之后不再回头看。这意味着:

  • 节点故障恢复后,原来集中在剩余节点的 Pod 不会自动回流
  • 新节点加入集群后,已有 Pod 不会自动迁移到新节点
  • 长期运行中,Pod 分布可能变得严重不均衡

Descheduler 的工作就是定期检查并纠正这些偏差——找出应该迁移的 Pod,驱逐它们,让 kube-scheduler 重新调度到更合适的位置。

textkube-scheduler: 创建时决策 → 不再调整
Descheduler: 定期巡检 → 驱逐偏差 Pod → kube-scheduler 重新调度

Descheduler 不负责调度,它只负责驱逐。被驱逐的 Pod 由 kube-scheduler 根据当前集群状态重新调度。

5.2 Descheduler 策略

Descheduler 提供两类策略插件:

Deschedule 类(检查单个 Pod 是否应该被驱逐):

策略 说明
RemovePodsViolatingNodeAffinity 驱逐不再满足节点亲和规则的 Pod(如节点标签变化后)
RemovePodsViolatingNodeTaints 驱逐不再容忍节点污点的 Pod(如新打了 NoSchedule 污点)
RemovePodsViolatingInterPodAntiAffinity 驱逐违反 Pod 反亲和规则的 Pod
RemovePodsHavingTooManyRestarts 驱逐重启次数过多的 Pod
PodLifeTime 驱逐超过指定年龄的 Pod
RemoveFailedPods 驱逐处于 Failed 状态的 Pod

Balance 类(检查一组 Pod 的整体分布是否合理):

策略 说明
RemoveDuplicates 确保同一 RS/Deployment 的副本不在同一节点重复
LowNodeUtilization 从高利用率节点驱逐 Pod 到低利用率节点
HighNodeUtilization 反向操作——将 Pod 从低利用率节点迁移到高利用率节点,减少碎片
RemovePodsViolatingTopologySpreadConstraint 驱逐违反 topologySpreadConstraints 的 Pod,使域间分布回到 maxSkew 限制内

5.3 RemovePodsViolatingTopologySpreadConstraint 详解

这是与均匀调度直接相关的策略。它的工作流程:

  1. 找到所有定义了 topologySpreadConstraints 的 Pod
  2. 计算各拓扑域的实际倾斜度
  3. 如果实际 skew > maxSkew,从倾斜度最大的域中驱逐最少数量的 Pod
  4. 驱逐后 kube-scheduler 重新调度,自然流向倾斜度小的域

配置示例

yamlapiVersion: "descheduler/v1alpha2"
kind: "DeschedulerPolicy"
profiles:
  - name: topology-rebalance
    pluginConfig:
    - name: "RemovePodsViolatingTopologySpreadConstraint"
      args:
        constraints:
          - DoNotSchedule          # 默认只处理硬约束
          # - ScheduleAnyway       # 加上此行也处理软约束
        namespaces:
          include: ["production"]
        labelSelector:
          matchLabels:
            app: critical-app
    plugins:
      balance:
        enabled:
          - "RemovePodsViolatingTopologySpreadConstraint"

支持的约束字段

字段 支持
maxSkew
topologyKey
whenUnsatisfiable
labelSelector
matchLabelKeys
nodeAffinityPolicy
nodeTaintsPolicy
minDomains

5.4 故障恢复重平衡场景

场景:可用区故障恢复

text初始状态:6 副本均匀分布在 zone-a(2) / zone-b(2) / zone-c(2)

zone-a 宕机 → 副本集中在 zone-b(3) / zone-c(3)  ← 严重不均衡!

zone-a 恢复 → 但 Pod 不会自动回流,仍然 zone-b(3) / zone-c(3)
                ← kube-scheduler 不做回顾性调整

Descheduler 运行:
  1. 检测到 skew=3-0=3 > maxSkew=1
  2. 从 zone-b 和 zone-c 各驱逐 1 个 Pod
  3. kube-scheduler 将新 Pod 调度到 zone-a
  4. 最终回到 zone-a(2) / zone-b(2) / zone-c(2) ✓

场景:新节点加入集群

text初始:4 副本分布在 node1(2) / node2(2)  ← hostname skew=0 没问题

node3 加入集群 → 但已有 Pod 不会自动迁移

Descheduler 运行:
  1. hostname skew: node1(2) vs node3(0) → skew=2 > maxSkew=1
  2. 从 node1 驱逐 1 个 Pod
  3. kube-scheduler 调度到 node3
  4. 最终 node1(1) / node2(2) / node3(1) → 继续调整直到均衡

5.5 安装 Descheduler

Helm 安装(推荐)

bash# 添加 Helm 仓库
helm repo add descheduler https://kubernetes-sigs.github.io/descheduler/
helm repo update

# 安装到 kube-system
helm install descheduler descheduler/descheduler \
  --namespace kube-system \
  --set deschedulingIntervalSeconds=3600   # 每 3600 秒运行一次

完整策略配置示例

yamlapiVersion: "descheduler/v1alpha2"
kind: "DeschedulerPolicy"
profiles:
  - name: production-rebalance
    pluginConfig:
    # 拓扑分布重平衡
    - name: "RemovePodsViolatingTopologySpreadConstraint"
      args:
        constraints:
          - DoNotSchedule
    # 去重:同一节点不放同一 RS 的多个副本
    - name: "RemoveDuplicates"
      args:
        excludeOwnerKinds:
          - "DaemonSet"
    # 节点亲和/污点违规
    - name: "RemovePodsViolatingNodeAffinity"
      args:
        nodeAffinityType:
          - "requiredDuringSchedulingIgnoredDuringExecution"
    - name: "RemovePodsViolatingNodeTaints"
    # 低利用率节点重平衡
    - name: "LowNodeUtilization"
      args:
        thresholds:
          "cpu": 20
          "memory": 20
          "pods": 20
        targetThresholds:
          "cpu": 60
          "memory": 60
          "pods": 60
    plugins:
      balance:
        enabled:
          - "RemovePodsViolatingTopologySpreadConstraint"
          - "RemoveDuplicates"
          - "LowNodeUtilization"
      deschedule:
        enabled:
          - "RemovePodsViolatingNodeAffinity"
          - "RemovePodsViolatingNodeTaints"

Dry-run 模式

先查看哪些 Pod 会被驱逐,不实际执行:

bash# 运行 dry-run
kubectl exec -n kube-system descheduler-xxx -- /bin/descheduler --dry-run

5.6 Descheduler 与 PDB 的交互

Descheduler 在驱逐 Pod 时必须遵守 PodDisruptionBudget。如果 PDB 的 disruptionsAllowed=0,Descheduler 无法驱逐该 Pod,重平衡操作被阻塞。

因此:

  • 为关键服务设置合理的 PDB(如 maxUnavailable: 1),既允许逐步驱逐又保护可用性
  • 不要设置 maxUnavailable: 0minAvailable: 100%,这会完全阻止 Descheduler 工作
  • Descheduler 不会绕过 PDB——如果 PDB 阻止驱逐,Descheduler 会跳过该 Pod

7. 六、PodDisruptionBudget(PDB)

6.1 自愿中断与非自愿中断

Kubernetes 将 Pod 中断分为两类:

类型 说明 PDB 是否保护
非自愿中断 节点硬件故障、内核崩溃、云厂商 Spot 实例回收、OOM Kill ❌ 无法保护
自愿中断 kubectl drain、节点维护、Cluster Autoscaler 缩容、Descheduler 驱逐 ✅ PDB 保护

PDB 只保护自愿中断,对非自愿中断无效。但非自愿中断导致的 Pod 丢失也会计入 PDB 预算——如果节点故障已经把健康 Pod 数降到 desiredHealthy 以下,后续的 kubectl drain 会被 PDB 阻止。

6.2 PDB API

yamlapiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: web-app-pdb
spec:
  # 以下两个字段只能选一个
  minAvailable: 2            # 驱逐后至少保留 2 个健康 Pod
  # maxUnavailable: 1        # 驱逐后最多 1 个 Pod 不可用

  selector:
    matchLabels:
      app: web-app            # 选择目标 Pod

  # v1.26+ 可选:不健康 Pod 的驱逐策略
  unhealthyPodEvictionPolicy: AlwaysAllow

minAvailable vs maxUnavailable

字段 含义 适用场景 百分比取整
minAvailable 驱逐后必须保持的最小健康 Pod 数 基于仲裁的有状态服务(如 etcd: minAvailable=2) 向上取整(保守:保护更多)
maxUnavailable 驱逐后允许的最大不可用 Pod 数 无状态服务(推荐,随副本数自适应) 向上取整(宽松:允许更多中断)

推荐:大多数无状态服务使用 maxUnavailable,因为它随副本数自动适应。有状态仲裁服务使用 minAvailable 设为仲裁数。

unhealthyPodEvictionPolicy(v1.26+)

策略 说明
IfHealthyBudget(默认) 不健康 Pod(如 CrashLoopBackOff)只能在应用整体健康时被驱逐。保守但可能导致 drain 死锁
AlwaysAllow 不健康 Pod 总是可以被驱逐,不管 PDB 预算。推荐用于大多数服务,避免 CrashLoopBackOff Pod 阻塞 drain

强烈建议:除非运行基于仲裁的有状态服务,都应设 AlwaysAllow。默认的 IfHealthyBudget 会让 CrashLoopBackOff 的 Pod 永远阻塞 kubectl drain

6.3 PDB 状态字段

bashkubectl get pdb web-app-pdb

输出示例:

textNAME          MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   CURRENT HEALTHY   DESIRED HEALTHY
web-app-pdb   N/A             1                 1                     5                 4
字段 说明
ALLOWED DISRUPTIONS 当前可以同时驱逐的 Pod 数量。如果为 0,所有自愿中断被阻止
CURRENT HEALTHY 当前健康的 Pod 数量
DESIRED HEALTHY PDB 要求的最小健康 Pod 数量

6.4 PDB 配置示例

无状态服务——maxUnavailable

yamlapiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: web-api-pdb
  namespace: production
spec:
  maxUnavailable: 1                        # 一次只允许驱逐 1 个 Pod
  unhealthyPodEvictionPolicy: AlwaysAllow   # 不健康 Pod 可随时驱逐
  selector:
    matchLabels:
      app: web-api

效果:kubectl drain 每次只驱逐 1 个 Pod,等待新 Pod Ready 后再驱逐下一个。

有状态仲裁服务——minAvailable

yamlapiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: etcd-pdb
  namespace: kube-system
spec:
  minAvailable: 2                           # 3 艘集群仲裁数 = floor(3/2)+1 = 2
  unhealthyPodEvictionPolicy: IfHealthyBudget # 有状态服务保守策略
  selector:
    matchLabels:
      app: etcd

百分比配置

yamlapiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: web-app-pdb
spec:
  maxUnavailable: "25%"     # 5 副本时允许 1 个不可用(ceil(5×0.25)=2,实际宽松)
  selector:
    matchLabels:
      app: web-app

6.5 PDB 与其他机制的交互

PDB vs Deployment 滚动更新

PDB 不约束 Deployment 的滚动更新。Deployment 控制器通过 maxSurgemaxUnavailable 自行管理更新节奏。PDB 只约束外部自愿中断(drain、Descheduler、Cluster Autoscaler)。

如果 kubectl drain 和滚动更新同时发生,两者叠加的不可用 Pod 数会受 PDB 检查。

PDB vs Cluster Autoscaler

Cluster Autoscaler 在缩容前检查节点上的 Pod 是否可被驱逐。如果任何 PDB 阻止驱逐,该节点不会被缩容。

常见阻塞场景:

- `maxUnavailable: 0` → 永远阻止驱逐,节点永远不缩容
- `minAvailable: 100%` → 同上
- 单副本 Deployment + 任何限制性 PDB → drain 永远阻塞

PDB vs Descheduler

Descheduler 驱逐 Pod 时调用 Eviction API,必须遵守 PDB。如果 disruptionsAllowed=0,Descheduler 跳过该 Pod。

6.6 常见陷阱

陷阱 说明
maxUnavailable: 0 阻止所有自愿中断,集群维护无法进行,AKS/GKE/EKS 升级会超时失败
单副本 + PDB 1 副本 Deployment + maxUnavailable:1 的 PDB 没有保护效果(唯一 Pod 可自由驱逐);minAvailable:1 则永远阻塞 drain
重叠 PDB 两个 PDB 选择相同的 Pod,Eviction API 返回 HTTP 500,导致不可预料的驱逐失败
HPA 缩容 + PDB HPA 缩容不受 PDB 限制,但缩容后可能使 disruptionsAllowed=0,阻塞并发 drain
百分比取整 7 副本 + maxUnavailable:"50%" → ceil(7×0.5)=4,实际允许 4 个不可用,比预期宽松

6.7 PDB 最佳实践

  1. 为每个 Deployment/StatefulSet 设置 PDB——无状态用 maxUnavailable: 1,有状态用 minAvailable 设为仲裁数
  2. 至少运行 2 个副本——单副本服务无法通过 PDB 同时保护可用性和允许维护
  3. 使用 unhealthyPodEvictionPolicy: AlwaysAllow——避免 CrashLoopBackOff Pod 阻塞 drain
  4. 不要设 maxUnavailable: 0——这会让集群维护和节点缩容完全阻塞
  5. 每个 PDB 选择唯一的 Pod 集——不要让两个 PDB 覆盖相同的 Pod
  6. Descheduler + PDB 联合配置——PDB 允许逐步驱逐(maxUnavailable: 1),Descheduler 定期重平衡

8. 七、优先级调度与抢占(PriorityClass & Preemption)

7.1 介绍

Pod 优先级(Priority) 是 Kubernetes 为 Pod 赋予的数值化重要性标记——值越大,优先级越高,调度器越优先调度,资源紧张时也越不容易被驱逐。

抢占(Preemption) 是优先级机制的延伸——当高优先级 Pod 无法调度时,调度器会驱逐(抢占) 低优先级 Pod 来腾出资源。

textPod 优先级 → 调度队列排序 → 高优先级 Pod 先调度
         ↓
   无法调度 → 触发抢占 → 驱逐低优先级 Pod → 高优先级 Pod 获得资源

关键区别:priority 影响调度顺序抢占能力,QoS 影响驱逐顺序。两者是正交的——一个高优先级 + BestEffort 的 Pod 在调度时优先,但在节点压力驱逐时会被优先驱逐(因为 BestEffort)。详见第八、九节。

PriorityClass 是 Kubernetes 用来定义优先级级别的集群级资源(无命名空间),Pod 通过 priorityClassName 引用它。

7.2 PriorityClass API

yamlapiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority          # 名称,Pod 通过 priorityClassName 引用
value: 1000000                  # 必填:优先级整数值,越大越优先
globalDefault: false            # 可选:是否作为默认优先级(集群中只能有一个)
preemptionPolicy: PreemptLowerPriority  # 可选:抢占策略(v1.24+ stable)
description: "此优先级类应用于 XYZ 服务"
字段 说明
value 32 位整数,范围 -2,147,483,648 到 1,000,000,000。值越大优先级越高。大于 10 亿的值保留给系统内置 PriorityClass
globalDefault 设为 true 时,所有未指定 priorityClassName 的 Pod 使用此优先级。集群中最多一个 PriorityClass 的 globalDefaulttrue。如果没有任何 globalDefault,未指定优先级的 Pod 优先级为 0
preemptionPolicy PreemptLowerPriority(默认):允许抢占低优先级 Pod;Never:禁止抢占。设为 Never 的 Pod 仍优先调度,但不会驱逐其他 Pod
description 任意字符串,用于告知集群用户该 PriorityClass 的使用场景

系统内置 PriorityClass

Kubernetes 默认提供两个高优先级 PriorityClass,确保关键组件始终被调度:

内置 PriorityClass value 用途
system-cluster-critical 2000000000 集群关键组件(如 CoreDNS、kube-proxy 等)
system-node-critical 2000001000 节点关键组件(如 kubelet 依赖的 DaemonSet)

自定义 PriorityClass 的 name 不能system- 为前缀。

非抢占式 PriorityClass

preemptionPolicy: Never 的 Pod 会被放在调度队列中优先级较低 Pod 之前,但不会抢占其他 Pod。适用场景:数据科学工作负载——希望优先调度于其他作业,但不愿意因为抢占而丢弃正在运行的计算结果。

yamlapiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority-nonpreempting
value: 1000000
preemptionPolicy: Never
globalDefault: false
description: "高优先级但不会抢占其他 Pod"

7.3 Pod 使用 PriorityClass

Pod 通过 spec.priorityClassName 引用 PriorityClass:

yamlapiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  priorityClassName: high-priority   # 引用 PriorityClass 名称
  containers:
  - name: nginx
    image: nginx:1.25

对 Deployment 来说,只需在 Pod 模板中指定:

yamlapiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
spec:
  replicas: 3
  template:
    spec:
      priorityClassName: high-priority
      containers:
      - name: nginx
        image: nginx:1.25

7.4 优先级对调度的影响

调度队列排序

当启用 Pod 优先级时,调度器按优先级降序排列所有 Pending Pod,高优先级 Pod 总是排在调度队列前面。

抢占流程

当高优先级 Pod P 无法调度时:

  1. 调度器寻找一个节点,如果在该节点上驱逐一个或多个优先级低于 P 的 Pod,P 就能被调度
  2. 找到后,驱逐低优先级 Pod(称为"牺牲者")
  3. 牺牲者被终止后,P 被调度到该节点

关键行为

  • 抢占只驱逐必要的低优先级 Pod,不会全部驱逐
  • 牺牲者获得体面终止期(默认 30s),然后被强制终止
  • Pstatus.nominatedNodeName 被设为目标节点名,标记"已为该 Pod 预留资源"
  • 如果牺牲者终止期间有更高优先级 Pod 到达,该节点可能被更高优先级的 Pod 获取

抢占不保证 PDB:调度器会尽量寻找不违反 PDB 的牺牲者,但如果找不到,仍然执行抢占,即使违反 PDB。这是"尽力而为"而非"保证"。

7.5 抢占的限制

限制 说明
Pod 间亲和性 如果 P 与低优先级 Pod 有 Pod 间亲和性,则不会抢占它们。推荐只对同等或更高优先级 Pod 设置亲和性
跨节点抢占 不支持。调度器不会驱逐另一个节点上的 Pod 来满足 P 的反亲和约束
体面终止延迟 牺牲者需要时间终止,高优先级 Pod 在此期间可能被抢占(被更高优先级 Pod 替代)

7.6 常见问题

问题 原因 解决
Pod 被不必要地抢占 错误地给 Pod 设置了高优先级 降低 priorityClassName 或清空(默认优先级 0)
有 Pod 被抢占,但抢占者没被调度 牺牲者终止期间来了更高优先级 Pod 正常行为——高优先级理应替代低优先级
高优先级 Pod 在低优先级 Pod 之前被抢占 调度器选择具有最低优先级 Pod 集合的节点,但如果该节点 Pod 受 PDB 保护,则选择其他节点 正常行为,PDB 影响抢占目标选择

7.7 优先级与 QoS 的关系

Pod 优先级和 QoS 是两个正交的维度

  • 调度抢占:只看优先级,不看 QoS
  • 节点压力驱逐:kubelet 综合考虑 QoS、优先级和资源使用量(详见第九节)
维度 优先级(Priority) QoS
作用者 kube-scheduler kubelet
触发时机 Pod 创建/调度时 节点资源压力时
影响范围 调度顺序 + 抢占 驱逐顺序 + OOM 评分
决定因素 PriorityClass 的 value 容器的 requests 和 limits 配置

9. 八、QoS(服务质量)类

8.1 介绍

QoS(Quality of Service) 是 Kubernetes 根据 Pod 中容器的资源 requests 和 limits 自动分配给 Pod 的"质量等级"。它决定了节点资源紧张时 Pod 被驱逐的优先级顺序

Kubernetes 将 Pod 分为三个 QoS 类:

textBestEffort(最差保护) → Burstable(中等保护) → Guaranteed(最强保护)
   先被驱逐 ←              中间              ←    最后被驱逐

核心原则:QoS 由 Pod 创建时确定,整个生命周期不变。原地资源调整如果导致 QoS 变化,会被准入阶段拒绝。

8.2 Guaranteed(保证类)

最严格的资源限制,最不可能被驱逐。Pod 中获得 Guaranteed QoS 需要满足

  • Pod 中的每个容器都必须设置 memory limit 和 memory request,且两者相等
  • Pod 中的每个容器都必须设置 CPU limit 和 CPU request,且两者相等
  • 以上所有值必须大于 0
yamlapiVersion: v1
kind: Pod
metadata:
  name: guaranteed-pod
spec:
  containers:
  - name: app
    image: nginx:1.25
    resources:
      requests:
        memory: "256Mi"
        cpu: "500m"
      limits:
        memory: "256Mi"   # limit == request → Guaranteed
        cpu: "500m"        # limit == request → Guaranteed

关键特性

  • 这些 Pod 不会被节点压力驱逐(除非所有容器的资源使用量都超过其 request)
  • 可以使用 static CPU 管理策略独占 CPU 核心
  • OOM 时 oom_score_adj-997(最不容易被 OOM Killer 杀死)

8.3 Burstable(可突发类)

有一些资源保证下限,但不要求严格的 limit 匹配。处于中间保护级别。

满足以下条件即为 Burstable

  • 不满足 Guaranteed 的条件
  • Pod 中至少有一个容器设置了内存或 CPU 的 request 或 limit
yamlapiVersion: v1
kind: Pod
metadata:
  name: burstable-pod
spec:
  containers:
  - name: app
    image: nginx:1.25
    resources:
      requests:
        memory: "256Mi"    # 设置了 request
        cpu: "500m"
      # 没有设置 limits → Burstable(limit 默认等于节点容量)

另一种 Burstable 场景——request 和 limit 不相等:

yamlresources:
  requests:
    memory: "256Mi"
    cpu: "500m"
  limits:
    memory: "512Mi"        # limit != request → Burstable
    cpu: "1000m"

关键特性

  • 有 request 下限保证,但可在资源充裕时"突发"使用更多资源
  • 只有在所有 BestEffort Pod 被驱逐后,才可能被驱逐
  • 资源使用量未超过 request 的 Burstable Pod 不会被驱逐
  • OOM 时 oom_score_adj 计算公式:min(max(2, 1000 - (1000 × memoryRequestBytes) / machineMemoryCapacityBytes), 999)

8.4 BestEffort(尽力而为类)

没有任何资源保证,优先级最低。Pod 是 BestEffort 当且仅当:

  • Pod 中所有容器都没有设置内存或 CPU 的 request 或 limit
  • Pod 本身也没有设置任何 Pod 级别的内存或 CPU 的 request 或 limit
yamlapiVersion: v1
kind: Pod
metadata:
  name: besteffort-pod
spec:
  containers:
  - name: app
    image: nginx:1.25
    # 没有 resources 字段 → BestEffort

关键特性

  • 可以使用节点上未被 Guaranteed 和 Burstable Pod 预留的所有剩余资源
  • 节点资源紧张时最先被驱逐
  • OOM 时 oom_score_adj1000(最容易被 OOM Killer 杀死)

8.5 QoS 判定逻辑总结

textPod 中每个容器是否都设置了 memory limit == memory request
        且 CPU limit == CPU request(都 > 0)?
        ├── 是 → Guaranteed
        └── 否 → 是否至少有一个容器设置了 memory 或 CPU 的 request 或 limit?
                ├── 是 → Burstable
                └── 否 → BestEffort

8.6 QoS 对驱逐的影响

QoS 类 驱逐优先级 OOM oom_score_adj 说明
BestEffort 最先被驱逐 1000 没有任何资源保证
Burstable(超过 request) 第二优先驱逐 ~2-999 取决于 request 占节点内存比例
Burstable(未超过 request) 最后被驱逐 同上 不会被驱逐,除非 Guaranteed Pod 也不够
Guaranteed 最后被驱逐 -997 只有所有容器都超过 request 才可能被驱逐

重要:kubelet 驱逐时不直接使用 QoS 类来决定驱逐顺序,而是综合考虑资源使用量、优先级和 request 的关系。但 QoS 类可以很好地预测驱逐顺序,两者高度一致。

8.7 独立于 QoS 的行为

以下行为与 QoS 类无关

  • 所有超过 limit 的容器都会被 kubelet 杀死并重启(不影响同 Pod 其他容器)
  • 容器超出 request 时,所在 Pod 成为驱逐候选对象(驱逐时整个 Pod 所有容器被终止)
  • kube-scheduler 的抢占逻辑不考虑 QoS 类,只看优先级
  • QoS 类在 Pod 创建时确定,整个生命周期不变

10. 九、节点压力驱逐(Node-pressure Eviction)

9.1 介绍

节点压力驱逐是 kubelet 主动终止 Pod 以回收节点资源的过程。当节点的内存、磁盘空间或 inode 等资源达到特定消耗水平时,kubelet 会驱逐 Pod 以防止资源耗尽(饥饿)。

textkubelet 监控节点资源 → 达到驱逐阈值 → 驱逐 Pod → 回收资源 → 节点恢复健康

节点压力驱逐不同于 API 发起的驱逐(kubectl drain、Descheduler 等):

维度 节点压力驱逐 API 发起驱逐
触发者 kubelet(自动) 用户/控制器(手动或自动)
遵守 PDB ❌ 不遵守 ✅ 遵守
遵守 terminationGracePeriodSeconds 硬驱逐不遵守,软驱逐有限遵守 ✅ 遵守
Pod 状态 设为 Failed 正常终止

9.2 驱逐信号与阈值

kubelet 使用驱逐信号(当前资源状态)与驱逐条件(阈值)进行比较,决定是否触发驱逐。

驱逐信号

驱逐信号 描述 Linux 专用
memory.available node.status.capacity[memory] - node.stats.memory.workingSet
nodefs.available 节点主文件系统可用空间
nodefs.inodesFree 节点主文件系统可用 inode
imagefs.available 容器镜像文件系统可用空间
imagefs.inodesFree 容器镜像文件系统可用 inode
containerfs.available 容器可写层文件系统可用空间
containerfs.inodesFree 容器可写层文件系统可用 inode
pid.available 可用进程 ID 数量

memory.available 的计算:在 Linux 上来自 cgroupfs,而非 free -m。kubelet 排除了 inactive_file(非活动 LRU 列表上的文件缓存),因为假设在压力下可回收。在 Windows 上来自全局内存提交级别。

文件系统分类

文件系统 存储内容
nodefs 节点主文件系统:本地磁盘卷、emptyDir、日志、临时存储(如 /var/lib/kubelet
imagefs 容器运行时专用:容器镜像(只读层)和可写层
containerfs 容器可写层专用(v1.31+ beta,镜像存储分离到 imagefs)

9.3 硬驱逐 vs 软驱逐

硬驱逐条件(Hard Eviction)

没有宽限期——达到阈值立即杀死 Pod:

yaml# kubelet 配置
evictionHard:
  memory.available: "100Mi"      # 内存不足 100Mi 时立即驱逐
  nodefs.available: "10%"        # 节点文件系统不足 10% 时立即驱逐
  imagefs.available: "15%"       # 镜像文件系统不足 15% 时立即驱逐
  nodefs.inodesFree: "5%"        # 节点 inode 不足 5% 时立即驱逐

kubelet 默认硬驱逐条件(Linux):

条件 默认值
memory.available < 100Mi
nodefs.available < 10%(Windows 节点)
imagefs.available < 15%
nodefs.inodesFree < 5%
imagefs.inodesFree < 5%

如果你修改了任何驱逐参数,其他参数的默认值会被清零。为保持默认值,设置 mergeDefaultEvictionSettings: true

软驱逐条件(Soft Eviction)

有宽限期——阈值持续超过宽限期后才触发驱逐:

yamlevictionSoft:
  memory.available: "1.5Gi"       # 软驱逐条件
evictionSoftGracePeriod:
  memory.available: "1m30s"       # 条件持续 1 分 30 秒后才触发
evictionMaxPodGracePeriod: 60     # Pod 终止的最大宽限期(秒)
参数 说明
evictionSoft 软驱逐阈值,形式 signal
evictionSoftGracePeriod 软条件需持续多久才触发驱逐
evictionMaxPodGracePeriod 软驱逐时 Pod 终止的最大允许宽限期。kubelet 取此值与 Pod 自身 terminationGracePeriodSeconds 的较小值

9.4 驱逐监控间隔

  • kubelet 每 housekeeping-interval(默认 10s)评估一次驱逐条件
  • 节点状况更新频率:--node-status-update-frequency(默认 10s
  • 节点状况转换过渡期:eviction-pressure-transition-period(默认 5m),防止震荡

9.5 节点状况映射

kubelet 将驱逐信号映射为节点状况,控制平面再将其映射为污点:

节点状况 对应驱逐信号 说明
MemoryPressure memory.available 节点内存压力,True 表示触发驱逐
DiskPressure nodefs.availablenodefs.inodesFreeimagefs.availableimagefs.inodesFreecontainerfs.availablecontainerfs.inodesFree 节点磁盘压力
PIDPressure pid.available(Linux) 节点 PID 不足

9.6 驱逐前资源回收

kubelet 在驱逐用户 Pod 之前,先尝试回收节点级资源

DiskPressure 时

text1. 对已死亡的 Pod 和容器进行垃圾收集
2. 删除未使用的容器镜像
3. 如果仍不足 → 驱逐用户 Pod

无 imagefs/containerfs(单一 nodefs):

textnodefs 达阈值 → 1. 垃圾收集死亡 Pod/容器 → 2. 删除未使用镜像 → 3. 驱逐 Pod

有 imagefs(镜像单独存储):

textnodefs 达阈值 → 垃圾收集死亡 Pod/容器
imagefs 达阈值 → 删除未使用镜像

9.7 驱逐时 Pod 的选择

kubelet 按以下优先级决定驱逐哪些 Pod(同时考虑三个因素):

  1. 资源使用量是否超过 request
  2. Pod 优先级
  3. 相对于 request 的资源使用量

驱逐顺序:

text第一优先(最容易被驱逐):
  BestEffort / Burstable Pod 中资源使用量超过其 request 的
  → 按优先级排序,优先级低的先被驱逐
  → 同等优先级按超出 request 的程度排序

第二优先:
  Burstable Pod 中资源使用量未超过其 request 的
  + Guaranteed Pod
  → 按优先级排序,优先级低的先被驱逐

关键理解:Guaranteed Pod 只有在所有容器都被指定了相等的 request 和 limit 时才保证不被驱逐。但如果系统守护进程(如 kubelet、journald)消耗的资源超过预留值,即使 Guaranteed Pod 也可能被驱逐——此时 kubelet 会选择优先级最低的 Guaranteed Pod。

9.8 最小驱逐回收

有时驱逐一个 Pod 只能回收少量资源,导致 kubelet 反复触发驱逐。通过 evictionMinimumReclaim 设置每次驱逐后额外回收的资源量:

yamlevictionHard:
  memory.available: "500Mi"
  nodefs.available: "1Gi"
evictionMinimumReclaim:
  memory.available: "0Mi"     # 回收到 500Mi 后不再额外回收
  nodefs.available: "500Mi"   # 回收到 1Gi 后额外回收 500Mi,直到 1.5Gi

9.9 OOM 行为与 QoS

如果 kubelet 来不及驱逐就被 OOM,内核的 oom_killer 接管。kubelet 根据 QoS 为每个容器设置 oom_score_adj

QoS oom_score_adj 说明
Guaranteed -997 几乎不会被 OOM 杀死
BestEffort 1000 最容易被 OOM 杀死
Burstable min(max(2, 1000 - 1000×request/总内存), 999) 内存 request 越大,越不容易被杀死

system-node-critical 优先级的 Pod 容器 oom_score_adj 也设为 -997

9.10 良好实践

驱逐策略与资源预留配合

确保调度器不会把 Pod 调度到"刚调度就触发驱逐"的节点:

text节点内存:10GiB
system-reserved: 1.5Gi  (系统预留 10% + 驱逐阈值 500Mi)
eviction-hard: memory.available<500Mi

这样系统预留 + 驱逐阈值 = 1.5Gi,节点实际上有 8.5Gi 可调度内存。

DaemonSet 与驱逐

如果希望 DaemonSet Pod 不被驱逐,给它设置足够高的 priorityClass

9.11 已知问题

问题 说明 缓解措施
kubelet 可能延迟感知内存压力 默认轮询 cAdvisor(10s 间隔),内存峰值可能错过 使用 --kernel-memcg-notification 启用即时通知
active_file 不被视为可用内存 大量 I/O 工作负载的页缓存被计入 active_file,导致 kubelet 误判内存不足 为 I/O 密集型容器设置相同的内存 limit 和 request

11. 调度策略对比总结

策略 作用方向 约束类型 粒度 适用场景
nodeSelector Pod → Node 硬约束 精确匹配 简单的节点分类
nodeAffinity Pod → Node 硬 + 軟 In/NotIn/Exists 等 复杂节点选择逻辑
podAffinity Pod → Pod 硬 + 軟 topologyKey 让相关 Pod 跑在一起
podAntiAffinity Pod → Pod 硬 + 軟 topologyKey 让 Pod 分散避免单点
topologySpreadConstraints Pod → 拓扑域 硬 + 軟 (maxSkew) topologyKey 精确均匀分布
Taint/Toleration Node → Pod NoSchedule/NoExecute 节点级 专用节点、故障隔离
PriorityClass Pod → 调度队列 抢占低优先级 Pod Pod 级 确保关键服务优先调度
QoS kubelet 驱逐排序 Guaranteed/Burstable/BestEffort Pod 级 资源紧张时的 Pod 保护级别
节点压力驱逐 kubelet → Pod 硬驱逐 + 軟驱逐 节点级 自动回收资源,防止节点饥饿
Descheduler 定期重平衡 驱逐违规 Pod 多策略 故障恢复、分布纠正
PDB 中断保护 限制自愿中断 Pod 组 维护期间保持可用

组合使用建议:生产环境中,通常用 PriorityClass 确保关键服务优先(如支付服务高优先级),nodeAffinity 硬策略确保 Pod 跑在指定区域,topologySpreadConstraints 双约束确保跨可用区和跨节点均匀分布,Taint/Toleration 实现专用节点准入控制,合理配置资源 requests/limits 以获得合适的 QoS 类(关键服务用 Guaranteed),PDB 保护维护期间的可用性,Descheduler 定期纠正分布偏差,节点压力驱逐作为兜底的自动保护机制。多者互补,缺一不可。

目录