🎨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 集合(如 buildtestdeploy),阶段间串行、阶段内并行。
Job(作业) Pipeline 中的最小执行单元,由 script 定义命令。
Runner(运行器) 实际执行 Job 的代理,可使用 GitLab 共享 Runner 或自建私有 Runner。
Executor(执行器) Runner 的执行方式:shell / docker / kubernetes / ssh / virtualbox 等。
Artifact(产物) Job 输出的文件,可被下游 Job 下载或提供给用户下载。
Cache(缓存) 跨流水线复用的依赖目录(如 .m2node_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(官方推荐,支持复合条件、变量运算、changesexists 等)。

步骤 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 串行,让 deploybuild 完成后立即触发,无需等待同阶段其它 Job。

💡 needs vs dependenciesneeds 同时控制「执行顺序 + 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 上存在容器逃逸风险。生产环境推荐:

  • Kanikogcr.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 lintgitlab-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|流水线执行太慢

  • 启用 needs DAG,打破 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 触发流水线的用户名

💬 如有疑问或希望探讨具体场景,欢迎在评论区留言交流。

posted @ 2026-05-01 09:46  丿似锦  阅读(96)  评论(0)    收藏  举报