清单本来就用 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)。
117 lines
5.1 KiB
Bash
Executable File
117 lines
5.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# ============================================================
|
||
# release.sh — 构建 + 推送(latest / v0.1 / 不可变 tag 三合一)
|
||
# ============================================================
|
||
# 本项目的发布策略:**清单与容器启动统一使用 latest**。
|
||
# kubernetes/deployment.yaml → lghost/lokistack:latest + imagePullPolicy: Always
|
||
# docker-compose.yml → lghost/lokistack:latest
|
||
# docker-compose.prod.yml → lghost/lokistack:${IMAGE_TAG:-latest}
|
||
#
|
||
# 所以每次发版必须让 latest 与 v0.1 **同时**指向新镜像,否则会出现
|
||
# "k8s 拉到新版、docker 起还是旧版"这种版本漂移。
|
||
#
|
||
# 本脚本另外额外打一个不可变 tag(<版本>-<日期>-<短commit>):
|
||
# - 它不写进任何清单,只用于事后追溯/回滚(仓库里能查到那一刻的确切镜像)
|
||
# - 需要"固定版本部署"的生产环境可以手动改成这个 tag
|
||
#
|
||
# 为什么不在脚本里改 yml 的 tag:
|
||
# 上一版脚本会把 deployment.yaml 的 image 改成不可变 tag 并把 pullPolicy
|
||
# 改成 IfNotPresent,结果与"统一用 latest"的约定相反,还容易忘记提交
|
||
# 导致下次 apply 退回旧 tag。改成默认不动清单。
|
||
#
|
||
# 用法:
|
||
# bash scripts/release.sh # 构建 + 推三个 tag + 滚动重启(拉 latest)
|
||
# bash scripts/release.sh --no-deploy # 只构建 + 推送,不碰集群
|
||
# bash scripts/release.sh --pin # 额外把 deployment.yaml 钉到不可变 tag(可选)
|
||
# ============================================================
|
||
set -euo pipefail
|
||
|
||
cd "$(dirname "$0")/.."
|
||
|
||
IMAGE_REPO="${IMAGE_REPO:-lghost/lokistack}"
|
||
VERSION="${VERSION:-2.0.0}"
|
||
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(发布前建议先提交)"
|
||
fi
|
||
IMMUTABLE_TAG="${VERSION}-$(date +%Y%m%d)-${SHORT_SHA}${DIRTY}"
|
||
|
||
echo "=========================================="
|
||
echo " 构建: ${IMAGE_REPO} → latest / v0.1 / ${IMMUTABLE_TAG}"
|
||
echo "=========================================="
|
||
docker build \
|
||
-t "${IMAGE_REPO}:latest" \
|
||
-t "${IMAGE_REPO}:v0.1" \
|
||
-t "${IMAGE_REPO}:${IMMUTABLE_TAG}" .
|
||
|
||
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
|
||
|
||
# ── 校验三个 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 [ "${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 "应用清单(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/.*@//')
|
||
echo ""
|
||
echo "=========================================="
|
||
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 "=========================================="
|
||
fi
|
||
|
||
echo ""
|
||
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"
|