使用参数和变量增加灵活性

已完成

模板的功能非常强大,因为它们具有可重用性。 你可以使用 Bicep 编写模板来部署多个环境或资源副本。

你所在的玩具公司会定期发布新产品,你需要使用 Bicep 模板创建每个产品发布所需的 Azure 资源。 需要避免使用固定资源名称。 许多类型的 Azure 资源都需要唯一的名称,因此,在模板中嵌入名称意味着无法重用模板来发布多个产品。 此外,你还必须根据发布玩具的位置在不同位置部署资源,这意味着你也不能将资源位置嵌入到模板中。

在本单元中,你将了解参数和变量,它们是两个 Bicep 功能,可以为模板增加灵活性,且可重复使用。 你还会了解表达式。

注意

本单元中显示的命令用于说明概念。 请暂时不要运行这些命令。 稍后你将练习在此处学到的知识。

参数和变量

使用参数,你可以从模板文件外部引入值。 例如,如果你使用 Azure CLI 或 Azure PowerShell 手动部署模板,系统会要求你为每个参数提供值。 你还可创建参数文件,其中列出了要用于部署的所有参数和值。 如果模板是从自动化过程(如部署管道)部署的,则管道可以提供参数值。

变量是在模板中定义和设置的。 使用变量,可以将重要信息存储在一个位置,并在整个模板中对其进行引用,而无需进行复制和粘贴。

对于将在每个部署之间更改的内容,通常情况下最好使用参数,例如:

  • 需要保持唯一的资源名称。
  • 将资源部署到的位置。
  • 影响资源定价的设置,如其 SKU、定价层和实例计数。
  • 访问未在模板中定义的其他系统所需的凭据和信息。

当你将为每个部署使用相同的值,但想让一个值在模板中可重用,或者当你想要使用表达式来创建一个复杂值时,变量通常是不错的选择。 此外,还可将变量用于不需要唯一名称的资源。

提示

对于参数和变量,使用良好的命名是非常重要的,这样可以使模板易于阅读和理解。 请确保你使用的是清晰一致的描述性名称。

添加参数

在 Bicep 中,可以定义如下参数:

param appServiceAppName string

我们来看看此定义每个部分的作用:

  • param 告诉 Bicep 你正在定义参数。
  • appServiceAppName 是参数的名称。 如果你手动部署模板,系统可能会要求输入一个值,因此名称清晰易懂是很重要的。 名称也是在模板中引用参数值的方式,就像资源符号名称一样。
  • string 是参数的类型。 你可以为 Bicep 参数指定多种不同类型,包括 string(针对文本)、int(针对数字)以及 bool(针对布尔值 true 或 false 值)。 还可以通过使用 arrayobject 类型传入更复杂的参数。

提示

尽量不要使用太多参数来通用化模板。 应使用业务场景所需的最少参数量。 请注意,如果你的需求发生变化,将来可以随时更改模板。

提供默认值

你可以选择性地为参数提供默认值。 指定默认值时,参数变为可选。 部署模板的人员可以根据需要指定值,但如果他们不需要,Bicep 将使用默认值。

下面介绍如何添加默认值:

param appServiceAppName string = 'toy-product-launch-1'

备注

在此示例中,Azure 应用服务应用名称具有一个硬编码的默认值。 这并不是理想状态,因为应用服务应用需要唯一的名称。 稍后你将解决此问题。

在模板中使用参数值

当你声明参数后,可以在模板的其余部分引用它。 我们看看如何在资源定义中使用你的新参数:

resource appServiceApp 'Microsoft.Web/sites@2022-03-01' = {
  name: appServiceAppName
  location: 'eastus'
  properties: {
    serverFarmId: appServicePlan.id
    httpsOnly: true
  }
}

请注意,该模板现在使用参数值(而不是硬编码值)设置应用资源的资源名称。

提示

Visual Studio Code 的 Bicep 扩展显示了可视指示器,告诉你是否未遵循建议的做法。 例如,如果你定义了一个不使用的参数,则系统会发出警告。 Bicep Linter 会在你工作的时候持续运行这些检查。

添加变量

你可以定义如下所示的变量:

var appServicePlanName = 'toy-product-launch-plan'

变量的定义方式与参数类似,但有几点区别:

  • 使用 var 关键字告诉 Bicep 你要声明一个变量。
  • 必须为变量提供值。
  • 变量不需要类型。 Bicep 可以基于你设置的值确定类型。

表达式

当你编写模板时,通常不需要硬编码值,甚至不需要在参数中指定它们。 相反,你需要在模板运行时发现值。 例如,你可能需要将模板中的所有资源部署到单个 Azure 区域,即你在其中创建了资源组的区域。 或者,你可能需要根据公司使用的特定命名策略自动为资源创建一个唯一的名称。

Bicep 中的表达式是一个强大的功能,可帮助你处理各种有趣的场景。 我们来看看可以在 Bicep 模板中使用表达式的几个位置。

源位置

当你编写和部署模板时,通常不需要单独指定每个资源的位置。 相反,你可能会有一个简单的业务规则,显示“默认情况下,将所有资源部署到创建资源组的同一位置”。

在 Bicep 中,可以创建一个名为 location 的参数,并使用表达式来设置其值:

param location string = resourceGroup().location

查看该参数的默认值。 它使用名为 resourceGroup() 的函数,让你可以访问有关将模板部署到的目标资源组的信息。 在本例中,模板使用 location 属性。 通常使用这种方法将资源部署到与资源组相同的 Azure 区域。

如果有人正在部署此模板,他们可能会选择在此处覆盖默认值,并使用不同的位置。

备注

Azure 中的某些资源只能部署到特定位置。 你可能需要单独的参数来设置这些资源的位置。

现在可以在模板中使用资源位置参数,如下所示:

resource appServiceApp 'Microsoft.Web/sites@2022-03-01' = {
  name: appServiceAppName
  location: location
  properties: {
    serverFarmId: appServicePlan.id
    httpsOnly: true
  }
}

资源名称

许多 Azure 资源都需要唯一的名称。 在你的场景中,你有两个需要唯一名称的资源:存储帐户和应用服务应用。 要求将这些值设置为参数可能会给使用该模板的人员增加难度,因为他们需要找到一个其他人未使用过的名称。

Bicep 还有一个名为 uniqueString() 的函数,当你创建资源名称时,它会派上用场。 在使用此函数时,需要提供一个种子值,该值在不同的部署中应该是不同的,但在相同资源的所有部署中都是一致的。

如果选择良好的种子值,则每次部署同一组资源时,都可以获取相同的名称,但每次使用相同的模板部署不同的资源集时,都会获得不同的名称。 我们看看如何使用 uniqueString() 函数:

param storageAccountName string = uniqueString(resourceGroup().id)

此参数的默认值将再次使用 resourceGroup() 函数,就像你设置资源位置时所做的那样。 不过,这次你获取的是资源组的 ID。 资源组 ID 如下所示:

/subscriptions/3e57e557-826f-460b-8f1c-4ce38fd53b32/resourceGroups/MyResourceGroup

资源组 ID 包含 Azure 订阅 ID (3e57e557-826f-460b-8f1c-4ce38fd53b32) 和资源组名称 (MyResourceGroup)。 资源组 ID 通常是资源名称种子值的合适候选项,原因如下:

  • 每次当你部署相同资源时,它们都将进入同一资源组。 uniqueString() 函数每次都会返回相同的值。
  • 如果你部署到 Azure 订阅中的两个不同的资源组中,则 resourceGroup().id 值会有所不同,因为资源组名称将不同。 对于每组资源,uniqueString() 函数将给出不同的值。
  • 如果你将部署到两个不同的 Azure 订阅,那么即使你使用相同的资源组名称,resourceGroup().id 值也会不同,因为 Azure 订阅 ID 会不同。 对于每组资源,uniqueString() 函数将再次给出不同的值。

提示

一般最好使用模板表达式来创建资源名称。 许多 Azure 资源类型都有关于允许的字符及其名称长度的规则。 在模板中嵌入资源名称的创建,意味着使用该模板的任何人都无需自己记得遵守这些规则。

组合字符串

如果你只是使用 uniqueString() 函数设置资源名称,可能会获得唯一的名称,但这些名称没有意义。 好的资源名称也应该是描述性的,这样才能清楚地知道资源的用途。 通常需要将有意义的单词或字符串与唯一值结合起来,创建一个名称。 这样一来,你就可以获得有意义且名称唯一的资源。

Bicep 有一个名为“字符串内插”的功能,可让你组合字符串。 我们来看看它的运作方式:

param storageAccountName string = 'toylaunch${uniqueString(resourceGroup().id)}'

storageAccountName 参数的默认值现在有两个部分:

  • toylaunch 是一个硬编码字符串,可帮助查看 Azure 中已部署资源的任何人了解存储帐户的用途。
  • ${uniqueString(resourceGroup().id)} 是指示 Bicep 评估 uniqueString(resourceGroup().id) 函数的输出,然后将其连接到字符串的方法。

提示

有时,uniqueString() 函数将创建以数字开头的字符串。 某些 Azure 资源(如存储帐户)不允许其名称以数字开头。 这意味着,最好使用字符串内插来创建资源名称,如前面的示例中所示。

为资源选择 SKU

你的团队的其他成员对你目前构建的 Bicep 代码具有深刻印象。 你们已经共同决定使用模板来部署资源,从而支持所有新玩具的发布。

你的一个同事建议为每个产品发布创建非生产环境,以帮助营销团队在将站点提供给客户之前对其进行测试。 但是,你需要确保不会在非生产环境上花费太多的资金,因此你们共同决定一些策略:

  • 在生产环境中,存储帐户将部署在 Standard_GRS(异地冗余存储)SKU 上,以实现高复原能力。 应用服务计划将部署在 P2v3 SKU 上,以获得高性能。
  • 在非生产环境中,存储帐户将部署在 Standard_LRS (本地冗余存储)SKU 上。 应用服务计划将部署在免费 F1 SKU 上。

满足这些业务需求的一种方法是使用参数来指定每个 SKU。 但是,指定每个 SKU 作为参数可能会变得难以管理,尤其是当你具有更大的模板时。 另一种方法是使用参数、变量和表达式的组合将业务规则嵌入到模板中。

首先,你可以指定一个参数,指示部署是用于生产环境还是非生产环境:

@allowed([
  'nonprod'
  'prod'
])
param environmentType string

请注意,此代码使用一些新语法来指定参数 environmentType 的允许值列表。 Bicep 不会让任何人部署模板,除非他们提供这些值之一。

接下来,你可以创建变量,根据环境确定用于存储帐户和应用服务计划的 SKU:

var storageAccountSkuName = (environmentType == 'prod') ? 'Standard_GRS' : 'Standard_LRS'
var appServicePlanSkuName = (environmentType == 'prod') ? 'P2V3' : 'F1'

这里还要注意一些新的语法。 让我们来详细讲解:

  • (environmentType == 'prod') 计算结果为布尔值(true 或 false),具体取决于用于 environmentType 参数的允许值。
  • ? 称为三元运算符,它计算 if/then 语句。 如果表达式为 true,则使用 ? 运算符后面的值。 如果表达式的计算结果为 false,则使用冒号 (:) 后面的值。

我们可以将这些规则转换为:

  • 对于 storageAccountSkuName 变量,如果 environmentType 参数设置为 prod,则使用 Standard_GRS SKU。 否则,使用 Standard_LRS SKU。
  • 对于 appServicePlanSkuName 变量,如果 environmentType 参数设置为 prod,则使用 P2V3 SKU 和 PremiumV3 层。 否则,使用 F1 SKU。

提示

当你创建类似于这样的多部分表达式时,最好使用变量,而不是将表达式直接嵌入资源属性中。 这使模板更易于阅读和理解,因为这样可以避免将资源定义与逻辑混淆。

在模板中使用参数、变量和表达式时,你就可以重复使用模板,并快速部署一组新的资源。 例如,每次营销部门要求你为下一次玩具发布部署新的网站时,为所部署的每个环境提供一些新参数值就可以了!