1.9.10.1. Terraform 与自动化
如果团队使用 Terraform 作为变更管理和部署管道的核心工具,可能需要以某种自动化方式编排 Terraform 的运行,以确保运行之间的一致性,并提供其他有趣的功能,例如与版本控制系统钩子的集成。
Terraform 的自动化可以有多种形式,并且程度不同。一些团队继续在本地运行 Terraform,但使用脚本代码来准备一致的工作目录来运行 Terraform,而另一些团队则完全在 Jenkins 等 CI 工具中运行 Terraform。
本篇涵盖了实现此类自动化时应考虑的一些事项,既确保 Terraform 的安全运行,又适应 Terraform 工作流程中当前需要仔细注意的一些限制。它假设 Terraform 将在非交互式环境中运行,无法在终端提示输入。对于脚本代码来说不一定如此,但在 CI 工具中运行时通常如此。
1.9.10.1.1. 自动化的 Terraform 命令行工作流
在自动化流程中运行 Terraform 时,重点通常是核心的 plan
/apply
循环。那么,使用 Terraform 命令行的流程大体如下:
- 初始化 Terraform 工作目录。
- 针对当前代码,为产生变化的资源计算变更计划
- 让操作员审查计划,以确保其可接受
- 应用计划描述的更改。
步骤 1、2 和 4 可以使用熟悉的 Terraform 命令以及一些附加选项来执行:
terraform init -input=false
初始化工作目录。terraform plan -out=tfplan -input=false
创建计划文件并将其保存到名为tfplan
的本地文件。terraform apply -input=false tfplan
执行存储在文件tfplan
中的计划。
-input=false
参数命令 Terraform 不应尝试提示输入,而是要求配置文件或命令行提供所有必要的值。因此,可能需要在 terraform plan
上使用 -var
和 -var-file
参数来指定所有传统上在交互式使用下手动输入的变量值。
强烈建议使用支持远程状态的 Backend,因为 Terraform 可以自动将持久保存状态,后续运行可以在找回并更新状态。选择支持状态锁定的 Backend 还将提供针对 Terraform 并发运行的竞争安全保障。
1.9.10.1.2. 控制自动化中的 Terraform 输出
默认情况下,一些 Terraform 命令会提示用户下一步可能执行的步骤,通常包括具体的下一步要运行的命令。
自动化工具通常会封装正在运行的命令的具体细节,只提供抽象的步骤,这时 Terraform 输出的此类消息反而令人困惑,且无法操作,如果它们无意中鼓励用户完全绕过自动化工具,则可能还是有害的。
当环境变量 TF_IN_AUTOMATION
设置为任何非空值时,Terraform 会对其输出进行一些细微调整,不再强调要运行的特定命令。所做的具体更改会随着时间的推移而变化,但一般来说,Terraform 发现该变量时,会认为存在某种包装了 Terraform 的应用程序,它们会帮助用户进行下一步。
为了降低复杂性,该功能主要针对 Terraform 主要的工作流程命令实现。无论该变量为何值如何,其他辅助命令仍可能会产生命令行建议。
1.9.10.1.3. 在不同的机器上运行 plan 和 apply
在 CI 工具中运行时,可能很难或无法确保 plan
和 apply
命令在同一台计算机上的同一目录中运行,并且所有的文件都保持相同。
在不同的机器上运行 plan
和 apply
需要一些额外的步骤来确保正确的行为。稳健的策略如下:
plan
完成后,保存整个工作目录,包括init
期间创建的.terraform
子目录,并将其保存在apply
阶段可以访问得到的位置。常见的选择是作为所选 CI 工具中的“Build Artifact”。- 在运行
apply
之前,获取上一步中创建的存档并将其解压到相同的绝对路径。这会重新创建plan
后出现的所有内容,避免在plan
步骤期间创建本地文件的奇怪问题。
Terraform 目前为此类自动化系统设置了一些必须满足的前提条件:
- 保存的计划文件可以包含子模块的绝对路径以及代码中引用的其他数据文件。因此,必须确保在相同的绝对路径中还原保存的工作目录。这通常是通过在某种隔离中运行 Terraform 来实现的,例如可以控制文件系统布局的 Docker 容器。
- Terraform 假设该计划将在与其创建时相同的操作系统和 CPU 架构上 Apply。例如,这意味着无法在 Windows 计算机上创建计划,然后将其应用到 Linux 服务器上。
- Terraform 期望用于生成计划的 Provider 程序插件在应用计划时可用且相同,以确保正确执行计划。如果在创建和应用计划之间升级 Terraform 或任何插件,将会产生错误。
- Terraform 无法自动检测用于创建计划的凭据是否授予对用于应用该计划的相同资源的访问权限。如果对每个凭据使用不同的凭据(例如,使用只读凭据生成计划),那么确保两套凭据在它们所属的相应服务的帐户中保持一致非常重要。
警告:计划文件包含代码的完整副本、计划所要应用的状态数据以及传递给 terraform plan
的所有变量。如果其中包含任意敏感数据,则包含计划文件的存档工作目录应受到相应保护。对于 Provider 使用的身份验证凭据,建议尽可能使用环境变量,因为这些变量不会被包含在计划中或由 Terraform 以任何其他方式保存到磁盘。
1.9.10.1.4. 交互式审批计划
自动化 Terraform 工作流程的另一个挑战是需要在计划和应用之间进行交互式审批步骤。为了稳健地实现这一点,重要的是要确保一次只能有一个计划未完成,或者两个步骤相互连接,以便批准计划将足够的信息传递到应用步骤,以确保应用正确的计划,与后来也存在的一些计划相反。
不同的 CI 工具以不同的方式解决这个问题,但通常这是通过构建管道功能实现的,其中可以按顺序应用不同的步骤,后面的步骤可以访问前面步骤生成的数据。
推荐的方法是一次只允许一个计划处于未应用状态。应用计划时,针对同一状态生成的任何其他现有计划都会失效,因为现在必须相对于新状态重新计算它们。通过强制计划按顺序获得批准(或驳回),可以避免这种情况。
1.9.10.1.5. 自动批准计划
虽然强烈建议对生产环境应用计划前要进行人工审查,但有时在预生产或开发环境中部署时需要采取更自动化的方法。
如果不需要手动批准,可以使用更简单的命令序列:
terraform init -input=false
terraform apply -input=false -auto-approve
apply
命令的这个变体隐式地创建一个新计划,然后立即应用它。 -auto-approve
选项告诉 Terraform 在应用计划之前不需要对计划进行交互式批准。
警告:当 Terraform 有权对基础设施进行破坏性更改时,始终建议对计划进行人工审查,除非在发生意外更改时可以容忍停机。仅对非关键基础设施使用自动批准。
1.9.10.1.6. 用 terraform plan 命令测试 Pull Requests
terraform plan
可以用来对 Terraform 配置的有效性进行某些有限的验证,而不影响实际的基础设施。尽管 plan
命令会更新状态以匹配实际资源,从而确保准确的计划,但更新后的状态文件并不会持久保存,因此可以安全地使用该命令来生成仅为了帮助代码审查而创建的“一次性”计划。
实现此类工作流程时,可以在相关代码审查工具(例如,Github Pull Request)中使用钩子,为每个正在审查的新提交触发 CI 工具。在这种情况下,Terraform 可以按如下方式运行:
terraform plan -input=false
与在“主”工作流程中一样,可能需要根据需要设置 -var
或 -var-file
。在这种情况下不使用 -out
选项,因为为代码审查目的而生成的计划永远不会被应用。相反,一旦合并更改,就可以从主版本控制分支创建并应用新计划。
警告:请注意,通过输入变量或环境变量将敏感秘密数据传递给 Terraform 将使任何可以提交 PR 的人都可以看到,因此在开源项目或任何私人项目上必须谨慎使用此流程部分,或所有贡献者不应能够直接访问凭据等。
1.9.10.1.7. 多环境部署
Terraform 的自动化通常会被用来创建数个相同的配置,比如为预发布、测试或多租户基础设施等场景生成平行的环境。这种情况下的自动化可以帮助确保为每个环境使用正确的设置,并且在每次操作之前正确配置工作目录。
多环境编排最有趣的两个命令是 terraform init
和 terraform workspace
。前者可以与其他参数一起使用,以针对环境之间的差异定制 Backend 配置,而后者可用于在单个 Backend 中存储的相同配置的多个状态之间安全切换。
如果可能,建议对所有环境使用单一后端配置,并使用 terraform workspace
命令在工作空间之间切换:
terraform init -input=false
terraform workspace select QA
在此使用模型中,Backend 存储中使用固定的命名方案,以允许多个状态共存,而无需任何进一步的配置。
或者,自动化工具可以将环境变量 TF_WORKSPACE
设置为现有工作空间名称,这将覆盖使用 terraform workspace select
命令所做的任何选择。建议仅在非交互式使用中使用此环境变量,因为在本地 shell 环境中,很容易忘记设置该变量并将变更应用到错误的状态。
在一些更复杂的情况下,不可能跨环境共享相同的 Backend 配置。例如,环境可能运行在完全独立的不同帐户的服务里,因此需要对 Backend 本身使用不同的凭据或端点。在这种情况下,可以通过 terraform init
的 -backend-config
选项覆盖后端配置设置。
1.9.10.1.8. 预先安装的插件
在默认使用情况下,terraform init
会自动下载并安装代码中使用的所有 Provider 程序的插件,并将它们放置在 .terraform
目录的子目录中。这为简单的情况提供了更简单的工作流程,并允许每段代码可以使用不同版本的插件。
在自动化环境中,可能需要禁用此行为,而是提供一组已安装在运行 Terraform 的系统上的固定插件。这样就避免了每次执行时重新下载插件的开销,并允许系统管理员控制可以使用哪些插件。
要使用此机制,请在系统上的某个位置创建一个 Terraform 运行时会将插件可执行文件放入其中的目录。已发布的插件文件可在 releases.hashicorp.com 上下载。请务必下载适合目标操作系统和体系结构的文件。
提取必要的插件后,新插件目录的内容将如下所示:
$ ls -lah /usr/lib/custom-terraform-plugins
-rwxrwxr-x 1 user user 84M Jun 13 15:13 terraform-provider-aws-v1.0.0-x3
-rwxrwxr-x 1 user user 84M Jun 13 15:15 terraform-provider-rundeck-v2.3.0-x3
-rwxrwxr-x 1 user user 84M Jun 13 15:15 terraform-provider-mysql-v1.2.0-x3
文件名末尾的版本信息很重要,它使得 Terraform 可以推断每个插件的版本号。可以安装同一 Provider 程序插件的多个版本,Terraform 将使用与 Terraform 代码中的 Provider 程序版本约束相匹配的最新版本。
填充此目录后,可以使用 terraform init
的 -plugin-dir
选项跳过常规的自动下载和插件发现行为:
terraform init -input=false -plugin-dir=/usr/lib/custom-terraform-plugins
使用该组参数时,只有给定目录中的插件可以被使用。这使系统管理员可以对执行环境进行强力控制,但另一方面,它会阻止使用尚未安装到本地插件目录中的较新插件版本。哪种方法更合适将取决于每个组织内的特定情况。
还可以通过创建 terraform.d/plugins/OS_ARCH
目录与配置一起提前安装插件,在自动下载其他插件之前将搜索该目录。 -get-plugins=false
参数可禁止 Terraform 自动下载其他插件。