2. Azure资源管理器模板入门
ARM 模板(Azure Resource Manager Template)是 Azure 原生基础设施即代码的载体。它本质上是一份 JSON 文档,描述“要部署哪些资源、这些资源长什么样、它们之间有什么依赖”。与在门户里逐项点击不同,模板把基础设施变成可版本化、可评审、可重复执行的声明式文件。本节不追求覆盖 ARM 的全部语法,而是通过一个最小但完整的模板,把存储账户和虚拟网络一起部署出来,从而建立对模板结构、参数、变量与输出的第一手认识。
一、模板的骨架:五个顶层节点
一份 ARM 模板的顶层结构由固定节点组成,理解它们的分工比记住语法更重要:
- $schema:声明模板遵循的语法版本,决定部署引擎如何校验模板。
- contentVersion:模板自身的版本号,便于团队追踪变更。
- parameters:部署时由外部传入的值,是模板的“输入接口”。
- variables:模板内部派生的值,用于消除重复、拼接命名。
- resources:真正要创建或更新的资源清单,是模板的核心。
- outputs:部署完成后回传的值,是模板的“输出接口”。
参数与变量的区别常被混淆:参数是“每次部署可能不同、需要外部决定”的值,例如环境名、区域;变量是“由参数或常量推导出来、模板内部复用”的值,例如根据环境名拼出的存储账户名。把该外部决定的东西放进参数,把该内部推导的东西放进变量,模板才具备复用性。
二、参数设计:让模板可复用
先设计参数。存储账户名在 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 输入被改动或跨资源组重名,应检查变量定义而非手工改名。