Blog

Use VSTS to deploy Functions as Infrastructure as Code

27 Jan, 2017
Xebia Background Header Wave

Azure Functions enable you to easily run small pieces of code in the cloud. To do this right, you need to setup continuous delivery of the infrastructure and the code involved. Otherwise you will end with an uncontrolled environment where nobody knows what code is actually running. In this blog post I’ll describe how to setup a deployment pipeline for Functions with VSTS. This will enable you to deploy Functions as Infrastructure as Code.

vstsfunctionpipelineFrom an deployment perspective an Azure Function contains of two parts:

  1. Azure infrastructure
  2. Function code

Both the ARM template and the code can be deployed from VSTS. By doing this, you can manage functions like any other Azure resource.

Create your first function:

  1. Download the Azure Functions CLI
  2. Create a function from the commandline with: func init

Create a build
In the build you prepare the artifact for the actual release steps. It copies the ARM template and the function source code to the correct directory in the ArtifactStagingDirectory. As last step it does a publish of the ArtifactStagingDirectory. You can also add tests The build pipeline looks like:

build

The source code for the function is structured like:

src
| - host.json
| - MyTimerFunction
| | - function.json
| | - project.json
| | - run.csx

All files have to be copied to the ArtifactStagingDirectory.

Deploy the infrastructure
The deployment of the Azure infrastructure can be done by an ARM template. In the deployment pipeline the first step is to validate the arm template, the second step to do an deployment to an test environment and the last step to deploy it on production. The infrastructure underneath the Azure Function is:

  • Azure Storage Account
  • Hosting Plan
  • Function App

In VSTS the template can be validated and deployed with the Azure Resource Group Deployment task. An sample ARM template that can be used for the deployment of the infrastructure:

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "functionappname": {
      "type": "string"
    }
  },
  "variables": {
    "serviceplanname": "[concat('functionserviceplan-',parameters('functionappname'),'-', uniqueString(resourceGroup().id))]",
    "functionstoragename": "[substring(toLower(concat('st',parameters('functionappname'), uniqueString(resourceGroup().id))),0,24)]"
  },
  "resources": [
    {
        "name": "[variables('serviceplanname')]",
        "type": "Microsoft.Web/serverfarms",
        "kind": "functionapp",
        "sku": {
            "name": "Y1",
            "tier": "Dynamic",
            "size": "Y1",
            "family": "Y",
            "capacity": 0
        },
        "apiVersion": "2015-08-01",
        "location": "[resourceGroup().location]",
        "properties": { "name": "[variables('serviceplanname')]" }
    },
    {
      "type": "Microsoft.Storage/storageAccounts",
      "name": "[variables('functionstoragename')]",
      "apiVersion": "2016-01-01",
      "sku": { "name": "Standard_LRS" },
      "location": "[resourceGroup().location]",
      "kind": "Storage"
    },
    {
        "type": "Microsoft.Web/sites",
        "kind": "functionapp",
        "name": "[parameters('functionappname')]",
        "apiVersion": "2015-08-01",
        "location": "[resourceGroup().location]",
        "properties": {
            "name": "[parameters('functionappname')]",
            "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('serviceplanname'))]",
            "hostNames": [ "[concat(parameters('functionappname'),'.azurewebsites.net')]" ],
            "enabledHostNames": [
                "[concat(parameters('functionappname'),'.azurewebsites.net')]",
                "[concat(parameters('functionappname'),'.scm.azurewebsites.net')]"
            ],
            "siteConfig": {
                "appSettings": [
                    { "name": "FUNCTIONS_EXTENSION_VERSION", "value": "~1" },
                    { "name": "AzureWebJobsDashboard", "value": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('functionstoragename'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('functionstoragename')), providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).keys[0].value)]" },
                    { "name": "AzureWebJobsStorage", "value": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('functionstoragename'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('functionstoragename')), providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).keys[0].value)]" },
                    { "name": "WEBSITE_NODE_DEFAULT_VERSION", "value": "6.5.0" }
                ]
            }
        },
        "dependsOn": [
            "[resourceId('Microsoft.Web/serverfarms', variables('serviceplanname'))]",
            "[resourceId('Microsoft.Storage/storageAccounts', variables('functionstoragename'))]"
        ]
    }
  ]
}

This template will deploy all infrastructure needed to start deploying the actual functions.

Release the code
As last step of the release, the code is deployed in the functionapp. For this the Azure App Service Deploy task is used. App Service Name must point to the name you deployed with the ARM template, the package folder can be set to the src directory of you function app.

Now you are ready to deploy your first function with your deployment pipeline.

Questions?

Get in touch with us to learn more about the subject and related solutions

Explore related posts