从一次提交得到一个可发布版本
承接 CI/CD 权责分离,本篇只负责构建与验证,不持有生产权限。继续使用 delivery-demo:提供首页、环境配置响应和健康端点的 Nginx 服务。数据库迁移在第 22 篇作为独立组件加入,不假装静态站点已经具备数据库业务。
环境约定:Linux runner、Docker Buildx、GitHub Actions;本地可用 Docker Desktop。示例构建 linux/amd64,目标服务器也应为该架构;ARM 服务器需统一修改构建平台。以下文件放在你自己的练习仓库。
沿用镜像,增加自动检查
保留构建与运行时配置的 site/、nginx/default.conf.template、Dockerfile 和 .dockerignore,不重新建立另一份示例。此时 /environment 已能反映 APP_ENV,首页与健康端点分别用于版本和健康验收。创建 scripts 目录,保存 scripts/test.sh:
#!/usr/bin/env bash
set -euo pipefail
IMAGE="${1:?传入待验证镜像}"
name="delivery-test-$$"
trap 'docker rm -f "$name" >/dev/null 2>&1 || true' EXIT
docker run -d --name "$name" "$IMAGE" >/dev/null
# 在容器内检查,不依赖宿主机端口分配。
for attempt in $(seq 1 30); do
if docker exec "$name" wget -q -O - http://127.0.0.1/healthz | grep -qx ok; then
docker exec "$name" wget -q -O - http://127.0.0.1/ | grep -q delivery-demo
docker exec "$name" wget -q -O - http://127.0.0.1/environment | grep -qx local
exit 0
fi
sleep 1
done
docker logs "$name"
exit 1本地验证:
docker build -t delivery-demo:test .
bash scripts/test.sh delivery-demo:test这是级冒烟检查。真实业务仍需自己的单元测试、接口测试与数据校验;HTTP 健康检查不能替代业务正确性验证。
构建一次,推送同一个镜像
将以下内容保存为 .github/workflows/ci.yml。PR 运行检查但不推送;只有合并到 main 才写入 GHCR。
name: CI
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Set image name
run: echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY,,}" >> "$GITHUB_ENV"
- name: Build local image
run: docker build --platform linux/amd64 --build-arg BUILD_LABEL="$GITHUB_SHA" -t "$IMAGE:$GITHUB_SHA" .
- name: Test image
run: bash scripts/test.sh "$IMAGE:$GITHUB_SHA"
- uses: docker/login-action@v3
if: github.event_name == 'push'
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Push and record digest
if: github.event_name == 'push'
run: |
set -euo pipefail
docker push "$IMAGE:$GITHUB_SHA"
DIGEST=$(docker buildx imagetools inspect "$IMAGE:$GITHUB_SHA" --format '{{.Manifest.Digest}}')
printf '%s@%s\n' "$IMAGE" "$DIGEST" > image.txt
cat image.txt >> "$GITHUB_STEP_SUMMARY"
- uses: actions/upload-artifact@v4
if: github.event_name == 'push'
with:
name: release-image
path: image.txt示例用版本标签便于阅读;团队维护的生产工作流应把第三方 Action 固定到审核过的完整 commit SHA,并通过依赖更新流程升级。不要将有生产凭据的工作流用于执行不可信 PR 代码。
image.txt 是交接记录,形如 ghcr.io/你的账号/你的仓库@sha256:…。它与 workflow run、源码 commit 一起归档。后续部署直接消费这条引用,不重新构建,不把邮件发送成功当作发布条件。
最小验证与失败排查
- 正常提交:测试成功,GHCR 有对应 commit 标签,Actions 附件包含 digest。
- 删除
site/healthz后提交测试 PR:检查失败,不能进入推送步骤。 denied:检查 Packages 写权限及仓库与 package 的关联;不要直接扩大为账户管理员权限。exec format error:检查目标架构与构建平台是否一致。- 推送成功但不能拉取:检查 package 可见性和拉取方凭据,按前面的制品库与镜像仓库排查。
小结与思考
CI 的交付物是经过验证的镜像及其来源记录。试着将首页改为 v2,比较两次 digest;为什么相同 commit 在基础镜像变化后重新构建,不一定得到相同 digest?