练习 - 部署 Web 应用程序

已完成

在玩具公司,网站开发团队将最新版本的网站提交到 Git 存储库。 现在,可以更新管道以生成网站,并将其部署到 Azure 应用服务。

在此过程中,将执行以下任务:

  • 为构建任务添加新的管道模板。
  • 更新管道以包含生成作业。
  • 添加新的冒烟测试。
  • 更新部署阶段以部署应用程序。
  • 运行管道。

为构建任务添加管道模板

添加一个新的作业定义,其中包含生成网站应用程序所需的步骤。

  1. 打开 Visual Studio Code。

  2. deploy/pipeline-templates 文件夹中,创建名为 build.yml的新文件。

    Visual Studio Code Explorer 的屏幕截图,其中显示了管道模板文件夹和“build.yml”文件。

  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 应用服务的部署步骤,但发布步骤需要应用服务应用的名称。 在此处,你将应用名称公开为 Bicep 文件的输出。

  1. 打开 deploy 文件夹中的 main.bicep 文件。

  2. 在文件内容的末尾,添加应用服务应用的名称作为输出。

    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 应用服务的新步骤:

    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. 选择管道的最新运行。

    Azure DevOps 的屏幕截图,其中显示了管道运行列表。突出显示了最新的管道运行。

    等待 生成 阶段成功完成。

    管道在运行 “验证”(测试环境) 阶段之前暂停,因为管道需要权限才能使用该阶段引用的变量组。 由于这是您在此项目中首次运行管道,因此需要您批准管道访问变量组的权限。 再次运行管道时,无需批准对同一变量组的访问权限。

  3. 选择“视图”

    Azure DevOps 的屏幕截图,其中显示了在“验证”阶段暂停的管道运行。需要权限才能继续。突出显示了“视图”按钮。

  4. 选择 “允许”。

    Azure DevOps 的屏幕截图,其中显示了管道需要有权使用 ToyWebsiteTest 变量组。突出显示“允许”按钮。

  5. 选择 “允许”。

    Azure DevOps 的屏幕截图,其中显示了权限确认界面。突出显示“允许”按钮。

    “验证”(测试环境)阶段成功完成。

    管道继续, 部署(测试环境) 阶段成功完成。 然后,管道运行 冒烟测试(测试环境) 阶段,但冒烟测试阶段失败。

  6. 选择 冒烟测试(测试环境) 阶段以打开管道日志。

    Azure DevOps 屏幕截图,显示了测试环境中管道运行的冒烟测试阶段。状态显示该阶段失败。

  7. 选择 “运行冒烟测试 ”步骤以查看管道日志的关联部分。

    Azure DevOps 的屏幕截图,显示管道运行日志,其中显示了冒烟测试的输出。JSON 健康测试结果被突出显示。

    请注意,管道日志包含运行状况检查响应。 响应指示应用程序与 Azure SQL 数据库的通信出现问题。 数据库尚未部署或配置,这就是为什么网站无法访问它的原因。 在下一个练习中,你将修复此配置问题。