结论先说:不要跟 HPA 的 Pod 级语义较劲,要么「把 sidecar 从扩缩信号里剥离出来」,要么「接受 sidecar 是固定成本、只按 main 的真实负载扩缩」。 在你 10 节点 / 200+ Pod 的规模下,我推荐两条路二选一,而不是叠着用:
一、先算清「到底谁在推 HPA」——别被 82% 这个数字骗了
你的 Pod 级利用率 = (0.85+1.2)/(0.5+2) = 82%,但拆开看:
- istio-proxy:0.85 / 0.5 = 170%(已经超 request,说明 request 设小了)
- main:1.2 / 2 = 60%(健康)
关键洞察:Envoy 的 CPU 是随流量线性涨的(mTLS/压缩/重试),它超 request 不是 bug,是你给它的 request 预算不足。 HPA 用 Pod 级算,等于「main 还有余量、sidecar 已经超载」时一起扩——方向其实是对的(sidecar 真扛不住了),只是你把「sidecar 超载」误判成了「白扩」。所以第一步不是改 HPA,是把 istio-proxy 的 request 从 0.5 提到实测 P99:
# 用一周的 P99 定 request,而不是拍脑袋
# Prometheus: histogram_quantile(0.99, sum(rate(container_cpu_usage_seconds_total{container="istio-proxy"}[5m])) by (container))
resources:
requests: {cpu: 1, memory: 512Mi} # 0.5 → 1,对齐 P99
limits: {cpu: 2, memory: 1Gi}
request 提到 1 后,Pod 级 = (0.85+1.2)/(1+2) ≈ 67% < 70% 阈值,扩容压力自然缓解,且没稀释 main(你之前担心的装箱率变差,是因为 request 提了但 limit 没同步——limit 也提到 2 就解决)。这一步零新增组件,先做,往往就够。
二、若 sidecar 负载长期波动、想真正「只看 main」:Prometheus Adapter + AverageValue
这是你问的方案 2,我给可直接落的配置。注意:AverageValue 要的是「绝对值」,不是百分比,所以指标得是 main 容器的绝对 CPU:
# prometheus-adapter api_rule.yaml
rules:
- series:
- match: {namespace: "order-svc", container: "main"}
name: container_cpu_usage_seconds_total
resources: {overrides: {namespace: {resource: "namespace"}, pod: {resource: "pod"}}}
metricsQuery: 'sum(rate(container_cpu_usage_seconds_total{{namespace="{{.namespace}}",pod="{{.name}}",container="main"}}[5m]))'
HPA 改成:
spec:
metrics:
- type: Resource
resource:
name: cpu
target:
type: AverageValue
averageValue: "1500m" # 按 main 绝对 CPU 1.5 核扩缩,sidecar 完全不进分母
运维成本量化:prometheus-adapter 在 10 节点集群稳态 RSS ~150–300MB、CPU 可忽略,多一条告警链路。判定阈值:如果你侧边有 ≥3 个「sidecar 占大头」的无状态服务,这套投入摊薄后划算;如果只有订单服务这一个,我更推荐下面方案三。
三、最省事的正解(成本视角我投这个):KEDA 按业务 QPS
你 main 的真实负载就是 QPS(3200 时 P99=38ms 健康)。sidecar 的 CPU 只是 QPS 的影子,直接控源头:
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata: {name: order-hpa, namespace: order-svc}
spec:
scaleTargetRef: {name: order-deploy}
minReplicaCount: 4
maxReplicaCount: 20
triggers:
- type: prometheus
metricName: http_requests_total
query: 'sum(rate(http_requests_total{service="order"}[1m]))'
threshold: "220" # 按「单副本健康 QPS」反推:3200/14≈229,留 10% 余量取 220
pollingInterval: 15
收益:扩缩信号直接绑定业务指标,sidecar 彻底出局;比「CPU 利用率」更贴业务(CPU 高可能是 GC,QPS 才是真负载)。成本:KEDA operator ~200MB + 复用你已有的 Prometheus,不新增指标链路。
落地顺序与一周观测
- Day 0–1:先把 istio-proxy request 提到 P99(方案一),观察 HPA 是否还白扩——很多团队到这就结束了。
- 若仍被 sidecar 噪声打扰:上 KEDA 按 QPS(方案三),不要同时上 prometheus-adapter(方案二)——两套扩缩信号会打架。
- 一周观测指标:
hpa_replicas曲线是否还在 sidecar 抖动时跳(应消失)- 节点 CPU 分配率从 89% 回落到 <75%(成本省回 ~30%)
- main P99 延迟稳定在 <50ms(别为了省钱把 main 挤爆)
- 扩容响应时间:KEDA 的 pollingInterval=15s 比 HPA 15s 一致,但 QPS 突增时 KEDA 触发更快(CPU 要等负载爬坡,QPS 是即时的)
一句话权衡:sidecar 的 CPU 是「流量税」,别让它决定你扩多少副本——要么把它 request 喂饱(最省),要么直接用 QPS 当扩缩信号(最准),Prometheus Adapter 的 per-container CPU 是「看起来最优雅但运维成本最高」的中间态,只有多 sidecar 服务时才值得。
补充一个可观测性侧的反例:我们去年做 sidecar 治理时,按"request 提到 P99"思路调整后,HPA 确实不抖了,但一周后发现 main 的 P99 在 18:00–20:00 被推高 12ms——根因是 istio-proxy 提 request 后,节点上 main 的 limit 相对变紧,GC 时长被放大。建议方案一落地时同步盯两条指标:container_cpu_cfs_throttled_seconds_total(main 容器)和 node_cpu_pressure 的 15min avg。另外 KEDA 按 QPS 的方案有个常被忽略的坑:QPS 突增但单请求变重(比如批量接口被调),CPU 实际会顶不住,threshold 要按"QPS×P99 单请求成本"反推而不是纯 QPS。