Azure Pipelines 架构解析

介绍

Azure Pipelines 是 Azure DevOps(SaaS 平台)的一个组件,是一个自动化 CI/CD 的“流水线”。类似的技术有GitHub ActionsJenkins

作为使用者,我们只需要在 YAML 中定义各个任务,触发 Pipeline,Azure Pipelines 就能帮我们自动执行这些任务。

那么,任务是怎么被执行的呢?是否存在并行任务上限?我们怎样设计更高效地定义 YAML,从而让一次 Pipeline 更快地跑完呢?—— 本文将从任务调度的角度,解析 Azure Pipelines 的架构。在探讨的过程中,上述问题自然能得到解答。

Azure Pipelines 术语

img

对于 Azure Pipelines 的使用者,一个 YAML 文件唯一定义了一条 Pipeline 的运行逻辑:

  • 一条 Pipeline 可以包含多个 Stage
  • 一个 Stage 可以包含多个 Job
  • 一个 Job 可以包含多个 Step
  • 一个 Step 可以包含多个 task

对于 Azure Pipelines 本身,当一条 Pipeline 被触发,它需要调用计算资源,去执行 Pipeline 的逻辑。具体来说,会有一台机器(agent)负责执行一个 job。

一个 job 只会在一台 agent 上跑,它不会被进一步地拆分。而一条 Pipeline 的所有 job 可能会分配给不同的 agent 去执行。

这种分配是如何完成的呢?

Azure Pipelines 任务调度

Azure Pipelines 的任务调度是一种典型的分布式任务调度模型(类似的技术有QUARTZxxl-job):

  • Azure Pipelines 上有一个**『任务队列』**,里面存放着所有待运行的 job(既指任务调度模型中的 Job/Task,又指 Pipeline 中的 job 术语)
  • Agent 是 Worker,它会定时地查看**『任务队列』**中是否有 job 要运行。如果有,则某一台 Agent 会竞争获得执行该 Job 的权利,同时获得访问相应资源的权限。此时,它就可以开始执行这个 job 了。

因此,一条 Pipeline 是这样执行的:

  1. 触发 Pipeline。Azure Pipelines 解析 YAML,拆分为多个 Job,存入『任务队列』中。
  2. 假如此时有空闲的 Agent,说明『任务队列』此时为空。那么,它监听到了任务队列中加入新 job 后,开始与其他 Agent(假如有的话)竞争执行该 job。
    • 竞争成功,开始执行;
    • 竞争失败,继续监听;
  3. 假如此时没有空闲的 Agent,则说明『任务队列』之前已经有 job 了,或者没有 Agent 跑完自己之前领取的 job。那么这些新产生的 job 就会有序地等待 Agent 来执行。(job 按照 Pipeline 触发的时间来排队等待,因此是公平的)

Parallel Jobs

任务调度框架意味着并行(parallel)。这也意味着,我们在定义 YAML 时,应该将没有依赖的 Steps 或 Tasks 提升为 Jobs,使得它们能够并行执行,缩短运行时间

在实习过程中,却鲜有人这样做,大家都用着默认的 Steps 级别,即使大部分 Agents 经常是空闲的。🤔

那么,既然一个新 job 能否立刻执行取决于空闲 Agent 的存在,增加 Agent 的数量是否就能提高整体的吞吐量(单位时间内执行完成的 job 数量)呢?—— 很遗憾,并不是。

微软毕竟要靠 Azure 盈利的,他们又提出了 『Parallel Jobs』这个概念,表示能并行执行的 job 上限。这个量是需要购买的。

也就是说,如果想并行跑 job,我们不仅需要购买 Azure VM agents,还需要购买 Parallel Jobs。即使我们自己有很多台机器,也还是需要购买 Parallel Jobs。

参考链接