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

완료됨

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

프로세스에서 다음을 수행합니다.

  • 빌드 작업에 대해 새로 호출된 워크플로를 추가합니다.
  • 빌드 작업을 포함하도록 워크플로를 업데이트합니다.
  • 새 스모크 테스트를 추가합니다.
  • 배포 작업을 업데이트하여 애플리케이션을 배포합니다.
  • 워크플로를 실행합니다.

빌드 작업에 대해 재사용 가능한 워크플로 추가

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

  1. Visual Studio Code를 엽니다.

  2. .github/workflows 폴더에 build.yml이라는 새 파일을 만듭니다.

    Screenshot of Visual Studio Code Explorer, with the dot github and workflows folders and the build dot YML file shown.

  3. build.yml 워크플로 파일에 다음 내용을 추가합니다.

    name: build-website
    
    on:
      workflow_call:
    
    jobs:
      build-application:
        name: Build application
        runs-on: ubuntu-latest
        steps:
        - uses: actions/checkout@v3
    
        - name: Install .NET Core
          uses: actions/setup-dotnet@v3
          with:
            dotnet-version: 3.1
    
        - name: Build publishable website
          run: |
            dotnet publish --configuration Release
          working-directory: ./src/ToyCompany/ToyCompany.Website
    
        - name: Zip publishable website
          run: |
            zip -r publish.zip .
          working-directory: ./src/ToyCompany/ToyCompany.Website/bin/Release/netcoreapp3.1/publish
    
        - name: Upload website as workflow artifact
          uses: actions/upload-artifact@v3
          with:
            name: website
            path: ./src/ToyCompany/ToyCompany.Website/bin/Release/netcoreapp3.1/publish/publish.zip
    

    이 작업은 .NET SDK를 설치하여 솔루션을 빌드합니다. 그런 다음 빌드 단계를 실행하여 웹 사이트 애플리케이션의 소스 코드를 Azure에서 실행할 준비가 된 컴파일된 파일로 전환합니다. 이후 작업은 컴파일된 아티팩트를 압축하고 이를 워크플로 아티팩트로 업로드합니다.

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

워크플로에 빌드 작업 추가

  1. ‘workflow.yml’ 파일을 엽니다.

  2. jobs: 라인 아래 lint 작업 앞에 정의한 재사용 가능한 워크플로를 사용하는 build라는 새 작업을 추가합니다.

    name: deploy-toy-website-end-to-end
    concurrency: toy-company
    
    on:
      push:
        branches:
          - main
      workflow_dispatch:
    
    permissions:
      id-token: write
      contents: read
    
    jobs:
    
      # Build the application and database.
      build:
        uses: ./.github/workflows/build.yml
    
      # Lint the Bicep file.
      lint:
        uses: ./.github/workflows/lint.yml
    
  3. deploy-test 작업을 새로운 build 작업에 종속되도록 업데이트합니다.

    # Deploy to the test environment.
    deploy-test:
      uses: ./.github/workflows/deploy.yml
      needs: [build, lint]
      with:
        environmentType: Test
        resourceGroupName: ToyWebsiteTest
        reviewApiUrl: https://sandbox.contoso.com/reviews
      secrets:
        AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID_TEST }}
        AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
        AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
        reviewApiKey: ${{ secrets.REVIEW_API_KEY_TEST }}
    
  4. deploy-production 작업 또한 buildlint 작업에 종속되도록 업데이트합니다.

    # Deploy to the production environment.
    deploy-production:
      uses: ./.github/workflows/deploy.yml
      needs:
      - lint
      - build
      - deploy-test
      with:
        environmentType: Production
        resourceGroupName: ToyWebsiteProduction
        reviewApiUrl: https://api.contoso.com/reviews
      secrets:
        AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID_PRODUCTION }}
        AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
        AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
        reviewApiKey: ${{ secrets.REVIEW_API_KEY_PRODUCTION }}
    

    프로덕션 배포는 테스트 배포에 따라 달라지므로 반드시 종속성을 지정할 필요는 없습니다. 하지만 작업 또는 환경을 다시 정렬하거나 제거하는 경우 워크플로가 잘못 실행되지 않도록 명시적으로 하는 것이 좋습니다.

    두 가지 방법으로 needs 목록을 지정합니다. 테스트 환경 배포의 종속성이 단일 줄에 나열되고, 프로덕션 환경은 여러 줄 목록을 사용하여 나열됩니다. 두 가지 방법은 동일합니다.

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

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

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

  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. 파일의 변경 내용을 저장합니다.

출력을 전파하도록 배포 작업 업데이트

이제 Bicep 배포의 출력 값을 받아서 나머지 워크플로에서 사용할 수 있도록 deploy 작업을 업데이트해야 합니다.

  1. .github/workflows 폴더에서 deploy.yml 파일을 엽니다.

  2. deploy 작업의 정의에서 appServiceAppName에 대한 새 출력을 추가합니다.

    deploy:
      needs: validate
      environment: ${{ inputs.environmentType }}
      runs-on: ubuntu-latest
      outputs:
        appServiceAppName: ${{ steps.deploy.outputs.appServiceAppName }}
        appServiceAppHostName: ${{ steps.deploy.outputs.appServiceAppHostName }}
      steps:
    

    참고

    Visual Studio Code 내 YAML 파일 작업을 시작할 때 문제가 있음을 알리는 빨간색 물결선이 표시될 수 있습니다. YAML 파일의 Visual Studio Code 확장이 파일의 스키마를 잘못 추측하기 때문입니다.

    확장에서 보고하는 문제를 무시할 수 있습니다. 또는 원하는 경우 파일 상단에 다음 코드를 추가하여 확장의 추측을 억제할 수 있습니다.

    # yaml-language-server: $schema=./deploy.yml
    

웹 사이트를 배포하는 작업 추가

  1. deploy 작업 정의 아래와 smoke-test 작업 정의 위에서 웹 사이트를 App Service에 배포할 새 작업을 정의합니다.

    deploy-website:
      needs: deploy
      environment: ${{ inputs.environmentType }}
      runs-on: ubuntu-latest
      steps:
      - uses: actions/download-artifact@v3
      - uses: azure/login@v1
        name: Sign in to Azure
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
      - uses: azure/webapps-deploy@v2
        name: Deploy website
        with:
          app-name: ${{ needs.deploy.outputs.appServiceAppName }}
          package: website/publish.zip
    

    참고

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

    작업은 needs 키워드를 사용하는 deploy 작업에 따라 달라집니다. 이 종속성이 유지되면 인프라가 준비될 때까지 웹 사이트가 배포되지 않습니다. 또한 작업에서 deploy 작업의 appServiceAppName 출력에 액세스할 수 있습니다.

    또한 이 작업에는 워크플로 아티팩트 다운로드 및 Azure에 로그인하는 단계가 포함됩니다. 각 작업은 자체 실행기에서 실행되므로 자체 포함되어야 합니다.

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

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

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

    name: deploy
    
    on:
      workflow_call:
        inputs:
          environmentType:
            required: true
            type: string
          resourceGroupName:
            required: true
            type: string
          reviewApiUrl:
            required: true
            type: string
        secrets:
          AZURE_CLIENT_ID:
            required: true
          AZURE_TENANT_ID:
            required: true
          AZURE_SUBSCRIPTION_ID:
            required: true
          reviewApiKey:
            required: true
    
    jobs:
      validate:
         runs-on: ubuntu-latest
         steps:
         - uses: actions/checkout@v3
         - uses: azure/login@v1
           name: Sign in to Azure
           with:
             client-id: ${{ secrets.AZURE_CLIENT_ID }}
             tenant-id: ${{ secrets.AZURE_TENANT_ID }}
             subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
         - if: inputs.environmentType != 'Production'
           uses: azure/arm-deploy@v1
           name: Run preflight validation
           with:
             deploymentName: ${{ github.run_number }}
             resourceGroupName: ${{ inputs.resourceGroupName }}
             template: ./deploy/main.bicep
             parameters: >
               environmentType=${{ inputs.environmentType }}
               reviewApiUrl=${{ inputs.reviewApiUrl }}
               reviewApiKey=${{ secrets.reviewApiKey }}
             deploymentMode: Validate
         - if: inputs.environmentType == 'Production'
           uses: azure/arm-deploy@v1
           name: Run what-if
           with:
             failOnStdErr: false
             resourceGroupName: ${{ inputs.resourceGroupName }}
             template: ./deploy/main.bicep
             parameters: >
               environmentType=${{ inputs.environmentType }}
               reviewApiUrl=${{ inputs.reviewApiUrl }}
               reviewApiKey=${{ secrets.reviewApiKey }}
             additionalArguments: --what-if
    
      deploy:
        needs: validate
        environment: ${{ inputs.environmentType }}
        runs-on: ubuntu-latest
        outputs:
          appServiceAppName: ${{ steps.deploy.outputs.appServiceAppName }}
          appServiceAppHostName: ${{ steps.deploy.outputs.appServiceAppHostName }}
        steps:
        - uses: actions/checkout@v3
        - uses: azure/login@v1
          name: Sign in to Azure
          with:
            client-id: ${{ secrets.AZURE_CLIENT_ID }}
            tenant-id: ${{ secrets.AZURE_TENANT_ID }}
            subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
        - uses: azure/arm-deploy@v1
          id: deploy
          name: Deploy Bicep file
          with:
            failOnStdErr: false
            deploymentName: ${{ github.run_number }}
            resourceGroupName: ${{ inputs.resourceGroupName }}
            template: ./deploy/main.bicep
            parameters: >
               environmentType=${{ inputs.environmentType }}
               reviewApiUrl=${{ inputs.reviewApiUrl }}
               reviewApiKey=${{ secrets.reviewApiKey }}
    
      deploy-website:
        needs: deploy
        environment: ${{ inputs.environmentType }}
        runs-on: ubuntu-latest
        steps:
        - uses: actions/download-artifact@v3
        - uses: azure/login@v1
          name: Sign in to Azure
          with:
            client-id: ${{ secrets.AZURE_CLIENT_ID }}
            tenant-id: ${{ secrets.AZURE_TENANT_ID }}
            subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
        - uses: azure/webapps-deploy@v2
          name: Deploy website
          with:
            app-name: ${{ needs.deploy.outputs.appServiceAppName }}
            package: website/publish.zip
    
      smoke-test:
        runs-on: ubuntu-latest
        needs: deploy
        steps:
        - uses: actions/checkout@v3
        - run: |
            $container = New-PesterContainer `
              -Path 'deploy/Website.Tests.ps1' `
              -Data @{ HostName = '${{needs.deploy.outputs.appServiceAppHostName}}' }
            Invoke-Pester `
              -Container $container `
              -CI
          name: Run smoke tests
          shell: pwsh
    
  2. 파일의 변경 내용을 저장합니다.

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

    git add .
    git commit -m "Build and deploy website application"
    git push
    
  4. 이 리포지토리에 처음 푸시하는 것이므로 로그인하라는 메시지가 표시됩니다.

    Windows에서는 1을 입력하여 웹 브라우저로 인증하고 Enter를 선택합니다.

    macOS에서는 권한 부여를 선택합니다.

  5. 브라우저 창이 표시됩니다. GitHub에 다시 로그인해야 할 수도 있습니다. 권한 부여를 선택합니다.

워크플로 실행

  1. 브라우저에서 작업으로 이동합니다.

    ‘초기 커밋’이라는 레이블이 지정된 워크플로의 첫 번째 실행은 실패로 표시됩니다. GitHub는 리포지토리를 만들 때 워크플로를 자동으로 실행했습니다. 암호는 해당 시점에 준비되지 않았기 때문에 실패했습니다. 이 오류는 무시해도 됩니다.

  2. deploy-toy-website-end-to-end 워크플로를 선택합니다.

  3. 가장 최근에 실행한 워크플로를 선택합니다.

  4. build 작업이 성공적으로 완료될 때까지 기다립니다.

    Screenshot of GitHub that shows the workflow run jobs.

  5. deploy-test / deploy 작업이 성공적으로 완료되기를 기다립니다.

    일부 경고는 주석 패널에 나열됩니다. 이러한 경고는 Bicep가 워크플로 로그에 정보 메시지를 쓰는 방식 때문에 발생합니다. 이러한 경고는 무시하면 됩니다.

  6. 그런 다음 워크플로는 deploy-test / smoke-test 작업을 실행하지만 스모크 테스트는 실패합니다.

    Screenshot of GitHub that shows the workflow run's smoke test job for the test environment. The status shows that the job has failed.

  7. deploy-test / smoke-test 작업을 선택하여 워크플로 로그를 엽니다.

  8. 스모크 테스트 실행 단계를 선택하여 연결된 워크플로 로그 섹션을 확인합니다.

    Screenshot of GitHub showing the workflow run log, with the output of the smoke test displayed. The JSON health test result is highlighted.

    워크플로 로그에서 웹 사이트 및 구성이 정상이 아님을 나타냅니다. 애플리케이션과 Azure SQL Database 간 통신에 문제가 있습니다. 데이터베이스를 아직 배포하거나 구성하지 않았기 때문에 웹 사이트가 데이터베이스에 액세스할 수 없습니다. 이 문제는 곧 해결할 예정입니다.