GitLab CI/CD实践指南

前言

CI/CD是现代软件开发的基础设施,GitLab CI/CD作为GitLab内置的持续集成/部署工具,提供了强大而灵活的自动化能力。本文将系统讲解GitLab CI/CD的核心概念、配置方法和最佳实践。


一、CI/CD基础概念

1.1 什么是CI/CD

**持续集成(CI)**:

  • 频繁地将代码集成到主干
  • 自动化构建和测试
  • 快速发现和定位错误

**持续部署(CD)**:

  • 自动将代码部署到生产环境
  • 减少人工干预
  • 快速交付新功能

1.2 GitLab CI/CD架构

1
2
3
4
5
6
7
8
9
代码提交
↓
触发Pipeline
↓
GitLab Runner执行
↓
构建 → 测试 → 部署
↓
反馈结果

二、GitLab Runner安装

2.1 Docker方式安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 安装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
docker exec -it gitlab-runner gitlab-runner register

# 输入信息:
# GitLab URL: https://gitlab.com/
# Registration token: 在项目Settings > CI/CD > Runners中获取
# Description: my-runner
# Tags: docker,linux
# Executor: docker
# Default Docker image: alpine:latest

2.2 Shell Executor配置

1
2
3
4
5
6
7
# 注册Shell Runner
gitlab-runner register \
--non-interactive \
--url "https://gitlab.com/" \
--registration-token "YOUR_TOKEN" \
--executor "shell" \
--description "shell-runner"

三、.gitlab-ci.yml配置

3.1 基础配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# .gitlab-ci.yml

# 定义阶段
stages:
- build
- test
- deploy

# 全局变量
variables:
MAVEN_OPTS: "-Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository"

# 缓存配置
cache:
paths:
- .m2/repository/
- node_modules/

# 构建阶段
build:
stage: build
image: maven:3.8-jdk-11
script:
- mvn clean package
artifacts:
paths:
- target/*.jar
expire_in: 1 week
only:
- master
- develop

# 测试阶段
test:
stage: test
image: maven:3.8-jdk-11
script:
- mvn test
coverage: '/Total.*?(\d+\.?\d*)%/'
only:
- master
- develop

# 部署阶段
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
script:
- scp target/*.jar user@server:/app/
- ssh user@server "systemctl restart myapp"
only:
- master
when: manual # 手动触发

3.2 多环境配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 开发环境
deploy:dev:
stage: deploy
script:
- echo "Deploying to dev environment"
- kubectl config use-context dev
- kubectl apply -f k8s/dev/
environment:
name: development
url: https://dev.example.com
only:
- develop

# 测试环境
deploy:staging:
stage: deploy
script:
- echo "Deploying to staging environment"
- kubectl config use-context staging
- kubectl apply -f k8s/staging/
environment:
name: staging
url: https://staging.example.com
only:
- master
when: manual

# 生产环境
deploy:prod:
stage: deploy
script:
- echo "Deploying to production"
- kubectl config use-context prod
- kubectl apply -f k8s/prod/
environment:
name: production
url: https://example.com
only:
- tags
when: manual

四、常用Job配置

4.1 Node.js项目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
image: node:16-alpine

stages:
- install
- lint
- test
- build
- deploy

cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/

# 安装依赖
install:
stage: install
script:
- npm ci
artifacts:
paths:
- node_modules/
expire_in: 1 hour

# 代码检查
lint:
stage: lint
script:
- npm run lint
dependencies:
- install

# 单元测试
test:unit:
stage: test
script:
- npm run test:unit
coverage: '/All files[^|]*\|[^|]*\s+([\d\.]+)/'
dependencies:
- install

# E2E测试
test:e2e:
stage: test
services:
- postgres:13
variables:
POSTGRES_DB: testdb
POSTGRES_USER: test
POSTGRES_PASSWORD: test
script:
- npm run test:e2e
dependencies:
- install

# 构建
build:
stage: build
script:
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 week
dependencies:
- install
only:
- master
- develop

# 部署
deploy:
stage: deploy
image: alpine:latest
before_script:
- apk add --no-cache rsync openssh-client
script:
- rsync -avz --delete dist/ user@server:/var/www/html/
dependencies:
- build
only:
- master

4.2 Docker镜像构建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
stages:
- build
- push
- deploy

variables:
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: ""
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA

# 构建镜像
build:
stage: build
image: docker:latest
services:
- docker:dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- docker build -t $IMAGE_TAG .
- docker tag $IMAGE_TAG $CI_REGISTRY_IMAGE:latest
- docker push $IMAGE_TAG
- docker push $CI_REGISTRY_IMAGE:latest

# 部署到Kubernetes
deploy:k8s:
stage: deploy
image: bitnami/kubectl:latest
script:
- kubectl config use-context $KUBE_CONTEXT
- kubectl set image deployment/myapp myapp=$IMAGE_TAG
- kubectl rollout status deployment/myapp
only:
- master

4.3 多架构构建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
build:multiarch:
stage: build
image: docker:latest
services:
- docker:dind
before_script:
- docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
- docker buildx create --use
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- |
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA \
--tag $CI_REGISTRY_IMAGE:latest \
--push \
.

五、高级功能

5.1 条件执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 只在特定分支执行
job:master:
script:
- echo "Only on master"
only:
- master

# 只在特定标签执行
job:tags:
script:
- echo "Only on tags"
only:
- tags

# 使用规则(rules)
job:rules:
script:
- echo "Using rules"
rules:
- if: '$CI_COMMIT_BRANCH == "master"'
- if: '$CI_COMMIT_TAG'
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
changes:
- src/**/*
- when: manual
allow_failure: true

# 排除特定分支
job:except:
script:
- echo "Except develop"
except:
- develop

5.2 并行执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 矩阵构建
test:parallel:
stage: test
parallel:
matrix:
- NODE_VERSION: ['14', '16', '18']
OS: ['ubuntu', 'alpine']
image: node:${NODE_VERSION}-${OS}
script:
- npm test

# 并行数量
test:split:
stage: test
parallel: 5
script:
- npm test -- --shard=$CI_NODE_INDEX/$CI_NODE_TOTAL

5.3 依赖管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
build:
stage: build
script:
- npm run build
artifacts:
paths:
- dist/

# 依赖特定job
deploy:
stage: deploy
dependencies:
- build # 只下载build的artifacts
script:
- deploy.sh

# 不需要任何依赖
test:unit:
stage: test
dependencies: [] # 不下载任何artifacts
script:
- npm test

5.4 动态子Pipeline

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 主Pipeline
generate:
stage: generate
script:
- python generate_pipeline.py > pipeline.yml
artifacts:
paths:
- pipeline.yml

# 触发子Pipeline
trigger:
stage: deploy
trigger:
include:
- artifact: pipeline.yml
job: generate

# 或者触发另一个项目
trigger:other:
stage: deploy
trigger:
project: group/other-project
branch: master

六、实战案例

6.1 Spring Boot项目完整流水线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# .gitlab-ci.yml

image: maven:3.8-jdk-11

variables:
MAVEN_OPTS: "-Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository"
SPRING_PROFILES_ACTIVE: "test"
DOCKER_IMAGE: "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"

stages:
- build
- test
- quality
- package
- deploy

cache:
paths:
- .m2/repository/

# 编译
compile:
stage: build
script:
- mvn clean compile
artifacts:
paths:
- target/classes/
expire_in: 1 hour

# 单元测试
test:unit:
stage: test
script:
- mvn test
coverage: '/Total.*?([0-9]{1,3})%/'
artifacts:
reports:
junit: target/surefire-reports/TEST-*.xml
coverage_report:
coverage_format: jacoco
path: target/site/jacoco/jacoco.xml

# 代码质量检查
sonarqube:
stage: quality
image: maven:3.8-jdk-11
script:
- mvn sonar:sonar
-Dsonar.projectKey=$CI_PROJECT_NAME
-Dsonar.host.url=$SONAR_URL
-Dsonar.login=$SONAR_TOKEN
only:
- master
- develop

# 打包
package:
stage: package
script:
- mvn package -DskipTests
artifacts:
paths:
- target/*.jar
expire_in: 1 week
only:
- master
- develop
- tags

# 构建Docker镜像
docker:build:
stage: package
image: docker:latest
services:
- docker:dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- docker build -t $DOCKER_IMAGE .
- docker tag $DOCKER_IMAGE $CI_REGISTRY_IMAGE:latest
- docker push $DOCKER_IMAGE
- docker push $CI_REGISTRY_IMAGE:latest
dependencies:
- package
only:
- master

# 部署到开发环境
deploy:dev:
stage: deploy
image: bitnami/kubectl:latest
script:
- kubectl config use-context dev
- |
kubectl set image deployment/myapp \
myapp=$DOCKER_IMAGE \
--record
- kubectl rollout status deployment/myapp
environment:
name: development
url: https://dev.example.com
only:
- develop

# 部署到生产环境
deploy:prod:
stage: deploy
image: bitnami/kubectl:latest
script:
- kubectl config use-context prod
- kubectl set image deployment/myapp myapp=$DOCKER_IMAGE
- kubectl rollout status deployment/myapp
environment:
name: production
url: https://example.com
only:
- tags
when: manual

6.2 Dockerfile

1
2
3
4
5
6
7
8
9
FROM openjdk:11-jre-slim

WORKDIR /app

COPY target/*.jar app.jar

EXPOSE 8080

ENTRYPOINT ["java", "-jar", "app.jar"]

七、最佳实践

7.1 安全管理

1
2
3
4
5
6
7
8
9
10
# 使用CI/CD变量存储敏感信息
variables:
DATABASE_URL: $DATABASE_URL # 在GitLab设置中配置
API_KEY: $API_KEY

# 文件变量
deploy:
before_script:
- echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
- chmod 600 ~/.ssh/id_rsa

7.2 加速构建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 使用缓存
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- .m2/repository/
- node_modules/
- .gradle/

# 只构建变更的部分
build:frontend:
rules:
- changes:
- frontend/**/*
script:
- cd frontend && npm run build

build:backend:
rules:
- changes:
- backend/**/*
script:
- cd backend && mvn package

7.3 通知集成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Slack通知
notify:slack:
stage: .post
image: curlimages/curl:latest
script:
- |
curl -X POST $SLACK_WEBHOOK_URL \
-H 'Content-Type: application/json' \
-d "{
\"text\": \"Pipeline $CI_PIPELINE_STATUS\",
\"attachments\": [{
\"color\": \"$([[ $CI_JOB_STATUS == 'success' ]] && echo 'good' || echo 'danger')\",
\"fields\": [
{\"title\": \"Project\", \"value\": \"$CI_PROJECT_NAME\", \"short\": true},
{\"title\": \"Branch\", \"value\": \"$CI_COMMIT_REF_NAME\", \"short\": true}
]
}]
}"
when: always

# 邮件通知(需要GitLab配置)
# 在Settings > CI/CD > General pipelines > Email notifications

八、故障排查

8.1 常见问题

1
2
3
4
5
6
7
8
9
# 查看Runner日志
docker logs gitlab-runner

# 查看Job日志
# 在GitLab UI中查看详细日志

# Debug模式
# 在CI/CD变量中设置:
# CI_DEBUG_TRACE = true

8.2 性能监控

1
2
3
4
5
6
7
# 添加性能追踪
before_script:
- export START_TIME=$(date +%s)

after_script:
- export END_TIME=$(date +%s)
- echo "Job duration: $((END_TIME - START_TIME)) seconds"

九、总结

GitLab CI/CD核心要点:

关键概念

  • Pipeline:完整的CI/CD流程
  • Stage:流程中的阶段
  • Job:具体的执行任务
  • Runner:执行任务的代理

最佳实践

  • 合理划分阶段
  • 使用缓存加速构建
  • 保护敏感信息
  • 实现环境隔离
  • 设置通知机制

掌握GitLab CI/CD,让你的部署自动化!


参考资料