「Azure 基础设施实战」

用 Terraform 落地一套真实可用的 Azure 基础设施:资源组与 VNet 组合编排、VM 与托管磁盘、AKS 集群、RBAC 最小权限与成本治理,并给出常见 Azure 排障清单。

1. Azure Provider 配置与认证

一句话总结: azurerm Provider 通过 subscription_id、tenant_id 与认证链完成初始化,生产推荐用 Managed Identity 或 Service Principal,密钥从环境变量注入而非硬编码。

Azure 资源的逻辑容器是订阅(Subscription),订阅之下再按资源组(Resource Group)组织。动手前先把订阅、租户与 Provider 版本固定下来,后续所有资源都挂在这三层之上。

terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.0"
    }
  }
}

provider "azurerm" {
  features {}

  subscription_id = var.subscription_id
  tenant_id       = var.tenant_id
}

1.1 认证方式的取舍

方式配置途径适用场景
环境变量ARM_CLIENT_ID / ARM_CLIENT_SECRETCI 注入、本地开发
Service Principalclient_id + client_secret团队统一身份、跨租户
Managed Identity运行在 Azure 上的资源自动获取云端 Runner、AKS 内运行
Azure CLIaz login 后 SDK 自动读取单机日常开发

生产环境最推荐 Managed Identity + Service Principal 的组合:代码里不出现任何长期密钥,权限由 Azure RBAC 统一治理。用环境变量跑通第一步最简单。

export ARM_SUBSCRIPTION_ID="<subscription-id>"
export ARM_CLIENT_ID="<app-id>"
export ARM_CLIENT_SECRET="<secret>"
export ARM_TENANT_ID="<tenant-id>"
terraform plan

一句话:Provider 是「配置模板」,subscription 与 tenant 决定资源归属,认证链决定「谁在操作」。

2. 资源组与 VNet 网络设计

一句话总结: 资源组是逻辑边界,VNet 提供网络隔离,子网划分内网/外网,NSG 是子网与网卡级防火墙,组合时先规划地址空间再让资源通过引用建立依赖。

一套最小可用的 Web 架构,Azure 网络层的正确组合顺序是:资源组 → VNet → 子网 → NSG → 公共 IP → 网卡。

resource "azurerm_resource_group" "main" {
  name     = "rg-${var.environment}-${var.region_short}"
  location = var.location
  tags = {
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

resource "azurerm_virtual_network" "main" {
  name                = "vnet-${var.environment}"
  resource_group_name = azurerm_resource_group.main.name
  location            = azurerm_resource_group.main.location
  address_space       = ["10.0.0.0/16"]
}

resource "azurerm_subnet" "web" {
  name                 = "snet-web"
  resource_group_name  = azurerm_resource_group.main.name
  virtual_network_name = azurerm_virtual_network.main.name
  address_prefixes     = ["10.0.1.0/24"]
}

resource "azurerm_subnet" "db" {
  name                 = "snet-db"
  resource_group_name  = azurerm_resource_group.main.name
  virtual_network_name = azurerm_virtual_network.main.name
  address_prefixes     = ["10.0.2.0/24"]
}

2.1 网络安全组只放行必要端口

NSG 支持子网关联与网卡关联两级,遵循最小放行原则:Web 只开放 80/443,SSH 只对办公网段开放。

resource "azurerm_network_security_group" "web" {
  name                = "nsg-web"
  location            = azurerm_resource_group.main.location
  resource_group_name = azurerm_resource_group.main.name

  security_rule {
    name                       = "Allow-HTTP"
    priority                   = 100
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "80"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }
  security_rule {
    name                       = "Allow-SSH"
    priority                   = 110
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "22"
    source_address_prefixes    = var.office_cidrs
    destination_address_prefix = "*"
  }
}

resource "azurerm_subnet_network_security_group_association" "web" {
  subnet_id                 = azurerm_subnet.web.id
  network_security_group_id = azurerm_network_security_group.web.id
}

一句话:VNet 划好网段、子网分好用途、NSG 按需放行,地址空间在创建时就要预留充足,避免事后重划。

3. 计算 VM 与扩展脚本

一句话总结: Azure VM 由网卡、公共 IP、SSH 密钥与 custom_data 组合声明,镜像用 source_image_reference 指定,登录密钥优先用 SSH 公钥而非密码。

Azure 虚拟机通过 azurerm_linux_virtual_machine 声明,它依赖网卡与公共 IP,初始化逻辑写进 custom_data。

resource "azurerm_public_ip" "web" {
  name                = "pip-web-${var.environment}"
  location            = azurerm_resource_group.main.location
  resource_group_name = azurerm_resource_group.main.name
  allocation_method   = "Static"
  sku                 = "Standard"
}

resource "azurerm_network_interface" "web" {
  name                = "nic-web-${var.environment}"
  location            = azurerm_resource_group.main.location
  resource_group_name = azurerm_resource_group.main.name

  ip_configuration {
    name                          = "internal"
    subnet_id                     = azurerm_subnet.web.id
    private_ip_address_allocation = "Dynamic"
    public_ip_address_id          = azurerm_public_ip.web.id
  }
}

resource "azurerm_linux_virtual_machine" "web" {
  name                            = "vm-web-${var.environment}"
  location                        = azurerm_resource_group.main.location
  resource_group_name             = azurerm_resource_group.main.name
  size                            = "Standard_B2s"
  admin_username                  = "ubuntu"
  network_interface_ids           = [azurerm_network_interface.web.id]
  disable_password_authentication = true

  admin_ssh_key {
    username   = "ubuntu"
    public_key = file(var.ssh_public_key_path)
  }

  source_image_reference {
    publisher = "Canonical"
    offer     = "0001-com-ubuntu-server-jammy"
    sku       = "22_04-lts"
    version   = "latest"
  }

  os_disk {
    caching              = "ReadWrite"
    storage_account_type = "Standard_LRS"
  }

  custom_data = base64encode(<<-EOT
    #!/bin/bash
    apt-get update -y
    apt-get install -y nginx
    systemctl enable nginx
  EOT
  )
}

一句话:VM 编排的关键是「引用不硬编码」——网卡、IP、密钥都从上游资源取,删除重建时依赖图自动按序执行。

4. 存储账户与托管磁盘

一句话总结: 存储账户承载 Blob/文件/队列等对象服务,托管磁盘随 VM 生命周期管理,两者都要开启加密并用 access tier 与生命周期规则控制成本。

存储账户是 Azure 的对象存储底座,用于静态资源、备份与日志;托管磁盘是 VM 的系统/数据盘,加密默认启用。

resource "azurerm_storage_account" "static" {
  name                     = "st${var.environment}static"
  resource_group_name      = azurerm_resource_group.main.name
  location                 = azurerm_resource_group.main.location
  account_tier             = "Standard"
  account_replication_type = "GRS"
  min_tls_version          = "TLS1_2"
  allow_blob_public_access = false

  blob_properties {
    versioning_enabled = true
  }
}

resource "azurerm_storage_container" "assets" {
  name                  = "assets"
  storage_account_name  = azurerm_storage_account.static.name
  container_access_type = "private"
}

一句话:存储是「内容与 IaC 解耦」的地方——桶与容器的生命周期由 Terraform 管理,具体对象由应用或流水线写入。

5. AKS 集群编排

一句话总结: AKS 用 azurerm_kubernetes_cluster 声明控制面与节点池,身份用 system_assigned_identity,网络用 kubenet 或 Azure CNI,kubeconfig 通过 data source 安全获取。

AKS 是 Azure 的托管 Kubernetes,Terraform 只负责声明集群与节点池,控制面由微软托管。

resource "azurerm_kubernetes_cluster" "aks" {
  name                = "aks-${var.environment}"
  location            = azurerm_resource_group.main.location
  resource_group_name = azurerm_resource_group.main.name
  dns_prefix          = "aks${var.environment}"

  default_node_pool {
    name       = "default"
    node_count = 3
    vm_size    = "Standard_D2s_v3"
  }

  identity {
    type = "SystemAssigned"
  }

  network_profile {
    network_plugin = "azure"
    network_policy = "calico"
  }

  tags = { Environment = var.environment }
}

5.1 节点池与升级

工作负载差异大时拆成多个节点池,系统池跑平台组件,用户池跑业务,并设置 autoscaling 让节点数随负载伸缩。

resource "azurerm_kubernetes_cluster_node_pool" "user" {
  name                  = "user"
  kubernetes_cluster_id = azurerm_kubernetes_cluster.aks.id
  vm_size               = "Standard_D4s_v3"
  enable_auto_scaling   = true
  node_count_min        = 2
  node_count_max        = 8
  node_labels = {
    role = "workload"
  }
}

获取 kubeconfig 用 data source,并把内容标为敏感,避免输出到日志。

data "azurerm_kubernetes_cluster" "aks" {
  name                = azurerm_kubernetes_cluster.aks.name
  resource_group_name = azurerm_resource_group.main.name
}

output "kubeconfig" {
  value     = data.azurerm_kubernetes_cluster.aks.kube_config_raw
  sensitive = true
}

一句话:AKS 是「托管控制面 + 自管节点池」的组合,版本升级、网络插件、节点池隔离在创建时就要定好。

6. RBAC 与 IAM 最小权限

一句话总结: Azure 权限体系以 RBAC 为核心,用角色分配(role_assignment)把最小权限授予身份,先 Scope 到资源组或资源,再按需扩展。

RBAC 三要素是「谁(主体)→ 对什么(作用域)→ 能做什么(角色)」。Terraform 里分别对应 principal_id、scope 与 role_definition_name。

data "azurerm_client_config" "current" {}

resource "azurerm_role_assignment" "aks_storage" {
  scope                = azurerm_storage_account.static.id
  role_definition_name = "Storage Blob Data Reader"
  principal_id         = azurerm_kubernetes_cluster.aks.identity[0].principal_id
}

resource "azurerm_role_assignment" "ops_contributor" {
  scope                = azurerm_resource_group.main.id
  role_definition_name = "Contributor"
  principal_id         = data.azurerm_client_config.current.object_id
}

一句话:RBAC 最小权限用「角色 + 作用域 + 主体」三要素表达,先给最小动作集,审计发现越权再逐步收紧。

7. 成本治理与预算

一句话总结: 成本治理靠「标签打底 + 预算告警 + 按需关闭」三件套,Terraform 用 tags、budget 与 schedule 资源把成本策略固化成代码。

云成本失控多半是「忘了关」与「没人看」。用统一标签标记环境与成本中心,再挂预算告警,达到阈值自动通知。

resource "azurerm_consumption_budget_resource_group" "monthly" {
  name              = "budget-rg-${var.environment}"
  resource_group_id = azurerm_resource_group.main.id

  amount     = 5000
  time_grain = "Monthly"

  time_period {
    start_date = "2026-10-01T00:00:00Z"
  }

  notification {
    enabled        = true
    threshold      = 80
    operator       = "GreaterThan"
    contact_emails = [var.budget_owner_email]
  }
}

7.1 标签与资源规划

所有资源统一补 cost_center 与 environment 标签,云账单按标签聚合,成本归属一目了然。开发环境还可以定时关机。

locals {
  common_tags = {
    Environment = var.environment
    CostCenter  = var.cost_center
    ManagedBy   = "terraform"
  }
}

一句话:成本治理不是「事后看账单」,而是把预算、标签、关机策略写进 IaC,让平台在创建资源的那一刻就带上成本纪律。

8. 总结

一套真实可用的 Azure 基础设施,核心是把资源组、网络、计算、存储、容器、权限、成本七类资源按「引用组合」的方式组织起来:

环节要点
Providersubscription/tenant + 认证链 + features 块
资源组逻辑边界,统一标签承载成本归属
网络VNet/子网/NSG 引用组合,地址空间预留充足
计算VM + custom_data,SSH 公钥而非密码
存储存储账户 + 生命周期规则,默认加密
容器AKS + 节点池自动伸缩,kubeconfig 敏感输出
权限RBAC 角色分配,作用域限定到资源组/资源
成本标签 + 预算告警 + 定时关机固化成代码

一句话收尾:Azure 实战的关键不是「创建资源」,而是「编排资源之间的引用关系与权限边界」。把地址空间规划、RBAC 作用域、成本预算这三件事做对,这套模板就能在 dev/staging/prod 之间稳定复制。下一篇「GCP 基础设施实战」将把同一套思路搬到 Google Cloud,对照两种云的编排差异。

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「terraform」更多文章

  1. Helm Provider 与应用发布:值注入与回滚
  2. 模块注册表与分发:版本、文档与测试
  3. DNS 与证书编排:托管区域与自动验证