Terraform 元参数
一、count 创建副本
适用于创建多个相似资源,使用 count.index
索引作为参数引用,主要应用场景,创建多台配置一致 ecs
由于 count 使用 下标 index 区分数据,所以在实际应用中,存在一些小问题,看个例子,假设需要 3 条 DNS A 记录资源,”tf-meta-demo1”, “tf-meta-demo2”, “tf-meta-demo3”
使用 count 大致是这么做的
main.tf
locals {
records = ["tf-meta-demo1", "tf-meta-demo2", "tf-meta-demo3"]
}
resource "alicloud_alidns_record" "dns" {
count = length(local.records)
domain_name = "yo-yo.fun"
rr = count.index
type = "A"
value = "192.168.1.100"
}
##### version、provider、variable ####
terraform {
required_version= ">= 1.1.9"
required_providers {
alicloud = {
source = "hashicorp/alicloud"
version = "1.197.0"
}
}
}
provider "alicloud" {
region = var.ALICLOUD_REGION
access_key = var.ALICLOUD_ACCESS_KEY
secret_key = var.ALICLOUD_SECRET_KEY
}
variable "ALICLOUD_ACCESS_KEY" {
type = string
}
variable "ALICLOUD_SECRET_KEY" {
type = string
}
variable "ALICLOUD_REGION" {
type = string
}
看起来很美好,资源也确实创建上去了
$ tf init
$ tf plan
$ tf apply
但是,假设此时删除 “tf-meta-demo2” 记录,会发生什么呢?
records = ["tf-meta-demo1", "tf-meta-demo3"]
重新 apply
Terraform will perform the following actions:
# alicloud_alidns_record.dns[1] will be updated in-place
~ resource "alicloud_alidns_record" "dns" {
id = "811392154952682496"
~ rr = "tf-meta-demo2" -> "tf-meta-demo3"
# (7 unchanged attributes hidden)
}
# alicloud_alidns_record.dns[2] will be destroyed
# (because index [2] is out of range for count)
- resource "alicloud_alidns_record" "dns" {
- domain_name = "yo-yo.fun" -> null
- id = "811392154935925760" -> null
- line = "default" -> null
- priority = 0 -> null
- rr = "tf-meta-demo3" -> null
- status = "ENABLE" -> null
- ttl = 600 -> null
- type = "A" -> null
- value = "192.168.1.100" -> null
}
Plan: 0 to add, 1 to change, 1 to destroy.
alicloud_alidns_record.dns[2]: Destroying... [id=811392154935925760]
alicloud_alidns_record.dns[1]: Modifying... [id=811392154952682496]
alicloud_alidns_record.dns[2]: Destruction complete after 4s
│ Error: [ERROR] terraform-provider-alicloud/alicloud/resource_alicloud_alidns_record.go:265: Resource 811392154952682496 UpdateDomainRecord Failed!!! [SDK alibaba-cloud-sdk-go ERROR]:
│ SDK.ServerError
│ ErrorCode: DomainRecordDuplicate
│ Recommend: https://next.api.aliyun.com/troubleshoot?q=DomainRecordDuplicate&product=Alidns
│ RequestId: DA0BEBBD-E33A-5FFD-B61C-3C73487BA7AE
│ Message: The DNS record already exists.
│ RespHeaders: map[Access-Control-Allow-Origin:[*] Connection:[keep-alive] Content-Length:[251] Content-Type:[application/json;charset=utf-8] Date:[Tue, 07 Feb 2023 02:32:38 GMT] X-Acs-Request-Id:[DA0BEBBD-E33A-5FFD-B61C-3C73487BA7AE] X-Acs-Trace-Id:[cefb87143c2c1b004686252cd6f8a3a8]]
│
│ with alicloud_alidns_record.dns[1],
│ on main.tf line 5, in resource "alicloud_alidns_record" "dns":
│ 5: resource "alicloud_alidns_record" "dns" {
Process finished with exit code 1
通过控制台返回的信息可以看到执行出现了异常,DomainRecordDuplicate
记录重复,仔细看一下上面的 action 信息,便可已得知原因
count 是通过 index 作为标识,我们从 list 删除 “tf-meta-demo2” 元素,导致 “tf-meta-demo3” 的下标变为了 [1],这时 terraform 就会理解为我们要做两件事
- 更改下标为 [1] 的主机记录 “tf-meta-demo2” -> “tf-meta-demo3”
- 删除下标为 [2] 的主机记录 “tf-meta-demo3”
通过这一段的信息也可以看出来
# 更改下标为 [1] 的主机记录
# alicloud_alidns_record.dns[1] will be updated in-place
~ resource "alicloud_alidns_record" "dns" {
id = "811392154952682496"
~ rr = "tf-meta-demo2" -> "tf-meta-demo3"
# (7 unchanged attributes hidden)
}
# 删除下标为 [2] 的主机记录,原因是下标 [2] 已不存在(out of range)
# alicloud_alidns_record.dns[2] will be destroyed
# (because index [2] is out of range for count)
我们需求是删除 “tf-meta-demo2”,但实际上产生的结果是 “tf-meta-demo3” 被删除了
可 “tf-meta-demo2” 记录为什么会保留了下来呢?按照 action 信息,不应该是对 “tf-meta-demo2” -> “tf-meta-demo3” 做更新处理吗?
这个问题确实有些诡异,不过仔细看这段
alicloud_alidns_record.dns[2]: Destroying... [id=811392154935925760]
alicloud_alidns_record.dns[1]: Modifying... [id=811392154952682496]
alicloud_alidns_record.dns[2]: Destruction complete after 4s
按照日志返回的信息,可以看出执行顺序是这样
- 删除
[id=811392154935925760]
资源 即 “tf-meta-demo3” - 更新
[id=811392154952682496]
资源 即 “tf-meta-demo2” - 销毁
dns[2]
于 4s 后
这么来看,问题出在 “更新”、“删除” 是同时发生的,由于删除的动作较慢,以致于 “更新” 操作执行时 “tf-meta-demo3” 仍旧存在,由此才会触发 UpdateDomainRecord Failed!
错误原因 DomainRecordDuplicate
可以打开 TF_LOG=DEBUG
验证一下我们的猜测
# 开始执行 Delete change: dns[2] "tf-meta-demo3"
2023-02-07T10:57:02.781+0800 [INFO] Starting apply for alicloud_alidns_record.dns[2]
2023-02-07T10:57:02.781+0800 [DEBUG] alicloud_alidns_record.dns[2]: applying the planned Delete change
# 开始执行 Update change: dns[1] "tf-meta-demo2" -> "tf-meta-demo3"
2023-02-07T10:57:02.785+0800 [INFO] Starting apply for alicloud_alidns_record.dns[1]
2023-02-07T10:57:02.785+0800 [DEBUG] alicloud_alidns_record.dns[1]: applying the planned Update change
# 日志输出
alicloud_alidns_record.dns[2]: Destroying... [id=811395660973522944]
alicloud_alidns_record.dns[1]: Modifying... [id=811392154952682496]
2023-02-07T10:57:03.154+0800 [ERROR] vertex "alicloud_alidns_record.dns[1]" error: [ERROR] terraform-provider-alicloud/alicloud/resource_alicloud_alidns_record.go:265: Resource 811392154952682496 UpdateDomainRecord Failed!!! [SDK alibaba-cloud-sdk-go ERROR]:
SDK.ServerError
ErrorCode: DomainRecordDuplicate
Recommend: https://next.api.aliyun.com/troubleshoot?q=DomainRecordDuplicate&product=Alidns
RequestId: EFA3DD0C-3CEC-5B25-890D-EAE9025798C7
Message: The DNS record already exists.
RespHeaders: map[Access-Control-Allow-Origin:[*] Connection:[keep-alive] Content-Length:[251] Content-Type:[application/json;charset=utf-8] Date:[Tue, 07 Feb 2023 02:57:01 GMT] X-Acs-Request-Id:[EFA3DD0C-3CEC-5B25-890D-EAE9025798C7] X-Acs-Trace-Id:[dab6c100c068f0916521a32fbe6abfc6]]
# 4s 后删除操作完成
2023-02-07T10:57:06.144+0800 [DEBUG] provider.terraform-provider-alicloud_v1.197.0.exe: 2023/02/07 10:57:06 [TRACE] Waiting 500ms before next try
alicloud_alidns_record.dns[2]: Destruction complete after 4s
二、for_each 创建副本
for_each 同样可以用来创建资源副本,并且相较于 count 更适合此类场景,for_each 支持遍历以下两种数据
- map(string)
- set(string):可以用 toset(list) 进行类型转换,不过会丢失重复数据
以上面的例子,调整代码
locals {
records = ["tf-meta-demo1", "tf-meta-demo2", "tf-meta-demo3"]
}
resource "alicloud_alidns_record" "dns" {
for_each = toset(local.records)
domain_name = "yo-yo.fun"
# 对于 list 类型 each.key = value
# 对于 map 类型 each.key = key、each.value = value
rr = each.key
type = "A"
value = "192.168.1.100"
}
补齐资源
$ tf apply
删除 “tf-meta-demo2”
records = ["tf-meta-demo1", "tf-meta-demo3"]
执行 plan 查看效果
Terraform will perform the following actions:
# alicloud_alidns_record.dns["tf-meta-demo2"] will be destroyed
# (because key ["tf-meta-demo2"] is not in for_each map)
- resource "alicloud_alidns_record" "dns" {
- domain_name = "yo-yo.fun" -> null
- id = "811398611095941120" -> null
- line = "default" -> null
- priority = 0 -> null
- rr = "tf-meta-demo2" -> null
- status = "ENABLE" -> null
- ttl = 600 -> null
- type = "A" -> null
- value = "192.168.1.100" -> null
}
Plan: 0 to add, 0 to change, 1 to destroy.
─────────────────────────────────────────────────────────────────────────────
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.
Process finished with exit code 0
执行 apply
# alicloud_alidns_record.dns["tf-meta-demo2"] will be destroyed
# (because key ["tf-meta-demo2"] is not in for_each map)
- resource "alicloud_alidns_record" "dns" {
- domain_name = "yo-yo.fun" -> null
- id = "811398611095941120" -> null
- line = "default" -> null
- priority = 0 -> null
- rr = "tf-meta-demo2" -> null
- status = "ENABLE" -> null
- ttl = 600 -> null
- type = "A" -> null
- value = "192.168.1.100" -> null
}
Plan: 0 to add, 0 to change, 1 to destroy.
alicloud_alidns_record.dns["tf-meta-demo2"]: Destroying... [id=811398611095941120]
alicloud_alidns_record.dns["tf-meta-demo2"]: Destruction complete after 1s
Apply complete! Resources: 0 added, 0 changed, 1 destroyed.
Process finished with exit code 0
OK,符合预期~
三、dynamic 动态内联块
通常来说,如 resource 等顶级块中只能以类似 name = expression
的形式进行一对一的赋值,但某些资源类型可能存在多个重复的内嵌块,如下所示:
terraform {
required_providers {
docker = {
source = "kreuzwerker/docker"
version = "3.0.1"
}
}
}
provider "docker" {
host = "unix:///var/run/docker.sock"
}
resource "docker_image" "nginx" {
name = "nginx:latest"
force_remove = false
keep_locally = true
}
resource "docker_container" "nginx" {
image = docker_image.nginx.name
name = "tf-nginx-dynamic-demo"
# ports 就属于可重复的内嵌块
# 配置宿主机 9180 容器 80 端口映射
ports {
external = 9080
internal = 80
ip = "0.0.0.0"
protocol = "tcp"
}
ports {
external = 9443
internal = 443
ip = "0.0.0.0"
protocol = "tcp"
}
}
为了加深理解,可以先执行过一遍
开始执行前对 docker 做下配置
$ /usr/lib/systemd/system/docker.service
ExecStart=/usr/bin/dockerd -H unix://var/run/docker.sock -H fd:// --containerd=/run/containerd/containerd.sock
重启服务
$ systemctl daemon-reload
$ systemctl restart docker
检查 socket
$ ls -al /var/run/docker.sock
srw-rw---- 1 root docker 0 Feb 7 12:58 /var/run/docker.sock
执行 init、plan
$ tf init
$ tf plan
执行计划信息如下
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:
# docker_container.nginx will be created
+ resource "docker_container" "nginx" {
+ attach = false
+ bridge = (known after apply)
+ command = (known after apply)
+ container_logs = (known after apply)
+ container_read_refresh_timeout_milliseconds = 15000
+ entrypoint = (known after apply)
+ env = (known after apply)
+ exit_code = (known after apply)
+ hostname = (known after apply)
+ id = (known after apply)
+ image = "nginx:latest"
+ init = (known after apply)
+ ipc_mode = (known after apply)
+ log_driver = (known after apply)
+ logs = false
+ must_run = true
+ name = "tf-nginx-dynamic-demo"
+ network_data = (known after apply)
+ read_only = false
+ remove_volumes = true
+ restart = "no"
+ rm = false
+ runtime = (known after apply)
+ security_opts = (known after apply)
+ shm_size = (known after apply)
+ start = true
+ stdin_open = false
+ stop_signal = (known after apply)
+ stop_timeout = (known after apply)
+ tty = false
+ wait = false
+ wait_timeout = 60
+ healthcheck {
+ interval = (known after apply)
+ retries = (known after apply)
+ start_period = (known after apply)
+ test = (known after apply)
+ timeout = (known after apply)
}
+ labels {
+ label = (known after apply)
+ value = (known after apply)
}
+ ports {
+ external = 9080
+ internal = 80
+ ip = "0.0.0.0"
+ protocol = "tcp"
}
+ ports {
+ external = 9443
+ internal = 443
+ ip = "0.0.0.0"
+ protocol = "tcp"
}
}
# docker_image.nginx will be created
+ resource "docker_image" "nginx" {
+ force_remove = false
+ id = (known after apply)
+ image_id = (known after apply)
+ keep_locally = true
+ name = "nginx:latest"
+ repo_digest = (known after apply)
}
Plan: 2 to add, 0 to change, 0 to destroy.
执行 apply 创建资源
$ tf apply
执行效果
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:
# docker_container.nginx will be created
+ resource "docker_container" "nginx" {
+ attach = false
+ bridge = (known after apply)
+ command = (known after apply)
+ container_logs = (known after apply)
+ container_read_refresh_timeout_milliseconds = 15000
+ entrypoint = (known after apply)
+ env = (known after apply)
+ exit_code = (known after apply)
+ hostname = (known after apply)
+ id = (known after apply)
+ image = "nginx:latest"
+ init = (known after apply)
+ ipc_mode = (known after apply)
+ log_driver = (known after apply)
+ logs = false
+ must_run = true
+ name = "tf-nginx-dynamic-demo"
+ network_data = (known after apply)
+ read_only = false
+ remove_volumes = true
+ restart = "no"
+ rm = false
+ runtime = (known after apply)
+ security_opts = (known after apply)
+ shm_size = (known after apply)
+ start = true
+ stdin_open = false
+ stop_signal = (known after apply)
+ stop_timeout = (known after apply)
+ tty = false
+ wait = false
+ wait_timeout = 60
+ healthcheck {
+ interval = (known after apply)
+ retries = (known after apply)
+ start_period = (known after apply)
+ test = (known after apply)
+ timeout = (known after apply)
}
+ labels {
+ label = (known after apply)
+ value = (known after apply)
}
+ ports {
+ external = 9080
+ internal = 80
+ ip = "0.0.0.0"
+ protocol = "tcp"
}
+ ports {
+ external = 9443
+ internal = 443
+ ip = "0.0.0.0"
+ protocol = "tcp"
}
}
# docker_image.nginx will be created
+ resource "docker_image" "nginx" {
+ force_remove = false
+ id = (known after apply)
+ image_id = (known after apply)
+ keep_locally = true
+ name = "nginx:latest"
+ repo_digest = (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
docker_image.nginx: Creating...
docker_image.nginx: Creation complete after 0s [id=sha256:62d49f9bab67f7c70ac3395855bf01389eb3175b374e621f6f191bf31b54cd5bnginx:latest]
docker_container.nginx: Creating...
docker_container.nginx: Creation complete after 1s [id=9a87c83e12e619e0202cd10a9f8fb1cc6b90b868f2258964239d64710101fd40]
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
列出容器
$ docker ps |grep nginx
9a87c83e12e6 nginx:latest "/docker-entrypoint.…" 44 seconds ago Up 43 seconds 0.0.0.0:9080->80/tcp, 0.0.0.0:9443->443/tcp tfnginx-dynamic-demo
OK,大致过了下流程,通过上例可以看到 ports{}
块的代码部分属性都是相同的,仅是数值不同,在特定场景下可能存在大量重复配置,对于此就可以考虑使用 dynamic 优化代码,dynamic 语法是这样的
使用 dynamic 定义动态模块,后面引号内的是 label,默认为即将生成的 “语句块名称”
for_each 遍历对象,支持 map、list、set
iterator 临时变量名称,未定义则使用 label 作为 变量名称
content 所生成语句块的内容
locals {
docker_nginx_ports = [{
external = 9080
internal = 80
},{
external = 9443
internal = 443
}]
}
resource "docker_container" "nginx" {
image = docker_image.nginx.name
name = "tf-nginx-dynamic-demo"
# ports 就属于可重复的内嵌块
# 保持 label 名称与 内嵌块名称 一致即可
dynamic "ports" {
for_each = local.docker_nginx_ports
content {
external = ports.value.external
internal = ports.value.internal
ip = "0.0.0.0"
protocol = "tcp"
}
}
}
创建资源
# 清理已有资源
$ tf destroy
# 查看执行计划
$ tf plan
# 创建资源
$ tf apply
执行效果
Terraform will perform the following actions:
# docker_container.nginx will be created
+ resource "docker_container" "nginx" {
+ attach = false
+ bridge = (known after apply)
+ command = (known after apply)
+ container_logs = (known after apply)
+ container_read_refresh_timeout_milliseconds = 15000
+ entrypoint = (known after apply)
+ env = (known after apply)
+ exit_code = (known after apply)
+ hostname = (known after apply)
+ id = (known after apply)
+ image = "nginx:latest"
+ init = (known after apply)
+ ipc_mode = (known after apply)
+ log_driver = (known after apply)
+ logs = false
+ must_run = true
+ name = "tf-nginx-dynamic-demo"
+ network_data = (known after apply)
+ read_only = false
+ remove_volumes = true
+ restart = "no"
+ rm = false
+ runtime = (known after apply)
+ security_opts = (known after apply)
+ shm_size = (known after apply)
+ start = true
+ stdin_open = false
+ stop_signal = (known after apply)
+ stop_timeout = (known after apply)
+ tty = false
+ wait = false
+ wait_timeout = 60
+ healthcheck {
+ interval = (known after apply)
+ retries = (known after apply)
+ start_period = (known after apply)
+ test = (known after apply)
+ timeout = (known after apply)
}
+ labels {
+ label = (known after apply)
+ value = (known after apply)
}
+ ports {
+ external = 9080
+ internal = 80
+ ip = "0.0.0.0"
+ protocol = "tcp"
}
+ ports {
+ external = 9443
+ internal = 443
+ ip = "0.0.0.0"
+ protocol = "tcp"
}
}
# docker_image.nginx will be created
+ resource "docker_image" "nginx" {
+ force_remove = false
+ id = (known after apply)
+ image_id = (known after apply)
+ keep_locally = true
+ name = "nginx:latest"
+ repo_digest = (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
docker_image.nginx: Creating...
docker_image.nginx: Creation complete after 0s [id=sha256:62d49f9bab67f7c70ac3395855bf01389eb3175b374e621f6f191bf31b54cd5bnginx:latest]
docker_container.nginx: Creating...
docker_container.nginx: Creation complete after 0s [id=c3a0ad09f28959d35a082f857f1535af0fefd063ff5190686a31312a7a33683d]
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
更多使用案例,可以看这里
四、depends_on 定义依赖
默认情况下,terraform 会帮我们处理好绝大部分资源的依赖关系,但是某些场景下,资源依赖关系是 terraform 无法预知的,例如:web 容器 依赖 redis 容器
resource "docker_image" "nginx" {
name = "nginx:latest"
force_remove = false
keep_locally = true
}
resource "docker_image" "redis" {
name = "redis:6.0.8"
force_remove = false
keep_locally = true
}
locals {
docker_nginx_ports = [{
external = 9080
internal = 80
},{
external = 9443
internal = 443
}]
}
resource "docker_container" "nginx" {
image = docker_image.nginx.name
name = "tf-nginx-dynamic-demo"
# ports 就属于可重复的内嵌块
# 保持 label 名称与 内嵌块名称 一致即可
dynamic "ports" {
for_each = local.docker_nginx_ports
content {
external = ports.value.external
internal = ports.value.internal
ip = "0.0.0.0"
protocol = "tcp"
}
}
}
resource "docker_container" "redis" {
image = docker_image.redis.name
name = "tf-redis-dynamic-demo"
ports {
external = 9379
internal = 6379
}
}
创建资源时顺序可能是这样的
# 1. nginx 容器创建完成
docker_container.redis: Creating...
docker_container.nginx: Creation complete after 0s [id=9a9e5c6456b0f3ba03c1cb695e9d3cca35154d8d5e69f49e92211f1aedd7d1db]
# 2. redis 容器创建完成
docker_container.redis: Creation complete after 0s [id=f3eb34e4a53a0e1d97c0f44a9e91b7d8eeffd63a3d9f64b38e12259245b8f23d]
但我们需求是先启动 redis 容器 再启动 web 容器,针对这种场景就需要通过 depends_on
定义资源的依赖关系,方法很简单只需要在 nginx 资源块中加上一行就行
resource "docker_container" "nginx" {
image = docker_image.nginx.name
name = "tf-nginx-dynamic-demo"
# ports 就属于可重复的内嵌块
# 保持 label 名称与 内嵌块名称 一致即可
dynamic "ports" {
for_each = local.docker_nginx_ports
content {
external = ports.value.external
internal = ports.value.internal
ip = "0.0.0.0"
protocol = "tcp"
}
}
# 显式声明依赖关系
# nginx 容器依赖 redis 容器
depends_on = [docker_container.redis]
}
执行创建
# 1. redis 容器创建完成
docker_container.redis: Creating...
docker_container.redis: Creation complete after 0s [id=b4e9aa0b7c1de297b0e6046a4ee87cde1feac6188509fbfb659428d78c89e5f6]
# 2. nginx 容器创建完成
docker_container.nginx: Creating...
docker_container.nginx: Creation complete after 1s [id=707609b46730201835478c87f3f6847a37a0da722cad54bdfb55427c9e13e115]
五、provider 自定义提供者
假设,某些资源要求在特定的地区、可用区、帐号中创建,此时我们就需要使用 自定义 provider 替代 默认 provider
resource 资源
针对 resource 资源,语法如下: provider = <provider_name>.<alias>
terraform {
required_version = "1.1.9"
required_providers {
alicloud = {
source = "hashicorp/alicloud"
version = "1.197.0"
}
}
}
variable "ALICLOUD_ACCESS_KEY" {
type = string
}
variable "ALICLOUD_SECRET_KEY" {
type = string
}
provider "alicloud" {
region = "cn-beijing"
access_key = var.ALICLOUD_ACCESS_KEY
secret_key = var.ALICLOUD_SECRET_KEY
}
provider "alicloud" {
region = "cn-shanghai"
alias = "shanghai"
access_key = var.ALICLOUD_ACCESS_KEY
secret_key = var.ALICLOUD_SECRET_KEY
}
resource "alicloud_vpc" "vpc-bj" {
vpc_name = "vpc_bj"
cidr_block = "172.16.0.0/12"
}
resource "alicloud_vpc" "vpc-sh" {
vpc_name = "vpc_sh"
# 通过 provider.<alias> 引用
provider = alicloud.shanghai
cidr_block = "172.16.0.0/12"
}
执行 apply
,浏览器打开控制台确认
bj_vpc
sh_vpc
module 模块
针对 module 模块,语法稍有不同: provider = {<provider_name> = <provider_name>.<alias>}
创建子模块 example-module
example-module/main.tf
resource "alicloud_vpc" "vpc-sh" {
vpc_name = var.vpc_name
cidr_block = var.cidr_block
}
example-module/variables.tf
variable "cidr_block" {}
variable "vpc_name" {}
multi-provider-demo.tf
provider "alicloud" {
region = "cn-beijing"
access_key = var.ALICLOUD_ACCESS_KEY
secret_key = var.ALICLOUD_SECRET_KEY
}
provider "alicloud" {
region = "cn-shanghai"
alias = "shanghai"
access_key = var.ALICLOUD_ACCESS_KEY
secret_key = var.ALICLOUD_SECRET_KEY
}
resource "alicloud_vpc" "vpc-bj" {
vpc_name = "vpc_bj"
cidr_block = "172.16.0.0/12"
}
module "vpc-sh" {
source = "./example-module"
vpc_name = "vpc_sh"
cidr_block = "172.16.0.0/12"
# provider = {<provider_name> = <provider_name>.<alias>}
providers = {alicloud = alicloud.shanghai}
}
执行 apply 虽然会提示warning 但不影响实际效果
$ tf apply
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:
# alicloud_vpc.vpc-bj will be created
+ resource "alicloud_vpc" "vpc-bj" {
+ cidr_block = "172.16.0.0/12"
+ id = (known after apply)
+ ipv6_cidr_block = (known after apply)
+ name = (known after apply)
+ resource_group_id = (known after apply)
+ route_table_id = (known after apply)
+ router_id = (known after apply)
+ router_table_id = (known after apply)
+ secondary_cidr_blocks = (known after apply)
+ status = (known after apply)
+ vpc_name = "vpc_bj"
}
# module.vpc-sh.alicloud_vpc.vpc-sh will be created
+ resource "alicloud_vpc" "vpc-sh" {
+ cidr_block = "172.16.0.0/12"
+ id = (known after apply)
+ ipv6_cidr_block = (known after apply)
+ name = (known after apply)
+ resource_group_id = (known after apply)
+ route_table_id = (known after apply)
+ router_id = (known after apply)
+ router_table_id = (known after apply)
+ secondary_cidr_blocks = (known after apply)
+ status = (known after apply)
+ vpc_name = "vpc_sh"
}
Plan: 2 to add, 0 to change, 0 to destroy.
╷
│ Warning: Provider alicloud is undefined
│
│ on multi-provider-demo.tf line 40, in module "vpc-sh":
│ 40: providers = {alicloud = alicloud.shanghai}
│
│ Module module.vpc-sh does not declare a provider named alicloud.
│ If you wish to specify a provider configuration for the module, add an entry for alicloud in the required_providers block within the module.
╵
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
alicloud_vpc.vpc-bj: Creating...
module.vpc-sh.alicloud_vpc.vpc-sh: Creating...
module.vpc-sh.alicloud_vpc.vpc-sh: Creation complete after 5s [id=vpc-uf6lmqfig0iyjzbxbgo8h]
alicloud_vpc.vpc-bj: Creation complete after 7s [id=vpc-2zexe3o630gsuiu9g0tuq]
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
控制台检查
六、lifecycle 生命周期
每种资源实例都具有 创建、更新、销毁 三个阶段,通过元参数 lifecycle 可以改变资源实例在生命周期变化的默认行为,相关参数有 3 个
create_before_destroy 创建销毁
在我们对资源的参数进行调整时,有些参数是支持 “更新” 操作的,但有些参数调整就必须得 “销毁实例再重建”,如下所示:
data "alicloud_images" "images_ds" {
owners = "system"
name_regex = "^centos_7"
architecture = "x86_64"
}
data "alicloud_zones" "default" {
available_instance_type = "ecs.s6-c1m1.small"
}
data "alicloud_instance_types" "default" {
availability_zone = data.alicloud_zones.default.zones[0].id
cpu_core_count = 1
memory_size = 1
}
resource "alicloud_vpc" "vpc" {
vpc_name = "vpc_1"
cidr_block = "172.16.0.0/12"
}
resource "alicloud_vswitch" "vsw" {
vpc_id = alicloud_vpc.vpc.id
cidr_block = "172.16.0.0/21"
zone_id = data.alicloud_zones.default.zones[0].id
vswitch_name = "vsw1"
}
resource "alicloud_security_group" "default" {
name = "sg1"
description = "terraform security group resource"
vpc_id = alicloud_vpc.vpc.id
security_group_type = "normal" // normal 普通级 enterprise 企业级
}
resource "alicloud_instance" "ecs" {
availability_zone = data.alicloud_zones.default.zones[0].id
security_groups = [alicloud_security_group.default.id]
instance_type = data.alicloud_instance_types.default.instance_types[0].id
system_disk_category = "cloud_efficiency"
system_disk_name = "tf_system_disk_name"
system_disk_description = "tf_system_disk_description"
image_id = data.alicloud_images.images_ds.images[0].id
instance_name = "server1"
vswitch_id = alicloud_vswitch.vsw.id
internet_max_bandwidth_out = 0
internet_charge_type = "PayByTraffic"
password = "root@1234"
# lifecycle {
# create_before_destroy = true
# }
}
先创建一台 ecs 资源
alicloud_instance.ecs: Creating...
alicloud_instance.ecs: Still creating... [10s elapsed]
alicloud_instance.ecs: Creation complete after 13s [id=i-2zeel7oe1p270xrhxz40]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
然后,修改 ecs 配置信息 server1 -> server2
instance_name = "server2"
执行 plan
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
~ update in-place
Terraform will perform the following actions:
# alicloud_instance.ecs will be updated in-place
~ resource "alicloud_instance" "ecs" {
id = "i-2zeel7oe1p270xrhxz40"
~ instance_name = "server1" -> "server2"
tags = {}
# (34 unchanged attributes hidden)
}
Plan: 0 to add, 1 to change, 0 to destroy.
instance_name 配置是可以直接更新的,但是资源名称发生修改是会触发 销毁重建
resource "alicloud_instance" "ecs2" {
# ...
}
查看 plan
Plan: 1 to add, 0 to change, 1 to destroy.
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
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.
执行 apply
$ tf apply
观察控制台
启用 create_before_destroy,重新修改 资源名称
resource "alicloud_instance" "ecs1" {
availability_zone = data.alicloud_zones.default.zones[0].id
security_groups = [alicloud_security_group.default.id]
instance_type = data.alicloud_instance_types.default.instance_types[0].id
system_disk_category = "cloud_efficiency"
system_disk_name = "tf_system_disk_name"
system_disk_description = "tf_system_disk_description"
image_id = data.alicloud_images.images_ds.images[0].id
instance_name = "server1"
vswitch_id = alicloud_vswitch.vsw.id
internet_max_bandwidth_out = 0
internet_charge_type = "PayByTraffic"
password = "root@1234"
lifecycle {
create_before_destroy = true
}
}
执行 apply 变更
$ tf apply
观察控制台,terraform 会先帮我们创建新的资源,再销毁旧资源
prevent_destroy 防止销毁
通过此参数防止资源被销毁,如下所示:
resource "alicloud_instance" "ecs1" {
availability_zone = data.alicloud_zones.default.zones[0].id
security_groups = [alicloud_security_group.default.id]
instance_type = data.alicloud_instance_types.default.instance_types[0].id
system_disk_category = "cloud_efficiency"
system_disk_name = "tf_system_disk_name"
system_disk_description = "tf_system_disk_description"
image_id = data.alicloud_images.images_ds.images[0].id
instance_name = "server1"
vswitch_id = alicloud_vswitch.vsw.id
internet_max_bandwidth_out = 0
internet_charge_type = "PayByTraffic"
password = "root@1234"
lifecycle {
# 先创建、再删除
create_before_destroy = true
# 避免资源销毁
prevent_destroy = true
}
}
执行效果
$ tf destroy
alicloud_vpc.vpc: Refreshing state... [id=vpc-2ze2gtqly8gyrj8hvf5d7]
alicloud_security_group.default: Refreshing state... [id=sg-2zec2n4oh40cz0tlqtnt]
alicloud_vswitch.vsw: Refreshing state... [id=vsw-2zen09vpwxvb9zo0yhiom]
alicloud_instance.ecs1: Refreshing state... [id=i-2zejex5tdx5uuwtfgyz3]
# 提示资源不能被销毁
╷
│ Error: Instance cannot be destroyed
│
│ on lifecycle-create_before_destroy.tf line 37:
│ 37: resource "alicloud_instance" "ecs1" {
│
│ Resource alicloud_instance.ecs1 has lifecycle.prevent_destroy set, but the plan calls for this resource to be destroyed. To avoid this error and
│ continue with the plan, either disable lifecycle.prevent_destroy or reduce the scope of the plan using the -target flag.
ignore_changes 忽略变更
忽略资源变更,操作云平台资源的方式有很多,Web 控制台、API、Terraform 等等,对于某些我们不希望触发资源更新的变更,例如 ecs 标签
resource "alicloud_instance" "ecs1" {
availability_zone = data.alicloud_zones.default.zones[0].id
security_groups = [alicloud_security_group.default.id]
instance_type = data.alicloud_instance_types.default.instance_types[0].id
system_disk_category = "cloud_efficiency"
system_disk_name = "tf_system_disk_name"
system_disk_description = "tf_system_disk_description"
image_id = data.alicloud_images.images_ds.images[0].id
instance_name = "server1"
vswitch_id = alicloud_vswitch.vsw.id
internet_max_bandwidth_out = 0
internet_charge_type = "PayByTraffic"
password = "root@1234"
lifecycle {
# 先创建、再删除
create_before_destroy = true
# 避免资源销毁
prevent_destroy = true
}
}
控制台添加标签
查看 执行计划
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
~ update in-place
Terraform will perform the following actions:
# alicloud_instance.ecs1 will be updated in-place
~ resource "alicloud_instance" "ecs1" {
id = "i-2zejex5tdx5uuwtfgyz3"
~ tags = {
- "dev" = "" -> null
}
# (35 unchanged attributes hidden)
}
Plan: 0 to add, 1 to change, 0 to destroy.
默认情况下会发现变更,但本地配置没有此 tags 信息,terraform 在 apply 以本地配置为准,要删除此 tags,假如要忽略 tags 信息的变更该怎么配置呢?
答案就是 ignore_changes
,忽略那些配置项的更改,就在 list 中用逗号分隔写出来就行
lifecycle {
# 先创建、再删除
create_before_destroy = true
# 避免资源销毁
prevent_destroy = true
ignore_changes = [tags]
}
再次执行 plan,如下所示 no change
# alicloud_instance.ecs1 has changed
~ resource "alicloud_instance" "ecs1" {
id = "i-2zejex5tdx5uuwtfgyz3"
+ tags = {
+ "dev" = ""
}
# (35 unchanged attributes hidden)
}
Unless you have made equivalent changes to your configuration, or ignored the relevant attributes using ignore_changes, the following plan may
include actions to undo or respond to these changes.
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
No changes. Your infrastructure matches the configuration.
Your configuration already matches the changes detected above. If you'd like to update the Terraform state to match, create and apply a
refresh-only plan:
terraform apply -refresh-only
replace_triggered_by 关联替换
当某一资源发生变更时,触发其他资源重建,例如,配置 ecs 关注 vpc name 字段,当 vpc name 发生变更,执行 apply 时自动进行重建
使用此特性,需要 terraform 版本大于 >=1.2
resource "alicloud_vpc" "vpc" {
vpc_name = "vpc_2"
cidr_block = "172.16.0.0/12"
}
# ...
resource "alicloud_instance" "ecs1" {
availability_zone = data.alicloud_zones.default.zones[0].id
security_groups = [alicloud_security_group.default.id]
instance_type = data.alicloud_instance_types.default.instance_types[0].id
system_disk_category = "cloud_efficiency"
system_disk_name = "tf_system_disk_name"
system_disk_description = "tf_system_disk_description"
image_id = data.alicloud_images.images_ds.images[0].id
instance_name = "server1"
vswitch_id = alicloud_vswitch.vsw.id
internet_max_bandwidth_out = 0
internet_charge_type = "PayByTraffic"
password = "root@1234"
lifecycle {
# 先创建、再删除
create_before_destroy = true
# 避免资源销毁
prevent_destroy = true
# 忽略变更
ignore_changes = [tags]
# 关联替换
replace_triggered_by = [alicloud_vpc.vpc.name]
}
}
调整 vpc 名称为 vpc_2,查看执行计划
╷
│ Error: Instance cannot be destroyed
│
│ on lifecycle-create_before_destroy.tf line 37:
│ 37: resource "alicloud_instance" "ecs1" {
│
│ Resource alicloud_instance.ecs1 has lifecycle.prevent_destroy set, but the plan calls for this resource to be destroyed. To avoid this error and
│ continue with the plan, either disable lifecycle.prevent_destroy or reduce the scope of the plan using the -target flag
出现错误了,原因是 prevent_destroy
与 replace_triggered_by
产生冲突了,因为检测到 vpc_name 要触发销毁重建,但由于设置 “避免销毁” 的参数,所以给 terraform 整不会了
# prevent_destroy = true
注释掉,再次执行 plan
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
~ update in-place
+/- create replacement and then destroy
Terraform will perform the following actions:
# alicloud_instance.ecs1 will be replaced due to changes in replace_triggered_by
+/- resource "alicloud_instance" "ecs1" {
~ credit_specification = "Standard" -> (known after apply)
+ deployment_set_group_no = (known after apply)
~ host_name = "iZ2zejex5tdx5uuwtfgyz3Z" -> (known after apply)
+ http_endpoint = (known after apply)
~ http_put_response_hop_limit = 0 -> (known after apply)
+ http_tokens = (known after apply)
~ id = "i-2zejex5tdx5uuwtfgyz3" -> (known after apply)
- internet_charge_type = "PayByTraffic" -> null
~ internet_max_bandwidth_in = -1 -> (known after apply)
~ ipv6_address_count = 0 -> (known after apply)
~ ipv6_addresses = [] -> (known after apply)
+ key_name = (known after apply)
~ maintenance_action = "AutoRecover" -> (known after apply)
- maintenance_notify = false -> null
~ private_ip = "172.16.0.214" -> (known after apply)
+ public_ip = (known after apply)
+ role_name = (known after apply)
~ secondary_private_ip_address_count = 0 -> (known after apply)
~ secondary_private_ips = [] -> (known after apply)
~ spot_duration = 0 -> (known after apply)
- spot_price_limit = 0 -> null
~ status = "Running" -> (known after apply)
~ stopped_mode = "Not-applicable" -> (known after apply)
~ subnet_id = "vsw-2zen09vpwxvb9zo0yhiom" -> (known after apply)
- system_disk_encrypted = false -> null
+ system_disk_performance_level = (known after apply)
- tags = {
- "dev" = ""
} -> null
~ volume_tags = {} -> (known after apply)
# (16 unchanged attributes hidden)
}
# alicloud_vpc.vpc will be updated in-place
~ resource "alicloud_vpc" "vpc" {
id = "vpc-2ze2gtqly8gyrj8hvf5d7"
name = "vpc_1"
~ vpc_name = "vpc_1" -> "vpc_2"
# (8 unchanged attributes hidden)
}
# 1 change: vpc_name 变更
# 1 add:新建 ecs
# 1 destroy:销毁 ecs
Plan: 1 to add, 1 to change, 1 to destroy.
执行 apply
$ tf apply
观察控制台
从控制台上看到 create_before_destroy
是生效的,先创建在销毁!