VagrantLibvirtPOCBuilder.rb, Le Guide Complet pour des Vagrantfiles Optimisés

VagrantLibvirtPOCBuilder.rb, Le Guide Complet pour des Vagrantfiles Optimisés

Salut à tous ! Aujourd’hui, je veux vous parler d’un module que j’ai créé, VagrantLibvirtPOCBuilder.rb, et pourquoi il pourrait être super utile pour vous en tant qu’ingénieur DevOps.

Qu’est-ce que fait VagrantLibvirtPOCBuilder.rb ?

Ce module facilite la gestion des machines virtuelles avec Vagrant et libvirt. Il permet de générer une configuration Vagrant personnalisée avec divers paramètres comme le nombre d’instances, le CPU, la mémoire, etc. Tout en une seule classe Ruby.

Pourquoi c’est cool ?

  • Simplicité: Finies les répétitions dans nos fichiers Vagrantfile.
  • Flexibilité: Personnalisez comme vous le souhaitez.
  • Fiabilité: Évite les erreurs humaines en automatisant les tâches.

Utilisation du Module VagrantLibvirtPOCBuilder.rb pour un Cluster HashiCorp Consul

Comment utiliser ce super module VagrantLibvirtPOCBuilder.rb que j’ai cuisiné pour vous ? Allez, accrochez-vous, je vous dis tout !

Arborescence des Fichiers/Dossiers

Pour utiliser ce module, il est crucial que l’arborescence de nos fichiers et dossiers suive une structure particulière. Disons que nous travaillons sur un projet appelé 42-consul pour créer un cluster HashiCorp Consul. Voici à quoi devrait ressembler notre structure de fichiers :

1/Users/btall/Labs
2├── VagrantLibvirtPOCBuilder.rb
3└── 42-consul
4    └── Vagrantfile

Explications

  • /Users/btall/Labs/VagrantLibvirtPOCBuilder.rb: Ce fichier contient le code source du module VagrantLibvirtPOCBuilder.rb. Il sert de base pour toutes les configurations Vagrant que nous souhaitons généraliser.

  • /Users/btall/Labs/42-consul/Vagrantfile: Ce fichier est le Vagrantfile spécifique à notre projet 42-consul. Il importera et utilisera le module VagrantLibvirtPOCBuilder.rb pour créer et configurer les machines virtuelles.

Le fichier Vagrantfile dans le dossier 42-consul fera référence au module VagrantLibvirtPOCBuilder. Cela permet de réutiliser un ensemble de configurations communes, tout en personnalisant ce qui est spécifique au projet 42-consul.

Exemple de Vagrantfile

Ouvrons notre Vagrantfile (/Users/btall/Labs/42-consul/Vagrantfile) et ajoutons quelque chose comme ça :

 1require_relative '../VagrantLibvirtPOCBuilder.rb'
 2
 3class ConsulCluster < VagrantLibvirtPOCBuilder
 4  def configure(config, node, i)
 5    # Mettons ici tout ce qui est spécifique à notre machine virtuelle
 6  end
 7end
 8
 9ConsulCluster.new(
10  num_instances: 3,
11  cpus: 2,
12  memory: 4096,
13  system_update: false,
14  box: 'generic/ubuntu2204',
15  disks_count: 0,
16  disks_size: '5G',
17  username: "btall"
18).generate

Dans cet exemple, je crée une nouvelle classe ConsulCluster qui hérite de VagrantLibvirtPOCBuilder. Ensuite, j’utilise la méthode generate pour lancer la magie à l’appel de la commande vagrant up.


VagrantLibvirtPOCBuilder.rb

  1class VagrantLibvirtPOCBuilder
  2  def initialize(num_instances:, cpus: 2, memory: 4096, system_update: false, box: 'generic/ubuntu2204', disks_count: 0, disks_size: '5G', username: "btall")
  3    caller_path = caller_locations.find { |loc| loc.path =~ /Vagrantfile$/ }&.path
  4    if caller_path
  5      @project_directory_name = File.basename(File.dirname(caller_path))
  6                                         .gsub(/[^0-9A-Za-z]/, '-')
  7                                         .downcase
  8    else
  9      $stderr.puts "Erreur : Impossible de trouver le Vagrantfile parent."
 10      exit 1
 11    end
 12
 13    @match_data = @project_directory_name.match(/(\d+)-(.+)/)
 14    if @match_data.nil? || @match_data[1].to_i < 1 || @match_data[1].to_i > 254
 15      $stderr.puts "Erreur : Le nom du répertoire doit être au format NNN-mydirectory!"
 16      $stderr.puts "         NNN doit être un nombre compris entre 1 et 254."
 17      exit 1
 18    end
 19
 20    @_debian_like = /(debian|ubuntu)/i
 21    @_redhat_like = /(centos|alma|rhel|rocky|fedora)/i
 22
 23    @cpus = cpus
 24    @memory = memory
 25    @username = username
 26    @system_update = system_update
 27    @num_instances = num_instances
 28    @disks_count = disks_count
 29    @disks_size = disks_size
 30    @box = box
 31    @disk_uuid = Time.now.utc.to_i
 32    setup_network_vars
 33  end
 34
 35  def setup_network_vars
 36    @network_name = @match_data[2]
 37    @network_domain = "#{@network_name}.lab"
 38    @network_subnet = "172.2.#{@match_data[1].to_i}"
 39    @private_network_subnet = "10.2.#{@match_data[1].to_i}"
 40    @private_network_subnet6 = "fd3c:b398:0698:0756"
 41    @network_cidr = "#{@network_subnet}.0/24"
 42  end
 43
 44  def provider_config(config)
 45    config.vm.provider "libvirt" do | lv |
 46      lv.connect_via_ssh = true
 47      lv.cpus = @cpus
 48      lv.keymap = "fr"
 49      lv.memory = @memory
 50      lv.graphics_type = "none"
 51      lv.default_prefix = "#{@username}-"
 52      lv.storage_pool_name = @username
 53      lv.uri = ENV['VAGRANT_LIBVIRT_URI']
 54      lv.management_network_address = @network_cidr
 55      lv.management_network_name = "#{@network_name}br2"
 56      lv.management_network_domain = @network_domain
 57    end
 58  end
 59
 60  def box_config(config)
 61    config.vm.box = @box
 62    config.vm.box_check_update = true
 63  end
 64
 65  def system_update(node)
 66    if @box =~ @_debian_like
 67      node.vm.provision "shell", inline: <<-SHELL
 68        sudo apt clean
 69        sudo apt update
 70        sudo apt upgrade -y
 71        sudo systemctl daemon-reload
 72      SHELL
 73    elsif @box =~ @_redhat_like
 74      node.vm.provision "shell", inline: <<-SHELL
 75        sudo yum update
 76        sudo yum upgrade -y
 77        sudo systemctl daemon-reload
 78      SHELL
 79    end
 80  end
 81
 82  def handle_disks(node, i)
 83    driverletters = ('a'..'z').to_a
 84    node.vm.provider "libvirt" do | lv |
 85      (1..@disks_count).each do |d|
 86        lv.storage :file,
 87          :device => "vd#{driverletters[d]}",
 88          :path => "#{@network_name}-#{i}-#{d}-#{@disk_uuid}.disk",
 89          :size => @disks_size,
 90          :bus => "virtio",
 91          :type => "raw"
 92      end
 93    end
 94  end
 95
 96  def instance_config(config, i)
 97    config.vm.synced_folder ".", "/vagrant", disabled: false, type: "rsync", rsync__args: ['--verbose', '--archive', '--delete', '-z'], rsync__exclude: ['.git', '.venv', '.vscode', 'venv']
 98    config.vm.define vm_name = "%s-%01d" % [@network_name, i] do |node|
 99      node.vm.hostname = "%s-%01d" % [@network_name, i]
100      node.vm.network :private_network,
101        :ip => "#{@private_network_subnet}.#{i+100}",
102        :libvirt__guest_ipv6 => 'yes',
103        :libvirt__ipv6_address => "#{@private_network_subnet6}::#{i+100}",
104        :libvirt__ipv6_prefix => "64",
105        :libvirt__forward_mode => "none",
106        :libvirt__dhcp_enabled => false
107      handle_disks(node, i) if @disks_count.is_a?(Integer) && @disks_count > 0 && @disks_count < 26
108      system_update(node) if (@system_update.is_a?(TrueClass) || @system_update.is_a?(FalseClass)) && @system_update
109      configure(config, node, i)
110    end
111  end
112
113  def configure(config, node, i)
114    raise NotImplementedError, "La méthode configure doit être implémentée."
115  end
116
117  def generate
118    Vagrant.configure(2) do | config |
119      provider_config(config)
120      box_config(config)
121      (1..@num_instances).each do | i |
122        instance_config(config, i)
123      end
124    end
125  end
126end

VagrantLibvirtPOCBuilder, sous le capot

L’explication détaillée vous permettra d’apprivoiser plus aisément le module. Alors, prenez votre temps et plongez dans les détails. 😅

Initialisation (initialize)

1def initialize(num_instances:, cpus: 2, memory: 4096, system_update: false, box: 'generic/ubuntu2204', disks_count: 0, disks_size: '5G', username: "btall")
2  # ...
3end
Option Valeur par défaut Description
num_instances (obligatoire) Nombre d’instances de la machine virtuelle à créer.
cpus 2 Nombre de CPUs alloués à chaque VM.
memory 4096 RAM allouée à chaque VM en Mo.
system_update false Si true, le système sera mis à jour lors de la provision.
box ‘generic/ubuntu2204’ Nom de la box Vagrant à utiliser.
disks_count 0 Nombre de disques supplémentaires à ajouter.
disks_size ‘5G’ Taille de chaque disque supplémentaire.
username ‘btall’ Nom d’utilisateur utilisé pour d’autres configurations, comme le pool de stockage.

Extraction du nom du dossier projet

1caller_path = caller_locations.find { |loc| loc.path =~ /Vagrantfile$/ }&.path
2if caller_path
3  @project_directory_name = File.basename(File.dirname(caller_path))
4                                     .gsub(/[^0-9A-Za-z]/, '-')
5                                     .downcase
6else
7  $stderr.puts "Erreur : Impossible de trouver le Vagrantfile parent."
8  exit 1
9end
  • caller_locations.find { |loc| loc.path =~ /Vagrantfile$/ }&.path : cette ligne cherche dans la pile d’appels le chemin du fichier Vagrantfile qui a instancié la classe.
  • File.basename(File.dirname(caller_path)) : extrait le nom du répertoire parent où se trouve le Vagrantfile.
  • .gsub(/[^0-9A-Za-z]/, '-') : remplace tout caractère non-alphanumérique par un tiret.
  • .downcase : convertit le nom du répertoire en minuscules.

Si le Vagrantfile parent n’est pas trouvé, le script génère une erreur et se termine.

Vérification du format du nom du dossier

1@match_data = @project_directory_name.match(/(\d+)-(.+)/)
2if @match_data.nil? || @match_data[1].to_i < 1 || @match_data[1].to_i > 254
3  $stderr.puts "Erreur : Le nom du répertoire doit être au format NNN-mydirectory!"
4  $stderr.puts "         NNN doit être un nombre compris entre 1 et 254."
5  exit 1
6end
  • @project_directory_name.match(/(\d+)-(.+)/) : cette expression régulière recherche un nombre (\d+) suivi d’un tiret, puis d’un ou plusieurs autres caractères ((.+)). Par exemple, dans “42-consul”, “42” serait capturé comme le premier groupe et “consul” comme le deuxième.
  • @match_data[1].to_i < 1 || @match_data[1].to_i > 254 : ici, on vérifie que le nombre extrait est compris entre 1 et 254. Cette plage est souvent utilisée dans le contexte des adresses IP sous-réseau.

Si le nom du répertoire n’est pas dans le format correct, le script affiche une erreur et se termine.

La raison de cette vérification stricte est que le nom du répertoire est utilisé pour définir des paramètres réseau, comme tu peux le voir dans la méthode setup_network_vars. Le nombre capturé est utilisé pour générer le sous-réseau, donc il doit être valide et unique pour éviter les conflits réseau.

Configuration Réseau (setup_network_vars)

1def setup_network_vars
2  # ...
3end

Puisque @project_directory_name est “42-consul”, les variables dérivées seraient :

Variable Exemple de valeur Description
@network_name ‘consul’ Nom du réseau dérivé du répertoire.
@network_domain ‘consul.lab’ Domaine du réseau.
@network_subnet ‘172.2.42’ Sous-réseau IPv4.
@private_network_subnet ‘10.2.42’ Sous-réseau privé IPv4.
@private_network_subnet6 ‘fd3c:b398:0698:0756’ Sous-réseau privé IPv6.

Configuration de l’Instance (instance_config)

1def instance_config(config, i)
2  # ...
3end
Option Valeur Description
config.vm.synced_folder “.”, “/vagrant” Dossier à synchroniser entre l’hôte et la VM.
node.vm.hostname “consul-1” (si i est 1) Nom d’hôte de la VM.
node.vm.network “private_network” Type de réseau à configurer.
:ip “10.2.42.101” (si i est 1) Adresse IP IPv4 pour le réseau privé.
:libvirt__guest_ipv6 ‘yes’ Activer IPv6 sur la VM.
:libvirt__ipv6_address “fd3c:b398:0698:0756::101” (si i est 1) Adresse IP IPv6 pour le réseau privé.

Gestion des Disques (handle_disks)

1def handle_disks(node, i)
2  # ...
3end
Option Exemple de valeur Description
:device “vdb” Lettre du périphérique de disque.
:path “consul-1-1-timestamp.disk” Chemin du fichier de disque.
:size “5G” Taille du disque.
:bus “virtio” Type de bus du disque.
:type “raw” Type du disque.

Configuration du Fournisseur (provider_config)

1def provider_config(config)
2  # ...
3end
Option Exemple de valeur Description
lv.connect_via_ssh true Utiliser SSH pour la connexion.
lv.cpus 2 Nombre de CPUs pour la VM.
lv.keymap “fr” Configuration du clavier.
lv.memory 4096 Mémoire en Mo pour la VM.
lv.default_prefix “btall-” Préfixe du nom de la VM.
lv.storage_pool_name “btall” Nom du pool de stockage.
lv.uri (Variable d’env) URI du fournisseur libvirt.
lv.management_network_address “172.2.42.0/24” Adresse du réseau de gestion.
lv.management_network_name “consulbr2” Nom du réseau de gestion.
lv.management_network_domain “consul.lab” Domaine du réseau de gestion.

Rafraîchis la page si la section des commentaires ci-dessous ne s'affiche pas correctement !
(Win) Ctrl+Shift+R ou (Mac) Cmd+Shift+R

Qui suis-je ?

Babacar TALL

Babacar TALL

Je suis un Cloud Architect et DevOps Microsoft Azure (+15 ans d’expérience) avec une longue expérience de développeur. Au cours de mon expérience, j’ai contribué à la mise en place, à l’exploitation et à la maintenance de programmes et …

En savoir plus