当前位置: 首页 > 知识库问答 >
问题:

具有许多应用程序的Azure函数的ARM模板针对不同环境和插槽的设置

龙佐
2023-03-14

我有两个Azure功能应用程序,它们使用部署时段、阶段和生产。这两个Azure函数应用程序在应用程序设置中有大约50个键:值对,用于定义各种API键、应用程序行为、连接字符串等。

我想将这两个Azure功能应用程序部署到五个不同的环境(CI、开发、QA、STG、PROD)。我相信使用ARM模板将这些资源部署到Azure比Azure CLI更好。我将在Azure DevOps发布管道中创建任务以实现这一点。

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

参数文件中定义的所有参数都必须在parameters对象的部署模板文件中定义,这也是真的吗?

我可以从参数文件中传入一个带有NameValuePairs的对象数组吗?这样我就不必在部署文件的顶部和siteConfig下定义整个参数列表。功能应用的应用设置?

这里的留档显示您只能提供字符串数组或具有许多key:值的单个对象。但是appSet是一个对象数组,其中每个对象有3个键:值对。

这就是资源在部署文件中的样子。我想简单地引用参数文件中的整个对象数组,但是看起来留档声明我在部署文件的顶部定义了所有50~参数,然后参数文件在由Azure CLI或Azure执行时重写这些参数运营模式任务。

        {
            "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文件。

问题是。。。

在参数中定义所有应用程序设置。json意味着我不能使用模板函数获取连接字符串或Azure密钥保险库值。

这就是我开始将一些应用程序设置移动到部署模板以使用模板功能的时候。因此,不要在参数中使用APPINSIGHTS_INSTRUMENTATIONKEY和其他AzureWebJobs*应用程序设置。json文件中,我在Microsoft的“properties”对象中提供了siteConfig对象。网站资源和Microsoft。网站/站点/插槽资源。

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

到目前为止,我觉得进行体面的ARM模板部署的唯一方法是部署资源,而不使用siteConfig对象或配置资源来应用应用程序设置,然后使用Azure CLI来部署应用程序设置。我想我可以学习如何使用Azure CLI或Azure运营模式管道任务检索Key Vault机密,但将其全部放在单个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:

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

共有3个答案

任飞鸣
2023-03-14

回答这篇文章:

参数文件中定义的所有参数都必须在parameters对象的部署模板文件中定义,这也是真的吗?

是,参数文件中的所有内容都需要在部署文件中定义。事实并非如此。部署文件中定义的所有内容不需要在参数文件中定义。部署文件中的定义可以具有默认值:

"location": {
  "type": "string",
  "defaultValue": "Central US",
  "metadata": {
    "description": "Specifies the Azure location where the key vault should be created."
  }
},

或者,可以在发布任务中将参数作为覆盖参数传入。

高经艺
2023-03-14

可以将静态配置与部署时引用相结合。使用Union模板函数将静态配置(对象或数组)与使用json模板函数包装的部署时值相结合。

在下面的示例中,我结合:

  • 静态基本配置对象
[union(
  variables('appServiceBaseConfig'), 
  variables('appService1'), 
  json(
    concat(
      '{\"APPINSIGHTS_INSTRUMENTATIONKEY\":\"', 
      reference(concat('microsoft.insights/components/', variables('applicationInsightsName')), '2015-05-01').InstrumentationKey,
       '\"}')
    )
  )
]
鲁涵映
2023-03-14

对不起,我没有太多的时间来回答你,你有一大堆问题,主要是关于“什么是……的最佳方式?”,答案总是“视情况而定”。

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

"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()获取资源的键,因此它仅在某些时候有用,或者像本例一样,如果您希望添加对同样在同一模板中创建的app insights的引用,如果您将设置作为参数传递,则这是不可能的。

  {
    "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'))]"
  }

您确实应该在部署时解析所有可以解析的内容,这样就可以将存储帐户(例如)连接字符串安全地添加到模板中,并且只在部署时解析。

另一个方便的提示是使用key Vault来存储任何无法在模板中解析的安全凭据、api密钥、连接字符串等。你提到需要它们,但然后你将它们提交给模板中的源代码控制...嗯,它们不会保密很长时间(另一个提示,确保它们都使用securestring而不是字符串类型,否则门户将在资源组的部署日志中公开它们)。您可以从应用程序设置访问密钥保管库,如下所示:

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

但要使上述功能正常工作,您需要为应用程序提供对vault“vault Name”的读访问权限,如果您使用托管服务标识,这应该可以。

 类似资料:
  • 我想做的最终游戏是有一个ARM模板,创建一个Azure功能应用程序,里面有两个功能,前面是API管理。 看起来像是在ARM模板中这样做,我需要在模板中创建单个函数本身,而不是简单地创建函数app,然后部署我的C#代码(通常会创建函数)。这是因为在模板中创建API管理资源之前,我需要函数存在,否则我将不得不返回并配置endpoint等以指向我的函数应用程序。 如果这些都是错的。纠正我。 我在博客文章

  • 我已经开发了azure定时器触发函数。我从功能应用程序的应用程序设置中获取计时器时间表,如下所示。 函数. json 这对于给定的静态时间表来说工作正常。但是当用户需要更改时间表时,该时间表应该能够根据另一个Web应用程序的用户要求进行更改。 我正在努力从外部应用程序动态地更改计划参数。我尝试的是部署一个ARM模板,从下面的ARM模板注入新的时间表值。 但是,这不是重写现有的appSettings

  • 我正在尝试创建一个脚本来使用powershell在Azure Web应用程序插槽中设置/更新应用程序设置。使用使用Azure Power Shell将应用程序设置添加到现有Azure Web应用程序中的示例,它可以工作。 我的问题是我希望“槽位设置”为真。在我发现的所有例子中以及在resources.azure.com,设置总是名称/值对,没有属性将值指定为“插槽设置”。 这甚至可能是脚本吗? 谢

  • 我正在使用一种通信标准(我无法控制),该标准定义要在各种数据包中发送/接收的数据项。 每个项目都由自定义类型和类型相关信息定义。这些项目很少会改变。我希望能够将项目构造函数的范围限制在一个地方,并将那里的所有项目定义为(类似于枚举)。 目前,我有一个用于类型的枚举和一个用于项的类。 这已经变得难以管理,我正在寻找更好的模式/结构。 我可以有一个抽象的Item类,以及每种类型的子类。使用此设置,我不

  • 我正在使用Grails 3.3.1和Spring boot。构建工具是gradle。所有与应用程序相关的属性都在application.yml文件中配置。 现在我想访问不同的。用于不同环境[开发、测试等]的yml文件。为此,我创造了不同的。每个环境的yml文件。用于运行服务器的命令是:grails-Dgrails。env=test run app Now当我访问任何属性时,它会提供来自应用程序测试

  • 我的团队希望使用ARM模板为我们的Web应用程序启用Application Insights Live Profiler。Application Insights的这一性能特性在以下链接https://docs.microsoft.com/en-us/azure/application-insights/app-insights-profiler中得到了解释。但是,我找不到任何关于如何使用ARM模