目录

k8s 调度与自动伸缩

导读

Kubernetes 把 Pod 放到哪个节点上运行,由调度器决定;Pod 数量随负载增减,则由自动伸缩机制负责。调度和伸缩是集群资源利用率的两个核心杠杆,前者决定资源分配的合理性,后者决定系统面对流量波动的弹性能力。掌握这两层机制,才能在成本与稳定性之间找到平衡点。

核心概念

调度器为每个新建的 Pod 选择最合适的节点,整个过程分为过滤与打分两个阶段。过滤阶段执行 Predicate 函数,淘汰不符合硬性条件的节点,比如资源不足、污点不匹配、亲和性冲突;打分阶段对剩余候选节点执行 Priority 函数,按资源均衡度、亲和性权重等维度逐项评分,最终把 Pod 绑定到得分最高的节点。这套两段式设计让调度器既能快速缩小候选范围,又能在可行节点中挑选最优解。

flowchart TD A[新 Pod 进入调度队列] --> B[Predicate 过滤阶段] B --> C{资源是否充足?} C -->|否| X[淘汰该节点] C -->|是| D{污点是否容忍?} D -->|否| X D -->|是| E{亲和性是否满足?} E -->|否| X E -->|是| F[节点进入候选列表] F --> G[Priority 打分阶段] G --> H[资源均衡评分] G --> I[亲和性权重评分] G --> J[节点亲和性评分] H --> K[综合加权汇总] I --> K J --> K K --> L[选择得分最高节点] L --> M[绑定 Pod 到节点] X --> N[调度失败重试]

图 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 能识别的格式。

sequenceDiagram participant HPA as HPA Controller participant MA as Metrics API participant CALC as HPA 计算逻辑 participant DC as Deployment Controller participant Pod as Pod HPA->>MA: 拉取 Pod 指标 CPU/内存 MA-->>HPA: 返回当前指标值 HPA->>CALC: 计算目标副本数 CALC-->>HPA: desired = current * avg / target alt 扩容 目标副本 > 当前副本 HPA->>DC: 更新 Deployment replicas DC->>Pod: 创建新 Pod else 缩容 目标副本 < 当前副本 HPA->>DC: 更新 Deployment replicas DC->>Pod: 删除多余 Pod end

图 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:NoSchedule
apiVersion: 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 机制如何在集群内外打通流量。