适用于具有许多订阅主题的服务总线的 Azure ARM 模板

Azure ARM template for Service bus with Topics with many Subscriptions

您好,我有一个 ARM 模板,用于创建带有主题和订阅的 ServiceBus。但我只能完成 1 个主题 - 1 个订阅,因为我无法进行嵌套循环来为每个主题创建多个订阅。

我希望我可以执行这样的模板:

参数:

{
   "serviceBusName": "mybus",
   "topics": 
    [ 
      { 
         "topicName": "mytopic1",
         "subscriptions": [ "mysubscription1", "mysubscription2"]
      },
      { 
         "topicName": "mytopic2",
         "subscriptions": [ "mysubscription1"]
      }  
    ]
}

这是我的实际模板:

{
  "$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "ServiceBusNamespaceName": {
      "type": "string"
    },
    "ServiceBusSku": {
      "type": "string",
      "allowedValues": [
        "Basic",
        "Standard"
      ],
      "defaultValue": "Standard"
    },
    "ServiceBusSmallSizeTopicInMb": {
      "type": "int",
      "defaultValue": 1024
    },
    "ServiceBusMaxSizeTopicInMb": {
      "type": "int",
      "defaultValue": 1024
    },
    "Topics": {
      "type": "array"
    }
  },
  "variables": {
    "DefaultSASKeyName": "RootManageSharedAccessKey",
    "DefaultAuthRuleResourceId": "[resourceId('Microsoft.ServiceBus/namespaces/authorizationRules', parameters('ServiceBusNamespaceName'), variables('DefaultSASKeyName'))]",
    "SbVersion": "2017-04-01"
  },
  "resources": [
    {
      "apiVersion": "2017-04-01",
      "name": "[parameters('ServiceBusNamespaceName')]",
      "type": "Microsoft.ServiceBus/namespaces",
      "location": "[resourceGroup().location]",
      "sku": {
        "name": "[parameters('ServiceBusSku')]"
      },
      "tags": {
        "displayName": "ServiceBus"
      }
    },
    {
      "copy": {
        "name": "topics",
        "count": "[length(parameters('Topics'))]"
      },
      "type": "Microsoft.ServiceBus/namespaces/topics",
      "name": "[concat(parameters('ServiceBusNamespaceName'), '/', parameters('Topics')[copyIndex()].topic)]",
      "apiVersion": "2017-04-01",
      "location": "[resourceGroup().location]",
      "scale": null,
      "properties": {
        "defaultMessageTimeToLive": "P1D",
        "maxSizeInMegabytes": "[parameters('ServiceBusMaxSizeTopicInMb')]",
        "requiresDuplicateDetection": false,
        "duplicateDetectionHistoryTimeWindow": "PT10M",
        "enableBatchedOperations": true,
        "status": "Active",
        "supportOrdering": true,
        "autoDeleteOnIdle": "P10675199DT2H48M5.4775807S",
        "enablePartitioning": false,
        "enableExpress": false
      },
      "dependsOn": [
        "[resourceId('Microsoft.ServiceBus/namespaces', parameters('ServiceBusNamespaceName'))]"
      ],
      "resources": [
        {
          "apiVersion": "[variables('sbVersion')]",
          "name": "[parameters('Topics')[copyIndex()].subscription]",
          "type": "Subscriptions",
          "dependsOn": [
            "[parameters('Topics')[copyIndex()].topic]"
          ],
          "properties": {
            "lockDuration": "PT1M",
            "requiresSession": "false",
            "defaultMessageTimeToLive": "P7D",
            "deadLetteringOnMessageExpiration": "false",
            "maxDeliveryCount": "2",
            "enableBatchedOperations": "true",
            "autoDeleteOnIdle": "P7D"
          }
        }
      ]
    }
  ],
  "outputs": {
    "NamespaceDefaultConnectionString": {
      "type": "string",
      "value": "[listkeys(variables('DefaultAuthRuleResourceId'), variables('SbVersion')).primaryConnectionString]"
    },
    "DefaultSharedAccessPolicyPrimaryKey": {
      "type": "string",
      "value": "[listkeys(variables('DefaultAuthRuleResourceId'), variables('SbVersion')).primaryKey]"
    }
  }
}

以及实际模板的参数示例 json:

{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "ServiceBusNamespaceName": {
            "value": "mybus"
        },
        "ServiceBusSku": {
            "value": "Standard"
        },
        "Topics ": {
            "value": [
                {
                    "topic": "mytopic1",
                    "subscription": "mysubscription1"
                },
                {
                    "topic": "mytopic2",
                    "subscription": "mysubscription1"
                }
            ]
        }
    }
}

一般来说,有两种方法可以做这样的事情。您可以重组代码,使订阅成为顶级资源。或者您使用 copyIndex 的命名变体来实现嵌套循环。可以在此博客 post 及其评论中看到这两种变体。

Azure ARM Templates – Are nested loops possible?

但是,对于您的情况,唯一的选择是将订阅设为顶级资源。有关详细信息,请参阅aka.ms/arm-copy/#looping-on-a-nested-resource

这是取自上述博客 post 的完整示例。

{
    "$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "serviceBusNamespaceName": {
            "type": "string",
            "metadata": {
                "description": "Name of the Service Bus namespace"
            }
        },
        "topics":{
            "type": "array",
            "metadata": {
                "description": "List of topics"
            }
        },
        "subscriptions":{
            "type": "array",
            "metadata": {
                "description": "List of subscriptions"
            }
        }

    },
    "variables": {},
    "resources": [
        {
            "type": "Microsoft.ServiceBus/namespaces",
            "sku": {
                "name": "Standard"
            },
            "name": "[parameters('serviceBusNamespaceName')]",
            "apiVersion": "2017-04-01",
            "location": "[resourceGroup().location]",
            "properties": {}
        },
        {
            "type": "Microsoft.ServiceBus/namespaces/topics",
            "name": "[concat(parameters('serviceBusNamespaceName'), '/', parameters('topics')[copyIndex()])]",
            "apiVersion": "2017-04-01",
            "location": "[resourceGroup().location]",
            "copy": {
                "name": "topicLoop",
                "count": "[length(parameters('topics'))]"
            },
            "properties": {},
            "dependsOn": [
                "[concat('Microsoft.ServiceBus/namespaces/', parameters('serviceBusNamespaceName'))]"
            ]
        },
        {
            "type": "Microsoft.ServiceBus/namespaces/topics/subscriptions",
            "name": "[concat(parameters('serviceBusNamespaceName'), '/', parameters('subscriptions')[copyIndex()].topic, '/', parameters('subscriptions')[copyIndex()].subscription)]",
            "apiVersion": "2017-04-01",
            "location": "[resourceGroup().location]",
            "copy": {
                "name": "subscriptionLoop",
                "count": "[length(parameters('subscriptions'))]"
            },
            "properties": {},
            "dependsOn": [
                "topicLoop"
            ]
        }
    ]
}
{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
      "serviceBusNamespaceName": {
        "value": "rjtestsbnmspace"
      },
      "topics": {
        "value": ["topic1", "topic2"]
      },
      "subscriptions": {
        "value": [{
          "topic": "topic1",
          "subscription": "subscription1"
          },
          {
            "topic": "topic1",
            "subscription": "subscription2"
          },
          {
            "topic": "topic2",
            "subscription": "subscription3"
          }
        ]
      }
    }
  }

此 VM 示例使用命名 copyIndex - 当嵌套循环不针对资源本身时它起作用。

{
  "name": "[concat(parameters('vmName'), padLeft(copyIndex(1), 2, '0'))]",
  "type": "Microsoft.Compute/virtualMachines",
  "copy": {
    "name": "vmLoop",
    "count": "[parameters('vmCount')]"
  },
  "properties": {
    "osProfile": {
      "computerName": "[concat(parameters('vmName'), padLeft(copyIndex(1), 2, '0'))]"
    },
    "hardwareProfile": {
      "vmSize": "[parameters('vmSize')]"
    },
    "storageProfile": {
      "osDisk": {
        "name": "[concat(parameters('vmName'), padLeft(copyIndex(1), 2, '0'),'_OSDisk')]",
        "createOption": "FromImage",
        "managedDisk": {
          "storageAccountType": "[parameters('vmDiskType')]"
        }
      },
      "copy": [
        {
          "name": "dataDisks",
          "count": "[parameters('dataDiskCount')]",
          "input": {
            "caching": "[parameters('dataDiskCaching')]",
            "name": "[concat(parameters('vmName'), padLeft(copyIndex('vmLoop', 1), 2, '0'), '-dataDisk', padLeft(copyIndex('dataDisks'), 2, '0'))]",
            "lun": "[copyIndex('dataDisks')]",
            "createOption": "Empty",
            "diskSizeGB": "[parameters('dataDiskSize')]",
            "managedDisk": {
              "storageAccountType": "[parameters('vmDiskType')]"
            }
          }
        }
      ]
    }
  }
}

你可以使用嵌套部署来实现,高级视图将是这样的:

{
    "apiVersion": "2017-05-10",
    "name": "[concat('topicLoop-', copyIndex())]",
    "type": "Microsoft.Resources/deployments",
    "copy": {
        "name": "topicLoop",
        "count": "[length(parameters('topics'))]"
    },
    "dependsOn": [
       // this should on the service bus resource
    ]
    "properties": {
        "mode": "Incremental",
        "templateLink": {
            "uri": "nested_template_link"
        },
        "parameters": {
            "iteration": {
                "value": "[parameters('topics')[copyIndex()]]"
            }
        }
    }
},

然后在你的嵌套模板中你会得到这样的东西:

{
    "type": "Microsoft.ServiceBus/namespaces/topics",
    "name": "[concat(parameters('serviceBusNamespaceName'), '/', parameters('iteration').topicName)]",
    "apiVersion": "2017-04-01",
    "location": "[resourceGroup().location]"
},
{
    "type": "Microsoft.ServiceBus/namespaces/topics/subscriptions",
    "name": "[concat(parameters('serviceBusNamespaceName'), '/', parameters('iteration').topicName, '/', parameters('iteration').subscriptions[copyIndex()])]",
    "apiVersion": "2017-04-01",
    "location": "[resourceGroup().location]",
    "copy": {
        "name": "subscriptionLoop",
        "count": "[length(parameters('iteration').subscriptions)]"
    },
    "properties": {},
    "dependsOn": [
        "[concat(parameters('serviceBusNamespaceName'), '/', parameters('iteration').topicName)"]
    ]
}

这将允许您保留现有的参数结构并使模板更加灵活,其他答案解决方案并不是真正可维护的(您必须编辑 2 个参数,而不是一个,这很重要,待定)

我在使用最简单的 topics/subscription 结构和使用带有 sub-deployments 的嵌套循环时设法让它工作。其中一个重要因素是您使用内部范围进行子部署,并将要为其创建订阅的主题作为参数传递给 sub-deployment.

{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {

        // Since we don't want to create complex ARM structure, we use the most basic variable structure
        // to define the topics and subscriptions.
        // The complexity is in the ARM template.
        "topics": {
            "type": "array",
            "defaultValue":
            [
                {
                    "name": "articles",
                    "subscriptions": [
                        "diagnostics",
                        "orders"
                    ]
                },
                {
                    "name": "customers",
                    "subscriptions": [
                        "diagnostics",
                        "orders"
                    ]
                },
                {
                    "name": "orders",
                    "subscriptions": [
                        "diagnostics",
                        "orders"
                    ]
                }
            ]
        }
    },
    "variables": {
        "baseName": "[resourceGroup().name]",
        "serviceBusName": "[variables('baseName')]",
        "topics": "[parameters('topics')]"
    },
    "resources": [
        {
            //
            // Create the Service-bus itself.
            //
            "name": "[variables('serviceBusName')]",
            "type": "Microsoft.ServiceBus/namespaces",
            "apiVersion": "2018-01-01-preview",
            "location": "[resourceGroup().location]",
            "sku": { "name": "Standard" },
            "kind": "Messaging",
            "properties": {},
            "resources": [
            ]
        },
        {
            //
            // Now we are going to create the topics. This is a loop throught the topics variable.
            //
            "copy": {
                "name": "topics-loop",
                "count": "[length(variables('topics'))]"
            },
            "name": "[concat(variables('serviceBusName'),'/', variables('topics')[copyIndex()].name)]",
            "type": "Microsoft.ServiceBus/namespaces/topics",
            "apiVersion": "2017-04-01",
            "dependsOn": [
                "[concat('Microsoft.ServiceBus/Namespaces/', variables('serviceBusName'))]"
            ],
            "properties": {}
        },
        {
            //
            // The following structure looks rather complex. Since nested loops are not supported for resources (it is for properties),
            // we create a deployment that is looped and passes the required variables as parameters to the inner deployment.
            // In the inner deployment another loop is created, and this creates the subscriptions.
            //

            "copy": {
                "name": "subscriptions-outer-loop",
                "count": "[length(variables('topics'))]"
            },

            // The name of the deployment.
            "name": "[concat(variables('serviceBusName'),'-',variables('topics')[copyIndex()].name,'-subscriptions')]",
            "type": "Microsoft.Resources/deployments",
            "apiVersion": "2019-10-01",
            "properties": {
                "mode": "Incremental",

                // Set the scope to Inner. Everything from the outer deployment that is needed in the inner template,
                // can be moved via parameters. You cannot do nested loops if you let te scope be outer.
                "expressionEvaluationOptions": {
                    "scope": "inner"
                },

                // The following parameters are defined by the inner template.
                "parameters": {
                    "serviceBusName": {
                        "value": "[variables('serviceBusName')]"
                    },

                    // The current topic, this is an important one, 
                    // it communicates the current topic to the inner template
                    "currentTopic": {
                        "value": "[variables('topics')[copyIndex()]]"
                    }
                },
                "template": {
                    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
                    "contentVersion": "1.0.0.0",
                    "parameters": {
                        // Define the parameters that are set by the outer template.
                        "serviceBusName": { "type": "string" },
                        "currentTopic": { "type": "object" }
                    },
                    "resources": [
                        {
                            // The inner loop. This will create the subscriptions.
                            "copy": {
                                "name": "subscriptions-inner-loop",
                                "count": "[length(parameters('currentTopic').subscriptions)]"
                            },
                            "name": "[concat(
                                    parameters('serviceBusName'),'/', 
                                    parameters('currentTopic').name, '/',
                                    parameters('currentTopic').subscriptions[copyIndex()])]",
                            "type": "Microsoft.ServiceBus/namespaces/topics/subscriptions",
                            "apiVersion": "2017-04-01",
                            "properties": {}
                        }
                    ]
                }
            },

            //This depends on the outer loop.
            "dependsOn": [ "topics-loop" ]
        }
    ]
}