首页/文章/Docker / DevOps
Docker / DevOps

GitHub Actions 自动构建和部署

用 CI 构建容器镜像,再通过受限 SSH 发布到单机 Compose 环境,加入并发控制、健康检查和回滚边界。

自动部署的目标不是让每次 push 都直接改生产,而是让发布过程可重复、可审计、失败可见。本文采用一个保守的单机流程:主分支变更先运行测试并构建不可变镜像,将镜像推送到 GHCR;受保护的 production environment 审批后,通过 SSH 在服务器拉取指定 tag 并重建 Compose 服务。

如果镜像仓库或部署平台不同,替换登录与传输步骤即可。示例使用第三方 GitHub Action 时必须审阅其代码和权限要求;正式环境应固定 action 到完整 commit SHA,并定期升级。

发布流水线结构

flowchart LR
  push[主分支提交 / 手动发布] --> test[测试与静态检查]
  test --> build[构建 OCI 镜像]
  build --> scan[镜像扫描 / 生成 SBOM]
  scan -->|通过| registry[(GHCR 不可变 tag)]
  registry --> gate{production 环境审批}
  gate --> deploy[SSH 到部署主机]
  deploy --> pull[拉取指定镜像并更新 Compose]
  pull --> health[等待 readiness / smoke test]
  health -->|通过| done[记录发布版本]
  health -->|失败| rollback[恢复上一个镜像 tag]

不要用 latest 表达某次发布。使用提交 SHA 作为镜像 tag,部署记录能回溯到源码;更严格的供应链场景还应验证签名和 provenance,并在生产节点以 digest 部署。

GitHub Actions 工作流示例

先在仓库的 Actions 设置中授权 packages: write,把生产密钥放在 production environment,并为该环境启用所需分支规则和审批。服务器应预装 Docker Compose 插件,且 SSH 用户只能执行部署所需操作。

# .github/workflows/deploy.yml
name: build-and-deploy

on:
  push:
    branches: [main]
  workflow_dispatch:

permissions:
  contents: read

concurrency:
  group: production-deploy
  cancel-in-progress: false

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    outputs:
      image: ${{ steps.meta.outputs.tags }}
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - id: meta
        uses: docker/metadata-action@v5
        with:
          images: ghcr.io/${{ github.repository }}
          tags: |
            type=sha,format=long
      - uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment: production
    permissions:
      contents: read
    steps:
      - name: Configure SSH
        env:
          DEPLOY_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
          KNOWN_HOSTS: ${{ secrets.DEPLOY_KNOWN_HOSTS }}
        run: |
          install -m 700 -d ~/.ssh
          printf '%s\n' "$DEPLOY_KEY" > ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519
          printf '%s\n' "$KNOWN_HOSTS" > ~/.ssh/known_hosts
          chmod 600 ~/.ssh/known_hosts
      - name: Deploy image
        env:
          DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
          IMAGE: ${{ needs.build.outputs.image }}
        run: |
          ssh "$DEPLOY_HOST" "cd /srv/leon-service && APP_IMAGE='$IMAGE' docker compose pull app && APP_IMAGE='$IMAGE' docker compose up -d --no-deps app"
          ssh "$DEPLOY_HOST" "cd /srv/leon-service && docker compose ps app"

这是骨架而非复制即用的安全模板:把 @v4 等 action ref 换成经审阅的完整 commit SHA;按项目加入单元测试、静态分析、依赖扫描、镜像扫描和发布后的 smoke test。工作流权限从只读开始,仅在构建 job 授予写包权限。GHCR 若为私有包,生产主机需要安全配置只读拉取凭证。

SSH、主机和权限

在 GitHub Environment secrets 中配置 DEPLOY_HOST、DEPLOY_SSH_KEY 和预先核验的 DEPLOY_KNOWN_HOSTS。服务器身份 key 应通过可信渠道核验后保存;不要在 workflow 里运行 ssh-keyscan 后就直接信任结果,它只能拿到当前网络返回的 key,不能证明服务器身份。

部署账号应使用专用密钥和最小权限。更进一步,可通过 authorized_keys 的 command= 限定入口脚本,或使用短期凭证/云 OIDC 身份,减少长期 SSH 私钥。不要把 Docker socket 暴露给不可信用户;能控制 Docker daemon 的用户通常拥有主机级高权限。

失败检测与回滚

docker compose ps 仅展示状态,不足以证明新版本能够服务真实请求。生产工作流应等待 readiness endpoint 成功,再调用一个低风险 smoke test。示例:

for attempt in $(seq 1 20); do
  if curl --fail --silent https://service.example.com/actuator/health/readiness >/dev/null; then
    exit 0
  fi
  sleep 3
done
echo "readiness probe did not become healthy" >&2
exit 1

回滚最好由服务器侧脚本基于已记录的上一个镜像 digest 执行,避免把任意命令拼进工作流输入。回滚应用镜像并不自动回滚数据库 schema;使用向前兼容迁移,并将不可逆数据变更安排在独立步骤。

发布系统还应保留镜像与日志,输出部署的 commit SHA、环境、执行人、开始/结束时间和探测结果。对关键生产环境启用审批和部署并发锁,避免两次发布同时修改同一 Compose 项目。

常见踩坑

  1. 在 pull_request 工作流里暴露生产 secrets:来自 fork 的 PR 不应获得部署密钥。构建与部署分成不同 job,并让部署只在可信分支和受保护环境运行。
  2. 把私钥写进命令行参数或日志:通过受控环境变量临时写入权限为 600 的文件,避免 set -x,运行结束后清理。
  3. 禁用 SSH host key 校验图省事:StrictHostKeyChecking=no 会使中间人攻击更容易;使用核验后的 known_hosts。
  4. 部署 tag 是浮动值:同一个 tag 被覆盖会让回滚和审计失去确定性。使用 commit SHA tag,记录并最好校验 digest。
  5. 生产节点无条件执行 docker compose down:这会扩大停机范围并可能误删依赖服务。只更新目标服务;数据卷清理命令尤其要审查。
  6. 工作流取消导致状态未知:生产 job 设置明确的 concurrency 策略;每次部署都写入版本记录,并在 job 结束时检查服务健康。
  7. 过度相信 Actions 自动脱敏:日志拼接、编码后的密钥或结构化秘密可能无法正确脱敏。不要打印秘密,不要把秘密嵌进构建参数或 artifact。

参考资料

文章反馈

如果这篇文章对你有帮助,欢迎发邮件交流。

通过邮箱联系 ↗继续阅读 →