用于 Azure Functions 的 ARM 模板,其中包含适用于不同环境和插槽的许多 appSettings [英] ARM Templates for Azure Functions with many appSettings for different environments and slots

查看:17
本文介绍了用于 Azure Functions 的 ARM 模板,其中包含适用于不同环境和插槽的许多 appSettings的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有两个使用部署槽、阶段和生产的 Azure Function 应用程序.这两个 Azure Function 应用程序在 Application Settings 中有大约 50~ 个 key:value 对,用于定义各种 API 键、应用程序行为、连接字符串等.

I've got two Azure Function apps that use deployment slots, stage and production. These two Azure Function apps have about 50~ key:value pairs in Application Settings to define various API keys, application behavior, connection strings, etc.

我想将这两个 Azure Function 应用部署到五个不同的环境(CI、DEV、QA、STG、PROD).我相信使用 ARM 模板将这些资源部署到 Azure 是比 Azure CLI 更好的选择.我将在我的 Azure DevOps 发布管道中创建任务来实现这一点.

I want to deploy these two Azure Function apps to five different environments (CI, DEV, QA, STG, PROD). I believe that deploying these resources to Azure using ARM templates is the better choice over Azure CLI. I will create tasks in my Azure DevOps release pipeline to achieve this.

为了将 ARM 模板分解为易于维护的东西,我想为每个环境创建一个 ARM 模板参数文件.为 Azure 函数定义部署文件时,要定义的属性之一是 siteConfig 对象,您可以在其中使用 NameValuePair 对象定义 appSettings 对象.对于每个环境,阶段和生产槽将具有不同的 API 密钥、连接字符串和应用程序行为.我的部署文件使用生产槽和阶段槽创建 Azure Function 应用程序.在部署文件中,我必须提供两次 appSettings NameValuePair 对象.然后,我必须为每个环境创建 5 个不同的参数文件.将其乘以 2,因为我有两个插槽.

In order to break down the ARM template into something easily maintainable, I wanted to create an ARM template parameter file for each environment. When defining the deployment file for the Azure Function, one of the properties to define is the siteConfig object, where you define the appSettings object with a NameValuePair object. For each environment, the stage and production slot will have different API keys, connection strings, and application behavior. My deployment file creates the Azure Function app with both the production slot and stage slot. In the deployment file, I have to provide the appSettings NameValuePair object twice. Then, I have to create 5 different parameter files for each environment. Multiply that by 2 because I have two slots.

参数文件中定义的所有参数是否也必须在参数对象中的部署模板文件中定义?

Is it also true that all parameters defined in the parameter file have to be defined in the deployment template file in the parameters object?

我是否可以只从参数文件中传入带有 NameValuePairs 的对象数组,这样我就不必在顶部的部署文件中以及函数应用的 siteConfig.appSettings 下定义整个参数列表?

Can I just pass in an array of objects with NameValuePairs from the parameter file so I don't have to have the entire list of parameters defined in the deployment file at the top and also under siteConfig.appSettings for the function app?

此处的文档 表明您只能提供一个字符串数组或具有许多键:值的单个对象.但 appSettings 是一个对象数组,其中每个对象都有 3 个键:值对.

The documentation here shows that you can only provide an array of strings or a single object with many key:values. But appSettings is an array of objects where each object has 3 key:value pairs.

这是资源在部署文件中的样子.我想简单地从参数文件中引用整个对象数组,但看起来文档指出我在部署文件的顶部定义了所有 50~ 参数,然后当由 Azure CLI 或执行时参数文件将覆盖这些参数Azure DevOps 任务.

This is what the resource looks like in the deployment file. I would like to simply reference an entire array of objects from the parameter file, but it looks like the documentation states that I define all 50~ parameters at the top of the deployment file, which then the parameter file overrides when executed by Azure CLI or Azure DevOps task.

        {
            "type": "Microsoft.Web/sites",
            "apiVersion": "2018-11-01",
            "name": "[parameters('function-app-name')]",
            "location": "[parameters('location')]",
            "tags": {
                "project": "[variables('projectName')]",
                "env": "[parameters('deploymentEnvironment')]"
            },
            "kind": "functionapp",
            "properties": {
                "enabled": true,
                "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]",
                "siteConfig": {
                    "appSettings": [] # I need to provide an array of objects here
                 }
            }
       }

除了我的抱怨...我不敢相信我必须为所有五个环境及其两个具有两个插槽的 Azure 函数创建 20 个参数文件.有没有更好的方法使用 ARM 模板和参数文件及其独特的应用程序设置来部署到我的所有环境及其部署槽?

In addition to my complaint...I can't believe I'm going to have to create 20 parameter files for all five environments and their two Azure Functions that have two slots. Is there a better way to deploy to all my environments and their deployment slots using ARM templates and parameter files with their unique application settings?

更新:

我能够拼凑各种创建特定环境的 ARM 模板的方法,并得出以下结果,但存在一些不方便的问题.首先,我将解释我现在所处的位置,然后提出与设计相关的问题.

I was able to piece together various methods for creating environment-specific ARM templates and came up with the following result, with some inconvenient problems. First, I'll explain where I am now and then bring up the problems associated with the design.

在我的部署模板中,我定义了两个参数.他们在这里:

In my deployment template, I've defined two parameters. Here they are:

        "deploymentEnvironment": {
            "type": "string",
            "allowedValues": [
                "CI",
                "DEV",
                "QA",
                "TRN",
                "STG",
                "PROD"
            ],
            "metadata": {
                "description": "Type of environment being deployed to. AKA "Stage" in release definition."
            }
        },
        "applicationSettings": {
            "type": "object",
            "metadata": {
                "description": "Application settings from function.parameters.json"
            }
        }

我的 function.parameters.json 的结构是这样的:

My function.parameters.json has a structure like this:

{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "applicationSettings": {
            "value": {
                "CI": {
                    "appsetting1": "",
                    "appsetting2": ""
                },
                "DEV": {
                    "appsetting1": "",
                    "appsetting2": ""            },
                "QA": {
                    "appsetting1": "",
                    "appsetting2": ""
                }
            }
        }
    }
}

对于每个环境,我都放置了我的所有连接字符串、apikey 和应用程序设置.

For each environment, I had placed all of my connection strings, apikeys, and application settings.

对于函数应用的生产槽,您可以添加一个资源"属性来应用配置.下面是整个函数应用部署:

For the production slot for the function app, you can add a "resources" property which applies configuration to it. Here is the entire function app deployment:

        {
            "name": "[parameters('function-app-name')]",
            "type": "Microsoft.Web/sites",
            "apiVersion": "2018-11-01",
            "kind": "functionapp",
            "location": "[parameters('location')]",
            "tags": {
                "project": "[variables('projectName')]",
                "env": "[parameters('deploymentEnvironment')]"
            },
            "properties": {
                "enabled": true,
                "serverFarmId": "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]"
            },
            "dependsOn": [
                "[resourceId('Microsoft.Insights/components/', variables('applicationInsightsName'))]",
                "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]",
                "[resourceId('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]"
            ],
            "resources": [
                {
                    "name": "appsettings",
                    "type": "config",
                    "apiVersion": "2018-11-01",
                    "properties": "[parameters('applicationSettings')[parameters('deploymentEnvironment')]]",
                    "dependsOn": [
                        "[resourceId('Microsoft.Web/sites/', parameters('function-app-name'))]"
                    ]
                }
            ]
        }

接下来是定义阶段槽部署资源.这里是:

Next up was defining the stage slot deployment resource. Here it is:

        {
            "type": "Microsoft.Web/sites/slots",
            "apiVersion": "2018-11-01",
            "name": "[concat(parameters('function-app-name'), '/stage')]",
            "location": "[parameters('location')]",
            "dependsOn": [
                "[resourceId('Microsoft.Web/sites/', parameters('function-app-name'))]"
            ],
            "tags": {
                "project": "[variables('projectName')]",
                "env": "[parameters('deploymentEnvironment')]"
            },
            "kind": "functionapp",
            "properties": {
                "enabled": true,
                "serverFarmId": "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]"
            },
            "resources": [
                {
                    "name": "appsettings",
                    "type": "config",
                    "apiVersion": "2018-11-01",
                    "properties": "[parameters('applicationSettings')[parameters('deploymentEnvironment')]]",
                    "dependsOn": [
                        "[resourceId('Microsoft.Web/sites/', parameters('function-app-name'))]",
                        "[resourceId('Microsoft.Web/sites/slots/', parameters('function-app-name'), 'stage')]"
                    ]
                }
            ]
        }

有了这个解决方案,我就不必为每个环境都有一堆parameters.json 文件了.

With this solution, I don't have to have a bunch of parameters.json files for each environment.

问题...

在 parameters.json 中定义所有应用程序设置意味着我不能使用模板函数来获取连接字符串或 Azure Key Vault 值.

Defining all of the application settings in parameters.json means I can't use template functions to get connection strings or Azure Key Vault values.

这是我开始将一些应用程序设置移动到部署模板以使用模板功能的时候.因此,我没有在 parameters.json 文件中包含 APPINSIGHTS_INSTRUMENTATIONKEY 和其他 AzureWebJobs* 应用程序设置,而是提供了 siteConfig 对象Microsoft.Web/Sites 资源Microsoft.Web/Sites/Slots 资源.

This is when I started to move some of the application settings to the deployment template to use template functions. So instead of having the APPINSIGHTS_INSTRUMENTATIONKEY and other AzureWebJobs* application settings in the parameters.json file, I provided the siteConfig object in the "properties" object for the Microsoft.Web/Sites resource and the Microsoft.Web/Sites/Slots resource.

这才是真正的失败 - 部署运行时,它将 siteConfig.appsettings 值与函数应用程序一起应用,然后当它应用 parameters.json 文件时,它删除了应用程序设置并仅应用了json,而不是将它们合并在一起.这是一个巨大的失望.在我使用 AzureCLI 进行的初始测试中,我使用了这个命令 az functionapp config appsettings set --name $functionAppName --resource-group $resourceGroupName --settings $settingsFile --slot $slot 来测试什么会发生在不在 json 文件中的应用程序设置,并且很高兴它从未删除应用程序设置.powershell 命令获取和设置值,很好地合并它并且永远不会删除.但是 ARM API 会删除所有这些名称值对并仅应用定义的内容.这意味着我不能使用模板函数来创建动态应用程序设置和 json 文件来应用静态应用程序设置.

This is the real bummer - When the deployment ran, it applied the siteConfig.appsettings values with the function app, then when it applied the parameters.json file, it deleted the application settings and applied only the ones from the json, instead of merging them together. That was a HUGE disappointment. In my initial testing with the AzureCLI, I used this command az functionapp config appsettings set --name $functionAppName --resource-group $resourceGroupName --settings $settingsFile --slot $slot to test what would happen with application settings that were not in a json file and was happy that it never deleted application settings. The powershell command gets and sets the values, merging it nicely and never deleting. But the ARM API deletes all those name value pairs and applies only what is defined. This means I cannot use template functions to create dynamic application settings and a json file to apply static application settings.

到目前为止,我觉得做一个像样的 ARM 模板部署的唯一方法就是部署没有 siteConfig 对象或配置资源的资源来应用应用程序设置,然后跟进 Azure CLI 来部署应用程序设置.我想我可以学习如何使用 Azure CLI 或 Azure DevOps 管道任务来检索 Key Vault 机密,但将它们全部放在一个 ARM 模板中会更好.

As of now, I feel like the only way to do a decent ARM template deployment is just deploy the resources without the siteConfig object or the config resource to apply application settings and then follow up with the Azure CLI to deploy the application settings. I suppose I could learn how to retrieve Key Vault secrets using Azure CLI or Azure DevOps pipeline tasks, but it would be even better to just have it all in a single ARM template.

作为参考,这是我尝试使用动态生成的 appSettings 和配置资源来定义更多 appsettings 时的整个部署模板.

For reference, here is my entire deployment template when I attempted to use dynamically generated appSettings and the config resource to define more appsettings.

{
    "$schema": "https://schema.management.azure.com/schemas/2019-08-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "function-app-name": {
            "defaultValue": "functionappname",
            "type": "String",
            "metadata": {
                "description": "The name of the function app that you wish to create."
            }
        },
        "sku": {
            "type": "string",
            "allowedValues": [
                "S1",
                "S2",
                "S3"
            ],
            "defaultValue": "S3",
            "metadata": {
                "description": "The pricing tier for the hosting plan."
            }
        },
        "storageAccountType": {
            "type": "string",
            "defaultValue": "Standard_LRS",
            "metadata": {
                "description": "Storage Account type"
            }
        },
        "location": {
            "type": "string",
            "defaultValue": "southcentralus",
            "metadata": {
                "description": "Location for all resources."
            }
        },
        "deploymentEnvironment": {
            "type": "string",
            "allowedValues": [
                "CI",
                "DEV",
                "QA",
                "TRN",
                "STG",
                "PROD"
            ],
            "metadata": {
                "description": "Type of environment being deployed to."
            }
        },
        "applicationSettings": {
            "type": "object",
            "metadata": {
                "description": "Application settings from function.parameters.json"
            }
        }
    },
    "variables": {
        "storageAccountName": "[concat('store', uniquestring(resourceGroup().id))]",
        "appServicePlanName": "[concat('ASP-', uniquestring(resourceGroup().id))]",
        "applicationInsightsName": "[concat('appInsights-', uniquestring(resourceGroup().id))]",
        "projectName": "DV"
    },
    "resources": [
        {
            "type": "Microsoft.Storage/storageAccounts",
            "apiVersion": "2019-04-01",
            "name": "[variables('storageAccountName')]",
            "kind": "Storage",
            "location": "[parameters('location')]",
            "sku": {
                "name": "[parameters('storageAccountType')]"
            },
            "tags": {
                "project": "[variables('projectName')]",
                "env": "[parameters('deploymentEnvironment')]"
            }
        },
        {
            "name": "[variables('appServicePlanName')]",
            "type": "Microsoft.Web/serverfarms",
            "apiVersion": "2019-08-01",
            "location": "[parameters('location')]",
            "properties": {
            },
            "tags": {
                "project": "[variables('projectName')]",
                "env": "[parameters('deploymentEnvironment')]"
            },
            "sku": {
                "Name": "[parameters('sku')]",
                "capacity": 2
            },
            "dependsOn": [
                "[resourceId('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]"
            ]
        },
        {
            "name": "[variables('applicationInsightsName')]",
            "apiVersion": "2015-05-01",
            "type": "Microsoft.Insights/components",
            "kind": "web",
            "location": "[parameters('location')]",
            "tags": {
                "project": "[variables('projectName')]",
                "env": "[parameters('deploymentEnvironment')]"
            },
            "properties": {
                "Application_Type": "web"
            }
        },
        {
            "name": "[parameters('function-app-name')]",
            "type": "Microsoft.Web/sites",
            "apiVersion": "2018-11-01",
            "kind": "functionapp",
            "location": "[parameters('location')]",
            "tags": {
                "project": "[variables('projectName')]",
                "env": "[parameters('deploymentEnvironment')]"
            },
            "properties": {
                "enabled": true,
                "serverFarmId": "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]",
                "siteConfig": {
                    "appSettings": [
                        {
                            "name": "APPINSIGHTS_INSTRUMENTATIONKEY",
                            "value": "[reference(concat('microsoft.insights/components/', variables('applicationInsightsName'))).InstrumentationKey]"
                        },
                        {
                            "name": "AzureWebJobsDashboard",
                            "value": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value)]"
                        },
                        {
                            "name": "AzureWebJobsStorage",
                            "value": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value)]"
                        },
                        {
                            "name": "FUNCTIONS_EXTENSION_VERSION",
                            "value": "~1"
                        }
                    ]
                }
            },
            "dependsOn": [
                "[resourceId('Microsoft.Insights/components/', variables('applicationInsightsName'))]",
                "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]",
                "[resourceId('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]"
            ],
            "resources": [
                {
                    "name": "appsettings",
                    "type": "config",
                    "apiVersion": "2018-11-01",
                    "properties": "[parameters('applicationSettings')[parameters('deploymentEnvironment')]]",
                    "dependsOn": [
                        "[resourceId('Microsoft.Web/sites/', parameters('function-app-name'))]"
                    ]
                }
            ]
        },
        {
            "type": "Microsoft.Web/sites/slots",
            "apiVersion": "2018-11-01",
            "name": "[concat(parameters('function-app-name'), '/stage')]",
            "location": "[parameters('location')]",
            "dependsOn": [
                "[resourceId('Microsoft.Web/sites/', parameters('function-app-name'))]"
            ],
            "tags": {
                "project": "[variables('projectName')]",
                "env": "[parameters('deploymentEnvironment')]"
            },
            "kind": "functionapp",
            "properties": {
                "enabled": true,
                "serverFarmId": "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]",
                "siteConfig": {
                    "appSettings": [
                        {
                            "name": "APPINSIGHTS_INSTRUMENTATIONKEY",
                            "value": "[reference(concat('microsoft.insights/components/', variables('applicationInsightsName'))).InstrumentationKey]"
                        },
                        {
                            "name": "AzureWebJobsDashboard",
                            "value": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value)]"
                        },
                        {
                            "name": "AzureWebJobsStorage",
                            "value": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value)]"
                        },
                        {
                            "name": "FUNCTIONS_EXTENSION_VERSION",
                            "value": "~1"
                        }
                    ]
                },
                "resources": [
                    {
                        "name": "appsettings",
                        "type": "config",
                        "apiVersion": "2018-11-01",
                        "properties": "[parameters('applicationSettings')[parameters('deploymentEnvironment')]]",
                        "dependsOn": [
                            "[resourceId('Microsoft.Web/sites/', parameters('function-app-name'))]"
                        ]
                    }
                ]
            }
        }
    ]
}

更新 2:

我提出了一个 github 问题,让他们解决 ARM 模板替换所有每个部署的应用程序设置.FWIW - 我还对一些 Azure 反馈帖子进行了投票.

推荐答案

对不起,我没有太多时间来回答,而且你有一堆问题主要与什么是最好的方法.......",答案总是视情况而定".

Sorry I don't have a huge amount of time to answer, and you have a bunch of questions which relate mainly to "what's the best way to...", and the answer is always "it depends".

我发现更容易管理的一件事是,您可以创建 Microsoft.Web/sites/config 类型的顶级资源,而不是使用 siteConfig 来设置所有应用程序设置代码>(我发现它有时很有用,因为您可以在创建站点后创建它们,因此如果您在其他地方有尚未设置的依赖项,将配置和站点分开会很方便).

One thing I find easier to manage is instead of using siteConfig to set all the app settings, you can create a top level resource of type Microsoft.Web/sites/config (which I find useful sometimes as you can create them after the site is created, so if you have dependencies elsewhere that aren't setup yet, it can be handy to separate out the config and the site).

"parameters": {
  "appSettings": {
    "type": "object",
    "defaultValue": {
      "property1": "value1",
      "property2": "value2"
    }
  }
}

"resources": [
  {
    "type": "Microsoft.Web/sites",
    "apiVersion": "2018-11-01",
    "name": "[parameters('function-app-name')]",
    "location": "[parameters('location')]",
    "kind": "functionapp",
    "properties": {
      "enabled": true,
      "serverFarmId": "..."
    }
  },
  {
    "type": "Microsoft.Web/sites/config",
    "name": "[concat(parameters('function-app-name'), '/appsettings')]",
    "apiVersion": "2018-11-01",
    "properties": "[parameters('appSettings')]"
    "dependsOn": [ "[resourceId('Microsoft.Web/sites/sites', parameters('function-app-name'))]",
  }
]

上面的一个缺点是你不能使用params部分的某些函数,所以你不能使用listKeys()来获取资源的键,所以它只是有时有用,或者像在此示例中,如果您想添加对同样在同一模板中创建的应用洞察的引用,如果您将设置作为参数传递,则这是不可能的.

One of the drawbacks of the above, is that you can't use certain functions in the params section, so you can't use listKeys() to get a key to a resource, so it's only useful sometimes, or like this example, if you wanted to add a reference to app insights which is also created in the same template, this isn't possible if you're passing in the settings as a param.

  {
    "type": "Microsoft.Web/sites/config",
    "name": "[concat(parameters('function-app-name'), '/appsettings')]",
    "apiVersion": "2018-11-01",
    "properties": {
      "property1": "value1",
      "property2": "value2",
      "APPINSIGHTS_INSTRUMENTATIONKEY": "[reference(resourceId('microsoft.insights/components/', variables('appInsightsName')), '2015-05-01').InstrumentationKey]"
    }
    "dependsOn": [ 
      "[resourceId('Microsoft.Web/sites/sites', parameters('function-app-name'))]",
      "[resourceId('microsoft.insights/components', variables('appInsightsName'))]"
  }

您确实应该在部署时解决所有问题,因此可以将存储帐户(例如)连接字符串安全地添加到模板中,并且仅在部署时解决.

You should really be resolving everything you can at deploy time, so a storage account (for example) connection string can be added into the template securely, and resolved at deploy time only.

另一个方便的技巧是使用密钥库来存储模板中无法解析的任何安全凭据、api 密钥、连接字符串等.您提到需要它们,但随后您将它们提交给模板中的源代码控制......好吧,它们不会保密很长时间(另一个提示,确保它们都使用安全字符串而不是字符串类型,否则门户会将它们暴露在资源组的部署日志).您可以像这样从应用设置访问密钥保管库:

Another handy tip, is to use key vault to store any secure credentials, api keys, connection strings etc that cannot be resolved in the template. You mention needing them, but then you're committing them to source control in the templates... Well, they wont stay secret very long (another tip, ensure they all use securestring instead of string types, otherwise the portal will expose them in the deployment logs for the resource group). You can access key vaults from app settings like this:

"secretConnectionString": "[concat('@Microsoft.KeyVault(SecretUri=https://', variables('vaultName'), '.vault.azure.net/secrets/my-connection-string/)')]",

但要使上述操作生效,您需要授予应用程序对保管库vaultName"的读取权限,如果您使用托管服务标识,这应该没问题.

But for the above to work, you will need to give your application read access to the vault "vaultName", which should be fine if you use managed service identities.

这篇关于用于 Azure Functions 的 ARM 模板,其中包含适用于不同环境和插槽的许多 appSettings的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆