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