Search code examples
azureautomationazure-functionsazure-resource-manager

How to deploy a Azure Function App with Blob Trigger along with Code using ARM Template?


I am creating an ARM template for the automation of Function App deployment from creation to code deployment. The Function App is configured on Linux (Python), and it activates a Blob Trigger for image processing when a user uploads an image. To achieve this using the ARM template, the necessary steps are:

  1. Establish a Storage Account.
  2. Create an App Insights instance to store logs.
  3. Develop a Function App.
  4. Define a specific function within the app.
  5. Configure a Blob Trigger, linking the function to a storage account.
  6. Deploy the code located at the specified URI to an Azure Storage Account.

I am following the Stack Overflow instructions, and my specific scenario aligns with the details provided in the below stackoverflow post (shortened the URL): https://shorturl.at/GJ247

The Code I have used refers to the above post:

{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "functionAppName": {
            "defaultValue": "func",
            "type": "String",
            "metadata": {
                "description": "The name of the Azure Function app."
            }
        },
        "storageAccountName": {
            "defaultValue": "strgacc",
            "type": "String",
            "metadata": {
                "description": "The name of the storage account that you wish to use."
            }
        },
        "storageAccountType": {
            "defaultValue": "Standard_LRS",
            "allowedValues": [
                "Standard_LRS",
                "Standard_GRS",
                "Standard_RAGRS"
            ],
            "type": "String",
            "metadata": {
                "description": "Storage Account type"
            }
        },
        "blobContainerName": {
            "defaultValue": "myBlob",
            "type": "String",
            "metadata": {
                "description": "The name of the blob container that you wish to use."
            }
        },
        "location": {
            "defaultValue": "[resourceGroup().location]",
            "type": "String",
            "metadata": {
                "description": "Location for all resources."
            }
        },
        "appInsightsLocation": {
            "defaultValue": "[resourceGroup().location]",
            "type": "String",
            "metadata": {
                "description": "Location for Application Insights"
            }
        },
        "functionWorkerRuntime": {
            "defaultValue": "python",
            "allowedValues": [
                "dotnet",
                "node",
                "python",
                "java"
            ],
            "type": "String",
            "metadata": {
                "description": "The language worker runtime to load in the function app."
            }
        },
        "functionName": {
            "defaultValue": "blobfunc",
            "type": "String",
            "metadata": {
                "description": "The name of the function that you wish to create."
            }
        },
        "linuxFxVersion": {
            "defaultValue": "Python|3.10",
            "type": "String",
            "metadata": {
                "description": "Required for Linux app to represent runtime stack in the format of 'runtime|runtimeVersion'. For example: 'Python|3.10'"
            }
        }
    },
    "variables": {
        "hostingPlanName": "[parameters('functionAppName')]",
        "applicationInsightsName": "[parameters('functionAppName')]"
    },
    "resources": [
        {
            "type": "Microsoft.Web/serverfarms",
            "apiVersion": "2021-02-01",
            "name": "[variables('hostingPlanName')]",
            "location": "[parameters('location')]",
             "properties": {
              "reserved": true
            },
            "sku": {
                "name": "S1",
                "tier": "Standard"
            }       
        },
        {
            "type": "Microsoft.Insights/components",
            "apiVersion": "2020-02-02",
            "name": "[variables('applicationInsightsName')]",
            "location": "[parameters('appInsightsLocation')]",
            "tags": {
                "[format('hidden-link:{0}', resourceId('Microsoft.Web/sites', variables('applicationInsightsName')))]": "Resource"
            },
            "kind": "web",
            "properties": {
                "Application_Type": "web"
            }
        },
        {
            "type": "Microsoft.Web/sites",
            "apiVersion": "2022-03-01",
            "name": "[parameters('functionAppName')]",
            "location": "[parameters('location')]",
            "dependsOn": [
                "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]"
            ],
            "kind": "functionapp,linux",
            "properties": {
                "reserved": true,
                "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]",
                "siteConfig": {
                    "linuxFxVersion": "[parameters('linuxFxVersion')]",
                    "appSettings": [
                        {
                            "name": "APPINSIGHTS_INSTRUMENTATIONKEY",
                            "value": "[reference(resourceId('Microsoft.Insights/components', parameters('functionAppName')), '2015-05-01').InstrumentationKey]"
                        },
                        {
                            "name": "AzureWebJobsStorage",
                            "value": "[format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', parameters('storageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2019-06-01').keys[0].value)]"
                        },
                        {
                            "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
                            "value": "[format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', parameters('storageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2019-06-01').keys[0].value)]"
                        },
                        {
                            "name": "WEBSITE_CONTENTSHARE",
                            "value": "[toLower(parameters('functionAppName'))]"
                        },
                        {
                            "name": "FUNCTIONS_EXTENSION_VERSION",
                            "value": "~4"
                        },
                        {
                            "name": "FUNCTIONS_WORKER_RUNTIME",
                            "value": "[parameters('functionWorkerRuntime')]"
                        },
                        {
                            "name": "SCM_DO_BUILD_DURING_DEPLOYMENT",
                            "value": "true"
                        }
                    ]
                }
            }
        },
        {
            "type": "Microsoft.Storage/storageAccounts",
            "apiVersion": "2021-08-01",
            "name": "[parameters('storageAccountName')]",
            "location": "[parameters('location')]",
            "sku": {
                "name": "Standard_LRS"
            },
            "kind": "StorageV2"
        },
        {
            "type": "Microsoft.Storage/storageAccounts/blobServices",
            "apiVersion": "2021-06-01",
            "name": "[format('{0}/{1}', parameters('storageAccountName'), 'default')]",
            "dependsOn": [
                "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]"
            ]
        },
        {
            "type": "Microsoft.Storage/storageAccounts/blobServices/containers",
            "apiVersion": "2019-06-01",
            "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), 'default', format('{0}default{1}', parameters('storageAccountName'), parameters('blobContainerName')))]",
            "dependsOn": [
                "[resourceId('Microsoft.Storage/storageAccounts/blobServices', parameters('storageAccountName'), 'default')]"
            ],
            "properties": {
                "publicAccess": "None"
            }
        },
        {
            "type": "Microsoft.Web/sites/functions",
            "apiVersion": "2018-02-01",
            "name": "[format('{0}/{1}', parameters('functionAppName'), format('{0}', parameters('functionName')))]",
            "dependsOn": [
                "[resourceId('Microsoft.Web/sites', parameters('functionAppName'))]",
                "[resourceId('Microsoft.Storage/storageAccounts/blobServices/containers', parameters('storageAccountName'), 'default', format('{0}default{1}', parameters('storageAccountName'), parameters('blobContainerName')))]"
            ],
            "properties": {
                "config": {
                    "bindings": [
                        {
                            "name": "Blob",
                            "type": "blobTrigger",
                            "direction": "in",
                            "path": "[format('default/{0}/{{name}}', parameters('blobContainerName'))]",
                            "connection": "AzureWebJobsStorage"
                        }
                    ],
                    "disabled": false
                }
            }
        }
    ]
}

I'm currently working on implementing the solution, but I've run into an error on the deployments page. Can someone please guide me in creating it? The error occurred while creating the function within the Function App. Could this be due to the absence of code inside it, or are there other potential issues?

{
    "status": "Failed",
    "error": {
        "code": "BadRequest",
        "message": "Encountered an error (InternalServerError) from host runtime.",
        "details": [
            {
                "message": "Encountered an error (InternalServerError) from host runtime."
            },
            {
                "code": "BadRequest"
            },
            {}
        ]
    }
}
---


  [1]: https://stackoverflow.com/questions/76812998/function-is-not-creating-inside-azure-function-app-using-arm-template

Solution

  • If you need to deploy the Azure function with code and requirement.txt then you will require a few things

    • One FTP server where you can keep your code file and requirements.txt file
    • One VM to deploy the function to the Azure function app
    • one script while deploying the script in the ARM template.

    Steps for deploying the function

    export FUNCTION_APP=${1}
    [ -z $FUNCTION_APP ]  && $ECHO "Error...FunctionApp Name is missing." && exit 1
    
    export RG_NAME=${3}
    [ -z $RG_NAME ]  && $ECHO "Error...RG NAME is missing." && exit 1
    
    export STORAGE_ACCOUNT=${4}
    [ -z $STORAGE_ACCOUNT ]  && $ECHO "Error... STORAGE_ACCOUNT NAME is missing." && exit 1
    
    export BLOB_COTAINER_NAME=${5}
    [ -z $BLOB_COTAINER_NAME ]  && $ECHO "Error...BLOB_COTAINER_NAME is missing." && exit 1
    
    
    copy_Binary()
    {
      [ -f /tmp/.copy_Binary ] && return 0
      
      $ECHO "Installing the Azure CLI..." >> $LOG
      curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
      [ $? -ne 0 ] && $ECHO "Failed to install Azure-CLI into the VM." >> $LOG && exit 1
      
      $ECHO "Configuring the Azure CLI..." >> $LOG
      az login --service-principal -u fcabaecd-984c-4ed0-9d05-69429e34de44 -p mkx8Q~upcFFlzMMBm_bTS7ewJxx9gT6taK-~Qb5R --tenant 31472f81-8fe4-49ec-8bc3-fa1c295640d7 --allow-no-subscriptions  >> $LOG 2>&1
      [ $? -ne 0 ] && $ECHO "Failed to login to azure  " >> $LOG && exit 1
      
      $ECHO "Setting the Customer Subscription..." >> $LOG
      az account set --subscription $SUBSCRIPTION_ID >> $LOG 2>&1
      [ $? -ne 0 ] && $ECHO "Failed to set subscription" >> $LOG && exit 1
    
      $ECHO "Setting the Customer Resource Group..." >> $LOG
      az configure --defaults group=$RG_NAME >> $LOG 2>&1
      [ $? -ne 0 ] && $ECHO "Failed to set resource group" >> $LOG && exit 1
      
      $ECHO "Update the config..." >> $LOG
      $ECHO "[core]" >> /root/.azure/config
      $ECHO "output = table" >> /root/.azure/config
      
      apt install unzip >> $LOG
      
      cd /home/ubuntu
      mkdir Azure_CFT
      cd Azure_CFT
      pwd >> $LOG
      $ECHO "Downloading Azure Function Core Tool Binary" >> $LOG
      wget "https://github.com/Azure/azure-functions-core-tools/releases/download/4.0.5390/Azure.Functions.Cli.linux-x64.4.0.5390.zip" >> $LOG
      [ $? -ne 0 ] && $ECHO "Failed to download the Azure Function Core Tools zip file." >> $LOG && exit 1
      
      $ECHO "Unzipping the Azure Function Core Tools Binary" >> $LOG
      unzip -d azure-functions-cli Azure.Functions.Cli.linux-x64.*.zip >> $LOG
      [ $? -ne 0 ] && $ECHO "Failed to Unzip the Azure Function Core Tools zip file." >> $LOG && exit 1
      
      cd azure-functions-cli 
      chmod +x func
      chmod +x gozip
      ./func >> $LOG
    
      export PATH=`pwd`:$PATH
      func
      
      
      $ECHO "Listing the functions in the $FUNCTION_APP" >> $LOG
      func azure functionapp list-functions $FUNCTION_APP >> $LOG
      [ $? -ne 0 ] && $ECHO "Failed to List the functions in FunctionApp" >> $LOG && exit 1
      
      cd /home/ubuntu
      mkdir NewFunction
      cd NewFunction
      $ECHO "Creating the new function project" >> $LOG
      func init FunctionProjFolder --worker-runtime python --model V2 >> $LOG
      
      $ECHO "Downloading the function_app.py file and requirements.txt files" >> $LOG
      FTP_UNAME=username
      FTP_PASS=password
      FTP_URL=ftp_url
      wget "ftp://${FTP_UNAME}:${FTP_PASS}@${FTP_URL}/function_app.py" >> $LOG 2>&1
      [ $? -ne 0 ] && $ECHO "Failed to Download the Function App Python file." >> $LOG && exit 1
      
      wget "ftp://${FTP_UNAME}:${FTP_PASS}@${FTP_URL}/requirements.txt" >> $LOG 2>&1
      [ $? -ne 0 ] && $ECHO "Failed to download the Requirements file." >> $LOG && exit 1
      
      
    
      
      $ECHO "Copying the updated function_app.py and requirements.txt in the FunctionProjFolder" >> $LOG
      cp function_app.py requirements.txt FunctionProjFolder/ >> $LOG
        
      cd FunctionProjFolder
      $ECHO "Publishing the function in the $FUNCTION_APP" >> $LOG
      #printf "${BLOB_COTAINER_NAME}/{name}\nAzureWebJobsStorage" | func new --language Python --template "Blob trigger" --name "myAzureFunction" >> $LOG
      func azure functionapp publish $FUNCTION_APP >> $LOG
      
    }
    
    #
    # Main
    #
    
    $ECHO "STEP: EXECUTION STARTED" >> $LOG
    $ECHO "Parameters Passed..." >> $LOG
    $ECHO "SUBSCRIPTION_ID= ${SUBSCRIPTION_ID}" >> $LOG
    $ECHO "RG_NAME= ${RG_NAME}" >> $LOG
    $ECHO "FUNCTION_APP= ${FUNCTION_APP}" >> $LOG
    $ECHO "STORAGE_ACCOUNT= ${STORAGE_ACCOUNT}" >> $LOG
    copy_Binary
    
    $ECHO "STEP: COMPLETED SUCCESSFULLY" >> $LOG
    

    While creating the ARM template create one script file keep it in the script folder and paste all the above commands for deploying the function to the Function App.

    Note:- define the ARM template for the VM in your mainTemplate.json file. If you don't require a VM then you can delete the VM after function deployment.

    Your zip file should follow the below structure.

      Azuretemplate.zip
        
        scripts(folder)/script.sh
        createUiDefinition.json
        mainTemplate.json
        viewDefinition.json
    

    and create the zip file and try to deploy it.