Serverless, DevOps, and CI/CD: Part 2

Continuous integration and automated builds

This is part 2 of a series around testing, continuous integration/continuous deployment, and DevOps for serverless Azure Functions. You can read part 1 on unit tests here.

For this example we have a simple Azure Function that can detect odd or even numbers, the next step is to make sure we have the right process and tools for deploying bits into production.

The core to any good code operations story generally revolves around source control. As nice as the Azure Portal is for getting started and trying out simple Azure Functions, anything that’s utilized in production needs to be source controlled and versioned. I chose to host this project in GitHub and will use Azure DevOps for CI/CD. You should note that Azure DevOps offers unlimited private repos and basic builds for free. So much like the Azure Functions, the DevOps pieces for this are free (or very inexpensive).

If I ever need to make bug fixes or feature improvements I do so in a separate branch and open a pull request before reviewing and merging it into the master branch.

To this point we’ve really just discussed good practices whether building Azure Functions or an operating system. So let’s get a little more specific to serverless functions. Imagine I have merged in an update to the master branch. Doing so should kick off an automated build and release system to confirm those changes are safe and push them into production.

In my Azure DevOps account, I create a new build pipeline. Even though my code is on GitHub, I can still integrate with it here and define the branch I want to automatically start updates.

When prompted for templates, there are more than a few options that seem they may work (and many do). But, I’m going to want to take advantage of some new deployment methods like “run-from-zip” I’ll get into during the release process. I’m also building a .NET Core 2 function (with the v2 runtime). So I’ll ignore the “Azure Web Site for ASP.NET” and “ASP.NET Core (.NET Framework)” templates and use the “ASP.NET Core” build template pictured below.

There are only a few modifications I need to make for this to work with functions. In the Process section, I’ll change the projects to restore and build to exclude test projects

**/*.csproj
!**/*Test*.csproj

And I’ll make my test projects a bit more flexible (for the naming conventions I prefer when we start to add things like integration tests)

**/*Test*/*.csproj

The only other change I need to make on the build is modifying the publish action to not publish a web project. In our case, the Azure Functions .NET SDK will produce the right build artifacts and web project settings would fail for missing things like a web.config file.

UN-select this

Now that the build is set up and configured we can save the build definition and queue up a build. Azure DevOps provides real-time console output of the worker building and testing our bits. After the build is complete I can see the unit tests we wrote from part 1 are running and passing as part of the build pipeline.

Now that the build pipeline works, I can configure what triggers a build. If you recall, my code is on GitHub. Even with my code hosted outside of Azure DevOps, I get some impressive continuous integration options inside of Azure DevOps. I can enable continuous integration on the master branch so any commits or merges to this branch will kick off a build. Even better, I can also turn on pull request validation. This way when someone creates a pull request on the master branch a build will start and update the pull request with results.

Enable continuous integration and pull request validation

To test this I opened a PR against my master branch, the bits compiled, and tests ran so I can see right in this PR if it has any issues with tests.

The result of these automated builds is a published “drop.” The drop is a zip file that contains the binaries and necessary metadata for an Azure Function project. I could use the drop to publish whatever way I prefer. Let’s finish this post off with a simple release process.

In Azure DevOps I can go to the releases section and create a new pipeline. I’ll configure it to release the drop from the build configured above, and in the first environment, I just need a single task: Deploy App Service. I am going to use the new 4.* preview version of this task as it comes with additional deployment options like “Run-From-Zip.” You can read more about Run-From-Zip here, but in short, it’s a way for the function app to be mounted to the instance as a zip artifact and contents are read directly from the zip. This results ultimately in less cold-start and more atomic deployments — which I can enable just by selecting this as my deploy option below.

And with that, I now have a CI/CD system centered around source code in GitHub. In the next post, I’ll break down more into some of the flexible ways I can manage different environments (dev, test, prod), including automated release gates to validate code works before publishing to future environments.