Упражнение. Рефакторинг файла Bicep

Завершено

Просмотрев с коллегами шаблон, вы решили выполнить рефакторинг файла, чтобы с ним было проще работать. В этом упражнении вы примените рекомендации, изученные в предыдущих разделах.

Ваша задача

Просмотрите шаблон Bicep, который вы сохранили ранее. Вспомните рекомендации по структурированию шаблонов. Попробуйте обновить свой шаблон, чтобы сделать его понятнее для коллег.

В следующих разделах вы найдете указатели на определенные части шаблона и рекомендации по элементам, которые, возможно, стоит изменить. Мы предлагаем вам пример, но ваш шаблон может выглядеть по-другому и это абсолютно нормально.

Совет

Выполняя рефакторинг, убедитесь в том, что ваш файл Bicep работает и вы случайно не внесли в него какие-то ошибки. В этом вам поможет расширение Bicep для Visual Studio Code. Посмотрите, нет ли под вашим кодом красных или желтых строк — они содержат ошибки или предупреждения. Еще один способ проверить файл на наличие проблем — это выбрать Вид>Проблемы.

Обновите параметры.

  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, введите новое имя, а затем нажмите клавишу ВВОД.

    Снимок экрана с 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 для непроизводственной среды
    План службы приложений S1, два экземпляра F1, один экземпляр
    Storage account GRS LRS
    База данных SQL S1 Базовая
  2. Можно ли с помощью набора конфигурации упростить определения параметров?

Обновление символьных имен

Взгляните на символьные имена ресурсов в шаблоне. Как можно их улучшить?

  1. Ваш шаблон Bicep содержит ресурсы с различными стилями написания символьных имен, например:

    • storageAccount и webSite, где в качестве разделителя используются средние заглавные буквы;
    • roleassignment и sqlserver, где заглавные буквы не используются;
    • sqlserverName_databaseName и AppInsights_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
      }
    }
    

    Является ли символьное имя достаточно описательным для того, чтобы с этим шаблоном могли работать другие пользователи?

    Совет

    Назначение ролей необходимо удостоверению потому, что веб-приложение использует управляемое удостоверение для подключения к серверу базы данных. Будет ли полезно пояснить это в шаблоне?

  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, планы Службы приложений — планами размещения, а приложения Службы приложений — веб-сайтами.

    Можете ли вы обновить их до последних имен, чтобы избежать путаницы в будущем?

Упрощение определений контейнеров 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
      }
    }
    

    Стоит ли жестко программировать значение свойства name SKU? И что это за странные значения для свойств collation и maxSizeBytes?

    Совет

    Для свойств collation и maxSizeBytes заданы значения по умолчанию. Если вы не укажете значения сами, будут использоваться значения по умолчанию. Помогает ли это вам понять, что с ними делать?

  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, отправьте запрос на вытягивание для интеграции изменений в главную ветвь. Запросы на вытягивание лучше отправлять после рефакторинга.