chore(release): 发布脚本对齐"统一使用 latest"策略

清单本来就用 latest(deployment.yaml / docker-compose.yml / prod.yml 的
${IMAGE_TAG:-latest}),但上一版 release.sh 会把 deployment.yaml 改成
不可变 tag 并把 pullPolicy 改成 IfNotPresent —— 与"统一用 latest"相反,
而且改完不提交的话下次 apply 又会退回最新 tag,容易造成实际版本与预期不符。

改为:
  1) 一次构建打三个 tag:latest / v0.1 / <版本>-<日期>-<commit>,同时推送
  2) 推送后**校验三个远端 digest 一致**,不一致直接退出(并发构建覆盖
     latest 的经典事故,先拦住别部署)
  3) 部署走 kubectl apply + rollout restart(image=latest + Always 强制重拉)
  4) 部署完再核对 Pod digest 与远端 latest,不一致立即提示
  5) 不再默认改清单;需要"固定版本部署"时显式 `--pin`
  6) 回滚方式写清楚:把旧的不可变 tag 重新打成 latest 推上去

同时把集群里被我为了避开 tag 竞争临时改成 v11-1028-2252 的 Deployment
恢复成 latest + Always(已验证 Pod / Hub latest / Hub v0.1 三者 digest 一致
= 187062644c0f)。
This commit is contained in:
opsx committed 2026-10-28 23:02:41 +00:00
1 parent 37e2f0febc
commit 8bea331183
1 file changed
+81 -38
+81 -38
View File
@@ -1,23 +1,28 @@
#!/usr/bin/env bash
# ============================================================
# release.sh — 构建 + 推送 + 更新 K8s 清单为不可变 tag
# release.sh — 构建 + 推送(latest / v0.1 / 不可变 tag 三合一)
# ============================================================
# 为什么需要它:
# 线上清单一直用 `lghost/lokistack:latest` + imagePullPolicy: Always。
# 这个组合的问题是**静默回滚**:任何一次构建(包括别人并行的、或几分钟前
# 基于旧代码启动的构建)都会覆盖 latest,节点重启后拉到的可能不是你以为的版本。
# 本项目已实际踩过:两个构建并行,后完成但代码更旧的把 latest 覆盖回去,
# Pod 跑回旧镜像,刚验过的功能"莫名消失"。
# 本项目的发布策略:**清单与容器启动统一使用 latest**。
# kubernetes/deployment.yaml → lghost/lokistack:latest + imagePullPolicy: Always
# docker-compose.yml → lghost/lokistack:latest
# docker-compose.prod.yml → lghost/lokistack:${IMAGE_TAG:-latest}
#
# 做法:
# 1) 用 <版本>-<日期>-<短commit> 作为不可变 tag(同 commit 同代码 → 同 tag)
# 2) 同时推 latest 供本地/开发使用(不用于生产清单)
# 3) 把 kubernetes/deployment.yaml 的 image 改成不可变 tag
# 4) 打印三方 digest 供核对(本地镜像 / Hub tag / Pod)
# 所以每次发版必须让 latest 与 v0.1 **同时**指向新镜像,否则会出现
# "k8s 拉到新版、docker 起还是旧版"这种版本漂移。
#
# 本脚本另外额外打一个不可变 tag(<版本>-<日期>-<短commit>):
# - 它不写进任何清单,只用于事后追溯/回滚(仓库里能查到那一刻的确切镜像)
# - 需要"固定版本部署"的生产环境可以手动改成这个 tag
#
# 为什么不在脚本里改 yml 的 tag:
# 上一版脚本会把 deployment.yaml 的 image 改成不可变 tag 并把 pullPolicy
# 改成 IfNotPresent,结果与"统一用 latest"的约定相反,还容易忘记提交
# 导致下次 apply 退回旧 tag。改成默认不动清单。
#
# 用法:
# bash scripts/release.sh # 构建 + 推送 + 改清单 + 部署
# bash scripts/release.sh --no-deploy # 只构建/推送/改清单,不 apply
# bash scripts/release.sh # 构建 + 推三个 tag + 滚动重启(拉 latest)
# bash scripts/release.sh --no-deploy # 只构建 + 推送,不碰集群
# bash scripts/release.sh --pin # 额外把 deployment.yaml 钉到不可变 tag(可选)
# ============================================================
set -euo pipefail
@@ -25,49 +30,87 @@ cd "$(dirname "$0")/.."
IMAGE_REPO="${IMAGE_REPO:-lghost/lokistack}"
VERSION="${VERSION:-2.0.0}"
DEPLOY="${1:-}"
MODE="${1:-}"
SHORT_SHA=$(git rev-parse --short=8 HEAD 2>/dev/null || echo "nogit")
DIRTY=""
if ! git diff --quiet 2>/dev/null; then
DIRTY="-dirty"
echo "⚠️ 工作区有未提交改动,tag 会带 -dirty 标记(发布前建议先提交)"
echo "⚠️ 工作区有未提交改动,不可变 tag 会带 -dirty(发布前建议先提交)"
fi
TAG="${VERSION}-$(date +%Y%m%d)-${SHORT_SHA}${DIRTY}"
IMMUTABLE_TAG="${VERSION}-$(date +%Y%m%d)-${SHORT_SHA}${DIRTY}"
echo "=========================================="
echo " 构建镜像: ${IMAGE_REPO}:${TAG}"
echo " 构建: ${IMAGE_REPO} → latest / v0.1 / ${IMMUTABLE_TAG}"
echo "=========================================="
docker build -t "${IMAGE_REPO}:${TAG}" -t "${IMAGE_REPO}:latest" .
IMAGE_ID=$(docker images --format '{{.ID}}' "${IMAGE_REPO}:${TAG}" | head -1)
echo "✅ 构建完成 $IMAGE_ID"
docker build \
-t "${IMAGE_REPO}:latest" \
-t "${IMAGE_REPO}:v0.1" \
-t "${IMAGE_REPO}:${IMMUTABLE_TAG}" .
echo "推送不可变 tag 与 latest..."
docker push "${IMAGE_REPO}:${TAG}" | tail -1
IMAGE_ID=$(docker images --format '{{.ID}}' "${IMAGE_REPO}:latest" | head -1)
echo "✅ 构建完成 ${IMAGE_ID}"
echo ""
echo "推送三个 tag(latest 与 v0.1 必须同 digest,否则环境间会版本漂移)..."
docker push "${IMAGE_REPO}:latest" | tail -1
docker push "${IMAGE_REPO}:v0.1" | tail -1
docker push "${IMAGE_REPO}:${IMMUTABLE_TAG}" | tail -1
echo "更新 kubernetes/deployment.yaml → ${IMAGE_REPO}:${TAG}"
# 只替换 image 行,pullPolicy 改成 IfNotPresent:
# 不可变 tag 不需要每次强拉,节点上有缓存就直接用(也避免仓库抖动导致启动失败)
sed -i "s|image: ${IMAGE_REPO}:.*|image: ${IMAGE_REPO}:${TAG}|" kubernetes/deployment.yaml
sed -i "s|imagePullPolicy: Always|imagePullPolicy: IfNotPresent|" kubernetes/deployment.yaml
# ── 校验三个 tag 指向同一个镜像 ──
echo ""
echo "校验远端 digest 一致性..."
digest_of() {
curl -s "https://hub.docker.com/v2/repositories/${IMAGE_REPO}/tags/$1" 2>/dev/null \
| python3 -c 'import sys,json;print(json.load(sys.stdin).get("digest","?"))' 2>/dev/null \
|| echo "?"
}
D_LATEST=$(digest_of latest)
D_V01=$(digest_of v0.1)
D_IMM=$(digest_of "${IMMUTABLE_TAG}")
echo " latest : ${D_LATEST:0:19}"
echo " v0.1 : ${D_V01:0:19}"
echo " ${IMMUTABLE_TAG}: ${D_IMM:0:19}"
if [ "${D_LATEST:0:19}" != "${D_V01:0:19}" ] || [ "${D_LATEST:0:19}" != "${D_IMM:0:19}" ]; then
echo "❌ 三个 tag digest 不一致,先别继续部署(可能是并发构建覆盖了 latest)"
exit 1
fi
echo " → 一致 ✅"
if [ "${DEPLOY}" != "--no-deploy" ]; then
if [ "${MODE}" = "--pin" ]; then
echo ""
echo "(--pin)把 kubernetes/deployment.yaml 钉到 ${IMMUTABLE_TAG}..."
sed -i "s|image: ${IMAGE_REPO}:.*|image: ${IMAGE_REPO}:${IMMUTABLE_TAG}|" kubernetes/deployment.yaml
sed -i "s|imagePullPolicy: Always|imagePullPolicy: IfNotPresent|" kubernetes/deployment.yaml
echo "⚠️ 记得提交 deployment.yaml,否则下次 apply 会退回 latest"
fi
if [ "${MODE}" != "--no-deploy" ]; then
export KUBECONFIG="${KUBECONFIG:-/root/.kube/config}"
echo "应用清单..."
echo ""
echo "应用清单(image: latest + Always → 强制重拉)..."
kubectl apply -f kubernetes/deployment.yaml
# 清单内容没变时 apply 不会触发滚动,这里显式重启一次
kubectl -n log rollout restart deploy/lokistack
kubectl -n log rollout status deploy/lokistack --timeout=300s
POD_DIGEST=$(kubectl -n log get po -l app=lokistack -o jsonpath='{.items[0].status.containerStatuses[0].imageID}' 2>/dev/null | sed 's/.*@//')
POD_DIGEST=$(kubectl -n log get po -l app=lokistack \
-o jsonpath='{.items[0].status.containerStatuses[0].imageID}' 2>/dev/null | sed 's/.*@//')
echo ""
echo "=========================================="
echo " 部署完成,请核对三方 digest 一致:"
echo " 清单 tag : ${IMAGE_REPO}:${TAG}"
echo " Pod : ${POD_DIGEST:0:19}"
echo " 部署完成,核对 Pod 与远端 latest:"
echo " 远端 latest : ${D_LATEST:0:19}"
echo " Pod 运行中 : ${POD_DIGEST:0:19}"
if [ "${POD_DIGEST:0:19}" = "${D_LATEST:0:19}" ]; then
echo " → 一致 ✅"
else
echo " → 不一致 ❌(Pod 可能还在拉取,或节点镜像缓存异常)"
fi
echo "=========================================="
echo "⚠️ 记得把 deployment.yaml 的变更一并提交,否则下次 apply 会退回旧 tag"
fi
echo ""
echo "回滚方法:"
echo " git log --oneline -- kubernetes/deployment.yaml # 找上一个 tag"
echo " git checkout <commit> -- kubernetes/deployment.yaml && kubectl apply -f kubernetes/deployment.yaml"
echo "回滚方法(latest 策略下靠不可变 tag 追溯):"
echo " docker pull ${IMAGE_REPO}:<上次的 ${VERSION}-日期-commit>"
echo " docker tag ${IMAGE_REPO}:<该 tag> ${IMAGE_REPO}:latest && docker push ${IMAGE_REPO}:latest"
echo " kubectl -n log rollout restart deploy/lokistack"