CI/CD 执行流程说明
描述 GitLab CI/CD 从代码提交到流水线完成的完整执行过程,以及 Docker 容器的创建与管理机制。
整体架构
┌─────────────────────────────────────────────────────────┐
│ Windows 宿主机 │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Docker Desktop│ │ Git Bash │ │
│ │ (WSL2) │ │ (git push) │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │
│ ┌──────┴───────────────────┴──────────────────────┐ │
│ │ Docker Engine │ │
│ │ │ │
│ │ ┌─────────┐ ┌─────────────┐ │ │
│ │ │ GitLab │ │ GitLab │ │ │
│ │ │ (CE) │◄─┤ Runner │ │ │
│ │ │ :8090 │ │ (常驻进程) │ │ │
│ │ └─────────┘ └──────┬──────┘ │ │
│ │ │ │ │
│ │ 为每个 Job 动态创建容器 ▼ │ │
│ │ │ │
│ │ ┌─────────────────┐ ┌─────────────────────┐ │ │
│ │ │ 辅助容器 │ │ 构建容器 │ │ │
│ │ │ (predefined) │ │ (python:3.11-slim) │ │ │
│ │ │ git clone │ │ pytest / pylint │ │ │
│ │ │ 缓存恢复/保存 │ │ bandit / claude │ │ │
│ │ │ artifact 上传 │ │ │ │ │
│ │ └─────────────────┘ └─────────────────────┘ │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
一、触发流程
开发者提交代码 (git push)
│
▼
GitLab 接收 push 事件
│
▼
解析 .gitlab-ci.yml
│
▼
创建 Pipeline(流水线)
│
▼
按阶段创建 Job(任务)
触发条件
| 事件 | 触发行为 |
|---|---|
git push 到任意分支 |
创建完整 Pipeline |
| 创建 Merge Request | 触发 MR Pipeline |
| 手动触发(Web/API) | 指定分支创建 Pipeline |
二、流水线阶段
Pipeline #47 (main 分支)
│
├─ Stage 1: test / code-quality / security ← 并行执行 (needs: [])
│ ├─ Job: run_tests (47s)
│ ├─ Job: code_quality (54s)
│ └─ Job: security_scan (49s)
│
├─ Stage 2: review
│ └─ Job: claude_review (24s) ← AI 代码审查
│
├─ Stage 3: deploy
│ └─ Job: deploy (12s) ← 打包部署
│
└─ 总耗时: 163 秒
阶段执行规则
| 规则 | 说明 |
|---|---|
| 同阶段内 | needs: [] 表示并行,否则按顺序执行 |
| 跨阶段 | 上一阶段全部完成后才启动下一阶段 |
allow_failure: true |
Job 失败不阻塞流水线(code-quality、security、review) |
rules |
控制 Job 是否创建(如 deploy 只在主分支执行) |
三、Docker 容器生命周期
每个 Job 的容器创建过程
以 run_tests 为例:
时间线(约 70 秒)
│
│ ┌─ 辅助容器启动 (predefined)
│ │ 镜像: gitlab-runner-helper
│ │ 大小: ~30MB
│ │
│ │ 任务:
│ │ ├─ 1. 准备执行环境
│ │ │ - 创建 /builds/lizhihua/calculator 目录
│ │ │ - 设置环境变量
│ │ │
│ │ ├─ 2. Git Clone
│ │ │ - git fetch origin
│ │ │ - git checkout <commit-sha>
│ │ │ - 耗时: ~3 秒
│ │ │
│ │ ├─ 3. 恢复缓存
│ │ │ - 从 Docker volume 提取 .cache/pip
│ │ │ - 命中缓存时 pip install 从 30s 降到 5s
│ │ │ - 耗时: ~1 秒
│ │ │
│ │ └─ 4. 准备构建容器
│ │ - 拉取 python:3.11-slim(首次约 124MB)
│ │ - 后续使用本地缓存(pull_policy: if-not-present)
│ │
│ ├─ 构建容器启动 (build)
│ │ 镜像: python:3.11-slim
│ │ 大小: ~124MB
│ │
│ │ 任务:
│ │ ├─ 执行 before_script
│ │ │ - python --version
│ │ │ - pip install pytest pytest-cov
│ │ │
│ │ └─ 执行 script
│ │ - pytest test_calculator.py -v --cov
│ │ - 输出测试报告、覆盖率
│ │
│ └─ 辅助容器再次介入
│ ├─ 保存缓存 (.cache/pip → Docker volume)
│ ├─ 上传 artifacts (report.xml, coverage.xml)
│ └─ 清理临时文件
│
│ 容器自动退出(Exited 0)
容器命名规则
runner-zapabj0l-project-14-concurrent-0-3aafad579f0e50e5-predefined
│ │ │ │ │ │
│ │ │ │ │ └─ 辅助容器标识
│ │ │ │ └─ Job 唯一哈希
│ │ │ └─ 并发槽位 (0, 1, 2)
│ │ └─ 项目 ID (14 = calculator)
│ └─ Runner Token 哈希
└─ 固定前缀
容器清理
Job 完成后容器自动退出,不会自动删除(保留用于调试)。可通过以下方式清理:
bash
# 清理已退出的容器
docker container prune -f
# Runner 配置中启用自动清理
# /etc/gitlab-runner/config.toml
[runners.docker]
disable_cache = true
shm_size = 0
四、Runner 配置说明
当前配置 (/etc/gitlab-runner/config.toml)
toml
concurrent = 3 # 最大并发 Job 数
request_concurrency = 4 # 同时向 GitLab 请求 Job 的数量
check_interval = 0 # 轮询间隔(0 = 默认)
[[runners]]
name = "Docker CI Runner"
url = "http://192.168.1.130:8090"
executor = "docker" # 使用 Docker 执行器
clone_url = "http://192.168.1.130:8090" # Git clone 地址
[runners.docker]
image = "python:3.11-slim" # 默认镜像
pull_policy = ["if-not-present"] # 本地有就不拉取
privileged = false # 不启用特权模式
volumes = ["/cache"] # 挂载缓存目录
关键配置项
| 配置 | 当前值 | 说明 |
|---|---|---|
concurrent |
3 | 最多同时运行 3 个 Job |
pull_policy |
if-not-present | 镜像存在本地时不重复下载 |
privileged |
false | 容器内不能运行 Docker(安全) |
volumes |
/cache | 持久化缓存目录 |
五、缓存机制
pip 缓存
yaml
# .gitlab-ci.yml
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
cache:
key: "${CI_COMMIT_REF_SLUG}" # 按分支隔离缓存
paths:
- .cache/pip
缓存存储位置
Runner 容器内部
└─ /cache
└─ main-protected.tar ← 缓存归档文件
缓存通过 Docker volume 持久化,容器重启后仍然可用。
缓存效果
| 场景 | pip install 耗时 |
|---|---|
| 首次(无缓存) | ~30 秒 |
| 命中缓存 | ~5 秒 |
| 缓存失效 | ~30 秒 |
六、资源消耗
常驻进程
| 组件 | 内存 | CPU(空闲) |
|---|---|---|
| GitLab (CE) | ~2 GB | ~5% |
| GitLab Runner | ~50 MB | ~1% |
| Docker Desktop (WSL2) | ~1 GB | ~2% |
| 合计 | ~3 GB | ~8% |
每个 Job 运行时
| 组件 | 内存 | 说明 |
|---|---|---|
| 辅助容器 | ~30 MB | git clone + 缓存处理 |
| 构建容器 | ~80-150 MB | 取决于安装的依赖 |
| 单 Job 峰值 | ~180 MB | |
| 3 Job 并行峰值 | ~540 MB | concurrent = 3 |
Docker Desktop 资源建议
| 资源 | 最低 | 推荐 |
|---|---|---|
| 内存 | 4 GB | 6 GB |
| CPU | 2 核 | 4 核 |
| 磁盘 | 20 GB | 40 GB |
七、常见问题
Q: 为什么 Job 一直 pending?
Runner 可能离线或配置丢失。检查:
bash
# 1. Runner 容器是否在运行
docker ps | grep runner
# 2. Runner 是否注册成功
docker exec gitlab-runner gitlab-runner list
# 3. 查看 Runner 日志
docker logs --tail 20 gitlab-runner
Q: 容器为什么显示 Exited?
正常行为。Job 完成后容器自动退出,状态码 0 表示成功,非 0 表示失败。
bash
# 查看所有退出的容器
docker ps -a --filter "status=exited"
# 批量清理
docker container prune -f
Q: 如何手动清理旧容器?
bash
# 清理所有已退出的容器
docker container prune -f
# 清理未使用的镜像
docker image prune -f
# 清理所有未使用的资源(谨慎)
docker system prune -f
Q: 并发数设多少合适?
取决于 Docker Desktop 分配的资源:
| 内存 | 推荐并发数 |
|---|---|
| 4 GB | 1-2 |
| 6 GB | 2-3 |
| 8 GB | 3-4 |
当前配置 concurrent = 3,分配 4GB 内存时可能偏紧,建议 Docker Desktop 至少分配 6GB。
Q: 辅助容器可以省略吗?
不可以。辅助容器负责 Git 操作和缓存管理,是 GitLab Runner Docker executor 的必要组件。但可以通过以下方式减少开销:
pull_policy = if-not-present避免重复拉取镜像- 增大
concurrent让多个 Job 共享同一个辅助容器镜像
八、完整执行时序图
开发者 GitLab Runner Docker
│ │ │ │
│── git push ────────────►│ │ │
│ │ │ │
│ │── 解析 .gitlab-ci.yml ──►│ │
│ │ 创建 Pipeline #47 │ │
│ │ 创建 5 个 Job │ │
│ │ │ │
│ │◄── 请求 Job ────────────│ │
│ │── 分配 run_tests ──────►│ │
│ │── 分配 code_quality ──►│ │
│ │── 分配 security_scan ─►│ │
│ │ │ │
│ │ │── 创建辅助容器 ────────►│
│ │ │ (git clone) │
│ │ │ │
│ │ │── 创建构建容器 x3 ─────►│
│ │ │ (pytest/pylint/bandit)│
│ │ │ │
│ │◄── 上传 Job 日志 ───────│ │
│ │◄── 上传 artifacts ──────│ │
│ │ │ │
│ │ │── 容器退出 ────────────►│
│ │ │ │
│ │── Stage 1 完成 ────────►│ │
│ │ 触发 review 阶段 │ │
│ │ │ │
│ │ │── claude_review ──────►│
│ │ │ (AI 代码审查) │
│ │ │ │
│ │── review 完成 ─────────►│ │
│ │ 触发 deploy 阶段 │ │
│ │ │ │
│ │ │── deploy ─────────────►│
│ │ │ (打包部署) │
│ │ │ │
│ │── Pipeline 完成 ───────►│ │
│◄── 通知(成功/失败)────│ │ │