Automatiser le déploiement de machines virtuelles avec PowerShell – Partie 1
Selon la taille de votre architecture VMware et le nombre de VMs que vous déployez, il peut être intéressant de prendre le temps nécessaire pour automatiser le déploiement de vos Vms.
Bien que nous soyons équipé de vRealize Orchestrator, j’ai opté pour un script en PowerShell, car :
- Cela ne nécessite aucune licence supplémentaire, cela peut donc être facilement implémenter.
- Sur le net on trouve très peu de documentations sur la création des workflows Orchestrator
L’exemple de déploiement de vm qui suit, s’appuie sur :
- Des Templates de Vms Windows et Linux
- Les fichiers de customisation système pour Linux et Microsoft (customisation du hostname, nom de domaine, dns, …)
- Un fichier json contenant les caractéristiques de la vm
Les fichiers de customisation mis en place par les équipes systèmes se trouvent (sur client HTML5) dans :
Menu -> Shortcuts -> Monitoring -> VM Customizations Specifications
Ici, nous en avons deux, l’un pour les vms Linux et le second pour les vm Windows
Dans notre script de déploiement, nous créons un objet « myVM » de type « PSCustomObject » à partir du fichier json, cet objet sert à stocker toutes les données de la vm que l’on va déployer.
Si vous souhaitez avoir plus d’information sur les PSCustomObject, vous pouvez lire l’article de Kevin Marquette ici
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
. function createObjetVm{ param( [string]$vmcible, [string]$pathfile, [hashtable]$datastoreClustersMap ) $disks = New-Object System.Collections.ArrayList; $myVM=[PSCustomObject]@{}; $valueObjet=(Get-Content "$($pathfile)\$($vmcible).json" -Raw) | ConvertFrom-Json $myVM | Add-Member -MemberType NoteProperty -Name 'name' -Value $vmcible $myVM | Add-Member -MemberType NoteProperty -Name 'hostname' -Value $valueObjet.hostname.tolower() $myVM | Add-Member -MemberType NoteProperty -Name 'template' -Value $valueObjet.template $myVM | Add-Member -MemberType NoteProperty -Name 'cluster' -Value $valueObjet.cluster $myVM | Add-Member -MemberType NoteProperty -Name 'site' -Value $valueObjet.site $myVM | Add-Member -MemberType NoteProperty -Name 'drsGroup' -Value $valueObjet.drsGroup $myVM | Add-Member -MemberType NoteProperty -Name 'nbProc' -Value $valueObjet.nbProc $myVM | Add-Member -MemberType NoteProperty -Name 'nbCoeur' -Value $valueObjet.nbCoeur $myVM | Add-Member -MemberType NoteProperty -Name 'memoryMb' -Value $valueObjet.memoryMb $myVM | Add-Member -MemberType NoteProperty -Name 'nbLan' -Value $valueObjet.nbLan $myVM | Add-Member -MemberType NoteProperty -Name 'ip1' -Value $valueObjet.ip1 $myVM | Add-Member -MemberType NoteProperty -Name 'mask1' -Value $valueObjet.mask1 $myVM | Add-Member -MemberType NoteProperty -Name 'gateway1' -Value $valueObjet.gateway1 $myVM | Add-Member -MemberType NoteProperty -Name 'dns11' -Value $valueObjet.dns11 $myVM | Add-Member -MemberType NoteProperty -Name 'dns21' -Value $valueObjet.dns21 $myVM | Add-Member -MemberType NoteProperty -Name 'nom1' -Value $valueObjet.nom1 $myVM | Add-Member -MemberType NoteProperty -Name 'domaine1' -Value $valueObjet.domaine1 $myVM | Add-Member -MemberType NoteProperty -Name 'reseau1' -Value $valueObjet.reseau1 $myVM | Add-Member -MemberType NoteProperty -Name 'ip2' -Value $valueObjet.ip2 $myVM | Add-Member -MemberType NoteProperty -Name 'mask2' -Value $valueObjet.mask2 $myVM | Add-Member -MemberType NoteProperty -Name 'gateway2' -Value $valueObjet.gateway2 $myVM | Add-Member -MemberType NoteProperty -Name 'dns12' -Value $valueObjet.dns12 $myVM | Add-Member -MemberType NoteProperty -Name 'dns22' -Value $valueObjet.dns22 $myVM | Add-Member -MemberType NoteProperty -Name 'nom2' -Value $valueObjet.nom2 $myVM | Add-Member -MemberType NoteProperty -Name 'domaine2' -Value $valueObjet.domaine2 $myVM | Add-Member -MemberType NoteProperty -Name 'reseau2' -Value $valueObjet.reseau2 $myVM | Add-Member -MemberType NoteProperty -Name 'volumeTotalDisk' -Value $valueObjet.volumeTotalDisk $myVM | Add-Member -MemberType NoteProperty -Name 'DatastoreSpecifique' -Value $valueObjet.DatastoreSpecifique #Ici c est le cas ou l on a specifié un datastore cible if (-not ([string]::IsNullOrEmpty($myVM.DatastoreSpecifique)) ){ # On verifie que le datastore cible permet d heberger la nouvelle VM write-host "Solution 1" $freespaceDatastore=[math]::Round((Get-Datastore -Name $myVM.DatastoreSpecifique).FreeSpaceGB,2) $capacityDatastore=[math]::Round((Get-Datastore -Name $myVM.DatastoreSpecifique).CapacityGB,2) $minFreespaceDatasore=[math]::Round($($capacityDatastore/5),2) $newFreeSpace=[math]::Round($($freespaceDatastore-$myVM.volumeTotalDisk),2) if ($newFreeSpace -gt $minFreespaceDatasore){ $myVM | Add-Member -MemberType NoteProperty -Name 'datastoreCluster' -Value $myVM.DatastoreSpecifique } else{ $myVM | Add-Member -MemberType NoteProperty -Name 'erreur' -Value " - L'espace Libre sur le Datastore $($myVM.DatastoreSpecifique) est insuffisant." } }else{ # si aucum datastore n a ete specifié # on determine le cluster de datastore avec une regle propre à notre structure ...... ...... $myVM | Add-Member -MemberType NoteProperty -Name 'datastoreCluster' -Value $datastoreCluster } $disks=$valueObjet.disks $myVM | Add-Member -MemberType NoteProperty -Name disks -Value $disks # ------------------------------------------------- # Parametre pour l'ajout des Annotations # ------------------------------------------------- $myVM | Add-Member -MemberType NoteProperty -Name 'dateDebut' -Value $valueObjet.dateDebut $myVM | Add-Member -MemberType NoteProperty -Name 'dateFin' -Value $valueObjet.dateFin $myVM | Add-Member -MemberType NoteProperty -Name 'plateform' -Value $valueObjet.plateform $myVM | Add-Member -MemberType NoteProperty -Name 'contact' -Value $valueObjet.contact1 $myVM | Add-Member -MemberType NoteProperty -Name 'projet' -Value $valueObjet.projet $myVM | Add-Member -MemberType NoteProperty -Name 'fonction' -Value $valueObjet.fonction ...... return $myVM } . |
1 – Préparation du fichier de Customisation de l’OS
Pour le fichier de customisation temporaire, on crée un nom de fichier horodaté, au cas ou l’on lancerait en parallèle plusieurs déploiements.
1 2 3 4 |
. $date = Get-Date -format "yyyyMMdd_hhmmss" $nameOSCustomizationSpec="temp"+$date . |
Puis, à partir du fichier de customisation en place (Linux ou Windows), on crée un nouveau fichier temporaire pour la customisation de la VM, avec les informations réseau comme l’IP , le masque de sous réseau, les serveurs DNS (si nécessaire) et les serveurs WINS (si nécessaire sur Windows, mais pas chez nous…)
Génération du fichier temporaire de customisation , dans le cas du déploiement d’une Vm Linux (les adresses DNS sont présents dans le fichier de customisation servant de modèle.)
1 2 3 4 5 6 7 8 9 10 11 12 13 |
. # --------------------------------------------------------------------------------------------------------------------------- # Customisation du modele temporaire temp01 # --------------------------------------------------------------------------------------------------------------------------- $Spec=Get-OSCustomizationSpec 'REDHAT' | New-OSCustomizationSpec -Name $nameOSCustomizationSpec -Type NonPersistent $Spec=set-OSCustomizationSpec $Spec -NamingScheme 'Fixed' -NamingPrefix $myVM.hostname # # Personnalisation du réseau # Get-OSCustomizationNicMapping -Spec $Spec | Set-OSCustomizationNicMapping -IPmode UseStaticIP -IpAddress $myVM.ip1 -SubnetMask $myVM.mask1 -DefaultGateway $myVM.gateway1 . |
Dans le cas du déploiement d’une VM Windows, nous ajoutons les serveurs DNS
1 2 3 4 5 6 7 8 9 10 11 12 |
. # --------------------------------------------------------------------------------------------------------------------------- # Customisation du modele temporaire temp01 # --------------------------------------------------------------------------------------------------------------------------- $Spec=Get-OSCustomizationSpec 'WINDOWS' | New-OSCustomizationSpec -Name $nameOSCustomizationSpec -Type NonPersistent $Spec=set-OSCustomizationSpec $Spec -NamingScheme 'Fixed' -NamingPrefix ($myVM.hostname).toUpper() # # Personnalisation du réseau # Get-OSCustomizationNicMapping -Spec $Spec | Set-OSCustomizationNicMapping -IPmode UseStaticIP -IpAddress $myVM.ip1 -SubnetMask $myVM.mask1 -DefaultGateway $myVM.gateway1 -DNS $myVM.dns11,$myVM.dns21 . |
Il faudra penser à supprimer ce fichier de customisation une fois la vm générée.
2 – Création de la nouvelle VM
Pour créer la Vm , la commande est « NEW-VM ». Mais à cette commande on va ajouter un certain nombre d’arguments et on va spécifier le fichier de customisation que l’on vient juste de créer.
1 2 3 4 5 |
. NEW-VM -name $myVM.name -template $myVM.template -ResourcePool $myVM.cluster -Datastore $myVM.datastoreCluster -OSCustomizationSpec $Spec -Location $repertoireVm . |
On retrouve donc ici les arguments :
- name : on indique le nom de la VM au sens VMWare, c’est-à-dire que l’on retrouve dans l’arborescence du vcenter.
- template : on indique le template qui doit être utilisé pour le déploiement de la VM.
- RessourcePool : on donne le nom du cluster d’ESXI sur lequel la VM doit être déployée.
- Datastore : on indique le nom du Datastore ou du Cluster de Datastore de destination
- OSCustomizationSpec : On indique le fichier de customisation qui doit être utilisé pour la customisation de l’OS
- Location : le nom du folder de destination de la vue logique « Vms and Templates »
La documentation de la cmdlet « NEW-VM » est disponible ici avec toutes les options.
La cmdlet NEW-VM effectue une opération synchrone, elle nous rend donc la main une fois l’opération de création terminée.
3 – Customisation de l’OS de la VM
Une fois la VM créée, il vous suffit de la démarré pour que la customisation de l’OS se lance.
1 2 3 4 5 6 7 8 |
. try { Start-vm $myVM.name -ErrorAction Stop } catch{ ...... mes actions en cas d'erreurs au démarrage } . |
Et vous n’avez plus qu’à attendre la fin de la customisation.
Pour surveiller le bon démarrage et la fin de la customisation, il faut effectuer une recherche dans les Events Logs VMWARE.
Pour le suivi des étapes de la customisation de l’OS, j’utilise donc la fonction ci-dessous :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
. function waitCustomisationVm{ param( [PSObject[]]$objectVm) $w=1 $nameVm=$objectVm.name # # On recherche dans les Events le message concernant la customisation # On boucle pendant un maximum de 900 Sec soit 15 min, apres on stoppe le deploiement de la VM # while($True -And $w -lt 90) { $DCvmEvents = get-vm -name $objectVm.name | Get-VIEvent $DCSucceededEvent = $DCvmEvents | Where { $_.GetType().Name -eq "CustomizationSucceeded" } $DCFailureEvent = $DCvmEvents | Where { $_.GetType().Name -eq "CustomizationFailed" } if ($DCFailureEvent) { write-host " Customization of VM failed" return $False } if ($DCSucceededEvent) { write-host "Customization of VM Completed Successfully!" return $true } Start-Sleep -Seconds 10 write-host "Attente $($w)/90" $w+=1 } write-host " TimeOut de 15 min atteint pendant la Customization de la VM $(objectVm.name)" return $False } . |
Une fois la Customisation finie, on supprime le fichier de customisation temporaire ….
1 2 3 4 5 6 7 8 9 10 |
. try{ remove-OSCustomizationSpec $nameOSCustomizationSpec -confirm:$false -ErrorAction Stop } catch{ ...... Mes actions si il y a une erreur } . |
Mais attention, à la fin de la customisation de l’OS, il y a un reboot de la machine et donc avant d’enchaîner les étapes suivantes et de passer à la customisation du hardware, il faut vérifier que la vm est bien redémarré. Pour cela, on vérifie le statut des tools de la VM.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
. <# --------------------------------------------------------------------------------------------------------------------------- # Function qui attends le demarrage des ToolsStatus # Retour Attendu : toolsOK ou toolsOld # --------------------------------------------------------------------------------------------------------------------------- #> function waitToolsOk{ param([PSObject[]]$objectVm) $nameVm=$objectVm.name write-host " - Function WaitToolsOk " write-host " Attente du demarrage de la VM : $($nameVm) " $w=0 do { Start-Sleep -Seconds 10; $toolsStatus = (get-vm -name $objectVm.name).ExtensionData.Guest.ToolsStatus; write-host $toolsStatus $w+=1 }while(($toolsStatus -ne "toolsOK") -And ($toolsStatus -ne "toolsOld") -And ($w -lt 60) ); if($w -lt 60){ write-host " Le serveur $($objectVm.name) est demarré. On attends 10 secondes avant de lancer l'arret" return $true } else { write-host " - Le serveur $($objectVm.name) : TimeOut de 10 min atteint lors du démarrage de la machine." return $false } } . |
Une fois les Tools de la VM redémarré, on peut maintenant continuer et passer à la customisation du Hardware, que nous allons détailler dans un second billet.
Pingback: Déployer Automatiquent des VMs en PowerShell (Partie 2) - Mon Post-It