如何在Azure中通过Terraform以Contributor角色部署Windows虚拟机
问题与解决方案
问题概述
使用Terraform 1.10.5部署Windows虚拟机,已获取client_id、subscription_id等信息,能通过data块引用现有资源组和网络,但不清楚如何通过指定的client_id(对应Service Principal)的Contributor角色权限完成部署,卡在角色应用环节。
核心解决方案
要让Terraform使用指定的Service Principal(对应client_id)的Contributor权限部署资源,需完成两个关键步骤:
- 确保该Service Principal已被授予目标范围(订阅或资源组)的Contributor角色(可提前通过Azure CLI/Portal配置,或用Terraform自动配置)
- 在Terraform的Azure Provider中配置该Service Principal的认证信息,让Terraform以其身份执行部署
步骤1:修正Provider配置与变量
先修正变量拼写错误,并在Provider中添加Service Principal认证信息:
修改后的variables.tf
variable "subscription_id" { default = "" description = "订阅ID" } variable "tenant_id" { # 修正拼写错误:原tenet_id改为tenant_id default = "" description = "租户ID" } variable "client_id" { # 添加client_id变量 default = "" description = "Service Principal的Client ID" } variable "client_secret" { # 添加client_secret变量 default = "" description = "Service Principal的Client Secret" sensitive = true } variable "tags" { default = "test" description = "资源标签" } variable "vm_count" { type = number # 修正类型:原字符串改为数字 default = 1 description = "部署虚拟机数量" } variable "resource_group_name" { default = "rg" description = "资源组名称" } variable "resource_vnet" { default = "vnet" description = "虚拟网络名称" } variable "resource_subnet" { default = "snet" description = "子网名称" } variable "prefix_id" { type = string default = "vm" description = "资源名称前缀" } variable "prefix_id_nsg" { type = string default = "vm-nsg" description = "NSG名称前缀" } variable "prefix_id_vms" { type = string default = "vm-00" description = "虚拟机名称前缀" }
修改后的provider.tf
terraform { required_version = ">=1.10.5" # 指定使用的Terraform版本 required_providers { azurerm = { source = "hashicorp/azurerm" version = "~>3.0" } random = { source = "hashicorp/random" version = "~>3.0" } } } provider "azurerm" { features {} subscription_id = var.subscription_id tenant_id = var.tenant_id client_id = var.client_id client_secret = var.client_secret # 可选:如果不需要自动注册资源提供商,保留此配置 resource_provider_registrations = "none" }
步骤2:修正main.tf中的错误
原main.tf中存在子网ID引用错误,同时可添加可选的角色分配资源(如果需要Terraform自动给SP分配Contributor角色):
修改后的main.tf
# 生成随机管理员密码 resource "random_password" "password" { length = 12 # 建议加长密码长度,符合Azure密码要求 min_lower = 1 min_upper = 1 min_numeric = 1 min_special = 1 special = true } # 引用Contributor角色定义 data "azurerm_role_definition" "contributor" { name = "Contributor" } # 引用现有资源组 data "azurerm_resource_group" "rg" { name = var.resource_group_name } # 引用现有虚拟网络 data "azurerm_virtual_network" "my_terraform_network" { name = var.resource_vnet resource_group_name = data.azurerm_resource_group.rg.name } # 引用现有子网 data "azurerm_subnet" "my_terraform_subnet" { name = var.resource_subnet resource_group_name = data.azurerm_resource_group.rg.name virtual_network_name = data.azurerm_virtual_network.my_terraform_network.name } # 可选:给Service Principal分配资源组级别的Contributor角色 # 注意:执行此代码的账号需要有资源组的Owner或User Access Administrator权限 resource "azurerm_role_assignment" "sp_contributor" { scope = data.azurerm_resource_group.rg.id role_definition_id = data.azurerm_role_definition.contributor.id principal_id = data.azurerm_client_config.current.object_id } # 获取当前Provider的Client配置(用于角色分配) data "azurerm_client_config" "current" {} # 创建网络安全组 resource "azurerm_network_security_group" "my_terraform_nsg" { name = var.prefix_id_nsg location = data.azurerm_resource_group.rg.location resource_group_name = data.azurerm_resource_group.rg.name security_rule { name = "RDP" priority = 1000 direction = "Inbound" access = "Allow" protocol = "*" source_port_range = "*" destination_port_range = "3389" source_address_prefix = "*" destination_address_prefix = "*" } security_rule { name = "web" priority = 1001 direction = "Inbound" access = "Allow" protocol = "Tcp" source_port_range = "*" destination_port_range = "80" source_address_prefix = "*" destination_address_prefix = "*" } } # 创建网络接口 resource "azurerm_network_interface" "my_terraform_nic" { count = var.vm_count name = "${var.prefix_id}-${count.index + 1}-nic" location = data.azurerm_resource_group.rg.location resource_group_name = data.azurerm_resource_group.rg.name ip_configuration { name = "ipconfig" subnet_id = data.azurerm_subnet.my_terraform_subnet.id # 修正:引用子网ID而非名称 private_ip_address_allocation = "Dynamic" } } # 关联NSG到网络接口 resource "azurerm_network_interface_security_group_association" "my_terraform_nic" { count = var.vm_count network_interface_id = azurerm_network_interface.my_terraform_nic[count.index].id network_security_group_id = azurerm_network_security_group.my_terraform_nsg.id } # 创建Windows虚拟机 resource "azurerm_windows_virtual_machine" "main" { count = var.vm_count name = "${var.prefix_id_vms}${count.index + 1}" admin_username = "test" admin_password = random_password.password.result location = data.azurerm_resource_group.rg.location resource_group_name = data.azurerm_resource_group.rg.name network_interface_ids = [azurerm_network_interface.my_terraform_nic[count.index].id] size = "Standard_DS1_v2" os_disk { name = "${var.prefix_id_vms}${count.index + 1}-osdisk" caching = "ReadWrite" storage_account_type = "Standard_LRS" } source_image_reference { publisher = "MicrosoftWindowsServer" offer = "WindowsServer" sku = "2022-datacenter-azure-edition" version = "latest" } tags = { Environment = var.tags # 修正标签格式为键值对 } } output "admin_pass" { sensitive = true value = azurerm_windows_virtual_machine.main[*].admin_password } output "vm_ids" { value = azurerm_windows_virtual_machine.main[*].id } output "private_ips" { value = azurerm_network_interface.my_terraform_nic[*].private_ip_address }
关键说明
角色授予方式:
- 方式一:提前通过Azure CLI执行以下命令给Service Principal分配资源组Contributor角色:
az role assignment create --assignee <client_id> --role "Contributor" --resource-group <resource_group_name> - 方式二:使用上述main.tf中的
azurerm_role_assignment资源自动分配,但执行Terraform的账号必须具备该资源组的Owner或User Access Administrator权限。
- 方式一:提前通过Azure CLI执行以下命令给Service Principal分配资源组Contributor角色:
认证方式:
- 除了在Provider中配置client_id/client_secret,也可通过环境变量传递:
export ARM_SUBSCRIPTION_ID="<subscription_id>" export ARM_TENANT_ID="<tenant_id>" export ARM_CLIENT_ID="<client_id>" export ARM_CLIENT_SECRET="<client_secret>"
- 除了在Provider中配置client_id/client_secret,也可通过环境变量传递:
密码要求:Azure虚拟机管理员密码需满足复杂度要求,建议将
random_password的length设置为12以上。
内容的提问来源于stack exchange,提问作者Mr. E
相关产品推荐
相关产品推荐

