Azure Pipelines 架构解析
介绍
Azure Pipelines 是 Azure DevOps(SaaS 平台)的一个组件,是一个自动化 CI/CD 的“流水线”。类似的技术有GitHub Actions 和 Jenkins。
作为使用者,我们只需要在 YAML 中定义各个任务,触发 Pipeline,Azure Pipelines 就能帮我们自动执行这些任务。
那么,任务是怎么被执行的呢?是否存在并行任务上限?我们怎样设计更高效地定义 YAML,从而让一次 Pipeline 更快地跑完呢?—— 本文将从任务调度的角度,解析 Azure Pipelines 的架构。在探讨的过程中,上述问题自然能得到解答。
Azure Pipelines 术语
对于 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 的任务调度是一种典型的分布式任务调度模型(类似的技术有QUARTZ 和 xxl-job):
- Azure Pipelines 上有一个**『任务队列』**,里面存放着所有待运行的 job(既指任务调度模型中的 Job/Task,又指 Pipeline 中的 job 术语)
- Agent 是 Worker,它会定时地查看**『任务队列』**中是否有 job 要运行。如果有,则某一台 Agent 会竞争获得执行该 Job 的权利,同时获得访问相应资源的权限。此时,它就可以开始执行这个 job 了。
因此,一条 Pipeline 是这样执行的:
- 触发 Pipeline。Azure Pipelines 解析 YAML,拆分为多个 Job,存入『任务队列』中。
- 假如此时有空闲的 Agent,说明『任务队列』此时为空。那么,它监听到了任务队列中加入新 job 后,开始与其他 Agent(假如有的话)竞争执行该 job。
- 竞争成功,开始执行;
- 竞争失败,继续监听;
- 假如此时没有空闲的 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。