Files

4.9 KiB
Raw Permalink Blame History

id, title, type, domain, stack, tags, level, prerequisites, est_hours, related, week
id title type domain stack tags level prerequisites est_hours related week
devops.cicd.scenario.multi-lang-pipeline 多语言仓库的流水线:Go + Python + 前端,按改动构建、自动部署和回滚 scenario devops
cicd
docker
go
python
vue
Monorepo
流水线
路径过滤
自动部署
回滚
intermediate
devops.cicd.pipeline
2
devops.cicd.release-rollback
devops.cicd.interview
devops.docker.scenario.compose-dev-env
14
15

多语言仓库的流水线:Go + Python + 前端,按改动构建、自动部署和回滚

业务背景与问题

一个 AI 知识库产品放在一个仓库里:api/(Go)、ai/(Python FastAPI)、web/(Vue)。之前的发布方式是开发者在本机构建、scp 到服务器、手动重启。问题:

  • 有人本机环境不同,构建出来的产物线上运行出错;
  • 只改了前端文案,也要把三个部分都重新构建一遍;
  • 发布出错时,没人记得上一个版本是什么,回滚靠手工。

约束

  • 小团队,使用代码托管平台自带的 CI(GitHub Actions / Gitea Actions 等)。
  • 单台或两台服务器,用 Docker Compose 部署。
  • 合并请求的检查要在 10 分钟内完成。
  • 模型 API Key 等密钥不能出现在仓库和日志里。

方案设计

flowchart LR
  PR[合并请求] --> C{改了哪些目录?}
  C -->|api/| A[Go: vet + test + 服务容器 PostgreSQL]
  C -->|ai/| P[Python: ruff + pytest(假模型客户端)]
  C -->|web/| W[前端: lint + test + build]
  A & P & W --> M[合并到 main]
  M --> I[构建镜像,标签 = 提交哈希,推送镜像仓库]
  I --> T[自动部署测试环境]
  T -->|人工审批| PROD[部署生产:健康检查失败自动回滚]
  1. 按改动路径执行:用路径过滤只运行改动部分的检查;所有检查作为合并的必要条件。
  2. 测试不依赖真实模型:AI 服务的单元测试注入假的模型客户端;真实模型的效果评测作为单独的手动任务(见 Agent 评测专题)。
  3. 不可变镜像:main 分支构建一次镜像,标签用提交哈希;测试和生产使用同一镜像。
  4. 部署脚本:拉取指定标签 → 启动 → 健康检查 → 记录成功的标签;失败自动回滚到上一个成功的标签。
  5. 数据库迁移:独立步骤,向后兼容(扩展 → 迁移 → 收缩)。

关键代码

完整文件见 examples/pipeline。路径过滤 + 只在 main 构建镜像:

jobs:
  changes:
    runs-on: ubuntu-latest
    outputs:
      api: ${{ steps.filter.outputs.api }}
    steps:
      - uses: actions/checkout@v4
      - uses: dorny/paths-filter@v3
        id: filter
        with:
          filters: |
            api: ['api/**']

  api:
    needs: changes
    if: needs.changes.outputs.api == 'true'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: cd api && go test ./...

  image:
    needs: [api]
    # 依赖任务被跳过时默认会连带跳过,所以用 always() 再判断结果
    if: >-
      always() && github.ref == 'refs/heads/main' &&
      !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled')
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: docker build -t registry.example.com/app/api:${GITHUB_SHA::12} api

部署和回滚见 deploy.sh。

取舍

选择 优点 缺点
单仓库 + 路径过滤(本方案) 一次合并请求可以同时改前后端;统一规范 流水线配置更复杂
每个服务一个仓库 流水线简单、权限独立 跨服务改动要多个合并请求协调
生产自动部署 最快 需要很好的测试和监控
生产人工审批(本方案) 可控 多一步人工操作

易错点

  • 必需检查被路径过滤跳过后,平台认为“检查未完成”导致无法合并:需要一个汇总任务(总是运行)作为必需检查。
  • 依赖任务被跳过导致镜像任务也被跳过:条件里加 always() 并判断结果。
  • 前端构建产物里写死了测试环境的接口地址:用相对路径 /api,由 Nginx 转发;或在运行时注入配置。
  • 部署脚本没有记录上一个成功版本:回滚时不知道回到哪里。
  • CI 日志里打印了环境变量:密钥泄露。

面试怎么讲

“我们的仓库里有 Go、Python 和前端三部分。我设计的流水线按改动路径只跑相关检查,所有检查是合并的必要条件;AI 服务的单元测试用假的模型客户端,不依赖外部 API。合并到 main 后构建一次镜像,用提交哈希做标签,测试和生产用同一个镜像;部署脚本做健康检查,失败自动回滚到上一个成功的标签。数据库迁移单独执行并保持向后兼容。”