练习 - 部署 Web 应用程序

已完成

在玩具公司,你的网站开发团队已将最新版本的网站提交到了 Git 存储库。 现在,你已准备好更新工作流以生成网站,并将其部署到 Azure 应用服务。

在此过程中,你将:

  • 为生成作业添加新的被调用的工作流。
  • 更新工作流以包含生成作业。
  • 添加新的版本验收测试。
  • 更新部署作业以部署应用程序。
  • 运行工作流。

为生成作业添加可重用工作流

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

  1. 打开 Visual Studio Code。

  2. 在 .github/workflows 文件夹中创建一个名为 build.yml 的新文件。

    Visual Studio Code 资源管理器的屏幕截图,其中显示了 .github/workflows 文件夹和 build.yml 文件。

  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. 在“作业:”行下方的“Lint 分析”作业之前,添加一个名为“生成”的新作业,该作业使用刚刚定义的可重用工作流:

    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 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. 更新“部署-生产”作业,使其也依赖于“生成”和“Lint 分析”作业。

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

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

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

    output appServiceAppName string = appServiceApp.name
    output appServiceAppHostName string = appServiceApp.properties.defaultHostName
    
  3. 保存对该文件所做的更改。

更新部署作业以传播输出

现在,需要更新“部署”作业,以获取 Bicep 部署的输出值,并使其可用于工作流的其余部分。

  1. 打开“.github/workflows”文件夹中的“deploy.yml”文件。

  2. 在“部署”作业的定义中,为 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-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 关键字的“部署”作业。 此依赖项可确保在基础结构准备就绪之前不会部署网站。 它还允许作业访问“部署”作业的 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 以使用 Web 浏览器进行身份验证,然后选择 Enter

    在 macOS 上,选择“授权”。

  5. 将会出现一个浏览器窗口。 你可能需要再次登录到 GitHub。 选择“授权”。

运行工作流

  1. 在浏览器中,转到“Actions”。

    工作流的第一次运行(标记为“初始提交”)显示为失败。 创建存储库时,GitHub 会自动运行工作流。 由于机密当时未准备就绪,所以失败了。 你可以忽略此次失败。

  2. 选择 deploy-toy-website-end-to-end 工作流。

  3. 选择工作流的最新运行。

  4. 等待“生成”作业成功完成。

    GitHub 的屏幕截图,其中显示了工作流运行作业。

  5. 等待“部署-测试/部署”作业成功完成。

    “注释”面板中列出了一些警告。 所有这些警告均因 Bicep 将信息性消息写入工作流日志的方式而起。 你可以忽略这些警告。

  6. 然后,工作流运行“部署-测试/版本验收-测试”作业,但版本验收测试失败:

    GitHub 的屏幕截图,显示测试环境中工作流运行的“版本验收测试”作业。状态显示作业失败。

  7. 选择“部署-测试/版本验收-测试”作业以打开工作流日志。

  8. 选择“运行版本验证测试”步骤,查看工作流日志的关联部分:

    显示工作流运行日志的 GitHub 屏幕截图,其中显示了冒烟测试的输出。突出显示了 JSON 运行状况测试结果。

    请注意,工作流日志指示网站和配置不正常。 应用程序与 Azure SQL 数据库的通信存在问题。 网站无法访问数据库是因为尚未部署或配置数据库。 你将很快解决此问题。