Terraform 与 CloudFormation:权威指南

已发表: 2022-03-11

如果您像我一样在互联网上搜索以帮助您在 CloudFormation 和 Terraform 作为您的下一个基础架构即代码 (IaC) 工具之间进行选择,但没有找到明确的答案,那么我很长一段时间都在分享您的痛苦。 现在,我对这两种工具都有丰富的经验,我可以就使用哪一种做出明智的决定。

TL;博士

对于 AWS 上的 IaC 项目,请选择 CloudFormation,因为:

  1. CloudFormation 对代码(即模板)和代码的实例化(即堆栈)进行了区分。 在 Terraform 中,没有这样的区别。 下一节将对此进行更多介绍。
  2. Terraform 不能很好地处理基本的依赖管理。 更多内容将在后面的部分中介绍。

区分代码和实例化

CloudFormation 和 Terraform 之间的一个区别是代码和实例在每个服务中如何相互关联。

CloudFormation 具有堆栈的概念,即模板的实例化。 同一模板可以由给定客户在给定帐户中、跨帐户或由不同客户无限地实例化。

Terraform 没有这样的概念,并且要求代码与其实例化之间存在一对一的关系。 这类似于为每个要运行的服务器复制 Web 服务器的源代码,或者每次需要运行应用程序而不是运行编译版本时都复制代码。

这一点在简单设置的情况下是相当微不足道的,但它很快成为中大型运营的主要痛点。 在 Terraform 中,每次需要从现有代码中创建新堆栈时,都需要复制代码。 复制/粘贴脚本文件是一种非常简单的方法来破坏自己并破坏您不打算接触的资源。

Terraform 实际上没有 CloudFormation 之类的堆栈概念,这清楚地表明 Terraform 是从头开始构建的,以便在代码与其管理的资源之间进行一对一的匹配。 后来,环境的概念(后来被重命名为“工作区”)部分纠正了这一点,但是使用这些的方式使得部署到不需要的环境非常容易。 这是因为您必须在部署之前运行terraform workspace select ,并且忘记此步骤将部署到先前选择的工作区,这可能是您想要的,也可能不是。

在实践中,使用 Terraform 模块确实可以缓解这个问题,但即使在最好的情况下,您也需要大量的样板代码。 事实上,这个问题非常严重,以至于人们需要围绕 Terraform 创建一个包装工具来解决这个问题:Terragrunt。

状态管理和权限

CloudFormation 和 Terraform 之间的另一个重要区别是它们各自管理状态和权限的方式。

CloudFormation 为您管理堆栈状态,并且不为您提供任何选项。 但根据我的经验,CloudFormation 堆栈状态一直很稳定。 此外,CloudFormation 允许权限较低的用户管理堆栈,而无需拥有堆栈本身所需的所有必要权限。 这是因为 CloudFormation 可以从附加到堆栈的服务角色获取权限,而不是从运行堆栈操作的用户获取权限。

Terraform 要求您为其提供一些后端来管理状态。 默认是一个本地文件,这完全不能令人满意,因为:

  1. 状态文件的健壮性与存储它的机器的健壮性完全相关。
  2. 这几乎使团队合作变得不可能。

所以你需要一个健壮和共享的状态,在 AWS 上这通常是通过使用 S3 存储桶来存储状态文件来实现的,并伴随着一个 DynamoDB 表来处理并发。

这意味着您需要为要实例化的每个堆栈手动创建 S3 存储桶和 DynamoDB 表,还需要手动管理这两个对象的权限,以限制权限较低的用户访问他们不应访问的数据。 如果你只有几个堆栈,那不会有太大问题,但如果你有 20 个堆栈要管理,那确实变得非常麻烦。

顺便说一句,在使用 Terraform 工作区时,每个工作区不可能有一个 DynamoDB 表。 这意味着,如果您希望 IAM 用户具有最小权限来执行部署,该用户将能够摆弄所有工作区的锁,因为 DynamoDB 权限没有细粒度到项目级别。

依赖管理

在这一点上,CloudFormation 和 Terraform 都可能有点棘手。 如果您更改资源的逻辑 ID(即名称),两者都会认为必须销毁旧资源并创建新资源。 因此,在任一工具中更改资源的逻辑 ID 通常不是一个好主意,尤其是对于 CloudFormation 中的嵌套堆栈。

如第一节所述,Terraform 不处理基本依赖项。 不幸的是,尽管明显缺乏变通方法,Terraform 开发人员并没有对这个长期存在的问题给予太多关注。

鉴于适当的依赖管理对于 IaC 工具绝对至关重要,一旦涉及到关键业务操作(例如部署到生产环境),Terraform 中的此类问题就会对其适用性提出质疑。 CloudFormation 给人一种更专业的感觉,AWS 总是非常注意确保它为客户提供生产级工具。 在我使用 CloudFormation 的这些年里,我从来没有遇到过依赖管理的问题。

CloudFormation 允许堆栈导出它的一些输出变量,然后可以被其他堆栈重用。 老实说,此功能是有限的,因为您将无法在每个区域实例化多个堆栈。 这是因为您不能导出两个具有相同名称的变量,并且导出的变量没有命名空间。

Terraform 不提供此类设施,因此您的选择较少。 Terraform 允许您导入另一个堆栈的状态,但这使您可以访问该堆栈中的所有信息,包括存储在该状态中的许多秘密。 或者,堆栈可以以存储在 S3 存储桶中的 JSON 文件的形式导出一些变量,但同样,此选项更麻烦:您必须决定使用哪个 S3 存储桶并为其授予适当的权限,然后编写所有在作者和读者方面自己编写代码。

Terraform 的一个优点是它具有数据源。 因此,Terraform 可以查询不受 Terraform 管理的资源。 但是,在实践中,当您想要编写通用模板时,这几乎没有关系,因为您不会假设任何关于目标帐户的内容。 CloudFormation 中的等价物是添加更多的模板参数,因此涉及重复和潜在的错误; 但是,根据我的经验,这从来都不是问题。

回到 Terraform 的依赖管理问题,另一个例子是当你尝试更新负载均衡器的设置时出现错误并得到以下信息:

 Error: Error deleting Target Group: ResourceInUse: Target group 'arn:aws:elasticloadbalancing:us-east-1:723207552760:targetgroup/strategy-api-default-us-east-1/14a4277881e84797' is currently in use by a listener or a rule status code: 400, request id: 833d8475-f702-4e01-aa3a-d6fa0a141905

预期的行为是 Terraform 检测到目标组是某些其他未删除资源的依赖项,因此,它不应该尝试删除它,但也不应该抛出错误。

运营

尽管 Terraform 是一个命令行工具,但很明显它需要人类来运行它,因为它非常具有交互性。 可以在批处理模式下(即从脚本)运行它,但这需要一些额外的命令行参数。 考虑到 IaC 工具的目的是自动化,Terraform 被开发为默认由人类运行的事实非常令人费解。

Terraform 很难调试。 错误消息通常非常基本,无法让您了解出了什么问题,在这种情况下,您必须使用TF_LOG=debug运行 Terraform,这会产生大量输出以进行拖网。 更复杂的是,Terraform 有时对 AWS 的 API 调用失败,但失败不是 Terraform 的问题。 相比之下,CloudFormation 提供了相当清晰的错误消息,其中包含足够的详细信息,可以让您了解问题所在。

一个示例 Terraform 错误消息:

 Error: error reading S3 bucket Public Access Block: NoSuchBucket: The specified bucket does not exist status code: 404, request id: 19AAE641F0B4AC7F, host id: rZkgloKqxP2/a2F6BYrrkcJthba/FQM/DaZnj8EQq/5FactUctdREq8L3Xb6DgJmyKcpImipv4s=

上面的错误消息显示了一个明确的错误消息,它实际上并没有反映潜在的问题(在这种情况下是权限问题)。

此错误消息还显示 Terraform 有时如何将自己绘制到角落。 例如,如果您在该存储桶上创建了一个 S3 存储桶和一个aws_s3_bucket_public_access_block资源,并且由于某种原因您在销毁该存储桶的 Terraform 代码中进行了一些更改——例如,在上述“更改意味着删除和创建”陷阱中—— Terraform 将在尝试加载aws_s3_bucket_public_access_block时卡住,但由于上述错误而不断失败。 Terraform 的正确行为是酌情替换或删除aws_s3_bucket_public_access_block

最后,您不能将 CloudFormation 帮助程序脚本与 Terraform 一起使用。 这可能会让人烦恼,尤其是当您希望使用 cfn-signal 时,它会告诉 CloudFormation EC2 实例已完成自身初始化并准备好处理请求。

语法、社区和回滚

在语法方面,与 CloudFormation 相比,Terraform 确实有一个很好的优势——它支持循环。 但根据我自己的经验,这个功能可能会有点危险。 通常,循环将用于创建许多相同的资源; 但是,当您想用不同的计数更新堆栈时,您可能需要链接旧资源和新资源(例如,使用zipmap()组合两个数组中的值,而这两个数组现在恰好是大小不同,因为一个数组具有旧循环大小的大小,另一个具有新循环大小的大小)。 确实,这样的问题可以在没有循环的情况下发生,但是如果没有循环,问题对于编写脚本的人来说会更加明显。 在这种情况下使用循环会混淆问题。

Terraform 的语法还是 CloudFormation 的语法更好,主要是偏好问题。 CloudFormation 最初只支持 JSON,但 JSON 模板很难阅读。 幸运的是,CloudFormation 还支持 YAML,它更易于阅读并允许注释。 不过,CloudFormation 的语法往往相当冗长。

Terraform 的语法使用 HCL,它是 JSON 的一种衍生物,非常特殊。 Terraform 提供了比 CloudFormation 更多的功能,而且它们通常更容易理解。 所以可以说 Terraform 在这一点上确实有一点优势。

Terraform 的另一个优势是其现成的社区维护模块集,这确实简化了模板的编写。 一个问题可能是此类模块可能不够安全,无法满足组织的要求。 因此,对于需要高度安全性的组织来说,审查这些模块(以及它们出现的更多版本)可能是必要的。

一般来说,Terraform 模块比 CloudFormation 嵌套堆栈灵活得多。 CloudFormation 嵌套堆栈倾向于隐藏其下方的所有内容。 从嵌套堆栈中,更新操作将显示嵌套堆栈将被更新,但不会详细显示嵌套堆栈内将发生什么。

最后一点,实际上可能是有争议的,是 CloudFormation 尝试回滚失败的部署。 这是一个非常有趣的功能,但不幸的是可能会很长(例如,CloudFormation 可能需要长达三个小时才能确定部署到 Elastic Container Service 失败)。 相反,在失败的情况下,Terraform 只是停止在原来的位置。 回滚功能是否是一件好事是有争议的,但我已经开始欣赏这样一个事实,即当更长的等待恰好是可以接受的权衡时,堆栈会尽可能地保持在工作状态。

为 Terraform 与 CloudFormation 辩护

Terraform 确实比 CloudFormation 具有优势。 在我看来,最重要的一点是,在应用更新时,Terraform 会向您显示您将要进行的所有更改,包括深入了解它正在使用的所有模块。 相比之下,CloudFormation 在使用嵌套堆栈时仅向您显示嵌套堆栈需要更新,但不提供深入了解细节的方法。 这可能令人沮丧,因为在点击“开始”按钮之前了解此类信息非常重要。

CloudFormation 和 Terraform 都支持扩展。 在 CloudFormation 中,可以使用您自己创建的 AWS Lambda 函数作为后端来管理所谓的“自定义资源”。 对于 Terraform,扩展更容易编写并成为代码的一部分。 所以在这种情况下,Terraform 有一个优势。

Terraform 可以处理许多云供应商。 这使 Terraform 能够在多个云平台之间统一给定部署。 例如,假设您在 AWS 和 Google Cloud Platform (GCP) 之间分配了一个工作负载。 通常,工作负载的 AWS 部分将使用 CloudFormation 进行部署,而 GCP 部分将使用 GCP 的 Cloud Deployment Manager。 使用 Terraform,您可以改为使用单个脚本在各自的云平台中部署和管理两个堆栈。 这样,您只需部署一个堆栈而不是两个。

Terraform 与 CloudFormation 的非参数

有相当多的非争论继续在互联网上流传。 最大的问题是,由于 Terraform 是多云的,您可以使用一个工具来部署所有项目,无论它们在什么云平台上完成。 从技术上讲,这是对的,但这并不是它看起来的大优势,尤其是在管理典型的单云项目时。 现实情况是,在(例如)CloudFormation 中声明的资源与在 Terraform 脚本中声明的相同资源之间几乎一一对应。 由于无论哪种方式都必须了解特定于云的资源的详细信息,因此差异归结为语法,这几乎不是管理部署的最大痛点。

有人认为,通过使用 Terraform,可以避免供应商锁定。 这个论点不成立,因为使用 Terraform,你被 HashiCorp(Terraform 的创建者)锁定,就像使用 CloudFormation,你被 AWS 锁定一样,对于其他云,依此类推平台。

Terraform 模块更容易使用这一事实对我来说并不重要。 首先,我认为 AWS 有意避免为基于社区的 CloudFormation 模板托管单个存储库,因为人们认为这对用户制造的安全漏洞和违反各种合规计划负有责任。

在更个人的层面上,我完全理解在软件开发中使用库的好处,因为这些库很容易运行到数万行代码中。 然而,在 IaC 的情况下,代码的大小通常要小得多,这样的模块通常只有几十行。 从某种意义上说,使用复制/粘贴实际上并不是一个坏主意,因为它避免了维护兼容性和将您的安全委托给不知名的人的问题。

许多开发人员和 DevOps 工程师不赞成使用复制/粘贴,这背后有充分的理由。 但是,我的观点是,对代码片段使用复制/粘贴可以让您轻松地根据需要对其进行定制,并且无需将其制作成库并花费大量时间使其通用。 维护这些代码片段的痛苦通常很小,除非您的代码在十几个或更多模板中重复出现。 在这种情况下,挪用代码并将其用作嵌套堆栈是有意义的,并且不重复自己的好处可能大于执行更新时无法看到嵌套堆栈内将要更新的内容的烦恼手术。

CloudFormation 与 Terraform 结论

借助 CloudFormation,AWS 希望为其客户提供坚如磐石的工具,该工具将始终按预期工作。 当然,Terraform 的团队也这样做了——但不幸的是,他们工具的一个关键方面,依赖管理似乎不是优先考虑的。

Terraform 可能会在您的项目中占有一席之地,尤其是在您拥有多云架构的情况下,在这种情况下,Terraform 脚本是统一您使用的各种云供应商的资源管理的一种方式。 但是在这种情况下,您仍然可以通过仅使用 Terraform 来管理已经使用其各自的特定于云的 IaC 工具实现的堆栈来避免 Terraform 的缺点。

Terraform 与 CloudFormation 的总体感觉是,CloudFormation 虽然不完美,但更专业和可靠,我绝对会推荐它用于任何不是特别多云的项目。