添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

GitLab CI/CD 是 GitLab 内置的持续集成/持续交付产品。本文将对 GitLab CI/CD 进行简要功能分析和实践。

顾名思义,GitLab CI/CD 由 CI 和 CD 两部分组成:

  • GitLab CI 用于在一个共享仓库内整合团队开发的代码。开发者通过 MR 提交代码,在合并代码前 MR 会触发一个流水线,构建、测试、验证新产生的代码。
  • GitLab CD 通过结构化的部署流水线来确保 CI 环节验证通过的代码的交付。

示意图如下:

image.png

核心概念

GitLab CI/CD 主要有四个核心概念,分别为 Pipelines(流水线)、Stages(阶段)、Jobs(任务)、Runners(运行器)。

Pipelines

Pipelines 是持续集成、持续交付、持续部署的最上层概念。

Pipeline 包含:

  • Jobs,定义具体要执行的内容。例如,编译或测试。
  • Stages,定义何时执行 Jobs。例如,测试 stage 在编译 stage 之后执行。
类型
  • 基础流水线。

Stages 串行执行,同一个 Stage 中的 Jobs 并行执行。

image.png
  • DAG(有向不循环图)流水线。

通过 needs 属性定义不同 Stage 中的 Jobs 之间的依赖关系,存在依赖关系的 jobs 的执行只需要等待依赖的 jobs 执行完毕就可以执行,而不用等待前一个 stage 中的所有 jobs 执行完毕。

image.png

GitLab CI/CD 通过依赖可视化(Needs Visualization)来展示 DAG 中 jobs 的依赖关系。示例:

  • 多项目流水线。

同一个流水线可以基于多个项目。

  • 父子流水线。

一个父流水线,可以触发多个子流水线。

image.png
  • MR 流水线。

只有 MR 才会触发该流水线。

  • 合并结果(Merged Results)流水线。

通常情况下,当一个 MR 发生,流水线会在源分支上运行。合并结果流水线,则会在一个合并了源分支和目标分支之后的 commit 上运行,该 commit 既不存在在源分支,也不存在在目标分支上。

  • 合并队列(Merge Trains)流水线。

合并队列(Merge Trains)流水线可以让多个合并结果流水线按照顺序先后执行,从而保证主干分支的纯净。

Stages

Stages 是 Jobs 上级,定义何时执行 Jobs。例如,测试 stage 在编译 stage 之后执行。多个 stages 只能串行执行。

Jobs

Jobs 是流水线最基础单元。Jobs 由运行器(Runners) 执行。同一个 stage 下的 jobs 默认并行。如果一个 stage 下的所有 jobs 执行成功,则继续执行下一个 stage。如果一个 stage 下任何一个 job 失败,流水线则终止执行。

Runners

Runners 是一个轻量的、高度可拓展的用于运行 Job 的代理。

流水线的必要条件

运行一个 GitLab CI/CD 流水线的必要条件有 2 个:

  • 在仓库根目录创建一个 .gitlab-ci.yml 文件;
  • 有可运行的运行器。

下面对 .gitlab-ci.yml 文件和运行器进行介绍。

.gitlab-ci.yml 文件

.gitlab-ci.yml 文件是用于定义流水线的 YAML 文件。主要定义:

  • Jobs 的结构和执行顺序;
  • 运行器在特定情况下如何运行。

YAML 语法,详见文档:https://docs.gitlab.com/ee/ci/yaml/README.html

YAML 示例

为了先让大家对 .gitlab-ci.yml 有个感知,先看 .gitlab-ci.yml 的一个简单示例。

stages:
  - build
  - test

build-code-job:
  stage: build
  script:
    - echo "Check the ruby version, then build some Ruby project files:"
    - ruby -v
    - rake

test-code-job1:
  stage: test
  script:
    - echo "If the files are built successfully, test some files with one command:"
    - rake test1

test-code-job2:
  stage: test
  script:
    - echo "If the files are built successfully, test other files with a different command:"
    - rake test2

触发方式

GitLab CI/CD 有以下触发方式:

  • 代码变更触发(Push/PulllRequest)
  • 手动页面触发
  • 定时触发
  • API 触发

普通变量 & 传参 & 环境变量

GitLab CI/CD 的所有变量均会存储为环境变量,并且通过环境变量的方式引用。

变量引用方式,如下:

test_variable:
  stage: test
  script:
    - echo $CI_JOB_STAGE

普通变量

预定义变量

Predefined CI/CD variables

GitLab CI/CD 有一套无需额外声明的默认预定义变量,包含仓库信息、用户信息、分支名、流水线 id、commit ids 等等。预定义变量列表:https://docs.gitlab.com/ee/ci/variables/predefined_variables.html 。

自定义变量

Custom CI/CD variables

变量可以通过 UI、API 或者在 .gitlab-ci.yml 文件中定义。

自定义变量有两种类型:

  • 变量类型

针对变量类型的变量,运行器会创建 key 和 value 相同的环境变量。

  • 文件类型

针对文件类型的变量,运行器会创建 key 相同的环境变量,并将变量值存储在临时文件中,value 设置为该临时文件路径。

在 .gitlab-ci.yml 中设置自定义变量:

variables:
  TEST: "HELLO WORLD"

在 .gitlab-ci.yml 中使用自定义变量:

script:
  - echo "$TEST"

组级别变量

Group-level CI/CD variables

通过 UI 在 GitLab Group 中设置的变量,对 Group 内的项目有效。

实例级别变量

Instance-level CI/CD variables

可通过 UI 或 API 来定义实例级别的变量。

此处所说的实例可以是一个 group 或者 一个 project 。设置后,针对该实例下的所有项目生效。

继承变量

通过 dependencies / needs 设置依赖 jobs,来自依赖 jobs 的变量被称为继承变量。

示例:

build:
  stage: build
  script:
    - echo "BUILD_VERSION=hello" >> build.env
  artifacts:
    reports:
      dotenv: build.env

deploy:
  stage: deploy
  script:
    - echo $BUILD_VERSION  # => hello
  dependencies:
    - build

变量优先级

优先级从高到底,如下:

  • 定时任务或手动触发的变量;
  • 项目级别变量;
  • 组级别变量;
  • 实例级别变量;
  • 继承变量;
  • YAML 中定义的 Job 级别局部变量;
  • YAML 中定义的全局变量;
  • 部署变量;
  • 预定义变量。

环境变量

GitLab CI/CD 中所有变量都以环境变量形式存在,因此环境变量的定义和使用参见上文中的普通变量。

传参

Gitlab CI/CD 没有专门的传参概念,使用了预定义变量(Predefined CI/CD variables)代替。

上下文

支持实例级别、组级别、项目级别、YAML 全局、YAML 局部等上下文。

表达式

GitLab CI/CD 支持含有以下运算符的表达式:

  • && 与
  • || 或
  • () 小括号
  • == 等于
  • != 不等于
  • =~  正则匹配
  • !~ 正则不匹配

内容执行

GitLab CI/CD 通过 script 来定义要执行的内容,可执行内容只能为 shell 命令或脚本。

例如:

job1:
  script: "bundle exec rspec"
  
job2:
  script:
    - uname -a
    - bundle exec rspec
 
job3:
  script:
    - 'curl --request POST --header "Content-Type: application/json" "https://gitlab/api/v4/projects"'

产物 & 缓存

产物

产物用于存储 stages 结果,用于跨 stages 传递。通过 artifacts 设置。

产物是由 job 产生的文件,可以在同一流水线中的后序 stage 中使用前序 stage 中的 job 产生的产物,但是不能在同一个 stage 中的不同 job 中使用产物。同时,产物也不能跨流水线使用。

只能设置成 job 局部有效,不能设置成全局有效。

可通过 expire 设置产物的失效时间,默认 30 天有效,产物失效后会被删除。

缓存

缓存用于存储项目依赖。通过 cache 设置。可以加速后序流水线的运行。

缓存可以通过 cache: 设置为全局或 job 局部有效。

设置为全局有效,则当前和后序流水线全局都可以使用该缓存。

设置为 job 局部有效,则:

  • 同一个流水线中的后序 job 可以使用;
  • 后序流水线的同一个 job 可以使用。

使用示例:

#
# https://gitlab.com/gitlab-org/gitlab/tree/master/lib/gitlab/ci/templates/Nodejs.gitlab-ci.yml
#
image: node:latest

# Cache modules in between jobs
cache:
  key: ${CI_COMMIT_REF_SLUG}
  paths:
    - .npm/

before_script:
  - npm ci --cache .npm --prefer-offline

test_async:
  script:
    - node ./specs/start.js ./specs/async.spec.js

暂停 & 续跑

可通过设置 when:manual 来实现流水线的自动暂停和手动续跑。

串并行

Stages 只能串行,同一 Stage 下的 Jobs 只能并行,通过 needs 定义不同 Stage 中的 Jobs 之间的依赖关系,存在依赖关系的 Jobs 的执行可不用等待前一个 Stage 中的所有 Jobs 执行完毕就可执行。

条件判断

可通过 rules:if / except / only / when 等配置当前 job 是否会被执行。

示例:

job1:
  script: echo "Hello, Rules!"
  rules:
    - if: '$CI_PIPELINE_SOURCE == "schedule"'
      when: manual
      allow_failure: true
    - if: '$CI_PIPELINE_SOURCE == "push"'
    
job2:
  # use regexp
  only:
    - /^issue-.*$/
  # use special keyword
  except:
    - branches
    
deploy_prod:
  stage: deploy
  script:
    - echo "Deploy to production server"
  environment:
    name: production
    url: https://example.com
  when: manual
  only:
    - master

重试 & 终止

自动重试

支持,通过 retry 设置

示例:

test:
  script: rspec
  retry: 2

手动重试

可在 UI 上手动重试整条流水线或某个 job 。

日志能力

GitLab CI/CD 提供了一些很有用的日志能力:

日志颜色

job 中的 script 可以使用  ANSI escape codes 为输出日志添加想要的颜色。例如:

job1:
  script:
    - echo -e "\e[31mThis text is red,\e[0m but this text isn't\e[31m however this text is red again."

还可以在 before_script 通过环境变量定义日志颜色,然后再 script 中引用。例如:

job1:
  before_script:
    - TXT_RED="\e[31m" && TXT_CLEAR="\e[0m"
  script:
    - echo -e "${TXT_RED}This text is red,${TXT_CLEAR} but this part isn't${TXT_RED} however this part is again."
    - echo "This text is not colored"

日志折叠

在海量日志场景下,日志折叠能力非常实用。GitLab CI/CD 可通过特殊标记来让用户自定义要折叠的日志:

  • 折叠区域开始标记:

\e[0Ksection_start:UNIX_TIMESTAMP:SECTION_NAME\r\e[0K + TEXT_OF_SECTION_HEADER

  • 折叠区域结束标记:

\e[0Ksection_end:UNIX_TIMESTAMP:SECTION_NAME\r\e[0K

示例:

job1:
  script:
    - echo -e "\e[0Ksection_start:`date +%s`:my_first_section\r\e[0KHeader of the 1st collapsible section"
    - echo 'this line should be hidden when collapsed'
    - echo -e "\e[0Ksection_end:`date +%s`:my_first_section\r\e[0K"

另外,还可以通过增加 [collapsed=true] 来控制日志展示时默认折叠自定义折叠区域。例如:

job1:
  script:
    - echo -e "\e[0Ksection_start:`date +%s`:my_first_section[collapsed=true]\r\e[0KHeader of the 1st collapsible section"
    - echo 'this line should be hidden automatically after loading the job log'
    - echo -e "\e[0Ksection_end:`date +%s`:my_first_section\r\e[0K"

运行器

在 GitLab CI/CD 中,运行器(Runner)是一个轻量的、高度可拓展的用于运行 Job 的代理。

GitLab Runner 由 Go 语言编写。可以运行在任何支持构建 Go 二进制的任何平台上,包括 Linux、macOS、Windows、FreeBSD 和 Docker。也可以运行在一个 Docker 容器内部或一个 Kubernetes 集群上。

支持多语言,包括 .Net、Java、Python、C、PHP 等等。

运行器类型

运行器有以下三个类型:

  • 通用运行器,适用于所有 groups 和 projects;
  • 组运行器,适用于某个 group 下的所有 projects 和子 group;
  • 特定运行器,只适用于特定 project 。

运行器版本

GitLab Runner 的版本需要和 GitLab 版本保持一致。不然可能带来一些不兼容问题。

运行器注册

私有运行器在使用前必须先注册。注册运行器时,必须选择一个执行器(executor)。

执行器

Executors

执行器定义每个 job 运行所在的环境。

有以下执行器可供选择:

Executor SSH Shell VirtualBox Parallels Docker Kubernetes Custom
Clean build environment for every build conditional (4)
Reuse previous clone if it exists conditional (4)
Runner file system access protected (5) conditional
Migrate runner machine partial partial
Zero-configuration support for concurrent builds ✗ (1) conditional (4)
Complicated build environments ✗ (2) ✓ (3) ✓ (3)
Debugging build problems easy easy hard hard medium medium medium

详见:https://docs.gitlab.com/runner/executors/README.html

运行器注册和执行器指定示例

# 使用 ruby:2.6 Docker 镜像注册运行器,并运行两个服务:postgres:latest 和 mysql:latest 。

cat > /tmp/test-config.template.toml << EOF
[[runners]]
[runners.docker]
[[runners.docker.services]]
name = "postgres:latest"
[[runners.docker.services]]
name = "mysql:latest"
EOF

sudo gitlab-runner register \
  --url "https://gitlab.example.com/" \
  --registration-token "PROJECT_REGISTRATION_TOKEN" \
  --description "docker-ruby:2.6" \
  --executor "docker" \
  --template-config /tmp/test-config.template.toml \
  --docker-image ruby:2.6

运行器执行流

下图展示了运行器如何被注册和调用。

在 .gitlab-ci.yml 中指定运行器

当注册运行器时,可以为运行器增加 tags 。

在 .gitlab-ci.yml 中为 jobs 指定 tags ,则当 jobs 运行时,就会使用指定 tags 的运行器。

例如:

job:
  tags:
    - ruby # 指定使用 tags 为 ruby 的运行器

在 .gitlab-ci.yml 中定义 image 和 services

在 .gitlab-ci.yml 中定义的 image 和 services 对所有 jobs 生效。

default:
  image: ruby:2.6

  services:
    - postgres:11.7

  before_script:
    - bundle install

test:
  script:
    - bundle exec rake spec

云原生能力

GitLab CI/CD 支持云原生

  • 使用不可变的 Docker 容器镜像定义 CI 任务环境,以便达到一致性和可复现性
  • 利用 k8s 集成来实现诸如 自动 DevOps、部署面板、审查应用程序、灰度部署等特性
  • 使用云部署模板轻松部署云容器和 k8s 环境

多语言能力

鉴于 GitLab CI/CD 的云原生能力,可以通过 Docker 镜像消除不同编程语言之间的隔阂,实现多语言的支持,包括 Node、.Net、Java、Python、C、PHP 等等

效率管理

流水线的效率直接影响了开发&交付效率,因此做好流水线的效率管理对于提升开发团队的开发&交付效率至关重要。

识别和规避流水线的运行瓶颈和高频错误,是做好流水线效率管理必要前提。

为了更好的帮助用户快速识别瓶颈和高频错误,GitLab CI/CD 还提供了可视化和分析大盘能力。

识别瓶颈和高频错误

辨别流水线是否高效的最简单的方法是看流水线整体、stages、jobs 的运行时间。

对流水线运行时间影响最大的因素有:

  • stages 和 jobs 的数量;
  • jobs 之间的串行依赖关系;
  • 流水线的关键路径,即流水线中运行时间最长的路径。

其他和 GitLab Runner 相关的会对运行时间造成影响的因素:

  • 运行器的能力和所能操控的资源;
  • 构建依赖和依赖安装时间;
  • 容器镜像大小;
  • 网络延迟情况;

流水线频繁的非必要失败也会大大降低开发效率,例如:

  • 随机失败的单元测试,或者不可靠的测试结果;
  • 测试覆盖率降低;
  • 可忽略性失败对流水线造成阻断;
  • 在运行时间很长的流水线中,有些本可以提早发生的错误没有尽早发生。

可视化

通过可视化视图可以方便的查看当前流水线编排情况、运行情况和 DAG 依赖关系等,并且可以直接在视图上进行暂停、续跑、重试、日志查看、产物下载等手动操作。

流水线可视化和依赖可视化图:

分析大盘

GitLab CI/CD 分析大盘主要提供历史和实时的流水线运行情况,包括基本信息、运行状态、耗时等。

分析大盘图:

与此同时,分析大盘还提供了性能分析能力,主要性能指标有:

  • 部署频率 。团队成功部署上线的频率。
  • **变更上线时间。**变更代码上线所花费的总时间。
  • **变更失败率。**引发线上问题的部署比例。
  • **线上恢复时间。**当发生线上问题时,线上恢复正常所花时间。

部署频率图:

最佳实践

对了更好的帮助用户做好流水线效率管理,GitLab CI/CD 提供了一些最佳实践建议。

避免非必要运行

  • 配置 interruptible,新运行的流水线可以终止其他运行中的相同流水线的运行;
  • 配置 rules,跳过没必要的运行节点;
  • 合理控制定时流水线的运行频率。

尽早失败

流水线应该合理设计,将运行时间较长的 jobs 尽量后置,运行时间较短或错误率较高的 jobs 尽量前置,做到尽早失败。比如将运行时间很长的构建任务后置在校验检查任务之后。

这样的好处是有效避免在流水线在做了很长时间无用运行之后才出现报错,导致用户白白等待。

有向不循环图(DAG)类型流水线

在基础配置中,jobs 总是会在前置 stage 中的所有 jobs 都完成后再执行。这种配置最简单,通常也是运行最慢的。DAG 类型和父子类型的流水线通常更灵活高效,不过这也让流水线变得更加难以理解和分析,可根据实际情况选择。

缓存

缓存也是一个很有效的提升运行效率的方法。比如缓存 NodeJS 中的 node_modules 。

Docker 镜像

下载和初始化 Docker 镜像在 jobs 执行过程中所花费的时间往往占比很大。

  • 尽量使用多个更小的镜像,而不是一个大而全的服务所有 jobs 的镜像;
  • 尽量降低 Docker 镜像的大小。

GitLab CI/CD 的特性

GitLab CI/CD 拥有以下特性:

  • 多平台

支持 Unix、Windows、macOS 和任何支持 Go 的其他平台

  • 多语言

脚本是命令行驱动的,并支持 Java、PHP、Puby、C 和其他语言

  • 稳定

运行在与GitLab不同的机器上。

  • 并行构建

GitLab将工作负载分布在多台机器上,以提高执行速度。

  • 灵活的流水线

每个阶段均可定义多个并行任务

  • 版本化的流水线

通过 git 来保证 .gitlab-ci.yml 的版本化管理

  • 自动扩容
  • 任务产物

GitLab 运行器上传二进制或者其他任务产物到 GitLab,可以通过 GitLab UI 或者 API 下载

  • 支持 Docker

使用自定义镜像、启动服务、构建新的镜像、甚至运行在 Kubernetes 上

  • 保护变量

加密存储环境保护变量

示例:一个简单的流水线 demo

通过一个简单的流水线 demo,测试上述分析中谈到的 GitLab CI/CD 的核心能力,如变量的定义和引用、表达式的使用、内容执行、产物 & 缓存、暂停 & 续跑、串并行(DAG)、条件判断、重试等。

仓库地址、yaml 配置、可视化执行结果,具体如下:

仓库地址

https://gitlab.com/korbinzhao1/gitlabcicd_demo

.gitlab-ci.yml 文件

# 变量定义
variables:
  DOMAIN: example.com
  WEBHOOK_URL: https://my-webhook.example.com
  IS_COMMIT_MESSAGE_MATCH: $CI_COMMIT_MESSAGE =~ /-draft$/
  IS_PIPELINE_SOURCE_EQUAL_PUSH: '$CI_PIPELINE_SOURCE == "push"'

# stages 执行顺序定义
stages:
  - variable_expression_stage
  - cache_artifact_stage
  - condition_stage
  - pause_continue_stage
  - dag_stage
  - retry_stage

variable_job: # 变量测试
  stage: variable_expression_stage
  script:
    - echo "变量测试"
    - echo $DOMAIN
    - echo $WEBHOOK_URL
    - echo CI_COMMIT_BRANCH $CI_COMMIT_BRANCH

expression_job: # 表达式测试
  stage: variable_expression_stage
  script:
    - echo "表达式测试"
    - echo $IS_COMMIT_MESSAGE_MATCH
    - echo $IS_PIPELINE_SOURCE_EQUAL_PUSH
  rules:
    - if: $DOMAIN == "example.com" # 表达式
   
cache_job: # 缓存测试
  stage: cache_artifact_stage
  script:
    - echo "缓存测试"
  cache:
    paths:
      - node_modules/something2cache/

artifact_job: # 产物测试
  stage: cache_artifact_stage
  script:
    - echo "产物测试"
  artifacts:
    paths:
      - node_modules/something2artifact/

condition1_job: # 条件判断测试
  stage: condition_stage
  script:
    - echo "条件判断测试1"
  rules:
    - if: $CI_COMMIT_MESSAGE =~ /-draft$/

condition2_job: # 条件判断测试
  stage: condition_stage
  script:
    - echo "条件判断测试2"
  when: always
  needs: ['expression_job']

pause_continue_job: # 暂停续跑测试
  stage: pause_continue_stage
  script:
    - echo "暂停续跑测试"
  when: manual

dag_job: # DAG 有向不循环图测试
  stage: dag_stage
  script:
    - echo "DAG 测试"
  needs: ['expression_job', 'variable_job', 'condition2_job']

retry_job: # 失败自动重试测试
  stage: retry_stage
  script:
    - echo "重试测试"
  rules:
    - if: '$CI_PIPELINE_SOURCE == "push"'
  retry: 2     

运行结果

可视化流水线

image.png

DAG 有向不循环图

image.png

产物

image.png

缓存

image.png

参考资料

  • Continuous Integration (CI) with GitLab
  • GitLab CI/CD
  • Pipeline Efficiency
  • Modernize your CI/CD
  • GitLab Runner
  • Pipeline success and duration charts

CI GitLab CD 竞品