1.2.1. Terraform初步体验
1.2.1.1. 安装
首先我们先安装Terraform。对于Ubuntu用户:
sudo apt-get update && sudo apt-get install -y wget curl gnupg software-properties-common
wget -O- https://apt.releases.hashicorp.com/gpg | \
gpg --dearmor | \
sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \
https://apt.releases.hashicorp.com $(lsb_release -cs) main" | \
sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update
sudo apt-get install terraform
对于CentOS用户:
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
sudo yum -y install terraform
对于Mac用户:
brew tap hashicorp/tap
brew install hashicorp/tap/terraform
对于Windows用户,官方推荐的包管理器是choco,可以去https://chocolatey.org/ 下载安装好chocolatey后,以管理员身份启动powershell,然后:
choco install terraform
如果只想纯手动安装,那么可以前往Terraform官网下载对应操作系统的可执行文件(Terraform是用go编写的,只有一个可执行文件),解压缩到指定的位置后,配置一下环境变量的PATH,使其包含Terraform所在的目录即可。
1.2.1.2. 验证
terraform version
Terraform v1.7.3
terraform -h
Usage: terraform [global options] <subcommand> [args]
The available commands for execution are listed below.
The primary workflow commands are given first, followed by
less common or more advanced commands.
Main commands:
init Prepare your working directory for other commands
validate Check whether the configuration is valid
plan Show changes required by the current configuration
apply Create or update infrastructure
destroy Destroy previously-created infrastructure
All other commands:
console Try Terraform expressions at an interactive command prompt
fmt Reformat your configuration in the standard style
force-unlock Release a stuck lock on the current workspace
get Install or upgrade remote Terraform modules
graph Generate a Graphviz graph of the steps in an operation
import Associate existing infrastructure with a Terraform resource
login Obtain and save credentials for a remote host
logout Remove locally-stored credentials for a remote host
metadata Metadata related commands
output Show output values from your root module
providers Show the providers required for this configuration
refresh Update the state to match remote systems
show Show the current state or a saved plan
state Advanced state management
taint Mark a resource instance as not fully functional
test Execute integration tests for Terraform modules
untaint Remove the 'tainted' state from a resource instance
version Show the current Terraform version
workspace Workspace management
Global options (use these before the subcommand, if any):
-chdir=DIR Switch to a different working directory before executing the
given subcommand.
-help Show this help output, or the help for a specified subcommand.
-version An alias for the "version" subcommand.
1.2.1.3. 一个简单的例子
为了给读者一个安全的体验环境,避免付费,我们下面使用 LocalStack 搭配 Terraform 来创建一批虚拟的云资源。
启动 LocalStack 最简单的方法是使用 Docker:
docker run \
--rm -it \
-p 4566:4566 \
-p 4510-4559:4510-4559 \
localstack/localstack
看到如下输出时说明 LocalStack 已经准备好了:
...
LocalStack version: 3.1.1.dev
LocalStack build date: 2024-02-14
LocalStack build git hash: ebaf4ea8d
2024-02-15T01:56:11.845 INFO --- [-functhread4] hypercorn.error : Running on https://0.0.0.0:4566 (CTRL + C to quit)
2024-02-15T01:56:11.845 INFO --- [-functhread4] hypercorn.error : Running on https://0.0.0.0:4566 (CTRL + C to quit)
2024-02-15T01:56:12.103 INFO --- [ MainThread] localstack.utils.bootstrap : Execution of "start_runtime_components" took 1804.56ms
Ready.
然后我们创建一个干净的空文件夹,在里面创建一个 main.tf
文件(.tf
就是 Terraform
,Terraform
代码大部分是 .tf
文件,语法是 HCL,当然目前也支持 JSON 格式的 Terraform 代码,但我们暂时只以 .tf
为例):
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~>5.0"
}
}
}
provider "aws" {
access_key = "test"
secret_key = "test"
region = "us-east-1"
s3_use_path_style = false
skip_credentials_validation = true
skip_metadata_api_check = true
skip_requesting_account_id = true
endpoints {
apigateway = "http://localhost:4566"
apigatewayv2 = "http://localhost:4566"
cloudformation = "http://localhost:4566"
cloudwatch = "http://localhost:4566"
dynamodb = "http://localhost:4566"
ec2 = "http://localhost:4566"
es = "http://localhost:4566"
elasticache = "http://localhost:4566"
firehose = "http://localhost:4566"
iam = "http://localhost:4566"
kinesis = "http://localhost:4566"
lambda = "http://localhost:4566"
rds = "http://localhost:4566"
redshift = "http://localhost:4566"
route53 = "http://localhost:4566"
s3 = "http://s3.localhost.localstack.cloud:4566"
secretsmanager = "http://localhost:4566"
ses = "http://localhost:4566"
sns = "http://localhost:4566"
sqs = "http://localhost:4566"
ssm = "http://localhost:4566"
stepfunctions = "http://localhost:4566"
sts = "http://localhost:4566"
}
}
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-trusty-14.04-amd64-server-20170727"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
owners = ["099720109477"] # Canonical
}
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
tags = {
Name = "HelloWorld"
}
}
resource "aws_eip_association" "eip_assoc" {
instance_id = aws_instance.web.id
allocation_id = aws_eip.example.id
}
resource "aws_eip" "example" {
domain = "vpc"
}
output "instance_id" {
value = aws_instance.web.id
}
这里要注意代码中的这一段:
provider "aws" {
access_key = "test"
secret_key = "test"
region = "us-east-1"
s3_use_path_style = false
skip_credentials_validation = true
skip_metadata_api_check = true
skip_requesting_account_id = true
endpoints {
apigateway = "http://localhost:4566"
apigatewayv2 = "http://localhost:4566"
cloudformation = "http://localhost:4566"
cloudwatch = "http://localhost:4566"
dynamodb = "http://localhost:4566"
ec2 = "http://localhost:4566"
es = "http://localhost:4566"
elasticache = "http://localhost:4566"
firehose = "http://localhost:4566"
iam = "http://localhost:4566"
kinesis = "http://localhost:4566"
lambda = "http://localhost:4566"
rds = "http://localhost:4566"
redshift = "http://localhost:4566"
route53 = "http://localhost:4566"
s3 = "http://s3.localhost.localstack.cloud:4566"
secretsmanager = "http://localhost:4566"
ses = "http://localhost:4566"
sns = "http://localhost:4566"
sqs = "http://localhost:4566"
ssm = "http://localhost:4566"
stepfunctions = "http://localhost:4566"
sts = "http://localhost:4566"
}
}
因为我们使用的是通过 LocalStack 模拟的虚拟 AWS 服务,所以在这里我们需要在 endpoints
中把各个服务 API 的端点设置为 LocalStack 暴露的本地端点。原本的 access_key
和 secret_key
应该是通过 AWS IAM 获取的,在这里我们就可以用假的 Key 来替代。
这段代码比较简单,头部的terraform
这一段声明了这段代码所需要的Terraform版本以及 AWS 插件版本,后面的 provider
段则是给出了调用 AWS API所需要的 key 和 region
等信息。
真正定义云端基础设施的代码就是后面的部分,分为三部分,data
、resource
和 output
。
data
代表利用 AWS 插件定义的 data
模型对 AWS 进行查询,例如我们在代码中利用 data
查询名为 "ubuntu/images/hvm-ssd/ubuntu-trusty-14.04-amd64-server-20170727
的 AWS 虚拟机镜像 ID,这样我们就不需要人工在界面上去查询相关 ID,再硬编码到代码中。请注意,由于我们使用的是 LocalStack 虚拟的 AWS,所以这里的 name
和 owners
都是参照了 LocalStack 自带的模拟数据 构造的。
resource
代表我们需要在云端创建的资源,在例子里我们创建了三个资源,分别是主机、弹性公网 ip,以及主机和公网 ip 的绑定。
我们在定义主机时给定了主机的尺寸名称等关键信息,最后,我们声明了一个output,名字是eip
,它的值就是我们创建的弹性公网ip的值。
运行这段代码很简单,让我们在代码所在的路径下进入命令行,执行:
$ terraform init
这时Terraform会进行初始化操作,通过官方插件仓库下载对应操作系统的UCloud插件。如果一切都正常,读者应该会看到:
Initializing the backend...
Initializing provider plugins...
- Finding latest version of hashicorp/aws...
- Installing hashicorp/aws v5.36.0...
- Installed hashicorp/aws v5.36.0 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.
╷
│ Warning: Provider development overrides are in effect
│
│ The following provider development overrides are set in the CLI configuration:
│ - azure/modtm in C:\Users\hezijie\go\bin
│ - lonegunmanb/aztfteam in C:\Users\hezijie\go\bin
│
│ Skip terraform init when using provider development overrides. It is not necessary and may error unexpectedly.
╵
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.
PS C:\project\mySpike> terraform terraform apply -auto-approve
Terraform has no command named "terraform".
To see all of Terraform's top-level commands, run:
terraform -help
然后我们可以预览一下代码即将产生的变更:
$ data.aws_ami.ubuntu: Reading...
data.aws_ami.ubuntu: Read complete after 0s [id=ami-1e749f67]
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:
# aws_eip.example will be created
+ resource "aws_eip" "example" {
+ allocation_id = (known after apply)
+ association_id = (known after apply)
+ carrier_ip = (known after apply)
+ customer_owned_ip = (known after apply)
+ domain = "vpc"
+ id = (known after apply)
+ instance = (known after apply)
+ network_border_group = (known after apply)
+ network_interface = (known after apply)
+ private_dns = (known after apply)
+ private_ip = (known after apply)
+ public_dns = (known after apply)
+ public_ip = (known after apply)
+ public_ipv4_pool = (known after apply)
+ tags_all = (known after apply)
+ vpc = (known after apply)
}
# aws_eip_association.eip_assoc will be created
+ resource "aws_eip_association" "eip_assoc" {
+ allocation_id = (known after apply)
+ id = (known after apply)
+ instance_id = (known after apply)
+ network_interface_id = (known after apply)
+ private_ip_address = (known after apply)
+ public_ip = (known after apply)
}
# aws_instance.web will be created
+ resource "aws_instance" "web" {
+ ami = "ami-1e749f67"
+ arn = (known after apply)
+ associate_public_ip_address = (known after apply)
+ availability_zone = (known after apply)
+ cpu_core_count = (known after apply)
+ cpu_threads_per_core = (known after apply)
+ disable_api_stop = (known after apply)
+ disable_api_termination = (known after apply)
+ ebs_optimized = (known after apply)
+ get_password_data = false
+ host_id = (known after apply)
+ host_resource_group_arn = (known after apply)
+ iam_instance_profile = (known after apply)
+ id = (known after apply)
+ instance_initiated_shutdown_behavior = (known after apply)
+ instance_lifecycle = (known after apply)
+ instance_state = (known after apply)
+ instance_type = "t3.micro"
+ ipv6_address_count = (known after apply)
+ ipv6_addresses = (known after apply)
+ key_name = (known after apply)
+ monitoring = (known after apply)
+ outpost_arn = (known after apply)
+ password_data = (known after apply)
+ placement_group = (known after apply)
+ placement_partition_number = (known after apply)
+ primary_network_interface_id = (known after apply)
+ private_dns = (known after apply)
+ private_ip = (known after apply)
+ public_dns = (known after apply)
+ public_ip = (known after apply)
+ secondary_private_ips = (known after apply)
+ security_groups = (known after apply)
+ source_dest_check = true
+ spot_instance_request_id = (known after apply)
+ subnet_id = (known after apply)
+ tags = {
+ "Name" = "HelloWorld"
}
+ tags_all = {
+ "Name" = "HelloWorld"
}
+ tenancy = (known after apply)
+ user_data = (known after apply)
+ user_data_base64 = (known after apply)
+ user_data_replace_on_change = false
+ vpc_security_group_ids = (known after apply)
}
Plan: 3 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ instance_id = (known after apply)
╷
│ Warning: AWS account ID not found for provider
│
│ with provider["registry.terraform.io/hashicorp/aws"],
│ on main.tf line 1, in provider "aws":
│ 1: provider "aws" {
│
│ See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.
╵
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run
"terraform apply" now.
这段输出告诉我们,代码即将创建 3 个新资源,修改 0 个资源,删除 0 个资源。资源的属性少部分是我们在代码中直接给出的,或是通过 data
查询的,所以在plan命令的结果中可以看到它们的值;更多的属性只有在资源真正被创建以后我们才能看到,所以会显示 (known after apply)
。
然后我们运行一下:
$ terraform apply
data.aws_ami.ubuntu: Reading...
data.aws_ami.ubuntu: Read complete after 0s [id=ami-1e749f67]
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:
# aws_eip.example will be created
+ resource "aws_eip" "example" {
+ allocation_id = (known after apply)
+ association_id = (known after apply)
+ carrier_ip = (known after apply)
+ customer_owned_ip = (known after apply)
+ domain = "vpc"
+ id = (known after apply)
+ instance = (known after apply)
+ network_border_group = (known after apply)
+ network_interface = (known after apply)
+ private_dns = (known after apply)
+ private_ip = (known after apply)
+ public_dns = (known after apply)
+ public_ip = (known after apply)
+ public_ipv4_pool = (known after apply)
+ tags_all = (known after apply)
+ vpc = (known after apply)
}
# aws_eip_association.eip_assoc will be created
+ resource "aws_eip_association" "eip_assoc" {
+ allocation_id = (known after apply)
+ id = (known after apply)
+ instance_id = (known after apply)
+ network_interface_id = (known after apply)
+ private_ip_address = (known after apply)
+ public_ip = (known after apply)
}
# aws_instance.web will be created
+ resource "aws_instance" "web" {
+ ami = "ami-1e749f67"
+ arn = (known after apply)
+ associate_public_ip_address = (known after apply)
+ availability_zone = (known after apply)
+ cpu_core_count = (known after apply)
+ cpu_threads_per_core = (known after apply)
+ disable_api_stop = (known after apply)
+ disable_api_termination = (known after apply)
+ ebs_optimized = (known after apply)
+ get_password_data = false
+ host_id = (known after apply)
+ host_resource_group_arn = (known after apply)
+ iam_instance_profile = (known after apply)
+ id = (known after apply)
+ instance_initiated_shutdown_behavior = (known after apply)
+ instance_lifecycle = (known after apply)
+ instance_state = (known after apply)
+ instance_type = "t3.micro"
+ ipv6_address_count = (known after apply)
+ ipv6_addresses = (known after apply)
+ key_name = (known after apply)
+ monitoring = (known after apply)
+ outpost_arn = (known after apply)
+ password_data = (known after apply)
+ placement_group = (known after apply)
+ placement_partition_number = (known after apply)
+ primary_network_interface_id = (known after apply)
+ private_dns = (known after apply)
+ private_ip = (known after apply)
+ public_dns = (known after apply)
+ public_ip = (known after apply)
+ secondary_private_ips = (known after apply)
+ security_groups = (known after apply)
+ source_dest_check = true
+ spot_instance_request_id = (known after apply)
+ subnet_id = (known after apply)
+ tags = {
+ "Name" = "HelloWorld"
}
+ tags_all = {
+ "Name" = "HelloWorld"
}
+ tenancy = (known after apply)
+ user_data = (known after apply)
+ user_data_base64 = (known after apply)
+ user_data_replace_on_change = false
+ vpc_security_group_ids = (known after apply)
}
Plan: 3 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ instance_id = (known after apply)
╷
│ Warning: AWS account ID not found for provider
│
│ with provider["registry.terraform.io/hashicorp/aws"],
│ on main.tf line 1, in provider "aws":
│ 1: provider "aws" {
│
│ See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.
╵
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
当我们运行 terraform apply
时,Terraform 会首先重新计算一下变更计划,并且像刚才执行 plan
命令那样把变更计划打印给我们,要求我们人工确认。让我们输入 yes
,然后回车:
aws_eip.example: Creating...
aws_instance.web: Creating...
aws_eip.example: Creation complete after 0s [id=eipalloc-c71b7984]
aws_instance.web: Still creating... [10s elapsed]
aws_instance.web: Creation complete after 10s [id=i-c22230eecf2b8a950]
aws_eip_association.eip_assoc: Creating...
aws_eip_association.eip_assoc: Creation complete after 0s [id=eipassoc-194f62e6]
╷
│ Warning: AWS account ID not found for provider
│
│ with provider["registry.terraform.io/hashicorp/aws"],
│ on main.tf line 1, in provider "aws":
│ 1: provider "aws" {
│
│ See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.
╵
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
Outputs:
instance_id = "i-c22230eecf2b8a950"
可以看到,Terraform 成功地创建了我们定义的资源,并且把我们定义的输出给打印了出来。
1.2.1.4. 清理
完成这个体验后,不要忘记清理我们的云端资源。我们可以通过调用 destroy
命令来轻松完成清理:
$ terraform destroy
data.aws_ami.ubuntu: Reading...
aws_eip.example: Refreshing state... [id=eipalloc-c71b7984]
data.aws_ami.ubuntu: Read complete after 0s [id=ami-1e749f67]
aws_instance.web: Refreshing state... [id=i-c22230eecf2b8a950]
aws_eip_association.eip_assoc: Refreshing state... [id=eipassoc-194f62e6]
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:
# aws_eip.example will be destroyed
- resource "aws_eip" "example" {
- allocation_id = "eipalloc-c71b7984" -> null
- association_id = "eipassoc-194f62e6" -> null
- domain = "vpc" -> null
- id = "eipalloc-c71b7984" -> null
- instance = "i-c22230eecf2b8a950" -> null
- network_interface = "eni-b689ba2c" -> null
- private_dns = "ip-10-73-124-108.ec2.internal" -> null
- private_ip = "10.73.124.108" -> null
- public_dns = "ec2-127-127-89-71.compute-1.amazonaws.com" -> null
- public_ip = "127.127.89.71" -> null
- tags = {} -> null
- tags_all = {} -> null
- vpc = true -> null
}
# aws_eip_association.eip_assoc will be destroyed
- resource "aws_eip_association" "eip_assoc" {
- allocation_id = "eipalloc-c71b7984" -> null
- id = "eipassoc-194f62e6" -> null
- instance_id = "i-c22230eecf2b8a950" -> null
- network_interface_id = "eni-b689ba2c" -> null
- private_ip_address = "10.73.124.108" -> null
- public_ip = "127.127.89.71" -> null
}
# aws_instance.web will be destroyed
- resource "aws_instance" "web" {
- ami = "ami-1e749f67" -> null
- arn = "arn:aws:ec2:us-east-1::instance/i-c22230eecf2b8a950" -> null
- associate_public_ip_address = true -> null
- availability_zone = "us-east-1a" -> null
- disable_api_stop = false -> null
- disable_api_termination = false -> null
- ebs_optimized = false -> null
- get_password_data = false -> null
- hibernation = false -> null
- id = "i-c22230eecf2b8a950" -> null
- instance_initiated_shutdown_behavior = "stop" -> null
- instance_state = "running" -> null
- instance_type = "t3.micro" -> null
- ipv6_address_count = 0 -> null
- ipv6_addresses = [] -> null
- monitoring = false -> null
- placement_partition_number = 0 -> null
- primary_network_interface_id = "eni-b689ba2c" -> null
- private_dns = "ip-10-73-124-108.ec2.internal" -> null
- private_ip = "10.73.124.108" -> null
- public_dns = "ec2-127-127-89-71.compute-1.amazonaws.com" -> null
- public_ip = "127.127.89.71" -> null
- secondary_private_ips = [] -> null
- security_groups = [] -> null
- source_dest_check = true -> null
- subnet_id = "subnet-73eb6c90" -> null
- tags = {
- "Name" = "HelloWorld"
} -> null
- tags_all = {
- "Name" = "HelloWorld"
} -> null
- tenancy = "default" -> null
- user_data_replace_on_change = false -> null
- vpc_security_group_ids = [] -> null
- root_block_device {
- delete_on_termination = true -> null
- device_name = "/dev/sda1" -> null
- encrypted = false -> null
- iops = 0 -> null
- tags = {} -> null
- throughput = 0 -> null
- volume_id = "vol-b56e5dc7" -> null
- volume_size = 8 -> null
- volume_type = "gp2" -> null
}
}
Plan: 0 to add, 0 to change, 3 to destroy.
Changes to Outputs:
- instance_id = "i-c22230eecf2b8a950" -> null
╷
│ Warning: AWS account ID not found for provider
│
│ with provider["registry.terraform.io/hashicorp/aws"],
│ on main.tf line 1, in provider "aws":
│ 1: provider "aws" {
│
│ See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.
╵
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
可以看到,Terraform 列出了它即将清理的资源信息,并且要求我们人工确认同意继续执行清理操作。我们输入 yes
,然后回车:
aws_eip_association.eip_assoc: Destroying... [id=eipassoc-194f62e6]
aws_eip_association.eip_assoc: Destruction complete after 0s
aws_eip.example: Destroying... [id=eipalloc-c71b7984]
aws_instance.web: Destroying... [id=i-c22230eecf2b8a950]
aws_eip.example: Destruction complete after 0s
aws_instance.web: Still destroying... [id=i-c22230eecf2b8a950, 10s elapsed]
aws_instance.web: Destruction complete after 10s
Destroy complete! Resources: 3 destroyed.
很快的,刚才创建的资源就全部被删除了。(在本例中因为我们使用了 LocalStack,所以我们并没有真的创建 AWS 资源,所以不执行 destroy
也没什么问题,但是在试验后清理实验资源是一个很好的习惯)
Terraform 与以往诸如 Ansible 等配置管理工具比较大的不同在于,它是根据代码计算出的目标状态与当前状态的差异来计算变更计划的,有兴趣的读者可以在执行 terraform apply
以后,直接再执行一次 terraform apply
,看看会发生什么,就能明白他们之间的差异。
实际上这段代码在 apply
以后,直接再次 apply
,得到的计划会是什么也不做:
$ terraform plan
data.aws_ami.ubuntu: Reading...
aws_eip.example: Refreshing state... [id=eipalloc-c12bcca1]
data.aws_ami.ubuntu: Read complete after 0s [id=ami-1e749f67]
aws_instance.web: Refreshing state... [id=i-0159574cc2f7986ad]
aws_eip_association.eip_assoc: Refreshing state... [id=eipassoc-7f849867]
No changes. Your infrastructure matches the configuration.
因为当前云端的资源状态已经完全符合代码所描述的期望状态了,所以 Terraform 什么也不会做。好了,这就是我们对 Terraform 的一个初步体验。