build(release): 部署后自愈 + 版本戳,堵住"latest 被旧镜像覆盖"这类问题
CI / Backend (go vet + test) (push) Waiting to run
CI / Frontend (lint + build + test) (push) Waiting to run

=== 背景:同类问题已经出现三次 ===
  1) Docker Hub 502 导致 latest 推失败,Pod 仍跑旧镜像
  2) 并发构建互相覆盖 latest(谁后完成谁生效)
  3) 中断的构建后来完成,把 latest 推回旧代码(本次差点中招)

第 2 类已由 flock 独占锁解决;但前两类还有一个共同的后半段:
**没有人核对"部署后 Pod 到底跑的是不是刚推的那版"**。

=== 本次新增 ===
1) 部署后自愈:rollout 完成即核对 Pod digest 与远端 latest;
   不一致自动 `kubectl delete po` 强制重拉(节点缓存了旧 latest 映射是常见原因),
   最多等 4 分钟,仍不一致才 exit 1 —— 不再"报个 ❌ 就结束"。
2) 版本戳:核对通过后给 Deployment 打三个 annotation
     lokistack.io/release-image = <digest>
     lokistack.io/release-tag   = <版本-日期-commit>
     lokistack.io/release-time  = <ISO8601>
   以后排查"线上跑哪版"直接 `kubectl -n log get deploy lokistack -o yaml | grep release-`,
   不用再在 docker images / Hub API / Pod 之间来回比对。

=== 用法 ===
  以后发版统一走 `bash scripts/release.sh`(build+push+deploy+自愈+打戳一次完成)
  不要再手敲 docker build + docker push —— 那样会绕过 flock 锁,回到互相覆盖。
  bash scripts/release.sh --dry-run   只做前置检查
  bash scripts/release.sh --no-deploy 只发镜像不动集群
This commit is contained in:
opsx committed 2026-10-31 07:08:23 +00:00
1 parent 7be41f0a6b
commit 63445bd598
1 file changed
+36 -3
+36 -3
View File
@@ -25,6 +25,12 @@
# bash scripts/release.sh --pin # 额外把 deployment.yaml 钉到不可变 tag(可选) # bash scripts/release.sh --pin # 额外把 deployment.yaml 钉到不可变 tag(可选)
# bash scripts/release.sh --dry-run # 只检查锁与前置条件,不构建不推送 # bash scripts/release.sh --dry-run # 只检查锁与前置条件,不构建不推送
# #
# 部署后自愈与版本戳:
# 滚动完成后核对 Pod digest 与远端 latest;不一致时自动删 Pod 强制重拉
# (节点镜像缓存/拉取时机问题),仍不一致才 exit 1。
# 核对通过后给 Deployment 打三个 annotation,直接看集群就知道线上跑哪版:
# lokistack.io/release-image / release-tag / release-time
#
# 并发保护(重要): # 并发保护(重要):
# 本项目所有环境都拉 latest,两个构建同时跑会互相覆盖 latest: # 本项目所有环境都拉 latest,两个构建同时跑会互相覆盖 latest:
# 后完成的那次生效,先完成的那次白推。曾出现"中断的构建后来完成, # 后完成的那次生效,先完成的那次白推。曾出现"中断的构建后来完成,
@@ -153,17 +159,44 @@ if [ "${MODE}" != "--no-deploy" ]; then
kubectl -n log rollout restart deploy/lokistack kubectl -n log rollout restart deploy/lokistack
kubectl -n log rollout status deploy/lokistack --timeout=300s kubectl -n log rollout status deploy/lokistack --timeout=300s
POD_DIGEST=$(kubectl -n log get po -l app=lokistack \ pod_digest() {
-o jsonpath='{.items[0].status.containerStatuses[0].imageID}' 2>/dev/null | sed 's/.*@//') kubectl -n log get po -l app=lokistack \
-o jsonpath='{.items[0].status.containerStatuses[0].imageID}' 2>/dev/null | sed 's/.*@//'
}
POD_DIGEST=$(pod_digest)
echo "" echo ""
echo "==========================================" echo "=========================================="
echo " 部署完成,核对 Pod 与远端 latest:" echo " 部署完成,核对 Pod 与远端 latest:"
echo " 远端 latest : ${D_LATEST:0:19}" echo " 远端 latest : ${D_LATEST:0:19}"
echo " Pod 运行中 : ${POD_DIGEST:0:19}" echo " Pod 运行中 : ${POD_DIGEST:0:19}"
# ── 自愈:digest 不一致时强制重拉一次 ──
# 常见原因:节点缓存了旧的 latest 映射、或 rollout 与 push 完成时间太接近。
# 直接删 Pod 让 kubelet 重新解析 latest 通常就恢复了,不需要人工介入。
if [ "${POD_DIGEST:0:19}" != "${D_LATEST:0:19}" ]; then
echo " ⚠️ 不一致,删除 Pod 强制重新拉取 latest..."
kubectl -n log delete po -l app=lokistack --wait=false >/dev/null 2>&1 || true
for _ in $(seq 1 30); do
sleep 8
POD_DIGEST=$(pod_digest)
[ "${POD_DIGEST:0:19}" = "${D_LATEST:0:19}" ] && break
done
echo " 重拉后 Pod : ${POD_DIGEST:0:19}"
fi
if [ "${POD_DIGEST:0:19}" = "${D_LATEST:0:19}" ]; then if [ "${POD_DIGEST:0:19}" = "${D_LATEST:0:19}" ]; then
echo " → 一致 ✅" echo " → 一致 ✅"
# ── 版本戳:下次排查"线上到底跑哪版"直接看 annotation ──
kubectl -n log annotate deploy/lokistack --overwrite \
"lokistack.io/release-image=${D_LATEST#sha256:}" \
"lokistack.io/release-tag=${IMMUTABLE_TAG}" \
"lokistack.io/release-time=$(date -Is)" >/dev/null 2>&1 \
&& echo " 已标注 run 版本:${D_LATEST:0:19} (${IMMUTABLE_TAG})"
else else
echo " → 不一致 ❌(Pod 可能还在拉取,或节点镜像缓存异常)" echo " → 不一致 ❌(重拉后仍不符,检查节点镜像缓存 / 网络)"
echo "=========================================="
exit 1
fi fi
echo "==========================================" echo "=========================================="
fi fi