Vagrant : Des provisions

Au-delà de la bonne pratique de versionner sa configuration infrastructure grâce au Vagrantfile, l’intérêt d’utiliser Vagrant est aussi de configurer plusieurs VMs à la fois. Le cas d’usage venant à l’esprit est le lab’ nécessitant plusieurs VMs d’usage différent et donc de ressources variées. Voyons donc ce cas pratique en un exposé étape par étape.

De bons tutoriaux vous exposeront l’installation et les premiers pas avec Virtualbox et Vagrant plus en détails. Je me permets d’être rapide. Dans un premier élan vous tentez un vagrant init qui -déception- ne fait que vous créer un Vagrantfile, le fameux que nous triturerons à souhait. Si vous lisez ce fichier de plus de 70 lignes en supprimant celles vides et les remarques (très instructives), vous obtenez bien peu de choses :

$ sed -e '/#/d' -e '/^$/d' Vagrantfile
Vagrant.configure(2) do |config|
  config.vm.box = "base"
end

Voyons comment l’étoffer pour répondre à notre besoin d’un complet lab’ aux VMs variées…

A noter le 2 qui est la version de l’API (par la suite j’utiliserai une variable pour plus de clarté). Le config.vm.box désigne la box en terme vagrant que nous allons utiliser soit ici « base » qui est la dernière LTS Ubuntu (actuellement la 14.04), avec un vieil agent Puppet (3.4.3) et un client Chef déjà installés et démarrés. Assez pratique certes mais en dehors des canons minimalistes de retour en grâce (je m’égare).

A quelques jours de l’arrivée de Xenial (Ubuntu 16.04), utilisons nommément la box ubuntu 14.04 (codename trusty). On supprime le Vagrantfile pour recréer celui-ci :

# -*- mode: ruby -*-
# vi: set ft=ruby :

# Vagrantfile API/syntax version.
# Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = "ubuntu/trusty64"
end

Deux petites adaptations, ce n’est pas le grand bouleversement.
Provisionnons, démarrons et connectons nous à cette VM par les commandes :

vagrant up
vagrant ssh

Notre VM s’appelle donc vagrant-ubuntu-trusty-64, soit. Voyons comment lui donner un nom et une adresse IP.
Il est courant de donner un nom de domaine pour ses lab’ du style lab, dev, local… pour ma part, j’utilise oloc. Quant au sous-réseau, j’utilise un 192.168.10.0. Nous allons donc valoriser respectivement SubNet et Domain, ainsi que Name.

# -*- mode: ruby -*-
# vi: set ft=ruby :
SubNet="192.168.10"
Domain="oloc"
Name="kiwi"

# Vagrantfile API/syntax version.
# Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box      = "ubuntu/trusty64"
  config.vm.hostname = "#{Name}.#{Domain}"
  config.vm.network :private_network, ip: "#{SubNet}.7"
end

Les deux nouvelles lignes peuvent surprendre les non-initiés au ruby avec la syntaxe #{variable} qui fournit la valeur de cette variable. Voilà, c’est dit. Et donc ici on déclare hostname et un nouveau réseau privé qui s’ajoutera à celui créé par Vagrant lui-même (en 10.0.2.0) permettant de se connecter à la VM via le vagrant ssh.
Un ifconfig permet de vérifier les deux interfaces eth0 (10.0.2.x) et eth1 (192.168.10.7). De même un hostname -f nous confirme le kiwi.oloc.

Voyons un peu ce qu’il se passe avec Virtualbox. A noter que le nom de la VM (au sens virtualbox) commence par le nom du répertoire (tests dans mon exemple) où vous avez créé votre Vagrantfile. On retrouve son nom exact et on regarde ses infos par les commandes suivantes :

vboxmanage list vms | awk -F'"' '{if ($2~"^tests") print $2}'
tests_default_1457948607864_32492
vboxmanage showvminfo tests_default_1457948607864_32492
# en cadeau pour les puristes de la variabilisation ou sans nommer le répertoire :
vboxmanage list vms | awk -F'"' -v dir=$(basename $(pwd)) '{if ($2~dir) print $2}'
vboxmanage showvminfo $(vboxmanage list vms | awk -F'"' -v dir=$(basename $(pwd)) '{if ($2~dir) print $2}')

Le résultat n’est pas si long et ce qui nous intéresse sont ces deux informations :

Memory size: 512MB
Number of CPUs: 1

Or pour notre première VM nous souhaiterions 1024 MB et 2 CPUs. De plus ce nom n’est vraiment pas très pratique à retrouver ! Dans notre boucle nous ajoutons donc cette nouvelle boucle de lignes de modifications :

config.vm.provider "virtualbox" do |vb|
  vb.customize ["modifyvm", :id, "--memory", 1024]
  vb.customize ["modifyvm", :id, "--cpus", 2]
  vb.customize ["modifyvm", :id, "--name", "kiwi"]
end

Nous confirmons le changement de nom dans l’inventaire Virtualbox pour kiwi à l’aide de la commande vboxmanage list vms.
En se connectant à notre VM par un vagrant ssh, on vérifie la mémoire et les cpus :

free -h
cat /proc/cpuinfo

Bien, sauf qu’un vagrant status nous révèle que pour lui la current machine s’appelle Default. Nous avons pris le soin de modifier le nom au niveau du provider (ici virtualbox), mais pas dans la mécanique Vagrant. Pour corriger cela il faut le déclarer dans une boucle define comme ceci [Notons cette version comme étant la mono-kiwi] :

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = "ubuntu/trusty64"  
  config.vm.define "#{Name}" do |machine|
    machine.vm.hostname = "#{Name}.#{Domain}"
    machine.vm.network :private_network, ip: "#{SubNet}.7"

    machine.vm.provider "virtualbox" do |vb|
      vb.customize ["modifyvm", :id, "--memory", 1024]
      vb.customize ["modifyvm", :id, "--cpus", 2]
    end
  end
end

Ces petits aménagements que nous venons d’effectuer devront être répétés pour les autres VMs de notre lab’. Qui dit « répétition » dit « boucle » (celle ou celui qui dit « copier/coller » sort). Dans un premier temps, imaginons que nous souhaitions plusieurs kiwis identiques.
Nous allons donc déclarer un nombre d’instances de kiwi sous la variable $num_instances. J’utilise pour l’occasion le $ au lieu du #{}, je ne m’étendrai pas, c’est ici une écriture plus simple. Et nous allons amorcer une boucle de la forme pour toutes les valeurs de 1 à num_instances, soit en ruby (1..$num_instances).each. Puis on définit un nom en deux parties, une string et un decimal sur deux caractères qui nous fournira du kiwi-01, kiwi-02, etc. Je vous laisse découvrir les aménagements de l’ip dans :

# -*- mode: ruby -*-
# vi: set ft=ruby :
SubNet = "192.168.10"
Domain = "oloc"
Name   = "kiwi"
$num_instances = 3

# Vagrantfile API/syntax version.
# Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = "ubuntu/trusty64"
  (1..$num_instances).each do |i|
    config.vm.define vm_name = "%s-%02d" % ["#{Name}", i] do |machine|
      machine.vm.hostname = "#{vm_name}.#{Domain}"
      machine.vm.network :private_network, ip: "#{SubNet}.#{i+6}"

      machine.vm.provider "virtualbox" do |vb|
        vb.customize ["modifyvm", :id, "--memory", 1024]
        vb.customize ["modifyvm", :id, "--cpus", 2]
      end
    end
  end
end
$ vagrant up
(...)
$ vagrant status
Current machine states:

kiwi-01                   running (virtualbox)
kiwi-02                   running (virtualbox)
kiwi-03                   running (virtualbox)

C’est déjà une première étape d’obtenir plusieurs VMs de mêmes caractéristiques, mais qu’en est-il si on veut que ces VMs soient de caractéristiques différentes !?
L’idée est courante, nous allons extraire les valeurs de nos variables dans un fichier externe à notre Vagrantfile. Parce que c’est feng shui avec le ruby j’ai choisi le format json. Nous allons procéder dans un premier temps avec notre kiwi. On vérifie la validité de notre json en l’affichant à l’aide de jq (apt-get install jq) :

$ jq . provision.json
[
  {
    "name": "kiwi",
    "iplastdigit": "7",
    "host": "kiwi",
    "memory": 1024,
    "cpus": 2
  }
]

Revenons donc à la version dite mono-kiwi du Vagrantfile et ajoutons un JSON.parse comme ci-après (c’est-à-dire après le pavé indigeste que vous êtes en train de lire) afin qu’il stocke les informations du json dans le fileContent. On peut se permettre de charger l’intégralité de ce petit fichier en mémoire. Ce contenu sera lu récursivement par le fileContent.each pour chaque node. Toujours pour les non-initiés au ruby, le node défini dans la boucle est ce qui est entre accolades dans le json. Et donc le node[:name] est la valeur de la clef name définie dans le json (le "name": "kiwi"). Et ce pour chaque noeud même si nous n’avons qu’un kiwi pour l’instant.
Toutes ces explications pour ce Vagrantfile :

# -*- mode: ruby -*-
# vi: set ft=ruby :

SubNet="192.168.10"
Domain="oloc"

fileContent = JSON.parse(File.read("./provision.json"), symbolize_names: true)

# Vagrantfile API/syntax version.
# Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = "ubuntu/trusty64"
  fileContent.each do |node|
    config.vm.define node[:name] do |machine|
      machine.vm.hostname = "#{node[:host]}.#{Domain}"
      machine.vm.network :private_network, ip: "#{SubNet}.#{node[:iplastdigit]}"

      machine.vm.provider "virtualbox" do |vb|
        vb.customize ["modifyvm", :id, "--memory", node[:memory]]
        vb.customize ["modifyvm", :id, "--cpus", node[:cpus]]
        vb.customize ["modifyvm", :id, "--name", node[:name]]
      end
    end
  end
end

Pour être sûr de notre résultat, en gros sauvage que je suis je supprime notre VM et tous ses fichiers :

vboxmanage unregistervm kiwi --delete

Si on procède à un nouveau vagrant up, on retrouve bien une VM inventoriée kiwi (name) dans virtualbox dont le hostname est kiwi.oloc, l’iP en 192.168.10.7, la mémoire à 1024 et avec 2 CPUs.
Soyons fous ! Corrigons la mémoire, et ajoutons une VM inventoriée sous le nom marsup et dont le hostname est koala.oloc, je ne détaille pas le reste que vous avez compris.

$ vagrant halt
$ vi provision.json
$ jq . provision.json
[
  {
    "name": "kiwi",
    "iplastdigit": "7",
    "host": "kiwi",
    "memory": 2048,
    "cpus": 2
  },
  {
    "name": "marsup",
    "iplastdigit": "13",
    "host": "koala",
    "memory": 1024,
    "cpus": 1
  }
]
$ vagrant up
$ vagrant status
$ vboxmanage showvminfo kiwi
$ vboxmanage showvminfo marsup

A partir de là, l’effet boule de neige peut opérer ! La répétition est en place dans un Vagrantfile d’une vingtaine de lignes. Il ne manque plus qu’à compléter notre fichier de provision.json avec autant de machines aux caractéristiques variées que nous souhaitons.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *