你的 Pod 是金魚嗎?Kubernetes 鏡像更新的 7 秒記憶陷阱
引言
對于這種案例,你們的處理思路是怎么樣的呢,是否真正的處理過,如果遇到,你們應該怎么處理。
我想大多數人都沒有遇到過。
最后有相關的學習群,有興趣可以加入。
引言:一場看似“靈異”的更新事故
某個周五的深夜,A 團隊的 DevOps 工程師小明正準備上線新功能。他自信地執行了滾動更新命令:
kubectl set image deployment/myapp myapp=myregistry.com/app:latest幾分鐘后,監控系統突然告警:“部分用戶請求返回舊版數據!”
小明檢查 Pod,發現一些 Pod 的鏡像哈希和倉庫中的最新鏡像不一致,仿佛舊版本代碼在集群中“陰魂不散”。
一場針對「鏡像鬼影」的偵查就此開始……
第一幕:鏡像鬼影的“作案手法”
1. 鏡像拉取策略的“潛規則”
Kubernetes 的 imagePullPolicy 控制鏡像拉取行為,共有三個選項:
? Always:每次創建 Pod 都強制從倉庫拉取鏡像(無論本地是否存在)。
? IfNotPresent:僅當本地不存在該鏡像時拉取(默認策略,“鬼影”元兇)。
? Never:完全依賴本地鏡像(通常僅用于測試或離線環境)。
關鍵問題:若使用 IfNotPresent 且鏡像標簽為 latest,當倉庫中的 latest 被覆蓋時,節點本地緩存的舊版鏡像會被誤認為“最新”。
2. 標簽覆蓋的“時空錯亂”
? CI/CD 流水線的常見錯誤:
# 每次構建都覆蓋 latest 標簽
docker build -t myregistry.com/app:latest .
docker push myregistry.com/app:latest? 后果:latest 標簽在不同時間點指向不同鏡像哈希(例如昨天是 sha256:abcd,今天是 sha256:1234)。Kubernetes 僅通過標簽名判斷是否需要更新,無法感知哈希變化。
3. 滾動更新的“認知盲區”
? Deployment 的更新邏輯:Kubernetes 通過對比 Deployment 中定義的鏡像名稱(如 myapp:latest)和當前 Pod 使用的鏡像名稱,決定是否觸發更新。
? 致命漏洞: 如果節點本地已緩存同名標簽的舊鏡像,新調度的 Pod 會直接使用本地緩存,導致集群中新舊版本共存。
第二幕:根因偵查——技術細節全解
1. 復現路徑:一場“量子態”的部署
圖片
2. 驗證實驗:親手制造“鬼影”
步驟 1:初始部署
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 3
template:
spec:
containers:
- name: myapp
image: myregistry.com/app:latest
imagePullPolicy: IfNotPresent # 默認配置
EOF步驟 2:推送新鏡像并更新
docker build -t myregistry.com/app:latest .
docker push myregistry.com/app:latest
kubectl set image deployment/myapp myapp=myregistry.com/app:latest步驟 3:檢查 Pod 鏡像哈希
kubectl get pods -o jsnotallow='{range .items[*]}{.metadata.name}{"\t"}{.status.containerStatuses[0].imageID}{"\n"}{end}'
# 輸出可能為:
# pod-1 myregistry.com/app:latest@sha256:abcd
# pod-2 myregistry.com/app:latest@sha256:1234
# pod-3 myregistry.com/app:latest@sha256:abcd第三幕:破解之術——徹底消滅鬼影
方案一:標簽唯一化(治本之策)
操作步驟:
1. 為每次構建生成唯一標簽:
COMMIT_SHA=$(git rev-parse --short HEAD)
docker build -t myregistry.com/app:$COMMIT_SHA .
docker push myregistry.com/app:$COMMIT_SHA2. 更新 Deployment 引用明確版本:
containers:
- name: myapp
image: myregistry.com/app:abc123 # 使用 Git Commit SHA
imagePullPolicy: IfNotPresent # 此時策略安全優點:
? 徹底避免標簽覆蓋問題。
? 天然支持版本回滾和審計。
方案二:強制拉取鏡像(應急方案)
適用場景:必須使用 latest 標簽時。操作步驟:
1. 修改 Deployment 配置:
containers:
- name: myapp
image: myregistry.com/app:latest
imagePullPolicy: Always # 強制每次拉取2. 觸發滾動更新:
kubectl rollout restart deployment/myapp注意事項:
? 可能增加部署時間(每次拉取鏡像)。
? 需確保鏡像倉庫訪問權限和網絡穩定性。
方案三:清理節點緩存(終極武器)
操作步驟:
1. 手動清理節點鏡像緩存:
# 登錄到節點執行
docker rmi myregistry.com/app:latest2. 自動清理(慎用):
# 使用 kubectl 在所有節點執行命令(需權限)
kubectl get nodes -o name | xargs -I{} kubectl debug node/{} --image=alpine -- rm -rf /var/lib/docker/image/overlay2/repositories.json風險提示:
? 可能影響其他服務的正常運行。
? 推薦在維護窗口期操作。
第四幕:防御工事——預防鬼影再現
1. 基礎設施加固
? 啟用鏡像倉庫的不可變標簽(Immutable Tags):多數鏡像倉庫(如 AWS ECR、Harbor)支持標簽不可變設置,禁止覆蓋已存在的標簽。
? 鏡像簽名與驗簽:使用 Cosign[1] 對鏡像簽名,并在 Kubernetes 中集成驗簽策略:
# 簽名鏡像
cosign sign --key mykey.pem myregistry.com/app:abc123
# 驗簽策略(需部署 Kyverno 或 OPA Gatekeeper)
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: check-image-signature
spec:
validationFailureAction: enforce
rules:
- name: verify-image-signature
match:
resources:
kinds:
- Pod
verifyImages:
- image: "myregistry.com/app:*"
key: |-
-----BEGIN PUBLIC KEY-----
YOUR_PUBLIC_KEY
-----END PUBLIC KEY-----2. 監控與告警
? Prometheus 監控鏡像版本一致性:通過 kube-state-metrics 采集 Pod 鏡像哈希,配置告警規則:
- alert: ImageHashMismatch
expr: |
count by (deployment) (
kube_pod_container_info{cnotallow="myapp"}
unless on(image) kube_deployment_spec_template_container_image{cnotallow="myapp"}
) > 0
annotations:
summary: "鏡像哈希不一致:{{ $labels.deployment }}"? Grafana 可視化面板:展示各 Deployment 的 Pod 鏡像哈希分布,快速發現“鬼影”。
3. 文化規范:團隊的“防鬼影”守則
? 禁止使用 latest 標簽:在 CI/CD 流程中強制檢查,拒絕推送 latest 標簽。
? 部署前預檢查腳本:
# 檢查 Deployment 是否使用 latest 標簽
if kubectl get deployment myapp -o jsnotallow='{.spec.template.spec.containers[0].image}' | grep -q ":latest"; then
echo "錯誤:禁止使用 latest 標簽!"
exit 1
fi? 定期培訓與攻防演練:模擬鏡像鬼影場景,訓練團隊快速定位和修復問題。
尾聲:鬼影退散,天下太平
通過唯一標簽、強制拉取策略、鏡像簽名和監控告警的四重防護,A 團隊終于告別了“鏡像鬼影”。小明在事后復盤會上感嘆:“原來 Kubernetes 不是玄學,只是我們對它的‘潛規則’了解太少!”
附:快速診斷命令清單
1. 查看 Pod 鏡像哈希:
kubectl get pods -o jsnotallow='{range .items[*]}{.metadata.name}{"\t"}{.status.containerStatuses[0].imageID}{"\n"}{end}'2. 對比倉庫鏡像元數據:
docker manifest inspect myregistry.com/app:latest | grep sha2563. 強制刪除節點緩存鏡像:
# 進入節點 Shell
kubectl debug node/<node-name> -it --image=alpine
# 在節點內執行
crictl rmi myregistry.com/app:latest從此,集群再無鬼影,工程師們終于可以安心喝咖啡了 ??。
結語
以上就是我們今天的內容,希望可以幫助到大家。
引用鏈接
[1] Cosign: https://github.com/sigstore/cosign

































