这里写目录标题
前言
代码从提交到上线,中间隔着检查、构建、部署一长串重复动作,靠人盯既慢又容易出错。CI/CD 就是把这套流程自动化、标准化的工程实践。
本文以 GitLab 为例,从概念、流水线、Runner 一直讲到怎么把 Claude Code 这类 AI 工具挂进流水线,把整个机制串清楚。
一、CI/CD 是什么
CI/CD 是两个概念拼起来的:
- CI(Continuous Integration,持续集成):开发人员提交代码后,自动跑检查和测试,确认这批代码能安全合进主仓库。
- CD(Continuous Delivery / Deployment,持续交付 / 部署):把验证过的代码或构建产物,按标准方式交付或部署到目标环境。
先看一个最关键的区别:
| 维度 | CI | CD |
|---|---|---|
| 核心目标 | 验证代码能否安全集成 | 把版本交付或部署出去 |
| 主要工作 | 静态检查、单测、构建验证 | 发镜像、部署服务器、健康检查、回滚 |
| 执行时机 | 提交代码、创建/更新 MR | 分支更新、版本发布、人工批准 |
| 最终结果 | 检查通过 / 产出可交付物 | 版本具备发布条件 / 已部署 |
重点:CI 和 CD 是按工程职责划分的概念,不是两个必须独立部署的软件系统。 比如"构建 Docker 镜像"这件事,既可以是 CI 阶段的构建验证,也可以是 CD 发版前的产物准备——边界是团队自己定的,没有绝对命令级分界。
CD 自己还有两种含义,别混淆:
- 持续交付(Continuous Delivery):自动备好可发布版本,但生产发布保留人工审批。
- 持续部署(Continuous Deployment):检查全过就自动部署到生产,不要人工批准。
一个典型流程长这样:
开发人员提交代码
↓
创建 MR / PR
↓
CI:自动检查、测试
↓
检查通过,合并代码
↓
构建并保存 Docker 镜像
↓
CD:交付或部署新版本
↓
目标服务器更新容器
↓
健康检查、验证运行
⚠️ 两句话钉死认知:CI 通过 ≠ 代码已上线;代码合并成功 ≠ 服务器已更新。 中间每一步都隔着一道门。
二、GitLab 流水线三件套:Pipeline / Stage / Job
1. 先说 MR:提交代码怎么变成"待办任务"
多人协作里,谁都不会直接往 main / develop 上写,而是开功能分支:
main 生产分支
develop 集成开发分支
feature/sem SEM 功能分支
feature/questionnaire 问卷功能分支
在 feature/sem 上写完:
git add .
git commit -m "feat: implement SEM"
git push origin feature/sem
然后申请把它合并到 develop。这个"申请合并"的动作:
- GitHub 叫 PR(Pull Request)
- GitLab 叫 MR(Merge Request)
本质都是一回事:请求把源分支合进目标分支。
易错点:PR/MR 本身不是 CI,它只是一个"可能触发 CI 的协作事件"。 真正的 CI 是 MR 创建后 GitLab 自动跑的那批检查。如果目标分支受保护,检查没过就不让合。
2. Pipeline / Stage / Job
GitLab 的自动化靠 Pipeline(流水线)组织,里面三个层次:
| 概念 | 含义 |
|---|---|
| Pipeline | 一次完整的流水线运行 |
| Stage | 流水线里的逻辑阶段 |
| Job | 一个具体任务,比如 pytest、docker build |
结构示意:
Pipeline
│
├── Stage: check
│ ├── Job: python-static
│ ├── Job: frontend-types
│ └── Job: ai-review
│
├── Stage: build
│ └── Job: docker-build
│
└── Stage: deploy
└── Job: deploy-test
规则很简单:同一 Stage 里的多个 Job 并行跑,下一个 Stage 等上一个 Stage 全部成功才开始。想打破"阶段串行"的默认行为,可以用 needs 显式声明 Job 间依赖,玩出更灵活的执行图。
3. 什么时候触发?
很多人以为"只有创建 MR 才触发 CI",其实触发方式很多:
| 事件 | 典型用途 |
|---|---|
git push | 检查新提交 |
| 创建/更新 MR | 质量检查、代码审查 |
| MR 合并到 develop | 构建镜像、部署测试环境 |
| 创建 Git Tag | 发布指定版本 |
| 手动运行 Pipeline | 人工触发构建/部署 |
| 定时任务 | 周期性测试、安全扫描 |
| API / 其他 Pipeline | 跨系统编排 |
核心机制是事件驱动:GitLab 收到事件后,读仓库里的
.gitlab-ci.yml,判断要不要建 Pipeline、跑哪些 Job。
看一段最小可读的 .gitlab-ci.yml:
stages:
- check
- deploy
# CI:代码检查
ci_check:
stage: check
image: python:3.11
script:
- python -m compileall -q .
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "develop"'
# CD:部署测试环境
deploy_test:
stage: deploy
needs: [ci_check]
tags:
- deploy-runner
script:
- ssh "$DEPLOY_USER@$DEPLOY_HOST" "cd /srv/app && docker compose pull && docker compose up -d"
rules:
- if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "develop"'
字段含义:
stages:定义有哪些阶段。script:Job 里要执行的命令。rules:Job 在什么条件下运行。needs:声明任务依赖(可跨 Stage)。tags:选择带特定标签的 Runner 来跑。
这段配置干了两件事:创建 MR 时跑 CI 检查;代码合进 develop 后再跑一次 CI,过了就部署测试环境。
CI 和 CD 可以写在同一个 YAML 里,也可以拆成多个文件,甚至跑在不同项目的不同流水线里——怎么切是工程选择,不是技术限制。
三、GitLab Runner:真正干活的人
到这里有个核心疑问:
GitLab 本身是个代码托管平台,它怎么去执行 Python 测试、Docker 构建、SSH 部署这些活? 它又不会在你服务器上长出手脚。
答案就是 GitLab Runner。
1. Server 和 Runner 谁干什么
CI/CD 里其实是三个角色:
| 组件 | 职责 |
|---|---|
| GitLab Server | 管仓库、解析配置、创建并调度 Job |
| GitLab Runner | 领 Job、准备环境、跑命令、上报结果 |
| 部署服务器 | 跑最终发布的业务系统 |
Runner 本质是一个独立的任务执行程序,可以装在任意一台 Linux 服务器、虚拟机或容器里。它不一定在 GitLab 服务器上,也不一定在测试服务器上——它是单独的"工人"。
常见架构:
GitLab Server
│
HTTP API 通信
│
GitLab Runner
│
┌─────────┴─────────┐
│ │
CI 检查 CD 部署
│ │
Python / Node.js SSH / Docker
Claude Code │
测试 / 生产服务器
GitLab.com 提供共享 Runner;公司自建 GitLab 一般要自己装 Runner。Runner 软件免费,但跑它的机器要算计算资源和维护成本。
2. Runner 怎么领任务?
Runner 是主动拉任务,不是被动等推送。流程:
GitLab 收到代码事件
↓
读取 CI/CD 配置
↓
创建 Pipeline 和 Jobs
↓
Runner 向 GitLab API 请求 Job
↓
GitLab 返回可执行 Job
↓
Runner 准备执行环境
↓
拉代码,执行 script
↓
上传日志、产物和执行状态
Runner 通过 GitLab Jobs API 领活:
POST /api/v4/jobs/request
拿到 Job 后,根据 Executor(执行器) 类型准备运行环境:
- Shell Executor:直接用宿主机 Shell 跑命令。
- Docker Executor:在一个 Docker 容器里跑 Job。
- Kubernetes Executor:用 K8s Pod 跑 Job。
易错点:Runner 所在的机器,和 Job 实际执行的环境,不一定是同一个进程或容器。 比如用 Docker Executor 时,Runner 只负责"调度管理",真正的检查命令跑在它拉起来的 Job 容器里。这俩是分开的。
四、Runner 怎么调用 Claude Code 这类工具
这是很多人会卡住的地方:Runner 怎么"集成" Claude Code?
答案是:根本不用集成它的内部代码。 Runner 只需要在 Job 脚本里启动 Claude Code CLI,就像调用 pytest、npm、docker 一样自然。
Claude Code 除了交互模式,还支持非交互式命令行调用:
claude -p "检查当前代码中的安全问题" \
--output-format json
在 Python 里也能用 subprocess 调:
import subprocess
result = subprocess.run(
[
"claude",
"-p",
"检查本次代码变更中的安全问题",
"--output-format",
"json",
],
capture_output=True,
text=True,
timeout=600,
)
print(result.stdout)
底层调用链:
GitLab Server
│ HTTP API
▼
GitLab Runner
│ 执行 Job 脚本
▼
Shell / Python 进程
│ subprocess 启动子进程
▼
Claude Code CLI
│ HTTPS API
▼
Claude 模型服务
│ 返回分析结果 + 工具调用请求
▼
Claude Code 执行允许的本地工具
▼
返回审查报告
▼
审查脚本解析结果,决定是否通过
▼
Runner 上报 GitLab
这里有两层通信,要分清楚:
第一层:Runner ↔ 本地程序。 Runner 跑脚本,脚本启动 Claude Code 子进程,靠命令行参数传任务、靠标准输出/标准错误/退出码收结果。
第二层:Claude Code ↔ 远程模型。 CLI 跑在本地执行环境里,通过 HTTPS 调远端模型。一次 claude -p 内部可能包含多轮模型调用、文件读取和工具使用,不一定只对应一次大模型 API 请求。
⚠️ 关键认知:模型成功返回报告 ≠ 审查通过。 比如模型发现了高危漏洞,Claude Code 进程可能照样正常退出(退出码 0),但你的审查脚本可以读 JSON 里的风险等级,主动返回非零退出码,让 GitLab 把这个 Job 标红失败。门禁逻辑握在你脚本手里,不在模型手里。
另外,无人值守运行需要配好模型 API 凭证、工具权限和执行环境;凭证走 CI/CD Variables / 密钥管理,绝不硬编码进仓库。
五、实战:从 MR 检查到自动部署
以一个 FastAPI + Vue + Docker 的科研应用为例,公司用 GitLab 管代码。
1. 已有的 CI 代码集成流程
开发人员在 feature/sem 上写完功能:
git add .
git commit -m "feat: 完成 SEM 功能"
git push origin feature/sem
然后创建 feature/sem → develop 的 Merge Request。GitLab 自动建流水线,跑四项检查:
| Job | 功能 |
|---|---|
policy-and-static | Python 语法、静态问题、仓库规范检查 |
frontend-types | 前端 TypeScript 类型检查 |
secret-scan | 敏感密钥泄露扫描 |
ai-review | 用 Claude Code 做安全、业务、架构审查 |
检查失败 → 改代码、再 push 到原分支 → 重新触发检查。
检查全过、无冲突、结果仍有效 → 自动合并机器人把 MR 合进 develop。
到这里完成的是"代码质量门禁 + 持续集成",并不代表服务器已经发版。 别把"合并"和"上线"画等号。
2. CD 怎么接进来?
develop 更新后,可以再串一段自动部署:
develop 分支更新
↓
触发 CI/CD Pipeline
↓
验证代码
↓
构建 Docker 镜像
↓
推送镜像仓库
↓
执行 CD 部署 Job
↓
Runner 通过 SSH 访问测试服务器
↓
拉新镜像、更新容器
↓
健康检查
Runner 通过 SSH 在服务器上执行部署命令:
ssh deploy@server \
"cd /srv/app && docker compose pull && docker compose up -d"
部署完用 docker ps 看运行中的容器和镜像版本。生产环境还能加人工审批、灰度发布、健康检查和回滚。
这一步要求提前备好:SSH 授权、网络连通、镜像仓库权限、服务器上的 Docker Compose 配置。少一样都跑不通。
六、踩坑点与总结
踩坑点
- 把"用了 GitLab"等同于"全流程自动化":有些公司只做了 MR 自动检查;有些做到镜像构建;有些能自动部署生产。自动化到哪一步,取决于配置,不取决于工具。别想当然。
- 流水线通过 ≠ 业务正确:静态检查和 AI 审查只覆盖配置范围内的问题。统计计算、计费、权限这类敏感业务,还得靠业务测试和人工验收兜底。
- 配置和环境混在一起:
.gitlab-ci.yml定义任务,GitLab 设置管变量/Runner/权限,Runner 负责执行,部署服务器跑应用——各司其职,别把服务器密码、SSH 私钥、API Key 写进仓库,走受保护的 CI/CD Variables 或密钥管理服务。 - 分片/Executor 没分清:Runner 机器 ≠ Job 执行环境。用 Docker Executor 时改了 Runner 宿主机的环境,Job 容器里根本看不到。
- 以为
claude -p退出码 0 就是审查通过:模型正常返回不代表没问题,门禁标准由你的脚本根据 JSON 内容决定,务必显式判断风险等级再定退出码。
总结
| 核心概念 | 一句话理解 |
|---|---|
| CI | 保证新代码满足集成检查要求 |
| CD | 把验证过的版本交付或部署出去 |
| PR / MR | 开发人员提出的代码合并申请 |
| Pipeline | 一次由配置组织起来的自动化流程 |
| Stage | 流水线里的逻辑阶段 |
| Job | 流水线里真正要执行的任务 |
| GitLab Server | 流水线管理和任务调度中心 |
| GitLab Runner | 真正执行 Job 的程序 |
| Executor | Runner 跑 Job 用的执行机制(Shell/Docker/K8s) |
.gitlab-ci.yml | 定义 Job、触发条件和依赖的配置文件 |
一句话串联: 开发人员
git push→ GitLab Server 根据事件和 YAML 创建流水线 → Runner 领任务、跑脚本、调用各种工具 → CI 把质量关、CD 把发版关 → 目标服务器跑起发布后的业务。打个比方:GitLab Server 是大脑(调度),Runner 是手脚(执行),
.gitlab-ci.yml是剧本(流程),CI 是质量门禁,CD 是发版动作。 五者齐备,"提交即上线"才算真正跑起来。
参考资料:
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/qq_38673558/article/details/170292787



