練習 - 重構 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 的「參與者」角色定義識別碼。 如何使用該資訊改進範本?
當有人部署範本時,他們要如何知道每個參數的用途? 可以新增一些描述來協助您的範本使用者嗎?
新增組態集
您與同事們討論過後,決定針對每個資源,視部署的環境使用特定 SKU。 您會針對每個資源決定以下 SKU:
資源 適用於生產環境的 SKU 適用於非生產環境的 SKU App Service 方案 S1,兩個執行個體 F1,一個執行個體 儲存體帳戶 GRS LRS SQL 資料庫 S1 基本 可以使用組態集簡化參數定義嗎?
更新符號名稱
請查看範本中資源的符號名稱。 您可以採取什麼措施來改進這些問題?
您的 Bicep 範本包含資源,這些資源的符號名稱具有各種不同大小寫樣式,例如:
storageAccount
和webSite
,其使用 camelCase 大小寫。roleassignment
和sqlserver
,其使用純小寫。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」,App Service 方案原先稱為「主控方案」,App Service 應用程式原先稱為「網站」。
可以將這些項目更新為最新的名稱,以避免日後混淆嗎?
簡化 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 與同事合作,則此時是提交「提取要求」以將變更整合至主要分支的最佳時機。 在執行一部分重構工作之後,最好提交提取要求。