k8s 调度与自动伸缩

导读
Kubernetes 把 Pod 放到哪个节点上运行,由调度器决定;Pod 数量随负载增减,则由自动伸缩机制负责。调度和伸缩是集群资源利用率的两个核心杠杆,前者决定资源分配的合理性,后者决定系统面对流量波动的弹性能力。掌握这两层机制,才能在成本与稳定性之间找到平衡点。
核心概念
调度器为每个新建的 Pod 选择最合适的节点,整个过程分为过滤与打分两个阶段。过滤阶段执行 Predicate 函数,淘汰不符合硬性条件的节点,比如资源不足、污点不匹配、亲和性冲突;打分阶段对剩余候选节点执行 Priority 函数,按资源均衡度、亲和性权重等维度逐项评分,最终把 Pod 绑定到得分最高的节点。这套两段式设计让调度器既能快速缩小候选范围,又能在可行节点中挑选最优解。
图 1:调度器过滤与打分两阶段决策流程
图 1 描绘了调度器从入队到绑定的完整路径,Predicate 阶段的三道关卡逐一筛除不符合硬性条件的节点,Priority 阶段三类评分加权汇总后选出最终节点。整个流程先保正确再求最优,被过滤淘汰的节点不再参与打分。资源请求与限制是过滤阶段的第一道依据,容器通过 resources.requests 声明运行所需 CPU 和内存,调度器据此判断节点剩余资源是否充足;resources.limits 约束容器能使用的上限,超过内存限制会触发 OOMKilled,超过 CPU 限制会被节流。请求值是调度的输入,限制值是运行期的硬约束,二者不要混为一谈。
节点亲和性让 Pod 表达对节点偏好的硬约束与软偏好。requiredDuringSchedulingIgnoredDuringExecution 属于硬约束,必须满足才能调度,相当于带标签增强版的 nodeSelector;preferredDuringSchedulingIgnoredDuringExecution 属于软偏好,打分阶段给予加权倾斜。Pod 亲和性与反亲和性则把约束对象从节点转移到其他 Pod,比如把缓存服务与 Web 服务调度到同节点降低访问延迟,或把同一应用的副本打散到不同节点避免单点故障。
污点与容忍度从节点角度控制谁能调度上来。节点通过 kubectl taint 打上污点,默认效果 NoSchedule 拒绝新 Pod,NoExecute 还会驱逐已在运行的 Pod。Pod 通过 tolerations 声明能容忍哪些污点,只有匹配的污点才允许调度。这套机制常用于独占节点场景,比如把 GPU 节点打污点,只让训练任务通过容忍度占用。
图解原理
HPA(Horizontal Pod Autoscaler)根据指标波动自动调整 Deployment 副本数,把人工扩容变成持续运行的闭环。它不直接操作 Pod,而是周期性拉取指标、计算目标副本数、更新 Deployment 的 replicas 字段,再由 Deployment Controller 真正创建或删除 Pod。整个链路依赖 Metrics Server 提供 CPU 与内存数据,自定义指标则需要部署 Prometheus Adapter 把业务指标转换成 Metrics API 能识别的格式。
图 2:HPA 采集指标并触发 Deployment 扩缩容的交互过程
图 2 展示了 HPA 的控制回路,目标副本数由当前副本、平均指标与目标利用率的乘除关系得出,超过阈值即触发扩容或缩容。目标副本数的计算公式为 desiredReplicas = currentReplicas * (currentMetric / targetMetric),当所有 Pod 的平均 CPU 利用率超过目标值,副本数按比例放大;低于目标值时按比例缩小。HPA 默认 15 秒轮询一次,并引入冷却窗口防止指标抖动导致副本数频繁震荡,扩容动作较快,缩容相对保守,默认要等 5 分钟稳定窗口才执行。
基于 CPU 的伸缩要求容器必须配置 resources.requests.cpu,否则 HPA 无法计算利用率,状态会显示为 unknown。自定义指标伸缩覆盖两类场景:Pod 级别指标如每秒请求数 QPS,适合按业务负载扩容;Object 级别指标如消息队列堆积深度,适合按外部依赖状态扩容。自定义指标的接入要部署 Prometheus Adapter 或 KEDA,把外部监控系统对接到 Kubernetes 的 Metrics API 聚合层,HPA 才能像读 CPU 一样读取这些业务指标。
VPA(Vertical Pod Autoscaler)做纵向伸缩,自动调整容器的 CPU 与内存请求值。它适用于无法水平扩展的有状态应用,比如单实例数据库。VPA 有 Off、Initial、Auto 三种更新模式,Auto 模式会驱逐并重建 Pod 以应用新请求值,对在线服务有中断风险。VPA 与 HPA 不宜同时作用于同一维度的资源,二者会相互冲突,官方建议 VPA 管内存、HPA 管 CPU。
Cluster Autoscaler 从集群层面伸缩节点数量。当 Pod 因资源不足处于 Pending 状态,它会向云厂商申请扩容节点;当节点利用率长期偏低,它会迁移 Pod 并回收节点。该组件与云厂商深度耦合,依赖 Cluster API 或各厂商的节点组接口。在节点扩容延迟较大的环境里,可以配合 HPA 的冷却窗口预留缓冲,避免流量激增时 Pod 等待节点就绪。
动手实操
先给 Deployment 配置资源请求与限制,这是 HPA 计算利用率的前提条件。
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
replicas: 2
selector:
matchLabels:
app: web-app
template:
metadata:
labels:
app: web-app
spec:
containers:
- name: nginx
image: nginx:1.25
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi创建基于 CPU 利用率的 HPA,目标值设为 50%,副本数在 1 到 10 之间波动。
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web-app
minReplicas: 1
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50查看 HPA 状态与实时指标,-w 参数持续 watch 副本数与利用率的变化。
kubectl get hpa web-app-hpa -w
kubectl describe hpa web-app-hpa自定义指标伸缩需要对接 Metrics API 聚合层。以下示例按每秒请求数扩容,前提是集群已部署 Prometheus Adapter 并注册了 http_requests_per_second 指标。
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-app-custom-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web-app
minReplicas: 2
maxReplicas: 20
metrics:
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: "100"节点亲和性示例,把 Pod 调度到带 disktype=ssd 标签的节点。
apiVersion: v1
kind: Pod
metadata:
name: affinity-demo
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disktype
operator: In
values:
- ssd
containers:
- name: app
image: nginx:1.25污点与容忍度配合使用。先给节点打污点,再让特定 Pod 通过容忍度调度上去。
kubectl taint nodes node-1 dedicated=gpu:NoScheduleapiVersion: v1
kind: Pod
metadata:
name: gpu-job
spec:
tolerations:
- key: "dedicated"
operator: "Equal"
value: "gpu"
effect: "NoSchedule"
containers:
- name: trainer
image: tensorflow/tensorflow:2.15.0-gpu压测验证 HPA 扩容效果。开一个临时 Pod 发压,观察副本数随 CPU 上升而增长。
kubectl run load-generator --image=busybox:1.36 -- \
/bin/sh -c "while true; do wget -q -O- http://web-app; done"
kubectl get deployment web-app -w常见问题与避坑
HPA 报缺少资源请求导致无法计算利用率。基于 CPU 的伸缩依赖容器声明 resources.requests.cpu,缺失时 HPA 状态会显示 unknown 并拒绝扩容。给所有容器补上请求值即可恢复。
指标延迟导致扩容滞后。Metrics Server 采集周期与 HPA 轮询间隔叠加,从流量上升到新 Pod 就绪往往需要一到两分钟。对突发流量敏感的服务,调高 minReplicas 作为预热缓冲,或配合 Cluster Autoscaler 预留节点容量。
不要在同一维度同时启用 VPA 和 HPA。两者都对 CPU 请求值做决策时,VPA 的调整会干扰 HPA 的利用率计算,造成副本数反复震荡。官方建议二者分管不同资源维度。
调度失败排查思路。Pod 长期 Pending 时用 kubectl describe pod <name> 查看 Events,常见原因包括资源不足、节点污点未匹配、亲和性无候选节点。结合 kubectl get nodes -o wide 的节点资源水位能快速定位瓶颈。
NoExecute 污点会驱逐在跑 Pod。给节点打 NoExecute 污点后,没有对应容忍度的 Pod 会被立即或延迟驱逐,生产环境慎用以免误伤在线服务。
小结与进阶
调度器用过滤打分两段式选节点,HPA、VPA、Cluster Autoscaler 分别在 Pod 副本、容器规格、节点数量三个层面伸缩资源,四者配合构成完整的弹性体系。下一篇将进入网络与服务暴露,讲解 Service、Ingress 与 DNS 机制如何在集群内外打通流量。