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 :
/Users/btall/Labs
├── VagrantLibvirtPOCBuilder.rb
└── 42-consul
└── 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 :
require_relative '../VagrantLibvirtPOCBuilder.rb'
class ConsulCluster < VagrantLibvirtPOCBuilder
def configure(config, node, i)
# Mettons ici tout ce qui est spécifique à notre machine virtuelle
end
end
ConsulCluster.new(
num_instances: 3,
cpus: 2,
memory: 4096,
system_update: false,
box: 'generic/ubuntu2204',
disks_count: 0,
disks_size: '5G',
username: "btall"
).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
class VagrantLibvirtPOCBuilder
def initialize(num_instances:, cpus: 2, memory: 4096, system_update: false, box: 'generic/ubuntu2204', disks_count: 0, disks_size: '5G', username: "btall")
caller_path = caller_locations.find { |loc| loc.path =~ /Vagrantfile$/ }&.path
if caller_path
@project_directory_name = File.basename(File.dirname(caller_path))
.gsub(/[^0-9A-Za-z]/, '-')
.downcase
else
$stderr.puts "Erreur : Impossible de trouver le Vagrantfile parent."
exit 1
end
@match_data = @project_directory_name.match(/(\d+)-(.+)/)
if @match_data.nil? || @match_data[1].to_i < 1 || @match_data[1].to_i > 254
$stderr.puts "Erreur : Le nom du répertoire doit être au format NNN-mydirectory!"
$stderr.puts " NNN doit être un nombre compris entre 1 et 254."
exit 1
end
@_debian_like = /(debian|ubuntu)/i
@_redhat_like = /(centos|alma|rhel|rocky|fedora)/i
@cpus = cpus
@memory = memory
@username = username
@system_update = system_update
@num_instances = num_instances
@disks_count = disks_count
@disks_size = disks_size
@box = box
@disk_uuid = Time.now.utc.to_i
setup_network_vars
end
def setup_network_vars
@network_name = @match_data[2]
@network_domain = "#{@network_name}.lab"
@network_subnet = "172.2.#{@match_data[1].to_i}"
@private_network_subnet = "10.2.#{@match_data[1].to_i}"
@private_network_subnet6 = "fd3c:b398:0698:0756"
@network_cidr = "#{@network_subnet}.0/24"
end
def provider_config(config)
config.vm.provider "libvirt" do | lv |
lv.connect_via_ssh = true
lv.cpus = @cpus
lv.keymap = "fr"
lv.memory = @memory
lv.graphics_type = "none"
lv.default_prefix = "#{@username}-"
lv.storage_pool_name = @username
lv.uri = ENV['VAGRANT_LIBVIRT_URI']
lv.management_network_address = @network_cidr
lv.management_network_name = "#{@network_name}br2"
lv.management_network_domain = @network_domain
end
end
def box_config(config)
config.vm.box = @box
config.vm.box_check_update = true
end
def system_update(node)
if @box =~ @_debian_like
node.vm.provision "shell", inline: <<-SHELL
sudo apt clean
sudo apt update
sudo apt upgrade -y
sudo systemctl daemon-reload
SHELL
elsif @box =~ @_redhat_like
node.vm.provision "shell", inline: <<-SHELL
sudo yum update
sudo yum upgrade -y
sudo systemctl daemon-reload
SHELL
end
end
def handle_disks(node, i)
driverletters = ('a'..'z').to_a
node.vm.provider "libvirt" do | lv |
(1..@disks_count).each do |d|
lv.storage :file,
:device => "vd#{driverletters[d]}",
:path => "#{@network_name}-#{i}-#{d}-#{@disk_uuid}.disk",
:size => @disks_size,
:bus => "virtio",
:type => "raw"
end
end
end
def instance_config(config, i)
config.vm.synced_folder ".", "/vagrant", disabled: false, type: "rsync", rsync__args: ['--verbose', '--archive', '--delete', '-z'], rsync__exclude: ['.git', '.venv', '.vscode', 'venv']
config.vm.define vm_name = "%s-%01d" % [@network_name, i] do |node|
node.vm.hostname = "%s-%01d" % [@network_name, i]
node.vm.network :private_network,
:ip => "#{@private_network_subnet}.#{i+100}",
:libvirt__guest_ipv6 => 'yes',
:libvirt__ipv6_address => "#{@private_network_subnet6}::#{i+100}",
:libvirt__ipv6_prefix => "64",
:libvirt__forward_mode => "none",
:libvirt__dhcp_enabled => false
handle_disks(node, i) if @disks_count.is_a?(Integer) && @disks_count > 0 && @disks_count < 26
system_update(node) if (@system_update.is_a?(TrueClass) || @system_update.is_a?(FalseClass)) && @system_update
configure(config, node, i)
end
end
def configure(config, node, i)
raise NotImplementedError, "La méthode configure doit être implémentée."
end
def generate
Vagrant.configure(2) do | config |
provider_config(config)
box_config(config)
(1..@num_instances).each do | i |
instance_config(config, i)
end
end
end
end
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
)
def initialize(num_instances:, cpus: 2, memory: 4096, system_update: false, box: 'generic/ubuntu2204', disks_count: 0, disks_size: '5G', username: "btall")
# ...
end
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
caller_path = caller_locations.find { |loc| loc.path =~ /Vagrantfile$/ }&.path
if caller_path
@project_directory_name = File.basename(File.dirname(caller_path))
.gsub(/[^0-9A-Za-z]/, '-')
.downcase
else
$stderr.puts "Erreur : Impossible de trouver le Vagrantfile parent."
exit 1
end
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
@match_data = @project_directory_name.match(/(\d+)-(.+)/)
if @match_data.nil? || @match_data[1].to_i < 1 || @match_data[1].to_i > 254
$stderr.puts "Erreur : Le nom du répertoire doit être au format NNN-mydirectory!"
$stderr.puts " NNN doit être un nombre compris entre 1 et 254."
exit 1
end
@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
)
def setup_network_vars
# ...
end
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
)
def instance_config(config, i)
# ...
end
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
)
def handle_disks(node, i)
# ...
end
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
)
def provider_config(config)
# ...
end
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. |