练习 - 重构 Bicep 文件
与同事一起查看模板后,你决定重构文件,使其更易于使用。 在本练习中,将应用前面单元中所学的最佳做法。
你的任务
查看之前保存的 Bicep 模板。 思考已了解到的模板构造方法建议。 尝试更新模板,以便于同事理解。
下面的部分中有一些指向模板特定部分的指针,以及有关你可能想要更改的内容的提示。 我们将提供推荐的解决方案,但你的模板内容可能会有所不同,这是完全正常的!
提示
完成重构过程时,请确保 Bicep 文件有效且未意外引入任何错误。 适用于 Visual Studio Code 的 Bicep 扩展可提供帮助。 注意代码下面的红色或黄色锯齿线,因为它们表示错误或警告。 你也可选择“查看”>“问题”来查看文件中问题的列表。
更新参数
模板中的某些参数不明确。 例如,请考虑以下参数:
@allowed([ 'F1' 'D1' 'B1' 'B2' 'B3' 'S1' 'S2' 'S3' 'P1' 'P2' 'P3' 'P4' ]) param skuName string = 'F1' @minValue(1) param skuCapacity int = 1
它们的用途是什么?
提示
如果你有一个试图理解的参数,Visual Studio Code 可提供帮助。 选择并按住(或右键单击)文件中任意位置的参数名,然后选择“查找所有引用”。
模板是否需要为
skuName
参数指定允许值的列表? 为这些参数选择不同的值后会影响哪些资源? 是否可以为参数提供更好的名称?提示
重命名标识符时,请务必在模板的所有部分保持一致。 这对于在整个模板中引用的参数、变量和资源尤其重要。
Visual Studio Code 提供了一种简便的符号重命名方法:选择想要重命名的标识符,选择 F2,输入新名称,然后选择 Enter:
这些步骤会重命名标识符并自动更新对它的所有引用。
managedIdentityName
参数没有默认值。 能否修复此问题,或者最好在模板中自动创建名称?查看
roleDefinitionId
参数定义:param roleDefinitionId string = 'b24988ac-6180-42a0-ab88-20f7382dd24c'
为何这里有个默认值
b24988ac-6180-42a0-ab88-20f7382dd24c
? 这个长标识符是什么意思? 其他人怎么知道是使用默认值还是重写默认值? 可以如何改进标识符? 将其用作参数有意义吗?提示
该标识符是 Azure 的“参与者”角色定义 ID。 如何使用该信息来改进模板?
人们部署模板时,怎样知道每个参数的用途是什么? 能否添加一些说明来为模板用户提供帮助?
添加配置集
你与同事沟通后,决定根据所部署的环境对每项资源使用特定的 SKU。 你决定为每项资源使用以下 SKU:
资源 用于生产环境的 SKU 用于非生产环境的 SKU 应用服务计划 S1,两个实例 F1,一个实例 存储帐户 GRS LRS SQL 数据库 S1 基本 可以使用配置集来简化参数定义吗?
更新符号名称
请查看模板中资源的符号名称。 你可以执行哪些操作来改进它们?
Bicep 模板包含具有各种符号名称大写样式的资源,例如:
storageAccount
和webSite
,它们使用驼峰命名法大写样式。roleassignment
和sqlserver
,它们使用扁平命名法 (flat case) 大写样式。sqlserverName_databaseName
和AppInsights_webSiteName
,它们使用蛇形命名法大写样式。
是否可以纠正这些问题,而使用一种统一的样式?
查看此角色分配资源:
resource roleassignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { name: guid(roleDefinitionId, resourceGroup().id) properties: { principalType: 'ServicePrincipal' roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionId) principalId: msi.properties.principalId } }
符号名称的说明是否足以帮助其他人使用此模板?
提示
标识需要角色分配的原因是 Web 应用使用其托管标识连接到数据库服务器。 这是否有助于在模板中阐明这一点?
几项资源的符号名称未反映出 Azure 资源的当前名称:
resource hostingPlan 'Microsoft.Web/serverfarms@2023-12-01' = { // ... } resource webSite 'Microsoft.Web/sites@2023-12-01' = { // ... } resource msi 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-07-31-preview' = { // ... }
托管标识曾被称为 MSI,应用服务计划曾被称为托管计划,应用服务应用曾被称为网站。
是否可以将这些名称更新为最新的名称,以避免将来出现混淆?
简化 blob 容器定义
查看 blob 容器的定义方式:
resource container1 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-05-01' = { parent: storageAccount::blobServices name: container1Name } resource productmanuals 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-05-01' = { name: '${storageAccount.name}/default/${productmanualsName}' }
其中一个使用
parent
属性,另一个则没有。 能否修复此问题,让它们保持一致?Blob 容器的名称在各环境之间不会发生改变。 你认为需要使用参数来指定名称吗?
这里有两个 blob 容器。 能否使用循环来部署它们?
更新资源名称
有一些参数可显式设置资源名称:
param managedIdentityName string param roleDefinitionId string = 'b24988ac-6180-42a0-ab88-20f7382dd24c' param webSiteName string = 'webSite${uniqueString(resourceGroup().id)}' param container1Name string = 'productspecs' param productmanualsName string = 'productmanuals'
还有其他方法可以执行此操作吗?
注意
请记住,资源一旦部署便无法再重命名。 修改已在使用的模板时,请谨慎更改模板创建资源名称的方式。 如果重新部署了模板且资源具有新名称,Azure 将创建另一项资源。 如果你在“完全”模式下部署旧资源,它甚至可能会被删除。
但不必担心,因为这只是个示例。
虽然 SQL 逻辑服务器需要全局唯一名称,但它的资源名称是使用变量设置的:
var sqlserverName = 'toywebsite${uniqueString(resourceGroup().id)}'
可如何进行改进?
更新依赖项和子资源
下面是你的其中一项资源,它包含
dependsOn
属性。 真的需要它吗?resource sqlserverName_AllowAllAzureIPs 'Microsoft.Sql/servers/firewallRules@2023-08-01-preview' = { name: '${sqlserver.name}/AllowAllAzureIPs' properties: { endIpAddress: '0.0.0.0' startIpAddress: '0.0.0.0' } dependsOn: [ sqlserver ] }
注意如何在模板中声明以下子资源:
resource sqlserverName_databaseName 'Microsoft.Sql/servers/databases@2023-08-01-preview' = { name: '${sqlserver.name}/${databaseName}' location: location sku: { name: 'Basic' } properties: { collation: 'SQL_Latin1_General_CP1_CI_AS' maxSizeBytes: 1073741824 } } resource sqlserverName_AllowAllAzureIPs 'Microsoft.Sql/servers/firewallRules@2023-08-01-preview' = { name: '${sqlserver.name}/AllowAllAzureIPs' properties: { endIpAddress: '0.0.0.0' startIpAddress: '0.0.0.0' } dependsOn: [ sqlserver ] }
如何修改这些资源的声明方式? 模板中还有其他需要更新的资源吗?
更新属性值
请查看 SQL 数据库资源的属性:
resource sqlserverName_databaseName 'Microsoft.Sql/servers/databases@2023-08-01-preview' = { name: '${sqlserver.name}/${databaseName}' location: location sku: { name: 'Basic' } properties: { collation: 'SQL_Latin1_General_CP1_CI_AS' maxSizeBytes: 1073741824 } }
对 SKU 的
name
属性值进行硬编码有意义吗?collation
和maxSizeBytes
属性的那些奇怪的值是什么?提示
collation
和maxSizeBytes
属性均设置为默认值。 如果未自行指定这些值,将使用默认值。 这能否帮助你确定如何处理它们?能否更改存储连接字符串的设置方式,以免根据资源定义复杂表达式?
resource webSite 'Microsoft.Web/sites@2023-12-01' = { name: webSiteName location: location properties: { serverFarmId: hostingPlan.id siteConfig: { appSettings: [ { name: 'APPINSIGHTS_INSTRUMENTATIONKEY' value: AppInsights_webSiteName.properties.InstrumentationKey } { name: 'StorageAccountConnectionString' value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${listKeys(storageAccount.id, storageAccount.apiVersion).keys[0].value}' } ] } } identity: { type: 'UserAssigned' userAssignedIdentities: { '${msi.id}': {} } } }
元素的顺序
你是否喜欢文件中的元素顺序? 如何通过移动元素提高文件的可读性?
请查看
databaseName
变量。 它应该位于当前的位置吗?var databaseName = 'ToyCompanyWebsite' resource sqlserverName_databaseName 'Microsoft.Sql/servers/databases@2023-08-01-preview' = { name: '${sqlserver.name}/${databaseName}' location: location sku: { name: 'Basic' } properties: { collation: 'SQL_Latin1_General_CP1_CI_AS' maxSizeBytes: 1073741824 } }
注意到注释掉的资源
webSiteConnectionStrings
了吗? 你认为它需要在文件中吗?
添加注释、标记和其他元数据
思考模板中可能不明显或需要额外解释的内容。 能否添加一些注释,让以后可能打开该文件的其他人员更容易明白?
请查看
webSite
资源的identity
变量:resource webSite 'Microsoft.Web/sites@2023-12-01' = { name: webSiteName location: location properties: { serverFarmId: hostingPlan.id siteConfig: { appSettings: [ { name: 'APPINSIGHTS_INSTRUMENTATIONKEY' value: AppInsights_webSiteName.properties.InstrumentationKey } { name: 'StorageAccountConnectionString' value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${listKeys(storageAccount.id, storageAccount.apiVersion).keys[0].value}' } ] } } identity: { type: 'UserAssigned' userAssignedIdentities: { '${msi.id}': {} } } }
这种句法很奇怪,不是吗? 你认为需要添加注释来帮助解释它吗?
请查看以下角色分配资源:
resource roleassignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { name: guid(roleDefinitionId, resourceGroup().id) properties: { principalType: 'ServicePrincipal' roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionId) principalId: msi.properties.principalId } }
资源的名称使用
guid()
函数。 它有助于解释原因吗?能否向角色分配添加说明?
能否为每项资源添加一组标记?
建议的解决方案
下面是如何重构模板的示例。 你的模板可能与其不完全相似,因为样式可能不同。
@description('The location into which your Azure resources should be deployed.')
param location string = resourceGroup().location
@description('Select the type of environment you want to provision. Allowed values are Production and Test.')
@allowed([
'Production'
'Test'
])
param environmentType string
@description('A unique suffix to add to resource names that need to be globally unique.')
@maxLength(13)
param resourceNameSuffix string = uniqueString(resourceGroup().id)
@description('The administrator login username for the SQL server.')
param sqlServerAdministratorLogin string
@secure()
@description('The administrator login password for the SQL server.')
param sqlServerAdministratorLoginPassword string
@description('The tags to apply to each resource.')
param tags object = {
CostCenter: 'Marketing'
DataClassification: 'Public'
Owner: 'WebsiteTeam'
Environment: 'Production'
}
// Define the names for resources.
var appServiceAppName = 'webSite${resourceNameSuffix}'
var appServicePlanName = 'AppServicePLan'
var sqlServerName = 'sqlserver${resourceNameSuffix}'
var sqlDatabaseName = 'ToyCompanyWebsite'
var managedIdentityName = 'WebSite'
var applicationInsightsName = 'AppInsights'
var storageAccountName = 'toywebsite${resourceNameSuffix}'
var blobContainerNames = [
'productspecs'
'productmanuals'
]
@description('Define the SKUs for each component based on the environment type.')
var environmentConfigurationMap = {
Production: {
appServicePlan: {
sku: {
name: 'S1'
capacity: 2
}
}
storageAccount: {
sku: {
name: 'Standard_GRS'
}
}
sqlDatabase: {
sku: {
name: 'S1'
tier: 'Standard'
}
}
}
Test: {
appServicePlan: {
sku: {
name: 'F1'
capacity: 1
}
}
storageAccount: {
sku: {
name: 'Standard_LRS'
}
}
sqlDatabase: {
sku: {
name: 'Basic'
}
}
}
}
@description('The role definition ID of the built-in Azure \'Contributor\' role.')
var contributorRoleDefinitionId = 'b24988ac-6180-42a0-ab88-20f7382dd24c'
var storageAccountConnectionString = 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${listKeys(storageAccount.id, storageAccount.apiVersion).keys[0].value}'
resource sqlServer 'Microsoft.Sql/servers@2023-08-01-preview' = {
name: sqlServerName
location: location
tags: tags
properties: {
administratorLogin: sqlServerAdministratorLogin
administratorLoginPassword: sqlServerAdministratorLoginPassword
version: '12.0'
}
}
resource sqlDatabase 'Microsoft.Sql/servers/databases@2023-08-01-preview' = {
parent: sqlServer
name: sqlDatabaseName
location: location
sku: environmentConfigurationMap[environmentType].sqlDatabase.sku
tags: tags
}
resource sqlFirewallRuleAllowAllAzureIPs 'Microsoft.Sql/servers/firewallRules@2023-08-01-preview' = {
parent: sqlServer
name: 'AllowAllAzureIPs'
properties: {
endIpAddress: '0.0.0.0'
startIpAddress: '0.0.0.0'
}
}
resource appServicePlan 'Microsoft.Web/serverfarms@2023-12-01' = {
name: appServicePlanName
location: location
sku: environmentConfigurationMap[environmentType].appServicePlan.sku
tags: tags
}
resource appServiceApp 'Microsoft.Web/sites@2023-12-01' = {
name: appServiceAppName
location: location
tags: tags
properties: {
serverFarmId: appServicePlan.id
siteConfig: {
appSettings: [
{
name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
value: applicationInsights.properties.InstrumentationKey
}
{
name: 'StorageAccountConnectionString'
value: storageAccountConnectionString
}
]
}
}
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${managedIdentity.id}': {} // This format is required when working with user-assigned managed identities.
}
}
}
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' = {
name: storageAccountName
location: location
sku: environmentConfigurationMap[environmentType].storageAccount.sku
kind: 'StorageV2'
properties: {
accessTier: 'Hot'
}
resource blobServices 'blobServices' existing = {
name: 'default'
resource containers 'containers' = [for blobContainerName in blobContainerNames: {
name: blobContainerName
}]
}
}
@description('A user-assigned managed identity that is used by the App Service app to communicate with a storage account.')
resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-07-31-preview'= {
name: managedIdentityName
location: location
tags: tags
}
@description('Grant the \'Contributor\' role to the user-assigned managed identity, at the scope of the resource group.')
resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(contributorRoleDefinitionId, resourceGroup().id) // Create a GUID based on the role definition ID and scope (resource group ID). This will return the same GUID every time the template is deployed to the same resource group.
properties: {
principalType: 'ServicePrincipal'
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', contributorRoleDefinitionId)
principalId: managedIdentity.properties.principalId
description: 'Grant the "Contributor" role to the user-assigned managed identity so it can access the storage account.'
}
}
resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = {
name: applicationInsightsName
location: location
kind: 'web'
tags: tags
properties: {
Application_Type: 'web'
}
}
提示
如果你在使用 GitHub 或 Azure Repos 与同事协作,此时将非常适合提交拉取请求,将更改集成到主分支。 建议在执行一部分重构工作后提交拉取请求。