11-GitLab CI
GitLab CI 是 GitLab 内置的 CI/CD 平台,和代码托管深度集成。它的优势是 All-in-One(代码 + CI + 制品仓库 + 安全扫描一个平台搞定)和私有化免费(CE 版本开源)。本章讲 .gitlab-ci.yml 结构、stage/job/pipeline 概念、runner、artifact/cache、include/extends 复用。
.gitlab-ci.yml 结构#
GitLab CI 的配置文件放在仓库根目录的 .gitlab-ci.yml,YAML 格式:
# .gitlab-ci.yml
variables:
IMAGE_NAME: $CI_REGISTRY_IMAGE
IMAGE_TAG: $CI_COMMIT_SHORT_SHA
# 全局缓存
cache:
key: "$CI_PROJECT_NAME-go-modules"
paths:
- .go/pkg/mod/
- .go/cache/
stages:
- test
- build
- deploy
# 单元测试
unit-test:
stage: test
image: golang:1.22-alpine
script:
- go test -race -coverprofile=coverage.out ./...
- go tool cover -func=coverage.out | tail -1
coverage: '/total:\s+\(statements\)\s+(\d+\.\d+)%/'
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xml
expire_in: 7 days
# lint
lint:
stage: test
image: golangci/golangci-lint:v1.57
script:
- golangci-lint run --timeout=5m
# 构建镜像(kaniko,不需要特权)
build-image:
stage: build
image:
name: gcr.io/kaniko-project/executor:v1.21.0-debug
entrypoint: [""]
script:
- mkdir -p /kaniko/.docker
- |
cat > /kaniko/.docker/config.json << EOF
{"credHelpers": {"$CI_REGISTRY": "ecr-login"}}
EOF
- /kaniko/executor
--context $CI_PROJECT_DIR
--dockerfile $CI_PROJECT_DIR/Dockerfile
--destination $IMAGE_NAME:$IMAGE_TAG
--cache=true
--cache-repo=$IMAGE_NAME/cache
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
# 部署到 staging
deploy-staging:
stage: deploy
image: alpine/git:latest
script:
- git clone https://gitlab-ci-token:$GITOPS_TOKEN@$GITOPS_REPO /tmp/gitops
- cd /tmp/gitops
- sed -i "s|image: $IMAGE_NAME:.*|image: $IMAGE_NAME:$IMAGE_TAG|g" envs/staging/app/deployment.yaml
- git add .
- git commit -m "ci: update app to $IMAGE_TAG [skip ci]"
- git push
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
environment:
name: staging
url: https://staging.example.com
# 部署到 production(手动)
deploy-production:
stage: deploy
image: alpine/git:latest
script:
- git clone https://gitlab-ci-token:$GITOPS_TOKEN@$GITOPS_REPO /tmp/gitops
- cd /tmp/gitops
- sed -i "s|image: $IMAGE_NAME:.*|image: $IMAGE_NAME:$IMAGE_TAG|g" envs/production/app/deployment.yaml
- git add .
- git commit -m "ci: update app to $IMAGE_TAG [skip ci]"
- git push
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: manual # 手动触发
environment:
name: production
url: https://example.comstage / job / pipeline 概念#
pipeline(一次 push 触发的整个流水线)
├── stage: test
│ ├── job: unit-test
│ ├── job: lint
│ └── job: integration-test ← 同 stage 内 job 并行
├── stage: build ← stage 间串行
│ └── job: build-image
└── stage: deploy
├── job: deploy-staging
└── job: deploy-production(when: manual)- Pipeline:一次 push/MR 触发的完整流水线
- Stage:阶段,stage 之间串行(前一个完成才进下一个)
- Job:阶段内的具体任务,同 stage 内 job 并行
关键控制:
needs:可以让 job 跨 stage 依赖,不必等整个 stage 完成rules:条件控制,决定 job 是否执行when: manual:手动触发allow_failure: true:失败不影响流水线整体状态
Runner 注册与标签#
Runner 是跑 job 的执行器。GitLab Runner 支持多种 executor:shell、docker、kubernetes、parallels、virtualbox。
K8s 动态 Pod Agent 是生产推荐#
Runner 跑在 K8s 上,每个 job 是一个 Pod,用完即销。这种模式解决了静态 Slave 的所有痛点:环境污染(每次干净容器)、资源浪费(用完即删)、扩容慢(K8s 自动调度)、配置漂移(Pod Template 声明式)。
# runner-values.yaml(Helm Chart 配置)
gitlabUrl: https://gitlab.example.com
# GitLab 16+ 已弃用 registration token(17.0 起默认禁用),改用认证 token(glrt- 前缀)
runnerToken: "glrt-xxxxxxxxxxxxxxxx"
runners:
config: |
[[runners]]
[runners.kubernetes]
namespace = "cicd"
image = "alpine:latest"
privileged = false # kaniko 不需要特权
cpu_request = "100m"
memory_request = "128Mi"
cpu_limit = "2"
memory_limit = "2Gi"
service_account = "gitlab-runner"
image_pull_secrets = ["regcred"]关键配置:
privileged = false:kaniko 构建镜像不需要特权,安全合规友好service_account:deploy 阶段访问 K8s API 用image_pull_secrets:拉私有镜像的 Secret,必须在 Runner 创建 Job Pod 的 namespace 里存在(这是常见坑)- RBAC:Runner 的 ServiceAccount 需要在 agent namespace 创建 Pod 的权限;deploy 阶段如果要更新其他 namespace 的 Deployment,需要 ClusterRoleBinding(或按目标 namespace 单独绑 Role)
标签(Tags)#
Job 通过 tags 选择 Runner:
build-image:
tags:
- docker # 只在有 docker 标签的 runner 上跑
script:
- docker build ...
gpu-test:
tags:
- gpu # 只在 GPU runner 上跑
script:
- python train.pyRunner 注册时声明自己的标签,job 通过标签匹配。
artifact / cache 传递#
Artifact:job 间产物传递#
build:
script:
- go build -o app ./cmd/server
artifacts:
paths:
- app
expire_in: 1 hour # 1 小时后自动删除
test:
needs: build
script:
- ./app --test # 拿到 build job 的产物Artifact 在 job 之间通过 GitLab 服务器中转,job 结束后上传、下游 job 下载。
Cache:跨流水线复用#
cache:
key: "$CI_PROJECT_NAME-go-modules"
paths:
- .go/pkg/mod/
- .go/cache/
unit-test:
script:
- go test ./...Cache 是跨流水线的——这次跑完上传缓存,下次流水线下载复用。适合依赖包这种"变化少、体积大"的内容。
区别#
| 维度 | Artifact | Cache |
|---|---|---|
| 用途 | job 间传递产物 | 跨流水线复用依赖 |
| 存储 | GitLab 服务器 | Runner 本地或 S3 |
| 失效 | expire_in 控制 | key 变化时失效 |
| 典型场景 | 构建产物、测试报告 | Go modules、npm node_modules |
include / extends 复用#
GitLab CI 的复用机制比 GitHub Actions 更原生——不需要搞 reusable workflow / composite action,YAML 原生的 include + extends 就够用。
include:引用外部配置#
# .gitlab-ci.yml
include:
- project: 'devops/ci-templates' # 引用其他仓库
ref: main
file: '/go-service.yml'
- template: Auto-DevOps.gitlab-ci.yml # 引用 GitLab 内置模板
- local: '/.gitlab/ci/test.yml' # 引用本仓库其他文件
# 覆盖或扩展
test:
extends: .test-template # 引用本地模板
variables:
GO_VERSION: "1.22"extends:本地模板继承#
.test-template:
image: golang:1.22-alpine
before_script:
- go mod download
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
unit-test:
extends: .test-template
script:
- go test ./...
integration-test:
extends: .test-template
script:
- go test -tags=integration ./...hidden key + anchor(YAML 原生复用)#
.deploy-template: &deploy-template # hidden key(点开头)+ anchor
image: alpine/git:latest
before_script:
- git config --global user.email "ci@example.com"
deploy-staging:
<<: *deploy-template # 合并 anchor
script:
- ./deploy.sh staging
deploy-production:
<<: *deploy-template
script:
- ./deploy.sh productionextends 比 YAML anchor 更易读,推荐用 extends。
环境与变量#
CI/CD Variables#
在项目 Settings → CI/CD → Variables 配置:
- Protected:只在 protected branch/tag 上可用(main、release/* 等)
- Masked:值不在 log 显示
- File type:变量内容写到临时文件,适合存证书、kubeconfig
deploy:
variables:
KUBECONFIG: $KUBECONFIG_FILE # File 类型变量
script:
- kubectl --kubeconfig=$KUBECONFIG apply -f deploy.yamlEnvironment#
deploy-production:
environment:
name: production
url: https://example.com
script:
- ./deploy.sh在 GitLab UI → Deploy → Environments 可以看到所有环境的部署历史、回滚按钮。
rules:精细控制#
# 只在 main 分支 push 时构建
build:
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: on_success
- when: never # 其他情况不执行
# PR 时跑测试但不部署
test:
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
# 路径过滤(Monorepo)
build-service-a:
rules:
- changes:
- services/service-a/**/*
- libs/shared/**/*
# tag 发版
release:
rules:
- if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/'resource_group:并发控制#
同一项目的部署不能并发——避免两个流水线同时改 GitOps 仓库冲突:
deploy-production:
resource_group: production
# 同一时间只有一个 job 持有这个 resource_group
script:
- ./deploy.shresource_group 的本质是互斥锁——同一 group 名的 job 会自动排队。这在 GitOps 模式下特别重要:多个流水线同时 git push 同一个 GitOps 仓库会冲突,加 resource_group 后自动串行。
实战要点#
- Kubernetes executor + kaniko 是生产标配。不需要特权容器、不需要 DinD、安全合规友好。
- cache 和 artifact 要分清。依赖用 cache(跨流水线),产物用 artifact(job 间)。
- Protected variables 保护生产密钥。
PROD_KUBECONFIG标记 protected,只在 protected branch 上可用,feature 分支 PR 拿不到。 resource_group防并发。GitOps 仓库的 push 容易冲突,加 resource_group 自动排队。include做组织级模板。把所有服务共用的 stage 抽到独立仓库,业务仓库include引用,改一处全组织生效。这是 GitLab CI 相比 GitHub Actions 的一个优势——include 是 YAML 原生能力,不需要学 reusable workflow 的语法。rules替代only/except。新版 GitLab 推荐rules,表达力更强,能写changes路径过滤、if条件组合。- CI/CD Variables 优先用 File 类型存证书。kubeconfig、SSL 证书这类多行内容用 File 类型变量,GitLab 自动写到临时文件,脚本里直接用路径引用。
小结#
GitLab CI 的优势是和 GitLab 代码托管深度集成——同一平台、同一权限模型、同一 UI 体验。.gitlab-ci.yml 配置简洁,include/extends 复用机制清晰,适合私有化部署和 All-in-One 场景。
GitLab CI 相比 GitHub Actions 的一个显著优势是 include 跨仓库引用更原生——不需要学 reusable workflow 的语法,YAML 原生的 include: - project: ... 就能引用其他仓库的配置。这让组织级 CI 模板化在 GitLab 里更容易落地。
它的劣势是生态不如 GitHub Actions 大(marketplace Action 少),且只能配合 GitLab 用——不像 GitHub Actions 可以被任何 Git 平台通过 webhook 调用。下一章讲老牌 CI 工具 Jenkins。