ARM Templates multi-cloud fähig machen

Ich wusste ehrlich gesagt nicht wirklich, wie ich den Artikel nennen sollte. Daher vielleicht erst mal zur Klärung, um was es mir geht:

Im Internet findet man viele Templates für ARM Deployments, die mehr oder weniger geeignet sind, auch außerhalb der normalen globalen Azure Cloud verwendet zu werden. Daher wollte ich mal ein paar Punkte zeigen, mit denen man seine Templates flexibler verfassen kann.

Variablen und Parameter

Grundsätzlich sollte alles, was im Template mehrfach vorkommt, in Variablen definiert werden, und alles, was sich von Deployment zu Deployment ändern kann, in Parametern, letzteres vorzugsweise in einer eigenen Datei. Bei den Azure Quickstart Templates, die auf GitHub bereitgestellt werden, findet sich stets das Template an sich in der Datei azuredeploy.json und die Parameter in azuredeploy.parameters.json.

Location

Eine weitere Stelle für Flexibilität ist die Region, sprich Location. Die läßt sich bei den einzelnen Ressourcen natürlich direkt definieren, zum Beispiel mit:

[code]
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"name": "mystorage",
"apiVersion": "2016-01-01",
"location": "germanycentral",
(...)

Möchte man eine andere Location, dann erfordert das ein Editieren an unter Umständen mehreren Stellen - man hat ja meist mehr als eine Ressource. Schritt 1 wäre also, das durch eine Variable zu ersetzen (und bei der Gelegenheit auch gleich den Storagenamen in eine Variable packen):

[code]
"variables": {
"storageAccountName": "mystorage",
"storagelocation": "germanycentral",
(...)

"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"name": "[variables('storageAccountName')]",
"apiVersion": "2016-01-01",
"location": "[variables('storagelocation')]",
(...)

Schon besser. Es geht aber noch einfacher. Man legt die Ressource einfach in der selben Region an wie die zugehörige Ressourcengruppe und spart sich die Variable:

[code]
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"name": "[variables('storageAccountName')]",
"apiVersion": "2016-01-01",
"location": "[resourceGroup().location]",
(...)

Wir verwenden hier die ARM Funktion resourceGroup() und greifen direkt auf das location-Attribut zu. Damit richtet sich die Region einfach nach der Region der Ressourcegruppe.

Dynamische Werte mit concat

Oft wird mit Hilfe der Funktion concat() eine URI für Blobs erzeugt:
[code]
"osDisk": {
"name": "osdisk",
"vhd": {
"uri": "[concat('https://',variables('storageAccountName'),'.blob.core.windows.net/',variables('StorageAccountContainerName'),'/',variables('OSDiskName'),'.vhd')]"
},
(..)

Das funktioniert zwar, aber nur, solange man sich im Environment der globalen Azure Cloud bewegt. In Azure Deutschland zum Beispiel ist der StorageEndpoint eben nicht mehr "blob.core.windows.net", sondern "blob.core.cloudapi.de" und schon funktioniert das Template nicht mehr. Das ist übrigens die Stelle, an der viele Deployment Templates scheitern. Wie macht man das jetzt aber flexibler?

Das ist zugegebenermaßen schon etwas schwieriger, aber wir schauen uns das auch Schritt für Schritt an. Obige Zeile in universeller Schreibweise wäre wie folgt:
[code]
"osDisk": {
"name": "osdisk",
"vhd": {
"uri": "[concat(reference(resourceId('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))).primaryEndpoints.blob, variables('StorageAccountContainerName'), '/', variables('OSDiskName'), '.vhd')]"
},
(..)

Puh. Also dann mal ran an die Zeile, am besten den ersten Teil von innen nach außen:
[code]
resourceId('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))

Das beschert uns die ID des Storageaccounts, das zuvor (an anderer Stelle im Template) angelegt wurde.
[code]
reference(<der obigen ID>)

wiederum liefert uns eine Referenz darauf und hat nebenbei den Effekt, dass wir hier implizit sagen, dass die Storage Ressource bereits fertig angelegt sein muss, bevor es hier weitergehen kann. Ersetzt also ein "dependsOn". Wir greifen auf die Eigenschaften dieser Referenz zu:
[code]
(reference(<der obigen ID>)).primaryEndpoints.blob

Da die StorageRessource automatisch mit dem richtigen Endpunkt angelegt wurde, lesen wir den hier jetzt einfach wieder aus. Genial, oder? Der Rest der Zeile ist simples String verketten mit concat, also den Containernamen, den Disknamen und die Endung. Fertig. Und diese Zeile funktioniert in allen Azure Environments ohne Änderung.

Achtung: die Eigenschaft "primaryEndpoints" steht in älteren API Versionen nicht zur Verfügung. Bitte darauf achten, dass die Definition der Storage-Ressource eine API-Version von "2016-01-01" oder neuer verwendet.

Nochmal Achtung: Mit API Version "2016-01-01" hat sich auch das Schema geändert. Hier mal im Vergleich:
[code highlight="2"]
{
"apiVersion": "2015-06-15",
"type": "Microsoft.Storage/storageAccounts",
"name": "[variables('StorageAccountName')]",
"location": "[resourceGroup().location]",
"properties": {
"accountType": "[variables('storageAccountType')]"
}
},

und das neuere Schema:

[code highlight="2"]
{
"apiVersion": "2016-01-01",
"type": "Microsoft.Storage/storageAccounts",
"name": "[variables('storageAccountName')]",
"location": "[resourceGroup().location]",
"properties": {},
"sku": {
"name": "[variables('storageAccountType')]"
},
"kind": "Storage"
},

Aber das sollte kein Problem sein. Übrigens werden die Azure Quickstart Templates auf GitHub nach und nach alle umgestellt oder sind es schon größtenteils.