1. 先把“自动缩放”这个东西拆清楚再动手
Kubernetes 自动缩放,我第一次在生产环境接触是在四年前。那时候 HPA 刚刚普及,团队里一群人对着“CPU 超过 70% 加副本”的规则一顿猛拍,结果一到大促就翻车。后来踩的坑多了,才意识到自动缩放不是三个字母 HPA 那么简单。本文以 v1.26.0 集群为例,按照“数据源 -> 水平缩放 -> 垂直缩放 -> 节点缩放”的顺序,把我整理过的实战经验完整讲一遍,适合刚接触自动缩放、准备在自己的部署环境里搭建 HPA/VPA/CA 的工程师参考,也适合已经被自动缩放整得焦头烂额、想系统排查一遍的同学。下面所有配置和命令,都是我在真实环境验证过的,可以直接复现。
1.1 三个层面的缩放,分别管哪一摊事
很多人把“自动缩放”和“增加副本数”画等号,这是第一个误区。在 Kubernetes 里,自动缩放至少分成三个层面:Pod 副本数的水平缩放(HPA)、Pod 资源配额的垂直缩放(VPA)、集群节点数量的容量缩放(Cluster Autoscaler,简称 CA)。这三个层面解决的问题完全不同。
- HPA(Horizontal Pod Autoscaler):管的是横向伸缩。副本数不够就加 Pod,副本数过剩就减 Pod。它解决的是“总处理能力不足”的问题,应对的是并发量、QPS 的波动。
- VPA(Vertical Pod Autoscaler):管的是垂直伸缩。它分析 Pod 的历史资源用量,自动调整 CPU/memory 的 requests。它解决的是“单个 Pod 的资源配额设得不合理”的问题。比如你写了个 cpu: 500m,实际经常飙到 900m,VPA 会帮你把基线拉高。
- CA(Cluster Autoscaler):管的是节点层面。当 Pod 因为节点资源不足而 Pending 时自动加机器,当节点资源利用率长期偏低时自动回收机器。它解决的是“整个集群容量不够”的问题。
我记得有个生活化的类比,跟团队新人讲的时候效果很好:服务是餐厅,Pod 是餐桌,HPA 是忙时加桌子,VPA 是把每张桌子换成大一号的,CA 则是发现店里实在坐不下了,去隔壁扩租铺面。三者配合,才能真正应对流量波动,缺一个都会在特定场景下出问题。比如只有 HPA 没有 CA,高峰期 Pod 塞满了现有节点,加副本也只能 Pending,服务照样挂。
1.2 为什么我先讲数据源,再讲缩放动作
自动缩放的核心其实不是“动作”,而是“数据”。无论是 HPA 还是 VPA,第一步都必须拿到 Pod 的真实资源用量。很多人上来就写 HPA YAML,结果 Deployment 里的容器没配置resources.requests,或者 metrics-server 根本没装好,导致缩放的决策依据全是“空气”,表现就是 HPA 的 TARGETS 列长期显示<unknown>,让你无从下手。
所以这篇文章的顺序是有讲究的。第二章先带你验证一个能正常采集资源指标的集群,第三章再上 HPA 做水平缩放,第四章讲 VPA 如何补充垂直维度,第五章把节点级别的 Cluster Autoscaler 串进来。每一步我都会给出配置文件、验证命令和踩坑记录,保证你照着走能落地。
2. 准备一个干净环境:kubeadm 初始化 v1.26.0
2.1 初始化命令与 preflight 检查的常见拦路虎
我这次的实验环境用的是 v1.26.0,初始化命令如下:
kubeadm init \ --kubernetes-version=v1.26.0 \ --apiserver-advertise-address=192.168.10.10 \ --pod-network-cidr=10.244.0.0/16执行后你会看到这样的输出:
[init] using kubernetes version: v1.26.0 [preflight] running pre-flight checks看到这句“running pre-flight checks”不代表万事大吉,它只是开始检查。我实际部署中遇到最多的几类检查失败是这样处理的:
- swap 未关闭。v1.26.0 的 kubelet 默认要求 swap 关闭,解决办法是
swapoff -a,然后把/etc/fstab里的 swap 行注释掉,保证重启后不反弹。 - 6443 端口被占用。常见于同一台机器上跑过旧版集群,
ss -lnt | grep 6443确认后,把残留的 kube-apiserver 进程清干净。 - sandbox 镜像拉取失败。v1.26.0 默认 pause 版本是 3.9,可以先执行
kubeadm config images pull提前把镜像拉好,避免 init 到一半卡住。
preflight 全部通过后,kubeadm 会生成 kubeconfig 文件和 Token。记得把$HOME/.kube/config拷好,后面所有kubectl操作都依赖它。然后选择一个 CNI 插件,我用的是 Calico,装完以后用kubectl get nodes确认节点状态变成 Ready,这是进入下一步的前提。
2.2 安装 metrics-server:给缩放装上“眼睛”
HPA 和 VPA 依赖的指标来自 metrics-server,它不是默认安装的组件。v1.26.0 配合 metrics-server v0.6.x 比较合适,用官方 components.yaml 安装:
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.6.4/components.yaml装完不要急着验证,先等着 Deployment 起来,再拿kubectl top试。我在这步必然踩的坑是:kubelet 使用的自签名证书不被 metrics-server 信任,Pod 能起来但一直处于 CrashLoopBackOff。日志里会看到 x509 certificate 相关报错。
解决办法是给 metrics-server 加一个启动参数--kubelet-insecure-tls。用 patch 命令改起来最快:
kubectl patch deployment metrics-server -n kube-system \ --type='json' \ -p='[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'还有一种典型场景是节点有多个内网 IP,metrics-server 连错网段导致超时。这时加上--kubelet-preferred-address-types=InternalIP基本能解决。改完以后,等 Pod 重新起来,然后验证:
kubectl top nodes kubectl top pods -A只要输出里有实实在在的 CPU 和内存数值,说明指标链路已经打通。这一步是整个自动缩放体系的基石,我会反复强调:指标不正常,后面全是白做。宁可多花十分钟把这一步验证透,也不要急着去写 HPA。
3. HPA 实操:把 Pod 数量“自动”起来
3.1 HPA 的缩放算法与两个容易被忽略的参数
HPA 的核心算法并不复杂,官方公式是:
desiredReplicas = ceil(currentReplicas * (currentMetricValue / desiredMetricValue))举个例子:当前 4 个副本,CPU 平均使用率 85%,目标值设为 50%,那么期望副本数就是ceil(4 * 85 / 50) = ceil(6.8) = 7。HPA 会按这个公式周期性地计算期望副本数,默认每 15 秒同步一次,这个周期由 kube-controller-manager 的--horizontal-pod-autoscaler-sync-period控制。
但公式只是表象,真正影响行为的是另外两个参数:
- 容忍度(tolerance):默认 0.1,即当指标偏离目标值在 10% 以内时,HPA 认为“可以接受”,不做伸缩。这个机制是为了避免临界点时副本数反复横跳。如果你的服务对抖动极度敏感,可以调小它,比如
--horizontal-pod-autoscaler-tolerance=0.05,但要小心过于频繁的伸缩动作。 - 冷却窗口(stabilizationWindowSeconds):这个参数决定 HPA 在做出缩容或扩容决定前,要多长时间内观察历史建议。尤其是缩容的冷却窗口,默认在 v1.26.0 里是 300 秒。
这套机制的整体逻辑很像开车时的油门控制:你不可能看到速度掉了一点就猛踩油门,仪表盘上的瞬时数值往往会被过滤掉,系统看的是更长时间窗口里的趋势。
3.2 autoscaling/v2:支持多指标和更精细的缩放行为
v1.26.0 里稳定的 HPA API 版本是autoscaling/v2,相比老版本的 v1,最大的增强是支持多指标和 behavior 缩放行为控制。
v1 版本一次只能配一个 CPU 目标,v2 可以同时监控 CPU、内存、自定义指标,取计算结果的最大值来调整副本数。同时 v2 里的behavior字段允许你分别定义 scaleUp 和 scaleDown 的策略,比如“一分钟内最多新增多少个副本”“缩容窗口期是多少秒”。这在实际生产中非常有用,因为老版本的 HPA 缩容特别激进,流量一波动,副本数立刻被打回原形,下一波高峰又来,扩容有延后,用户体验就会抖动。
新环境我建议直接使用 autoscaling/v2,理由很简单:语义清晰、多指标支持、行为可控制。在老版本集群上做迁移也不复杂,核心就是 YAML 结构从targetCPUUtilizationPercentage改成 metrics 列表里的resource.cpu.target.averageUtilization。
3.3 一份可以直接复现的 HPA 配置
假设我们有一个叫web的 Deployment,容器里已经配好了requests。这是 HPA 能工作的前提,关于这一点我后面还会强调。下面是完整的 HPA 配置:
apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: web-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: web minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 60 - type: Resource resource: name: memory target: type: AverageValue averageValue: 800Mi behavior: scaleDown: stabilizationWindowSeconds: 300 policies: - type: Percent value: 50 periodSeconds: 60 scaleUp: stabilizationWindowSeconds: 0 selectPolicy: Max policies: - type: Percent value: 100 periodSeconds: 60 - type: Pods value: 4 periodSeconds: 60应用后查看状态:
kubectl apply -f web-hpa.yaml kubectl get hpa web-hpa正常时 TARGETS 列会显示类似CPU: 37%/60%,memory: 780Mi/800Mi。如果显示Unknown,去查 Deployment 是否设置了 requests,这是新手最常犯的错。另外我在这里用 memory 的 AverageValue 目标,实际生产环境我不建议对 memory 设过死的值,因为内存有大页缓存、GC 波动等因素,经常会出现“指标超了但 Pod 运行良好”的误判,更适合的做法是监控内存使用率趋势,或者干脆只靠 CPU 加自定义业务指标。
3.4 缩容震荡问题:为什么副本数不能像过山车一样掉
HPA 新手上路,最容易碰到的不是不扩容,而是缩容太猛。服务流量一波动,HPA 在下一轮同步就把副本削到最小值,过了几分钟流量又上来,又开始扩容,如此反复。业务侧看到的现象就是 Pod 频繁被杀、频繁新建,日志和监控一片混乱。
v2 的 behavior 字段就是用来治这个问题的。我的配置里scaleDown.stabilizationWindowSeconds: 300表示系统会记录过去 5 分钟的所有缩容建议,然后选择最保守的那个执行。配了Percent: 50,periodSeconds: 60之后,HPA 在一分钟内最多缩掉当前副本数的 50%。这两个组合的效果是:即使指标短暂下跌,也不会立刻触发大规模缩容,系统会“迟疑”一会儿,确认趋势形成后才动手。
扩容侧我反而激进一点:Percent: 100意味着每分钟最多可以增加一倍副本,Pods: 4表示每分钟最多加 4 个,selectPolicy: Max取两者中较大的。这样在流量突增时,扩容能跟上,而缩容始终以稳定优先。
这套配置我反复强调一点:扩容要快,缩容要慢。这是自动缩放里最值得背诵的经验。
4. VPA:把 Pod 的资源基线调准
4.1 VPA 的三个组件与底层原理
HPA 解决“加桌子”的问题,VPA 解决“换大桌子”的问题。在实际业务里,很多 Pod 不是不够用,而是 requests 设得太小,导致调度器低估资源需求,节点超卖严重,最终某个 Pod 被 OOM,甚至整个节点被打挂。VPA 的价值在于,它可以自动把资源 requests 调整到一个科学合理的值。
VPA 包含三个核心组件:
- Recommender:负责采集 Pod 历史资源用量,计算推荐值。它是 VPA 的“大脑”,输出的是推荐 requests。
- Updater:在 updateMode 为 Auto 时,负责驱逐需要变更的 Pod,让它以新的资源请求重建。
- Admission Controller:拦截新建 Pod 的请求,把推荐值写入到容器的 requests 中。
推荐值的计算不简单,它用的是分位数。比如 Recommender 会考察 Pod 在最近 8 天内的 CPU 使用,CPU 用的是第 90 百分位,而内存因为释放不及时,会用第 95 百分位甚至峰值。所以 VPA 推荐的 requests 通常比手工拍脑袋的数值更贴近真实需求,尤其是内存这种波动大的资源。
4.2 VPA 的部署方式与 Auto 模式配置
安装 VPA 最直接的方式是克隆官方仓库然后跑脚本:
git clone https://github.com/kubernetes/autoscaler cd autoscaler/vertical-pod-autoscaler ./hack/vpa-up.shvpa-up.sh会在集群里创建 VPA 相关的 Deployment 和 Webhook。不过每次升级或重启这些组件时,Webhook 的证书就可能失效,导致创建 Pod 时报Internal error occurred: failed calling webhook。我的建议是生产环境把证书托管给 cert-manager,不要用脚本自动生成的临时证书。
下面是一份 Auto 模式的 VPA 配置:
apiVersion: autoscaling.k8s.io/v1 kind: VerticalPodAutoscaler metadata: name: web-vpa spec: targetRef: apiVersion: "apps/v1" kind: Deployment name: web updatePolicy: updateMode: "Auto" resourcePolicy: containerPolicies: - containerName: "*" minAllowed: cpu: 100m memory: 128Mi maxAllowed: cpu: "4" memory: 4GiupdateMode: Auto表示推荐值算出来以后,VPA 会主动修改 Deployment 的 Pod template 并触发重建。minAllowed和maxAllowed可以防止 VPA 给出极端值,比如内存建议被算到 8Gi,但你的容器最多也就需要 2Gi,那就在上限里卡住。我个人建议生产环境第一周用updateMode: Initial,只对新创建的 Pod 生效,不做主动驱逐,观察推荐值是否合理,再切到 Auto。
4.3 VPA 与 HPA 共存的正确姿势
VPA 和 HPA 绝对不能盲目叠加,这是官方文档也明确警告过的:不要对同一个 Deployment 的 CPU/内存指标同时使用 HPA 和 VPA。原因很直接:VPA 会修改 requests,而 HPA 的 CPU 目标值是“利用率百分比”,分母就是 requests。requests 从 500m 变成 1000m,即使 CPU 绝对使用量没变,利用率百分比也会差一倍,两个系统会互相拉扯,最终导致副本数和 requests 双双反复横跳。
我在生产环境的做法是两种:
- 如果服务有明显的峰谷周期,先只上 VPA,运行一段时间让 requests 归位到合理水平,然后再加 HPA;
- 如果必须同时用,让 HPA 走自定义指标(比如 QPS、P99 时延),VPA 只负责 CPU/memory requests,两者的数据源不同,互不干扰。
此外,VPA Auto 模式会驱逐 Pod 来应用新 requests,如果服务只有一个副本,驱逐期间就是断服窗口。所以 VPA 上线前一定要保证服务的副本数至少为 2,或者有滚动发布能力兜底。这个细节我在实际项目里栽过跟头,一个内部工具只跑了一个副本,VPA 一调整,监控里直接出现一道断档。
5. 节点级别缩放:Cluster Autoscaler 的实战逻辑
5.1 CA 的扩容与缩容判定逻辑
HPA 和 VPA 都是“集群内”的伸缩,它们能调动的资源上限受到集群节点数量的限制。如果所有节点都满了,HPA 就算把 maxReplicas 调到 100,新 Pod 也只能停留在 Pending 状态。这时候需要 Cluster Autoscaler(简称 CA)出马。
CA 的扩容触发条件只有一个:出现了无法调度的 Pod。典型链路是这样的:HPA 决定扩容副本,调度器发现现有节点资源不足,新 Pod 进入 Pending,CA 检测到 Pending Pod 后,在云厂商的自动伸缩组里增加一台新节点,节点 Ready 后调度器把 Pod 调度上去。整个链路是自动完成的,不需要人工去云控制台创建机器。
缩容则相反。CA 会定期检查每个节点的资源利用率,如果某个节点上的所有 Pod 都能被重新调度到其他节点,而且该节点的整体利用率低于某个阈值(默认是 50%)持续 10 分钟,它就会考虑把这个节点回收掉。这里有两个保护机制值得注意:一是刚扩容出来的节点有 10 分钟的“冷静期”,不会被立刻缩掉;二是 CA 在缩容前会模拟调度,确保所有 Pod 都能找到新家,否则会放弃缩容。
5.2 部署 CA 时,云厂商配置与 HPA 配合顺序
CA 的部署不算复杂,但因为要调用云厂商的 API,所以需要提前准备好凭证。以常用的云环境为例,需要把节点池的自动缩放范围和区域信息配置到 CA 的启动参数里:
./cluster-autoscaler \ --nodes=1:10:default-node-group \ --scale-down-utilization-threshold=0.5 \ --scale-down-unneeded-time=10m \ --max-nodes-total=100--nodes格式是min:max:节点组名称,--scale-down-unneeded-time是缩容前的观察时间。CA 部署到 kube-system 命名空间后,可以用kubectl logs观察它的决策日志,日志里会明确写“Pod unschedulable"、"Scale up”这类信息,排查起来很直观。
生产和 HPA 配合的完整顺序,我在多次大促活动的复盘里总结成下面几步:
- HPA 先扩容 Pod,直到 maxReplicas;
- 新 Pod 因节点资源不足进入 Pending;
- CA 感知 Pending Pod,自动扩容节点;
- 流量下降后 HPA 先缩 Pod,节点利用率随之下降;
- CA 再按缩容阈值回收多余节点。
这个链条里最容易被忽略的是“等待时间”。流量高峰过后,HPA 可能 5 分钟内就会把副本缩下来,但 CA 的缩容观察期通常是 10 分钟起步,所以你不会看到“流量刚降,节点跟着秒删”的情况。反过来,如果发现节点长期不缩,优先检查是不是有 Pod 被 PDB 保护着,或者节点的利用率一直没低于阈值。
6. 高频问题排查速查表与实操心得
6.1 指标拿不到、target 显示 Unknown 的排查链路
自动缩放调试过程中,我遇到最多的问题就是“HPA 配好了但不起作用”。下面这张表是我平时排查用的速查表,按频率排序:
| 现象 | 可能原因 | 排查方法 | 解决办法 |
|---|---|---|---|
kubectl top nodes报 metrics not available 错误 | metrics-server 未部署或启动失败 | kubectl get pods -n kube-system查看 metrics-server 状态与日志 | 加上--kubelet-insecure-tls,检查证书与网络 |
kubectl top pods有数据,但 HPA 的 TARGETS 显示 Unknown | Deployment 未设置 requests | kubectl get pod -o yaml查看 output 里是否有 resources.requests | 给所有容器补上 CPU/memory requests |
| HPA 的 CPU 一直显示 0%/目标 | target 类型配成了 AverageValue,但业务实际用量低于该绝对值 | kubectl get hpa -o yaml确认 metrics 类型 | 根据业务特征选择 Utilization 还是 AverageValue |
| HPA 报 500 错误 | 聚合 API 未正确注册 | `kubectl get apiservice | grep metrics` |
如果 metrics-server 已经工作,kubectl top也有数据,但 HPA 仍然异常,那就要检查 HPA 的 events。任何异常都会在kubectl describe hpa里留下事件记录,这个命令比你翻日志高效得多。
6.2 缩放震荡、副本数来回跳的解决手段
缩放震荡的本质是反馈回路不稳定:指标短期超过目标,扩容,新 Pod 还没 ready,负载被打散后指标又快速回落,缩容,等新 Pod 一释放流量,指标又开始上涨。如此循环,最后监控图上看到的是一条锯齿线。
我的经验是三层手段一起用:
- 应用层:加快服务的启动速度,配置合理的 readinessProbe,让新 Pod 业务就绪后 HPA 才把它纳入决策范围。
- HPA 层:把 scaleDown 的 stabilizationWindowSeconds 从 0 提到 300 秒以上,让缩容变得“迟钝”一点。必要时调大容忍度。
- 指标层:如果用的是自定义指标(比如通过 Prometheus Adapter 暴露的 QPS),把指标的聚合窗口拉长到 1-2 分钟,过滤掉短暂尖峰。
还有一个很容易踩的坑:Prometheus Adapter 默认的抓取间隔是 30 秒,如果这 30 秒里 Deployment 发生了一次重新调度,指标可能出现短暂空洞,HPA 拿到空数据后有可能把它当成 0,直接触发缩容。这种问题的特征就是“缩容过于积极,明明刚扩容完就缩回去了”。
6.3 整条链路的验证顺序与检查命令清单
最后整理一份从 kubeadm 初始化到自动缩放全链路的验证顺序。我强烈建议不要跳步,每一步都确认通过后再进入下一步:
kubectl get nodes # 节点是否 Ready kubectl get pods -A # 核心组件是否 Running kubectl top nodes # metrics-server 是否采集到节点指标 kubectl top pods -A # metrics-server 是否采集到 Pod 指标 kubectl get hpa -A # HPA 的 TARGETS 是否正常 kubectl describe hpa <name> # HPA 事件是否有异常 kubectl logs -n kube-system <CA-pod> # CA 的伸缩决策日志哪一步输出不符合预期,就先处理哪一步,不要跳过障碍去调 HPA 参数。自动缩放最怕的不是参数不好,而是数据源不健康,问题却被误判成缩放策略的问题。
7. 一些真正值得带走的经验
自动缩放这套体系,在 v1.26.0 这种新版本上已经比较完善,但它的成熟不代表配置简单。我最大的体会是:不要试图一口气把所有组件都配上,而是先把单一组件跑通、压测、观察,再加下一个。先上 HPA,看它能不能在压测下从 2 个副本拉到 8 个,再把 CA 接进来,看 Pending Pod 是否能触发新节点,最后才是 VPA 这种需要长期观察的组件。
个人在实际操作中还有个习惯:把 maxReplicas 预留到预估峰值的 2 倍。这看起来是浪费,实际上是在给 CA 扩容节点争取时间。比如你预计高峰需要 30 个副本,就把 maxReplicas 设成 60,这样当 CA 还在等待云厂商创建节点的几分钟里,HPA 可以先用现有节点的余量多塞一些 Pod。等到新节点 Ready,副本数再逐步收敛。这个小技巧帮我在几次真实的大促中保住了 SLO。
如果你读完这篇文章,按照链路一步步把环境验证下来,自动缩放就不再是玄学。它就是一个有数据、有反馈、有稳定机制控制的系统工程。希望这些踩坑经验能让你少走弯路。