CI/CD development documentation
CI/CD development documentation
此处列出了特定于 CI / CD 的开发指南.
如果要创建新的 CI / CD 模板,请阅读GitLab CI / CD 模板的开发指南 .
CI Architecture overview
以下是 CI 体系结构的简化图. 为了集中在主要组件上,省略了一些细节.
在左侧,我们有一些事件可以根据各种事件触发管道(由用户或自动化触发):
git push
是触发管道的最常见事件.- The Web API.
- 用户单击 UI 中的”运行管道”按钮.
- 创建或更新合并请求时 .
- 将 MR 添加到合并列车时 .
- A scheduled pipeline.
- 当项目被订阅到上游项目时 .
- 启用自动 DevOps 时 .
- 当 GitHub 集成用于外部请求请求时 .
- 当上游管道包含桥接作业时 ,该作业会触发下游管道.
触发任何这些事件将调用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
状态,这在以下情况下可能发生:
- 作业是在管道的第一阶段创建的.
- 该作业需要手动启动,并且已被触发.
- 前一阶段的所有作业均已成功完成. 在这种情况下,我们将所有工作从下一阶段过渡到
pending
. - 该作业使用
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
,该命令:
- 从
pending
作业池中选择要运行的下一个作业 - 分配给跑步者
- 通过 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
运行者只能选择针对受保护的分支(例如生产部署)运行的作业.
当我们增加池中的”奔跑者”数量时,如果将同一工作分配给不同”奔跑者”,也会增加发生冲突的机会. 为防止这种情况,我们会适当地挽救冲突错误并在列表中分配下一个作业.