gitlab-ci的使用

背景

计划让持续集成的笔记中专注与对比相同与不同点.
关于gitlab-ci的详细使用放在这里

配置文件

配置文件名为 .gitlab-ci.yml. 官方提供了CI Lint做yml的语法检查.
配置文件举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
stages:
- test
- build
- deploy

variables:
IMAGE: docker.registry/name/${CI_PROJECT_NAMESPACE}-${CI_PROJECT_NAME}

before_script:
- IMAGE_TAG=${IMAGE}:${CI_COMMIT_SHA:0:8}

test_all:
image: "pymicro"
stage: test
services:
- name: mysql:5.6
alias: mysql
veriables:
MYSQL_DATABASE: db
MYSQL_ROOT_PASSWORD: password
before_script:
- pip install -U -r requirements.txt
script:
- flake8 app
- pytest tests

build_image:
image: "docker:17.11"
stage: build
services:
- name: "docker:17.12.0-ce-dind"
alias: dockerd
variables:
DOCKER_HOST: tcp://dockerd:2375
only:
- master
tags:
- build
script:
- docker build -t ${IMAGE_TAG} -f Dockerfile .
- docker push ${IMAGE_TAG}

deploy_production:
stage: deploy
variables:
GIT_STRATEGY: none
only:
- master
when: manual
tags:
- deploy-production
script:
- kubectl set image deploy/myproject "app=${IMAGE_TAG}" --record

基础界面

核心抽象

  • pipeline: 成功触发后执行的一系列操作,通常包含多个stage
  • stage: 一个stage里可以有一个或多个job(但这些job并非串行而是并行),
    当一个stage成功,下一个stage里的job才会执行.
  • job: 最基础的单位,里面会script关键字来指定要跑什么命令

系统关键字

stages和stage

全局范围内,自定义stages关键词.
每个job内使用stage关键词来定义阶段.
同一个stage里的job并行运行,
上一个stage成功,下一个stage里的job才会执行.

有一些默认的stages全局定义,即使不手动定义也可以使用

  • .pre
  • build
  • test
  • deploy
  • .post

其中 .pre.post 不会被手动定义覆盖顺序.
其他的可以

tags

用于指定运行一个job所用的机器

image和service

runner无论是什么类型,实体机器还是docker也好,
gitlab都规定需要在内部下载docker来做操作.

image指定一个docker的镜像.
如果也有service,则会附带另外一些docker.

1
2
3
4
5
6
7
8
default_job:
image: python:3.6

services:
- mysql:5.7

before_script:
- apt-get install mysql-client

variables

手动设置一些环境变量

before~script~, script, after~script~

script用来定义一个job的具体任务,
before~script和afterscript分别定义script之前和之后的动作~.
可以针对一个任务,也可以针对所有任务.

cache

不同的job是在不同的docker中执行的,
如果希望上一个docker中产生的文件能够共享到目前job所使用的docker中.

  1. 不想再重复安装npm库
  2. 不想再重新下载或编译某些文件等等

但注意有时如果缓存文件过大,反而会拖累速度.

artifacts

指定一些路径,runner会把对应的文件上传到gitlab中,供用户下载.
通常是打包好的apk文件等,用户希望查看或使用的东西.

allow~failure~

默认值为false,代表如果一个job失败,那么该stage失败,pipeline也会失败.
如果为true则可继续执行下去.

needs

用于编排不同job之间的时序,并且可以覆盖stage的限定.
比如一个软件需要在多个平台(win,linux,mac)发布,
linux平台的test虽然属于test stage,但完全没必要等待mac平台的build stage完成,
因此配置文件可以写成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
linux:build:
stage: build

mac:build:
stage: build

lint:
stage: test
needs: []

linux:rspec:
stage: test
needs: ["linux:build"] # 只等待自己平台的build job完成,且覆盖stage的设定

linux:rubocop:
stage: test
needs: ["linux:build"]

mac:rspec:
stage: test
needs: ["mac:build"]

mac:rubocop:
stage: test
needs: ["mac:build"]

only和excpet

一个job的stage属性用于配置该job属于那个阶段.
only属性则用来配置触发条件,except作用相反
可选的值有

  • <branch名> push特定分支时触发
  • tags push的分支带有标签
  • pushes push上去就触发
  • web 手动在gitlab上点击 Run Pipeline时触发
  • merge~requests~ 创建或更新一个merge requests时触发

细分项目

  • refs
  • variables
  • changes
  • kubernetes

when

用于指定触发条件,可选的值有

  • on~success~(默认值): 只有前面stages的所有工作成功时才执行
  • on~failure~: 当前面stages中任意一个jobs失败后执行
  • always: 无论前面stages中jobs状态如何都执行
  • never: 永不执行
  • manual: 手动执行
  • delayed: 延迟执行

environment

在gitlab的web端,Operations>>Environments中会有一些环境的列表,
一般来说还会附带一个按钮用于跳转到该环境的首页.
一般来说deploy stage中的job可以设置该字段来定义如何配置这些环境
子配置有

  • name(默认子配置): 部署到的环境名,比如production,staging,不可为空
  • url: 对应环境的地址,最终会在web端的按钮中变成链接
  • on~stop~
  • action
  • auto~stopin~
  • kubernetes

pages

如果结果需要提交到gitlab pages上,则使用pages字段来配置

parallel

一个stage中的job是并行执行的,
但不意味着没有限制.
在job中使用parallel?

retry

用于设置job失败后重试的行为
子配置有

  • max(默认子配置): 重试次数上限
  • when: 错误类型匹配时才重试

错误类型有:

  • always
  • unknown~failure~
  • script~failure~
  • api~failure~
  • stuck~ortimeoutfailure~
  • runner~systemfailure~
  • missing~dependencyfailure~
  • runner~unsupported~
  • stale~schedule~
  • job~executiontimeout~
  • archived~failure~
  • unmet~prerequisites~
  • scheduler~failure~
  • data~integrityfailure~

release

生成一个release object?

timeout

trigger

resource~group~

coverage

dependencies

interruptible

继承关系

为了避免重复写每个job中的公共部分,
gitlab-ci允许使用继承的方式来定义job.

inherit

使用 default: 定义job模板,然后可以在全局使用 variables: 定义全局的变量.

在每个具体的job中,
可以使用 inherit.defaultinherit.variables 来定义继承全局哪些东西.
true表示出全部,false表示全不.也可以使用数组具体指定
如果全部要继承,什么都不写就可以.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
default:
image: 'ruby:2.4'
before_script:
- echo Hello World

variables:
DOMAIN: example.com
WEBHOOK_URL: https://my-webhook.example.com

rubocop:
inherit:
default: false
variables: false
script: bundle exec rubocop

rspec:
inherit:
default: [image]
variables: [WEBHOOK_URL]
script: bundle exec rspec

capybara:
inherit:
variables: false
script: bundle exec capybara

karma:
inherit:
default: true
variables: [DOMAIN]
script: karma

extends

job和job之间也可以使用 extends 来实现继承.
子job中定义的相同字段的新值会覆盖父job中的值,不同的则会合并.
注意此处的 .test 是一个隐藏的job,不会被主动执行,
通常专门定义得抽象一些用于继承.

另外父job也不一定需要是该文件中的job,也可以是从外部include进来的job

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.tests:
script: rake test
stage: test
only:
refs:
- branches

rspec:
extends: .tests
script: rake rspec
only:
variables:
- $RSPEC

# 相当于
rspec:
script: rake rspec
stage: test
only:
refs:
- branches
variables:
- $RSPEC

锚点

使用锚点的作用几乎的 extends 的效果相同.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
.job_template: &job_definition  # Hidden key that defines an anchor named 'job_definition'
image: ruby:2.6
services:
- postgres
- redis

test1:
<<: *job_definition # Merge the contents of the 'job_definition' alias
script:
- test1 project

test2:
<<: *job_definition # Merge the contents of the 'job_definition' alias
script:
- test2 project

# 相当于
.job_template:
image: ruby:2.6
services:
- postgres
- redis

test1:
image: ruby:2.6
services:
- postgres
- redis
script:
- test1 project

test2:
image: ruby:2.6
services:
- postgres
- redis
script:
- test2 project

include

用于导入外部的yaml脚本.减少每次写脚本的工作量
默认支持4种方式

  • local 当前项目的其他文件
  • file 自己帐号下其他项目的配置文件
  • remote 其他项目下的配置文件
  • template gitlab官方提供的模板

环境变量

目前已知的有两种

  • 一些内置变量比如 CI_COMMIT_MESSAGE (gitlab自己的CI环境)
  • 自定义的环境变量
    • variables字段声明的环境变量
    • UI界面可设置的变量

ssh操作

比如在某个release阶段,需要从具体执行任务的docker中,
ssh到自己的服务器做一些操作,
那么就需要做一些设置

如何使用密钥

gitlab支持在项目的Settings>CI/CD>Variables中设置自定义的变量,
(变量支持一些属性:
protected: 在特定分支的pipeline不会披露变量内容
masked: 即使在脚本中写 echo $var 也会显示 [masked])
在此处定义好变量后,就可以在CI配置文件中调用变量的值.
通常的做法是使用ssh-add的方式隐式地添加密钥.
而不是将内容输出到pem文件然后手动拼凑ssh的config文件

1
2
3
4
5
6
7
8
9
before_script:
# 检测并安装ssh-agent
- 'command -v ssh-agent >/dev/null || ( apt-get update -y && apt-get install openssh-client -y )'

# 启动ssh-agent
- eval $(ssh-agent -s)

# 添加密钥
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -

known~host问题~

第一次连接服务器时总是会被提问是否信任,需要用户手动交互,
CI中如果是交互的则会报错.
因此需要再把known~host添加上~

1
2
3
4
5
before_script:
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- echo "$SSH_KNOWN_HOSTS" >> ~/.ssh/known_hosts
- chmod 644 ~/.ssh/known_hosts

多行命令

如果在目标机器上执行多行命令,则多次写前缀有些不方便,可以借用EOF这种方式写多行命令

1
2
3
4
5
6
7
script:
- scp blog-nginx.tar [email protected]:~/Downloads/
- ssh [email protected] << EOF
- docker load < /home/pi/Downloads/blog-nginx.tar
- docker container rm -f blog || true
- docker run --name blog -p 83:80 -d blog-nginx:$TIMESTAMP
- EOF

略过CI过程

当用户的更改并不涉及代码(比如更改了说明文档),
此时很大概率希望略过CI,则可以:

  • 在commit message中使用 [ci skip][skip ci], 大小写不敏感,位置不敏感.

  • 在Git 2.10版本后,还可以使用 -o 来传递CI参数

    1
    git push -o ci.skip

参考

  1. gitlab-ci官方文档
  2. 汉化的gitlab-ci官方文档
  3. 一个比较好的gitlab-ci介绍