Deploy Linux AppService Function App with ARM template
Published: 2020-08-13 06:08:04

I explain how to do it for [Linux Premium Plan](https://dev.to/tsuyoshiushio/deploy-premium-linux-function-app-with-arm-template-2i6n). 

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. 
* [Azure Functions Deployment Testing suite](https://github.com/TsuyoshiUshio/FunctionsDeploymentTestingSuite)

# serverfarms

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

* [Resolving “The parameter LinuxFxVersion has an invalid value” Error](https://crmchap.co.uk/resolving-the-parameter-linuxfxversion-has-an-invalid-value-error-azure-devops/)

```json
        {
            "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](https://dev.to/tsuyoshiushio/deploy-premium-linux-function-app-with-arm-template-2i6n)

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

```json
        {
            "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

* [Understand the structure and syntax of ARM templates](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/template-syntax)

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

```json
{
  "$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](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/templates-cloud-consistency#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](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/template-syntax#variables) |
| functions | No | User-defined functions that are available within the template. | [functions](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/template-syntax#functions) |
| resources | Yes | Resource types that are deployed or updated in a resource group or subscription.| [resources](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/template-syntax#resources) |
| outputs | No | Values that are returned after deployment. It is useful to get `ConnectionString` for example | [output](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/template-syntax#outputs)


## 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](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/template-functions-string#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](https://github.com/Azure-Samples/openhack-devops-proctor). This repo is like a treasure box of DevOps practices.

```bash
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. 

```bash
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

```

* [Deploy resources with ARM templates and Azure CLI](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/deploy-cli)

* [template functions](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/template-functions)

* [ARM template best practices](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/template-best-practices)