“video-use”标题看似简短,实则是我们生产环境里一套完整的 Kubernetes 资源治理方案。当时摆在我们面前的局面很现实:业务流量一天内有多个明显波峰波谷,白天人力高峰和晚间活动高峰交错出现,固定规格的节点池要么在高峰期被打满,要么在低谷期大量浪费。视频转码、推流网关、审核服务这些核心应用对资源的需求各不相同,手动调整副本数根本来不及。这套方案落地后,我们真正实现了“流量涨,实例自动涨;流量退,实例自动退”,资源利用率和管理效率都上了一个台阶。这篇文章我把设计思路、核心参数、灰度过程和故障排查完整拆解出来,给同样被资源问题折磨的运维、SRE 和后端架构同学一个可直接参考的样本。
1. 资源治理的整体思路与方案选型
1.1 为什么最终选了 Kubernetes 原生的 HPA
先说结论:我们不是没试过其他方案,正是因为对比过,才坚定选了 HPA。市面上能实现弹性伸缩的产品不少,有云厂商自带的 CA(Cluster Autoscaler),有各种第三方组件,也有人把业务层的自愈逻辑直接写在代码里。但它们要么绑定特定云厂商的基础设施,要么和业务耦合太深,要么需要在业务代码里侵入式地实现一套伸缩规则,改造和维护成本都很高。
HPA 不一样。它是 Kubernetes 控制面内置的控制器,天生就和 Pod、Deployment、Service 这些核心资源属于同一套体系。我们需要的不只是“能扩容”,而是“能按照业务的实时负载自动扩容”。HPA 直接通过 Metrics Server 采集 CPU、内存指标,也可以扩展自定义指标,在控制循环里不断计算当前指标值和期望副本数的偏差,再通过调整 Deployment 的 replicas 来收敛偏差。整条链路没有任何外部强依赖,监控组件挂了也不影响已有 Pod 的运行,这对视频业务要求的稳定性来说非常重要。
HPA 的算法也是开箱即用的。它基于当前副本数、当前指标值、目标值三者的比例关系,计算出期望副本数。举一个具体例子:某个 Deployment 当前有 4 个副本,当前 CPU 使用率是 80%,目标利用率是 50%,那么期望副本数 = 4 × (80 / 50) = 6.4,向上取整得到 7。如果某一个 Pod 的指标值特别高,它会先做“单 Pod 指标值 / 目标值”的判断,当结果大于 1 时,直接按这个 Pod 的指标值参与计算,防止单个热点 Pod 被平均数据掩盖。这个机制在处理视频转码、推流合成这类典型的高 CPU 场景时尤为有效,不会因为某个实例异常就把整个服务的伸缩决策带偏。
1.2 配额、优先级与节点池:资源治理的三根支柱
只靠 HPA 扩容,其实解决不了“资源从哪里来”的问题。我们最初上线的时候,HPA 能把一个服务的副本数从 2 扩到 20,但集群里如果只剩几台低配节点,扩容出来的 Pod 就只能挤在一起,或者一直卡在 Pending 状态。这时候真正需要的是配额、优先级、节点池三个层面的配合。
配额解决的是“资源总量”的问题。我们把整个集群的资源按 Namespace 划分成不同资源池,每个池子有独立的 CPU、内存上限,服务只能在配额范围内伸缩,不会出现一个业务把整个集群资源吃光的情况。比如视频转码服务所在的 Namespace 配额是 32 核 CPU、64GB 内存,即使 HPA 因为异常指标一直扩容,到达配额上限就会被 API Server 拒绝创建新 Pod 的请求,天然形成一道保护屏障。
优先级解决的是“资源竞争规则”的问题。通过 PriorityClass,关键业务在资源紧张时优先获得调度,而可延后的批处理任务可以随时被抢占。我们给视频推流网关设置了最高的优先级(比如 1000000),给离线转码任务设置了较低优先级(比如 1000),一旦资源池出现竞争,调度器会优先保证高优先级 Pod 运行。这在高峰期特别有用,不会出现因为几个大任务占满节点、网关 Pod 反而被挤掉线的异常情况。
节点池解决的是“资源放在哪里”的问题。我们的 GPU 节点单独划池,视频转码这类需要 GPU 的任务固定调度到 GPU 池,普通 API 服务走 CPU 池,两类任务互不干扰。这种隔离不只是性能上的考虑,也有成本上的考量:GPU 节点价格高出普通节点数倍,如果不做隔离,一个普通的 CPU 密集型服务调度到 GPU 节点上,不仅浪费了昂贵的 GPU 资源,还会影响真正需要 GPU 的转码任务。
这三根支柱配合起来,才构成了一套完整的资源治理闭环。HPA 负责算“需要多少副本”,配额负责限制“最多能用多少资源”,PriorityClass 负责决定“资源不够时谁先上”,节点池负责确保“上去之后有合适的机器”。缺少任何一根,都会在实际运行中露出短板。
1.3 多集群资源池的共享策略
业务规模扩大到一定程度后,单一集群已经很难满足所有需求。我们在 video-use 的架构里加入了多集群管理的思路。每个业务集群独立承载自己的服务,但我们构建了一个统一调度层,把各个集群的空闲资源汇聚到一个共享资源池中。当某个集群出现流量高峰、本地资源不够时,调度层会把溢出的工作负载调度到有空闲能力的兄弟集群上。
这里的关键在于“资源池的共享并不是无条件的”。第一,我们给每个集群设置了水位线,比如 CPU 使用率超过 70% 就视为高水位,不再接收溢出的负载。为什么要设水位线?因为跨集群调度会带来额外的网络开销和数据同步延迟,如果源集群本身已经高负载,再把任务调度过去,反而会让整个系统雪上加霜。第二,每个集群在共享池中能借用的资源也有上限,这个上限会根据该集群的历史峰值和业务优先级动态调整。核心业务集群的借用上限设得高一些,非核心业务集群设得低一些,避免一个集群的突发流量把整个共享池的资源都借走,影响其他集群的基本运行。
2. 核心配置解析与关键参数的调优过程
2.1 Metrics Server 的部署与指标采集链路
HPA 依赖指标来源,默认情况下使用的是 Metrics Server。这个组件的原理并不复杂:它通过 kubelet 的 Summary API 采集每个节点上所有容器的 CPU、内存使用数据,再通过 Metrics API 暴露给 HPA 调用。但有一个细节很多人容易忽略:Metrics Server 采集的是容器在上一个采集周期内的平均使用量,而不是瞬时值。采集周期默认是 15 秒,HPA 默认每 15 秒同步一次指标,如果业务容器的负载波动非常快,就会出现“指标还没采集到,负载峰值已经过去了”的情况,HPA 的响应自然就不够灵敏。
我们在 video-use 中把 Metrics Server 的采集周期调整为 10 秒,同时优化了 HPA 的--horizontal-pod-autoscaler-sync-period参数,让伸缩决策的响应更快。但这里也要提醒一句:缩短采集周期会增加 API Server 和 kubelet 的压力。尤其是集群规模达到上千节点的时候,所有节点的指标汇总会对 API Server 产生不小的请求量,需要评估监控链路的承载能力。我们目前的做法是核心链路的采集周期保持 10 秒,非核心服务继续用默认值,这样既保证了关键业务的响应速度,又不会让整个集群的监控链路过度负载。
2.2 HPA 核心参数:minReplicas、maxReplicas 与 targetAverageUtilization
HPA 配置里的三个核心参数,每一个都需要根据业务特点仔细敲定,不能照抄文档示例。
minReplicas的意义在于保证服务的基础吞吐能力。对视频 API 网关来说,哪怕深夜没有任何流量,也必须保持一定数量的副本随时接收请求,否则睡梦中来一个突发请求,冷启动会直接导致高延迟。我们的生产配置是把网关类服务的最小副本数设为 3,既保留了冗余,又不至于太多浪费资源。对于更核心的推流网关,minReplicas 设到了 5,因为这类服务一旦出现请求积压,用户体验会立刻下降,容不得半点意外。
maxReplicas决定了一个服务最多可以扩展到多少副本。这个值不能拍脑袋定,它受限于命名空间的配额、节点池的容量以及下游依赖(如数据库连接数)的可承受上限。我们把视频转码服务的 maxReplicas 设定为 12,就是经过压测确认过的:数据库连接池在 12 个副本并行写入时达到最佳吞吐,再多反而会因为连接争抢导致性能下降。这个“最优副本数”需要通过真实的压测数据得出来,盲目的数字只会埋下隐患。
targetAverageUtilization是触发扩缩容的阈值。这个值的设定要考虑业务负载的特征和副本数变化的惯性。如果一个服务在高峰期的负载曲线非常陡峭,阈值可以适当调低,让扩容提前发生;如果负载曲线比较平缓,阈值可以调高一些,避免服务频繁伸缩。视频转码服务我们用的是 60%,因为转码任务对 CPU 的消耗非常稳定,60% 的阈值可以在负载爬升早期就触发扩容。网关服务用的是 50%,它的负载波动更频繁,阈值稍低一些能让预留空间更大。批处理任务用的是 80%,这类任务对延迟不敏感,可以把资源利用得越满越好。
2.3 自定义指标扩展:不止 CPU 和内存
CPU 和内存指标在大部分场景下够用,但视频业务有几个场景必须使用自定义指标。最典型的就是队列深度。我们有一个视频审核服务,任务从消息队列里拉取,拉取速度直接决定业务吞吐。当队列积压越多,说明消费能力不足,这时单纯看 CPU 可能毫无反应,因为任务都在等待 I/O,CPU 根本跑不满。我们通过 Prometheus Adapter 把队列积压数量暴露为自定义指标,HPA 根据积压数量实时计算副本数,才彻底解决这个问题。
自定义指标接入 HPA 的流程其实并不复杂,核心是两步:第一步,在 Prometheus 中定义并采集业务指标,例如通过 exporter 将 MQ 的队列深度暴露成mq_queue_depth;第二步,通过 Prometheus Adapter 的配置,把指标名称映射成 Kubernetes 的 Custom Metrics API 资源,HPA 就能像使用 CPU 指标一样使用它了。这里要特别留意,自定义指标的值通常是一个绝对值,比如当前积压 5000 条消息,HPA 对绝对值的处理方式是按每个副本的期望处理能力来换算的。例如我们设置单副本期望处理能力为 1000 条积压,当前积压 5000,当前副本数 3,期望副本数就是 5000 / 1000 = 5。这个设计思路要求你对业务指标的物理意义有清晰的理解,否则指标配置得再花哨,也只是一堆没有指导意义的数字。
3. 从预发到生产:video-use 的灰度落地过程
3.1 灰度策略的选定与发布流程
我们把 video-use 部署到生产环境的路径可以说是稳扎稳打:先在预发集群全量验证,再在正式集群按 10% 的流量逐步放量。为什么是 10%?因为我们想让真实流量来验证 HPA 的弹性伸缩对业务延迟的影响,而不是靠压测模拟。10% 的流量足够刺激一次真实的扩容和缩容动作,但即使出问题,爆炸半径也可控。这个方法看起来很保守,但收益非常高,因为生产环境的流量特征和预发环境存在本质差异,只有真实流量才能暴露出那些在测试中根本发现不了的问题。
灰度期间我们重点观察三个指标:Pod 的启动时间、扩容触发到副本就绪的延迟、以及缩容后是否出现流量抖动。视频推流网关是最先灰度验证的业务,因为它的流量特征最明显,早高峰和晚高峰的请求量差距能达到 5 倍以上,HPA 的每一次伸缩决策都能被清晰观察到。灰度通过后,我们再把这个配置推广到其他业务。每个业务的灰度周期至少观察 3 个完整的业务波峰波谷,确保伸缩策略在峰值和低谷期都不会出问题。
3.2 灰度期间踩过的坑与应急预案
第一次灰度我们就踩了坑。某个服务在 HPA 生效后频繁出现副本震荡,原因很直白:指标采集周期和 HPA 同步周期叠加,产生了一个控制回路上的振荡。HPA 每次检测到 CPU 高于目标值就扩容,扩容后 CPU 短暂下降,下一轮检测又触发缩容,结果是副本数在 4 和 8 之间来回跳,服务后端的负载均衡器被频繁变化的节点列表搅得焦头烂额,部分请求甚至出现短暂的 502。
排查这个问题的时候我们发现,光看 HPA 的事件列表根本看不出规律,因为事件只记录了“扩容了多少副本”,没有记录“为什么扩容”。于是我们同时抓了三份数据:Metrics Server 的原始指标曲线、HPA 控制器的决策日志、Deployment 的副本数变更历史。三条数据放在一起对比,振荡的规律立刻清楚了:指标数据在高位和低位之间快速交替,HPA 的反应总是慢半拍,于是形成了扩了又缩、缩了又扩的死循环。
解决办法有两个:一是调大 HPA 的--horizontal-pod-autoscaler-tolerance参数,默认值是 0.1,我们调到 0.2,让更小幅度的指标波动不再触发扩缩容。这个参数的本质是设置一个“死区范围”,指标偏差在容忍度范围内就不做任何操作。二是给 Deployment 加上 scaleDown 的stabilizationWindowSeconds参数,延长缩容冷静期,防止刚扩容完就立刻缩容。我们设的是 300 秒,意思是缩容决策一旦做出,在 5 分钟内不能再次缩容。这两个调整叠加之后,副本数曲线变得平滑多了,震荡问题彻底消失。
3.3 正式放量与容量压测的核对方法
灰度验证完成后,正式放量之前,我们还做了一次严谨的容量核对。方法并不复杂,但在生产环境非常有效:先把预发集群的流量入口切到正式集群,然后把正式集群的副本数人工固定在某个水位,逐步增加模拟流量,直到出现资源瓶颈,记录瓶颈点对应的 QPS 和 Pod 数。有了这组数据,再结合 HPA 的算法公式,我们就能算出当前配置的理论最大吞吐,然后反推是否需要调整对外承诺的 SLA。
这组数据还有一个很重要的用途:验证 HPA 的扩容上限是否合理。如果压测显示 8 个副本就能扛住 2 倍峰值流量,而 maxReplicas 设的是 16,那我们就知道还有一半的冗余可以应对更极端的场景。如果 16 个副本都扛不住,那就得重新评估节点池容量和配额设置了。这个过程是数据驱动的,每一步都留有记录,之后复盘时也能搞清楚当初为什么做这样的决策。用数据说话,灰度落地才不是碰运气。
4. 常见问题、故障实录与运维建议
4.1 扩容成功但 Pod 一直 Pending 怎么办
这个问题出现的频率不低,尤其容易在业务流量突然暴涨的时候出现。表象是 HPA 已经把副本数从 5 扩到了 15,但新增的 Pod 一直处于 Pending 状态,后续扩容出来的 Pod 白白占用了配额,请求还是大量超时。这时候如果只看 HPA 状态,你会以为扩容已经完成,但实际上服务根本没有真正扩容到位。
我们排查这类问题时,先看了节点的资源水位,发现可用 CPU 明明足够,于是怀疑是节点亲和性或污点问题。最后定位到的根因是:新增服务需要挂载一个本地数据卷,而满足数据卷要求的节点只有两个,扩容出来的多个 Pod 都挤在这两个节点上,资源不够自然就 Pending 了。解决方案是在 Deployment 里声明了拓扑分布约束,让扩容出来的 Pod 尽量分散到不同的可用区,同时给集群补充了支持该数据卷的节点。这起故障给我们的教训是:HPA 扩容只是“创建 Pod 请求”,Pod 能否成功调度,还取决于调度器的规则和节点实际资源状态。排查故障时,先把视角从 HPA 切换到调度器,问题往往一目了然。
4.2 缩容比扩容更难:冷数据与优雅退出的处理
很多新手只关注扩容时副本数蹭蹭上涨的爽快感,却忽略了缩容才是真正考验产品架构设计的地方。我们的视频点播服务在缩容时就遇到过问题:Pod 被删除后,正在处理的请求被硬生生中断。按照 Kubernetes 的默认行为,删除 Pod 时会先发送 SIGTERM 信号,默认等待 30 秒后发送 SIGKILL。但问题在于,我们的服务端代码根本没有处理 SIGTERM 信号,收到信号后直接退出了,正在跑的任务自然就断了。
处理这个问题需要在业务代码里监听 SIGTERM 信号,收到后先停止接收新请求,等存量请求处理完毕再主动退出。我们给每个服务设置了一个最大等待时间,比如 30 秒,超过这个时间就强制退出,避免容器长时间不终止导致节点资源泄漏。同时要配合调整terminationGracePeriodSeconds参数,默认是 30 秒,如果业务请求的平均处理时间比较长,这个值需要相应调大。另外还有一个隐蔽的坑:如果服务使用了消息队列消费,缩容时一定要手动关闭消费者组里的当前消费者,而不是完全依靠 Pod 删除时自动触发,否则同一个消息会被多个消费者重复消费,造成数据不一致。缩容的优雅退出做得好,业务的稳定性才算真正稳了。
4.3 最后的运维建议:告警、日志与容量规划
video-use 上线后,我们把运维团队的核心精力集中到了三类事情上:告警、日志、容量规划。
告警规则的核心不是监控 HPA 本身,而是监控副本数和实际业务指标的关系。我们保留了几条非常有代表性的告警规则:比如“副本数已经达到 maxReplicas 但队列积压仍在上涨”,这条是严重告警,说明当前容量已经不够了,需要立刻介入;再比如“副本数在 10 分钟内伸缩超过 5 次”,这条是性能告警,说明伸缩策略可能存在震荡或者配置不合理。这两类告警一个盯容量瓶颈,一个盯策略健康度,比单纯监控 CPU 使用率有意义得多。
日志层面,我们给 HPA 控制器开启了详细日志,同时把 HPA 的每一次决策动作,无论是扩容、缩容还是跳过,都记录到独立的日志文件里,方便事后审计。这类日志通常不会太多,但每一次扩容背后的原因、当时的指标值、目标副本数都有据可查。线上出现问题时,翻这些日志能节省大量排查时间。
容量规划上,我们每个季度都会做一次资源复盘:结合业务增长曲线、各服务的峰值流量、以及 HPA 的实际伸缩记录,推导下一季度的节点池扩容计划。这个过程看似繁琐,但没有它,任何弹性伸缩方案都只是表面的自动化,资源池只会越用越乱。我们曾经遇到过某个月流量的增长远超预期,但因为复盘及时,提前一个星期加了节点,没有影响到用户体验。容量规划不是临时抱佛脚,而是持续性的例行工作。
5. 实操过程中的其他心得与扩展思路
5.1 配置模板化的收益
在多个业务复用 video-use 方案时,我们把 HPA、PriorityClass、ResourceQuota 的配置做成了一套可复用的模板。每个业务方只要通过一个简单的参数文件声明自己的资源画像,包括最小副本数、最大副本数、目标利用率等,就能自助完成接入。这样带来的好处非常直观:平台团队不再需要每天帮不同业务调配置,业务方也能根据自己的实际情况灵活修改参数。
模板化的核心是把“不变的部分”和“变化的部分”拆开。不变的部分包括 Metrics Server、Prometheus Adapter 这些基础设施的部署方式,变化的部分只有每个业务的几个数字参数。我们使用 Helm Chart 来管理这些模板,业务方提交一个 values.yaml 就能完成整个接入流程。之前需要一整天才能完成的新服务接入,现在压缩到了半小时以内,效率提升非常明显。
5.2 成本控制与资源利用率的平衡
最后想聊一下成本。资源治理做到最后,其实都是在成本和稳定性之间找平衡。HPA 帮你把副本数控制在刚好满足需求的水平,这是节省成本的基本盘。但我们也发现,很多服务缩容后 CPU 利用率依然长期偏低,说明这些服务的 minReplicas 设置得过高了,或者业务本身已经不再需要那么大的基础容量。于是我们定期分析所有服务的 CPU 使用率中位数,把那些长期低于 20% 的服务筛选出来,逐个调整它们的 minReplicas,直接从源头降低资源占用。
这个工作每个季度执行一次,效果非常显著。最近一次调整,我们把 40 多个服务的 minReplicas 平均降低了约 30%,每月节省下来的资源成本相当可观。而且因为目标利用率阈值没有变,服务的响应延迟完全没有受到影响,线上服务质量稳如磐石。省下来的成本,反手就投入到节点池的扩容上,既保证了稳定性,又优化了成本结构,这是我认为这套方案最划算的地方。资源治理不是把资源压得越紧越好,而是在保证业务服务质量的前提下,让每一份资源都花在刀刃上。
5.3 给后来者的一些实操建议
如果让我给准备落地类似方案的同学一些建议,我会强调几点:先从小流量业务开始验证,不要一上来就治理核心链路;监控体系必须先于弹性伸缩建立,否则出了问题连排查的方向都没有;HPA 的参数不是设一次就能一劳永逸的,随着业务流量特征的变化,需要定期复盘和调整。这套方案真正跑起来之后,你会发现它带来的不只是资源利用率的提升,更重要的是,运维同学终于不用在凌晨三点被扩容短信炸醒了。那种“系统自己在正确的时间做了正确的事”的感觉,值得你花时间去打磨。