毕业论文
您现在的位置: 版本控制 >> 版本控制介绍 >> 正文 >> 正文

现代CI系统太复杂,方向跑偏了

来源:版本控制 时间:2022/8/17
北京治疗白癜风到底需要多少钱 http://m.39.net/news/a_5214886.html

与几年前相比,现在的CI平台要强大得多。总的来说,这是一件好事。借助强大的CI平台,软件公司和开发人员可以更频繁地发布更可靠的软件,这对软件用户或客户来说是有利的。一些集中式CI平台(如GitHubActions、GitLabPipelines和Bitbucket)带来了规模效益,互联网提供了有关如何使用它们的信息。只要搜索一下如何在CI平台Y上执行X操作,就可以找到一些可以直接复制和粘贴的代码。毕竟,没有人愿意为了CI配置问题浪费太多时间,他们只是想快速发布产品。

现代的CI系统太复杂了

CI平台的进步是以增加复杂性为代价的,我越来越觉得现代CI系统太复杂了。

从根本上讲,CI平台是一种远程代码执行服务,执行代码是为了构建、测试和发布软件。因此,CI平台通常会提供一系列增值特性,让你能更轻松地发布软件。这里有很多不同的方法和商业模式,其中一种常见的增值特性是使用某种类型的配置文件(通常是YAML),它本身提供了常见的功能,例如配置版本控制系统的检出,并指定要运行的命令,而我遇到的问题就是从这里开始。

现代CI平台的YAML配置很强大。以下是GitHubActions工作流YAML的一些特性:

一个嵌入式模板系统,可以将源YAML扩展为最终可用的YAML文档,包括一种自定义的迷你表达式语言。

作业触发器

命名变量

根据条件执行作业

作业之间的依赖关系

定义基于Docker的运行时环境

加密秘钥

构成每个作业的步骤以及这些步骤应该执行哪些动作

如果我们稍微扩展一下范围,把GitHub提供的Actions包含进来,就会有:

执行Git检出

存储工作流/作业使用的工件

缓存工作流/作业使用的工件

安装通用编程语言和环境(如Java、Node.js、Python和Ruby)

还有更多

当然还有第三方Actions,而且有很多!

这里有很多特性是必需的,我很难说出哪一个是多余的。所有这些特性对于足够强大的CI产品来说似乎都是必需的。如果你的产品不提供其中某些特性,就没有人会用它。

那么,我想要抱怨的是什么呢?

我假定一个CI系统复杂到与构建系统变得难以区分。那么,你能说服我或你自己:GitHubActions、GitLabCI和其他CI系统都不是构建系统吗?那些基础元素都有了,GitHubActions工作流由作业组成,作业由步骤组成,就像Makefile由规则组成,规则由命令组成。CI系统和构建系统之间主要的区别在于形式和执行模型(传统上看,构建系统是在本地,是单机的,而CI系统是在远程,是分布式的)。

然后,我们反过来想:一个构建系统复杂到与CI系统变得难以区分。前面我说过,CI系统是一种远程执行代码的服务。虽然从传统上看,构建系统是在本地运行(因此不是服务),但现代的构建系统(如Bazel、Buck、Gradle)完全不一样。Bazel将远程执行和远程缓存作为内置特性,而这些也是现代CI系统的内置功能!如果我用Bazel建立了一个构建系统,然后定义一个服务器端Git推送钩子,让远程服务器触发Bazel进行构建、运行测试并将结果发布到某处,那么这就变成了一个CI系统吗?我想是的!虽然很粗糙,但我认为它就是一个CI系统。

如果你有仔细阅读,就会得出这样的结论:足够复杂的CI系统和足够复杂的构建系统在我看来是一样的。两者都提供了一个服务器池,提供了通用的计算/执行功能和构建/交付软件的特性,如任务间工件交换、缓存、依赖关系和用于定义任务的迷你语言。

现代CI系统让我感到困扰的地方是:我觉得自己是在重新创造一个构建系统,并将构建系统的逻辑碎片化了。CI配置不可避免地会转化为一堆复杂的YAML,其中包含各种缓存和依赖关系优化,以便保持较短的执行时间和可靠性——就像构建系统一样。你会发现,你的构建系统有了CI系统的味道,反之亦然。你最终需要管理两个复杂的平台/系统,而不是一个。

因为构建系统比CI系统更为一般化(我认为一个足够高级的构建系统可以做的事情是一个足够复杂的CI系统的超集),这意味着如果构建系统足够高级,那么CI系统就是冗余的。所以,这篇文章的标题可以进一步:CI系统不是太复杂了,而是说它们就不应该存在。CI特性应该作为构建系统的扩展。

除了冗余问题,我认为对系统进行统一对用户来说更为友好。将CI系统集成到构建系统中(作为常规开发工作流的一部分),可以更容易地将CI系统的全部功能暴露给开发人员。请想象一下,你可以在不将变更推到远程服务器的情况下直接运行CI作业,就像在本地进行构建或测试一样。这样可以极大地缩短变更周期。

但请不要误解我的意思,CI系统的某些功能在构建系统中是找不到的(比如集中式结果报告和用于触发作业的UI/API),它们绝对是有必要存在的。当然,远程计算和作业定义对于构建系统来说是完全冗余的。

现代CI产品的方向跑偏了

如果你假设构建系统和CI系统之间很相似,就会发现很多现代CI产品(如GitHubActions、GitLabCI和其他产品)的方向跑偏了:它们被定义成用来运行CI系统的特定领域平台。实际上,它们应该退后一步,被定位成构建系统(可能还包括批处理作业,比如数据仓库/数据管道中常见的那些)所需的更广泛的通用计算平台。

在这个层面上,每一个CI产品都是不一样的。我甚至认为GitHubActions是一个CI产品,而不是一个平台。下面我来解释一下为什么。

在我看来,在一个理想的CI平台上,我能够要求执行一组特别的任务。我能够使用API来定义任务,让平台运行它们、上传工件、报告任务结果以便执行其他依赖任务,等等。

GitHubActions有一个API,可以用来与服务发生交互,但有一个关键的特性无法实现,就是用它来定义特定的工作单元:远程执行服务。定义特定工作单元的唯一方法是将工作流YAML文件提交到代码库中。

GitLabPipelines要好一些。GitLabPipelines支持父子管道(不同管道之间的依赖关系)、多项目管道(不同项目/代码库之间的依赖关系)和动态子管道(在定义新管道的管道作业中生成YAML文件)等特性。动态子管道是一种重要的特性,它们通常将提交的YAML配置与远程执行服务分离开来。这里缺少的是一个无需通过父管道/YAML就可以实现该功能的API。如果存在这种API,你就可以在GitLabPipeline之上构建自己的构建/CI/批处理系统,减少GitLabPipeline的YAML配置文件及其创建者的预期对你带来的约束。

像GitHubActions和GitLabPipelines这样的CI产品与其说是平台,不如说是产品,因为它们都是基于一个通用的远程执行服务,将一个自成体系的配置机制(YAML文件)和WebUI(以及相应的API)紧密耦合在一起。对于我来说,要将这些产品视为平台,它们需要提供通过API来调度计算的能力,不受内置YAML配置机制的限制。GitLab几乎已经实现了,目前还不清楚GitHub是否(或是否有兴趣)朝这个方向发展。

我想顺便提一下Taskcluster,作为GitHub、GitLab等CI产品的反例。这一小节的内容对整篇文章来说并不是最重要的,可以随意跳过。但如果你想知道为工程师打造的CI平台应该是什么样子的,或者你是CI平台的开发者,想要了解一些值得借鉴的想法,那就读下去吧。

Mozilla的Taskcluster最初是为了开发Firefox而构建的通用CI平台。在年和年推出之时,它是独一无二的,它的一些原始功能至今还找不到能够与之媲美的。或许Mozilla申请了什么专利,但在开源领域,没有可以与之匹敌的产品,就连我所知道的那些非开源CI平台也常常无法提供Taskcluster的所有功能。

据我所知,Taskcluster是目前唯一一个适用于大型项目的开放CI平台。

Taskcluster让我很喜欢的一点是它提供了用来定义执行单元的核心原语。任务是Taskcluster的核心执行原语,多个任务被连接在一起形成DAG(这与构建系统的工作方式差不多)。

我们通过向队列服务发出API请求来创建任务,这个API请求实际上就是在调度这个工作单元。

定义好的任务实际上就是带有元数据的计算单元,这些元数据包括任务依赖关系、任务拥有的权限/范围,等等。如果你使用过GitHubActions、GitLabPipelines,你就会看到很多你熟悉的基本元素:要执行的命令列表、要在Docker映像中执行的命令、构成工件的文件路径、重试设置,等等。

Taskcluster所提供的特性远远超过了GitHub、GitLab等产品。

Taskcluster提供了IAM(身份识别与访问管理)风格的作用域特性来实现访问控制。作用域控制你可以执行什么操作、可以访问什么服务、可以使用哪些Runner特性(例如是否可以使用ptrace)、可以访问哪些秘钥,等等。例如,Firefox的Taskcluster设置是这样的:不可信任务是无法访问Firefox构建任务的签名密钥的。Taskcluster是我所知道的唯一一个提供了足够多保护措施的CI平台,这些保护措施可以降低CI平台作为远程代码执行服务的风险。Taskcluster的安全模型让GitHubActions、GitLabPipelines和其他常用的CI服务看起来更像是数据泄露和软件供应链漏洞工厂。

Taskcluster支持使用YAML文件来定义任务,不过它已经提供了一个通用的调度API,所以你不需要这么做。你可以使用自己的配置或前端来定义任务,不过Taskcluster并不关心这些,因为它是一个真正的平台。事实上,Firefox在很大程度上避免使用YAML,而是构建了自己的任务定义功能。Firefox代码库里有很多代码,在运行时将生成数千个离散的任务,这些任务构成了Firefox的构建和版本DAG,并将其中的一些子图注册为Taskcluster任务。这个功能就是它的一个迷你构建系统,Taskcluster平台承担了执行/评估机制的角色。

Taskcluster的模型和能力远远超过了GitHubActions或GitLabPipelines,有很多伟大的想法值得借鉴。

Taskcluster是一个非常强大的用户CI,但现在还没有可供所有人使用的集中式实例(比如像GitHub或GitLab那样),而且学习曲线也相当陡峭。所有这些能力都是以复杂性为代价的。我不能随意向普通用户推荐Taskcluster。但如果其他CI产品无法满足你的需求,你想建立自己的CI平台,又负担得起请几个人来支持你的CI平台,那么Taskcluster就值得考虑。

未来展望

在我的理想世界里,存在着一种远程代码执行服务平台,其目的是为近实时和批处理/延迟执行的任务提供服务。它可能是为软件开发而量身定制的,因为这些领域的特定特性将其与其他作为服务工具的通用计算(如Kubernetes、Lambda等)区分开来。

DAG的概念被融入到执行模型当中,你可以将执行单元定义成图来获得依赖关系。你可以定义独立的、特别的工作单元,也可以定义一组单元,但不像构建系统那样,需要在整个执行过程中运行代理来协调任务的执行。

在我的理想世界里,只需要一个DAG来指定所有的构建、测试和发布任务。没有N+1系统或配置需要管理,也没有额外的平台需要维护,因为一切都是统一的。通过合并实现了规模经济,提高了整体效率。

平台由worker池子组成,这些worker运行负责执行任务的代理。有些池子用于近实时/同步RPC风格的调用,有些池子用于调度/延迟/异步任务。你可以定义自己的worker池和worker。高级客户可能会使用由临时worker(如EC2Spot实例)组成的自动伸缩组对容量进行伸缩,以相对低的成本满足需求。当不再需要这些容量时就终止worker,以此来节约成本(Firefox的Taskcluster实例已经这样做至少6年了)。

对于最终用户来说,一个本地构建包含了驱动或调度用以生成所需构建组件的完整任务图的子集。一个CI构建/测试由实现该目标所必需的任务图的子集组成(它可能是本地构建图的一个超集)。版本的发布也一样。

至于如何配置前端和定义执行单元,平台只需要提供一个东西:一个可以用来调度/执行作业的API。但是,为了让这个产品对用户友好,它也应该提供YAML配置文件(就像现在的CI系统那样)。这样,大部分用户可以继续使用简化的YAML界面,而高级用户可以使用底层的调度/执行API来开发他们自己的驱动程序。人们为他们的构建系统开发插件,并集成到这个平台上。有人会将现有的可扩展构建系统(如Bazel、Buck和Gradle)中的节点转换为平台的计算任务,这样就可以实现构建系统和CI系统(可能还有数据管道之类的东西)的统一。

最后,因为我们讨论的是为软件开发量身定制的系统,所以我们需要有健壮的结果/报告API和界面。如果人们看不到这些奇特的分布式远程计算在做什么,那谁会知道它们究竟给我们带来了哪些好处?这可能很专业,因为如何跟踪结果与特定的领域有关。高级用户可能想要构建自己的结果跟踪服务,但平台至少应该提供一个通用的服务(就像GitHubActions和GitLabPipelines那样)。因为这是一项巨大的增值特性,如果没有这项特性,很少人会使用你的产品。

但是,我所愿景的统一化世界并不会解决上面提到的CI复杂性问题:一个足够大的构建/CI系统总是具有内在的复杂性,可能需要专门的人来维护。不过,由于复杂的CI系统几乎总是附加在复杂的构建系统上,因此通过合并构建系统和CI系统可以缩小复杂性的表面积(比如,你不需要操心构建/CI互操作性问题)。

我愿景中的所有组件现在都以某种形式存在着。Bazel、GradleEnterprise和其他现代构建系统都有用于远程执行和缓存的RPC。它们甚至是可扩展的,你可以开发自己的插件来改变构建系统的核心功能(当然是在不同的程度上)。Taskcluster和GitLabPipelines支持任务的DAG调度。一些批处理作业执行框架(如Airflow)看起来非常像是特定领域的特别版Taskcluster。我们缺少的是一个可以将所有这些功能捆绑在一起的单一的产品或服务。

我确信,我所愿景的不是能否实现的问题,而是我们是否应该实现以及谁来实现的问题。

这可能就是问题的所在。我不想这么说,但除了少数公司之外,我真的怀疑这种服务能否在短期内成为一种广泛可用的服务。

我的愿景的价值在于统一离散的系统(构建、CI,也许还有一些临时的系统,如数据管道,这些系统本身就足够复杂)。出于业务/效率方面的原因,你需要将它们统一起来。毕竟,如果它们没有那么复杂或低效,你可能就不会关心如何让它们变得更简单或更快。从这方面讲,我们可能已经过滤掉了90%的市场,因为他们的系统还不够复杂。

要实现这个愿景,需要采用足够先进的构建系统,充当统一DAG远程执行服务的大脑。一些公司和项目将采用先进的构建系统(如Bazel),因为他们有资源、技术知识和效率激励机制,但其他很多公司不会这么做。相对于简单的构建系统,高级的构建系统所提供的额外好处往往是微不足道的。很多公司将构建和CI支持视为产品开发成本。如果你能够在一个并不先进的构建系统上取得成功,并且在没有过多困难的情况下只花费一小部分成本就取得足够好的成效,那将成为很多公司和项目相继效仿的榜样。人们并不关心有关构建系统和CI系统的争论是一个怎样的结果:他们只想发布产品。

在我看来,这个想法的总体目标市场太小了。在未来几年内,没有任何有技术专长的公司能够实现和提供这样的服务。我在这个领域工作了10年,见证了Taskcluster模式的潜力,看到了以前的、现在的和潜在的雇主都在为此不同程度地挣扎着。我知道这个想法对某些人来说是非常有价值的。尽管这对一些公司来说很重要,但我的直觉是,他们只代表了整个潜在市场的一小部分,对于GitHub或GitLab这样的现有CI产品来说,这块蛋糕太小了,目前还不足以引起他们的注意。

我不认为在这个领域创业是个好主意:因为获取客户太难了。而且,由于很多核心技术已经存在于现有的工具中,在专利知识产权方面并没有什么护城河可以阻止那些财力雄厚的模仿者。你最好的退出方式可能是被微软/GitHub、GitLab或像亚马逊/AWS这样想在这个领域发展的公司收购。

我认为,我们最希望的是看到现有的CI平台能够实现这个愿景,并向全世界发布,或者作为开源项目或服务提供出来。GitHub、GitLab和其他代码托管提供商是理想的候选者,因为它们的社区有助于推动行业的采用。

我不确定是什么时候,但我打赌GitHub/微软会率先采取行动。他们在更广泛的市场/产品捆绑方面有更强烈的动机(想想他们已经在VisualStudio或GitHubWorkspaces中集成构建和CI系统)。此外,他们面临着一些巨大的构建系统和CI挑战(特别是Windows)。很明显,微软正在GitHub上做开发,甚至是公开的。微软工程师将感受到离散的构建和CI系统给他们带来的痛苦和限制。最终,GitHub上至少需要有一个构建系统远程执行服务。我希望GitHub(或其他公司)将其作为一个统一的平台/服务/产品而不是离散的服务来实现,因为正如我所说的,它们实际上是相同的问题。但提供统一的产品并非阻力最小的途径,所以谁知道会发生什么。

结论

如果我有打响指的魔力,可以让离散的构建、CI(或许还有批处理系统,如数据管道)系统向前快速发展10年,那么我会:

让Taskcluster成为最好的远程执行服务平台。

添加对实时同步执行API(如Bazel的远程执行API)的支持,作为对现有批处理/异步能力的补充。

定义Starlark方言,这样就可以像Bazel等构建工具中的原语一样定义CI/发布任务。

迫使其他构建工具(如Bazel)做出改进,缩短构建时间。

提供用于平台交互、查看结果报告的高级WebUI。

向全世界发布。

这个梦想会很快成为现实吗?可能不会,但梦想还是要有的。或许,一些读者可能会自己去追逐这个梦想。

转载请注明:http://www.0431gb208.com/sjszlfa/1365.html