Implementing a CI/CD Pipeline for Azure Functions using Azure DevOps
“The most powerful tool we have as developers is automation.”
Continuous Delivery allows multiple developers to work on a single project with limited interference. Gone are the days when individual developers could construct applications of enterprise value along with the practice of long-lived code branches.
MercuryWorks utilizes a modified GitFlow branching strategy (read more on Version Control) and pull requests (PRs) are required in order to merge feature branches to the dev or main branch. PRs have completion policies that include reviewers and a successful build (including unit tests and static code analysis). These should be viewed as learning experiences, and ways to keep blockers and errors in testing environments to a minimum.
During the build process, we utilize the third-party (and open source) SonarQube tool to scan our source code for bugs, vulnerabilities and gauge code quality. Currently, we utilize SonarQube review during a build (outside the PR process); we plan to soon expand this process to include pre-PR scans to provide additional context to code reviews.
Continuous Delivery & Deployment
Continuous Delivery refers to the ability to release code to a production-like environment with minimal manual interference. At Mercury, Staging is the most production-like environment that we continuously deploy to (although we do require a manual button push).
In a standard pipeline at MercuryWorks, deployment to the Development environment occurs automatically after a successful build of the dev branch. Deployment to the Staging environment requires an approval by select developers or product manager; deployment to Production requires a merge to the main branch and requires an approval before being deployed.
When deploying to a PaaS infrastructure (commonly utilizing an Azure App Service), we use a separate App Service for each environment to enable implementation of the blue/green deployment pattern.
Within each App Service we create a slot and direct the deployment pipeline to this slot. Following this successful deployment the pipeline performs a “slot swap” to both warm up the slot and then sets this slot as the default for the App Service. Sometimes an application brings with it a startup cost and this slot pre-warming reduces this potential impact to users. It also limits the downtime for deployments and allows for a quick switch to a previous version.
To provide visibility on what has been completed on each production deployment we have implemented a process that will list out the Product Backlog Items related to any PRs created between deployments.
For each client a page in the wiki has been created for each client this has been implemented for under Clients > <Client Name> with the name of Upcoming Release Notes. After code has been fully deployed to Staging the work items in all Pull Requests since the last time Staging was deployed are collected and saved to the Upcoming Release Notes page. Each time code is updated, a new full entry is made.
When the time comes for a production deployment, the code is merged to the master branch and a final Release Notes page is created. Under Clients > <Client Name> > Release Notes> a new page will be created with the name of the Release in Azure DevOps (Example: Release-254). On this page, a consolidated collection of User Stories linked to Pull Requests since the last production deployment will be listed. After this page is created, the Upcoming Release Notes page will be cleared to start a fresh staging rolling release log.
Mercury develops pipelines in Yaml and stored in the repository with the application code. The general structure of the pipeline is the same between all projects. The common stages of a pipeline are –
- Application Build (select tasks are required for PR completion)
- Deployment to Dev Environment (including Functional Tests)
- Deployment to Staging Environment (including Functional Tests)
- Deployment to Production Environment
A single repository can have multiple pipelines associated with it depending on the projects. There may be a .NET Core API project which is deployed to a separate infrastructure than the related (sometimes multiple) front-end React project.
There are some tasks that are the same between each of these project types and even the same between stages of the same pipeline. In this case we utilize templates which allow us to break out small portions (like deploying to an application to an App Service) into a single file referenced by the Dev, Staging, and Production stages without having to repeat blocks of code.
We share a number of generalized scripts in the MercuryWorks DevOps repository for use between all pipelines. The template Releases and Task Groups expect this to be added as an artifact with the name of ‘shared’ with each group of scripts being accessible through ‘shared/<FeatureName>’.
Azure DevOps is a powerful tool for implementing a continuous integration and continuous delivery (CI/CD) workflow. With features such as Repos for version control and Pipelines for build and deployment, Azure DevOps makes it easy to automate the build, test and release process, allowing teams to deliver software faster and more reliably. Using Azure DevOps, engineering teams can improve the efficiency and speed of their software development, leading to increased productivity and competitiveness.