Azure, Azure DevOps, Deployment Environments, Professional, YAML Pipelines

Azure Deployment Environments – Integrating into your CI Process

Introduction

This is a follow up to my series of post regarding Azure Deployment Environments (ADE). This specific one will build off the Creating an Application Sandbox by using that template to:

  • Deploy an ADE Environment
  • Deploy Application Code to the new ADE
  • Run a Load Test Against the ADE
  • Destroy the ADE

These steps are designed for how to configure Azure Deployment Environments in your CI Pipeline and this would execute when creating a Pull Request (PR).

Deploy an ADE Environment

As mentioned the ADE template will match the one used in the Creating an Application Sandbox which will leverage Bicep Registries. This template will also provision the access required to CosmosDB via an RBAC roles.

Here is a link to the complete ADE Template

One thing we will need to accommodate for though is how to automate this. Up to this point the ADE has relied on a user providing input to deploy. Well thankfully ADEs support these arguments being passed in via .json in the CLI. In order to automatically create this ADE we will need to leverage the combination of the CLI and the ADE’s .json file. Here is what mine looks like:

{
      "baseName":"adecosmosapp2",
      "location":"eastus",
      "logAnalyticsResourceGroup":"rg-logging-dev-eus",
      "logAnalyticsWorkspace":"la-logging-dev-eus",
      "cosmosDBResourceGroup":"rg-webdata-dev-eus",
      "cosmosDBName":"cosmoswebdatadeveus"
    }

As one can see there isn’t anything fancy here. If wanting to use your own instance then will need to update the values accordingly.

So our CLI command would then look something like:

az devcenter dev environment create --dev-center-name <DevCenterName> --project-name <DevCenterProjectName> --catalog-name <DevCenterCatalogName> --environment-definition-name <EnvironmentDefinitionName> --environment-type <DevCenterProjectEnvironmentTypeName> --name <DeploymentEnvironmentInstanceToCreateName> --parameters infrastructure/parameters/ade.eus.parameters.json

Deploy Application Code to the new ADE

The process to deploy code to our ADE will be the same way we deploy our code to environments attached to our Software Delivery Lifecycle.

In our case we leverage the AzureRmWebAppDeployment task which will preform a zipdeploy to the location we just created as part of our ADE creation.

Load Test

I am not an expert in load testing. I recognize that there is an art here. My thought is to just illustrate the possible in this process. To that end I am running a simple JMeter script who is just hitting the app service URL to simulate traffic.

Since the ADE is linked to Application Insights and that Insights is workspace based and tied to a Log Analytics Workspace we are able to retain the results of our test and confirm the activity of our Load Test. If interested in Azure Load Testing check out my post on getting started with it.

Destroy the ADE

At the end of our Continuous Integration pipeline we want to clean up our ADE. This will allow for cost optimization and ensure that when this pipeline is redeployed it is done so with brand new infrastructure. It is important to note we will want this task to run every time no matter what.

Again if we are worried about troubleshooting and keeping our test results all of these are stored in Log Analytics for long term retention and evaluating any logs and/or load test results.

This can be accomplished with the following script:

'az devcenter dev environment delete --dev-center <DevCenterName> --project-name <DevCenterProjectName> --name <DeploymentEnvironmentInstanceToCreateName> --yes  '

YAML Pipeline File

Below is the fully expanded YAML file required to:

  • Deploy an ADE Environment
  • Deploy Application Code to the new ADE
  • Run a Load Test Against the ADE
  • Destroy the ADE
trigger:
  branches:
    include:
    - main
pool:
  vmImage: 'ubuntu-latest'
stages:
- stage: adecosmosapp2_build
  variables:
  - name: solutionPath
    value: $(Build.SourcesDirectory)//
  jobs:
  - job: Publish_infrastructure
    steps:
    - task: PublishPipelineArtifact@1
      displayName: 'Publish Pipeline Artifact infrastructure '
      inputs:
        targetPath: infrastructure
        artifact: infrastructure
        properties: ''
  - job: Publish_tests
    steps:
    - task: PublishPipelineArtifact@1
      displayName: 'Publish Pipeline Artifact tests '
      inputs:
        targetPath: tests
        artifact: tests
        properties: ''
  - job: whatif_adecosmosapp2_dev2_eus
    steps:
    - task: AzureCLI@2
      displayName: validate bicep
      inputs:
        azureSubscription: AzureDevExtServiceConnection
        scriptType: 'pscore'
        scriptLocation: 'inlineScript'
        inlineScript: 'az deployment group validate --resource-group <ResourceGroupName> --name azureADOCLIDeployment --template-file infrastructure/main.bicep --parameters infrastructure/parameters/dev2.eus.parameters.json '
    - task: AzureCLI@2
      displayName: what-if bicep
      inputs:
        azureSubscription: AzureDevExtServiceConnection
        scriptType: 'pscore'
        scriptLocation: 'inlineScript'
        inlineScript: az deployment group what-if --resource-group <ResourceGroupName> --name azureADOCLIDeployment --template-file infrastructure/main.bicep --parameters infrastructure/parameters/dev2.eus.parameters.json --out yamlc
  - job: build_publish_todo
    steps:
    - task: UseDotNet@2
      displayName: Use .NET SDK v
      inputs:
        packageType: 'sdk'
        version: ''
        includePreviewVersions: true
    - task: NuGetAuthenticate@0
      displayName: 'NuGet Authenticate'
    - task: DotNetCoreCLI@2
      displayName: dotnet build
      inputs:
        command: build
        projects: $(Build.SourcesDirectory)/src/todo/**/*.csproj
        arguments: --configuration Release
    - task: DotNetCoreCLI@2
      displayName: 'dotnet publish'
      inputs:
        command: publish
        publishWebProjects: True
        projects: $(Build.SourcesDirectory)/src/todo/**/*.csproj
        arguments: '--configuration Release --output drop/todo '
        zipAfterPublish: true
    - task: PublishPipelineArtifact@1
      displayName: 'Publish Pipeline Artifact todo '
      inputs:
        targetPath: drop/todo
        artifact: todo
        properties: ''
- stage: adecosmosapp2_ade_build
  jobs:
  - job: adecosmosapp2_eus_create_ade
    dependsOn: []
    steps:
    - task: AzureCLI@2
      displayName: Deploy ADE Environment
      inputs:
        azureSubscription: AzureDevExtServiceConnection
        scriptType: 'pscore'
        scriptLocation: 'inlineScript'
        inlineScript: 'az devcenter dev environment create --dev-center-name <DevCenterName> --project-name <DevCenterProjectName> --catalog-name <DevCenterCatalogName> --environment-definition-name <EnvironmentDefinitionName> --environment-type <DevCenterProjectEnvironmentTypeName> --name <DeploymentEnvironmentInstanceToCreateName> --parameters infrastructure/parameters/ade.eus.parameters.json '
  - deployment: adecosmosapp2_app_cicd_eus
    environment:
      name: cicd
    dependsOn:
    - adecosmosapp2_eus_create_ade
    strategy:
      runOnce:
        deploy:
          steps:
          - task: AzureRmWebAppDeployment@4
            inputs:
              ConnectionType: 'AzureRM'
              azureSubscription: <AzureServiceConnectionName>
              appType: webAppLinux
              WebAppName: <WebAppNameCreatedByADE>
              packageForLinux: $(Pipeline.Workspace)/todo/*.zip
  - deployment: run_azure_load_test_cicd_eus
    environment:
      name: cicd_loadtest
    dependsOn:
    - adecosmosapp2_app_cicd_eus
    strategy:
      runOnce:
        deploy:
          steps:
          - task: AzureLoadTest@1
            inputs:
              azureSubscription: <AzureServiceConnectionName>
              loadTestConfigFile: $(Pipeline.Workspace)/tests/SampleApp.yaml
              resourceGroup: <LoadTestingResourceGroupName>
              loadTestResource: <LoadTestingResourceName>
              secretsJSONObject: ''
              secrets: ''
              env: ' [ { "name": "webapp", "value": "<FullURLOfADEWebApp>" } ]'
  - job: adecosmosapp2_eus_delete_ade
    dependsOn:
    - run_azure_load_test_cicd_eus
    condition: always()
    steps:
    - task: AzureCLI@2
      displayName: Destroy ADE Environment
      inputs:
        azureSubscription: <AzureServiceConnectionName>
        scriptType: 'pscore'
        scriptLocation: 'inlineScript'
        inlineScript: 'az devcenter dev environment delete --dev-center <DevCenterName> --project-name <DevCenterProjectName> --name <DeploymentEnvironmentInstanceToCreateName> --yes  '

I do have this processed templated out in TheYAMLPIpelineOne and do anticipate a future post going into one way to break down the deployment into multiple templates to promote reusability.

Conclusion

Integrating Azure Deployment Environments in your CI Pipeline is a complicated topic; however, one that can have vast benefits. Leveraging Azure Deployment Environment with Azure DevOps Pipelines can facilitate new conversation son how to shift practices further left. In this instance we demoing how shift Azure Load Testing all the way to CI Builds initiated at Pull Request.