大规模Terraform实践:管理16.5万云资源的架构揭秘
当前规模
我们目前管理着:
- 16.5万个云资源
- 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)。这意味着某些工作区管理着超过4000个资源,使得计划时间极其漫长(30分钟以上),并增加了任何出错变更的影响范围。
这些过长的计划时间(痛点#2)和累积的漂移(痛点#3)将我们团队推向第二个反模式——使用Terraform的-target功能。这个功能允许开发人员将变更限制在完整基础设施配置的子集内。虽然在有限情况下有用,但它通过仅对Terraform无环图(映射所有资源依赖关系)的子集应用变更来工作,如果滥用会导致各种意外的混乱。
总体而言,这个工具缺口是开发人员繁琐工作和风险的来源。对于我们这种规模的组织来说,显然需要自动化我们的基础设施发布过程。
我们的解决方案:使用Terraform Cloud自动化Terraform
我们评估了几个基础设施自动化工具——特别是Spacelift、Terraform Cloud和Atlantis。最终决定使用Terraform Cloud,主要是因为与Hashicorp合作的优势,他们规模更大、更成熟,并且编写和拥有Terraform。
成功推出Terraform Cloud需要对我们的开发人员工作流程进行两大改变:
- 从"先应用后合并"工作流转向"先合并后应用"工作流
- 转向非定向应用
我们通过几次培训课程、详细的FAQ、专门的Slack问题频道,以及在最初几个月仔细监控Terraform Cloud来确保没有发布积压、运行错误等,来帮助缓解这种过渡的痛苦。
我们使用增量推出策略来限制影响范围,让我们的工程团队有时间先在低风险工作区建立熟悉度,并学习和调整我们对Terraform Cloud代理的资源容量规划。
影响:效率、可靠性和开发人员幸福感
这种改变带来的影响:
- 消除了漂移(上述问题#3),这是风险和开发人员繁琐工作的主要来源
- 每年节省约8000个开发小时(40名基础设施专家 × 4小时/周 × 50周/年 = 8000小时)——相当于找回4名开发人员!
- 每个工作区的所有变更审计日志都链接到提交和作者
- 推测性计划——可以在数十个受影响的工作区中自动测试预期变更,结果直接显示在GitHub CI中
这张截图显示了我们如何将推测性计划限制在少量金丝雀工作区(本例中只有一个)。
自从两年前最初推出Terraform Cloud以来,我们继续以各种大大小小的方式改进这个系统。
我们现在如何运行Terraform Cloud
我们在自己的AWS账户中运行Terraform Cloud的自托管安装,运行我们的TFC代理。(Hashicorp称此产品为Terraform Cloud for Business。)我们更喜欢将所有对生产基础设施的管理访问权限保留在内部,而不向Hashicorp授予任何生产权限。
我们在自己的ECS集群中运行这些Terraform Cloud代理。我们的合同允许我们运行最多200个并发代理,虽然我们通常在两个代理池中运行120个(开发池40个,生产池80个)。这使我们能够高并发地向625个工作区发布变更。例如,如果单个变更影响80个工作区,它可以同时应用到所有80个工作区。
我们密切监控的事项
- 代理耗尽/并发限制:如果持续一段时间没有可用代理,我们会呼叫值班人员(我们打算某天实现自动扩展)
- 计划时间:如果开发环境中的计划时间超过4分钟,我们会通知团队
- 基础设施漂移:在测量到最小漂移一年后,我们最终停止了测量,因为漂移在我们的基础设施中不再有意义地存在
生活质量优化
虽然Terraform Cloud对我们来说是一个很好的工具,但作为大规模组织和高阶用户,我们发现它缺少一些我们需要的功能。以下是我们围绕它构建的自定义功能。
TFC CLI
我们的一些Terraform模块在许多工作区中使用。例如,我们有261个工作区受到"deploy"模块变更的影响。任何影响此模块的变更都需要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提供程序。我们构建了一个tfc-workspace模块,用来配置每个工作区。
所有权委托
我们的团队拥有Terraform Cloud作为提供给我们的基础设施、安全和开发团队的服务。我们努力保持这些工作区处于无错误状态,每月更新提供程序,并及时处理任何弃用警告。但是,有些工作区管理着我们团队专业知识之外的资源,这时我们需要委托给适当的团队来解决这些问题。为了解决这个问题,我们开发了一个约定,向每个工作区应用标签,如owner:{github_team_name},例如owner:infra-monolith或owner:security-eng。这允许我们在工作区出现问题时通知适当的团队。
这是我们现在如何标记工作区的示例。我们通过tfc-workspace terraform模块应用这些,以保持命名一致。
TFC使用报告
最近,我们的Terraform Cloud合同需要续订,这意味着我们需要预测未来的增长和使用情况。不幸的是,Terraform Cloud只告诉您当前时间点的总管理资源,但没有其他信息。
为此,我们构建了一个脚本,使用TFC API查询每个工作区的每个状态版本,回溯一年,并将这些数据制成CSV表格,之后我们构建一些图表。这些图表允许我们按提供程序资源类型(例如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升级提供程序
拥有625个工作区,每个工作区平均使用3个提供程序,那就是1875个唯一的提供程序-工作区升级要执行。我们使用Dependabot来帮助完成这个任务,每月定期升级所有提供程序。
即使在这种规模下管理Dependabot也需要一些工作,因此我们构建了自动化,允许我们细粒度地操作dependabot.yml文件。这使我们能够允许列表某些提供程序进行升级,拒绝列表其他提供程序,将升级隔离到仅开发或生产工作区,或对具有特殊条件的单个工作区进行处理。这是一个显示我们的dependabot.yml结构的小片段。为所有工作区完全生成时,这个文件运行到2000多行yaml。
持续改进:为规模优化
我们的基础设施发布系统仍在进行中。它并不完美,但我们每天都在继续改进。以下是我们希望接下来做的事情:
分阶段推出
我们目前使用主分支。一旦合并到主分支,它就会发布到大多数工作区(我们经过验证的客户或GxP客户例外,他们只接收季度发布)。我们希望转向分阶段推出,具有更多发布层级(例如开发、预发布、生产、gxp)。我们将在提升到下一层级之前验证整个层级的成功。
将大型工作区分解为许多较小工作区
虽然我们已经从350个工作区增长到625个,但我们仍然有许多工作区管理着数千个Terraform资源。这使得计划和应用操作完成缓慢。由于现在向所有工作区发布变更已完全自动化,我们应该进一步分解这些工作区,将这625个工作区分解为1500多个工作区,以减少计划和应用时间,并最小化影响范围。
增强通知
将Slack通知重新分配给其他用户的能力一直是一个受欢迎的功能请求。此外,如果Slack机器人准备一个仅限于受影响工作区的tfc review命令,那将会很好。
代理自动扩展
自动扩展这种工作负载很复杂,Hashicorp支持一个EKS Operator来做这件事。我们希望将我们的代理池迁移到EKS,以采用受支持的模式。
开源
我们构建了许多自定义工具来支持我们的基础设施自动化。其中大部分解决了我们想象许多其他团队也有的用例,因此我们希望开源这项工作。
需要整个团队的努力
这个系统是由许多人共同构建的,感谢Benchling许多工程师的合作和见解。我们深深感谢所有那些在帮助我们达到今天基础设施状态的合作伙伴!
我们希望这篇文章对您和您的组织在设计可扩展的云基础设施时有所帮助。