CI/CD development documentation

原文:https://docs.gitlab.com/ee/development/cicd/

CI/CD development documentation

此处列出了特定于 CI / CD 的开发指南.

如果要创建新的 CI / CD 模板,请阅读GitLab CI / CD 模板的开发指南 .

CI Architecture overview

以下是 CI 体系结构的简化图. 为了集中在主要组件上,省略了一些细节.

CI software architecture

在左侧,我们有一些事件可以根据各种事件触发管道(由用户或自动化触发):

触发任何这些事件将调用CreatePipelineService ,后者将输入事件数据并触发用户,然后尝试创建管道.

CreatePipelineService很大程度上依赖于YAML Processor组件,该组件负责将 YAML Blob 作为输入并返回管道的抽象数据结构(包括阶段和所有作业). 该组件还可以在处理 YAML 时验证其结构,并返回任何语法或语义错误. 在YAML Processor组件中,我们定义了所有可用于构建管道的关键字 .

CreatePipelineService接收YAML Processor返回的抽象数据结构,然后将其转换为持久化模型(管道,阶段,作业等). 之后,就可以处理管道了. 处理管道意味着按执行顺序(阶段或 DAG)运行作业,直到以下任一情况为止:

  • 所有预期的作业均已执行.
  • 故障会中断管道执行.

处理管道的组件是ProcessPipelineService ,它负责将所有管道的作业移至完成状态. 创建管道时,其所有作业最初都处于created状态. 该服务根据流水线结构查看在created阶段可以处理哪些作业. 然后,它们将它们移到pending状态,这意味着它们现在可以被 Runner 拾取 . 执行作业后,它可以成功完成或失败. 管道中作业的每个状态转换都会再次触发此服务,该服务会寻找下一个要转换为完成的作业. 在此过程中, ProcessPipelineService更新作业,阶段和整个管道的状态.

在图的右侧,我们有一个列表运动员连接到 GitLab 实例. 这些可以是共享运行者,组运行者或项目特定的运行者. Runners 与 Rails 服务器之间的通信通过一组 API 端点(称为Runner API Gateway .

我们可以注册,删除和验证运行器,这也将导致对数据库的读/写查询. 连接了 Runner 之后,它会继续询问要执行的下一个作业. 这将调用RegisterJobService ,后者将选择下一个作业并将其分配给 Runner. 此时,作业将转换为running状态,由于状态更改,该状态再次触发ProcessPipelineService . 有关更多详细信息,请参阅” 作业调度” .

在执行作业时,运行程序将日志以及任何可能需要存储的工件发送回服务器. 此外,作业可能依赖于先前作业中的工件才能运行. 在这种情况下,Runner 将使用专用的 API 端点下载它们.

工件存储在对象存储中,而元数据保留在数据库中. 工件的重要示例是报表(JUnit,SAST,DAST 等),这些报表在合并请求中进行了解析和呈现.

作业状态转换并非全部自动化. 用户可以运行手动作业 ,取消管道,重试特定的失败作业或整个管道. 导致作业更改状态的任何事件都将触发ProcessPipelineService ,因为它负责跟踪整个管道的状态.

一种特殊类型的作业是桥接作业 ,当过渡到pending状态时,该作业在服务器端执行. 这项工作负责创建下游管道,例如多项目或子管道. 每次触发下游管道时,工作流程循环都将从CreatePipelineService重新开始.

Job scheduling

创建管道时,将为所有阶段一次创建所有作业,初始状态为created . 这使得可视化管道的全部内容成为可能.

跑步者将不会看到具有created状态的作业. 为了能够将作业分配给 Runner,该作业必须首先转换为pending状态,这在以下情况下可能发生:

  1. 作业是在管道的第一阶段创建的.
  2. 该作业需要手动启动,并且已被触发.
  3. 前一阶段的所有作业均已成功完成. 在这种情况下,我们将所有工作从下一阶段过渡到pending .
  4. 该作业使用needs:指定了 DAG 依赖项needs:并且所有依赖项都已完成.

连接了 Runner 时,它将通过连续轮询服务器来请求下一个pending作业运行.

注意: Runner 用于与 GitLab 交互的 API 端点在lib/api/runner.rb中定义

服务器收到请求后,将根据Ci::RegisterJobService算法选择pending作业,然后将其分配并发送给 Runner.

在当前阶段完成所有作业后,服务器通过将其状态更改为” pending “,从下一阶段”解锁”所有作业. 现在,当 Runner 请求新作业时,可以由调度算法选择这些内容,并像这样继续进行,直到完成所有阶段.

Communication between Runner and GitLab server

使用注册令牌注册了 Runner 之后,服务器便知道其可以执行的作业类型. 这取决于:

  • 它注册的赛跑者类型为:
    • 共享跑步者
    • 团体赛跑者
    • 项目特定的跑步者
  • 任何关联的标签.

跑步者通过请求作业执行POST /api/v4/jobs/request来启动通信. 尽管轮询通常每隔几秒钟发生一次,但如果作业队列不变,我们将通过 HTTP 标头利用缓存来减少服务器端的工作量.

该 API 端点运行Ci::RegisterJobService ,该命令:

  1. pending作业池中选择要运行的下一个作业
  2. 分配给跑步者
  3. 通过 API 响应将其呈现给 Runner

Ci::RegisterJobService

此服务使用 3 个顶级查询来收集大多数作业,并且根据 Runner 注册到的级别选择它们:

  • 选择共享的 Runner(实例级别)的作业
  • 选择组级别运行器的作业
  • 选择项目亚军的工作

This list of jobs is then filtered further by matching tags between job and Runner tags.

注意:如果作业包含标签,则与所有标签都不匹配的跑步者将不会选择该作业. 跑步者可能具有比该工作定义的标签更多的标签,但反之则没有.

最后,如果 Runner 仅能选择带标签的作业,则所有未带标签的作业都会被过滤掉.

在这一点上,我们遍历剩余的pending作业,然后尝试根据其他策略分配”可以选择” Runner 可以选择的第一个作业. 例如,标记为protected运行者只能选择针对受保护的分支(例如生产部署)运行的作业.

当我们增加池中的”奔跑者”数量时,如果将同一工作分配给不同”奔跑者”,也会增加发生冲突的机会. 为防止这种情况,我们会适当地挽救冲突错误并在列表中分配下一个作业.