Search code examples
azure-devopscontinuous-integrationsingle-page-applicationazure-static-web-app

Best practice for SPA rollback on Azure Static Webapp with Azure Devops Deployment?


I come from the backend world where I built docker images and push them to a registry.

Recently there was the need to develop an angular SPA and host it on azure static webapps. The pipelines are running on azure devops and are defined as yaml.

I have a really simple, working pipeline which does the build and deployment:

trigger:
  none

parameters:
  - name: buildConfiguration
    type: string
    default: none
  - name: deploymentToken
    type: string
    default: none

steps:
  - task: NodeTool@0
    inputs:
      versionSpec: '20.x'
    displayName: 'Install Node.js'

  - script: |
      npm ci
      npm run build -- --configuration=${{ parameters.buildConfiguration }}
    displayName: 'Install dependencies and build Angular app'

  - task: AzureStaticWebApp@0
    inputs:
      app_location: 'dist'
      output_location: ''
      skip_app_build: true
      skip_api_build: true
      config_file_location: dist/assets
      azure_static_web_apps_api_token: ${{ parameters.deploymentToken }}
    displayName: 'Deploy to Azure Static Web Apps'

However, I just thought about what to do when I need to rollback fast to the previous version.

What is a good practice in this case? Should build-artifacts (.zip in this case?) be stored somewhere? Is azure pipeline artifacts appropriate for that?

Currently I would have to keep the release branches (we do git flow) and rerun the pipeline based on that branch, which is basically a new build and does not deploy the exactly the same artifact I guess.

I could also define a new pipeline which takes a git tag (release/x.x.x) and a target environment (static webapp dev, staging, prod) which checks out the specific version tag, does the build and the deployment again.

Would the publish artifact task be appropriate for something like this?

https://learn.microsoft.com/en-us/azure/devops/pipelines/artifacts/pipeline-artifacts?view=azure-devops&tabs=yaml-task

steps:
- task: PublishPipelineArtifact@1
  inputs:
    targetPath: $(System.DefaultWorkingDirectory)/bin/WebApp
    artifactName: WebApp

I am not sure if I should have a second stage which implements a deployment job. In my mind that job would download the previously published artifact before the deployment.

Later, if an error happens and I need to rollback, I would rerun the deployment stage of a previous pipeline run. Then it just downloads the previous artifact instead of building it anew.

I guess i need a hint for the right direction to take:

  • Is rebuilding and redeploying a good approach?
  • If not, what is a good alternative?
    • Is publishing the pipeline artifact after the build to download it on a second deploy stage a valid approach? If yes, can this be rerun later if a newer version fails?

Solution

  • Best practice questions are often frowned upon on StackOverflow. To properly answer this could require many other questions related to what problem you're trying to solve, or what parts of the problem are more important than others. For example, how critical is the application, are you trying to optimize costs, reduce operational overhead, improve traceability, or time for recovery?

    There is nothing wrong with the suggestions you've provided. Using the publish task to create a pipeline artifact and using separate stages is a good approach, as you're separating build from deploy. You could accomplish a rollback approach by re-running the "deploy stage" to redeploy the previously built artifact. They key difference between this approach and re-running the entire pipeline is that you'll some time required to produce the artifact. Whether you re-run the entire pipeline or deploy a previous artifact is the same outcome. So if you're only concern is how long it takes to compile, this is a suitable solution.

    One thing to keep in mind: pipeline artifacts are not permanent. The default configuration retains 3 versions per branch and 30 days of the "default branch". There is an option to "retain" a build, which overrides the default expiration policies. This can be introduced as automation as part of your pipeline.

    The above outlines possible implementation details for re-deploying a previously built artifact based on the git commit. It's easy to understand and maintain. However, there are other approaches that are subjectively better but add other levels of complexity and cost. For example, you could do a blue/green or canary deployment by introducing deployment slots to your infrastructure.

    For example, a deployment job in your Azure pipeline has the ability to tap into the deployment lifecycle and gradually roll out the application.

    jobs:
    - deployment: deploy
      environment: production
      strategy:
        runOnce:
          preDeploy:
            steps:
            # steps to prepare the deployment,
            # e.g. flip traffic to the production slot
          deploy:
            steps:
            # steps to perform the deployment
          routeTraffic:
            steps:
            # steps to test the deployment
            # e.g. route a percentage of traffic to the new slot
          postRouteTraffic:
            steps:
            # monitor the traffic, check for errors, etc
          on:
            success:
              steps:
              # flip all remaining traffic to the new slot
              # record telemetry, re-enable monitoring tools, etc
            failure:
              steps:
              # automatically rollback to the original slot
              # record failure telemetry