GitLab的CI/CD使用问题汇总

摘要:本文总结在使用 GitLab 进行 CI/CD 部署中遇到的问题汇总。

基础入门

随着持续集成和持续部署的理念逐渐被更多的开发者所接受,目前类似的服务和技术层出不穷。目前开源领域 Jenkins 肯定是 CI/CD 的大佬,笔者也曾使用过,最近新出的 Blue Ocean 版本更加符合现代化 CI/CD。但是通过笔者的使用,发现问题很多,比如安装 JDK 就有一个 bug,需要装两个才能在构建时显示 JDK 选择。所以有很长一段时间感觉 Jenkins 的使用肯定会遇到很多坑,所以没有进一步的研究。后来更多的是使用云端服务,比如 travis-ci 和 circle ci,travis-ci 免费版只能构建开源程序,而 circle ci 不做限制,只是在 build 时间上做了限制,但是对于少量项目的构建戳戳由于。

两个产品的体验都还不错,travis-ci 目前用来自动化部署 hexo 博客, circle ci 用来部署更复杂的一个使用 Latex 构建的 PDF。两个产品都是基于 YAML 配置文件来实现自动化部署,其实 Jenkins 同样支持文件配置,称为 Jenkinsfile。

最近开始试用 GitLab,这也是一个老牌的 Git 服务器产品,最近开始支持 CI/CD。前段时间 GitHub 被微软收购的时候,着实火了一把。通过最近的试用,发现产品做得确实很好,而且其开源社区版功能非常给力,基本覆盖了主要功能,而收费的企业版都是一些锦上添花的功能,这一点 GitLab 确实是一个良心产品。

通过 GitLab 可以一站式解决中小企业的代码托管及自动化集成部署等功能。

GitLab 同样是使用 YAML 文件来描述自动化部署的流程,文件名为 .gitlab-ci.yml。目前官方文档也很详细,有一个完整的目录(使用功能和时间索引),在此推荐几篇基础的文档,在开始使用 GitLab 之前应该整体了解。

GitLab CI/CD 文档汇总

GitLab CI/CD 基础入门篇,应该大概浏览一下,有一个整体概念。

GitLab CI/CD 配置文件详解

stage 和 job 什么关系

在 GitLab 配置文件中有两个重要的概念就是 stage 和 job,stage 是部署的流程,而 job 是部署的最小运行单元。

具体关系参考该文档:GitLab PipeLine

这里使用的时候需要注意一点就是关于 stage 和 job 的命令:

  1. 不能使用保留字,比如 before_script
  2. 不管是 stage 还是 job 名字理论上都是任意,注意保留字和某些特殊符号可能不允许

常见问题

部署过程如何访问其他私有库

这个问题参考该文档:在GitLab CI/CD 过程中使用 SSH keys

实际上访问的原理还是 SSH Key,一般我们都是将我们需要授权的客户端的 SSH 的 public key 加到帐户的授权列表中,然后这个授权列表的 SSH 请求都自动授权 push 和 fetch 的权限。这里 GitLab 为了避免创建一个只用于构建的用户,而加入了 Deploy key 的功能,也就是在 GitLab 的 Deploy Keys 中加入了某个 SSH 的 public key,那么使用这个 public key 就会获取一定的权限。

目前 GitLab 支持 项目独有的 Deploy Key 和整个服务器的 Deploy Key,后者需要管理员设置,这里介绍一下,如果设置了整个服务器的 Deploy Key,如何在项目目使用。

当我们通过 Admin 管理界面加入 Deploy Key 后,这个 key 实际上并没有任何用途,我们需要将这个 key 授权给某个项目。

我们需要到项目的 Settings-> Repository -> Deploy Keys 的最下面有三个选项卡,选择对应的选项卡,enable 对应的 key,同时如果需要写入权限,还要点击编辑勾上 write access allowed。一旦开启了这个权限,在 Admin 界面就会显示对应开启写权限的项目,因为这个是一个可能的安全隐患,所以需要管理员注意。

enter description here

通过官方文档发现过程较繁琐,还需要加入 know_hosts 等操作。我这里通过使用 gitlab runner 的用户进行一下 SSH 操作,之后的访问就没有问题了。首先仍然需要进入到 gitlab-runner 的用户创建 SSH key pair

1
2
3
4
5
6
7
sudo su - gitlab
ssh-keygen -t rsa -C "[email protected]"
# run ssh agent
eval $(ssh-agent -s)
# add key
ssh-add ~/.ssh/id_rsa
# try to clone a git repo and add know hosts

做完这个操作,在这个 gitlab-runner 用户空间下执行的任务都会有权限访问项目。

如何获取当前 Tag

参考官方文档: GitLab CI/DI 的变量

该文档详细介绍了 GitLab 预设的一些变量,可以方便我们在自动构建和部署时候使用,由于一般我们会在有新的 tag 推送到远程后开始构建,所以 GitLab 预设了变量来获取当前的分支的 tag。

1
CI_COMMIT_TAG

这里说明一点,如果当前推送的是 tag, CI_COMMIT_REF_NAME 这个变量也会获取当前 tag,实际上GitLab 的 tag 也算是一种分支。但是如果推送了其他分支, CI_COMMIT_REF_NAME 将会是分支名,而 CI_COMMIT_TAG 将会为空。

如何传递 artifacts

关于 artifacts 可以参考 GitLab 的配置文件详解文档 artifacts 部分

这里主要注意在不同的 stage 和 job 之间共享 artifact 的原理和路径问题。

stage 的共享,需要首先引入依赖,这时候就自动依赖 stage 的 artifact。路径和配置的路径一致。

job 之间实际上是不共享 artifact 的,因为 job 的理念是独立和并发,如果依赖了 artifact 就应该是属于不同的 stage

但是我们可以在一个 stage 的不同的 jobs 中创建 artifact 最终该 stage 将会分享所有的 artifact,但是在最终的下载页面,将会有独立的 artifact 下载。

比如

1
2
3
4
5
6
7
stage1:
job1:
artifacts:
paths: xxx/yyy
job2:
artifacts:
paths: xxx/zzz

最终在下一个 stage 将会看到路径 xxx/yyy 和 xxx/zzz,但是对于每个 jobs 的 artifact 都会有独立的下载,并且内容不会包含关系。job2 的 artifact 不会包含 xxx/yyy

如何使用 cache 加速重复的构建

增加 cache 选项

1
2
3
4
5
6
7
8
cache:
paths:
# cache maven dependencies
- .m2/repository
# cach npm dependencies
- node_modules/
variables:
MAVEN_OPTS: "-Dmaven.repo.local=.m2"

如何使用 submodule

如果我们在一个项目需要构建的时候,会触发构建其他多个项目,可以考虑创建一个独立的构建项目使用 submodule 来引入其他项目。这一点可以参考另外一篇关于这种构建的思考。

使用 submodule 需要注意一下几点

需要配置更新

1
2
3
4
5
6
7
# get the code from submodules
variables:
GIT_SUBMODULE_STRATEGY: recursive
before_script:
- git config --global http.sslverify false
- git submodule sync --recursive
- git submodule update --init --recursive

加入如下配置,每次构建会自动 pull 对应的 submodule

submodule 可以指定 branch

1
2
3
4
5
# add submodule to track master branch
git submodule add -b master [URL to Git repo];

# update your submodule
git submodule update --remote

从 Git 1.8.2 开始支持该操作,这样在 .gitmodules 文件中会有如下内容

1
2
3
4
[submodule "name"]
path = name
url = ../../name.git
branch = master

submodule 也需要更新

在使用中发现,如果在更改了其他代码然后推送代码后,其submodule 并不会更新,需要我们在提交项目在每个 submodule 中做一次 pull 操作,然后在提交。

而且在部署代码过程中对代码进行了修改,这时候这些修改会被缓存起来,下一次构建还会保留这些更改,所以需要自己在结束构建的时候清理 git,或者在下一次构建的时候保证代码是 remote 最新的。

清理代码

1
2
3
4
# 如果代码只是修改没有提交
git reset HEAD --hard
# should keep the ignord files
git clean -fd

强制使用 remote 最新的代码

1
2
git reset --hard origin/master
git pull origin master

如何约束构建的触发条件

1
2
3
4
5
6
# only build tags and triggers
only:
- ci-test # branch
- tags
- triggers
#when: manual

通过 only 来约束, 如果 when 设置为 manual,那么需要手动触发该 job

如何实现 job 的并发

需要配置 gitlab-run 的配置文件,参考该文档 GitLab Runner 高级配置

只需要将对应的配置文件 /etc/gitlab-runner/config.toml 改为

1
concurrent = 4 # 大于1

如何执行脚本

需要首先授予脚本执行权限

1
2
- chmod +x ./backend-build.sh
- ./backend-build.sh

执行 powershell

1
2
- pwsh -File deploy-server-170-4.ps1
- cat ~/.ssh/id_rsa.pub

如何访问 GtiLab Rest API

参考官方 Restful API 文档

curl 在 script 的使用

在 script 使用 curl 命令需要一定的技巧,推荐使用方法来构造命令,否则需要大量的转义字符,json 字符串也很难读

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
commande() {
curl -X POST \
-H "Content-Type: application/json" \
-H "Private-Token: PERSON_ACCESS_TOKEN" \
-k \
-v \
-d@- \
https://gitlab.dualshield-cloud.com/api/v4/projects/{PROJECT_ID}/repository/tags/${CI_COMMIT_TAG}/release <<EOF
{
"tag_name": "${CI_COMMIT_TAG}",
"description": "${release_note}"
}
EOF
}
# echo $commande
commande

案例:使用 API 为指定 tag 添加 Release Note

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
commande() {
curl -X POST \
-H "Content-Type: application/json" \
-H "Private-Token: PERSON_ACCESS_TOKEN" \
-k \
-v \
-d@- \
https://gitlab.dualshield-cloud.com/api/v4/projects/{PROJECT_ID}/repository/tags/${CI_COMMIT_TAG}/release <<EOF
{
"tag_name": "${CI_COMMIT_TAG}",
"description": "${release_note}"
}
EOF
}
# echo $commande
commande

部署思路