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

Azure多存储账户与MSI创建及角色分配Terraform配置问题

问题场景

使用Terraform批量创建Azure存储账户与用户托管标识(MSI),需求是单个存储账户可绑定多个MSI并分配不同角色。目前已在tfvars中为存储账户定义role_assignments块,但因MSI尚未创建,无法直接引用其principal_id;尝试用locals处理时触发「The “for_each” set includes values derived from resource attributes that…」错误,需解决隐式依赖导致的配置问题。


现有模块代码

托管标识模块(../modules/userIdentity)

#-------------------
# 托管标识
#-------------------
resource "azurerm_user_assigned_identity" "user_assigned_identity" {
  name                = var.name
  location            = var.location
  resource_group_name = var.resource_group_name
}

存储账户模块(../modules/storageAccount)

#-------------------
# 存储账户
#-------------------  
resource "azurerm_storage_account" "storage_account" {
  name                     = "storageaccountname"
  resource_group_name      = azurerm_resource_group.example.name
  location                 = azurerm_resource_group.example.location
  account_tier             = "Standard"
  account_replication_type = "GRS"

  tags = {
    environment = "staging"
  }
}

#-------------------
# 存储容器
#-------------------
resource "azurerm_storage_container" "storage_container" {
  for_each              = var.containers
  name                  = try(each.value.container_name, null)
  container_access_type = try(each.value.access_type, null)
  storage_account_name  = azurerm_storage_account.storage_account.name
}

#-----------------
# 角色分配
#-----------------
resource "azurerm_role_assignment" "role_assignment" {
  for_each             = var.role_assignments
  description          = try(each.value.description, null)
  role_definition_name = try(each.value.role, null)
  principal_id         = try(each.value.principal_id, null)
  scope                = azurerm_storage_account.storage_account.id
}

模块调用代码(根模块)

#-------------------
# 存储账户实例
#-------------------
module "storage_account" {
  source = "../modules/storageAccount"

  for_each                        = var.storage_account
  name                            = lower(replace(each.value.name, "/[[:^alnum:]]/", ""))
  resource_group_name             = each.value.resource_group
  location                        = var.location
  containers                      = try(each.value.storage_container, {})
}

#-----------------
# 用户托管标识实例
#-----------------
module "UserManagedIdentity" {
  source = "../modules/userIdentity"

  for_each            = var.UserManagedIdentity
  name                = lower(each.value.name)
  location            = var.location
  resource_group_name = each.value.resource_group
}

TFVARS配置

storage_account = {
  new_tf_sa = {
    name           = "test_sa"
    resource_group = "learn_rg"
    storage_container = {
      testcontainer = {
        container_name = "tf-states"
        access_type    = "private"
      }
    }
    role_assignments = {
      test_mi_role_assignment = {
        mi_name       = "test1_user_identity"
        role          = "Contributor"
        description   = "Assign Contributor role"
      },
      test2_mi_role_assignment = {
        mi_name       = "test2_user_identity"
        role          = "Contributor"
        description   = "Assign Contributor role"
      }
    }
  }
}

UserManagedIdentity = {
#-------------------------------#
# 测试托管标识 #
#-------------------------------#
  test1_user_identity = {
    name           = "mi-test1"
    resource_group = "learn_rg"
  },
    
  test2_user_identity = {
    name           = "mi-test2"
    resource_group = "learn_rg"
  }
}

解决方案

核心思路是在根模块中提前关联MSI与存储账户的角色分配关系,确保for_each使用的键为静态变量值,避免依赖未创建的资源属性。

1. 托管标识模块添加输出

在../modules/userIdentity中添加输出,暴露MSI的principal_id:

output "principal_id" {
  type        = string
  value       = azurerm_user_assigned_identity.user_assigned_identity.principal_id
  description = "用户托管标识的Principal ID"
}

2. 根模块创建locals关联角色分配

在根模块中定义locals,将存储账户的role_assignments与对应的MSI principal_id绑定:

locals {
  # 构建包含principal_id的存储账户角色分配映射
  storage_account_role_assignments = {
    for sa_key, sa in var.storage_account : sa_key => {
      for ra_key, ra in sa.role_assignments : ra_key => {
        mi_name      = ra.mi_name
        role         = ra.role
        description  = ra.description
        # 通过MSI模块的键查找对应的principal_id
        principal_id = module.UserManagedIdentity[ra.mi_name].principal_id
      }
    }
  }
}

3. 修改存储账户模块调用

更新根模块中存储账户的调用,传递构建好的带principal_id的角色分配:

module "storage_account" {
  source = "../modules/storageAccount"

  for_each                        = var.storage_account
  name                            = lower(replace(each.value.name, "/[[:^alnum:]]/", ""))
  resource_group_name             = each.value.resource_group
  location                        = var.location
  containers                      = try(each.value.storage_container, {})
  # 传递包含principal_id的角色分配
  role_assignments                = local.storage_account_role_assignments[each.key]
}

4. 存储账户模块修正变量与角色分配资源

在../modules/storageAccount中添加变量定义:

variable "role_assignments" {
  type = map(object({
    mi_name      = string
    role         = string
    description  = string
    principal_id = string
  }))
  default     = {}
  description = "存储账户的角色分配列表"
}

修正角色分配资源,移除不必要的try:

resource "azurerm_role_assignment" "role_assignment" {
  for_each             = var.role_assignments
  description          = each.value.description
  role_definition_name = each.value.role
  principal_id         = each.value.principal_id
  scope                = azurerm_storage_account.storage_account.id
  # 跳过AAD检查,避免部分场景下的权限错误
  skip_service_principal_aad_check = true
}

关键说明

  • 确保tfvars中role_assignments的mi_name与UserManagedIdentity的键完全匹配,才能正确关联到对应的MSI。
  • 通过locals将静态变量(mi_name)与资源输出(principal_id)绑定,for_each使用的键来自静态变量,避免了隐式依赖问题。
  • 模块职责拆分清晰:托管标识模块负责创建并输出标识信息,存储账户模块负责资源创建与权限分配,根模块负责依赖关联。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 06:50:59