Automatisation du déploiement de VM Virtualbox

Automatisation du déploiement de VM Virtualbox

Jusqu’ici j’ai montĂ© mes laboratoires en accès par pont dans Virtualbox mais je n’ai pas le mĂŞme adressage entre mon bureau et ma maison et c’Ă©tait casse bonbon : je devais ajouter une seconde interface rĂ©seau Ă  mes VM d’une part mais aussi modifier mon hosts selon mon lieu de travail.

Il y avait pas mal d’effet de bord comme le simple fait qu’Ă  la maison, mes VM n’accĂ©daient pas Ă  Internet tant que je ne modifiais pas leur passerelle par dĂ©faut ; nia nia nia modifier ma configuration rĂ©seau, nia nia nia Ă©teindre et rallumer les interfaces… une tannĂ©e !

Pour Ă©viter ça, on va crĂ©er un rĂ©seau NAT dans Virtualbox. En mode clic-o-drome, tu peux faire Fichier –> Outils –> Network Manager (ou Ctrl + H) et cliquer sur CrĂ©er (ou Ctrl + Shift + C) mais c’est plus amusant de se familiariser avec la ligne de commande pour bricoler.

Bash
# Création du réseau NAT
VBoxManage natnetwork add --netname ansible_lab --network "10.0.0.0/24" --enable --dhcp on

# Liste des réseaux NAT
VBoxManage natnetwork list
NAT Networks:

Name:         ansible_lab
Enabled:      Yes
Network:      10.0.0.0/24
Gateway:      10.0.0.1
DHCP Server:  Yes
IPv6:         No
IPv6 Prefix:  fd17:625c:f037:2::/64
IPv6 Default: No
loopback mappings (ipv4)
        127.0.0.1=2

1 network found

Les commandes sont accessibles Ă  qui comprend 3 ou 4 mots d’anglais technique alors je te les dĂ©tailles pas plus que ça. Je suis sĂ»r que tu auras mĂŞme pas Ă  faire un effort.

Un truc cool lorsqu’on liste les rĂ©seaux NAT, Virtualbox affiche les informations du rĂ©seau dont la passerelle. J’avais lu vite fait que la passerelle Ă©tait la première adresse du rĂ©seau mais j’avais pas pris le temps de chercher une source officielle. LĂ  y a plus de doute, j’ai son IP !

Modèle de machine et déploiement automatique

Comme je te l’Ă©crivais au dĂ©but de mon prĂ©cĂ©dent laboratoire, je me suis montĂ© une Debian 12 de base que je clonais au besoin (de mĂ©moire, j’ai juste installĂ© ssh et vim dessus). C’Ă©tait pas spĂ©cialement long mais c’Ă©tait pas non plus hyper fun. Du coup j’ai ajoutĂ© ma clĂ© SSH et ai exportĂ© la machine en ova puis, j’ai Ă©cris un petit script pour gĂ©nĂ©rer mes VMs avec les objectifs suivant :

  • CrĂ©er une nouvelle VM Ă  l’aide d’un template avec la quantitĂ© de mĂ©moire et le nombre de vCPU dĂ©sirĂ©,
  • L’affecter au groupe dĂ©signĂ©,
  • Affecter Ă  la carte rĂ©seau du système parent en cas de connexion par pont ou Ă  un rĂ©seau NAT et crĂ©er une rĂ©servation DHCP,
  • VĂ©rifier que si l’on souhaite crĂ©er une rĂ©servation DHCP, l’adresse demandĂ©e ne soit pas dĂ©jĂ  attribuĂ©e et supprimer l’existante si nĂ©cessaire,
  • DĂ©marrer la machine et,
  • Mettre Ă  jour mon fichier ~/.ssh/config selon que ce soit la machine qui me servira de rebond (via une redirection de port) ou celles vers lesquelles je rebondirai.

L’intĂ©rĂŞt du script est qu’en très peu de temps j’ai une VM prĂŞte sur laquelle je peux me connecter.

D’une manière gĂ©nĂ©rale, si on le chatouille trop le serveur DHCP de Virtualbox est fragile alors au pire prends la suite de cet article comme un projet artistique…

mkvm.sh
#!/bin/bash

programname=$0

function usage {
    echo "Create a new virtual machine from an OVA template"
    echo "Usage:"
    echo "$programname --distro DISTRO \\"
    echo "             --vmname VMNAME \\"
    echo "             --group VMGROUP \\"
    echo "             --mem MEMORY \\"
    echo "             --vcpu CPU \\"
    echo "             --network (nat|bridged) \\"
    echo "               [--bridgeadapter BRIDGE_ADAPTER_NAME] \\"
    echo "               [--natname NAT_NETWORK_NAME] \\"
    echo "                 [--ip IP_ADDRESS] \\"
    echo "                 [--keep-existing-lease] \\"
    echo "             [--ssh-config] \\"
    echo "               [--ssh-ip 127.0.0.1] \\"
    echo "               [--ssh-port 22] \\"
    echo "               [--ssh-proxy PROXY_JUMP_HOST] \\"   
    echo "             [--no-start]"
    exit 1
}

# Default values
DISTRO=""
VMNAME=""
VMGROUP=""
MEM=1024
VCPU=2
NETWORK="nat"
NATNAME="natnetwork"
BRIDGEADAPTER=""
IP_ADDRESS=""
KEEP_EXISTING_LEASE=false
SSH_CONFIG=false
SSH_IP_ADDRESS=""
SSH_PORT="22"
PROXYJUMP_HOST=""
START_VM=true

# Parse arguments
while [[ $# -gt 0 ]]; do
    case $1 in
        --distro)
            DISTRO=$2
            shift 2
            ;;
        --vmname)
            VMNAME=$2
            shift 2
            ;;
        --group)
            VMGROUP=$2
            shift 2
            ;;
        --mem)
            MEM=$2
            shift 2
            ;;
        --vcpu)
            VCPU=$2
            shift 2
            ;;
        --network)
            NETWORK=$2
            shift 2
            ;;
        --natname)
            NATNAME=$2
            shift 2
            ;;
        --bridgeadapter)
            BRIDGEADAPTER=$2
            shift 2
            ;;
        --ip)
            IP_ADDRESS=$2
            shift 2
            ;;
        --keep-existing-lease)
            KEEP_EXISTING_LEASE=true
            shift
            ;;
        --ssh-config)
            SSH_CONFIG=true
            shift
            ;;
        --ssh-ip)
            SSH_IP_ADDRESS=$2
            shift 2
            ;;
        --ssh-port)
            SSH_PORT=$2
            shift 2
            ;;
        --ssh-proxy)
            PROXYJUMP_HOST=$2
            shift 2
            ;;
        --no-start)
            START_VM=false
            shift
            ;;
        *)
            echo "Unknown option: $1"
            usage
            ;;
    esac
done

# Validate arguments
if [[ -z $DISTRO || -z $VMNAME || -z $VMGROUP ]]; then
    echo "Missing required arguments!"
    usage
fi

if [[ $NETWORK != "nat" && $NETWORK != "bridged" ]]; then
    echo "Invalid network type! Choose 'nat' or 'bridged'."
    usage
fi

TEMPLATE_PATH="$HOME/Documents/Tools/VMTemplates/${DISTRO}.ova"

if [[ ! -f $TEMPLATE_PATH ]]; then
    echo "Template file $TEMPLATE_PATH does not exist."
    exit 1
fi

echo "Checking if IP is already reserved"
EXISTING_LEASE=$(VBoxManage list dhcpservers | awk '
/Individual Config:.*MAC/ { mac = $NF }
/Fixed Address:/ { print mac, $3 }
' | grep $IP_ADDRESS)
if [[ ! -z $EXISTING_LEASE ]]; then
    if $KEEP_EXISTING_LEASE; then
        echo "Error : $(echo $EXISTING_LEASE | cut -d ' ' -f2 ) is already leased by $(echo $EXISTING_LEASE | cut -d ' ' -f1)"
        exit 1
    else
        echo "Removing existing lease : $EXISTING_LEASE"
        VBoxManage dhcpserver modify --network=$NATNAME --mac-address=$(echo $EXISTING_LEASE | cut -d ' ' -f1) --remove-config
    fi
fi

echo "Importing VM from template..."
VBoxManage import "$TEMPLATE_PATH" --vsys 0 --vmname "$VMNAME" --memory "$MEM" --cpus "$VCPU"

echo "Setting VM group..."
VBoxManage modifyvm "$VMNAME" --groups "/$VMGROUP"

echo "Configuring network..."
if [[ $NETWORK == "nat" ]]; then
    VBoxManage modifyvm "$VMNAME" --nic1 natnetwork --nat-network1 "$NATNAME"
    if [[ ! -z $IP_ADDRESS ]]; then
        # Obtain the MAC address of the VM
        MAC=$(VBoxManage showvminfo "$VMNAME" --machinereadable | grep "macaddress1" | cut -d '=' -f2 | tr -d '"')
        # Assign the specified IP to the VM in the NAT network
        VBoxManage dhcpserver modify --network=$NATNAME --mac-address=$MAC --fixed-address=$IP_ADDRESS
        VBoxManage dhcpserver restart --network=$NATNAME
        echo "IP $IP_ADDRESS reserved for VM $VMNAME with MAC address $MAC in NAT network $NATNAME."
    fi
else
    if [[ -z $BRIDGEADAPTER ]]; then
        BRIDGEADAPTER=$(VBoxManage list bridgedifs | grep '^Name:' | head -n 1 | awk '{ print $2 }')
    fi
    VBoxManage modifyvm "$VMNAME" --nic1 bridged --bridgeadapter1 "$BRIDGEADAPTER"
fi

if $START_VM; then
    echo "Starting VM in headless mode..."
    VBoxManage startvm "$VMNAME" --type headless
    echo "VM $VMNAME started successfully."
else
    echo "VM created but not started (opt-out selected)."
fi

if $SSH_CONFIG; then
    HOST_TO_ADD="\nHost $VMNAME"
    HOST_TO_ADD+=" $(echo $VMNAME | awk '{print tolower($0)}')"
    if [[ -z $SSH_IP_ADDRESS ]]; then
        SSH_IP=$IP_ADDRESS
    fi
    HOST_TO_ADD+="\n\tHostname $SSH_IP"
    if [[ ! -z $SSH_PORT ]]; then
        HOST_TO_ADD+="\n\tPort $SSH_PORT"
    fi
    if [[ ! -z $PROXYJUMP_HOST ]]; then
        HOST_TO_ADD+="\n\tProxyJump $PROXYJUMP_HOST"
    fi
    printf "$HOST_TO_ADD" >> ~/.ssh/config
fi

echo "VM $VMNAME created successfully with the following settings:"
echo " - Distribution: $DISTRO"
echo " - Memory: $MEM MB"
echo " - vCPUs: $VCPU"
echo " - Group: $VMGROUP"
echo " - Network: $NETWORK"
if [[ $NETWORK == "nat" ]]; then
    echo "   NAT Network Name: $NATNAME"
    [[ ! -z $IP_ADDRESS ]] && echo "   Reserved IP Address: $IP_ADDRESS"
else
    echo "   Bridged Adapter: $BRIDGEADAPTER"
fi
Voir plus

Première machine et rebond vers le réseau NAT

J’ai testĂ© le script avec la crĂ©ation de la première machine de mon prochain laboratoire, LAB-BASTION-01.

Bash
./newvm.sh --distro debian12 --vmname LAB-BASTION-01 --group LABANSIBLE --mem 512 --vcpu 1 --network nat --natname ansible_lab --ip 10.0.0.254 --ssh-config --ssh-ip 127.0.0.1 --ssh-port 5555
Importing VM from template...
0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
Interpreting /home/antoine/Documents/Tools/VMTemplates/debian12.ova...
OK.
Disks:
  vmdisk1       21474836480     -1      http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized    debian12-disk001.vmdk   -1      -1

Virtual system 0:
 0: Suggested OS type: "Debian_64"
    (change with "--vsys 0 --ostype <type>"; use "list ostypes" to list all possible values)
 1: VM name specified with --vmname: "LAB-BASTION-01"
 2: Suggested VM group "/Templates"
    (change with "--vsys 0 --group <group>")
 3: Suggested VM settings file name "/home/antoine/VirtualBox VMs/Templates/TemplateDebian12 1/TemplateDebian12 1.vbox"
    (change with "--vsys 0 --settingsfile <filename>")
 4: Suggested VM base folder "/home/antoine/VirtualBox VMs"
    (change with "--vsys 0 --basefolder <path>")
 5: No. of CPUs specified with --cpus: 1
 6: Guest memory specified with --memory: 512 MB
 7: USB controller
    (disable with "--vsys 0 --unit 7 --ignore")
 8: Network adapter: orig Bridged, config 3, extra slot=0;type=Bridged
 9: CD-ROM
    (disable with "--vsys 0 --unit 9 --ignore")
10: IDE controller, type PIIX4
    (disable with "--vsys 0 --unit 10 --ignore")
11: IDE controller, type PIIX4
    (disable with "--vsys 0 --unit 11 --ignore")
12: SATA controller, type AHCI
    (disable with "--vsys 0 --unit 12 --ignore")
13: Hard disk image: source image=debian12-disk001.vmdk, target path=debian12-disk001.vmdk, controller=12;port=0
    (change target path with "--vsys 0 --unit 13 --disk path";
    change controller with "--vsys 0 --unit 13 --controller <index>";
    change controller port with "--vsys 0 --unit 13 --port <n>";
    disable with "--vsys 0 --unit 13 --ignore")
0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
Successfully imported the appliance.
Setting VM group...
Configuring network...
IP 10.0.0.254 reserved for VM LAB-BASTION-01 with MAC address 080027D39E1D in NAT network ansible_lab.
Starting VM in headless mode...
Waiting for VM "LAB-BASTION-01" to power on...
VM "LAB-BASTION-01" has been successfully started.
VM LAB-BASTION-01 started successfully.
VM LAB-BASTION-01 created successfully with the following settings:
 - Distribution: debian12
 - Memory: 512 MB
 - vCPUs: 1
 - Group: LABANSIBLE
 - Network: nat
   NAT Network Name: ansible_lab
   Reserved IP Address: 10.0.0.254
Voir plus


La machine crĂ©Ă©e, j’ai redirigĂ© le port 5555 de mon système hĂ´te vers le SSH de LAB-BASTION-01.

Bash
VBoxManage natnetwork modify --netname ansible_lab --port-forward-4 "ssh_to_vm:tcp:[]:5555:[10.0.0.254]:22"

Ça y est j’ai accès Ă  mon bastion et par rebond aux autres machines du rĂ©seau après crĂ©ation de celles-ci.

Afin de nettoyer le serveur DHCP de Virtualbox et mon fichier de config SSH, on va créer un second script pour décommissionner proprement les VMs.

rmvm.sh
#!/bin/bash

VMNAME="$1"

# Check if vmname is provided
if [ -z "$VMNAME" ]; then
    echo "Usage: $0 <VMNAME>"
    exit 1
fi

# Get mac address and network infos
echo "Getting VM informations"
VM_INFO=$(VBoxManage showvminfo "$VMNAME" --machinereadable)
MAC=$(echo "$VM_INFO" | grep "macaddress1" | cut -d '=' -f2 | tr -d '"'i | sed 's/../&:/g; s/:$//' | tr 'A-F' 'a-f')
echo "Found MAC $MAC"
NETWORK_TYPE=$(echo "$VM_INFO" | grep nic1 | cut -d '=' -f2 | tr -d '"')

# If VM is in nat network
if [[ $NETWORK_TYPE == "natnetwork" ]]; then
    NAT_NETWORK=$(echo "$VM_INFO" | grep "nat-network1" | cut -d '=' -f2 | tr -d '"')
    echo "VM belongs to the NAT network $NAT_NETWORK"
    DHCP_RESERVATION=$(VBoxManage list dhcpservers | grep $MAC | cut -d " " -f7)
    # If there is a fixed address in the scope
    if [[ ! -z $DHCP_RESERVATION ]]; then
        echo "Removing DHCP fixed address for $MAC"
        VBoxManage dhcpserver modify --network=$NAT_NETWORK --mac-address=$MAC --remove-config
    fi
fi

# Poweroff and delete VM
echo "Shutting down the VM $VMNAME"
VBoxManage controlvm "$VMNAME" poweroff 2>/dev/null
echo "Deleting the VM $VMNAME"
VBoxManage unregistervm "$VMNAME" --delete

# Removing ssh config record
echo "Cleaning ssh config file"
cp ~/.ssh/config ~/.ssh/config.bak

awk -v host="$VMNAME" '
  $1 == "Host" && $2 == host {skip=1; next}
  skip && /^\t/ {next}
  skip && !/^\t/ {skip=0}
  {print}
' ~/.ssh/config.bak > ~/.ssh/config

Le script rmvm.sh conserve un léger défaut : il laisse des lignes vides. Au moins il ne défonce pas le reste de mon fichier de configuration donc je vais en rester là.
D’une manière plus gĂ©nĂ©rale, une fonction d’aide sur les paramètres serait la bienvenue si je partageais le script Ă  plus grande Ă©chelle.

Bon on est d’accord que le DHCP c’est pas glop mais pour un premier jet ça fait le job. Je trouverai bien une techno plus tard pour rĂ©gler ce problème.
Toujours est-il, voici les scripts en action.

fin de la cassette

Fin de la bande

Laisser un commentaire

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