You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

修改文件后执行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
}

问题根因分析

折腾了半天终于找到核心问题:

  1. 模块执行时机不匹配:hashicorp/dir/template模块是在Terraform的规划阶段就扫描指定目录的文件哈希,但我的null_resource.build_frontend是在执行阶段才运行npm run build生成新的前端产物。这就导致Terraform规划时,新的build文件还没生成,模块扫到的还是旧文件的状态,自然认为没有变化。
  2. 触发器逻辑不合理:原来的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 13:38:11