연습 - 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 키를 선택합니다.

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

    이러한 단계를 통해 식별자의 이름이 변경되고 이에 대한 모든 참조가 자동으로 업데이트됩니다.

  2. managedIdentityName 매개 변수에는 기본값이 없습니다. 이를 수정하거나 템플릿 내에서 이름을 자동으로 만들 수 있나요?

  3. roleDefinitionId 매개 변수 정의를 살펴보세요.

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

    b24988ac-6180-42a0-ab88-20f7382dd24c의 기본값이 있는 이유는 무엇인가요? 긴 식별자는 무엇을 의미하나요? 다른 사람이 기본값을 사용할지 아니면 값을 재정의할지를 어떻게 알 수 있나요? 식별자를 개선하기 위해 수행할 수 있는 작업은 무엇인가요? 이것을 매개 변수로 사용하는 것이 적절한가요?

    이 식별자는 Azure에 대한 기여자 역할 정의 ID입니다. 이러한 정보를 사용하여 템플릿을 개선하려면 어떻게 해야 하나요?

  4. 사용자가 템플릿을 배포하는 경우 각 매개 변수가 어떤 용도로 사용되는지 어떻게 알 수 있나요? 템플릿의 사용자에게 도움이 되는 설명을 추가할 수 있나요?

구성 집합 추가

  1. 동료에게 이야기하고 배포되는 환경에 따라 각 리소스에 대해 특정 SKU를 사용하기로 결정합니다. 각 리소스에 대해 다음과 같은 SKU를 결정합니다.

    리소스 프로덕션용 SKU 비프로덕션용 SKU
    App Service 계획 S1, 2개의 인스턴스 F1, 1개의 인스턴스
    스토리지 계정 GRS LRS
    SQL Database S1 Basic
  2. 구성 집합을 사용하여 매개 변수 정의를 단순화할 수 있나요?

심볼 이름 업데이트

템플릿에서 리소스의 심볼 이름을 확인합니다. 이를 개선하기 위해 무엇을 할 수 있나요?

  1. Bicep 템플릿에는 다음과 같은 심볼 이름에 대한 다양한 대문자 표시 스타일을 포함하는 리소스가 있습니다.

    • storageAccountwebSite: 카멜 표기법(camelCase) 대문자를 사용합니다.
    • roleassignmentsqlserver: 단순 대/소문자를 사용합니다.
    • sqlserverName_databaseNameAppInsights_webSiteName: 스네이크 대/소문자를 사용합니다.

    한 가지 스타일을 일관되게 사용하도록 수정할 수 있나요?

  2. 다음 역할 할당 리소스를 확인합니다.

    resource roleassignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = {
      name: guid(roleDefinitionId, resourceGroup().id)
    
      properties: {
        principalType: 'ServicePrincipal'
        roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionId)
        principalId: msi.properties.principalId
      }
    }
    

    다른 사람이 이 템플릿으로 작업하는 데 도움이 될 만큼 충분히 설명하는 심볼 이름인가요?

    ID에 역할 할당이 필요한 이유는 웹앱이 관리 ID를 사용하여 데이터베이스 서버에 연결하기 때문입니다. 템플릿에서 이러한 사실을 명확하게 나타내는 것이 도움이 되나요?

  3. 일부 리소스에 Azure 리소스의 현재 이름을 반영하지 않는 다음과 같은 심볼 이름이 있습니다.

    resource hostingPlan 'Microsoft.Web/serverfarms@2020-06-01' = {
      // ...
    }
    resource webSite 'Microsoft.Web/sites@2020-06-01' = {
      // ...
    }
    resource msi 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = {
      // ...
    }
    

    이전에는 관리 ID를 MSI로, App Service 요금제를 호스팅 플랜로, App Service 앱을 웹 사이트로 지칭했습니다.

    나중에 혼동을 피하기 위해 최신 이름으로 업데이트할 수 있나요?

Blob 컨테이너 정의 간소화

  1. Blob 컨테이너를 정의하는 방법을 살펴보세요.

    resource container1 'Microsoft.Storage/storageAccounts/blobServices/containers@2019-06-01' = {
      parent: storageAccount::blobServices
      name: container1Name
    }
    
    resource productmanuals 'Microsoft.Storage/storageAccounts/blobServices/containers@2019-06-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@2014-04-01' = {
      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@2020-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@2014-04-01' = {
      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@2020-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@2020-06-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@2020-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@2020-06-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@2020-04-01-preview' = {
      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@2019-06-01-preview' = {
  name: sqlServerName
  location: location
  tags: tags
  properties: {
    administratorLogin: sqlServerAdministratorLogin
    administratorLoginPassword: sqlServerAdministratorLoginPassword
    version: '12.0'
  }
}

resource sqlDatabase 'Microsoft.Sql/servers/databases@2020-08-01-preview' = {
  parent: sqlServer
  name: sqlDatabaseName
  location: location
  sku: environmentConfigurationMap[environmentType].sqlDatabase.sku
  tags: tags
}

resource sqlFirewallRuleAllowAllAzureIPs 'Microsoft.Sql/servers/firewallRules@2014-04-01' = {
  parent: sqlServer
  name: 'AllowAllAzureIPs'
  properties: {
    endIpAddress: '0.0.0.0'
    startIpAddress: '0.0.0.0'
  }
}

resource appServicePlan 'Microsoft.Web/serverfarms@2020-06-01' = {
  name: appServicePlanName
  location: location
  sku: environmentConfigurationMap[environmentType].appServicePlan.sku
  tags: tags
}

resource appServiceApp 'Microsoft.Web/sites@2020-06-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@2019-06-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@2018-11-30' = {
  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@2020-04-01-preview' = {
  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@2018-05-01-preview' = {
  name: applicationInsightsName
  location: location
  kind: 'web'
  tags: tags
  properties: {
    Application_Type: 'web'
  }
}

GitHub 또는 Azure Repos를 사용하여 동료와 함께 작업하는 경우 변경 내용을 주 분기에 통합하기 위해 지금 바로 끌어오기 요청을 제출하는 것이 좋습니다. 리팩터링 작업을 수행한 후에 끌어오기 요청을 제출하는 것이 좋습니다.