使用 Terraform 在 Azure Kubernetes 服务 中创建应用程序网关入口控制器

文章使用以下 Terraform 和 Terraform 提供程序版本进行测试:

使用 Terraform 可以定义、预览和部署云基础结构。 使用 Terraform 时,请使用 HCL 语法来创建配置文件。 利用 HCL 语法,可指定 Azure 这样的云提供程序和构成云基础结构的元素。 创建配置文件后,请创建一个执行计划,利用该计划,可在部署基础结构更改之前先预览这些更改。 验证了更改后,请应用该执行计划以部署基础结构。

Azure Kubernetes 服务 (AKS) 管理托管的 Kubernetes 环境。 使用 AKS 可以快速轻松地部署和管理容器化应用程序,而无需具备容器业务流程方面的专业知识。 AKS 还可以消除操作和维护任务使应用程序脱机而造成的负担。 使用 AKS,可以按需执行预配、升级和缩放资源等任务。

应用程序网关入口控制器 (AGIC) 为 Kubernetes 服务提供各种功能。 这些功能包括反向代理、可配置的流量路由和 TLS 终止。 Kubernetes 入口资源用于为单个 Kubernetes 服务配置入口规则。 入口控制器允许单个 IP 地址将流量路由到 Kubernetes 群集中的多个服务。 上述所有功能由 Azure 应用程序网关提供,因此,应用程序网关是 Azure 上的 Kubernetes 的理想入口控制器。

在本文中,学习如何:

  • 使用 AKS 创建 Kubernetes 群集,应用程序网关作为入口控制器
  • 定义 Kubernetes 群集
  • 创建应用程序网关资源
  • 创建 Kubernetes 群集
  • 测试 Kubernetes 群集的可用性

备注

本文中的示例代码位于 Microsoft Terraform GitHub 存储库中。

1.配置环境

  • Azure 订阅:如果没有 Azure 订阅,请在开始之前创建一个免费帐户
  • Azure 服务主体:演示需要可以分配角色的服务主体。 如果已有可以分配角色的服务主体,则可以使用该服务主体。 如果需要创建服务主体,有两个选项:

    演示代码需要以下服务主体值:appId、、passworddisplayNametenant

  • 服务主体对象 ID:运行以下命令来获取服务主体的对象 ID:az ad sp list --display-name "<display_name>" --query "[].{\"Object ID\":objectId}" --output table

  • SSH 密钥对:使用以下文章之一:

  • 安装 HelmHelm 是 Kubernetes 包管理器。

  • 安装 GNU wget:确保在任何命令行上运行 wget 且没有任何参数即可访问 wget。 可以从 官方 GNU wget 网站安装 wget

2. 将 Azure 存储配置为存储 Terraform 状态

Terraform 在本地通过 terraform.tfstate 文件跟踪状态。 在单用户环境中,此模式非常合适。 但是,在更常见的多用户环境中,需要使用 Azure 存储来跟踪服务器上的状态。 本部分介绍如何检索所需的存储帐户信息和创建存储容器。 然后,Terraform 状态信息将存储到该容器中。

  1. 使用以下选项之一创建 Azure 存储帐户:

  2. 浏览到 Azure 门户

  3. 在“Azure 服务”下,选择“存储帐户” 。 (如果主页面上未显示“存储帐户”选项,请选择“更多服务”来找到此选项。 )

  4. “存储帐户 ”页上,选择 Terraform 将存储状态信息的存储帐户。

  5. “存储帐户 ”页上的左侧菜单中,在 “安全 + 网络 ”部分中,选择 “访问密钥”。

    存储帐户页具有用于获取访问密钥的菜单选项。

  6. “访问密钥 ”页上,选择“ 显示键 ”以显示键值。

    “访问密钥”页具有显示键值的选项。

  7. 找到页面上的 key1 ,并选择右侧的图标,将密钥值复制到剪贴板。

    使用方便的图标按钮可将密钥值复制到剪贴板。

  8. 在命令行提示符下,运行 az storage container create。 此命令在 Azure 存储帐户中创建容器。 将占位符替换为 Azure 存储帐户的相应值。

    az storage container create -n tfstate \
       --account-name <storage_account_name> \
       --account-key <storage_account_key>
    
  9. 命令成功完成后,会显示一个 JSON 块,其键为 “created” ,值为 true。 还可以运行 az storage container list 来验证容器是否已成功创建。

    az storage container list \
       --account-name <storage_account_name> \
       --account-key <storage_account_key>
    

3. 实现 Terraform 代码

  1. 创建用于测试和运行示例 Terraform 代码的目录,并将其设为当前目录。

  2. 创建名为 providers.tf 的文件并插入下列代码。

    terraform {
    
      required_version = ">=0.12"
    
      required_providers {
        azurerm = {
          source  = "hashicorp/azurerm"
          version = "~>2.0"
        }
      }
      backend "azurerm" {
        resource_group_name  = "<storage_account_resource_group>"
        storage_account_name = "<storage_account_name>"
        container_name       = "tfstate"
        key                  = "codelab.microsoft.tfstate"
      }
    }
    
    provider "azurerm" {
      features {}
    }
    

    要点:

    • 设置为 resource_group_name 存储帐户的资源组。
    • 设置为 storage_account_name 存储帐户名称。
  3. 创建名为 main.tf 的文件并插入下列代码:

    resource "random_pet" "rg-name" {
      prefix = var.resource_group_name_prefix
    }
    
    resource "azurerm_resource_group" "rg" {
      name     = random_pet.rg-name.id
      location = var.resource_group_location
    }
    
    # Locals block for hardcoded names
    locals {
      backend_address_pool_name      = "${azurerm_virtual_network.test.name}-beap"
      frontend_port_name             = "${azurerm_virtual_network.test.name}-feport"
      frontend_ip_configuration_name = "${azurerm_virtual_network.test.name}-feip"
      http_setting_name              = "${azurerm_virtual_network.test.name}-be-htst"
      listener_name                  = "${azurerm_virtual_network.test.name}-httplstn"
      request_routing_rule_name      = "${azurerm_virtual_network.test.name}-rqrt"
      app_gateway_subnet_name        = "appgwsubnet"
    }
    
    # User Assigned Identities 
    resource "azurerm_user_assigned_identity" "testIdentity" {
      resource_group_name = azurerm_resource_group.rg.name
      location            = azurerm_resource_group.rg.location
    
      name = "identity1"
    
      tags = var.tags
    }
    
    resource "azurerm_virtual_network" "test" {
      name                = var.virtual_network_name
      location            = azurerm_resource_group.rg.location
      resource_group_name = azurerm_resource_group.rg.name
      address_space       = [var.virtual_network_address_prefix]
    
      subnet {
        name           = var.aks_subnet_name
        address_prefix = var.aks_subnet_address_prefix
      }
    
      subnet {
        name           = "appgwsubnet"
        address_prefix = var.app_gateway_subnet_address_prefix
      }
    
      tags = var.tags
    }
    
    data "azurerm_subnet" "kubesubnet" {
      name                 = var.aks_subnet_name
      virtual_network_name = azurerm_virtual_network.test.name
      resource_group_name  = azurerm_resource_group.rg.name
      depends_on           = [azurerm_virtual_network.test]
    }
    
    data "azurerm_subnet" "appgwsubnet" {
      name                 = "appgwsubnet"
      virtual_network_name = azurerm_virtual_network.test.name
      resource_group_name  = azurerm_resource_group.rg.name
      depends_on           = [azurerm_virtual_network.test]
    }
    
    # Public Ip 
    resource "azurerm_public_ip" "test" {
      name                = "publicIp1"
      location            = azurerm_resource_group.rg.location
      resource_group_name = azurerm_resource_group.rg.name
      allocation_method   = "Static"
      sku                 = "Standard"
    
      tags = var.tags
    }
    
    resource "azurerm_application_gateway" "network" {
      name                = var.app_gateway_name
      resource_group_name = azurerm_resource_group.rg.name
      location            = azurerm_resource_group.rg.location
    
      sku {
        name     = var.app_gateway_sku
        tier     = "Standard_v2"
        capacity = 2
      }
    
      gateway_ip_configuration {
        name      = "appGatewayIpConfig"
        subnet_id = data.azurerm_subnet.appgwsubnet.id
      }
    
      frontend_port {
        name = local.frontend_port_name
        port = 80
      }
    
      frontend_port {
        name = "httpsPort"
        port = 443
      }
    
      frontend_ip_configuration {
        name                 = local.frontend_ip_configuration_name
        public_ip_address_id = azurerm_public_ip.test.id
      }
    
      backend_address_pool {
        name = local.backend_address_pool_name
      }
    
      backend_http_settings {
        name                  = local.http_setting_name
        cookie_based_affinity = "Disabled"
        port                  = 80
        protocol              = "Http"
        request_timeout       = 1
      }
    
      http_listener {
        name                           = local.listener_name
        frontend_ip_configuration_name = local.frontend_ip_configuration_name
        frontend_port_name             = local.frontend_port_name
        protocol                       = "Http"
      }
    
      request_routing_rule {
        name                       = local.request_routing_rule_name
        rule_type                  = "Basic"
        http_listener_name         = local.listener_name
        backend_address_pool_name  = local.backend_address_pool_name
        backend_http_settings_name = local.http_setting_name
      }
    
      tags = var.tags
    
      depends_on = [azurerm_virtual_network.test, azurerm_public_ip.test]
    }
    
    resource "azurerm_role_assignment" "ra1" {
      scope                = data.azurerm_subnet.kubesubnet.id
      role_definition_name = "Network Contributor"
      principal_id         = var.aks_service_principal_object_id
    
      depends_on = [azurerm_virtual_network.test]
    }
    
    resource "azurerm_role_assignment" "ra2" {
      scope                = azurerm_user_assigned_identity.testIdentity.id
      role_definition_name = "Managed Identity Operator"
      principal_id         = var.aks_service_principal_object_id
      depends_on           = [azurerm_user_assigned_identity.testIdentity]
    }
    
    resource "azurerm_role_assignment" "ra3" {
      scope                = azurerm_application_gateway.network.id
      role_definition_name = "Contributor"
      principal_id         = azurerm_user_assigned_identity.testIdentity.principal_id
      depends_on           = [azurerm_user_assigned_identity.testIdentity, azurerm_application_gateway.network]
    }
    
    resource "azurerm_role_assignment" "ra4" {
      scope                = azurerm_resource_group.rg.id
      role_definition_name = "Reader"
      principal_id         = azurerm_user_assigned_identity.testIdentity.principal_id
      depends_on           = [azurerm_user_assigned_identity.testIdentity, azurerm_application_gateway.network]
    }
    
    resource "azurerm_kubernetes_cluster" "k8s" {
      name       = var.aks_name
      location   = azurerm_resource_group.rg.location
      dns_prefix = var.aks_dns_prefix
    
      resource_group_name = azurerm_resource_group.rg.name
    
      http_application_routing_enabled = false
    
      linux_profile {
        admin_username = var.vm_user_name
    
        ssh_key {
          key_data = file(var.public_ssh_key_path)
        }
      }
    
      default_node_pool {
        name            = "agentpool"
        node_count      = var.aks_agent_count
        vm_size         = var.aks_agent_vm_size
        os_disk_size_gb = var.aks_agent_os_disk_size
        vnet_subnet_id  = data.azurerm_subnet.kubesubnet.id
      }
    
      service_principal {
        client_id     = var.aks_service_principal_app_id
        client_secret = var.aks_service_principal_client_secret
      }
    
      network_profile {
        network_plugin     = "azure"
        dns_service_ip     = var.aks_dns_service_ip
        docker_bridge_cidr = var.aks_docker_bridge_cidr
        service_cidr       = var.aks_service_cidr
      }
    
      role_based_access_control {
        enabled = var.aks_enable_rbac
      }
    
      depends_on = [azurerm_virtual_network.test, azurerm_application_gateway.network]
      tags       = var.tags
    }
    
  4. 创建名为 variables.tf 的文件并插入下列代码:

    variable "resource_group_name_prefix" {
      default     = "rg"
      description = "Prefix of the resource group name that's combined with a random ID so name is unique in your Azure subscription."
    }
    
    variable "resource_group_location" {
      default     = "eastus"
      description = "Location of the resource group."
    }
    
    variable "aks_service_principal_app_id" {
      description = "Application ID/Client ID  of the service principal. Used by AKS to manage AKS related resources on Azure like vms, subnets."
    }
    
    variable "aks_service_principal_client_secret" {
      description = "Secret of the service principal. Used by AKS to manage Azure."
    }
    
    variable "aks_service_principal_object_id" {
      description = "Object ID of the service principal."
    }
    
    variable "virtual_network_name" {
      description = "Virtual network name"
      default     = "aksVirtualNetwork"
    }
    
    variable "virtual_network_address_prefix" {
      description = "VNET address prefix"
      default     = "192.168.0.0/16"
    }
    
    variable "aks_subnet_name" {
      description = "Subnet Name."
      default     = "kubesubnet"
    }
    
    variable "aks_subnet_address_prefix" {
      description = "Subnet address prefix."
      default     = "192.168.0.0/24"
    }
    
    variable "app_gateway_subnet_address_prefix" {
      description = "Subnet server IP address."
      default     = "192.168.1.0/24"
    }
    
    variable "app_gateway_name" {
      description = "Name of the Application Gateway"
      default     = "ApplicationGateway1"
    }
    
    variable "app_gateway_sku" {
      description = "Name of the Application Gateway SKU"
      default     = "Standard_v2"
    }
    
    variable "app_gateway_tier" {
      description = "Tier of the Application Gateway tier"
      default     = "Standard_v2"
    }
    
    variable "aks_name" {
      description = "AKS cluster name"
      default     = "aks-cluster1"
    }
    variable "aks_dns_prefix" {
      description = "Optional DNS prefix to use with hosted Kubernetes API server FQDN."
      default     = "aks"
    }
    
    variable "aks_agent_os_disk_size" {
      description = "Disk size (in GB) to provision for each of the agent pool nodes. This value ranges from 0 to 1023. Specifying 0 applies the default disk size for that agentVMSize."
      default     = 40
    }
    
    variable "aks_agent_count" {
      description = "The number of agent nodes for the cluster."
      default     = 3
    }
    
    variable "aks_agent_vm_size" {
      description = "VM size"
      default     = "Standard_D3_v2"
    }
    
    variable "kubernetes_version" {
      description = "Kubernetes version"
      default     = "1.11.5"
    }
    
    variable "aks_service_cidr" {
      description = "CIDR notation IP range from which to assign service cluster IPs"
      default     = "10.0.0.0/16"
    }
    
    variable "aks_dns_service_ip" {
      description = "DNS server IP address"
      default     = "10.0.0.10"
    }
    
    variable "aks_docker_bridge_cidr" {
      description = "CIDR notation IP for Docker bridge."
      default     = "172.17.0.1/16"
    }
    
    variable "aks_enable_rbac" {
      description = "Enable RBAC on the AKS cluster. Defaults to false."
      default     = "false"
    }
    
    variable "vm_user_name" {
      description = "User name for the VM"
      default     = "vmuser1"
    }
    
    variable "public_ssh_key_path" {
      description = "Public key path for SSH."
      default     = "~/.ssh/id_rsa.pub"
    }
    
    variable "tags" {
      type = map(string)
    
      default = {
        source = "terraform"
      }
    }
    
  5. 创建名为 output.tf 的文件并插入下列代码。

    output "resource_group_name" {
      value = azurerm_resource_group.rg.name
    }
    
    output "client_key" {
      value = azurerm_kubernetes_cluster.k8s.kube_config.0.client_key
    }
    
    output "client_certificate" {
      value = azurerm_kubernetes_cluster.k8s.kube_config.0.client_certificate
    }
    
    output "cluster_ca_certificate" {
      value = azurerm_kubernetes_cluster.k8s.kube_config.0.cluster_ca_certificate
    }
    
    output "cluster_username" {
      value = azurerm_kubernetes_cluster.k8s.kube_config.0.username
    }
    
    output "cluster_password" {
      value = azurerm_kubernetes_cluster.k8s.kube_config.0.password
    }
    
    output "kube_config" {
      value     = azurerm_kubernetes_cluster.k8s.kube_config_raw
      sensitive = true
    }
    
    output "host" {
      value = azurerm_kubernetes_cluster.k8s.kube_config.0.host
    }
    
    output "identity_resource_id" {
      value = azurerm_user_assigned_identity.testIdentity.id
    }
    
    output "identity_client_id" {
      value = azurerm_user_assigned_identity.testIdentity.client_id
    }
    
    output "application_ip_address" {
      value = azurerm_public_ip.test.ip_address
    }
    
  6. 创建名为 terraform.tfvars 的文件并插入下列代码。

    aks_service_principal_app_id = "<service_principal_app_id>"
    
    aks_service_principal_client_secret = "<service_principal_password>"
    
    aks_service_principal_object_id = "<service_principal_object_id>"
    

    要点:

    • 设置为 aks_service_principal_app_id 服务主体 appId 值。
    • 设置为 aks_service_principal_client_secret 服务主体 password 值。
    • 设置为 aks_service_principal_object_id 服务主体对象 ID。 (用于获取此值的 Azure CLI 命令位于 “配置环境 ”部分。)

4. 初始化 Terraform

运行 terraform init,将 Terraform 部署进行初始化。 此命令下载管理 Azure 资源所需的 Azure 模块。

terraform init

5. 创建 Terraform 执行计划

运行 terraform plan 以创建执行计划。

terraform plan -out main.tfplan

要点:

  • terraform plan 命令将创建一个执行计划,但不会执行它。 它会确定创建配置文件中指定的配置需要执行哪些操作。 此模式允许你在对实际资源进行任何更改之前验证执行计划是否符合预期。
  • 使用可选 -out 参数可以为计划指定输出文件。 使用 -out 参数可以确保所查看的计划与所应用的计划完全一致。
  • 若要详细了解如何使执行计划和安全性持久化,请参阅安全警告一节

6. 应用 Terraform 执行计划

运行 terraform apply,将执行计划应用到云基础结构。

terraform apply main.tfplan

要点:

  • 上面的 terraform apply 命令假设之前运行了 terraform plan -out main.tfplan
  • 如果为 -out 参数指定了不同的文件名,请在对 terraform apply 的调用中使用该相同文件名。
  • 如果未使用参数 -out ,则调用 terraform apply 时不带任何参数。

7.验证结果:测试 Kubernetes 群集

可以使用 Kubernetes 工具来验证新建的群集。

  1. 若要获取 Kubernetes 配置并从 Azure 访问凭据,请运行 az aks get-credentials

    az aks get-credentials --name <aks_cluster_name>  \
       --resource-group <resource_group_name> \
       --overwrite-existing
    

    要点:

    • <aks_cluster_name>占位符替换为aks_namedefault文件) variables.tf (块的值。
    • <resource_group_name> 占位符替换为随机生成的资源组名称。 通过运行 echo "$(terraform output resource_group_name)"获取资源组名称。
  2. 验证群集的运行状况。

    kubectl get nodes
    

    要点:

    • 会显示工作器节点的详细信息,状态为“就绪”。

    使用 kubectl 工具可以验证 Kubernetes 群集的运行状况

8.安装 Azure AD Pod 标识

Azure Active Directory Pod Identity 提供对 Azure 资源管理器的基于令牌的访问。

Azure AD Pod Identity 会将以下组件添加到 Kubernetes 群集:

若要将 Azure AD Pod 标识安装到群集,需要知道是否启用或禁用 RBAC。 对于此演示,默认禁用 RBAC。 通过块default的值在variables.tf文件中aks_enable_rbac完成启用或禁用 RBAC。

  • 如果 启用了 RBAC,请运行以下命令:

    kubectl create -f https://raw.githubusercontent.com/Azure/aad-pod-identity/master/deploy/infra/deployment-rbac.yaml
    
  • 如果 禁用 RBAC,请运行以下命令:

    kubectl create -f https://raw.githubusercontent.com/Azure/aad-pod-identity/master/deploy/infra/deployment.yaml
    

9. 安装 Helm

使用 Helm 安装 application-gateway-kubernetes-ingress 包:

  1. 运行以下 helm 命令以添加 AGIC Helm 存储库。

    helm repo add application-gateway-kubernetes-ingress https://appgwingress.blob.core.windows.net/ingress-azure-helm-package/
    
  2. 更新 AGIC Helm 存储库。

    helm repo update
    

10. 安装 AGIC Helm 图表

  1. 下载 helm-config.yaml 以配置 AGIC。 (如果无权访问 wget,请参阅“ 配置环境 ”部分。)

    wget https://raw.githubusercontent.com/Azure/application-gateway-kubernetes-ingress/master/docs/examples/sample-helm-config.yaml -O helm-config.yaml
    
  2. 在文本编辑器中打开 helm-config.yaml 文件。

  3. 输入顶级键的值。

  4. 输入块的值 appgw

    • appgw.subscriptionId:指定用于创建应用网关的 Azure 订阅 ID。
    • appgw.resourceGroup:指定随机生成的资源组名称。 通过运行获取资源组名称 echo "$(terraform output resource_group_name)"
    • appgw.name:指定应用程序网关的名称。 此值通过app_gateway_namedefault的值在variables.tf文件中设置。
    • appgw.shared:此布尔标志默认为 false。 如果需要共享应用网关,请将其设置为true该网关。
  5. 输入块的值 kubernetes

    • kubernetes.watchNamespace:指定 AGIC 应监视的命名空间。 命名空间可以是单字符串值,也可以是逗号分隔的命名空间列表。 将此变量注释掉,或将其设置为空白或空字符串会导致入口控制器观察所有可访问的命名空间。
  6. 输入块的值 armAuth

    • 如果指定 armAuth.typeaadPodIdentity

      • armAuth.identityResourceID:通过运行 echo "$(terraform output identity_resource_id)"获取标识资源 ID。
      • armAuth.identityClientId:通过运行 echo "$(terraform output identity_client_id)"来获取标识客户端 ID。
    • 如果指定 armAuth.typeservicePrincipal,请参阅 使用服务主体

  7. 安装应用程序网关入口控制器包:

    helm install -f helm-config.yaml application-gateway-kubernetes-ingress/ingress-azure --generate-name
    
  8. 若要从标识中获取键值,可以运行 az identity show

    az identity show -g <resource_group_name> -n <identity_name>
    

    要点:

    • <resource_group_name> 占位符替换为随机生成的资源组名称。 通过运行 echo "$(terraform output resource_group_name)"获取资源组名称。
    • <identity_name> 占位符替换为此演示的标识名称。 标识名称默认在identity1main.tf文件中。
    • 给定订阅的所有标识都可以通过运行 az identity list

11.安装示例应用

安装应用网关、AKS 和 AGIC 后,安装示例应用。

  1. 使用 curl 命令下载 YAML 文件:

    curl https://raw.githubusercontent.com/Azure/application-gateway-kubernetes-ingress/master/docs/examples/aspnetapp.yaml -o aspnetapp.yaml
    
  2. 应用 YAML 文件:

    kubectl apply -f aspnetapp.yaml
    

12.验证结果:测试示例应用

  1. 运行以下 Terraform 命令以获取应用的 IP 地址。

    echo "$(terraform output application_ip_address)"
    
  2. 使用浏览器,转到上一步中指示的 IP 地址。

    运行示例应用。

13. 清理资源

删除应用网关、AKS 和 AGIC 资源

不再需要通过 Terraform 创建的资源时,请执行以下步骤:

  1. 运行 terraform plan 并指定 destroy 标志。

    terraform plan -destroy -out main.destroy.tfplan
    

    要点:

    • terraform plan 命令将创建一个执行计划,但不会执行它。 它会确定创建配置文件中指定的配置需要执行哪些操作。 此模式允许你在对实际资源进行任何更改之前验证执行计划是否符合预期。
    • 使用可选 -out 参数可以为计划指定输出文件。 使用 -out 参数可以确保所查看的计划与所应用的计划完全一致。
    • 若要详细了解如何使执行计划和安全性持久化,请参阅安全警告一节
  2. 运行 terraform apply 以应用执行计划。

    terraform apply main.destroy.tfplan
    

删除存储帐户

注意

仅删除包含此演示中使用的存储帐户的资源组(如果不对任何其他帐户使用)。

运行 az group delete 以删除此演示中使用的资源组 (及其存储帐户) 。

az group delete --name <storage_resource_group_name> --yes

要点:

  • storage_resource_group_name 占位符替换为 resource_group_name 文件中的值 providers.tf

删除服务主体

注意

仅当未将其用于任何其他内容时,才删除此演示中使用的服务主体。

az ad sp delete --id <service_principal_object_id>

Azure 上的 Terraform 故障排除

如果在角色分配期间应用 Terraform 执行计划时收到“403 错误”,则通常意味着服务主体角色不包括在 Azure RBAC 中分配角色的权限。 有关内置角色的详细信息,请参阅 Azure 内置角色。 通过以下选项可以解决错误:

  • 使用“所有者”角色创建服务主体。 建议的做法是,应授予执行给定作业所需的最低特权。 因此,仅当服务主体要用于该容量时,才使用“所有者”角色。
  • 根据所需的角色创建自定义角色,例如参与者。 根据你使用的基本角色,将操作添加到Microsoft.Authorization/*/WriteActions块或将其从NotActions块中删除。 有关自定义角色的详细信息,请参阅 Azure 自定义角色

排查在 Azure 上使用 Terraform 时遇到的常见问题

后续步骤