Multi-environment deployments for Compiled C# Azure Functions with VSTS Release Management
- Published on
- Reading time
- Simon Waight
This post covers an approach you can use to deploy compiled C# Functions using the tooling available in Visual Studio 2017 and various Build and Release Management Tasks contained in Visual Studio Team Services (VSTS).
Note that this post discusses deploying to the v1 Functions runtime platform.
I was lucky enough to speak with Damian Brady on the DevOps Labs show on Channel 9 and cover the first part of this blog. If you've watched that, or you've come here via the Github repository for the solution we used, then we'll go down to the next level and really look at how you can recreate this setup in your environment.
There are a few moving pieces we need to get into place first in order to complete the configuration. Let's take a look at those.
a. Connecting environments
Note that in order to complete these steps you may require elevated privileges in one or more of the mentioned services. If you do not have access to an admin-level account in any of the services you will likely need to ask someone to configure these for you.
> Github to VSTS
In our demonstration we're using a Github repository as our source repository and allowing VSTS' Build capability to perform Continuous Integration Builds when a commit occurs on the master branch. Microsoft has documented how to configure Github to connect with VSTS already, so go and take a read and head back here when you're done setting up the integration.
> VSTS to Azure
We use the Azure Resource Manager Service Endpoint option in VSTS to configure our connection into Azure from VSTS. There is documentation from Microsoft around how you setup the connection, including steps to create a custom Service Principal (or use a pre-existing one your Azure admin has created for you). Once again, go have a read and once you have a working Service Endpoint in VSTS head back over here.
b. Configure SendGrid
If you'd like to run the Functions once deployed you will need to configure SendGrid so you can use the binding in the Functions being deployed. You can follow the official Azure documentation on setting up a (free) SendGrid account and then make sure to set the API key value for the AzureWebJobsSendGridApiKey App Setting for your deployed Functions.
c. Create target Azure Resources
Go ahead and also create a Function App in the Subscription you want to deploy to (ensure that the Service Principal you setup previously has Contributor-level access to, at minimum, the Resource Group that will contain the Function).
You can use the process we document here to deploy to either a Service Plan or Consumption Plan, though there is a minor difference we will see later in the post.
The Azure resources to deploy should include:
- Application Insights
- Cosmos DB Account - Add Database "quotedemo" with Collections "quotes" and "leases"
- Function App (can be Consumption or Service Plan)
- Storage Account (can be created at same time as Function App)
Before we move on, make sure to capture the following:
- Application Insights Telemetry Key (shown on the 'Essentials' part of the Application Insights instance)
- Cosmos DB Account URL and Access Key
- Function App:
- AzureWebJobsStorage (Service Plan);
- WEBSITE_CONTENTAZUREFILECONNECTIONSTRING (Consumption Plan);
- WEBSITE_CONTENTSHARE (Consumption Plan);
- FUNCTIONS_EXTENSION_VERSION (most likely set to "~1").
d. Configure Application Insights for Release Annotations
In this scenario we will need to enable the Application Insights API or order to support Release Annotations which make it possible to see new release markers on timelines.
Once again, Microsoft has this well documented, including the Task you need to add in VSTS to allow you to create Annotations.
OK! Now we are ready to configure the Build and Release Management steps in VSTS.
2. Configure the Build
In a Team Project in VSTS you wish to use host the Build and Release Management Definitions go ahead and create a new Build.
Remember to select Github as your source repository.
When you are prompted for a template, select the ASP.Net Core (.Net Framework) build.
If you want a Continuous Integration (CI) build then ensure you set the Trigger as required.
At this point we have a build that produces a packaged web application that can be pushed to the Azure App Service hosting the Function App. We could add more Tasks to the Build to do this, but we want to support multiple environments so this is where Release Management comes into play.
I recommend you run the Build to ensure it's functional and to produce an artefact we can use in our next step.
3. Configure Release Management
Now we have a build that produces a build artefact we can now use VSTS Release Management (RM) to deploy and configure this artefact to any environment we can reach.
Let's go ahead and choose to create a new Release Management Definition. When you have the option, select the "Azure App Service Deployment" template.
The resulting Definition will be very vanilla and contain a single Task. We need to make some changes to deploy our service exactly as we'd like.
a. Add the Build Artefact
First we need to tell Release Management what we want to deploy, so let's go ahead and add our existing Build by clicking on the Artefacts box and selecting our Build as shown below.
If you wish to enable Continuous Deployment (CD) into Environments you can click on the lightning bolt on the Artefact and enable the CD trigger. Note that you can still stop automated deployments by putting in approvals or making deployments manual - by creating a Release you always have a build artefact to deploy.
b. Configure RM Tasks
This is where your previously completed configuration with the Azure Service Endpoint and in setting up the resources in Azure will come into play.
Click on the Tasks tab and the RM Definition will open.
Once open click on the Environment at the top of the Task list. As we are going to deploy all assets into a single Subscription we can set up a few items that will apply to all Tasks in the RM Definition.
The first thing we will do is to select the Service Endpoint we previously setup (below it is named "Service Principal for Demo", but you can name it anything meaningful).
Once you select the method of connection to the Azure Subscription change the "App type" field to be "Function App" and then from the final picker, select the Function App instance you setup earlier. If you don't see it, it could be that you placed it another subscription or that the Service Endpoint does not have sufficient rights to list the Function Apps in the Subscription.
Your setting should look something like the below.
We could deploy the sample code now, but it would fail to run because it is missing configuration.
c. Deploying configuration
You will notice that up until now we've not dealt with any of the runtime configuration settings for the Function. When you develop locally the Functions Tools in VSTS will generate a "local.settings.json" file, but it will be blocked from commit via the gitignore included in the project type. It's recommended you don't change this, and even if you it won't help you on deployment anyway (so... y'know, why bother to change the ignore file?)
For this Task we are going to need to pull in a free Marketplace Task - the Azure WebApp Configuration from Pascal Naber (Xpirit). This Task is a wrapper around some Azure Cmdlets, but it does a great job of removing your overhead in managing that. 🙂
You will need to be a VSTS admin in order to install Marketplace Tasks (if you aren't you can still request an admin to install them).
Once installed you can now add the Task to your Release Management Definition after the App Service Deployment (as shown below).
The Task expects any App Settings you need to deploy to be added as Variables to the Definition. So, for example, if we want to control the value we set for the 'APPINSIGHTS_INSTRUMENTATIONKEY' App Setting in our target Function we would create a Variable in our Release called 'appsetting.APPINSIGHTS_INSTRUMENTATIONKEY' and set the value to be the Telemetry Key we captured earlier in the post.
The beauty of this approach is that you can one-way save secrets and they won't show up (or be recoverable) via the Variables tab again. There is also an option to write them to Azure Key Vault if you want.
Below is a sample of the Variables once setup.
The eagle-eyed amongst you might spot that I am also setting default Function values (FUNCTIONS_EXTENSION_VERSION, AzureWebJobsStorage, AzureWebJobsDashboard for a Service Plan).
This is because I force the Application Settings Task to overwrite all existing values in the App Service. This is on purpose - it ensures no manual fixes are ever safe in Azure and our Release Management Definition is the source of truth for both the Artefact and the Configuration.
Note: For Consumption Plans make sure you set WEBSITE_CONTENTAZUREFILECONNECTIONSTRING, WEBSITE_CONTENTSHARE in addition to the above values. If you don't your deployment will fail after the first deployment (this is the source of the error in the video).
The full set of Variables for our sample is listed below.
- AppInsightsApiKey - API key you configured earlier for Application Insights (not Telemetry Key).
- AppInsightsApp - Application ID configured earlier for Application Insights (also not Telemetry Key).
- appsetting.APPINSIGHTS_INSTRUMENTATIONKEY - use telemetry key from Application Insights.
- appsetting.AzureWebJobsDashboard - use exiting value from Function (before first deployment).
- appsetting.AzureWebJobsSendGridApiKey - use SendGrid API key you setup earlier (should start 'SG.').
- appsetting.AzureWebJobsStorage - use exiting value from Function (before first deployment).
- appsetting.CosmosConnection - use Connection String from Cosmos Account you setup earlier.
- appsetting.FUNCTIONS_EXTENSION_VERSION - use exiting value from Function (before first deployment).
- appsetting.NotificationsSender - use an email address you control (to be used as From: in emails).
Service Plan deployment
- None: above list is all you need
Consumption Plan deployment
- appsetting.WEBSITE_CONTENTAZUREFILECONNECTIONSTRING - use exiting value from Function (before first deployment).
- appsetting.WEBSITE_CONTENTSHARE - use exiting value from Function (before first deployment).
Once you've configured the Variables you should now be able to save the Release Management definition and create a Release to test out your deployment.
I've recorded a quick video (see below) that shows this end-to-end and also has an additional bonus step of hitting an HTTP Triggered endpoint on the Function as a post-deployment confirmation step (you will need to copy a Host key from the Function App and save it as 'VersionApiKey' in the Variable to use to call the API, then add the Smoke Web Test Task from the Marketplace).
So what should the demo Function do? It should trigger an email to a recipient when a record is added to Cosmos DB. The recipient is listed in the record that is inserted, samples of which are included in the Github project. If you can't get it running make sure to leave a comment and I'll help you out!