基于docker搭建的gitlab实现CI/CD

在开发过程中,提起CI/CD就会想到使用gitlab来实现。本文使用docker搭建gitlab环境,基于官方文档说明并尝试对常见CI/CD操作做讲解说明。

1.gitlab 所需硬件配置

目前,gitlab对内存要求最小是4G。本文使用服务器配置:阿里云ECS,4核8G

配置低的话,容易把系统搞死,gitlab 比较占资源

2.安装docker

  • 卸载老版本docker

    1
    2
    3
    4
    5
    6
    7
    8
    yum remove docker \
    docker-client \
    docker-client-latest \
    docker-common \
    docker-latest \
    docker-latest-logrotate \
    docker-logrotate \
    docker-engine
  • 安装docker基础包

    1
    yum install -y yum-utils device-mapper-persistent-data lvm2
  • 设置仓库

    1
    yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
  • 安装docker引擎

    1
    2
    3
    4
    5
    6
    #安装最新版本(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

    1
    [root@linux-4core-8g-centos64 ~]$ sudo systemctl start docker

执行 docker ps,看到下面内容

1
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

启动一个 docker 镜像测试验证

1
2
3
4
5
6
7
8
9
10
11
12
[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镜像

1
2
3
4
5
6
7
8
9
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 查看容器启动情况

1
2
3
[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默认密码

1
2
3
4
5
6
7
8
9
10
11
12
# 进到容器中
[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。

1
2
3
4
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容器启动情况

1
2
3
4
[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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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语法格式

首先,理清几个核心概念:

https://docs.gitlab.com/ee/ci/pipelines/

  • Pipelines
  • Stage
  • Jobs

Pipelines 是持续集成、交付和部署的顶级组成部分,其组成包括:

  • Jobs,定义该怎么做。例如,编译或测试代码的作业
  • Stage,定义何时运行工作。例如,在编译代码的阶段后运行测试的阶段

Jobs 由 runner 执行。如果有足够的并发runner,同一阶段的多个Jobs在同一阶段并行执行。

举一个实际例子,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 整个 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 关键词是:

https://docs.gitlab.com/ee/ci/yaml/

  • 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 看到

流水线实践与调试

  1. interruptible

    https://docs.gitlab.com/ee/ci/yaml/#interruptible

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 选项

  1. release
    在使用git打tag后,创建 release

    https://docs.gitlab.com/ee/ci/yaml/#release

1
2
3
4
5
6
7
8
9
10
11
12
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.'
  1. timeout
    如果某个 job 执行非常耗时,在执行时间超过 timeout 设置之后,则 job 执行失败
1
2
3
4
5
6
7
build:
script: build.sh
timeout: 3 hours 30 minutes

test:
script: rspec
timeout: 3h 30m
  1. resource_group

使用 Resource_group 创建一个资源组,以确保 job 在同一项目的不同 pipelines 中相互排斥。

https://docs.gitlab.com/ee/ci/yaml/#resource_group
如果是多个 pipelines并行执行,用于限定 job 只能同时由一个 pipelines 执行,类似于编程语言中的信号量
resource_group 的值只要不是保留关键字就行

1
2
3
deploy-to-production:
script: deploy
resource_group: production

多个 pipelines 同时执行,保证同时只有一个在执行 deploy-to-production,一般在发布所在的 job 中设置此项。

  1. debug 调试
    进入容器进行调试,需要配置 runner 开启 debug。
1
2
3
4
5
6
7
8
# 打开 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 的地址:端口
  1. 部署冻结期

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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应用

项目目录结构:

1
2
3
4
5
.
└── main.go
└── go.mod
└── Dockerfile
└── .gitlab-ci.yml

main.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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容器

1
2
3
[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

1
2
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

1
2
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。

1
2
3
4
5
6
7
8
9
10
[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