Terraform 是管理基础设施及代码(IaC)最常用的工具之一,它能使我们安全且可预测地对基础设施应用更改。刚开始上手 Terraform 可能会感觉有些不容易,但很快就能对该工具有基本的了解,随之可以开始运行命令、创建和重构 Terraform 代码。在此过程中,许多新用户面临着如何正确构建代码、使用高级功能、在 IaC 流程中应用软件开发最佳实践等方面的细微差别和问题。
在本篇文章中,我们将讨论使用 Terraform 管理 IaC 的最佳实践,帮助您将 Terraform 技能提升到一个新的水平 。
如何构建 Terraform 项目
在开始讨论 Terraform 的一些最佳实践之前,我们先来看看构建 Terraform 项目的一些策略。在 Terraform 的世界中,构建配置的方式没有正确或错误之分,而且您在网上找到的大多数建议结构都带有很大的主观色彩。在决定如何设置 Terraform 配置时,最重要的是了解您的基础设施需求和用例,并制定适合您的团队和项目的解决方案。
如果我们正在处理一个基础设施组件有限的小型项目,那么保持 Terraform 配置尽可能是比较合适的方式。在这类情况下,我们可以只配置根模块必需的文件,即根目录中存在的配置文件。一个小项目可以只包含这些文件main.tf、variables.tf、README.md。您可能会发现方便使用的其他一些文件包括:outputs.tf(用于定义项目的输出值)、versions.tf(用于收集配置的任何固定版本)以及providers.tf配置与您使用的提供商相关的选项,尤其是在有多个提供商的情况下。
我们的主要入口点是main.tf,在简单的用例中,我们可以在那里添加所有资源。我们在variables.tf中定义变量,并在terraform.tfvars中为它们赋值。我们使用文件outputs.tf来声明输出值。
当处理较大的项目时,会更加复杂,我们需要找出适合项目的最佳结构。
首先将 Terraform 代码分解为可重用的组件,不同的团队可以相应地使用和定制。我们可以通过为基础设施部分创建单独的模块来实现这一点,这些模块应该在不同的环境、项目和团队中重用。
常见的做法是根据所有权和责任、变更率和管理难易程度来分离模块。对于每个模块,我们需要定义其输入和输出并彻底记录它们,以便使用者能够有效地使用它们。然后,我们可以利用outputs和terraform_remote_state来跨模块甚至不同 Terraform 状态引用值。
请注意,使用terraform_remote_state数据源意味着访问整个状态快照,这可能会引发安全问题。在不同状态之间共享参数的另一种选择是利用外部工具 [1] 来发布和使用数据,例如 Amazon SSM Parameter Store 或 HashiCorp Consul。
接下来需要决定将所有 Terraform 代码保留在单个存储库 ( monorepo ) 中,或是将 Terraform 配置分离到多个代码存储库中。这两种方法都有缺点和优点。目前有一种趋势,即避免巨大的单一存储库并使用单独的配置来实现更快的模块开发和灵活性。
通常,我们必须处理大量不同的基础设施环境,而 Terraform 中有多种方法可以处理这个问题。一个合适且容易遵循的做法是为不同的环境单独配置 Terraform。这样不同的环境就有自己的状态,可以单独测试和管理,而共享行为则通过共享或远程模块实现。一种选择是每个环境使用单独的目录,并为每个目录保留单独的状态。另一种选择是将所有 Terraform 配置保留在同一目录中,并为每个环境传递不同的环境变量以相应地参数化配置。
这里您可以找到每个目录的三个不同环境的示例结构:生产、staging 和测试。每个环境都有自己的状态,并在利用公共或共享模块的同时与其他环境分开管理。尽管这种方法会带来一些代码重复,但我们获得了更高的清晰度、环境隔离和可扩展性。
一般来说,我们希望为特定所有者定义具有有限范围和爆炸半径的 Terraform 配置。为了最大限度地降低风险,我们应该尝试将项目分解为小型工作区/堆栈,并使用基于角色的访问控制(RBAC)对它们进行分段访问。
Terraform 最佳实践
在前面的部分中,我们讨论了一些通用的 IaC 最佳实践。我们根据组织结构和需求探索了一些优化 Terraform 代码的选项。这里我们将深入研究将 Terraform 代码提升到新水平的具体要点,希望能够为你和你的团队提供有关实验、研究和实施对您的用例有意义的实践的提示和指导。
使用远程状态
在去做一些尝试和试验的时候使用本地状态是可以的,高于此情况的内容都可以使用远程共享状态位置。为状态使用远程后端是您在团队中工作时应该采用的首要最佳实践之一。选择一个支持状态锁定的选项,以避免多人同时更改状态。将状态视为不可变,避免手动状态更改。确保有状态备份,以便在发生灾难时可以使用。对于某些后端(例如 AWS S3),可以启用版本控制以实现快速轻松的状态恢复。
使用现有的共享和社区模块
检查是否已经有合适的用例的模块,避免自己编写所需模块重复造轮子,这样就能节省许多时间。您可以检查 Terraform Registry [2] 以获取可用模块。Terraform 拥有庞大成熟的社区,用户还可以借助社区的力量解决问题。热心的用户也可以通过改进社区或报告问题来帮助社区。
导入现有基础设施
如果您接手了一个已有几年历史的项目,那么其基础设施的某些部分很可能是手动创建的。不用担心,您可以将现有基础设施导入Terraform 并避免从多个端点管理基础设施。
避免变量硬编码
请尽量避免对变量进行硬编码。想一想,将您直接分配的值定义为变量对将来的更改是否更有意义。更重要的是,确认是否可以在不进行显式设置的情况下通过数据源获取属性值。例如,不要从控制台查找 AWS 账户 ID 并将其在terraform.tfvars中设置为:
aws_account_id=”99999999999”
我们可以从数据源中获取账户 ID。
data "aws_caller_identity" "current" {}
locals {
account_id = data.aws_caller_identity.current.account_id
}
始终格式化并验证
在 IaC 中长期一致性至关重要,Terraform 为我们提供了一些工具来帮助我们实现这一目标。请记住运行用terraform fmt和 用terraform validate以正确格式化代码并捕获错过的任何问题。理想情况下应该通过 CI/CD 流水线或 pre-commit hook 自动完成。
使用一致的命名规则
我们可以在网上找到许多有关 Terraform 代码命名规则的建议。最重要的不是规则本身,而是找到您的团队熟悉的规则,并共同努力使其保持一致。请参阅以下易于遵循的规则列表:
在名称中使用下划线_作为分隔符并使用小写字母。
资源名称中尽量不要重复资源类型。
对于单值变量和属性,请使用单数名词。对于列表或地图,使用复数名词来表明它代表多个值。
始终对变量和输出使用描述性名称,并记住包含说明。