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

如何在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
}

关键说明

  1. 角色授予方式:

    • 方式一:提前通过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权限。
  2. 认证方式:

    • 除了在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>"
      
  3. 密码要求:Azure虚拟机管理员密码需满足复杂度要求,建议将random_password的length设置为12以上。

内容的提问来源于stack exchange,提问作者Mr. E

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 07:54:57