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:
1 parent
37e2f0febc
commit
8bea331183
1 file changed
+81
-38
+81
-38
@@ -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"
|
||||
Reference in new issue
Block a user