第3章 CI/CD 实践¶
学习目标¶
- 理解 CI/CD 的核心理念与价值,区分持续集成、持续交付、持续部署
- 掌握 GitHub Actions 进阶用法,能编写复杂工作流
- 了解 Jenkins 的基本配置与适用场景
- 学会设计自动化测试与自动化部署流水线
- 掌握蓝绿部署与金丝雀发布等渐进式发布策略
问题导入¶
某团队 Agent 服务上线后,每次发版都是一场"深夜惊魂":①开发合代码后,运维手动拉代码、装依赖、构建镜像、SSH 上传、重启服务,全程 2 小时;②没有自动化测试,几次发版把回归 bug 带上线,凌晨回滚;③一次发版直接替换所有实例,新版本有内存泄漏导致全站崩溃 15 分钟;④团队不敢频繁发版,迭代周期被拉长到两周一次。
这些痛点的根因是:缺少自动化交付流水线。CI/CD(持续集成/持续部署)把代码提交后的构建、测试、部署全部自动化,让发版从"手工冒险"变成"流水线作业"。本章讲解如何用 GitHub Actions 与 Jenkins 搭建 Agent 项目的 CI/CD 流水线,并实现安全的渐进式发布。
核心概念¶
1. CI/CD 理念¶
- 持续集成(CI):开发人员频繁把代码合并到主干,每次合并自动触发构建与测试,尽早发现集成问题
- 持续交付(CD, Delivery):CI 通过后自动打包成可部署制品,随时可一键发布到生产
- 持续部署(CD, Deployment):制品通过后自动部署到生产,无需人工干预
三者关系层层递进。CI/CD 的核心价值:缩短从代码到上线的反馈周期,降低发版风险,提升迭代速度。
2. GitHub Actions 进阶¶
GitHub Actions 是 GitHub 内置的 CI/CD 平台,核心概念:
- Workflow(工作流):YAML 文件定义,放在
.github/workflows/ - Job(任务):工作流由多个 job 组成,可并行或串行
- Step(步骤):job 内的执行单元,可以是 shell 命令或预定义 Action
- Matrix(矩阵):在多个 Python 版本/操作系统上并行测试
- Secrets:加密存储 API Key 等敏感信息
- Artifact:构建产物上传保存,供后续 job 使用
- Cache:缓存依赖,加速构建
3. Jenkins 配置¶
Jenkins 是老牌开源 CI/CD 工具,适合企业内网与复杂流程:
- Pipeline as Code:用
Jenkinsfile定义流水线,与代码同仓库 - 插件生态:支持 Docker、K8s、Slack 通知等数百插件
- Master-Agent 架构:Master 调度,Agent 执行,支持横向扩展
- 凭据管理:内置 Credentials 存储密钥
Jenkins 与 GitHub Actions 的选择:开源项目与云原生场景优先 Actions;企业内网、强合规、复杂审批流选 Jenkins。
4. 自动化测试¶
AI Agent 的测试金字塔:
- 单元测试:工具函数、Prompt 模板、解析逻辑(pytest)
- 集成测试:Agent 与外部服务的交互(mock LLM、真实 Redis)
- 端到端测试:完整对话流程验证(用录制回放避免真实 API 消耗)
- Prompt 回归测试:固定输入对比输出,检测 Prompt 改动导致的语义漂移
CI 中必须跑的测试:lint(ruff)+ 类型检查(mypy)+ 单元测试 + 集成测试。端到端测试因 LLM 调用成本高,可用 mock 或仅在发版前跑。
5. 自动化部署¶
部署自动化的关键:
- 制品优先:部署的是构建好的镜像,不是源码
- 环境隔离:staging 与 production 配置严格分离
- 回滚机制:每次部署保留前一版本,可一键回滚
- 健康检查:部署后自动验证服务健康,失败则回滚
- 蓝绿与金丝雀:渐进式发布降低风险
6. 蓝绿部署与金丝雀发布¶
-
蓝绿部署:维护两套完全相同的环境(蓝、绿),新版本部署到备用环境,验证通过后流量切换。优点:切换瞬间完成、回滚极快(切回去即可)。缺点:资源翻倍。
-
金丝雀发布:新版本先只承接一小部分流量(如 5%),观察指标无异常后逐步扩大(5% → 20% → 50% → 100%)。优点:风险可控、资源渐进。缺点:需要流量分发能力。
AI Agent 场景下金丝雀发布特别重要:新 Prompt 或新模型可能影响输出质量,需逐步放量观察用户反馈与成本指标。
原理图¶
flowchart LR
Dev[开发提交代码] --> PR[Pull Request]
subgraph CI["持续集成 CI"
Lint[代码检查 Lint]
Test[单元/集成测试]
Build[构建镜像]
Push[推送镜像仓库]
end]
PR --> Lint --> Test --> Build --> Push
subgraph CD["持续部署 CD"
DeployStaging[部署 Staging]
E2E[端到端测试]
Approve[人工审批]
Canary[金丝雀发布 5%]
Observe[观察指标]
Full[全量发布]
end]
Push --> DeployStaging --> E2E --> Approve --> Canary --> Observe
Observe -->|正常| Full
Observe -->|异常| Rollback[自动回滚]
图:CI/CD 流水线——代码提交触发 CI(lint→测试→构建→推送),CD 经 staging 验证与审批后金丝雀渐进发布,异常自动回滚
示例代码¶
1. GitHub Actions 部署工作流¶
# .github/workflows/deploy.yml
name: Build and Deploy
on:
push:
branches: [main]
workflow_dispatch: # 支持手动触发
env:
IMAGE_NAME: mycorp/agent-api
REGISTRY: registry.mycorp.com
jobs:
# ---------- CI 阶段 ----------
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11"]
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: pip
- name: Install dependencies
run: |
pip install -r requirements.txt
pip install ruff mypy pytest pytest-asyncio
- name: Lint
run: ruff check app/
- name: Type check
run: mypy app/
- name: Run tests
run: pytest tests/ --cov=app --cov-report=xml
- name: Upload coverage
uses: codecov/codecov-action@v4
with:
file: ./coverage.xml
build-and-push:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_TOKEN }}
- name: Extract version
id: version
run: echo "tag=${GITHUB_SHA::7}" >> $GITHUB_OUTPUT
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }}
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max
# ---------- CD 阶段 ----------
deploy-staging:
needs: build-and-push
runs-on: ubuntu-latest
environment: staging
steps:
- name: Deploy to staging
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.STAGING_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_SSH_KEY }}
script: |
cd /opt/agent
docker compose pull
docker compose up -d
sleep 10
curl -f http://localhost:8000/health || exit 1
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment: production # 需要人工审批
steps:
- name: Canary deploy
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.PROD_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_SSH_KEY }}
script: |
cd /opt/agent
# 金丝雀:先更新 1 个实例
docker compose up -d --no-deps --scale agent-api=3 agent-api
sleep 15
# 健康检查
curl -f http://localhost:8000/health || (echo "健康检查失败,回滚" && docker compose rollback && exit 1)
echo "金丝雀实例健康,等待观察 5 分钟后全量"
2. Jenkinsfile 流水线¶
pipeline {
agent any
environment {
IMAGE = "registry.mycorp.com/agent-api:${env.BUILD_NUMBER}"
}
stages {
stage('Checkout') {
steps { checkout scm }
}
stage('Test') {
steps {
sh 'pip install -r requirements.txt'
sh 'ruff check app/'
sh 'pytest tests/ --cov=app'
}
}
stage('Build') {
steps {
sh "docker build -t ${IMAGE} ."
sh "docker push ${IMAGE}"
}
}
stage('Deploy Staging') {
steps {
sh "ssh deploy@staging 'cd /opt/agent && docker compose pull && docker compose up -d'"
}
}
stage('Approval') {
steps { input message: '批准部署到生产?', ok: '部署' }
}
stage('Deploy Production') {
steps {
sh "ssh deploy@prod 'cd /opt/agent && docker compose pull && docker compose up -d'"
}
}
}
post {
failure {
slackSend channel: '#alerts', message: "构建失败: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
}
}
}
3. Prompt 回归测试示例¶
import pytest
from app.agent import Agent
# 录制的固定输入与期望关键词
REGRESSION_CASES = [
{"input": "查询订单 O12345 状态", "expect_keywords": ["订单", "状态"]},
{"input": "我想退款", "expect_keywords": ["退款", "流程"]},
]
@pytest.mark.parametrize("case", REGRESSION_CASES)
def test_prompt_regression(case, mock_llm_client):
"""Prompt 改动后验证输出仍包含关键语义"""
agent = Agent(client=mock_llm_client)
output = agent.run(case["input"])
for kw in case["expect_keywords"]:
assert kw in output, f"输出缺失关键语义: {kw}"
企业案例¶
案例 1:Agent 项目 CI/CD 全流程¶
某团队为客服 Agent 搭建完整 CI/CD 流水线。CI 阶段:PR 触发 ruff lint、mypy 类型检查、pytest 单元测试、Prompt 回归测试(mock LLM),全部通过才允许合并。CD 阶段:合并到 main 后自动构建镜像推送 Harbor,部署到 staging 跑端到端测试,人工审批后金丝雀发布——先更新 1 个实例承接 10% 流量,观察 30 分钟的延迟、错误率、Token 成本,正常则全量。
改造效果:①发版时间从 2 小时降到 15 分钟;②回归 bug 在 CI 阶段拦截,上线事故下降 80%;③金丝雀发布让一次 Prompt 缺陷在 10% 流量时被发现,5 分钟回滚,影响面极小;④团队敢于每日发版,迭代速度提升 3 倍。关键经验:Prompt 回归测试是 AI Agent CI 的特色,能防止 Prompt 改动引发的语义漂移。
案例 2:蓝绿部署保障零停机¶
某金融场景 Agent 对可用性要求极高,团队用蓝绿部署:维护 blue、green 两套 K8s 部署,新版本部署到备用环境,运行 10 分钟健康检查与流量回放验证,通过后切换 Service 选择器把流量切到新版本。回滚只需把选择器切回旧版本,秒级完成。代价是资源翻倍,但对金融场景值得。
常见问题¶
Q1:CI 与 CD 的边界在哪?
A:CI 关注"代码能否正确集成",产出是经过测试的制品(镜像);CD 关注"制品能否安全上线",产出是运行中的服务。分界线是镜像仓库:CI 把镜像推上去,CD 把镜像拉下来部署。
Q2:GitHub Actions 的 secrets 安全吗?
A:secrets 加密存储,日志中自动脱敏。但要注意:①PR 来自 fork 时默认不注入 secrets,防止泄露;②不要把 secret 直接 echo 到日志;③定期轮换密钥;④最小权限原则,每个 secret 只赋必要权限。
Q3:金丝雀发布如何判断"正常"?
A:监控四类指标:①技术指标(错误率、延迟、CPU/内存);②业务指标(对话完成率、用户满意度);③AI 指标(Token 成本、幻觉率);④用户反馈(差评、投诉)。任一异常即回滚,避免只看技术指标而忽略 AI 质量退化。
Q4:Agent 的端到端测试太贵(真实调 LLM),怎么办?
A:①用录制回放(VCR.py 录制 LLM 响应,测试时回放);②CI 用 mock LLM,发版前手动跑真实端到端;③用便宜的小模型(如 GPT-4o-mini)做冒烟测试,生产用大模型;④测试用例聚焦流程逻辑而非具体文案。
Q5:蓝绿部署与金丝雀怎么选?
A:蓝绿适合"需要瞬间切换+快速回滚"的场景(如金融、强一致);金丝雀适合"需要观察质量指标"的场景(如 AI Agent,新 Prompt 可能影响输出质量)。AI Agent 一般优先金丝雀。
面试题¶
- 简答题:解释 CI、持续交付、持续部署的区别。
参考要点:CI 自动构建测试;持续交付自动打包制品可随时一键发布;持续部署自动部署到生产无需人工。三者层层递进,自动化程度递增。
- 实践题:设计一个 Agent 项目的 GitHub Actions 工作流,包含测试、构建、金丝雀部署。
参考要点:test job 跑 lint+pytest+Prompt 回归;build job 构建推送镜像;deploy-staging 部署 staging;deploy-production 用 environment 审批 + 金丝雀策略。
- 设计题:金丝雀发布中如何决定"继续放量"还是"回滚"?
参考要点:设定 SLO 阈值(错误率<1%、P99 延迟<3s、Token 成本波动<20%);观察期至少 30 分钟;技术+业务+AI 指标综合判断;任一异常自动回滚。
- 对比题:蓝绿部署与金丝雀发布的优缺点对比。
参考要点:蓝绿切换快、回滚快,但资源翻倍、无法渐进观察;金丝雀资源渐进、风险可控,但需要流量分发与监控能力、发布周期长。
- 原理题:为什么 AI Agent 的 CI 要专门做 Prompt 回归测试?
参考要点:Prompt 改动可能改变输出语义,传统单元测试无法捕获;固定输入对比输出关键词,能检测语义漂移;mock LLM 避免真实调用成本。
练习题¶
-
工作流编写:为你的 Agent 项目编写 GitHub Actions 工作流,包含 lint、单元测试、镜像构建推送,并在 PR 上触发。
-
金丝雀模拟:用 Docker Compose 启动 3 个 Agent 实例,编写脚本模拟金丝雀发布:更新 1 个实例后做健康检查,通过则更新其余实例,失败则回滚。
-
Prompt 回归:为你的 Agent 编写 5 个 Prompt 回归测试用例,用 mock LLM 客户端验证输出包含关键语义。
小结¶
CI/CD 是现代软件交付的发动机。本章核心:CI 自动化构建测试保障代码质量,CD 自动化部署加速交付;GitHub Actions 适合云原生项目,Jenkins 适合企业内网复杂流程;AI Agent 的 CI 要加入 Prompt 回归测试;金丝雀发布是 AI 应用的首选策略,能渐进观察质量指标。流水线就绪后,如何控制 LLM 成本与加固安全,是第 4 章的主题。