--- id: devops.cicd.scenario.multi-lang-pipeline title: 多语言仓库的流水线:Go + Python + 前端,按改动构建、自动部署和回滚 type: scenario domain: devops stack: [cicd, docker, go, python, vue] tags: [Monorepo, 流水线, 路径过滤, 自动部署, 回滚] level: intermediate prerequisites: [devops.cicd.pipeline] est_hours: 2 related: [devops.cicd.release-rollback, devops.cicd.interview, devops.docker.scenario.compose-dev-env] week: [14, 15] --- # 多语言仓库的流水线:Go + Python + 前端,按改动构建、自动部署和回滚 ## 业务背景与问题 一个 AI 知识库产品放在一个仓库里:`api/`(Go)、`ai/`(Python FastAPI)、`web/`(Vue)。之前的发布方式是开发者在本机构建、`scp` 到服务器、手动重启。问题: - 有人本机环境不同,构建出来的产物线上运行出错; - 只改了前端文案,也要把三个部分都重新构建一遍; - 发布出错时,没人记得上一个版本是什么,回滚靠手工。 ## 约束 - 小团队,使用代码托管平台自带的 CI(GitHub Actions / Gitea Actions 等)。 - 单台或两台服务器,用 Docker Compose 部署。 - 合并请求的检查要在 10 分钟内完成。 - 模型 API Key 等密钥不能出现在仓库和日志里。 ## 方案设计 ```mermaid 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](../examples/pipeline/)。路径过滤 + 只在 main 构建镜像: ```yaml 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](../examples/pipeline/deploy.sh)。 ## 取舍 | 选择 | 优点 | 缺点 | | --- | --- | --- | | **单仓库 + 路径过滤(本方案)** | 一次合并请求可以同时改前后端;统一规范 | 流水线配置更复杂 | | 每个服务一个仓库 | 流水线简单、权限独立 | 跨服务改动要多个合并请求协调 | | 生产自动部署 | 最快 | 需要很好的测试和监控 | | **生产人工审批(本方案)** | 可控 | 多一步人工操作 | ## 易错点 - 必需检查被路径过滤跳过后,平台认为“检查未完成”导致无法合并:需要一个汇总任务(总是运行)作为必需检查。 - 依赖任务被跳过导致镜像任务也被跳过:条件里加 `always()` 并判断结果。 - 前端构建产物里写死了测试环境的接口地址:用相对路径 `/api`,由 Nginx 转发;或在运行时注入配置。 - 部署脚本没有记录上一个成功版本:回滚时不知道回到哪里。 - CI 日志里打印了环境变量:密钥泄露。 ## 面试怎么讲 > “我们的仓库里有 Go、Python 和前端三部分。我设计的流水线按改动路径只跑相关检查,所有检查是合并的必要条件;AI 服务的单元测试用假的模型客户端,不依赖外部 API。合并到 main 后构建一次镜像,用提交哈希做标签,测试和生产用同一个镜像;部署脚本做健康检查,失败自动回滚到上一个成功的标签。数据库迁移单独执行并保持向后兼容。”