Terraform管理S3跨区域复制与生命周期规则状态波动问题
Terraform S3配置反复创建/删除问题排查与解决
我用Terraform搭建了一个项目,用来创建S3桶、配置跨区域复制(CRR)、版本控制以及删除旧版本的生命周期规则。但遇到了异常:无代码变更时,每次执行Terraform命令,只要AWS里存在跨区域复制和生命周期规则,Terraform就会尝试删除它们;如果这些配置已经被删除,又会重新创建,状态始终不对。就算清空AWS资源重新部署,还是会陷入这种反复波动的循环。
执行计划输出
执行terraform init && terraform plan -var-file xyz.tfvars的计划输出如下:
$ terraform init && terraform plan -var-file xyz.tfvars ... Terraform will perform the following actions: # module.test_s3_bucket["bnpl-docs"].aws_s3_bucket.s3_bucket will be updated in-place ~ resource "aws_s3_bucket" "s3_bucket" { id = "bnpl-docs" tags = {} # (11 unchanged attributes hidden) - replication_configuration { - role = "arn:aws:iam::....:role/bnpl-docs-s3-bucket-replication" -> null - rules { - id = "version-replication" -> null - priority = 0 -> null - status = "Enabled" -> null - destination { - bucket = "arn:aws:s3:::bnpl-docs-crr" -> null - storage_class = "STANDARD" -> null } } } # (1 unchanged block hidden) } # module.test_s3_bucket["bnpl-docs"].aws_s3_bucket_lifecycle_configuration.s3_bucket[0] will be created + resource "aws_s3_bucket_lifecycle_configuration" "s3_bucket" { + bucket = "bnpl-docs" + id = (known after apply) + rule { + id = "version-retention" + status = "Enabled" + expiration { + days = 0 + expired_object_delete_marker = true } + noncurrent_version_expiration { + noncurrent_days = 30 } } } Plan: 1 to add, 1 to change, 0 to destroy.
执行apply后,AWS中这两项配置都会消失;再次执行计划和apply,这两项又会被创建。
相关模块配置
主模块调用
module "test_s3_bucket" { source = "./modules/test-s3-bucket" for_each = local.aws_s3_bucket_map bucket_name = each.key versioning = each.value.version_config } module "test_s3_bucket_repli" { source = "./modules/test-s3-bucket" providers = { aws = aws.repli } for_each = local.aws_s3_bucket_map_repli bucket_name = each.key versioning = each.value.version_config } module "test_s3_bucket_repli_config" { source = "./modules/test-s3-bucket-replication" for_each = local.aws_s3_bucket_map_repli src_bucket = { name = module.test_s3_bucket[each.value.src_bucket_name].name arn = module.test_s3_bucket[each.value.src_bucket_name].arn } dest_bucket = { name = module.test_s3_bucket_repli[each.key].name arn = module.test_s3_bucket_repli[each.key].arn } }
test_s3_bucket模块内容
terraform { required_providers { aws = { source = "hashicorp/aws" version = "~> 3.0" } } } ## # Bucket with configuration ## resource "aws_s3_bucket" "s3_bucket" { bucket = var.bucket_name } resource "aws_s3_bucket_acl" "s3_bucket" { bucket = aws_s3_bucket.s3_bucket.id acl = "private" } resource "aws_s3_bucket_versioning" "s3_bucket" { bucket = aws_s3_bucket.s3_bucket.id versioning_configuration { status = "Enabled" } } resource "aws_s3_bucket_lifecycle_configuration" "s3_bucket" { count = var.versioning == null ? 0 : 1 bucket = aws_s3_bucket.s3_bucket.id rule { id = var.versioning.rule_id expiration { expired_object_delete_marker = true } noncurrent_version_expiration { noncurrent_days = var.versioning.expiration_days } status = "Enabled" } } resource "aws_s3_bucket_public_access_block" "s3_bucket" { bucket = aws_s3_bucket.s3_bucket.id block_public_acls = true block_public_policy = true ignore_public_acls = true restrict_public_buckets = true }
test-s3-bucket-replication模块内容
terraform { required_providers { aws = { source = "hashicorp/aws" version = "~> 3.0" } } } resource "aws_s3_bucket_replication_configuration" "bucket_main" { bucket = var.src_bucket.name role = aws_iam_role.s3_bucket_main_replication.arn rule { id = "version-replication" status = "Enabled" destination { bucket = var.dest_bucket.arn storage_class = "STANDARD" } } } resource "aws_iam_role" "s3_bucket_main_replication" { name = "${var.src_bucket.name}-s3-bucket-replication" assume_role_policy = file("${path.module}/files/policies/sts-s3-assume.json") } resource "aws_iam_policy" "s3_bucket_main_replication" { name = "${var.src_bucket.name}-s3-bucket-replication" policy = templatefile("${path.module}/files/policies/s3-bucket-replication.json", { source_bucket_arn = var.src_bucket.arn, destination_bucket_arn = var.dest_bucket.arn, }) } resource "aws_iam_role_policy_attachment" "s3_bucket_main_replication" { role = aws_iam_role.s3_bucket_main_replication.name policy_arn = aws_iam_policy.s3_bucket_main_replication.arn }
问题根源
- 复制配置资源冲突:旧版AWS Provider(3.x)中,
aws_s3_bucket资源会默认读取桶上的复制配置作为自身属性,但你同时用aws_s3_bucket_replication_configuration单独管理复制规则,导致Terraform状态不一致:aws_s3_bucket认为配置里没有复制规则,每次计划都会试图删除实际存在的配置;而aws_s3_bucket_replication_configuration又会在下次计划时重新创建,形成循环。 - 生命周期规则判断逻辑不稳定:
aws_s3_bucket_lifecycle_configuration的count判断依赖var.versioning == null,如果变量传递存在细微偏差,会导致Terraform无法稳定识别资源是否应该存在,触发反复创建/删除。
解决步骤
1. 隔离复制配置的状态冲突
在test_s3_bucket模块的aws_s3_bucket资源中添加生命周期忽略规则,告诉Terraform不要管理该资源上的复制配置,交给单独的复制资源处理:
resource "aws_s3_bucket" "s3_bucket" { bucket = var.bucket_name lifecycle { ignore_changes = [replication_configuration] } }
2. 修正生命周期规则的判断逻辑
将count的判断条件改为更严谨的非空检查,避免变量类型判断误差:
resource "aws_s3_bucket_lifecycle_configuration" "s3_bucket" { count = var.versioning != null ? 1 : 0 bucket = aws_s3_bucket.s3_bucket.id rule { id = var.versioning.rule_id expiration { expired_object_delete_marker = true } noncurrent_version_expiration { noncurrent_days = var.versioning.expiration_days } status = "Enabled" } }
3. 重置Terraform状态
执行命令同步状态并修复不一致:
# 刷新状态,同步AWS实际资源到Terraform状态 terraform refresh -var-file xyz.tfvars # 重新计划确认状态一致 terraform plan -var-file xyz.tfvars
如果仍有问题,可手动移除冲突状态条目后重新导入:
# 移除旧的复制配置状态条目 terraform state rm module.test_s3_bucket["bnpl-docs"].aws_s3_bucket.s3_bucket.replication_configuration # 导入复制配置资源 terraform import module.test_s3_bucket_repli_config["bnpl-docs-crr"].aws_s3_bucket_replication_configuration.bucket_main bnpl-docs
4. 升级AWS Provider(可选推荐)
AWS Provider 4.x及以上版本彻底将复制配置从aws_s3_bucket资源中分离,强制使用aws_s3_bucket_replication_configuration管理,从根源避免这类冲突。可将模块中的Provider版本改为:
terraform { required_providers { aws = { source = "hashicorp/aws" version = "~> 4.0" } } }
内容的提问来源于stack exchange,提问作者Phil
相关产品推荐
相关产品推荐

