Esercizio - Effettuare il refactoring del file Bicep

Completato

Dopo aver esaminato il modello con i colleghi, si decide di effettuare il refactoring del file in modo che possano usarlo più facilmente. In questo esercizio si applicano le procedure consigliate apprese nelle unità precedenti.

Attività dell'utente

Esaminare il modello Bicep salvato in precedenza. Tenere presenti le indicazioni lette su come strutturare i modelli. Provare ad aggiornare il modello in modo che risulti più comprensibile per i colleghi.

Nelle sezioni successive sono disponibili alcune linee guida per parti specifiche del modello e alcuni suggerimenti sugli elementi che potrebbe essere necessario modificare. La soluzione presentata è un suggerimento. Il modello in uso potrebbe essere diverso, il che è perfettamente accettabile.

Suggerimento

Durante il processo di refactoring, è opportuno assicurarsi che il file Bicep sia valido e che non siano stati introdotti accidentalmente errori. A questo scopo, è utile l'estensione Bicep per Visual Studio Code. Prestare attenzione a eventuali righe ondulate rosse o gialle sotto il codice, perché indicano un errore o un avviso. È anche possibile visualizzare un elenco dei problemi nel file selezionando Visualizza>Problemi.

Aggiornare i parametri

  1. Alcuni parametri nel modello non sono chiari. Ad esempio, considerare questi parametri:

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

    Per che cosa vengono usati?

    Suggerimento

    Se non è chiara la natura di un parametro, Visual Studio Code può essere utile. Selezionare e tenere premuto (o fare clic con il pulsante destro del mouse) su un nome di parametro in un punto qualsiasi del file e selezionare Trova tutti i riferimenti.

    Il modello deve specificare l'elenco di valori consentiti per il parametro skuName? Quali risorse sono interessate dalla scelta di valori diversi per questi parametri? Esistono nomi migliori che è possibile assegnare ai parametri?

    Suggerimento

    Quando si rinominano gli identificatori, assicurarsi di farlo in modo coerente in tutte le parti del modello. È importante soprattutto per i parametri, le variabili e le risorse a cui si fa riferimento in tutto il modello.

    Visual Studio Code offre un modo pratico per rinominare i simboli: selezionare l'identificatore che si vuole rinominare, premere F2, immettere un nuovo nome e quindi premere INVIO:

    Screenshot di Visual Studio Code che mostra come rinominare un simbolo.

    Questi passaggi consentono di rinominare l'identificatore e aggiornare automaticamente tutti i riferimenti.

  2. Il parametro managedIdentityName non ha un valore predefinito. È possibile risolvere il problema o, meglio ancora, creare automaticamente il nome nel modello?

  3. Analizzare la definizione del parametro roleDefinitionId:

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

    Perché è presente un valore predefinito b24988ac-6180-42a0-ab88-20f7382dd24c? Cosa significa questo lungo identificatore? In che modo un altro utente può stabilire se usare il valore predefinito o eseguirne l'override? Cosa si può fare per migliorare l'identificatore? Ha senso usarlo come parametro?

    Suggerimento

    Tale identificatore è l'ID definizione del ruolo Contributor per Azure. Come si possono usare queste informazioni per migliorare il modello?

  4. Quando un utente distribuisce il modello, come può sapere a che cosa serve ogni parametro? È possibile aggiungere descrizioni utili agli utenti del modello?

Aggiungere un set di configurazione

  1. Dopo aver parlato con i colleghi, si decide di usare SKU specifici per ogni risorsa, a seconda dell'ambiente da distribuire. Si scelgono questi SKU per ognuna delle risorse:

    Conto risorse SKU per la produzione SKU per la non produzione
    Piano di servizio app S1, due istanze F1, una istanza
    Account di archiviazione GRS LRS
    Database SQL S1 Di base
  2. È possibile usare un set di configurazione per semplificare le definizioni dei parametri?

Aggiornare i nomi simbolici

Esaminare i nomi simbolici per le risorse nel modello. Cosa si può fare per migliorarli?

  1. Il modello Bicep contiene risorse con svariati stili per l'uso di maiuscole e minuscole nei nomi simbolici, ad esempio:

    • storageAccount e webSite, che usano lo stile camelCase.
    • roleassignment e sqlserver, che usano solo lettere minuscole.
    • sqlserverName_databaseName e AppInsights_webSiteName, che usano lo stile snake_case.

    È possibile sistemarle e usare un solo stile in modo coerente?

  2. Esaminare questa risorsa assegnazione di ruolo:

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

    Il nome simbolico è sufficientemente descrittivo da consentire a qualcun altro di usare questo modello?

    Suggerimento

    Il motivo per cui è necessaria un'assegnazione di ruolo per l'identità è che l'app Web usa la propria identità gestita per connettersi al server di database. Si è in grado di chiarire questo concetto nel modello?

  3. Alcune risorse hanno nomi simbolici che non corrispondono ai nomi correnti delle risorse di 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' = {
      // ...
    }
    

    Le identità gestite venivano chiamate MSI, i piani di servizio app venivano chiamati piani di hosting e le app di Servizio app venivano chiamate siti Web.

    È possibile aggiornarli con i nomi più recenti per evitare confusione in futuro?

Semplificare le definizioni dei contenitori BLOB

  1. Osservare come vengono definiti i contenitori 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}'
    }
    

    Uno di essi usa la proprietà parent, l'altro no. È possibile correggerli in modo che siano coerenti?

  2. I nomi dei contenitori BLOB non cambieranno tra gli ambienti. Si ritiene che i nomi debbano essere specificati usando i parametri?

  3. Sono presenti due contenitori BLOB. Si possono distribuire usando un ciclo?

Aggiornare i nomi delle risorse

  1. Esistono alcuni parametri che impostano in modo esplicito i nomi delle risorse:

    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'
    

    Esiste un altro modo per farlo?

    Attenzione

    Tenere presente che le risorse non possono essere rinominate dopo la distribuzione. Se si modificano modelli già in uso, prestare attenzione quando si modifica il modo in cui il modello crea i nomi delle risorse. Se il modello viene distribuito nuovamente e la risorsa ha un nuovo nome, Azure creerà un'altra risorsa. Potrebbe persino eliminare la risorsa precedente se lo si distribuisce in modalità completa.

    Non è necessario preoccuparsene, dal momento che è solo un esempio.

  2. Il nome della risorsa del server logico SQL viene impostato usando una variabile, anche se è necessario un nome univoco globale:

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

    Come è possibile risolvere il problema?

Aggiornare le dipendenze e le risorse figlio

  1. Ecco una delle risorse, che include una proprietà dependsOn. È davvero necessaria?

    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. Si noti come queste risorse figlio vengono dichiarate nel modello:

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

    Come è possibile modificare il modo in cui queste risorse vengono dichiarate? Nel modello sono presenti anche altre risorse che devono essere aggiornate?

Aggiornare i valori delle proprietà

  1. Esaminare le proprietà della risorsa database 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
      }
    }
    

    Ha senso impostare come hardcoded il valore della proprietà name dello SKU? E che cosa sono quegli strani valori delle proprietà collation e maxSizeBytes?

    Suggerimento

    Le proprietà collation e maxSizeBytes vengono impostate sui valori predefiniti. Se non si specificano i valori manualmente, verranno usati i valori predefiniti. Questo è utile per decidere come procedere in proposito?

  2. È possibile modificare la modalità di impostazione della stringa di connessione di archiviazione in modo che l'espressione complessa non sia definita inline con la risorsa?

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

Ordine degli elementi

  1. Si è soddisfatti dell'ordine degli elementi nel file? Come si può migliorare la leggibilità del file spostando gli elementi?

  2. Esaminare la variabile databaseName. Va bene dove si trova ora?

    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. Si è notato che la risorsa webSiteConnectionStrings è stata impostata come commento? Si ritiene che debba essere presente nel file?

Aggiungere commenti, tag e altri metadati

Si pensi a qualsiasi elemento del modello che potrebbe non risultare ovvio o richiedere una spiegazione aggiuntiva. È possibile aggiungere commenti per renderlo più chiaro per gli altri utenti che potrebbero aprire il file in futuro?

  1. Esaminare la proprietà identity della risorsa webSite:

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

    Non è insolita questa sintassi? Si ritiene che sia necessario spiegarla con un commento?

  2. Esaminare la risorsa assegnazione di ruolo:

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

    Il nome della risorsa usa la funzione guid(). Può essere utile spiegare perché?

  3. È possibile aggiungere una descrizione all'assegnazione di ruolo?

  4. È possibile aggiungere un set di tag a ogni risorsa?

Soluzione suggerita

Ecco un esempio di come è possibile effettuare il refactoring del modello. Il modello potrebbe non essere esattamente come il seguente, perché lo stile potrebbe essere diverso.

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

Suggerimento

Se si lavora con i colleghi usando GitHub o Azure Repos, questo è il momento ideale per inviare una richiesta pull per integrare le modifiche nel ramo principale. È consigliabile inviare le richieste pull dopo un'operazione di refactoring.