연습 - 웹 애플리케이션 배포

완료됨

완구 회사 웹 사이트 개발 팀은 최신 버전의 웹 사이트를 Git 리포지토리에 커밋했습니다. 이제 파이프라인을 업데이트하여 웹 사이트를 빌드하고 Azure App Service에 배포할 수 있습니다.

이 프로세스에서는 다음 작업을 수행합니다.

  • 빌드 작업에 대한 새 파이프라인 템플릿을 추가합니다.
  • 빌드 작업을 포함하도록 파이프라인을 업데이트합니다.
  • 새 스모크 테스트를 추가합니다.
  • 배포 스테이지를 업데이트하여 애플리케이션을 배포합니다.
  • 파이프라인을 실행합니다.

빌드 작업에 대한 파이프라인 템플릿 추가

웹 사이트 애플리케이션을 빌드하는 데 필요한 단계를 포함하는 새 작업 정의를 추가합니다.

  1. Visual Studio Code를 엽니다.

  2. ‘deploy/pipeline-templates’ 폴더에 ‘build.yml’이라는 새 파일을 만듭니다.

    Screenshot of Visual Studio Code Explorer, with the pipeline-templates folder and the 'build.yml' file shown.

  3. ‘build.yml’ 파이프라인 템플릿 파일에 다음 내용을 추가합니다.

    jobs:
    - job: Build
      displayName: Build application and database
      pool:
        vmImage: windows-latest
    
      steps:
    
      # Build, copy, and publish the website.
      - task: DotNetCoreCLI@2
        displayName: Build publishable website
        inputs:
          command: 'publish'
          publishWebProjects: true
    
      - task: CopyFiles@2
        displayName: Copy publishable website
        inputs:
          sourceFolder: '$(Build.SourcesDirectory)/src/ToyCompany/ToyCompany.Website/bin'
          contents: '**/publish.zip'
          targetFolder: '$(Build.ArtifactStagingDirectory)/website'
          flattenFolders: true
    
      - task: PublishBuildArtifacts@1
        displayName: Publish website as pipeline artifact
        inputs:
          pathToPublish: '$(Build.ArtifactStagingDirectory)/website'
          artifactName: 'website'
    

    작업은 빌드 단계를 실행하여 웹 사이트 애플리케이션의 소스 코드를 Azure에서 실행할 준비가 된 컴파일된 파일로 전환합니다. 그런 다음 컴파일된 아티팩트를 임시 준비 폴더에 복사하고 파이프라인 아티팩트로 게시합니다.

  4. 파일의 변경 내용을 저장합니다.

첫 번째 파이프라인 스테이지의 이름을 변경하고 빌드 작업 추가

  1. ‘deploy’ 폴더에서 ‘azure-pipelines.yml’ 파일을 엽니다.

  2. ‘린트’ 스테이지를 수정합니다. 이름을 Build로 변경하고 만든 build.yml 파이프라인 템플릿을 사용하는 빌드 작업을 추가합니다.

    trigger:
      batch: true
      branches:
        include:
        - main
    
    pool:
      vmImage: ubuntu-latest
    
    stages:
    
    - stage: Build
      jobs:
      # Build the Visual Studio solution.
      - template: pipeline-templates/build.yml
    
      # Lint the Bicep file.
      - template: pipeline-templates/lint.yml
    
    # Deploy to the test environment.
    - template: pipeline-templates/deploy.yml
      parameters:
        environmentType: Test
    
    # Deploy to the production environment.
    - template: pipeline-templates/deploy.yml
      parameters:
        environmentType: Production
    
  3. 파일의 변경 내용을 저장합니다.

스모크 테스트 파일 업데이트

웹 사이트 개발자는 웹 사이트에 상태 엔드포인트를 추가했습니다. 이 엔드포인트는 웹 사이트가 온라인 상태이고 데이터베이스에 연결할 수 있는지 확인합니다. 여기서는 배포 파이프라인에서 상태 검사를 호출하는 새 스모크 테스트를 추가합니다.

  1. ‘deploy’ 폴더에서 ‘Website.Tests.ps1’ 파일을 엽니다.

  2. 상태 검사를 호출하는 새 테스트 사례를 추가합니다. 응답 코드가 성공을 나타내는 200이 아닌 경우 테스트 사례가 실패합니다.

    param(
      [Parameter(Mandatory)]
      [ValidateNotNullOrEmpty()]
      [string] $HostName
    )
    
    Describe 'Toy Website' {
    
        It 'Serves pages over HTTPS' {
          $request = [System.Net.WebRequest]::Create("https://$HostName/")
          $request.AllowAutoRedirect = $false
          $request.GetResponse().StatusCode |
            Should -Be 200 -Because "the website requires HTTPS"
        }
    
        It 'Does not serves pages over HTTP' {
          $request = [System.Net.WebRequest]::Create("http://$HostName/")
          $request.AllowAutoRedirect = $false
          $request.GetResponse().StatusCode | 
            Should -BeGreaterOrEqual 300 -Because "HTTP is not secure"
        }
    
        It 'Returns a success code from the health check endpoint' {
          $response = Invoke-WebRequest -Uri "https://$HostName/health" -SkipHttpErrorCheck
          Write-Host $response.Content
          $response.StatusCode |
            Should -Be 200 -Because "the website and configuration should be healthy"
        }
    
    }
    
  3. 파일의 변경 내용을 저장합니다.

Bicep 파일에 출력 추가

웹 사이트를 Azure App Service에 게시하는 배포 단계를 추가하려고 하지만 게시 단계에는 App Service 앱의 이름이 필요합니다. 여기서는 앱 이름을 Bicep 파일의 출력으로 노출합니다.

  1. ‘deploy’ 폴더에서 ‘main.bicep’ 파일을 엽니다.

  2. 파일 내용의 끝에 App Service 앱의 이름을 출력으로 추가합니다.

    output appServiceAppName string = appServiceApp.name
    output appServiceAppHostName string = appServiceApp.properties.defaultHostName
    
  3. 파일의 변경 내용을 저장합니다.

배포 스테이지 업데이트

  1. ‘deploy/pipeline-templates’ 폴더에서 ‘deploy.yml’ 파일을 엽니다.

  2. 배포 스테이지의 배포 작업 정의(59행 근처)에서 Windows 호스트된 에이전트 풀을 사용하도록 작업을 구성합니다.

    - stage: Deploy_${{parameters.environmentType}}
      displayName: Deploy (${{parameters.environmentType}} Environment)
      jobs:
      - deployment: DeployWebsite
        displayName: Deploy website
        pool:
          vmImage: windows-latest
        variables:
        - group: ToyWebsite${{parameters.environmentType}}
        environment: ${{parameters.environmentType}}
        strategy:
    

    나중에 데이터베이스 작업을 위해 추가하는 파이프라인 단계 중 일부를 실행하려면 Windows 운영 체제가 필요합니다. 파이프라인 내 여러 작업에 대해 여러 에이전트 풀을 사용할 수 있으므로 다른 작업은 Ubuntu Linux 파이프라인 에이전트 풀을 계속 사용합니다.

  3. 배포 작업의 SaveDeploymentOutputs 단계에서 Bicep 배포 출력의 앱 이름 값으로 새 파이프라인 변수를 추가합니다.

    - bash: |
        echo "##vso[task.setvariable variable=appServiceAppName]$(echo $DEPLOYMENT_OUTPUTS | jq -r '.appServiceAppName.value')"
        echo "##vso[task.setvariable variable=appServiceAppHostName;isOutput=true]$(echo $DEPLOYMENT_OUTPUTS | jq -r '.appServiceAppHostName.value')"
      name: SaveDeploymentOutputs
      displayName: Save deployment outputs into variables
      env:
        DEPLOYMENT_OUTPUTS: $(deploymentOutputs)
    

    스모크 테스트 스테이지에서 appServiceAppHostName 변수가 사용되므로 이 변수에는 isOutput=true 속성이 적용되어 있습니다. appServiceAppName 변수는 동일한 파이프라인 단계 및 작업에서 설정되고 사용됩니다. 따라서 isOutput=true 설정이 필요하지 않습니다.

  4. ‘배포’ 작업 내용 마지막 부분에 새 단계를 추가하여 Azure App Service에 앱을 배포합니다.

    steps:
      - checkout: self
    
      - task: AzureResourceManagerTemplateDeployment@3
        name: DeployBicepFile
        displayName: Deploy Bicep file
        inputs:
          connectedServiceName: ToyWebsite${{parameters.environmentType}}
          deploymentName: $(Build.BuildNumber)
          location: ${{parameters.deploymentDefaultLocation}}
          resourceGroupName: $(ResourceGroupName)
          csmFile: deploy/main.bicep
          overrideParameters: >
            -environmentType $(EnvironmentType)
            -reviewApiUrl $(ReviewApiUrl)
            -reviewApiKey $(ReviewApiKey)
          deploymentOutputs: deploymentOutputs
    
      - bash: |
          echo "##vso[task.setvariable variable=appServiceAppName]$(echo $DEPLOYMENT_OUTPUTS | jq -r '.appServiceAppName.value')"
          echo "##vso[task.setvariable variable=appServiceAppHostName;isOutput=true]$(echo $DEPLOYMENT_OUTPUTS | jq -r '.appServiceAppHostName.value')"
        name: SaveDeploymentOutputs
        displayName: Save deployment outputs into variables
        env:
          DEPLOYMENT_OUTPUTS: $(deploymentOutputs)
    
      - task: AzureRmWebAppDeployment@4
        name: DeployWebsiteApp
        displayName: Deploy website
        inputs:
          appType: webApp
          ConnectionType: AzureRM
          azureSubscription: ToyWebsite${{parameters.environmentType}}
          ResourceGroupName: $(ResourceGroupName)
          WebAppName: $(appServiceAppName)
          Package: '$(Pipeline.Workspace)/website/publish.zip'
    

    참고

    YAML 파일의 들여쓰기에 유의하여 새 배포 단계가 DeployBicepFile 단계와 동일한 수준으로 들여쓰기되도록 해야 합니다. 확실하지 않은 경우 다음 단계의 예제에서 전체 ‘deploy.yml’ 파일 내용을 복사합니다.

    파이프라인 정의에서 아티팩트를 명시적으로 다운로드하지 않았습니다. 배포 작업을 사용하므로 Azure Pipelines는 자동으로 아티팩트를 다운로드합니다.

deploy.yml 파일 내용을 확인하고 변경 내용을 커밋합니다.

  1. deploy.yml 파일이 다음 예제와 같이 표시되는지 확인합니다.

    parameters:
    - name: environmentType
      type: string
    - name: deploymentDefaultLocation
      type: string
      default: westus3
    
    stages:
    
    - ${{ if ne(parameters.environmentType, 'Production') }}:
      - stage: Validate_${{parameters.environmentType}}
        displayName: Validate (${{parameters.environmentType}} Environment)
        jobs:
        - job: ValidateBicepCode
          displayName: Validate Bicep code
          variables:
          - group: ToyWebsite${{parameters.environmentType}}
          steps:
            - task: AzureResourceManagerTemplateDeployment@3
              name: RunPreflightValidation
              displayName: Run preflight validation
              inputs:
                connectedServiceName: ToyWebsite${{parameters.environmentType}}
                location: ${{parameters.deploymentDefaultLocation}}
                deploymentMode: Validation
                resourceGroupName: $(ResourceGroupName)
                csmFile: deploy/main.bicep
                overrideParameters: >
                  -environmentType $(EnvironmentType)
                  -reviewApiUrl $(ReviewApiUrl)
                  -reviewApiKey $(ReviewApiKey)
    
    - ${{ if eq(parameters.environmentType, 'Production') }}:
      - stage: Preview_${{parameters.environmentType}}
        displayName: Preview (${{parameters.environmentType}} Environment)
        jobs:
        - job: PreviewAzureChanges
          displayName: Preview Azure changes
          variables:
          - group: ToyWebsite${{parameters.environmentType}}
          steps:
            - task: AzureCLI@2
              name: RunWhatIf
              displayName: Run what-if
              inputs:
                azureSubscription: ToyWebsite${{parameters.environmentType}}
                scriptType: 'bash'
                scriptLocation: 'inlineScript'
                inlineScript: |
                  az deployment group what-if \
                    --resource-group $(ResourceGroupName) \
                    --template-file deploy/main.bicep \
                    --parameters environmentType=$(EnvironmentType) \
                                 reviewApiUrl=$(ReviewApiUrl) \
                                 reviewApiKey=$(ReviewApiKey)
    
    - stage: Deploy_${{parameters.environmentType}}
      displayName: Deploy (${{parameters.environmentType}} Environment)
      jobs:
      - deployment: DeployWebsite
        displayName: Deploy website
        pool:
          vmImage: windows-latest
        variables:
        - group: ToyWebsite${{parameters.environmentType}}
        environment: ${{parameters.environmentType}}
        strategy:
          runOnce:
            deploy:
              steps:
                - checkout: self
    
                - task: AzureResourceManagerTemplateDeployment@3
                  name: DeployBicepFile
                  displayName: Deploy Bicep file
                  inputs:
                    connectedServiceName: ToyWebsite${{parameters.environmentType}}
                    deploymentName: $(Build.BuildNumber)
                    location: ${{parameters.deploymentDefaultLocation}}
                    resourceGroupName: $(ResourceGroupName)
                    csmFile: deploy/main.bicep
                    overrideParameters: >
                      -environmentType $(EnvironmentType)
                      -reviewApiUrl $(ReviewApiUrl)
                      -reviewApiKey $(ReviewApiKey)
                    deploymentOutputs: deploymentOutputs
    
                - bash: |
                    echo "##vso[task.setvariable variable=appServiceAppName]$(echo $DEPLOYMENT_OUTPUTS | jq -r '.appServiceAppName.value')"
                    echo "##vso[task.setvariable variable=appServiceAppHostName;isOutput=true]$(echo $DEPLOYMENT_OUTPUTS | jq -r '.appServiceAppHostName.value')"
                  name: SaveDeploymentOutputs
                  displayName: Save deployment outputs into variables
                  env:
                    DEPLOYMENT_OUTPUTS: $(deploymentOutputs)
    
                - task: AzureRmWebAppDeployment@4
                  name: DeployWebsiteApp
                  displayName: Deploy website
                  inputs:
                    appType: webApp
                    ConnectionType: AzureRM
                    azureSubscription: ToyWebsite${{parameters.environmentType}}
                    ResourceGroupName: $(ResourceGroupName)
                    WebAppName: $(appServiceAppName)
                    Package: '$(Pipeline.Workspace)/website/publish.zip'
    
    - stage: SmokeTest_${{parameters.environmentType}}
      displayName: Smoke Test (${{parameters.environmentType}} Environment)
      jobs:
      - job: SmokeTest
        displayName: Smoke test
        variables:
          appServiceAppHostName: $[ stageDependencies.Deploy_${{parameters.environmentType}}.DeployWebsite.outputs['DeployWebsite.SaveDeploymentOutputs.appServiceAppHostName'] ]
        steps:
          - task: PowerShell@2
            name: RunSmokeTests
            displayName: Run smoke tests
            inputs:
              targetType: inline
              script: |
                $container = New-PesterContainer `
                  -Path 'deploy/Website.Tests.ps1' `
                  -Data @{ HostName = '$(appServiceAppHostName)' }
                Invoke-Pester `
                  -Container $container `
                  -CI
    
          - task: PublishTestResults@2
            name: PublishTestResults
            displayName: Publish test results
            condition: always()
            inputs:
              testResultsFormat: NUnit
              testResultsFiles: 'testResults.xml'
    
  2. 파일의 변경 내용을 저장합니다.

  3. Visual Studio Code 터미널에서 다음 명령을 실행하여 변경 내용을 커밋하고 Git 리포지토리에 푸시합니다.

    git add .
    git commit -m "Build and deploy website application"
    git push
    

파이프라인 실행

  1. 브라우저에서 Pipelines로 이동합니다.

  2. 가장 최근에 실행한 파이프라인을 선택합니다.

    Screenshot of Azure DevOps showing the pipeline run list. The latest pipeline run is highlighted.

    ‘빌드’ 스테이지가 성공적으로 완료될 때까지 기다립니다.

    파이프라인은 단계가 참조하는 변수 그룹을 사용할 수 있는 권한이 필요하므로 유효성 검사(테스트 환경) 단계를 실행하기 전에 일시 중지됩니다. 이 프로젝트에서 처음으로 파이프라인을 실행하기 때문에 변수 그룹에 대한 파이프라인의 액세스를 승인해야 합니다. 파이프라인을 다시 실행하면 동일한 변수 그룹에 대한 액세스를 승인할 필요가 없습니다.

  3. 보기를 선택합니다.

    Screenshot of Azure DevOps showing the pipeline run paused at the Validate stage. Permission is required to continue. The View button is highlighted.

  4. 허용을 선택합니다.

    Screenshot of Azure DevOps showing that the pipeline needs permission to use the ToyWebsiteTest variable group. The Permit button is highlighted.

  5. 허용을 선택합니다.

    Screenshot of Azure DevOps showing the permission confirmation interface. The Permit button is highlighted.

    ‘유효성 검사(테스트 환경)’ 스테이지가 성공적으로 완료됩니다.

    파이프라인이 계속되고 배포(테스트 환경) 단계가 성공적으로 완료됩니다. 그런 다음, 파이프라인은 스모크 테스트(테스트 환경) 스테이지를 실행하지만 스모크 테스트 스테이지가 실패합니다.

  6. 스모크 테스트(테스트 환경) 스테이지를 선택하여 파이프라인 로그를 엽니다.

    Screenshot of Azure DevOps showing the pipeline run's Smoke Test stage for the test environment. The status shows that the stage failed.

  7. 스모크 테스트 실행 단계를 선택하여 연결된 파이프라인 로그 섹션을 확인합니다.

    Screenshot of Azure DevOps showing the pipeline run log, with the output of the smoke test displayed. The JSON health test result is highlighted.

    파이프라인 로그에는 상태 검사 응답이 포함됩니다. 응답은 애플리케이션과 Azure SQL Database 간 통신에 문제가 있음을 나타냅니다. 데이터베이스가 아직 배포 또는 구성되지 않았으므로 웹 사이트에서 데이터베이스에 액세스할 수 없습니다. 다음 연습에서는 이 구성 문제를 해결합니다.