发布的输入必须先确定
本课是 CD 段的第一篇。CI 段(17 篇 持续集成流水线)负责产出可发布的镜像 digest,本课把它部署到。CD 与 CI 的边界划分依据 16 篇 CI/CD 权责分离。
本课在单台练习服务器部署前面课程的 delivery-demo。需要 Docker Compose v2(支持 up --wait)、Bash、curl,以及仓库课配置的镜像拉取权限。先完成 Compose 发布,再进入 19 篇 K8s 应用部署(含 K3s 集群建立)。
前面的本地 web + db 用于验证网络与存储,本课在独立目标目录先发布 web,数据库生命周期独立管理。不要把前面练习目录直接替换成本课文件,或通过 down -v 清除数据;数据库版本演进在后续迁移课处理。
发布记录至少包含镜像 digest、配置版本、目标环境、发起人和审批结果。git pull 获取分支最新状态会改变发布输入,所以本例不在服务器拉取浮动分支。
定义服务与健康条件
在部署用户拥有的 /opt/delivery-demo 中保存 compose.yaml:
services:
web:
image: ${IMAGE_REF:?必须提供镜像 digest 引用}
ports:
- "127.0.0.1:8080:80"
environment:
APP_ENV: ${APP_ENV:-production}
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "wget -q -O - http://127.0.0.1/healthz | grep -qx ok"]
interval: 5s
timeout: 3s
retries: 6
start_period: 5s这里仅绑定服务器回环地址。远程查看可建立 SSH 隧道:ssh -L 8080:127.0.0.1:8080 部署用户@服务器,再在本机访问 http://127.0.0.1:8080。
Compose 不提供跨服务事务,也不会在部分失败时自动恢复旧版。普通 depends_on 只描述启动依赖;数据库就绪需配合 healthcheck 和 condition: service_healthy。单机替换可能短暂中断,本课不承诺零停机。
将部署结果变成可检查的退出码
在同一目录保存 deploy.sh。它只接受 digest 引用,部署前先拉取,部署后验证健康与首页;失败保留现场,回退由操作人明确选择。
#!/usr/bin/env bash
set -euo pipefail
cd /opt/delivery-demo
ref="${1:?传入镜像 digest 引用}"
[[ "$ref" =~ ^ghcr\.io/[a-z0-9._/-]+@sha256:[a-f0-9]{64}$ ]] || exit 2
# Linux flock 防止手动发布和流水线同时操作。
exec 9>.deploy.lock
flock -n 9 || { echo '已有发布正在执行'; exit 3; }
export IMAGE_REF="$ref"
docker compose config --quiet
docker compose pull
if ! docker compose up -d --wait --wait-timeout 90; then
docker compose ps
docker compose logs --tail=100 web
exit 1
fi
curl --fail --silent --show-error http://127.0.0.1:8080/ | grep -q delivery-demo
# 只有验收成功,才更新稳定版本记录。
if [ -f current-image.txt ]; then cp current-image.txt previous-image.txt; fi
printf '%s\n' "$ref" > current-image.txt
printf '发布通过:%s\n' "$ref"首次执行:
cd /opt/delivery-demo
bash deploy.sh "$(cat image.txt)"image.txt 从 CI 附件取得。发布失败后先收集日志;若确认可回退且已有上一稳定记录,将 失败前的 current-image.txt 传给脚本。成功发布后主动回退才使用 previous-image.txt。不要在发布结束时立即清除回退镜像。
GitHub Actions 生产审批
在 GitHub 建立 production Environment,配置 required reviewers、允许部署的分支,以及以下 Environment secrets:SERVER_HOST、SERVER_USER、SERVER_KEY、SERVER_FINGERPRINT。不同套餐和仓库可见性对环境保护规则的支持不同,先确认实际配置生效;仅写 environment: production 不会自动创建审批规则。
通过可信渠道核对服务器 SSH host fingerprint。密钥和密码是可配置的认证方式,并非天然互斥;本例用专用部署密钥,不使用个人管理员登录。
保存 .github/workflows/cd.yml:
name: Deploy Compose
on:
workflow_dispatch:
inputs:
image_ref:
description: 从审核过的 CI 记录复制完整 digest 引用
required: true
type: string
permissions:
contents: read
concurrency:
group: compose-production
cancel-in-progress: false
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- name: Validate input
env:
IMAGE_REF: ${{ inputs.image_ref }}
run: |
[[ "$IMAGE_REF" =~ ^ghcr\.io/[a-z0-9._/-]+@sha256:[a-f0-9]{64}$ ]]
- name: Deploy approved image
uses: appleboy/ssh-action@v1.2.0
env:
IMAGE_REF: ${{ inputs.image_ref }}
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SERVER_KEY }}
fingerprint: ${{ secrets.SERVER_FINGERPRINT }}
envs: IMAGE_REF
script: |
set -eu
bash /opt/delivery-demo/deploy.sh "$IMAGE_REF"审批人核对 image reference 和 CI 来源;高级篇再把来源校验自动化。服务器上的脚本和 Compose 文件也应版本化并记录变更,本课首次由维护者安装审核过的版本;变更它们属于另一次发布。
最小验收与边界
- 提交代码只触发 CI,生产服务器不变化。
- 手动发起 CD 后,配置了 reviewer 的任务等待审批;拒绝后不连接服务器。
- 使用不存在的 digest:在拉取阶段失败,旧容器继续运行。
- 用缺少健康端点的实验镜像:任务失败,不记录为稳定版本;检查日志并恢复失败前的稳定镜像。
- 成功后检查首页、健康端点、容器镜像引用和 workflow 结果一致。
本课验收的是单机发布能力。高可用、业务指标和数据库兼容性分别在后续课程加入,不能由 up -d 的成功退出推导出来。