Azure DevOps —CI/CD in a nutshell

Péter Vaskó
Tresorit Engineering
8 min readJul 30, 2019

Why we need DevOps?

Imagine the following scenario: You have a retail store with a web shop. Customers can go into your store and buy the products there or purchase online. Both ways IT is involved, you need to generate and print invoices, update inventory information, process card payments, add new items or make some UI changes on the online store etc.

What can go wrong?

Being unable to update inventory information may delay shipment, which will cause unsatisfied customers, an unavailable or unstable online store will generate poor customer experience. I don’t think anyone would want any of those to happen.

If something goes wrong the most important thing is how quickly you can restore a stable state, how can you be sure that the problem caused by some manual configuration change or a quick fix pushed to a critical production system.

To prevent this you need to apply a few rules and make the process of delivering changes as automatic as possible involving as less manual intervention as possible.

If you’re interested in an easy reading amazing book, take a quick look at The Phoenix Project

Simple example

In this simple example we’ll host a web site in Microsoft Azure and our main goal is to push the changes quickly and safely to the production environment as fast as possible.

The website is a stripped down ASP.NET Core web application. You can find the source here: https://github.com/peter-vasko/devops.web

The question is how you can build up a pipeline that (semi)automatically pushes any change to the live/production environment.

  • The first step you can take to protect your release process is to protect the master branch — or the branch where the release ready code is tracked. It really depends on your branching strategy which branch(es) you want to protect. This way no direct commits are allowed on this branch only pull requests. Check the links on how to protect your branches on Github or Azure DevOps.
  • The second step is to setup continuous integration with automatic self testing builds. The benefit of this is that every commit made on a specific remote branch will trigger a build which executes the unit tests as well. High code coverage increases the confidence that any newly committed code won’t break any existing application logic.
  • The third step is to setup your release pipeline which will ship the successfully built application. Also you need to guarantee that the application is deployed only through the deployment pipeline. So no manual deployments or post-deployment configurations are allowed.
  • The fourth step is to have a separated test environment somewhat identical to the production environment. Somewhat identical means that, if you have a database the version should be the same in both environments, but test database can run on a machine with reduced performance, if the production system has load balancing the test environment should also have load balancing, but the number of the load balanced instances can be cut back. This way you can expect that the application in the test environment will behave the same way as in the production environment.

Prerequisites

If you want to try out the next step by step setup you need to have following

Setup build

Let’s connect with GitHub to access the repositories. Once the connection is established the available repositories and branches are listed when creating a new pipeline.

A Pipeline is basically a sequence of steps grouped in jobs. The first step is to connect with the version control system. In our case it’s GitHub and we’ll use the classic editor. The classic editor is GUI over the YAML configuration scripts and for the first try it’s easier to understand.

Select the ASP.NET Core (.NET Framework) template from the list, name your pipeline and check the branch for manual and scheduled builds.

One small tweak, change the Test files section’s first line like the one below. We are using the xUnit testing tool and the default settings of the test file filter will include the xunit.runner.visualstudio.dotnetcore.testadapter.dll which is not a test assembly and will break our pipeline.

Enable continuous integration, so whenever there’s a change committed to the previously selected branch the Pipeline will run.

Clone the pipeline for the master branch. Change the name of the pipeline as it will no longer reference the develop branch and set the default branch to master in the Get Sources step.

Setup release for development

After we’ve set up the builds let’s create the releases which will be responsible to deploy the “output” of the builds to the app services. Select the Azure App Service deployment template from the list.

The Deploy Azure App Service step needs some configuration as this is the place where you need to select the Azure subscription where App Service is located. If the App Service is not created at this point you can create it and hit the refresh button when it’s done.

The next step is to select the artifact to deploy from. Select the develop pipeline so the develop branch of devops.web will be deployed to the Web App selected previously.

Enable continuous deployment so whenever a build successfully ran on a branch, a release is automatically triggered and the application is deployed to the Web App.

Setup a release for production

Before we start creating the release pipeline, let’s stop for a moment and think of what we want to achieve. There are a few scenarios we can follow:

  • After the master build is done let’s push everything in production
    Once the build finished create the release
    Deploy the application to the Staging Slot (assuming there’s a Staging slot)
    Swap the two slots after deployment to staging slot is done
    Pros: Fast delivery
    Cons: No testing in the Staging environment
  • After the master build is done let’s push everything to Staging
    Once the build finished create the release
    Deploy the application to the Staging Slot (assuming there’s a Staging slot)
    Manually Swap the two slots after testing is done in the Staging environment
    Pros: Delivery is still can be fast and there’s an opportunity to test the Staging environment
    Cons: There’s a manual step in the release pipeline
  • After the master build is done let’s create the release
    Once the build finished create the release
    Manually deploy the application to the Staging Slot (assuming there’s a Staging slot)
    Manually Swap the two slots after testing is done in the Staging environment
    Pros: More control over when to deploy to the Staging environment
    Cons: At least one manual step in the pipeline

We’ll go with the second scenario, to see how the approval flow works in Azure DevOps.

When a build successfully ran a release is created and deployed to the Staging slot of the Production Web App

After the deployment succeeded we don’t want the swap to happen immediately so we can define Pre-deployment approval user(s) and force approval policies.

Once there is a release triggered the pipeline stops at the Swap step and waits for approval

If staging is looking good, and all the additional testing is done, the defined approver can Approve the Swap.

Variables

In most of the cases a web application is not just single web page, but visitors are able to log in, query some data from a database etc. In these cases you need to have a connection string (in case of databases) in the configuration of your web application. These information such as connection string API keys to a service are considered a secret. It’s a bad practice to store them in your version control system, because those who can read the code can gain access to those resources.

You can store these environment specific — different values for Development and Production in our case — variables in the Variables section of your release.

There’s a configuration value in the devops.web’s appsettings.json file called Secret. It’s value is empty. If you run this application locally the page will display that this configuration variable is not set.

Let’s setup our release to set this variable during the deployment process. Naming the variable is important. We want to set the Secret variable so our variable name must be Secret. If we’d like to set the Default value of LogLevel our variable name would be Logging.LogLevel.Default.

You can find more information on variable substitution on this page.

So we need to create a variable called Secret

As we don’t want to expose this secret to anyone who can access this edit page, we need to hide this information by clicking the lock next to the Value

At this point you can reveal the value by opening the lock, but after saving the release “opening” the lock will clear the value of the Secret variable.

The only thing left to do is to configure the deployment step to set the variable in the appsetting.json file

Let’s create a release and when it’s done the web page will display that the Secret variable is set.

Extras

Slack integration

If you’re using Slack, you can integrate your pipelines to send notifications about pipeline events, this way whoever joined the channel will be notified on the configured events.

Badges

If you want to track you pipelines latest status on a central dashboard, you can embed the image URL to dashboards to show the pipeline statuses on that page.

--

--