Terraform AWS动态时间默认标签引发全标签重建问题求助
问题描述
我使用Terraform AWS Provider v5.21.0,希望通过provider的default_tags块给所有资源添加一个随每次terraform plan/apply变化的LastTerraformApply标签。
我的values.tfvars配置:
tags = { Service = "some_service" Project = "some_project" Environment = "prod" }
初始provider配置能正常给所有资源应用上述标签:
provider "aws" { region = "eu-west-3" default_tags { tags = var.tags } # ... 其他配置 }
当我给default_tags添加固定值的LastTerraformApply标签时,仅该标签会更新,无异常:
provider "aws" { region = "eu-west-3" default_tags { tags = merge(var.tags, { LastTerraformApply = "foo" } ) } # ... 其他配置 }
但改用动态时间值时,每次执行plan/apply,Terraform都会尝试销毁并重建所有资源的所有标签,而非仅更新LastTerraformApply:
provider "aws" { region = "eu-west-3" default_tags { tags = merge(var.tags, { LastTerraformApply = formatdate("DD-MM-YYYY@hh:mm Z", timestamp()) } ) } # ... 其他配置 }
Plan阶段的变更提示显示所有标签被标记为移除,tags_all变为"known after apply":
# aws_mq_broker.broker will be updated in-place ~ resource "aws_mq_broker" "broker" { id = "b-xxxxxxxxxx" ~ tags = { - "Environment" = "prod" -> null - "LastTerraformApply" = "16-10-2023@13:02 UTC" -> null - "Project" = "some_project" -> null - "Service" = "some_service" -> null } ~ tags_all = { - "Environment" = "prod" -> null - "LastTerraformApply" = "16-10-2023@13:02 UTC" -> null - "Project" = "some_project" -> null - "Service" = "some_service" -> null } -> (known after apply) # (14 unchanged attributes hidden) # (8 unchanged blocks hidden) }
将时间函数移至local块后,问题依旧。
原因分析
核心问题是timestamp()函数在Terraform的plan阶段属于未知值——Terraform无法提前确定该函数的最终输出,只能在apply阶段计算出具体时间。
当default_tags中的tags map包含未知值时,整个map会被Terraform视为整体未知。在对比资源当前状态时,Terraform无法识别哪些标签是已知的固定值、哪些是动态值,只能判定现有所有标签需要被移除,然后替换为新的(但新的标签集合要到apply阶段才能确定),从而出现所有标签被重建的现象。
而使用固定值"foo"时,整个tags map是完全已知的,Terraform能精准对比出只有LastTerraformApply标签需要更新,因此不会影响其他标签。
解决方案
方案1:在资源级别定义标签(推荐,直观可控)
放弃provider级别的default_tags,改用local变量统一管理标签,然后在每个资源的tags字段引用该变量。这种方式下,Terraform能识别出只有LastTerraformApply是动态值,仅更新该标签:
locals { # 基础固定标签 base_tags = var.tags # 动态标签 dynamic_tags = { LastTerraformApply = formatdate("DD-MM-YYYY@hh:mm Z", timestamp()) } # 合并所有标签 all_tags = merge(local.base_tags, local.dynamic_tags) } # 示例资源配置 resource "aws_mq_broker" "broker" { # ... 其他资源配置 tags = local.all_tags }
方案2:使用terraform_data封装动态值(无需修改资源)
通过terraform_data资源在apply阶段生成并存储时间值,这样在plan阶段,LastTerraformApply的值是上一次apply的已知值,仅在apply时更新为新时间,从而让default_tags中的map保持部分已知状态,避免全量标签重建:
# 存储上次apply的时间,每次apply会更新该值 resource "terraform_data" "last_apply_time" { input = formatdate("DD-MM-YYYY@hh:mm Z", timestamp()) } provider "aws" { region = "eu-west-3" default_tags { tags = merge(var.tags, { LastTerraformApply = terraform_data.last_apply_time.output }) } # ... 其他配置 }
该方案的优势是无需修改现有资源的标签配置,default_tags仍能自动应用到所有资源;缺点是会在Terraform状态中多一个terraform_data资源。
内容的提问来源于stack exchange,提问作者PEPin

