K8s 系列 | 第 20 天:PriorityClass 与抢占式调度机制


title: K8s 系列 | 第 20 天:PriorityClass 与抢占式调度机制
date: 2026-07-06 08:00:00
tags:
– Kubernetes
– K8s系列
– DevOps
– PriorityClass
– 调度
– 资源管理
– 生产运维


第 20/30 天


K8s 系列 | 第 20 天:PriorityClass 与抢占式调度机制

引言

在 Kubernetes 集群中,Pod 调度遵循默认的 FIFO(先进先出)顺序——谁先创建谁先被调度。但在真实生产环境中,并非所有 Pod 都具有相同的重要性:关键系统组件(如 CoreDNS、监控 Agent)必须优先运行,而批处理任务可以在资源空闲时再执行。如果集群资源紧张,低优先级的批处理任务不应该阻塞高优先级的业务 Pod。

Kubernetes 的 PriorityClass(优先级类)抢占式调度(Preemption) 机制正是为解决这一问题而设计的。本文将从原理到实战,深入解析如何通过优先级管理保障关键业务 Pod 的调度稳定性。


一、核心概念

1.1 什么是 PriorityClass?

PriorityClass 是一个集群级别的资源对象(scheduling.k8s.io/v1 API 组),用于定义 Pod 的优先级值。优先级值是一个 32 位整数,值越高代表 Pod 越重要。当调度器无法为高优先级 Pod 找到可用节点时,会尝试抢占(Evict) 该节点上优先级较低的 Pod,腾出资源来运行高优 Pod。

📌 取值范围:优先级可以设置为 -2147483648 到 1000000000 之间的任何整数。Kubernetes 系统预留了较高和较低的范围给关键系统组件。

1.2 抢占式调度的工作原理

抢占式调度并非在每个调度周期都发生——它仅在以下条件同时满足时触发:

  1. 有一个待调度的 Pod(称为 preemptor,抢占者)
  2. 当前集群中没有节点能满足该 Pod 的资源请求
  3. 存在至少一个节点,如果能驱逐该节点上的部分低优先级 Pod,就能满足 preemptor 的需求

触发抢占后,调度器会执行以下步骤:

  1. 节点筛选:找出所有可能通过驱逐低优 Pod 来满足需求的节点
  2. 受害者选择:在每个候选节点上,按优先级从低到高选择要驱逐的 Pod
  3. 驱逐执行:向选中的受害者 Pod 发送优雅终止信号(SIGTERM)
  4. 资源释放:等待受害者 Pod 终止后,preemptor 被调度到该节点
# 一个简单的 priorityclass 示例
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000000
globalDefault: false
description: "关键业务 Pod 使用的高优先级"

1.3 优先级与 QoS 的关系

PriorityClass 与 Kubernetes 的 QoS(服务质量)机制是两个独立的维度:

维度 机制 影响范围
PriorityClass 调度优先级 + 抢占能力 调度阶段决策
QoS (Guaranteed/Burstable/BestEffort) 节点资源不足时的驱逐顺序 运行阶段决策

当节点资源耗尽时,kubelet 会先驱逐 BestEffort Pod,再驱逐 Burstable Pod,最后驱逐 Guaranteed Pod。但如果结合 PriorityClass,优先级高的 Pod 即使 QoS 等级更低,也会在节点压力下比低优先级的 Guaranteed Pod 更晚被驱逐


二、PriorityClass 的创建与管理

2.1 创建 PriorityClass 对象

PriorityClass 是集群级别的资源,不属于任何 Namespace。

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: production-critical
value: 1000000
globalDefault: false
description: "生产环境关键业务 Pod"
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: production-normal
value: 500000
globalDefault: false
description: "生产环境普通业务 Pod"
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: batch-jobs
value: 100000
globalDefault: false
description: "批处理任务,优先级最低"

创建命令:

# 创建上述三个 PriorityClass
kubectl apply -f priority-classes.yaml

# 查看集群中已有的 PriorityClass
kubectl get priorityclass

# 查看某个 PriorityClass 的详细信息
kubectl describe priorityclass production-critical

2.2 系统内置 PriorityClass

Kubernetes 自带两个系统 PriorityClass(在 1.14+ 版本中):

名称 用途
system-cluster-critical 2000000000 集群关键组件(CoreDNS, metrics-server 等)
system-node-critical 2000001000 节点级关键组件(kubelet, container-runtime 等)

这些值远高于用户自定义的 PriorityClass,确保系统组件永远不会被用户 Pod 抢占。

2.3 为 Pod 指定 PriorityClass

在 Pod 或 Deployment 的 spec 中通过 priorityClassName 字段引用:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
  namespace: production
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      priorityClassName: production-critical  # 引用 PriorityClass
      containers:
      - name: nginx
        image: nginx:1.25
        resources:
          requests:
            cpu: "500m"
            memory: "512Mi"
          limits:
            cpu: "1"
            memory: "1Gi"

⚠️ 注意:如果 Pod 未指定 priorityClassName,则使用集群默认 PriorityClass(即 globalDefault: true 的那个)。如果没有设置默认 PriorityClass,则 Pod 的默认优先级为 0


三、抢占式调度实战

3.1 模拟资源紧张场景

让我们创建一个实验来观察抢占行为。首先在一个 2 核节点上部署多个低优先级 Pod,填满资源:

# 创建低优先级 PriorityClass
cat <<EOF | kubectl apply -f -
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: low-priority
value: 1000
globalDefault: false
description: "低优先级 Pod,可被抢占"
EOF

# 部署 3 个低优先级 Pod,每个请求 800m CPU
# 在一个 2 核节点上,3 个 Pod 请求 2400m CPU,会超分但可能部分 pending
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: low-priority-workload
  namespace: default
spec:
  replicas: 3
  selector:
    matchLabels:
      app: low-priority
  template:
    metadata:
      labels:
        app: low-priority
    spec:
      priorityClassName: low-priority
      containers:
      - name: stress
        image: polinux/stress
        command: ["stress"]
        args: ["--cpu", "1", "--vm", "1", "--vm-bytes", "128M", "--timeout", "600s"]
        resources:
          requests:
            cpu: "800m"
            memory: "256Mi"
          limits:
            cpu: "1"
            memory: "512Mi"
EOF

3.2 部署高优先级 Pod 触发抢占

# 创建高优先级 PriorityClass
cat <<EOF | kubectl apply -f -
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000000
globalDefault: false
description: "高优先级 Pod,可抢占低优 Pod"
EOF

# 部署一个高优先级 Pod,需要 1.5 核 CPU
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: high-priority-preemptor
  namespace: default
spec:
  priorityClassName: high-priority
  containers:
  - name: critical-app
    image: nginx:1.25
    resources:
      requests:
        cpu: "1500m"
        memory: "256Mi"
      limits:
        cpu: "2"
        memory: "512Mi"
EOF

3.3 观察抢占事件

# 查看调度器日志,观察抢占事件
kubectl logs -n kube-system $(kubectl get pods -n kube-system -l component=kube-scheduler -o name | head -1) | grep -i preempt

# 查看被抢占 Pod 的事件
kubectl get events --sort-by='.lastTimestamp' | grep -i preempt

# 查看高优先级 Pod 的调度详情
kubectl describe pod high-priority-preemptor

# 查看被驱逐的 Pod 状态(应该处于 Terminating)
kubectl get pods -o wide | grep low-priority

输出示例中你会看到类似这样的事件:

2m33s       Normal    Preempted       pod/low-priority-workload-xxx    Preempted by kube-system/kube-scheduler-xxx
2m33s       Normal    Preempting      pod/high-priority-preemptor     preempting 1 pods on node worker-1

3.4 抢占行为的关键细节

非抢占式等待:从 Kubernetes 1.15 开始,Pod 可以设置 spec.preemptionPolicy: Never,表示该 Pod 不会抢占其他 Pod,而是排队等待资源释放。适用于批处理任务、CI/CD Job 等不需要紧急运行的 Pod。

apiVersion: v1
kind: Pod
metadata:
  name: non-preempting-pod
spec:
  priorityClassName: high-priority
  preemptionPolicy: Never  # 不抢占,仅用于调度时的优先级排序
  containers:
  - name: app
    image: nginx:1.25
    resources:
      requests:
        cpu: "1"
        memory: "512Mi"

优雅终止:被抢占的 Pod 会收到 SIGTERM 信号,并等待 terminationGracePeriodSeconds(默认 30s)完成优雅关闭。如果超时未退出,则被 SIGKILL 强制终止。

PDB 尊重:如果被抢占的 Pod 有 PodDisruptionBudget(PDB),调度器会尊重 PDB 的限制。例如,如果 PDB 要求某个应用至少有 2 个副本运行,那么抢占过程中调度器不会同时驱逐超过 PDB 允许数量的 Pod。


四、PriorityClass 的最佳实践

4.1 生产环境推荐的优先级分层

优先级范围       | 用途示例
─────────────────┼──────────────────────────────
1000000000       | 系统关键组件(系统保留)
 900000000       | 集群基础设施(ingress-controller, cert-manager, monitoring)
 800000000       | 在线关键业务(用户请求路径上的服务)
 500000000       | 在线普通业务(非关键路径的后端服务)
 100000000       | 离线批处理(日志分析、数据清洗)
  50000000       | 开发测试 Pod
       0         | 默认优先级

4.2 实施建议

  1. 创建统一的 PriorityClass 清单:作为集群初始化的一部分,在同一份 YAML 中定义所有优先级层,并纳入 GitOps 管理。

  2. 避免过多层级:3~5 个层级通常足够。过多层级会增加调度复杂性,且实际效果不明显。

  3. 为关键命名空间设置默认 PriorityClass

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: production-default
value: 500000
globalDefault: false  # 不要设置为全局默认,否则所有未指定优先级的 Pod 都会使用这个值
description: "production 命名空间的默认优先级"

然后通过 Admission Controller(如 Kyverno 或 OPA/Gatekeeper)自动注入:

# Kyverno 策略:为 production 命名空间的 Pod 注入 PriorityClass
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: inject-priority-class
spec:
  rules:
  - name: assign-production-priority
    match:
      resources:
        kinds:
        - Pod
        namespaces:
        - production
    mutate:
      patchStrategicMerge:
        spec:
          priorityClassName: production-default
  1. 监控抢占事件:频繁的抢占说明集群资源规划不合理,应考虑扩容或优化 Pod 资源请求。
# 监控抢占事件频率
kubectl get events --all-namespaces --field-selector reason=Preempted --watch

4.3 常见陷阱与注意事项

陷阱 说明 解决方案
优先级反转 低优先级的 Critical Pod 被高优先级的非 Critical Pod 抢占 仔细规划优先级数值,留足安全间距
抢占风暴 抢占一个 Pod 后引发连环抢占 使用 PDB 限制最小可用副本数
优先级膨胀 每个团队都要求最高优先级 建立审批流程,通过 Namespace quota 间接控制
未设置优先级 所有 Pod 默认优先级为 0 设置 globalDefault PriorityClass 或通过准入控制器注入

五、PriorityClass 与其它调度特性的协同

5.1 与 PodDisruptionBudget 的交互

PDB 限制了自愿中断(包括抢占)时允许的最大不可用副本数:

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: web-app-pdb
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: web

如果某个节点上有 3 个 web-app 副本,PDB 要求至少 2 个可用,则抢占器一次最多只能驱逐 1 个 web-app Pod。调度器会循环尝试,直到满足资源需求。

5.2 与 Cluster Autoscaler 的协作

当 Cluster Autoscaler 与 PriorityClass 配合使用时,会产生更智能的扩容决策:

  • 如果高优先级 Pod 因资源不足无法调度,但抢占失败(没有足够低优先级 Pod 可抢占),Cluster Autoscaler 会触发节点扩容
  • 低优先级 Pod 阻塞调度时,Cluster Autoscaler 可能不会立即扩容,因为低优 Pod 的调度优先级较低

建议在生产环境中将 PriorityClass 与 Cluster Autoscaler 配合使用,实现”高优保障 + 弹性扩容”的完美组合。


总结

本文深入解析了 Kubernetes PriorityClass 与抢占式调度机制:

要点 说明
PriorityClass 定义 Pod 调度优先级的集群资源对象
抢占式调度 高优先 Pod 通过驱逐低优先 Pod 获取资源
preemptionPolicy 设为 Never 可使 Pod 不抢占,仅用于优先级排序
PDB 协同 PDB 限制抢占时对应用的破坏程度
最佳实践 3~5 个优先级层 + 统一管理 + 抢占事件监控

合理使用 PriorityClass 可以有效保障关键业务 Pod 的调度优先级,避免”低优先级任务占着资源不让”的尴尬局面。但也要记住:优先级机制是”冷启动”的权宜之计,持续的资源规划和容量管理才是根本解决之道。


下期预告

第 21 天我们将探讨 资源配额与 LimitRange:多租户资源管控,看如何通过 ResourceQuota 和 LimitRange 防止单个 Namespace 耗尽集群资源,实现多团队环境下的公平分配。


系列目录

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片快捷回复

    暂无评论内容