3. Bicep语言基础

ARM 模板(JSON)能描述完整的 Azure 资源拓扑,但它的书写体验长期被诟病:大量花括号、字符串拼接、concat() 函数、dependsOn 手工维护,以及无法在编辑器中获得可靠的类型提示。Bicep 是 ARM 模板的领域特定语言(DSL),它最终会编译成标准 ARM JSON,因此部署时走的仍是 Azure Resource Manager 的同一条通道,但源码层面被大幅简化。理解 Bicep 的关键,不是把它当成一门新语言去背语法,而是把它看作“ARM 模板的可读写法”:声明式语义不变,表达方式变短、变清晰、可静态检查。

从 ARM 到 Bicep:同一份部署,两种写法

先看一个最小对比。ARM 中声明一个存储账户,需要 $schema、contentVersion、resources 数组、type/apiVersion/name/location/sku/kind 等字段,引用参数要写 [parameters('storageName')]。Bicep 中同样的资源写成:

param location string = resourceGroup().location
param storageName string
param skuName string = 'Standard_LRS'

resource stg 'Microsoft.Storage/storageAccounts@2023-01-01' = {
  name: storageName
  location: location
  sku: {
    name: skuName
  }
  kind: 'StorageV2'
  properties: {
    minimumTlsVersion: 'TLS1_2'
    allowBlobPublicAccess: false
  }
}

output storageId string = stg.id

可以看到几个直接收益:resource 关键字后跟符号名 stg,后续用 stg.id 即可引用属性,无需 reference();参数类型写在名字后面,默认值用 =;字符串插值用 '${var}' 而不是 concat();dependsOn 在多数情况下由编译器根据符号引用自动推导。编译后生成的 JSON 与手写 ARM 等价,但源码可读性和可维护性完全不同。

参数、变量与类型约束

Bicep 的参数(param)用于接收部署时输入,变量(var)用于在模板内部复用表达式。参数应尽量带类型和约束,让错误在编译期暴露,而不是等到部署失败。常用装饰器包括:

  • @allowed([...]):限定取值集合,例如环境名只允许 dev/test/prod。
  • @minLength / @maxLength:约束字符串长度,存储账户名 3–24 字符。
  • @minValue / @maxValue:约束数值范围,如副本数 1–10。
  • @secure():标记敏感参数,部署日志中不回显,适合管理员密码、连接字符串。
  • @description('...'):为参数提供说明,生成文档和门户表单时可见。

变量则适合承载“由参数推导出的中间结果”,例如根据环境拼接命名前缀:

@allowed(['dev', 'test', 'prod'])
param env string

var namePrefix = 'app-${env}'
var isProd = env == 'prod'
var storageName = 'st${replace(namePrefix, '-', '')}${uniqueString(resourceGroup().id)}'

这里 uniqueString() 保证全局唯一,replace() 去掉连字符以满足存储账户命名限制。变量不会出现在部署参数中,只在编译期展开,因此适合放这类派生逻辑。

循环与条件:批量与可选资源

Bicep 用 for 表达式表达循环,用三元表达式或 if 条件表达可选资源。循环常见于按数组批量创建资源,例如为多个区域各建一个存储账户:

param locations array = [
  'eastasia'
  'southeastasia'
]

resource stg 'Microsoft.Storage/storageAccounts@2023-01-01' = [for loc in locations: {
  name: 'st${replace(loc, '-', '')}${uniqueString(resourceGroup().id)}'
  location: loc
  sku: { name: 'Standard_LRS' }
  kind: 'StorageV2'
}]

output stgNames array = [for (loc, i) in locations: stg[i].name]

注意 stg[i] 的索引访问:循环资源会生成一个数组,按声明顺序对应。条件资源则写成 resource x '...' = if (condition) { ... },例如只在生产环境创建诊断设置。循环与条件可以组合,但要注意:条件为 false 时该资源不会出现在部署中,引用它的 output 需要用 ? 安全访问或加判断,否则编译报错。

Bicep CLI:编译、验证与部署

Bicep 源码不能直接提交给 ARM,必须先编译为 JSON。常用命令如下:

# 安装(Windows 可用 winget,跨平台可用 az bicep install)
az bicep install

# 编译为 ARM JSON
bicep build main.bicep --outfile main.json

# 仅做类型与语法检查,不产出文件
bicep build main.bicep --stdout > /dev/null

# 查看将生成的资源变更(What-If)
az deployment group what-if \
  --resource-group rg-iac-demo \
  --template-file main.bicep \
  --parameters env=dev

# 直接部署 Bicep(CLI 会自动编译)
az deployment group create \
  --resource-group rg-iac-demo \
  --template-file main.bicep \
  --parameters env=dev storageName=stiacdemo001

实际工作中,bicep build 应纳入 CI 的第一道关卡:语法错误、类型不匹配、未定义符号都会在此阶段失败,避免把问题带到部署环节。az deployment group what-if 则用于预览变更,它会列出将创建、修改、删除的资源,是评审 IaC 变更的核心手段。部署时若直接传 --template-file main.bicep,Azure CLI 会调用 Bicep CLI 自动编译,但生产流水线更推荐显式 bicep build 后再部署 JSON,便于审计和缓存。

Bicep 编译与部署链路 源码 → 编译 → 预览 → 部署 → 结果 main.bicep param env string var prefix = 'app-${env}' resource stg '...' = { name: prefix } bicep build main.json (ARM) "$schema": "...deploymentTemplate" "parameters": { "env": {...} } "resources": [ { "type": "Microsoft.Storage/..." } ] "outputs": { ... } what-if 变更预览 + 创建 stg ~ 修改 appService - 删除旧 plan 评审通过后部署 deploy ARM 引擎 资源提供程序 依赖排序 幂等执行 返回 outputs Bicep 语法要素与 ARM 对应关系 param / @secure() → parameters 段 var / 字符串插值 → variables 段 for / if 条件 → copy / condition 符号引用 stg.id → reference / dependsOn 编译期检查:类型不匹配、未定义符号、循环索引越界会在 bicep build 阶段失败; 部署期检查:配额、命名冲突、权限不足由 ARM 在 what-if / create 阶段返回。

编译期与部署期的分工

把上面的链路拆开看,Bicep 的价值集中在“编译期”。bicep build 会做符号解析、类型检查、循环展开和依赖推导,任何拼写错误、类型不匹配、循环索引越界都会在这一步失败,而不是等到 ARM 返回一个含糊的 InvalidTemplate。部署期则由 ARM 负责资源提供程序调用、依赖排序、幂等执行和最终状态收敛。两者边界清晰:能在编译期发现的,不要留到部署期。

一个常见的实践是:在 CI 中先跑 bicep build 生成 JSON 并归档,再对 JSON 执行 what-if,最后才 create。这样每次变更都有可审计的编译产物,评审时看的是 JSON 差异和 what-if 输出,而不是直接看 Bicep 源码猜测行为。对于多环境,参数文件(.bicepparam 或 JSON 参数文件)与模板分离,同一份 main.bicep 通过不同参数部署到 dev/test/prod,避免复制模板。

Bicep 的 uniqueString() 结果由输入决定,同一资源组 ID 在同一订阅下稳定,因此适合生成全局唯一名称;但它不是随机数,不要用它生成密码或密钥,敏感值应使用 @secure() 参数或 Key Vault 引用。

小结与检查要点