起因:工具 Pod 人間蒸發
某天要查資料庫,照慣例執行 kubectl exec -it psql-client,結果 Pod 不見了。
這個 psql-client 是用來連 RDS PostgreSQL 的工具容器,平常拿它跑 SQL 查詢、檢查資料表大小。問題是——沒有人記得刪過它,也沒有任何記錄顯示是誰或什麼原因讓它消失。
這件事本身影響不大,重建一個就好。但它暴露了三個更深層的問題:為什麼 Pod 會無聲消失?為什麼查不到是誰刪的?為什麼整個叢集沒有留下任何操作軌跡?
陷阱一:裸 Pod 沒有人管它的死活
問題本質
當初建立 psql-client 的指令大概是這樣:
kubectl run psql-client --image=postgres:15-alpine --restart=Never -- sleep infinity
這行指令建立的是一個裸 Pod(Bare Pod)——直接建立 Pod 物件,不隸屬於任何 Deployment、ReplicaSet 或 StatefulSet。
裸 Pod 和 Deployment 管理的 Pod,差別在於有沒有 Controller 在背後看著它。Deployment 的 ReplicaSet Controller 會持續監控 Pod 數量,少一個就補一個。裸 Pod 沒有 Controller,它的生死完全取決於節點的命運。
不處理會怎樣
裸 Pod 在以下情境會永久消失:
- 節點縮容:Auto Scaler 移除節點時,上面的裸 Pod 直接消失,不會被重新排程
- 節點升級:EKS 節點群組更新 AMI 時,舊節點上的裸 Pod 隨之銷毀
- 節點故障:底層 EC2 掛掉,裸 Pod 不會在其他節點重建
- 驅逐(Eviction):節點資源不足時,裸 Pod 通常最先被驅逐
關鍵在於:這些事件都不會產生告警。Pod 就是靜靜地消失了,直到你某天需要用它才發現。
解決方案:用 Deployment 管理
apiVersion: apps/v1
kind: Deployment
metadata:
name: psql-client
spec:
replicas: 1
selector:
matchLabels:
app: psql-client
template:
metadata:
labels:
app: psql-client
spec:
containers:
- name: psql-client
image: postgres:15-alpine
command: ["sleep", "infinity"]
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
cpu: 100m
memory: 128Mi
為什麼要設 resources?因為這是一個 24/7 運行但幾乎不做事的容器。cpu: 10m 代表 0.01 核心,memory: 32Mi 是最低限度的記憶體。設定 requests 也能避免它在資源緊張時被最先驅逐——沒有 requests 的 Pod 屬於 BestEffort QoS 等級,是驅逐優先順序最高的。
有人可能會問:一個偶爾才用的工具,有必要 24/7 跑著嗎?確實可以改成「需要時才建、用完就刪」的模式。但如果團隊成員不熟悉 kubectl 指令,一個隨時可用的工具 Pod 省去的溝通成本遠超那 10m CPU。
陷阱二:K8s Events 只活一小時
發現 Pod 消失後,第一反應是查 Events:
kubectl get events --field-selector involvedObject.name=psql-client
結果:No resources found。
為什麼查不到
Kubernetes Events 的預設 TTL 是 1 小時。這是 kube-apiserver 的 --event-ttl 參數決定的,而在 EKS 中,你無法修改這個參數——因為 Control Plane 由 AWS 託管。
1 小時意味著什麼?意味著只要你不是在事件發生後立刻去查,所有線索都已經消失。Pod 被驅逐、被刪除、OOMKilled——什麼都查不到。
為什麼這麼短
這不是 AWS 的限制,而是 Kubernetes 的設計選擇。Events 儲存在 etcd 中,而 etcd 是整個叢集的核心狀態儲存。如果 Events 保留太久,etcd 的儲存壓力會影響叢集效能。1 小時是在「可追溯性」和「儲存效率」之間的取捨。
怎麼解
既然不能改 TTL,就把 Events 導出去。在 EKS 上,最直接的方式是啟用 Control Plane Logging 中的 Audit Log,讓所有 API 操作記錄進 CloudWatch Logs(下一節會詳細說明)。
關鍵差異:etcd Events 是 K8s 內部的短期紀錄,Audit Log 則是持久化到外部儲存的完整操作日誌。兩者記錄的內容也不同——Events 偏向「發生了什麼事」(Pod scheduled、image pulled),Audit Log 偏向「誰做了什麼操作」(user X deleted pod Y)。
陷阱三:EKS Audit Log 預設是關閉的
查完 Events 之後,我試著從 CloudWatch Logs 找線索:
aws logs filter-log-events \
--log-group-name "/aws/eks/my-cluster/cluster" \
--filter-pattern '"psql-client" "delete"' \
--region ap-east-2
結果:ResourceNotFoundException。Log Group 根本不存在。
預設狀態
EKS 的 Control Plane Logging 有五種日誌類型:
| 日誌類型 | 內容 | 預設狀態 |
|---|---|---|
api | API Server 請求/回應 | 關閉 |
audit | 誰在什麼時候做了什麼 | 關閉 |
authenticator | 身份驗證事件 | 關閉 |
controllerManager | Controller 運作日誌 | 關閉 |
scheduler | Pod 排程決策 | 關閉 |
全部預設關閉。 這代表除非你主動啟用,否則叢集上發生的所有操作都不會留下紀錄。有人刪了 Production 的 Deployment?查不到。有人改了 Secret?查不到。
啟用 Audit Log
aws eks update-cluster-config \
--name my-cluster \
--region ap-east-2 \
--logging '{"clusterLogging":[{"types":["audit"],"enabled":true}]}'
啟用後,所有 Kubernetes API 呼叫都會記錄到 CloudWatch Logs 的 /aws/eks/<cluster-name>/cluster Log Group。每筆記錄包含:
- 誰:操作者的身份(IAM User/Role)
- 什麼時候:精確時間戳
- 做了什麼:API 動詞(create、delete、patch…)
- 對什麼資源:namespace、resource type、resource name
- 從哪裡:來源 IP
費用控制:設定保留期限
Audit Log 的量不小——每個 API 呼叫都會產生一筆記錄。CloudWatch Logs 預設保留期限是永久,這會導致儲存費用持續累積。
aws logs put-retention-policy \
--log-group-name "/aws/eks/my-cluster/cluster" \
--retention-in-days 60
60 天足以涵蓋大多數事後追查的需求。如果有合規要求需要更長的保留期間,可以考慮將日誌導出到 S3(成本約為 CloudWatch 的十分之一)。
結語
這三個問題有一個共同點:在出事之前,你完全不會意識到它們的存在。裸 Pod 好好跑著、Events 查不查無所謂、Audit Log 沒開也不影響服務。直到某天需要追查問題時,才發現所有線索都已經不在了。
維運的本質不是救火,而是在火災發生前確保消防系統可用。這三個設定——Deployment 管理工具 Pod、理解 Events TTL 的限制、啟用 Audit Log——都是花十分鐘就能完成的事,但能在關鍵時刻省下幾小時的盲目排查。
