Auto deployment VM with VSTS agent for pool

Wszyscy który robią depyolemnty z VSTS spotykają się z sytuacją, kiedy standardowe 240min się kończy i trzeba stworzyć sobie taką maszynę. Sam robiłem taką maszynę parę razy i kiedy znów pojawiła się ta konieczność postanowiłem stworzyć automatyczny deployment który nam stworzy z JSON’a cała maszynkę i podepnie ją do Agent pool w VSTS.

Miałem parę pomysłów, aby zrobić bardzo uniwersalny template który nie tylko przyda się wam, ale również mi w przyszłości do budowania innych szablonów. Także w skrócie opiszę ciekawostki które zastosowałem szablonie, który znajdziecie na moim GitHubie.

GitHubhttps://github.com/RogalaPiotr/JustCloudPublic/tree/master/simple-vm-with-installation-vsts-agent

Założenie dotyczące szablonu: chciałbym dodać informację, gdzie ważnym jest zwrócenie uwagi, że maszyna ma być odizolowana od naszej sieci wewnętrznej, dlatego szablon jest infrastruktura stand alone, aby było bezpiecznie i w razie czego można ją usuną lub powołać więcej agentów do deployment’ów.

Opis szablonu

Sekcja Parameters:

w tej sekcji podajemy dane które przydadzą nam się do deplyment’u i automatycznego podłączenia do VSTS’a.

  • adminUsername, adminPassword – lokalny użytkownik i hasło,
  • dnsLabelPrefix – zostanie automatycznie wygenerowany podczas deployment’u, więc nie ma konieczności go zmieniać,
  • vmName – nazwa naszej maszyny oraz na podstawie tej nazwy zostaną nazwane wszystkie nasze resource jest: VNET, NSG, Storage…
  • urlvsts – adres do naszego projektu VSTS np.: https://project1.visualstudio.com,
  • auth – rodzaj poświadczenia – wybrany domyślnie PAT,
  • token – token security który umożliwi nam podłączenie się do projektu. Więcej informacji jak stworzyć Security Token poniżej:
  • pool – nazwa puli, do której zostanie dodana maszyna w VSTS’ie – ustawiony jest na default
  • AccessIPNSG – adres, który tutaj podacie zostanie dodany do NSG i tylko z tego adresu dostaniecie się po RDP do maszyny,
  • Tag – tagi mogą ulec waszej modyfikacji ustawione są na Project: VSTSAgent.

Sekcja Variables:

  • vmsize – ustawiony na “Standard_B1s” – dosyć tani i wystarczający na maszynę deployment’owy – pamiętaj, aby sprawdzić, czy masz możliwość deploy’owania tej maszyny w swojej subskrypcji w innym przypadku zgłoś request do supportu Microsoft w celu uruchomienia wielkości B_size.
  • urldonwloadagent – w tym miejscu jest podany link do ściągnięcia aktualnego zip’a z agentem VSTS – w razie zmiany wersji należy zaktualizować link na aktualny
  • filescriptURI – skrypt napisany przeze mnie w celu automatycznego pobrania i zainstalowania agenta na maszynie: https://raw.githubusercontent.com/RogalaPiotr/JustCloudPublic/master/simple-vm-with-installation-vsts-agent/vstsagent.ps1
  • filescriptURISplit – bardzo ciekawa funkcja, która rozbija powyższy url na tekst tam, gdzie jest slah “/” co w efekcie generuje nam obiekt
  • filescriptName – kolejna fajna funkcja, gdzie na podstawie powyższego splita zabieramy nazwę skryptu, który posłuży nam do instalacji w CustomScriptExtension,
  • agentname – każdy dodany Agent do puli w VSTS będzie nosił nazwę NazwaMaszynyagent.

Sekcja Resources:

  • type: Microsoft.Network/networkSecurityGroups – NSG z dostępem RDP tylko z adresu IP który dodamy podczas deploy’mentu parametr: AccessIPNSG
  • type: Microsoft.Network/publicIPAddresses – Publiczny adres dla naszej VM, aby móc się do niej podłączyć z zewnątrz.
  • type: Microsoft.Network/virtualNetworks – VNet
  • type: Microsoft.Compute/virtualMachines – tworzenie maszyny wirtualnej z Windows 2016 i Managed Disk
    • type: Microsoft.Compute/virtualMachines/extensions – instalacja agenta VSTS, bazując na napisanym skrypcie i udostępnionym na GitHubie: vstsagent.ps1 zostanie on użyty podczas deplyoment’u a podczas jego wykonywaniu dodamy informację związane z url VSTS, tokenem itp. Pełna komenda w linii 257: “commandToExecute”
  • type: Microsoft.DevTestLab/schedules – dzięki temu nasza maszynie będzie wyłączana codziennie o 18:00 zona: W. Europe Standard Time – ten feature działa tylko kiedy maszyna jest włączona pozwoli to nam zapomnieć o wyłączaniu, a mimo wszystko nie będziemy tracić pieniędzy za jej bezczynność.

Sekcja Outputs:

  • PublicDNS – po wykonaniu deployment’u wyświetli nam publiczny adres DNS dla VM,
  • Hostname – wyświetli nazwę maszyny, którą wprowadziliśmy w parametrach,
  • VSTSAgentName – wyświetli nazwę agenta jaka będzie widoczna w VSTS,
  • VSTSProjectName – wyświetli nazwę projektu VSTS jaki został wprowadzony,
  • ScriptURI – wyświetli źródło z jakiego został pobrany skrypt do instalacji Agenta,
  • AccessRDPFrom – wyświetli adres IP który został dodany do NSG, aby miał dostęp do RDP

Szablon:

{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
      "adminUsername": {
        "type": "string",
        "metadata": {
          "description": "Username for the Virtual Machine."
        }
      },
      "adminPassword": {
        "type": "securestring",
        "metadata": {
          "description": "Password for the Virtual Machine."
        }
      },
      "dnsLabelPrefix": {
        "type": "string",
        "defaultValue": "[concat('x', uniqueString(resourceGroup().id))]",
        
        "metadata": {
          "description": "Unique DNS Name for the Public IP used to access the Virtual Machine."
        }
      },
      "vmName": {
        "type": "string",
        "metadata": {
          "description": "Unique DNS Name for the Public IP used to access the Virtual Machine."
        }
      },
      "urlvsts": {
        "type": "string",
        "metadata": {
          "description": "URL for your VSTS Project ex. https://project1.visualstudio.com."
        }
      },
      "auth": {
        "type": "string",
        "defaultValue": "pat",
        "metadata": {
          "description": "Unique DNS Name for the Public IP used to access the Virtual Machine."
        }
      },
      "token": {
        "type": "securestring",
        "metadata": {
          "description": "Security token for VSTS project."
        }
      },
      "pool": {
        "type": "string",
        "defaultValue": "default",
        "metadata": {
          "description": "Pool name in VSTS - Default is a main."
        }
      },
      "AccessIPNSG": {
        "type": "string",
        "metadata": {
          "description": "Your publif IP it will added for NSG for connection via RDP."
        }
      },
      "tag": {
        "type": "object",
        "defaultValue": {
            "key1": "Project",
            "value1": "VSTSAgent"
        },
        "metadata": {
            "description": "Tag Values"
        }
    }
  },
    "variables": {
      "windowsOSVersion": "2016-Datacenter",
      "vmsize": "Standard_B1s",
      "publicIPAddressName": "[concat(parameters('vmName'), '-pip')]",
      "virtualNetworkName": "[concat(parameters('vmName'), '-vnet')]",
      "NSGname": "[concat(parameters('vmName'), '-nsg')]",
      "nicName": "[concat(parameters('vmName'), '-nic')]",
      "subnetName": "[concat(parameters('vmName'), '-subnet')]",
      "addressPrefix": "10.0.0.0/16",
      "subnetPrefix": "10.0.0.0/24",
      "subnetRef": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('virtualNetworkName'), variables('subnetName'))]",
      "urldonwloadagent": "https://vstsagentpackage.azureedge.net/agent/2.133.3/vsts-agent-win-x64-2.133.3.zip",
      "filescriptURI": "https://raw.githubusercontent.com/RogalaPiotr/JustCloudPublic/master/simple-vm-with-installation-vsts-agent/vstsagent.ps1",
      "filescriptURISplit": "[split(variables('filescriptURI'), '/')]",
      "filescriptName": "[last(variables('filescriptURISplit'))]",
      "agentname": "[concat(parameters('vmName'), 'agent')]"
    },
    "resources": [
      {
        "type": "Microsoft.Network/networkSecurityGroups",
        "name": "[variables('NSGName')]",
        "location": "[resourceGroup().location]",
        "tags": {
          "[parameters('tag').key1]": "[parameters('tag').value1]"
        },
        "apiVersion": "2018-03-01",
        "properties": {
          "securityRules": [
            {
              "name": "RDP",
              "properties": {
                "description": "Allow IP for RDP",
                "protocol": "TCP",
                "sourcePortRange": "*",
                "destinationPortRange": "3389",
                "sourceAddressPrefix": "[parameters('AccessIPNSG')]",
                "destinationAddressPrefix": "*",
                "access": "Allow",
                "priority": 100,
                "direction": "Inbound"
              }
            }
          ]
        }
      },
      {
        "apiVersion": "2016-03-30",
        "type": "Microsoft.Network/publicIPAddresses",
        "name": "[variables('publicIPAddressName')]",
        "location": "[resourceGroup().location]",
        "tags": {
          "[parameters('tag').key1]": "[parameters('tag').value1]"
        },
        "properties": {
          "publicIPAllocationMethod": "Dynamic",
          "dnsSettings": {
            "domainNameLabel": "[parameters('dnsLabelPrefix')]"
          }
        }
      },
      {
        "apiVersion": "2016-03-30",
        "type": "Microsoft.Network/virtualNetworks",
        "name": "[variables('virtualNetworkName')]",
        "dependsOn": [
          "[resourceId('Microsoft.Network/networkSecurityGroups/', variables('NSGName'))]"
        ],
        "location": "[resourceGroup().location]",
        "tags": {
          "[parameters('tag').key1]": "[parameters('tag').value1]"
        },
        "properties": {
          "addressSpace": {
            "addressPrefixes": [
              "[variables('addressPrefix')]"
            ]
          },
          "subnets": [
            {
              "name": "[variables('subnetName')]",
              "properties": {
                "addressPrefix": "[variables('subnetPrefix')]",
                "networkSecurityGroup": {
                  "id": "[resourceId('Microsoft.Network/networkSecurityGroups',variables('NSGName'))]"
              }
              }
            }
          ]
        }
      },
      {
        "apiVersion": "2016-03-30",
        "type": "Microsoft.Network/networkInterfaces",
        "name": "[variables('nicName')]",
        "location": "[resourceGroup().location]",
        "tags": {
          "[parameters('tag').key1]": "[parameters('tag').value1]"
        },
        "dependsOn": [
          "[resourceId('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]",
          "[resourceId('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
        ],
        "properties": {
          "ipConfigurations": [
            {
              "name": "ipconfig1",
              "properties": {
                "privateIPAllocationMethod": "Dynamic",
                "publicIPAddress": {
                  "id": "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]"
                },
                "subnet": {
                  "id": "[variables('subnetRef')]"
                }
              }
            }
          ]
        }
      },
      {
        "apiVersion": "2016-04-30-preview",
        "type": "Microsoft.Compute/virtualMachines",
        "name": "[parameters('vmName')]",
        "location": "[resourceGroup().location]",
        "tags": {
          "[parameters('tag').key1]": "[parameters('tag').value1]"
        },
        "dependsOn": [
          "[resourceId('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
        ],
        "properties": {
          "licenseType": "Windows_Server",
          "hardwareProfile": {
            "vmSize": "[variables('vmsize')]"
          },
          "osProfile": {
            "computerName": "[parameters('vmName')]",
            "adminUsername": "[parameters('adminUsername')]",
            "adminPassword": "[parameters('adminPassword')]"
          },
          "storageProfile": {
            "imageReference": {
              "publisher": "MicrosoftWindowsServer",
              "offer": "WindowsServer",
              "sku": "[variables('windowsOSVersion')]",
              "version": "latest"
            },
            "osDisk": {
              "createOption": "FromImage",
              "name": "[concat(parameters('vmName'), '-osdisk')]"
            }
          },
          "networkProfile": {
            "networkInterfaces": [
              {
                "id": "[resourceId('Microsoft.Network/networkInterfaces',variables('nicName'))]"
              }
            ]
          }
        },
        "resources": [
          {
            "name": "[concat(parameters('vmName'),'/VSTSAgentInstall')]",
            "type": "Microsoft.Compute/virtualMachines/extensions",
            "location": "[resourceGroup().location]",
            "tags": {
              "[parameters('tag').key1]": "[parameters('tag').value1]"
            },
            "dependsOn": [
              "[concat('Microsoft.Compute/virtualMachines/', parameters('vmName'))]"
            ],
            "apiVersion": "2015-06-15",
            "properties": {
              "publisher": "Microsoft.Compute",
              "type": "CustomScriptExtension",
              "typeHandlerVersion": "1.9",
              "autoUpgradeMinorVersion": true,
              "settings": {
                "fileUris": [
                  "[variables('filescriptURI')]"
                  ]
              },
              "protectedSettings": {
                "commandToExecute": "[concat('powershell -ExecutionPolicy Unrestricted -File ',variables('filescriptName'),' -url ',variables('urldonwloadagent'),' -urlvsts ',parameters('urlvsts'),' -auth ',parameters('auth'),' -token ',parameters('token'),' -pool ',parameters('pool'),' -agentname ',variables('agentname'))]"
              }
            }
          }
        ]
  
      },
      {
        "apiVersion": "2016-05-15",
        "type": "Microsoft.DevTestLab/schedules",
        "name": "[concat('shutdown-computevm-', parameters('vmName'))]",
        "location": "[resourceGroup().location]",
        "tags": {
          "[parameters('tag').key1]": "[parameters('tag').value1]"
        },
        "dependsOn": [
          "[resourceId('Microsoft.Compute/virtualMachines/', parameters('vmName'))]"
        ],
        "properties": {
          "status":"Enabled",
          "timeZoneId":"W. Europe Standard Time",
          "taskType":"ComputeVmShutdownTask",
          "notificationSettings":{
            "status":"Disabled",
            "timeInMinutes":15,
            "webhookUrl":null
          },
          "targetResourceId":"[resourceId('Microsoft.Compute/virtualMachines', parameters('vmName'))]",
          "dailyRecurrence":{
            "time":"1800"
          }
        }
      }
    ],
    "outputs": {
      "PublicDNS": {
        "type" : "string",
        "value": "[reference(variables('publicIPAddressName')).dnsSettings.fqdn]"
      },
      "HostName": {
        "type" : "string",
        "value": "[parameters('vmName')]"
      },
      "VSTSAgentName": {
        "type" : "string",
        "value": "[variables('agentname')]"
      },  
      "VSTSProjectName": {
        "type" : "string",
        "value": "[parameters('urlvsts')]"
      },  
      "ScritpURI": {
        "type" : "string",
        "value": "[variables('filescriptURI')]"
      },
      "AccessRDPFrom": {
        "type" : "string",
        "value": "[parameters('accessIPNSG')]"
      }
    }
  }

Przykład:

Aby wykonać deployment należy utworzyć Resource Group’ę:

New-AzureRMResourceGroup -Name VSTS -Location westeurope

Wykonanie deployment’u:

New-AzureRMResourceGroupDeployment -ResourceGroupName VSTS -TemplateURI "https://raw.githubusercontent.com/RogalaPiotr/JustCloudPublic/master/simple-vm-with-installation-vsts-agent/azuredeploy.json" -Verbose

Efekt w portalu po deploy’mencie:

Widok puli agentów w VSTS:

Maszyna jest gotowa do deploy’mentów, jeśli potrzebujesz więcej maszyn możesz bez oporu deploy’ować większą ilość 🙂

Czas deplymentu to: 15 minutes 46 seconds.