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 :

/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 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 :

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
OptionValeur par défautDescription
num_instances(obligatoire)Nombre d’instances de la machine virtuelle à créer.
cpus2Nombre de CPUs alloués à chaque VM.
memory4096RAM allouée à chaque VM en Mo.
system_updatefalseSi true, le système sera mis à jour lors de la provision.
box‘generic/ubuntu2204’Nom de la box Vagrant à utiliser.
disks_count0Nombre 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 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

@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 :

VariableExemple de valeurDescription
@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
OptionValeurDescription
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
OptionExemple de valeurDescription
: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
OptionExemple de valeurDescription
lv.connect_via_sshtrueUtiliser SSH pour la connexion.
lv.cpus2Nombre de CPUs pour la VM.
lv.keymap“fr”Configuration du clavier.
lv.memory4096Mé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