Terraform
创建 Terraform 计划
编写 Terraform 配置后,核心 Terraform 工作流程包含三个主要步骤:
- 初始化(Initialize):准备工作空间,以便 Terraform 可以应用配置。
- 计划(Plan):允许您在应用更改之前预览 Terraform 将要进行的更改。
- 应用(Apply):执行计划中定义的更改,以创建、更新或销毁资源。
配置基础设施时,Terraform 会在应用任何更改之前创建执行计划。Terraform 通过将您的 Terraform 配置与基础设施状态进行比较来创建计划。执行计划包含一组用于创建、更新或销毁资源的更改。您可以使用 terraform plan 命令将配置与资源状态进行比较、在应用更改之前进行审查,或刷新工作空间的状态。Terraform plan 支持 CI/CD 管道中的自动化工作流程,通过确保 Terraform 应用的基础设施更改与您或您的团队批准的更改相匹配,即使部署过程在不同的机器上或不同的时间完成。
在本教程中,您将了解 Terraform 如何生成执行计划、计划包含的内容,以及 terraform plan 命令在 Terraform 工作流程中的作用。为此,您将创建一个并应用已保存的 Terraform 计划,审查其内容,并分析计划如何反映配置更改。您还将学习如何在创建 Terraform 计划时定位特定资源。
先决条件
您可以使用 Terraform Community Edition 或 HCP Terraform 以相同的流程完成本教程。HCP Terraform 是一个平台,可用于管理和执行您的 Terraform 项目。它包含远程状态和执行、结构化计划输出、工作区资源摘要等功能。
选择 HCP Terraform 标签页以使用 HCP Terraform 完成本教程。
本教程假设您熟悉 Terraform 工作流程。如果您是 Terraform 新手,请先完成 入门教程。
为了完成本教程,您需要以下条件
- 本地安装 Terraform v1.6+。
- 一个配置了本地凭证的AWS 账户,这些凭证已配置为与 Terraform 一起使用。
- jq 命令行工具。
克隆示例仓库
在终端中,克隆 learn-terraform-plan 仓库。
$ git clone https://github.com/hashicorp-education/learn-terraform-plan
导航到克隆的仓库。
$ cd learn-terraform-plan
审查配置
此示例配置通过资源、本地模块和公共模块创建一个 EC2 实例。modules/aws-ec2-instance 子目录包含用于创建实例的本地模块。
$ tree
.
├── LICENSE
├── main.tf
├── modules
│ └── aws-ec2-instance
│ ├── main.tf
│ └── variables.tf
├── README.md
├── terraform.tf
└── variables.tf
Terraform 使用 terraform.tf 文件中指定的提供商版本。
terraform.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "6.2.0"
}
random = {
source = "hashicorp/random"
version = "3.5.1"
}
}
## ...
required_version = "~> 1.6"
}
打开顶级 main.tf 文件。此配置使用 aws 提供商通过 Ubuntu AMI 创建 EC2 实例。
main.tf
provider "aws" {
region = var.region
}
provider "random" {}
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
owners = ["099720109477"] # Canonical
}
resource "random_pet" "instance" {
length = 2
}
module "ec2-instance" {
source = "./modules/aws-ec2-instance"
ami_id = data.aws_ami.ubuntu.id
instance_name = random_pet.instance.id
}
此配置使用 random_pet 资源为实例生成名称。module.ec2-instance 块使用本地 aws-ec2-instance 模块来定义您的实例。
配置还将随机宠物名称传递给 hello 模块,该模块将生成包含随机宠物名称的输出。
main.tf
module "hello" {
source = "joatmon08/hello/random"
version = "6.0.0"
hellos = {
hello = random_pet.dog.id
second_hello = "World"
}
some_key = "secret"
}
初始化配置
为了生成执行计划,Terraform 需要安装配置引用的提供商和模块。然后,它将引用它们来创建计划。
使用 terraform init 初始化 Terraform 配置。
$ terraform init
Initializing the backend...
Initializing modules...
- ec2-instance in modules/aws-ec2-instance
Downloading registry.terraform.io/joatmon08/hello/random 6.0.0 for hello...
- hello in .terraform/modules/hello
Initializing provider plugins...
- Reusing previous version of hashicorp/aws from the dependency lock file
- Reusing previous version of hashicorp/random from the dependency lock file
- Installing hashicorp/aws v6.2.0...
- Installed hashicorp/aws v6.2.0 (signed by HashiCorp)
- Installing hashicorp/random v3.5.1...
- Installed hashicorp/random v3.5.1 (signed by HashiCorp)
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
创建计划
有三个命令会告诉 Terraform 生成执行计划:
terraform plan命令创建一个计划,其中包含一组使资源与配置匹配的更改。这使您可以在应用更改之前预览 Terraform 将采取的操作来修改基础设施。Terraform plan 不会对您的资源进行任何更改,您必须应用计划才能让 Terraform 进行更改。您还可以使用
-out标志保存计划。稍后,您可以应用已保存的计划,Terraform 将只执行计划中列出的更改。在自动化 Terraform 管道中,应用已保存的计划文件可确保 Terraform 只进行您期望的更改,即使您的管道在不同的机器上或不同的时间运行。terraform apply命令应用 Terraform 计划。如果您未传递已保存的计划,Terraform 将创建一个计划,并在应用计划之前提示您批准。terraform destroy命令创建一个执行计划来删除工作空间管理的所有资源。
使用 -out 标志生成已保存的计划。您将在本教程后面审查和应用此计划。
$ terraform plan -out "tfplan"
data.aws_ami.ubuntu: Reading...
data.aws_ami.ubuntu: Read complete after 0s [id=ami-055744c75048d8296]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# random_pet.instance will be created
+ resource "random_pet" "instance" {
+ id = (known after apply)
+ length = 2
+ separator = "-"
}
# module.ec2-instance.aws_instance.main will be created
+ resource "aws_instance" "main" {
## ...
Plan: 4 to add, 0 to change, 0 to destroy.
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Saved the plan to: tfplan
To perform exactly these actions, run the following command to apply:
terraform apply "tfplan"
Terraform 已创建计划并将其保存在 tfplan 文件中。
由于您尚未应用此配置,Terraform 计划创建其中定义的所有资源。
当您创建计划时,Terraform 会检查工作空间中是否存在现有状态文件。由于您尚未应用此配置,您的工作空间状态为空,Terraform 计划创建配置中定义的所有资源。
您可以应用已保存的计划文件来执行这些更改,但计划的内容不是人类可读的格式。使用 terraform show 命令打印出已保存的计划。
$ terraform show "tfplan"
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# random_pet.instance will be created
+ resource "random_pet" "instance" {
+ id = (known after apply)
+ length = 2
+ separator = "-"
}
## ...
# module.hello.random_pet.server will be created
+ resource "random_pet" "server" {
+ id = (known after apply)
+ keepers = {
+ "hello" = (known after apply)
+ "secret_key" = "secret"
}
+ length = 2
+ separator = "-"
}
Plan: 4 to add, 0 to change, 0 to destroy.
这与您创建已保存计划时 Terraform 打印的输出相同。您可以在应用已保存的计划之前与团队一起审查此计划输出,以确保更改符合您的预期。
Terraform 还可以将已保存计划的内容报告为 JSON。这在自动化管道中使用 Terraform 时通常很有用,因为您可以使用代码来检查计划。
将已保存的计划转换为 JSON,将其传递给 jq 进行格式化,并将输出保存到新文件中。
$ terraform show -json "tfplan" | jq > tfplan.json
审查计划
在本节中,您将审查 Terraform 在计划文件中捕获的有关资源的数据。使用 jq 命令查询计划的 JSON 格式版本。
Terraform 记录用于生成计划的 Terraform 版本以及计划文件格式的版本。这将确保您在使用已保存计划时使用相同的版本来应用这些更改。
$ jq '.terraform_version, .format_version' tfplan.json
"1.12.0"
"1.2"
审查计划配置
.configuration JSON 对象是 terraform plan 时配置的快照。
此配置快照捕获 .terraform.lock.hcl 文件中记录的提供商版本,确保您使用生成计划的相同提供商版本来应用它。请注意,配置同时考虑了根模块和子模块使用的提供商版本。
$ jq '.configuration.provider_config' tfplan.json
{
"aws": {
"name": "aws",
"full_name": "registry.terraform.io/hashicorp/aws",
"version_constraint": "6.2.0",
"expressions": {
"region": {
"references": [
"var.region"
]
}
}
},
"random": {
"name": "random",
"full_name": "registry.terraform.io/hashicorp/random",
"version_constraint": "3.5.1"
}
}
configuration 部分进一步组织了顶级 root_module 中定义的资源。
$ jq '.configuration.root_module.resources' tfplan.json
[
{
"address": "random_pet.instance",
"mode": "managed",
"type": "random_pet",
"name": "instance",
"provider_config_key": "random",
"expressions": {
"length": {
"constant_value": 2
}
},
"schema_version": 0
},
## ...
module_calls 部分包含所用模块的详细信息、它们的输入变量和输出,以及要创建的资源。
$ jq '.configuration.root_module.module_calls' tfplan.json
{
"ec2-instance": {
"source": "./modules/aws-ec2-instance",
"expressions": {
"ami_id": {
"references": [
"data.aws_ami.ubuntu.id",
"data.aws_ami.ubuntu"
]
},
"instance_name": {
"references": [
"random_pet.instance.id",
"random_pet.instance"
]
}
},
## ...
configuration 对象还记录了资源编写配置中对其他资源的任何引用,这有助于 Terraform 在应用计划时确定正确的操作顺序。
$ jq '.configuration.root_module.module_calls.hello.expressions.hellos.references' tfplan.json
[
"random_pet.instance.id",
"random_pet.instance"
]
审查计划的资源更改
审查本地模块 ec2-instance 中 aws_instance 资源的计划更改。
表示包括:
action字段捕获此资源采取的操作,在本例中为create。before字段捕获运行前的资源状态。在本例中,值为null,因为资源尚不存在。after字段捕获要为资源定义的状态。after_unknown字段捕获将通过操作计算或确定的值列表,并将其设置为true。before_sensitive和after_sensitive字段捕获任何标记为sensitive的值列表。当您应用配置时,Terraform 将使用这些列表来确定要隐藏哪些输出值。
$ jq '.resource_changes[] | select( .address == "module.ec2-instance.aws_instance.main")' tfplan.json
{
"address": "module.ec2-instance.aws_instance.main",
"module_address": "module.ec2-instance",
"mode": "managed",
"type": "aws_instance",
"name": "main",
"provider_name": "registry.terraform.io/hashicorp/aws",
"change": {
"actions": [
"create"
],
"before": null,
"after": {
"ami": "ami-055744c75048d8296",
"credit_specification": [],
"get_password_data": false,
"hibernation": null,
"instance_type": "t2.micro",
"launch_template": [],
"source_dest_check": true,
"timeouts": null,
"user_data_replace_on_change": false,
"volume_tags": null
},
"after_unknown": {
"arn": true,
"associate_public_ip_address": true,
"availability_zone": true,
## ...
}
}
}
审查计划值
planned_values 对象是资源“before”和“after”值差异的报告,向您展示了使用此计划文件运行的预期结果。
在此示例中,module.ec2-instance.aws_instance 资源包含您将用于在 Terraform 配置中引用资源的地址、提供商名称以及所有属性的值作为一个对象。此格式在一个对象中解决了先前状态和预期状态之间的差异,以演示配置的计划结果,这对于计划数据的任何下游使用者来说都更容易使用。例如,Terraform Sentinel CLI 根据此处记录的计划结果测试策略。HCP Terraform 中的成本估算功能也依赖于 planned_values 数据来确定您的基础设施支出的变化。
$ jq '.planned_values' tfplan.json
{
"root_module": {
"resources": [
{
"address": "random_pet.instance",
## ...
}
],
"child_modules": [
{
"resources": [
{
"address": "module.ec2-instance.aws_instance.main",
"mode": "managed",
"type": "aws_instance",
"name": "main",
"provider_name": "registry.terraform.io/hashicorp/aws",
"schema_version": 1,
"values": {
"ami": "ami-034568121cfdea9c3",
"credit_specification": [],
"get_password_data": false,
## ...
应用已保存的计划
在终端中,应用已保存的计划。
$ terraform apply "tfplan"
random_pet.instance: Creating...
random_pet.instance: Creation complete after 0s [id=apt-zebra]
module.hello.random_pet.number_2: Creating...
module.hello.random_pet.server: Creating...
module.hello.random_pet.number_2: Creation complete after 0s [id=sweet-kid]
module.hello.random_pet.server: Creation complete after 0s [id=more-swan]
module.ec2-instance.aws_instance.main: Creating...
module.ec2-instance.aws_instance.main: Still creating... [10s elapsed]
module.ec2-instance.aws_instance.main: Still creating... [20s elapsed]
module.ec2-instance.aws_instance.main: Still creating... [30s elapsed]
module.ec2-instance.aws_instance.main: Creation complete after 32s [id=i-04107c0289b72e9c1]
Apply complete! Resources: 4 added, 0 changed, 0 destroyed.
Terraform 根据已保存的计划应用了更改。
修改配置
输入变量允许您轻松更新配置值,而无需编辑配置文件。
打开顶级配置目录中的 variables.tf 文件。添加以下配置以定义用于 hello 模块的新输入变量。
variables.tf
variable "secret_key" {
type = string
sensitive = true
description = "Secret key for hello module"
}
然后,创建 terraform.tfvars 文件,并设置新的 secret_key 输入变量值。
terraform.tfvars
secret_key = "TOPSECRET"
最后,更新 main.tf 中 hello 模块的配置以引用新的输入变量。
main.tf
module "hello" {
source = "joatmon08/hello/random"
version = "6.0.0"
hellos = {
hello = random_pet.instance.id
second_hello = "World"
}
some_key = var.secret_key
}
创建新计划
创建新的 Terraform 计划并将其保存为 tfplan-input-var。
$ terraform plan -out "tfplan-input-var"
random_pet.instance: Refreshing state... [id=apt-zebra]
module.hello.random_pet.number_2: Refreshing state... [id=sweet-kid]
module.hello.random_pet.server: Refreshing state... [id=more-swan]
data.aws_ami.ubuntu: Reading...
data.aws_ami.ubuntu: Read complete after 0s [id=ami-055744c75048d8296]
module.ec2-instance.aws_instance.main: Refreshing state... [id=i-04107c0289b72e9c1]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement
Terraform will perform the following actions:
# module.hello.random_pet.server must be replaced
-/+ resource "random_pet" "server" {
~ id = "more-swan" -> (known after apply)
~ keepers = { # forces replacement
# Warning: this attribute value will be marked as sensitive and will not
# display in UI output after applying this change.
~ "secret_key" = (sensitive value)
# (1 unchanged element hidden)
}
# (2 unchanged attributes hidden)
}
Plan: 1 to add, 0 to change, 1 to destroy.
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Saved the plan to: tfplan-input-var
To perform exactly these actions, run the following command to apply:
terraform apply "tfplan-input-var"
将新的计划文件转换为机器可读的 JSON 格式。
$ terraform show -json tfplan-input-var | jq > tfplan-input-var.json
审查新计划
当您创建此计划时,Terraform 确定工作目录中已包含一个状态文件,并使用该状态来规划资源更改。
由于 Terraform 使用现有资源和输入变量创建了此计划,因此您的计划文件包含一些新字段。
审查计划输入变量
现在您已经定义了输入变量,Terraform 也会将它们捕获在计划文件中。
$ jq '.variables' tfplan-input-var.json
{
"project_name": {
"value": "terraform-plan"
},
"region": {
"value": "us-east-1"
},
"secret_key": {
"value": "TOPSECRET"
}
}
与输入变量不同,Terraform 不会将配置中使用的任何环境变量的值记录在计划文件中。使用环境变量是向 Terraform 传递敏感值(例如提供商凭证)的推荐方法之一。
审查计划 prior_state
当您创建此计划时,Terraform 确定工作目录中已包含一个状态文件,并使用该状态来规划资源更改。与第一次运行的计划文件不同,此文件现在包含一个 prior_state 对象,该对象捕获计划操作之前状态文件的确切内容。
$ jq '.prior_state' tfplan-input-var.json
{
"format_version": "1.0",
"terraform_version": "1.6.0",
"values": {
"root_module": {
"resources": [
{
"address": "data.aws_ami.ubuntu",
"mode": "data",
"type": "aws_ami",
"name": "ubuntu",
"provider_name": "registry.terraform.io/hashicorp/aws",
"schema_version": 0,
## ...
}
]
}
}
}
审查计划资源更改
现在您的状态文件跟踪资源,Terraform 在创建执行计划时会考虑现有状态。例如,module.hello.random_pet.server 对象现在在 before 和 after 字段中都包含数据,分别表示先前的配置和所需的配置。
$ jq '.resource_changes[] | select( .address == "module.hello.random_pet.server")' tfplan-input-var.json
{
"address": "module.hello.random_pet.server",
"module_address": "module.hello",
"mode": "managed",
"type": "random_pet",
"name": "server",
"provider_name": "registry.terraform.io/hashicorp/random",
"change": {
"actions": [
"delete",
"create"
],
"before": {
"id": "tidy-crawdad",
## ...
"after": {
"keepers": {
"hello": "fun-possum",
"secret_key": "TOPSECRET"
},
## ...
"action_reason": "replace_because_cannot_update"
}
请注意,actions 列表现在设置为 ["delete","create"],并且 action_reason 是 "replace_because_cannot_update" - 对资源的 secret_key 的更改是破坏性的,因此 Terraform 必须删除并重新创建此资源。Terraform 根据您更改的提供商属性来确定它是否可以在原地更新资源或必须重新创建它。
在工作目录中创建资源后,Terraform 使用先前的状态、刷新操作返回的数据以及编写的配置来确定要进行的更改。Terraform 支持您可以使用额外的标志来修改其构建执行计划的方式。例如,您可以创建仅刷新状态文件而不修改资源配置的计划,或仅针对特定资源进行更新或替换。
清理基础设施
完成本教程后,请在继续之前销毁创建的资源。创建并应用销毁计划。
$ terraform plan -destroy -out "tfplan-destroy"
## ...
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
- destroy
Terraform will perform the following actions:
# random_pet.instance will be destroyed
- resource "random_pet" "instance" {
## ...
Plan: 0 to add, 0 to change, 4 to destroy.
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Saved the plan to: tfplan-destroy
To perform exactly these actions, run the following command to apply:
terraform apply "tfplan-destroy"
当您使用 -destroy 标志时,Terraform 会创建一个销毁配置中所有资源的计划。应用该计划以销毁您的资源。
$ terraform apply "tfplan-destroy"
## ...
Terraform used the selected providers to generate the following execution plan.
Resource actions are indicated with the following symbols:
- destroy
Terraform will perform the following actions:
# random_pet.instance will be destroyed
- resource "random_pet" "instance" {
## ...
Plan: 0 to add, 0 to change, 4 to destroy.
Do you really want to destroy all resources?
Terraform will destroy all your managed infrastructure, as shown above.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value: yes
## ...
Destroy complete! Resources: 4 destroyed.
terraform destroy 命令是一个快捷方式,它创建一个销毁计划,然后等待您批准。保存销毁计划允许您在应用之前进行审查,就像常规的已保存计划一样。
后续步骤
在本教程中,您了解了 Terraform 如何构建执行计划并使用已保存的计划。您还探讨了 terraform plan 和 terraform apply 命令之间的关系。
查看以下教程以尝试一些可用的计划模式和选项:
了解如何使用仅刷新计划来更新状态文件,使其与资源的实际配置保持一致。
了解如何使用
-replace标志重新创建资源。了解有关使用资源定位来限定受 Terraform 操作影响的资源范围的更多信息。
查看JSON 输出格式文档,了解有关计划文件内容和格式的更多详细信息。