gitlab runner 学习

0 阅读41分钟

Git安装

ipCPU内存硬盘用途主机名
192.168.91.412C4G40GBdocker,gitservergitserver
192.168.91.422C2G40GBdocker,runnerrunner
# gitserver、runner
# Docker安装
wget -O /etc/yum.repos.d/docker-ce.repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
yum -y install --setopt=obsoletes=0 docker-ce-24.0.6-1.el7
# 这里使用了habor私服
cat << EOF > /etc/docker/daemon.json
{
  "registry-mirrors": ["http://hd1esep4.mirror.aliyuncs.com"],
  "insecure-registries": ["172.25.10.67"]
}
EOF
systemctl enable --now docker

# gitserver
# 调整swap大小为6G
swapoff -a
cp /dev/dm-1 /centos-myswap1
cp /dev/dm-1 /centos-myswap2
echo '/centos-myswap1 swap                            swap    defaults        0 0' >> /etc/fstab
echo '/centos-myswap2 swap                            swap    defaults        0 0' >> /etc/fstab
swapon -a

free -h | grep Swap
Swap:          6.0G          0B        6.0G


# 创建文件 /etc/yum.repos.d/gitlab.repo
[gitlab]
name=gitlab-ce
baseurl=https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el7
enabled=1
gpgcheck=0

# gitlab-ce安装
yum -y install gitlab-ce-14.1.0-ce.0.el7

# gitlab-ce配置
# 修改 /etc/gitlab/gitlab.rb 32行的配置如下
external_url 'http://192.168.91.41'

# 启动gitlab-ce
gitlab-ctl reconfigure
gitlab-ctl status

# 重置密码
gitlab-rake "gitlab:password:reset[root]"
Enter password:
Confirm password:
Password successfully updated for user with username root.

# runner
# 准备镜像
# public.ecr.aws/gitlab/gitlab-runner:alpine-v14.1.0
docker pull 172.25.10.67/base_repo/gitlab-runner:alpine-v14.1.0
# public.ecr.aws/docker/library/alpine:3.18.9
docker pull 172.25.10.67/base_repo/alpine:3.18.9
# public.ecr.aws/docker/library/node:12.21.0-alpine
docker pull 172.25.10.67/base_repo/node:12.21.0-alpine

https://github.com/ElementUI/element-starter这个项目导入到安装好的gitlab中

Runner

执行器使用

安装runner

# 运行GitLab Runner,-v /srv/gitlab-runner/config:/etc/gitlab-runner 表示将gitlab-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 \
  172.25.10.67/base_repo/gitlab-runner:alpine-v14.1.0

也可以直接安装,安装软件在https://gitlab-runner-downloads.s3.amazonaws.com/latest/index.html这里下载

注册runner

注册runner的过程就是将一个runner与项目绑定起来。这个runner会与GitLab建立联系,并在适当的时候进行通信。你可以在一台计算机上注册多个runner,为多个项目提供服务

# 为项目注册runner,会进入名为gitlab-runner的容器,执行register命令,并携带executor、docker-image、tag-list等诸多参数
docker run --rm -v /srv/gitlab-runner/config:/etc/gitlab-runner  gitlab/gitlab-runner:v14.1.0 \
 register \
  --non-interactive \
  --executor "docker" \
  --docker-image alpine \
  --url "{MY_GITLAB_HOST}" \
  --registration-token "{PROJECT_REGISTRATION_TOKEN}" \
  --description "docker-runner" \
  --tag-list "docker,aws" \
  --run-untagged="true" \
  --locked="false" \
  --access-level="not_protected" \
  --docker-volumes "/opt/gitlab-runner-cache:/cache"
  • --executor "docker":这里是指定执行器为Docker,执行器是流水线真正的执行环境。在GitLab Runner中,有很多执行器可用,除了Docker,还有Shell、SSH、Kubernetes,每种执行器都有其独有的特征。其中,Docker执行器是最方便的,可以零配置进行迁移
  • --url "{MY_GITLAB_HOST}":这个参数用于指定GitLab的域名,表明注册的runner要与GitLab进行关联。每一个注册的runner都可以服务一个独立安装的GitLab
  • --registration-token "PROJECT_REGISTRATION_TOKEN":用于设置注册的Token,每个项目都有一个独有的Token—— 这个可以在项目的设置页面看到,进入某个项目Settings→CI/CD展开runners
  • --tag-list "docker,aws":指定runner的标签,可以填写多个,用逗号分隔,尽量不要与其他runner的标签重复。标签会直接在编写流水线时使用,注册后也可以在GitLab中进行修改
  • --non-interactive:添加该参数表示每个注册的runner都是独立的,不会相互影响。在GitLab Runner的配置文件里,每个注册的runner都可以进行单独配置
  • --docker-image alpine:如果选择Docker作为执行器,就需要指定一个基础镜像
  • --docker-volumes "/opt/gitlab-runner-cache:/cache" 指定挂载目录,/cache目录用于做本地缓存
docker run --rm -v /srv/gitlab-runner/config:/etc/gitlab-runner gitlab/gitlab-runner:v14.1.0 \
 register \
  --non-interactive \
  --executor "docker" \
  --docker-image 172.25.10.67/base_repo/alpine:3.18.9 \
  --url "http://192.168.91.41/" \
  --registration-token "jham7VEccBotmBR1_XCU" \
  --description "docker-runner" \
  --tag-list "myTest" \
  --run-untagged="true" \
  --locked="false" \
  --access-level="not_protected" \
  --docker-volumes "/opt/gitlab-runner-cache:/cache"
...
Registering runner... succeeded                     runner=Lf8HxdXT
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!

运行上述代码,就可以在runners下看到刚刚注册成功的runner。如果框中的颜色是绿色,则表示正常,可以使用。如果显示的是其他颜色,则表示不可使用,需要重新注册或修改注册参数

根据使用范围的不同,runner可以分为3种类型,分别是只用于某一特定项目的特有runner、可以用于一个群组中所有项目的群组runner,以及可用于每个项目的共享runner

执行器介绍

每一种执行器都有其独有的特点,在执行流水线时,也会有一些差异。各种执行器特点如下所示

image.png

各种执行器在执行流水线时的表现也有很多差异,各种执行器在执行流水线时的差异如下所示

image.png

如果在使用runner的过程中需要对其进行一些特殊的配置,比如修改内存的限制、并发数或者挂载本地目录,这时就需要修改runner的配置文件/srv/gitlab-runner/config/config.toml

config.toml完整的文件中有很多配置参数,其中几个重要且常用的参数如下

  • concurrent:限制runner能够同时执行多少个作业
  • log_level:定义日志的格式,可选项debug、info、warn、error、fatal和panic
  • check_interval:检查新任务的间隔,以秒为单位,默认为3

还有一部分是有关[session_server]的。这部分是用于配置调试流水线的,配置一个session地址,你可以在流水线的Web页面,进入一个交互式的控制台。这对于在线调试非常方便。其中,有3个参数需要注意

  • listen_address:session服务的网络地址
  • advertise_address:gitlab-runner对外的服务端口,如果没有定义,直接使用listen_address
  • session_timeout:session的过期时间

如果注册了执行器runner,会产生[[runners]]配置。其中会有一些默认值,也有一些是用户在注册runner时填写的,如token、url、name、executor,表明了当前的runner使用了哪种执行器

volumes表明挂载主机哪些目录到容器中,通过该方法可以将流水线中的一些文件存放到主机上。此外,runner下还有一个参数limit,可以配置当前的runner能同时执行多少个作业

每一种执行器都有其自己独特的配置项,并且所有的配置项都集中在config.toml中。如果你是在类UNIX系统上安装的GitLab Runner,使用root用户安装,该配置文件存放在/etc/gitlab-runner/。非root用户,一般存放在~/.gitlab-runner/路径下,使用该方式安装后会创建一个GitLab Runner的用户gitlab-runner,流水线的运行也是使用该用户,如果需要在流水线中使用Docker的话,需要将用户gitlab-runner添加到docker用户组中

runner的工作流程

image.png

当用户注册一个runner时,是使用registration_token,向GitLab发送一个POST请求,然后GitLab会返回给GitLab Runner注册成功的信息,且runner需携带runner_token作为后续通信的凭证。紧接着会进入一个循环,GitLab Runner会使用runner_token向GitLab轮询发送请求,检查是否有流水线作业要执行,GitLab会返回任务的负载以及job_token,job_token会向下传递,传递到runner的执行器中,然后执行器去下载源码、下载artifacts、执行作业内容,最后再把作业的状态上报给GitLab Runner,GitLab Runner携带job_token通知GitLab更新作业的状态

流水线.gitlab-ci.yml

流水线文件通常是默认放在项目根目录的.gitlab-ci.yml文件,不过,开发者也可以在GitLab中修改这一配置。在项目中打开Settings→CI/CD,展开General pipelines菜单

要重新指定流水线文件的路径和文件名称,有一点需要保证,即该文件必须是以.yml为扩展名的文件,否则流水线将无法执行。除了指定项目中的某一个文件作为流水线文件,开发者还可以指定一个公开的文件作为该项目的流水线文件。如果指定了项目中的文件,因为该文件是存放在项目中的,所以也会被纳入版本的管理,每个版本的流水线内容可能不一样

在GitLab的web页面中,可以使用官方的Pipeline Editor,可以编辑并校验.gitlab-ci.yml。Pipeline Editor的用法非常简单,单击项目左侧的CI/CD→Editor,GitLab会打开项目默认分支的.gitlab-ci.yml文件

在Pipeline Editor中编辑流水线内容,不仅可以实时校验文件内容准确性,还可以实时显示最近提交的流水线的运行情况,用户交互体验非常好

.gitlab-ci.yml文件的内容是项目的流水线,其重要组成部分就是一个个的作业。作业是流水线中最小的单元,每个作业都是一个独立的执行单元。开发者可以将安装npm包写成一个作业,也可以将构建项目写成一个作业,抑或将上传文件到远程服务器写成一个作业。作业的内容都是用关键词定义的。作业的执行顺序一般由阶段来定义。一个阶段是由一组作业组成的,同一个阶段的作业是并行运行的,通常在流水线中作业的运行顺序不是由上而下的,而是按照定义阶段的顺序,也就是说,运行完一个阶段的所有作业再去运行下一阶段的作业。当然,也有例外,例如使用关键词need来实现作业之间的依赖

简单流水线示例

stages:
  - install
  - build
  - deploy
install_job:
  stage: install
  script: echo 'hello install'
build_job:
  stage: build
  script: echo 'hello build'
deploy_job:
  stage: deploy
  script: echo 'hello deploy'

在这个流水线中,我们按照顺序定义了3个阶段,即install、build和deploy。这3个阶段的作业会按照定义的顺序执行,也就是说,优先执行install阶段中的所有作业,然后执行build阶段的作业,最后执行deploy阶段的作业

这里我们没有定义流水线执行的时机,那么默认流水线会在推送代码到GitLab或者创建一个tag时被触发。注意,用来创建tag的分支必须包含.gitlab-ci.yml文件。GitLab Runner检测到有流水线需要执行,会自动拉取特定版本的代码并按照顺序执行每一个作业。install阶段的install_job作业会被优先执行,该作业下的script内容将被执行,在控制台输出hello install;随后执行build阶段的build_job作业;最后执行deploy阶段的deploy_job作业。开发者可以为每一个作业限定执行时机,例如推送代码时执行,或者合并请求时执行。这些功能的实现都离不开关键词

关键词

GitLab官方提供了很多像stages、script这样的关键词。在14.1.0版本中,共有35个关键词(其中variables既是全局关键词,也是作业关键词),包括31个作业关键词(定义在作业上的,只对当前作业起作用,分别是after_script、allow_failure、artifacts、before_script、cache、coverage、dependencies、dast_configuration、environment、except、extends、image、inherit、interruptible、needs、only、pages、parallel、release、resource_group、retry、rules、script、secrets、services、stage、tags、timeout、trigger、variables和when,以及5个全局关键词(定义在流水线全局的,对整个流水线起作用,分别是stages、workflow、include、default和variables)。使用这些关键词,开发者可以很方便地编写流水线。例如,使用when关键词将一个作业从自动执行改为手动执行

build_job:
  script: echo 'hello cicd'
  when: manual

又如,想让某个作业在master分支被执行,可以使用only关键词

deploy_job:
  script: echo 'start deploy'
  only:
    - master

初阶关键词

stages

stages是一个全局的关键词,它的值是一个数组,用于说明当前流水线包含哪些阶段,一般在.gitlab-ci.yml文件的顶部定义。如果没有定义该属性,则使用默认值。stages有5个默认值,.pre、build、test、deploy、.post,注意,.pre与.post不能单独在作业中使用,必须要有其他阶段的作业才能使用

stage

stage关键词是定义在具体作业上的,定义了当前作业的阶段,其配置值必须取自全局关键词stages。注意,全局关键词是stages,定义作业的阶段是stage。如果流水线中没有定义stages的值,那么作业的stage有以下几个默认值可供使用,.pre、build、test、deploy、.post,开发者可以在不定义全局stages的情况下直接定义作业的stage(即使用默认值)

ready_job:
  stage: build  
  script: echo '1'
test_code:
  stage: test  
  script: echo '1'
test_business:
  stage: test  
  script: echo '1'
deploy_job:
  stage: deploy  
  script: echo '1'

作业的stage属性默认值是test。如果一条流水线既没有定义stages,其中的作业也没有指定stage,那么该流水线的所有作业都属于test阶段

script

script关键词用于定义当前作业要执行的脚本。通常情况下,每个作业都需要定义script的内容(除了使用trigger触发的作业)。用script定义的内容会在runner的执行器中执行

npm_inst:
  script: npm install

这里定义了一个npm_inst的作业,script关键词定义了一行内容npm install,这是一个npm命令,用于安装Node.js依赖包。需要说明的是,在每个作业开始时,runner会进行一系列的初始化,这些初始化包括将当前的项目代码下载到执行器的工作目录,并进入项目的根目录,同时清空一些不需要的文件。在不同的执行器上,会有一些差异。在执行npm install时,其实就是在项目的根目录下执行。如果GitLab Runner是直接在宿主机上安装的,而不是使用Docker,那么在执行npm install之前,你需要在宿主机上安装Node.js。但如果开发者的执行器是Docker,就需要在这个作业上指定node镜像,这样script的内容才可以正常执行

npm_inst:
  image: 172.25.10.67/base_repo/node:12.21.0-alpine
  script: 
    - npm install --registry=https://mirrors.cloud.tencent.com/npm/
    - npm build

多行脚本内容使用YAML文件中的数组来表示,使用-开头来表示每一行脚本。如果script中的内容有引号,则需要用单引号将整段内容包裹起来

use_curl_job:
  script:
    - 'curl --request POST --header "Content-Type: application/json" "https://gitlab.com/api/v4/projects"'

这里的脚本不仅仅是指Shell脚本,只要稍加配置,Windows命令提示符窗口和Windows PowerShell中的命令也是可以执行的,Windows上安装的.exe软件也可以被调用。这些特性使项目在跨平台编译时变得更简单。值得一提的是,开发者还可以设置脚本执行时的颜色

cache

cache关键词可以管理流水线中的缓存、上传和下载。这个关键词可以定义在关键词default中(对整个流水线起作用),也可以单独配置在具体的作业中。这样配置,就可以将不同作业中共用的文件或者文件夹缓存起来—— 在后续执行阶段中都会被恢复到工作目录,从而避免在多个作业之间重复下载造成资源浪费。注意要缓存的文件,路径必须是当前工作目录的相对路径

为什么会用到缓存呢?这是因为流水线中的每个作业都是独立运行的,如果没有缓存,运行上一个作业时安装的项目依赖包,运行下一个作业还需要安装一次。如果将上一个作业安装的依赖包缓存起来,在下一个作业运行时将其恢复到工作目录中,就可以大大减少资源的浪费。缓存用得最多的场景就是缓存项目的依赖包。每一种编程语言都有自己的包管理器,例如,Node.js应用使用NPM来管理依赖包,Java应用使用Maven来管理依赖包,Python应用使用pip来管理依赖包。这些依赖包安装完成后,可能不只为一个作业所使用,项目的构建作业需要使用它们,测试作业也需要使用它们。由于多个作业的执行环境可能不一致,而且在某些执行器中作业被执行完成后会自动清空所有依赖包,在这些情况下,就需要将这些依赖包缓存起来,以便在多个作业之间传递使用

关键词cache的配置项有很多,最重要的是paths这个属性,用于指定要缓存的文件路径

npm_init:
  script: npm install
  cache:
    paths:
      - node_modules
      - binaries/*.apk
      - .config

可以看到,npm_init作业执行npm install,安装了项目所需要的依赖包。在这个作业结束后,执行器会将工作目录中的node_modules、binaries目录下所有以.apk为扩展名的文件以及当前目录下的.config文件压缩成一个压缩包,缓存起来

如果项目有多个分支,想要设置多个缓存,这时可以使用全局配置cache的key来设置(全局缓存对整个流水线有用)

default:
  cache:
    key: "$CI_COMMIT_REF_SLUG"
    paths:
      - node_modules/

key的值可以使用字符串,也可以使用变量,其默认值是default。上面,key的值就是CI中的变量、当前的分支或tag。在执行流水线的过程中,对于使用相同key缓存的作业,执行器会先尝试恢复之前的缓存

在一个作业中最多可以定义4个key,例如,下面配置了2个key

test-job:
  stage: build
  cache:
    - key:
        files:
          - Gemfile.lock
      paths:
        - vendor/ruby
    - key:
        files:
          - yarn.lock
      paths:
        - .yarn-cache/
  script:
    - bundle install --path=vendor
    - yarn install --cache-folder .yarn-cache
    - echo 'install done'

开发者还可以将cache的key指向一个文件列表,这样做之后,只要这些文件内容没有变动,key就不会变,在执行时就会使用之前的缓存

如果runner的执行器是Shell,缓存的文件默认是存放在本地,即/home/gitlab-runner/cache/<user>/<project>/<cache-key>/cache.zip;如果执行器是Docker,本地缓存会被存放在/var/lib/docker/volumes/<volume-id>/_data/<user>/<project>/<cache-key>/cache.zip目录中,对应到容器内部是/cache目录,这里已经将/cache目录映射到宿主机/opt/gitlab-runner-cache目录。使用Docker或使用rpm的方式安装的GitLab Runner默认使用本地缓存。开发者还可以将缓存放在一些分布式的存储平台,如AWS S3、MinIO—— 这需要做一些配置。作业能否使用以前的缓存完全取决于两次缓存的key是否一致,如果在一个项目中两条流水线命中了同一个key的缓存,那么不管这个缓存是否是在当前流水线中创建的,都可以被使用。缓存通常不能跨项目,但可以跨流水线。缓存默认的key是default,要获得更好的体验,我们强烈建议开发者在使用缓存时定义key,当然也可以对不同的分支使用不同的key

image

image关键词用于指定一个Docker镜像作为基础镜像来执行当前的作业,比如开发者要使用Node.js来构建前端项目

use_image_job:
  image: 172.25.10.67/base_repo/node:12.21.0-alpine
  script: npm - v

如果runner的执行器是Docker,这样指定image是没什么问题的。但如果注册的runner执行器是Shell,那么image是没有任何作用的。Shell执行器需要在宿主机上安装Node.js才能运行NPM的指令。这是Docker执行器与Shell执行器的一大区别

如果当前的作业既需要Node.js的镜像,又需要Golang的镜像,那么可以采用的处理方法有两种:一种是将其拆分成两个作业,一个作业使用Node.js镜像执行对应的脚本,另一个作业使用Golang镜像处理对应的内容;另一种是构建一个新的镜像,将Golang镜像和Node.js的镜像包括在其中,使之包含所需要的Golang环境和Node.js环境。开发者甚至可以将流水线中所有用到的镜像构建到一个镜像中,虽然镜像会比较大,但是很方便。此外,image关键词也支持使用私有仓库的镜像

tags

tags关键词用于指定使用哪个runner来执行当前作业。开发者可以为一条流水线指定一个runner,也可以针对某一个作业指定一个runner。在为项目注册runner时,开发者需要填写runner的tags—— 这是一个用逗号分隔的字符串数组,用于表明一个runner可以有多个标签。项目所有可用的runner包含在项目的runner菜单中,每个runner至少有一个标签

可以指定tags,让指定的runner运行

tags_example:
  tags:
    - myTest
  script: echo 'hello fizz'

在上述示例中,指定作业的tags为myTest,这样作业就能找到对应的runner来执行了。如果指定的tags能找到多个runner,那么流水线中的作业会在多个runner之间进行调度。一般来讲,除非必要,建议使用同一个runner执行整条流水线,这样可以保持一致性和可靠性。如果编写的作业没有指定tags,那么在执行时,系统会去寻找那些可用的、公共的runner去执行(如果项目开启了允许使用共享runner执行)。有些项目的流水线也可以在不指定tags的情况下执行

variables

在开发流水线的过程中,开发者可以使用variables关键词来定义一些变量。这些变量默认会被当作环境变量,变量的引入让流水线的编写更具灵活性、更具扩展性,可满足各种复杂业务场景的需要。GitLab CI/CD中的变量的定义与使用方式也是非常丰富的

在.gitlab-ci.yml文件中定义变量

variables:
  USER_NAME: "fizz"
print_var:
  script: echo $USER_NAME

变量名的推荐写法是使用大写字母,使用下画线_来分隔多个单词。在使用时,可以直接使用变量名,也可以使用变量名,或使用变量名,或使用{变量名}。为了与正常字符串区分开来,我们推荐使用后一种方式。如果将变量定义在全局范围,则该变量对于任何一个作业都可用;如果将变量定义在某个作业,那么该变量只能在当前作业可用;如果局部变量与全局变量同名,则局部变量会覆盖全局变量

variables:
  USER_NAME: 'fizz'
test:
  variables:
    USER_NAME: 'ZK'
  script: echo 'hello' $USER_NAME

在作业test中,全局变量USER_NAME的值fizz会被局部变量USER_NAME的值ZK所覆盖,因此最终输出的结果是hello ZK

注意:如果在某一个作业的script中修改了一个全局变量的值,那么新值只在当前的脚本中有效。对于其他作业,全局变量依然是当初定义的值

在CI/CD设置中定义变量

除了在.gitlab-ci.yml中显式地定义变量,开发者还可以在项目的CI/CD中设置一些自定义变量。在这里,开发者可以定义一些比较私密的变量,例如登录DockerHub的账号、密码,或者登录服务器的账号、密码或私钥

将隐秘的信息变量定义在这里,然后勾选Mask variable复选框,这样在流水线的日志中,该变量将不会被显式地输出(但对变量值有一定格式要求)。这可以使流水线更安全,不会直接在代码中暴露隐秘信息。开发者还可以将一些变量设置为只能在保护分支使用

如果有些变量需要在一个群组的项目中使用,可以设置群组CI/CD变量

注意,开发者也可以在群组的范围下注册runner。注册的runner对于在群组中的每一个项目都可使用

除了预设一些自定义变量,开发者还可以在手动执行流水线时,定义流水线需要的变量,这样做有可能会覆盖定义的其他变量

如果想查看当前流水线所有的变量,可以在script中执行export指令

预设变量

除了用户自定义变量,在GitLab CI/CD中也有很多预设变量,用于描述当前操作人、当前分支、项目名称、当前触发流水线的方式等。使用这些预设变量可以大幅度降低开发流水线的难度,将业务场景分割得更加精确

一些常见的预设变量如下所示

  • CI_COMMIT_BRANCH :提交分支的名称
  • GITLAB_USER_NAME:触发当前作业的GitLab用户名
  • CI_COMMIT_REF_NAME:正在构建项目的分支或tag名
  • CI_COMMIT_SHA:提交的修订号
  • CI_COMMIT_SHORT_SHA:提交的8位数修订号
  • CI_COMMIT_TAG:提交的tag名称,只在tag流水线中可见
  • CI_JOB_NAME:作业的名称
  • CI_PROJECT_NAME:项目的名称

when

when关键词提供了一种监听作业状态的功能,只能定义在具体作业上。如果作业失败或者成功,则可以去执行一些额外的逻辑。例如当流水线失败时,发送邮件通知运维人员

when的选项如下所示

  • on_success:此为默认值,如果一个作业使用when: on_success,那么在此之前的阶段的其他作业都成功执行后,才会触发当前的作业

  • on_failure:如果一个作业使用when:on_failure,当在此之前的阶段中有作业失败或者流水线被标记为失败后,才会触发该作业

  • always:不管之前的作业的状态如何,都会执行该作业

  • manual:当用when:manual修饰一个作业时,该作业只能被手动执行

  • delayed:当某个作业设置了when:delayed时,当前作业将被延迟执行,而延迟多久可以用start_in来定义,如定义为5 seconds、30 minutes、1 day、1 week等

  • never:流水线不被执行或者使用rule关键词限定的不被执行的作业

如果开发者想要监听当前流水线的失败状态,并在流水线失败时执行作业

fail_job:
  stage: deploy
  script: echo 'Everything is going to be alright,Maybe not today but eventually'
  when: on_failure

注意:该作业必须放到最后一个阶段来执行,只有这样,才能监听到之前所有阶段的作业失败状态。如果之前的作业没有失败,该作业将不会被执行;如果之前的作业有一个失败,该作业就会被执行

artifacts

在执行流水线的过程,开发者可能需要将一些构建出的文件保存起来,比如一些JAR包、测试报告,这时就可以使用artifacts关键词来实现。开发者可以使用artifacts关键词配置多个目录或文件列表,作业一旦完成,这些文件或文件夹会被上传到GitLab—— 这些文件会在下一个阶段的作业中被自动恢复到工作目录,以便复用。通过这种方式,开发者可以很好地持久化测试报告和其他文件,也可以在GitLab上自由查看和下载这些文件

通过artifacts的配置项,开发者可以很容易地设置其大小和有效期,也可以使用通配符来选择特定格式的文件

artifacts_test_job:
  script: npm run build
  artifacts:
    paths:
      - /dist

上面的作业,会在执行完npm run build后,将/dist目录作为artifacts上传到GitLab上

下面给出一个完整示例,包括更复杂的配置项

build_job:
  stage: build  
  image: 172.25.10.67/base_repo/node:12.21.0-alpine
  script: 
    - npm install --registry=https://mirrors.cloud.tencent.com/npm/
  cache:
    key:
      files:
        - package.json
      prefix: ui
    paths:
      - node_modules

test_job:
  stage: test  
  script: ls -l node_modules
  cache:
    key:
      files:
        - package.json
      prefix: ui
    # 拉取缓存时paths也必须要配置
    paths:
      - node_modules
    policy: pull # 表示只拉取缓存

deploy_job:
  stage: deploy
  image: 172.25.10.67/base_repo/node:12.21.0-alpine
  script: npm run build --registry=https://mirrors.cloud.tencent.com/npm/
  cache:
    key:
      files:
        - package.json
      prefix: ui
    paths:
      - node_modules
    policy: pull
  artifacts:
    paths:
      - dist
      - "*.js"
    exclude:
      - binaries/**/*.o
    expire_in: 1 week
    name: "$CI_JOB_NAME"

在上述示例中,我们定义了一个deploy_job作业,在作业完成后,它会将/dist和当前目录下所有以.js为扩展名的文件存储起来,并将binaries目录下的所有以.o为扩展名的文件排除掉。文件的有效期是1周,artifacts名称使用当前的作业名称来命名

我们只展示artifacts几个常用配置项的用法,如下所示

  • exclude:用于排除一些文件或文件夹
  • expire_in:artifacts的有效期,写法如下,42 seconds,3 mins 4 sec,2 hrs 20 min,2h20min,6 mos 1 day,47 yrs 6 mos and 4d,3 weeks and 2 days,never
  • expose_as:使用一个字符串来定义artifacts在GitLab UI上显示的名称
  • name:定义artifacts的名称
  • paths:定义artifacts存储的文件或文件夹,以便将其传入一个文件列表或文件夹列表
  • public:表明artifacts是否是公开的

有些开发者不知道何时该用cache、何时该用artifacts。需要明确的是,cache大多数用于项目的依赖包;artifacts常用于作业输出的一些文件、文件夹,比如构建出的dist目录、JAR包、测试报告。此外,cache可以被手动被清空,而artifacts是会过期的

before_script

before_script关键词与script关键词类似,都用于定义作业需要执行的脚本、命令行。不同之处在于before_script必须是一个数组。更重要的是,before_script的内容执行的时机是执行script内容之前、artifacts被恢复之后。开发者也可以在default关键字中定义全局的before_script,定义后其将在每个作业中执行

after_script

after_script关键词用于定义一组在作业执行结束后执行的脚本。与before_script的不同之处在于它的执行时机以及执行环境—— after_script是在单独的Shell环境中执行的,对于在before_script或者script中定义或修改的变量,它是无权访问的。after_script还有一些其他特殊之处:如果当前作业失败,它也会被执行;如果作业被取消或者超时,它将不会被执行

only与except

only与except这两个关键词用于控制当前作业是否被执行,或当前作业的执行时机。only是只有当条件满足时才会执行该作业;except是排除定义的条件,在其他情况下该作业都会被执行。如果一个作业没有被only、except或者rules修饰,那么该作业的将默认被only修饰,值为tags或branchs。最常用的语法就是,控制某个作业只有在修改某个分支时才被执行。如清单4-17所示,只有修改了test分支的代码,该作业才会被执行

only_example:
  script: deploy test
  only:
    - test

only与except下可以配置4种值:refs,variables,changes,kubernetes

only:refs/except:refs

如果only/except关键词下配置的是refs,表明作业只有在某个分支或某个流水线类型下才会被添加到流水线中或被排除。清单4-18给出了only:refs的使用示例

test:
  script: deploy test
  only:
    - test
build:
  script: deploy test
  only:
    refs:
      - test
deploy:
  script: deploy test
  only:
    refs:
      - tags
      - schedules

在上述示例中,虽然test作业与build作业下only的定义方式不一样,但是作用都是一样的,即只有修改了test分支的代码后,作业才会被执行。deploy作业下的only是使用refs来定义的—— 使用tags与schedules。这意味着只有项目创建了tags或者当前是定时部署该作业才会被执行。像tags与schedules这样的refs修饰词还有很多,如下所示

  • api:使用pipeline API触发的流水线
  • branches:当分支的代码被改变时触发的流水线
  • chat:使用GitLab ChatOps命令触发的流水线
  • merge_requests:流水线由创建或更新merge_request触发
  • web:使用GitLab Web上的Run pipeline触发的流水线

此外,refs的值也可以配置成正则表达式,如/^issue-.*$/

only:variables/except:variables

only:variables与except:variables可以根据CI/CD中的变量来动态地将作业添加到流水线中

test:
  script: deploy test
  only:
    variables:
      - $USER_NAME === "fizz"

在上述示例中,只有定义的变量USER_NAME等于fizz时,该作业才会被执行。开发者可以配置多个only:variables的条件判断,只要有一个条件符合,作业就会被执行

only:changes/except:changes

使用changes来修饰关键词only适用于某些文件改变后触发作业的情景。例如,只有项目中Dockerfile文件改变后,才执行构建Docker镜像的作业;又如,一个项目中有多个应用,针对某个文件夹的变动,执行某一个应用的作业。这些针对文件改变执行或不执行的作业都可以使用only:changes或except:changes来定义

test:
  script: deploy test
  only:
    changes:
      - Dockerfile
      - fe/**/*

在上述示例中,我们定义了一个test作业,该作业只有在修改了Dockerfile或者fe目录下的文件才会被执行(注:在tag流水线或定时流水线中,该作业也会被执行)

only:kubernetes/except:kubernetes

only:kubernetes与except:kubernetes用于判断项目是否接入了Kubernetes服务,进而来控制作业是否被执行

deploy:
  script: deploy test
  only:
    kubernetes: active

在上面的示例中,只有项目中存在可用的Kubernetes服务时,作业才会被执行

中阶关键词

coverage

在GitLab CI/CD中,开发者可以使用关键词coverage配置一个正则表达式来提取作业日志中输出的代码覆盖率,提取后可以将之展示到代码分支上

test:
  script: npm test
  coverage: '/Code coverage: \d+\.\d+/'

在上述示例中,我们在作业test中将coverage配置为 '/Code coverage: \d+.\d+/'。注意,coverage的值必须以/开头和结尾。如果该作业输出了Code coverage: 67.89这种格式的日志,会被GitLab CI/CD记录起来。如果有多个日志符合规则,取最后一个记录

dependencies

dependencies关键词可以定义当前作业下载哪些前置作业的artifacts,或者不下载之前的artifacts。dependencies的值只能取自上一阶段的作业名称,可以是一个数组,如果是空数组,则表明不下载任何artifacts。在GitLab CI/CD中,所有artifacts在下一阶段都是被默认下载的,如果artifacts非常大或者一条流水线有很多artifacts,则默认下载全部artifacts就会很低效。正确的做法是使用dependencies来控制,仅下载必要的artifacts

stages:
  - build  
  - deploy
build_windows:
  stage: build   
  script: 
    - echo "start build on windows"
  artifacts:
    paths:
      - binaries/
      - "*.js"
build_mac:
  stage: build
  script: 
    - echo "start build on mac"
  artifacts:
    paths:
      - binaries/
      - "*.js"
deploy_mac:
  stage: deploy
  script: echo 'deploy mac'
  dependencies:
    - build_mac
deploy_windows:
  stage: deploy
  script: echo 'deploy windows'
  dependencies:
    - build_windows
release_job:
  stage: deploy
  script: echo 'release version'
  dependencies: []

在上述示例中,deploy_mac作业只会下载build_mac作业的artifacts,deploy_windows作业只会下载build_windows作业的artifacts,而release_job作业不会下载任何artifacts

allow_failure

allow_failure关键词用于设置当前作业失败时流水线是否继续运行,也就是说,是否允许当前作业失败。在一般场景下,allow_failure的默认值为false,即不允许作业错误,作业错误流水线就会停止往下运行。如果一个作业配置了allow_failure为true,并且在运行时出现了错误,那么在该作业的名称后会有一个黄色的感叹号,并且流水线会继续往下运行。一般将allow_failure设置为true的作业都是非硬性要求的作业。比如在一个临时分支做的代码检查作业,允许代码检查作业失败

test1:
  stage:test
  script: echo 'start test1'
test2:
  stage:test
  script: echo 'Life is sometimes not to risk more dangerous than adventure'
  allow_failure: true
deploy:
  stage: deploy
  script: echo 'start deploy'

在上述示例中,test1与test2同属test阶段,会同时执行,并且test2中配置了allow_failure:true。如果test1执行失败,流水线就会停止运行,下一阶段中的deploy作业将不会执行。如果只是test2执行失败,那么流水线会继续运行,作业deploy将会执行

extends

extends关键词可用于继承一些配置模板。利用这个关键词,开发者可以重复使用一些作业配置。extends关键词的值可以是流水线中的一个作业名称,也可以是一组作业名称

.test:
  script: npm lint
  stage: test
  only:
    refs:
      - branches
test_job:
  extends: .test 
  script: npm test
  only:
    variables:
      - $USER_NAME

在上述的示例中,有两个作业,一个是.test,另一个是test_job。可以看到,在test_job中配置了extends: .test

在GitLab CI/CD中,如果一个作业的名称以"."开头,则说明该作业是一个隐藏作业,任何时候都不会执行。这也是注释作业的一种方法,上文说的配置模板就是指这类被注释的作业。test_job继承了作业.test的配置项,两个作业的配置项会进行一次合并。test_job中没有而.test作业中有的,会被追加到test_job中。test_job中已经有的不会被覆盖

最后,test_job的作业内容如下所示

test_job:
  stage: test
  script: npm test
  only:
     refs:
       - branches
     variables:
       - $USER_NAME

开发者可以将流水线中一组作业的公共部分提取出来,写到一个配置模板中,然后使用extends来继承。这样做可以大大降低代码的冗余,提升可读性,并方便后续统一修改

default

default是一个全局关键词,定义在.gitlab-ci.yml文件中,但不能定义在具体的作业中。default下面设置的所有值都将自动合并到流水线所有的作业中,这意味着使用default可以设置全局的属性。能够使用default设置的属性有after_script、artifacts、before_script、cache、image、interruptible、retry、services、tags和timeout

default:
  image: nginx
  before_script:
    - echo 'job start'
  after_script:
    - echo 'job end'
  retry: 1
build:
  script: npm run
test:
  image: node
  before_script:
    - echo 'let us run job'
  script: npm lint

可以看到,在default下定义了image、before_script、after_script和retry这4个属性。这些属性会被合并到所有作业里。如果一个作业没有定义image、before_script、after_script或retry,则使用default下定义的;如果定义了,则使用作业中定义的。default下定义的属性只有在作业没有定义时才会生效。根据default的合并规则,作业build和作业test合并后的代码如下所示

default:
  image: nginx
  before_script:
    - echo 'job start'
  after_script:
    - echo 'job end'
  retry: 1
build:
  image: nginx
  before_script:
    - echo 'job start'
  after_script:
    - echo 'job end'
  retry: 1
  script: npm run
test:
  after_script:
    - echo 'job end'
  retry: 1
  image: node
  before_script:
    - echo 'let us run job'
  script: npm lint

如果开发者想要实现在某些作业上不使用default定义的属性,但又不想设置一个新的值来覆盖,这时可以使用关键词inherit来实现

inherit

inherit关键词可以限制作业是否使用default或者variables定义的配置。inherit下有两个配置,即default与variables。我们先来看一下如何使用inherit:default。inherit下有两个配置,即default与variables

default:
  retry: 2
  image: nginx
  before_script:
    - echo 'start run'
  after_script:
    - echo 'end run'
test:
  script: echo 'hello'
  inherit:
    default: false
deploy:
  script: echo 'I want you to be happy,but I want to be the reason'
  inherit:
    default: 
      - retry
      - image

在上述的例子中,我们定义了一个default,并设置了4个全局的配置,即retry、image、before_script和after_script。在test作业中,我们设置inherit为default:false,这表明该作业不会合并default的属性,也就意味着default的4个属性都不会设置到test作业中。在另一个作业deploy中,我们设置inherit的default的retry和image,这样设置后,作业deploy将会合并default的retry和image属性。也就是说,inherit: default下可以设置true或false,也可以设置一个数组,数组中的值取自default的属性

让我们再来看一下inherit:variables的用法。inherit:variables下可以设置true或者false,也可以设置一个数组,数组的值取自全局定义的variables

variables:
  NAME: "This is variable 1"
  AGE: "This is variable 2"
  SEX: "This is variable 3"
test:
  script: echo "该作业不会继承全局变量"
  inherit:
    variables: false
deploy:
  script: echo "该作业将继承全局变量 NAME和AGE"
  inherit:
    variables:
      - NAME
      - AGE

在上述的例子中,我们定义了3个全局变量,并在test作业中设置inherit为variables:false,这样设置后,全局变量不会被引入test作业中;在deploy作业中,将inherit设置为variables:-NAME-AGE,这样设置后,全局变量NAME和AGE将被引入deploy作业中

interruptible

interruptible关键词用于配置旧的流水线能否被新的流水线取消,主要应用于“同一分支有新的流水线已经开始运行时,旧的流水线将被取消”的场景。该关键词既可以定义在具体作业中,也可以定义在全局关键词default中。interruptible关键词的默认值为false,即旧的流水线不会被取消。

要取消旧的流水线,还需要在GitLab上进行项目配置,即单击项目设置下的CI/CD子菜单,勾选Auto-cancel redundant pipelines选项

stages:
  - install
  - build
  - deploy
install_job:
  stage: install
  script:
    - echo "Can be canceled."
  interruptible: true
build_job:
  stage: build
  script:
    - echo "Can not be canceled."
deploy_job:
  stage: deploy
  script:
    - echo "Because build_job can not be canceled, this step can never be canceled, even   
though it's set as interruptible."
  interruptible: true

在上述例子中,作业install_job设置了interruptible:true。作业build_job没有设置interruptible。作业deploy_job设置了interruptible:true。当作业install_job正在运行或者准备阶段,如果此时在同一分支有新的流水线被触发,那么旧的流水线会被取消。但如果旧的流水线已经运行到了build_job,此时再有新的流水线被触发,则旧的流水线不会被取消。只要运行了一个不能被取消的作业,则该流水线就不会被取消,这就是取消的规则。所以,如果开发者想要达到无论旧的流水线运行到了哪个作业,只要有新流水线被触发,旧的流水线就要被取消这一目的,可以在default关键词下设置interruptible为true

needs

needs关键词用于设置作业之间的依赖关系。跳出依据阶段的运行顺序,为作业之间设置依赖关系,可以提高作业的运行效率。通常,流水线中的作业都是按照阶段的顺序来运行的,前一个阶段的所有作业顺利运行完毕,下一阶段的作业才会运行。但如果一个作业使用needs设置依赖作业后,只要所依赖的作业运行完成,它就会运行。这样就会大大提高运行效率,减少总的运行时间

stages:
  - install
  - build
  - deploy
install_java:
  stage: install
  script: echo 'start install'
install_vue:
  stage: install
  script: echo 'start install'
build_java:
  stage: build
  needs: ["install_java"]
  script: echo 'start build java'
build_vue:
  stage: build
  needs: ["install_vue"]
  script: echo 'start build vue'
build_html:
  stage: build
  needs: []
  script: echo 'start build html'
job_deploy:
  stage: deploy
  script: echo 'start deploy'

在上面的例子中,我们定义了3个阶段,即install、build和deploy。按照常规的运行顺序,install阶段的作业会优先运行;等到install阶段所有的作业都完成后,build阶段的作业才会运行;最后deploy阶段的作业得以运行。但由于该项目是一个前、后端不分离的项目,即包含了Java后端应用和Vue前端应用—— 这两个应用的安装依赖和构建是相互独立的,因此我们在build_java和build_vue两个作业中设置了各自的依赖作业,即build_java作业依赖install_java作业,build_vue作业依赖install_vue作业。这样设置后,只要install_java作业运行完毕,build_java就会开始运行。build_vue与此同理。我们在作业build_html中设置了needs: [],这样设置后,虽然它属于第二队列build阶段,该作业将会放到第一队列运行,当流水线触发时它就会运行。待作业build_vue与build_java运行完毕后,deploy阶段的job_deploy作业才会运行

needs还可以设置跨流水线的依赖关系

父流水线

create-artifact:
  stage: build
  script: echo "sample artifact" > artifact.txt
  artifacts:
    paths: [artifact.txt]
child-pipeline:
  stage: test
  trigger:
    include: child.yml
    strategy: depend
  variables:
    PARENT_PIPELINE_ID: $CI_PIPELINE_ID

子流水线

use-artifact:
  stage: deploy
  script: cat artifact.txt
  needs:
    - pipeline: $PARENT_PIPELINE_ID
      job: create-artifact

pages

pages关键词用于将作业artifacts发布到GitLab Pages,其中需要用到GitLab Pages服务—— 这是一个静态网站托管服务。注意,需要将网站资源放到artifacts根目录下的public目录中,且作业名必须是pages

pages:
  stage: deploy
  script:
    - mkdir .public
    - cp -r * .public
    - mv .public public
  artifacts:
    paths:
      - public

在上述的例子中,我们定义了一个名为pages的作业,然后将网站的静态资源都复制到public目录中—— 为避免复制死循环,可以先创建一个临时目录,最后配置artifacts的路径为public。这样作业运行后,就会将artifacts发布到GitLab Pages上。如果GitLab是私有化部署,需要管理员开启GitLab Pages功能

parallel

parallel关键词用于设置一个作业同时运行多少次,取值范围为2~50,这对于非常耗时且消耗资源的作业来说是非常合适的。要在同一时间多次运行同一个任务,开发者需要有多个可用的runner,或者单个runner允许同时运行多个作业

test:
   script: echo 'hello WangYi'
   parallel: 5

在上述例子中,我们定义了一个test作业,并设置该作业的parallel为5,这样该作业将会并行运行5次。作业名称以test 1/5、test 2/5、test 3/5这样命名,以此类推

parallel关键词除了可以配置数字,还可以配置matrix。使用matrix可以为同时运行的作业注入不同的变量值

deploystacks:
  stage: deploy
  script:
    - echo ${PROVIDER}_${STACK}
  parallel:
    matrix:
      - PROVIDER: aws
        STACK:
          - monitoring
          - app1
          - app2
      - PROVIDER: ovh
        STACK: [monitoring, backup, app]
      - PROVIDER: [gcp, vultr]
        STACK: [data, processing]

在上述例子中,我们可以生成10个作业,每个作业都有两个变量,即PROVIDER与STACK的组合,并且这两个变量的值都不一样

这在需要同时运行多个相同任务但具有不同参数的情况下非常有用。例如,你可能希望在不同的环境中运行相同的测试,或者在不同的数据集上运行相同的构建过程

retry

retry关键词用于设置作业在运行失败时的重试次数,取值为0、1或2,默认值为0。如果设置为2,则作业最多再运行2次。除了可以在作业上设置,retry关键词还可以在default关键词下设置,为每个作业设置统一的重试次数

build:
  script: npm build
  retry: 2

在上面的例子中,我们定义了一个build作业,如果该作业第一次运行失败,将会继续尝试运行,且最多再尝试运行2次

除了简单设置重试次数,retry还可以设置为当特定错误出现时进行重试

build:
  script: npm build
  retry: 
    max: 2
    when: runner_system_failure

在上述例子中,如果错误类型是runner_system_failure则进行重试,如果为其他错误类型则不会进行重试。类似的错误类型还有如下几种

  • always:任务错误都会重试
  • unknown_failure:未知失败时重试
  • script_failure:当执行脚本失败时重试
  • api_failure:当错误类型是API失败时重试

timeout

timeout关键词用于设置一个作业的超时时间,超过该时间,流水线就会被标记为运行失败

timeout关键词的取值为特定的时间格式,如3600 seconds、60 minutes、one hour、3h 30m等

build:
  script: npm build
  timeout: 1h

在上面的例子中,我们定义了一个build作业,并设置timeout的值为1h。除了将timeout定义在具体作业上,开发者还可以将之定义在default下为流水线中的每一个作业设置超时时间。注意,超时时间不能长于runner的失效期

release

release关键词用于创建发布。如果流水线使用的是Shell执行器,要创建发布,必须安装官方提供的release-cli,这是一个创建发布的命令行工具。如果是Docker执行器的话,可以直接使用官方提供的镜像registry.gitlab.com/gitlab-org/release-cli:latest

release关键词下有很多配置项,有些是必填的,有些是选填的,如下所示。tag_name:必填,项目中的Git标签。name:选填,release的名称,如果不填,则使用tag_name的值。description:必填,release的描述,可以指向项目中的一个文件。ref:选填,release的分支或者tag。如果不填,则使用tag_name。milestones:选填,与release关联的里程碑。released_at:选填,release创建的日期和时间,如'2021-03-15T08:00:00Z'。assets:links:选填,资产关联,可以配置多个资源链接

下面的配置,是通过打tag时构建并发布

release_job:
  stage: release
  image: registry.gitlab.com/gitlab-org/release-cli:latest
  rules:
    - if: $CI_COMMIT_TAG                  
  script:
    - echo "Running the release job."
  release:
    name: 'Release $CI_COMMIT_TAG'
    description: 'Release created using the release-cli.'

通过 release 关键字,你可以在流水线中定义和发布软件版本,并将相关信息(如版本号、描述、资产等)记录在 GitLab 项目中。这有助于团队更好地管理和跟踪软件的发布历史,可以在"Deployments/Releases中查看"

高阶关键词

rules

由于项目的流水线内容都是定义在.gitlab-ci.yml文件中的,为了应对各种业务场景、各种分支的特殊作业,开发者需要编写复杂的条件来实现在特定场景下运行特定的作业,这个时候可以使用关键词rules。

关键词rules是一个定义在作业上的关键词。它不仅可以使用自定义变量、预设变量来限定作业是否运行,还可以通过判断项目中某些文件是否改变以及是否存在某些文件来决定作业是否运行。开发者可以设置多条判断语句,如果有多条规则命中,会按照第一匹配原则来决定作业是否会运行。关键词rules是关键词only/except的“加强版”,在相同的场景下官方推荐使用rules,官方对关键词only/except将不再开发新的特性。rules下有6个配置项,分别是if、changes、exists、allow_failure和variables

rules:if

rules:if用于条件判断,可以配置多个表达式,当表达式的结果为true时,该作业将被运行

test_job:
  script: echo "Hello, ZY!"
  rules:
    - if: '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^feature/ && $CI_MERGE_REQUEST_  
TARGET_BRANCH_NAME != $CI_DEFAULT_BRANCH'
      when: never
    - if: '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^feature/'
      when: manual
      allow_failure: true
    - if: '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME'

在上述例子中,我们使用rules:if定义了3个表达式。注意,第一个表达式的含义是,当前的代码改动中,如果在当前的合并请求中源分支是以feature开头的,且目标分支不是项目的默认分支,则当前作业不会被添加到流水线中。注意,如果用when:never修饰一个rules:if,表明若命中该表达式不会运行。此外,在rules:if中使用的变量格式必须是$VARIABLE

rules:changes

为了满足指定文件改变而运行特定作业的场景需求,GitLab CI/CD提供了rules:changes。开发者可以配置一个文件列表,只要列出的文件有一个改动,该作业就会运行

docker_build:
  script: docker build -t my-image:$CI_COMMIT_REF_SLUG .
  rules:
    - changes:
        - Dockerfile

在上述例子中,我们将rules:changes指向Dockerfile文件,这意味着只要Dockerfile更改了,docker_build作业就会运行。这里需要注意:首先,配置的文件路径必须是项目根目录的相对路径;其次,当流水线类型是定时流水线或者tag流水线时,该作业也会运行。所以开发者应尽量在分支流水线或者合并流水线中使用它

rules:exists

rules:exists可以用于实现根据某些文件是否存在而运行作业:如果配置的文件存在于项目中,则运行作业;如果不存在,则不运行作业

docker_build:
  script: docker build -t fizz-app:$CI_COMMIT_REF_SLUG .
  rules:
    - exists:
        - Dockerfile

在上述例子中,如果项目中存在Dockerfile,则该作业会运行,否则不运行

rules:allow_failure

rules:allow_failure可以配置当前作业运行失败后,使流水线不停止,而继续往下运行。其默认值为false,表示作业运行失败则流水线停止运行

test_job:
  script: echo "Hello, Rules!"
  rules:
    - if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH'
      when: manual
      allow_failure: true

在上面的例子中,如果作业运行失败,流水线不会停止运行

rules:variables

rules:variables用于对变量进行操作,可以在满足条件时修改或创建变量

rules_var:
  variables:
    DEPLOY_VARIABLE: "default-deploy"
  rules:
    - if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH
      variables:                              
        DEPLOY_VARIABLE: "deploy-production"  
    - if: $CI_COMMIT_REF_NAME == 'feature'
      variables:
        IS_A_FEATURE: "true"                  
  script:
    - echo "Run script with $DEPLOY_VARIABLE as an argument"
    - echo "Run another script if $IS_A_FEATURE exists"

在上述例子中,如果当前推送的分支是默认分支,则会将变量DEPLOY_VARIABLE修改为deploy-production;如果当前推送的分支是feature,则新建一个变量为IS_A_FEATURE,令其值为true

workflow

workflow是一个全局关键词,可用于配置多个规则来限定流水线是否运行。workflow的配置项只有一个rules,上一节介绍的rules下的所有配置项都可以在此处使用,如rules:if、rules:changes、rules:exists等。与作业中的rules不同,在workflow中定义的rules是直接作用于流水线的,如果命中了一条规则,流水线就会运行。还有,用rules:variables创建的变量会在整个流水线中可见,属于全局变量,所有的作业都可以使用它

workflow:
  rules:
    - if: $CI_COMMIT_MESSAGE =~ /-draft$/
      when: never
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
      variables:
        IS_A_MR: "true" 
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

在上述例子中,我们用3条规则来配置workflow。第一条规则,如果提交的信息中包含-draft,流水线不会触发;第二条规则,如果当前的操作创建了一个合并请求,会声明一个值为true的变量IS_A_MR,该变量的值可以被作业中的变量覆盖;第三条规则,如果提交的分支是默认分支,则运行流水线。编写触发规则都离不开CI/CD中的预设变量,每个变量有其独特的用处,有些变量在特定场景下才会有值,开发者掌握了预设变量使用规则,一切就会变得很简单

trigger

trigger关键词用于创建下游流水线,即在流水线中再触发新的流水线,尤其适用于构建部署复杂的项目或者多个微服务的项目结构。使用该关键词,开发者可以创建父子流水线(在一个项目中运行两条流水线),也可以创建跨项目流水线。下面来看一下它的使用规则

如果一个作业配置了trigger,那么该作业有些属性将不能被定义,如script、after_script和before_script。能够配置的属性只有以下几个:stage、allow_failure、rules、only、except、when、extends和needs

trigger-other-project:
  stage: deploy
  trigger:
    project: my/deployment
    branch: stable-2022
trigger-child-pipeline:
  stage: deploy
  trigger:
    include: path/to/microservice_a.yml

在上述例子中,我们定义了两个作业,即trigger-other-project和trigger-child-pipeline。第一个作业trigger-other-project会触发my/deployment项目的stable-2022分支的流水线。如果不定义branch,则触发该项目的默认分支。第二个作业trigger-child-pipeline会触发一个下游流水线,该流水线的内容定义在该项目的path/to/microservice_a.yml文件中。默认情况下,一旦下游流水线得以创建,触发流水线的作业就会变为成功状态,并且上游的流水线会继续运行,并不会等待下游流水线运行完成。如果开发者需要上游流水线在下游流水线运行完成后再继续运行,可以在作业上配置strategy: depend

trigger-microservice_a:
  stage: deploy
  variables:
    ENVIRONMENT: staging
  trigger:
    include: path/to/microservice_a.yml
    strategy: depend

在关键词trigger中配置strategy: depend,可以使当前流水线待下游流水线完成后再继续运行。在上述例子中,我们也定义了一个值为staging的变量ENVIRONMENT。该变量会直接注入下游流水线中,让下游流水线可以根据该变量做一些自定义的调整

include

include关键词用于引入模板,即允许开发者在.gitlab-ci.yml文件中引入外部的YAML文件。引入的文件可以是本项目中的,也可以是一个可靠的公网YAML文件,还可以是官方的模板文件。我们可以将常用的一些配置模板定义在仓库外的一个公网YAML文件中,然后使用include引入,这样做之后,修改引入的文件将不会触发流水线,在下次运行流水线时生效。为了安全起见,开发者应该引入那些可靠的YAML文件

前文提到,开发者可以在.gitlab-ci.yml中定义一个配置模板作业,然后用extends关键词来继承它,以降低配置、提取公共代码。但对于跨项目或者多项目,共享配置是无法单独使用extends来实现的。对此GitLab CI/CD团队提供了关键词include,用于引入外部的YAML文件。每个关键词都有自己存在的理由和满足的需求,这就需要我们在学习时结合实际来思考。include是一个全局关键词,一般定义在.gitlab-ci.yml的头部,可以一次引入多个文件,但文件的扩展名必须是.yml或.yaml

include关键词下有4个配置项,分别是local、file、remote和template。每个配置项都可以引入不同类型的YAML文件,且都允许引入多个文件。local的值必须指向本项目的文件,file的值可以指向其他项目的文件,remote用于引入公网文件资源,template配置项只能用于引入GitLab官方编写的模板文件。这4个配置项可以单独使用,也可以组合使用

include:local

include:local用于引入本地文件,且只能引入当前项目的文件,需要以/开头,表示项目根目录。引入的文件必须与当前的.gitlab-ci.yml文件位于同一分支,无法使用Git submodules的路径

include:
  - local: '/templates/.fe-ci-template.yml'

在上述例子中,我们会引入项目目录/templates下的.fe-ci-template.yml文件,而该文件会被合并到.gitlab-ci.yml文件中。合并时,对于相同的key,我们将使用.gitlab-ci.yml的配置。注意,.fe-ci-template.yml文件要与.gitlab-ci.yml位于同一分支。因为只引入了一个文件,所以上述例子也可以简写成include: '/templates/.fe-ci-template.yml'

对于大型项目的流水线,引入的模板资源不止一个,如果一个一个引入,无疑会很麻烦。这时可以将模板文件存放在一个文件夹下,在.gitlab-ci.yml中用通配符来匹配该目录下的所有模板文件,进行批量引入。批量引入模板文件可以写成include:'/fe/*.yml',这样将会引入fe目录下的所有以.yml结尾的文件,但不会引入fe的子目录下的YAML文件。如果要引入一个目录下所有的YAML文件,并引入该目录所有子级目录下的YAML文件,可以配置为include: '/fe/**.yml'

include:file

include:file用于引入其他项目文件,可以在.gitlab-ci.yml文件中引入另一个项目的文件。开发者需要指定项目的完整路径,如果有群组,那么需要加上群组路径

include:
  - project: 'fe-group/my-project'
    ref: main
    file: '/templates/.gitlab-ci-template.yml'
  - project: 'test-group/my-project'
    ref: v1.0.0
    file: '/templates/.gitlab-ci-template.yml'
  - project: 'be-group/my-project'
    ref: 787123b47f14b552955ca2786bc9542ae66fee5b  # Git SHA
    file: 
      - '/templates/.gitlab-ci-template.yml'
      - '/templates/.tests.yml'

在上述例子中,我们用include引入了3个项目的模板资源,即fe-group/my-project、test-group/my-project和be-group/my-project。每个项目都指定了分支、标签或者Git SHA。这里也展示了file是可以配置多个文件的。对于项目名称,开发者可以使用变量指定

include:remote

include:remote用于引入公网文件,即可以引入公网的资源。开发者需要指定文件资源的完整路径,由于文件资源必须是公开、不需要授权的,因此可以使用HTTP或HTTPS的GET请求获得

include:
  - remote: 'https://▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓.gitlab-ci.yml'

使用该方法可以在不增加版本记录的情况下,对流水线进行修改。开发者可以将流水线所有内容定义在一个外网的文件上,使用include引入。注意,这可能会存在安全隐患。使用include引入文件后,在流水线运行时,会将所有的引入文件做一次快照合并到.gitlab-ci.yml文件中。在流水线运行后再修改引用的文件,并不会触发流水线,也不会更改已经运行的流水线结果,所修改的内容会在流水线下一次运行时应用。如果重新运行旧的流水线,运行的依然是修改前的内容

include:template

include:template用于引入官方模板文件,也就是说,可以将官方的一些模板引入流水线中

include:
  - template: Android-Fastlane.gitlab-ci.yml
  - template: Auto-DevOps.gitlab-ci.yml

在上述例子中,我们引入了两个官方的模板。引入这些模板时,不用填写完整路径,只需要保证文件名称正确