IaaS V2 – Architecture et création d’une machine virtuelle avec une approche impérative et déclarative

Avec le nouveau portail et le nouveau modèle de déploiement ARM (Azure Resource Manager), on entend régulièrement parler de « IaaS V2 ». Au-delà des nouvelles fonctionnalités proposées par ARM (Groupes de ressources, Template de définitions, RBAC…), c’est l’architecture même des machines virtuelles qui a évolué dans Azure. Dans ce blog post, nous allons aborder l’architecture globale des machines virtuelles avec ARM et détailler comment provisionner des machines virtuelles avec deux approches différentes.

 

ASM vs ARM : Une nouvelle architecture

Avec le modèle ASM (Azure service Management) les machines virtuelles utilisent un cloud service : ce dernier possède une adresse IP virtuelle qui permet d’accéder aux différentes machines, il est défini par un nom DNS et utilise un DNS azure pour résoudre les noms des machines virtuelles. Il propose un système de load balancing et de scaling automatique pour les vms qu’il contient.

Le modèle de déploiement ARM n’utilise plus de cloud service : Les machines virtuelles provisionnées via ARM ne sont plus contenues dans un « cloud service » mais à l’intérieur d’un « virtual network ». Ce dernier doit lui-même être contenu dans un « ressource group ». Dorénavant toutes les machines virtuelles possèdent leur propre adresse IP publique (dynamique ou statique). Un « network adapter » permet de mettre en place du load balancing entre les vms. Les règles d’accès aux vms sont maintenant gérées par l’intermédiaire d’un « Network Security Group » (NSG) qui peut s’appliquer au niveau d’une VM ou d’un sous-réseau.

 

iaasV2

 

Lors de la création d’une machine virtuelle, un ensemble de composants est donc cré dans le groupe de ressources défini :

  1. Un réseau virtuel : Réseau qui encapsule une ou plusieurs machine(s) virtuelle(s). Il est possible de créer des sous-réseaux à l’intérieur pour mettre en place des topologies complexes.
  2. La machine virtuelle : Instance de la machine virtualisée.
  3. Une interface réseau : Définit les relations que la machine virtuelle peut avoir avec l’extérieur et au sein de son réseau / sous-réseau.
  4. Un groupe de sécurité (NSG) : Définit un ensemble de règle sur la / les interfaces réseaux de la machine virtuelle ou directement au niveau de son sous-réseau. On parle de « Inbound Security Rule » pour les règles d’accès en entrée et d’ « Output Security Rule » pour les règles de sorties.
  5. Une adresse IP publique : Cette dernière permet à la VM d’être accessible depuis l’extérieur. Elle peut être « dynamique » ou « statique ». Si l’adresse IP publique est « dynamique », elle changera automatiquement à chaque redémarrage de la VM. Au contraire si elle est « statique », l’adresse IP publique est réservée et ne changera pas à chaque redémarrage.
  6. Un compte de stockage : Stocke le disque OS et les disques de données de la machine virtuelle.

 

Dans l’exemple ci-dessous, une machine virtuelle nommée “sql-server-data” a été provisionnée dans le groupe de ressource “iaas-data-rg”. On retrouve les composants cités ci-dessus :

rg

 

Création d’une machine virtuelle

Il existe aujourd’hui plusieurs façons de créer une machine virtuelle :

  • Le nouveau portail Azure (ARM)
  • L’ancien portail Azure (ASM) 
  • Les outils en ligne de commandes (PowerShell / Azure CLI)

 

En utilisant les outils en ligne de commandes, deux approches sont utilisables, l’approche « impérative » et l’approche « déclarative ».

L’approche « impérative » est l’approche historiquement utilisée pour manager des ressources dans Azure. Pour la création d’une machine virtuelle, elle consiste à définir explicitement tous les composants nécessaires à la machine virtuelle dans un script. L’ordre de définition des composants dans le script est très important car il y a une notion de dépendance entre certains d’entre eux, par exemple la compte de stockage doit être créé avec la machine virtuelle, de même l’adresse IP publique doit être créé avant l’interface réseau.

L’approche « déclarative » consiste à utiliser un fichier json pour définir l’intégralité de son environnement, ce sont les fameux template Azure également appelés template ARM. Dans un template ARM l’ordre de définition des ressources n’est pas important, car les dépendances entre les ressources sont explicitement définies au sein du template. Les ressources sans dépendances sont déployées en parallèle, ce qui est un avantage non négligeable pour déployer de gros environnements.

Nous allons donc utiliser ces deux approches pour déployer deux machines virtuelles dans deux groupes de ressources différents nommés respectivement « IaasV2-imperative-rg » et « IaaS-V2-declarative-rg. Nous allons donc mettre en place deux scripts PowerShell, un pour chaque approche. Chaque script sera responsable de la création d’une infrastructure complète, soit un groupe de ressource, un machine virtuelle et ses composants.

 

L’approche « impérative »

Pour bien comprendre l’architecture IaaS V2, créer une machine virtuelle avec cette approche est intéressante car il faut définir tous les composants nécessaires au fonctionnement de la vm dans le bon ordre.

Dans le script ci-dessous, on définit dans un premier temps un ensemble de variables que l’on peut diviser en deux familles :

  • Les variables d’infrastructures (relative à l’emplacement de la VM et à son stockage)
  • Les variables de nommages pour nommer convenablement et avec une certaine logique les composants de la machine virtuelle. Dans mon exemple, je suffixe chaque composant par son type pour pouvoir les reconnaître facilement à la lecture.

Dans un deuxième temps, on définit un par un les composant nécessaires à la création de la machine virtuelle :

  1. Network Security rule (NSR) et Network Security Group (NSG)
  2. Virtual network
  3. Public IP
  4. Network Interface
  5. Configuration de la VM (Nom et Groupe de ressources)
  6. Affectation du réseau virtuel à la VM
  7. Configuration de l’OS
  8. Configuration des disques et de l’image de base
  9. Création de la VM
$storageAccountName = "iaasvtwostoragei";
$resourceGroupName = "IaasV2-imperative-rg";
$location = "North Europe"
$adminUsername = 'adminis'
$adminPassword = 'my@PassWorD!'
 
$rand = Get-Random -Minimum 10000 -Maximum 99999
$vmName = 'VM-{0}' -f $rand
$vmSize = 'Standard_A1'
$nicName = '{0}-NIC' -f $rand
$ipName = '{0}-IP' -f $rand
$domName = 'vm-test-domain-is' -f $rand
$vnetName = '{0}-NET' -f $vmName
 
# Authenticate on Azure Subscription
Login-AzureRmAccount

#Create the resource Group
$ResourceGroup = New-AzureRmResourceGroup -Name $resourceGroupName -Location $location
 
# Create the storage account to store the .vhds/{1}osdisk
$storageAccount = New-AzureRmStorageAccount -ResourceGroupName $resourceGroupName -Name $storageAccountName  -Type Standard_LRS -Location $location
 
# Create the Network Security Rules to allow RDP
Write-Verbose 'Creating the RDP Network Security Rules' 
$NSGrule = New-AzureRmNetworkSecurityRuleConfig -Name rdp-rule -Description "Allow RDP" `
            -Access Allow -Protocol Tcp -Direction Inbound -Priority 100 `
            -SourceAddressPrefix Internet -SourcePortRange * `
            -DestinationAddressPrefix * -DestinationPortRange 3389
 
# Create the network security group based on network security rules
$NSGName = 'VM{0}-NSG' -f $vmSuffix
$nsg = New-AzureRmNetworkSecurityGroup -ResourceGroupName $resourceGroupName -Location $ResourceGroup.Location -Name $NSGName -SecurityRules $NSGrule
                  
# Create the Network Security Rules
$NSGrule = New-AzureRmNetworkSecurityRuleConfig -Name rdp-rule -Description "Allow RDP" `
            -Access Allow -Protocol Tcp -Direction Inbound -Priority 100 `
            -SourceAddressPrefix Internet -SourcePortRange * `
            -DestinationAddressPrefix * -DestinationPortRange 3389
 
# Create the network security group based on network security rules
$NSGName = 'VM{0}-nsg' -f $vmSuffix
$nsg = New-AzureRmNetworkSecurityGroup -ResourceGroupName $resourceGroupName -Location $ResourceGroup.Location -Name $NSGName -SecurityRules $NSGrule
                  
# Create the virtual network and a default subnet
$vnetDef = New-AzureRmVirtualNetwork -ResourceGroupName $resourceGroupName -Location $ResourceGroup.Location -Name $vnetName -AddressPrefix '10.0.0.0/16'
$vnet = $vnetDef | Add-AzureRmVirtualNetworkSubnetConfig -Name 'main-subnet' -AddressPrefix '10.0.0.0/24' | Set-AzureRmVirtualNetwork
 
# 1. Public IP
$pip = New-AzureRmPublicIpAddress -ResourceGroupName $resourceGroupName -Location $ResourceGroup.Location -Name $ipName -DomainNameLabel $domName -AllocationMethod Dynamic
 
# 2. Nic using the Public Ip
$nic = New-AzureRmNetworkInterface -ResourceGroupName $resourceGroupName -Location $ResourceGroup.Location -Name $nicName -PublicIpAddressId $pip.Id -SubnetId $vnet.Subnets[0].Id -NetworkSecurityGroupId $nsg.Id
 
# VM configuration (Name and Size)
$vm = New-AzureRmVMConfig -VMName $vmName -VMSize $vmSize
 
# Add the NIC to the VM
$vm = Add-AzureRmVMNetworkInterface -VM $vm -Id $nic.Id
 
# 3. OS Configuration
 
# Define credential for the Windows OS
$cred = New-Object PSCredential $adminUsername, ($adminPassword | ConvertTo-SecureString -AsPlainText -Force)
 
# Define the vm operating system using the credentials
$vm = Set-AzureRmVMOperatingSystem -VM $vm -Windows -ComputerName $vmName -Credential $cred -ProvisionVMAgent -EnableAutoUpdate
 
# Definite the OS disk for the VM
$osDiskUri = '{0}vhds/{1}osdisk.vhd' -f $storageAccount.PrimaryEndpoints.Blob.ToString(), $vmName.ToLower()
 
$vm = Set-AzureRmVMOSDisk -VM $vm -Name "OsDisk" -VhdUri $osDiskUri -Caching ReadWrite -CreateOption FromImage
 
$vm = Set-AzureRmVMSourceImage -VM $vm -PublisherName "MicrosoftWindowsServer" -Offer "WindowsServer" -Skus "2012-R2-Datacenter" -Version "latest"
 
 
# Create the VM
New-AzureRmVM -ResourceGroupName $resourceGroupName -Location $ResourceGroup.Location -VM $vm

 

Une fois le script exécuté avec succès, on retrouve la machine virtuelle et ses composant au sein du groupe de ressource défini : On remarque au passage qu’aucun déploiement n’a été effectué. Cette notion de déploiement apparait uniquement lors de l’utilisation d’un template ARM Sourire

 

vm-old-school

 

L’approche « déclarative »

Avec cette approche nous allons « déclarer » l’ensemble des composants d’une machine virtuelle dans un fichier de définition .json. Pour l’architecture et la mise la création d’un template ARM je vous renvoie vers cet ancien post.

Avant la définition des ressources, il est nécessaire de définir un ensemble de paramètres dans le fichier de définition pour pouvoir réutiliser ce dernier facilement. Dans notre exemple, 6 paramètres sont définis :

  • Le nom d’utilisateur de la machine virtuelle
  • Le mot de passe de l’utilisateur
  • Le nom de la machine virtuelle
  • La taille de la machine virtuelle
  • Le nom DNS pour accéder à la machine
  • La version d’ubuntu à utiliser

Après les paramètres, nous définissons un ensemble de variables qui nous permettent de mettre en place une logique de nommage pour les composants suivant :

  • Compte de stockage
  • Adresse IP publique
  • Réseau virtuel
  • Interface réseau

De plus on indique les informations nécessaires à l’utilisation d’une image de machine virtuelle disponible sur le Market Place Azure : L’éditeur et le nom de l’image.

Dans le nœud “ressources”  nous pouvons définir les composants de la vm sans ordre particulier. Pour définir une dépendance entre les ressources, il faut utiliser le nœud « dependsOn » de chaque ressource.

Dans le script suivant, ce nœud est utilisé pour deux ressources :

  • L’interface réseau qui a besoin d’une adresse IP publique et d’un réseau virtuel
  • La machine virtuelle  qui a besoin d’un compte de stockage pour stocker son .vhd et d’une interface réseau pour définir son accessibilité.

 

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "adminUsername": {
      "type": "string",
      "minLength": 1,
      "metadata": {
        "description": "User name for the Virtual Machine."
      }
    },
    "adminPassword": {
      "type": "securestring",
      "metadata": {
        "description": "Password for the Virtual Machine."
      }
    },
    "dnsNameForPublicIP": {
      "type": "string",
      "minLength": 1,
      "metadata": {
        "description": "Globally unique DNS Name for the Public IP used to access the Virtual Machine."
      }
    },
    "vmName" :{
      "type": "string",
      "defaultValue" : "MyUbuntuVm"
    },
   "vmSize": { 
     "type": "string",
     "defaultValue" : "Standard_D1"
    },
    "ubuntuOSVersion": {
      "type": "string",
      "defaultValue": "14.04.2-LTS",
      "allowedValues": [
        "12.04.5-LTS",
        "14.04.2-LTS"
      ],
      "metadata": {
        "description": "The Ubuntu version for the VM. This will pick a fully patched image of this given Ubuntu version. Allowed values: 12.04.5-LTS, 14.04.2-LTS, 15.04."
      }
    }
  },
  "variables": {
    "vhdStorageName": "[concat('vhdstorage', uniqueString(resourceGroup().id))]",
    "vhdStorageContainerName": "vhds",
    "imagePublisher": "Canonical",
    "imageOffer": "UbuntuServer",
    "OSDiskName": "osdiskforlinuxsimple",
    "nicName": "[concat(parameters('vmName'), '-' ,'nic')]",
    "publicIPName" : "[concat(parameters('vmName'), '-' ,'ip')]",
    "publicIPAddressType": "Dynamic",
    "virtualNetworkName": "[concat(parameters('vmName'), '-' ,'vNet')]",
    "addressPrefix": "10.0.0.0/16",
    "subnetName": "Subnet",
    "subnetPrefix": "10.0.0.0/24",
    "vnetId": "[resourceId('Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]",
    "subnetRef": "[concat(variables('vnetId'), '/subnets/', variables('subnetName'))]"
  },
  "resources": [
    {
      "type": "Microsoft.Storage/storageAccounts",
      "name": "[variables('vhdStorageName')]",
      "apiVersion": "2015-06-15",
      "location": "[resourceGroup().location]",
      "tags": {
        "displayName": "StorageAccount"
      },
      "properties": {
        "accountType": "Standard_LRS"
      }
    },
    {
      "apiVersion": "2015-06-15",
      "type": "Microsoft.Network/publicIPAddresses",
      "name": "[parameters('dnsNameForPublicIP')]",
      "location": "[resourceGroup().location]",
      "tags": {
        "displayName": "PublicIPAddress"
      },
      "properties": {
        "publicIPAllocationMethod": "[variables('publicIPAddressType')]",
        "dnsSettings": {
          "domainNameLabel": "[parameters('dnsNameForPublicIP')]"
        }
      }
    },
    {
      "apiVersion": "2015-06-15",
      "type": "Microsoft.Network/virtualNetworks",
      "name": "[variables('virtualNetworkName')]",
      "location": "[resourceGroup().location]",
      "tags": {
        "displayName": "VirtualNetwork"
      },
      "properties": {
        "addressSpace": {
          "addressPrefixes": [
            "[variables('addressPrefix')]"
          ]
        },
        "subnets": [
          {
            "name": "[variables('subnetName')]",
            "properties": {
              "addressPrefix": "[variables('subnetPrefix')]"
            }
          }
        ]
      }
    },
    {
      "apiVersion": "2015-06-15",
      "type": "Microsoft.Network/networkInterfaces",
      "name": "[variables('nicName')]",
      "location": "[resourceGroup().location]",
      "tags": {
        "displayName": "NetworkInterface"
      },
      "dependsOn": [
        "[concat('Microsoft.Network/publicIPAddresses/', parameters('dnsNameForPublicIP'))]",
        "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
      ],
      "properties": {
        "ipConfigurations": [
          {
            "name": "ipConfig",
            "properties": {
              "privateIPAllocationMethod": "Dynamic",
              "publicIPAddress": {
                "id": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('dnsNameForPublicIP'))]"
              },
              "subnet": {
                "id": "[variables('subnetRef')]"
              }
            }
          }
        ]
      }
    },
    {
      "apiVersion": "2015-06-15",
      "type": "Microsoft.Compute/virtualMachines",
      "name": "[parameters('vmName')]",
      "location": "[resourceGroup().location]",
      "tags": {
        "displayName": "VirtualMachine"
      },
      "dependsOn": [
        "[concat('Microsoft.Storage/storageAccounts/', variables('vhdStorageName'))]",
        "[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
      ],
      "properties": {
        "hardwareProfile": {
          "vmSize": "[parameters('vmSize')]"
        },
        "osProfile": {
          "computerName": "[parameters('vmName')]",
          "adminUsername": "[parameters('adminUsername')]",
          "adminPassword": "[parameters('adminPassword')]"
        },
        "storageProfile": {
          "imageReference": {
            "publisher": "[variables('imagePublisher')]",
            "offer": "[variables('imageOffer')]",
            "sku": "[parameters('ubuntuOSVersion')]",
            "version": "latest"
          },
          "osDisk": {
            "name": "osdisk",
            "vhd": {
              "uri": "[concat('http://', variables('vhdStorageName'), '.blob.core.windows.net/', variables('vhdStorageContainerName'), '/', variables('OSDiskName'), '.vhd')]"
            },
            "caching": "ReadWrite",
            "createOption": "FromImage"
          }
        },
        "networkProfile": {
          "networkInterfaces": [
            {
              "id": "[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]"
            }
          ]
        }
      }
    }
  ]
}

Pour déployer l’environnement en utilisant ce template ARM, il faut utiliser la commande PowerShell suivante :

New-AzureRmResourceGroupDeployment 

Cette commande va créer un nouveau déploiement basé sur le template ARM spécifié. On peut considérer qu’un déploiement correspond à une architecture déployée dans un groupe de ressource Azure à un instant t.

$TemplateFile = 'C:\Templates\ubuntu-vm.json'
$TemplateParametersFile = 'C:\Templates\ubuntu-vm.parameters.json';
$ResourceGroupName = 'IaasV2-declarative-rg'
$ResourceGroupLocation = 'North Europe'

Login-AzureRmAccount

# Create or update the resource group using the specified template file and template parameters file
New-AzureRmResourceGroup -Name $ResourceGroupName -Location $ResourceGroupLocation -Verbose -Force -ErrorAction Stop 

New-AzureRmResourceGroupDeployment -Name ('deploy_' + '-' + ((Get-Date).ToUniversalTime()).ToString('MMdd-HHmm')) `
-ResourceGroupName $ResourceGroupName `
-TemplateFile $TemplateFile `
-TemplateParameterFile $TemplateParametersFile `
-DeploymentDebugLogLevel All `
-Force –Verbose

Après le déploiement du template « ubuntu-vm.json », on récupère dans la console un identifiant de déploiement :

deployment-end 

 

Ce dernier est accessible depuis le portail Azure :

 

deployments

 

Une vue de détails du déploiement est accessible et permet :

  • De suivre l’évolution / statut de son déploiement
  • De récupérer le template ARM du déploiement
  • De redéployer son infrastructure à partir d’un déploiement spécifique

 

deploy-details

 

Sympa non ?!

Happy Coding ! Sourire

Ces billets pourraient aussi vous intéresser

Vous nous direz ?!

Commentaires

comments powered by Disqus