Terraform核心概念

todo

一、Provider:基础设施管理驱动

Provider是Terraform中一个非常重要的组件,它是一个用来管理基础设施的后端驱动,可以理解为Terraform的插件。每个云服务厂商会实现面向各家云服务的Provider,其中会包含资源元数据的定义,上层请求的处理和后端OpenAPI的调用以及响应处理。

Terraform调用不同的Provider完成不同类型资源的统一管理,目前大多数云平台均实现了各自的Provider。Provider无需手动安装,Terraform会在init阶段根据模板中的定义自动加载。

二、Resource:资源的定义和管理

Resource是Terraform中的一个重要概念,它是Provider的重要组成部分。每个Resource是对特定基础设施特定资源的实现,它通过实现Create、Update、Read和Delete方法来管理特定资源的生命周期。

2.1、定义资源

每个Resource都是通过关键字resource来定义的,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
resource "huaweicloud_vpc" "main" {
name = "net1"
cidr = "192.168.0.0/16"
}

resource "huaweicloud_vpc_subnet" "main" {
name = "zone1-1"
availability_zone = "cn-east-2a"
vpc_id = huaweicloud_vpc.main.id
cidr = "192.168.1.0/24"
gateway_ip = "192.168.1.1"
dns_list = ["100.125.17.29","100.125.135.29"]
}

对于一个Resource的定义来说,一般会包含如下几个部分:

  • huaweicloud_vpc,是要操作的资源类型。它用于告诉Terraform将要操作的资源,其类型是VPC还是ECS等其他资源。需要注意的是,每个云厂商都有各自的资源类型实现。
  • main,是我们为这个资源起的名字。它用来标识所定义的资源,在同一个模板(即当前目录下所有以.tf结尾的文件)中,对同一资源类型的标识必须唯一。
  • {…},大括号里的内容是资源的配置参数。它用来定义资源的属性,比如VPC的名字、网段等。

在上述代码中,我们定义了名为net1、网段为192.168.0.0/16的VPC网络,并在这个网络下创建了一个名为zone1-1、网段为192.168.1.0/24的子网。

2.2、创建资源

在完成资源的定义后,我们可以先通过terraform plan预览模板将要在云平台上创建的资源:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
$ terraform plan
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create

Terraform will perform the following actions:

# huaweicloud_vpc.main will be created
+ resource "huaweicloud_vpc" "main" {
+ cidr = "192.168.0.0/16"
+ enterprise_project_id = (known after apply)
+ id = (known after apply)
+ name = "net1"
+ region = (known after apply)
+ routes = (known after apply)
+ shared = (known after apply)
+ status = (known after apply)
}

# huaweicloud_vpc_subnet.main will be created
+ resource "huaweicloud_vpc_subnet" "main" {
+ availability_zone = "cn-east-2a"
+ cidr = "192.168.1.0/24"
+ dhcp_enable = true
+ dns_list = [
+ "100.125.17.29",
+ "100.125.135.29",
]
+ gateway_ip = "192.168.1.1"
+ id = (known after apply)
+ name = "zone1-1"
+ primary_dns = (known after apply)
+ region = (known after apply)
+ secondary_dns = (known after apply)
+ subnet_id = (known after apply)
+ vpc_id = (known after apply)
}

Plan: 2 to add, 0 to change, 0 to destroy.

------------------------------------------------------------------------

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.

可以看到,terraform将创建两个资源huaweicloud_vpc.main和huaweicloud_vpc_subnet.main,它们的某些属性为known after apply,这个意思是说该参数的值需要在执行terraform apply之后才能知道,通常像这样的字段如果没有显示的设置值,云平台将会自动生成。

在确认无误后,就可以执行terraform apply进行实际的创建操作了,输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
$ terraform apply
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create

Terraform will perform the following actions:

# huaweicloud_vpc.main will be created
+ resource "huaweicloud_vpc" "main" {
+ cidr = "192.168.0.0/16"
+ enterprise_project_id = (known after apply)
+ id = (known after apply)
+ name = "net1"
+ region = (known after apply)
+ routes = (known after apply)
+ shared = (known after apply)
+ status = (known after apply)
}

# huaweicloud_vpc_subnet.main will be created
+ resource "huaweicloud_vpc_subnet" "main" {
+ availability_zone = "cn-east-2a"
+ cidr = "192.168.1.0/24"
+ dhcp_enable = true
+ dns_list = [
+ "100.125.17.29",
+ "100.125.135.29",
]
+ gateway_ip = "192.168.1.1"
+ id = (known after apply)
+ name = "zone1-1"
+ primary_dns = (known after apply)
+ region = (known after apply)
+ secondary_dns = (known after apply)
+ subnet_id = (known after apply)
+ vpc_id = (known after apply)
}

Plan: 2 to add, 0 to change, 0 to destroy.

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

huaweicloud_vpc.main: Creating...
huaweicloud_vpc.main: Creation complete after 6s [id=867655c8-7641-4fbf-b8c4-34989e560d46]
huaweicloud_vpc_subnet.main: Creating...
huaweicloud_vpc_subnet.main: Creation complete after 8s [id=a35290c9-d9c5-4ee0-a24f-1fe65c88c0b8]

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

为了安全起见,执行apply之后需要输入yes进行确认,当然我们可以在apply之后添加-auto-approve,这表示跳过确认环节直接创建。如果创建成功会输出资源的ID,此时,我们可以登录到Web控制台上查看验证这个创建操作;否则,会输出错误信息。

2.3、变更资源

对于一个IaC工具来说,资源的变更非常简单,只需要修改模板中定义的属性值即可。在Terraform中对资源的变更有两种情况:

  • 原地变更(update in place),即在不改变资源生命周期的情况下,实现对资源属性的修改,如变更资源的名称、描述、便签等。
  • 重建变更(destroy and then create replacemnt),资源的某些属性是不支持变更的,在这种情况下,如果修改资源属性,Terraform会先删除原有资源,然后按照最新的模板定义创建新的资源,从而间接实现资源的变更操作。对于不支持变更的属性,在各云厂商的provider文档中,会将该属性显示的声明为ForceNew。

值得注意的是,在Terraform输出的信息中,会对增删改操作进行标识:

  • +,表示创建
  • ~,表示更新,左边的内容是旧值,右边的内容是新值
  • -,表示释放

接下来,我们分别演示这两种变更情况。

2.3.1、原地变更

首先,我们来修改VPC的名字,将net1改为net,修改后的模板文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
resource "huaweicloud_vpc" "main" {
name = "net"
cidr = "192.168.0.0/16"
}

resource "huaweicloud_vpc_subnet" "main" {
name = "zone1-1"
availability_zone = "cn-east-2a"
vpc_id = huaweicloud_vpc.main.id
cidr = "192.168.1.0/24"
gateway_ip = "192.168.1.1"
dns_list = ["100.125.17.29","100.125.135.29"]
}

修改完成后,执行apply操作,其结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
$ terraform apply
huaweicloud_vpc.main: Refreshing state... [id=867655c8-7641-4fbf-b8c4-34989e560d46]
huaweicloud_vpc_subnet.main: Refreshing state... [id=a35290c9-d9c5-4ee0-a24f-1fe65c88c0b8]

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:

# huaweicloud_vpc.main will be updated in-place
~ resource "huaweicloud_vpc" "main" {
id = "867655c8-7641-4fbf-b8c4-34989e560d46"
~ name = "net1" -> "net"
tags = {}
# (6 unchanged attributes hidden)
}

Plan: 0 to add, 1 to change, 0 to destroy.

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

huaweicloud_vpc.main: Modifying... [id=867655c8-7641-4fbf-b8c4-34989e560d46]
huaweicloud_vpc.main: Modifications complete after 2s [id=867655c8-7641-4fbf-b8c4-34989e560d46]

Apply complete! Resources: 0 added, 1 changed, 0 destroyed.

可以看到,terraform只进行了原地修改(updated in-place)操作,将ne1改为了net。

2.3.2、重建变更

接着,我们来修改子网网段,将192.168.1.0/16改为192.168.2.0/16,修改后的模板文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
resource "huaweicloud_vpc" "main" {
name = "net"
cidr = "192.168.0.0/16"
}

resource "huaweicloud_vpc_subnet" "main" {
name = "zone1-1"
availability_zone = "cn-east-2a"
vpc_id = huaweicloud_vpc.main.id
cidr = "192.168.2.0/24"
gateway_ip = "192.168.2.1"
dns_list = ["100.125.17.29","100.125.135.29"]
}

修改完成后,执行apply操作,其结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
$ terraform apply
huaweicloud_vpc.main: Refreshing state... [id=867655c8-7641-4fbf-b8c4-34989e560d46]
huaweicloud_vpc_subnet.main: Refreshing state... [id=73b58ff0-0a3c-43db-927a-a4804e600a2d]

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement

Terraform will perform the following actions:

# huaweicloud_vpc_subnet.main must be replaced
-/+ resource "huaweicloud_vpc_subnet" "main" {
~ cidr = "192.168.1.0/24" -> "192.168.2.0/24" # forces replacement
~ gateway_ip = "192.168.1.1" -> "192.168.2.1" # forces replacement
~ id = "73b58ff0-0a3c-43db-927a-a4804e600a2d" -> (known after apply)
name = "zone1-1"
~ primary_dns = "100.125.17.29" -> (known after apply)
~ region = "cn-east-2" -> (known after apply)
~ secondary_dns = "100.125.135.29" -> (known after apply)
~ subnet_id = "a5870016-b575-45ec-b7a7-538f35c46b13" -> (known after apply)
- tags = {} -> null
# (4 unchanged attributes hidden)
}

Plan: 1 to add, 0 to change, 1 to destroy.

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

huaweicloud_vpc_subnet.main: Destroying... [id=73b58ff0-0a3c-43db-927a-a4804e600a2d]
huaweicloud_vpc_subnet.main: Destruction complete after 9s
huaweicloud_vpc_subnet.main: Creating...
huaweicloud_vpc_subnet.main: Creation complete after 9s [id=efc326dd-b911-4ce4-b6b4-11cc53bf690e]

Apply complete! Resources: 1 added, 0 changed, 1 destroyed.

可以看到,修改子网网段是一个forces replacement的行为,这会导致旧资源先被释放,然后再创建新资源,从而实现修改操作(destroy and then create replacement)。

需要注意的是,释放的资源无法实现回滚,即数据无法恢复,因此在apply前一定要通过plan仔细查看哪些资源会被重建,以免造成不可恢复的错误。

2.4、查看资源

2.4.1、查看所有资源

想要查看已经创建了哪些资源,最简单的方式是执行show命令,此时terraform会输出所有已创建资源及其属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
$ terraform show
# huaweicloud_vpc.main:
resource "huaweicloud_vpc" "main" {
cidr = "192.168.0.0/16"
enterprise_project_id = "0"
id = "867655c8-7641-4fbf-b8c4-34989e560d46"
name = "net"
region = "cn-east-2"
routes = []
shared = false
status = "OK"
tags = {}
}

# huaweicloud_vpc_subnet.main:
resource "huaweicloud_vpc_subnet" "main" {
availability_zone = "cn-east-2a"
cidr = "192.168.2.0/24"
dhcp_enable = true
dns_list = [
"100.125.17.29",
"100.125.135.29",
]
gateway_ip = "192.168.2.1"
id = "77bc0afc-9bf8-492d-af83-bdbe76dfbdda"
name = "zone1-1"
primary_dns = "100.125.17.29"
region = "cn-east-2"
secondary_dns = "100.125.135.29"
subnet_id = "6a63bd78-7fba-43f0-89ae-df25b4f19b93"
vpc_id = "867655c8-7641-4fbf-b8c4-34989e560d46"
}

该方法虽然简单,但如果资源非常多的时候,输出的信息就会让人眼花缭乱了。此时,就可以使用state命令了

2.4.2、查看特定资源

首先,我们可以执行state list罗列出当前所有的资源,每个资源的显示格式为:<资源类型>.<资源名称>,如下:

1
2
3
$ terraform state list
huaweicloud_vpc.main
huaweicloud_vpc_subnet.main

接着,在找到目标资源后,执行state show <资源类型>.<资源名称>,即可实现对特定资源的查看,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ terraform state show huaweicloud_vpc_subnet.main
# huaweicloud_vpc_subnet.main:
resource "huaweicloud_vpc_subnet" "main" {
availability_zone = "cn-east-2a"
cidr = "192.168.2.0/24"
dhcp_enable = true
dns_list = [
"100.125.17.29",
"100.125.135.29",
]
gateway_ip = "192.168.2.1"
id = "77bc0afc-9bf8-492d-af83-bdbe76dfbdda"
name = "zone1-1"
primary_dns = "100.125.17.29"
region = "cn-east-2"
secondary_dns = "100.125.135.29"
subnet_id = "6a63bd78-7fba-43f0-89ae-df25b4f19b93"
vpc_id = "867655c8-7641-4fbf-b8c4-34989e560d46"
}

2.5、释放资源

2.5.1、释放所有资源

想要释放资源,只需要执行destroy命令即可,但是该命令会将模板中定义的所有资源都释放掉,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
$ terraform destroy

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
- destroy

Terraform will perform the following actions:

# huaweicloud_vpc.main will be destroyed
- resource "huaweicloud_vpc" "main" {
- cidr = "192.168.0.0/16" -> null
- enterprise_project_id = "0" -> null
- id = "867655c8-7641-4fbf-b8c4-34989e560d46" -> null
- name = "net" -> null
- region = "cn-east-2" -> null
- routes = [] -> null
- shared = false -> null
- status = "OK" -> null
- tags = {} -> null
}

# huaweicloud_vpc_subnet.main will be destroyed
- resource "huaweicloud_vpc_subnet" "main" {
- availability_zone = "cn-east-2a" -> null
- cidr = "192.168.2.0/24" -> null
- dhcp_enable = true -> null
- dns_list = [
- "100.125.17.29",
- "100.125.135.29",
] -> null
- gateway_ip = "192.168.2.1" -> null
- id = "77bc0afc-9bf8-492d-af83-bdbe76dfbdda" -> null
- name = "zone1-1" -> null
- primary_dns = "100.125.17.29" -> null
- region = "cn-east-2" -> null
- secondary_dns = "100.125.135.29" -> null
- subnet_id = "6a63bd78-7fba-43f0-89ae-df25b4f19b93" -> null
- vpc_id = "867655c8-7641-4fbf-b8c4-34989e560d46" -> null
}

Plan: 0 to add, 0 to change, 2 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: n

Destroy cancelled.

2.5.2、释放特定资源

如果想要删除某个特定的资源,可以通过-target=<资源类型>.<资源名称>来指定要资源的资源,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
$ terraform destroy -target=huaweicloud_vpc_subnet.main

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
- destroy

Terraform will perform the following actions:

# huaweicloud_vpc_subnet.main will be destroyed
- resource "huaweicloud_vpc_subnet" "main" {
- availability_zone = "cn-east-2a" -> null
- cidr = "192.168.2.0/24" -> null
- dhcp_enable = true -> null
- dns_list = [
- "100.125.17.29",
- "100.125.135.29",
] -> null
- gateway_ip = "192.168.2.1" -> null
- id = "77bc0afc-9bf8-492d-af83-bdbe76dfbdda" -> null
- name = "zone1-1" -> null
- primary_dns = "100.125.17.29" -> null
- region = "cn-east-2" -> null
- secondary_dns = "100.125.135.29" -> null
- subnet_id = "6a63bd78-7fba-43f0-89ae-df25b4f19b93" -> null
- vpc_id = "867655c8-7641-4fbf-b8c4-34989e560d46" -> null
}

Plan: 0 to add, 0 to change, 1 to destroy.


Warning: Resource targeting is in effect

You are creating a plan with the -target option, which means that the result
of this plan may not represent all of the changes requested by the current
configuration.

The -target option is not for routine use, and is provided only for
exceptional situations such as recovering from errors or mistakes, or when
Terraform specifically suggests to use it as part of an error message.

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

huaweicloud_vpc_subnet.main: Destroying... [id=77bc0afc-9bf8-492d-af83-bdbe76dfbdda]
huaweicloud_vpc_subnet.main: Destruction complete after 10s

Warning: Applied changes may be incomplete

The plan was created with the -target option in effect, so some changes
requested in the configuration may have been ignored and the output values may
not be fully updated. Run the following command to verify that no other
changes are pending:
terraform plan

Note that the -target option is not suitable for routine use, and is provided
only for exceptional situations such as recovering from errors or mistakes, or
when Terraform specifically suggests to use it as part of an error message.


Destroy complete! Resources: 1 destroyed.

除此之外,还有一种间接删除资源的方式:归功于Terraform状态一致性的特点,当模板发生变更后,再次执行apply命令时,它会检查模板与状态文件是否一致,从而触发变更操作,因此,我们可以在模板中把要释放的资源删除,然后执行apply命令来完成资源的释放。

三、Data Source:资源信息的动态查询

对资源的查询是一个很常用的操作,比如,查看某个Region下有哪些可用区,某个可用区下有哪些实例规格,每个Region下有哪些镜像,当前账号下有多少机器等等。通过对资源及其属性的查询,可以帮助和引导我们进行下一步的操作。

除此之外,在编写Terraform模板时,Resource使用的参数有些是固定的静态变量,但有些情况下参数变量不确定、可选值不清楚或者参数可能随时变化。比如,我们创建ECS实现时,通常需要指定镜像ID和实例规格,首先我们要知道某个镜像ID对应的字符串、特定CPU核数和内存对应的实例规格。

以上这些信息虽然可以通过控制台或帮助文档手动查询到,但是一方面查询不方便,另一方面,我们的模板可能随时会更新。如果将这些信息硬编码在代码中,一旦我们这些信息发生变化,就需要重新修改代码,非常不灵活。

在Terraform中,Data Source就是用来查询资源信息的,每个Data Source实现对一个特定资源的动态查询。在模板中定义过滤条件,执行plan或者apply即可动态返回符合条件的资源。在编写模板时,可以通过引用的方式将Data Source的结果动态呈现更Resource。

四、State:资源的状态存储

到这里,我们应该会发现一个现象:在执行show和state list命令时,执行速度非常快,随着命令的结束,资源的属性很快就被展示出来了,而其它命令的执行都需要等几秒甚至几分钟,这个就跟Terraform的实现机制有关了。

Terraform是一个有状态的应用,在完成资源的创建和修改之后,它会将资源最新的状态和属性存储在一个称之为state的文件中,该文件的名称默认为terraform.tfstate,存储位置默认为资源模板所在的目录。state文件可以看作是Terraform存储资源属性的“数据库”,当执行show和state list命令时,Terraform直接读取的是state文件,无需调用云平台的API,而其它的命令需要与API交互后才返回。

state文件非常重要,它只从属于一个特定的模板,模板变了state就会跟着变。因此,如果state文件被损坏或者被删除,Terraform就会认为其管理的资源发生了变更和移除,此时再执行apply命令将会按照模板的定义变更或重建资源,直到模板对资源的定义与state中保存的内容保持一致。

state与模板的依附关系在团队协作时尤为重要,在拷贝模板代码的同时,如果想维护同一套资源,state也需要一起拷贝,而这无形中就增加了代码的维护成本。为了解决这个问题,Terraform提供了远端存储state的能力remote state,它可以将state文件存放在远端的共享存储上,以实现模板与state的管理分离。

模板与state的高度一致性也是Terraform的一大亮点。Terraform虽然是面向客户端的,但它也是有状态的,这意味着Terraform所管理的资源,不能通过其他工具和服务(如Web控制台,API等)来变更,否则,在下次执行apply时,Terrafrom会因为状态的不一致而触发变更。

五、参考


Terraform核心概念
https://kuberxy.github.io/2021/01/05/Terraform核心概念/
作者
Mr.x
发布于
2021年1月5日
许可协议