我有两个使用部署槽、阶段和生产的 Azure Function 应用程序。这两个 Azure Function 应用程序在应用程序设置中有大约 50~ 个键:值对,用于定义各种 API 键、应用程序行为、连接字符串等。
我想将这两个 Azure Function 应用程序部署到五个不同的环境(CI、DEV、QA、STG、PROD)。我相信使用 ARM 模板将这些资源部署到 Azure 是比 Azure CLI 更好的选择。我将在我的 Azure DevOps 发布管道中创建任务来实现这一点。
为了将 ARM 模板分解为易于维护的东西,我想为每个环境创建一个 ARM 模板参数文件。为 Azure 函数定义部署文件时,要定义的属性之一是siteConfig 对象,您可以在其中使用 NameValuePair 对象定义 appSettings 对象。对于每个环境,阶段和生产槽将具有不同的 API 密钥、连接字符串和应用程序行为。我的部署文件使用生产槽和阶段槽创建 Azure Function 应用程序。在部署文件中,我必须提供两次 appSettings NameValuePair 对象。然后,我必须为每个环境创建 5 个不同的参数文件。将其乘以 2,因为我有两个插槽。
参数文件中定义的所有参数是否也必须在参数对象的部署模板文件中定义?
我可以只从参数文件中传入一个带有 NameValuePairs 的对象数组,这样我就不必在顶部的部署文件中以及函数应用程序的 siteConfig.appSettings 下定义整个参数列表吗?
此处的文档显示您只能提供字符串数组或具有许多键:值的单个对象。但是 appSettings 是一个对象数组,其中每个对象都有 3 个键:值对。
这是资源在部署文件中的样子。我想简单地从参数文件中引用整个对象数组,但看起来文档指出我在部署文件的顶部定义了所有 50~ 参数,然后当由 Azure CLI 或执行时,参数文件将覆盖这些参数Azure DevOps 任务。
{
"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 模板和参数文件及其独特的应用程序设置,是否有更好的方法来部署到我的所有环境及其部署槽?
更新:
我能够拼凑各种方法来创建特定于环境的 ARM 模板,并得出以下结果,但存在一些不方便的问题。首先,我将解释我现在所处的位置,然后提出与设计相关的问题。
在我的部署模板中,我定义了两个参数。他们来了:
"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 具有这样的结构:
{
"$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 和应用程序设置。
对于函数应用程序的生产槽,您可以添加一个“资源”属性来应用配置。这是整个函数应用部署:
{
"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'))]"
]
}
]
}
接下来是定义阶段槽部署资源。这里是:
{
"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 文件。
问题...
在 parameters.json 中定义所有应用程序设置意味着我不能使用模板函数来获取连接字符串或 Azure Key Vault 值。
这是我开始将一些应用程序设置移动到部署模板以使用模板功能的时候。因此,我没有在 parameters.json 文件中使用 APPINSIGHTS_INSTRUMENTATIONKEY 和其他 AzureWebJobs* 应用程序设置,而是在Microsoft.Web/Sites 资源和Microsoft.Web/Sites/Slots 资源的“properties”对象中提供了siteConfig 对象。
这才是真正的失败 - 部署运行时,它将 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 文件来应用静态应用程序设置。
到目前为止,我觉得进行体面的 ARM 模板部署的唯一方法是部署没有 siteConfig 对象或配置资源的资源来应用应用程序设置,然后使用 Azure CLI 来部署应用程序设置。我想我可以学习如何使用 Azure CLI 或 Azure DevOps 管道任务来检索 Key Vault 机密,但将它们全部放在一个 ARM 模板中会更好。
作为参考,当我尝试使用动态生成的 appSettings 和配置资源来定义更多 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 反馈帖子进行了投票。