1.2 .gitlab-ci.yml里使用的关键字2
今天在家里又看了一些.gitlab-ci.yml
文件里使用的关键字,晚上看的时候感觉这种效率很低,但是还是想把这个文件里使用的关键字全都过一遍。目前也没有摸索到更好的学习办法,暂且先这样吧。先看看今天学习的几个关键字和概念。
variables
image
service
隐藏任务
extends
stage
before_script
script
after_script
variables
用来定义变量,既可以在全局范围使用,也可以单任务级别使用。在gitlab里,变量分为两种类型:
- 自定义变量,即使用variables关键字定义的变量
- 预定义变量,即gitlab官方提前定义好的变量,可以直接使用。例如CI_COMMIT_REF_NAME,表示正在构建项目的分支或者tag名称。
变量定义的一些规则:
- 在变量的名称和值里都只能出现数字和字符串。
- 出现同名变量时,后出现的值覆盖先出现的。
- 所有YAML格式定义的变量在服务容器里都可以访问到。
示例如下:
variables:
DEPLOY_SITE: "https://example.com/"
deploy_job:
stage: deploy
script:
- deploy-script --url $DEPLOY_SITE --path "/"
deploy_review_job:
stage: deploy
variables:
REVIEW_PATH: "/review"
script:
- deploy-review-script --url $DEPLOY_SITE --path $REVIEW_PATH
调用方式和shell里调用变量的方式一样。
手动流水线里可以使用value和description来定义预填充变量:
示例如下:
variables:
DEPLOY_ENVIRONMENT:
value: "staging" # Deploy to staging by default
description: "The deployment target. Change this variable to 'canary' or 'production' if needed."
但是手动流水线的概念还没搞明白,所以这块内容在后面需要进行修改和更新。
image
用来指定运行这个任务的docker镜像。基本用法如下所示:
default:
image: ruby:2.6
services:
- postgres:11.7
before_script:
- bundle install
test:
script:
- bundle exec rake spec
上面的示例中使用default
关键字定义了一个全局的镜像参数,即所有任务默认都会使用这个镜像。除此之外,还可以在任务级别定义,即每个任务使用自己的镜像:
build-job1:
stage: build
image: nginx:1.8.0
在上面这个示例中,build-job1任务使用nginx:1.8.0镜像。(待测试)
镜像的名称遵循下面的格式:
-
image: <image-name>
(等同于image-name:latest
) image: <image-name>:<tag>
-
image: <image-name>@<digest>
即使用最新镜像,标签指定镜像,哈希值指定镜像
image的扩展用法
image:name
用来指定镜像的名称,用法如下:
image:
name: 'registry.example.com/my/image:latest'
这种用法和上面的三种没有区别。这种用法一般是和下面的entrypoint
搭配使用。
image:entrypoint
entrypoint
是用来覆盖镜像默认的执行命令。用法如下:
17.06及之后的版本
image:
name: super/sql:experimental
entrypoint: [""]
17.03及之前的版本
image:
name: super/sql:experimental
entrypoint: ["/bin/sh", "-c"]
上面这两种格式,会让runner基于镜像super/sql:experimental
启动一个新镜像,但是会使用默认的shell来覆盖原来镜像的启动命令。
除了使用默认shell覆盖以外, 还可以使用其他任何你想使用的命令来覆盖,只需要将命令写入上面的entrypoint后面的数组里即可。
services
这个关键字用来指定一个服务容器镜像,在任务运行的时候,会创建一个服务容器,链接到image关键字里指定的镜像创建的任务容器上。实际上的流程比这一句要复杂的多,简单概括一下,应该是:
- 根据services里指定的镜像,启动一个服务容器(service container)
- 根据job里的image启动一个任务容器(job container)
- 将服务容器和任务容器链接到一起,然后在任务容器里可以通过主机名访问服务容器里提供的服务(目前还不知道有什么用)
目前服务容器主要作用还是当做一个单独的小数据库使用,官方文档上提供的4种服务容器类型分别是:
- postgresql
- mysql
- redis
- gitlab
具体的用法后面再具体研究,这个services
在.gitlab-ci.yml
文件里的用法如下所示:
default:
before_script:
- bundle install
test:2.6:
image: ruby:2.6
services:
- postgres:11.7
script:
- bundle exec rake spec
test:2.7:
image: ruby:2.7
services:
- postgres:12.2
script:
- bundle exec rake spec
在test:2.6任务里,定义的任务容器镜像是ruby:2.6
,服务镜像是postgres:11.7
。
除了这种格式以外,还有更复杂一点的定义方式,看下面的示例:
default:
image:
name: ruby:2.6
entrypoint: ["/bin/bash"]
services:
- name: my-postgres:11.7
alias: db-postgres
entrypoint: ["/usr/local/bin/db-postgres"]
command: ["start"]
before_script:
- bundle install
test:
script:
- bundle exec rake spec
在services下支持另外4个参数:
- name,服务名称,也是镜像名称
- alias,别名,可以用来在任务容器里访问服务,因为默认名称在带有registry信息的时候非常长,不方便使用,可以设置一个简短的别名。
- entrypoint,用来覆盖服务镜像默认执行命令
- command,和Dockerfile里的CMD指令类似,用来覆盖镜像里默认的CMD指令。
隐藏任务(hidden_job)
如果想临时禁用一个任务,除了将它注释起来以外,还可以在它名称前面加一个点号,将它变成一个隐藏任务,这样在Gitlab的UI界面就看不到这个任务了,它也不会实际执行,如下所示:
.hidden_job:
script:
- run test
隐藏任务在.gitlab-ci.yml
里的用法一般是用来做一些命令、变量或者配置的模板。在其他位置导入,例如昨天发布的文章里介绍的YAML锚,就可以导入隐藏任务模板。
extends
extends
关键字是用来复用配置部分,和它功能类似的是YAML锚,但是它更灵活且可读性更强。它和YAML锚的区别是,可以从include
关键字包含进来的配置文件里复用配置。
看下面的示例:
.tests:
script: rake test
stage: test
only:
refs:
- branches
rspec:
extends: .tests
script: rake rspec
only:
variables:
- $RSPEC
在这个例子里,rspec任务复用了来自.tests
任务模板里的配置。然后在实际执行时,Gitlab会做以下操作:
- 基于键做深度迭代合并
- 合并.tests的内容到rspec任务
- 不会合并键对应的值
最终解析出来的rspec任务内容如下所示:
rspec:
script: rake test
stage: test
only:
refs:
- branches
variables:
- $RSPEC
从结果中可以看到,相同的键only
,它下面的两个值会合并到一起,用于最终的条件判断。only
关键字在后面会介绍。
多级别继承
extends
支持多级别继承,在实际使用的时候,应该避免超过3层的命令,最多可以使用11层(但是不要使用,太过复杂)。下面的实例演示了一个2层继承:
.tests:
rules:
- if: "$CI_PIPELINE_SOURCE" = "push"
.rspec:
extends: .tests
script: rake test
rspec 1:
variables:
RSPEC_SUITE: '1'
extends: .rspec
rspec 2:
variables:
RSPEC_SUITE: '2'
extends: .rspec
spinach
extends: .tests
script: rake spinach
在上面的示例中,rspec1
和rspec2
这两个任务都是双层扩展,他们俩扩展.rspec
这个隐藏任务模板的内容,而.rspec
扩展来自.tests
任务模板的内容。
合并细节
可以使用extends
来合并哈希字典,而不是数组。合并使用的算法是“最近范围优先”。所以来自最后一个同名成员会覆盖之前定义的,示例如下;
.only-important:
variables:
URL; "http://my-url.internal"
IMPORTANT_VAR: "the details"
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_COMMIT_BRANCH == "stable"
tags:
- production
script:
- echo "Hello world"
.in-docker:
variables:
URL: "http://docker-url.internal"
tags:
- docker
image: alpine
rspec:
variables:
GITLAB: "is-awesome"
extends:
- .only-important
- .in-docker
script:
- rake rspec
在上面的示例里,同时扩展了.only-important
和.in-docker
这两个隐藏任务模板。而在这两个模板里面,有一个相同的变量URL
,一个相同的tags
。后扩展的是.in-docker
模板,那么最终生效的是.in-docker
里的URL变量以及tags
,即URL变量的值是 "http://docker-url.internal"
,tags
的值是docker
。最终的rspec
任务的内容是:
rspec:
variables:
URL: "http://docker-url.internal"
IMPORTANT_VAR: "the details"
GITLAB: "is-awesome"
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_COMMIT_BRANCH == "stable"
tags:
- docker
image: alpine
script:
- rake rspec
script
的内容不会合并,因此最终生效的是rspec
里自己的script
,即- rake rspec
。
extends
和include
结合使用
要复用来自其他配置文件里的配置,可以结合使用extends
和include
,示例如下:
included.yaml
文件内容
.template:
script:
- echo Hello!
.gitlab-ci.yml
文件内容:
include: included.yml
useTemplate:
image: alpine
extends: .template
extends
会首先到.gitlab-ci.yml
文件里找.template
模板的定义,如果没找到,才会去included.yml
文件里查找。
stage
stage
关键字用来定义任务所属的阶段,在相同stage
里的任务会并行执行。如果没有定义stage
参数,那么这个任务默认属于test
阶段。
stage
示例如下:
stages:
- build
- test
- deploy
job1:
stage: build
script:
- echo "This job compiles code."
job2:
stage: test
script:
- echo "This job tests the compiled code. It runs when the build stage completes."
job3:
script:
- echo "This job also runs in the test stage".
job4:
stage: deploy
script:
- echo "This job deploys the code. It runs when the test stage completes."
对于并发执行的任务,有一些前提条件:
- 有多个runner,可以一次运行多个任务
- 一个runner里的concurrent设置大于1,表示一个runner可以同时运行超过1个任务,此时可以并发执行多个任务。
stage: .pre
和stage: .post
.pre
阶段里的任务会在流水线开始执行之前就执行完成。.post
是流水线任务执行完成以后执行的阶段。用户不需要自己定义.pre
或.post
阶段,可以直接在任务里直接使用stage: .pre
和stage: .post
将任务划分到这两个阶段即可。看下面的示例:
stages:
- build
- test
job1:
stage: build
script:
- echo "This job runs in the build stage."
first-job:
stage: .pre
script:
- echo "This job runs in the .pre stage, before all other stages."
job2:
stage: test
script:
- echo "This job runs in the test stage."
last-job:
stage: .post
script:
- echo "This job runs in the .post stage, after all other stages."
在上面的示例里,4个任务的执行顺序按照规则依次是:
- first-job
- job1
- job2
- last-job
before_script
before_script
用来定义一个命令数组,在任务运行之前,会先执行这部分命令。但是artifacts
比before_script
先执行,后面会介绍artfacts
的用途。
beforce_script
里定义的命令类型包括:
- 单行命令
- 分为多行的单个长命令
- YAML锚
`before_script示例如下:
job:
before_script:
- echo "Execute this command before any `script:` command"
script:
- echo "This command execute after before-script command"
提示,before_script
里定义的命令和script
里定义的命令是按照顺序在相同的shell里执行的。
script
使用script
指定runner
执行的shell
脚本。除了触发任务(trigger job)以外,其他任务都需要使用script
关键字指定执行的脚本。例如:
单个命令
job:
script: "bundle exec rspec"
多个命令
job:
script:
- uname -a
- bundle exec rspec
有时候需要将命令使用单引号或双引号括起来,如下所示:
job:
script:
- curl --request POST --header 'Content-Type: application/json' \
"https://gitlab/api/v4/projects"
job:
script:
- 'curl --request POST --header "Content-Type: application/json" \
"https://gitlab/api/v4/projects"'
在写这些命令的时候,需要注意下面这些特殊字符:
{, }, [, ], ,, &, *, #, ?, |, -, <, >, =, !, %, @,
如果某个命令的返回结果不是0,那么整个任务就会执行失败,可以捕获命令的返回结果,主动报错。但是可以保证任务不结束:
job:
script:
- false || exit_code=$?
- if [ $exit_code -ne 0 ]; then echo "Previous command failed"; fi;
after_script
使用after_script
可以定义一个命令数组,用于在任务执行完成之后执行,包括失败的任务。
在after_script里定义的命令类型包括:
- 单行命令
- 分为多行的单个长命令
- YAML锚
after_script示例:
job:
script:
- echo "An example script section"
after_script:
- echo "Execute this command after the `script` section completes"
提示:在after_script
里指定的脚本是在新shell里执行的,和before_script
以及script
命令的执行环境是隔离开的。因此:
- 会将当前工作目录设置为默认值
- 无权访问before_script或script里定义的命令做出的修改,包括:
- 在script脚本里定义的命令别名或导出的变量
- 在工作树以外做出的修改(取决于runner执行器),像在before_script里或script里安装的软件。
- 有一个独立的超时时间,硬编码为5min
- 不会影响任务的结束代码。如果任务的script部分成功执行,但是after_script超时导致失败。整个任务的结束代码还是0(表示任务成功执行)
- 如果任务超时或者被取消,after_script不会执行。
以上就是今天学习的几个脚本关键字,明天继续。