112 lines
4.9 KiB
Markdown
112 lines
4.9 KiB
Markdown
---
|
||
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 后构建一次镜像,用提交哈希做标签,测试和生产用同一个镜像;部署脚本做健康检查,失败自动回滚到上一个成功的标签。数据库迁移单独执行并保持向后兼容。”
|