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

如何通过Terraform单步完成CFN栈的创建与更新?

问题

尝试通过Terraform创建带自承担信任关系的IAM角色,之前用CloudFormation时需要分两步:先运行模板创建角色,再运行另一个模板更新自承担配置。现在想在Terraform里一步完成这个流程,但遇到了问题:

  • 手动修改Terraform代码里的模板路径,分两次执行可以成功,但无法自动化
  • 尝试定义两个同名的aws_cloudformation_stack资源,先创建再更新,报错“栈已存在”
  • 找不到能直接处理CFN栈更新的Terraform模块

当前可运行但需手动修改的代码:

resource "aws_cloudformation_stack" "uc_cloud_resources-cfn" {
  name          = "${local.prefix}-${lower(var.BusinessUnit)}-${lower(var.Department)}-${lower(var.Environment)}-sc-stack"
  capabilities  = ["CAPABILITY_NAMED_IAM","CAPABILITY_IAM"]
  parameters    = {
    RoleName     = "${var.RoleName}"
    BucketName   = "${var.BucketName}"
    BusinessUnit = "${var.BusinessUnit}"
    Environment  = "${var.Environment}"
    Department   = "${var.Department}"
  }
  #template_body   = file("/path/to/deployment/template1.json")
  template_body   = file("/path/to/deployment/template2.json")
}

尝试过的双资源代码(报错“栈已存在”):

resource "aws_cloudformation_stack" "uc_cloud_resources-cfn" {
  name          = "${local.prefix}-${lower(var.BusinessUnit)}-${lower(var.Department)}-${lower(var.Environment)}-sc-stack"
  capabilities  = ["CAPABILITY_NAMED_IAM","CAPABILITY_IAM"]
  parameters    = {
    RoleName     = "${var.RoleName}"
    BucketName   = "${var.BucketName}"
    BusinessUnit = "${var.BusinessUnit}"
    Environment  = "${var.Environment}"
    Department   = "${var.Department}"
  }
  template_body   = file("/path/to/template1.json")
}

resource "aws_cloudformation_stack" "uc_cloud_resources-cfn_update" {
  name = "${local.prefix}-${lower(var.BusinessUnit)}-${lower(var.Department)}-${lower(var.Environment)}-sc-stack"
  capabilities  = ["CAPABILITY_NAMED_IAM","CAPABILITY_IAM"]
  depends_on      = [ aws_cloudformation_stack.uc_cloud_resources-cfn ]
  parameters      = {
    RoleName        = "${var.RoleName}"
    BucketName      = "${var.BucketName}"
    BusinessUnit    = "${var.BusinessUnit}"
    Environment     = "${var.Environment}"
    Department      = "${var.Department}"
  }
  template_body   = file("/path/to/template2.json")
}
解决方案

方案1:合并CloudFormation模板(最优)

根本不需要分两次操作,直接在一个CFN模板里定义带自承担信任策略的IAM角色,Terraform只需调用一次该模板即可。

示例合并后的CFN模板(template_combined.json):

{
  "Resources": {
    "MyAssumableRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "RoleName": {"Ref": "RoleName"},
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "AWS": {"Ref": "AWS::AccountId"}
              },
              "Action": "sts:AssumeRole"
            }
          ]
        },
        "Tags": [
          {
            "Key": "BusinessUnit",
            "Value": {"Ref": "BusinessUnit"}
          },
          {
            "Key": "Department",
            "Value": {"Ref": "Department"}
          },
          {
            "Key": "Environment",
            "Value": {"Ref": "Environment"}
          }
        ]
      }
    }
  },
  "Parameters": {
    "RoleName": {"Type": "String"},
    "BusinessUnit": {"Type": "String"},
    "Department": {"Type": "String"},
    "Environment": {"Type": "String"}
  }
}

对应的Terraform代码:

resource "aws_cloudformation_stack" "uc_cloud_resources-cfn" {
  name          = "${local.prefix}-${lower(var.BusinessUnit)}-${lower(var.Department)}-${lower(var.Environment)}-sc-stack"
  capabilities  = ["CAPABILITY_NAMED_IAM","CAPABILITY_IAM"]
  parameters    = {
    RoleName     = var.RoleName
    BusinessUnit = var.BusinessUnit
    Environment  = var.Environment
    Department   = var.Department
  }
  template_body = file("/path/to/template_combined.json")
}

方案2:用Terraform的null_resource+local-exec调用AWS CLI控制流程

如果必须保留两个CFN模板,可以通过null_resource执行AWS CLI命令,先创建栈,再更新栈,利用depends_on保证顺序:

# 第一步:创建初始栈
resource "null_resource" "create_initial_stack" {
  provisioner "local-exec" {
    command = <<EOT
aws cloudformation create-stack \
  --name ${local.prefix}-${lower(var.BusinessUnit)}-${lower(var.Department)}-${lower(var.Environment)}-sc-stack \
  --capabilities CAPABILITY_NAMED_IAM CAPABILITY_IAM \
  --parameters ParameterKey=RoleName,ParameterValue=${var.RoleName} \
               ParameterKey=BucketName,ParameterValue=${var.BucketName} \
               ParameterKey=BusinessUnit,ParameterValue=${var.BusinessUnit} \
               ParameterKey=Environment,ParameterValue=${var.Environment} \
               ParameterKey=Department,ParameterValue=${var.Department} \
  --template-body file:///path/to/template1.json
EOT
  }
}

# 第二步:更新栈
resource "null_resource" "update_stack" {
  depends_on = [null_resource.create_initial_stack]

  provisioner "local-exec" {
    command = <<EOT
aws cloudformation update-stack \
  --name ${local.prefix}-${lower(var.BusinessUnit)}-${lower(var.Department)}-${lower(var.Environment)}-sc-stack \
  --capabilities CAPABILITY_NAMED_IAM CAPABILITY_IAM \
  --parameters ParameterKey=RoleName,ParameterValue=${var.RoleName} \
               ParameterKey=BucketName,ParameterValue=${var.BucketName} \
               ParameterKey=BusinessUnit,ParameterValue=${var.BusinessUnit} \
               ParameterKey=Environment,ParameterValue=${var.Environment} \
               ParameterKey=Department,ParameterValue=${var.Department} \
  --template-body file:///path/to/template2.json
EOT
  }
}

注意:需要确保运行Terraform的环境已配置AWS凭证,且有权限执行cloudformation create-stack和update-stack操作。

方案3:直接用Terraform的IAM资源(绕开CFN)

既然用Terraform,完全可以直接创建IAM角色,不需要依赖CFN模板,更简洁可控:

resource "aws_iam_role" "assumable_role" {
  name = var.RoleName

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
        }
        Action = "sts:AssumeRole"
      }
    ]
  })

  tags = {
    BusinessUnit = var.BusinessUnit
    Department   = var.Department
    Environment  = var.Environment
  }
}

# 如果需要附加桶权限策略,可添加以下资源
resource "aws_iam_role_policy" "bucket_access" {
  name   = "${var.RoleName}-bucket-access"
  role   = aws_iam_role.assumable_role.id
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = ["s3:GetObject", "s3:PutObject"]
        Resource = "arn:aws:s3:::${var.BucketName}/*"
      }
    ]
  })
}

data "aws_caller_identity" "current" {}

这种方式不需要依赖CFN,直接通过Terraform原生资源管理IAM角色,避免了两次操作的麻烦。

内容的提问来源于stack exchange,提问作者Ismail

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 04:02:39