在过去的几个月里,Slack的CI基础设施经历了激动人心的变革。由于开发人员对Jenkins的诸多不满(从安全问题、停机时间到普遍较差的用户体验),内部压力促使我们将大部分CI任务从Jenkins迁移到GitHub Actions。
今年夏天我在Slack的实习项目涉及创建一个转换工具,能够自动将Jenkins流水线迁移到GitHub Actions,从而节省开发人员时间并加速迁移进程。该项目取得了成功,预计可将迁移时间减半,节省超过1300小时。本篇博客将重点介绍这个转换工具,以及为期7周的设计、实施和改进过程。
在开始之前,您需要了解一些术语:
- 流水线:一组用于组织Jenkins作业的Jenkins插件。在本项目开始时,我们有242个Jenkins流水线需要迁移
- 实例:托管Jenkins流水线的Jenkins实例。我们需要从3个Jenkins实例迁移
- 工作流:GitHub Actions的CI作业
- GHA:GitHub Actions的缩写
项目规划
问题思考
项目的成功标准如下:
- 创建工具,自动化手动迁移Jenkins流水线到GHA的困难
- 在现有Jenkins作业的大量子集上运行此工具
- 创建关于转换过程的文档
工具选择
经过研究,我们确定了一些有用的工具来从Jenkins转换到GHA:
- GitHub Actions导入器:GitHub发布的工具,可以审计Jenkins实例,并将所有流水线完全转换、部分转换或无法转换为GitHub Actions
- Python脚本:预计可以使用正则表达式脚本修复导入器的部分错误
- 大型语言模型(LLM):用于修复过于复杂而无法使用正则表达式处理的错误
解决方案
第一部分 - 导入器
在一周的时间内,我们在多个Jenkins实例上运行了导入器。该工具使用简单:从GitHub CLI安装,配置后运行类似命令:
|
|
导入器会为被审计Jenkins实例上的每个Jenkins流水线输出GitHub Actions工作流文件。
运行导入器后,我们对它的性能表现感到好奇。幸运的是,导入器在完成Jenkins实例审计后会创建审计摘要。审计文件包含关于有多少Jenkins作业被完全转换、部分转换和转换失败的信息。
大约50%的情况下,导入器能够无错误地将流水线从Jenkins迁移到GHA。5%的情况下导入器完全失败,其余作业被部分导入 - 它创建了工作流YAML文件,但无法完全填充它。
第二部分 - 研究
在项目的这个阶段,我们知道导入器的工作流还有很大的改进空间。大约一半的流水线没有完全转换,因此一个重要目标是研究导致它们无法完全转换的不支持的Jenkins步骤。
在研究导入器创建的几十个YAML文件后,很明显我们需要对工作流YAML文件进行4种主要类型的修正:
- 用内部镜像替换受速率限制的操作
- 用其他操作替换操作
- 为最终用户添加有用的注释
- 删除不必要的注释
在两周的时间里,我们研究了每一项:
1 - 用内部镜像替换受速率限制的操作
投资回报率:非常高
默认情况下,如果您使用GitHub Actions市场中的任意操作,任何由该操作发出的请求都会命中github.com API。对此有速率限制。然而,命中我们自托管的GitHub Enterprise实例的操作具有更高的速率限制。因此,我们需要用我们在GitHub Enterprise实例上拥有的这些操作的内部镜像替换受速率限制的操作。
2 - 用其他操作替换操作
投资回报率:高
在某些情况下,为了执行特定任务,导入器会使用一个操作,而我们更希望它使用另一个操作。例如,对于发送Slack消息,导入器通常选择使用rtCamp/action-slack-notify操作。自然,Slack有用于发送Slack消息的内部操作,我们更愿意为此目的使用内部库。
这类任务似乎适合LLM处理,因为两个操作通常具有不同的语法,仅使用Python的字符串方法很难在它们之间转换。初步测试也显示LLM在此任务上的完美表现,这令人放心。
3 - 为最终用户添加有用的注释
投资回报率:中等
工作流文件中的某些操作项只能手动完成。例如,工作流文件中用于访问机密的语法假定机密将存储在GitHub上。然而,我们不将机密存储在GitHub上,将它们移动到GitHub既耗时又违反我们的安全策略。更好的做法是让最终用户编辑工作流文件,以便从我们存储它们的位置读取机密,而不是从GitHub读取。
4 - 删除不必要的注释
投资回报率:低至中等
回想一下,大约45%的流水线只是部分转换。这意味着导入器找不到Jenkins环境或构建步骤的GHA等效项。发生这种情况时,导入器会在工作流中留下类似"X Jenkins项目不受支持"的注释。
事实证明,许多Jenkins流水线不受支持的原因是因为该功能在GHA中已经包含或不需要。因此,大多数那些"X不受支持"的注释并不太重要,最好删除。
第三部分 - 修正工具
概述
实施的最后部分是修正工具,它将修正导入器的工作流。我们从一开始就知道该工具将平衡使用Python的字符串方法和使用LLM。从上一节注意,在4类修正工具的操作项中,唯一需要AI的是"用其他操作替换操作"。因此修正工具主要是非AI的。制作修正工具花了大约3周时间。
实施架构
以下是鸟瞰图。修正工具的输入是包含工作流yaml文件的目录路径。然后发生以下事件链:
- 该目录中每个yaml文件的路径被写入数组
- 然后,对于该数组中的每个元素:
- 读取该路径处yaml文件的内容
- 对这些文件内容进行每个相关的非AI修正,并写回原始文件
- 对这些文件内容进行每个相关的AI修正,并写回原始文件
最终结果是,输入到修正工具的目录中的每个yaml文件都被原地编辑。
实施非AI修正
如前所述,对导入器的yaml文件需要完成的大部分工作归结为"在包含特定字符串的行后添加注释"或"用一个字符串替换另一个字符串"。Python的String.replace()方法在这里被广泛使用。
实施AI修正
提示工程既是科学也是艺术。它是科学,因为有一个有用的公式:
使用以下结构:
- 上下文设置(让LLM知道任务是什么以及为什么这样做)
- 具体步骤(几个指令,每个都是关于LLM必须完成的非常具体的单独任务。过长的提示比过于模糊的提示危险小得多)
- 输出指令(关于LLM应如何呈现其输出的指令)
在提示的上下文设置部分,包括任何相关的语法和文档。
提示工程也是艺术,因为尝试提示并查看您对AI输出的感受也是关键部分。这个过程有几轮:
- 从非常基本的提示开始
- 查看LLM的响应
- 向提示添加细节和上下文以防止LLM犯任何错误
我们最终得到的一个提示示例:
|
|
我们检查了大约24个LLM的输出,并注意到令人惊讶的事情 - 执行请求任务的准确率达到100%。要求LLM进行这些修正的一个主要担忧是幻觉和其他意外的副作用,因此看到LLM的表现令人放心。
影响和最终用户反馈
影响
首先,考虑手动将流水线从Jenkins转换为GHA的过程:
- 参考您的Jenkins流水线,编写GHA工作流yaml文件
- 测试该工作流yaml并根据需要进行调试
- 为工作流编写文档
- 从其Jenkins实例中删除流水线
请注意这些步骤按难度和时间成本的降序排列。
转换工具让最终用户完全跳过第1步,并在第2步中更加轻松。最终用户获得的工作流文件几乎没有缺陷。他们可以直接跳到调试,这将加速进行,因为他们获得的工作流具有高度准确性。
该工具尝试将大约242个流水线从Jenkins转换为GitHub Actions。开发人员手动将一个Jenkins流水线转换为GHA所需的时间各不相同。以下是根据开发人员类型的一些平均估计:
- 对Jenkins和GHA经验丰富:2小时(基本上是大部分工作日)
- 对Jenkins经验丰富但对GHA不熟悉:5小时(这些人需要学习GHA语法,所以是大部分工作日)
- 对两者都不熟悉:10小时(对于刚接触Jenkins和GHA的人来说,这可能需要超过一天的时间。这个数字包括其他人帮助他们的时间)
假设10%的最终用户是类型1,40%是类型2,50%是类型3,将所有流水线转换为GHA的总时间约为1700小时。
假设使用此工具,一半的流水线无需编辑即可运行(这是现实的,因为一半的流水线完全转换),另一半平均需要3小时来调试。那么将所有流水线转换为GHA将需要约360小时。
因此,该工具预计将节省超过1300小时,即将每个Jenkins流水线移动到GHA所需时间的80%。
最终评论
在撰写本文时,距离转换工具的输出发布已经过去两周,最终用户的反馈现在开始到达。很明显,Slack的开发人员发现该工具有用,但我也听说生成的工作流中存在一些错误。即使从实习一开始,我就预计会发生这种情况,但担心可能很难对这些事件进行分类。
事实证明,没有问题。我为这个转换制作了一个Canvas文档,每当工具的最终用户遇到问题时,我都会用问题的详细信息及其解决方案更新canvas。随着更多用户使用该工具,我获得了更多反馈,canvas变得更加全面。
致谢
感谢所有贡献知识并帮助这个项目取得进展的每个人。
现在比以往任何时候,Slack都需要GHA专家。这听起来像您吗?如果是这样,我们正在招聘!