修改文件后执行terraform apply,aws_s3_bucket_object资源未触发更新的问题求助
问题:Terraform修改前端文件后,AWS S3 Bucket Object资源未触发更新
我最近遇到一个头疼的问题:第一次执行terraform apply成功部署前端文件到S3后,我修改了前端源码,再次跑terraform apply时,本以为aws_s3_bucket_object.frontend_bucket_content会同步更新文件,结果完全没反应。我的完整代码如下:
locals { frontend_directory = "./frontend" frontend_deliverables = "${local.frontend_directory}/build" frontend_bucket_name = "my-bucket" } resource "null_resource" "build_frontend" { provisioner "local-exec" { command = "npm run build" interpreter = ["PowerShell", "-Command"] working_dir = "${path.module}/frontend" } triggers = { always_run = timestamp() } } resource "aws_s3_bucket" "frontend_bucket" { bucket = local.frontend_bucket_name server_side_encryption_configuration { rule { apply_server_side_encryption_by_default { sse_algorithm = "AES256" } } } } module "template_files" { source = "hashicorp/dir/template" base_dir = local.frontend_deliverables } resource "aws_s3_bucket_object" "frontend_bucket_content" { for_each = module.template_files.files bucket = aws_s3_bucket.frontend_bucket.bucket key = each.key content_type = each.value.content_type source = each.value.source_path etag = each.value.digests.md5 server_side_encryption = "AES256" depends_on = [ null_resource.build_frontend ] } resource "aws_cloudfront_distribution" "s3_distribution" { origin { domain_name = aws_s3_bucket.frontend_bucket.bucket_regional_domain_name origin_id = "S3-${aws_s3_bucket.frontend_bucket.id}" s3_origin_config { origin_access_identity = aws_cloudfront_origin_access_identity.origin_access_identity.cloudfront_access_identity_path } } default_root_object = "index.html" restrictions { geo_restriction { restriction_type = "none" } } default_cache_behavior { viewer_protocol_policy = "redirect-to-https" allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"] cached_methods = ["GET", "HEAD"] target_origin_id = "S3-${aws_s3_bucket.frontend_bucket.id}" forwarded_values { query_string = false cookies { forward = "none" } } } viewer_certificate { cloudfront_default_certificate = true } enabled = true } resource "aws_cloudfront_origin_access_identity" "origin_access_identity" { comment = "my-tf-test-identity" } data "aws_iam_policy_document" "s3_policy" { statement { actions = ["s3:GetObject"] resources = ["${aws_s3_bucket.frontend_bucket.arn}/*"] principals { type = "AWS" identifiers = [aws_cloudfront_origin_access_identity.origin_access_identity.iam_arn] } } } resource "aws_s3_bucket_policy" "frontend_s3_bucket_policy" { bucket = aws_s3_bucket.frontend_bucket.id policy = data.aws_iam_policy_document.s3_policy.json }
问题根因分析
折腾了半天终于找到核心问题:
- 模块执行时机不匹配:
hashicorp/dir/template模块是在Terraform的规划阶段就扫描指定目录的文件哈希,但我的null_resource.build_frontend是在执行阶段才运行npm run build生成新的前端产物。这就导致Terraform规划时,新的build文件还没生成,模块扫到的还是旧文件的状态,自然认为没有变化。 - 触发器逻辑不合理:原来的
null_resource用timestamp()做触发器,每次都会执行build,但模块在规划时看不到新生成的文件,所以后续S3资源还是不会更新。
修复方案
我调整了代码逻辑,让Terraform能正确感知文件变化,步骤如下:
1. 替换模板模块为本地文件数据源
放弃使用hashicorp/dir/template,改用Terraform内置的fileset和filemd5来动态检测文件变化。先在locals块里添加文件集合定义:
locals { frontend_directory = "./frontend" frontend_deliverables = "${local.frontend_directory}/build" frontend_bucket_name = "my-bucket" # 扫描build目录下所有文件(排除目录本身) frontend_files = fileset(local.frontend_deliverables, "**/*") }
2. 优化null_resource的触发逻辑
把原来的timestamp()触发器改成基于前端源文件的哈希总和,只有当源文件变化时才重新执行build,同时让Terraform能提前感知到需要更新:
resource "null_resource" "build_frontend" { provisioner "local-exec" { command = "npm run build" interpreter = ["PowerShell", "-Command"] working_dir = "${path.module}/frontend" } # 用前端源码目录下所有文件的哈希总和作为触发器 triggers = { source_files_hash = sha1(join("", [for f in fileset(local.frontend_directory, "**/*") : filemd5("${local.frontend_directory}/${f}")])) } }
3. 重构S3 Bucket Object资源
直接遍历我们定义的local.frontend_files,并用filemd5获取文件哈希来触发S3对象的更新,同时自动推断content_type(比模块的设置更可靠):
resource "aws_s3_bucket_object" "frontend_bucket_content" { for_each = toset(local.frontend_files) bucket = aws_s3_bucket.frontend_bucket.bucket key = each.value source = "${local.frontend_deliverables}/${each.value}" server_side_encryption = "AES256" # 文件内容变化时,MD5哈希会变,Terraform会自动触发更新 etag = filemd5("${local.frontend_deliverables}/${each.value}") # 自动匹配常见文件类型的content_type content_type = lookup({ ".html" = "text/html", ".css" = "text/css", ".js" = "application/javascript", ".json" = "application/json", ".png" = "image/png", ".jpg" = "image/jpeg", ".svg" = "image/svg+xml", ".ico" = "image/x-icon" }, regex("\\.[^.]+$", each.value), "application/octet-stream") depends_on = [null_resource.build_frontend] }
修复原理
fileset和filemd5在规划阶段就会计算文件哈希,当前端源文件变化时,null_resource的触发器会触发build,同时Terraform能预见到build后产物的哈希变化- 直接用文件的MD5哈希作为S3对象的
etag,只要文件内容有变动,Terraform就能检测到差异,自动触发S3对象的更新
内容的提问来源于stack exchange,提问作者Tar
相关产品推荐
相关产品推荐

