diff --git a/scripts/release.sh b/scripts/release.sh index 0873b61..bbe777b 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -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 -- 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"