Exercice – Déployer une application web

Effectué

Dans votre entreprise de jouets, votre équipe de développement de site web a validé la dernière version du site web dans votre référentiel Git. Vous êtes maintenant prêt à mettre à jour votre pipeline pour générer le site web et le déployer sur Azure App Service.

Au cours de ce processus, vous effectuez les tâches suivantes :

  • Ajouter un nouveau modèle de pipeline pour la tâche de build.
  • Mettre à jour le pipeline pour inclure la tâche de build.
  • Ajouter un nouveau test de détection de fumée.
  • Mettre à jour la phase de déploiement pour déployer l’application.
  • Exécuter le pipeline.

Ajouter un modèle de pipeline pour la tâche de build

Ajoutez une nouvelle définition de travail qui contient les étapes nécessaires à la génération de l’application de site web.

  1. Ouvrez Visual Studio Code.

  2. Dans le dossier deploy/pipeline-templates, créez un fichier nommé build.yml.

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

  3. Ajoutez le contenu suivant dans le fichier de modèle de pipeline 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'
    

    Le travail exécute une étape de génération pour transformer le code source de l’application de site web en un fichier compilé prêt à être exécuté dans Azure. Le travail copie ensuite l’artefact compilé dans un dossier intermédiaire temporaire et le publie en tant qu’artefact de pipeline.

  4. Enregistrez les modifications apportées au fichier.

Renommer la première phase du pipeline et ajouter une tâche de build

  1. Ouvrez le fichier azure-pipelines.yml dans le dossier deploy.

  2. Modifiez la phase Lint. Renommez-la Build et ajoutez une tâche de build qui utilise le modèle de pipeline build.yml que vous avez créé.

    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. Enregistrez les modifications apportées au fichier.

Mettre à jour le fichier de test de détection de fumée

Les développeurs du site web ont ajouté un point de terminaison d’intégrité au site web. Ce point de terminaison vérifie que le site web est en ligne et qu’il peut accéder à la base de données. Ici, vous ajoutez un nouveau test de détection de fumée pour appeler le contrôle d’intégrité à partir de votre pipeline de déploiement.

  1. Ouvrez le fichier Website.Tests.ps1 dans le dossier deploy.

  2. Ajoutez un nouveau cas de test qui appelle le contrôle d’intégrité. Le cas de test échoue si le code de réponse n’est pas 200, valeur qui indique une réussite.

    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. Enregistrez les modifications apportées au fichier.

Ajouter la sortie au fichier Bicep

Vous souhaitez ajouter une étape de déploiement qui publie votre site web sur Azure App Service, mais l’étape de publication nécessite le nom de l’application App Service. Ici, vous exposez le nom de l’application en tant que sortie de votre fichier Bicep.

  1. Ouvrez le fichier main.bicep dans le dossier deploy.

  2. À la fin du contenu du fichier, ajoutez le nom de l’application App Service comme sortie.

    output appServiceAppName string = appServiceApp.name
    output appServiceAppHostName string = appServiceApp.properties.defaultHostName
    
  3. Enregistrez les modifications apportées au fichier.

Mettre à jour la phase de déploiement

  1. Ouvrez le fichier deploy.yml dans le dossier deploy/pipeline-templates.

  2. Dans la définition du travail de déploiement de la phase Déployer (près de la ligne 59), configurez le travail pour utiliser le pool d’agents hébergés 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:
    

    Certaines étapes du pipeline que vous ajoutez ultérieurement pour travailler avec votre base de données nécessitent le système d’exploitation Windows pour s’exécuter. Vous pouvez utiliser différents pools d’agents pour différents travaux dans votre pipeline, de sorte que les autres travaux continuent à utiliser le pool d’agents de pipeline Ubuntu Linux.

  3. Dans l’étape Déployer SaveDeploymentOutputs du travail, ajoutez une nouvelle variable de pipeline avec la valeur du nom de l’application à partir de la sortie du déploiement 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)
    

    Notez que la propriété isOutput=true est appliquée à la variable appServiceAppHostName, car cette variable est utilisée dans la phase de test de détection de fumée. La variable appServiceAppName est définie et utilisée dans la même phase de pipeline et le même travail. Il n’a donc pas besoin du paramètre isOutput=true.

  4. À la fin du contenu du travail Deploy, ajoutez une nouvelle étape pour déployer l’application sur 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'
    

    Notes

    Soyez vigilant avec la mise en retrait du fichier YAML et veillez à ce que la nouvelle étape de déploiement soit mise en retrait au même niveau que l’étape DeployBicepFile. Si vous n’êtes pas sûr, copiez l’intégralité du contenu du fichier deploy.yml à partir de l’exemple figurant à l’étape suivante.

    Notez que vous n’avez pas téléchargé de manière explicite l’artefact dans la définition du pipeline. Comme vous utilisez un travail de déploiement, Azure Pipelines télécharge automatiquement l’artefact pour vous.

Vérifier le contenu du fichier deploy.yml et valider vos modifications

  1. Vérifiez que votre fichier deploy.yml ressemble à l’exemple suivant :

    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. Enregistrez les modifications apportées au fichier.

  3. Dans le terminal Visual Studio Code, validez et envoyez (push) vos modifications dans votre dépôt Git en exécutant les commandes suivantes :

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

Exécuter le pipeline

  1. Dans votre navigateur, accédez à Pipelines.

  2. Sélectionnez l’exécution la plus récente de votre pipeline.

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

    Attendez que la phase Générer se termine avec succès.

    Le pipeline s’interrompt avant d’exécuter la phase Valider (environnement de test), car le pipeline a besoin d’une autorisation pour utiliser le groupe de variables auquel la phase fait référence. Vous devez approuver l’accès du pipeline au groupe de variables, car c’est la première fois que vous exécutez le pipeline dans ce projet. Quand vous réexécuterez ce pipeline, vous n’avez pas besoin d’approuver l’accès au même groupe de variables.

  3. Sélectionnez Affichage.

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

  4. Sélectionnez Autoriser.

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

  5. Sélectionnez Autoriser.

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

    La phase Valider (environnement de test) se termine avec succès.

    Le pipeline continue et la phase Déployer (environnement de test) se termine correctement. Le pipeline exécute alors la phase Test de détection de fumée (environnement de test), mais la phase de test de détection de fumée échoue.

  6. Sélectionnez la phase Test de détection de fumée (environnement de test) pour ouvrir le journal de pipeline.

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

  7. Sélectionnez l’étape Effectuer des tests de détection de fumée pour consulter la section associée du journal de pipeline.

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

    Notez que le journal de pipeline comprend la réponse du contrôle d’intégrité. Cette réponse indique qu’il y a un problème avec la communication de l’application avec Azure SQL Database. La base de données n’est pas encore déployée ou configurée, c’est pourquoi le site web ne peut pas y accéder. Dans l’exercice suivant, vous corrigez ce problème de configuration.