在部署级别配置资源组和 azure 函数

Provisioning resource group and azure function at deployment level

我编写了下面的脚本来执行以下操作:

问题出在设置AzureWebJobsStorage时,函数应用程序中的应用程序设置。 resourceId 函数无法解析存储帐户。查看 resourceId 函数的文档时,它指出:

When used with a subscription-level deployment, the resourceId() function can only retrieve the ID of resources deployed at that level. [docs]

但是现在不知道怎么解决!

模板:

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "resourceGroupName": {
      "type": "string"
    },
    "functionName": {
      "type": "string"
    },
    "storageAccName": {
      "type": "string"
    },
    "namingPrefix": {
      "type": "string"
    }
  },
  "variables": {
    "resourceGroupLocation": "North Europe",
    "planName": "[replace(concat(variables('resourceGroupLocation'), 'Plan'),' ','')]",
    "resourceGroupName": "[concat(parameters('namingPrefix'), '-', parameters('resourceGroupName'))]",
    "functionName": "[concat(parameters('namingPrefix'), '-', parameters('functionName'))]",
    "storageAccName": "[toLower(concat(parameters('namingPrefix'), parameters('storageAccName')))]"
  },
  "resources": [
    {
      "type": "Microsoft.Resources/resourceGroups",
      "apiVersion": "2018-05-01",
      "location": "[variables('resourceGroupLocation')]",
      "name": "[variables('resourceGroupName')]",
      "properties": {}
    },
    {
      "type": "Microsoft.Resources/deployments",
      "apiVersion": "2019-05-01",
      "name": "NestedTemplate",
      "resourceGroup": "[variables('resourceGroupName')]",
      "dependsOn": [
        "[variables('resourceGroupName')]"
      ],
      "properties": {
        "mode": "Incremental",
        "template": {
          "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
          "contentVersion": "1.0.0.0",
          "resources": [
            {
              "type": "Microsoft.Storage/storageAccounts",
              "apiVersion": "2019-04-01",
              "name": "[variables('storageAccName')]",
              "location": "[variables('resourceGroupLocation')]",
              "sku": {
                "name": "Standard_LRS",
                "tier": "Standard"
              },
              "kind": "Storage",
              "properties": {
                "networkAcls": {
                  "bypass": "AzureServices",
                  "virtualNetworkRules": [],
                  "ipRules": [],
                  "defaultAction": "Allow"
                },
                "supportsHttpsTrafficOnly": true,
                "encryption": {
                  "services": {
                    "file": {
                      "enabled": true
                    },
                    "blob": {
                      "enabled": true
                    }
                  },
                  "keySource": "Microsoft.Storage"
                }
              }
            },
            {
              "type": "Microsoft.Web/serverfarms",
              "apiVersion": "2016-09-01",
              "name": "[variables('planName')]",
              "location": "[variables('resourceGroupLocation')]",
              "sku": {
                "name": "Y1",
                "tier": "Dynamic",
                "size": "Y1",
                "family": "Y",
                "capacity": 0
              },
              "kind": "functionapp",
              "properties": {
                "name": "[variables('planName')]",
                "computeMode": "Dynamic",
                "perSiteScaling": false,
                "reserved": false,
                "targetWorkerCount": 0,
                "targetWorkerSizeId": 0
              }
            },
            {
              "type": "Microsoft.Web/sites",
              "apiVersion": "2016-08-01",
              "name": "[variables('functionName')]",
              "location": "[variables('resourceGroupLocation')]",
              "dependsOn": [
                "[variables('planName')]",
                "[variables('appInsightsName')]",
                "[variables('storageAccName')]"
              ],
              "kind": "functionapp",
              "identity": {
                "type": "SystemAssigned"
              },
              "properties": {
                "enabled": true,
                "hostNameSslStates": [
                  {
                    "name": "[concat(variables('functionName'), '.azurewebsites.net')]",
                    "sslState": "Disabled",
                    "hostType": "Standard"
                  },
                  {
                    "name": "[concat(variables('functionName'), '.scm.azurewebsites.net')]",
                    "sslState": "Disabled",
                    "hostType": "Repository"
                  }
                ],
                "siteConfig": {
                  "appSettings": [
                    {
                      "name": "AzureWebJobsStorage",
                      "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccName'), ';AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccName')), providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).key1)]"
                    },
                    {
                      "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
                      "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccName'), ';AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccName')), providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).key1)]"
                    },
                    {
                      "name": "WEBSITE_CONTENTSHARE",
                      "value": "[variables('functionName')]"
                    },
                    {
                      "name": "FUNCTIONS_WORKER_RUNTIME",
                      "value": "node"
                    },
                    {
                      "name": "WEBSITE_NODE_DEFAULT_VERSION",
                      "value": "10.14.1"
                    },
                    {
                      "name": "FUNCTIONS_EXTENSION_VERSION",
                      "value": "~2"
                    }
                  ]
                },
                "serverFarmId": "[variables('planName')]",
                "reserved": false
              }
            }
          ]
        }
      }
    }
  ]
}

使用以下行执行:

New-AzDeployment -Location "North Europe" -TemplateFile $TemplateFilePath -TemplateParameterFile $ParametersFilePath -namingPrefix $namingPrefix;

输出

 Resource Microsoft.Storage/storageAccounts 'testStorageAccount' failed with message '{
  "error": {
    "code": "ResourceNotFound",
    "message": "The Resource 'Microsoft.Storage/storageAccounts/testStorageAccount' under resource group '<null>'
was not found."
  }
}'

已编辑 -

抱歉,我之前抢了先机,问题出在 New-AzDeployment 上,它专门用于部署订阅级资源。

https://docs.microsoft.com/en-us/powershell/module/az.resources/new-azdeployment?view=azps-2.7.0

以上 link 的节选 -

The New-AzDeployment cmdlet adds a deployment at the current subscription scope. This includes the resources that the deployment requires.

An Azure resource is a user-managed Azure entity. A resource can live in a resource group, like database server, database, website, virtual machine, or Storage account. Or, it can be a subscription level resource, like role definition, policy definition, etc.

To add resources to a resource group, use the New-AzResourceGroupDeployment which creates a deployment at a resource group. The New-AzDeployment cmdlet creates a deployment at the current subscription scope, which deploys subscription level resources.

文档令人困惑,并且没有描述 resourceId() 在该级别的工作原理。它真的应该说:

When used in a subscription level deployment resourceId() can only get the resource IDs of resource groups (Microsoft.Resources/resourceGroups), policies (Microsoft.Authorization/policyAssignments), and role definitions (Microsoft.Authorization/roleDefinitions), as these are subscription level specific resources.

因为它实际上是这样工作的。 More docs here.

关于如何从这里开始,您只需在订阅级别在一个模板中部署资源组,在资源组级别在另一个模板中部署资源。

对于以后遇到此问题的任何人(可能是我自己),我被迫在我的 powershell 脚本中创建资源组,然后改用 new-AzResourceGroupDeployment。

为适应这一点,对部署模板的更改很少(我删除了资源组并将嵌套模板提升了一层)。但是,我也错误地访问了存储帐户密钥。这已在下面的代码中更新。

$resourceGroup = Get-AzResourceGroup -Name $resourceGroupName -ErrorAction SilentlyContinue
    if(!$resourceGroup)
    {
        Write-Host "Creating resource group '$resourceGroupName' in location '$resourceGroupLocation'";
        New-AzResourceGroup -Name $resourceGroupName -Location $resourceGroupLocation
    }
    else{
        Write-Host "Using existing resource group '$resourceGroupName'";
    }

    # Start the deployment
    Write-Host "Starting deployment...";
    if(Test-Path $parametersFilePath) {
        New-AzResourceGroupDeployment -ResourceGroupName $resourceGroupName -TemplateFile $TemplateFilePath -TemplateParameterFile $parametersFilePath;
    }
{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "resourceGroupName": {
      "type": "string"
    },
    "functionName": {
      "type": "string"
    },
    "storageAccName": {
      "type": "string"
    },
    "namingPrefix": {
      "type": "string"
    }
  },
  "variables": {
    "resourceGroupLocation": "North Europe",
    "planName": "[replace(concat(variables('resourceGroupLocation'), 'Plan'),' ','')]",
    "resourceGroupName": "[concat(parameters('namingPrefix'), '-', parameters('resourceGroupName'))]",
    "functionName": "[concat(parameters('namingPrefix'), '-', parameters('functionName'))]",
    "storageAccName": "[toLower(concat(parameters('namingPrefix'), parameters('storageAccName')))]"
  },
  "resources": [
    {
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2019-04-01",
      "name": "[variables('storageAccName')]",
      "location": "[variables('resourceGroupLocation')]",
      "sku": {
        "name": "Standard_LRS",
        "tier": "Standard"
      },
      "kind": "Storage",
      "properties": {
        "networkAcls": {
          "bypass": "AzureServices",
          "virtualNetworkRules": [],
          "ipRules": [],
          "defaultAction": "Allow"
        },
        "supportsHttpsTrafficOnly": true,
        "encryption": {
          "services": {
            "file": {
              "enabled": true
            },
            "blob": {
              "enabled": true
            }
          },
          "keySource": "Microsoft.Storage"
        }
      }
    },
    {
      "type": "Microsoft.Web/serverfarms",
      "apiVersion": "2016-09-01",
      "name": "[variables('planName')]",
      "location": "[variables('resourceGroupLocation')]",
      "sku": {
        "name": "Y1",
        "tier": "Dynamic",
        "size": "Y1",
        "family": "Y",
        "capacity": 0
      },
      "kind": "functionapp",
      "properties": {
        "name": "[variables('planName')]",
        "computeMode": "Dynamic",
        "perSiteScaling": false,
        "reserved": false,
        "targetWorkerCount": 0,
        "targetWorkerSizeId": 0
      }
    },
    {
      "type": "Microsoft.Web/sites",
      "apiVersion": "2016-08-01",
      "name": "[variables('functionName')]",
      "location": "[variables('resourceGroupLocation')]",
      "dependsOn": [
        "[variables('planName')]",
        "[variables('appInsightsName')]",
        "[variables('storageAccName')]"
      ],
      "kind": "functionapp",
      "identity": {
        "type": "SystemAssigned"
      },
      "properties": {
        "enabled": true,
        "hostNameSslStates": [
          {
            "name": "[concat(variables('functionName'), '.azurewebsites.net')]",
            "sslState": "Disabled",
            "hostType": "Standard"
          },
          {
            "name": "[concat(variables('functionName'), '.scm.azurewebsites.net')]",
            "sslState": "Disabled",
            "hostType": "Repository"
          }
        ],
        "siteConfig": {
          "appSettings": [
            {
              "name": "AzureWebJobsStorage",
              "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccName'), ';AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccName')), providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).keys[0].value)]"
            },
            {
              "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
              "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccName'), ';AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccName')), providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).keys[0].value)]"
            },
            {
              "name": "WEBSITE_CONTENTSHARE",
              "value": "[variables('functionName')]"
            },
            {
              "name": "FUNCTIONS_WORKER_RUNTIME",
              "value": "node"
            },
            {
              "name": "WEBSITE_NODE_DEFAULT_VERSION",
              "value": "10.14.1"
            },
            {
              "name": "FUNCTIONS_EXTENSION_VERSION",
              "value": "~2"
            }
          ]
        },
        "serverFarmId": "[variables('planName')]",
        "reserved": false
      }
    }
  ]
}

您已经 运行 使用了一些 "limitations" 的模板语言,这使得目前很难做到这一点(我们正在努力改进两者)。

1) Inline 嵌套部署在评估模板语言表达式([] 中的任何内容)时具有顶级部署的范围,这有时很方便(您可以共享变量示例),但通常会导致一些问题(如 resourceId 函数)。 ARM 一直以这种方式行事,但随着订阅级别部署的出现,它的问题有点多(你 运行 更喜欢它)。要解决此问题,您可以使用链接模板 - 我知道这并不总是理想的,但它们会按预期运行。

2) 您要 运行 关注的第二件事是,如果 ARM 认为您正在访问的资源不在同一部署中,则会立即评估 list*() 函数。由于#1,这就是 ARM 在这种情况下的想法,以及为什么尝试 concat() 资源 ID 仍然不起作用。

除此之外,远离 apiVersions 的 providers() 函数,它不是确定性的,并且该函数的结果可能会在您不知情的情况下发生变化。您在原始 post 中用于 listKeys 的代码在一段时间前确实有效,您可能会在示例中看到它,但平台的变化可能会破坏该功能的行为。文字 apiVersions 在 ARM 模板中总是更好。

我遇到了这个问题,发现他们最近通过在部署资源和函数范围上定义内部和外部范围更好地支持嵌套模板。

https://docs.microsoft.com/bs-latn-ba/azure/azure-resource-manager/templates/cross-scope-deployment?tabs=azure-cli#how-functions-resolve-in-scopes

https://www.youtube.com/watch?v=96XxVyxrhZI