Ćwiczenie — refaktoryzacja pliku Bicep

Ukończone

Po przejrzeniu szablonu ze współpracownikami decydujesz się na refaktoryzację pliku, aby ułatwić im pracę. W tym ćwiczeniu zastosujesz najlepsze rozwiązania poznane w poprzednich lekcjach.

Zadanie

Przejrzyj zapisany wcześniej szablon Bicep. Zastanów się nad poradami, które znasz, jak utworzyć strukturę szablonów. Spróbuj zaktualizować szablon, aby ułatwić współpracownikom zrozumienie.

W następnych sekcjach znajdują się wskazówki dotyczące określonych części szablonu i wskazówki dotyczące elementów, które warto zmienić. Udostępniamy sugerowane rozwiązanie, ale Twój szablon może wyglądać inaczej, co jest całkowicie ok!

Napiwek

Podczas pracy z procesem refaktoryzacji dobrze jest upewnić się, że plik Bicep jest prawidłowy i że nie zostały przypadkowo wprowadzone żadne błędy. Rozszerzenie Bicep dla programu Visual Studio Code pomaga w tym celu. Zwróć uwagę na czerwone lub żółte linie poniżej kodu, ponieważ wskazują one błąd lub ostrzeżenie. Możesz również wyświetlić listę problemów w pliku, wybierając pozycję Wyświetl>problemy.

Aktualizowanie parametrów

  1. Niektóre parametry w szablonie nie są jasne. Rozważmy na przykład następujące parametry:

    @allowed([
      'F1'
      'D1'
      'B1'
      'B2'
      'B3'
      'S1'
      'S2'
      'S3'
      'P1'
      'P2'
      'P3'
      'P4'
    ])
    param skuName string = 'F1'
    
    @minValue(1)
    param skuCapacity int = 1
    

    Do czego służą?

    Napiwek

    Jeśli masz parametr, który próbujesz zrozumieć, program Visual Studio Code może pomóc. Wybierz i przytrzymaj (lub kliknij prawym przyciskiem myszy) nazwę parametru w dowolnym miejscu w pliku i wybierz pozycję Znajdź wszystkie odwołania.

    Czy szablon musi określać listę dozwolonych wartości parametru skuName ? Jakie zasoby mają wpływ na wybór różnych wartości dla tych parametrów? Czy istnieją lepsze nazwy, które można nadać parametrom?

    Napiwek

    Podczas zmieniania nazw identyfikatorów pamiętaj, aby stale zmieniać ich nazwy we wszystkich częściach szablonu. Jest to szczególnie ważne w przypadku parametrów, zmiennych i zasobów, które odwołujesz się do całego szablonu.

    Program Visual Studio Code oferuje wygodny sposób zmiany nazwy symboli: wybierz identyfikator, którego nazwę chcesz zmienić, wybierz klawisz F2, wprowadź nową nazwę, a następnie wybierz klawisz Enter:

    Screenshot from Visual Studio Code that shows how to rename a symbol.

    Te kroki powodują zmianę nazwy identyfikatora i automatyczne aktualizowanie wszystkich odwołań do niego.

  2. Parametr managedIdentityName nie ma wartości domyślnej. Czy można rozwiązać ten problem lub, jeszcze lepiej, utworzyć nazwę automatycznie w szablonie?

  3. Przyjrzyj się definicji parametru roleDefinitionId :

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

    Dlaczego istnieje wartość domyślna b24988ac-6180-42a0-ab88-20f7382dd24c? Co oznacza ten długi identyfikator? Jak ktoś inny wiedziałby, czy użyć wartości domyślnej, czy zastąpić ją? Co można zrobić, aby ulepszyć identyfikator? Czy ma to nawet sens jako parametr?

    Napiwek

    Ten identyfikator to identyfikator definicji roli Współautor dla platformy Azure. Jak można użyć tych informacji, aby ulepszyć szablon?

  4. Kiedy ktoś wdroży szablon, w jaki sposób będzie wiedział, dla którego parametru jest dany parametr? Czy możesz dodać opisy, aby pomóc użytkownikom szablonu?

Dodawanie zestawu konfiguracji

  1. Rozmawiasz ze współpracownikami i decydujesz się na użycie określonych jednostek SKU dla każdego zasobu, w zależności od wdrażanego środowiska. Decydujesz się na te jednostki SKU dla każdego z zasobów:

    Zasób Jednostka SKU dla środowiska produkcyjnego Jednostka SKU dla nieprodukcyjnej
    Plan usługi App Service S1, dwa wystąpienia F1, jedno wystąpienie
    Konto magazynu GRS LRS
    Baza danych SQL S1 Podstawowy
  2. Czy można użyć zestawu konfiguracji, aby uprościć definicje parametrów?

Aktualizowanie nazw symbolicznych

Przyjrzyj się nazwam symbolicznym zasobów w szablonie. Co można zrobić, aby je poprawić?

  1. Szablon Bicep zawiera zasoby z różnymi stylami wielkich liter dla ich nazw symbolicznych, takich jak:

    • storageAccount i webSite, które używają wielkiej litery camelCase.
    • roleassignment i sqlserver, które używają płaskiej wielkości liter.
    • sqlserverName_databaseName i AppInsights_webSiteName, które używają wielkości liter węża.

    Czy można je naprawić, aby używać jednego stylu spójnie?

  2. Spójrz na ten zasób przypisania roli:

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

    Czy nazwa symboliczna jest wystarczająco opisowa, aby pomóc komuś innemu pracować z tym szablonem?

    Napiwek

    Przyczyną, dla którego tożsamość wymaga przypisania roli, jest to, że aplikacja internetowa używa swojej tożsamości zarządzanej do nawiązywania połączenia z serwerem bazy danych. Czy pomaga to wyjaśnić w szablonie?

  3. Kilka zasobów ma symboliczne nazwy, które nie odzwierciedlają bieżących nazw zasobów platformy 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' = {
      // ...
    }
    

    Tożsamości zarządzane używane do wywoływaniu msI, planów usługi App Service używanych do wywoływanych planów hostingu i aplikacji usługi App Service używanych do wywoływanych witryn internetowych.

    Czy można je zaktualizować do najnowszych nazw, aby uniknąć nieporozumień w przyszłości?

Uproszczenie definicji kontenera obiektów blob

  1. Zobacz, jak są zdefiniowane kontenery obiektów 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}'
    }
    

    Jeden z nich używa parent właściwości , a drugi nie. Czy można rozwiązać te problemy, aby były spójne?

  2. Nazwy kontenerów obiektów blob nie zmienią się między środowiskami. Czy uważasz, że nazwy muszą być określone przy użyciu parametrów?

  3. Istnieją dwa kontenery obiektów blob. Czy można je wdrożyć przy użyciu pętli?

Aktualizowanie nazw zasobów

  1. Istnieją pewne parametry, które jawnie ustawiają nazwy zasobów:

    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'
    

    Czy jest inny sposób, w jaki można to zrobić?

    Uwaga

    Pamiętaj, że nie można zmienić nazwy zasobów po ich wdrożeniu. Podczas modyfikowania szablonów, które są już używane, należy zachować ostrożność podczas zmiany sposobu tworzenia nazw zasobów przez szablon. Jeśli szablon zostanie wdrożony ponownie, a zasób ma nową nazwę, platforma Azure utworzy kolejny zasób. Może nawet usunąć stary zasób, jeśli wdrożysz go w trybie ukończonym .

    Nie musisz się tym martwić, ponieważ jest to tylko przykład.

  2. Nazwa zasobu serwera logicznego SQL jest ustawiana przy użyciu zmiennej, mimo że potrzebuje globalnie unikatowej nazwy:

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

    Jak można to poprawić?

Aktualizowanie zależności i zasobów podrzędnych

  1. Oto jeden z zasobów, który zawiera dependsOn właściwość. Czy to naprawdę potrzebuje?

    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. Zwróć uwagę, jak te zasoby podrzędne są deklarowane w szablonie:

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

    Jak można zmodyfikować sposób deklarowanego tych zasobów? Czy istnieją inne zasoby w szablonie, które również powinny zostać zaktualizowane?

Aktualizowanie wartości właściwości

  1. Przyjrzyj się właściwościom zasobu bazy danych 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
      }
    }
    

    Czy ma to sens, aby trwale zakodować wartość właściwości jednostki SKU name ? A co to są te dziwne wartości dla collation właściwości i maxSizeBytes ?

    Napiwek

    Właściwości collation i maxSizeBytes są ustawione na wartości domyślne. Jeśli nie określisz wartości samodzielnie, zostaną użyte wartości domyślne. Czy to pomaga zdecydować, co z nimi zrobić?

  2. Czy można zmienić sposób ustawiania parametry połączenia magazynu tak, aby wyrażenie złożone nie było zdefiniowane w tekście z zasobem?

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

Kolejność elementów

  1. Czy jesteś zadowolony z kolejności elementów w pliku? Jak można poprawić czytelność pliku, przenosząc elementy wokół?

  2. Przyjrzyj się zmiennej databaseName . Czy należy on do miejsca, w którym jest teraz?

    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. Czy zauważono zasób z komentarzem? webSiteConnectionStrings Czy uważasz, że musi znajdować się w pliku?

Dodawanie komentarzy, tagów i innych metadanych

Zastanów się nad wszystkimi elementami w szablonie, które mogą nie być oczywiste lub które wymagają dodatkowego wyjaśnienia. Czy możesz dodać komentarze, aby ułatwić innym osobom otwarcie pliku w przyszłości?

  1. Przyjrzyj webSite się właściwości zasobu 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}': {}
        }
      }
    }
    

    Ta składnia jest dziwna, prawda? Czy uważasz, że wymaga to komentarza, aby go wyjaśnić?

  2. Spójrz na zasób przypisania roli:

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

    Nazwa zasobu używa guid() funkcji . Czy pomogłoby to wyjaśnić, dlaczego?

  3. Czy można dodać opis do przypisania roli?

  4. Czy można dodać zestaw tagów do każdego zasobu?

Sugerowane rozwiązanie

Oto przykład refaktoryzacji szablonu. Szablon może nie wyglądać dokładnie tak, ponieważ twój styl może być inny.

@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'
  }
}

Napiwek

Jeśli pracujesz ze współpracownikami przy użyciu repozytoriów GitHub lub Azure Repos, warto przesłać żądanie ściągnięcia w celu zintegrowania zmian z gałęzią główną. Dobrym pomysłem jest przesyłanie żądań ściągnięcia po wykonaniu refaktoryzacji.