在开发过程中,提起CI/CD就会想到使用gitlab来实现。本文使用docker搭建gitlab环境,基于官方文档说明并尝试对常见CI/CD操作做讲解说明。
1.gitlab 所需硬件配置
目前,gitlab对内存要求最小是4G。本文使用服务器配置:阿里云ECS,4核8G
配置低的话,容易把系统搞死,gitlab 比较占资源
2.安装docker
-
卸载老版本docker
yum remove docker \ docker-client \ docker-client-latest \ docker-common \ docker-latest \ docker-latest-logrotate \ docker-logrotate \ docker-engine
-
安装docker基础包
yum install -y yum-utils device-mapper-persistent-data lvm2
-
设置仓库
yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
-
安装docker引擎
#安装最新版本(latest) yum install docker-ce docker-ce-cli containerd.io #查看版本 yum list docker-ce --showduplicates | sort -r # 安装指定版本 sudo yum install docker-ce-<VERSION_STRING> docker-ce-cli-<VERSION_STRING> containerd.io
-
启动docker
[root@linux-4core-8g-centos64 ~]$ sudo systemctl start docker
执行 docker ps
,看到下面内容
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
启动一个 docker 镜像测试验证
[root@linux-4core-8g-centos64 ~]$ docker run hello-world
## 看到下面内容,即为成功
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
2db29710123e: Pull complete
Digest: sha256:13e367d31ae85359f42d637adf6da428f76d75dc9afeb3c21faea0d976f5c651
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
......
此时 docker 安装成功。
3.安装gitlab镜像
sudo docker run --detach \
--hostname gitlab.ihero.ren \
--publish 443:443 --publish 80:80 --publish 222:22 \
--name gitlab \
--restart always \
--volume /srv/gitlab/config:/etc/gitlab \
--volume /srv/gitlab/logs:/var/log/gitlab \
--volume /srv/gitlab/data:/var/opt/gitlab \
gitlab/gitlab-ce:latest
参数说明
-
hostname 域名或ip
gitlab.ihero.ren 是笔者自己域名,容器启动后可通过域名访问。域名需要解析到对应的主机IP上
-
publish 端口映射
-
restart 重启方式
-
gitlab/gitlab-ce:latest 镜像名称
-
volume 目录挂载
使用 docker ps
查看容器启动情况
[root@linux-4core-8g-centos64 ~]$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
61c12e659503 gitlab/gitlab-ce:latest "/assets/wrapper" 3 minutes ago Up 3 minutes (healthy) 0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp, 0.0.0.0:222->22/tcp, :::222->22/tcp gitlab
看到 STATUS (healthy)
意味着已经启动成功,此时在浏览器通过域名访问。

查看 gitlab 初始root默认密码
# 进到容器中
[root@linux-4core-8g-centos64 ~] docker exec -it gitlab /bin/bash
# 容器内查看root初始密码
root@gitlab:/# cat /etc/gitlab/initial_root_password
# WARNING: This value is valid only in the following conditions
# 1. If provided manually (either via `GITLAB_ROOT_PASSWORD` environment variable or via `gitlab_rails['initial_root_password']` setting in `gitlab.rb`, it was provided before database was seeded for the first time (usually, the first reconfigure run).
# 2. Password hasn't been changed manually, either via UI or via command line.
#
# If the password shown here doesn't work, you must reset the admin password following https://docs.gitlab.com/ee/security/reset_user_password.html#reset-your-root-password.
Password: tZ0qQaHdEavR7H+0yuqrFZVdt4IYHN7CUmKsP05uiVc=
此时,使用root默认密码登录,登录之后修改密码,并创建测试用的代码仓库。

4.安装 gitlab runner
runner 作为一个单独的容器支撑 gitlab 中 job 命令的执行,runner可以配置多个,每个 runner 配置自己 tag。
sudo docker run -d --name gitlab-runner -p 8093:8093 --restart always \
-v /srv/gitlab-runner/config:/etc/gitlab-runner \
-v /var/run/docker.sock:/var/run/docker.sock \
gitlab/gitlab-runner:latest
runner 对外暴露的端口 8093
查看runner容器启动情况
[root@linux-4core-8g-centos64 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
851f3a9fd950 gitlab/gitlab-runner:latest "/usr/bin/dumb-init …" 3 seconds ago Up 3 seconds 0.0.0.0:8093->8093/tcp, :::8093->8093/tcp gitlab-runner
61c12e659503 gitlab/gitlab-ce:latest "/assets/wrapper" 3 hours ago Up 3 hours (healthy) 0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp, 0.0.0.0:222->22/tcp, :::222->22/tcp gitlab
可以看到 gitlab-runner 容器监听在 8093端口。
5.注册gitlab runner到gitlab
gitlab -> Setting 获取 runner registration token

runner 注册到 gitlab
docker run --rm -v /srv/gitlab-runner/config:/etc/gitlab-runner gitlab/gitlab-runner register \
--non-interactive \
--executor "docker" \
--docker-image alpine:latest \
--url "http://gitlab.ihero.ren/" \
--registration-token "GR1348941ZCPzw2JGx4ubLcwn25dy" \
--description "register-runner" \
--tag-list "test-cicd,dockercicd" \
--run-untagged="true" \
--locked="false" \
--access-level="not_protected"
#看到下面内容,则注册成功
Runtime platform arch=amd64 os=linux pid=7 revision=76984217 version=15.1.0
Running in system-mode.
Registering runner... succeeded runner=GR1348941ZCPzw2JG
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!
参数说明
--url 是gitlab的地址
--registration-token 是gitlab中分配给注册runner用的token
--tag-list 当前runner的tag,用在 .gitlab-ci.yml 中 tags选项指定使用哪个runner
在 gitlab 中查看 runner 注册是否成功,如下所示多了一个可用的 runner,它的 tag-list 为 test-cicd, dockercicd

6.添加 CI/CD文件.gitlab-ci.yml

选择默认模板

7.概念与实践
官方手册是最好的文档
https://docs.gitlab.com/ee/ci/
.gitlab-ci.yml使用yaml语法格式
首先,理清几个核心概念:
- Pipelines
- Stage
- Jobs
Pipelines 是持续集成、交付和部署的顶级组成部分,其组成包括:
- Jobs,定义该怎么做。例如,编译或测试代码的作业
- Stage,定义何时运行工作。例如,在编译代码的阶段后运行测试的阶段
Jobs 由 runner 执行。如果有足够的并发runner,同一阶段的多个Jobs在同一阶段并行执行。
举一个实际例子,如下所示:
# 整个 yaml 构成一个 `Pipelines`
# stages 包含的 stage 可以没有具体的 job
# stages 的次序决定着 CI/CD 执行过程
stages:
# 先执行所有 test stage 的job
- test
# 再执行所有 build stage 的job
- build
# 最后执行所有 deploy stage 的job
- deploy
format: # 一个job,job最终由 runner 执行
# job 所属的 stage
stage: test
# job 需要执行的动作,可以由多行组成,每一行作为一个shell命令执行
script:
- go fmt $(go list ./... | grep -v /vendor/)
- go vet $(go list ./... | grep -v /vendor/)
- go test -race $(go list ./... | grep -v /vendor/)
关键字
最新 GitLab CI/CD 官方文档中,大概列出了30多个关键词,其中有31个 job 关键词是:
-
before_script
定义每个job执行前运行的命令数组
-
after_script
定义每个job执行完后运行的命令数组,包括失败的作业
-
allow_failure
写在job中,指明作业失败时,是否应继续运行 pipeline
-
artifacts
-
cache
将当前环境目录中的一些文件、文件夹缓存起来,在其他 job 中使用
-
coverage
-
dependencies
-
dast_configuration
-
environment
来定义工作部署的环境变量
-
only
定义什么时候执行 job
-
except
与 only 相反,定义什么时候不执行 job
-
extends
-
image
指明 执行 job 所使用的容器环境
-
inherit
-
interruptible
-
needs
-
pages
-
parallel
-
release
-
resource_group
-
retry
失败后可以重试的次数,只能是:0 (default), 1, or 2
-
rules
-
script
job 就提执行的动作,一组 shell 命令
-
secrets
-
services
-
stage
用在 job 中指定当前 job 所属的 stage,为全局 stages 中一个具体值
-
tags
使用 tags 从所有可用于项目的 runner 列表中选择一个特定的 runner
-
timeout
定义 job 执行的超时时间,如果 job 执行时间超过 timeout,则视为失败
-
trigger
-
variables
定义 job 所需的环境变量
-
when
job 何时执行
此外5个全局关键词分别是:
-
stages
设置全局 执行 stage
如果 .gitlab-ci.yml 文件中没有 stages,默认的stages为:.pre / build / test / deploy / .post -
workflow
-
include
加载其他 .gitlab-ci.yml 文件到当前的 CI/CD配置中
-
default
-
variables
变量的定义方式
变量的定义方式目前主要分为3种:
1.在 .gitlab-ci.yml 文件中自定义
2.gitlab pipeline 中预定义的变量
https://docs.gitlab.com/ee/ci/variables/predefined_variables.html
3.在项目中设置
Setting > CI/CD -> Variables
变量用在哪里
目前,主要用在2个地方:
https://docs.gitlab.com/ee/ci/variables/where_variables_can_be_used.html
1.gitlab 的 .gitlab-ci.yml 文件中
2.GitLab Runner 的 config.toml 文件中
Pipeline triggers
Pipeline 触发方式目前大概分为4种:
1.代码推送触发
https://docs.gitlab.com/ee/ci/yaml/#trigger
通过在 .gitlab-ci.yml 文件中设置 trigger 来实现,运行特定 branch,tag 或 commit 的 Pipeline
2.定时任务触发

3.调用接口触发
4.web hook 触发
3、4的触发方式可以在 Setting -> CI/CD -> Pipeline triggers 看到

流水线实践与调试
- interruptible
stages:
- stage1
- stage2
- stage3
step-1:
stage: stage1
script:
- echo "Can be canceled."
interruptible: true
step-2:
stage: stage2
script:
- echo "Can not be canceled."
step-3:
stage: stage3
script:
- echo "Because step-2 can not be canceled, this step can never be canceled, even though it's set as interruptible."
interruptible: true
新的 pipeline 取消旧的 pipeline,需要同时具备以下两点:
① 旧的 pipeline 刚好在执行设置了 interruptible 的job, 并且它处于 running or pending 状态
② Setting -> CI/CD -> General pipelines -> 勾选 Auto-cancel redundant pipelines 选项
- release
在使用git打tag后,创建 release
stages:
- release
release_job:
stage: release
image: registry.gitlab.com/gitlab-org/release-cli:latest
script:
- echo "Running the release job."
release:
tag_name: $CI_COMMIT_TAG
name: 'Release $CI_COMMIT_TAG'
description: 'Release created using the release-cli.'

- timeout
如果某个 job 执行非常耗时,在执行时间超过 timeout 设置之后,则 job 执行失败
build:
script: build.sh
timeout: 3 hours 30 minutes
test:
script: rspec
timeout: 3h 30m
- resource_group
使用 Resource_group 创建一个资源组,以确保 job 在同一项目的不同 pipelines 中相互排斥。
https://docs.gitlab.com/ee/ci/yaml/#resource_group
如果是多个 pipelines并行执行,用于限定 job 只能同时由一个 pipelines 执行,类似于编程语言中的信号量
resource_group 的值只要不是保留关键字就行
deploy-to-production:
script: deploy
resource_group: production
多个 pipelines 同时执行,保证同时只有一个在执行 deploy-to-production,一般在发布所在的 job 中设置此项。
- debug 调试
进入容器进行调试,需要配置 runner 开启 debug。
# 打开 runner 配置文件
vim /srv/gitlab-runner/config/config.toml
## 在 session_server 中配置下面3项
[session_server]
session_timeout = 1800
listen_address = "[::]:8093" ## 监听的端口
advertise_address = "127.0.0.1:8093" # runner 的地址:端口

- 部署冻结期
举个例子:节假日公司进入发布封锁期,此时不能随意发布代码,在gitlab中通过 Deploy freezes 来实现指定时间段内禁止执行 job。

stages:
- test
- release
test_job:
stage: test
script:
- echo "test stage, $CI_DEPLOY_FREEZE"
release_job:
stage: release
image: registry.gitlab.com/gitlab-org/release-cli:latest
script:
- echo "Running the release job."
release:
tag_name: $CI_COMMIT_TAG
name: 'Release $CI_COMMIT_TAG'
description: 'Release created using the release-cli.'
rules:
- if: $CI_DEPLOY_FREEZE == null
由于当前时间在冻结期内, 所以 $CI_DEPLOY_FREEZE为 true,会导致 release_job 不能执行。
实现冻结期需要同时设置以下两点:
① setting 中设置 Deploy freezes
② .gitlab-ci.yml 文件中 job 设置 rules
使用gitlab与docker-file构建部署go应用
项目目录结构:
.
└── main.go
└── go.mod
└── Dockerfile
└── .gitlab-ci.yml
main.go
package main
import (
"log"
"net/http"
)
func main() {
s := http.NewServeMux()
s.HandleFunc("/hello", func(writer http.ResponseWriter, request *http.Request) {
writer.Write([]byte("hello world"))
})
if err := http.ListenAndServe(":8080", s); err != nil {
log.Fatalln(err)
}
}
Dockerfile
FROM golang:1.14-alpine
# Create a directory for the app
RUN mkdir /app
# Copy all files from the current directory to the app directory
COPY . /app
# Set working directory
WORKDIR /app
# Run command as described:
# go build will build an executable file named server in the current directory
RUN go build -o server .
# Run the server executable
CMD [ "/app/server" ]
.gitlab-ci.yml
stages:
- test
- build
- deploy
- release
test_job:
stage: test
script:
- echo "test stage"
release_job:
stage: release
#没有image会报错 docker: not found
image: docker
script:
- docker build -t goapp . #构建的docker镜像名称为 goapp
- if [ $(docker ps -aq --filter name=mygo) ]; then docker rm -f mygo;fi
- docker run -d -p 8008:8080 --name mygo goapp #基于镜像goapp生成mygo容器
- echo 'deploy docker image success.'
pipeline 执行完后,发现出现一个新的go容器
[root@linux-4core-8g-centos64 config]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a55bda5938b3 goapp "/app/server" 8 minutes ago Up 8 minutes 0.0.0.0:8008->8080/tcp, :::8008->8080/tcp mygo
此时,可以通过浏览器访问对应接口。

常见报错问题:
① docker: not found
docker build -t goapp .
/bin/sh: eval: line 116: docker: not found
此时报错,是因为在 job 中执行时找不到 docker 命令,在 .gitlab-ci.yml 对应的 job 中添加 image: docker
即可。
② dial tcp: lookup docker on 100.100.2.136:53: no such host
error during connect: Post "http://docker:2375/v1.24/build?buildargs=%7B%7D&cachefrom=%5B%5D&cgroupparent=&cpuperiod=0&cpuquota=0&cpusetcpus=&cpusetmems=&cpushares=0&dockerfile=Dockerfile&labels=%7B%7D&memory=0&memswap=0&networkmode=default&rm=1&shmsize=0&t=goapp&target=&ulimits=null&version=1": dial tcp: lookup docker on 100.100.2.136:53: no such host
ERROR: Job failed: exit code 1
此时需要修改 runner 的 config.toml 配置文件,添加 docker volume。
[runners.docker]
tls_verify = false
image = "alpine:latest"
privileged = false
disable_entrypoint_overwrite = false
oom_kill_disable = false
disable_cache = false
# 配置这里
volumes = ["/cache", "/usr/bin/docker:/usr/bin/docker", "/var/run/docker.sock:/var/run/docker.sock"]
shm_size = 0
/usr/bin/docker:/usr/bin/docker 把宿主机的 docker 客户端挂载到容器中,在容器内使用
/var/run/docker.sock:/var/run/docker.sock 把宿主机的 Docker daemon 挂载到容器内
8.拓展阅读
1.https://docs.gitlab.com/ee/ci/ 官方
2.https://docs.gitlab.cn/jh/ci/yaml/index.html
3.https://segmentfault.com/a/1190000010442764
4.https://blog.csdn.net/github_35631540/category_11733415.html
5.https://space.bilibili.com/38841498/channel/collectiondetail?sid=148793
本文链接:https://www.ivansli.com/archives/527/
本文系原创作品,版权所有(禁止转载)