9

我有两个使用部署槽、阶段和生产的 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 反馈帖子进行了投票

4

5 回答 5

8

抱歉,我没有太多时间来回答,而且您有很多问题主要与“什么是最好的方式……”有关,而答案总是“视情况而定”。

我发现更容易管理的一件事是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'))]"
  }

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

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

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

但要使上述工作正常进行,您需要授予应用程序对保管库“vaultName”的读取权限,如果您使用托管服务标识,这应该没问题。

于 2019-12-18T13:33:57.663 回答
7

可以将静态配置与部署时参考结合起来。您使用union 模板函数将静态配置(对象或数组)与您使用json 模板函数包装的一些部署时值结合起来。

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

  • 静态基础配置对象
  • 一个静态服务特定的配置对象
  • 部署时 Application Insights 配置值
[union(
  variables('appServiceBaseConfig'), 
  variables('appService1'), 
  json(
    concat(
      '{\"APPINSIGHTS_INSTRUMENTATIONKEY\":\"', 
      reference(concat('microsoft.insights/components/', variables('applicationInsightsName')), '2015-05-01').InstrumentationKey,
       '\"}')
    )
  )
]
于 2020-04-25T22:11:52.470 回答
0

对于任何来到这个线程的人,我想提供一个替代上述使用union. 我首先选择了那个选项,但开始发现很难使用形成 json 的字符串连接,所以想提供这个替代方案:

[union(
  variables('appServiceBaseConfig'), 
  variables('appService1'), 
  createObject(
    'APPINSIGHTS_INSTRUMENTATIONKEY', reference(concat('microsoft.insights/components/', variables('applicationInsightsName')), '2015-05-01').InstrumentationKey,
    'MY_VALUE', 'xyz'
  )
 )
]

这个额外建议答案的原因是,createObject如果您有多个键值对,该函数可以更容易地构造对象。

注意:如果您按照文档中的注释使用 Powershell cli,则当前仅在 ARM 中支持这样的多行函数。尝试在 Azure DevOps 中部署时,我花了一段时间才弄清楚这一点:facepalm:

于 2021-07-10T11:26:50.293 回答
0

只需向该线程添加输入以添加我对此问题的解决方案。我不确定覆盖应用程序设置问题是否已在普通 ARM 模板中得到修复,但发布管道步骤允许您设置在部署应用程序时部署的其他应用程序设置。因此,我在 ARM 模板中设置了我的所有天蓝色函数在创建时需要定义的动态应用程序设置/应用程序设置,然后我在发布管道中设置了其他应用程序设置(使用带有秘密变量的变量组来隐藏它们值)。像这样: 发布管道快照

于 2020-12-30T06:07:31.953 回答
0

回答这个问题:

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

是的,参数文件中的所有内容都需要在部署文件中定义。反之则不然。部署文件中定义的所有内容都不需要在参数文件中定义。部署文件中的定义可以有一个默认值:

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

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

于 2019-12-18T19:37:43.743 回答