跳转至

09 部署与发布:让 AI 安全地把版本送上生产

部署不是“把代码推上去”,而是一次 受控的、可回滚的、可验证的状态变更。AI 可以执行部署,但人必须掌握发布节奏和批准权。


1. 环境与晋级路径

local ──► dev ──► staging ──► prod
  │        │         │          │
  │        │         │          └─ 生产验证 + 监控
  │        │         └─ 预发验收(接近生产)
  │        └─ 联调、E2E、Word 报告
  └─ 开发、单元测试
环境 用途 数据 谁能部署
local 开发 本地 Docker 开发者
dev 联调/E2E 合成数据 AI + 开发者
staging 预发验收 脱敏/合成 AI + 人工批准
prod 生产 真实数据 人工批准,AI 执行

规则:不允许从 local 直接跳 prod;每个环境都要有独立的配置、数据库和凭证。


2. 配置与密钥

2.1 12-Factor 配置

  • 配置来自环境变量;
  • 代码中不写死 URL、密钥、账号;
  • 每个环境独立;
  • 敏感值放 Secrets Manager / SSM Parameter Store;
  • 配置变更也走版本控制(值除外)。
# 示例
DATABASE_URL=postgres://...
REDIS_URL=redis://...
JWT_SECRET=...          # 来自 Secrets Manager
AWS_REGION=ap-northeast-1
LOG_LEVEL=info

2.2 密钥管理

  • 不要把密钥提交到 Git;
  • 不要把密钥写进 Docker 镜像;
  • 不要在日志/错误响应中输出密钥;
  • 使用 IAM Role 而不是长期 Access Key(在 AWS 上);
  • 定期轮换;
  • 最小权限。

3. CI/CD 流水线

3.1 标准阶段

1. checkout
2. lint / format check
3. unit test
4. integration test (Testcontainers)
5. contract test (OpenAPI)
6. build (binary / frontend dist / image)
7. security scan (deps / image)
8. push image to ECR
9. deploy dev
10. smoke test dev
11. manual approval
12. deploy staging
13. smoke test staging
14. manual approval
15. deploy prod
16. smoke test prod
17. 通知 + 记录

3.2 GitHub Actions 示例

name: ci-cd

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

env:
  AWS_REGION: ap-northeast-1
  ECR_REPOSITORY: teamtask-api
  ECS_CLUSTER: teamtask-dev
  ECS_SERVICE: teamtask-api
  CONTAINER_NAME: api

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-go@v5
        with:
          go-version: "1.23"

      - name: Lint
        run: |
          gofmt -l . | (! grep .)
          go vet ./...

      - name: Unit tests
        run: go test -race -coverprofile=coverage.out ./...

      - name: Integration tests
        run: go test -tags=integration ./...

      - name: Build
        run: go build ./cmd/api

  frontend:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "22"
          cache: npm
          cache-dependency-path: frontend/package-lock.json
      - run: npm ci
        working-directory: frontend
      - run: npm run typecheck
        working-directory: frontend
      - run: npm run lint
        working-directory: frontend
      - run: npm run test
        working-directory: frontend
      - run: npm run build
        working-directory: frontend

  deploy-dev:
    needs: [test, frontend]
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment: dev
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@v4

      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.AWS_ROLE_DEV }}
          aws-region: ${{ env.AWS_REGION }}

      - uses: aws-actions/amazon-ecr-login@v2
        id: ecr

      - name: Build and push
        env:
          REGISTRY: ${{ steps.ecr.outputs.registry }}
          IMAGE_TAG: ${{ github.sha }}
        run: |
          docker build -t "$REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" ./backend
          docker push "$REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"

      - name: Render task definition
        id: taskdef
        uses: aws-actions/amazon-ecs-render-task-definition@v1
        with:
          task-definition: infra/ecs/task-definition-dev.json
          container-name: ${{ env.CONTAINER_NAME }}
          image: ${{ steps.ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:${{ github.sha }}

      - name: Deploy
        uses: aws-actions/amazon-ecs-deploy-task-definition@v2
        with:
          task-definition: ${{ steps.taskdef.outputs.task-definition }}
          cluster: ${{ env.ECS_CLUSTER }}
          service: ${{ env.ECS_SERVICE }}
          wait-for-service-stability: true

      - name: Smoke test
        run: |
          curl -fsS https://dev-api.example.com/healthz
          curl -fsS https://dev-api.example.com/readyz

  deploy-prod:
    needs: deploy-dev
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment: production   # 在 GitHub 中配置 required reviewers
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@v4
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.AWS_ROLE_PROD }}
          aws-region: ${{ env.AWS_REGION }}
      # ... 与 dev 类似,替换 cluster/service/task definition

关键点:

  • 用 OIDC(role-to-assume)而不是长期 Access Key;
  • environment: production 配置人工审批;
  • 每个环境独立 role;
  • 部署后必须冒烟;
  • 失败自动停止,不自动重试到生产。

3.3 其他 CI 系统

  • GitLab CI:stages + rules + environment + when: manual。
  • Jenkins:pipeline + input 步骤做审批。
  • 你已有的 cicd-* / publish-* 技能可以作为“部署执行器”,但要确保:
  • 输入版本号/分支明确;
  • 有审批;
  • 有回滚;
  • 有日志。

4. AWS 部署的四种常见形态

4.1 前端:S3 + CloudFront

cd frontend
npm ci
npm run build

aws s3 sync dist/ "s3://$FRONTEND_BUCKET/" --delete
aws cloudfront create-invalidation \
  --distribution-id "$CLOUDFRONT_DISTRIBUTION_ID" \
  --paths "/*"

注意:

  • --delete 会删除 S3 中不在 dist/ 的文件,确认 bucket 只放前端产物;
  • 缓存策略:HTML 不缓存或短缓存,静态资源带 hash 长缓存;
  • 自定义错误页回退到 index.html(SPA 路由)。

4.2 后端:ECS Fargate

# 登录 ECR
aws ecr get-login-password --region "$AWS_REGION" | \
  docker login --username AWS --password-stdin "$ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com"

# 构建/推送
docker build -t "$IMAGE_URI:$TAG" ./backend
docker push "$IMAGE_URI:$TAG"

# 更新服务
aws ecs update-service \
  --cluster "$CLUSTER" \
  --service "$SERVICE" \
  --force-new-deployment \
  --region "$AWS_REGION"

# 等待稳定
aws ecs wait services-stable \
  --cluster "$CLUSTER" \
  --services "$SERVICE" \
  --region "$AWS_REGION"

4.3 后端:Lambda

aws lambda update-function-code \
  --function-name "$FUNCTION_NAME" \
  --image-uri "$IMAGE_URI:$TAG" \
  --region "$AWS_REGION"

aws lambda wait function-updated \
  --function-name "$FUNCTION_NAME" \
  --region "$AWS_REGION"

4.4 数据库迁移

不要在应用启动时自动迁移生产数据库。 正确做法:

  1. 在 CI 中跑一个一次性的迁移任务(ECS task / Lambda / 跳板机);
  2. 迁移前备份/快照;
  3. 迁移使用 expand-contract;
  4. 迁移失败自动停止部署;
  5. 记录迁移版本和耗时。
# 示例:在 CI runner 中执行
make migrate-up

如果使用 RDS:

aws rds create-db-snapshot \
  --db-instance-identifier "$DB_ID" \
  --db-snapshot-identifier "$DB_ID-pre-$TAG"

5. 基础设施即代码(IaC)

推荐用 Terraform / AWS CDK / CloudFormation 管理:

  • 网络、ALB、ECS、RDS、S3、CloudFront、IAM、Secrets;
  • 环境隔离(workspace/stack);
  • 变更走 PR + plan/diff;
  • 生产 apply 需审批;
  • 状态文件加密存储(S3 + DynamoDB lock)。

不要让 AI 直接在控制台点资源。 控制台操作不可复现、不可审计。用 IaC。


6. AWS 登录与 Syncthing 同步

6.1 推荐:AWS SSO

aws configure sso
# 输入 SSO start URL、region、account、role
aws sso login --profile teamtask-dev
aws sts get-caller-identity --profile teamtask-dev

使用:

export AWS_PROFILE=teamtask-dev
aws s3 ls

SSO 的优点:

  • 短期凭证,自动过期;
  • 不需要在本地保存长期 Access Key;
  • 可以按账号/角色最小权限;
  • 适合多环境切换。

6.2 为什么用 Syncthing 同步登录态

AWS SSO 的缓存文件在:

~/.aws/sso/cache/*.json

里面是短期 token(通常几十分钟到几小时)。如果你在多台机器/Windows + WSL 之间切换,重复登录很烦。Syncthing 可以把 ~/.aws/sso/cache 同步过去,让另一台机器直接复用未过期的登录态。

6.3 安全边界(非常重要)

可以同步:

  • ~/.aws/config(不含密钥,只有 profile 配置)
  • ~/.aws/sso/cache/*.json(短期 token)

不要同步:

  • ~/.aws/credentials(长期 Access Key)
  • 任何包含 aws_secret_access_key 的文件
  • 生产账号的长期凭证

必须做到:

  • Syncthing 设备之间启用加密/可信设备;
  • 不要同步到共享、公用、不受控的设备;
  • 文件权限 600;
  • 定期清理过期 cache;
  • 怀疑泄露时立即在 AWS 中撤销会话/轮换凭证;
  • 生产环境优先在 CI 中用 OIDC,不依赖个人登录态。

6.4 Syncthing 配置示例

在 Syncthing 中添加文件夹:

Folder ID: aws-sso
Path:      /home/<user>/.aws          (WSL)
或         C:\Users\<user>\.aws       (Windows)

Ignore patterns:
  credentials
  cli/cache/*
  **/*.pem

只同步 config 和 sso/cache,不要整个 .aws 无脑同步。

注意:Syncthing 的 ignore 语法可能因版本不同。配置后在另一台机器确认 credentials 没有被同步过去。

6.5 登录态过期怎么办

aws sso login --profile teamtask-dev

如果 Agent 在部署时遇到 ExpiredToken:

  1. 停止部署;
  2. 由人重新登录;
  3. 验证 aws sts get-caller-identity;
  4. 继续。

不要让 AI 自动尝试刷新/绕过登录。 凭证过期是安全机制,不是 bug。


7. 发布策略

7.1 三种常见策略

策略 适用 优点 缺点
滚动发布 大多数服务 简单、资源省 新旧版本短暂共存
蓝绿 需要快速回滚 切换快、回滚简单 资源翻倍
金丝雀 高风险变更 影响面小 需要流量控制/监控

7.2 功能开关

高风险功能用 feature flag 控制:

FEATURE_TASK_TAGS=false

发布后先关闭,验证基础功能,再逐步打开。出问题直接关开关,不用回滚代码。

7.3 数据库变更与发布顺序

1. 部署兼容新旧 schema 的代码(expand)
2. 执行迁移(加列/加表)
3. 回填数据
4. 切换读写到新结构
5. 观察
6. 删除旧结构(contract)

永远不要先删列再部署新代码。


8. 发布检查清单(G7)

8.1 发布前

  • G6 验收通过,人已签字
  • CI 全绿
  • 镜像/构建产物已推送,tag 可追溯
  • 数据库迁移已评审,有备份/回滚
  • 配置/密钥已就绪
  • 发布窗口、负责人、通知渠道明确
  • 回滚方案已演练或至少验证过
  • 监控/告警正常
  • 生产测试账号/数据准备就绪
  • 人工批准生产发布

8.2 发布中

  • 按顺序执行(迁移 → 后端 → 前端)
  • 每步后检查健康检查
  • 观察错误率/延迟/日志
  • 冒烟测试通过
  • 记录发布版本、时间、操作人

8.3 发布后

  • 线上 E2E(见 10 线上验证)
  • 监控基线对比
  • 用户反馈渠道畅通
  • 发布记录写入 docs/05-release/
  • 更新 CSV 的 prod_deploy_status

8.4 回滚条件

出现以下任一情况,立即回滚:

  • 错误率显著上升;
  • P0/P1 功能不可用;
  • 数据写入错误;
  • 性能严重下降;
  • 安全事件。

回滚步骤:

1. 切回上一个镜像/任务定义
2. 如有不兼容迁移,执行回滚脚本/恢复快照
3. 关闭功能开关
4. 验证健康检查和冒烟
5. 通知相关人
6. 记录事故

9. 部署 Agent 提示词

9.1 部署到 dev

你是发布工程师。请把 main 分支的最新提交部署到 dev。

前提:
- CI 已全绿
- 镜像 tag 使用 commit SHA
- 目标:ECS cluster=teamtask-dev, service=teamtask-api
- 前端:S3 bucket=teamtask-dev-frontend, CloudFront distribution=...

步骤:
1. 确认 `aws sts get-caller-identity` 是 dev 账号。
2. 构建并推送镜像。
3. 更新 ECS 服务并等待稳定。
4. 执行前端构建和 S3 同步 + CloudFront 失效。
5. 跑冒烟:/healthz, /readyz, 登录页 200。
6. 输出:镜像 tag、任务定义版本、部署时间、冒烟结果、任何错误。
7. 更新 docs/05-release/dev-<date>.md 和 CSV。

约束:
- 不要操作 prod 资源。
- 不要修改 IaC。
- 遇到权限/凭证错误,停止并报告。

9.2 部署到生产

你是发布工程师。请准备生产发布,但不要直接执行。

输入:
- 版本:<commit/tag>
- 变更内容:<release notes>
- 回滚版本:<previous tag>
- 迁移:<有/无,文件列表>

请输出:
1. 发布前检查清单的逐项结果
2. 将要执行的命令(按顺序)
3. 每一步的验证方法
4. 回滚命令
5. 风险和注意事项

等我明确回复“批准发布”后,再执行。

生产部署必须有人类批准。 不要让 Agent 从“准备”直接滑到“执行”。


10. 常见问题

问题 1:本地能跑,线上 500

排查:

  • 环境变量缺失/不同;
  • 数据库迁移未执行;
  • 权限/IAM role 不同;
  • 网络/安全组;
  • 时区/编码;
  • 依赖版本不同(锁文件)。

问题 2:部署后旧页面还在

  • CDN 缓存;
  • Service Worker;
  • 浏览器缓存;
  • index.html 被长缓存。

处理:HTML 短缓存/no-cache,静态资源带 hash,CloudFront 失效。

问题 3:迁移把线上锁住

  • 大表加索引未用 CONCURRENTLY;
  • 长时间事务;
  • 未分批回填。

处理:expand-contract、并发索引、分批、低峰执行。

问题 4:AWS 登录态过期

  • aws sso login 重新登录;
  • 检查 Syncthing 同步是否成功;
  • 不要让 Agent 自动重试到锁定。

问题 5:AI 在生产执行了危险命令

预防:

  • 生产凭证不放在开发环境;
  • 使用只读角色做验证;
  • 写操作需人工批准;
  • 命令白名单/包装脚本;
  • 审计日志;
  • 备份。

11. 本章交付清单

  • CI/CD 流水线可用
  • dev/staging/prod 环境独立
  • 密钥管理合规
  • 数据库迁移有备份/回滚
  • 发布检查清单完成
  • 人工批准记录
  • 冒烟测试通过
  • 发布记录已归档
  • CSV 的 prod_deploy_status 已更新

下一章:10 线上验证与运营 —— 在生产环境再走一遍用户流程,用同一套证据链完成最终验收。