如何通过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
相关产品推荐
相关产品推荐

