EKS CloudWatch 成本優化完整實戰:Log Retention、Fluent Bit 停用、disable_metric_extraction boolean 陷阱,以及 Auto Mode NodePool 衝突排查¶
文檔資訊
- 分類: debugging
- 難度: intermediate
- 預估閱讀時間: 14 分鐘
- 標籤:
eks,cloudwatch,cost-optimization,container-insights,disable_metric_extraction,addon-configuration,fluent-bit,log-retention,bash-script,multi-cluster,eks-auto-mode,karpenter,cluster-autoscaler,bottlerocket,nodepool,pdb,pod-eviction
摘要¶
對三個 EKS 叢集(locust-eks、ps-eks、ps-eks-prod)執行 CloudWatch 成本優化,完整執行三階段:(1) 設定 12 個 log group retention 為 7 天,(2) 停用 Fluent Bit 日誌收集,(3) 停用 Container/Pod 細粒度 metrics。第三步中使用 object 格式的 disable_metric_extraction 導致 cloudwatch-agent pods 全數 CrashLoopBackOff,根本原因是 CloudWatch Agent v1.0 只接受 boolean,修正後恢復正常。另外發現 ps-eks 同時啟用 EKS Auto Mode NodePool(Karpenter),搶先調度 pod 到 Bottlerocket 節點,導致 Istio init container 卡在 Init:0/1,CA 因看到「No unschedulable pods」而不觸發擴容。透過 nodePools=[] 移除 Karpenter NodePool,保留 elasticLoadBalancing/blockStorage,再手動擴容 managed node group 並 drain 舊節點(PDB 阻擋需 kubectl delete pod 強制驅逐)後,問題解決。
關鍵學習¶
-
disable_metric_extraction 必須是 boolean(true),object 格式 {container: true, pod: true} 在 CloudWatch Agent v1.0 下觸發 CrashLoopBackOff,v4.9.0 和 v4.10.1 兩個版本都會踩到此坑
-
CrashLoopBackOff 診斷:kubectl logs
-n amazon-cloudwatch --tail=30,搜尋 'Expected: boolean, given: object' 錯誤 -
Log group retention 設定後用 describe-log-groups 查 RetentionDays 欄位,None 表示未設定,dataplane/host 容易遺漏
-
EKS Auto Mode NodePool(Karpenter)event-driven,反應速度秒級,比 CA 的 2-4 分鐘快,會搶先調度 pod
-
CA 不擴容的根本原因:Karpenter 搶先啟動節點,pod 已被調度,CA 看到 No unschedulable pods,不觸發 scale-up
-
Auto Mode 節點識別:hostname 格式為 i-xxxxxxxxx(instance ID),label 有 eks.amazonaws.com/compute-type=auto;managed node group 節點格式為 ip-x-x-x-x.region.compute.internal
-
aws eks update-cluster-config 修改 compute-config 時,必須同時傳入 --compute-config、--kubernetes-network-config、--storage-config 三個,否則報錯
-
設 nodePools=[] 而非 enabled=false,可保留 Auto Mode 的 elasticLoadBalancing 和 blockStorage,只移除 Karpenter NodePool
-
PDB 阻擋 drain 時,若評估可接受短暫中斷,直接 kubectl delete pod 強制驅逐是最快解法
-
CA 和 Auto Mode NodePool 不建議同時存在:Karpenter 搶走 pod 後 CA 誤判不需擴容,難以預測調度行為
技術細節¶
CloudWatch Addon 的類型陷阱¶
amazon-cloudwatch-observability addon 的 config-translator 組件在解析 JSON 配置時,對 disable_metric_extraction 欄位有嚴格類型驗證。
錯誤格式(object,導致 CrashLoopBackOff):
正確格式(boolean):
錯誤訊息來自 pod logs:
Under path : /logs/metrics_collected/kubernetes/disable_metric_extraction | Error : Invalid type. Expected: boolean, given: object
E! Configuration validation first phase failed. Agent version: 1.0.
此錯誤在 **v4.9.0 和 v4.10.1 兩個版本**上都會觸發,且錯誤訊息需要看 pod logs 才能發現。addon schema 雖然顯示支援巢狀配置,但 CloudWatch Agent v1.0 的 config-translator 不接受 object 類型。
EKS Auto Mode NodePool 與 Cluster Autoscaler 的衝突根因¶
當叢集同時存在 CA(管理 managed node group)和 EKS Auto Mode(managed Karpenter NodePool)時,新的 pod 會被 Karpenter 調度到 Auto Mode 節點(Bottlerocket OS),而非 CA 管理的節點(AL2023)。
CA logs 直接確認原因:
Karpenter 因 event-driven 設計(秒級)比 CA(2-4 分鐘)快,搶先啟動節點並調度 pod。CA 看到 No unschedulable pods,因此不觸發擴容。既有 workload 的 Istio init container 在 Bottlerocket 節點上卡住(Init:0/1),但 CA 無法感知此問題。
節點識別方式:
- Auto Mode 節點:hostname 格式為 i-xxxxxxxxx(instance ID),label 有 eks.amazonaws.com/compute-type=auto,OS 為 Bottlerocket
- Managed node group 節點:hostname 格式為 ip-x-x-x-x.region.compute.internal,OS 為 AL2023
Auto Mode API 指令的陷阱¶
aws eks update-cluster-config 修改 compute-config 時,必須同時傳入三個 config 型別,否則報錯 The type for cluster update was not provided:
aws eks update-cluster-config \
--name ps-eks \
--region ap-southeast-1 \
--compute-config '{"enabled":true,"nodePools":[]}' \
--kubernetes-network-config '{"elasticLoadBalancing":{"enabled":true}}' \
--storage-config '{"blockStorage":{"enabled":true}}'
設 nodePools=[] 而非 enabled=false,可保留 Auto Mode 的 elasticLoadBalancing 和 blockStorage 功能,只移除 Karpenter NodePool。
PDB 阻擋 Drain 的處理方式¶
當 kubectl drain 被 PodDisruptionBudget 阻擋時(如 istio-alb-ingress-gateway-dev):
error when evicting pods/"istio-alb-ingress-gateway-dev-xxx" -n "istio-system" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
若評估可接受短暫中斷(非生產環境),直接 kubectl delete pod 強制驅逐:
Deployment controller 會自動在新節點建立替代 pod。
What Changed¶
CloudWatch 成本優化三階段全部完成¶
針對三個 EKS 叢集(locust-eks、ps-eks、ps-eks-prod)的 12 個 log group 全部設定 7 天 retention(初次遺漏 dataplane/host,查 describe-log-groups 發現 None 後補設)。停用兩個 active 叢集的 Fluent Bit(fluent-bit DaemonSet 成功消失)。停用 Container/Pod 細粒度 metrics 時,第一次使用 object 格式觸發所有 cloudwatch-agent pods CrashLoopBackOff,看 pod logs 找到 Expected: boolean, given: object 後修正為 disable_metric_extraction: true,所有 pods 恢復 Running。
Auto Mode NodePool 衝突問題完整解決¶
ps-eks 叢集在 CloudWatch 操作完成後發現 Auto Mode NodePool 與 CA 共存問題。CA logs 直接確認根因:No unschedulable pods,因為 Karpenter 搶先在 Bottlerocket 節點上調度 pod,導致 CA 誤判不需擴容。Istio init container 在 Bottlerocket 節點上卡在 Init:0/1。
解決流程:透過 nodePools=[] 移除 Karpenter NodePool(保留 elasticLoadBalancing/blockStorage)→ 手動擴容 managed node group desiredSize 從 2→3 → drain Auto Mode 節點(i-0193785783d82837c)→ PDB 阻擋 istio-alb-ingress-gateway-dev 需 kubectl delete pod 強制驅逐 → 所有 ps namespace pods 遷移到正常節點(ip-192-168-100-49)並恢復 Running 2/2。
So What¶
這份文檔涵蓋兩個高頻率踩坑陷阱:
陷阱一(CloudWatch boolean 陷阱):disable_metric_extraction 的類型錯誤會讓所有叢集的 CloudWatch monitoring 中斷,且錯誤訊息不直觀,必須翻 pod logs。這個坑在 v4.9.0 和 v4.10.1 兩個版本都存在,任何未來執行相同優化的人都可能踩到。
陷阱二(Auto Mode NodePool 搶調度):當叢集從傳統架構(CA + managed node group)啟用 EKS Auto Mode 時,EKS 預設會加入 general-purpose 和 system NodePool。這些 NodePool 的 Karpenter 會搶先調度 pod 到 Bottlerocket 節點,CA 因為看不到 unschedulable pods 而完全沉默。問題難以察覺,因為 CA 不報錯、只是不動作。
Trade-offs¶
- disable_metric_extraction: true 停用**所有** container/pod 層級的 metric extraction,無法選擇性停用,這是 CloudWatch Agent v1.0 schema 的限制
- 停用 Fluent Bit 後 application logs 不再送 CloudWatch,需確認有其他日誌路徑(如 self-managed Fluent Bit 或 Loki)
- 7 天 retention 適合降低儲存成本,有合規需求(audit logs)的 log group 需個別調整
- 維持 CA + 移除 Auto Mode NodePool(方案 B,本次採用):保留 elasticLoadBalancing/blockStorage,但失去 Karpenter 秒級擴容優勢;CA 擴容需 2-4 分鐘;為最小風險選擇
- 完全啟用 Auto Mode(未選):擴容快、零運維、自動安全更新(Bottlerocket + 21 天強制輪替),但需要徹底驗證所有 workload 與 Bottlerocket OS 的相容性,特別是 Istio init container
- kubectl delete pod 強制驅逐(本次採用):比等待 PDB 放行快,但會短暫中斷該服務;適合非生產環境
- **停用 metrics 後需等待一個計費週期**才能看到費用變化
Try It Fast¶
# Phase 1:設定所有 log group retention(三個叢集都跑)
for CLUSTER in locust-eks ps-eks ps-eks-prod; do
for LOG_TYPE in application performance dataplane host; do
aws logs put-retention-policy \
--log-group-name "/aws/containerinsights/${CLUSTER}/${LOG_TYPE}" \
--retention-in-days 7 \
--region ap-southeast-1
done
done
# 驗證(None 表示未設定,需補設)
aws logs describe-log-groups \
--log-group-name-prefix /aws/containerinsights/ \
--query 'logGroups[].{Name:logGroupName,RetentionDays:retentionInDays}' \
--output table
# Phase 2 + 3:停用 Fluent Bit + 停用細粒度 metrics(正確的 boolean 格式)
aws eks update-addon \
--cluster-name locust-eks \
--addon-name amazon-cloudwatch-observability \
--configuration-values '{"containerLogs":{"enabled":false},"agent":{"config":{"logs":{"metrics_collected":{"kubernetes":{"enhanced_container_insights":true,"accelerated_compute_metrics":false,"disable_metric_extraction":true}}}}}}' \
--region ap-southeast-1 \
--resolve-conflicts OVERWRITE
# 若出現 CrashLoopBackOff,立即檢查 logs
kubectl get pods -n amazon-cloudwatch -l app.kubernetes.io/name=cloudwatch-agent
kubectl logs <pod-name> -n amazon-cloudwatch --tail=30
# 搜尋 'Expected: boolean, given: object' → disable_metric_extraction 格式錯誤
# 移除 Auto Mode NodePool(保留 elasticLoadBalancing/blockStorage)
# 必須同時傳三個 config,否則報錯 'The type for cluster update was not provided'
aws eks update-cluster-config \
--name ps-eks \
--region ap-southeast-1 \
--compute-config '{"enabled":true,"nodePools":[]}' \
--kubernetes-network-config '{"elasticLoadBalancing":{"enabled":true}}' \
--storage-config '{"blockStorage":{"enabled":true}}'
# 驗證 nodePools 已清空
aws eks describe-cluster --name ps-eks --region ap-southeast-1 \
--query 'cluster.computeConfig' --output json
# Drain Auto Mode 節點(hostname 為 instance ID 格式)
kubectl drain i-0193785783d82837c --ignore-daemonsets --delete-emptydir-data
# 若 PDB 阻擋,強制驅逐
kubectl delete pod <blocked-pod-name> -n <namespace>
# 確認 CA 沒有擴容時,檢查 CA logs
kubectl logs deploy/cluster-autoscaler -n kube-system --tail=50 | grep -i -E "scale|expan|unschedulable"
# 出現 'No unschedulable pods' → pods 已被 Karpenter 調度,CA 正常但不動作
# 解法:移除 Auto Mode NodePool,讓 CA 重新接管
Recommendation¶
- 永遠使用 boolean 格式:
disable_metric_extraction只能設true或false,object 格式在 CloudWatch Agent v1.0 下導致 CrashLoopBackOff - 按順序執行並驗證:Log Retention → Fluent Bit → Metrics,每步驟後確認狀態再繼續
- 補設遺漏的 log group:application/performance 容易先設,dataplane/host 也要設定,用
describe-log-groups查 None 找出遺漏項 - 啟用 EKS Auto Mode 前先確認 workload 相容性:Bottlerocket OS 與某些 Istio init container 不相容,建議先在非生產環境驗證
- CA 和 Auto Mode NodePool 不建議同時存在:Karpenter 搶走 pod 後 CA 誤判不需擴容,兩者行為相互干擾,選一種機制負責節點管理
- 移除 NodePool 而非 disable Auto Mode:用
nodePools=[]保留 elasticLoadBalancing/blockStorage,比完全關閉 Auto Mode 更穩妥(方案 B) - update-cluster-config 必須同時傳三個 config:
--compute-config、--kubernetes-network-config、--storage-config缺一不可,否則報錯 - PDB 阻擋 drain 時評估風險再強制驅逐:非生產環境可
kubectl delete pod強制驅逐,生產環境建議先確認有副本再操作