It’s quick and easy to get started with Azure Functions. In a matter of hours, you can have a working Function App that performs some simple tasks. It’s at that point that you’ll want to test your code in a non-production environment and find any bugs. There are a variety of ways to distinguish between deployment environments, including some built-in tooling, but as always, there are pros and cons to every choice.
I’ll start by going over the different ways to separate development from staging and production, and then finish up with a template of what the ideal setup looks like.
Deployment slots let you run multiple versions of your Function App at the same time, each with their own configuration and settings. That means you can run version 1.0 of a Function App in the production slot, with version 1.1 running in the staging slot. You can then easily swap the slots so that version 1.1 is now in the production slot, and version 1.0 in the staging slot.
This is more akin to Blue/Green deployments rather than staging and production environments. For that reason, I prefer to think of them as standby environments that can be brought online at a moment’s notice.
There is an interesting case to be made for deployment slots as it relates to lowering the risk of production deployments. Because the production and staging applications can run side-by-side and share the same configuration, final testing can be done on a near-live environment that uses the same databases, storage, and other resources. When you’re confident the new version is working as expected, the slots can be swapped with very little chance that anything goes wrong.
Deployment Slots are configurable through the Azure Portal. Remember that they are still in preview for Azure Functions, so there’s always the possibility of bugs or breaking changes.
This is probably the most common way to distinguishing development from staging and production, and it obviously applies to much more than just Azure Functions.
It’s quite common to name resources for a given environment with a prefix of some sort. For Function Apps, that could look something like:
- development-functionApp1, development-functionApp2
- staging-functionApp1, staging-functionApp2
- production-functionApp1, production-functionApp2
The name of the function repeats from one environment to the other. In this case, functionApp1 and functionApp2. The prefix (development-, staging-, production-) indicates which environment the Function App is meant to run in. While rudimentary, it does make it easy to see at a glance which environment a Function App belongs to.
A naming convention is a great start to distinguishing between development, staging, and production. But it does create a lot of noise at the top level of the Function Apps dashboard, which can make it hard to find what you’re looking for.
Assigning each environment’s functions to a separate resource group is a neat way to clean up the dashboard for a project with many Function Apps. The dashboard gives you the ability to filter and group by resource group. So if you’re only interested in seeing production functions, you can easily do so with the built-in portal functionality.
There’s another option which provides even more isolation than those I just looked at, and that’s having separate subscriptions for each environment.
The extra layer of isolation between the environments means there is less risk of accidental mistakes, since you can limit access to non-development environments to only those who need it.
Putting development, staging and production in separate subscriptions also keeps everything logically separated. The global filter in the Azure portal makes it easy to switch back and forth, or select multiple subscriptions at a time, so the view you’re looking for is never more than two clicks away.
The Ideal Solution
It’s probably clear by now that using only one of the above approaches isn’t sufficient to manage separate environments. The ideal solution combines all the options mentioned above to provide the most flexibility, ease of use, and testability. Here’s what it looks like:
First, we keep production and non-production environments in separate subscriptions. This gives us finer control over who can access what. You could also split development and staging into their own subscriptions — the graphic above combines them for simplicity’s sake.
Organizing the Function Apps into resource groups give you the ability to filter the functions down by logical component of the system. For example, an “ordering” resource group could contain all Function Apps related to product orders, a “shipping” resource group could contain the Function Apps needed for shipping to customers, etc.
Appropriately naming the resource groups and Function Apps per environment gives you a highly visual cue to indicate what you’re looking at. Naming is hard, but when done right, it can make all the difference for someone trying to understand the structure of the serverless application.
And last but not least, the production Function Apps can use deployment slots to gives us the ability to do final testing against a production-like environment, reducing the risk of any final issues getting through the cracks.