Exercício - Refatore seu arquivo Bicep

Concluído

Depois de revisar seu modelo com seus colegas, você decide refatorar o arquivo para facilitar o trabalho com eles. Neste exercício, você aplica as melhores práticas aprendidas nas unidades anteriores.

A sua tarefa

Revise o modelo Bicep que você salvou anteriormente. Pense nos conselhos que leu sobre como estruturar os seus modelos. Tente atualizar seu modelo para facilitar a compreensão de seus colegas.

Nas próximas seções, há algumas dicas para partes específicas do modelo e algumas dicas sobre coisas que você pode querer alterar. Nós fornecemos uma solução sugerida, mas seu modelo pode parecer diferente, o que é perfeitamente OK!

Gorjeta

À medida que você trabalha no processo de refatoração, é bom garantir que seu arquivo Bicep seja válido e que você não tenha introduzido erros acidentalmente. A extensão Bicep para Visual Studio Code ajuda com isso. Cuidado com quaisquer linhas onduladas vermelhas ou amarelas abaixo do seu código, porque elas indicam um erro ou um aviso. Você também pode exibir uma lista dos problemas em seu arquivo selecionando Exibir>problemas.

Atualizar os parâmetros

  1. Alguns parâmetros no seu modelo não estão claros. Por exemplo, considere estes parâmetros:

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

    Para que são utilizados?

    Gorjeta

    Se você tiver um parâmetro que está tentando entender, o Visual Studio Code pode ajudar. Selecione e segure (ou clique com o botão direito do mouse) um nome de parâmetro em qualquer lugar do arquivo e selecione Localizar todas as referências.

    O modelo precisa especificar a lista de valores permitidos para o skuName parâmetro? Quais recursos são afetados pela escolha de valores diferentes para esses parâmetros? Existem nomes melhores que você pode dar os parâmetros?

    Gorjeta

    Ao renomear identificadores, certifique-se de renomeá-los consistentemente em todas as partes do modelo. Isso é especialmente importante para parâmetros, variáveis e recursos aos quais você se refere em todo o modelo.

    O Visual Studio Code oferece uma maneira conveniente de renomear símbolos: selecione o identificador que você deseja renomear, selecione F2, insira um novo nome e selecione Enter:

    Captura de tela do Visual Studio Code que mostra como renomear um símbolo.

    Essas etapas renomeiam o identificador e atualizam automaticamente todas as referências a ele.

  2. O managedIdentityName parâmetro não tem um valor padrão. Você poderia corrigir isso ou, melhor ainda, criar o nome automaticamente dentro do modelo?

  3. Veja a definição do roleDefinitionId parâmetro:

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

    Por que há um valor padrão de b24988ac-6180-42a0-ab88-20f7382dd24c? O que significa esse identificador longo? Como alguém saberia se deveria usar o valor padrão ou substituí-lo? O que você poderia fazer para melhorar o identificador? Faz mesmo sentido ter isso como parâmetro?

    Gorjeta

    Esse identificador é a ID de definição de função de Colaborador para o Azure. Como você pode usar essas informações para melhorar o modelo?

  4. Quando alguém implanta o modelo, como saberá para que serve cada parâmetro? Você pode adicionar algumas descrições para ajudar os usuários do seu modelo?

Adicionar um conjunto de configurações

  1. Você fala com seus colegas e decide usar SKUs específicas para cada recurso, dependendo do ambiente que está sendo implantado. Você decide sobre estes SKUs para cada um dos seus recursos:

    Recurso SKU para produção SKU para não produção
    Plano do Serviço de Aplicações S1, duas instâncias F1, uma instância
    Conta de armazenamento GRS LRS
    Base de dados SQL S1 Básica
  2. Você pode usar um conjunto de configurações para simplificar as definições de parâmetro?

Atualizar os nomes simbólicos

Dê uma olhada nos nomes simbólicos para os recursos no modelo. O que você poderia fazer para melhorá-los?

  1. Seu modelo Bicep contém recursos com uma variedade de estilos de maiúsculas para seus nomes simbólicos, como:

    • storageAccount e webSite, que usam maiúsculas camelCase.
    • roleassignment e sqlserver, que usam maiúsculas e minúsculas simples.
    • sqlserverName_databaseName e AppInsights_webSiteName, que usam maiúsculas e minúsculas.

    Você pode corrigi-los para usar um estilo de forma consistente?

  2. Veja este recurso de atribuição de função:

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

    O nome simbólico é descritivo o suficiente para ajudar outra pessoa a trabalhar com este modelo?

    Gorjeta

    A razão pela qual a identidade precisa de uma atribuição de função é que o aplicativo Web usa sua identidade gerenciada para se conectar ao servidor de banco de dados. Isso ajuda você a esclarecer isso no modelo?

  3. Alguns recursos têm nomes simbólicos que não refletem os nomes atuais dos recursos do 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' = {
      // ...
    }
    

    As identidades gerenciadas costumavam ser chamadas de MSIs, os planos do Serviço de Aplicativo costumavam ser chamados de planos de hospedagem e os aplicativos do Serviço de Aplicativo costumavam ser chamados de sites.

    Você pode atualizá-los para os nomes mais recentes para evitar confusão no futuro?

Simplifique as definições do contêiner de blob

  1. Veja como os contêineres de blob são definidos:

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

    Um deles usa o parent imóvel e o outro não. Você pode corrigi-los para ser consistente?

  2. Os nomes dos contêineres de blob não serão alterados entre ambientes. Você acha que os nomes precisam ser especificados usando parâmetros?

  3. Existem dois recipientes de blob. Eles poderiam ser implantados usando um loop?

Atualizar os nomes dos recursos

  1. Existem alguns parâmetros que definem explicitamente os nomes dos recursos:

    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'
    

    Existe outra maneira de fazer isso?

    Atenção

    Lembre-se de que os recursos não podem ser renomeados depois de implantados. Ao modificar modelos que já estão em uso, tenha cuidado ao alterar a maneira como o modelo cria nomes de recursos. Se o modelo for reimplantado e o recurso tiver um novo nome, o Azure criará outro recurso. Ele pode até excluir o recurso antigo se você implantá-lo no modo Completo .

    Você não precisa se preocupar com isso aqui, porque é apenas um exemplo.

  2. O nome do recurso do servidor lógico SQL é definido usando uma variável, mesmo que ele precise de um nome globalmente exclusivo:

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

    Como você poderia melhorar isso?

Atualizar dependências e recursos filho

  1. Aqui está um dos seus recursos, que inclui uma dependsOn propriedade. Será que ele realmente precisa disso?

    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. Observe como esses recursos filho são declarados em seu modelo:

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

    Como modificar a forma como esses recursos são declarados? Existem outros recursos no modelo que também devem ser atualizados?

Atualizar valores de propriedade

  1. Dê uma olhada nas propriedades do recurso do banco de dados 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
      }
    }
    

    Faz sentido codificar o valor da propriedade do name SKU? E quais são esses valores estranhos para as collation e maxSizeBytes propriedades?

    Gorjeta

    As collation propriedades e maxSizeBytes são definidas com os valores padrão. Se você mesmo não especificar os valores, os valores padrão serão usados. Isso ajuda-o a decidir o que fazer com eles?

  2. Você pode alterar a maneira como a cadeia de conexão de armazenamento é definida para que a expressão complexa não seja definida em linha com o recurso?

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

Ordem dos elementos

  1. Está satisfeito com a ordem dos elementos no ficheiro? Como você poderia melhorar a legibilidade do arquivo movendo os elementos?

  2. Dê uma olhada na databaseName variável. Pertence onde está agora?

    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. Reparou no recurso comentado, webSiteConnectionStrings? Você acha que isso precisa estar no arquivo?

Adicionar comentários, tags e outros metadados

Pense em qualquer coisa no modelo que possa não ser óbvia ou que precise de explicações adicionais. Você pode adicionar comentários para torná-lo mais claro para outras pessoas que podem abrir o arquivo no futuro?

  1. Dê uma olhada na webSite propriedade do identity recurso:

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

    Essa sintaxe é estranha, não é? Você acha que isso precisa de um comentário para ajudar a explicá-lo?

  2. Observe o recurso de atribuição de função:

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

    O nome do recurso usa a guid() função. Ajudaria a explicar porquê?

  3. Você pode adicionar uma descrição à atribuição de função?

  4. Você pode adicionar um conjunto de tags a cada recurso?

Solução sugerida

Aqui está um exemplo de como você pode refatorar o modelo. Seu modelo pode não ser exatamente assim, porque seu estilo pode ser diferente.

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

Gorjeta

Se você estiver trabalhando com seus colegas usando o GitHub ou o Azure Repos, este seria um ótimo momento para enviar uma solicitação pull para integrar suas alterações na ramificação principal. É uma boa ideia enviar solicitações pull depois de fazer um trabalho de refatoração.