Files
zhuangang/topics/devops/cicd/scenarios/multi-lang-pipeline.md
T

112 lines
4.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 后构建一次镜像,用提交哈希做标签,测试和生产用同一个镜像;部署脚本做健康检查,失败自动回滚到上一个成功的标签。数据库迁移单独执行并保持向后兼容。”