練習 - 部署 Web 應用程式

已完成

在您的玩具公司,您的網站開發小組已向您的 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. 修改 Lint 階段。 將之重新命名為 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 服務的部署步驟,但發佈步驟需要 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. 在 [Deploy] \(部署\) 階段部署作業的定義中 (接近第 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. Deploy 作業的 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. 在您的瀏覽器中,移至 [管線]

  2. 選取管線的最近一次執行。

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

    等候 [Build] \(建置\) 階段順利完成。

    管線在執行驗證 (測試環境)階段之前暫停,因為管線需要權限才能使用階段所參考的變數群組。 您必須核准管線對變數群組的存取權,因為這是您第一次在此專案中執行管線。 當您再次執行管線時,您便不需要核准相同變數群組的存取權。

  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.

    [Validate (Test Environment)] \(驗證 (測試環境)\) 階段已順利完成。

    管線會繼續,且部署 (測試環境) 階段順利完成。 管線接著會執行 [Smoke Test (Test Environment)] \(煙霧測試 (測試環境)\) 階段,但是煙霧測試階段會失敗。

  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. 選取 [Run smoke tests] \(執行煙霧測試\) 步驟,以檢視管線記錄的相關聯區段。

    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 的通訊發生問題。 資料庫尚未部署或設定,因此無法供網站存取。 在下一個練習中,您會修正此設定問題。