8.3 本地发布与回滚实验
前置知识:本章前两页
预计时间:30 分钟
练习环境:继续使用
$release_lab_dir;容器小节使用 Docker Engine 28+ 的默认 bridge/NAT 网络及 Bash、curl 7.71+完成标志:用同一 JAR 构建两个可识别版本,按已知良好 image ID 实际回滚,并验证结果
自动化结构已经审过,但远程 registry 和生产环境不属于这个练习的范围。本实验留在本机,用同一个 JAR 完成版本切换、健康检查、证据记录、实际回滚和清理。
cd "$release_lab_dir"热身:验证制品
mvn --batch-mode verify
sha256sum target/release-lab-1.0.0.jar
jar tf target/release-lab-1.0.0.jar | headmacOS 可用 shasum -a 256。临时项目默认没有 Git 历史,此时记录源码文件和 JAR 的哈希;只有源码确实来自提交时才记录 commit SHA。保留构建日志;不要把 -DskipTests 写进默认发布路径。
启动 JAR 后,从另一个终端请求 /health 与 /,再用普通终止信号结束你刚启动的 PID。不要用 kill -9 作为首选,它不给应用清理机会。
挑战:容器候选版本
这里先验证已知良好 v1,再运行 v2,最后主动发起一次回滚演练,按记录的 v1 image ID 启动并重新核对 /health、/version 和容器实际镜像。两份镜像复用同一个 JAR,仅公开版本标识不同;这验证版本切换,不证明业务兼容性或零停机。临时端口会随容器改变,因此也不模拟生产流量入口。
(
set -e
lab_tag="workshop-ch08-release:${RANDOM}-${RANDOM}"
good_tag="${lab_tag}-good"
candidate_tag="${lab_tag}-candidate"
active_id=
cleanup_release() {
if [ -n "$active_id" ]; then
docker stop "$active_id" >/dev/null 2>&1 || true
fi
docker image rm "$candidate_tag" "$good_tag" >/dev/null 2>&1 || true
}
trap cleanup_release EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
start_and_check() {
active_id=$(docker run --detach --rm --env HOST=0.0.0.0 \
--publish 127.0.0.1::8080 "$1")
published=$(docker port "$active_id" 8080/tcp)
host_port=${published##*:}
curl --fail --silent --show-error --retry 10 --retry-all-errors \
--retry-delay 1 --max-time 2 "http://127.0.0.1:$host_port/health"
observed=$(curl --fail --silent --show-error --max-time 2 \
"http://127.0.0.1:$host_port/version")
test "$observed" = "$2"
printf 'version=%s image=%s\n' "$observed" "$1"
}
docker build --build-arg APP_VERSION=v1 -t "$good_tag" .
good_image=$(docker image inspect "$good_tag" --format '{{.Id}}')
start_and_check "$good_image" v1
docker stop "$active_id"
active_id=
docker build --build-arg APP_VERSION=v2 -t "$candidate_tag" .
candidate_image=$(docker image inspect "$candidate_tag" --format '{{.Id}}')
test "$good_image" != "$candidate_image"
start_and_check "$candidate_image" v2
docker logs "$active_id"
docker image inspect "$candidate_tag" --format '{{json .RepoDigests}}'
docker stop "$active_id"
active_id=
start_and_check "$good_image" v1
test "$(docker inspect "$active_id" --format '{{.Image}}')" = "$good_image"
printf 'rollback verified\n'
)预期依次看到 version=v1、version=v2、version=v1 和 rollback verified,子 Shell 退出时清理本次容器与标签。本地镜像通常没有 registry RepoDigests,但有内容寻址的 image ID;推送后才记录 registry digest。本页没有执行推送。
写发布与回滚判据
候选 commit / artifact hash / image digest:
发布前通过的测试:
数据库迁移是否向后兼容:
健康检查路径与超时:
观测窗口:
停止推广条件:
已知良好 digest:
回滚命令与负责人:
回滚后验证:回滚不是“再拉一次 latest”。它应把流量或部署声明恢复到已知良好的不可变版本。若数据库迁移不向后兼容,单纯换回镜像可能仍会失败,必须提前设计前向兼容或恢复方案。
排障
| 现象 | 处理 |
|---|---|
| 端口冲突 | 查看监听者,优先换随机回环端口,不杀未知进程 |
| 线上仍是旧代码 | 对比部署 digest、容器 .Image、响应版本字段和缓存层 |
no main manifest attribute | 检查 JAR manifest 与打包插件;不要假定普通 JAR 可执行 |
| health 返回 200 但业务失败 | 健康检查过浅;增加关键依赖就绪检查和外部合成请求 |
| 容器刚启动就退出 | docker logs、退出码、入口命令、配置和权限 |
实验结束后确认临时项目再删除:
cd
ls -ld -- "$workdir"
rm -r -- "$workdir"
unset release_lab_dir本机实验通过后,你已经有了版本切换和回滚的可核对记录。第一卷接着学习程序本身的语法与结构。