Terraform创建DynamoDB模块时为何替换现有表而非新建?
Terraform DynamoDB模块替换现有表问题排查
问题背景
使用Terraform 0.15创建AWS DynamoDB模块时,发现Terraform会尝试替换已有的DynamoDB表(销毁旧表后重建新表),但使用全新的Terraform状态文件时,能够直接创建新表而不影响旧表。相关代码及terraform plan输出如下:
模块代码
locals { table_name_from_env = var.dynamodb_table table_name = join("-", [local.table_name_from_env, lower(var.Component)]) kinesis_name = join("-", [local.table_name, "kinesis"]) } resource "aws_dynamodb_table" "non_autoscaled" { count = !var.autoscaling_enabled ? 1 : 0 name = "${local.table_name}" read_capacity = "${var.read_capacity}" write_capacity = "${var.write_capacity}" billing_mode = "${var.billing_mode}" hash_key = "${var.hash_key}" range_key = var.range_key dynamic "attribute" { for_each = var.attributes content { name = attribute.value.name type = attribute.value.type } } ttl { enabled = var.ttl_enabled attribute_name = var.ttl_attribute_name } tags = tomap({"Component" = "${var.Component}"}) } resource "aws_dynamodb_table" "autoscaled" { count = var.autoscaling_enabled ? 1 : 0 name = "${local.table_name}" read_capacity = "${var.read_capacity}" write_capacity = "${var.write_capacity}" billing_mode = "${var.billing_mode}" hash_key = "${var.hash_key}" range_key = var.range_key dynamic "attribute" { for_each = var.attributes content { name = attribute.value.name type = attribute.value.type } } ttl { enabled = var.ttl_enabled attribute_name = var.ttl_attribute_name } } resource "aws_kinesis_stream" "dynamodb_table_kinesis" { count = var.kinesis_enabled ? 1 : 0 name = "${local.kinesis_name}" shard_count = "${var.shard_count}" stream_mode_details { stream_mode = "${var.kinesis_stream_mode}" } } resource "aws_dynamodb_kinesis_streaming_destination" "dynamodb_table_kinesis_dest_non_autoscaled"{ count = var.kinesis_enabled && !var.autoscaling_enabled ? 1 : 0 stream_arn = aws_kinesis_stream.dynamodb_table_kinesis[0].arn table_name = aws_dynamodb_table.non_autoscaled[0].name } resource "aws_dynamodb_kinesis_streaming_destination" "dynamodb_table_kinesis_dest_autoscaled"{ count = var.kinesis_enabled && var.autoscaling_enabled ? 1 : 0 stream_arn = aws_kinesis_stream.dynamodb_table_kinesis[0].arn table_name = aws_dynamodb_table.autoscaled[0].name }
Terraform Plan输出
+ terraform plan module.aws_managed_dynamodb_table.aws_kinesis_stream.dynamodb_table_kinesis[0]: Refreshing state... [id=arn:aws:kinesis:stream/dynamodb-testing12345-coms-kinesis] module.aws_managed_dynamodb_table.aws_dynamodb_table.non_autoscaled[0]: Refreshing state... [id=dynamodb-testing12345-coms] module.aws_managed_dynamodb_table.aws_dynamodb_kinesis_streaming_destination.dynamodb_table_kinesis_dest_non_autoscaled[0]: Refreshing state... [id=dynamodb-testing12345-coms,arn:aws:kinesis:ap-south-1:stream/dynamodb-testing12345-coms-kinesis] Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: -/+ destroy and then create replacement Terraform will perform the following actions: # module.aws_managed_dynamodb_table.aws_dynamodb_kinesis_streaming_destination.dynamodb_table_kinesis_dest_non_autoscaled[0] must be replaced -/+ resource "aws_dynamodb_kinesis_streaming_destination" "dynamodb_table_kinesis_dest_non_autoscaled" { ~ id = "dynamodb-testing12345-coms,arn:aws:kinesis:ap-south-1:stream/dynamodb-testing12345-coms-kinesis" -> (known after apply) ~ stream_arn = "arn:aws:kinesis:ap-south-1:stream/dynamodb-testing12345-coms-kinesis" -> (known after apply) # forces replacement ~ table_name = "dynamodb-testing12345-coms" -> "dynamodb-testing123456-coms" # forces replacement } # module.aws_managed_dynamodb_table.aws_dynamodb_table.non_autoscaled[0] must be replaced -/+ resource "aws_dynamodb_table" "non_autoscaled" { ~ arn = "arn:aws:dynamodb:ap-south-1:table/dynamodb-testing12345-coms" -> (known after apply) ~ id = "dynamodb-testing12345-coms" -> (known after apply) ~ name = "dynamodb-testing12345-coms" -> "dynamodb-testing123456-coms" # forces replacement + stream_arn = (known after apply) - stream_enabled = false -> null + stream_label = (known after apply) + stream_view_type = (known after apply) tags = { "Component" = "XXX" } # (6 unchanged attributes hidden) ~ point_in_time_recovery { ~ enabled = false -> (known after apply) } + server_side_encryption { + enabled = (known after apply) + kms_key_arn = (known after apply) } # (3 unchanged blocks hidden) } # module.aws_managed_dynamodb_table.aws_kinesis_stream.dynamodb_table_kinesis[0] must be replaced -/+ resource "aws_kinesis_stream" "dynamodb_table_kinesis" { ~ arn = (known after apply) ~ id = (known after apply) ~ name = "dynamodb-testing12345-coms-kinesis" -> "dynamodb-testing123456-coms-kinesis" # forces replacement - shard_level_metrics = [] -> null - tags = {} -> null ~ tags_all = {} -> (known after apply) # (4 unchanged attributes hidden) # (1 unchanged block hidden) } Plan: 3 to add, 0 to change, 3 to destroy.
问题原因及解决方法
核心原因
- Terraform状态绑定逻辑:Terraform通过状态文件跟踪已创建的资源。复用同一状态文件时,Terraform会将配置中的资源与状态记录的现有资源关联。一旦资源的不可修改属性(如DynamoDB表名)变化,Terraform会判定需销毁旧资源重建新资源——因为AWS不允许修改DynamoDB表名,只能删除后新建。
- 表名变更触发替换:从
terraform plan输出可见,表名从dynamodb-testing12345-coms变为dynamodb-testing123456-coms,该字段是ForceNew属性,直接触发资源替换。 - 双资源设计隐患:代码用
count分别定义non_autoscaled和autoscaled两个DynamoDB表资源,且共用同一个local.table_name。当autoscaling_enabled参数切换时,旧表资源会被销毁,新表资源会被创建,同样导致表替换。
解决方向
根据需求选择对应方案:
需求1:保留旧表,同时创建新表
- 使用Terraform工作区:每个工作区对应独立状态文件,不同工作区资源相互隔离。执行
terraform workspace new <新工作区名>切换后再部署。 - 给表名添加唯一标识:在
local.table_name中加入随机字符串、环境标识或时间戳,确保表名唯一。示例:locals { table_name_from_env = var.dynamodb_table unique_suffix = random_string.suffix.result table_name = join("-", [local.table_name_from_env, lower(var.Component), local.unique_suffix]) kinesis_name = join("-", [local.table_name, "kinesis"]) } resource "random_string" "suffix" { length = 6 special = false upper = false } - 指定独立状态文件:部署时通过
-state参数指定不同状态文件路径,例如terraform plan -state=./new-table.tfstate。
需求2:不替换旧表,保持现有表不变
- 调整
var.dynamodb_table或var.Component参数,让local.table_name与现有表名一致,恢复状态文件与资源的匹配关系。 - 重构双表资源设计:将自动扩缩容配置与表资源分离,使用
aws_appautoscaling_target和aws_appautoscaling_policy管理自动扩缩容,避免通过count切换不同表资源。示例:resource "aws_dynamodb_table" "main" { name = local.table_name read_capacity = var.read_capacity write_capacity = var.write_capacity billing_mode = var.billing_mode hash_key = var.hash_key range_key = var.range_key # 其他属性... } # 自动扩缩容配置(仅当autoscaling_enabled为true时创建) resource "aws_appautoscaling_target" "dynamodb_read" { count = var.autoscaling_enabled ? 1 : 0 max_capacity = var.read_max_capacity min_capacity = var.read_min_capacity resource_id = "table/${aws_dynamodb_table.main.name}" scalable_dimension = "dynamodb:table:ReadCapacityUnits" service_namespace = "dynamodb" } resource "aws_appautoscaling_policy" "dynamodb_read" { count = var.autoscaling_enabled ? 1 : 0 name = "read-scaling-policy" policy_type = "TargetTrackingScaling" resource_id = aws_appautoscaling_target.dynamodb_read[0].resource_id scalable_dimension = aws_appautoscaling_target.dynamodb_read[0].scalable_dimension service_namespace = aws_appautoscaling_target.dynamodb_read[0].service_namespace target_tracking_scaling_policy_configuration { target_value = var.read_target_utilization predefined_metric_specification { predefined_metric_type = "DynamoDBReadCapacityUtilization" } } }
内容的提问来源于stack exchange,提问作者Deepak Mourya
相关产品推荐
相关产品推荐

