我们如何大规模运行 Terraform
管理超过16.5万个云资源,跨越数百个工作空间,听起来可能令人望而生畏。但对我们来说,这只是Benchling的日常工作。以下是我们如何做到的。
我们目前管理着:
- 165k 个云资源
- 625 个 Terraform 工作空间
- 38 个 AWS 账户
- 170 名工程师(其中40名是基础设施专家)
我们每天执行:
- 225 次基础设施发布(
terraform apply操作) - 723 次计划(
terraform plan操作)
在过去的两年里,我们一直在成功运行Benchling的基础设施发布系统(剧透:是Terraform Cloud)。在此期间,我们的基础设施规模翻了一番,而发布开销却几乎没有增加。
[按下回车或点击查看完整尺寸图片]
Terraform Cloud 之前:混乱时期
我们的基础设施发布过程并非一直如此顺利。让我带你回到之前的状态。
正如小型Terraform项目的常见指导,我们团队过去通常通过笔记本电脑应用所有基础设施变更。同样按照常见指导,我们团队使用S3存储状态文件,并用DynamoDB进行状态锁定,以防止应用时发生冲突。对于处理十几个工作空间的小团队来说,这是个很好的策略。然而,随着团队工作空间规模的增长,这种方法开始慢慢失效。就像温水煮青蛙的寓言。在我们做出转变时,Benchling已经管理着350个工作空间。我们正在接近沸点。
痛点:开发者的辛劳与低效
用这种方法管理350个工作空间有几个缺点:
- 需要基础设施团队拥有更高的AWS访问权限。
- 耗时,因为工程师必须导航到每个目录,运行
terraform apply,审查并批准运行,然后验证是否成功。通常,一个变更可能影响超过120个工作空间,这意味着要重复这个过程120次。(我们开发了一个自定义的Python脚本来帮助实现一定程度的并行化。) - 积累基础设施漂移。工程师经常在应用变更时发现大量不相关的待处理基础设施变更。这种情况可能由多种原因引起——之前的工程师在推出变更时遗漏了该工作空间、未意识到需要执行应用步骤,或者没注意到他们的应用失败了。遇到这种情况的不幸工程师需要追踪导致漂移的变更作者,确认该变更是否是有意且安全的,然后推出变更。接着,他们必须为每个受影响的工作空间重复这个过程。
- 应用一个变更很容易就花掉一整天,特别是如果你遇到了意外的漂移(痛点#3)。由于新增工作空间带来的发布开销,团队被激励采用了几种反模式。
第一种反模式是将尽可能多的资源放入单个目录/工作空间,以最小化需要应用的工作空间数量(从而最小化痛点#2)。这意味着有些工作空间管理着超过4k的资源,这使得计划时间长得令人痛苦(30分钟以上),并扩大了任何出错变更的爆炸半径。
这些怪物工作空间的过长计划时间(痛点#2)和积累的漂移(痛点#3)将我们的团队推向第二种反模式——使用 Terraform 的 -target 功能。该功能允许开发者将变更限制在完整基础设施配置的一个子集。虽然在有限情况下可能有用,但它通过仅对Terraform非循环图(映射所有资源依赖关系)的子集应用变更来工作,因此如果滥用,可能导致各种意想不到的混乱。Terraform的创建者Hashicorp自己也强烈不建议将 -target 功能用于常规操作,因为它可能带来副作用。
总的来说,这个工具缺口是开发者辛劳和风险的来源。很明显,对于像我们这样规模的组织,我们需要自动化我们的基础设施发布流程。
我们的解决方案:使用 Terraform Cloud 自动化 Terraform
我们评估了几种基础设施自动化工具——特别是Spacelift、Terraform Cloud和Atlantis。最终我们决定使用Terraform Cloud,主要是考虑到与Hashicorp合作的优势,他们规模更大、更成熟,并且创建并拥有Terraform。
成功推出Terraform Cloud需要对我们的开发者工作流程进行两大改变。具体来说:
- 从“先应用后合并”工作流转向“先合并后应用”工作流。这在推出时是一个很大的不确定性来源,因为在将PR合并到主分支之前,确实没有办法在所有工作空间上测试应用时错误。
- 转向非目标性应用(无
-target)。
我们通过几次培训会议、详细的FAQ、专门的Slack频道答疑,以及在最初几个月仔细监控Terraform Cloud以确保没有发布积压、运行错误等,帮助缓解了这次过渡的痛苦。
我们采用了增量推出策略来限制爆炸半径,给我们的工程团队时间先在低风险工作空间建立熟悉度,并学习和调整我们对Terraform Cloud代理的资源容量规划。
影响:效率、可靠性和开发者幸福感
这一改变带来的影响:
- 消除了漂移(上述问题#3),这是一个巨大的风险和开发者辛劳来源。
- 每年节省约8000个开发者小时(40名基础设施专家 × 每周4小时 × 每年50周 = 8000小时)——这相当于挽回了4名开发者的时间!
- 每个变更的审计日志与提交和作者相关联。我们无法过分强调这对调试问题有多大的帮助。
- 推测性计划——一个预期的变更可以自动在数十个受影响的工作空间上测试,结果直接显示在GitHub CI中。
[按下回车或点击查看完整尺寸图片]
这张截图显示了我们如何将推测性计划限制在少数金丝雀工作空间(本例中只有一个)。
自从我们两年前最初推出Terraform Cloud以来,我们一直在许多方面不断优化和改进这个系统。
我们今天如何运行 Terraform Cloud
我们托管自己的Terraform Cloud安装,TFC代理在我们自己的AWS账户中运行。(Hashicorp称此产品为 Terraform Cloud for Business。)我们倾向于将所有对生产基础设施的管理访问权限保留在内部,而不授予Hashicorp任何生产权限。我们在自己的ECS集群中运行这些Terraform Cloud代理。我们的合同允许我们运行多达200个并发代理,不过我们通常在两个代理池中运行120个(开发池40个,生产池80个)。这使我们能够以高并发性向625个工作空间发布变更。例如,如果一个变更影响了80个工作空间,它可以同时应用到所有80个工作空间。
我们痴迷监控的事项
- 代理耗尽/并发限制:如果持续一段时间没有可用代理,我们会呼叫值班人员(我们打算有一天实现自动扩缩容)。
- 计划时间:如果开发环境的计划时间超过4分钟,我们会通知团队。我们最关心的是确保开发工作空间的快速计划时间,因为这会减少基础设施开发期间的开发者反馈循环。
- 基础设施漂移:在测量了最小漂移一年后,我们最终停止了测量,因为漂移在我们的基础设施中已不再有意义地存在。这是因为 1) 所有应用都是非目标性的,2) 默认情况下零工程师拥有生产写入权限,3) 发布非常频繁,任何漂移都会被下一次发布迅速解决。
生活质量优化
尽管Terraform Cloud对我们来说是一个很棒的工具,但作为一个大规模组织和高级用户,我们发现它缺乏一些我们需要的功能。以下是我们围绕它构建的自定义功能。
TFC CLI
我们的一些Terraform模块被许多工作空间使用。例如,我们的“deploy”模块的变更会影响261个工作空间。任何影响此模块的变更都需要261次审查和批准,即使实际的变更实质上相同。点击Terraform Cloud UI很繁琐,所以我们写了一个CLI。我们的工具允许我们运行 tfc apply --commit abcd1234 来审查计划和应用变更。更复杂的调用可能看起来像 tfc review --commit abcd1234 --wildcard update:module.stack.*.access_controls --include-tag type:deploy。该命令会自动批准与特定提交SHA、通配符资源地址和提供标签/标签匹配的变更。
随着时间的推移,我们添加了其他几个功能,但使用最多的命令是 tfc run(触发新计划)和 tfc review(审查并批准待定应用)。
通知
因为我们要求对每个生产变更进行手动审查和批准,开发者很容易合并他们的变更然后忘记应用它。我们构建了一个Slack通知服务来解决这个问题。它每10分钟运行一次,并通知提交作者任何待处理的Terraform Cloud应用。它只在工作时间运行,并进行指数退避,以免过于烦人。
工作空间管理器
我们有625个工作空间,所以我们**当然用Terraform管理我们的Terraform工作空间!**我们大量使用 tfe provider。我们构建了一个 tfc-workspace 模块,用于配置每个工作空间。
所有权委派
我们团队将Terraform Cloud作为一项服务提供给我们的基础设施、安全和开发团队。我们努力将这些工作空间保持在无错误状态,每月更新provider,并及时处理任何弃用警告。然而,有些工作空间管理的资源超出了我们团队的专业范围,这时我们需要委派给适当的团队来处理这些问题。为了解决这个问题,我们开发了一种惯例,即为每个工作空间应用标签,例如 owner:{github_team_name},如 owner:infra-monolith 或 owner:security-eng。这允许我们在工作空间出现问题时通知适当的团队。
这是我们今天如何标记工作空间的一个例子。我们通过 tfc-workspace terraform模块应用这些标签以保持命名一致性。
TFC 使用情况报告
最近,我们的Terraform Cloud合同即将续签,这意味着我们需要预测未来的增长和使用情况。不幸的是,Terraform Cloud只告诉你当前时间点的总管理资源数,其他信息一概没有。
为此,我们构建了一个脚本,使用TFC API查询每个工作空间的每个状态版本,回溯一年,并将这些数据制成CSV,然后我们构建一些图表。这些图表允许我们按provider资源类型(例如 aws_s3_bucket, aws_ec2_instance)、工作空间类型(例如 type:region)或AWS账户跟踪增长。有点麻烦,但它有效。
TFC 状态备份
我们需要一个灾难恢复策略,以防Terraform Cloud宕机。在灾难恢复事件中,如果获得批准,我们可以利用应急功能和流程恢复到本地模式。然而,这里的一个空白是,我们需要访问存储在Terraform Cloud中的状态文件。为了防止状态文件丢失,我们实施了这篇文章的一个变体,在每次应用后将它们备份到S3桶。
[按下回车或点击查看完整尺寸图片]
这是我们的状态备份webhook示例。在 terraform apply 完成后,它会触发一个lambda函数,将terraform状态文件从Terraform Cloud复制到S3中的次要位置,以便在灾难恢复事件中使用。
工作空间依赖图
Terraform Cloud的一个优点是,它允许给定的工作空间监视特定仓库目录的变更。例如,一个工作空间可以监视 tf-modules/* 的变更,如果该目录中的任何内容发生变化,就会触发计划。然而,在我们这样大的规模下,这效果不佳,因为我们既使用单仓库,又拥有180多个模块,625个工作空间各自使用这些模块的某个子集。(例如,如果所有625个工作空间都跟踪 tf-modules/*,并且该目录中的单个文件被更改,那么它将触发625次运行,很快耗尽我们120个代理的代理池,即使大多数工作空间导致空操作。)因此,我们构建了一个自定义工具,映射出每个工作空间的模块依赖树,并生成一个yaml配置,由我们的 tfc-workspace 模块读取以确定要监视哪些目录。
[按下回车或点击查看完整尺寸图片]
这显示了我们为一个示例工作空间跟踪的目录。
使用 Dependabot 升级 Provider
有625个工作空间,每个平均使用3个provider,那就是1875个唯一的provider-工作空间升级需要执行。我们使用Dependabot来帮助处理,每月定期升级所有provider。
即使在这种规模下管理Dependabot也需要一些工作,因此我们构建了自动化工具,允许我们对 dependabot.yml 文件进行细粒度操作。这使我们能够允许列表某些provider进行升级,拒绝列表其他provider,将升级隔离到仅开发或生产工作空间,或者对个别工作空间进行特殊条件处理。这是一个要点,展示了我们的 dependabot.yml 是如何构建的。为所有工作空间完全生成后,这个文件会达到2000多行yaml。
持续改进:为规模优化
我们的基础设施发布系统仍在改进中。它并不完美,但我们每天都在持续改进。以下是我们希望下一步实现的目标:
- 分阶段推出:我们目前使用主分支。一旦合并到主分支,它就会发布到大多数工作空间(我们的验证客户或GxP客户是例外,他们只接收季度发布)。我们希望转向具有更多发布层级(例如开发、暂存、生产、gxp)的分阶段推出。我们将在推广到下一层级之前在层级内验证成功。
- 将大型工作空间分解为多个较小的工作空间:虽然我们已经从350个工作空间增长到625个,但我们仍然有许多管理着数千个Terraform资源的工作空间。这使得计划和应用的完成速度很慢。既然现在向所有工作空间发布变更已经完全自动化,我们应该进一步分解这些工作空间,将这625个工作空间分解成1500多个工作空间,以减少计划和应用时间,并最小化爆炸半径。
- 增强通知:能够将Slack通知重新分配给其他用户是一个受欢迎的功能请求。此外,如果Slack机器人能准备一个仅限于受影响工作空间的
tfc review命令就更好了。 - 代理自动扩缩容:自动扩缩容这种类型的工作负载很复杂,Hashicorp支持一个EKS Operator来实现这一点。我们希望将我们的代理池迁移到EKS,以采用受支持的模式。
- 开源:我们构建了许多自定义工具来支持我们的基础设施自动化。其中大部分解决的用例我们想象许多其他团队也有,所以我们很乐意开源这项工作。
需要团队协作
这个系统是由许多双手共同构建的,感谢Benchling许多工程师的协作和见解。我们深深感谢所有帮助我们走到今天基础设施状况的Benchling同仁!
我们希望这篇文章对你和你的组织在设计规模化云基础设施时有所帮助。