我有两个使用部署槽,阶段和生产的Azure Function应用程序。这两个Azure Function应用在应用程序设置中具有大约50〜key:value对,以定义各种API key ,应用程序行为,连接字符串等。
我想将这两个Azure Function应用程序部署到五个不同的环境(CI,DEV,QA,STG,PROD)。我相信使用ARM模板将这些资源部署到Azure是比Azure CLI更好的选择。我将在Azure DevOps发布管道中创建任务以实现此目的。
为了将ARM模板分解为易于维护的内容,我想为每个环境创建一个ARM模板参数文件。在定义Azure功能的部署文件时,要定义的属性之一是siteConfig object,您可以在其中使用NameValuePair对象定义appSettings对象。对于每种环境,舞台和生产插槽将具有不同的API key ,连接字符串和应用程序行为。我的部署文件使用生产插槽和舞台插槽创建了Azure Function应用。在部署文件中,我必须提供两次appSettings NameValuePair对象。然后,我必须为每个环境创建5个不同的参数文件。将其乘以2,因为我有两个插槽。
是否还必须在参数对象的部署模板文件中定义参数文件中定义的所有参数?
我是否可以仅从参数文件中传入带有NameValuePairs的对象数组,这样就不必在顶部的部署文件中以及函数app的siteConfig.appSettings下定义完整的参数列表?
The documentation here显示您只能提供字符串数组或具有许多key:values的单个对象。但是appSettings是一个对象数组,其中每个对象都有3个键:值对。
这就是部署文件中资源的外观。我想简单地从参数文件中引用整个对象数组,但是看起来像文档状态一样,我在部署文件的顶部定义了全部50个参数,然后由Azure CLI或Windows Azure执行时,参数文件将覆盖这些参数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')]"
]
}
]
}
使用此解决方案,我不必为每个环境都设置一堆parameter.json文件。
问题...
在parameters.json中定义所有应用程序设置意味着我无法使用模板函数来获取连接字符串或Azure Key Vault值。
这是我开始将一些应用程序设置移至部署模板以使用模板功能的时间。因此,我没有在parameters.json文件中使用APPINSIGHTS_INSTRUMENTATIONKEY和其他AzureWebJobs *应用程序设置,而是在siteConfig object和Microsoft.Web/Sites resource的“属性”对象中提供了Microsoft.Web/Sites/Slots resource。
这是真正的麻烦-部署运行时,它通过功能app应用了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对象或config资源的资源来应用应用程序设置,然后跟上Azure CLI来部署应用程序设置。我想我可以学习如何使用Azure CLI或Azure DevOps管道任务来检索Key Vault secret ,但是最好将它们全部放在一个ARM模板中会更好。
作为引用,当我尝试使用动态生成的appSettings和config资源定义更多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:
I raised a github issue to have them fix the problem with ARM templates replacing all of the application settings on each deployment.
FWIW-I also voted on some Azure feedback post。
最佳答案
抱歉,我没有太多时间来回答,您还有很多问题,这些问题主要与“什么是最好的方法...”有关,而答案始终是“取决于情况”。
我发现更容易管理的一件事是,您可以创建siteConfig
类型的顶层资源(而不是使用Microsoft.Web/sites/config
来设置所有应用程序设置)(我发现有时它很有用,因为您可以在创建网站后创建它们),因此依赖项(尚未在其他地方设置),将配置和站点分离开来会很方便。
"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()获取资源的键,因此它仅在某些时候有用,例如本示例,如果您想添加对在同一模板中创建的应用洞察力的引用,则在将设置作为参数传递时,则不可能。
{
"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'))]"
}
您实际上应该在部署时解决所有问题,因此可以将存储帐户(例如)连接字符串安全地添加到模板中,并且仅在部署时解决。
另一个方便的技巧是使用 keystore 来存储模板中无法解析的任何安全凭据,API key ,连接字符串等。您提到需要它们,但是随后将它们提交给模板中的源代码控制...好吧,它们不会长时间保密(另一个提示,请确保它们全部使用securestring而不是字符串类型,否则门户网站会将其公开。资源组的部署日志)。您可以通过以下应用程序设置访问 key 仓库:
"secretConnectionString": "[concat('@Microsoft.KeyVault(SecretUri=https://', variables('vaultName'), '.vault.azure.net/secrets/my-connection-string/)')]",
但是,要使上述方法起作用,您将需要为应用程序授予对存储库“vaultName”的读取访问权限,如果使用托管服务身份,这应该很好。
关于azure - 具有针对不同环境和插槽的许多appSettings的Azure功能的ARM模板,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59383303/