2. Azure资源管理器与基础设施即代码

Azure资源管理器(Azure Resource Manager,ARM)是Azure控制平面上的统一部署与管理层。无论你通过门户、CLI、PowerShell、SDK还是ARM模板发起操作,请求最终都会进入ARM,由它完成身份验证、授权、模板解析、依赖排序、资源提供程序调用和状态记录。理解ARM的工作方式,是掌握基础设施即代码(Infrastructure as Code,IaC)的前提:IaC的核心不是“把点击操作写成脚本”,而是把期望状态声明成可版本化、可审查、可重复执行的模板,由平台负责收敛到该状态。

ARM模板的骨架结构

一个ARM模板本质是一个JSON文档,顶层由若干固定段组成。最常用的段包括:$schema用于声明模板语言版本与部署范围;contentVersion是模板作者自定义的版本标记;parameters定义部署时需要外部传入的值;variables定义模板内部复用的表达式;resources是模板的核心,声明要创建或更新的资源;outputs用于把部署结果回传给调用方。下面是一个最小但完整的示例,用于部署一个存储账户:

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "storagePrefix": { "type": "string", "minLength": 3, "maxLength": 11 },
    "location": { "type": "string", "defaultValue": "[resourceGroup().location]" },
    "skuName": { "type": "string", "defaultValue": "Standard_LRS",
      "allowedValues": ["Standard_LRS", "Standard_GRS", "Premium_LRS"] }
  },
  "variables": {
    "storageName": "[concat(parameters('storagePrefix'), uniqueString(resourceGroup().id))]"
  },
  "resources": [
    {
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2023-01-01",
      "name": "[variables('storageName')]",
      "location": "[parameters('location')]",
      "sku": { "name": "[parameters('skuName')]" },
      "kind": "StorageV2",
      "properties": { "minimumTlsVersion": "TLS1_2", "allowBlobPublicAccess": false }
    }
  ],
  "outputs": {
    "storageId": { "type": "string", "value": "[resourceId('Microsoft.Storage/storageAccounts', variables('storageName'))]" }
  }
}

注意apiVersion不是可省略的装饰项。它决定ARM调用资源提供程序时使用的接口契约,不同版本支持的属性集合可能不同。生产模板应固定apiVersion,避免因隐式升级导致属性被忽略或校验失败。

参数与变量的分工

参数面向“每次部署可能不同的输入”,变量面向“模板内部推导出的常量或组合值”。判断标准很简单:如果这个值需要由流水线、运维人员或不同环境提供,就做成参数;如果它能由其他参数、资源组属性或函数计算得到,就做成变量。把可推导的值做成参数,会让调用方承担不必要的负担,也容易产生不一致。

参数应尽量加约束:allowedValues限制枚举,minLength/maxLength限制长度,defaultValue提供合理缺省。例如存储账户名必须全局唯一且只含小写字母和数字,长度3到24。示例中用uniqueString(resourceGroup().id)生成确定性哈希后缀,既保证同一资源组重复部署得到同名资源,又降低全局重名概率。这里storagePrefix限制为3到11位,加上13位哈希后缀,总长不超过24位。

ARM模板函数区分大小写,字符串比较默认不区分大小写,但资源类型和属性名必须严格按提供程序定义书写。调试时优先用az deployment group validate做静态校验,再执行实际部署。

部署模式:增量与完整

部署模式决定ARM如何处理资源组中“模板未声明”的资源。Incremental(增量)是默认模式:模板中声明的资源被创建或更新,未声明的资源保持原样。Complete(完整)模式则会把资源组内“不在模板中”的资源删除。完整模式适合把资源组视为一个整体单元、由单一模板完全拥有的场景;如果资源组里还有手工创建或其他流水线管理的资源,使用完整模式会造成误删。

需要特别说明:完整模式只作用于资源组范围内的资源,不会删除资源组本身,也不会删除资源组之外的对象。另一个容易混淆的点是“重新部署同一模板”并不等于“重建资源”:如果属性未变化,ARM通常返回无操作;如果属性变化,则执行更新。删除资源必须通过完整模式或显式删除操作完成。

资源依赖管理

ARM会根据模板中的引用关系自动推断依赖,例如通过reference()或resourceId()引用另一资源时,ARM会建立隐式依赖。但有些依赖无法从属性引用中推断,例如“先创建网络安全组,再把子网关联到该NSG”,或“先建密钥保管库,再让应用服务读取密钥”。这类情况必须用dependsOn显式声明。

依赖关系本质上是一个有向无环图。ARM按拓扑顺序并行部署无依赖分支,遇到依赖则等待前置资源成功。若图中出现环,模板校验阶段就会失败。因此,dependsOn应只声明真实存在的前置条件,滥用会导致部署串行化、耗时增加,也会掩盖本应通过引用自动推断的关系。

ARM模板部署:依赖图与执行顺序 parameters storagePrefix / location / skuName variables storageName = concat + uniqueString NSG Microsoft.Network/networkSecurityGroups VNet + Subnet 子网关联NSG(显式dependsOn) Storage Account name = variables('storageName') dependsOn 引用 引用 Key Vault Microsoft.KeyVault/vaults App Service 读取KV密钥(显式dependsOn) dependsOn 执行顺序(拓扑排序) 第1层:NSG、Key Vault(无前置依赖,可并行) 第2层:VNet+Subnet、App Service(等待各自前置资源成功) 第3层:Storage Account(依赖variables解析,无跨资源依赖,可与第1层并行) 实线蓝箭头 = 隐式依赖(属性引用自动推断) | 橙色箭头 = 显式依赖(dependsOn声明)

部署范围与作用域

ARM模板可以在不同作用域部署:资源组、订阅、管理组和租户。资源组作用域最常用,模板中resources直接写资源即可。订阅作用域常用于创建资源组本身、策略分配或角色分配,此时资源类型需要写成Microsoft.Resources/resourceGroups。跨作用域部署可通过嵌套部署或Microsoft.Resources/deployments实现,但要注意:嵌套部署中的resourceGroup()等函数返回的是嵌套部署所在作用域,而不是外层作用域,写表达式时必须明确当前上下文。

可执行检查清单

在把模板提交到流水线之前,建议按以下清单逐项确认,这些检查项直接对应本节的核心知识点:

检查项判断依据常见问题
apiVersion是否固定每个资源类型都显式写出apiVersion省略或使用latest导致属性被忽略
参数是否有约束枚举用allowedValues,长度用min/maxLength调用方传入非法值,部署中途失败
变量是否可推导变量值来自参数、函数或资源属性把可推导值做成参数,调用方负担重
部署模式是否匹配资源组是否由该模板完全拥有误用Complete模式删除手工资源
依赖是否完整无法从引用推断的前置条件已写dependsOnNSG与子网关联顺序错误导致部署失败
依赖是否最小dependsOn只包含真实前置资源滥用导致串行化,部署时间变长
输出是否可用outputs返回resourceId或连接信息下游流水线无法获取资源标识

把上述检查固化为流水线中的validate步骤,可以在真正消耗资源之前拦截大部分模板错误。ARM模板的价值不在于“写一次”,而在于“每次部署都得到可预期的结果”——参数约束保证输入可控,变量保证推导一致,部署模式保证边界清晰,依赖管理保证顺序正确。这四点共同构成了基础设施即代码在Azure上的基本工程约束。