EKS 集群成本失控的六个常见原因
集群账单每月上涨但业务量没变?这六个原因覆盖了我们排查过的绝大多数情况。
为什么容器集群的成本特别容易失控
虚拟机时代,加一台机器是个需要审批的动作。容器时代,开发提个 PR 改一下 replicas 就能多占几倍资源,而且没人会察觉。加上资源请求(requests)的设置往往靠拍脑袋,浪费就这样累积起来。
下面六个原因按我们排查的出现频率排序。
六个原因与排查方法
-
01
资源请求远高于实际用量
最常见。开发怕 OOM 就把 requests 设得很大,调度器按 requests 占位,节点看起来满了实际 CPU 只用了 15%。排查:对比 Pod 的 requests 与实际 usage,比值超过 3 倍的都是候选优化项。
交付物:requests vs usage 对比表、调整建议清单
-
02
节点规格与 Pod 规格不匹配
比如 Pod 需要 3 vCPU,节点是 4 vCPU,一个节点只能放一个 Pod,剩下 1 核浪费。排查:看节点的资源碎片率。Karpenter 能自动挑合适规格,Cluster Autoscaler 不行。
交付物:节点碎片分析、规格调整方案
-
03
没用 Spot
无状态服务跑在按需实例上,是最直接的浪费。排查:统计各 NodePool 的 capacity-type 分布,无状态负载的 Spot 占比应该在 60% 以上。
交付物:Spot 占比统计、可迁移负载清单
-
04
僵尸负载
早就没人用的服务还在跑,测试环境的临时部署忘了删,废弃的 CronJob 每天还在执行。排查:看各 Namespace 的入口流量,长期零流量的服务找负责人确认。
交付物:零流量服务清单、清理确认单
-
05
跨可用区流量费
服务间调用随机跨可用区,流量费在大流量场景下相当可观。排查:看 VPC Flow Logs 里的跨 AZ 流量占比。解法是拓扑感知路由(Topology Aware Routing),优先同 AZ 调用。
交付物:跨 AZ 流量统计、拓扑路由配置
-
06
日志与监控成本
容易被忽略但金额不小。DEBUG 级别日志全量入 CloudWatch,Prometheus 指标基数爆炸(label 里放了 user_id 这种高基数字段)。排查:看 CloudWatch Logs 的 IncomingBytes 和 Prometheus 的时间序列数量。
交付物:日志量分析、指标基数报告
requests 与实际用量对比
#!/usr/bin/env bash
# 对比 Pod 的资源请求与实际用量,找出超配严重的负载
# 依赖:kubectl、metrics-server、jq
set -euo pipefail
NS="${1:---all-namespaces}"
printf '%-24s %-38s %10s %10s %8s\n' NAMESPACE POD REQ_CPU USE_CPU RATIO
printf '%s\n' "$(printf '=%.0s' {1..96})"
# 取当前用量
kubectl top pods $NS --no-headers 2>/dev/null > /tmp/top.txt
kubectl get pods $NS -o json | jq -r '
.items[]
| select(.status.phase == "Running")
| . as $p
| [$p.metadata.namespace, $p.metadata.name,
([$p.spec.containers[].resources.requests.cpu // "0"]
| map(if test("m$") then (sub("m$";"") | tonumber)
else (tonumber * 1000) end)
| add)] | @tsv
' | while IFS=$'\t' read -r ns pod req_m; do
use_m=$(awk -v n="$ns" -v p="$pod" '
$0 ~ p { gsub(/m$/,", $2); print $2; exit }
' /tmp/top.txt)
[[ -z "${use_m:-}" ]] && continue
[[ "$use_m" -eq 0 ]] && use_m=1
ratio=$(awk -v r="$req_m" -v u="$use_m" 'BEGIN { printf "%.1f", r/u }')
# 只输出超配 3 倍以上的
if (( $(awk -v x="$ratio" 'BEGIN { print (x >= 3) }') )); then
printf '%-24s %-38s %9sm %9sm %7sx\n' "$ns" "${pod:0:38}" "$req_m" "$use_m" "$ratio"
fi
done
echo
echo "提示:ratio 高不代表一定要降。先确认是否有周期性峰值或启动瞬时高峰。"
kubectl top 只看当前瞬时值,正式评估要用 Prometheus 查一段时间的 p95,避免把有周期性峰值的服务误判成超配。
优化动作优先级
| 动作 | 预期收益 | 风险 | 建议顺序 |
|---|---|---|---|
| 清理僵尸负载 | 5%-20% | 低(需确认无人使用) | 1 |
| 调整 requests | 15%-35% | 中(需观察 OOM) | 2 |
| 引入 Karpenter | 10%-25% | 低 | 3 |
| 无状态服务上 Spot | 30%-50%(该部分) | 中(需容错设计) | 4 |
| Graviton 迁移 | 15%-25% | 中(需兼容验证) | 5 |
| 日志级别与采样治理 | 5%-15% | 低 | 并行做 |
| 拓扑感知路由 | 跨 AZ 流量降 60%+ | 低 | 并行做 |
| 购买 Savings Plans | 覆盖部分降 30%+ | 低(但有承诺) | 最后(等用量稳定) |
顺序很重要。先把浪费挤掉再买承诺折扣,否则会为浪费的资源锁三年价格。
怎么让优化不反弹
技术手段之外,最有效的是把成本数据推到团队面前。我们的做法是用 Kubecost 或标签方案按 Namespace 算成本,每月自动推送给各团队负责人。看得见就会有人管。