Home NewsX Dive into ARM template from a Function App

Dive into ARM template from a Function App

by info.odysseyx@gmail.com
0 comment 17 views


Create Function Apps (or other Azure resources) using ARM templates to organize related resources and highly customize some properties of these resources. However, if you do not understand the dependencies between Azure resources (for example, Function App depends on Storage Account), it is difficult to customize the template. This article briefly explains resource dependencies in ARM templates using Function App as an example.

index

Resource structure of a function app

ARM’s dependOn property

ARM’s runtime features

ARM’s nested distribution

Perfect example

Resource structure of a function app

In simple terms, a function app is an abstract concept that consists of an application, an app service plan, and storage. Their roles are as follows:

  • Application: The function app itself, which is responsible for executing various custom triggers and fetching or storing relevant data from other services.
  • App Service Plan: A collection of hardware resources that actually host your Function Apps. The serverless nature of a Function App is transparent to end users, but there are still instances that load and run these applications.
  • Storage (i.e. storage account): Function apps use blobs/files to store application-related settings, actual code, and other data.

From the above explanation, we can see that there is a specific order in which we need to create this set of resources. Storage and App Service Plans must be created before the Function App.

ARM’s dependOn property

Please refer to The following ARM template snippet:

derring_0-1725263554070.png

If we look closely at the process of creating a storage account, we will see that it is also an abstract concept that includes several services (e.g. blob, file, table, queue). The actual file is a more refined resource that needs to be created. Correctly, the file service must be created before the file share, and the order of creation is as follows: storage account before file service, before file share.

That is, file share creation depends on the existence of a file service, and file service creation depends on the existence of a storage account.

The so-called “depends on” is the dependOn attribute of the ARM template. In the above example, you can see the dependency behavior of these three resources. The description of dependOn can help you ensure the order of resource creation during the process, thus avoiding the problem of missing resources.

ARM’s runtime features

Can I use the same approach to specify that App Service Plan and Function App should be created after Storage? The answer is no.. The reason is that authorization and communication between Function App and Storage basically uses Connection String, which is randomly generated by the system while creating Storage. This makes it impossible (and insecure) for ARM template to pre-specify Storage’s Connection String.

Instead, ARM uses functions like listKeys to dynamically retrieve the relevant Connection String from an already created Storage resource and use it as a parameter to the Function App to later communicate with Storage. See the following ARM template snippet.

derring_1-1725263708508.png

The problem is that the listKeys function is called a runtime function, and its execution order has the highest priority in the entire deployment. In the same deployment, the runtime function executes immediately and returns the result without considering the order of dependOn. For more information, see this article. Resource not found (dependsOn not working) – ARM Template Sandbox (bmoore-msft.blog)

Is there any way to solve this problem? The answer is yes. The runtime functions mentioned above will only be executed first in the same deployment. You can avoid this problem by modifying the deployment to a nested form.

ARM’s nested distribution

Please refer to The following ARM template snippet:

derring_0-1725265016694.png

In addition to creating the following three resources from the original resource, you can also create other deployments, such as “Microsoft.Resources/deployments”.

  • “Microsoft.Storage/storageAccounts”
  • “Microsoft.Storage/storageAccounts/fileservices”
  • “Microsoft.Storage/storageAccounts/fileServices/shares”

Adding the dependOn attribute will cause all executions of ASPResourcesDeployment, including runtime functions, to be executed only after Storage has been created.

Perfect example

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "resourcePrefix": {
      "type": "string",
      "defaultValue": "[concat('ringe', '-', uniqueString(newGuid()))]",
      "metadata": {
        "description": "Do not modify this name"
      }
    }
  },
  "variables": {
    "storageAccountName": "[concat(replace(parameters('resourcePrefix'),'-', ''), 'sa')]",
    "storageAccountSkuName": "Standard_LRS",
    "storageAccountSupportsHttpsTrafficOnly": true,
    "storageAccountMinimumTlsVersion": "TLS1_2",
    "storageAccountDefaultToOAuthAuthentication": true,
    "storageAccountIsShareSoftDeleteEnabled": true,
    "storageAccountShareSoftDeleteRetentionDays": 7,
    "storageAccountFileShareName": "ringe-file",
    "storageAccountFileShareShareQuota": 5120,
    "storageAccountFileShareEnabledProtocols": "SMB",
    "planName": "[concat(parameters('resourcePrefix'), '-', 'plan')]",
    "planKind": "linux",
    "planSkuTier": "Dynamic",
    "planSkuName": "Y1",
    "planWorkerSize": "0",
    "planWorkerSizeId": "0",
    "planNumberOfWorkers": "1",
    "planReserved": true,
    "functionName": "[concat(parameters('resourcePrefix'), '-', 'func')]",
    "functionKind": "functionapp,linux",
    "functionSiteConfigAppSettingsFUNCTIONS_EXTENSION_VERSION": "~4",
    "functionSiteConfigAppSettingsFUNCTIONS_WORKER_RUNTIME": "node",
    "functionSiteConfigAppSettingsWEBSITE_CONTENTSHARE": "ringe-func",
    "functionSiteConfigUse32BitWorkerProcess": false,
    "functionSiteConfigFtpsState": "FtpsOnly",
    "functionSiteConfigLinuxFxVersion": "Node|18",
    "functionClientAffinityEnabled": false,
    "functionPublicNetworkAccess": "Enabled",
    "functionHttpsOnly": true,
    "functionServerFarmId": "[concat('subscriptions/', subscription().subscriptionId, '/resourcegroups/', resourceGroup().name, '/providers/Microsoft.Web/serverfarms/', variables('planName'))]",
    "scriptName": "[concat(replace(parameters('resourcePrefix'),'-', ''), 'sh')]"
  },
  "resources": [
    {
      "comments": "Storage Account",
      "apiVersion": "2022-05-01",
      "type": "Microsoft.Storage/storageAccounts",
      "location": "[resourceGroup().location]",
      "name": "[variables('storageAccountName')]",
      "tags": {},
      "sku": {
          "name": "[variables('storageAccountSkuName')]"
      },
      "properties": {
          "supportsHttpsTrafficOnly": "[variables('storageAccountSupportsHttpsTrafficOnly')]",
          "minimumTlsVersion": "[variables('storageAccountMinimumTlsVersion')]",
          "defaultToOAuthAuthentication": "[variables('storageAccountDefaultToOAuthAuthentication')]"
      }
    },
    {
      "comments": "Storage Account: fileservices",
      "apiVersion": "2022-05-01",
      "type": "Microsoft.Storage/storageAccounts/fileservices",
      "name": "[concat(variables('storageAccountName'), '/default')]",
      "properties": {
        "shareDeleteRetentionPolicy": {
          "enabled": "[variables('storageAccountIsShareSoftDeleteEnabled')]",
          "days": "[variables('storageAccountShareSoftDeleteRetentionDays')]"
        }
      },
      "dependsOn": [
        "[concat('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]"
      ]
    },
    {
      "comments": "Storage Account: fileshares",
      "apiVersion": "2021-04-01",
      "type": "Microsoft.Storage/storageAccounts/fileServices/shares",
      "location": "[resourceGroup().location]",
      "name": "[concat(variables('storageAccountName'), '/default/', variables('storageAccountFileShareName'))]",
      "dependsOn": [
        "[resourceId('Microsoft.Storage/storageAccounts/fileServices', variables('storageAccountName'), 'default')]",
        "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"
      ],
      "properties": {
          "shareQuota": "[variables('storageAccountFileShareShareQuota')]",
          "enabledProtocols": "[variables('storageAccountFileShareEnabledProtocols')]"
      }
    },
    {
      "comments": "ASP nested resources due to Storage Account",
      "apiVersion": "2017-05-10",
      "type": "Microsoft.Resources/deployments",
      "name": "ASPResourcesDeployment",
      "dependsOn": [
        "[concat('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]"
      ],
      "properties": {
        "mode": "Incremental",
        "template": {
          "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
          "contentVersion": "1.0.0.0",
          "parameters": {},
          "variables": {},
          "resources": [
            {
              "comments": "ASP",
              "apiVersion": "2018-11-01",
              "type": "Microsoft.Web/serverfarms",
              "location": "[resourceGroup().location]",
              "name": "[variables('planName')]",
              "kind": "[variables('planKind')]",
              "sku": {
                "Tier": "[variables('planSkuTier')]",
                "Name": "[variables('planSkuName')]"
              },
              "tags": {},
              "dependsOn": [],
              "properties": {
                "name": "[variables('planName')]",
                "workerSize": "[variables('planWorkerSize')]",
                "workerSizeId": "[variables('planWorkerSizeId')]",
                "numberOfWorkers": "[variables('planNumberOfWorkers')]",
                "reserved": "[variables('planReserved')]"
              }
            },
            {
              "comments": "Function App",
              "apiVersion": "2018-11-01",
              "type": "Microsoft.Web/sites",
              "location": "[resourceGroup().location]",
              "name": "[variables('functionName')]",
              "kind": "[variables('functionKind')]",
              "tags": {},
              "dependsOn": [
                "[concat('Microsoft.Web/serverfarms/', variables('planName'))]"
              ],
              "properties": {
                "name": "[variables('functionName')]",
                "siteConfig": {
                  "appSettings": [
                    {
                      "name": "FUNCTIONS_EXTENSION_VERSION",
                      "value": "[variables('functionSiteConfigAppSettingsFUNCTIONS_EXTENSION_VERSION')]"
                    },
                    {
                      "name": "FUNCTIONS_WORKER_RUNTIME",
                      "value": "[variables('functionSiteConfigAppSettingsFUNCTIONS_WORKER_RUNTIME')]"
                    },
                    {
                      "name": "AzureWebJobsStorage",
                      "value": "[concat('DefaultEndpointsProtocol=https;AccountName=",variables("storageAccountName'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts',variables('storageAccountName')),'2019-06-01').keys[0].value,';EndpointSuffix=','core.windows.net')]"
                    },
                    {
                      "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
                      "value": "[concat('DefaultEndpointsProtocol=https;AccountName=",variables("storageAccountName'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts',variables('storageAccountName')),'2019-06-01').keys[0].value,';EndpointSuffix=','core.windows.net')]"
                    },
                    {
                      "name": "WEBSITE_CONTENTSHARE",
                      "value": "[variables('functionSiteConfigAppSettingsWEBSITE_CONTENTSHARE')]"
                    }
                  ],
                  "cors": {
                    "allowedOrigins": []
                  },
                  "use32BitWorkerProcess": "[variables('functionSiteConfigUse32BitWorkerProcess')]",
                  "ftpsState": "[variables('functionSiteConfigFtpsState')]",
                  "linuxFxVersion": "[variables('functionSiteConfigLinuxFxVersion')]"
                },
                "clientAffinityEnabled": "[variables('functionClientAffinityEnabled')]",
                "virtualNetworkSubnetId": null,
                "publicNetworkAccess": "[variables('functionPublicNetworkAccess')]",
                "httpsOnly": "[variables('functionHttpsOnly')]",
                "serverFarmId": "[variables('functionServerFarmId')]"
              }
            }
          ],
          "outputs": {}
        }
      }
    }
  ],
  "outputs": {}
}

You can publish this template using a simple method (you can replace “ringe” that appears in the template and CLI with your project name).

az group create --name ringe-test-rg --location westus
az deployment group create --resource-group ringe-test-rg --template-file ringe.json





Source link

You may also like

Leave a Comment

Our Company

Welcome to OdysseyX, your one-stop destination for the latest news and opportunities across various domains.

Newsletter

Subscribe my Newsletter for new blog posts, tips & new photos. Let's stay updated!

Laest News

@2024 – All Right Reserved. Designed and Developed by OdysseyX