terraform的初步步骤 (Terraform’s first step)
总结
这个页面的目的是让读者能够直观地理解Terraform是什么。有关Terraform的详细信息请参考以下内容。
Terraform 是一种用于以代码构建和管理云环境的工具,不仅适用于 AWS,也支持 GCP 和 Azure 资源。
在 DevOps 环境中,据说有一些使用 Terraform 来销毁问题服务器并重新构建以迅速恢复服务的实践。尽管受到许可等因素的限制,并不能一概而论可以做到所有事情,但仅仅能通过代码管理配置已经是一个巨大的优势。
你是怎么使用的?
terraform 根据.tf文件中的资源信息进行创建。
provider.tf 是必需的,但基本上没有.tf文件的命名规则等,它会从执行init的目录中的.tf文件中读取资源的信息,所以根据所需的资源来决定.tf文件的名称。
以下是我在创建新的VPC到EC2时随意命名的目录情况。
yamanashi@DESKTOP-9KCV2CQ:~/studymyself/terraform/aws/make_ec2_01$ tree ./
./
├── ec2.tf
├── igw.tf
├── provider.tf
├── rt.tf
├── sg.tf
├── terraform.tfstate
├── terraform.tfstate.backup
└── vpc.tf
使用Terraform进行配置管理
尽管Terraform可以基于代码创建资源,但其本质是管理而非创建。如果只是简单地创建资源,那么使用各个云提供商的azcli、gcloud、awscli足矣,无需使用Terraform。
本文主要介绍了Terraform配置管理的概念以及相关注意事项。
.tfstate文件
使用 Terraform 创建资源时,会生成一个名为 terraform.tfstate 的文件。
这个 terraform.tfstate 文件用于管理基于 .tf 文件创建的资源的信息和配置。在执行 apply 或 plan 时,会比较云上资源的状态和 terraform.tfstate 中记录的资源配置。
如果内容有差异,您可以删除或创建资源以修复这些差异。因此,即使连续多次执行创建资源的terraform apply命令,第二次及之后的执行会显示如下,并且不会创建多个资源。
yamanashi@DESKTOP-9KCV2CQ:~/studymyself/terraform/aws/make_ec2_01$ terraform apply
aws_vpc.test-vpc: Refreshing state... [id=vpc-02c30b828453c8908]
aws_internet_gateway.test-igw: Refreshing state... [id=igw-07016852b089d2d1a]
aws_route_table.test-routetable: Refreshing state... [id=rtb-0c9ce935c7c847ec4]
aws_security_group.test-sg: Refreshing state... [id=sg-0fb49bf714c72a67b]
aws_subnet.test-subnet-a: Refreshing state... [id=subnet-03f06d9427892f253]
aws_security_group_rule.outbound_all: Refreshing state... [id=sgrule-3759350995]
aws_security_group_rule.inbound_ssh: Refreshing state... [id=sgrule-1405075988]
aws_security_group_rule.inbound_icmp: Refreshing state... [id=sgrule-1057746502]
aws_instance.terraform-ec2-01: Refreshing state... [id=i-0804f19757d0f5bdd]
aws_route_table_association.rt_association: Refreshing state... [id=rtbassoc-0754805fc19a3f01a]
aws_route.route-to-igw: Refreshing state... [id=r-rtb-0c9ce935c7c847ec41080289494]
Warning: Version constraints inside provider configuration blocks are deprecated
on provider.tf line 2, in provider "aws":
2: version = "~> 2.0"
Terraform 0.13 and earlier allowed provider version constraints inside the
provider configuration block, but that is now deprecated and will be removed
in a future version of Terraform. To silence this warning, move the provider
version constraint into the required_providers block.
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
如果存在差异,会怎么样呢?

我会在这个状态下再次运行terraform apply。
yamanashi@DESKTOP-9KCV2CQ:~/studymyself/terraform/aws/make_ec2_01$ terraform apply
aws_vpc.test-vpc: Refreshing state... [id=vpc-02c30b828453c8908]
aws_subnet.test-subnet-a: Refreshing state... [id=subnet-03f06d9427892f253]
aws_route_table.test-routetable: Refreshing state... [id=rtb-0c9ce935c7c847ec4]
aws_internet_gateway.test-igw: Refreshing state... [id=igw-07016852b089d2d1a]
aws_security_group.test-sg: Refreshing state... [id=sg-0fb49bf714c72a67b]
aws_security_group_rule.inbound_icmp: Refreshing state... [id=sgrule-1057746502]
aws_security_group_rule.inbound_ssh: Refreshing state... [id=sgrule-1405075988]
aws_security_group_rule.outbound_all: Refreshing state... [id=sgrule-3759350995]
aws_instance.terraform-ec2-01: Refreshing state... [id=i-0804f19757d0f5bdd]
aws_route_table_association.rt_association: Refreshing state... [id=rtbassoc-0754805fc19a3f01a]
aws_route.route-to-igw: Refreshing state... [id=r-rtb-0c9ce935c7c847ec41080289494]
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
~ update in-place
Terraform will perform the following actions:
# aws_instance.terraform-ec2-01 will be updated in-place
~ resource "aws_instance" "terraform-ec2-01" {
id = "i-0804f19757d0f5bdd"
~ tags = {
~ "Name" = "terraform-ec2-test" -> "terraform-ec2-01"
}
# (25 unchanged attributes hidden)
# (3 unchanged blocks hidden)
}
Plan: 0 to add, 1 to change, 0 to destroy.
Warning: Version constraints inside provider configuration blocks are deprecated
on provider.tf line 2, in provider "aws":
2: version = "~> 2.0"
Terraform 0.13 and earlier allowed provider version constraints inside the
provider configuration block, but that is now deprecated and will be removed
in a future version of Terraform. To silence this warning, move the provider
version constraint into the required_providers block.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value:
当对比到terraform.tfstate文件中的配置与当前AWS的配置存在差异时,您可以选择明确和修正这些变更点。让我们尝试输入”是”来确认。
~snip~
aws_instance.terraform-ec2-01: Modifying... [id=i-0804f19757d0f5bdd]
aws_instance.terraform-ec2-01: Still modifying... [id=i-0804f19757d0f5bdd, 10s elapsed]
aws_instance.terraform-ec2-01: Modifications complete after 13s [id=i-0804f19757d0f5bdd]
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.

问题已经修复,没有任何问题了。
通过这个功能,使用terraform构建的环境,即使在手动操作中进行了更改,也可以很容易地识别和修复更改点。
使用terraform来进行配置管理的一个目标是创建.tf文件,并且只使用plan和apply命令就可以完全识别出构成的差异。
在进行组织管理时所面临的问题
实际上,能够创建资源的tf代码和能够成功执行配置管理的代码之间存在相当大的差异,正如前面所提到的。
有很多资源会以重新创建的方式来行动,原因是有些资源会以重新创建的方式来行动。麻烦的是,与采取重新创建行动的资源相关的资源也可能被卷入重新创建的过程中,所以实际范围只有试过才会知道。
当获取并将ElasticIP地址绑定到EC2实例时,如果不采取任何措施,在每次执行apply时,EC2实例将会被重新创建,原因不明。当然,只要EC2实例本身没有绑定ElasticIP地址,就不会出现这种行为。
让我们亲自去看看吧。
我更新了.tf文件,以便将弹性IP地址分配给EC2实例(terraform-ec2-01),以便可以从外部连接。在此状态下,应用一次,再次应用将得到以下结果。
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
~ update in-place
-/+ destroy and then create replacement
Terraform will perform the following actions:
# aws_eip.ec2-eip will be updated in-place
~ resource "aws_eip" "ec2-eip" {
id = "eipalloc-039cc86c08d71bcc1"
~ instance = "i-0804f19757d0f5bdd" -> (known after apply)
tags = {}
# (9 unchanged attributes hidden)
}
# aws_instance.terraform-ec2-01 must be replaced
-/+ resource "aws_instance" "terraform-ec2-01" {
~ arn = "arn:aws:ec2:ap-northeast-1:788373527495:instance/i-0804f19757d0f5bdd" -> (known after apply)
~ associate_public_ip_address = true -> false # forces replacement
~ cpu_core_count = 1 -> (known after apply)
~ cpu_threads_per_core = 1 -> (known after apply)
- disable_api_termination = false -> null
- ebs_optimized = false -> null
- hibernation = false -> null
+ host_id = (known after apply)
~ id = "i-0804f19757d0f5bdd" -> (known after apply)
~ instance_state = "running" -> (known after apply)
~ ipv6_address_count = 0 -> (known after apply)
~ ipv6_addresses = [] -> (known after apply)
- monitoring = false -> null
+ network_interface_id = (known after apply)
+ outpost_arn = (known after apply)
+ password_data = (known after apply)
+ placement_group = (known after apply)
~ primary_network_interface_id = "eni-0a280fb6ac9fe2a41" -> (known after apply)
~ private_dns = "ip-10-40-0-10.ap-northeast-1.compute.internal" -> (known after apply)
~ public_dns = "ec2-54-238-91-142.ap-northeast-1.compute.amazonaws.com" -> (known after apply)
~ public_ip = "54.238.91.142" -> (known after apply)
~ security_groups = [] -> (known after apply)
tags = {
"Name" = "terraform-ec2-01"
}
~ tenancy = "default" -> (known after apply)
~ volume_tags = {} -> (known after apply)
# (9 unchanged attributes hidden)
- credit_specification {
- cpu_credits = "standard" -> null
}
+ ebs_block_device {
+ delete_on_termination = (known after apply)
+ device_name = (known after apply)
+ encrypted = (known after apply)
+ iops = (known after apply)
+ kms_key_id = (known after apply)
+ snapshot_id = (known after apply)
+ volume_id = (known after apply)
+ volume_size = (known after apply)
+ volume_type = (known after apply)
}
+ ephemeral_block_device {
+ device_name = (known after apply)
+ no_device = (known after apply)
+ virtual_name = (known after apply)
}
~ metadata_options {
~ http_endpoint = "enabled" -> (known after apply)
~ http_put_response_hop_limit = 1 -> (known after apply)
~ http_tokens = "optional" -> (known after apply)
}
+ network_interface {
+ delete_on_termination = (known after apply)
+ device_index = (known after apply)
+ network_interface_id = (known after apply)
}
~ root_block_device {
~ device_name = "/dev/xvda" -> (known after apply)
~ encrypted = false -> (known after apply)
~ iops = 100 -> (known after apply)
+ kms_key_id = (known after apply)
~ volume_id = "vol-0957d4e0c891f76bb" -> (known after apply)
# (3 unchanged attributes hidden)
}
}
Plan: 1 to add, 1 to change, 1 to destroy.
Warning: Version constraints inside provider configuration blocks are deprecated
on provider.tf line 2, in provider "aws":
2: version = "~> 2.0"
Terraform 0.13 and earlier allowed provider version constraints inside the
provider configuration block, but that is now deprecated and will be removed
in a future version of Terraform. To silence this warning, move the provider
version constraint into the required_providers block.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value:
为什么要删除EC2实例并重新创建呢?随之而来的是Elastic IP绑定的实例ID也需要更改。
实际上,与tfstate文件中记录的配置没有任何变化,但为什么会变成这样。
如果不加注意地直接执行这个操作,那么亚马逊云服务实例(EC2)将会被重新创建,若有应用运行在上面,那么它将会被清除。
因此,Terraform代码需要在创建资源之后采取措施,以防止资源意外重新创建。
阻止不必要的删除行为。
作为其中一种方法,可以对资源进行生命周期配置。
参考以下链接了解更多信息:https://www.terraform.io/docs/language/meta-arguments/lifecycle.html#ignore_changes
我們將在不詳述細節的情況下,給想防止變更的資源添加以下內容,這是最簡單的方法。
lifecycle {
ignore_changes = all
}
如果使用lifecycle,您可以使用ignore_changes属性来指定要忽略的更改。如上所述,使用all可以快速指定全部属性并忽略任何变化。
這個添加了ec2.tf的文件如下所示。
resource "aws_instance" "terraform-ec2-01" {
ami = "ami-02892a4ea9bfa2192"
availability_zone = "ap-northeast-1a"
instance_type = "t2.micro"
key_name = "study_ec2"
subnet_id = aws_subnet.test-subnet-a.id
vpc_security_group_ids = [aws_security_group.test-sg.id]
associate_public_ip_address = false
private_ip = "10.40.0.10"
root_block_device {
volume_type = "gp2"
volume_size = 8
delete_on_termination = true
}
tags = {
"Name" = "terraform-ec2-01"
}
lifecycle {
ignore_changes = all
}
}
resource "aws_eip" "ec2-eip" {
instance = aws_instance.terraform-ec2-01.id
vpc = true
}
让我们看看在实际中将生命周期设置应用到ec2实例的情况下,执行apply的结果。
下面的执行结果是在执行一次terraform apply之后重新执行的结果。
yamanashi@DESKTOP-9KCV2CQ:~/studymyself/terraform/aws/make_ec2_01$ terraform apply
aws_vpc.test-vpc: Refreshing state... [id=vpc-02c30b828453c8908]
aws_internet_gateway.test-igw: Refreshing state... [id=igw-07016852b089d2d1a]
aws_route_table.test-routetable: Refreshing state... [id=rtb-0c9ce935c7c847ec4]
aws_security_group.test-sg: Refreshing state... [id=sg-0fb49bf714c72a67b]
aws_subnet.test-subnet-a: Refreshing state... [id=subnet-03f06d9427892f253]
aws_route.route-to-igw: Refreshing state... [id=r-rtb-0c9ce935c7c847ec41080289494]
aws_route_table_association.rt_association: Refreshing state... [id=rtbassoc-0754805fc19a3f01a]
aws_security_group_rule.outbound_all: Refreshing state... [id=sgrule-3759350995]
aws_security_group_rule.inbound_ssh: Refreshing state... [id=sgrule-1405075988]
aws_security_group_rule.inbound_icmp: Refreshing state... [id=sgrule-1057746502]
aws_instance.terraform-ec2-01: Refreshing state... [id=i-0804f19757d0f5bdd]
aws_eip.ec2-eip: Refreshing state... [id=eipalloc-039cc86c08d71bcc1]
Warning: Version constraints inside provider configuration blocks are deprecated
on provider.tf line 2, in provider "aws":
2: version = "~> 2.0"
Terraform 0.13 and earlier allowed provider version constraints inside the
provider configuration block, but that is now deprecated and will be removed
in a future version of Terraform. To silence this warning, move the provider
version constraint into the required_providers block.
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
现在不再会不小心删除和重建 EC2 实例。
现在不会再出现意外删除并重新创建 EC2 实例的情况。
关于其他方法
在执行计划和申请时指定要操作的资源。
在terraform命令中,存在着-target选项。当在-target之后指定资源时,可以将指定的资源作为plan或apply的目标对象。
例:应用terraform -target aws_instance.terraform-ec2-01
通过仅指定添加到.tf文件中的资源,可以减少对无关资源的影响。
手動建立並僅以Terraform進行管理
在中文中,有一种方法是手动创建资源,然后由Terraform引用和管理资源。在这种情况下,无法通过Terraform更改这些资源的配置。