🎨GitLab CI/CD 入门实战

本文面向希望系统掌握 GitLab CI/CD 的开发者与运维工程师,按「理论 → 上手 → 实战 → 进阶 → 安全 → 排错 → 对比」的路径循序展开,读完即可在真实项目中落地一条高效、安全、可观测的流水线。
🚀 一、引言:为什么需要 GitLab CI/CD?
在现代软件开发中,持续集成(CI) 与 持续交付/部署(CD) 是提升交付效率与代码质量的核心实践。GitLab CI/CD 作为 GitLab 的内置能力,将代码、流水线、监控无缝打通,是当前最流行的一体化 DevOps 方案之一。
核心优势一览
| 优势 | 说明 |
|---|---|
| 🔗 深度集成 | 与 Git 仓库、MR、Issue、Registry 原生联动,无需外部工具链。 |
| 📄 声明式配置 | 一份 .gitlab-ci.yml 即可定义完整流水线,易读易维护。 |
| 🌐 多环境支持 | 兼容 Shell、Docker、Kubernetes,适配裸机到云原生全场景。 |
| 🧱 生态丰富 | 内置 SAST、DAST、依赖扫描、Auto DevOps 等开箱即用模板。 |
| 💰 成本可控 | 支持自建 Runner,敏感项目可完全私有化部署。 |
📚 二、核心概念与架构
2.1 基础术语
| 术语 | 定义 |
|---|---|
| Pipeline(流水线) | 由多个 Stage 和 Job 组成的自动化流程,由 push、MR、tag、schedule 等事件触发。 |
| Stage(阶段) | 逻辑分组的 Job 集合(如 build、test、deploy),阶段间串行、阶段内并行。 |
| Job(作业) | Pipeline 中的最小执行单元,由 script 定义命令。 |
| Runner(运行器) | 实际执行 Job 的代理,可使用 GitLab 共享 Runner 或自建私有 Runner。 |
| Executor(执行器) | Runner 的执行方式:shell / docker / kubernetes / ssh / virtualbox 等。 |
| Artifact(产物) | Job 输出的文件,可被下游 Job 下载或提供给用户下载。 |
| Cache(缓存) | 跨流水线复用的依赖目录(如 .m2、node_modules),用于加速构建。 |
| Environment(环境) | 部署目标的抽象(dev/staging/prod),支持回滚与历史追踪。 |
2.2 流水线类型
| 类型 | 触发方式 | 典型用途 |
|---|---|---|
| Branch Pipeline | push 到分支 | 日常构建与测试 |
| Merge Request Pipeline | 创建/更新 MR | 代码评审前的门禁检查 |
| Tag Pipeline | 打 tag | 正式发版 |
| Scheduled Pipeline | 定时任务 | 夜间回归、安全扫描 |
| Parent-Child Pipeline | trigger 关键字 |
大型单仓多模块拆分 |
| Multi-Project Pipeline | 跨项目 trigger |
微服务联动发布 |
2.3 工作流概览
开发者 push / 提 MR
│
▼
┌─────────────────┐ 触发规则 (rules)
│ .gitlab-ci.yml │ ───────────────────┐
└─────────────────┘ │
│ ▼
▼ ┌───────────────┐
Pipeline ──► Stage₁ ──► Stage₂ ──► ... ──► Stageₙ
│ │ │
Job×N Job×N Job×N
│
▼
Runner(Shell / Docker / K8s)
│
▼
Artifact / Cache / 环境部署
💡 关键点:Stage 顺序串行,但通过
needs可构建 DAG 流水线,解除不必要的等待。
⚡ 三、快速上手:三步跑通第一条流水线
步骤 1:编写 .gitlab-ci.yml
在项目根目录新建 .gitlab-ci.yml,定义最小可用流水线:
stages:
- build
- test
- deploy
build_job:
stage: build
script:
- mvn clean package -DskipTests
artifacts:
paths:
- target/*.jar
expire_in: 1 week
test_job:
stage: test
script:
- mvn test
deploy_job:
stage: deploy
script:
- scp target/*.jar user@server:/path/to/deploy
environment:
name: production
url: https://your-app.com
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
💡
only/except仍受支持但属于 legacy 关键字,新项目请优先使用表达力更强的rules(官方推荐,支持复合条件、变量运算、changes、exists等)。
步骤 2:安装并注册 Runner
# 1. 安装(以 CentOS 为例)
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh | sudo bash
sudo yum install -y gitlab-runner
# 2. 注册(Token 在 项目 Settings → CI/CD → Runners 获取)
sudo gitlab-runner register
# 3. 启动
sudo gitlab-runner start
sudo gitlab-runner status
步骤 3:推送代码,触发流水线
git add .gitlab-ci.yml
git commit -m "ci: add pipeline configuration"
git push origin main
推送后访问 CI/CD → Pipelines 即可实时查看执行状态。
🛠️ 四、实战案例:Java 项目自动化部署
4.1 场景描述
对一个 Maven 项目,实现:构建 → 单元测试 → SSH 部署到远程服务器 → 重启服务。
4.2 完整配置
stages:
- build
- test
- deploy
variables:
MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository"
cache:
key: "$CI_COMMIT_REF_SLUG"
paths:
- .m2/repository/
build:
stage: build
image: maven:3.8.4-openjdk-17
script:
- mvn clean package -DskipTests
artifacts:
paths:
- target/*.jar
expire_in: 1 day
test:
stage: test
image: maven:3.8.4-openjdk-17
script:
- mvn test
artifacts:
when: always
reports:
junit: target/surefire-reports/TEST-*.xml
deploy:
stage: deploy
image: alpine:latest
before_script:
- apk add --no-cache openssh-client
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh && chmod 700 ~/.ssh
- ssh-keyscan -H $DEPLOY_HOST >> ~/.ssh/known_hosts
script:
- scp target/*.jar $DEPLOY_USER@$DEPLOY_HOST:/opt/app/app.jar
- ssh $DEPLOY_USER@$DEPLOY_HOST "sudo systemctl restart app"
environment:
name: production
url: https://your-app.com
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
4.3 关键设计要点
| 维度 | 做法 | 原因 |
|---|---|---|
| 🔐 敏感信息 | SSH_PRIVATE_KEY 等放入 Settings → CI/CD → Variables,勾选 Masked + Protected |
避免明文泄露到日志或代码仓库 |
| 🛡️ SSH 安全 | 使用管道写入私钥,并通过 ssh-keyscan 预置 known_hosts |
防止中间人攻击、规避进程替换的兼容问题 |
| 🚀 构建加速 | 按分支缓存 .m2/repository |
大幅减少重复下载依赖 |
| 🧹 存储治理 | Artifact 设置 expire_in |
防止长期累积占用存储配额 |
🌍 五、多语言与多场景示例
5.1 Node.js 项目
image: node:20-alpine
cache:
key:
files:
- package-lock.json
paths:
- node_modules/
stages: [install, lint, test, build]
install:
stage: install
script:
- npm ci
lint:
stage: lint
script:
- npm run lint
test:
stage: test
script:
- npm test -- --coverage
coverage: '/All files[^|]*\|[^|]*\s+([\d.]+)/'
build:
stage: build
script:
- npm run build
artifacts:
paths:
- dist/
5.2 Python 项目
image: python:3.11-slim
cache:
paths:
- .cache/pip
- venv/
before_script:
- python -m venv venv
- source venv/bin/activate
- pip install -r requirements.txt
stages: [lint, test]
lint:
stage: lint
script:
- pip install ruff
- ruff check .
test:
stage: test
script:
- pytest --junitxml=report.xml --cov=app
artifacts:
reports:
junit: report.xml
5.3 Go 项目
image: golang:1.22
variables:
GOPATH: $CI_PROJECT_DIR/.go
GOCACHE: $CI_PROJECT_DIR/.gocache
cache:
paths:
- .go/pkg/mod/
- .gocache/
stages: [test, build]
test:
stage: test
script:
- go vet ./...
- go test -race -coverprofile=coverage.out ./...
build:
stage: build
script:
- CGO_ENABLED=0 go build -ldflags="-s -w" -o app ./cmd/app
artifacts:
paths: [app]
5.4 Kubernetes 部署
deploy_k8s:
stage: deploy
image: bitnami/kubectl:latest
variables:
KUBE_NAMESPACE: production
before_script:
- echo "$KUBE_CONFIG" | base64 -d > $HOME/.kube/config
script:
- kubectl set image deployment/myapp
myapp=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
-n $KUBE_NAMESPACE
- kubectl rollout status deployment/myapp -n $KUBE_NAMESPACE --timeout=5m
environment:
name: production
kubernetes:
namespace: $KUBE_NAMESPACE
rules:
- if: '$CI_COMMIT_TAG'
🎯 六、进阶能力与最佳实践
按「流程控制 → 构建加速 → 质量内建 → 工程复用」四个维度进阶。
6.1 流程控制
① 条件执行(rules)
deploy_prod:
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: on_success
- when: never
deploy_dev:
environment:
name: development
rules:
- if: '$CI_COMMIT_BRANCH =~ /^feature\/.*/'
release:
rules:
- if: '$CI_COMMIT_TAG'
② 手动确认(manual gate)
deploy_prod:
stage: deploy
when: manual
allow_failure: false
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
③ 子流水线(parent-child)
trigger_backend:
trigger:
include: backend/.gitlab-ci.yml
strategy: depend
trigger_frontend:
trigger:
include: frontend/.gitlab-ci.yml
strategy: depend
④ 稳定性控制(retry / timeout / resource_group)
flaky_e2e:
script: [./run-e2e.sh]
retry:
max: 2
when:
- runner_system_failure
- stuck_or_timeout_failure
timeout: 30m
deploy_prod:
stage: deploy
resource_group: production # 同 group 内 Job 串行,避免并发部署踩踏
script: [./deploy.sh]
| 关键字 | 作用 | 建议 |
|---|---|---|
retry |
针对指定失败类型自动重试 | 仅对基础设施/超时类失败开启,避免掩盖真实 bug |
timeout |
Job 级超时(默认 1h) | 为长测试显式放宽,防止挂起占用 Runner |
resource_group |
同名 Job 串行化 | 生产部署、数据库迁移等必须独占环境的场景 |
⑤ Review App(MR 动态预览环境)
review:
stage: deploy
script: [./deploy-review.sh $CI_COMMIT_REF_SLUG]
environment:
name: review/$CI_COMMIT_REF_SLUG
url: https://$CI_COMMIT_REF_SLUG.review.example.com
on_stop: stop_review
auto_stop_in: 1 week
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
stop_review:
stage: deploy
script: [./destroy-review.sh $CI_COMMIT_REF_SLUG]
when: manual
environment:
name: review/$CI_COMMIT_REF_SLUG
action: stop
每个 MR 自动获得独立预览链接,auto_stop_in 过期或 MR 合并/关闭后自动回收资源。
6.2 构建加速
① 矩阵并行
test:
parallel:
matrix:
- TEST_SUITE: [unit, integration, e2e]
JDK_VERSION: ["11", "17"]
script:
- mvn test -Dsuite=$TEST_SUITE
② DAG 流水线(needs)
deploy:
stage: deploy
needs:
- job: build
artifacts: true
- job: test
打破严格的 Stage 串行,让 deploy 在 build 完成后立即触发,无需等待同阶段其它 Job。
💡
needsvsdependencies:needs同时控制「执行顺序 + artifacts 下载」;dependencies仅控制 artifacts 下载(执行顺序仍由 Stage 决定)。新项目统一用needs即可。
③ 中断过时流水线
default:
interruptible: true
workflow:
auto_cancel:
on_new_commit: interruptible
6.3 质量内建
① 测试报告 + 覆盖率
test:
script:
- mvn test jacoco:report
coverage: '/Total.*?([0-9]{1,3})%/'
artifacts:
when: always
reports:
junit: target/surefire-reports/TEST-*.xml
coverage_report:
coverage_format: cobertura
path: target/site/jacoco/jacoco.xml
② 容器镜像构建与推送
build_image:
stage: build
image: docker:24.0
services:
- docker:24.0-dind
variables:
DOCKER_TLS_CERTDIR: "/certs"
script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
- docker build -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" .
- docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"
⚠️ 安全提示:DinD 依赖
privileged: true,在共享 Runner 上存在容器逃逸风险。生产环境推荐:
- Kaniko(
gcr.io/kaniko-project/executor):无需 Docker daemon,无需特权;- Buildah / Podman:Rootless 构建;
- Buildx + remote builder:Runner 仅作为 OCI client 推送镜像。
③ 代码质量扫描
include:
- template: Code-Quality.gitlab-ci.yml
- template: Security/SAST.gitlab-ci.yml
- template: Security/Dependency-Scanning.gitlab-ci.yml
- template: Security/Secret-Detection.gitlab-ci.yml
6.4 工程复用
① include 跨项目复用
include:
- project: 'devops/ci-templates'
ref: main
file: '/templates/java.yml'
- template: 'Security/SAST.gitlab-ci.yml'
② extends 复用配置块
.base-job:
image: maven:3.8.4-openjdk-17
cache:
paths:
- .m2/repository/
build:
extends: .base-job
script:
- mvn clean package
③ 使用 YAML 锚点
.deploy_template: &deploy_template
image: alpine:latest
before_script:
- apk add --no-cache openssh-client
deploy_staging:
<<: *deploy_template
script: [./deploy.sh staging]
deploy_prod:
<<: *deploy_template
script: [./deploy.sh prod]
🔐 七、安全与合规最佳实践
7.1 凭据管理
| 做法 | 说明 |
|---|---|
| ✅ 使用 CI/CD Variables | 敏感信息放入项目/群组级变量,勾选 Masked 掩码日志输出 |
| ✅ 区分 Protected | Protected 变量仅在受保护分支/tag 可用,隔离生产凭据 |
| ✅ 使用外部 Vault | 对接 HashiCorp Vault、AWS Secrets Manager,CI 运行时按需拉取 |
| ❌ 避免 echo 打印变量 | 即便有 Mask 也可能因换行/拆分导致日志泄露 |
❌ 不要提交 .env 到仓库 |
即便是 .gitignore 也要配合 pre-commit 校验 |
🔑 推荐:使用 OIDC id_tokens 实现免密钥部署(GitLab 15.7+)
GitLab 可为每个 Job 签发短效 JWT,与 AWS / GCP / Azure / HashiCorp Vault 建立 OIDC 信任,彻底消除长期 AccessKey。
deploy_aws:
image: amazon/aws-cli:latest
id_tokens:
AWS_ID_TOKEN:
aud: https://gitlab.com # 与云侧 IdP 信任配置保持一致
script:
- >
CREDS=$(aws sts assume-role-with-web-identity
--role-arn "$AWS_ROLE_ARN"
--role-session-name "gitlab-$CI_JOB_ID"
--web-identity-token "$AWS_ID_TOKEN"
--duration-seconds 3600
--query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken]'
--output text)
- export AWS_ACCESS_KEY_ID=$(echo $CREDS | awk '{print $1}')
- export AWS_SECRET_ACCESS_KEY=$(echo $CREDS | awk '{print $2}')
- export AWS_SESSION_TOKEN=$(echo $CREDS | awk '{print $3}')
- aws s3 sync dist/ s3://$BUCKET
优势:
- 无长期密钥:凭据随 Job 结束即失效,无需轮换
- 精细授权:云侧可按项目路径、分支、环境、MR 来源等做条件策略(如
sub声明匹配project_path:foo/bar:ref_type:branch:ref:main) - 可审计:每次假设角色都能追溯到具体 Pipeline/Job
7.2 Runner 隔离
- 生产 Runner 与开发 Runner 分离:通过 tag 约束关键部署 Job。
- 使用 Docker / Kubernetes Executor:避免 Shell Executor 污染宿主环境。
- 禁用
privileged模式:除非必须(如 DinD),否则关闭特权容器。
7.3 供应链安全
include:
- template: Security/SAST.gitlab-ci.yml
- template: Security/Dependency-Scanning.gitlab-ci.yml
- template: Security/Container-Scanning.gitlab-ci.yml
- template: Security/Secret-Detection.gitlab-ci.yml
- template: Security/License-Scanning.gitlab-ci.yml
启用后,每次 MR 都会生成安全报告并与主分支差异对比,发现新增漏洞直接阻断合并。
7.4 审计与合规
- 开启 Audit Events 跟踪敏感操作(变量变更、Runner 注册、流水线授权)。
- 使用 Compliance Frameworks 强制关键项目必须包含安全扫描 Stage。
- 为生产环境启用 Protected Environments,必须走审批才能部署。
📊 八、可观测性与流水线监控
8.1 内建指标
GitLab 自带 CI/CD → Analytics 面板,关注三个核心指标:
| 指标 | 含义 | 优化方向 |
|---|---|---|
| Pipeline Duration | 流水线总耗时 | 通过 needs、并行与缓存缩短 |
| Success Rate | 成功率 | 定位 flaky 测试、不稳定依赖 |
| Deployment Frequency | 部署频率(DORA 指标) | 体现持续交付成熟度 |
8.2 集成 Prometheus + Grafana
# gitlab-runner 自身暴露 metrics
gitlab-runner run --listen-address=":9252"
Prometheus 抓取后,可在 Grafana 观察:
- Runner 空闲/繁忙数量
- Job 队列等待时长
- 并发执行数与资源使用率
8.3 日志与告警
- Job Trace 归档:大规模 Runner 建议接入 S3/OSS 长期存储。
- 失败告警:通过 Integrations → Slack / 企业微信 / 飞书 自动推送失败通知。
- SLA 看板:针对关键项目设置「主分支流水线成功率 ≥ 99%」的报警阈值。
🧩 九、排错指南:常见问题与解决方案
Q1|Runner 未执行作业
- 检查状态:
sudo gitlab-runner status,确认进程在线。 - 标签匹配:Job 的
tags与 Runner 注册标签必须完全一致。 - 项目权限:私有 Runner 需在 Runner 设置中授权该项目使用。
- 并发限制:
concurrent配置不足会导致 Job 排队。
Q2|配置文件语法错误
- CI Lint:GitLab 提供 CI/CD → Editor → Lint,可实时校验 YAML。
- 本地验证:使用
glab ci lint或gitlab-ci-local跑 dry-run。
Q3|SSH 部署失败
- 私钥以变量注入,并显式配置
known_hosts。 - 如使用 Shell 执行器,确保目标服务器上 Runner 用户具备必要权限。
- 遵循最小权限原则,避免直接使用 root。
Q4|缓存没有生效
cache.key是否稳定(建议按分支$CI_COMMIT_REF_SLUG)。- 缓存路径必须位于项目工作目录内(如
.m2/repository/),绝对路径~/.m2/...在 Docker executor 下不会被缓存。 - 多 Runner 场景建议配置 分布式缓存(S3/MinIO)。
Q5|流水线执行太慢
- 启用
needsDAG,打破 Stage 串行。 - 使用
parallel:matrix分片执行大测试套件。 - 预构建基础镜像,避免每次重装依赖。
- 利用
interruptible: true自动取消过时的并发流水线。
Q6|Merge Request Pipeline 不生效
- 确保
rules中包含$CI_PIPELINE_SOURCE == "merge_request_event"。 - 开启 Settings → Merge requests → Pipelines for merged results。
Q7|"Job failed: exit code 137"
- 容器 OOM 被杀:排查 Job 内存占用,调整 Runner
memory_limit或拆分任务。 - 典型场景:前端
webpack构建、大型 Java 项目mvn package。
⚖️ 十、横向对比:GitLab CI vs Jenkins vs GitHub Actions
| 维度 | GitLab CI | Jenkins | GitHub Actions |
|---|---|---|---|
| 配置形式 | YAML(集中) | Groovy DSL / UI | YAML(集中) |
| 与代码平台集成 | 原生一体 | 需插件 | 原生一体 |
| 插件生态 | 中等(模板丰富) | 极其丰富 | 丰富(Marketplace) |
| 私有化部署 | ✅ 完整支持 | ✅ 完整支持 | ⚠️ 需 GH Enterprise |
| 权限/审计 | ✅ 企业级 | 需额外插件 | ✅ 企业级 |
| 学习曲线 | 低 | 中高 | 低 |
| 适合场景 | 一体化 DevOps | 复杂遗留流水线 | GitHub 生态优先 |
💬 选型建议:如果代码在 GitLab,优先直接使用 GitLab CI;若已有大规模 Jenkins 历史资产,可渐进迁移;GitHub 项目则首选 Actions。
📖 十一、预定义变量速查表
| 变量 | 说明 |
|---|---|
$CI_PROJECT_DIR |
仓库克隆后的工作目录 |
$CI_PROJECT_PATH |
项目命名空间路径(如 group/app) |
$CI_COMMIT_SHA |
本次提交完整 SHA |
$CI_COMMIT_SHORT_SHA |
前 8 位短 SHA,常用于镜像 tag |
$CI_COMMIT_BRANCH |
当前分支名(非 MR 上下文) |
$CI_COMMIT_TAG |
当前 Tag 名(仅 Tag pipeline) |
$CI_COMMIT_REF_SLUG |
安全化的分支/Tag 名,可用于缓存 key |
$CI_PIPELINE_SOURCE |
触发来源:push / merge_request_event / schedule / web 等 |
$CI_MERGE_REQUEST_IID |
MR 编号 |
$CI_REGISTRY / $CI_REGISTRY_IMAGE |
内置 Container Registry 地址/镜像名 |
$CI_JOB_TOKEN |
临时 Token,用于 API 调用与跨项目 trigger |
$GITLAB_USER_LOGIN |
触发流水线的用户名 |
💬 如有疑问或希望探讨具体场景,欢迎在评论区留言交流。

浙公网安备 33010602011771号