練習 - 重構 Bicep 檔案

已完成

在與同事檢閱您的範本之後,您決定重構檔案,讓同事更容易使用。 在此練習中,您會套用您在先前單元中學到的最佳做法。

您的工作

請檢閱稍早儲存的 Bicep 範本。 回想您已讀過關於如何設計範本結構的建議。 嘗試更新您的範本,讓同事更容易理解。

下列各節會有一些範本特定部分的指標,以及建議變更內容的提示。 我們提供建議的解決方案,但您的範本看起來可能會不同,這完全沒問題!

提示

當您逐步完成重構流程時,建議您確定 Bicep 檔案有效,而且您沒有意外產生任何錯誤。 Visual Studio Code 的 Bicep 延伸模組可協助進行這項處理。 請留意程式碼下方的任何紅色或黃色波浪線,因為這些波浪線表示有錯誤或警告。 您也可以藉由選取 [檢視]> [問題],來檢視檔案中的問題清單。

更新參數

  1. 範本中的某些參數並不清楚。 例如,請思考這些參數:

    @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

    Visual Studio Code 的螢幕擷取畫面,其中顯示如何重新命名符號。

    這些步驟重新命名識別碼,並自動更新其所有參考。

  2. managedIdentityName 參數沒有預設值。 可以修正該問題,或甚至在範本中自動建立名稱嗎?

  3. 查看 roleDefinitionId 參數定義:

    param roleDefinitionId string = 'b24988ac-6180-42a0-ab88-20f7382dd24c'
    

    為什麼會有 b24988ac-6180-42a0-ab88-20f7382dd24c 的預設值呢? 這個很長的識別碼代表什麼意思? 其他人如何知道要使用預設值還是要加以覆寫? 您可以採取什麼措施來改善識別碼? 將這個設定為參數是否有意義?

    提示

    該識別碼是 Azure 的「參與者」角色定義識別碼。 如何使用該資訊改進範本?

  4. 當有人部署範本時,他們要如何知道每個參數的用途? 可以新增一些描述來協助您的範本使用者嗎?

新增組態集

  1. 您與同事們討論過後,決定針對每個資源,視部署的環境使用特定 SKU。 您會針對每個資源決定以下 SKU:

    資源 適用於生產環境的 SKU 適用於非生產環境的 SKU
    App Service 方案 S1,兩個執行個體 F1,一個執行個體
    儲存體帳戶 GRS LRS
    SQL 資料庫 S1 基本
  2. 可以使用組態集簡化參數定義嗎?

更新符號名稱

請查看範本中資源的符號名稱。 您可以採取什麼措施來改進這些問題?

  1. 您的 Bicep 範本包含資源,這些資源的符號名稱具有各種不同大小寫樣式,例如:

    • storageAccountwebSite,其使用 camelCase 大小寫。
    • roleassignmentsqlserver,其使用純小寫。
    • sqlserverName_databaseNameAppInsights_webSiteName,其使用蛇形大小寫。

    可以修正這些問題,一致地使用一個樣式嗎?

  2. 請查看此角色指派資源:

    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 應用程式會使用其受控識別連線至資料庫伺服器。 這可協助您在範本中加以闡明嗎?

  3. 有幾個具有符號名稱的資源並未反映 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 容器定義

  1. 請查看 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 屬性,另一個則不使用。 可以修正這些定義方式以保持一致嗎?

  2. Blob 容器名稱不會因為環境不同而變更。 您認為需要使用參數指定名稱嗎?

  3. 這裡有兩個 Blob 容器。 可以使用迴圈部署這些容器嗎?

更新資源名稱

  1. 這裡有一些明確設定資源名稱的參數:

    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 將會建立另一項資源。 如以「完整」模式部署,甚至可能會刪除舊的資源。

    在此不必擔心,因為這只是一個範例。

  2. SQL 邏輯伺服器的資源名稱是使用變數進行設定,即使它需要全域唯一名稱也一樣:

    var sqlserverName = 'toywebsite${uniqueString(resourceGroup().id)}'
    

    您要如何改善這種情況?

更新相依性和子資源

  1. 以下是您資源的其中一個,其包含 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
      ]
    }
    
  2. 請注意這些子資源在範本中的宣告方式:

    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
      ]
    }
    

    如何修改這些資源的宣告方式? 範本中是否還有任何其他應更新的資源?

更新屬性值

  1. 請查看 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 屬性值硬式編碼是否合理? 且 collationmaxSizeBytes 屬性值為什麼這麼奇怪?

    提示

    collationmaxSizeBytes 屬性是設定為預設值。 如果未自行指定值,即會使用預設值。 這是否能協助您決定要如何處理這些項目?

  2. 您可否變更儲存體連接字串的設定方式,不要讓複雜運算式的定義與資源一致?

    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}': {}
        }
      }
    }
    

元素順序

  1. 您對檔案中的元素順序感到滿意嗎? 如何藉由移動元素來改善檔案的可讀性?

  2. 查看 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
      }
    }
    
  3. 您是否注意到資源 webSiteConnectionStrings 已進行註解? 您認為其需要在檔案中註解嗎?

新增註解、標記和其他中繼資料

請想一下範本中是否有任何內容不夠明確或需要額外說明。 您可以新增註解,讓以後可能開啟此檔案的其他人更清楚嗎?

  1. 請看一下 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}': {}
        }
      }
    }
    

    這種語法很奇怪,不是嗎? 您認為這裡需要註解來協助說明嗎?

  2. 查看角色指派資源:

    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() 函式。 它是否有助於說明原因?

  3. 您是否可以為角色指派新增描述?

  4. 您是否可以為每個資源新增一組標記?

建議的解決方案

下列是可能的重構範本範例。 您的範本可能和這個範本不完全一樣,因為樣式可能不同。

@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 與同事合作,則此時是提交「提取要求」以將變更整合至主要分支的最佳時機。 在執行一部分重構工作之後,最好提交提取要求。