Custom hostnames i ARM templates

Custom hostnames i en template kan godt være svær at angive korrekt, da
man nogen gange ikke har et custom hostname på et miljø, men andre gange
har. Mange tror fejlagtig at man blot kan bruge en condition
på ens
hostnameBinding
til at styre det, men det kan man ikke. Jeg vil i
dette indlæg vise en af løsningerne til det, en nested deployment.
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"customHostname": {
"type": "string",
"defaultValue": ""
},
"hostingPlanName": {
"type": "string",
"minLength": 1
},
"skuName": {
"type": "string",
"defaultValue": "S1"
},
"skuCapacity": {
"type": "int",
"defaultValue": 1
}
},
"variables": {
"appName": "[concat('webSite', uniqueString(resourceGroup().id))]",
"nonwwwHostname": "[parameters('customHostname')]",
"wwwHostname": "[concat('www.', parameters('customHostname'))]"
},
"resources": [
{
"apiVersion": "2018-02-01",
"condition": "[not(empty(parameters('customHostname')))]",
"name": "[concat(variables('appName'),'/',variables('nonwwwHostname'))]",
"type": "Microsoft.Web/sites/hostNameBindings",
"location": "[resourceGroup().location]",
"properties": {
"domainId": null,
"hostNameType": "Verified",
"siteName": "[concat(variables('appName'),'/',variables('nonwwwHostname'))]"
}
},
//Custom www hostnames
{
"apiVersion": "2018-02-01",
"condition": "[not(empty(parameters('customHostname')))]",
"name": "[concat(variables('appName'),'/', variables('wwwHostname'))]",
"type": "Microsoft.Web/sites/hostNameBindings",
"location": "[resourceGroup().location]",
"properties": {
"domainId": null,
"hostNameType": "Verified",
"siteName": "[concat(variables('appName'),'/',variables('wwwHostname'))]"
},
"dependsOn": [
"[concat('Microsoft.Web/sites/',concat(variables('appName'),'/hostNameBindings/',variables('nonwwwHostname')))]"
]
},
{
"apiVersion": "2015-08-01",
"name": "[parameters('hostingPlanName')]",
"type": "Microsoft.Web/serverfarms",
"location": "[resourceGroup().location]",
"tags": {
"displayName": "HostingPlan"
},
"sku": {
"name": "[parameters('skuName')]",
"capacity": "[parameters('skuCapacity')]"
},
"properties": {
"name": "[parameters('hostingPlanName')]"
}
},
{
"apiVersion": "2015-08-01",
"name": "[variables('appName')]",
"type": "Microsoft.Web/sites",
"location": "[resourceGroup().location]",
"tags": {
"[concat('hidden-related:', resourceGroup().id, '/providers/Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]": "Resource",
"displayName": "Website"
},
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]"
],
"properties": {
"name": "[variables('appName')]",
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('hostingPlanName'))]"
}
},
{
"apiVersion": "2014-04-01",
"name": "[concat(parameters('hostingPlanName'), '-', resourceGroup().name)]",
"type": "Microsoft.Insights/autoscalesettings",
"location": "[resourceGroup().location]",
"tags": {
"[concat('hidden-link:', resourceGroup().id, '/providers/Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]": "Resource",
"displayName": "AutoScaleSettings"
},
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]"
],
"properties": {
"enabled": false,
"name": "[concat(parameters('hostingPlanName'), '-', resourceGroup().name)]",
"targetResourceUri": "[concat(resourceGroup().id, '/providers/Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]"
}
}
]
}
Som giver følgende fejl, da vi slet ikke har angivet nogen custom hostname (den er tom i vores parameter array):
12:36:27 - Template deployment returned the following errors:
12:36:27 - 12:36:26 - Error: Code=InvalidTemplate; Message=Deployment template validation failed: 'The template resource 'webSitega4vb4sabeo74/' for type 'Microsoft.Web/sites/hostNameBindings' at line '104' and column '56' has incorrect segment lengths. A nested resource type must have identical number of segments as its resource name. A root resource type must have segment length one greater than its resource name. Please see https://aka.ms/arm-template/#resources for usage details.'.
12:36:27 - The deployment validation failed
Hvilket er mystisk, da vi jo har angivet i en condition
at vi
resourcen ikke skal med, hvis netop customHostname
er tom
"condition": "[not(empty(parameters('customHostname')))]",
Som sådan er det her ikke en fejl, men noget man ofte ser skrevet om på
nettet i diverse hjælpe foraer, fordi folk generelt set ikke forstår
detaljerne bag en ARM. Detajlen er at en deployment
altid vil
*validere *alle subresourcerne, også dem som er beskyttet af en
condition
og da vores ARM template er en stor deployment, så vil den
validere alt. Der findes mange løsninger på det her. Dem som man
oftest ser er:
- pille custom hostname ud af din ARM template, og kør den seperat i din release manager, som et seperat step, og lav her et tjek på om den skal køres
- smid din hostname i et array, og loop over den
- undlad helt at sætte custom hostnames op via ARM'en, og gør det manuelt bagefter
Nummer 1 kan jeg på en måde godt lide, men så ikke: det skaber et ekstra step i din deployment, som kan virke ugennemskuelig
- du vil gerne have den provisioneret, men alt du provisionere er i din ARM UNDTAGEN din custom hostname. Fremfinding af custom hostname setup i din ARM, virker ikke, da den ikke er med i din ARM
Nummer 2 er meget fin, men den overlader rigtig meget til fantasien. Jeg kan godt lide at utrykke mig meget explicit i min provisionering; noget ikke alle kan lide. Hvis jeg vil have sat et root hostname op samt et www subdomæne hertil, vil jeg gerne explicit have disse to med i min ARM. Laver man et loop i ARM'en så virker det ikke så explicit. Jeg er hellere ikke fan af loop'et i ARM'en da den er meget anonym, og der kan godt gå lidt tid før man ser at det ikke bare er et hostname man prøver at sætte op men flere.
Nummer 3 er et no go i min verden når snakken falder på custom hostnames. Jeg vil altid gerne have mulighed for at provisionere sådan noget her automatisk, så jeg fx kan sætte nye miljøer op.
Nested templates
Min nuværende løsning er lidt kringlet, hvis man ikke er vant til at
læse en ARM template, men jeg bruger en nested deployment template.
Tager vi udgangspunkt i min oprindelige ARM ovenover, fjerner de to
hostnameBindings
og i stedet tilføjer:
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2019-10-01",
"condition": "[not(empty(parameters('customHostname')))]",
"name": "customHostnameDeploy",
"dependsOn": [
"[variables('appName')]"
],
"properties": {
"mode": "Incremental",
"expressionEvaluationOptions": {
"scope": "outer"
},
"template": {
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"resources": [
{
"apiVersion": "2018-02-01",
"condition": "[not(empty(parameters('customHostname')))]",
"name": "[concat(variables('appName'),'/',variables('nonwwwHostname'))]",
"type": "Microsoft.Web/sites/hostNameBindings",
"location": "[resourceGroup().location]",
"properties": {
"domainId": null,
"hostNameType": "Verified",
"siteName": "[concat(variables('appName'),'/',variables('nonwwwHostname'))]"
}
},
//Custom www hostnames
{
"apiVersion": "2018-02-01",
"condition": "[not(empty(parameters('customHostname')))]",
"name": "[concat(variables('appName'),'/', variables('wwwHostname'))]",
"type": "Microsoft.Web/sites/hostNameBindings",
"location": "[resourceGroup().location]",
"properties": {
"domainId": null,
"hostNameType": "Verified",
"siteName": "[concat(variables('appName'),'/',variables('wwwHostname'))]"
},
"dependsOn": [
"[concat('Microsoft.Web/sites/',concat(variables('appName'),'/hostNameBindings/',variables('nonwwwHostname')))]"
]
}
]
}
}
}
Får vi en fin validering
13:08:06 - VERBOSE: 13:08:06 - Template is valid.
Valideringsmotoren tjekker ikke min sub deployment, da den nu faktisk
validere på baggrund af om condition
er opfyldt.
Hvorfor den gør det netop med sub deployments og ikke med standard resourcer, det ved jeg ikke. Jeg har aldrig fundet svaret.
Jeg kan godt lide denne løsning:
- min provisionering er samlet - hostname, webapp
- den er explicit - jeg vil gerne have to hostnames
Har man meget store løsninger bliver man nød til at hive det ud i som
moduler, og her kommer linked ARM templates til sin ret. Her kunne man
hive hostnameBindings ud for sig, og ikke bruge en nested template, men
en linked ARM deployment, og stadig have condition på. Her ligger det
ikke samlet mere, men når en løsning bliver meget stor, vokser ARM'en
hurtigt, og så giver det mening at have det hele splittet op, dog kommer
condition
stadig til sin ret, og vil blive valideret korrekt, så man
ikke får valideringsfejl.