├── .github ├── CODE_OF_CONDUCT.md ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── function-app-dedicated-plan ├── README.md ├── azuredeploy.json ├── azuredeploy.parameters.json └── main.bicep ├── function-app-deployment-slot ├── README.md ├── azuredeploy.json ├── azuredeploy.parameters.json └── main.bicep ├── function-app-linux-consumption-remote-build ├── README.md ├── azuredeploy.json ├── azuredeploy.parameters.json └── main.bicep ├── function-app-linux-consumption ├── README.md ├── azuredeploy.json ├── azuredeploy.parameters.json └── main.bicep ├── function-app-linux-flex-consumption ├── README.md ├── azuredeploy.json └── azuredeploy.parameters.json ├── function-app-premium-plan ├── README.md ├── azuredeploy.json ├── azuredeploy.parameters.json └── main.bicep ├── function-app-private-endpoints-storage-private-endpoints ├── README.md ├── azuredeploy.json ├── azuredeploy.parameters.json ├── images │ └── function-app-private-endpoints-storage-private-endpoints.jpg └── main.bicep ├── function-app-storage-private-endpoints ├── README.md ├── azuredeploy.json ├── azuredeploy.parameters.json ├── images │ └── function-app-storage-private-endpoints.jpg └── main.bicep ├── function-app-vnet-integration ├── README.md ├── azuredeploy.json ├── azuredeploy.parameters.json └── main.bicep ├── function-app-windows-consumption ├── README.md ├── azuredeploy.json ├── azuredeploy.parameters.json └── main.bicep ├── images └── deploytoazure.png ├── zip-deploy-arm-az-cli ├── README.md ├── azuredeploy.json └── azuredeploy.parameters.json ├── zip-deploy-arm-github-workflow ├── README.md ├── azuredeploy.json └── workflow.yml └── zip-deploy-run-from-package ├── README.md ├── azuredeploy.json └── azuredeploy.parameters.json /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | > Please provide us with the following information: 5 | > --------------------------------------------------------------- 6 | 7 | ### This issue is for a: (mark with an `x`) 8 | ``` 9 | - [ ] bug report -> please search issues before submitting 10 | - [ ] feature request 11 | - [ ] documentation issue or request 12 | - [ ] regression (a behavior that used to work and stopped in a new release) 13 | ``` 14 | 15 | ### Minimal steps to reproduce 16 | > 17 | 18 | ### Any log messages given by the failure 19 | > 20 | 21 | ### Expected/desired behavior 22 | > 23 | 24 | ### OS and Version? 25 | > Windows 7, 8 or 10. Linux (which distribution). macOS (Yosemite? El Capitan? Sierra?) 26 | 27 | ### Versions 28 | > 29 | 30 | ### Mention any other details that might be useful 31 | 32 | > --------------------------------------------------------------- 33 | > Thanks! We'll be in touch soon. 34 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Purpose 2 | 3 | * ... 4 | 5 | ## Does this introduce a breaking change? 6 | 7 | ``` 8 | [ ] Yes 9 | [ ] No 10 | ``` 11 | 12 | ## Pull Request Type 13 | What kind of change does this Pull Request introduce? 14 | 15 | 16 | ``` 17 | [ ] Bugfix 18 | [ ] Feature 19 | [ ] Code style update (formatting, local variables) 20 | [ ] Refactoring (no functional changes, no api changes) 21 | [ ] Documentation content changes 22 | [ ] Other... Please describe: 23 | ``` 24 | 25 | ## How to Test 26 | * Get the code 27 | 28 | ``` 29 | git clone [repo-address] 30 | cd [repo-name] 31 | git checkout [branch-name] 32 | npm install 33 | ``` 34 | 35 | * Test the code 36 | 37 | ``` 38 | ``` 39 | 40 | ## What to Check 41 | Verify that the following are valid 42 | * ... 43 | 44 | ## Other Information 45 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to [project-title] 2 | 3 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 4 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 5 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. 6 | 7 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 8 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 9 | provided by the bot. You will only need to do this once across all repos using our CLA. 10 | 11 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 12 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 13 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 14 | 15 | - [Code of Conduct](#coc) 16 | - [Issues and Bugs](#issue) 17 | - [Feature Requests](#feature) 18 | - [Submission Guidelines](#submit) 19 | 20 | ## Code of Conduct 21 | Help us keep this project open and inclusive. Please read and follow our [Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 22 | 23 | ## Found an Issue? 24 | If you find a bug in the source code or a mistake in the documentation, you can help us by 25 | [submitting an issue](#submit-issue) to the GitHub Repository. Even better, you can 26 | [submit a Pull Request](#submit-pr) with a fix. 27 | 28 | ## Want a Feature? 29 | You can *request* a new feature by [submitting an issue](#submit-issue) to the GitHub 30 | Repository. If you would like to *implement* a new feature, please submit an issue with 31 | a proposal for your work first, to be sure that we can use it. 32 | 33 | * **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr). 34 | 35 | ## Submission Guidelines 36 | 37 | ### Submitting an Issue 38 | Before you submit an issue, search the archive, maybe your question was already answered. 39 | 40 | If your issue appears to be a bug, and hasn't been reported, open a new issue. 41 | Help us to maximize the effort we can spend fixing issues and adding new 42 | features, by not reporting duplicate issues. Providing the following information will increase the 43 | chances of your issue being dealt with quickly: 44 | 45 | * **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps 46 | * **Version** - what version is affected (e.g. 0.1.2) 47 | * **Motivation for or Use Case** - explain what are you trying to do and why the current behavior is a bug for you 48 | * **Browsers and Operating System** - is this a problem with all browsers? 49 | * **Reproduce the Error** - provide a live example or a unambiguous set of steps 50 | * **Related Issues** - has a similar issue been reported before? 51 | * **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be 52 | causing the problem (line of code or commit) 53 | 54 | You can file new issues by providing the above information at the corresponding repository's issues link: https://github.com/[organization-name]/[repository-name]/issues/new]. 55 | 56 | ### Submitting a Pull Request (PR) 57 | Before you submit your Pull Request (PR) consider the following guidelines: 58 | 59 | * Search the repository (https://github.com/[organization-name]/[repository-name]/pulls) for an open or closed PR 60 | that relates to your submission. You don't want to duplicate effort. 61 | 62 | * Make your changes in a new git fork: 63 | 64 | * Commit your changes using a descriptive commit message 65 | * Push your fork to GitHub: 66 | * In GitHub, create a pull request 67 | * If we suggest changes then: 68 | * Make the required updates. 69 | * Rebase your fork and force push to your GitHub repository (this will update your Pull Request): 70 | 71 | ```shell 72 | git rebase master -i 73 | git push -f 74 | ``` 75 | 76 | That's it! Thank you for your contribution! 77 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_type: sample 3 | languages: 4 | - csharp 5 | - python 6 | - java 7 | - nodejs 8 | - typescript 9 | - json 10 | products: 11 | - azure-functions 12 | - azure 13 | --- 14 | 15 | # ARM Templates for Function App Deployment 16 | 17 | This repo contains currently available Azure Resource Manager templates for deploying Function App with recommended settings and best practices. 18 | 19 | ## Flex Consumption 20 | 21 | The examples for the Flex Consumption plan are located in a different place. 22 | 23 | Please visit [FlexConsumption Samples](https://github.com/azure-samples/azure-functions-flex-consumption-samples/) for these scenarios. 24 | 25 | 1. Functions Quickstarts using Azure Developer CLI to create and deploy your code. 26 | 2. ARM Template [Sample](https://github.com/Azure-Samples/azure-functions-flex-consumption-samples/blob/main/IaC/armtemplate/README.md) 27 | 3. Bicep Template [Sample](https://github.com/Azure-Samples/azure-functions-flex-consumption-samples/blob/main/IaC/bicep/README.md) 28 | 4. Terraform Template [Sample](https://github.com/Azure-Samples/azure-functions-flex-consumption-samples/blob/main/IaC/terraform/README.md) 29 | 30 | Create and deploy Functions app for following OS and SKU combinations: 31 | 32 | 1. Create [Function App with Premium Plan](/function-app-premium-plan) on Windows/Linux 33 | 2. Create [Function App with Dedicated Plan](/function-app-dedicated-plan) on Windows/Linux 34 | 3. Create [Function App with Consumption Plan on Windows](/function-app-windows-consumption) 35 | 4. Create Function App with Consumption Plan on Linux: 36 | - [Deploy zip package with Run From Package](/function-app-linux-consumption) 37 | - [Deploy zip package with Remote Build](/function-app-linux-consumption-remote-build) 38 | 5. Create [Function App with Flex Consumption Plan on Linux](/function-app-linux-flex-consumption) 39 | 40 | Create Functions app and resources for following networking scenarios: 41 | 42 | 1. Create [Function App with a Deployment Slot](/function-app-deployment-slot) 43 | 2. Create [Function App with Virtual Network Integration](/function-app-vnet-integration) 44 | 3. Create [Function App with Azure Storage private endpoints](/function-app-storage-private-endpoints) 45 | 4. Create [Function App with private endpoints and Azure Storage with private endpoints](/function-app-private-endpoints-storage-private-endpoints) 46 | 47 | Deploy Functions app code for following scenarios: 48 | 1. Deploy [Function App with ZipDeploy using Run From Package](/zip-deploy-run-from-package) 49 | 2. If Function App with private endpoints and Azure Storage with private endpoints: 50 | - Deploy [Function App with ARM template using Az CLI](/zip-deploy-arm-az-cli) 51 | - Deploy [Function App with ARM template in GitHub Workflow](/zip-deploy-arm-github-workflow) 52 | 53 | This repo also contains wiki pages on the following: 54 | 55 | 1. [Best Practices Guide](../../wiki/Best-Practices-Guide) 56 | 2. [Frequently Asked Questions (FAQs)](../../wiki/Frequently-Asked-Questions-(FAQs)) 57 | 3. [Important App Settings for Function Apps](../../wiki/App-Settings-for-Function-Apps) 58 | 4. [ARM Templates for Function App with different Hosting Plans](../../wiki/ARM-Templates-for-Function-Apps-with-different-Hosting-Plans) 59 | 60 | For more information on deploying ARM template, please refer: [Deploy resources with ARM templates](https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/deploy-portal) 61 | 62 | 63 | -------------------------------------------------------------------------------- /function-app-dedicated-plan/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: This template provisions a function app on a dedicated hosting plan, meaning it will be run and billed just like any App Service site. 3 | page_type: sample 4 | products: 5 | - azure 6 | - azure-resource-manager 7 | urlFragment: function-app-dedicated-plan 8 | languages: 9 | - bicep 10 | - json 11 | --- 12 | # Azure Function App Hosted on Dedicated Plan 13 | 14 | This sample Azure Resource Manager template deploys an Azure Function App hosted on Dedicated plan and required resource including ZipDeploy extension to mount zip package for deployment. 15 | 16 | [![Deploy to Azure](/images/deploytoazure.png)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure-Samples%2Ffunction-app-arm-templates%2Fmain%2Ffunction-app-dedicated-plan%2Fazuredeploy.json) 17 | 18 | ### OS 19 | 20 | This template has a parameter `functionPlanOS` to choose Windows or Linux OS. Windows is selected by default. If you choose Linux, then parameter `linuxFxVersion` will be required, so you can skip it for Windows. 21 | 22 | ### Dedicated Plan 23 | 24 | The Azure Function app provisioned in this sample uses an [Azure Functions Dedicated plan](https://docs.microsoft.com/en-us/azure/azure-functions/dedicated-plan). 25 | 26 | + **Microsoft.Web/serverfarms**: The Azure Functions Dedicated plan (a.k.a. App Service plan) 27 | 28 | ### Azure Function App 29 | 30 | The Function App uses the [AzureWebJobsStorage](https://docs.microsoft.com/azure/azure-functions/functions-app-settings#azurewebjobsstorage) app setting to connect to a Storage Account. 31 | 32 | + **Microsoft.Web/sites**: The function app instance. 33 | 34 | ### ZipDeploy Extension 35 | 36 | The Zip Deploy extension is added along with recommended app setting `WEBSITE_RUN_FROM_PACKAGE=1` to mount the zip package for deployment. This is the recommended path for deployment, except for [Linux Consumption Plan](/function-app-linux-consumption) 37 | 38 | + **Microsoft.Web/sites/extensions**: The ZipDeploy extension. 39 | 40 | ### Azure Storage account 41 | 42 | The Storage account that the Function uses for operation and for file contents. 43 | 44 | + **Microsoft.Storage/storageAccounts**: [Azure Functions requires a storage account](https://docs.microsoft.com/azure/azure-functions/storage-considerations) for the function app instance. 45 | 46 | ### Application Insights 47 | 48 | [Application Insights](https://docs.microsoft.com/azure/azure-monitor/app/app-insights-overview) is used to provide [monitor the Azure Function](https://docs.microsoft.com/azure/azure-functions/functions-monitoring). 49 | 50 | + **Microsoft.Insights/components**: The Application Insights instance used by the Azure Function for monitoring. 51 | 52 | `Tags: Microsoft.Storage/storageAccounts, microsoft.insights/components, Microsoft.Web/serverfarms, Microsoft.Web/sites, Microsoft.Web/sites/extensions` 53 | -------------------------------------------------------------------------------- /function-app-dedicated-plan/azuredeploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "metadata": { 5 | "_generator": { 6 | "name": "bicep", 7 | "version": "0.10.61.36676", 8 | "templateHash": "16616879859592489661" 9 | } 10 | }, 11 | "parameters": { 12 | "functionAppName": { 13 | "type": "string", 14 | "defaultValue": "[format('func-{0}', uniqueString(resourceGroup().id))]", 15 | "metadata": { 16 | "description": "The name of the Azure Function app." 17 | } 18 | }, 19 | "storageAccountType": { 20 | "type": "string", 21 | "defaultValue": "Standard_LRS", 22 | "allowedValues": [ 23 | "Standard_LRS", 24 | "Standard_GRS", 25 | "Standard_RAGRS" 26 | ], 27 | "metadata": { 28 | "description": "Storage Account type" 29 | } 30 | }, 31 | "location": { 32 | "type": "string", 33 | "defaultValue": "[resourceGroup().location]", 34 | "metadata": { 35 | "description": "Location for all resources." 36 | } 37 | }, 38 | "appInsightsLocation": { 39 | "type": "string", 40 | "defaultValue": "[resourceGroup().location]", 41 | "metadata": { 42 | "description": "Location for Application Insights" 43 | } 44 | }, 45 | "functionWorkerRuntime": { 46 | "type": "string", 47 | "defaultValue": "node", 48 | "allowedValues": [ 49 | "dotnet", 50 | "node", 51 | "python", 52 | "java" 53 | ], 54 | "metadata": { 55 | "description": "The language worker runtime to load in the function app." 56 | } 57 | }, 58 | "functionPlanOS": { 59 | "type": "string", 60 | "defaultValue": "Windows", 61 | "allowedValues": [ 62 | "Windows", 63 | "Linux" 64 | ], 65 | "metadata": { 66 | "description": "Specifies the OS used for the Azure Function hosting plan." 67 | } 68 | }, 69 | "functionAppPlanSku": { 70 | "type": "string", 71 | "defaultValue": "S1", 72 | "allowedValues": [ 73 | "S1", 74 | "S2", 75 | "S3" 76 | ], 77 | "metadata": { 78 | "description": "Specifies the Azure Function hosting plan SKU." 79 | } 80 | }, 81 | "packageUri": { 82 | "type": "string", 83 | "metadata": { 84 | "description": "The zip content url." 85 | } 86 | }, 87 | "linuxFxVersion": { 88 | "type": "string", 89 | "defaultValue": "", 90 | "metadata": { 91 | "description": "Only required for Linux app to represent runtime stack in the format of 'runtime|runtimeVersion'. For example: 'python|3.9'" 92 | } 93 | } 94 | }, 95 | "variables": { 96 | "hostingPlanName": "[parameters('functionAppName')]", 97 | "applicationInsightsName": "[parameters('functionAppName')]", 98 | "storageAccountName": "[format('{0}azfunctions', uniqueString(resourceGroup().id))]", 99 | "isReserved": "[if(equals(parameters('functionPlanOS'), 'Linux'), true(), false())]" 100 | }, 101 | "resources": [ 102 | { 103 | "type": "Microsoft.Storage/storageAccounts", 104 | "apiVersion": "2022-05-01", 105 | "name": "[variables('storageAccountName')]", 106 | "location": "[parameters('location')]", 107 | "sku": { 108 | "name": "[parameters('storageAccountType')]" 109 | }, 110 | "kind": "Storage" 111 | }, 112 | { 113 | "type": "Microsoft.Web/serverfarms", 114 | "apiVersion": "2022-03-01", 115 | "name": "[variables('hostingPlanName')]", 116 | "location": "[parameters('location')]", 117 | "sku": { 118 | "tier": "Standard", 119 | "name": "[parameters('functionAppPlanSku')]", 120 | "family": "S", 121 | "capacity": 1 122 | }, 123 | "properties": { 124 | "reserved": "[variables('isReserved')]" 125 | } 126 | }, 127 | { 128 | "type": "Microsoft.Insights/components", 129 | "apiVersion": "2020-02-02", 130 | "name": "[variables('applicationInsightsName')]", 131 | "location": "[parameters('appInsightsLocation')]", 132 | "tags": { 133 | "[format('hidden-link:{0}', resourceId('Microsoft.Web/sites', variables('applicationInsightsName')))]": "Resource" 134 | }, 135 | "properties": { 136 | "Application_Type": "web" 137 | }, 138 | "kind": "web" 139 | }, 140 | { 141 | "type": "Microsoft.Web/sites", 142 | "apiVersion": "2022-03-01", 143 | "name": "[parameters('functionAppName')]", 144 | "location": "[parameters('location')]", 145 | "kind": "[if(variables('isReserved'), 'functionapp,linux', 'functionapp')]", 146 | "properties": { 147 | "reserved": "[variables('isReserved')]", 148 | "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]", 149 | "siteConfig": { 150 | "alwaysOn": true, 151 | "linuxFxVersion": "[if(variables('isReserved'), parameters('linuxFxVersion'), json('null'))]", 152 | "appSettings": [ 153 | { 154 | "name": "APPINSIGHTS_INSTRUMENTATIONKEY", 155 | "value": "[reference(resourceId('Microsoft.Insights/components', variables('applicationInsightsName')), '2015-05-01').InstrumentationKey]" 156 | }, 157 | { 158 | "name": "AzureWebJobsStorage", 159 | "value": "[format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('storageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2022-05-01').keys[0].value)]" 160 | }, 161 | { 162 | "name": "FUNCTIONS_EXTENSION_VERSION", 163 | "value": "~4" 164 | }, 165 | { 166 | "name": "FUNCTIONS_WORKER_RUNTIME", 167 | "value": "[parameters('functionWorkerRuntime')]" 168 | }, 169 | { 170 | "name": "WEBSITE_NODE_DEFAULT_VERSION", 171 | "value": "~14" 172 | }, 173 | { 174 | "name": "WEBSITE_RUN_FROM_PACKAGE", 175 | "value": "1" 176 | } 177 | ] 178 | } 179 | }, 180 | "dependsOn": [ 181 | "[resourceId('Microsoft.Insights/components', variables('applicationInsightsName'))]", 182 | "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]", 183 | "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]" 184 | ] 185 | }, 186 | { 187 | "type": "Microsoft.Web/sites/extensions", 188 | "apiVersion": "2022-03-01", 189 | "name": "[format('{0}/{1}', parameters('functionAppName'), 'zipdeploy')]", 190 | "properties": { 191 | "packageUri": "[parameters('packageUri')]" 192 | }, 193 | "dependsOn": [ 194 | "[resourceId('Microsoft.Web/sites', parameters('functionAppName'))]" 195 | ] 196 | } 197 | ] 198 | } -------------------------------------------------------------------------------- /function-app-dedicated-plan/azuredeploy.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /function-app-dedicated-plan/main.bicep: -------------------------------------------------------------------------------- 1 | @description('The name of the Azure Function app.') 2 | param functionAppName string = 'func-${uniqueString(resourceGroup().id)}' 3 | 4 | @description('Storage Account type') 5 | @allowed([ 6 | 'Standard_LRS' 7 | 'Standard_GRS' 8 | 'Standard_RAGRS' 9 | ]) 10 | param storageAccountType string = 'Standard_LRS' 11 | 12 | @description('Location for all resources.') 13 | param location string = resourceGroup().location 14 | 15 | @description('Location for Application Insights') 16 | param appInsightsLocation string = resourceGroup().location 17 | 18 | @description('The language worker runtime to load in the function app.') 19 | @allowed([ 20 | 'dotnet' 21 | 'node' 22 | 'python' 23 | 'java' 24 | ]) 25 | param functionWorkerRuntime string = 'node' 26 | 27 | @description('Specifies the OS used for the Azure Function hosting plan.') 28 | @allowed([ 29 | 'Windows' 30 | 'Linux' 31 | ]) 32 | param functionPlanOS string = 'Windows' 33 | 34 | @description('Specifies the Azure Function hosting plan SKU.') 35 | @allowed([ 36 | 'S1' 37 | 'S2' 38 | 'S3' 39 | ]) 40 | param functionAppPlanSku string = 'S1' 41 | 42 | @description('The zip content url.') 43 | param packageUri string 44 | 45 | @description('Only required for Linux app to represent runtime stack in the format of \'runtime|runtimeVersion\'. For example: \'python|3.9\'') 46 | param linuxFxVersion string = '' 47 | 48 | var hostingPlanName = functionAppName 49 | var applicationInsightsName = functionAppName 50 | var storageAccountName = '${uniqueString(resourceGroup().id)}azfunctions' 51 | var isReserved = ((functionPlanOS == 'Linux') ? true : false) 52 | 53 | resource storageAccount 'Microsoft.Storage/storageAccounts@2022-05-01' = { 54 | name: storageAccountName 55 | location: location 56 | sku: { 57 | name: storageAccountType 58 | } 59 | kind: 'Storage' 60 | } 61 | 62 | resource hostingPlan 'Microsoft.Web/serverfarms@2022-03-01' = { 63 | name: hostingPlanName 64 | location: location 65 | sku: { 66 | tier: 'Standard' 67 | name: functionAppPlanSku 68 | family: 'S' 69 | capacity: 1 70 | } 71 | properties: { 72 | reserved: isReserved 73 | } 74 | } 75 | 76 | resource applicationInsight 'Microsoft.Insights/components@2020-02-02' = { 77 | name: applicationInsightsName 78 | location: appInsightsLocation 79 | tags: { 80 | 'hidden-link:${resourceId('Microsoft.Web/sites', applicationInsightsName)}': 'Resource' 81 | } 82 | properties: { 83 | Application_Type: 'web' 84 | } 85 | kind: 'web' 86 | } 87 | 88 | resource functionApp 'Microsoft.Web/sites@2022-03-01' = { 89 | name: functionAppName 90 | location: location 91 | kind: (isReserved ? 'functionapp,linux' : 'functionapp') 92 | properties: { 93 | reserved: isReserved 94 | serverFarmId: hostingPlan.id 95 | siteConfig: { 96 | alwaysOn: true 97 | linuxFxVersion: (isReserved ? linuxFxVersion : json('null')) 98 | appSettings: [ 99 | { 100 | name: 'APPINSIGHTS_INSTRUMENTATIONKEY' 101 | value: reference(applicationInsight.id, '2015-05-01').InstrumentationKey 102 | } 103 | { 104 | name: 'AzureWebJobsStorage' 105 | value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccountName};EndpointSuffix=${environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}' 106 | } 107 | { 108 | name: 'FUNCTIONS_EXTENSION_VERSION' 109 | value: '~4' 110 | } 111 | { 112 | name: 'FUNCTIONS_WORKER_RUNTIME' 113 | value: functionWorkerRuntime 114 | } 115 | { 116 | name: 'WEBSITE_NODE_DEFAULT_VERSION' 117 | value: '~14' 118 | } 119 | { 120 | name: 'WEBSITE_RUN_FROM_PACKAGE' 121 | value: '1' 122 | } 123 | ] 124 | } 125 | } 126 | } 127 | 128 | resource zipdeploy 'Microsoft.Web/sites/extensions@2022-03-01' = { 129 | parent: functionApp 130 | name: 'zipdeploy' 131 | properties: { 132 | packageUri: packageUri 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /function-app-deployment-slot/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: This template provisions a function app on a Premium plan with production slot and an additional deployment slot. 3 | page_type: sample 4 | products: 5 | - azure 6 | - azure-resource-manager 7 | urlFragment: function-app-deployment-slot 8 | languages: 9 | - bicep 10 | - json 11 | --- 12 | # Azure Function App with a Deployment Slot 13 | 14 | This sample Azure Resource Manager template deploys an Azure Function App with production slot and an additional deployment slot. 15 | 16 | [![Deploy to Azure](/images/deploytoazure.png)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure-Samples%2Ffunction-app-arm-templates%2Fmain%2Ffunction-app-deployment-slot%2Fazuredeploy.json) 17 | 18 | ### OS 19 | 20 | This template has a parameter `functionPlanOS` to choose Windows or Linux OS. Windows is selected by default. If you choose Linux, then parameter `linuxFxVersion` will be parameter, so you can skip it for Windows. 21 | 22 | ### Elastic Premium Plan 23 | 24 | The Azure Function app provisioned in this sample uses an [Azure Functions Elastic Premium plan](https://docs.microsoft.com/azure/azure-functions/functions-premium-plan#features). 25 | 26 | + **Microsoft.Web/serverfarms**: The Azure Functions Premium plan (a.k.a. Elastic Premium plan) 27 | 28 | ### Azure Function App 29 | 30 | The Function App uses the [AzureWebJobsStorage](https://docs.microsoft.com/azure/azure-functions/functions-app-settings#azurewebjobsstorage) and [WEBSITE_CONTENTAZUREFILECONNECTIONSTRING](https://docs.microsoft.com/azure/azure-functions/functions-app-settings#website_contentazurefileconnectionstring) app settings to connect to a Storage Account. 31 | 32 | + **Microsoft.Web/sites**: The function app instance. 33 | 34 | ### Deployment Slot 35 | 36 | Azure Functions [deployment slots](https://docs.microsoft.com/en-us/azure/azure-functions/functions-deployment-slots) allow your function app to run different instances called "slots". Slots are different environments exposed via a publicly available endpoint. One app instance is always mapped to the production slot, and you can swap instances assigned to a slot on demand. 37 | 38 | Function apps running under the Apps Service plan may have multiple slots, while under the Consumption plan only one slot is allowed. 39 | 40 | For Windows, do not need to set the [WEBSITE_CONTENTSHARE](https://docs.microsoft.com/en-us/azure/azure-functions/functions-app-settings#website_contentshare) setting in a deployment slot. This setting is generated for you when the app is created in the deployment slot. 41 | 42 | + **Microsoft.Web/sites/slots**: The deployment slot for the function app. 43 | 44 | For swapping the 2 slots, it is recommended to have 2 separate templates: 45 | 1. First run this template successfully for deploying to slot using ZipDeploy. 46 | 2. Then run this [template for swapping slots](https://azure.github.io/AppService/2019/10/02/Swap-slots-with-arm-templates.html). 47 | 48 | ### ZipDeploy Extension 49 | 50 | The Zip Deploy extension is added for "deployment" slot along with recommended app setting `WEBSITE_RUN_FROM_PACKAGE=1` to mount the zip package for deployment. This is the recommended path for deployment, except for [Linux Consumption Plan](/function-app-linux-consumption) 51 | 52 | + **Microsoft.Web/sites/slots/extensions**: The ZipDeploy extension for "deployment" slot. 53 | 54 | ### Azure Storage account 55 | 56 | The Storage account that the Function uses for operation and for file contents. 57 | 58 | + **Microsoft.Storage/storageAccounts**: [Azure Functions requires a storage account](https://docs.microsoft.com/azure/azure-functions/storage-considerations) for the function app instance. 59 | 60 | ### Application Insights 61 | 62 | [Application Insights](https://docs.microsoft.com/azure/azure-monitor/app/app-insights-overview) is used to provide [monitor the Azure Function](https://docs.microsoft.com/azure/azure-functions/functions-monitoring). 63 | 64 | + **Microsoft.Insights/components**: The Application Insights instance used by the Azure Function for monitoring. 65 | 66 | `Tags: Microsoft.Storage/storageAccounts, microsoft.insights/components, Microsoft.Web/serverfarms, Microsoft.Web/sites, Microsoft.Web/sites/slots` 67 | -------------------------------------------------------------------------------- /function-app-deployment-slot/azuredeploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "functionAppName": { 6 | "defaultValue": "[format('func-{0}', uniqueString(resourceGroup().id))]", 7 | "type": "String", 8 | "metadata": { 9 | "description": "The name of the Azure Function app." 10 | } 11 | }, 12 | "SlotName": { 13 | "defaultValue": "deployment", 14 | "type": "String", 15 | "metadata": { 16 | "description": "The name of the slot for Azure Function app." 17 | } 18 | }, 19 | "storageAccountType": { 20 | "defaultValue": "Standard_LRS", 21 | "allowedValues": [ 22 | "Standard_LRS", 23 | "Standard_GRS", 24 | "Standard_RAGRS" 25 | ], 26 | "type": "String", 27 | "metadata": { 28 | "description": "Storage Account type" 29 | } 30 | }, 31 | "location": { 32 | "defaultValue": "[resourceGroup().location]", 33 | "type": "String", 34 | "metadata": { 35 | "description": "Location for all resources." 36 | } 37 | }, 38 | "appInsightsLocation": { 39 | "defaultValue": "[resourceGroup().location]", 40 | "type": "String", 41 | "metadata": { 42 | "description": "Location for Application Insights" 43 | } 44 | }, 45 | "functionWorkerRuntime": { 46 | "defaultValue": "node", 47 | "allowedValues": [ 48 | "dotnet", 49 | "node", 50 | "python", 51 | "java" 52 | ], 53 | "type": "String", 54 | "metadata": { 55 | "description": "The language worker runtime to load in the function app." 56 | } 57 | }, 58 | "functionPlanOS": { 59 | "defaultValue": "Windows", 60 | "allowedValues": [ 61 | "Windows", 62 | "Linux" 63 | ], 64 | "type": "String", 65 | "metadata": { 66 | "description": "Specifies the OS used for the Azure Function hosting plan." 67 | } 68 | }, 69 | "functionAppPlanSku": { 70 | "defaultValue": "EP1", 71 | "allowedValues": [ 72 | "EP1", 73 | "EP2", 74 | "EP3" 75 | ], 76 | "type": "String", 77 | "metadata": { 78 | "description": "Specifies the Azure Function hosting plan SKU." 79 | } 80 | }, 81 | "packageUri": { 82 | "type": "String", 83 | "metadata": { 84 | "description": "The zip content url." 85 | } 86 | }, 87 | "linuxFxVersion": { 88 | "defaultValue": "", 89 | "type": "String", 90 | "metadata": { 91 | "description": "Only required for Linux app to represent runtime stack in the format of 'runtime|runtimeVersion'. For example: 'python|3.9'" 92 | } 93 | } 94 | }, 95 | "variables": { 96 | "hostingPlanName": "[parameters('functionAppName')]", 97 | "applicationInsightsName": "[parameters('functionAppName')]", 98 | "storageAccountName": "[format('{0}azfunctions', uniqueString(resourceGroup().id))]", 99 | "isReserved": "[if(equals(parameters('functionPlanOS'), 'Linux'), true(), false())]", 100 | "slotContentShareName": "[format('{0}-{1}', parameters('functionAppName'), parameters('SlotName'))]" 101 | }, 102 | "resources": [ 103 | { 104 | "type": "Microsoft.Storage/storageAccounts", 105 | "apiVersion": "2022-05-01", 106 | "name": "[variables('storageAccountName')]", 107 | "location": "[parameters('location')]", 108 | "sku": { 109 | "name": "[parameters('storageAccountType')]" 110 | }, 111 | "kind": "Storage" 112 | }, 113 | { 114 | "type": "Microsoft.Web/serverfarms", 115 | "apiVersion": "2022-03-01", 116 | "name": "[variables('hostingPlanName')]", 117 | "location": "[parameters('location')]", 118 | "sku": { 119 | "tier": "ElasticPremium", 120 | "name": "[parameters('functionAppPlanSku')]", 121 | "family": "EP" 122 | }, 123 | "kind": "elastic", 124 | "properties": { 125 | "maximumElasticWorkerCount": 20, 126 | "reserved": "[variables('isReserved')]" 127 | } 128 | }, 129 | { 130 | "type": "Microsoft.Insights/components", 131 | "apiVersion": "2020-02-02", 132 | "name": "[variables('applicationInsightsName')]", 133 | "location": "[parameters('appInsightsLocation')]", 134 | "tags": { 135 | "[format('hidden-link:{0}', resourceId('Microsoft.Web/sites', variables('applicationInsightsName')))]": "Resource" 136 | }, 137 | "kind": "web", 138 | "properties": { 139 | "Application_Type": "web" 140 | } 141 | }, 142 | { 143 | "type": "Microsoft.Web/sites", 144 | "apiVersion": "2022-03-01", 145 | "name": "[parameters('functionAppName')]", 146 | "location": "[parameters('location')]", 147 | "dependsOn": [ 148 | "[resourceId('Microsoft.Insights/components', variables('applicationInsightsName'))]", 149 | "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]", 150 | "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]" 151 | ], 152 | "kind": "[if(variables('isReserved'), 'functionapp,linux', 'functionapp')]", 153 | "properties": { 154 | "reserved": "[variables('isReserved')]", 155 | "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]", 156 | "siteConfig": { 157 | "linuxFxVersion": "[if(variables('isReserved'), parameters('linuxFxVersion'), json('null'))]", 158 | "appSettings": [ 159 | { 160 | "name": "APPINSIGHTS_INSTRUMENTATIONKEY", 161 | "value": "[reference(resourceId('Microsoft.Insights/components', variables('applicationInsightsName'))).InstrumentationKey]" 162 | }, 163 | { 164 | "name": "AzureWebJobsStorage", 165 | "value": "[format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('storageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2022-05-01').keys[0].value)]" 166 | }, 167 | { 168 | "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING", 169 | "value": "[format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('storageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2022-05-01').keys[0].value)]" 170 | }, 171 | { 172 | "name": "WEBSITE_CONTENTSHARE", 173 | "value": "[toLower(parameters('functionAppName'))]" 174 | }, 175 | { 176 | "name": "FUNCTIONS_EXTENSION_VERSION", 177 | "value": "~4" 178 | }, 179 | { 180 | "name": "FUNCTIONS_WORKER_RUNTIME", 181 | "value": "[parameters('functionWorkerRuntime')]" 182 | }, 183 | { 184 | "name": "WEBSITE_NODE_DEFAULT_VERSION", 185 | "value": "~14" 186 | }, 187 | { 188 | "name": "WEBSITE_RUN_FROM_PACKAGE", 189 | "value": "1" 190 | } 191 | ] 192 | } 193 | } 194 | }, 195 | { 196 | "type": "Microsoft.Web/sites/slots", 197 | "apiVersion": "2022-03-01", 198 | "name": "[format('{0}/{1}', parameters('functionAppName'), parameters('SlotName'))]", 199 | "location": "[parameters('location')]", 200 | "dependsOn": [ 201 | "[resourceId('Microsoft.Insights/components', variables('applicationInsightsName'))]", 202 | "[resourceId('Microsoft.Web/sites', parameters('functionAppName'))]", 203 | "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]", 204 | "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]" 205 | ], 206 | "kind": "[if(variables('isReserved'), 'functionapp,linux', 'functionapp')]", 207 | "properties": { 208 | "reserved": "[variables('isReserved')]", 209 | "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]", 210 | "siteConfig": { 211 | "linuxFxVersion": "[if(variables('isReserved'), parameters('linuxFxVersion'), json('null'))]", 212 | "appSettings": [ 213 | { 214 | "name": "APPINSIGHTS_INSTRUMENTATIONKEY", 215 | "value": "[reference(resourceId('Microsoft.Insights/components', variables('applicationInsightsName'))).InstrumentationKey]" 216 | }, 217 | { 218 | "name": "AzureWebJobsStorage", 219 | "value": "[format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('storageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2022-05-01').keys[0].value)]" 220 | }, 221 | { 222 | "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING", 223 | "value": "[format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('storageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2022-05-01').keys[0].value)]" 224 | }, 225 | { 226 | "name": "WEBSITE_CONTENTSHARE", 227 | "value": "[variables('slotContentShareName')]" 228 | }, 229 | { 230 | "name": "FUNCTIONS_EXTENSION_VERSION", 231 | "value": "~4" 232 | }, 233 | { 234 | "name": "FUNCTIONS_WORKER_RUNTIME", 235 | "value": "[parameters('functionWorkerRuntime')]" 236 | }, 237 | { 238 | "name": "WEBSITE_NODE_DEFAULT_VERSION", 239 | "value": "~14" 240 | }, 241 | { 242 | "name": "WEBSITE_RUN_FROM_PACKAGE", 243 | "value": "1" 244 | } 245 | ] 246 | } 247 | } 248 | }, 249 | { 250 | "type": "Microsoft.Web/sites/slots/extensions", 251 | "apiVersion": "2021-03-01", 252 | "name": "[format('{0}/{1}/ZipDeploy', parameters('functionAppName'), parameters('SlotName'))]", 253 | "dependsOn": [ 254 | "[resourceId('Microsoft.Web/sites/slots', parameters('functionAppName'), parameters('SlotName'))]" 255 | ], 256 | "properties": { 257 | "packageUri": "[parameters('packageUri')]" 258 | } 259 | } 260 | ] 261 | } 262 | -------------------------------------------------------------------------------- /function-app-deployment-slot/azuredeploy.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /function-app-deployment-slot/main.bicep: -------------------------------------------------------------------------------- 1 | @description('The name of the Azure Function app.') 2 | param functionAppName string = 'func-${uniqueString(resourceGroup().id)}' 3 | 4 | @description('Storage Account type') 5 | @allowed([ 6 | 'Standard_LRS' 7 | 'Standard_GRS' 8 | 'Standard_RAGRS' 9 | ]) 10 | param storageAccountType string = 'Standard_LRS' 11 | 12 | @description('Location for all resources.') 13 | param location string = resourceGroup().location 14 | 15 | @description('Location for Application Insights') 16 | param appInsightsLocation string = resourceGroup().location 17 | 18 | @description('The language worker runtime to load in the function app.') 19 | @allowed([ 20 | 'dotnet' 21 | 'node' 22 | 'python' 23 | 'java' 24 | ]) 25 | param functionWorkerRuntime string = 'node' 26 | 27 | @description('Specifies the OS used for the Azure Function hosting plan.') 28 | @allowed([ 29 | 'Windows' 30 | 'Linux' 31 | ]) 32 | param functionPlanOS string = 'Windows' 33 | 34 | @description('Specifies the Azure Function hosting plan SKU.') 35 | @allowed([ 36 | 'EP1' 37 | 'EP2' 38 | 'EP3' 39 | ]) 40 | param functionAppPlanSku string = 'EP1' 41 | 42 | @description('Only required for Linux app to represent runtime stack in the format of \'runtime|runtimeVersion\'. For example: \'python|3.9\'') 43 | param linuxFxVersion string = '' 44 | 45 | var hostingPlanName = functionAppName 46 | var applicationInsightsName = functionAppName 47 | var storageAccountName = '${uniqueString(resourceGroup().id)}azfunctions' 48 | var isReserved = ((functionPlanOS == 'Linux') ? true : false) 49 | var slotContentShareName = '${functionAppName}-deployment' 50 | 51 | resource storageAccount 'Microsoft.Storage/storageAccounts@2022-05-01' = { 52 | name: storageAccountName 53 | location: location 54 | sku: { 55 | name: storageAccountType 56 | } 57 | kind: 'Storage' 58 | } 59 | 60 | resource hostingPlan 'Microsoft.Web/serverfarms@2022-03-01' = { 61 | name: hostingPlanName 62 | location: location 63 | sku: { 64 | tier: 'ElasticPremium' 65 | name: functionAppPlanSku 66 | family: 'EP' 67 | } 68 | properties: { 69 | maximumElasticWorkerCount: 20 70 | reserved: isReserved 71 | } 72 | kind: 'elastic' 73 | } 74 | 75 | resource applicationInsight 'Microsoft.Insights/components@2020-02-02' = { 76 | name: applicationInsightsName 77 | location: appInsightsLocation 78 | tags: { 79 | 'hidden-link:${resourceId('Microsoft.Web/sites', applicationInsightsName)}': 'Resource' 80 | } 81 | properties: { 82 | Application_Type: 'web' 83 | } 84 | kind: 'web' 85 | } 86 | 87 | resource functionApp 'Microsoft.Web/sites@2022-03-01' = { 88 | name: functionAppName 89 | location: location 90 | kind: (isReserved ? 'functionapp,linux' : 'functionapp') 91 | properties: { 92 | reserved: isReserved 93 | serverFarmId: hostingPlan.id 94 | siteConfig: { 95 | linuxFxVersion: (isReserved ? linuxFxVersion : json('null')) 96 | appSettings: [ 97 | { 98 | name: 'APPINSIGHTS_INSTRUMENTATIONKEY' 99 | value: applicationInsight.properties.InstrumentationKey 100 | } 101 | { 102 | name: 'AzureWebJobsStorage' 103 | value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccountName};EndpointSuffix=${environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}' 104 | } 105 | { 106 | name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING' 107 | value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccountName};EndpointSuffix=${environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}' 108 | } 109 | { 110 | name: 'WEBSITE_CONTENTSHARE' 111 | value: toLower(functionAppName) 112 | } 113 | { 114 | name: 'FUNCTIONS_EXTENSION_VERSION' 115 | value: '~4' 116 | } 117 | { 118 | name: 'FUNCTIONS_WORKER_RUNTIME' 119 | value: functionWorkerRuntime 120 | } 121 | { 122 | name: 'WEBSITE_NODE_DEFAULT_VERSION' 123 | value: '~14' 124 | } 125 | ] 126 | } 127 | } 128 | } 129 | 130 | resource slot 'Microsoft.Web/sites/slots@2022-03-01' = { 131 | parent: functionApp 132 | name: 'deployment' 133 | kind: (isReserved ? 'functionapp,linux' : 'functionapp') 134 | location: location 135 | properties: { 136 | reserved: isReserved 137 | serverFarmId: hostingPlan.id 138 | siteConfig: { 139 | linuxFxVersion: (isReserved ? linuxFxVersion : json('null')) 140 | appSettings: [ 141 | { 142 | name: 'APPINSIGHTS_INSTRUMENTATIONKEY' 143 | value: applicationInsight.properties.InstrumentationKey 144 | } 145 | { 146 | name: 'AzureWebJobsStorage' 147 | value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccountName};EndpointSuffix=${environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}' 148 | } 149 | { 150 | name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING' 151 | value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccountName};EndpointSuffix=${environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}' 152 | } 153 | { 154 | name: 'WEBSITE_CONTENTSHARE' 155 | value: slotContentShareName 156 | } 157 | { 158 | name: 'FUNCTIONS_EXTENSION_VERSION' 159 | value: '~4' 160 | } 161 | { 162 | name: 'FUNCTIONS_WORKER_RUNTIME' 163 | value: functionWorkerRuntime 164 | } 165 | { 166 | name: 'WEBSITE_NODE_DEFAULT_VERSION' 167 | value: '~14' 168 | } 169 | ] 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /function-app-linux-consumption-remote-build/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: This template provisions a function app on a Linux Consumption plan, which is a dynamic hosting plan. The app runs on demand and you're billed per execution, with no standing resource committment. 3 | page_type: sample 4 | products: 5 | - azure 6 | - azure-resource-manager 7 | urlFragment: function-app-linux-consumption-remote-build 8 | languages: 9 | - bicep 10 | - json 11 | --- 12 | # Azure Function App Hosted on Linux Consumption Plan 13 | 14 | This sample Azure Resource Manager template deploys an Azure Function App on Linux Consumption plan and required resource including the app setting to deploy using zip package when **remote build** is needed (for example: to get Linux specific packages in python, node.js). 15 | 16 | [![Deploy to Azure](/images/deploytoazure.png)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure-Samples%2Ffunction-app-arm-templates%2Fmain%2Ffunction-app-linux-consumption-remote-build%2Fazuredeploy.json) 17 | 18 | ### OS 19 | 20 | This template is for Azure Function app hosted on **Linux Consumption plan** only. 21 | 22 | ### Comsumption Plan 23 | 24 | The Azure Function app provisioned in this sample uses an [Azure Functions Consumption plan](https://docs.microsoft.com/en-us/azure/azure-functions/consumption-plan). 25 | 26 | + **Microsoft.Web/serverfarms**: The Azure Functions Consumption plan (a.k.a. Dynamic plan) 27 | 28 | ### Azure Function App 29 | 30 | The Function App uses the [AzureWebJobsStorage](https://docs.microsoft.com/azure/azure-functions/functions-app-settings#azurewebjobsstorage) app settings to connect to a Storage Account. 31 | 32 | + **Microsoft.Web/sites**: The function app instance. 33 | 34 | ### Deploy using .ZIP package with Remote Build 35 | 36 | To enable the remote build processes with ZipDeploy extension, update the following to your application settings: 37 | + `WEBSITE_RUN_FROM_PACKAGE=0` or Remove WEBSITE_RUN_FROM_PACKAGE app setting 38 | + `SCM_DO_BUILD_DURING_DEPLOYMENT=true` 39 | 40 | NOTE: ZipDeploy extension with the appSetting `SCM_DO_BUILD_DURING_DEPLOYMENT=true` is honored only if `WEBSITE_RUN_FROM_PACKAGE=0`or Remove WEBSITE_RUN_FROM_PACKAGE app setting. 41 | 42 | ### Azure Storage account 43 | 44 | The Storage account that the Function uses for operation and for file contents. 45 | 46 | + **Microsoft.Storage/storageAccounts**: [Azure Functions requires a storage account](https://docs.microsoft.com/azure/azure-functions/storage-considerations) for the function app instance. 47 | 48 | ### Application Insights 49 | 50 | [Application Insights](https://docs.microsoft.com/azure/azure-monitor/app/app-insights-overview) is used to provide [monitor the Azure Function](https://docs.microsoft.com/azure/azure-functions/functions-monitoring). 51 | 52 | + **Microsoft.Insights/components**: The Application Insights instance used by the Azure Function for monitoring. 53 | 54 | `Tags: Microsoft.Storage/storageAccounts, microsoft.insights/components, Microsoft.Web/serverfarms, Microsoft.Web/sites, Microsoft.Web/sites/extensions` 55 | -------------------------------------------------------------------------------- /function-app-linux-consumption-remote-build/azuredeploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "metadata": { 5 | "_generator": { 6 | "name": "bicep", 7 | "version": "0.10.61.36676", 8 | "templateHash": "2440974564149075183" 9 | } 10 | }, 11 | "parameters": { 12 | "functionAppName": { 13 | "type": "string", 14 | "defaultValue": "[format('func-{0}', uniqueString(resourceGroup().id))]", 15 | "metadata": { 16 | "description": "The name of the Azure Function app." 17 | } 18 | }, 19 | "storageAccountType": { 20 | "type": "string", 21 | "defaultValue": "Standard_LRS", 22 | "allowedValues": [ 23 | "Standard_LRS", 24 | "Standard_GRS", 25 | "Standard_RAGRS" 26 | ], 27 | "metadata": { 28 | "description": "Storage Account type" 29 | } 30 | }, 31 | "location": { 32 | "type": "string", 33 | "defaultValue": "[resourceGroup().location]", 34 | "metadata": { 35 | "description": "Location for all resources." 36 | } 37 | }, 38 | "appInsightsLocation": { 39 | "type": "string", 40 | "defaultValue": "[resourceGroup().location]", 41 | "metadata": { 42 | "description": "Location for Application Insights" 43 | } 44 | }, 45 | "functionWorkerRuntime": { 46 | "type": "string", 47 | "defaultValue": "node", 48 | "allowedValues": [ 49 | "dotnet", 50 | "node", 51 | "python", 52 | "java" 53 | ], 54 | "metadata": { 55 | "description": "The language worker runtime to load in the function app." 56 | } 57 | }, 58 | "linuxFxVersion": { 59 | "type": "string", 60 | "metadata": { 61 | "description": "Required for Linux app to represent runtime stack in the format of 'runtime|runtimeVersion'. For example: 'python|3.9'" 62 | } 63 | }, 64 | "packageUri": { 65 | "type": "string", 66 | "metadata": { 67 | "description": "The zip content url." 68 | } 69 | } 70 | }, 71 | "variables": { 72 | "hostingPlanName": "[parameters('functionAppName')]", 73 | "applicationInsightsName": "[parameters('functionAppName')]", 74 | "storageAccountName": "[format('{0}azfunctions', uniqueString(resourceGroup().id))]" 75 | }, 76 | "resources": [ 77 | { 78 | "type": "Microsoft.Storage/storageAccounts", 79 | "apiVersion": "2022-05-01", 80 | "name": "[variables('storageAccountName')]", 81 | "location": "[parameters('location')]", 82 | "sku": { 83 | "name": "[parameters('storageAccountType')]" 84 | }, 85 | "kind": "Storage" 86 | }, 87 | { 88 | "type": "Microsoft.Web/serverfarms", 89 | "apiVersion": "2021-02-01", 90 | "name": "[variables('hostingPlanName')]", 91 | "location": "[parameters('location')]", 92 | "sku": { 93 | "name": "Y1", 94 | "tier": "Dynamic", 95 | "size": "Y1", 96 | "family": "Y" 97 | }, 98 | "properties": { 99 | "computeMode": "Dynamic", 100 | "reserved": true 101 | } 102 | }, 103 | { 104 | "type": "Microsoft.Insights/components", 105 | "apiVersion": "2020-02-02", 106 | "name": "[variables('applicationInsightsName')]", 107 | "location": "[parameters('appInsightsLocation')]", 108 | "tags": { 109 | "[format('hidden-link:{0}', resourceId('Microsoft.Web/sites', variables('applicationInsightsName')))]": "Resource" 110 | }, 111 | "properties": { 112 | "Application_Type": "web" 113 | }, 114 | "kind": "web" 115 | }, 116 | { 117 | "type": "Microsoft.Web/sites", 118 | "apiVersion": "2022-03-01", 119 | "name": "[parameters('functionAppName')]", 120 | "location": "[parameters('location')]", 121 | "kind": "functionapp,linux", 122 | "properties": { 123 | "reserved": true, 124 | "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]", 125 | "siteConfig": { 126 | "linuxFxVersion": "[parameters('linuxFxVersion')]", 127 | "appSettings": [ 128 | { 129 | "name": "APPINSIGHTS_INSTRUMENTATIONKEY", 130 | "value": "[reference(resourceId('Microsoft.Insights/components', parameters('functionAppName')), '2015-05-01').InstrumentationKey]" 131 | }, 132 | { 133 | "name": "AzureWebJobsStorage", 134 | "value": "[format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('storageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value)]" 135 | }, 136 | { 137 | "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING", 138 | "value": "[format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('storageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value)]" 139 | }, 140 | { 141 | "name": "WEBSITE_CONTENTSHARE", 142 | "value": "[toLower(parameters('functionAppName'))]" 143 | }, 144 | { 145 | "name": "FUNCTIONS_EXTENSION_VERSION", 146 | "value": "~4" 147 | }, 148 | { 149 | "name": "FUNCTIONS_WORKER_RUNTIME", 150 | "value": "[parameters('functionWorkerRuntime')]" 151 | }, 152 | { 153 | "name": "SCM_DO_BUILD_DURING_DEPLOYMENT", 154 | "value": "true" 155 | } 156 | ] 157 | } 158 | }, 159 | "dependsOn": [ 160 | "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]", 161 | "[resourceId('Microsoft.Insights/components', variables('applicationInsightsName'))]", 162 | "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]" 163 | ] 164 | }, 165 | { 166 | "type": "Microsoft.Web/sites/extensions", 167 | "apiVersion": "2022-03-01", 168 | "name": "[format('{0}/{1}', parameters('functionAppName'), 'zipdeploy')]", 169 | "properties": { 170 | "packageUri": "[parameters('packageUri')]" 171 | }, 172 | "dependsOn": [ 173 | "[resourceId('Microsoft.Web/sites', parameters('functionAppName'))]" 174 | ] 175 | } 176 | ] 177 | } -------------------------------------------------------------------------------- /function-app-linux-consumption-remote-build/azuredeploy.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /function-app-linux-consumption-remote-build/main.bicep: -------------------------------------------------------------------------------- 1 | @description('The name of the Azure Function app.') 2 | param functionAppName string = 'func-${uniqueString(resourceGroup().id)}' 3 | 4 | @description('Storage Account type') 5 | @allowed([ 6 | 'Standard_LRS' 7 | 'Standard_GRS' 8 | 'Standard_RAGRS' 9 | ]) 10 | param storageAccountType string = 'Standard_LRS' 11 | 12 | @description('Location for all resources.') 13 | param location string = resourceGroup().location 14 | 15 | @description('Location for Application Insights') 16 | param appInsightsLocation string = resourceGroup().location 17 | 18 | @description('The language worker runtime to load in the function app.') 19 | @allowed([ 20 | 'dotnet' 21 | 'node' 22 | 'python' 23 | 'java' 24 | ]) 25 | param functionWorkerRuntime string = 'node' 26 | 27 | @description('Required for Linux app to represent runtime stack in the format of \'runtime|runtimeVersion\'. For example: \'python|3.9\'') 28 | param linuxFxVersion string 29 | 30 | @description('The zip content url.') 31 | param packageUri string 32 | 33 | var hostingPlanName = functionAppName 34 | var applicationInsightsName = functionAppName 35 | var storageAccountName = '${uniqueString(resourceGroup().id)}azfunctions' 36 | 37 | resource storageAccount 'Microsoft.Storage/storageAccounts@2022-05-01' = { 38 | name: storageAccountName 39 | location: location 40 | sku: { 41 | name: storageAccountType 42 | } 43 | kind: 'Storage' 44 | } 45 | 46 | resource hostingPlan 'Microsoft.Web/serverfarms@2021-02-01' = { 47 | name: hostingPlanName 48 | location: location 49 | sku: { 50 | name: 'Y1' 51 | tier: 'Dynamic' 52 | size: 'Y1' 53 | family: 'Y' 54 | } 55 | properties: { 56 | computeMode: 'Dynamic' 57 | reserved: true 58 | } 59 | } 60 | 61 | resource insight 'Microsoft.Insights/components@2020-02-02' = { 62 | name: applicationInsightsName 63 | location: appInsightsLocation 64 | tags: { 65 | 'hidden-link:${resourceId('Microsoft.Web/sites', applicationInsightsName)}': 'Resource' 66 | } 67 | properties: { 68 | Application_Type: 'web' 69 | } 70 | kind: 'web' 71 | } 72 | 73 | resource site 'Microsoft.Web/sites@2022-03-01' = { 74 | name: functionAppName 75 | location: location 76 | kind: 'functionapp,linux' 77 | properties: { 78 | reserved: true 79 | serverFarmId: hostingPlan.id 80 | siteConfig: { 81 | linuxFxVersion: linuxFxVersion 82 | appSettings: [ 83 | { 84 | name: 'APPINSIGHTS_INSTRUMENTATIONKEY' 85 | value: reference(resourceId('Microsoft.Insights/components', functionAppName), '2015-05-01').InstrumentationKey 86 | } 87 | { 88 | name: 'AzureWebJobsStorage' 89 | value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccountName};EndpointSuffix=${environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}' 90 | } 91 | { 92 | name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING' 93 | value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccountName};EndpointSuffix=${environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}' 94 | } 95 | { 96 | name: 'WEBSITE_CONTENTSHARE' 97 | value: toLower(functionAppName) 98 | } 99 | { 100 | name: 'FUNCTIONS_EXTENSION_VERSION' 101 | value: '~4' 102 | } 103 | { 104 | name: 'FUNCTIONS_WORKER_RUNTIME' 105 | value: functionWorkerRuntime 106 | } 107 | { 108 | name: 'SCM_DO_BUILD_DURING_DEPLOYMENT' 109 | value: 'true' 110 | } 111 | ] 112 | } 113 | } 114 | dependsOn: [ 115 | insight 116 | ] 117 | } 118 | 119 | resource zipDeploy 'Microsoft.Web/sites/extensions@2022-03-01' = { 120 | parent: site 121 | name: 'zipdeploy' 122 | properties: { 123 | packageUri: packageUri 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /function-app-linux-consumption/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: This template provisions a function app on a Linux Consumption plan, which is a dynamic hosting plan. The app runs on demand and you're billed per execution, with no standing resource committment. 3 | page_type: sample 4 | products: 5 | - azure 6 | - azure-resource-manager 7 | urlFragment: function-app-linux-consumption 8 | languages: 9 | - bicep 10 | - json 11 | --- 12 | # Azure Function App Hosted on Linux Consumption Plan 13 | 14 | This sample Azure Resource Manager template deploys an Azure Function App on Linux Consumption plan and required resource including the app setting to deploy using zip package. 15 | 16 | [![Deploy to Azure](/images/deploytoazure.png)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure-Samples%2Ffunction-app-arm-templates%2Fmain%2Ffunction-app-linux-consumption%2Fazuredeploy.json) 17 | 18 | ### OS 19 | 20 | This template is for Azure Function app hosted on **Linux Consumption plan** only. 21 | 22 | ### Comsumption Plan 23 | 24 | The Azure Function app provisioned in this sample uses an [Azure Functions Consumption plan](https://docs.microsoft.com/en-us/azure/azure-functions/consumption-plan). 25 | 26 | + **Microsoft.Web/serverfarms**: The Azure Functions Consumption plan (a.k.a. Dynamic plan) 27 | 28 | ### Azure Function App 29 | 30 | The Function App uses the [AzureWebJobsStorage](https://docs.microsoft.com/azure/azure-functions/functions-app-settings#azurewebjobsstorage) app settings to connect to a Storage Account. 31 | 32 | + **Microsoft.Web/sites**: The function app instance. 33 | 34 | ### Deploy using .ZIP package 35 | 36 | ZipDeploy extension with the appSetting `WEBSITE_RUN_FROM_PACKAGE=1` is not supported only for Linux Consumption plan. For Linux Consumption plan: 37 | 1. Do not use ZipDeploy extension. 38 | 2. Set appSetting `WEBSITE_RUN_FROM_PACKAGE=URL` for deployment using the .zip package url. 39 | 40 | ### Azure Storage account 41 | 42 | The Storage account that the Function uses for operation and for file contents. 43 | 44 | + **Microsoft.Storage/storageAccounts**: [Azure Functions requires a storage account](https://docs.microsoft.com/azure/azure-functions/storage-considerations) for the function app instance. 45 | 46 | ### Application Insights 47 | 48 | [Application Insights](https://docs.microsoft.com/azure/azure-monitor/app/app-insights-overview) is used to provide [monitor the Azure Function](https://docs.microsoft.com/azure/azure-functions/functions-monitoring). 49 | 50 | + **Microsoft.Insights/components**: The Application Insights instance used by the Azure Function for monitoring. 51 | 52 | `Tags: Microsoft.Storage/storageAccounts, microsoft.insights/components, Microsoft.Web/serverfarms, Microsoft.Web/sites` 53 | -------------------------------------------------------------------------------- /function-app-linux-consumption/azuredeploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "metadata": { 5 | "_generator": { 6 | "name": "bicep", 7 | "version": "0.10.61.36676", 8 | "templateHash": "7028085632643346163" 9 | } 10 | }, 11 | "parameters": { 12 | "functionAppName": { 13 | "type": "string", 14 | "defaultValue": "[format('func-{0}', uniqueString(resourceGroup().id))]", 15 | "metadata": { 16 | "description": "The name of the Azure Function app." 17 | } 18 | }, 19 | "storageAccountType": { 20 | "type": "string", 21 | "defaultValue": "Standard_LRS", 22 | "allowedValues": [ 23 | "Standard_LRS", 24 | "Standard_GRS", 25 | "Standard_RAGRS" 26 | ], 27 | "metadata": { 28 | "description": "Storage Account type" 29 | } 30 | }, 31 | "location": { 32 | "type": "string", 33 | "defaultValue": "[resourceGroup().location]", 34 | "metadata": { 35 | "description": "Location for all resources." 36 | } 37 | }, 38 | "appInsightsLocation": { 39 | "type": "string", 40 | "defaultValue": "[resourceGroup().location]", 41 | "metadata": { 42 | "description": "Location for Application Insights" 43 | } 44 | }, 45 | "functionWorkerRuntime": { 46 | "type": "string", 47 | "defaultValue": "node", 48 | "allowedValues": [ 49 | "dotnet", 50 | "node", 51 | "python", 52 | "java" 53 | ], 54 | "metadata": { 55 | "description": "The language worker runtime to load in the function app." 56 | } 57 | }, 58 | "linuxFxVersion": { 59 | "type": "string", 60 | "metadata": { 61 | "description": "Required for Linux app to represent runtime stack in the format of 'runtime|runtimeVersion'. For example: 'python|3.9'" 62 | } 63 | }, 64 | "packageUri": { 65 | "type": "string", 66 | "metadata": { 67 | "description": "The zip content url." 68 | } 69 | } 70 | }, 71 | "variables": { 72 | "hostingPlanName": "[parameters('functionAppName')]", 73 | "applicationInsightsName": "[parameters('functionAppName')]", 74 | "storageAccountName": "[format('{0}azfunctions', uniqueString(resourceGroup().id))]" 75 | }, 76 | "resources": [ 77 | { 78 | "type": "Microsoft.Storage/storageAccounts", 79 | "apiVersion": "2022-05-01", 80 | "name": "[variables('storageAccountName')]", 81 | "location": "[parameters('location')]", 82 | "sku": { 83 | "name": "[parameters('storageAccountType')]" 84 | }, 85 | "kind": "Storage" 86 | }, 87 | { 88 | "type": "Microsoft.Web/serverfarms", 89 | "apiVersion": "2022-03-01", 90 | "name": "[variables('hostingPlanName')]", 91 | "location": "[parameters('location')]", 92 | "sku": { 93 | "name": "Y1", 94 | "tier": "Dynamic", 95 | "size": "Y1", 96 | "family": "Y" 97 | }, 98 | "properties": { 99 | "reserved": true 100 | } 101 | }, 102 | { 103 | "type": "Microsoft.Insights/components", 104 | "apiVersion": "2020-02-02", 105 | "name": "[variables('applicationInsightsName')]", 106 | "location": "[parameters('appInsightsLocation')]", 107 | "tags": { 108 | "[format('hidden-link:{0}', resourceId('Microsoft.Web/sites', parameters('functionAppName')))]": "Resource" 109 | }, 110 | "properties": { 111 | "Application_Type": "web" 112 | }, 113 | "kind": "web" 114 | }, 115 | { 116 | "type": "Microsoft.Web/sites", 117 | "apiVersion": "2022-03-01", 118 | "name": "[parameters('functionAppName')]", 119 | "location": "[parameters('location')]", 120 | "kind": "functionapp,linux", 121 | "properties": { 122 | "reserved": true, 123 | "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]", 124 | "siteConfig": { 125 | "linuxFxVersion": "[parameters('linuxFxVersion')]", 126 | "appSettings": [ 127 | { 128 | "name": "APPINSIGHTS_INSTRUMENTATIONKEY", 129 | "value": "[reference(resourceId('Microsoft.Insights/components', parameters('functionAppName')), '2022-03-01').InstrumentationKey]" 130 | }, 131 | { 132 | "name": "AzureWebJobsStorage", 133 | "value": "[format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('storageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2022-05-01').keys[0].value)]" 134 | }, 135 | { 136 | "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING", 137 | "value": "[format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('storageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2022-05-01').keys[0].value)]" 138 | }, 139 | { 140 | "name": "WEBSITE_CONTENTSHARE", 141 | "value": "[toLower(parameters('functionAppName'))]" 142 | }, 143 | { 144 | "name": "FUNCTIONS_EXTENSION_VERSION", 145 | "value": "~4" 146 | }, 147 | { 148 | "name": "FUNCTIONS_WORKER_RUNTIME", 149 | "value": "[parameters('functionWorkerRuntime')]" 150 | }, 151 | { 152 | "name": "WEBSITE_RUN_FROM_PACKAGE", 153 | "value": "[parameters('packageUri')]" 154 | } 155 | ] 156 | } 157 | }, 158 | "dependsOn": [ 159 | "[resourceId('Microsoft.Insights/components', variables('applicationInsightsName'))]", 160 | "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]", 161 | "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]" 162 | ] 163 | } 164 | ] 165 | } -------------------------------------------------------------------------------- /function-app-linux-consumption/azuredeploy.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /function-app-linux-consumption/main.bicep: -------------------------------------------------------------------------------- 1 | @description('The name of the Azure Function app.') 2 | param functionAppName string = 'func-${uniqueString(resourceGroup().id)}' 3 | 4 | @description('Storage Account type') 5 | @allowed([ 6 | 'Standard_LRS' 7 | 'Standard_GRS' 8 | 'Standard_RAGRS' 9 | ]) 10 | param storageAccountType string = 'Standard_LRS' 11 | 12 | @description('Location for all resources.') 13 | param location string = resourceGroup().location 14 | 15 | @description('Location for Application Insights') 16 | param appInsightsLocation string = resourceGroup().location 17 | 18 | @description('The language worker runtime to load in the function app.') 19 | @allowed([ 20 | 'dotnet' 21 | 'node' 22 | 'python' 23 | 'java' 24 | ]) 25 | param functionWorkerRuntime string = 'node' 26 | 27 | @description('Required for Linux app to represent runtime stack in the format of \'runtime|runtimeVersion\'. For example: \'python|3.9\'') 28 | param linuxFxVersion string 29 | 30 | @description('The zip content url.') 31 | param packageUri string 32 | 33 | var hostingPlanName = functionAppName 34 | var applicationInsightsName = functionAppName 35 | var storageAccountName = '${uniqueString(resourceGroup().id)}azfunctions' 36 | 37 | resource storageAccount 'Microsoft.Storage/storageAccounts@2022-05-01' = { 38 | name: storageAccountName 39 | location: location 40 | sku: { 41 | name: storageAccountType 42 | } 43 | kind: 'Storage' 44 | } 45 | 46 | resource hostingPlan 'Microsoft.Web/serverfarms@2022-03-01' = { 47 | name: hostingPlanName 48 | location: location 49 | sku: { 50 | name: 'Y1' 51 | tier: 'Dynamic' 52 | size: 'Y1' 53 | family: 'Y' 54 | } 55 | properties: { 56 | reserved: true 57 | } 58 | } 59 | 60 | resource applicationInsight 'Microsoft.Insights/components@2020-02-02' = { 61 | name: applicationInsightsName 62 | location: appInsightsLocation 63 | tags: { 64 | 'hidden-link:${resourceId('Microsoft.Web/sites', functionAppName)}': 'Resource' 65 | } 66 | properties: { 67 | Application_Type: 'web' 68 | } 69 | kind: 'web' 70 | } 71 | 72 | resource functionApp 'Microsoft.Web/sites@2022-03-01' = { 73 | name: functionAppName 74 | location: location 75 | kind: 'functionapp,linux' 76 | properties: { 77 | reserved: true 78 | serverFarmId: hostingPlan.id 79 | siteConfig: { 80 | linuxFxVersion: linuxFxVersion 81 | appSettings: [ 82 | { 83 | name: 'APPINSIGHTS_INSTRUMENTATIONKEY' 84 | value: reference(resourceId('Microsoft.Insights/components', functionAppName), '2020-02-02').InstrumentationKey 85 | } 86 | { 87 | name: 'AzureWebJobsStorage' 88 | value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccountName};EndpointSuffix=${environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}' 89 | } 90 | { 91 | name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING' 92 | value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccountName};EndpointSuffix=${environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}' 93 | } 94 | { 95 | name: 'WEBSITE_CONTENTSHARE' 96 | value: toLower(functionAppName) 97 | } 98 | { 99 | name: 'FUNCTIONS_EXTENSION_VERSION' 100 | value: '~4' 101 | } 102 | { 103 | name: 'FUNCTIONS_WORKER_RUNTIME' 104 | value: functionWorkerRuntime 105 | } 106 | { 107 | name: 'WEBSITE_RUN_FROM_PACKAGE' 108 | value: packageUri 109 | } 110 | ] 111 | } 112 | } 113 | dependsOn: [ 114 | applicationInsight 115 | ] 116 | } 117 | -------------------------------------------------------------------------------- /function-app-linux-flex-consumption/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: This template provisions a function app on a Flex Consumption plan. 3 | page_type: sample 4 | products: 5 | - azure 6 | - azure-resource-manager 7 | urlFragment: function-app-linux-flex-consumption 8 | languages: 9 | - json 10 | --- 11 | # Azure Function App Hosted on Flex Consumption Plan (Linux) 12 | 13 | This sample Azure Resource Manager template deploys an Azure Function App on Flex Consumption plan (Linux) and required resource including code package deployment with remote build option. 14 | 15 | [![Deploy to Azure](/images/deploytoazure.png)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure-Samples%2Ffunction-app-arm-templates%2Fmain%2Ffunction-app-linux-flex-consumption%2Fazuredeploy.json) 16 | 17 | ### OS 18 | 19 | This template is for Azure Function app hosted on **Flex Consumption plan (Linux)** only. 20 | 21 | ### Flex Consumption Plan 22 | 23 | The Azure Function app provisioned in this sample uses an [Azure Functions Flex Consumption plan](https://learn.microsoft.com/en-us/azure/azure-functions/flex-consumption-plan). 24 | 25 | + **Microsoft.Web/serverfarms**: The Azure Functions Flex Consumption plan 26 | 27 | ### Azure Function App 28 | 29 | The Function App uses the [AzureWebJobsStorage__accountName](https://learn.microsoft.com/en-us/azure/azure-functions/functions-app-settings#azurewebjobsstorage__accountname) app setting to connect to a Storage Account and [configured deployment settings](https://learn.microsoft.com/en-us/azure/azure-functions/flex-consumption-how-to?tabs=azure-cli%2Cvs-code-publish&pivots=programming-language-python#configure-deployment-settings) with system-assigned identity. 30 | 31 | + **Microsoft.Web/sites**: The function app instance. 32 | 33 | ### OneDeploy 34 | To deploy/release new code with .ZIP package. It uses storage account and authentication method [configured in deployment settings](../function-app-linux-flex-consumption/azuredeploy.json#L220C5-L231C6) 35 | 36 | + **Microsoft.Web/sites/extensions**: Allows choosing remote build process (for example: to get Linux specific packages in python, node.js) in OneDeploy extension using the following property: 37 | + `"remoteBuild": true` => enable remote build 38 | + `"remoteBuild": false` => disable remote build 39 | 40 | ### Azure Storage account 41 | 42 | The Storage account that the Function uses for operation and for file contents. 43 | 44 | + **Microsoft.Storage/storageAccounts**: [Azure Functions requires a storage account](https://docs.microsoft.com/azure/azure-functions/storage-considerations) for the function app instance. 45 | 46 | ### Application Insights 47 | 48 | [Application Insights](https://docs.microsoft.com/azure/azure-monitor/app/app-insights-overview) is used to provide [monitor the Azure Function](https://docs.microsoft.com/azure/azure-functions/functions-monitoring). 49 | 50 | + **Microsoft.Insights/components**: The Application Insights instance used by the Azure Function for monitoring. 51 | 52 | `Tags: Microsoft.Storage/storageAccounts, microsoft.insights/components, Microsoft.Web/serverfarms, Microsoft.Web/sites, Microsoft.Web/sites/extensions` 53 | -------------------------------------------------------------------------------- /function-app-linux-flex-consumption/azuredeploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "functionAppName": { 6 | "defaultValue": "[format('func-{0}', uniqueString(resourceGroup().id))]", 7 | "type": "String", 8 | "metadata": { 9 | "description": "The name of the Azure Function app." 10 | } 11 | }, 12 | "storageAccountType": { 13 | "defaultValue": "Standard_LRS", 14 | "allowedValues": [ 15 | "Standard_LRS", 16 | "Standard_GRS", 17 | "Standard_RAGRS" 18 | ], 19 | "type": "String", 20 | "metadata": { 21 | "description": "Storage Account type" 22 | } 23 | }, 24 | "location": { 25 | "defaultValue": "[resourceGroup().location]", 26 | "type": "String", 27 | "metadata": { 28 | "description": "Location for all resources." 29 | } 30 | }, 31 | "appInsightsLocation": { 32 | "defaultValue": "[resourceGroup().location]", 33 | "type": "String", 34 | "metadata": { 35 | "description": "Location for Application Insights" 36 | } 37 | }, 38 | "functionAppRuntime": { 39 | "defaultValue": "python", 40 | "allowedValues": [ 41 | "dotnet-isolated", 42 | "python", 43 | "java", 44 | "node", 45 | "powerShell" 46 | ], 47 | "type": "String", 48 | "metadata": { 49 | "description": "The language worker runtime to load in the function app." 50 | } 51 | }, 52 | "functionAppRuntimeVersion": { 53 | "defaultValue": "3.11", 54 | "allowedValues": [ 55 | "3.10", 56 | "3.11", 57 | "7.4", 58 | "8.0", 59 | "10", 60 | "11", 61 | "17", 62 | "20" 63 | ], 64 | "type": "String", 65 | "metadata": { 66 | "description": "The language worker runtime version to load in the function app." 67 | } 68 | }, 69 | "maximumInstanceCount": { 70 | "defaultValue": 100, 71 | "type": "Int" 72 | }, 73 | "instanceMemoryMB": { 74 | "defaultValue": 2048, 75 | "allowedValues": [ 76 | 512, 77 | 2048, 78 | 4096 79 | ], 80 | "type": "Int" 81 | }, 82 | "packageUri": { 83 | "type": "String", 84 | "metadata": { 85 | "description": "The zip content url." 86 | } 87 | }, 88 | "roleNameGuid": { 89 | "defaultValue": "[newGuid()]", 90 | "type": "String", 91 | "metadata": { 92 | "description": "A new GUID used to identify the role assignment" 93 | } 94 | } 95 | }, 96 | "variables": { 97 | "hostingPlanName": "[parameters('functionAppName')]", 98 | "applicationInsightsName": "[parameters('functionAppName')]", 99 | "storageAccountName": "[format('{0}azfunctions', uniqueString(resourceGroup().id))]", 100 | "resourceToken": "[toLower(uniqueString(subscription().id, resourceGroup().name, parameters('location')))]", 101 | "deploymentStorageContainerName": "[concat('app-package-', take(parameters('functionAppName'), 32),'-', take(variables('resourceToken'), 7))]", 102 | "storageBlobContributorRoleId": "[concat(subscription().Id, '/providers/Microsoft.Authorization/roleDefinitions/ba92f5b4-2d11-453d-a403-e96b0029c9fe')]" //Storage Blob Data Contributor role 103 | }, 104 | "resources": [ 105 | { 106 | "type": "Microsoft.Storage/storageAccounts", 107 | "apiVersion": "2023-01-01", 108 | "name": "[variables('storageAccountName')]", 109 | "location": "[parameters('location')]", 110 | "sku": { 111 | "name": "[parameters('storageAccountType')]" 112 | }, 113 | "kind": "StorageV2", 114 | "properties": { 115 | "accessTier": "Hot", 116 | "allowSharedKeyAccess": false 117 | } 118 | }, 119 | { 120 | "type": "Microsoft.Storage/storageAccounts/blobServices", 121 | "apiVersion": "2023-01-01", 122 | "name": "[format('{0}/{1}', variables('storageAccountName'), 'default')]", 123 | "dependsOn": [ 124 | "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]" 125 | ] 126 | }, 127 | { 128 | "type": "Microsoft.Storage/storageAccounts/blobServices/containers", 129 | "apiVersion": "2023-01-01", 130 | "name": "[format('{0}/{1}/{2}', variables('storageAccountName'), 'default', variables('deploymentStorageContainerName'))]", 131 | "dependsOn": [ 132 | "[resourceId('Microsoft.Storage/storageAccounts/blobServices', variables('storageAccountName'), 'default')]" 133 | ], 134 | "properties": { 135 | "publicAccess": "None" 136 | } 137 | }, 138 | { 139 | "type": "Microsoft.Web/serverfarms", 140 | "apiVersion": "2023-12-01", 141 | "name": "[variables('hostingPlanName')]", 142 | "location": "[parameters('location')]", 143 | "sku": { 144 | "tier": "FlexConsumption", 145 | "name": "FC1" 146 | }, 147 | "kind": "functionapp", 148 | "properties": { 149 | "reserved": true 150 | } 151 | }, 152 | { 153 | "type": "Microsoft.Insights/components", 154 | "apiVersion": "2020-02-02", 155 | "name": "[variables('applicationInsightsName')]", 156 | "location": "[parameters('appInsightsLocation')]", 157 | "tags": { 158 | "[format('hidden-link:{0}', resourceId('Microsoft.Web/sites', variables('applicationInsightsName')))]": "Resource" 159 | }, 160 | "kind": "web", 161 | "properties": { 162 | "Application_Type": "web" 163 | } 164 | }, 165 | { 166 | "type": "Microsoft.Web/sites", 167 | "apiVersion": "2023-12-01", 168 | "name": "[parameters('functionAppName')]", 169 | "location": "[parameters('location')]", 170 | "dependsOn": [ 171 | "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]", 172 | "[resourceId('Microsoft.Insights/components', variables('applicationInsightsName'))]", 173 | "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]" 174 | ], 175 | "kind": "functionapp,linux", 176 | "identity": { 177 | "type": "SystemAssigned" 178 | }, 179 | "properties": { 180 | "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]", 181 | "functionAppConfig": { 182 | "deployment": { 183 | "storage": { 184 | "type": "blobContainer", 185 | "value": "[concat(reference(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))).primaryEndpoints.blob, variables('deploymentStorageContainerName'))]", 186 | "authentication": { 187 | "type": "SystemAssignedIdentity" 188 | } 189 | } 190 | }, 191 | "scaleAndConcurrency": { 192 | "maximumInstanceCount": "[parameters('maximumInstanceCount')]", 193 | "instanceMemoryMB": "[parameters('instanceMemoryMB')]" 194 | }, 195 | "runtime": { 196 | "name": "[parameters('functionAppRuntime')]", 197 | "version": "[parameters('functionAppRuntimeVersion')]" 198 | } 199 | }, 200 | "siteConfig": { 201 | "appSettings": [ 202 | { 203 | "name": "AzureWebJobsStorage__accountName", 204 | "value": "[variables('storageAccountName')]" 205 | }, 206 | { 207 | "name": "APPINSIGHTS_INSTRUMENTATIONKEY", 208 | "value": "[reference(resourceId('Microsoft.Insights/components', parameters('functionAppName')), '2020-02-02').InstrumentationKey]" 209 | } 210 | ] 211 | } 212 | } 213 | }, 214 | { //Gives the function app access to the storage account using system assigned managed identity 215 | "type": "Microsoft.Authorization/roleAssignments", 216 | "apiVersion": "2022-04-01", 217 | "name": "[parameters('roleNameGuid')]", 218 | "dependsOn": [ 219 | "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]", 220 | "[resourceId('Microsoft.Web/sites', parameters('functionAppName'))]" 221 | ], 222 | "properties": { 223 | "principalId": "[reference(resourceId('Microsoft.Web/sites', parameters('functionAppName')), '2016-08-01', 'Full').identity.principalId]", 224 | "roleDefinitionId": "[variables('storageBlobContributorRoleId')]" 225 | }, 226 | "scope": "[concat('Microsoft.Storage/storageAccounts', '/', variables('storageAccountName'))]" 227 | }, 228 | { //Wait for 30 seconds before starting OneDeploy to complete role assignment before deployment 229 | "type": "Microsoft.Resources/deploymentScripts", 230 | "apiVersion": "2020-10-01", 231 | "name": "WaitSection", 232 | "location": "[parameters('location')]", 233 | "dependsOn": [ 234 | "[resourceId('Microsoft.Web/sites', parameters('functionAppName'))]" 235 | ], 236 | "kind": "AzurePowerShell", 237 | "properties": { 238 | "azPowerShellVersion": "7.0", 239 | "scriptContent": "start-sleep -Seconds 30", 240 | "cleanupPreference": "Always", 241 | "retentionInterval": "PT1H" 242 | } 243 | }, 244 | { 245 | "type": "Microsoft.Web/sites/extensions", 246 | "apiVersion": "2022-09-01", 247 | "name": "[format('{0}/{1}', parameters('functionAppName'), 'onedeploy')]", 248 | "dependsOn": [ 249 | "WaitSection" 250 | ], 251 | "properties": { 252 | "packageUri": "[parameters('packageUri')]", 253 | "remoteBuild": true 254 | } 255 | } 256 | ] 257 | } 258 | -------------------------------------------------------------------------------- /function-app-linux-flex-consumption/azuredeploy.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /function-app-premium-plan/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: This template provisions a function app on a Premium plan. 3 | page_type: sample 4 | products: 5 | - azure 6 | - azure-resource-manager 7 | - bicep 8 | urlFragment: function-app-premium-plan 9 | languages: 10 | - bicep 11 | - json 12 | --- 13 | # Azure Function App Hosted on Premium Plan 14 | 15 | This sample Azure Resource Manager template deploys an Azure Function App hosted on Premium plan and required resource including ZipDeploy extension to mount zip package for deployment. 16 | 17 | [![Deploy to Azure](/images/deploytoazure.png)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure-Samples%2Ffunction-app-arm-templates%2Fmain%2Ffunction-app-premium-plan%2Fazuredeploy.json) 18 | 19 | ### OS 20 | 21 | This template has a parameter `functionPlanOS` to choose Windows or Linux OS. Windows is selected by default. If you choose Linux, then parameter `linuxFxVersion` will be required, so you can skip it for Windows. 22 | 23 | ### Premium Plan 24 | 25 | The Azure Function app provisioned in this sample uses an [Azure Functions Elastic Premium plan](https://docs.microsoft.com/azure/azure-functions/functions-premium-plan#features). 26 | 27 | + **Microsoft.Web/serverfarms**: The Azure Functions Premium plan (a.k.a. Elastic Premium plan) 28 | 29 | ### Azure Function App 30 | 31 | The Function App uses the [AzureWebJobsStorage](https://docs.microsoft.com/azure/azure-functions/functions-app-settings#azurewebjobsstorage) and [WEBSITE_CONTENTAZUREFILECONNECTIONSTRING](https://docs.microsoft.com/azure/azure-functions/functions-app-settings#website_contentazurefileconnectionstring) app settings to connect to a Storage Account. 32 | 33 | + **Microsoft.Web/sites**: The function app instance. 34 | 35 | ### ZipDeploy Extension 36 | 37 | The Zip Deploy extension is added along with recommended app setting `WEBSITE_RUN_FROM_PACKAGE=1` to mount the zip package for deployment. This is the recommended path for deployment, except for [Linux Consumption Plan](/function-app-linux-consumption) 38 | 39 | + **Microsoft.Web/sites/extensions**: The ZipDeploy extension. 40 | 41 | ### Azure Storage account 42 | 43 | The Storage account that the Function uses for operation and for file contents. 44 | 45 | + **Microsoft.Storage/storageAccounts**: [Azure Functions requires a storage account](https://docs.microsoft.com/azure/azure-functions/storage-considerations) for the function app instance. 46 | 47 | ### Application Insights 48 | 49 | [Application Insights](https://docs.microsoft.com/azure/azure-monitor/app/app-insights-overview) is used to provide [monitor the Azure Function](https://docs.microsoft.com/azure/azure-functions/functions-monitoring). 50 | 51 | + **Microsoft.Insights/components**: The Application Insights instance used by the Azure Function for monitoring. 52 | 53 | `Tags: Microsoft.Storage/storageAccounts, microsoft.insights/components, Microsoft.Web/serverfarms, Microsoft.Web/sites, Microsoft.Web/sites/extensions` 54 | -------------------------------------------------------------------------------- /function-app-premium-plan/azuredeploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "metadata": { 5 | "_generator": { 6 | "name": "bicep", 7 | "version": "0.10.61.36676", 8 | "templateHash": "8819040794260330687" 9 | } 10 | }, 11 | "parameters": { 12 | "functionAppName": { 13 | "type": "string", 14 | "defaultValue": "[format('func-{0}', uniqueString(resourceGroup().id))]", 15 | "metadata": { 16 | "description": "The name of the Azure Function app." 17 | } 18 | }, 19 | "storageAccountType": { 20 | "type": "string", 21 | "defaultValue": "Standard_LRS", 22 | "allowedValues": [ 23 | "Standard_LRS", 24 | "Standard_GRS", 25 | "Standard_RAGRS" 26 | ], 27 | "metadata": { 28 | "description": "Storage Account type" 29 | } 30 | }, 31 | "location": { 32 | "type": "string", 33 | "defaultValue": "[resourceGroup().location]", 34 | "metadata": { 35 | "description": "Location for all resources." 36 | } 37 | }, 38 | "appInsightsLocation": { 39 | "type": "string", 40 | "defaultValue": "[resourceGroup().location]", 41 | "metadata": { 42 | "description": "Location for Application Insights" 43 | } 44 | }, 45 | "functionWorkerRuntime": { 46 | "type": "string", 47 | "defaultValue": "node", 48 | "allowedValues": [ 49 | "dotnet", 50 | "node", 51 | "python", 52 | "java" 53 | ], 54 | "metadata": { 55 | "description": "The language worker runtime to load in the function app." 56 | } 57 | }, 58 | "functionPlanOS": { 59 | "type": "string", 60 | "defaultValue": "Windows", 61 | "allowedValues": [ 62 | "Windows", 63 | "Linux" 64 | ], 65 | "metadata": { 66 | "description": "Specifies the OS used for the Azure Function hosting plan." 67 | } 68 | }, 69 | "functionAppPlanSku": { 70 | "type": "string", 71 | "defaultValue": "EP1", 72 | "allowedValues": [ 73 | "EP1", 74 | "EP2", 75 | "EP3" 76 | ], 77 | "metadata": { 78 | "description": "Specifies the Azure Function hosting plan SKU." 79 | } 80 | }, 81 | "packageUri": { 82 | "type": "string", 83 | "metadata": { 84 | "description": "The zip content url." 85 | } 86 | }, 87 | "linuxFxVersion": { 88 | "type": "string", 89 | "defaultValue": "", 90 | "metadata": { 91 | "description": "Only required for Linux app to represent runtime stack in the format of 'runtime|runtimeVersion'. For example: 'python|3.9'" 92 | } 93 | } 94 | }, 95 | "variables": { 96 | "hostingPlanName": "[parameters('functionAppName')]", 97 | "applicationInsightsName": "[parameters('functionAppName')]", 98 | "storageAccountName": "[format('{0}azfunctions', uniqueString(resourceGroup().id))]", 99 | "isReserved": "[if(equals(parameters('functionPlanOS'), 'Linux'), true(), false())]" 100 | }, 101 | "resources": [ 102 | { 103 | "type": "Microsoft.Storage/storageAccounts", 104 | "apiVersion": "2022-05-01", 105 | "name": "[variables('storageAccountName')]", 106 | "location": "[parameters('location')]", 107 | "sku": { 108 | "name": "[parameters('storageAccountType')]" 109 | }, 110 | "kind": "Storage" 111 | }, 112 | { 113 | "type": "Microsoft.Web/serverfarms", 114 | "apiVersion": "2022-03-01", 115 | "name": "[variables('hostingPlanName')]", 116 | "location": "[parameters('location')]", 117 | "sku": { 118 | "tier": "ElasticPremium", 119 | "name": "[parameters('functionAppPlanSku')]", 120 | "family": "EP" 121 | }, 122 | "properties": { 123 | "maximumElasticWorkerCount": 20, 124 | "reserved": "[variables('isReserved')]" 125 | }, 126 | "kind": "elastic" 127 | }, 128 | { 129 | "type": "Microsoft.Insights/components", 130 | "apiVersion": "2020-02-02", 131 | "name": "[variables('applicationInsightsName')]", 132 | "location": "[parameters('appInsightsLocation')]", 133 | "tags": { 134 | "[format('hidden-link:{0}', resourceId('Microsoft.Web/sites', variables('applicationInsightsName')))]": "Resource" 135 | }, 136 | "properties": { 137 | "Application_Type": "web" 138 | }, 139 | "kind": "web" 140 | }, 141 | { 142 | "type": "Microsoft.Web/sites", 143 | "apiVersion": "2022-03-01", 144 | "name": "[parameters('functionAppName')]", 145 | "location": "[parameters('location')]", 146 | "kind": "[if(variables('isReserved'), 'functionapp,linux', 'functionapp')]", 147 | "properties": { 148 | "reserved": "[variables('isReserved')]", 149 | "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]", 150 | "siteConfig": { 151 | "linuxFxVersion": "[if(variables('isReserved'), parameters('linuxFxVersion'), json('null'))]", 152 | "appSettings": [ 153 | { 154 | "name": "APPINSIGHTS_INSTRUMENTATIONKEY", 155 | "value": "[reference(resourceId('Microsoft.Insights/components', variables('applicationInsightsName')), '2015-05-01').InstrumentationKey]" 156 | }, 157 | { 158 | "name": "AzureWebJobsStorage", 159 | "value": "[format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('storageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2022-05-01').keys[0].value)]" 160 | }, 161 | { 162 | "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING", 163 | "value": "[format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('storageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2022-05-01').keys[0].value)]" 164 | }, 165 | { 166 | "name": "WEBSITE_CONTENTSHARE", 167 | "value": "[toLower(parameters('functionAppName'))]" 168 | }, 169 | { 170 | "name": "FUNCTIONS_EXTENSION_VERSION", 171 | "value": "~4" 172 | }, 173 | { 174 | "name": "FUNCTIONS_WORKER_RUNTIME", 175 | "value": "[parameters('functionWorkerRuntime')]" 176 | }, 177 | { 178 | "name": "WEBSITE_NODE_DEFAULT_VERSION", 179 | "value": "~14" 180 | }, 181 | { 182 | "name": "WEBSITE_RUN_FROM_PACKAGE", 183 | "value": "1" 184 | } 185 | ] 186 | } 187 | }, 188 | "dependsOn": [ 189 | "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]", 190 | "[resourceId('Microsoft.Insights/components', variables('applicationInsightsName'))]", 191 | "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]" 192 | ] 193 | }, 194 | { 195 | "type": "Microsoft.Web/sites/extensions", 196 | "apiVersion": "2022-03-01", 197 | "name": "[format('{0}/{1}', parameters('functionAppName'), 'zipdeploy')]", 198 | "properties": { 199 | "packageUri": "[parameters('packageUri')]" 200 | }, 201 | "dependsOn": [ 202 | "[resourceId('Microsoft.Web/sites', parameters('functionAppName'))]" 203 | ] 204 | } 205 | ] 206 | } -------------------------------------------------------------------------------- /function-app-premium-plan/azuredeploy.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /function-app-premium-plan/main.bicep: -------------------------------------------------------------------------------- 1 | @description('The name of the Azure Function app.') 2 | param functionAppName string = 'func-${uniqueString(resourceGroup().id)}' 3 | 4 | @description('Storage Account type') 5 | @allowed([ 6 | 'Standard_LRS' 7 | 'Standard_GRS' 8 | 'Standard_RAGRS' 9 | ]) 10 | param storageAccountType string = 'Standard_LRS' 11 | 12 | @description('Location for all resources.') 13 | param location string = resourceGroup().location 14 | 15 | @description('Location for Application Insights') 16 | param appInsightsLocation string = resourceGroup().location 17 | 18 | @description('The language worker runtime to load in the function app.') 19 | @allowed([ 20 | 'dotnet' 21 | 'node' 22 | 'python' 23 | 'java' 24 | ]) 25 | param functionWorkerRuntime string = 'node' 26 | 27 | @description('Specifies the OS used for the Azure Function hosting plan.') 28 | @allowed([ 29 | 'Windows' 30 | 'Linux' 31 | ]) 32 | param functionPlanOS string = 'Windows' 33 | 34 | @description('Specifies the Azure Function hosting plan SKU.') 35 | @allowed([ 36 | 'EP1' 37 | 'EP2' 38 | 'EP3' 39 | ]) 40 | param functionAppPlanSku string = 'EP1' 41 | 42 | @description('The zip content url.') 43 | param packageUri string 44 | 45 | @description('Only required for Linux app to represent runtime stack in the format of \'runtime|runtimeVersion\'. For example: \'python|3.9\'') 46 | param linuxFxVersion string = '' 47 | 48 | var hostingPlanName = functionAppName 49 | var applicationInsightsName = functionAppName 50 | var storageAccountName = '${uniqueString(resourceGroup().id)}azfunctions' 51 | var isReserved = ((functionPlanOS == 'Linux') ? true : false) 52 | 53 | resource storageAccount 'Microsoft.Storage/storageAccounts@2022-05-01' = { 54 | name: storageAccountName 55 | location: location 56 | sku: { 57 | name: storageAccountType 58 | } 59 | kind: 'Storage' 60 | } 61 | 62 | resource hostingPlan 'Microsoft.Web/serverfarms@2022-03-01' = { 63 | name: hostingPlanName 64 | location: location 65 | sku: { 66 | tier: 'ElasticPremium' 67 | name: functionAppPlanSku 68 | family: 'EP' 69 | } 70 | properties: { 71 | maximumElasticWorkerCount: 20 72 | reserved: isReserved 73 | } 74 | kind: 'elastic' 75 | } 76 | 77 | resource insight 'Microsoft.Insights/components@2020-02-02' = { 78 | name: applicationInsightsName 79 | location: appInsightsLocation 80 | tags: { 81 | 'hidden-link:${resourceId('Microsoft.Web/sites', applicationInsightsName)}': 'Resource' 82 | } 83 | properties: { 84 | Application_Type: 'web' 85 | } 86 | kind: 'web' 87 | } 88 | 89 | resource site 'Microsoft.Web/sites@2022-03-01' = { 90 | name: functionAppName 91 | location: location 92 | kind: (isReserved ? 'functionapp,linux' : 'functionapp') 93 | properties: { 94 | reserved: isReserved 95 | serverFarmId: hostingPlan.id 96 | siteConfig: { 97 | linuxFxVersion: (isReserved ? linuxFxVersion : json('null')) 98 | appSettings: [ 99 | { 100 | name: 'APPINSIGHTS_INSTRUMENTATIONKEY' 101 | value: reference(insight.id, '2015-05-01').InstrumentationKey 102 | } 103 | { 104 | name: 'AzureWebJobsStorage' 105 | value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccountName};EndpointSuffix=${environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}' 106 | } 107 | { 108 | name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING' 109 | value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccountName};EndpointSuffix=${environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}' 110 | } 111 | { 112 | name: 'WEBSITE_CONTENTSHARE' 113 | value: toLower(functionAppName) 114 | } 115 | { 116 | name: 'FUNCTIONS_EXTENSION_VERSION' 117 | value: '~4' 118 | } 119 | { 120 | name: 'FUNCTIONS_WORKER_RUNTIME' 121 | value: functionWorkerRuntime 122 | } 123 | { 124 | name: 'WEBSITE_NODE_DEFAULT_VERSION' 125 | value: '~14' 126 | } 127 | { 128 | name: 'WEBSITE_RUN_FROM_PACKAGE' 129 | value: '1' 130 | } 131 | ] 132 | } 133 | } 134 | } 135 | 136 | resource zipDeploy 'Microsoft.Web/sites/extensions@2022-03-01' = { 137 | parent: site 138 | name: 'zipdeploy' 139 | properties: { 140 | packageUri: packageUri 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /function-app-private-endpoints-storage-private-endpoints/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: This template provisions a function app on a Premium plan that has private endpoints and communicates with Azure Storage over private endpoints. 3 | page_type: sample 4 | products: 5 | - azure 6 | - azure-resource-manager 7 | urlFragment: function-app-private-endpoints-storage-private-endpoints 8 | languages: 9 | - bicep 10 | - json 11 | --- 12 | # Azure Function App with private endpoint and Azure Storage with private endpoints 13 | 14 | This sample Azure Resource Manager template deploys an Azure Function App that communicates with the Azure Storage account referenced by the AzureWebJobsStorage and WEBSITE_CONTENTAZUREFILECONNECTIONSTRING app settings, [via private endpoints](https://docs.microsoft.com/en-us/azure/azure-functions/functions-networking-options#private-endpoint-connections). 15 | 16 | [![Deploy to Azure](/images/deploytoazure.png)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure-Samples%2Ffunction-app-arm-templates%2Fmain%2Ffunction-app-private-endpoints-storage-private-endpoints%2Fazuredeploy.json) 17 | 18 | ![Function App with Storage Private Endpoints](/function-app-private-endpoints-storage-private-endpoints/images/function-app-private-endpoints-storage-private-endpoints.jpg) 19 | 20 | ### OS 21 | 22 | This template has a parameter `functionPlanOS` to choose Windows or Linux OS. Windows is selected by default. If you choose Linux, then parameter `linuxFxVersion` will be parameter, so you can skip it for Windows. 23 | 24 | ### Elastic Premium Plan 25 | 26 | The Azure Function app provisioned in this sample uses an [Azure Functions Elastic Premium plan](https://docs.microsoft.com/azure/azure-functions/functions-premium-plan#features). 27 | 28 | + **Microsoft.Web/serverfarms**: The Azure Functions Premium plan (a.k.a. Elastic Premium plan) 29 | 30 | ### Azure Function App 31 | 32 | The Function App uses the [AzureWebJobsStorage](https://docs.microsoft.com/azure/azure-functions/functions-app-settings#azurewebjobsstorage) and [WEBSITE_CONTENTAZUREFILECONNECTIONSTRING](https://docs.microsoft.com/azure/azure-functions/functions-app-settings#website_contentazurefileconnectionstring) app settings to connect to a private endpoint-secured Storage Account. 33 | 34 | + **Microsoft.Web/sites**: The function app instance. 35 | 36 | ### Azure Storage account 37 | 38 | The Storage account that the Function uses for operation and for file contents. 39 | 40 | + **Microsoft.Storage/storageAccounts**: [Azure Functions requires a storage account](https://docs.microsoft.com/azure/azure-functions/storage-considerations) for the function app instance. 41 | 42 | ### Virtual Network 43 | 44 | Azure resources in this sample either integrate with or are placed within a virtual network. The use of private endpoints keeps network traffic contained with the virtual network. 45 | 46 | The sample uses two subnets: 47 | 48 | - Subnet for Azure Function virtual network integration. This subnet is delegated to the Function App. 49 | - Subnet for private endpoints. Private IP addresses are allocated from this subnet. 50 | 51 | ### Private Endpoints 52 | 53 | [Azure Private Endpoints](https://docs.microsoft.com/azure/private-link/private-endpoint-overview) are used to connect to specific Azure resources using a private IP address This ensures that network traffic remains within the designated virtual network, and access is available only for specific resources. This sample configures private endpoints for the following Azure resources: 54 | 55 | - [Azure Funcion App](https://docs.microsoft.com/en-us/azure/app-service/networking/private-endpoint) 56 | - [Azure Storage](https://docs.microsoft.com/azure/storage/common/storage-private-endpoints) 57 | - Azure File storage 58 | - Azure Blob storage 59 | - Azure Queue storage 60 | - Azure Table storage 61 | 62 | ### Private DNS Zones 63 | 64 | Using a private endpoint to connect to Azure resources means connecting to a private IP address instead of the public endpoint. Existing Azure services are configured to use existing DNS to connect to the public endpoint. The DNS configuration will need to be overridden to connect to the private endpoint. 65 | 66 | A private DNS zone will be created for each Azure resource configured with a private endpoint. A DNS A record is created for each private IP address associated with the private endpoint. 67 | 68 | The following DNS zones are created in this sample: 69 | 70 | - privatelink.azurewebsites.net 71 | - privatelink.queue.core.windows.net 72 | - privatelink.blob.core.windows.net 73 | - privatelink.table.core.windows.net 74 | - privatelink.file.core.windows.net 75 | 76 | ### Application Insights 77 | 78 | [Application Insights](https://docs.microsoft.com/azure/azure-monitor/app/app-insights-overview) is used to provide [monitor the Azure Function](https://docs.microsoft.com/azure/azure-functions/functions-monitoring). 79 | 80 | + **Microsoft.Insights/components**: The Application Insights instance used by the Azure Function for monitoring. 81 | 82 | ### NOTE: 83 | 84 | + This ARM template will secure your Function App by configuring the Private Endpoint, eliminating public exposure. You can connect to your App from on-premises networks that connects to the VNet using a VPN or ExpressRoute private peering. 85 | + This ARM template will allow access to the storage account through the private endpoints only. So, you will not be able to access the data storage in the storage account through the portal or otherwise. 86 | + You can give access to your secured IP address or virtual network for the data storage in the storage account, by [Managing the default network access rule](https://docs.microsoft.com/en-us/azure/storage/common/storage-network-security?tabs=azure-portal#change-the-default-network-access-rule) 87 | 88 |
89 | 90 | For more information on configuring Azure Storage firewalls and virtual networks, please refer: [Configure Azure Storage firewalls and virtual networks](https://docs.microsoft.com/en-us/azure/storage/common/storage-network-security?tabs=azure-portal) 91 | 92 | For more information on Azure Functions networking options and VNET integration, please refer: [Azure Functions Networking Options](https://docs.microsoft.com/en-us/azure/azure-functions/functions-networking-options#restrict-your-storage-account-to-a-virtual-network) 93 | 94 | `Tags: Microsoft.Network/privateDnsZones/virtualNetworkLinks, Microsoft.Network/privateEndpoints/privateDnsZoneGroups, Microsoft.Network/virtualNetworks, Microsoft.Network/privateDnsZones, Microsoft.Network/privateEndpoints, Microsoft.Storage/storageAccounts, Microsoft.Storage/storageAccounts/fileServices/shares, Microsoft.Insights/components, Microsoft.Web/serverfarms, Microsoft.Web/sites` 95 | -------------------------------------------------------------------------------- /function-app-private-endpoints-storage-private-endpoints/azuredeploy.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /function-app-private-endpoints-storage-private-endpoints/images/function-app-private-endpoints-storage-private-endpoints.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/function-app-arm-templates/fc7311939d3bac5706778af24c8be82a7889a230/function-app-private-endpoints-storage-private-endpoints/images/function-app-private-endpoints-storage-private-endpoints.jpg -------------------------------------------------------------------------------- /function-app-private-endpoints-storage-private-endpoints/main.bicep: -------------------------------------------------------------------------------- 1 | @description('The name of the Azure Function app.') 2 | param functionAppName string = 'func-${uniqueString(resourceGroup().id)}' 3 | 4 | @description('The location into which the resources should be deployed.') 5 | param location string = resourceGroup().location 6 | 7 | @description('The language worker runtime to load in the function app.') 8 | @allowed([ 9 | 'dotnet' 10 | 'node' 11 | 'python' 12 | 'java' 13 | ]) 14 | param functionWorkerRuntime string = 'node' 15 | 16 | @description('Specifies the OS used for the Azure Function hosting plan.') 17 | @allowed([ 18 | 'Windows' 19 | 'Linux' 20 | ]) 21 | param functionPlanOS string = 'Windows' 22 | 23 | @description('Specifies the Azure Function hosting plan SKU.') 24 | @allowed([ 25 | 'EP1' 26 | 'EP2' 27 | 'EP3' 28 | ]) 29 | param functionAppPlanSku string = 'EP1' 30 | 31 | @description('The name of the Azure Function hosting plan.') 32 | param functionAppPlanName string = 'plan-${uniqueString(resourceGroup().id)}' 33 | 34 | @description('The name of the backend Azure storage account used by the Azure Function app.') 35 | param functionStorageAccountName string = 'st${uniqueString(resourceGroup().id)}' 36 | 37 | @description('The name of the virtual network for virtual network integration.') 38 | param vnetName string = 'vnet-${uniqueString(resourceGroup().id)}' 39 | 40 | @description('The name of the virtual network subnet to be associated with the Azure Function app.') 41 | param functionSubnetName string = 'snet-func' 42 | 43 | @description('The name of the virtual network subnet used for allocating IP addresses for private endpoints.') 44 | param privateEndpointSubnetName string = 'snet-pe' 45 | 46 | @description('The IP adddress space used for the virtual network.') 47 | param vnetAddressPrefix string = '10.100.0.0/16' 48 | 49 | @description('The IP address space used for the Azure Function integration subnet.') 50 | param functionSubnetAddressPrefix string = '10.100.0.0/24' 51 | 52 | @description('The IP address space used for the private endpoints.') 53 | param privateEndpointSubnetAddressPrefix string = '10.100.1.0/24' 54 | 55 | @description('Only required for Linux app to represent runtime stack in the format of \'runtime|runtimeVersion\'. For example: \'python|3.9\'') 56 | param linuxFxVersion string = '' 57 | 58 | var applicationInsightsName = 'appi-${uniqueString(resourceGroup().id)}' 59 | var privateFunctionAppDnsZoneName = 'privatelink.azurewebsites.net' 60 | var privateEndpointFunctionAppName = '${functionAppName}-private-endpoint' 61 | var privateStorageFileDnsZoneName = 'privatelink.file.${environment().suffixes.storage}' 62 | var privateEndpointStorageFileName = '${functionStorageAccountName}-file-private-endpoint' 63 | var privateStorageTableDnsZoneName = 'privatelink.table.${environment().suffixes.storage}' 64 | var privateEndpointStorageTableName = '${functionStorageAccountName}-table-private-endpoint' 65 | var privateStorageBlobDnsZoneName = 'privatelink.blob.${environment().suffixes.storage}' 66 | var privateEndpointStorageBlobName = '${functionStorageAccountName}-blob-private-endpoint' 67 | var privateStorageQueueDnsZoneName = 'privatelink.queue.${environment().suffixes.storage}' 68 | var privateEndpointStorageQueueName = '${functionStorageAccountName}-queue-private-endpoint' 69 | var functionContentShareName = 'function-content-share' 70 | var isReserved = ((functionPlanOS == 'Linux') ? true : false) 71 | 72 | resource vnet 'Microsoft.Network/virtualNetworks@2022-05-01' = { 73 | name: vnetName 74 | location: location 75 | properties: { 76 | addressSpace: { 77 | addressPrefixes: [ 78 | vnetAddressPrefix 79 | ] 80 | } 81 | subnets: [ 82 | { 83 | name: functionSubnetName 84 | properties: { 85 | privateEndpointNetworkPolicies: 'Enabled' 86 | privateLinkServiceNetworkPolicies: 'Enabled' 87 | delegations: [ 88 | { 89 | name: 'webapp' 90 | properties: { 91 | serviceName: 'Microsoft.Web/serverFarms' 92 | } 93 | } 94 | ] 95 | addressPrefix: functionSubnetAddressPrefix 96 | } 97 | } 98 | { 99 | name: privateEndpointSubnetName 100 | properties: { 101 | privateEndpointNetworkPolicies: 'Disabled' 102 | privateLinkServiceNetworkPolicies: 'Enabled' 103 | addressPrefix: privateEndpointSubnetAddressPrefix 104 | } 105 | } 106 | ] 107 | } 108 | } 109 | 110 | resource privateStorageFileDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = { 111 | name: privateStorageFileDnsZoneName 112 | location: 'global' 113 | } 114 | 115 | resource privateStorageBlobDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = { 116 | name: privateStorageBlobDnsZoneName 117 | location: 'global' 118 | } 119 | 120 | resource privateStorageQueueDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = { 121 | name: privateStorageQueueDnsZoneName 122 | location: 'global' 123 | } 124 | 125 | resource privateStorageTableDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = { 126 | name: privateStorageTableDnsZoneName 127 | location: 'global' 128 | } 129 | 130 | resource privateStorageFileDnsZoneLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = { 131 | parent: privateStorageFileDnsZone 132 | name: '${privateStorageFileDnsZoneName}-link' 133 | location: 'global' 134 | properties: { 135 | registrationEnabled: false 136 | virtualNetwork: { 137 | id: vnet.id 138 | } 139 | } 140 | } 141 | 142 | resource privateStorageBlobDnsZoneLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = { 143 | parent: privateStorageBlobDnsZone 144 | name: '${privateStorageBlobDnsZoneName}-link' 145 | location: 'global' 146 | properties: { 147 | registrationEnabled: false 148 | virtualNetwork: { 149 | id: vnet.id 150 | } 151 | } 152 | } 153 | 154 | resource privateStorageTableDnsZoneLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = { 155 | parent: privateStorageTableDnsZone 156 | name: '${privateStorageTableDnsZoneName}-link' 157 | location: 'global' 158 | properties: { 159 | registrationEnabled: false 160 | virtualNetwork: { 161 | id: vnet.id 162 | } 163 | } 164 | } 165 | 166 | resource privateStorageQueueDnsZoneLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = { 167 | parent: privateStorageQueueDnsZone 168 | name: '${privateStorageQueueDnsZoneName}-link' 169 | location: 'global' 170 | properties: { 171 | registrationEnabled: false 172 | virtualNetwork: { 173 | id: vnet.id 174 | } 175 | } 176 | } 177 | 178 | resource privateEndpointStorageFile 'Microsoft.Network/privateEndpoints@2022-05-01' = { 179 | name: privateEndpointStorageFileName 180 | location: location 181 | properties: { 182 | subnet: { 183 | id: resourceId('Microsoft.Network/virtualNetworks/subnets', vnetName, privateEndpointSubnetName) 184 | } 185 | privateLinkServiceConnections: [ 186 | { 187 | name: 'MyStorageFilePrivateLinkConnection' 188 | properties: { 189 | privateLinkServiceId: storageAccount.id 190 | groupIds: [ 191 | 'file' 192 | ] 193 | } 194 | } 195 | ] 196 | } 197 | dependsOn: [ 198 | vnet 199 | ] 200 | } 201 | 202 | resource privateEndpointStorageBlob 'Microsoft.Network/privateEndpoints@2022-05-01' = { 203 | name: privateEndpointStorageBlobName 204 | location: location 205 | properties: { 206 | subnet: { 207 | id: resourceId('Microsoft.Network/virtualNetworks/subnets', vnetName, privateEndpointSubnetName) 208 | } 209 | privateLinkServiceConnections: [ 210 | { 211 | name: 'MyStorageBlobPrivateLinkConnection' 212 | properties: { 213 | privateLinkServiceId: storageAccount.id 214 | groupIds: [ 215 | 'blob' 216 | ] 217 | } 218 | } 219 | ] 220 | } 221 | dependsOn: [ 222 | vnet 223 | ] 224 | } 225 | 226 | resource privateEndpointStorageTable 'Microsoft.Network/privateEndpoints@2022-05-01' = { 227 | name: privateEndpointStorageTableName 228 | location: location 229 | properties: { 230 | subnet: { 231 | id: resourceId('Microsoft.Network/virtualNetworks/subnets', vnetName, privateEndpointSubnetName) 232 | } 233 | privateLinkServiceConnections: [ 234 | { 235 | name: 'MyStorageTablePrivateLinkConnection' 236 | properties: { 237 | privateLinkServiceId: storageAccount.id 238 | groupIds: [ 239 | 'table' 240 | ] 241 | } 242 | } 243 | ] 244 | } 245 | dependsOn: [ 246 | vnet 247 | ] 248 | } 249 | 250 | resource privateEndpointStorageQueue 'Microsoft.Network/privateEndpoints@2022-05-01' = { 251 | name: privateEndpointStorageQueueName 252 | location: location 253 | properties: { 254 | subnet: { 255 | id: resourceId('Microsoft.Network/virtualNetworks/subnets', vnetName, privateEndpointSubnetName) 256 | } 257 | privateLinkServiceConnections: [ 258 | { 259 | name: 'MyStorageQueuePrivateLinkConnection' 260 | properties: { 261 | privateLinkServiceId: storageAccount.id 262 | groupIds: [ 263 | 'queue' 264 | ] 265 | } 266 | } 267 | ] 268 | } 269 | dependsOn: [ 270 | vnet 271 | ] 272 | } 273 | 274 | resource privateEndpointStorageFilePrivateDnsZoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2022-05-01' = { 275 | parent: privateEndpointStorageFile 276 | name: 'filePrivateDnsZoneGroup' 277 | properties: { 278 | privateDnsZoneConfigs: [ 279 | { 280 | name: 'config' 281 | properties: { 282 | privateDnsZoneId: privateStorageFileDnsZone.id 283 | } 284 | } 285 | ] 286 | } 287 | } 288 | 289 | resource privateEndpointStorageBlobPrivateDnsZoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2022-05-01' = { 290 | parent: privateEndpointStorageBlob 291 | name: 'blobPrivateDnsZoneGroup' 292 | properties: { 293 | privateDnsZoneConfigs: [ 294 | { 295 | name: 'config' 296 | properties: { 297 | privateDnsZoneId: privateStorageBlobDnsZone.id 298 | } 299 | } 300 | ] 301 | } 302 | } 303 | 304 | resource privateEndpointStorageTablePrivateDnsZoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2022-05-01' = { 305 | parent: privateEndpointStorageTable 306 | name: 'tablePrivateDnsZoneGroup' 307 | properties: { 308 | privateDnsZoneConfigs: [ 309 | { 310 | name: 'config' 311 | properties: { 312 | privateDnsZoneId: privateStorageTableDnsZone.id 313 | } 314 | } 315 | ] 316 | } 317 | } 318 | 319 | resource privateEndpointStorageQueuePrivateDnsZoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2022-05-01' = { 320 | parent: privateEndpointStorageQueue 321 | name: 'queuePrivateDnsZoneGroup' 322 | properties: { 323 | privateDnsZoneConfigs: [ 324 | { 325 | name: 'config' 326 | properties: { 327 | privateDnsZoneId: privateStorageQueueDnsZone.id 328 | } 329 | } 330 | ] 331 | } 332 | } 333 | 334 | resource storageAccount 'Microsoft.Storage/storageAccounts@2022-05-01' = { 335 | name: functionStorageAccountName 336 | location: location 337 | kind: 'StorageV2' 338 | sku: { 339 | name: 'Standard_LRS' 340 | } 341 | properties: { 342 | publicNetworkAccess: 'Disabled' 343 | allowBlobPublicAccess: false 344 | networkAcls: { 345 | bypass: 'None' 346 | defaultAction: 'Deny' 347 | } 348 | } 349 | } 350 | 351 | resource fileService 'Microsoft.Storage/storageAccounts/fileServices/shares@2022-05-01' = { 352 | name: '${functionStorageAccountName}/default/${functionContentShareName}' 353 | dependsOn: [ 354 | storageAccount 355 | ] 356 | } 357 | 358 | resource applicationInsight 'Microsoft.Insights/components@2020-02-02' = { 359 | name: applicationInsightsName 360 | location: location 361 | kind: 'web' 362 | properties: { 363 | Application_Type: 'web' 364 | } 365 | } 366 | 367 | resource functionAppPlan 'Microsoft.Web/serverfarms@2022-03-01' = { 368 | name: functionAppPlanName 369 | location: location 370 | sku: { 371 | tier: 'ElasticPremium' 372 | name: functionAppPlanSku 373 | size: functionAppPlanSku 374 | family: 'EP' 375 | } 376 | kind: 'elastic' 377 | properties: { 378 | maximumElasticWorkerCount: 20 379 | reserved: isReserved 380 | } 381 | } 382 | 383 | resource site 'Microsoft.Web/sites@2022-03-01' = { 384 | name: functionAppName 385 | location: location 386 | kind: (isReserved ? 'functionapp,linux' : 'functionapp') 387 | properties: { 388 | reserved: isReserved 389 | serverFarmId: functionAppPlan.id 390 | siteConfig: { 391 | functionsRuntimeScaleMonitoringEnabled: true 392 | linuxFxVersion: (isReserved ? linuxFxVersion : json('null')) 393 | appSettings: [ 394 | { 395 | name: 'APPINSIGHTS_INSTRUMENTATIONKEY' 396 | value: applicationInsight.properties.InstrumentationKey 397 | } 398 | { 399 | name: 'AzureWebJobsStorage' 400 | value: 'DefaultEndpointsProtocol=https;AccountName=${functionStorageAccountName};AccountKey=${storageAccount.listkeys().keys[0].value}' 401 | } 402 | { 403 | name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING' 404 | value: 'DefaultEndpointsProtocol=https;AccountName=${functionStorageAccountName};AccountKey=${storageAccount.listkeys().keys[0].value}' 405 | } 406 | { 407 | name: 'WEBSITE_CONTENTSHARE' 408 | value: functionContentShareName 409 | } 410 | { 411 | name: 'FUNCTIONS_EXTENSION_VERSION' 412 | value: '~4' 413 | } 414 | { 415 | name: 'FUNCTIONS_WORKER_RUNTIME' 416 | value: functionWorkerRuntime 417 | } 418 | { 419 | name: 'WEBSITE_NODE_DEFAULT_VERSION' 420 | value: '~14' 421 | } 422 | { 423 | name: 'WEBSITE_VNET_ROUTE_ALL' 424 | value: '1' 425 | } 426 | { 427 | name: 'WEBSITE_CONTENTOVERVNET' 428 | value: '1' 429 | } 430 | ] 431 | } 432 | } 433 | dependsOn: [ 434 | fileService 435 | privateStorageFileDnsZoneLink 436 | privateEndpointStorageFilePrivateDnsZoneGroup 437 | privateStorageBlobDnsZoneLink 438 | privateEndpointStorageBlobPrivateDnsZoneGroup 439 | privateStorageTableDnsZoneLink 440 | privateEndpointStorageTablePrivateDnsZoneGroup 441 | privateStorageQueueDnsZoneLink 442 | privateEndpointStorageQueuePrivateDnsZoneGroup 443 | ] 444 | } 445 | 446 | resource networkConfig 'Microsoft.Web/sites/networkConfig@2022-03-01' = { 447 | parent: site 448 | name: 'virtualNetwork' 449 | properties: { 450 | subnetResourceId: resourceId('Microsoft.Network/virtualNetworks/subnets', vnetName, functionSubnetName) 451 | swiftSupported: true 452 | } 453 | dependsOn: [ 454 | vnet 455 | ] 456 | } 457 | 458 | resource privateFunctionAppDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = { 459 | name: privateFunctionAppDnsZoneName 460 | location: 'global' 461 | } 462 | 463 | resource privateDnsZoneLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = { 464 | parent: privateFunctionAppDnsZone 465 | name: '${privateFunctionAppDnsZoneName}-link' 466 | location: 'global' 467 | properties: { 468 | registrationEnabled: false 469 | virtualNetwork: { 470 | id: vnet.id 471 | } 472 | } 473 | } 474 | 475 | resource privateEndpoint 'Microsoft.Network/privateEndpoints@2022-05-01' = { 476 | name: privateEndpointFunctionAppName 477 | location: location 478 | properties: { 479 | subnet: { 480 | id: resourceId('Microsoft.Network/virtualNetworks/subnets', vnetName, privateEndpointSubnetName) 481 | } 482 | privateLinkServiceConnections: [ 483 | { 484 | name: 'MyFunctionAppPrivateLinkConnection' 485 | properties: { 486 | privateLinkServiceId: site.id 487 | groupIds: [ 488 | 'sites' 489 | ] 490 | } 491 | } 492 | ] 493 | } 494 | dependsOn: [ 495 | vnet 496 | ] 497 | } 498 | 499 | resource privateDnsZoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2022-05-01' = { 500 | parent: privateEndpoint 501 | name: 'funcPrivateDnsZoneGroup' 502 | properties: { 503 | privateDnsZoneConfigs: [ 504 | { 505 | name: 'config' 506 | properties: { 507 | privateDnsZoneId: privateFunctionAppDnsZone.id 508 | } 509 | } 510 | ] 511 | } 512 | } 513 | -------------------------------------------------------------------------------- /function-app-storage-private-endpoints/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: This template provisions a function app on a Premium plan that communicates with Azure Storage over private endpoints. 3 | page_type: sample 4 | products: 5 | - azure 6 | - azure-resource-manager 7 | urlFragment: function-app-storage-private-endpoints 8 | languages: 9 | - bicep 10 | - json 11 | --- 12 | # Azure Function App with Private Endpoint Secured Azure Storage 13 | 14 | This sample Azure Resource Manager template deploys an Azure Function App that communicates with the Azure Storage account referenced by the AzureWebJobsStorage and WEBSITE_CONTENTAZUREFILECONNECTIONSTRING app settings, [via private endpoints](https://docs.microsoft.com/en-us/azure/azure-functions/functions-networking-options#private-endpoint-connections). 15 | 16 | [![Deploy to Azure](/images/deploytoazure.png)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure-Samples%2Ffunction-app-arm-templates%2Fmain%2Ffunction-app-storage-private-endpoints%2Fazuredeploy.json) 17 | 18 | ![Function App with Storage Private Endpoints](/function-app-storage-private-endpoints/images/function-app-storage-private-endpoints.jpg) 19 | 20 | ### OS 21 | 22 | This template has a parameter `functionPlanOS` to choose Windows or Linux OS. Windows is selected by default. If you choose Linux, then parameter `linuxFxVersion` will be parameter, so you can skip it for Windows. 23 | 24 | ### Elastic Premium Plan 25 | 26 | The Azure Function app provisioned in this sample uses an [Azure Functions Elastic Premium plan](https://docs.microsoft.com/azure/azure-functions/functions-premium-plan#features). 27 | 28 | + **Microsoft.Web/serverfarms**: The Azure Functions Premium plan (a.k.a. Elastic Premium plan) 29 | 30 | ### Azure Function App 31 | 32 | The Function App uses the [AzureWebJobsStorage](https://docs.microsoft.com/azure/azure-functions/functions-app-settings#azurewebjobsstorage) and [WEBSITE_CONTENTAZUREFILECONNECTIONSTRING](https://docs.microsoft.com/azure/azure-functions/functions-app-settings#website_contentazurefileconnectionstring) app settings to connect to a private endpoint-secured Storage Account. 33 | 34 | + **Microsoft.Web/sites**: The function app instance. 35 | 36 | ### Azure Storage account 37 | 38 | The Storage account that the Function uses for operation and for file contents. 39 | 40 | + **Microsoft.Storage/storageAccounts**: [Azure Functions requires a storage account](https://docs.microsoft.com/azure/azure-functions/storage-considerations) for the function app instance. 41 | 42 | ### Virtual Network 43 | 44 | Azure resources in this sample either integrate with or are placed within a virtual network. The use of private endpoints keeps network traffic contained with the virtual network. 45 | 46 | The sample uses two subnets: 47 | 48 | - Subnet for Azure Function virtual network integration. This subnet is delegated to the Function App. 49 | - Subnet for private endpoints. Private IP addresses are allocated from this subnet. 50 | 51 | ### Private Endpoints 52 | 53 | [Azure Private Endpoints](https://docs.microsoft.com/azure/private-link/private-endpoint-overview) are used to connect to specific Azure resources using a private IP address This ensures that network traffic remains within the designated virtual network, and access is available only for specific resources. This sample configures private endpoints for the following Azure resources: 54 | 55 | - [Azure Storage](https://docs.microsoft.com/azure/storage/common/storage-private-endpoints) 56 | - Azure File storage 57 | - Azure Blob storage 58 | - Azure Queue storage 59 | - Azure Table storage 60 | 61 | ### Private DNS Zones 62 | 63 | Using a private endpoint to connect to Azure resources means connecting to a private IP address instead of the public endpoint. Existing Azure services are configured to use existing DNS to connect to the public endpoint. The DNS configuration will need to be overridden to connect to the private endpoint. 64 | 65 | A private DNS zone will be created for each Azure resource configured with a private endpoint. A DNS A record is created for each private IP address associated with the private endpoint. 66 | 67 | The following DNS zones are created in this sample: 68 | 69 | - privatelink.queue.core.windows.net 70 | - privatelink.blob.core.windows.net 71 | - privatelink.table.core.windows.net 72 | - privatelink.file.core.windows.net 73 | 74 | ### Application Insights 75 | 76 | [Application Insights](https://docs.microsoft.com/azure/azure-monitor/app/app-insights-overview) is used to provide [monitor the Azure Function](https://docs.microsoft.com/azure/azure-functions/functions-monitoring). 77 | 78 | + **Microsoft.Insights/components**: The Application Insights instance used by the Azure Function for monitoring. 79 | 80 | ### NOTE: 81 | 82 | + This ARM template will allow access to the storage account through the private endpoints only. So, you will not be able to access the data storage in the storage account through the portal or otherwise. 83 | + You can give access to your secured IP address or virtual network for the data storage in the storage account, by [Managing the default network access rule](https://docs.microsoft.com/en-us/azure/storage/common/storage-network-security?tabs=azure-portal#change-the-default-network-access-rule) 84 | 85 |
86 | 87 | For more information on configuring Azure Storage firewalls and virtual networks, please refer: [Configure Azure Storage firewalls and virtual networks](https://docs.microsoft.com/en-us/azure/storage/common/storage-network-security?tabs=azure-portal) 88 | 89 | For more information on Azure Functions networking options and VNET integration, please refer: [Azure Functions Networking Options](https://docs.microsoft.com/en-us/azure/azure-functions/functions-networking-options#restrict-your-storage-account-to-a-virtual-network) 90 | 91 | `Tags: Microsoft.Network/privateDnsZones/virtualNetworkLinks, Microsoft.Network/privateEndpoints/privateDnsZoneGroups, Microsoft.Network/virtualNetworks, Microsoft.Network/privateDnsZones, Microsoft.Network/privateEndpoints, Microsoft.Storage/storageAccounts, Microsoft.Storage/storageAccounts/fileServices/shares, Microsoft.Insights/components, Microsoft.Web/serverfarms, Microsoft.Web/sites` 92 | -------------------------------------------------------------------------------- /function-app-storage-private-endpoints/azuredeploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "metadata": { 5 | "_generator": { 6 | "name": "bicep", 7 | "version": "0.10.61.36676", 8 | "templateHash": "12847217906836811677" 9 | } 10 | }, 11 | "parameters": { 12 | "functionAppName": { 13 | "type": "string", 14 | "defaultValue": "[format('func-{0}', uniqueString(resourceGroup().id))]", 15 | "metadata": { 16 | "description": "The name of the Azure Function app." 17 | } 18 | }, 19 | "location": { 20 | "type": "string", 21 | "defaultValue": "[resourceGroup().location]", 22 | "metadata": { 23 | "description": "The location into which the resources should be deployed." 24 | } 25 | }, 26 | "functionWorkerRuntime": { 27 | "type": "string", 28 | "defaultValue": "node", 29 | "allowedValues": [ 30 | "dotnet", 31 | "node", 32 | "python", 33 | "java" 34 | ], 35 | "metadata": { 36 | "description": "The language worker runtime to load in the function app." 37 | } 38 | }, 39 | "functionPlanOS": { 40 | "type": "string", 41 | "defaultValue": "Windows", 42 | "allowedValues": [ 43 | "Windows", 44 | "Linux" 45 | ], 46 | "metadata": { 47 | "description": "Specifies the OS used for the Azure Function hosting plan." 48 | } 49 | }, 50 | "functionAppPlanSku": { 51 | "type": "string", 52 | "defaultValue": "EP1", 53 | "allowedValues": [ 54 | "EP1", 55 | "EP2", 56 | "EP3" 57 | ], 58 | "metadata": { 59 | "description": "Specifies the Azure Function hosting plan SKU." 60 | } 61 | }, 62 | "functionAppPlanName": { 63 | "type": "string", 64 | "defaultValue": "[format('plan-{0}', uniqueString(resourceGroup().id))]", 65 | "metadata": { 66 | "description": "The name of the Azure Function hosting plan." 67 | } 68 | }, 69 | "functionStorageAccountName": { 70 | "type": "string", 71 | "defaultValue": "[format('st{0}', uniqueString(resourceGroup().id))]", 72 | "metadata": { 73 | "description": "The name of the backend Azure storage account used by the Azure Function app." 74 | } 75 | }, 76 | "vnetName": { 77 | "type": "string", 78 | "defaultValue": "[format('vnet-{0}', uniqueString(resourceGroup().id))]", 79 | "metadata": { 80 | "description": "The name of the virtual network for virtual network integration." 81 | } 82 | }, 83 | "functionSubnetName": { 84 | "type": "string", 85 | "defaultValue": "snet-func", 86 | "metadata": { 87 | "description": "The name of the virtual network subnet to be associated with the Azure Function app." 88 | } 89 | }, 90 | "privateEndpointSubnetName": { 91 | "type": "string", 92 | "defaultValue": "snet-pe", 93 | "metadata": { 94 | "description": "The name of the virtual network subnet used for allocating IP addresses for private endpoints." 95 | } 96 | }, 97 | "vnetAddressPrefix": { 98 | "type": "string", 99 | "defaultValue": "10.100.0.0/16", 100 | "metadata": { 101 | "description": "The IP adddress space used for the virtual network." 102 | } 103 | }, 104 | "functionSubnetAddressPrefix": { 105 | "type": "string", 106 | "defaultValue": "10.100.0.0/24", 107 | "metadata": { 108 | "description": "The IP address space used for the Azure Function integration subnet." 109 | } 110 | }, 111 | "privateEndpointSubnetAddressPrefix": { 112 | "type": "string", 113 | "defaultValue": "10.100.1.0/24", 114 | "metadata": { 115 | "description": "The IP address space used for the private endpoints." 116 | } 117 | }, 118 | "linuxFxVersion": { 119 | "type": "string", 120 | "defaultValue": "", 121 | "metadata": { 122 | "description": "Only required for Linux app to represent runtime stack in the format of 'runtime|runtimeVersion'. For example: 'python|3.9'" 123 | } 124 | } 125 | }, 126 | "variables": { 127 | "applicationInsightsName": "[format('appi-{0}', uniqueString(resourceGroup().id))]", 128 | "privateStorageFileDnsZoneName": "[format('privatelink.file.{0}', environment().suffixes.storage)]", 129 | "privateEndpointStorageFileName": "[format('{0}-file-private-endpoint', parameters('functionStorageAccountName'))]", 130 | "privateStorageTableDnsZoneName": "[format('privatelink.table.{0}', environment().suffixes.storage)]", 131 | "privateEndpointStorageTableName": "[format('{0}-table-private-endpoint', parameters('functionStorageAccountName'))]", 132 | "privateStorageBlobDnsZoneName": "[format('privatelink.blob.{0}', environment().suffixes.storage)]", 133 | "privateEndpointStorageBlobName": "[format('{0}-blob-private-endpoint', parameters('functionStorageAccountName'))]", 134 | "privateStorageQueueDnsZoneName": "[format('privatelink.queue.{0}', environment().suffixes.storage)]", 135 | "privateEndpointStorageQueueName": "[format('{0}-queue-private-endpoint', parameters('functionStorageAccountName'))]", 136 | "functionContentShareName": "function-content-share", 137 | "isReserved": "[if(equals(parameters('functionPlanOS'), 'Linux'), true(), false())]" 138 | }, 139 | "resources": [ 140 | { 141 | "type": "Microsoft.Network/virtualNetworks", 142 | "apiVersion": "2022-05-01", 143 | "name": "[parameters('vnetName')]", 144 | "location": "[parameters('location')]", 145 | "properties": { 146 | "addressSpace": { 147 | "addressPrefixes": [ 148 | "[parameters('vnetAddressPrefix')]" 149 | ] 150 | }, 151 | "subnets": [ 152 | { 153 | "name": "[parameters('functionSubnetName')]", 154 | "properties": { 155 | "privateEndpointNetworkPolicies": "Enabled", 156 | "privateLinkServiceNetworkPolicies": "Enabled", 157 | "delegations": [ 158 | { 159 | "name": "webapp", 160 | "properties": { 161 | "serviceName": "Microsoft.Web/serverFarms" 162 | } 163 | } 164 | ], 165 | "addressPrefix": "[parameters('functionSubnetAddressPrefix')]" 166 | } 167 | }, 168 | { 169 | "name": "[parameters('privateEndpointSubnetName')]", 170 | "properties": { 171 | "privateEndpointNetworkPolicies": "Disabled", 172 | "privateLinkServiceNetworkPolicies": "Enabled", 173 | "addressPrefix": "[parameters('privateEndpointSubnetAddressPrefix')]" 174 | } 175 | } 176 | ] 177 | } 178 | }, 179 | { 180 | "type": "Microsoft.Network/privateDnsZones", 181 | "apiVersion": "2020-06-01", 182 | "name": "[variables('privateStorageFileDnsZoneName')]", 183 | "location": "global" 184 | }, 185 | { 186 | "type": "Microsoft.Network/privateDnsZones", 187 | "apiVersion": "2020-06-01", 188 | "name": "[variables('privateStorageBlobDnsZoneName')]", 189 | "location": "global" 190 | }, 191 | { 192 | "type": "Microsoft.Network/privateDnsZones", 193 | "apiVersion": "2020-06-01", 194 | "name": "[variables('privateStorageQueueDnsZoneName')]", 195 | "location": "global" 196 | }, 197 | { 198 | "type": "Microsoft.Network/privateDnsZones", 199 | "apiVersion": "2020-06-01", 200 | "name": "[variables('privateStorageTableDnsZoneName')]", 201 | "location": "global" 202 | }, 203 | { 204 | "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", 205 | "apiVersion": "2020-06-01", 206 | "name": "[format('{0}/{1}', variables('privateStorageFileDnsZoneName'), format('{0}-link', variables('privateStorageFileDnsZoneName')))]", 207 | "location": "global", 208 | "properties": { 209 | "registrationEnabled": false, 210 | "virtualNetwork": { 211 | "id": "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName'))]" 212 | } 213 | }, 214 | "dependsOn": [ 215 | "[resourceId('Microsoft.Network/privateDnsZones', variables('privateStorageFileDnsZoneName'))]", 216 | "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName'))]" 217 | ] 218 | }, 219 | { 220 | "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", 221 | "apiVersion": "2020-06-01", 222 | "name": "[format('{0}/{1}', variables('privateStorageBlobDnsZoneName'), format('{0}-link', variables('privateStorageBlobDnsZoneName')))]", 223 | "location": "global", 224 | "properties": { 225 | "registrationEnabled": false, 226 | "virtualNetwork": { 227 | "id": "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName'))]" 228 | } 229 | }, 230 | "dependsOn": [ 231 | "[resourceId('Microsoft.Network/privateDnsZones', variables('privateStorageBlobDnsZoneName'))]", 232 | "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName'))]" 233 | ] 234 | }, 235 | { 236 | "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", 237 | "apiVersion": "2020-06-01", 238 | "name": "[format('{0}/{1}', variables('privateStorageTableDnsZoneName'), format('{0}-link', variables('privateStorageTableDnsZoneName')))]", 239 | "location": "global", 240 | "properties": { 241 | "registrationEnabled": false, 242 | "virtualNetwork": { 243 | "id": "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName'))]" 244 | } 245 | }, 246 | "dependsOn": [ 247 | "[resourceId('Microsoft.Network/privateDnsZones', variables('privateStorageTableDnsZoneName'))]", 248 | "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName'))]" 249 | ] 250 | }, 251 | { 252 | "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", 253 | "apiVersion": "2020-06-01", 254 | "name": "[format('{0}/{1}', variables('privateStorageQueueDnsZoneName'), format('{0}-link', variables('privateStorageQueueDnsZoneName')))]", 255 | "location": "global", 256 | "properties": { 257 | "registrationEnabled": false, 258 | "virtualNetwork": { 259 | "id": "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName'))]" 260 | } 261 | }, 262 | "dependsOn": [ 263 | "[resourceId('Microsoft.Network/privateDnsZones', variables('privateStorageQueueDnsZoneName'))]", 264 | "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName'))]" 265 | ] 266 | }, 267 | { 268 | "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", 269 | "apiVersion": "2022-05-01", 270 | "name": "[format('{0}/{1}', variables('privateEndpointStorageFileName'), 'filePrivateDnsZoneGroup')]", 271 | "properties": { 272 | "privateDnsZoneConfigs": [ 273 | { 274 | "name": "config", 275 | "properties": { 276 | "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', variables('privateStorageFileDnsZoneName'))]" 277 | } 278 | } 279 | ] 280 | }, 281 | "dependsOn": [ 282 | "[resourceId('Microsoft.Network/privateEndpoints', variables('privateEndpointStorageFileName'))]", 283 | "[resourceId('Microsoft.Network/privateDnsZones', variables('privateStorageFileDnsZoneName'))]" 284 | ] 285 | }, 286 | { 287 | "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", 288 | "apiVersion": "2022-05-01", 289 | "name": "[format('{0}/{1}', variables('privateEndpointStorageBlobName'), 'blobPrivateDnsZoneGroup')]", 290 | "properties": { 291 | "privateDnsZoneConfigs": [ 292 | { 293 | "name": "config", 294 | "properties": { 295 | "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', variables('privateStorageBlobDnsZoneName'))]" 296 | } 297 | } 298 | ] 299 | }, 300 | "dependsOn": [ 301 | "[resourceId('Microsoft.Network/privateEndpoints', variables('privateEndpointStorageBlobName'))]", 302 | "[resourceId('Microsoft.Network/privateDnsZones', variables('privateStorageBlobDnsZoneName'))]" 303 | ] 304 | }, 305 | { 306 | "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", 307 | "apiVersion": "2022-05-01", 308 | "name": "[format('{0}/{1}', variables('privateEndpointStorageTableName'), 'tablePrivateDnsZoneGroup')]", 309 | "properties": { 310 | "privateDnsZoneConfigs": [ 311 | { 312 | "name": "config", 313 | "properties": { 314 | "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', variables('privateStorageTableDnsZoneName'))]" 315 | } 316 | } 317 | ] 318 | }, 319 | "dependsOn": [ 320 | "[resourceId('Microsoft.Network/privateEndpoints', variables('privateEndpointStorageTableName'))]", 321 | "[resourceId('Microsoft.Network/privateDnsZones', variables('privateStorageTableDnsZoneName'))]" 322 | ] 323 | }, 324 | { 325 | "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", 326 | "apiVersion": "2022-05-01", 327 | "name": "[format('{0}/{1}', variables('privateEndpointStorageQueueName'), 'queuePrivateDnsZoneGroup')]", 328 | "properties": { 329 | "privateDnsZoneConfigs": [ 330 | { 331 | "name": "config", 332 | "properties": { 333 | "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', variables('privateStorageQueueDnsZoneName'))]" 334 | } 335 | } 336 | ] 337 | }, 338 | "dependsOn": [ 339 | "[resourceId('Microsoft.Network/privateEndpoints', variables('privateEndpointStorageQueueName'))]", 340 | "[resourceId('Microsoft.Network/privateDnsZones', variables('privateStorageQueueDnsZoneName'))]" 341 | ] 342 | }, 343 | { 344 | "type": "Microsoft.Network/privateEndpoints", 345 | "apiVersion": "2022-05-01", 346 | "name": "[variables('privateEndpointStorageFileName')]", 347 | "location": "[parameters('location')]", 348 | "properties": { 349 | "subnet": { 350 | "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('privateEndpointSubnetName'))]" 351 | }, 352 | "privateLinkServiceConnections": [ 353 | { 354 | "name": "MyStorageFilePrivateLinkConnection", 355 | "properties": { 356 | "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('functionStorageAccountName'))]", 357 | "groupIds": [ 358 | "file" 359 | ] 360 | } 361 | } 362 | ] 363 | }, 364 | "dependsOn": [ 365 | "[resourceId('Microsoft.Storage/storageAccounts', parameters('functionStorageAccountName'))]", 366 | "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName'))]" 367 | ] 368 | }, 369 | { 370 | "type": "Microsoft.Network/privateEndpoints", 371 | "apiVersion": "2022-05-01", 372 | "name": "[variables('privateEndpointStorageBlobName')]", 373 | "location": "[parameters('location')]", 374 | "properties": { 375 | "subnet": { 376 | "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('privateEndpointSubnetName'))]" 377 | }, 378 | "privateLinkServiceConnections": [ 379 | { 380 | "name": "MyStorageBlobPrivateLinkConnection", 381 | "properties": { 382 | "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('functionStorageAccountName'))]", 383 | "groupIds": [ 384 | "blob" 385 | ] 386 | } 387 | } 388 | ] 389 | }, 390 | "dependsOn": [ 391 | "[resourceId('Microsoft.Storage/storageAccounts', parameters('functionStorageAccountName'))]", 392 | "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName'))]" 393 | ] 394 | }, 395 | { 396 | "type": "Microsoft.Network/privateEndpoints", 397 | "apiVersion": "2022-05-01", 398 | "name": "[variables('privateEndpointStorageTableName')]", 399 | "location": "[parameters('location')]", 400 | "properties": { 401 | "subnet": { 402 | "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('privateEndpointSubnetName'))]" 403 | }, 404 | "privateLinkServiceConnections": [ 405 | { 406 | "name": "MyStorageTablePrivateLinkConnection", 407 | "properties": { 408 | "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('functionStorageAccountName'))]", 409 | "groupIds": [ 410 | "table" 411 | ] 412 | } 413 | } 414 | ] 415 | }, 416 | "dependsOn": [ 417 | "[resourceId('Microsoft.Storage/storageAccounts', parameters('functionStorageAccountName'))]", 418 | "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName'))]" 419 | ] 420 | }, 421 | { 422 | "type": "Microsoft.Network/privateEndpoints", 423 | "apiVersion": "2022-05-01", 424 | "name": "[variables('privateEndpointStorageQueueName')]", 425 | "location": "[parameters('location')]", 426 | "properties": { 427 | "subnet": { 428 | "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('privateEndpointSubnetName'))]" 429 | }, 430 | "privateLinkServiceConnections": [ 431 | { 432 | "name": "MyStorageQueuePrivateLinkConnection", 433 | "properties": { 434 | "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('functionStorageAccountName'))]", 435 | "groupIds": [ 436 | "queue" 437 | ] 438 | } 439 | } 440 | ] 441 | }, 442 | "dependsOn": [ 443 | "[resourceId('Microsoft.Storage/storageAccounts', parameters('functionStorageAccountName'))]", 444 | "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName'))]" 445 | ] 446 | }, 447 | { 448 | "type": "Microsoft.Storage/storageAccounts", 449 | "apiVersion": "2022-05-01", 450 | "name": "[parameters('functionStorageAccountName')]", 451 | "location": "[parameters('location')]", 452 | "kind": "StorageV2", 453 | "sku": { 454 | "name": "Standard_LRS" 455 | }, 456 | "properties": { 457 | "publicNetworkAccess": "Disabled", 458 | "allowBlobPublicAccess": false, 459 | "networkAcls": { 460 | "bypass": "None", 461 | "defaultAction": "Deny" 462 | } 463 | } 464 | }, 465 | { 466 | "type": "Microsoft.Storage/storageAccounts/fileServices/shares", 467 | "apiVersion": "2022-05-01", 468 | "name": "[format('{0}/default/{1}', parameters('functionStorageAccountName'), variables('functionContentShareName'))]", 469 | "dependsOn": [ 470 | "[resourceId('Microsoft.Storage/storageAccounts', parameters('functionStorageAccountName'))]" 471 | ] 472 | }, 473 | { 474 | "type": "Microsoft.Insights/components", 475 | "apiVersion": "2020-02-02", 476 | "name": "[variables('applicationInsightsName')]", 477 | "location": "[parameters('location')]", 478 | "kind": "web", 479 | "properties": { 480 | "Application_Type": "web" 481 | } 482 | }, 483 | { 484 | "type": "Microsoft.Web/serverfarms", 485 | "apiVersion": "2022-03-01", 486 | "name": "[parameters('functionAppPlanName')]", 487 | "location": "[parameters('location')]", 488 | "sku": { 489 | "tier": "ElasticPremium", 490 | "name": "[parameters('functionAppPlanSku')]", 491 | "size": "[parameters('functionAppPlanSku')]", 492 | "family": "EP" 493 | }, 494 | "kind": "elastic", 495 | "properties": { 496 | "maximumElasticWorkerCount": 20, 497 | "reserved": "[variables('isReserved')]" 498 | } 499 | }, 500 | { 501 | "type": "Microsoft.Web/sites", 502 | "apiVersion": "2022-03-01", 503 | "name": "[parameters('functionAppName')]", 504 | "location": "[parameters('location')]", 505 | "kind": "[if(variables('isReserved'), 'functionapp,linux', 'functionapp')]", 506 | "properties": { 507 | "reserved": "[variables('isReserved')]", 508 | "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('functionAppPlanName'))]", 509 | "siteConfig": { 510 | "functionsRuntimeScaleMonitoringEnabled": true, 511 | "linuxFxVersion": "[if(variables('isReserved'), parameters('linuxFxVersion'), json('null'))]", 512 | "appSettings": [ 513 | { 514 | "name": "APPINSIGHTS_INSTRUMENTATIONKEY", 515 | "value": "[reference(resourceId('Microsoft.Insights/components', variables('applicationInsightsName'))).InstrumentationKey]" 516 | }, 517 | { 518 | "name": "AzureWebJobsStorage", 519 | "value": "[format('DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1}', parameters('functionStorageAccountName'), listKeys(resourceId('Microsoft.Storage/storageAccounts', parameters('functionStorageAccountName')), '2022-05-01').keys[0].value)]" 520 | }, 521 | { 522 | "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING", 523 | "value": "[format('DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1}', parameters('functionStorageAccountName'), listKeys(resourceId('Microsoft.Storage/storageAccounts', parameters('functionStorageAccountName')), '2022-05-01').keys[0].value)]" 524 | }, 525 | { 526 | "name": "WEBSITE_CONTENTSHARE", 527 | "value": "[variables('functionContentShareName')]" 528 | }, 529 | { 530 | "name": "FUNCTIONS_EXTENSION_VERSION", 531 | "value": "~4" 532 | }, 533 | { 534 | "name": "FUNCTIONS_WORKER_RUNTIME", 535 | "value": "[parameters('functionWorkerRuntime')]" 536 | }, 537 | { 538 | "name": "WEBSITE_NODE_DEFAULT_VERSION", 539 | "value": "~14" 540 | }, 541 | { 542 | "name": "WEBSITE_VNET_ROUTE_ALL", 543 | "value": "1" 544 | }, 545 | { 546 | "name": "WEBSITE_CONTENTOVERVNET", 547 | "value": "1" 548 | } 549 | ] 550 | } 551 | }, 552 | "dependsOn": [ 553 | "[resourceId('Microsoft.Insights/components', variables('applicationInsightsName'))]", 554 | "[resourceId('Microsoft.Web/serverfarms', parameters('functionAppPlanName'))]", 555 | "[resourceId('Microsoft.Storage/storageAccounts', parameters('functionStorageAccountName'))]", 556 | "[resourceId('Microsoft.Network/privateEndpoints/privateDnsZoneGroups', variables('privateEndpointStorageBlobName'), 'blobPrivateDnsZoneGroup')]", 557 | "[resourceId('Microsoft.Network/privateEndpoints/privateDnsZoneGroups', variables('privateEndpointStorageFileName'), 'filePrivateDnsZoneGroup')]", 558 | "[resourceId('Microsoft.Network/privateEndpoints/privateDnsZoneGroups', variables('privateEndpointStorageQueueName'), 'queuePrivateDnsZoneGroup')]", 559 | "[resourceId('Microsoft.Network/privateEndpoints/privateDnsZoneGroups', variables('privateEndpointStorageTableName'), 'tablePrivateDnsZoneGroup')]", 560 | "[resourceId('Microsoft.Network/privateDnsZones/virtualNetworkLinks', variables('privateStorageBlobDnsZoneName'), format('{0}-link', variables('privateStorageBlobDnsZoneName')))]", 561 | "[resourceId('Microsoft.Network/privateDnsZones/virtualNetworkLinks', variables('privateStorageFileDnsZoneName'), format('{0}-link', variables('privateStorageFileDnsZoneName')))]", 562 | "[resourceId('Microsoft.Network/privateDnsZones/virtualNetworkLinks', variables('privateStorageQueueDnsZoneName'), format('{0}-link', variables('privateStorageQueueDnsZoneName')))]", 563 | "[resourceId('Microsoft.Network/privateDnsZones/virtualNetworkLinks', variables('privateStorageTableDnsZoneName'), format('{0}-link', variables('privateStorageTableDnsZoneName')))]", 564 | "[resourceId('Microsoft.Storage/storageAccounts/fileServices/shares', split(format('{0}/default/{1}', parameters('functionStorageAccountName'), variables('functionContentShareName')), '/')[0], split(format('{0}/default/{1}', parameters('functionStorageAccountName'), variables('functionContentShareName')), '/')[1], split(format('{0}/default/{1}', parameters('functionStorageAccountName'), variables('functionContentShareName')), '/')[2])]" 565 | ] 566 | }, 567 | { 568 | "type": "Microsoft.Web/sites/networkConfig", 569 | "apiVersion": "2022-03-01", 570 | "name": "[format('{0}/{1}', parameters('functionAppName'), 'virtualNetwork')]", 571 | "properties": { 572 | "subnetResourceId": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('functionSubnetName'))]", 573 | "swiftSupported": true 574 | }, 575 | "dependsOn": [ 576 | "[resourceId('Microsoft.Web/sites', parameters('functionAppName'))]", 577 | "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName'))]" 578 | ] 579 | } 580 | ] 581 | } -------------------------------------------------------------------------------- /function-app-storage-private-endpoints/azuredeploy.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /function-app-storage-private-endpoints/images/function-app-storage-private-endpoints.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/function-app-arm-templates/fc7311939d3bac5706778af24c8be82a7889a230/function-app-storage-private-endpoints/images/function-app-storage-private-endpoints.jpg -------------------------------------------------------------------------------- /function-app-storage-private-endpoints/main.bicep: -------------------------------------------------------------------------------- 1 | @description('The name of the Azure Function app.') 2 | param functionAppName string = 'func-${uniqueString(resourceGroup().id)}' 3 | 4 | @description('The location into which the resources should be deployed.') 5 | param location string = resourceGroup().location 6 | 7 | @description('The language worker runtime to load in the function app.') 8 | @allowed([ 9 | 'dotnet' 10 | 'node' 11 | 'python' 12 | 'java' 13 | ]) 14 | param functionWorkerRuntime string = 'node' 15 | 16 | @description('Specifies the OS used for the Azure Function hosting plan.') 17 | @allowed([ 18 | 'Windows' 19 | 'Linux' 20 | ]) 21 | param functionPlanOS string = 'Windows' 22 | 23 | @description('Specifies the Azure Function hosting plan SKU.') 24 | @allowed([ 25 | 'EP1' 26 | 'EP2' 27 | 'EP3' 28 | ]) 29 | param functionAppPlanSku string = 'EP1' 30 | 31 | @description('The name of the Azure Function hosting plan.') 32 | param functionAppPlanName string = 'plan-${uniqueString(resourceGroup().id)}' 33 | 34 | @description('The name of the backend Azure storage account used by the Azure Function app.') 35 | param functionStorageAccountName string = 'st${uniqueString(resourceGroup().id)}' 36 | 37 | @description('The name of the virtual network for virtual network integration.') 38 | param vnetName string = 'vnet-${uniqueString(resourceGroup().id)}' 39 | 40 | @description('The name of the virtual network subnet to be associated with the Azure Function app.') 41 | param functionSubnetName string = 'snet-func' 42 | 43 | @description('The name of the virtual network subnet used for allocating IP addresses for private endpoints.') 44 | param privateEndpointSubnetName string = 'snet-pe' 45 | 46 | @description('The IP adddress space used for the virtual network.') 47 | param vnetAddressPrefix string = '10.100.0.0/16' 48 | 49 | @description('The IP address space used for the Azure Function integration subnet.') 50 | param functionSubnetAddressPrefix string = '10.100.0.0/24' 51 | 52 | @description('The IP address space used for the private endpoints.') 53 | param privateEndpointSubnetAddressPrefix string = '10.100.1.0/24' 54 | 55 | @description('Only required for Linux app to represent runtime stack in the format of \'runtime|runtimeVersion\'. For example: \'python|3.9\'') 56 | param linuxFxVersion string = '' 57 | 58 | var applicationInsightsName = 'appi-${uniqueString(resourceGroup().id)}' 59 | var privateStorageFileDnsZoneName = 'privatelink.file.${environment().suffixes.storage}' 60 | var privateEndpointStorageFileName = '${functionStorageAccountName}-file-private-endpoint' 61 | var privateStorageTableDnsZoneName = 'privatelink.table.${environment().suffixes.storage}' 62 | var privateEndpointStorageTableName = '${functionStorageAccountName}-table-private-endpoint' 63 | var privateStorageBlobDnsZoneName = 'privatelink.blob.${environment().suffixes.storage}' 64 | var privateEndpointStorageBlobName = '${functionStorageAccountName}-blob-private-endpoint' 65 | var privateStorageQueueDnsZoneName = 'privatelink.queue.${environment().suffixes.storage}' 66 | var privateEndpointStorageQueueName = '${functionStorageAccountName}-queue-private-endpoint' 67 | var functionContentShareName = 'function-content-share' 68 | var isReserved = ((functionPlanOS == 'Linux') ? true : false) 69 | 70 | resource vnet 'Microsoft.Network/virtualNetworks@2022-05-01' = { 71 | name: vnetName 72 | location: location 73 | properties: { 74 | addressSpace: { 75 | addressPrefixes: [ 76 | vnetAddressPrefix 77 | ] 78 | } 79 | subnets: [ 80 | { 81 | name: functionSubnetName 82 | properties: { 83 | privateEndpointNetworkPolicies: 'Enabled' 84 | privateLinkServiceNetworkPolicies: 'Enabled' 85 | delegations: [ 86 | { 87 | name: 'webapp' 88 | properties: { 89 | serviceName: 'Microsoft.Web/serverFarms' 90 | } 91 | } 92 | ] 93 | addressPrefix: functionSubnetAddressPrefix 94 | } 95 | } 96 | { 97 | name: privateEndpointSubnetName 98 | properties: { 99 | privateEndpointNetworkPolicies: 'Disabled' 100 | privateLinkServiceNetworkPolicies: 'Enabled' 101 | addressPrefix: privateEndpointSubnetAddressPrefix 102 | } 103 | } 104 | ] 105 | } 106 | } 107 | 108 | resource privateStorageFileDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = { 109 | name: privateStorageFileDnsZoneName 110 | location: 'global' 111 | } 112 | 113 | resource privateStorageBlobDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = { 114 | name: privateStorageBlobDnsZoneName 115 | location: 'global' 116 | } 117 | 118 | resource privateStorageQueueDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = { 119 | name: privateStorageQueueDnsZoneName 120 | location: 'global' 121 | } 122 | 123 | resource privateStorageTableDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = { 124 | name: privateStorageTableDnsZoneName 125 | location: 'global' 126 | } 127 | 128 | resource privateStorageFileDnsZoneLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = { 129 | parent: privateStorageFileDnsZone 130 | name: '${privateStorageFileDnsZoneName}-link' 131 | location: 'global' 132 | properties: { 133 | registrationEnabled: false 134 | virtualNetwork: { 135 | id: vnet.id 136 | } 137 | } 138 | } 139 | 140 | resource privateStorageBlobDnsZoneLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = { 141 | parent: privateStorageBlobDnsZone 142 | name: '${privateStorageBlobDnsZoneName}-link' 143 | location: 'global' 144 | properties: { 145 | registrationEnabled: false 146 | virtualNetwork: { 147 | id: vnet.id 148 | } 149 | } 150 | } 151 | 152 | resource privateStorageTableDnsZoneLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = { 153 | parent: privateStorageTableDnsZone 154 | name: '${privateStorageTableDnsZoneName}-link' 155 | location: 'global' 156 | properties: { 157 | registrationEnabled: false 158 | virtualNetwork: { 159 | id: vnet.id 160 | } 161 | } 162 | } 163 | 164 | resource privateStorageQueueDnsZoneLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = { 165 | parent: privateStorageQueueDnsZone 166 | name: '${privateStorageQueueDnsZoneName}-link' 167 | location: 'global' 168 | properties: { 169 | registrationEnabled: false 170 | virtualNetwork: { 171 | id: vnet.id 172 | } 173 | } 174 | } 175 | 176 | resource privateEndpointStorageFilePrivateDnsZoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2022-05-01' = { 177 | parent: privateEndpointStorageFile 178 | name: 'filePrivateDnsZoneGroup' 179 | properties: { 180 | privateDnsZoneConfigs: [ 181 | { 182 | name: 'config' 183 | properties: { 184 | privateDnsZoneId: privateStorageFileDnsZone.id 185 | } 186 | } 187 | ] 188 | } 189 | } 190 | 191 | resource privateEndpointStorageBlobPrivateDnsZoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2022-05-01' = { 192 | parent: privateEndpointStorageBlob 193 | name: 'blobPrivateDnsZoneGroup' 194 | properties: { 195 | privateDnsZoneConfigs: [ 196 | { 197 | name: 'config' 198 | properties: { 199 | privateDnsZoneId: privateStorageBlobDnsZone.id 200 | } 201 | } 202 | ] 203 | } 204 | } 205 | 206 | resource privateEndpointStorageTablePrivateDnsZoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2022-05-01' = { 207 | parent: privateEndpointStorageTable 208 | name: 'tablePrivateDnsZoneGroup' 209 | properties: { 210 | privateDnsZoneConfigs: [ 211 | { 212 | name: 'config' 213 | properties: { 214 | privateDnsZoneId: privateStorageTableDnsZone.id 215 | } 216 | } 217 | ] 218 | } 219 | } 220 | 221 | resource privateEndpointStorageQueuePrivateDnsZoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2022-05-01' = { 222 | parent: privateEndpointStorageQueue 223 | name: 'queuePrivateDnsZoneGroup' 224 | properties: { 225 | privateDnsZoneConfigs: [ 226 | { 227 | name: 'config' 228 | properties: { 229 | privateDnsZoneId: privateStorageQueueDnsZone.id 230 | } 231 | } 232 | ] 233 | } 234 | } 235 | 236 | resource privateEndpointStorageFile 'Microsoft.Network/privateEndpoints@2022-05-01' = { 237 | name: privateEndpointStorageFileName 238 | location: location 239 | properties: { 240 | subnet: { 241 | id: resourceId('Microsoft.Network/virtualNetworks/subnets', vnetName, privateEndpointSubnetName) 242 | } 243 | privateLinkServiceConnections: [ 244 | { 245 | name: 'MyStorageFilePrivateLinkConnection' 246 | properties: { 247 | privateLinkServiceId: functionStorageAccount.id 248 | groupIds: [ 249 | 'file' 250 | ] 251 | } 252 | } 253 | ] 254 | } 255 | dependsOn: [ 256 | vnet 257 | ] 258 | } 259 | 260 | resource privateEndpointStorageBlob 'Microsoft.Network/privateEndpoints@2022-05-01' = { 261 | name: privateEndpointStorageBlobName 262 | location: location 263 | properties: { 264 | subnet: { 265 | id: resourceId('Microsoft.Network/virtualNetworks/subnets', vnetName, privateEndpointSubnetName) 266 | } 267 | privateLinkServiceConnections: [ 268 | { 269 | name: 'MyStorageBlobPrivateLinkConnection' 270 | properties: { 271 | privateLinkServiceId: functionStorageAccount.id 272 | groupIds: [ 273 | 'blob' 274 | ] 275 | } 276 | } 277 | ] 278 | } 279 | dependsOn: [ 280 | vnet 281 | ] 282 | } 283 | 284 | resource privateEndpointStorageTable 'Microsoft.Network/privateEndpoints@2022-05-01' = { 285 | name: privateEndpointStorageTableName 286 | location: location 287 | properties: { 288 | subnet: { 289 | id: resourceId('Microsoft.Network/virtualNetworks/subnets', vnetName, privateEndpointSubnetName) 290 | } 291 | privateLinkServiceConnections: [ 292 | { 293 | name: 'MyStorageTablePrivateLinkConnection' 294 | properties: { 295 | privateLinkServiceId: functionStorageAccount.id 296 | groupIds: [ 297 | 'table' 298 | ] 299 | } 300 | } 301 | ] 302 | } 303 | dependsOn: [ 304 | vnet 305 | ] 306 | } 307 | 308 | resource privateEndpointStorageQueue 'Microsoft.Network/privateEndpoints@2022-05-01' = { 309 | name: privateEndpointStorageQueueName 310 | location: location 311 | properties: { 312 | subnet: { 313 | id: resourceId('Microsoft.Network/virtualNetworks/subnets', vnetName, privateEndpointSubnetName) 314 | } 315 | privateLinkServiceConnections: [ 316 | { 317 | name: 'MyStorageQueuePrivateLinkConnection' 318 | properties: { 319 | privateLinkServiceId: functionStorageAccount.id 320 | groupIds: [ 321 | 'queue' 322 | ] 323 | } 324 | } 325 | ] 326 | } 327 | dependsOn: [ 328 | vnet 329 | ] 330 | } 331 | 332 | resource functionStorageAccount 'Microsoft.Storage/storageAccounts@2022-05-01' = { 333 | name: functionStorageAccountName 334 | location: location 335 | kind: 'StorageV2' 336 | sku: { 337 | name: 'Standard_LRS' 338 | } 339 | properties: { 340 | publicNetworkAccess: 'Disabled' 341 | allowBlobPublicAccess: false 342 | networkAcls: { 343 | bypass: 'None' 344 | defaultAction: 'Deny' 345 | } 346 | } 347 | } 348 | 349 | resource share 'Microsoft.Storage/storageAccounts/fileServices/shares@2022-05-01' = { 350 | name: '${functionStorageAccountName}/default/${functionContentShareName}' 351 | dependsOn: [ 352 | functionStorageAccount 353 | ] 354 | } 355 | 356 | resource applicationInsight 'Microsoft.Insights/components@2020-02-02' = { 357 | name: applicationInsightsName 358 | location: location 359 | kind: 'web' 360 | properties: { 361 | Application_Type: 'web' 362 | } 363 | } 364 | 365 | resource functionAppPlan 'Microsoft.Web/serverfarms@2022-03-01' = { 366 | name: functionAppPlanName 367 | location: location 368 | sku: { 369 | tier: 'ElasticPremium' 370 | name: functionAppPlanSku 371 | size: functionAppPlanSku 372 | family: 'EP' 373 | } 374 | kind: 'elastic' 375 | properties: { 376 | maximumElasticWorkerCount: 20 377 | reserved: isReserved 378 | } 379 | } 380 | 381 | resource functionApp 'Microsoft.Web/sites@2022-03-01' = { 382 | name: functionAppName 383 | location: location 384 | kind: (isReserved ? 'functionapp,linux' : 'functionapp') 385 | properties: { 386 | reserved: isReserved 387 | serverFarmId: functionAppPlan.id 388 | siteConfig: { 389 | functionsRuntimeScaleMonitoringEnabled: true 390 | linuxFxVersion: (isReserved ? linuxFxVersion : json('null')) 391 | appSettings: [ 392 | { 393 | name: 'APPINSIGHTS_INSTRUMENTATIONKEY' 394 | value: applicationInsight.properties.InstrumentationKey 395 | } 396 | { 397 | name: 'AzureWebJobsStorage' 398 | value: 'DefaultEndpointsProtocol=https;AccountName=${functionStorageAccountName};AccountKey=${functionStorageAccount.listKeys().keys[0].value}' 399 | } 400 | { 401 | name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING' 402 | value: 'DefaultEndpointsProtocol=https;AccountName=${functionStorageAccountName};AccountKey=${functionStorageAccount.listKeys().keys[0].value}' 403 | } 404 | { 405 | name: 'WEBSITE_CONTENTSHARE' 406 | value: functionContentShareName 407 | } 408 | { 409 | name: 'FUNCTIONS_EXTENSION_VERSION' 410 | value: '~4' 411 | } 412 | { 413 | name: 'FUNCTIONS_WORKER_RUNTIME' 414 | value: functionWorkerRuntime 415 | } 416 | { 417 | name: 'WEBSITE_NODE_DEFAULT_VERSION' 418 | value: '~14' 419 | } 420 | { 421 | name: 'WEBSITE_VNET_ROUTE_ALL' 422 | value: '1' 423 | } 424 | { 425 | name: 'WEBSITE_CONTENTOVERVNET' 426 | value: '1' 427 | } 428 | ] 429 | } 430 | } 431 | dependsOn: [ 432 | share 433 | privateStorageFileDnsZoneLink 434 | privateEndpointStorageFilePrivateDnsZoneGroup 435 | privateStorageBlobDnsZoneLink 436 | privateEndpointStorageBlobPrivateDnsZoneGroup 437 | privateStorageTableDnsZoneLink 438 | privateEndpointStorageTablePrivateDnsZoneGroup 439 | privateStorageQueueDnsZoneLink 440 | privateEndpointStorageQueuePrivateDnsZoneGroup 441 | ] 442 | } 443 | 444 | resource networkConfig 'Microsoft.Web/sites/networkConfig@2022-03-01' = { 445 | parent: functionApp 446 | name: 'virtualNetwork' 447 | properties: { 448 | subnetResourceId: resourceId('Microsoft.Network/virtualNetworks/subnets', vnetName, functionSubnetName) 449 | swiftSupported: true 450 | } 451 | dependsOn: [ 452 | vnet 453 | ] 454 | } 455 | -------------------------------------------------------------------------------- /function-app-vnet-integration/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: This template provisions a function app on a Premium plan with regional virtual network integration enabled to a newly created virtual network. 3 | page_type: sample 4 | products: 5 | - azure 6 | - azure-resource-manager 7 | - bicep 8 | urlFragment: function-app-vnet-integration 9 | languages: 10 | - bicep 11 | - json 12 | --- 13 | # Azure Function App with Virtual Network Integration 14 | 15 | This sample Azure Resource Manager template deploys an Azure Function Premium plan with [virtual network integration](https://docs.microsoft.com/en-us/azure/azure-functions/functions-networking-options#virtual-network-integration) enabled and allows the Azure Function to utilizes resources within the virtual network. 16 | 17 | [![Deploy to Azure](/images/deploytoazure.png)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure-Samples%2Ffunction-app-arm-templates%2Fmain%2Ffunction-app-vnet-integration%2Fazuredeploy.json) 18 | 19 | ### Virtual Network 20 | 21 | The virtual network into which the Azure Function Premium plan shall be integrated. 22 | 23 | + **Microsoft.Network/virtualNetworks**: The virtual network for which to integrate, and one subnet to which the function app plan is delegated. 24 | 25 | ### OS 26 | 27 | This template has a parameter `functionPlanOS` to choose Windows or Linux OS. Windows is selected by default. If you choose Linux, then parameter `linuxFxVersion` will be parameter, so you can skip it for Windows. 28 | 29 | ### Elastic Premium Plan 30 | 31 | The Azure Function app provisioned in this sample uses an [Azure Functions Elastic Premium plan](https://docs.microsoft.com/azure/azure-functions/functions-premium-plan#features). 32 | 33 | + **Microsoft.Web/serverfarms**: The Azure Functions Premium plan (a.k.a. Elastic Premium plan) 34 | 35 | ### Azure Function App 36 | 37 | The Function App uses the [AzureWebJobsStorage](https://docs.microsoft.com/azure/azure-functions/functions-app-settings#azurewebjobsstorage) and [WEBSITE_CONTENTAZUREFILECONNECTIONSTRING](https://docs.microsoft.com/azure/azure-functions/functions-app-settings#website_contentazurefileconnectionstring) app settings to connect to a private endpoint-secured Storage Account. 38 | 39 | + **Microsoft.Web/sites**: The function app instance. 40 | 41 | ### Azure Storage account 42 | 43 | The Storage account that the Function uses for operation and for file contents. 44 | 45 | + **Microsoft.Storage/storageAccounts**: [Azure Functions requires a storage account](https://docs.microsoft.com/azure/azure-functions/storage-considerations) for the function app instance. 46 | 47 | ### Application Insights 48 | 49 | [Application Insights](https://docs.microsoft.com/azure/azure-monitor/app/app-insights-overview) is used to provide [monitor the Azure Function](https://docs.microsoft.com/azure/azure-functions/functions-monitoring). 50 | 51 | + **Microsoft.Insights/components**: The Application Insights instance used by the Azure Function for monitoring. 52 | 53 | ### NOTE 54 | 55 | + For more information on configuring Azure Storage firewalls and virtual networks, please refer: [Configure Azure Storage firewalls and virtual networks](https://docs.microsoft.com/en-us/azure/storage/common/storage-network-security?tabs=azure-portal) 56 | 57 | + For more information on Azure Functions networking options and VNET integration, please refer: [Azure Functions Networking Options](https://docs.microsoft.com/en-us/azure/azure-functions/functions-networking-options#restrict-your-storage-account-to-a-virtual-network) 58 | 59 | `Tags: Microsoft.Network/virtualNetworks, Microsoft.Storage/storageAccounts, Microsoft.Insights/components, Microsoft.Web/serverfarms, Microsoft.Web/sites` 60 | -------------------------------------------------------------------------------- /function-app-vnet-integration/azuredeploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "metadata": { 5 | "_generator": { 6 | "name": "bicep", 7 | "version": "0.10.61.36676", 8 | "templateHash": "14925466958906821754" 9 | } 10 | }, 11 | "parameters": { 12 | "functionAppName": { 13 | "type": "string", 14 | "defaultValue": "[format('func-{0}', uniqueString(resourceGroup().id))]", 15 | "metadata": { 16 | "description": "The name of the Azure Function app." 17 | } 18 | }, 19 | "storageAccountType": { 20 | "type": "string", 21 | "defaultValue": "Standard_LRS", 22 | "allowedValues": [ 23 | "Standard_LRS", 24 | "Standard_GRS", 25 | "Standard_RAGRS" 26 | ], 27 | "metadata": { 28 | "description": "Storage Account type" 29 | } 30 | }, 31 | "location": { 32 | "type": "string", 33 | "defaultValue": "[resourceGroup().location]", 34 | "metadata": { 35 | "description": "Location for all resources." 36 | } 37 | }, 38 | "appInsightsLocation": { 39 | "type": "string", 40 | "defaultValue": "[resourceGroup().location]", 41 | "metadata": { 42 | "description": "Location for Application Insights" 43 | } 44 | }, 45 | "functionWorkerRuntime": { 46 | "type": "string", 47 | "defaultValue": "node", 48 | "allowedValues": [ 49 | "dotnet", 50 | "node", 51 | "python", 52 | "java" 53 | ], 54 | "metadata": { 55 | "description": "The language worker runtime to load in the function app." 56 | } 57 | }, 58 | "functionPlanOS": { 59 | "type": "string", 60 | "defaultValue": "Windows", 61 | "allowedValues": [ 62 | "Windows", 63 | "Linux" 64 | ], 65 | "metadata": { 66 | "description": "Specifies the OS used for the Azure Function hosting plan." 67 | } 68 | }, 69 | "functionAppPlanSku": { 70 | "type": "string", 71 | "defaultValue": "EP1", 72 | "allowedValues": [ 73 | "EP1", 74 | "EP2", 75 | "EP3" 76 | ], 77 | "metadata": { 78 | "description": "Specifies the Azure Function hosting plan SKU." 79 | } 80 | }, 81 | "vnetName": { 82 | "type": "string", 83 | "defaultValue": "[format('vnet-{0}', uniqueString(resourceGroup().id))]", 84 | "metadata": { 85 | "description": "The name of the virtual network to be created." 86 | } 87 | }, 88 | "subnetName": { 89 | "type": "string", 90 | "defaultValue": "[format('subnet-{0}', uniqueString(resourceGroup().id))]", 91 | "metadata": { 92 | "description": "The name of the subnet to be created within the virtual network." 93 | } 94 | }, 95 | "linuxFxVersion": { 96 | "type": "string", 97 | "defaultValue": "", 98 | "metadata": { 99 | "description": "Only required for Linux app to represent runtime stack in the format of 'runtime|runtimeVersion'. For example: 'python|3.9'" 100 | } 101 | } 102 | }, 103 | "variables": { 104 | "vnetAddressPrefix": "10.0.0.0/16", 105 | "subnetAddressPrefix": "10.0.0.0/24", 106 | "hostingPlanName": "[parameters('functionAppName')]", 107 | "applicationInsightsName": "[parameters('functionAppName')]", 108 | "storageAccountName": "[format('{0}azfunctions', uniqueString(resourceGroup().id))]", 109 | "isReserved": "[if(equals(parameters('functionPlanOS'), 'Linux'), true(), false())]" 110 | }, 111 | "resources": [ 112 | { 113 | "type": "Microsoft.Network/virtualNetworks", 114 | "apiVersion": "2022-05-01", 115 | "name": "[parameters('vnetName')]", 116 | "location": "[parameters('location')]", 117 | "properties": { 118 | "addressSpace": { 119 | "addressPrefixes": [ 120 | "[variables('vnetAddressPrefix')]" 121 | ] 122 | }, 123 | "subnets": [ 124 | { 125 | "name": "[parameters('subnetName')]", 126 | "properties": { 127 | "addressPrefix": "[variables('subnetAddressPrefix')]", 128 | "delegations": [ 129 | { 130 | "name": "delegation", 131 | "properties": { 132 | "serviceName": "Microsoft.Web/serverFarms" 133 | } 134 | } 135 | ] 136 | } 137 | } 138 | ] 139 | } 140 | }, 141 | { 142 | "type": "Microsoft.Storage/storageAccounts", 143 | "apiVersion": "2022-05-01", 144 | "name": "[variables('storageAccountName')]", 145 | "location": "[parameters('location')]", 146 | "sku": { 147 | "name": "[parameters('storageAccountType')]" 148 | }, 149 | "kind": "Storage" 150 | }, 151 | { 152 | "type": "Microsoft.Web/serverfarms", 153 | "apiVersion": "2022-03-01", 154 | "name": "[variables('hostingPlanName')]", 155 | "location": "[parameters('location')]", 156 | "sku": { 157 | "tier": "ElasticPremium", 158 | "name": "[parameters('functionAppPlanSku')]", 159 | "family": "EP" 160 | }, 161 | "properties": { 162 | "maximumElasticWorkerCount": 20, 163 | "reserved": "[variables('isReserved')]" 164 | }, 165 | "kind": "elastic" 166 | }, 167 | { 168 | "type": "Microsoft.Insights/components", 169 | "apiVersion": "2020-02-02", 170 | "name": "[variables('applicationInsightsName')]", 171 | "location": "[parameters('appInsightsLocation')]", 172 | "tags": { 173 | "[format('hidden-link:{0}', resourceId('Microsoft.Web/sites', variables('applicationInsightsName')))]": "Resource" 174 | }, 175 | "properties": { 176 | "Application_Type": "web" 177 | }, 178 | "kind": "web" 179 | }, 180 | { 181 | "type": "Microsoft.Web/sites", 182 | "apiVersion": "2022-03-01", 183 | "name": "[parameters('functionAppName')]", 184 | "location": "[parameters('location')]", 185 | "kind": "[if(variables('isReserved'), 'functionapp,linux', 'functionapp')]", 186 | "properties": { 187 | "reserved": "[variables('isReserved')]", 188 | "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]", 189 | "siteConfig": { 190 | "linuxFxVersion": "[if(variables('isReserved'), parameters('linuxFxVersion'), json('null'))]", 191 | "appSettings": [ 192 | { 193 | "name": "APPINSIGHTS_INSTRUMENTATIONKEY", 194 | "value": "[reference(resourceId('Microsoft.Insights/components', variables('applicationInsightsName'))).InstrumentationKey]" 195 | }, 196 | { 197 | "name": "AzureWebJobsStorage", 198 | "value": "[format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix= {1};AccountKey={2}', variables('storageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2022-05-01').keys[0].value)]" 199 | }, 200 | { 201 | "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING", 202 | "value": "[format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2};', variables('storageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2022-05-01').keys[0].value)]" 203 | }, 204 | { 205 | "name": "WEBSITE_CONTENTSHARE", 206 | "value": "[toLower(parameters('functionAppName'))]" 207 | }, 208 | { 209 | "name": "FUNCTIONS_EXTENSION_VERSION", 210 | "value": "~4" 211 | }, 212 | { 213 | "name": "FUNCTIONS_WORKER_RUNTIME", 214 | "value": "[parameters('functionWorkerRuntime')]" 215 | }, 216 | { 217 | "name": "WEBSITE_NODE_DEFAULT_VERSION", 218 | "value": "~14" 219 | } 220 | ] 221 | } 222 | }, 223 | "dependsOn": [ 224 | "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]", 225 | "[resourceId('Microsoft.Insights/components', variables('applicationInsightsName'))]", 226 | "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]", 227 | "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName'))]" 228 | ] 229 | }, 230 | { 231 | "type": "Microsoft.Web/sites/networkConfig", 232 | "apiVersion": "2022-03-01", 233 | "name": "[format('{0}/{1}', parameters('functionAppName'), 'virtualNetwork')]", 234 | "properties": { 235 | "subnetResourceId": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('subnetName'))]", 236 | "swiftSupported": true 237 | }, 238 | "dependsOn": [ 239 | "[resourceId('Microsoft.Web/sites', parameters('functionAppName'))]", 240 | "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName'))]" 241 | ] 242 | } 243 | ] 244 | } -------------------------------------------------------------------------------- /function-app-vnet-integration/azuredeploy.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /function-app-vnet-integration/main.bicep: -------------------------------------------------------------------------------- 1 | @description('The name of the Azure Function app.') 2 | param functionAppName string = 'func-${uniqueString(resourceGroup().id)}' 3 | 4 | @description('Storage Account type') 5 | @allowed([ 6 | 'Standard_LRS' 7 | 'Standard_GRS' 8 | 'Standard_RAGRS' 9 | ]) 10 | param storageAccountType string = 'Standard_LRS' 11 | 12 | @description('Location for all resources.') 13 | param location string = resourceGroup().location 14 | 15 | @description('Location for Application Insights') 16 | param appInsightsLocation string = resourceGroup().location 17 | 18 | @description('The language worker runtime to load in the function app.') 19 | @allowed([ 20 | 'dotnet' 21 | 'node' 22 | 'python' 23 | 'java' 24 | ]) 25 | param functionWorkerRuntime string = 'node' 26 | 27 | @description('Specifies the OS used for the Azure Function hosting plan.') 28 | @allowed([ 29 | 'Windows' 30 | 'Linux' 31 | ]) 32 | param functionPlanOS string = 'Windows' 33 | 34 | @description('Specifies the Azure Function hosting plan SKU.') 35 | @allowed([ 36 | 'EP1' 37 | 'EP2' 38 | 'EP3' 39 | ]) 40 | param functionAppPlanSku string = 'EP1' 41 | 42 | @description('The name of the virtual network to be created.') 43 | param vnetName string = 'vnet-${uniqueString(resourceGroup().id)}' 44 | 45 | @description('The name of the subnet to be created within the virtual network.') 46 | param subnetName string = 'subnet-${uniqueString(resourceGroup().id)}' 47 | 48 | @description('Only required for Linux app to represent runtime stack in the format of \'runtime|runtimeVersion\'. For example: \'python|3.9\'') 49 | param linuxFxVersion string = '' 50 | 51 | var vnetAddressPrefix = '10.0.0.0/16' 52 | var subnetAddressPrefix = '10.0.0.0/24' 53 | var hostingPlanName = functionAppName 54 | var applicationInsightsName = functionAppName 55 | var storageAccountName = '${uniqueString(resourceGroup().id)}azfunctions' 56 | var isReserved = ((functionPlanOS == 'Linux') ? true : false) 57 | 58 | resource vnet 'Microsoft.Network/virtualNetworks@2022-05-01' = { 59 | name: vnetName 60 | location: location 61 | properties: { 62 | addressSpace: { 63 | addressPrefixes: [ 64 | vnetAddressPrefix 65 | ] 66 | } 67 | subnets: [ 68 | { 69 | name: subnetName 70 | properties: { 71 | addressPrefix: subnetAddressPrefix 72 | delegations: [ 73 | { 74 | name: 'delegation' 75 | properties: { 76 | serviceName: 'Microsoft.Web/serverFarms' 77 | } 78 | } 79 | ] 80 | } 81 | } 82 | ] 83 | } 84 | } 85 | 86 | resource storageAccount 'Microsoft.Storage/storageAccounts@2022-05-01' = { 87 | name: storageAccountName 88 | location: location 89 | sku: { 90 | name: storageAccountType 91 | } 92 | kind: 'Storage' 93 | } 94 | 95 | resource hostingPlan 'Microsoft.Web/serverfarms@2022-03-01' = { 96 | name: hostingPlanName 97 | location: location 98 | sku: { 99 | tier: 'ElasticPremium' 100 | name: functionAppPlanSku 101 | family: 'EP' 102 | } 103 | properties: { 104 | maximumElasticWorkerCount: 20 105 | reserved: isReserved 106 | } 107 | kind: 'elastic' 108 | } 109 | 110 | resource insight 'Microsoft.Insights/components@2020-02-02' = { 111 | name: applicationInsightsName 112 | location: appInsightsLocation 113 | tags: { 114 | 'hidden-link:${resourceId('Microsoft.Web/sites', applicationInsightsName)}': 'Resource' 115 | } 116 | properties: { 117 | Application_Type: 'web' 118 | } 119 | kind: 'web' 120 | } 121 | 122 | resource site 'Microsoft.Web/sites@2022-03-01' = { 123 | name: functionAppName 124 | location: location 125 | kind: (isReserved ? 'functionapp,linux' : 'functionapp') 126 | properties: { 127 | reserved: isReserved 128 | serverFarmId: hostingPlan.id 129 | siteConfig: { 130 | linuxFxVersion: (isReserved ? linuxFxVersion : json('null')) 131 | appSettings: [ 132 | { 133 | name: 'APPINSIGHTS_INSTRUMENTATIONKEY' 134 | value: insight.properties.InstrumentationKey 135 | } 136 | { 137 | name: 'AzureWebJobsStorage' 138 | value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccountName};EndpointSuffix= ${environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}' 139 | } 140 | { 141 | name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING' 142 | value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccountName};EndpointSuffix=${environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value};' 143 | } 144 | { 145 | name: 'WEBSITE_CONTENTSHARE' 146 | value: toLower(functionAppName) 147 | } 148 | { 149 | name: 'FUNCTIONS_EXTENSION_VERSION' 150 | value: '~4' 151 | } 152 | { 153 | name: 'FUNCTIONS_WORKER_RUNTIME' 154 | value: functionWorkerRuntime 155 | } 156 | { 157 | name: 'WEBSITE_NODE_DEFAULT_VERSION' 158 | value: '~14' 159 | } 160 | ] 161 | } 162 | } 163 | dependsOn: [ 164 | vnet 165 | ] 166 | } 167 | 168 | resource functionAppName_virtualNetwork 'Microsoft.Web/sites/networkConfig@2022-03-01' = { 169 | parent: site 170 | name: 'virtualNetwork' 171 | properties: { 172 | subnetResourceId: resourceId('Microsoft.Network/virtualNetworks/subnets', vnetName, subnetName) 173 | swiftSupported: true 174 | } 175 | dependsOn: [ 176 | vnet 177 | ] 178 | } 179 | -------------------------------------------------------------------------------- /function-app-windows-consumption/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: This template provisions a function app on a Windows Consumption plan, which is a dynamic hosting plan. The app runs on demand and you're billed per execution, with no standing resource committment. 3 | page_type: sample 4 | products: 5 | - azure 6 | - azure-resource-manager 7 | urlFragment: function-app-windows-consumption 8 | languages: 9 | - bicep 10 | - json 11 | --- 12 | # Azure Function App Hosted on Windows Consumption Plan 13 | 14 | This sample Azure Resource Manager template deploys an Azure Function App on Windows Consumption plan and required resource including ZipDeploy extension to mount zip package for deployment. 15 | 16 | [![Deploy to Azure](/images/deploytoazure.png)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure-Samples%2Ffunction-app-arm-templates%2Fmain%2Ffunction-app-windows-consumption%2Fazuredeploy.json) 17 | 18 | ### OS 19 | 20 | This template is for Azure Function app hosted on **Windows Consumption plan** only. 21 | 22 | ### Comsumption Plan 23 | 24 | The Azure Function app provisioned in this sample uses an [Azure Functions Consumption plan](https://docs.microsoft.com/en-us/azure/azure-functions/consumption-plan). 25 | 26 | + **Microsoft.Web/serverfarms**: The Azure Functions Consumption plan (a.k.a. Dynamic plan) 27 | 28 | ### Azure Function App 29 | 30 | The Function App uses the [AzureWebJobsStorage](https://docs.microsoft.com/azure/azure-functions/functions-app-settings#azurewebjobsstorage) and [WEBSITE_CONTENTAZUREFILECONNECTIONSTRING](https://docs.microsoft.com/azure/azure-functions/functions-app-settings#website_contentazurefileconnectionstring) app settings to connect to a Storage Account. 31 | 32 | + **Microsoft.Web/sites**: The function app instance. 33 | 34 | ### ZipDeploy Extension 35 | 36 | The Zip Deploy extension is added along with recommended app setting `WEBSITE_RUN_FROM_PACKAGE=1` to mount the zip package for deployment. This is the recommended path for deployment, except for [Linux Consumption Plan](/function-app-linux-consumption) 37 | 38 | + **Microsoft.Web/sites/extensions**: The ZipDeploy extension. 39 | 40 | ### Azure Storage account 41 | 42 | The Storage account that the Function uses for operation and for file contents. 43 | 44 | + **Microsoft.Storage/storageAccounts**: [Azure Functions requires a storage account](https://docs.microsoft.com/azure/azure-functions/storage-considerations) for the function app instance. 45 | 46 | ### Application Insights 47 | 48 | [Application Insights](https://docs.microsoft.com/azure/azure-monitor/app/app-insights-overview) is used to provide [monitor the Azure Function](https://docs.microsoft.com/azure/azure-functions/functions-monitoring). 49 | 50 | + **Microsoft.Insights/components**: The Application Insights instance used by the Azure Function for monitoring. 51 | 52 | `Tags: Microsoft.Storage/storageAccounts, microsoft.insights/components, Microsoft.Web/serverfarms, Microsoft.Web/sites, Microsoft.Web/sites/extensions` 53 | -------------------------------------------------------------------------------- /function-app-windows-consumption/azuredeploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "functionAppName": { 6 | "type": "string", 7 | "defaultValue": "[format('func-{0}', uniqueString(resourceGroup().id))]", 8 | "metadata": { 9 | "description": "The name of the Azure Function app." 10 | } 11 | }, 12 | "storageAccountType": { 13 | "type": "string", 14 | "defaultValue": "Standard_LRS", 15 | "allowedValues": [ 16 | "Standard_LRS", 17 | "Standard_GRS", 18 | "Standard_RAGRS" 19 | ], 20 | "metadata": { 21 | "description": "Storage Account type" 22 | } 23 | }, 24 | "location": { 25 | "type": "string", 26 | "defaultValue": "[resourceGroup().location]", 27 | "metadata": { 28 | "description": "Location for all resources." 29 | } 30 | }, 31 | "functionWorkerRuntime": { 32 | "type": "string", 33 | "defaultValue": "node", 34 | "allowedValues": [ 35 | "dotnet", 36 | "node", 37 | "python", 38 | "java" 39 | ], 40 | "metadata": { 41 | "description": "The language worker runtime to load in the function app." 42 | } 43 | }, 44 | "packageUri": { 45 | "type": "string", 46 | "metadata": { 47 | "description": "The zip content url." 48 | } 49 | } 50 | }, 51 | "variables": { 52 | "hostingPlanName": "[parameters('functionAppName')]", 53 | "applicationInsightsName": "[parameters('functionAppName')]", 54 | "storageAccountName": "[format('{0}azfunctions', uniqueString(resourceGroup().id))]" 55 | }, 56 | "resources": [ 57 | { 58 | "type": "Microsoft.Storage/storageAccounts", 59 | "apiVersion": "2022-05-01", 60 | "name": "[variables('storageAccountName')]", 61 | "location": "[parameters('location')]", 62 | "sku": { 63 | "name": "[parameters('storageAccountType')]" 64 | }, 65 | "kind": "Storage" 66 | }, 67 | { 68 | "type": "Microsoft.Web/serverfarms", 69 | "apiVersion": "2022-03-01", 70 | "name": "[variables('hostingPlanName')]", 71 | "location": "[parameters('location')]", 72 | "sku": { 73 | "name": "Y1", 74 | "tier": "Dynamic", 75 | "size": "Y1", 76 | "family": "Y" 77 | }, 78 | "properties": { 79 | "computeMode": "Dynamic" 80 | } 81 | }, 82 | { 83 | "type": "Microsoft.Insights/components", 84 | "apiVersion": "2020-02-02", 85 | "name": "[variables('applicationInsightsName')]", 86 | "location": "[parameters('location')]", 87 | "tags": { 88 | "[format('hidden-link:{0}', resourceId('Microsoft.Web/sites', variables('applicationInsightsName')))]": "Resource" 89 | }, 90 | "properties": { 91 | "Application_Type": "web" 92 | }, 93 | "kind": "web" 94 | }, 95 | { 96 | "type": "Microsoft.Web/sites", 97 | "apiVersion": "2022-03-01", 98 | "name": "[parameters('functionAppName')]", 99 | "location": "[parameters('location')]", 100 | "kind": "functionapp", 101 | "properties": { 102 | "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]", 103 | "siteConfig": { 104 | "appSettings": [ 105 | { 106 | "name": "APPINSIGHTS_INSTRUMENTATIONKEY", 107 | "value": "[reference(resourceId('Microsoft.Insights/components', variables('applicationInsightsName')), '2020-02-02').InstrumentationKey]" 108 | }, 109 | { 110 | "name": "AzureWebJobsStorage", 111 | "value": "[format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('storageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2021-09-01').keys[0].value)]" 112 | }, 113 | { 114 | "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING", 115 | "value": "[format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('storageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2021-09-01').keys[0].value)]" 116 | }, 117 | { 118 | "name": "WEBSITE_CONTENTSHARE", 119 | "value": "[toLower(parameters('functionAppName'))]" 120 | }, 121 | { 122 | "name": "FUNCTIONS_EXTENSION_VERSION", 123 | "value": "~4" 124 | }, 125 | { 126 | "name": "FUNCTIONS_WORKER_RUNTIME", 127 | "value": "[parameters('functionWorkerRuntime')]" 128 | }, 129 | { 130 | "name": "WEBSITE_NODE_DEFAULT_VERSION", 131 | "value": "~14" 132 | }, 133 | { 134 | "name": "WEBSITE_RUN_FROM_PACKAGE", 135 | "value": "1" 136 | } 137 | ] 138 | } 139 | }, 140 | "dependsOn": [ 141 | "[resourceId('Microsoft.Insights/components', variables('applicationInsightsName'))]", 142 | "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]", 143 | "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]" 144 | ] 145 | }, 146 | { 147 | "type": "Microsoft.Web/sites/extensions", 148 | "apiVersion": "2022-03-01", 149 | "name": "[format('{0}/{1}', parameters('functionAppName'), 'zipdeploy')]", 150 | "properties": { 151 | "packageUri": "[parameters('packageUri')]" 152 | }, 153 | "dependsOn": [ 154 | "[resourceId('Microsoft.Web/sites', parameters('functionAppName'))]" 155 | ] 156 | } 157 | ] 158 | } 159 | -------------------------------------------------------------------------------- /function-app-windows-consumption/azuredeploy.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /function-app-windows-consumption/main.bicep: -------------------------------------------------------------------------------- 1 | @description('The name of the Azure Function app.') 2 | param functionAppName string = 'func-${uniqueString(resourceGroup().id)}' 3 | 4 | @description('Storage Account type') 5 | @allowed([ 6 | 'Standard_LRS' 7 | 'Standard_GRS' 8 | 'Standard_RAGRS' 9 | ]) 10 | param storageAccountType string = 'Standard_LRS' 11 | 12 | @description('Location for all resources.') 13 | param location string = resourceGroup().location 14 | 15 | @description('The language worker runtime to load in the function app.') 16 | @allowed([ 17 | 'dotnet' 18 | 'node' 19 | 'python' 20 | 'java' 21 | ]) 22 | param functionWorkerRuntime string = 'node' 23 | 24 | @description('The zip content url.') 25 | param packageUri string 26 | 27 | var hostingPlanName = functionAppName 28 | var applicationInsightsName = functionAppName 29 | var storageAccountName = '${uniqueString(resourceGroup().id)}azfunctions' 30 | 31 | resource storageAccount 'Microsoft.Storage/storageAccounts@2022-05-01' = { 32 | name: storageAccountName 33 | location: location 34 | sku: { 35 | name: storageAccountType 36 | } 37 | kind: 'Storage' 38 | } 39 | 40 | resource hostingPlan 'Microsoft.Web/serverfarms@2022-03-01' = { 41 | name: hostingPlanName 42 | location: location 43 | sku: { 44 | name: 'Y1' 45 | tier: 'Dynamic' 46 | size: 'Y1' 47 | family: 'Y' 48 | } 49 | properties: { 50 | computeMode: 'Dynamic' 51 | } 52 | } 53 | 54 | resource applicationInsight 'Microsoft.Insights/components@2020-02-02' = { 55 | name: applicationInsightsName 56 | location: location 57 | tags: { 58 | 'hidden-link:${resourceId('Microsoft.Web/sites', applicationInsightsName)}': 'Resource' 59 | } 60 | properties: { 61 | Application_Type: 'web' 62 | } 63 | kind: 'web' 64 | } 65 | 66 | resource functionApp 'Microsoft.Web/sites@2022-03-01' = { 67 | name: functionAppName 68 | location: location 69 | kind: 'functionapp' 70 | properties: { 71 | serverFarmId: hostingPlan.id 72 | siteConfig: { 73 | appSettings: [ 74 | { 75 | name: 'APPINSIGHTS_INSTRUMENTATIONKEY' 76 | value: reference(applicationInsight.id, '2015-05-01').InstrumentationKey 77 | } 78 | { 79 | name: 'AzureWebJobsStorage' 80 | value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccountName};EndpointSuffix=${environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}' 81 | } 82 | { 83 | name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING' 84 | value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccountName};EndpointSuffix=${environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}' 85 | } 86 | { 87 | name: 'WEBSITE_CONTENTSHARE' 88 | value: toLower(functionAppName) 89 | } 90 | { 91 | name: 'FUNCTIONS_EXTENSION_VERSION' 92 | value: '~4' 93 | } 94 | { 95 | name: 'FUNCTIONS_WORKER_RUNTIME' 96 | value: functionWorkerRuntime 97 | } 98 | { 99 | name: 'WEBSITE_NODE_DEFAULT_VERSION' 100 | value: '~14' 101 | } 102 | { 103 | name: 'WEBSITE_RUN_FROM_PACKAGE' 104 | value: '1' 105 | } 106 | ] 107 | } 108 | } 109 | } 110 | 111 | resource zipDeploy 'Microsoft.Web/sites/extensions@2022-03-01' = { 112 | parent: functionApp 113 | name: 'zipdeploy' 114 | properties: { 115 | packageUri: packageUri 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /images/deploytoazure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/function-app-arm-templates/fc7311939d3bac5706778af24c8be82a7889a230/images/deploytoazure.png -------------------------------------------------------------------------------- /zip-deploy-arm-az-cli/README.md: -------------------------------------------------------------------------------- 1 | # Az CLI commands for deployment using ARM Template 2 | This workflow can be used when the following conditions are met: 3 | 1. Function app is locked behind [private endpoint](https://docs.microsoft.com/en-us/azure/app-service/networking/private-endpoint). 4 | 2. Storage account is locked behind [private endpoints](https://docs.microsoft.com/en-us/azure/storage/common/storage-private-endpoints). 5 | 3. Want to deploy from your local machine that is not in the Virtual Network. 6 | 7 | ### PRe-requisite 8 | 1. Build and create [.zip package](https://github.com/Azure-Samples/function-app-arm-templates/wiki/Best-Practices-Guide#deployment-zip-file-requirements) of your code in a folder. 9 | 2. Have a copy of the ARM template [azuredeploy.json](/zip-deploy-arm-az-cli/azuredeploy.json) in the same folder. 10 | 3. Have a copy of the ARM template [azuredeploy.parameters.json](/zip-deploy-arm-az-cli/azuredeploy.parameters.json) in the same folder. 11 | 12 | ### Steps: 13 | 14 | 1. Run the following commands in PowerShell Command prompt: 15 | 16 | ``` 17 | az login 18 | 19 | az ad sp create-for-rbac --name --role contributor --scopes /subscriptions//resourceGroups/ --sdk-auth 20 | 21 | az storage account create -n -g 22 | 23 | az storage container create -n --account-name 24 | 25 | az storage blob upload -f --account-name -c -n package.zip --overwrite true 26 | 27 | az storage blob generate-sas --full-uri --permissions r --expiry (get-date).AddMinutes(30).ToString("yyyy-MM-ddTHH:mm:ssZ") --account-name -c -n package.zip 28 | ``` 29 | 30 | 2. Copy paste the SAS URL generated above and your function app name in the azuredeploy.parameters.json 31 | 32 | 3. Run rest of the commands in PowerShell Command prompt: 33 | 34 | ``` 35 | az deployment group create --name --resource-group --template-file azuredeploy.json --parameters azuredeploy.parameters.json 36 | 37 | az storage container delete -n --account-name 38 | ``` 39 | -------------------------------------------------------------------------------- /zip-deploy-arm-az-cli/azuredeploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "functionAppName": { 6 | "type": "string", 7 | "metadata": { 8 | "description": "The name of the Azure Function app." 9 | } 10 | }, 11 | "location": { 12 | "type": "string", 13 | "defaultValue": "[resourceGroup().location]", 14 | "metadata": { 15 | "description": "The location into which the resources should be deployed." 16 | } 17 | }, 18 | "packageUri": { 19 | "type": "string", 20 | "metadata": { 21 | "description": "The zip content url." 22 | } 23 | } 24 | }, 25 | "resources": [ 26 | { 27 | "name": "[concat(parameters('functionAppName'), '/ZipDeploy')]", 28 | "type": "Microsoft.Web/sites/extensions", 29 | "apiVersion": "2021-02-01", 30 | "location": "[parameters('location')]", 31 | "properties": { 32 | "packageUri": "[parameters('packageUri')]" 33 | } 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /zip-deploy-arm-az-cli/azuredeploy.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "functionAppName": { 6 | "value": "" 7 | }, 8 | "packageUri": { 9 | "value": "" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /zip-deploy-arm-github-workflow/README.md: -------------------------------------------------------------------------------- 1 | # Github Workflow for deployment using ARM Template 2 | This workflow can be used when the following conditions are met: 3 | 1. Function app is locked behind [private endpoint](https://docs.microsoft.com/en-us/azure/app-service/networking/private-endpoint). 4 | 2. Storage account is locked behind [private endpoints](https://docs.microsoft.com/en-us/azure/storage/common/storage-private-endpoints). 5 | 3. Want to setup [continuous deployment](https://docs.microsoft.com/en-us/azure/azure-functions/functions-continuous-deployment) pipeline for GitHub repo. 6 | 7 | ### Pre-requisite 8 | 1. Have a copy of the ARM template [azuredeploy.json](/zip-deploy-arm-github-workflow/azuredeploy.json) in the root of the repo. 9 | 2. Setup [Azure Service Principle for RBAC as Deployment Credential](/zip-deploy-arm-github-workflow#Azure-Service-Principle-for-RBAC-as-Deployment-Credential) using the steps below. 10 | 3. Update evironment variables in the [workflow.yml](/zip-deploy-arm-github-workflow/workflow.yml) with those of your app. 11 | 4. This template is for DotNet function app on Windows OS. Please refer [sample templates](https://github.com/Azure/actions-workflow-samples/tree/master/FunctionApp) for other languages and OS to modify this template accordingly. 12 | 13 | ### Azure Service Principle for RBAC as Deployment Credential 14 | You have to create an [Azure Service Principal for RBAC](https://docs.microsoft.com/en-us/azure/role-based-access-control/overview) and add them as a GitHub Secret in your repository. 15 | 1. Download Azure CLI from [here](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest), run `az login` to login with your Azure credentials. 16 | 2. Run Azure CLI command 17 | ``` 18 | az ad sp create-for-rbac --name "myApp" --role contributor \ 19 | --scopes /subscriptions/{subscription-id}/resourceGroups/{resource-group}/providers/Microsoft.Web/sites/{app-name} \ 20 | --sdk-auth 21 | 22 | # Replace {subscription-id}, {resource-group}, and {app-name} with the names of your subscription, resource group, and Azure function app. 23 | # The command should output a JSON object similar to this: 24 | 25 | { 26 | "clientId": "", 27 | "clientSecret": "", 28 | "subscriptionId": "", 29 | "tenantId": "", 30 | (...) 31 | } 32 | ``` 33 | 3. Paste the json response from above Azure CLI to your Github Repository > Settings > Secrets > Add a new secret > **AZURE_CREDENTIALS** 34 | 35 | ### Dependencies on other Github Actions 36 | * [Checkout](https://github.com/actions/checkout) Checkout your Git repository content into GitHub Actions agent. 37 | * [Azure Login](https://github.com/Azure/actions) Login with your Azure credentials for function app deployment authentication. 38 | * To build app code in a specific language based environment, use setup actions: 39 | * [Setup DotNet](https://github.com/actions/setup-dotnet) Build your DotNet core function app or function app extensions. 40 | * [Setup Node](https://github.com/actions/setup-node) Resolve Node function app dependencies using npm. 41 | * [Setup Python](https://github.com/actions/setup-python) Resolve Python function app dependencies using pip. 42 | * [Setup Java](https://github.com/actions/setup-java) Resolve Java function app dependencies using maven. 43 | 44 | If you are looking for a GitHub Action to deploy your customized container image into an Azure Functions container, use [`azure/functions-container-action`](https://github.com/Azure/functions-container-action). 45 | -------------------------------------------------------------------------------- /zip-deploy-arm-github-workflow/azuredeploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "functionAppName": { 6 | "type": "string", 7 | "metadata": { 8 | "description": "The name of the Azure Function app." 9 | } 10 | }, 11 | "location": { 12 | "type": "string", 13 | "defaultValue": "[resourceGroup().location]", 14 | "metadata": { 15 | "description": "The location into which the resources should be deployed." 16 | } 17 | }, 18 | "packageUri": { 19 | "type": "string", 20 | "metadata": { 21 | "description": "The zip content url." 22 | } 23 | } 24 | }, 25 | "resources": [ 26 | { 27 | "name": "[concat(parameters('functionAppName'), '/ZipDeploy')]", 28 | "type": "Microsoft.Web/sites/extensions", 29 | "apiVersion": "2021-02-01", 30 | "location": "[parameters('location')]", 31 | "properties": { 32 | "packageUri": "[parameters('packageUri')]" 33 | } 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /zip-deploy-arm-github-workflow/workflow.yml: -------------------------------------------------------------------------------- 1 | name: Build and deploy dotnet core app to Azure Function App - cp-win-dotnet 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | 9 | env: 10 | AZURE_RESOURCE_GROUP: 'your-resource-group-name' # set this to your Azure Resource group's name 11 | FUNCTION_APP: 'your-function-app-name' # set this to your Azure Function app's name 12 | STORAGE_ACCOUNT: 'new-storage-for-deployment' # storage will be created for deployment if not exists. Requirements: unique name, 3 to 24 characters in length, use numbers and lower-case letters only 13 | CONTAINER: 'temporarydeploymentcontainer' # temporary container needed for deployment, can keep name as it is 14 | AZURE_FUNCTIONAPP_PACKAGE_PATH: '.' # set this to the path to your web app project, defaults to the repository root 15 | DOTNET_VERSION: '6.0.x' # set this to the dotnet version to use 16 | 17 | jobs: 18 | build-and-deploy: 19 | runs-on: windows-latest 20 | steps: 21 | 22 | - name: Azure Login 23 | uses: azure/login@v1 24 | with: 25 | creds: ${{ secrets.AZURE_CREDENTIALS }} 26 | 27 | - name: Checkout GitHub Action 28 | uses: actions/checkout@v2 29 | 30 | - name: Setup DotNet ${{ env.DOTNET_VERSION }} Environment 31 | uses: actions/setup-dotnet@v1 32 | with: 33 | dotnet-version: ${{ env.DOTNET_VERSION }} 34 | 35 | - name: Resolve Project Dependencies Using Dotnet 36 | shell: pwsh 37 | run: | 38 | pushd './${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}' 39 | dotnet build --configuration Release --output ./output 40 | popd 41 | 42 | - name: Zip Function App Content 43 | shell: pwsh 44 | run: | 45 | Compress-Archive -Path ${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}/output/* -DestinationPath ${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}/package.zip 46 | 47 | - name: Setup Storage, Container & Blob for Deployment 48 | shell: pwsh 49 | run: | 50 | az storage account create -n ${{ env.STORAGE_ACCOUNT }} -g ${{ env.AZURE_RESOURCE_GROUP }} 51 | az storage container create -n ${{ env.CONTAINER }} --account-name ${{ env.STORAGE_ACCOUNT }} 52 | az storage blob upload -f ${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}/package.zip --account-name ${{ env.STORAGE_ACCOUNT }} -c ${{ env.CONTAINER }} -n package.zip --overwrite true 53 | 54 | - name: Zip Deployment using ARM Template 55 | shell: pwsh 56 | run: | 57 | $Env:Expiry=(get-date).AddMinutes(30).ToString("yyyy-MM-ddTHH:mm:ssZ") 58 | $Env:ZIP_URL=$(az storage blob generate-sas --full-uri --permissions r --expiry $Env:Expiry --account-name ${{ env.STORAGE_ACCOUNT }} -c ${{ env.CONTAINER }} -n package.zip) 59 | az deployment group create --name deploy --resource-group ${{ env.AZURE_RESOURCE_GROUP }} --template-file azuredeploy.json --parameters functionAppName='${{ env.FUNCTION_APP }}' packageUri=$Env:ZIP_URL 60 | 61 | - name: Delete Temporary Container & Blob 62 | shell: pwsh 63 | run: | 64 | az storage container delete -n ${{ env.CONTAINER }} --account-name ${{ env.STORAGE_ACCOUNT }} -------------------------------------------------------------------------------- /zip-deploy-run-from-package/README.md: -------------------------------------------------------------------------------- 1 | # Function App Deployment with ZipDeploy Run From Package 2 | 3 | ### ZipDeploy with ARM template 4 | 5 | ZipDeploy is intended for xcopy or ftp style deployment. By default, It unzips the artifacts and lay them out exactly to d:\home\site\wwwroot. You can use any tooling (such as one coming with Windows) to zip your content. 6 | 7 | It is recommended to set appSettings `WEBSITE_RUN_FROM_PACKAGE=1`, to allow Zip package deployed with ZipDeploy to mount as read-only virtual filesystem directly without deflating or extracting. The advantage is to allow atomic and reliable deployment (no more files being locked). 8 | 9 | NOTE: ZipDeploy with the appSetting `WEBSITE_RUN_FROM_PACKAGE=1` is not supported for Linux Consumption plan. 10 | 11 | If you have an existing Function App with right appSettings and want to perform deployment with ZipDeploy extension, then here is the template: 12 | 13 | [![Deploy to Azure](/images/deploytoazure.png)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure-Samples%2Ffunction-app-arm-templates%2Fmain%2Fzip-deploy-run-from-package%2Fazuredeploy.json) 14 | 15 | The following example shows declartion of ZipDeploy extension in site resources along with the recommended WEBSITE_RUN_FROM_PACKAGE appSetting: 16 | 17 | NOTE: Please include rest of the existing app settings in the template to preserve them. 18 | 19 | ```json 20 | { 21 | "name": "[parameters('siteName')]", 22 | "type": "Microsoft.Web/sites", 23 | "apiVersion": "2021-02-01", 24 | "location": "[parameters('location')]", 25 | "properties": { 26 | "siteConfig": { 27 | "appSettings": [ 28 | { 29 | "name": "WEBSITE_RUN_FROM_PACKAGE", 30 | "value": "1" 31 | } 32 | ] 33 | } 34 | }, 35 | "resources": [ 36 | { 37 | "name": "ZipDeploy", 38 | "type": "Extensions", 39 | "apiVersion": "2021-02-01", 40 | "dependsOn": [ 41 | "[concat('Microsoft.Web/sites/', parameters('siteName'))]" 42 | ], 43 | "properties": { 44 | "packageUri": "[parameters('packageUri')]" 45 | } 46 | } 47 | ] 48 | } 49 | ``` 50 | 51 | Since it will mount as read-only, app runtime will not be able to create or modify files under d:\home\site\wwwroot. In addition, Azure Functions Portal will also prevent you from modifying the Function Apps. 52 | 53 | ### For Linux Consumption plan: 54 | 55 | 1. Do not use ZipDeploy extension. 56 | 2. Set appSetting `WEBSITE_RUN_FROM_PACKAGE=URL` for deployment using the .zip package url. For example: 57 | 58 | NOTE: Please include rest of the existing app settings in the template to preserve them. 59 | 60 | ```json 61 | { 62 | "name": "[parameters('siteName')]", 63 | "type": "Microsoft.Web/sites", 64 | "apiVersion": "2021-02-01", 65 | "location": "[parameters('location')]", 66 | "properties": { 67 | "siteConfig": { 68 | "appSettings": [ 69 | { 70 | "name": "WEBSITE_RUN_FROM_PACKAGE", 71 | "value": "[parameters('packageUri')]" 72 | } 73 | ] 74 | } 75 | } 76 | } 77 | ``` 78 | -------------------------------------------------------------------------------- /zip-deploy-run-from-package/azuredeploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "functionAppName": { 6 | "type": "string", 7 | "metadata": { 8 | "description": "The name of the Azure Function app." 9 | } 10 | }, 11 | "location": { 12 | "type": "string", 13 | "defaultValue": "[resourceGroup().location]", 14 | "metadata": { 15 | "description": "The location into which the resources should be deployed." 16 | } 17 | }, 18 | "packageUri": { 19 | "type": "string", 20 | "metadata": { 21 | "description": "The zip content url." 22 | } 23 | } 24 | }, 25 | "resources": [ 26 | { 27 | "name": "[concat(parameters('functionAppName'), '/ZipDeploy')]", 28 | "type": "Microsoft.Web/sites/extensions", 29 | "apiVersion": "2021-02-01", 30 | "location": "[parameters('location')]", 31 | "properties": { 32 | "packageUri": "[parameters('packageUri')]" 33 | } 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /zip-deploy-run-from-package/azuredeploy.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | } 6 | } 7 | --------------------------------------------------------------------------------