Übung: Bereitstellen einer Web-App in Azure

Abgeschlossen

In Ihrem Spielzeugunternehmen hat Ihr Websiteentwicklungsteam die neueste Version der Website in Ihr Git-Repository committet. Nun können Sie Ihre Pipeline aktualisieren, um die Website zu erstellen und auf Azure App Service bereitzustellen.

In diesem Prozess führen Sie die folgenden Aufgaben aus:

  • Fügen Sie eine neue Pipelinevorlage für den Buildauftrag hinzu.
  • Aktualisieren Sie die Pipeline, um den Buildauftrag aufzunehmen.
  • Fügen Sie eine neue Feuerprobe hinzu.
  • Aktualisieren Sie die Bereitstellungsphase, um die Anwendung bereitzustellen.
  • Ausführen der Pipeline.

Hinzufügen einer Pipelinevorlage für den Buildauftrag

Fügen Sie neue Auftragsdefinition hinzu, die die erforderlichen Schritte zum Erstellen der Websiteanwendung enthält.

  1. Öffnen Sie Visual Studio Code.

  2. Erstellen Sie im Ordner deploy/pipeline-templates eine neue Datei mit dem Namen build.yml.

    Screenshot of Visual Studio Code Explorer, with the pipeline-templates folder and the 'build.yml' file shown.

  3. Fügen Sie der Pipelinevorlagendatei build.yml den folgenden Inhalt hinzu:

    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'
    

    Der Auftrag führt einen Buildschritt aus, um den Quellcode der Websiteanwendung in eine kompilierte Datei umzuwandeln, die in Azure ausgeführt werden kann. Der Auftrag kopiert dann das kompilierte Artefakt in einen temporären Stagingordner und veröffentlicht es als Pipelineartefakt.

  4. Speichern Sie die geänderte Datei.

Umbenennen der ersten Pipelinephase und Hinzufügen eines Buildauftrags

  1. Öffnen Sie die Datei azure-pipelines.yml im Ordner deploy.

  2. Ändern Sie die Lint-Phase. Benennen Sie sie in Build um, und fügen Sie einen Buildauftrag hinzu, der die erstellte Pipelinevorlage build.yml verwendet.

    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. Speichern Sie die geänderte Datei.

Aktualisieren der Datei für die Feuerprobe

Die Websiteentwickler haben der Website einen Integritätsendpunkt hinzugefügt. Dieser Endpunkt überprüft, ob die Website online ist und die Datenbank erreichen kann. Hier fügen Sie eine neue Feuerprobe hinzu, um die Integritätsprüfung aus Ihrer Bereitstellungspipeline aufzurufen.

  1. Öffnen Sie die Datei Website.Tests.ps1 im Ordner deploy.

  2. Fügen Sie einen neuen Testfall hinzu, der die Integritätsprüfung aufruft. Der Testfall schlägt fehl, wenn nicht der Antwortcode 200 für einen erfolgreichen Test angegeben wird.

    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. Speichern Sie die geänderte Datei.

Hinzufügen der Ausgabe zur Bicep-Datei

Sie möchten einen Bereitstellungsschritt hinzufügen, der Ihre Website in Azure App Service veröffentlicht, aber der Veröffentlichungsschritt erfordert den Namen der App Service-App. Hier machen Sie den App-Namen als Ausgabe aus Ihrer Bicep-Datei verfügbar.

  1. Öffnen Sie die Datei main.bicep im Ordner deploy.

  2. Fügen Sie am Ende des Dateiinhalts den Namen der App Service-App als Ausgabe hinzu.

    output appServiceAppName string = appServiceApp.name
    output appServiceAppHostName string = appServiceApp.properties.defaultHostName
    
  3. Speichern Sie die geänderte Datei.

Aktualisieren der Bereitstellungsphase

  1. Öffnen Sie im Ordner deploy/pipeline-templates die Datei deploy.yml.

  2. Konfigurieren Sie in der Definition des Bereitstellungsauftrags der Bereitstellungsphase (in der Nähe von Zeile 59) den Auftrag für die Verwendung des von Windows gehosteten Agentpools:

    - 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:
    

    Einige der Pipelineschritte, die Sie später für die Arbeit mit Ihrer Datenbank hinzufügen, erfordern die Ausführung des Windows-Betriebssystems. Sie können verschiedene Agentpools für verschiedene Aufträge in Ihrer Pipeline verwenden, sodass die anderen Aufträge weiterhin den Ubuntu Linux-Pipeline-Agentpool verwenden.

  3. Fügen Sie im Schritt SaveDeploymentOutputs des Bereitstellungsauftrags eine neue Pipelinevariable mit dem Wert des App-Namens aus der Bicep-Bereitstellungsausgabe hinzu:

    - 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)
    

    Beachten Sie, dass auf die Variable appServiceAppHostName die Eigenschaft isOutput=true angewendet wird, da diese Variable in der Feuerprobephase verwendet wird. Die appServiceAppName Variable wird festgelegt und in derselben Pipelinephase und in demselben Auftrag verwendet. Daher ist die isOutput=true Einstellung nicht erforderlich.

  4. Fügen Sie am Ende des Inhalts des Bereitstellungsauftrags einen neuen Schritt hinzu, um die App in Azure App Service bereitzustellen:

    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'
    

    Hinweis

    Achten Sie beim Einzug der YAML-Datei darauf, dass der neue Bereitstellungsschritt auf der gleichen Ebene wie der Schritt DeployBicepFile eingezogen wird. Wenn Sie nicht sicher sind, kopieren Sie den gesamten Inhalt der Datei deploy.yml aus dem Beispiel im nächsten Schritt.

    Beachten Sie, dass Sie das Artefakt nicht explizit in die Pipelinedefinition heruntergeladen haben. Da Sie einen Bereitstellungsauftrag verwenden, lädt Azure Pipelines das Artefakt automatisch für Sie herunter.

Überprüfen des Inhalts der Datei deploy.yml und Committen Ihrer Änderungen

  1. Vergewissern Sie sich, dass die Datei deploy.yml wie im folgenden Beispiel aussieht:

    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. Speichern Sie die geänderte Datei.

  3. Committen und pushen Sie Ihre Änderungen im Visual Studio Code-Terminal in Ihr Git-Repository, indem Sie die folgenden Befehle ausführen:

    git add .
    git commit -m "Build and deploy website application"
    git push
    

Führen Sie die Pipeline aus.

  1. Wechseln Sie in Ihrem Browser zu Pipelines.

  2. Wählen Sie die letzte Ausführung Ihrer Pipeline aus.

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

    Warten Sie, bis die Buildphase erfolgreich abgeschlossen wurde.

    Die Pipeline wird angehalten, bevor sie die Phase Validieren (Testumgebung) ausführt, da die Pipeline die Berechtigung zur Verwendung der Variablengruppe benötigt, auf die die Phase verweist. Sie müssen den Zugriff der Pipeline auf die Variablengruppe genehmigen, da Sie die Pipeline zum ersten Mal in diesem Projekt ausführen. Wenn Sie die Pipeline noch mal ausführen, müssen Sie den Zugriff auf dieselbe Variablengruppe nicht genehmigen.

  3. Wählen Sie Ansicht aus.

    Screenshot of Azure DevOps showing the pipeline run paused at the Validate stage. Permission is required to continue. The View button is highlighted.

  4. Wählen Sie Zulassen aus.

    Screenshot of Azure DevOps showing that the pipeline needs permission to use the ToyWebsiteTest variable group. The Permit button is highlighted.

  5. Wählen Sie Zulassen aus.

    Screenshot of Azure DevOps showing the permission confirmation interface. The Permit button is highlighted.

    Die Phase Überprüfen (Testumgebung) wurde erfolgreich abgeschlossen.

    Die Pipeline wird fortgesetzt, und die Phase Bereitstellen (Testumgebung) wird erfolgreich abgeschlossen. Die Pipeline führt dann die Phase Feuerprobe (Testumgebung) aus, aber die Feuerprobephase schlägt fehl.

  6. Wählen Sie die Phase Feuerprobe (Testumgebung) aus, um das Pipelineprotokoll zu öffnen.

    Screenshot of Azure DevOps showing the pipeline run's Smoke Test stage for the test environment. The status shows that the stage failed.

  7. Wählen Sie den Schritt Run smoke tests (Feuerprobe ausführen) aus, um den zugehörigen Abschnitt des Pipelineprotokolls anzuzeigen.

    Screenshot of Azure DevOps showing the pipeline run log, with the output of the smoke test displayed. The JSON health test result is highlighted.

    Beachten Sie, dass das Pipelineprotokoll die Antwort der Integritätsprüfung enthält. Die Antwort gibt an, dass es ein Problem mit der Kommunikation der Anwendung mit der Azure SQL-Datenbank gibt. Die Datenbank wird noch nicht bereitgestellt oder konfiguriert, weshalb die Website nicht darauf zugreifen kann. In der nächsten Übung beheben Sie dieses Konfigurationsproblem.