Kubernetes資源白吃型容器掃描實戰:誰在浪費你的CPU和內存?
1. 背景:我們要找的不是“高消耗”,而是“低產能”
在日常運維中,很多人喜歡盯著哪些 Pod 用資源最多,仿佛“高 CPU、高內存”才是麻煩制造者。但我們這次的視角反過來了:
我們關注的是——那些資源申請了一大堆,結果幾乎沒怎么用的容器!
說白了:你申請了 1 核 2G,結果只用了 20m、100Mi,這不就是“吃不完還拿一堆”的典型資源浪費?
這些資源申請過多但實際使用很少的容器,是拖累集群整體資源利用率的罪魁禍首——調度器認為節點沒資源,但其實有的是空間!
所以我們寫了一個腳本,專門找出這些“白吃型”容器,幫助我們優化資源分配。
#!/bin/bash
OUTPUT_FILE="output.md"
# 寫入Markdown表頭
echo "| NAMESPACE | POD | CONTAINER | CPU_USED(m) | CPU_REQUEST(m) | CPU_LIMIT(m) | MEM_USED(Mi) | MEM_REQUEST(Mi) | MEM_LIMIT(Mi) |" > "$OUTPUT_FILE"
echo "| :- | :- | :- | :- | :- | :- | :- | :- | :- |" >> "$OUTPUT_FILE"
# 打印終端表頭
printf "%-20s %-40s %-30s %-10s %-15s %-15s %-15s %-15s %-15s\n" \
"NAMESPACE" "POD" "CONTAINER" "CPU_USED(m)" "CPU_REQUEST(m)" "CPU_LIMIT(m)" "MEM_USED(Mi)" "MEM_REQUEST(Mi)" "MEM_LIMIT(Mi)"
# 遍歷pods
kubectl get pods --all-namespaces --no-headers | awk '{print $1, $2}' | while read namespace pod; do
kubectl top pod "$pod" -n "$namespace" --containers --no-headers 2>/dev/null | while read -r pod_name container cpu_used mem_used; do
cpu_used_value=$(echo "$cpu_used" | sed 's/m//')
mem_used_value=$(echo "$mem_used" | sed 's/Mi//')
if [[ -z "$cpu_used_value" ]]; then
continue
fi
cpu_request=$(kubectl get pod "$pod" -n "$namespace" -o jsonpath="{.spec.containers[?(@.name==\"$container\")].resources.requests.cpu}" 2>/dev/null)
mem_request=$(kubectl get pod "$pod" -n "$namespace" -o jsonpath="{.spec.containers[?(@.name==\"$container\")].resources.requests.memory}" 2>/dev/null)
cpu_limit=$(kubectl get pod "$pod" -n "$namespace" -o jsonpath="{.spec.containers[?(@.name==\"$container\")].resources.limits.cpu}" 2>/dev/null)
mem_limit=$(kubectl get pod "$pod" -n "$namespace" -o jsonpath="{.spec.containers[?(@.name==\"$container\")].resources.limits.memory}" 2>/dev/null)
# cpu_request處理
if [[ "$cpu_request" == *m ]]; then
cpu_request_value=$(echo "$cpu_request" | sed 's/m//')
elif [[ -n "$cpu_request" ]]; then
cpu_request_value=$((cpu_request * 1000))
else
cpu_request_value=0
fi
# mem_request處理
if [[ "$mem_request" == *Mi ]]; then
mem_request_value=$(echo "$mem_request" | sed 's/Mi//')
elif [[ "$mem_request" == *Gi ]]; then
mem_request_value=$(echo "$mem_request" | sed 's/Gi//' | awk '{print $1 * 1024}')
else
mem_request_value=0
fi
# cpu_limit處理
if [[ "$cpu_limit" == *m ]]; then
cpu_limit_value=$(echo "$cpu_limit" | sed 's/m//')
elif [[ -n "$cpu_limit" ]]; then
cpu_limit_value=$((cpu_limit * 1000))
else
cpu_limit_value=0
fi
# mem_limit處理
if [[ "$mem_limit" == *Mi ]]; then
mem_limit_value=$(echo "$mem_limit" | sed 's/Mi//')
elif [[ "$mem_limit" == *Gi ]]; then
mem_limit_value=$(echo "$mem_limit" | sed 's/Gi//' | awk '{print $1 * 1024}')
else
mem_limit_value=0
fi
cpu_used_value=${cpu_used_value:-0}
mem_used_value=${mem_used_value:-0}
# 打印到終端
printf "%-20s %-40s %-30s %-10s %-15s %-15s %-15s %-15s %-15s\n" \
"$namespace" "$pod" "$container" "$cpu_used_value" "$cpu_request_value" "$cpu_limit_value" "$mem_used_value" "$mem_request_value" "$mem_limit_value"
# 同時追加到Markdown文件
echo "| $namespace | $pod | $container | $cpu_used_value | $cpu_request_value | $cpu_limit_value | $mem_used_value | $mem_request_value | $mem_limit_value |" >> "$OUTPUT_FILE"
done
done
echo "? 結果已保存到 $OUTPUT_FILE,并同步打印到了終端!"2. 腳本干了什么?
我們這個腳本做的事情其實非常簡單、直接:
在所有命名空間中,掃描每個容器,只保留那些 CPU 和內存實際使用量都低于它的資源 Request 和 Limit 的容器。
條件如下:
?CPU_USED < CPU_REQUEST 且 CPU_USED < CPU_LIMIT?MEM_USED < MEM_REQUEST 且 MEM_USED < MEM_LIMIT
只要滿足以上條件的容器,我們就認定它“吃不完”,并將其列入輸出報告中。
舉個例子
假設某個容器配置如下:
項目 | 數值 |
CPU Used | 35m |
CPU Request | 200m |
CPU Limit | 500m |
Mem Used | 90Mi |
Mem Request | 512Mi |
Mem Limit | 1024Mi |
這個容器看起來“沒啥問題”,但從資源角度,它就是個嚴重冗余:
?申請了 200m,結果只用了 35m,浪費超過 80%?內存申請了 512Mi,結果只用了 90Mi,浪費近 83%
我們會把這個容器列出來,并記錄在 Markdown 表格中。
3. 輸出結果長什么樣?
我們輸出的數據像這樣:
NAMESPACE | POD | CONTAINER | CPU_USED(m) | CPU_REQUEST(m) | CPU_LIMIT(m) | MEM_USED(Mi) | MEM_REQUEST(Mi) | MEM_LIMIT(Mi) |
default | user-service-xxx | user-api | 35 | 200 | 500 | 90 | 512 | 1024 |
auth | token-service | signer | 10 | 100 | 200 | 50 | 256 | 512 |
每一行,都是一個“吃不完”的容器。
4. 我們為什么只關注“使用 < 請求/限制”的?
Kubernetes 的調度邏輯是以 Request 值 為準的:
?你申請了 500m CPU,系統就預留給你這么多?哪怕你實際上只用 5m,別人也搶不到你這部分資源
所以,這些“吃不完”的容器,就是資源調度的黑洞:
?不會觸發報警?不會拖垮服務?但就是霸占資源,別的服務調不過來
定期找出這些容器,把 Request 降下來,能顯著提升集群整體可用資源量,從而:
?節省節點數量?降低成本?提升調度成功率
5. 實際收獲
我們在多個測試和生產集群中跑了腳本,得出了幾個有趣結論:
5.1 大量容器“吃不完”
?CPU 使用長期低于 50m,但申請卻是 500m 的服務比比皆是?內存使用不足 100Mi,申請卻是 1G、2G 的也不少
這些服務配置基本可以砍一半都綽綽有余。
5.2 資源浪費并不等于性能好
很多團隊出于“保險”目的,習慣性給服務多申請點資源,但現實是:
多申請 ≠ 更穩定,反而會阻塞別人用資源,降低集群整體健康度。
資源配太多,不但沒必要,還會讓 HPA 失效(因為看起來沒啥使用率變化)。
5.3 調整資源配置的真實效果
我們試著將幾個服務的資源 Request 按實際使用情況下調了 50%:
?節省節點數約 2 臺(每臺 16 核 64G)?新部署的服務調度成功率提升明顯?系統整體負載下降,擴容需求延后
6. 建議下一步怎么做?
以下是我們建議的實踐方案:
?定期執行這個資源篩查腳本(每周一次即可)
加個 CronJob 或 Jenkins 任務,把結果郵件發給團隊。
?將輸出表格作為資源優化依據
可以給開發負責人看,讓他們根據實際使用情況調整資源。
?把結果導入 Grafana/Excel 做可視化
更直觀地展示每個 Namespace 的資源浪費情況,有助于決策和資源管控。
7. 附:如何使用這個腳本?
?要求集群部署了 metrics-server[1]?腳本使用標準 kubectl 和 bash 語法,不依賴額外插件?輸出為 Markdown 表格,終端也會實時顯示
8. 總結一句話
真正拖慢你 Kubernetes 集群的,不是吃得太多的容器,而是那些“吃得太少還拿得多”的!
9. 小問答時間(Q&A)
?Q1:為什么資源使用小于 Request 和 Limit 也值得關注? ?? 因為這意味著資源配置過度了!雖然不會造成服務故障,但會導致集群資源浪費,影響其他 Pod 的調度甚至增加成本。
?Q4:要不要把資源配得很寬裕,以防突發流量? ?? 不推薦!可以使用 HPA(Horizontal Pod Autoscaler)來應對突發流量,而不是長期浪費資源。按需自動擴容才是現代云原生的正確打開方式。
References
[1] metrics-server: https://github.com/kubernetes-sigs/metrics-server





























