DEV Community

Tsuyoshi Ushio
Tsuyoshi Ushio

Posted on

Deploy Linux AppService Function App with ARM template

I explain how to do it for Linux Premium Plan.

Compared with Linux Premium, Linux AppService is much easier. I'll add some resources for the best practices for the ARM template.

You can find a sample in here.

serverfarms

For the AppService Plan with Linux, kind as Linux and reserved as true is the point.

        {
            "type": "Microsoft.Web/serverfarms",
            "apiVersion": "2018-02-01",
            "name": "[parameters('hostingPlanName')]",
            "location": "[parameters('location')]",
            "kind": "Linux",
            "sku": {
                "tier": "[parameters('sku')]",
                "name": "[parameters('skuCode')]"
            },
            "properties": {
                "reserved": true
            }
        },

Web/sites

kind as functionapp,linux, linuxFxVersion is the point. For more details about the linuxFxVersion please refer to Deploy Premium Linux Function App with ARM template

For the premium and consumption plan, we DONT need WEBSITE_CONTENTAZUREFILECONNECTIONSTRING and WEBSITE_CONTENTSHARE.

        {
            "apiVersion": "2018-11-01",
            "name": "[parameters('name')]",
            "type": "Microsoft.Web/sites",
            "kind": "functionapp,linux",
            "location": "[parameters('location')]",
            "tags": {},
            "dependsOn": [
                "[resourceId('microsoft.insights/components', parameters('appInsightsName'))]",
                "[resourceId('Microsoft.Web/serverfarms', parameters('hostingPlanName'))]",
                "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]"
            ],
            "properties": {
                "name": "[parameters('name')]",
                "siteConfig": {
                    "linuxFxVersion": "[parameters('linuxFxVersion')]",
                    "alwaysOn": "[parameters('alwaysOn')]",
                    "appSettings": [
                        {
                            "name": "FUNCTIONS_EXTENSION_VERSION",
                            "value": "~3"
                        },
                        {
                            "name": "FUNCTIONS_WORKER_RUNTIME",
                            "value": "[parameters('functionWorkerRuntime')]"
                        },
                        {
                            "name": "APPINSIGHTS_INSTRUMENTATIONKEY",
                            "value": "[reference(concat('microsoft.insights/components/', parameters('appInsightsName')), '2015-05-01').InstrumentationKey]"
                        },
                        {
                            "name": "APPLICATIONINSIGHTS_CONNECTION_STRING",
                            "value": "[reference(concat('microsoft.insights/components/', parameters('appInsightsName')), '2015-05-01').ConnectionString]"
                        },
                        {
                            "name": "AzureWebJobsStorage",
                            "value": "[concat('DefaultEndpointsProtocol=https;AccountName=',parameters('storageAccountName'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2019-06-01').keys[0].value,';EndpointSuffix=','core.windows.net')]"
                        }
                    ]
                },
                "serverFarmId": "[concat('/subscriptions/', parameters('subscriptionId'),'/resourcegroups/', parameters('serverFarmResourceGroup'), '/providers/Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]",
                "clientAffinityEnabled": false
            }
        },

APPLICATIONINSIGHTS_CONNECTION_STRING

What is that? As you can see, it is the same value. They want to move on CONNECTION_STRING that they can add other key/value info. Currently, we need to specify both.

key value (sample)
APPINSIGHTS_INSTRUMENTATIONKEY 96cd2ffe-ef28-4809-b98a-8f1da9da4b5a
APPLICATIONINSIGHTS_CONNECTION_STRING InstrumentationKey=96cd2ffe-ef28-4809-b98a-8f1da9da4b5a

NOTE: The key is removed already.

ARM template resources

Understand the structure

I didn't know the functions section. It means, we can write custom functions with using existing build-in functions.

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "",
  "apiProfile": "",
  "parameters": {  },
  "variables": {  },
  "functions": [  ],
  "resources": [  ],
  "outputs": {  }
}
name required description sample
$schema Yes Location of the JSON schema file that describes the version of the template language. The version number you use depends on the scope of the deployment and your JSON editor. If you're using VS Code with the Azure Resource Manager tools extension, use the latest version https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#
contentVersion Yes Version of the template 1.0.0.0
apiProfile No An API version that serves as a collection of API versions for resource types. Use this value to avoid having to specify API versions for each resource in the template. For more details refer to Track versions using API profiles 2018–03-01-hybrid
parameters No Values that are provided when deployment is executed to customize resource deployment. See the sample
variables No Values that are used in the template. Usually modify it from parameters or default values. Variables
functions No User-defined functions that are available within the template. functions
resources Yes Resource types that are deployed or updated in a resource group or subscription. resources
outputs No Values that are returned after deployment. It is useful to get ConnectionString for example output

How to set the default value to the parameter?

defaultValue works. Also you can add description on the metadata section. It helps users to understand the meaning.

        "linuxFxVersion": {
            "type": "string",
            "defaultValue": "Java|8",
            "metadata": {
                "description": "linuxFxVersion that specify the language runtime for linux. (Java|8) for example"
            }               
        },

How to generate random characters

I want to add suffix for the storage account name. If you delete a storage account, then you want to use the same name, it won't work for a while (around 2 hours). Some resources requires random values. Terraform provide it, how about ARM template?

There is. However, it looks not great. It is uniquestring it says

The returned value isn't a random string, but rather the result of a hash function. The returned value is 13 characters long. It isn't globally unique.

I eventually, create the random string with bash script. I frequently reference this repo. DevOps Openhack Proctor Repository. This repo is like a treasure box of DevOps practices.

randomChar() {
    s=abcdefghijklmnopqrstuvxwyz0123456789
    p=$(( $RANDOM % 36))
    echo -n ${s:$p:1}
}

randomNum() {
    echo -n $(( $RANDOM % 10 ))
}

postfix="$(randomChar;randomChar;randomChar;randomNum;)"

How to deploy the template from Azure CLI

Create a resource group with az group create then use az deployment group create. I avoid the parameter files. I wanted to create resources with some base string. e.g. tsuyoshi then I'd like to post fix these. tsuyoshi012234sed, tsuyoshi-app, tsuyoshi-plan or something like that. It is enough through the parameters. Also, want to avoid the confusion when we both use parameter files and parameter.

if [ `az group exists -n $resourceGroup -o tsv` == false ]; then
    echo "Resource group with name" $resourceGroup "could not be found. Creating new resource group.."
    set -e
    (
        set -x
        echo "0-Provision Resource Group (az group create --name $resourceGroup --location $location)"
        az group create --name $resourceGroup --location "$location"
    )
else
    echo "Using existing resource group..."
fi

echo "SubscriptionId : ${subscriptionId}"

echo "Deploying Function App..."

echo "1-Provision FunctionApp (az group deployment create --name ${deploymentName} --resource-group $resourceGroupName --template-file $templateFile --parameters subscriptionId=$subscriptionId serverFarmResourceGroup=$resourceGroup name=$functionAppName location="${location}" hostingPlanName=$hostingPlanName storageAccountName=$storageAccountName appInsightsName=$appInsightsName linuxFxVersion=$linuxFxVersion  functionWorkerRuntime=$functionWorkerRuntime)"
az deployment group create \
    --name ${deploymentName} \
    --resource-group $resourceGroup \
    --template-file $templateFile \
    --parameters subscriptionId=$subscriptionId serverFarmResourceGroup=$resourceGroup name=$functionAppName location="${location}" hostingPlanName=$hostingPlanName storageAccountName=$storageAccountName appInsightsName=$appInsightsName linuxFxVersion="$linuxFxVersion" functionWorkerRuntime=$functionWorkerRuntime

Top comments (0)