Azure Resource Manager是Azure下一代的管理機制,透過Azure Resource Manager我們可以將所需要的Infrastructure透過Template file定義,然後以Powershell將定義好的Infrastructure佈署到Azure上。
相關的資源可以參考以下的MSDN文件:
- https://msdn.microsoft.com/en-us/library/azure/dn835138.aspx
- https://azure.microsoft.com/en-us/documentation/articles/resource-group-overview/
本系列文章會使用ARM Template佈署一組如下的虛擬機器、虛擬網路並設定HTTP Load Balance Set。
為了讓這個佈署更具彈性,因此我們會希望可以透過參數的方式指定要建立多少台機器,並為每一台機器設定一張網卡;同時,將所有的機器的80 port加入Load Balance中。
另外,我們會建立一台JumpBox,並為這台Jumpbox啟用RDP連線。
所有的動作,都會透過Visual Studie 2013與Azure SDK 2.6進行。
- 首先開啟Visual Studio 2013,新增一個Azure Resource Manager專案
- 打開DemploymentTemplateFile.json,可以看到一個基本的Template大致上分成這幾部分:parameters, variables, resources。其中
- parameters定義了這個Template File可以接受的參數,在佈署時可以透過指定參數值來改變佈署環境,例如要開幾台機器,要開在哪個資料中心等等
- variables定義在Template file中所需要的變數值,一般在這裡我們會將一些比較複雜的資訊透過variable的方式定義,在之後Template file中使用他;例如Resource ID。
- resources則是我們定義實際infrastructure所需要使用到的resource的地方
- 在這個範例中,我希望透過這個Template在佈署時,我可以指定將上述的整個系統佈署到哪個資料中心、要有幾台機器、機器所使用的Storage名稱、VM Size以及Public DNS名稱等等;我們也可以透過"defaultValue”來指定預設值。因此,在Parameters這裡,我需告以下的參數。
1: parameters": {
2: "vmCount": {
3: "type": "int",
4: "metadata": {
5: "description": "Number of VMs"
6: }
7: },
8: "storageAccountName": {
9: "type": "string",
10: "metadata": {
11: "description": "Name of storage account"
12: }
13: },
14: "adminUsername": {
15: "type": "string",
16: "metadata": {
17: "description": "Admin username"
18: }
19: },
20: "adminPassword": {
21: "type": "securestring",
22: "metadata": {
23: "description": "Admin password"
24: }
25: },
26: "dnsNameforLBIP": {
27: "type": "string",
28: "metadata": {
29: "description": "DNS for Load Balancer IP"
30: }
31: },
32: "avsetName": {
33: "type": "string",
34: "defaultValue": "myAvSet"
35: },
36: "vmSize": {
37: "type": "string",
38: "defaultValue": "Standard_D2",
39: "metadata": {
40: "description": "Size ofthe VM"
41: }
42: }
43: }
- 接著,先宣告以下的variables
1: "variables": {
2: "storageAccountType": "Standard_LRS",
3: "addressPrefix": "10.0.0.0/16",
4: "subnetName": "Subnet-1",
5: "subnetPrefix": "10.0.0.0/24",
6: "publicIPAddressType": "Dynamic",
7: "vmNicNamePrefix": "nicVMs",
8: "imagePublisher": "MicrosoftWindowsServer",
9: "imageOffer": "WindowsServer",
10: "imageSKU": "2012-R2-Datacenter",
11: "vnetName": "myVNET",
12: "publicIPAddressName": "myPublicIP",
13: "lbName": "myLB",
14: "vmNamePrefix": "myVM",
15: "vnetID": "[resourceId('Microsoft.Network/virtualNetworks',variables('vnetName'))]",
16: "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
17: "publicIPAddressID": "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]",
18: "lbID": "[resourceId('Microsoft.Network/loadBalancers',variables('lbName'))]",
19: "frontEndIPConfigID": "[concat(variables('lbID'),'/frontendIPConfigurations/LoadBalancerFrontEnd')]",
20: "lbPoolID": "[concat(variables('lbID'),'/backendAddressPools/BackendPool')]",
21: "lbProbeID": "[concat(variables('lbID'),'/probes/tcpProbe')]",
22: "jumpBoxNICID": "[resourceId('Microsoft.Network/networkInterfaces','nicVMs0')]",
23: "jumpBoxBEIPID": "[concat(variables('jumpBoxNICID'), '/ipConfigurations/ipconfig1')]"
24: },
- Azure Resource Manager透過新的方式來取得機器的Image Source:
- Image Publisher
- Image Offer
- Image SKU
- Image Publisher
- 可以透過Get-AzureVMImageOffer、Get-AzureVMImageSKU與Get-AzureVMImagePublisher來取得可使用的Publisher、Offer與SKU。
- vnetID、subnetRef、publicIPAddressID、lbID、lbPoolID、lbProbeID這幾個變數紀載了這些Resource的Unique ID,後續我們設定Resource時會需要用到。Resource ID的格式基本上如:[ResourceProviderName/ResourceName/{Name}];例如[resourceId(“Microsoft.Network/loadBalancers/myLB”)]指的是Microsoft.Network Provider所提供的loadBalancers這種Resource,其中名稱為myLB的Load Balancer物件。
- 定義完parameters與variables之後,接著就可以進入真正的工作了。
- 在Azure Resource Manager的世界裡,所有組成服務的基本元件都是一個Resource;以一台虛擬機器來說,需要網卡,網卡需要綁IP,虛擬機器本身等等都是一個Resource。為了要建置一台機器,我們需要先宣告所需要的Resource:
- Storage Account:用來放VHD
- Public IP:指定給機器的Load Balancer
- Load Balancer
- 需要設定InbountNatRule來Load Balance HTTP 80 port
- Network Interface:每台機器至少需要一張網卡
- Virtual Network:所有的機器都必須位於Virtual Network中
- Storage Account:用來放VHD
- 首先是宣告Storage Account;注意其中accountType為我們要建立的Stroage類型,例如Locally Redundant、Geo-Redundant等等。而Storage Name也是透過[parameters(‘storageAccountName’)]取得輸入的storageAccountName這個參數值。
1: {
2: "type": "Microsoft.Storage/storageAccounts",//Storage Account的Resource Provier Type
3: "name": "[parameters('storageAccountName')]",//Storage Account的名稱,
4: //這裡是傳入的參數值,在我們執行powershell可以指定
5: "apiVersion": "2015-05-01-preview", // API Version,固定值
6: "location": "[resourceGroup().location]", // 資源要建立在哪個資料中心,
7: //傳入參數,透過resourceGroup().location取得輸入值
8: "properties": {
9: "accountType": "[variables('storageAccountType')]"
10:
11: },
- 宣告一個availibility set資源
1: {
2: "name": "[parameters('avsetName')]",
3: "type": "Microsoft.Compute/availabilitySets",
4: "location": "[resourceGroup().location]",
5: "apiVersion": "2015-05-01-preview",
6: "tags": {
7: "displayName": "avset"
8: }
9: },
- 宣告一個public IP address資源。public IP address資源可以附加到Load Balancer或是Network Interface上。設定中的domainNameLabel則是我們所給予這個IP位址的DNS名稱。這邊如果我們指定abc,則最後的DNS名稱為abc.[datacenter].cloudapp.azure.com
1: {
2: "apiVersion": "2015-05-01-preview",
3: "type": "Microsoft.Network/publicIPAddresses",
4: "name": "[variables('publicIPAddressName')]",
5: "location": "[resourceGroup().location]",
6: "properties": {
7: "publicIPAllocationMethod": "[variables('publicIPAddressType')]",
8: "dnsSettings": {
9: "domainNameLabel": "[parameters('dnsNameforLBIP')]"
10: }
11: }
12: },
- 宣告虛擬網路;目前為止宣告的內容都很直觀,只要了解格式就很容易。
1: {
2: "apiVersion": "2015-05-01-preview",
3: "type": "Microsoft.Network/virtualNetworks",
4: "name": "[variables('vnetName')]",
5: "location": "[resourceGroup().location]",
6: "properties": {
7: "addressSpace": {
8: "addressPrefixes": [
9: "[variables('addressPrefix')]"
10: ]
11: },
12: "subnets": [
13: {
14: "name": "[variables('subnetName')]",
15: "properties": {
16: "addressPrefix": "[variables('subnetPrefix')]"
17: }
18: }
19: ]
20: }
21: },
- 接下來宣告一個Public Load Balancer;我們會把剛剛宣告的Public IP位址賦予此Load Balancer;並且,由於這個Demo是建立幾台web server並透過Load Balancer做流量分配,因此,我們還會建立一個針對HTTP 80 port的Load Balancer Rule。
- 為了方便起見,我將loadBalancer的設定拆為幾個部分來看,首先是基本設定;這裡的設定就如同先前一樣,只是給予基本的資料,同時指定這個load balancer資源必須要依賴public IP address資源。
1: {
2: "apiVersion": "2015-05-01-preview",
3: "name": "[variables('lbName')]",
4: "type": "Microsoft.Network/loadBalancers",
5: "location": "[resourceGroup().location]",
6: "dependsOn": [
7: "[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]"
8: ],
9: //omitted
10: }
- 接著在properties中指定這個load balancer的Public IP為我們先前宣告的Public IP Address資源
1: "properties": {
2: "frontendIPConfigurations": [
3: {
4: "name": "LoadBalancerFrontEnd",
5: "properties": {
6: "publicIPAddress": {
7: "id": "[variables('publicIPAddressID')]"
8: }
9: }
10: }
11: ],
12: //omitted
13: }
- 這個load balancer他的後端IP範圍會被指定在BackendPool這個IP Range中
1: "backendAddressPools": [
2: {
3: "name": "BackendPool"
4: }
5: ],
- 宣告一個Probe規則,針對後端的HTTP 80 port做health check;每五秒一次,兩次無法透過TCP連線到80 port,便認為該台機器無法服務80 port
1: "probes": [
2: {
3: "name": "tcpProbe",
4: "properties": {
5: "protocol": "tcp",
6: "port": 80,
7: "intervalInSeconds": "5",
8: "numberOfProbes": "2"
9: }
10: }
11: ]
- 宣告一個HTTP Load Balancer規則;將由frontendIPConfiguration中指定的Public IP資源流入的流量,分散到backendAddressPool這個資源所指定的IP Range,其Portocol為tcp,Public/Private Port均為80;Probe為先前建立的probe。如果已經熟悉原本的VM建立步驟的話,這一個動作就是在設定Load Balance Set
1: "loadBalancingRules": [
2: {
3: "name": "LBRule",
4: "properties": {
5: "frontendIPConfiguration": {
6: "id": "[variables('frontEndIPConfigID')]"
7: },
8: "backendAddressPool": {
9: "id": "[variables('lbPoolID')]"
10: },
11: "protocol": "tcp",
12: "frontendPort": 80,
13: "backendPort": 80,
14: "enableFloatingIP": false,
15: "idleTimeoutInMinutes": 5,
16: "probe": {
17: "id": "[variables('lbProbeID')]"
18: }
19: }
20: }
21: ],
- 最後,建立一個Inbount NAT Rule,指定將Public 50001 port導向3389 port
1: "inboundNatRules": [
2: {
3: "name": "RDP-JumpBox",
4:
5: "properties": {
6: "frontendIPConfiguration": {
7: "id": "[variables('frontEndIPConfigID')]"
8: },
9: /*
10: "backendIPConfiguration": {
11: "id": "[variables('jumpBoxBEIPID')]"
12: },
13: */
14: "protocol": "tcp",
15: "frontendPort": 50001,
16: "backendPort": 3389,
17: "enableFloatingIP": false
18: }
19: }
20: ],
- 完整的宣告如下:
1: {
2: "apiVersion": "2015-05-01-preview",
3: "name": "[variables('lbName')]",
4: "type": "Microsoft.Network/loadBalancers",
5: "location": "[resourceGroup().location]",
6: "dependsOn": [
7: "[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]"
8: ],
9: "properties": {
10: "frontendIPConfigurations": [
11: {
12: "name": "LoadBalancerFrontEnd",
13: "properties": {
14: "publicIPAddress": {
15: "id": "[variables('publicIPAddressID')]"
16: }
17: }
18: }
19: ],
20: "backendAddressPools": [
21: {
22: "name": "BackendPool"
23: }
24: ],
25: "loadBalancingRules": [
26: {
27: "name": "LBRule",
28: "properties": {
29: "frontendIPConfiguration": {
30: "id": "[variables('frontEndIPConfigID')]"
31: },
32: "backendAddressPool": {
33: "id": "[variables('lbPoolID')]"
34: },
35: "protocol": "tcp",
36: "frontendPort": 80,
37: "backendPort": 80,
38: "enableFloatingIP": false,
39: "idleTimeoutInMinutes": 5,
40: "probe": {
41: "id": "[variables('lbProbeID')]"
42: }
43: }
44: }
45: ],
46: "inboundNatRules": [
47: {
48: "name": "[concat('RDP-JumpBox']",
49:
50: "properties": {
51: "frontendIPConfiguration": {
52: "id": "[variables('frontEndIPConfigID')]"
53: },
54: /*
55: "backendIPConfiguration": {
56: "id": "[variables('jumpBoxBEIPID')]"
57: },
58: */
59: "protocol": "tcp",
60: "frontendPort": 50001,
61: "backendPort": 3389,
62: "enableFloatingIP": false
63: }
64: }
65: ],
66: "probes": [
67: {
68: "name": "tcpProbe",
69: "properties": {
70: "protocol": "tcp",
71: "port": 80,
72: "intervalInSeconds": "5",
73: "numberOfProbes": "2"
74: }
75: }
76: ]
77: }
78: },
- 宣告給所有web server使用的Network Interface資源;由於目前我們並不知道會建立幾來web server,這個數量會在稍後透過powersehll佈署資源時傳入;因此這在裡,我們透過copyIndex()與copy來指定在這裡我們會根據傳入參數vmCount的值,跑一個迴圈。
- 所建立出來的interface名稱如:myNic0、myNic1…etc
1: {
2: "apiVersion": "2015-05-01-preview",
3: "type": "Microsoft.Network/networkInterfaces",
4: "name": "[concat(variables('vmNicNamePrefix'), copyindex())]",
5: "location": "[resourceGroup().location]",
6: "copy": {
7: "name": "mainloop",
8: "count": "[parameters('vmCount')]"
9: },
10: "dependsOn": [
11: "[concat('Microsoft.Network/virtualNetworks/', variables('vnetName'))]",
12: "[concat('Microsoft.Network/loadBalancers/', variables('lbName'))]"
13: ],
14: "properties": {
15: "ipConfigurations": [
16: {
17: "name": "ipconfig1",
18: "properties": {
19: "privateIPAllocationMethod": "Dynamic",
20: "subnet": {
21: "id": "[variables('subnetRef')]"
22: },
23: "loadBalancerBackendAddressPools": [
24: {
25: "id": "[concat(variables('lbID'), '/backendAddressPools/BackendPool')]"
26: }
27: ]
28: }
29: }
30: ]
31: }
32: },
- 宣告JumpBox使用的NIC,與web server NIC的差別在於多了loadBalancerInboundNatRules宣告這張網卡會使用到load balancer上宣告的inbound NAT規則。
1: {
2: "apiVersion": "2015-05-01-preview",
3: "type": "Microsoft.Network/networkInterfaces",
4: "name": "nicJumpBox",
5: "location": "[resourceGroup().location]",
6: "dependsOn": [
7: "[concat('Microsoft.Network/virtualNetworks/', variables('vnetName'))]",
8: "[concat('Microsoft.Network/loadBalancers/', variables('lbName'))]"
9: ],
10: "properties": {
11: "ipConfigurations": [
12: {
13: "name": "ipconfig1",
14: "properties": {
15: "privateIPAllocationMethod": "Dynamic",
16: "subnet": {
17: "id": "[variables('subnetRef')]"
18: },
19: "loadBalancerBackendAddressPools": [
20: {
21: "id": "[concat(variables('lbID'), '/backendAddressPools/BackendPool')]"
22: }
23: ],
24: "loadBalancerInboundNatRules": [
25: {
26: "id": "[concat(variables('lbID'),'/inboundNatRules/RDP-JumpBox')]"
27:
28: }
29: ]
30: }
31: }
32: ]
33: }
34: }
- 以上都完成後,接著要宣告所有的web Server VM;看起來很長,但大多只是標準格式。
- VM的名稱為以[vmPrefix][0,1,2,3…etc]的格式命命
- VHD的檔名也會透過copyindex()與copy動態產生
- VM的名稱為以[vmPrefix][0,1,2,3…etc]的格式命命
1: {
2: "apiVersion": "2015-05-01-preview",
3: "type": "Microsoft.Compute/virtualMachines",
4: "name": "[concat(variables('vmNamePrefix'),copyindex())]",
5: "location": "[resourceGroup().location]",
6: "dependsOn": [
7: "[concat('Microsoft.Storage/storageAccounts/', parameters('storageAccountName'))]",
8: "[concat('Microsoft.Network/networkInterfaces/', concat(variables('vmNicNamePrefix'), copyindex()))]"
9: ],
10: "copy": {
11: "name": "mainloop",
12: "count": "[parameters('vmCount')]"
13: },
14: "properties": {
15: "hardwareProfile": {
16: "vmSize": "[parameters('vmSize')]"
17: },
18: "availabilitySet": {
19: "id": "[resourceId('Microsoft.Compute/availabilitySets',parameters('avsetName'))]"
20: },
21: "osProfile": {
22: "computername": "[concat(variables('vmNamePrefix'),copyindex())]",
23: "adminUsername": "[parameters('adminUsername')]",
24: "adminPassword": "[parameters('adminPassword')]"
25: },
26: "storageProfile": {
27: "imageReference": {
28: "publisher": "[variables('imagePublisher')]",
29: "offer": "[variables('imageOffer')]",
30: "sku": "[variables('imageSKU')]",
31: "version": "latest"
32: },
33: "osDisk": {
34: "name": "osdisk",
35: "vhd": {
36: "uri": "[concat('http://',parameters('storageAccountName'),'.blob.core.windows.net/vhds/','osdisk-vm-', copyindex(), '.vhd')]"
37: },
38: "caching": "ReadWrite",
39: "createOption": "FromImage"
40: }
41: },
42: "networkProfile": {
43: "networkInterfaces": [
44: {
45: "properties": {
46: "primary": false
47: },
48: "id": "[resourceId('Microsoft.Network/networkInterfaces',concat(variables('vmNicNamePrefix'), copyindex()))]",
49: "copy": {
50: "name": "mainloop",
51: "count": "[parameters('vmCount')]"
52: }
53: }
54: ]
55: }
56: }
57: }
- 宣告Jumpbox VM
1: {
2: Version": "2015-05-01-preview",
3: e": "Microsoft.Compute/virtualMachines",
4: e": "jumpbox",
5: ation": "[resourceGroup().location]",
6: endsOn": [
7: "[concat('Microsoft.Storage/storageAccounts/', parameters('storageAccountName'))]",
8: "[concat('Microsoft.Network/networkInterfaces/', 'nicJumpBox')]"
9: ],
10: perties": {
11: "hardwareProfile": {
12: "vmSize": "[parameters('vmSize')]"
13: },
14: "availabilitySet": {
15: "id": "[resourceId('Microsoft.Compute/availabilitySets',parameters('avsetName'))]"
16: },
17: "osProfile": {
18: "computername": "[variables('vmNamePrefix')]",
19: "adminUsername": "[parameters('adminUsername')]",
20: "adminPassword": "[parameters('adminPassword')]"
21: },
22: "storageProfile": {
23: "imageReference": {
24: "publisher": "[variables('imagePublisher')]",
25: "offer": "[variables('imageOffer')]",
26: "sku": "[variables('imageSKU')]",
27: "version": "latest"
28: },
29: "osDisk": {
30: "name": "osdisk",
31: "vhd": {
32: "uri": "[concat('http://',parameters('storageAccountName'),'.blob.core.windows.net/vhds/','osdisk-vm-jumpbox.vhd')]"
33: },
34: "caching": "ReadWrite",
35: "createOption": "FromImage"
36: }
37: },
38: "networkProfile": {
39: "networkInterfaces": [
40: {
41: "properties": {
42: "primary": false
43: },
44: "id": "[resourceId('Microsoft.Network/networkInterfaces','nicJumpBox')]"
45: }
46: ]
47: }
48: }
49: }
- 接著,宣告DSC Extension,我們要透過DSC設定IIS Server;所使用的powershell script已經壓縮為zip檔案並上傳到public accessiable的Azure Storage了
1: {
2: "type": "Microsoft.Compute/virtualMachines/extensions",
3: "name": "[concat(variables('vmNamePrefix'),copyindex(),'/DSC-', copyindex())]",
4: "copy": {
5: "name": "mainloop",
6: "count": "[parameters('vmCount')]"
7: },
8: "apiVersion": "2015-05-01-preview",
9: "location": "[resourceGroup().location]",
10: "dependsOn": [
11: "[concat('Microsoft.Compute/virtualMachines/', concat(variables('vmNamePrefix'),copyindex()))]"
12: ],
13: "properties": {
14: "publisher": "Microsoft.Powershell",
15: "type": "DSC",
16: "typeHandlerVersion": "1.9",
17: "settings": {
18: "ModulesUrl": "http://tesskyrim.blob.core.windows.net/test/setup.zip",
19: "SasToken": "",
20: "ConfigurationFunction": "setup.ps1\\SetupIIS",
21: "Properties": {
22: "MachineName": "[concat(variables('vmNamePrefix'),copyindex())]"
23: }
24: },
25: "protectedSettings": null
26: }
27: }
- 完整的Powershell如下
1: Configuration SetupIIS
2: {
3: param ($MachineName)
4:
5: Node $MachineName
6: {
7: #Install the IIS Role
8: WindowsFeature IIS
9: {
10: Ensure = “Present”
11: Name = “Web-Server”
12: }
13:
14: #Install ASP.NET 4.5
15: WindowsFeature ASP
16: {
17: Ensure = “Present"
18: Name = "Web-Asp-Net45"
19: }
20:
21: WindowsFeature WebServerManagementConsole
22: {
23: Name = "Web-Mgmt-Console"
24: Ensure = "Present"
25: }
26: }
27: }
- 完整的sample在此
- 打開powershell,執行以下的指令佈署整個環境到Azure
1: New-AzureResourceGroup -Name michi-arm-new2-RG -Location "East Asia" -TemplateFile DeploymentTemplateFile1.json -storageAccountNameFromTemplate michiarmstg2 -adminUsername azureuser -dnsNameforLBIP michiarm201506292 -vmSize Standard_A0 -vmCount 3