<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>自动伸缩 · XB 现场笔记</title><link>https://www.xbcatm.com/zh/tags/%E8%87%AA%E5%8A%A8%E4%BC%B8%E7%BC%A9/</link><description>基于 HTTP、DNS、TLS、Caddy、SSH、发布与恢复的可复现运维现场笔记。</description><generator>Hugo</generator><language>zh-CN</language><atom:link href="https://www.xbcatm.com/zh/tags/%E8%87%AA%E5%8A%A8%E4%BC%B8%E7%BC%A9/index.xml" rel="self" type="application/rss+xml"/><item><title>水平自动伸缩按需扩缩容</title><link>https://www.xbcatm.com/zh/posts/k8s-hpa-scales-on-demand/</link><pubDate>Thu, 04 Nov 2027 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/posts/k8s-hpa-scales-on-demand/</guid><description>HPA 根据指标推导副本建议，再应用上下限、稳定机制和速率策略；新增 Pod 还需完成启动才能提供容量。</description><content:encoded>&lt;p&gt;HorizontalPodAutoscaler 根据配置的指标目标调整可伸缩工作负载。它的输出是期望副本数，不是“额外 Pod 已经接收流量”的保证。调度、镜像启动、初始化、就绪状态以及节点容量，都会决定这个建议多久才能转变成真正有用的服务能力。设计控制循环时，必须把这些延迟一起考虑，不能只根据一条公式估计恢复时间。&lt;/p&gt;
&lt;h2 id="指标产生的是副本建议"&gt;指标产生的是副本建议&lt;/h2&gt;
&lt;p&gt;对于简单的逐 Pod 平均值，期望副本等于当前副本数乘以当前指标除以目标指标，再向上取整。比值为二时，在额外控制介入之前会建议翻倍。比值低于一可能建议缩容，但容差、缺失测量、就绪处理、上下限和扩缩行为，都可能阻止或限制最终动作。公式描述起点，不是所有条件下都直接生效的完整算法。&lt;/p&gt;
&lt;p&gt;CPU 利用率以资源 requests 为分母。改变请求值，即使用掉的 CPU 没变，也可能改变利用率，所以纵向与横向伸缩需要协调。指标接口必须可用，目标还要支持 scale 子资源。HPA 修改 Deployment 的副本数量，再由 Deployment 控制器管理 ReplicaSet 与 Pod；这几层职责不同，不应把它们统称为一个瞬间完成的操作。&lt;/p&gt;
&lt;p&gt;默认协调周期为十五秒，但它不是端到端响应时间的承诺。指标自身还有采集窗口和传输延迟。需要区分跨 Pod 的平均用量、测量管线里的时间平均，以及控制器对建议值的稳定处理。一条过旧但格式有效的数字，仍可能误导运行正常的控制器，造成尖峰过去以后才扩容的现象。&lt;/p&gt;
&lt;h2 id="稳定的是建议历史不是上次调整时间"&gt;稳定的是建议历史，不是上次调整时间&lt;/h2&gt;
&lt;p&gt;缩容稳定机制保留近期副本建议，并在配置窗口中选择最高值。默认缩容窗口为五分钟，autoscaling/v2 的 behavior 可以进一步调整。它不是从上一次真正改变副本数开始计时的简单冷却期。即使某次较高建议没有改变现有数量，只要仍在窗口里，也可能推迟下一次缩容，这正是下面模型专门展示的差异。&lt;/p&gt;
&lt;p&gt;速率策略另外限制一段时间内可以改变多少 Pod 或百分比。因此扩容也可能被限制，不能说它永远立即、无限进行。最小副本应考虑启动延迟所需余量，最大副本应符合节点容量和成本。稳定机制能降低反复变化，却不能修复选错瓶颈或目标值的问题；把窗口加长只是改变响应方式，不会让错误信号变正确。&lt;/p&gt;
&lt;h2 id="选择增加副本能够缓解的压力"&gt;选择增加副本能够缓解的压力&lt;/h2&gt;
&lt;p&gt;CPU 对计算密集型请求可能有效，队列深度或每副本工作量可能更适合后台工作者。但延迟或任意资源图表，并不会天然成为合适的控制输入。如果所有副本都在等待同一台已经饱和的数据库，继续增加副本可能扩大争用，反而延长等待时间。需要先确认瓶颈能否随着应用副本增加而解除。&lt;/p&gt;
&lt;p&gt;具体检查新增一个副本后会发生什么：工作能否分配给它，每副本压力是否下降，下游是否还有足够容量。长连接、分区不均与缓存预热都会推迟效果。控制器仅仅改变数量，不会替应用重新分片或自动迁移已有连接。观察这些行为，可以判断问题应该通过扩容解决，还是需要改变工作分配或下游容量。&lt;/p&gt;
&lt;h2 id="坏掉的样子"&gt;坏掉的样子&lt;/h2&gt;
&lt;p&gt;缺失指标表示未知需求，不表示需求为零。多指标配置中，即使一路失败，另一条有效扩容建议仍可能使用；但失败来源会阻止其余指标建议的缩容。个别 Pod 缺失样本也有保守处理。应通过条件和事件区分这些情况，而不是假定每次缺少一个样本就停止所有动作，也不要据此手工删除仍在服务的容量。&lt;/p&gt;
&lt;p&gt;maxReplicas 过低时，计算出的建议被限制，ScalingLimited 可以显示这一情况，但队列仍在增长。过高的下限也会掩盖真实低需求。部署自动化反复写固定 replicas 会与 HPA 竞争。还应检查 CPU 请求变化和启动就绪状态，再归咎于普通滚动更新：HPA 支持 Deployment 发布，并不是把每个初始化 Pod 都当成可以随意缩掉容量的零负载样本。&lt;/p&gt;
&lt;h2 id="快速验证"&gt;快速验证&lt;/h2&gt;
&lt;p&gt;保存为 &lt;code&gt;hpa_loop.py&lt;/code&gt;，执行 &lt;code&gt;python3 hpa_loop.py&lt;/code&gt;。它用标准库建模比例计算、上下限，以及取近期最高建议的缩容窗口。模型明确省略容差、速率策略、逐 Pod 样本和真实 Kubernetes 协调，不应被当作生产控制器的完整复制品。运行只涉及本地算术与列表状态，不访问集群。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;math&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;TARGET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;50.0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;MIN_REPLICAS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;MAX_REPLICAS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;WINDOW_S&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;desired&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;replicas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;replicas&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;TARGET&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MIN_REPLICAS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MAX_REPLICAS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;replicas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;t=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;s metric=missing hold=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;replicas&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;replicas&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;recommendation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;desired&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;replicas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;[:]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;history&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;WINDOW_S&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recommendation&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;recommendation&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;replicas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;replicas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;recommendation&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;t=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;s raw=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;recommendation&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;replicas&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;history&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;replicas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;replicas&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;replicas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;replicas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;240&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;replicas&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;replicas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;replicas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;replicas&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="c1"&gt;# A recent recommendation, not a recent scale event.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;replicas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;replicas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;541&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;replicas&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;replicas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;replicas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;replicas&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;desired&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;desired&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;PASS: recommendation history holds 6 at t=400, then shrinks to 2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;零秒时模型从三个副本扩到六个。二百四十秒时再次建议六个，数量并未变化。四百秒时新的原始建议为两个，但近期的六副本建议仍在窗口内，所以继续保持六个；此时距离上次实际扩容已超过五分钟。五百四十一秒时，那个较高建议离开窗口，模型才缩到两个。这说明约束来自建议历史，而不是上次调整的时间戳。&lt;/p&gt;
&lt;p&gt;两种语言中的代码均在本轮通过 Python 3.12.14，包含输入缺失保持和两个边界断言。这些结果只证明模型声明的行为。调试真实集群时，还应按时间对照 HPA 条件、副本建议、实际数量、Pod 就绪和应用延迟。本轮没有运行真实控制器或指标管线，本地成功不能代替真实系统验证。&lt;/p&gt;
&lt;h2 id="延伸阅读"&gt;延伸阅读&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.xbcatm.com/zh/posts/kubernetes-requests-limits-floor-cap/"&gt;requests 与 limits：Pod 的下限与上限&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.xbcatm.com/zh/posts/k8s-rolling-update-is-a-strategy/"&gt;Kubernetes 滚动更新逐步替换 Pod&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/"&gt;Kubernetes 水平自动伸缩文档&lt;/a&gt;，HPA 如何推演副本数，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/"&gt;HorizontalPodAutoscaler 演练&lt;/a&gt;，多指标与状态条件的完整示例，已核验 2026-10-15。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>HPA 按负载缩放 Deployment 的副本</title><link>https://www.xbcatm.com/zh/posts/kubernetes-hpa-autoscale-workloads/</link><pubDate>Wed, 11 Aug 2027 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/posts/kubernetes-hpa-autoscale-workloads/</guid><description>横向自动伸缩器盯一个指标，把副本数量调到目标区间内。</description><content:encoded>&lt;p&gt;HorizontalPodAutoscaler 是反馈控制器，不是容量规划器，默认同步间隔为十五秒。根据配置，它读取资源、自定义或外部指标，再更新工作负载的 scale 子资源。基本计算为 desiredReplicas = ceil(currentReplicas * currentMetricValue / desiredMetricValue)，之后还要考虑缺失数据、就绪状态、容忍度、稳定窗口及上下界，不能只靠这一行公式预测全部实际行为。&lt;/p&gt;
&lt;p&gt;一份看起来正确的 HPA 对象，不证明容量必然及时到位。指标可用性、请求、副本上限、调度及应用启动都会影响结果。应分别查看建议副本数与真实就绪容量；提高期望数量不会制造新节点，也不能自动修好受阻发布。判断伸缩是否有效，必须跟踪从指标到应用接流量的整段过程。&lt;/p&gt;
&lt;h2 id="比值与死区"&gt;比值与死区&lt;/h2&gt;
&lt;p&gt;CPU 利用率目标把实际 CPU 用量与相关请求比较，得到百分比，目标本身也是百分比。如果每个 Pod 只有一个容器，都请求 200m，目标为百分之六十，那么每 Pod 120m 是便于理解的简化等价值。它不是异构 Pod 或所有指标类型通用的分母；多个容器和不同请求会改变聚合结果。&lt;/p&gt;
&lt;p&gt;默认容忍度为一附近的 0.1。应把它理解为控制死区，而不是承诺 132m 永不伸缩、133m 必然动作。指标聚合、整数利用率、就绪状态及实现中的算术都会影响边界。忽略其他修正时，三个副本以 160m 对比 120m 目标，会建议四个副本；舍入、速率策略与最终能否调度仍需分别观察，不能从一个毫核数字直接推出真实扩容时刻。&lt;/p&gt;
&lt;h2 id="缺失指标与一票否决"&gt;缺失指标与一票否决&lt;/h2&gt;
&lt;p&gt;缺失 Pod 指标采用保守处理，假定的贡献会削弱建议扩容或缩容的幅度。CPU 对尚未就绪的 Pod 还有额外处理，涉及可配置的就绪延迟与初始化窗口。这些规则围绕启动采样及就绪变化，不表示所有 Pod 都一律被忽略整整五分钟。诊断时应查看实际采样和状态时间，不能把默认窗口当成一个简单倒计时。&lt;/p&gt;
&lt;p&gt;CPU 利用率计算需要相关资源请求；即使 kubectl top 能报告用量，缺失 request 仍可能让该指标不可计算。配置多个指标时，HPA 选择最大的建议副本数；一个不可用指标可能阻止缩容，另一个有效指标却仍能支持扩容。因此，缺一个来源不代表整个控制器必然停止，必须结合每个指标的结果和状态条件解释。&lt;/p&gt;
&lt;h2 id="稳定窗口与速率限制"&gt;稳定窗口与速率限制&lt;/h2&gt;
&lt;p&gt;默认缩容会考虑五分钟建议窗口，并选取其中仍相关的最高建议，以减轻振荡。这不是从外部负载曲线回落开始严格等待五分钟的保证。建议记录、当前副本、上下界和行为配置共同决定何时缩减。默认扩容稳定窗口为零，但用户可以修改行为；没有稳定窗口也不表示完全没有同步、采样和启动延迟。&lt;/p&gt;
&lt;p&gt;默认扩容速率策略允许十五秒内增加四个 Pod 或当前数量的一倍，取允许变化较大的策略。默认缩容策略可允许在其周期内全部减少，但仍受到稳定窗口和最小副本数限制。这是允许速率，不是自动按一、五、十不断增长的命令。指标必须要求相应扩容，maxReplicas 仍会封顶，新增副本还要经过实际调度与就绪过程。&lt;/p&gt;
&lt;h2 id="坏掉的样子"&gt;坏掉的样子&lt;/h2&gt;
&lt;p&gt;利用率 unknown 可能来自缺失请求、指标不可用、授权问题或其他获取与计算错误。应读取 ScalingActive 及其原因，不能把 unknown 直接当作没有 CPU 样本的证明。需要检查所有相关容器，只查第一个容器不能确定整份 Pod 的分母；同时还要核对准入默认值与实际 HPA 指标类型。&lt;/p&gt;
&lt;p&gt;达到 maxReplicas 的工作负载，即使控制器正确执行配置，也可能继续过载。ScalingLimited 能说明数量受限，但容量规划还需要节点空间、启动延迟和下游承载能力。提高上限可能只制造更多 Pending Pod，或压垮共享依赖。宣布自动伸缩成功前，应比较期望副本、可用副本与服务延迟，不能只看到 desired 数字增加就认为问题解决。&lt;/p&gt;
&lt;h2 id="快速验证"&gt;快速验证&lt;/h2&gt;
&lt;p&gt;以下保留的只读诊断本轮没有重新执行。请明确选择获准测试集群和命名空间，并把简略输出与完整 HPA、所有相关容器一起核对。&lt;/p&gt;
&lt;p&gt;确认指标存在、分母存在，以及控制器到底决定了什么：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl get hpa web -o wide
kubectl top pods -l app=web
kubectl get deploy web -o jsonpath=&amp;#39;{.spec.template.spec.containers[0].resources.requests.cpu}{&amp;#34;\n&amp;#34;}&amp;#39;
kubectl get hpa web -o jsonpath=&amp;#39;{range .status.conditions[*]}{.type}={.status} {.reason}{&amp;#34;\n&amp;#34;}{end}&amp;#39;
kubectl describe hpa web | sed -n &amp;#39;/Events/,$p&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;第一行给出当前利用率对目标利用率；这一列显示 &lt;code&gt;&amp;lt;unknown&amp;gt;&lt;/code&gt;，说明所需值未能建立，并不必然表示原始用量样本缺失。第三条在没有 CPU request 时什么都不打印，而这正是第二条能跑通、HPA 却仍拒绝伸缩的常见原因。最后两条给出状况原因和 &lt;code&gt;FailedGetResourceMetric&lt;/code&gt; 事件，真正的解释在那里。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/"&gt;Horizontal Pod Autoscaling&lt;/a&gt;，算法、容忍度、缺失指标处理、稳定窗口与默认策略，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/"&gt;HPA walkthrough&lt;/a&gt;，对 metrics-server 与 requests 的依赖，已核验 2026-10-15。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kubernetes-sigs/metrics-server"&gt;metrics-server&lt;/a&gt;，HPA 读取的 resource metrics API 实现，已核验 2026-10-15。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>