跳至主要內容

gitlab ci/cd 的两大要素

PaperDragon...大约 32 分钟

gitlab ci/cd 的两大要素

  • gitlab runner
  • gitlab-ci.yml

.gitlab-ci.yml 基本关键词的使用

before_script
script
after_script

allow_failure 
artifiacts 
cache
coverage 
dependencies
environment
except
extends
image
include
interruprible
only
pages
parallel
release
resource_group
retry
rules
services
stage
tags
timeout
trigger
variables
when

关键字分为两大类

  • 全局关键字
  • 普通关键字

全局关键字

  • stages , 用来定义流水线各个阶段的执行顺序

  • workflow , 控制那种类型的流水线可以运行

  • include , 从其他YAML里导入配置

stages

stages 是用来定义流水线各个阶段的,示例如下:

stages:
  - build
  - test
  - deploy

上面的示例中就定义了一个三个阶段的流水线,里面各个阶段的定义顺序就是每个阶段的执行顺序,即安装build、test、deploy这样的顺序来执行。

如果你没有主动使用stages关键字定义阶段的话,gitlab默认会有下面5个阶段:

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

workflow

workflow是用来决定执行哪个流水线以及这个流水线是否执行的关键字。

它和stages是一个级别,

在使用的时候,一般需要和rules关键字搭配使用。rules关键字是一个普通关键字,是用来定义流程是否执行的规则,通过if条件和when条件的组合来判断和规则匹配的流水线是否执行。if判断条件是否匹配,when决定符合条件的流水线是否执行。光看概念可能比较模糊,直接看示例。

workflow:
  rules:
    - if: $CI_COMMIT_MESSAGE =~ /-draft$/
      when: never
    - if: $CI_PIPELINE_SOURCE == "push"

在上面的示例中,定义的是严格的规则。里面使用了两个Gitlab预定义的变量$CI_COMMIT_MESSAGE$CI_PIPELINE_SOURCE,前者的含义是你往gitlab仓库里提交的commit信息,后者的含义是你往gitlab仓库里做的操作(例如推送push,合并请求merge等)。上面两个条件的以上就是:

  • 如果提交信息里有-draft,那么这个流水线就不会运行(when: never表示永不执行)
  • 如果做的操作是push,那么流水线就会执行。

只有符合上面两个条件的流水线会有下一步操作,其他任何类型流水线都不会执行。这个东西有点类似于linux里的防火墙,即符合某些规则的流量会放行或拒绝,其他不匹配的全部丢弃。 有严格的匹配,也有宽松的匹配,看下面的例子:

workflow:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "schedule"'
      when: never
    - if: '$CI_PIPELINE_SOURCE == "push"'
      when: never
    - when: always

这个例子里匹配前面两个条件的流水线不会执行(when: never),其他流水线全部执行(when: always

根据不同的条件使用不同的变量值

在文章开头的时候,第一个问题是怎么在任务里使用变量。答案就是variables关键字,这是一个普通关键字,既可以在全局使用,也可以在任务里使用。特别是和workflow组合使用,在不同条件下有不同的值,看下面的示例:

variables:
  DEPLOY_VARIABLE: "default-deploy"

workflow:
  rules:
    - if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH
      variables:
        DEPLOY_VARIABLE: "deploy-production"  # 覆盖全局定义的 DEPLOY_VARIABLE
    - if: $CI_COMMIT_REF_NAME =~ /feature/
      variables:
        IS_A_FEATURE: "true"                  # 定义一个新变量
    - when: always                            # 其他任何条件都运行流水线

job1:
  variables:
    DEPLOY_VARIABLE: "job1-default-deploy"
  rules:
    - if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH
      variables:                                   # 在任务级别覆盖定义的DEPLOY_VARIABLE 变量
        DEPLOY_VARIABLE: "job1-deploy-production"   
    - when: on_success                             # 在其他情况运行任务
  script:
    - echo "Run script with $DEPLOY_VARIABLE as an argument"
    - echo "Run another script if $IS_A_FEATURE exists"

job2:
  script:
    - echo "Run script with $DEPLOY_VARIABLE as an argument"
    - echo "Run another script if $IS_A_FEATURE exists"

在上面的示例中,定义了一个全局变量DEPLOY_VARIABLE,默认值是default_deploy,然后会根据下面的workflow判断,在不同的任务中会使用不同的值,分为以下几种情况:

  • 当触发流水线的分支是默认分支时(一般是master),则

    • job1里DEPLOY_VARIABLE的值是job1-deploy-production(因为job1里的rules匹配了,此时rules下面的变量定义生效)
    • job2里DEPLOY_VARIABLE的值是deploy-production,此时workflow里的rules匹配,变量定义生效。
  • 当触发的分支是feature时,workflow里的rules规则匹配,会创建变量

    IS_A_FEATURE: "true"
    

    ,对两个任务同时生效。

    • job1里的DEPLOY_VARIABLE的值是job1-default-production,局部变量覆盖全局变量
    • job2里的DEPLOY_VARIABLE的值是default-deploy,使用最开始的全局变量
  • 当触发流水线的分支是其他时

    • job1里DEPLOY_VARIABLE的值是job1-default-deploy,局部变量覆盖全局变量
    • job2里DEPLOY_VARIABLE的值是default-deploy,使用最开始的全局变量

变量关键字variables和规则rules解决我们在第一篇文章里提出来的两个问题。

include

include关键字也是一个全局关键字,用来导入其他YAML文件,这样可以把一个很长的.gitlab-ci.yml文件分割成很多个小文件,然后通过include包含进来,可以提高文件的可读性和复用性。 include包含进来的文件必须是.yml.yaml结尾的,其他类型文件会被忽略。支持下面四种包含方法:

  • local,导入相同项目下的文件
  • file,导入其他项目仓库下的文件
  • remote,从远程URL里导入文件,这个URL必须是可以公开访问的。
  • template,包含Gitlab提供的模板文件

local

导入本仓库里的文件时,需要以仓库根目录为起点,而.gitlab-ci.yml文件刚好位于根目录下,因此可以直接使用相对路径来写。基本用法如下所示:

include: configs/conf.yaml 

还可以使用通配符来匹配

include: configs/*.yaml
include: configs/**/*.yml

第一个是导入configs目录下的所有yaml文件 第二个是导入configs所有子目录下的所有yaml文件

file

要导入相同gitlab服务器上其他仓库里的文件,可以使用file格式,这个时候需要使用绝对路径,如下所示:

include:
  - project: 'userA/projectA'
    file: '/templates/.gitlab-ci-template.yml'

上面的命令会导入userA空间下projectA项目里的templates/.gitlab-ci-template.yml文件。

还可以指定分支号、tag号或直接通过哈希值导入,示例如下:

include:
  # 通过分支号导入  
  - project: 'userA/projectA'
    ref: master
    file: '/templates/.gitlab-ci-template.yml'

  # 通过tag导入
  - project: 'userA/projectA'
    ref: v1.0.1
    file: '/templates/.gitlab-ci-template.yml'

  # 通过哈希值导入
  - project: 'userA/projectA'
    ref:  7234123b
    file: '/templates/.gitlab-ci-template.yml'

如果想导入一个项目里的多个文件,除了使用上面说到的通配符以外,还可以使用文件列表的方式,如下所示:

include:
  - project: 'userA/projectA'
    file: 
      - '/templates/.gitlab-ci-template.yml'
      - '/configs/conf.yaml'

remote

remote用来导入远程仓库里的文件,示例如下:

include:
  - remote: 'https://gitlab.com/userA/projectA/-/raw/main/.gitlab-ci.yaml'

template

目前还不知道Gitlab提供的模板是什么意思,也没有正式使用。只有官网上的一个例子:

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

上面是导入多个模板的方式,还可以导入单个模板,用法和file、local类似。

以上就是三个全局变量的全部内容,明天再接着看其他变量的内容。

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:entrypointentrypoint是用来覆盖镜像默认的执行命令。用法如下: 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 

在上面的示例中,rspec1rspec2这两个任务都是双层扩展,他们俩扩展.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

extendsinclude结合使用

要复用来自其他配置文件里的配置,可以结合使用extendsinclude,示例如下: 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: .prestage: .post

.pre阶段里的任务会在流水线开始执行之前就执行完成。.post是流水线任务执行完成以后执行的阶段。用户不需要自己定义.pre.post阶段,可以直接在任务里直接使用stage: .prestage: .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用来定义一个命令数组,在任务运行之前,会先执行这部分命令。但是artifactsbefore_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示例:

ob:
  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不会执行。

以上就是今天学习的几个脚本关键字,明天继续。

💖预定义 CI/CD 变量open in new window

预定义变量参考

变量GitLabRunner描述
CHAT_CHANNEL10.6全部触发ChatOpsopen in new window命令的源聊天频道。
CHAT_INPUT10.6全部使用ChatOpsopen in new window命令 传递的附加参数。
CHAT_USER_ID14.4全部触发ChatOpsopen in new window命令 的用户的聊天服务用户 ID 。
CI全部0.4可用于 CI/CD 中执行的所有作业。true有空的时候。
CI_API_V4_URL11.7全部GitLab API v4 根 URL。
CI_BUILDS_DIR全部11.10执行构建的顶级目录。
CI_COMMIT_AUTHOR13.11全部Name <email>格式 提交的作者。
CI_COMMIT_BEFORE_SHA11.2全部分支上存在的上一个最新提交。始终0000000000000000000000000000000000000000在合并请求管道中。
CI_COMMIT_BRANCH12.60.5提交分支名称。在分支管道中可用,包括默认分支的管道。在合并请求管道或标签管道中不可用。
CI_COMMIT_DESCRIPTION10.8全部提交的描述。如果标题少于 100 个字符,则没有第一行的消息。
CI_COMMIT_MESSAGE10.8全部完整的提交消息。
CI_COMMIT_REF_NAME9.0全部为其构建项目的分支或标签名称。
CI_COMMIT_REF_PROTECTED11.11全部true如果作业正在运行以获取受保护的引用。
CI_COMMIT_REF_SLUG9.0全部CI_COMMIT_REF_NAME小写,缩短为 63 字节0-9,除. 没有前导/尾随。在 URL、主机名和域名中使用。 a-z``-``-
CI_COMMIT_SHA9.0全部构建项目的提交版本。
CI_COMMIT_SHORT_SHA11.7全部的前八个字符CI_COMMIT_SHA
CI_COMMIT_TAG9.00.5提交标记名称。仅在标签的管道中可用。
CI_COMMIT_TIMESTAMP13.4全部ISO 8601 格式的提交时间戳。
CI_COMMIT_TITLE10.8全部提交的标题。消息的完整第一行。
CI_CONCURRENT_ID全部11.10单个执行器中构建执行的唯一 ID。
CI_CONCURRENT_PROJECT_ID全部11.10单个执行器和项目中构建执行的唯一 ID。
CI_CONFIG_PATH9.40.5CI/CD 配置文件的路径。默认为.gitlab-ci.yml. 在正在运行的管道中只读。
CI_DEBUG_TRACE全部1.7true如果启用 了调试日志记录(跟踪) 。open in new window
CI_DEFAULT_BRANCH12.4全部项目默认分支的名称。
CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX13.7全部通过依赖代理拉取镜像的顶级组镜像前缀。
CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX14.3全部通过依赖代理拉取镜像的直接组镜像前缀。
CI_DEPENDENCY_PROXY_PASSWORD13.7全部通过依赖代理拉取图像的密码。
CI_DEPENDENCY_PROXY_SERVER13.7全部用于登录依赖代理的服务器。这相当于$CI_SERVER_HOST:$CI_SERVER_PORT.
CI_DEPENDENCY_PROXY_USER13.7全部通过依赖代理拉取图像的用户名。
CI_DEPLOY_FREEZE13.2全部仅当管道在部署冻结窗口open in new window期间运行时才可用。true有空的时候。
CI_DEPLOY_PASSWORD10.8全部GitLab Deploy Tokenopen in new window的认证密码,如果项目有的话。
CI_DEPLOY_USER10.8全部GitLab Deploy Tokenopen in new window的身份验证用户名,如果项目有的话。
CI_DISPOSABLE_ENVIRONMENT全部10.1仅当作业在一次性环境中执行时才可用(仅为此作业创建并在执行后处置/销毁的东西 - 除shell和之外的所有执行器ssh)。true有空的时候。
CI_ENVIRONMENT_NAME8.15全部此作业的环境名称。设置后可用environment:nameopen in new window
CI_ENVIRONMENT_SLUG8.15全部环境名称的简化版本,适合包含在 DNS、URL、Kubernetes 标签等中。设置后可用environment:nameopen in new window。slug 被截断为 24 个字符open in new window
CI_ENVIRONMENT_URL9.3全部此作业的环境 URL。设置后可用environment:urlopen in new window
CI_ENVIRONMENT_ACTION13.11全部为此作业的环境指定的操作注释。设置后可用environment:actionopen in new window。可以是startpreparestop
CI_ENVIRONMENT_TIER14.0全部此作业 的环境部署层。open in new window
CI_HAS_OPEN_REQUIREMENTS13.1全部仅当管道的项目有开放需求open in new window时才可用。true有空的时候。
CI_JOB_ID9.0全部作业的内部 ID,在 GitLab 实例中的所有作业中是唯一的。
CI_JOB_IMAGE12.912.9运行作业的 Docker 映像的名称。
CI_JOB_JWT12.10全部一个 RS256 JSON Web 令牌,用于向支持 JWT 身份验证的第三方系统进行身份验证,例如HashiCorp 的 Vaultopen in new window
CI_JOB_JWT_V114.6全部与 相同的值CI_JOB_JWT
CI_JOB_JWT_V214.6全部alpha:open in new window一种新格式化的 RS256 JSON Web 令牌,以增加兼容性。与 类似CI_JOB_JWT,除了 issuer (iss) 声明从 更改gitlab.comhttps://gitlab.comsub已从 更改job_id为包含项目路径的字符串,并aud添加了声明。格式可能会发生变化。请注意,该aud字段是一个常量值。在多个依赖方中信任 JWT 会导致一个 RP 向另一个 RP 发送 JWT 并恶意充当作业open in new window
CI_JOB_MANUAL8.12全部true如果作业是手动启动的。
CI_JOB_NAME9.00.5作业的名称。
CI_JOB_STAGE9.00.5作业阶段的名称。
CI_JOB_STATUS全部13.5执行每个运行器阶段时的作业状态。与 一起使用after_scriptopen in new window。可以是successfailedcanceled
CI_JOB_TOKEN9.01.2使用某些 API 端点open in new window进行身份验证的令牌。只要作业正在运行,令牌就有效。
CI_JOB_URL11.10.5作业详细信息 URL。
CI_JOB_STARTED_AT13.10全部作业开始时的 UTC 日期时间,采用ISO 8601open in new window格式。
CI_KUBERNETES_ACTIVE13.0全部仅当管道具有可用于部署的 Kubernetes 集群时才可用。true有空的时候。
CI_NODE_INDEX11.5全部作业集中作业的索引。仅当作业使用parallelopen in new window.
CI_NODE_TOTAL11.5全部此作业并行运行的实例总数。1如果作业不使用,则设置为parallelopen in new window
CI_OPEN_MERGE_REQUESTS13.8全部使用当前分支和项目作为合并请求源的最多四个合并请求的逗号分隔列表。如果分支具有关联的合并请求,则仅在分支和合并请求管道中可用。例如,gitlab-org/gitlab!333,gitlab-org/gitlab-foss!11
CI_PAGES_DOMAIN11.8全部托管 GitLab 页面的配置域。
CI_PAGES_URL11.8全部GitLab 页面站点的 URL。始终是CI_PAGES_DOMAIN.
CI_PIPELINE_ID8.10全部当前管道的实例级 ID。此 ID 在 GitLab 实例上的所有项目中都是唯一的。
CI_PIPELINE_IID11.0全部当前管道的项目级 IID(内部 ID)。此 ID 仅在当前项目中是唯一的。
CI_PIPELINE_SOURCE10.0全部管道是如何触发的。可以是push, web, schedule, api, external, chat, webide, merge_request_event, external_pull_request_event, parent_pipeline, trigger, 或pipelineopen in new window.
CI_PIPELINE_TRIGGERED全部全部true如果作业被触发open in new window
CI_PIPELINE_URL11.10.5管道详细信息的 URL。
CI_PIPELINE_CREATED_AT13.10全部创建管道时的 UTC 日期时间,采用ISO 8601open in new window格式。
CI_PROJECT_CONFIG_PATH13.8 至 13.12全部在 GitLab 14.0 中删除。open in new window使用CI_CONFIG_PATH.
CI_PROJECT_DIR全部全部存储库克隆到的完整路径,以及作业的运行位置。如果设置了 GitLab Runnerbuilds_dir参数,则此变量设置为相对于builds_dir. 有关更多信息,请参阅高级 GitLab 运行器配置open in new window
CI_PROJECT_ID全部全部当前项目的 ID。此 ID 在 GitLab 实例上的所有项目中都是唯一的。
CI_PROJECT_NAME8.100.5项目目录的名称。例如,如果项目 URL 为gitlab.example.com/group-name/project-1CI_PROJECT_NAME则为project-1
CI_PROJECT_NAMESPACE8.100.5作业的项目命名空间(用户名或组名)。
CI_PROJECT_PATH_SLUG9.3全部$CI_PROJECT_PATH小写字母,字符不被替换a-z或被缩短为 63 个字节。在 URL 和域名中使用。 0-9``-
CI_PROJECT_PATH8.100.5包含项目名称的项目命名空间。
CI_PROJECT_REPOSITORY_LANGUAGES12.3全部存储库中使用的语言的逗号分隔小写列表。例如ruby,javascript,html,css.
CI_PROJECT_ROOT_NAMESPACE13.20.5作业的根项目命名空间(用户名或组名)。例如,如果CI_PROJECT_NAMESPACEroot-group/child-group/grandchild-groupCI_PROJECT_ROOT_NAMESPACEroot-group
CI_PROJECT_TITLE12.4全部GitLab Web 界面中显示的人类可读的项目名称。
CI_PROJECT_URL8.100.5项目的 HTTP(S) 地址。
CI_PROJECT_VISIBILITY10.3全部项目可见性。可以是internalprivatepublic
CI_PROJECT_CLASSIFICATION_LABEL14.2全部项目对外授权分类标签open in new window
CI_REGISTRY_IMAGE8.100.5项目的 Container Registry 的地址。仅当为项目启用了 Container Registry 时才可用。
CI_REGISTRY_PASSWORD9.0全部将容器推送到项目的 GitLab 容器注册表的密码。仅当为项目启用了 Container Registry 时才可用。此密码值与 相同,CI_JOB_TOKEN并且仅在作业运行时才有效。使用CI_DEPLOY_PASSWORD长期访问注册表
CI_REGISTRY_USER9.0全部将容器推送到项目的 GitLab 容器注册表的用户名。仅当为项目启用了 Container Registry 时才可用。
CI_REGISTRY8.100.5GitLab 容器注册表的地址。仅当为项目启用了 Container Registry 时才可用。:port如果在注册表配置中指定了一个值,则 此变量包含一个值。
CI_REPOSITORY_URL9.0全部克隆 Git 存储库的 URL。
CI_RUNNER_DESCRIPTION8.100.5跑步者的描述。
CI_RUNNER_EXECUTABLE_ARCH全部10.6GitLab Runner 可执行文件的操作系统/架构。可能与执行者的环境不一样。
CI_RUNNER_ID8.100.5正在使用的跑步者的唯一 ID。
CI_RUNNER_REVISION全部10.6运行作业的运行器的修订版。
CI_RUNNER_SHORT_TOKEN全部12.3用于验证新作业请求的运行者令牌的前八个字符。用作跑步者的唯一 ID。
CI_RUNNER_TAGS8.100.5运行者标签的逗号分隔列表。
CI_RUNNER_VERSION全部10.6运行作业的 GitLab Runner 的版本。
CI_SERVER_HOST12.1全部GitLab 实例 URL 的主机,没有协议或端口。例如gitlab.example.com.
CI_SERVER_NAME全部全部协调作业的 CI/CD 服务器的名称。
CI_SERVER_PORT12.8全部GitLab 实例 URL 的端口,没有主机或协议。例如8080.
CI_SERVER_PROTOCOL12.8全部GitLab 实例 URL 的协议,没有主机或端口。例如https.
CI_SERVER_REVISION全部全部安排作业的 GitLab 修订版。
CI_SERVER_URL12.7全部GitLab 实例的基本 URL,包括协议和端口。例如https://gitlab.example.com:8080.
CI_SERVER_VERSION_MAJOR11.4全部GitLab 实例的主要版本。例如,如果 GitLab 版本为13.6.1CI_SERVER_VERSION_MAJOR则为13.
CI_SERVER_VERSION_MINOR11.4全部GitLab 实例的次要版本。例如,如果 GitLab 版本为13.6.1CI_SERVER_VERSION_MINOR则为6.
CI_SERVER_VERSION_PATCH11.4全部GitLab 实例的补丁版本。例如,如果 GitLab 版本为13.6.1CI_SERVER_VERSION_PATCH则为1.
CI_SERVER_VERSION全部全部GitLab 实例的完整版本。
CI_SERVER全部全部可用于 CI/CD 中执行的所有作业。yes有空的时候。
CI_SHARED_ENVIRONMENT全部10.1仅当作业在共享环境中执行时才可用(在 CI/CD 调用中持续存在的东西,如shellorssh执行器)。true有空的时候。
GITLAB_CI全部全部可用于 CI/CD 中执行的所有作业。true有空的时候。
GITLAB_FEATURES10.6全部GitLab 实例和许可证可用的许可功能的逗号分隔列表。
GITLAB_USER_EMAIL8.12全部开始作业的用户的电子邮件。
GITLAB_USER_ID8.12全部启动作业的用户的 ID。
GITLAB_USER_LOGIN10.0全部启动作业的用户的用户名。
GITLAB_USER_NAME10.0全部启动作业的用户的名称。
TRIGGER_PAYLOAD13.9全部Webhook 有效负载。仅在使用 webhook 触发open in new window管道时可用。

普通关键字

今天自己看了这么多关键字,感觉最有用的几个关键字是:

  • artifacts,用来配置不同任务之间传递的文件
  • rules、when,用来配置在什么时候启用任务,有多种条件的结合
  • tags,用来选择不同的runner
  • retry,任务执行失败时可以重试,而不是直接结束任务和流水线。
  • !reference,用来选择其他位置定义的变量或脚本,类似于锚,但是比锚更灵活。

gitlab-ci.yml里使用的YAML特性

本来打算今天继续介绍.gitlab-ci.yml文件里使用的其他关键字,但是普通的关键字太多,而且一些重要关键字内容都比较长。需要花更多的时间去整理。今天暂时把理解了的一部分内容先放上来。

这篇文章要介绍的是.gitlab-ci.yml文件里使用的YAML特性,主要介绍:

  • 一些特殊符号的含义
  • 锚的含义和使用。

特殊符号

.gitlab-ci.yml文件里常用的特殊符号主要有以下几个:

  • &,用来定义一个锚的名称,具体什么是锚,有什么用,下面第二部分会具体讲解。
  • *,后面跟上锚的名称,用来引用锚
  • <<,用于字典合并的,YAML文件的格式和嵌套字典非常类似,一层一层嵌套的键值对。而这个符号就是用来在指定位置插入新的键值对或者嵌套的键值对。因此被称为字典合并。

上面三个符号,除了最后一个以外,前面两个都是和锚相关的,这三个符号都是用来提高.gitlab-ci.yml文件里代码的复用性及可读性。降低代码复杂度,让你写出来的.gitlab-ci.yml文件更容易理解。

锚(anchor)

锚是用来提高代码复用性的,类似于编程语言里的函数。在编程语言里,先把一些常用功能抽象出来,定义成函数。然后在需要使用的位置字节调用函数即可。而锚的作用也是这样,可以把需要执行的脚本先定义好,然后在需要的位置调用即可。定义锚的格式是:&anchor_name,调用锚的格式则是*anchor_name,就是上面说的两个特殊符号&*号的作用,我们来看两个具体示例:

.job_template: &job_configuration  # 隐藏定义一个叫做'job_configuration'的锚的内容
  image: ruby:2.6
  services:
    - postgres
    - redis

test1:
  <<: *job_configuration           # 合并 'job_configuration' 别名的内容
  script:
    - test1 project

test2:
  <<: *job_configuration           # 合并 'job_configuration' 别名的内容
  script:
    - test2 project

在上面这个示例里不光演示了锚的定义和使用,还定义了字典合并符号<<的使用。这里来详细介绍一下。在上面的示例里首先定义了一个隐藏任务模板.job_template,所谓的隐藏任务就是在任务名称前面加上一个点号.,然后在Gitlab的UI界面就看不到这个任务对应的图标,在实际运行的时候也不会跑这个任务。隐藏任务一般是用来当做模板使用,就像上面例子里的用法。 隐藏任务定义好以后,在它名称后面使用&job_configuration定义一个锚叫做job_configuration,它下面的内容就是模板的内容。 然后在下面的任务test1和test2中,使用字典合并符号<<:*job_configuration,表示要在这个位置导入隐藏任务模板里的内容。当正式开始运行以后,解析出来的实际任务是:

.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

表示脚本的锚

锚的另外一个常用方式是,将多个任务里都使用的命令抽出来,定义为锚,然后在任务里通过锚的名称直接调用,看下面的示例:

.some-script-before: &some-script-before
  - echo "Execute this script first"

.some-script: &some-script
  - echo "Execute this script second"
  - echo "Execute this script too"

.some-script-after: &some-script-after
  - echo "Execute this script last"

job1:
  before_script:
    - *some-script-before
  script:
    - *some-script
    - echo "Execute something, for this job only"
  after_script:
    - *some-script-after

job2:
  script:
    - *some-script-before
    - *some-script
    - echo "Execute something else, for this job only"
    - *some-script-after

在上面的示例中,就是将多个不同位置的脚本都抽出来,定义成锚,然后在下面的任务里直接调用。

用于变量的锚

在Gitlab的官方文档上,锚的最后一种用法是,将多个位置都要使用的同名变量抽出来定义为锚,然后直接调用,给出的示例是:

variables: &global-variables
  SAMPLE_VARIABLE: sample_variable_value
  ANOTHER_SAMPLE_VARIABLE: another_sample_variable_value

# a job that must set the GIT_STRATEGY variable, yet depend on global variables
job_no_git_strategy:
  stage: cleanup
  variables:
    <<: *global-variables
    GIT_STRATEGY: none
  script: echo $SAMPLE_VARIABLE

但是在这里我感觉它给出的例子非常不恰当,例如下面的代码:

variables: 
  SAMPLE_VARIABLE: sample_variable_value
  ANOTHER_SAMPLE_VARIABLE: another_sample_variable_value

# a job that must set the GIT_STRATEGY variable, yet depend on global variables
job_no_git_strategy:
  stage: cleanup
  variables:
    GIT_STRATEGY: none
  script: echo $SAMPLE_VARIABLE

在这里我不需要直接定义锚,也可以直接使用variables关键字实现变量GIT_STRATEGY的定义。也可以直接通过$SAMPLE_VARIABLE实现对全局变量SAMPLE_VARIABLE的调用。目前还没看出这两种方式的区别,如果后面验证了这两个位置的区别,再来更新这部分的文档。

以上就是今天弄懂的一些东西

cache缓存和artifacts工件

缓存是任务下载和保存的一个或多个文件,随后的任务可以直接使用这部分文件,用以加快执行速度。而工件也是任务执行过程中保存下来的文件。

缓存和工件的区别

缓存一般是用于依赖,像从网上下载下来的依赖包。缓存一般是存储在runner所在的机器上(如果是容器方式,则会新开一个容器,专门用来存放缓存数据),如果开启了分布式缓存技术,还能够存放到s3存储上。

而工件则一般是用来保存不同阶段构建时生成的一些中间文件,是由任务生成,保存在gitlab所在主机上,可以在gitlab页面的GUI 流水线里下载。(保存在什么位置还不知道)

缓存和工件都会定义相对于项目根目录的文件路径,并且都不能链接到项目外部的文件。

缓存的保存位置

所有任务里里定义的缓存都打包到一个cache.zip文件里。runner配置会定义在哪里存储这个文件。默认情况下,缓存是存储在runner安装机器上,而位置也会根据执行器(executor)的不同而不同,具体的情况如下所示:

runner执行器类型缓存的路径
shell(linux主机类型)本地目录,在gitlab-runner用户的家目录:/home/gitlab-runner/cache/<user>/<project>/<cache-key>/cache.zip.
Docker (容器类型)Docker卷下,路径是:/var/lib/docker/volumes/<volume-id>/_data/<user>/<project>/<cache-key>/cache.zip
Docker主机(自动扩展runner,例如k8s)和Docker一样

如果缓存和工件都使用了同一个目录,那么缓存可能会覆盖工件的内容,因为缓存更先存储。

缓存打包和解压是怎么执行的?

下面是两个不同阶段的任务示例:

stages:
  - build
  - test

before_script:
  - echo "Hello"

job A:
  stage: build
  script:
    - mkdir vendor/
    - echo "build" > vendor/hello.txt
  cache:
    key: build-cache
    paths:
      - vendor/
  after_script:
    - echo "World"

job B:
  stage: test
  script:
    - cat vendor/hello.txt
  cache:
    key: build-cache
    paths:
      - vendor/

如果在一个机器上安装了一个runner,那么你项目里的所有任务都会运行在相同的主机上,任务执行流程是:

  1. 流水线执行
  2. job A开始运行
  3. 执行before_script脚本
  4. 执行script脚本
  5. 执行after_script脚本
  6. cache部分运行,vendor/目录会被打包压缩到cache.zip,这个文件然后会被保存到上面说到的目录下(根据执行器和cache: key的不同而不同)
  7. job B运行
  8. 缓存解压(如果根据cache: key在对应目录下找到了)
  9. before_script脚本执行
  10. script脚本执行
  11. 流水线结束。

缓存的清除

Runner会使用缓存来加速任务的执行,因为它可以复用数据,但是有时候会导致一些不一致的问题,因此需要清除缓存。有两种方式:

1. 修改cache:key来清除缓存

在.gitlab-ci.yml文件里修改cache: key的值,下一次流水线运行时,缓存就会存储在不同的位置(旧的缓存是否会被清除,待验证)

2. 手动清除缓存

在Gitlab的web界面,按下面流程执行:

  • 在顶部,选择Menu->Projects,找到你的项目
  • 在左侧边栏,点击CI/CD->Pipelines页
  • 在右上方,点击Clear runner caches

下一次提交代码的时候,你的CI/CD任务会使用一个新的缓存。

你认为这篇文章怎么样?
  • 0
  • 0
  • 0
  • 0
  • 0
  • 0
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v3.1.3