Convert ARM Template to Bicep and Deploy with Azure DevOps - Part 5 - Deploy

2022, Aug 12

This is the fifth and the last part of the series where an ARM Template is converted to Bicep. In this article, we should continue on Phase 5 - Deploy.

Phased Migration

As a reference to Part 4, Microsoft suggests a migration in 5 phases, to convert the ARM Template into Bicep Template.

Five-Phases

The comprehensive details provided by Microsoft Docs are available as a reference1.

Phase 5 - Deploy

In this phase, we are going to deploy the final Bicep to production.

Check the PlayGoKids repository for this article demo.

Some actions need to be verified to give us confidence that the migrated template will work:

  • Prepare a rollback plan

Make sure that in the event of breaking existing components/services, you have a way of reverting the changes. Create an inventory of the components/services you use, backups if needed, to keep any downtime to a minimum if any issues arise from a deployment.

  • Run the what-if operation against production

Similar to what was executed in Test, running the what-if operation in production is the safest approach to compare the state of components/services. For more details on the what-if operation, check Part 4.

  • Deploy manually

Before creating pipelines, run the deployment from your local machine (with the what-if operation you achieve that), then create the Azure DevOps or GitHub Actions pipeline.

  • Run smoke tests

Check whether what was deployed to components/services is functioning properly, by validating the solutions deployed.

Azure DevOps

Assuming that all checkpoints above were validated, let's automate the release of the converted Bicep explored during this series.

Pipelines

Looking at the folders/files on the repository:

  • deploy folder contains the Bicep definition
  • web folder contains a dummy web app
  • azure-pipelines.yml is the main pipeline to be used on Azure DevOps
  • template-build.yml is the template to build the dummy web app and copy files
  • template-deploy.yml is the template to deploy the Bicep resources and the dummy web app

By opening web\Program.cs we can verify the dummy web app that will be used in our Smoke Test:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

The magic lies in the YML files. The orchestrator is available on azure-pipelines.yml:

trigger:
- main

variables:
  buildConfiguration: 'Release'

stages:
- stage: 'Build'
  displayName: 'Build the solution'
  jobs:
    - template: template-build.yml
      parameters:
        appName: 'web'
        csproj: '**/web.csproj'

- stage: 'DeployDev'
  displayName: 'Deploy to Dev'
  condition: succeeded()
  jobs:
  - template: template-deploy.yml
    parameters:
      appName: 'web'
      resourceLocation: 'australiaeast'
      env: 'dev'
      envFullName: 'Development'
      variableGroupName: 'sample-dev'

- stage: 'DeployTest'
  displayName: 'Deploy to Test'
  condition: succeeded()
  dependsOn:
    - DeployDev
  jobs:
  - template: template-deploy.yml
    parameters:
      appName: 'web'
      resourceLocation: 'australiaeast'
      env: 'tst'
      envFullName: 'Test'
      variableGroupName: 'sample-tst'

- stage: 'DeployUat'
  displayName: 'Deploy to Uat'
  condition: succeeded()
  dependsOn:
    - DeployTest
  jobs:
  - template: template-deploy.yml
    parameters:
      appName: 'web'
      resourceLocation: 'australiaeast'
      env: 'uat'
      envFullName: 'UAT'
      variableGroupName: 'sample-uat'

- stage: 'DeployProd'
  displayName: 'Deploy to Prod'
  condition: succeeded()
  dependsOn:
    - DeployUat
  jobs:
  - template: template-deploy.yml
    parameters:
      appName: 'web'
      resourceLocation: 'australiaeast'
      env: 'prd'
      envFullName: 'Production'
      variableGroupName: 'sample-prd'

The pipeline basically orchestrates the build and deployment in different stages, using the condition suceeded() and dependsOn for the release of the different environments Dev, Test, Uat and Prod.

The template-build.yml is used in the first stage:

  parameters:
  - name: appName 
    type: string
  
  - name: csproj
    type: string

  jobs:
  - job: 'Build'
    displayName: 'Build job'

    pool:
      vmImage: 'ubuntu-latest'

    steps:
    # Copy the deployment files
    - task: CopyFiles@2
      displayName: Copy deployment files
      inputs:
        SourceFolder: '2022-07-25/deploy'
        Contents: '**'
        TargetFolder: '$(Build.ArtifactStagingDirectory)/drop/deploy'

    # Use dotnet version
    - task: UseDotNet@2
      displayName: 'Use .NET 6 sdk'
      inputs:
        packageType: sdk
        version: '6.x'

    # Build and create the zip package
    - task: DotNetCoreCLI@2
      displayName: 'Building ${{ parameters.appName }}'    
      inputs:
        command: 'publish'
        publishWebProjects: false
        projects: ${{ parameters.csproj }}
        arguments: '--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)/drop/${{ parameters.appName }}'
        modifyOutputPath: false
        zipAfterPublish: true

    # Publish artifacts
    - task: PublishBuildArtifacts@1
      inputs:
        PathtoPublish: '$(Build.ArtifactStagingDirectory)/drop'
        ArtifactName: 'drop'

The steps include copying deployment files, using dotnet version .NET 6, building the dummy web app and publishing the artifacts.

Once the build template is executed, then template-deploy.yml is executed in the following stages:

  parameters:
  - name: variableGroupName
    type: string

  - name: appName 
    type: string
    
  - name: resourceLocation 
    type: string
  
  - name: env 
    type: string
    values:
    - dev
    - tst
    - uat
    - prd
  
  - name: envFullName 
    type: string
    values:
    - Development
    - Test
    - UAT
    - Production

  jobs:
  - deployment: DeployTo${{ parameters.env }}
    environment: '${{ parameters.envFullName }}'
    pool:
      vmImage: 'ubuntu-latest'
    variables:
      - group: ${{ parameters.variableGroupName }}
    strategy:
      runOnce:
        deploy:
          steps:
          - checkout: self

          #Create Infrastructure dependencies required
          - task: AzureResourceManagerTemplateDeployment@3
            displayName: 'Provision Azure Resources'
            inputs:
              azureResourceManagerConnection: '$(ServiceConnection)'
              subscriptionId: '$(SubscriptionId)'
              resourceGroupName: '$(ResourceGroupName)'
              location: '${{parameters.resourceLocation}}'
              csmFile: '$(Pipeline.Workspace)/drop/deploy/azuredeploy.bicep'
              csmParametersFile: '$(Pipeline.Workspace)/drop/deploy/azuredeploy.parameters.${{parameters.env}}.json'
              overrideParameters: '-resourceGroupName "$(ResourceGroupName)"'

          #Deploy .Net code to Azure Web app
          - task: AzureWebApp@1
            displayName: 'Deploy ${{ parameters.appName }} to Azure Web app'
            inputs:
              azureSubscription: '$(Subscription)'
              appType: 'webApp'
              appName: 'sample-web-${{parameters.env}}'
              package: '$(Pipeline.Workspace)/**/*.zip'
              runtimeStack: 'DOTNETCORE|6.0'

It is worth mentioning that this pipeline uses Variable Groups in Azure DevOps. Make sure you have the following variable groups:

Variable groups

and variables:

Variables

The Bicep definition is deployed using the task AzureResourceManagerTemplateDeployment, and the dummy web app on AzureWebApp.

When executing the pipeline, the stages will be triggered sequentially.

DevOps

And the solution should be running on the deployed environments:

Sample

This is the conclusion of this series. Hope you have learned to convert ARM Templates to Bicep, and getting to automate with Azure DevOps.

If you were helped, please star this repository.

Full Series

Phase 1 - Convert

Phase 2 - Migration

Phase 3 - Refactor

Phase 4 - Test

Phase 5 - Deploy


  1. References: Migrate to Bicep