【CI/CD·GitLab篇】快速入门:.gitlab-ci.yml 语法与 Runner 部署
前言
如果你用的是 GitLab 管理代码,那 GitLab CI 就是天然的选择——不需要额外部署 Jenkins,CI/CD 配置直接写在仓库里。本篇带你从零开始:部署 Runner、编写 .gitlab-ci.yml、跑通第一条流水线。
一、GitLab CI 架构
┌──────────────────────────────────────┐
│ GitLab 实例 │
│ ┌──────────┐ ┌──────────────────┐ │
│ │ Git 仓库 │ │ CI/CD 调度引擎 │ │
│ │ │ │ - 解析 .gitlab-ci│ │
│ │ │ │ - 分发 Job 给 Runner│ │
│ └──────────┘ └────────┬─────────┘ │
└─────────────────────────┼────────────┘
│ 分发 Job
┌──────────────┼──────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Runner 1│ │ Runner 2│ │ Runner 3│
│ (shell) │ │ (docker)│ │ (k8s) │
└─────────┘ └─────────┘ └─────────┘
关键概念:
| 概念 | 说明 |
|------|------|
| Pipeline | 一条流水线,包含多个 Stage |
| Stage | 阶段,同一阶段的 Job 并行执行 |
| Job | 具体任务,是流水线的基本执行单元 |
| Runner | 执行 Job 的 Agent 进程 |
| Executor | Runner 的执行方式:shell / docker / kubernetes |
二、Runner 部署
方式一:Docker 部署(推荐)
# 拉取 Runner 镜像
docker pull gitlab/gitlab-runner:latest
# 启动 Runner
docker run -d \
--name gitlab-runner \
--restart always \
-v /srv/gitlab-runner/config:/etc/gitlab-runner \
-v /var/run/docker.sock:/var/run/docker.sock \
gitlab/gitlab-runner:latest
# 注册 Runner(从 GitLab → Settings → CI/CD → Runners 获取 Token)
docker exec -it gitlab-runner gitlab-runner register
# 交互式配置:
# 1. GitLab instance URL: https://gitlab.com (或你的 GitLab 地址)
# 2. Registration token: 从 GitLab 获取
# 3. Executor: docker
# 4. Default Docker image: alpine:latest
方式二:Kubernetes 部署
# gitlab-runner-values.yaml
image:
registry: docker.io
image: gitlab/gitlab-runner
tag: alpine-v16.9.1
gitlabUrl: https://gitlab.com
runnerToken: "YOUR_RUNNER_TOKEN"
rbac:
create: true
runners:
config: |
[[runners]]
name = "k8s-runner"
executor = "kubernetes"
[runners.kubernetes]
namespace = "gitlab-runner"
image = "alpine:latest"
privileged = true
[[runners.kubernetes.volumes.empty_dir]]
name = "docker-certs"
mount_path = "/certs/client"
medium = ""
# 用 Helm 部署
helm repo add gitlab https://charts.gitlab.io
helm install gitlab-runner gitlab/gitlab-runner -f gitlab-runner-values.yaml -n gitlab-runner
Executor 类型选择
| Executor | 隔离性 | 性能 | 适用场景 |
|---------|--------|------|---------|
| shell | 无 | 最快 | 简单脚本、不需要隔离 |
| docker | 好 | 中等 | 需要环境隔离的标准构建 |
| kubernetes | 好 | 弹性 | 大规模 CI、按需扩缩 |
| docker+machine | 好 | 中等 | 需要自动扩缩的 Docker 环境 |
**培训要点**:生产环境推荐 docker executor——每次 Job 在独立容器中运行,环境干净、互不干扰。shell executor 虽然快但环境会互相污染。
三、.gitlab-ci.yml 语法详解
基本结构
# .gitlab-ci.yml
# 定义阶段(按顺序执行,同阶段 Job 并行)
stages:
- build
- test
- deploy
# 全局变量
variables:
MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository"
DOCKER_REGISTRY: "registry.mycompany.com"
# 构建阶段
build:
stage: build
image: maven:3.9-eclipse-temurin-17
cache:
key: maven-${CI_COMMIT_REF_SLUG}
paths:
- .m2/repository
script:
- mvn clean package -DskipTests
artifacts:
paths:
- target/*.jar
expire_in: 1 hour
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "main"
# 测试阶段
test:
stage: test
image: maven:3.9-eclipse-temurin-17
needs: [build]
script:
- mvn test
artifacts:
reports:
junit: target/surefire-reports/TEST-*.xml
# 部署阶段
deploy:
stage: deploy
image: bitnami/kubectl:latest
needs: [test]
environment:
name: production
url: https://myapp.com
rules:
- if: $CI_COMMIT_BRANCH == "main"
script:
- kubectl set image deployment/myapp app=$DOCKER_REGISTRY/myapp:$CI_COMMIT_SHORT_SHA -n prod
- kubectl rollout status deployment/myapp -n prod --timeout=180s
关键语法元素
1. image — 指定执行 Job 的容器镜像
job:
image: maven:3.9-eclipse-temurin-17
# 或指定 entrypoint
image:
name: maven:3.9-eclipse-temurin-17
entrypoint: [""] # 覆盖镜像默认 entrypoint
2. variables — 变量定义
variables:
# 全局变量
APP_NAME: "myapp"
# 引用 GitLab 内置变量
IMAGE_TAG: "$CI_COMMIT_SHORT_SHA"
DEPLOY_ENV: "$CI_COMMIT_BRANCH == 'main' ? 'prod' : 'dev'" # 不支持三元,用 rules
# Job 级变量(覆盖全局)
deploy-prod:
variables:
K8S_NAMESPACE: "prod"
3. script — 执行脚本
job:
script:
- echo "Hello" # 单行
- | # 多行脚本
if [ "$CI_COMMIT_BRANCH" = "main" ]; then
echo "Deploying to prod"
else
echo "Deploying to dev"
fi
- > # 折叠多行
docker build -t myapp .
&& docker push myapp
before_script:
- echo "Before main script" # 在 script 之前执行
- docker login $REGISTRY -u $REG_USER -p $REG_PASS
after_script:
- echo "After main script" # 在 script 之后执行(即使 script 失败也执行)
4. rules — 条件触发(推荐替代 only/except)
job:
rules:
# PR 时触发
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: on_success
# main 分支推送时触发
- if: $CI_COMMIT_BRANCH == "main"
when: on_success
# Tag 发布时触发
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
when: manual # 手动触发
# 定时触发
- if: $CI_PIPELINE_SOURCE == "schedule"
when: always
# 其他情况不执行
- when: never
5. needs — 跨阶段依赖(可跳过阶段顺序)
# 不用 needs:按 stages 顺序执行
stages:
- build
- test # build 全部完成后才开始
- deploy
# 用 needs:Job 级依赖,可以跨阶段
test-unit:
stage: test
needs: [build] # 只等 build 完成就开始,不等其他 test Job
test-integration:
stage: test
needs: [build, deploy-test] # 需要 build 和 deploy-test 都完成
6. cache — 缓存
build:
cache:
# 按分支缓存
key: ${CI_COMMIT_REF_SLUG}
# 按文件内容缓存(文件不变则用缓存)
key:
files:
- package-lock.json
- pom.xml
paths:
- .m2/repository
- node_modules/
policy: pull-push # 拉取并推送
7. artifacts — 产物传递
build:
artifacts:
paths:
- target/*.jar # 传递给下游 Job
reports:
junit: target/surefire-reports/TEST-*.xml # 测试报告
coverage_report:
coverage_format: jacoco
path: target/site/jacoco/jacoco.xml # 覆盖率报告
expire_in: 1 week # 产物保留时间
exclude:
- target/tmp/** # 排除不需要的文件
四、模板与扩展
隐藏 Job 模板(Anchor 语法)
# 定义模板(以 . 开头的 Job 不会执行)
.build-template: &build-template
image: maven:3.9-eclipse-temurin-17
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- .m2/repository
before_script:
- echo "Building $APP_NAME"
# 使用模板
build-service-a:
<<: *build-template
variables:
APP_NAME: "service-a"
script:
- mvn clean package -DskipTests
build-service-b:
<<: *build-template
variables:
APP_NAME: "service-b"
script:
- mvn clean package -DskipTests
include — 引用外部配置
# 引用同一仓库的文件
include:
- local: '/ci/build-template.yml'
- local: '/ci/deploy-template.yml'
# 引用其他仓库的文件
include:
- project: 'devops/ci-templates'
ref: 'main'
file: '/java-build.yml'
# 引用远程 URL
include:
- remote: 'https://raw.githubusercontent.com/myorg/ci-templates/main/java-build.yml'
extends — 继承扩展
.build:
variables:
BUILD_DIR: "target"
script:
- mvn clean package
build-prod:
extends: .build
variables:
BUILD_DIR: "target"
PROFILES: "prod"
script:
- mvn clean package -P$PROFILES
五、实战:完整 Spring Boot 项目流水线
# .gitlab-ci.yml
stages:
- build
- test
- scan
- package
- deploy-test
- deploy-staging
- deploy-prod
variables:
MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository"
REGISTRY: "registry.mycompany.com"
IMAGE_NAME: "$REGISTRY/myapp"
IMAGE_TAG: "$CI_COMMIT_SHORT_SHA"
# === 构建阶段 ===
build:
stage: build
image: maven:3.9-eclipse-temurin-17
cache:
key:
files: [pom.xml]
paths: [.m2/repository]
policy: pull-push
script:
- mvn clean compile -DskipTests
artifacts:
paths: [target/]
expire_in: 1 hour
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "main"
# === 测试阶段(并行)===
unit-test:
stage: test
image: maven:3.9-eclipse-temurin-17
needs: [build]
cache:
key: { files: [pom.xml] }
paths: [.m2/repository]
policy: pull
script: mvn test
artifacts:
reports:
junit: target/surefire-reports/TEST-*.xml
integration-test:
stage: test
image: maven:3.9-eclipse-temurin-17
needs: [build]
services:
- name: postgres:16-alpine
alias: postgres
variables:
POSTGRES_PASSWORD: test
variables:
SPRING_DATASOURCE_URL: "jdbc:postgresql://postgres:5432/test"
script: mvn verify -Pintegration
rules:
- if: $CI_COMMIT_BRANCH == "main"
# === 安全扫描 ===
sonar-scan:
stage: scan
image: sonarsource/sonar-scanner-cli:latest
needs: [unit-test]
script:
- sonar-scanner
-Dsonar.projectKey=myapp
-Dsonar.sources=src
-Dsonar.host.url=$SONAR_HOST
-Dsonar.login=$SONAR_TOKEN
rules:
- if: $CI_COMMIT_BRANCH == "main"
# === 镜像构建 ===
package:
stage: package
image: docker:24
needs: [unit-test]
services: [docker:24-dind]
script:
- docker build -t $IMAGE_NAME:$IMAGE_TAG .
- echo $REG_PASS | docker login $REGISTRY -u $REG_USER --password-stdin
- docker push $IMAGE_NAME:$IMAGE_TAG
rules:
- if: $CI_COMMIT_BRANCH == "main"
# === 部署测试环境 ===
deploy-test:
stage: deploy-test
image: bitnami/kubectl:latest
needs: [package]
environment:
name: test
script:
- kubectl set image deployment/myapp app=$IMAGE_NAME:$IMAGE_TAG -n test
- kubectl rollout status deployment/myapp -n test --timeout=180s
rules:
- if: $CI_COMMIT_BRANCH == "main"
# === 部署预发环境(手动触发)===
deploy-staging:
stage: deploy-staging
image: bitnami/kubectl:latest
needs: [deploy-test]
environment:
name: staging
when: manual
script:
- kubectl set image deployment/myapp app=$IMAGE_NAME:$IMAGE_TAG -n staging
- kubectl rollout status deployment/myapp -n staging --timeout=180s
rules:
- if: $CI_COMMIT_BRANCH == "main"
# === 部署生产环境(手动触发 + Tag 限制)===
deploy-prod:
stage: deploy-prod
image: bitnami/kubectl:latest
needs: [deploy-staging]
environment:
name: production
when: manual
script:
- kubectl set image deployment/myapp app=$IMAGE_NAME:$IMAGE_TAG -n prod
- kubectl rollout status deployment/myapp -n prod --timeout=300s
rules:
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
六、本篇要点回顾
1. GitLab CI 的核心概念:Pipeline → Stage → Job,Runner 是执行者
2. Runner 推荐 docker executor,用 Docker 部署或 Helm 部署到 K8s
3. .gitlab-ci.yml 用 rules 控制触发条件,needs 控制 Job 依赖
4. cache 复用依赖,artifacts 传递产物,include 复用配置
5. 环境部署用 when: manual 控制人工审批
下一篇预告:《进阶实战:多环境部署与 CI/CD 变量管理》——从基础语法进入工程实践,学习如何管理多环境配置和变量。
更多推荐
所有评论(0)