Serverless computing with Azure Function in a DevOps Model

In a DevOps Model, one of the crucial aspect for Engineers is to manage Infrastructure. No matter how much ever we automate this task becomes cumbersome especially when we have multiple servers involved. While Azure ARM templates helps us spin up an environment on the fly we still have the need to maintain and support the servers. To overcome this hurdle, the direction we are moving towards is to go “Serverless”, that is the cloud provider dynamically manages the allocation of machine resources, and bills based on the actual amount of resources consumed by an application, rather than billing based on pre-purchased units of capacity. This will reduce the cost incurred by an application utilizing the compute resource. In a nutshell, we are not going to pay for scale or for the capacity of resources, but just for the compute (how many times we run and how long we run). And Of course, we don’t have to think about managing these servers.
In this post, I’m going to talk about how we can build a solution using Azure Function (latest version with VS2017 15.3) in DevOps fashion to run small discrete piece of software in the cloud.

For those who have been using Azure Functions that was released with VS2015, might have noticed that the model has completely changed although the fundamentals are the same. Please visit Part1 and Part2 channel 9 video in which Jeff Hollan explains all in depth. The version that was released with VS2015 has been discontinued as the future focus is on pre-compiled functions with the intent to have .NET Standard 2.0, there are dependencies that will only exist in Visual Studio 2017 Update 3 and beyond.
Let’s dive in to the flow to see how can we Create, build and deploy Azure Functions.

Prerequisites

  • You must have either the “ASP.NET and web development” or “Azure development” workload installed.

Create

Creation of Azure function in Visual studio is as easy as just selecting the project template. Then, it a matter of what logic you need which you can create by adding a Function from the available option.

Right-click on the project to add a new function which will then walk you through the selection wizard.

Cloud -> Azure Functions

 

In the Solution explorer, you will see that now we have an easy option to add dependent libraries from nuget.org or any nuget store. And yes, now its builds in to a class library. This might be new to people who worked with Azure Functions in VS2015

[caption id="attachment_125" align="alignnone" width="522"] Azure Function (VS2017) changes[/caption]

 

There is a list of operations that you can do when you create a new Azure function please ref this MSDN article for details for Azure triggers and bindings.. each of which is explained in detail.

I want to spend a minute looking at the folder structure created when we build the solution.

Inside the folder ..\ganesh\..\ServerlessComputingSample\ServerlessComputingSample\bin\Debug\net461 you will see the following

And inside HttpTriggerCharp (the sample function that I added) we will see function.json

So, unlike VS2015 template function.json is now part of build output than being part of the solution.

Here is the content of it,

As you would have noticed function.json includes the details on bindings and the library location.

Note:

Connecting to On-prem SQL server from Azure Functions is possible and it is identical to how we do it for APP services. The only thing we should change is that during creation of Azure Functions we should use _App Service Plan_ instead of Consumption plan. Once we select App service plan, we can click on All settings and create a hybrid connection and follow through to connect to on-premise SQL server. To know the difference between Consumption and App service plan please refer this article.

 

Build and Release Tasks in VSO

Simple set of Build task include the following where we build the solution and copy the artifacts. Including MSBuild Arguments for the sake of clarity to package the solution.

Since Azure Functions builds as a DLL there is small change I have done to my solution structure. Although Azure function builds as a DLL , it follow App service path for deployment. As such, for deployment automation, I have added another empty App service project to my solution, with a Folder named Library and made that as the output path for Function project. Added the reference to the function DLL from the newly generated DLL in the Library folder. We use this project during our Deployment to simply deploy App service which in-turn deploys our function App.

Coming to the release definition we will see the following tasks(you can have additional tasks based on your project requirement) ,

The first task is for Resource Deployment. In the beginning I had mentioned about Deployment of Resources through Resource template, this is where we do that for our function app. There are many resource template available in Azure Quick Start Template that you can use in your Resource group project for deployment.

For automation Resource group creation I have added an empty Azure Resource Group project and modified it

Here is the sample Template detail that needs to be placed in azuredeploy.json

 {
   "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
   "contentVersion": "1.0.0.0",
   "parameters": {
       "appName": {
           "type": "string",
           "metadata": {
               "description": "The name of the function app that you wish to create."
           }
       },
       "sku": {
           "type": "string",
           "allowedValues": [
               "Free",
               "Shared",
               "Basic",
               "Standard"
            ],
           "defaultValue": "Standard",
           "metadata": {
               "description": "The pricing tier for the hosting plan."
           }
       },

       "workerSize": {
           "type": "string",
           "allowedValues": [
               "0",
               "1",
               "2"
           ],
           "defaultValue": "0",
           "metadata": {
               "description": "The instance size of the hosting plan (small, medium, or large)."
           }
       },

       "storageAccountType": {
           "type": "string",
           "defaultValue": "Standard_LRS",
           "allowedValues": [
               "Standard_LRS",
               "Standard_GRS",
               "Standard_ZRS",
               "Premium_LRS"
           ],
           "metadata": {
               "description": "Storage Account type"
           }
       }
   },

   "variables": {
       "functionAppName": "[parameters('appName')]",
       "hostingPlanName": "[parameters('appName')]",
       "storageAccountName": "[concat(uniquestring(resourceGroup().id), 'functions')]"
   },

   "resources": [
       {
           "type": "Microsoft.Storage/storageAccounts",
           "name": "[variables('storageAccountName')]",
           "apiVersion": "2015-06-15",
           "location": "[resourceGroup().location]",
           "properties": {
               "accountType": "[parameters('storageAccountType')]"
           }
       },
       {
           "type": "Microsoft.Web/serverfarms",
           "apiVersion": "2015-04-01",
           "name": "[variables('hostingPlanName')]",
           "location": "[resourceGroup().location]",
           "properties": {
               "name": "[variables('hostingPlanName')]",
               "sku": "[parameters('sku')]",
               "workerSize": "[parameters('workerSize')]",
               "hostingEnvironment": "",
               "numberOfWorkers": 1
           }
       },
       {
           "apiVersion": "2015-04-01",
           "type": "Microsoft.Web/sites",
           "name": "[variables('functionAppName')]",
           "location": "[resourceGroup().location]",
           "kind": "functionapp",
           "properties": {
               "name": "[variables('functionAppName')]",
               "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]",
               "hostingEnvironment": "",
                     "clientAffinityEnabled": false,
                     "alwaysOn": true
          },
           "dependsOn": [
               "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]",
               "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"
           ],
           "resources": [

               {
                   "apiVersion": "2016-03-01",
                   "name": "appsettings",
                   "type": "config",
                   "dependsOn": [
                       "[resourceId('Microsoft.Web/sites', variables('functionAppName'))]",
                       "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"
                   ],

                   "properties": {

                       "AzureWebJobsStorage": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listkeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2015-05-01-preview').key1,';')]",

                       "AzureWebJobsDashboard": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listkeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2015-05-01-preview').key1,';')]",

                       "FUNCTIONS_EXTENSION_VERSION": "~1"
                   }
               }
                     ]
       }
   ]
}

 

Wanted to point out that you will need service principle for this task which you create by following the details in this article.

Azure App service task deployment is where we will deploy our App Service Deployment project that we created earlier. I won’t go in to details as this is straight forward.

Configuration setup is done through PowerShell script. Here is the PowerShell Script that you can use to set up Azure Function app settings, you can run this from a script task for deployment automation.

 Param(
[string]$myResourceGroup,
[string]$mySite)
$webApp = Get-AzureRMWebAppSlot -ResourceGroupName $myResourceGroup -Name $mySite -Slot production
$appSettingList = $webApp.SiteConfig.AppSettings
$setting = @{}
ForEach ($kvp in $appSettingList) {
$setting[$kvp.Name] = $kvp.Value
}
$setting['settingName1'] = <"Settingvalue1">
$setting['settingName2'] = <"Settingvalue2">
$setting['settingName3'] = <"Settingvalue3">
Set-AzureRMWebAppSlot -ResourceGroupName $myResourceGroup -Name $mySite -AppSettings $setting -Slot production
Write-Host “Done!”

It is important for us to have a proper test automation as that will decide how reliable our automation is.

Conclusion

Putting all together, in this post we saw how easy it is for us to stitch everything together were we have Resources being created from Resource Template, build and release is automated, Application settings created automatically through PowerShell for Serverless Azure Functions. Adding a CICD trigger in VSO will turn this to a model where each code check-in will trigger as build and its corresponding release, This, in a way, will help us get our code to production much faster.

Hope this helps while working with Azure functions…

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

^Ganesh Shankaran

Comments

  • Anonymous
    July 05, 2017
    Thanks Ganesh, its very helpful :)