
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 moduleVagrantLibvirtPOCBuilder.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 fichierVagrantfile
qui a instancié la classe.File.basename(File.dirname(caller_path))
: extrait le nom du répertoire parent où se trouve leVagrantfile
..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. |