2015-01-27
Altitude testar att deploy Azure ARM templates
Om du har varit uppmärksam så kanske du har sett den här nyheten/förändringen i Azure portalen
Nu har du alltså valet att manuellt deploya en VM till den nya Azure Resource Manager (ARM) stacken ifrån portalen.
Inte nog med det så kan du även deploya dina applikationer med ARM templates via portalen.
Men vi ska inte göra något av det utan vi ska använda oss av Visual Studio (VS) för att skapa en ARM template och sedan deploya den via VS och Powershell.
Först så måste man installera MS Visual Studio, jag kör med 2015 Community Edition. Sen måste man även ha Azure Tools, i detta ögonblick då jag skriver så kör jag med Azure Tools 2.7 som lättast installeras via Microsoft Web Platform Installer.
I VS skapar jag ett projekt och väljer här att börja med en färdig template som jag sedan ska modifiera.
New -> Project -> Templates -> Visual C# -> Cloud -> Azure Resource Group
Jag valde mallen som heter ”Windows Server Virtual Machines with Load Balancer”, detta ger dig ett projekt med 3 filer.
En Powershell fil som är ett exempel på hur man depolyar mallen och sedan två json filer. [Name].json är filen vi kommer att jobba med. [Name].param.dev.json är en fil där man kan fördefiniera parametrar till ”[name].json”-filen när man deployar den till Azure. Det är en ganska ointressant fil men vi kommer att redigera den senare, för det kommer att ställa till besvär om man struntar i den helt.
Vi öppnar nu [name].json, se till att hitta fönstret i VS som heter JSON Outline den kommer att göra ditt liv mycket lättare när du jobbar med den har typen av filer. I våran [name].json har ni nu, ett gäng parametrar, en hög variabler och några resurser.
Parametrarna är som alla förstår in-data till mallen, tänk att du ska skapar en mall för en applikation och sedan vill du deploya den till 20 olika kunder Kund1, Kund2 Kund3 … då vill man inte ha hårdkodade namn i sin mall.
Sen har vi variabler, dessa är bra av många anledningar, du kan spara mycket tangenttryckningar på att skapa en variabel. Säg att varje VM ska ha ett namn som är (ApplikationensNamn + kundprefix + löpnummer) då är det en typisk variabel. En variabel kan innehålla andra variabler och parametrar samt statisk test och funktioner. En funktion som du kommer att se mycket är. ”Concat” Concat är en simpel funktion som helt enkelt parar samman alla parametrar den får. Variabeln ovan skulle t.ex. bli
”vmName” : ”[concat(variable(’appname’),parameter(’customerprefix’),’1′)]”.
Andra funktioner som vi kommer att använda är ”copyindex” och ”padLeft”, men det återkommer vi till senare.
Nu när vi har våra variabler och parametrar så behöver vi resurser som kan nyttja all in-data vi har skapat oss och i detta exempel så har vi från början redan fått ett par resurser.
Vi har, SorageAccount, AvailabilitySet, VirtualNetwork, NetworkInterface, Loadbalancer och en VM
Men vi skulle ju ha två VMs och en LB, har vi bara en VM och en LB nu eller? Låt oss kolla i json-filen. Jag börjar med att markera min VM.
Namnet på VM’n är VMname + copyindex() vilket innebär att den kommer få parametern Vmnamn plus ett löpnummer varje gång den kopieras. Detta gör man med en copy. Längre ner så hittar vi kodsnutten
”copy”: {
”name”: ”virtualMachineLoop”,
”count”: ”[variables(’numberOfInstances’)]”
},
Denna säger att alla inställningar för denna VM ska skapas ”Count” antal gånger. Vi ser att Count bestäms av en variabel som heter numberOfinstances.
Om vi går till variabeln som heter numberOfinstances ser vi där att den är lika med 2
”numberOfInstances”: 2,
Om vi vill, skulle vi kunna skapa en ny parameter som istället heter numberOfinstances och sedan ändra så att ”copy” såg ut såhär.
”copy”: {
”name”: ”virtualMachineLoop”,
”count”: ”[parameters(’numberOfInstances’)]”
},
Nu ska du komma ihåg att det inte bara är i VMn som använder denna variable, utan vi hittar den även på andra ställen, i detta fall så används numberOfInstances också för nätverkskorten eftersom varje VM behöver ha varsitt nätverkskort.
Förut talade jag om funktioner och jag kommer nu använda två eller tre av dessa för att ändra datornamnet på min VM. Eftersom jag inte nöjer mig med hur den döps i exemplet så kommer jag ändra på det. Slutresultatet jag vill uppnå är, ([prefix]+’vmn’+[löpnummer]+’applicationName’)
Slutresultatet blir :
”name”: ”[concat(parameters(’CustomerPrefix’),’vmn’, padLeft(copyindex(1),2,’0′),variables(’NamingSufix’))]”,
Där ’CustomerPrefix’ parametern sätts av administratören när mallen deployas,
’vmn’ alltid en fast text,
padLeft(copyindex(1),2,’0′) sätter löpnumret, Här har jag gjort två förändringar. I originalet så skulle löpnummersekvensen blir 0,1,2,3,4. Jag vill att den ska börja på 1 därför ändrar jag copyindex() till copyindex(1). Därefter så vill jag att löpnumret alltid ska vara minst två tecken långt. För att lösa detta använder jag mig av padLeft som har tre parametrar första är indata, andra minsta antalet tecken i utdatan och slutligen vad som ska fyllas på med från vänster.
Hade jag skrivit padLeft(’hej’,5,’X’) blir resultatet XXhej.
Slutligen så har jag en variabel som heter NamingSufix som innehåller applikationens namn.
Innan jag deployar min mall så kommer jag göra ett flertal förändringar bara för att få bort så många parametrar som möjligt. Jag kommer istället ersätta dem med variabler som dynamiskt sätter rätt värde.
I o med att jag ändrar antalet parametrar så kommer vi nu till problemet med ”[name].param.dev.json”-filen som jag pratade om innan. Eftersom jag har tagit bort en massa parametrar så ligger dessa fortfarande kvar i param filen och skulle jag försöka deploya mallen från VS nu så skulle den skicka med ett flertal parametrar som inte finns och den skulle misslyckas. Därför går jag in i ”[name].param.dev.json”-filen och tar bort parametrar som inte används.
Jag kommer även lägga till en NSG regel och knyta den till ett av mina subnät.
{
”apiVersion”: ”2015-05-01-preview”,
”type”: ”Microsoft.Network/networkSecurityGroups”,
”name”: ”[variables(’NSGnameDMZ’)]”,
”location”: ”[resourceGroup().location]”,
”properties”: {
”securityRules”: [
{
”name”: ”rdp_rule”,
”properties”: {
”description”: ”Allow RDP”,
”protocol”: ”Tcp”,
”sourcePortRange”: ”*”,
”destinationPortRange”: ”3389”,
”sourceAddressPrefix”: ”Internet”,
”destinationAddressPrefix”: ”*”,
”access”: ”Allow”,
”priority”: 100,
”direction”: ”Inbound”
}
},
{
”name”: ”web_rule”,
”properties”: {
”description”: ”Allow WEB”,
”protocol”: ”Tcp”,
”sourcePortRange”: ”*”,
”destinationPortRange”: ”80”,
”sourceAddressPrefix”: ”Internet”,
”destinationAddressPrefix”: ”*”,
”access”: ”Allow”,
”priority”: 101,
”direction”: ”Inbound”
}
}
]
}
}
Om ni som jag kör Azure Tools 2.7 kommer ni se att detta inte riktigt är klart ännu, NSG regeln kommer få en generisk ikon och namnet kommer inte bli lika beskrivande som för de andra resurserna.
Jag har även gjort ett försökt att skapa en S2S VPN Gateway men det känns som det är någon version bort innan det börjar fungera bra.
Jag modifierar även mitt vNet och skapar ett nytt subnät som jag sedan knyter till min NSG regel.
”name”: ”[variables(’DMZSubnetName’)]”,
”properties”: {
”addressPrefix”: ”10.0.2.0/24”,
”networkSecurityGroup”: {
”id”: ”[resourceId(’Microsoft.Network/networkSecurityGroups’, variables(’NSGnameDMZ’))]”
}
}
Efter mycket test och fel så kanske man frågar sig själv varför?
För att nästa gång du ska göra samma deployment kanske du bara behöver ändra på två parametrar. 🙂
Men varför kan jag inte göra det i Powershell som jag alltid har gjort?
Klar du kan, men problemet som jag ser det i Powershell är att du måste göra all felkontroll manuellt. En ARM template är som en Powershell DSC. Du bestämmer hur slutresultatet ska se ut och vilka beroenden dom har, sen låter du Azure göra jobbet.
Det tar inte slut här, du kan nästla fler olika templates tillsammans och du kan även lägga in DSC konfiguration i din ARM template, så när din VM är uppsatt så kommer den att konfigureras utefter ett önskat tillstånd.
Tänk om alla applikationsutvecklare även skickade med en DSC som installerade, konfigurerade och reparerade applikationen. Samt en ARM template som konfigurerade hela infrastrukturen i Azure baserat på 2-3 parametrar.
Jag tror att vi snart är där.
Längst ner på sidan hittar du den slutliga json-filen som jag har skapat och nu kan vi testa att deploya den på riktigt. Högerklicka på projektet och välj, deploy –> new deployment…
Logga in med dina Azure uppgifter och skapa en ny resursgrupp, välj land och fyll i alla parametrar genom att klicka på knappen ”Edit Parameters…” när du är klar klicka på deploy. Nere i Output-önstret kan du följa processen.
När din deployment är klar kommer du upptäcka två saker.
- Inget syns i den gamla portalen
- Allt du just har skapat ligger under nya kategorier i den nya portalen. Storage accounts & Storage accounts (classic) osv.
O vad säger det oss? Jo, att det som vi såg som självklart innan nu är ”Classic” och vart tror du vinden blåser?
Bättre att hoppa på tåget redan nu tänkte jag, men som sagt det är inte riktigt klar ännu. En rolig sak är när man öppnat sin ”resource group” och tittar vad som finns i den så ligger allt där. Allt utom vissa saker!?
Jag har i mitt exempel även skapat en localnetworkGateway och en VirtualNetworkGateway dessa syns bara när man väljer att ta bort gruppen, då utan ikoner.
{
”$schema”: ”https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#”,
”contentVersion”: ”1.0.0.0”,
”parameters”: {
”CustomerPrefix”: {
”type”: ”string”,
”defaultValue”: ”zxx”,
”metadata”: {
”description”: ”Customer naming prefix”
}
},
”LoadBalancerIP”: {
”type”: ”string”,
”defaultValue”: ”10.0.1.8”,
”metadata”: {
”description”: ”Ip of the internal LB”
}
},
”adminPassword”: {
”type”: ”securestring”,
”metadata”: {
”description”: ”Admin password”
}
},
”imagePublisher”: {
”type”: ”string”,
”defaultValue”: ”MicrosoftWindowsServer”,
”metadata”: {
”description”: ”Image Publisher”
}
},
”imageOffer”: {
”type”: ”string”,
”defaultValue”: ”WindowsServer”,
”metadata”: {
”description”: ”Image Offer”
}
},
”imageSKU”: {
”type”: ”string”,
”defaultValue”: ”2012-R2-Datacenter”,
”metadata”: {
”description”: ”Image SKU”
}
},
”vmSize”: {
”type”: ”string”,
”defaultValue”: ”Standard_A1”,
”metadata”: {
”description”: ”This is the allowed list of VM sizes”
}
}
},
”variables”: {
”availabilitySetName”: ”[concat(parameters(’CustomerPrefix’),’avs01adfs’)]”,
”storageAccountType”: ”Standard_LRS”,
”vNetName”: ”[concat(parameters(’CustomerPrefix’),’vnt01infra’)]”,
”vnetID”: ”[resourceId(’Microsoft.Network/virtualNetworks’,variables (’vNetName’))]”,
”gatewaySubnetRef”: ”[concat(variables(’vnetID’),’/subnets/GatewaySubnet’)]”,
”DMZSubnetName”: ”[concat(parameters(’CustomerPrefix’),’sub01dmz’)]”,
”DMZSubnetRef”: ”[concat(variables(’vnetID’),’/subnets/’,variables (’DMZSubnetName’))]”,
”ProdSubnetName”: ”[concat(parameters(’CustomerPrefix’),’sub01prod’)]”,
”ProdSubnetRef”: ”[concat(variables(’vnetID’),’/subnets/’,variables (’ProdSubnetName’))]”,
”numberOfInstances”: 2,
”NamingSufix”: ”app1”,
”adminUserName”: ”aadmin”,
”LoadBalancerName”: ”[concat(parameters(’CustomerPrefix’),’lbs01adfs’)]”,
”networkInterfaceName”: ”InternalNic”,
”NSGnameDMZ”: ”[concat(parameters(’CustomerPrefix’),’nsg02dmz’)]”,
”StorageAccountName”: ”[concat(parameters(’CustomerPrefix’),’sta01infra’)]”,
”lbID”: ”[resourceId(’Microsoft.Network/loadBalancers’,variables(’loadBalancerName’))]”,
”PublicGatewayIPName”: ”[concat(variables (’vNetName’),’gwip’)]”,
”gatewayName” : ”[concat(parameters(’CustomerPrefix’),’gwn01infra’)]”
},
”resources”: [
{
”apiVersion”: ”2015-05-01-preview”,
”type”: ”Microsoft.Storage/storageAccounts”,
”name”: ”[variables(’StorageAccountName’)]”,
”location”: ”[resourceGroup().location]”,
”tags”: {
”displayName”: ”StorageAccount”
},
”properties”: {
”accountType”: ”[variables(’storageAccountType’)]”
}
},
{
”apiVersion”: ”2015-05-01-preview”,
”type”: ”Microsoft.Compute/availabilitySets”,
”name”: ”[variables(’availabilitySetName’)]”,
”location”: ”[resourceGroup().location]”,
”tags”: {
”displayName”: ”AvailabilitySet”
}
},
{
”apiVersion”: ”2015-05-01-preview”,
”type”: ”Microsoft.Network/virtualNetworks”,
”name”: ”[variables (’vNetName’)]”,
”location”: ”[resourceGroup().location]”,
”dependsOn”: [
”[concat(’Microsoft.Network/networkSecurityGroups/’, variables(’NSGnameDMZ’))]”
],
”tags”: {
”displayName”: ”VirtualNetwork”,
”Description”: ”ADFS Role”
},
”properties”: {
”addressSpace”: {
”addressPrefixes”: [
”10.0.0.0/16”
]
},
”subnets”: [
{
”name”: ”[variables(’ProdSubnetName’)]”,
”properties”: {
”addressPrefix”: ”10.0.1.0/24”
}
},
{
”name”: ”[variables(’DMZSubnetName’)]”,
”properties”: {
”addressPrefix”: ”10.0.2.0/24”,
”networkSecurityGroup”: {
”id”: ”[resourceId(’Microsoft.Network/networkSecurityGroups’, variables(’NSGnameDMZ’))]”
}
}
},
{
”name”: ”GatewaySubnet”,
”properties”: {
”addressPrefix”: ”10.0.3.0/29”
}
}
]
}
},
{
”apiVersion”: ”2015-05-01-preview”,
”type”: ”Microsoft.Network/networkInterfaces”,
”name”: ”[concat(variables(’networkInterfaceName’), copyindex(1))]”,
”location”: ”[resourceGroup().location]”,
”tags”: {
”displayName”: ”NetworkInterface”
},
”copy”: {
”name”: ”nicLoop”,
”count”: ”[variables(’numberOfInstances’)]”
},
”dependsOn”: [
”[concat(’Microsoft.Network/virtualNetworks/’, variables (’vNetName’))]”,
”[concat(’Microsoft.Network/loadBalancers/’, variables(’loadBalancerName’))]”
],
”properties”: {
”ipConfigurations”: [
{
”name”: ”ipconfig1”,
”properties”: {
”privateIPAllocationMethod”: ”Dynamic”,
”subnet”: {
”id”: ”[variables(’ProdSubnetRef’)]”
},
”loadBalancerBackendAddressPools”: [
{
”id”: ”[concat(variables(’lbID’), ’/backendAddressPools/BackendPool1’)]”
}
]
}
}
]
}
},
{
”apiVersion”: ”2015-05-01-preview”,
”type”: ”Microsoft.Network/loadBalancers”,
”name”: ”[variables(’LoadBalancerName’)]”,
”location”: ”[resourceGroup().location]”,
”tags”: {
”displayName”: ”LoadBalancer”
},
”dependsOn”: [ ”[variables(’vnetID’)]” ],
”properties”: {
”frontendIpConfigurations”: [
{
”properties”: {
”subnet”: {
”Id”: ”[variables(’ProdSubnetRef’)]”
},
”privateIPAddress”: ”[parameters(’LoadBalancerIP’)]”,
”privateIPAllocationMethod”: ”Static”
},
”name”: ”LoadBalancerFrontend”
}
],
”backendAddressPools”: [
{
”name”: ”BackendPool1”
}
],
”loadBalancingRules”: [
{
”properties”: {
”frontendIPConfiguration”: {
”Id”: ”[concat(resourceId(’Microsoft.Network/loadBalancers’, variables(’loadBalancerName’)), ’/frontendIpConfigurations/LoadBalancerFrontend’)]”
},
”backendAddressPool”: {
”Id”: ”[concat(resourceId(’Microsoft.Network/loadBalancers’, variables(’loadBalancerName’)), ’/backendAddressPools/BackendPool1’)]”
},
”probe”: {
”Id”: ”[concat(resourceId(’Microsoft.Network/loadBalancers’, variables(’loadBalancerName’)), ’/probes/lbprobe’)]”
},
”protocol”: ”Tcp”,
”frontendPort”: 80,
”backendPort”: 80,
”idleTimeoutInMinutes”: 15
},
”Name”: ”lbrule”
}
],
”probes”: [
{
”properties”: {
”protocol”: ”Tcp”,
”port”: 80,
”intervalInSeconds”: 15,
”numberOfProbes”: 2
},
”name”: ”lbprobe”
}
]
}
},
{
”apiVersion”: ”2015-05-01-preview”,
”type”: ”Microsoft.Compute/virtualMachines”,
”name”: ”[concat(parameters(’CustomerPrefix’),’vmn’, padLeft(copyindex(1),2,’0′),variables(’NamingSufix’))]”,
”copy”: {
”name”: ”virtualMachineLoop”,
”count”: ”[variables(’numberOfInstances’)]”
},
”location”: ”[resourceGroup().location]”,
”tags”: {
”displayName”: ”VirtualMachines”
},
”dependsOn”: [
”[concat(’Microsoft.Storage/storageAccounts/’, variables(’StorageAccountName’))]”,
”[concat(’Microsoft.Network/networkInterfaces/’, variables(’networkInterfaceName’), copyindex(1))]”,
”[concat(’Microsoft.Compute/availabilitySets/’, variables(’availabilitySetName’))]”
],
”properties”: {
”availabilitySet”: {
”id”: ”[resourceId(’Microsoft.Compute/availabilitySets’,variables(’availabilitySetName’))]”
},
”hardwareProfile”: {
”vmSize”: ”[parameters(’vmSize’)]”
},
”osProfile”: {
”computername”: ”[concat(parameters(’CustomerPrefix’),’vmn’, padLeft(copyindex(1),2,’0′),variables(’NamingSufix’))]”,
”adminUsername”: ”[variables(’adminUsername’)]”,
”adminPassword”: ”[parameters(’adminPassword’)]”
},
”storageProfile”: {
”imageReference”: {
”publisher”: ”[parameters(’imagePublisher’)]”,
”offer”: ”[parameters(’imageOffer’)]”,
”sku”: ”[parameters(’imageSKU’)]”,
”version”: ”latest”
},
”osDisk”: {
”name”: ”osdisk”,
”vhd”: {
”uri”: ”[concat(’http://’,variables(’StorageAccountName’),’.blob.core.windows.net/vhds/’,’osdisk’, copyindex(1), ’.vhd’)]”
},
”caching”: ”ReadWrite”,
”createOption”: ”FromImage”
}
},
”networkProfile”: {
”networkInterfaces”: [
{
”id”: ”[resourceId(’Microsoft.Network/networkInterfaces’,concat(variables(’networkInterfaceName’),copyindex(1)))]”
}
]
}
}
},
{
”apiVersion”: ”2015-05-01-preview”,
”type”: ”Microsoft.Network/networkSecurityGroups”,
”name”: ”[variables(’NSGnameDMZ’)]”,
”location”: ”[resourceGroup().location]”,
”properties”: {
”securityRules”: [
{
”name”: ”rdp_rule”,
”properties”: {
”description”: ”Allow RDP”,
”protocol”: ”Tcp”,
”sourcePortRange”: ”*”,
”destinationPortRange”: ”3389”,
”sourceAddressPrefix”: ”Internet”,
”destinationAddressPrefix”: ”*”,
”access”: ”Allow”,
”priority”: 100,
”direction”: ”Inbound”
}
},
{
”name”: ”web_rule”,
”properties”: {
”description”: ”Allow WEB”,
”protocol”: ”Tcp”,
”sourcePortRange”: ”*”,
”destinationPortRange”: ”80”,
”sourceAddressPrefix”: ”Internet”,
”destinationAddressPrefix”: ”*”,
”access”: ”Allow”,
”priority”: 101,
”direction”: ”Inbound”
}
}
]
}
},
{
”apiVersion”: ”2014-12-01-preview”,
”type”: ”Microsoft.Network/publicIPAddresses”,
”name”: ”[variables(’PublicGatewayIPName’)]”,
”location”: ”[resourceGroup().location]”,
”properties”: {
”publicIPAllocationMethod”: ”Dynamic”
}
},
{
”apiVersion”: ”2015-05-01-preview”,
”type”: ”Microsoft.Network/virtualNetworkGateways”,
”name”: ”[variables(’gatewayName’)]”,
”location”: ”[resourceGroup().location]”,
”dependsOn”: [
”[concat(’Microsoft.Network/publicIPAddresses/’, variables(’PublicGatewayIPName’))]”,
”[concat(’Microsoft.Network/virtualNetworks/’, variables (’vNetName’))]”
],
”properties”: {
”ipConfigurations”: [
{
”properties”: {
”privateIPAllocationMethod”: ”Dynamic”,
”subnet”: {
”id”: ”[variables(’gatewaySubnetRef’)]”
},
”publicIPAddress”: {
”id”: ”[resourceId(’Microsoft.Network/publicIPAddresses’,variables(’PublicGatewayIPName’))]”
}
},
”name”: ”vnetGatewayConfig”
}
],
”gatewayType”: ”Vpn”,
”vpnType”: ”RouteBased”,
”enableBgp”: false
}
}
]
}
Tack för att du tog dig tid att läsa. //Jon Jander
Relaterade inlägg
Vill du vara säker på att inte missa något
Som du märker brinner vi för att dela med oss av våra erfarenheter, nyttiga lärdomar och spaningar ut i exosfären. Se till att följa vårt nyhetsbrev eller vårt flöde på Linkedin så du inte missar något.