跳到主要内容

1 篇博文 含有标签「Scheduler」

Kubernetes 调度器

查看所有标签

Kubernetes 全景解析 (4):调度器、资源管理与弹性伸缩

· 阅读需 21 分钟
Rainy
雨落无声,代码成诗 —— 致力于技术与艺术的极致平衡

前言

在 Kubernetes 集群中,调度器(Scheduler)扮演着"决策中枢"的角色——每一个 Pod 应该运行在哪个节点上,都由它来决定。而资源管理(Resource Management)与弹性伸缩(Autoscaling)则是保障集群高效、稳定运行的关键机制。这三者共同构成了 K8s 资源层的核心能力。

本文将从资源模型出发,深入剖析 kube-scheduler 的调度机制,全面讲解 HPA/VPA/Cluster Autoscaler 三级弹性伸缩策略,并介绍 ResourceQuota、LimitRange、PriorityClass 等资源治理工具,帮助你构建一套完整的 K8s 资源管理知识体系。


一、资源模型基础

1.1 Kubernetes 可管理资源类型

Kubernetes 对容器可以使用的资源进行了抽象,目前支持以下几种核心资源类型:

资源类型缩写说明可压缩
CPUcpu计算资源,以核心(core)为单位,支持小数(如 0.5 = 500m)
Memorymemory内存资源,以字节为单位(支持 Ki/Mi/Gi)
Ephemeral Storageephemeral-storage临时存储(日志、EmptyDir 等),以字节为单位
Extended Resourcesnvidia.com/gpu扩展资源(如 GPU、Infiniband),由设备插件注册
可压缩 vs 不可压缩资源
  • 可压缩资源(Compressible):CPU 是可压缩的。当 Pod 超过 CPU Limit 时,不会被杀掉,而是被限流(throttled),表现为性能下降。
  • 不可压缩资源(Incompressible):内存和存储是不可压缩的。当 Pod 超过 Memory Limit 时,会被 OOM Killer 杀掉并重启。

理解这一区别对于合理设置资源配额至关重要。

1.2 Request vs Limit

Kubernetes 通过 requestslimits 两个字段来控制容器的资源使用:

  • Request(请求量):调度器依据此值决定将 Pod 调度到哪个节点(保证最低可用资源)
  • Limit(限制量):运行时容器可使用的资源上限(硬限制)
apiVersion: v1
kind: Pod
metadata:
name: resource-demo
spec:
containers:
- name: app
image: nginx:1.25
resources:
requests:
cpu: "250m" # 0.25 核,调度依据
memory: "256Mi" # 256 MiB,调度依据
limits:
cpu: "500m" # 0.5 核,运行时上限
memory: "512Mi" # 512 MiB,运行时上限,超出则 OOM Kill
Limit 可以省略吗?

如果只设置了 requests 而没有设置 limits,在默认情况下(未配置 LimitRange),limits 默认等于节点可分配资源上限,这意味着容器可以无限使用资源。在生产环境中,强烈建议同时设置 requests 和 limits

1.3 QoS 服务质量类别

Kubernetes 根据资源配额的设置方式,将 Pod 分为三个 QoS(Quality of Service)等级。当节点资源不足时,K8s 会优先驱逐低 QoS 的 Pod。

三个 QoS 等级的详细对比:

QoS 等级条件CPU 行为内存行为驱逐优先级
Guaranteed所有容器 Request == Limit(CPU + Memory)稳定,不会被限流超限则 OOM Kill最低(最后驱逐)
Burstable至少一个容器设置了 Request 或 Limit(但不满足 Guaranteed)可能被限流超限则 OOM Kill中等
BestEffort所有容器均未设置 Request 和 Limit最先被限流最先被 OOM Kill最高(最先驱逐)
生产环境建议

对于核心业务(如支付、订单),建议设置为 Guaranteed 级别,确保资源独占;对于一般业务(如日志处理),设置为 Burstable 即可;BestEffort 仅适用于测试或临时任务。


二、kube-scheduler:调度器深度解析

2.1 调度流程概览

kube-scheduler 是 Kubernetes 的核心组件之一,负责将新创建的、未调度的 Pod 分配到合适的节点上。根据官方文档,kube-scheduler 通过**两步操作(2-step operation)**完成节点选择:

kube-scheduler selects a node for the pod in a 2-step operation: 1. Filtering 2. Scoring —— Kubernetes Scheduler 官方文档

Scheduling Profiles vs Scheduling Policies

官方文档指出,有两种方式配置调度器的过滤和打分行为:

  1. Scheduling Profiles(推荐):通过配置插件(Plugins)实现不同调度阶段,包括 QueueSortFilterScoreBindReservePermit 等扩展点。
  2. Scheduling Policies(旧版方式):通过配置 Predicates(过滤)和 Priorities(打分)来定义调度策略。

2.2 过滤阶段(Filtering)

过滤阶段的目标是快速排除不满足条件的节点,缩小候选范围。通过过滤的节点称为可行节点(Feasible Nodes)。以下是常见的过滤策略:

过滤策略说明配置方式
NodeName直接指定节点名称,跳过所有调度逻辑spec.nodeName
nodeSelector基于节点标签的简单匹配spec.nodeSelector
nodeAffinity基于节点标签的高级匹配(支持软约束)spec.affinity.nodeAffinity
podAffinityPod 亲和性,倾向于与某些 Pod 部署在一起spec.affinity.podAffinity
podAntiAffinityPod 反亲和性,倾向于远离某些 Podspec.affinity.podAntiAffinity
Taint/Toleration节点污点与 Pod 容忍度匹配spec.tolerations
资源检查节点剩余资源是否满足 Pod 的 Request自动
Volume 检查节点是否能挂载 Pod 所需的存储卷自动

2.3 打分阶段(Scoring)

经过过滤后,调度器对剩余的候选节点进行打分,选择得分最高的节点。常见的打分策略包括:

打分策略权重说明
NodeResourcesFit默认 1资源均衡分配,倾向于选择资源使用率更均衡的节点
NodeAffinity默认 1满足节点亲和性软约束的节点获得加分
PodAffinity默认 1满足 Pod 亲和性软约束的节点获得加分
ImageLocality默认 1节点上已存在 Pod 所需镜像时加分(减少拉取时间)
TaintToleration默认 1容忍节点污点的 Pod 获得加分
InterPodAffinity默认 1Pod 间亲和/反亲和性评分
VolumeBinding默认 1PV/PVC 绑定评分
自定义调度器与 Scheduler Framework

Kubernetes 允许你编写自定义调度器(Custom Scheduler),通过实现 Scheduler Framework 的扩展点(Extension Points)来插入自定义逻辑。Scheduling Profiles 配置中支持的插件阶段包括:QueueSortFilterScoreBindReservePermitPreBindPostBind 等。更多详情请参阅 kube-scheduler 配置参考

2.4 nodeSelector:简单节点选择

nodeSelector 是最简单的节点选择方式,通过标签匹配来约束 Pod 的调度目标:

apiVersion: v1
kind: Pod
metadata:
name: gpu-pod
spec:
nodeSelector:
gpu: "nvidia-tesla-t4" # 只调度到具有此标签的节点
disktype: "ssd"
containers:
- name: cuda-app
image: nvidia/cuda:12.0-base
resources:
limits:
nvidia.com/gpu: "1" # 申请 1 块 GPU

2.5 nodeAffinity:高级节点亲和性

nodeAffinity 支持更灵活的匹配规则,包括硬约束(required)软约束(preferred)

apiVersion: v1
kind: Pod
metadata:
name: affinity-pod
spec:
affinity:
nodeAffinity:
# 硬约束:必须满足,否则 Pod 无法调度
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values: ["us-east-1a", "us-east-1b"]
- key: node-type
operator: NotIn
values: ["spot-instance"]
# 软约束:尽量满足,不满足也能调度
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
preference:
matchExpressions:
- key: node-role.kubernetes.io/worker
operator: In
values: ["high-memory"]
containers:
- name: app
image: nginx:1.25
操作符说明

nodeAffinity 支持的操作符:InNotInExistsDoesNotExistGtLt。其中 GtLt 仅用于数值比较。

2.6 podAffinity 与 podAntiAffinity

Pod 亲和性用于控制 Pod 之间的部署位置关系:

apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
affinity:
# Pod 亲和性:与 cache Pod 部署在同一可用区
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: cache
topologyKey: topology.kubernetes.io/zone
# Pod 反亲和性:不同副本尽量分布在不同节点
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: web
topologyKey: kubernetes.io/hostname
containers:
- name: web
image: nginx:1.25
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
topologyKey 的重要性

topologyKey 是亲和性规则的关键字段,它定义了拓扑域的划分维度。常用的值包括:

  • kubernetes.io/hostname:节点级别
  • topology.kubernetes.io/zone:可用区级别
  • topology.kubernetes.io/region:区域级别

选择合适的 topologyKey 对于实现高可用部署至关重要。

2.7 Taint 与 Toleration:污点容忍机制

Taint(污点)是作用于节点上的标记,用于排斥不容忍该污点的 Pod。Toleration(容忍度)是 Pod 上的声明,表示可以容忍特定的污点。

# 给节点添加污点
# kubectl taint nodes node1 dedicated=gpu:NoSchedule

apiVersion: v1
kind: Pod
metadata:
name: gpu-workload
spec:
# 声明容忍度
tolerations:
- key: "dedicated"
operator: "Equal"
value: "gpu"
effect: "NoSchedule"
- key: "node-role.kubernetes.io/master"
operator: "Exists"
effect: "NoSchedule"
containers:
- name: cuda-app
image: nvidia/cuda:12.0-base

Taint 的 effect 类型:

Effect说明
NoSchedule不调度新的不容忍 Pod(已运行的 Pod 不受影响)
PreferNoSchedule尽量不调度(软约束)
NoExecute不调度,且驱逐已运行的不容忍 Pod
常见内置 Taint

Kubernetes 自动为特定场景的节点添加 Taint:

  • node.kubernetes.io/not-ready:节点未就绪
  • node.kubernetes.io/unreachable:节点不可达
  • node.kubernetes.io/memory-pressure:内存压力
  • node.kubernetes.io/disk-pressure:磁盘压力
  • node.kubernetes.io/network-unavailable:网络不可用
  • node.kubernetes.io/unschedulable:节点被标记为不可调度(cordon)

三、HPA:水平 Pod 自动伸缩

3.1 HPA 工作原理

Horizontal Pod Autoscaler(HPA)通过监控 Pod 的资源使用指标,自动调整 Deployment/ReplicaSet/StatefulSet 的副本数量,使应用能够根据负载变化自动扩缩。

3.2 扩缩算法详解

HPA 的核心算法如下:

期望副本数 = ceil(当前副本数 × (当前指标值 / 目标指标值))
算法示例

假设 Deployment 当前有 4 个副本,目标 CPU 利用率为 50%:

  • 当前平均 CPU 利用率为 80% → 期望副本数 = ceil(4 × 80/50) = ceil(6.4) = 7
  • 当前平均 CPU 利用率为 20% → 期望副本数 = ceil(4 × 20/50) = ceil(1.6) = 2

注意:HPA 不会将副本数缩减到低于 spec.replicas.minReplicas(默认为 1)。

3.3 指标类型

HPA 支持四种指标类型:

指标类型数据来源示例
ResourceMetrics Server(CPU/内存)CPU 利用率 50%
Container ResourceMetrics Server(容器级别)容器内存使用量
Object自定义指标(如 Ingress QPS)每秒请求数
External外部指标系统(如 Prometheus)Kafka 队列积压消息数

3.4 行为配置

从 Kubernetes 1.18 开始,HPA 引入了 behavior 字段,允许精细控制扩缩行为:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web-app
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
- type: Resource
resource:
name: memory
target:
type: AverageValue
averageValue: "500Mi"
behavior:
# 缩容冷却窗口与策略
scaleDown:
stabilizationWindowSeconds: 300 # 5 分钟内不重复缩容
policies:
- type: Percent
value: 10 # 每次最多缩容 10%
periodSeconds: 60 # 每 60 秒允许一次缩容
- type: Pods
value: 2 # 或每次最多缩容 2 个 Pod
periodSeconds: 60
selectPolicy: Min # 取两种策略中更保守的
# 扩容策略
scaleUp:
stabilizationWindowSeconds: 0 # 扩容不需要冷却
policies:
- type: Percent
value: 100 # 允许一次扩容 100%
periodSeconds: 15 # 每 15 秒允许一次扩容
- type: Pods
value: 4 # 或每次最多扩容 4 个 Pod
periodSeconds: 15
selectPolicy: Max # 取两种策略中更激进的
扩缩行为建议
  • 扩容应该快:用户不希望等待太久,建议设置较短的 periodSeconds(如 15s)
  • 缩容应该慢:避免频繁缩容导致的服务抖动,建议设置较长的 stabilizationWindowSeconds(如 300s)
  • 使用 selectPolicy:扩容选 Max(激进),缩容选 Min(保守)

四、VPA:垂直 Pod 自动伸缩

4.1 VPA 工作模式

Vertical Pod Autoscaler(VPA)通过调整 Pod 的 CPU 和内存 Request/Limit 来实现垂直伸缩。VPA 支持三种工作模式:

模式说明适用场景
Auto自动更新 Pod 的资源配额(会重启 Pod)非关键业务,可容忍重启
Recreate自动更新,且在更新时重启 Pod同 Auto
Off仅提供建议,不执行变更评估阶段,收集数据

4.2 VPA 与 HPA 的协作与冲突

VPA 与 HPA 的冲突

VPA 和 HPA 不能同时基于 CPU/内存指标工作。原因很简单:HPA 通过增加副本数来降低单个 Pod 的资源使用率,而 VPA 通过增加单个 Pod 的资源配额来满足需求。两者同时基于 CPU/内存工作会导致"扩容循环"——HPA 增加 Pod 数量导致单个 Pod 负载降低,VPA 随之降低资源配额,然后 HPA 又需要更多 Pod 来满足需求。

解决方案:HPA 基于 CPU/内存指标伸缩,VPA 仅基于自定义指标(如内存使用量)工作;或者使用 HPA 基于 CPU 伸缩,VPA 仅调整内存。

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: web-app-vpa
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: web-app
updatePolicy:
updateMode: "Auto" # Auto / Recreate / Off
resourcePolicy:
containerPolicies:
- containerName: "*"
minAllowed:
cpu: "100m"
memory: "128Mi"
maxAllowed:
cpu: "2"
memory: "2Gi"
controlledResources: ["cpu", "memory"]
recommenders:
- name: custom-recommender # 可自定义推荐器

五、Cluster Autoscaler:节点级伸缩

5.1 CA 工作原理

Cluster Autoscaler(CA)负责在集群级别自动调整节点数量。当 Pod 因资源不足而无法调度(Pending)时,CA 会自动添加新节点;当节点利用率持续偏低时,CA 会自动移除空闲节点。

CA 的核心工作逻辑:

  1. 扩容触发:检测到有 Pod 处于 Pending 状态,且原因是资源不足
  2. 评估节点组:根据 Pod 的资源需求、节点亲和性等约束,选择合适的节点组
  3. 创建节点:调用云厂商 API 创建新节点
  4. 缩容触发:节点利用率持续低于阈值(默认 50%),且节点上的 Pod 可以被迁移到其他节点
  5. 驱逐 Pod:安全驱逐节点上的 Pod(非 Critical Pod、非 DaemonSet Pod)
  6. 删除节点:调用云厂商 API 删除节点

5.2 CA 与 HPA 的配合

CA 和 HPA 的配合构成了 Kubernetes 的两级弹性伸缩体系

层级组件伸缩维度响应速度粒度
第一级HPAPod 数量秒级细粒度
第二级Cluster Autoscaler节点数量分钟级粗粒度

典型工作流程:负载增加 → HPA 增加 Pod 副本 → 节点资源不足 → Pod Pending → CA 添加新节点 → 新 Pod 被调度到新节点。

5.3 云厂商集成

CA 需要与云厂商的 API 集成来管理节点池:

云厂商节点组实现CA 配置
AWSASG(Auto Scaling Group) / EKS Managed Node Group--nodegroup-auto-discovery=asg:tag=k8s.io/cluster-autoscaler/enabled
GCPMIG(Managed Instance Group) / GKE Node Pool自动发现
AzureVMSS(Virtual Machine Scale Set) / AKS Node Pool--nodegroup-auto-discovery=asg:tag=k8s.io/cluster-autoscaler/enabled
阿里云ECI / ACK 节点池--nodegroup-auto-discovery=asg:tag=k8s.io/cluster-autoscaler/enabled
CA 的安全限制

Cluster Autoscaler 不会驱逐以下 Pod:

  • 通过 PDB(PodDisruptionBudget)保护的 Pod(驱逐会违反最小可用数)
  • 没有被 Controller 管理的裸 Pod(Bare Pod)
  • 使用本地存储的 Pod(数据会随节点删除而丢失)
  • kube-system 命名空间中且不是 DaemonSet 管理的 Pod
  • 配置了 "cluster-autoscaler.kubernetes.io/safe-to-evict": "false" 注解的 Pod

六、资源配额与限制

6.1 ResourceQuota:命名空间级资源配额

ResourceQuota 用于限制一个命名空间中可以创建的资源总量,防止某个团队或项目占用过多集群资源。

apiVersion: v1
kind: ResourceQuota
metadata:
name: team-a-quota
namespace: team-a
spec:
hard:
# 计算资源配额
requests.cpu: "20" # 该命名空间最多请求 20 核 CPU
requests.memory: "40Gi" # 最多请求 40Gi 内存
limits.cpu: "40" # 最多限制 40 核 CPU
limits.memory: "80Gi" # 最多限制 80Gi 内存
# 对象数量配额
pods: "50" # 最多 50 个 Pod
services: "10" # 最多 10 个 Service
persistentvolumeclaims: "20" # 最多 20 个 PVC
configmaps: "20" # 最多 20 个 ConfigMap
secrets: "20" # 最多 20 个 Secret
# 存储
requests.storage: "100Gi" # 最多请求 100Gi 存储
ResourceQuota 的作用范围

ResourceQuota 只限制设置了 requestslimits 的 Pod。如果一个 Pod 没有设置资源请求,它不会计入 ResourceQuota 的配额,但也不会被调度(除非命名空间配置了 LimitRange 来提供默认值)。

6.2 LimitRange:默认资源限制

LimitRange 用于为命名空间中的 Pod 或 Container 设置默认的资源请求和限制值,确保即使 Pod 的 YAML 中没有声明资源,也能获得合理的默认值。

apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: team-a
spec:
limits:
# Container 级别默认值
- type: Container
default: # 默认 Limit
cpu: "500m"
memory: "512Mi"
defaultRequest: # 默认 Request
cpu: "100m"
memory: "128Mi"
max: # 最大允许值
cpu: "2"
memory: "2Gi"
min: # 最小允许值
cpu: "50m"
memory: "64Mi"
# Pod 级别限制
- type: Pod
max:
cpu: "4"
memory: "4Gi"

6.3 PriorityClass:优先级与抢占

PriorityClass 用于定义 Pod 的优先级。当集群资源不足时,高优先级的 Pod 可以抢占低优先级 Pod 的资源。

# 定义高优先级类
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
description: "核心业务 Pod,优先调度"
value: 1000000
globalDefault: false
preemptionPolicy: PreemptLowerPriority
# 定义低优先级类
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: low-priority
description: "批处理任务,资源空闲时运行"
value: 100
globalDefault: false
preemptionPolicy: PreemptLowerPriority
# 使用优先级类
apiVersion: v1
kind: Pod
metadata:
name: critical-app
spec:
priorityClassName: high-priority
containers:
- name: app
image: nginx:1.25
resources:
requests:
cpu: "1"
memory: "1Gi"
limits:
cpu: "2"
memory: "2Gi"
抢占机制

当高优先级 Pod 无法调度时,调度器会尝试驱逐低优先级 Pod 来释放资源。抢占过程分为两步:

  1. 驱逐(Eviction):找到可以抢占的低优先级 Pod
  2. 等待(Waiting):等待被驱逐的 Pod 优雅终止(graceful shutdown)

系统保留的 PriorityClass:

  • system-cluster-critical(值 2000000000):系统组件专用
  • system-node-critical(值 2000001000):节点关键组件专用(如 kube-proxy)

七、调度策略最佳实践

7.1 生产环境调度建议

场景建议策略
核心业务Guaranteed QoS + nodeAffinity 硬约束 + 高优先级 + PDB 保护
批处理任务BestEffort/Burstable QoS + 低优先级 + 反亲和性避免影响在线业务
GPU 工作负载nodeSelector + Taint/Toleration + ResourceQuota 限制 GPU 数量
高可用部署podAntiAffinity(hostname 级别)+ topologySpreadConstraints
多可用区部署nodeAffinity(zone 级别)+ topologySpreadConstraints

7.2 常见调度问题排查

问题现象可能原因排查命令
Pod 一直 Pending资源不足 / 亲和性不满足 / Taint 不匹配kubectl describe pod <name>
Pod 频繁被驱逐节点资源压力 / QoS 等级过低kubectl get events --sort-by=.metadata.creationTimestamp
节点资源不均衡缺少反亲和性配置 / 打分权重不合理kubectl top nodes
HPA 不生效未安装 Metrics Server / 指标未就绪kubectl get hpa -w
VPA 与 HPA 冲突同时基于 CPU/内存工作检查 VPA 和 HPA 的指标配置
排查 Pending Pod 的黄金法则

当 Pod 处于 Pending 状态时,第一步永远是执行 kubectl describe pod <name>,查看 Events 部分。调度器会在 Events 中详细记录过滤失败的原因,例如:

0/5 nodes are available: 1 Insufficient cpu, 3 node(s) had taint {node.kubernetes.io/disk-pressure:}, that the pod didn't tolerate, 1 node(s) didn't match node selector.

这条信息清晰地告诉你:1 个节点 CPU 不足,3 个节点有磁盘压力污点,1 个节点不匹配 nodeSelector。


八、官方文档参考

本文内容基于 Kubernetes v1.34 官方文档校验,以下是相关官方文档链接:

调度相关

资源管理相关

配额与限制


九、本章小结

本文深入剖析了 Kubernetes 调度与资源管理的核心机制,主要知识点回顾如下:

  1. 资源模型:K8s 通过 Request/Limit 控制容器的资源使用,QoS 等级(Guaranteed/Burstable/BestEffort)决定了资源不足时的驱逐顺序。

  2. 调度器:kube-scheduler 通过官方定义的"Filtering → Scoring"两步操作完成节点选择,随后通过 Binding 将 Pod 绑定到目标节点。nodeSelector/nodeAffinity/podAffinity/Taint-Toleration 提供了灵活的调度约束。

  3. 弹性伸缩

    • HPA:水平伸缩,调整副本数量,响应最快
    • VPA:垂直伸缩,调整资源配额,适合优化资源利用率
    • Cluster Autoscaler:节点级伸缩,配合 HPA 实现完整的弹性能力
  4. 资源治理:ResourceQuota 限制命名空间资源总量,LimitRange 提供默认资源值,PriorityClass 实现优先级与抢占。

  5. 最佳实践:核心业务使用 Guaranteed QoS + 高优先级 + PDB 保护;批处理任务使用低优先级 + 反亲和性;多可用区部署使用 topologySpreadConstraints。

掌握这些机制,你就能在生产环境中构建一个高效、稳定、弹性的 Kubernetes 资源管理体系。