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

완료됨

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

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

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

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

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

  1. Visual Studio Code를 엽니다.

  2. deploy/pipeline-templates 폴더에서 build.yml 새 파일을 만듭니다.

    파이프라인 템플릿 폴더와 'build.yml' 파일이 표시된 Visual Studio Code Explorer의 스크린샷

  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. 배포 폴더에서 azure-pipelines.yml 파일을 엽니다.

  2. Lint 단계를 수정합니다. 이름을 빌드로 바꾸고 만든 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. 가장 최근에 실행된 파이프라인을 선택하십시오.

    파이프라인 실행 목록을 보여 주는 Azure DevOps의 스크린샷 최신 파이프라인 실행이 강조 표시됩니다.

    빌드 단계가 성공적으로 완료될 때까지 기다립니다.

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

  3. 보기를 선택합니다.

    유효성 검사 단계에서 일시 중지된 파이프라인 실행을 보여 주는 Azure DevOps의 스크린샷 계속하려면 권한이 필요합니다. 보기 단추가 강조 표시됩니다.

  4. 허용을 선택합니다.

    ToyWebsiteTest 변수 그룹을 사용할 수 있는 권한이 파이프라인에 필요하다는 것을 보여 주는 Azure DevOps의 스크린샷. 허용 단추가 강조 표시됩니다.

  5. 허용을 선택합니다.

    권한 확인 인터페이스를 보여 주는 Azure DevOps의 스크린샷 허용 단추가 강조 표시됩니다.

    유효성 검사(테스트 환경) 단계가 성공적으로 완료됩니다.

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

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

    테스트 환경에 대한 파이프라인 실행의 스모크 테스트 단계를 보여 주는 Azure DevOps의 스크린샷 상태는 스테이지가 실패했음을 보여 줍니다.

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

    스모크 테스트의 출력이 표시된 파이프라인 실행 로그를 보여 주는 Azure DevOps의 스크린샷. JSON 상태 테스트 결과가 강조 표시됩니다.

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