2. Azure资源管理器模板入门

ARM 模板(Azure Resource Manager Template)是 Azure 原生基础设施即代码的载体。它本质上是一份 JSON 文档,描述“要部署哪些资源、这些资源长什么样、它们之间有什么依赖”。与在门户里逐项点击不同,模板把基础设施变成可版本化、可评审、可重复执行的声明式文件。本节不追求覆盖 ARM 的全部语法,而是通过一个最小但完整的模板,把存储账户和虚拟网络一起部署出来,从而建立对模板结构、参数、变量与输出的第一手认识。

一、模板的骨架:五个顶层节点

一份 ARM 模板的顶层结构由固定节点组成,理解它们的分工比记住语法更重要:

  • $schema:声明模板遵循的语法版本,决定部署引擎如何校验模板。
  • contentVersion:模板自身的版本号,便于团队追踪变更。
  • parameters:部署时由外部传入的值,是模板的“输入接口”。
  • variables:模板内部派生的值,用于消除重复、拼接命名。
  • resources:真正要创建或更新的资源清单,是模板的核心。
  • outputs:部署完成后回传的值,是模板的“输出接口”。

参数与变量的区别常被混淆:参数是“每次部署可能不同、需要外部决定”的值,例如环境名、区域;变量是“由参数或常量推导出来、模板内部复用”的值,例如根据环境名拼出的存储账户名。把该外部决定的东西放进参数,把该内部推导的东西放进变量,模板才具备复用性。

ARM 模板结构与部署数据流 部署时输入 environment location vnetAddressPrefix parameters 类型 / 默认值 allowedValues minLength 校验 variables storageName 拼接 subnetName 唯一后缀 resources(部署核心) Microsoft.Storage storageAccounts SKU / kind / 名称 Microsoft.Network virtualNetworks 地址空间 / 子网 outputs storageAccountName · vnetId · subnetId dependsOn 控制创建顺序

二、参数设计:让模板可复用

先设计参数。存储账户名在 Azure 中必须全局唯一、仅含小写字母和数字、长度 3–24 位,因此不能把完整名字写死,而应把“环境标识”作为参数,由模板拼接出唯一名称。虚拟网络的地址空间则适合作为带默认值的参数,便于不同环境覆盖。

"parameters": {
  "environment": {
    "type": "string",
    "defaultValue": "dev",
    "allowedValues": ["dev", "test", "prod"],
    "metadata": { "description": "环境标识,用于生成资源名称" }
  },
  "location": {
    "type": "string",
    "defaultValue": "[resourceGroup().location]"
  },
  "vnetAddressPrefix": {
    "type": "string",
    "defaultValue": "10.10.0.0/16"
  }
}

这里 [resourceGroup().location] 是 ARM 模板函数,表示“取当前资源组所在区域”。把区域默认值绑定到资源组,可以避免部署时忘记传区域导致资源落到意外位置。allowedValues 则把 environment 限制在三个合法值内,部署时传错会直接失败,而不是生成一个命名混乱的资源。

三、变量:消除重复与拼接命名

变量在模板内部计算,不对外暴露。存储账户名需要全局唯一,常用做法是“前缀 + 环境 + 短随机后缀”。ARM 提供 uniqueString() 函数,它基于传入字符串生成确定性的 13 位哈希,同一组输入永远得到同一结果,因此重复部署不会改名。

"variables": {
  "storageName": "[concat('st', parameters('environment'), uniqueString(resourceGroup().id))]",
  "vnetName": "[concat('vnet-', parameters('environment'))]",
  "subnetName": "snet-app"
}

注意 uniqueString(resourceGroup().id) 的输入是资源组 ID,因此同一资源组内该后缀稳定;换资源组则后缀变化,天然避免跨资源组重名。存储账户名最终形如 stdevxxxxxxxxxxxxx,长度可控且符合命名规则。

四、resources:两个资源的声明与依赖

resources 数组里每个元素至少包含 type、apiVersion、name、location 和 properties。存储账户与虚拟网络之间没有强依赖,可以并行创建;但虚拟网络内部的子网是 virtualNetworks 的子资源,通过 properties.subnets 内联声明即可,无需单独 dependsOn。

"resources": [
  {
    "type": "Microsoft.Storage/storageAccounts",
    "apiVersion": "2023-01-01",
    "name": "[variables('storageName')]",
    "location": "[parameters('location')]",
    "sku": { "name": "Standard_LRS" },
    "kind": "StorageV2",
    "properties": {
      "accessTier": "Hot",
      "minimumTlsVersion": "TLS1_2",
      "allowBlobPublicAccess": false
    }
  },
  {
    "type": "Microsoft.Network/virtualNetworks",
    "apiVersion": "2023-04-01",
    "name": "[variables('vnetName')]",
    "location": "[parameters('location')]",
    "properties": {
      "addressSpace": {
        "addressPrefixes": [ "[parameters('vnetAddressPrefix')]" ]
      },
      "subnets": [
        {
          "name": "[variables('subnetName')]",
          "properties": {
            "addressPrefix": "10.10.1.0/24"
          }
        }
      ]
    }
  }
]

几个值得注意的工程细节:allowBlobPublicAccess: false 关闭匿名公共访问,是存储账户的安全基线;minimumTlsVersion: TLS1_2 拒绝低版本 TLS;子网前缀 10.10.1.0/24 必须落在 10.10.0.0/16 之内,否则部署会报地址空间不匹配。这些不是可选项,而是模板应该固化的默认安全姿态。

五、outputs:把部署结果交出去

部署完成后,后续脚本或流水线往往需要知道存储账户名、虚拟网络 ID 和子网 ID。outputs 就是模板的返回值,可用 reference() 或 resourceId() 取得运行时属性。

"outputs": {
  "storageAccountName": {
    "type": "string",
    "value": "[variables('storageName')]"
  },
  "vnetId": {
    "type": "string",
    "value": "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]"
  },
  "subnetId": {
    "type": "string",
    "value": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('vnetName'), variables('subnetName'))]"
  }
}

六、部署与结果判读

把上述片段合并保存为 azuredeploy.json,用 Azure CLI 部署到已有资源组:

az deployment group create \
  --resource-group rg-iac-demo \
  --template-file azuredeploy.json \
  --parameters environment=dev vnetAddressPrefix=10.20.0.0/16

命令返回的 outputs 段会直接给出三个值。判断部署是否成功,看三点:其一,provisioningState 为 Succeeded;其二,outputs 中的 storageAccountName 符合 stdev 前缀加 13 位后缀的形态;其三,在门户或 az network vnet subnet list 中确认子网地址前缀为 10.20.1.0/24,说明参数覆盖生效。若报 StorageAccountAlreadyTaken,说明 uniqueString 输入被改动或跨资源组重名,应检查变量定义而非手工改名。

参数负责“外部可变”,变量负责“内部推导”,outputs 负责“结果外传”。三者边界清晰,模板才既安全又可复用。首次编写时,建议先用最小资源跑通部署,再逐步加入诊断设置、NSG、标签等治理要素。