Dans cette huitième partie de la série sur KVM, nous allons voir comment automatiser la création de nos VMs afin de ne pas avoir à faire à la main tout ce que je vous ai expliqué jusqu’à présent.
Il se peut que vous arriviez ici suite à :
- une recherche Google
- vous venez de ma série de tutos KVM
- vous suivez la série Virtualisation 2022 avec KVM
Pour information, entre cet article et les précédents de la série dédiée à KVM, j’ai opéré des changements dans la gestion du stockage et du réseau.De ce fait, ici, on trouvera les versions adaptées à l’usage de KVM fait dans cette série Virtualisation 2022 avec KVM.
Je vous propose donc ici quelques scripts pour :
- créer un template prêt à déployer
- créer des VMs à partir de ce template
- supprimer des VMs
C’est loin d’être parfait, mais ça a le mérite d’exister et de fonctionner.
I – Script Make.sh
Script nommé make.sh qui permet de générer une archive TAR avec une Debian destinée à nos futures bécanes.
Le script :
#!/bin/bash name="debugo" echo -e "\e[32m *** Création template ${name} *** \e[39m" mkdir ${name} echo "Deboostrap" debootstrap --no-check-gpg --include=grub-pc,linux-image-amd64,locales --exclude=os-prober stable ${name} > /dev/null echo "Configuration" # Montages nécessaires pour le chroot mount -t sysfs /sys ${name}/sys mount -t proc /proc ${name}/proc mount --bind /dev ${name}/dev mount -t devpts /dev/pts ${name}/dev/pts mount --bind /tmp ${name}/tmp # Config date LANG=C chroot ${name} /bin/bash -c "ln -fs /usr/share/zoneinfo/Europe/Paris /etc/localtime" > /dev/null 2>&1 LANG=C chroot ${name} /bin/bash -c "dpkg-reconfigure --frontend=noninteractive tzdata" > /dev/null 2>&1 # Config Locales sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' ${name}/etc/locale.gen sed -i -e 's/# fr_FR.UTF-8 UTF-8/fr_FR.UTF-8 UTF-8/' ${name}/etc/locale.gen LANG=C chroot ${name} /bin/bash -c "dpkg-reconfigure --frontend=noninteractive locales" > /dev/null 2>&1 LANG=C chroot ${name} /bin/bash -c "update-locale LANG=fr_FR.UTF-8" echo -e "\e[32mUpdate Secu\e[39m" echo "deb http://security.debian.org/debian-security bullseye-security main contrib non-free" >> ${name}/etc/apt/sources.list echo "deb-src http://security.debian.org/debian-security bullseye-security main contrib non-free" >> ${name}/etc/apt/sources.list chroot ${name} /bin/bash -c "apt update && apt upgrade -y" > /dev/null echo "Installation Package" chroot ${name} /bin/bash -c "apt-get install acpid ssh python3 python3-apt ntp nftables sudo -y -q" > /dev/null 2>&1 chroot ${name} /bin/bash -c "systemctl enable acpid" > /dev/null 2>&1 chroot ${name} /bin/bash -c "apt-get remove iptables --purge -y -q" > /dev/null 2>&1 chroot ${name} /bin/bash -c "apt-get install bsd-mailx tree htop bash-completion wget man-db git python3-apt apt-transport-https ca-certificates curl gnupg-agent software-properties-common -y -q" > /dev/null 2>&1 chroot ${name} /bin/bash -c "apt-get clean" > /dev/null 2>&1 echo -e "Création de l'archive" umount ${name}/{sys,proc,dev/pts,dev,tmp}; tar zcvf ${name}.tar.gz ${name}/ > /dev/null echo -e "\e[32mTemplate ${name} prêt !\e[39m"
Le script est simple : debootstrap, montages qui vont bien, configuration (date et locales), installation de paquets (bye iptable, hello nftables, gestion acpid, python, …).
A la différence de mon article ou je détaillais la préparation du template, on ne copie pas le squelette (les fichiers de configuration), cela sera fait lors de la création de la VM.
Ensuite, création de l’archive. Je ne supprime pas le répertoire, il servira pour les updates par la suite.
Tout ceci donc dans un fichier make.sh, puis :
# chmod +x make.sh # ./make.sh
And voila !
En quelques minutes, un TAR tout chaud pour nos futures machines !
II – Création VM
Pour la création de VM, le script est plus complexe, je le découpe donc en bloc pour le présenter.
Dans ce script, j’utilise des variables dans le fichier et des variables en ligne de commande. Pour les options qui ne bougent pas trop, j’ai fait le choix de ne pas les passer en CLI, car ça l’alourdit vraiment. Comme en général, je fais mes bécanes de mêmes types au même moment, je règle leurs tailles, RAM, CPU, dans le script, puis les options nominales par CLI.
Les options par CLI sont donc le nom, l’ID (sert pour la génération de l’IP) et le stockage
./create -n nomdelavm -i sonid -s 1
Pour le stockage, j’ai deux VG (voir : Stockage avancé avec KVM, LVM et Raid) donc, je choisis 0 ou 1 avec l’option -s. VG1 pour les Master, VG0 pour les Slaves.
A – Variables, checks et récapitulatif
Créez un fichier create.sh et commencez par mettre ce qui suit dedans :
#!/bin/bash # Options size_swap=1 size_var=3 size=4 ram=1024 cpu=1 # Setup KVM kvmnet="intern-vlans" vlandef="vlansadmin" vol_kvm_main="lvm1" vg_main="vg1" vol_kvm_back="lvm0" vg_back="vg0" template="debugo.tar.gz" os="debian10" dir_mount="/mnt" user="toto"
On commence par définir des variables.
- size_swap=1 : la taille du Swap, en Go
- size_var=3 : la taille de /var, en Go
- size=4 : la taille de /, en Go
- ram=1024 : ram en Mo
- cpu=1 : le nombre de cpus
Concernant la partitionnement, vous remarquerez que je mets /var à part.
Ensuite, les options « hyperviseur » :
- kvmnet= »intern-vlans » : le nom du pool dans kvm
- vlandef= »vlansadmin » : le nom du vlan dans kvm
- vol_kvm_main= »lvm1″ : le nom du LVM Main dans KVM
- vg_main= »vg1″ : le VG Main
- vol_kvm_back= »lvm0″ : le nom du LVM Back dans KVM
- vg_back= »vg0″ : le VG Back
- template= »debugo.tar.gz » : l’archive à utiliser, créée avec le script make.sh
- os= »debian10″ : le nom de l’os (Oui, meme pour Debian 11)
- dir_mount= »/mnt » : le point de montage où l’on va faire notre tambouille.
- user= »toto » : l’user par défaut
Pour le réseau, je connecte mes bécanes qu’à un seul port au démarrage. Celui ci achemine deux VLANs. Côté KVM, ce trunk est identifié sur le portgroup vlansadmin dans le réseau intern-vlans.
Plus d’informations à ce sujet ici : Gestion d’un réseau complexe KVM et Openvswitch.
Au niveau stockage, deux pools, un pour les Master, un pour les Slave, j’explique le principe ici : Stockage avancé avec KVM, LVM et Raid.
Et je termine en définissant un user par défaut. Il sera utilisé par Ansible par la suite. Et comme dans Ansible pour le moment, je ne me suis pas penché sur le stockage de mdp, pour le moment, j’utilise ce compte, en passant le mdp du compte en argument de mes playbooks (-K) afin qu’il puisse élever ses privilèges.
Ensuite, quelques checks :
while getopts n:i:s: flag do case "${flag}" in n) name=${OPTARG};; i) id=${OPTARG};; s) stock=${OPTARG};; esac done fonction info() { echo -e "\e[$2m $1 \e[39m \n" } function input_pwd() { ok=0 empty=1 while [ $ok -eq 0 -o $empty -eq 1 ] do echo "Password ?"; read -s pwd echo "Confirmation ?"; read -s pwdc if [ ! -z "$pwd" -o ! -z "$pwdc" ]; then empty=0 else empty=1; fi if [ "$pwd" = "$pwdc" ] && [ $empty -eq 0 ]; then ok=1 else echo "Les MDP doivent etre identiques et non nuls!"; fi done } # Checks params re='^[0-9]+$' if [ ! -n "$name" ];then echo 'Name (-n) manque à la pelle, oui on peut être drôle...' >&2;exit 1;fi if [ ! -n "$stock" ];then echo 'Emplacement stockage (-s) manque' >&2;exit 1;fi if [ ! -n "$id" ];then echo 'Id (-i) manque' >&2;exit 1;fi if [ -n "$id" ] && ! [[ $id =~ $re ]];then echo 'ID (-i) non numerique' >&2;exit 1;fi if ! [[ "$id" -ge 1 && "$id" -le 254 ]];then echo 'ID (-i) non correct (doit être entre 1 et 254)' >&2;exit 1;fi if [ -n "$size" ] &&! [[ $size =~ $re ]];then echo 'Size non numerique' >&2;exit 1;fi if [ -n "$cpu" ] && ! [[ $cpu =~ $re ]];then echo 'Cpu non numerique' >&2;exit 1;fi if [ -n "$ram" ] && ! [[ $ram =~ $re ]];then echo 'RAM non numerique' >&2;exit 1;fi if [ -n "$size_swap" ] && ! [[ $size_swap =~ $re ]];then echo 'Swap non numerique' >&2;exit 1;fi if [ -n "$size_var" ] && ! [[ $size_var =~ $re ]];then echo 'Var non numerique' >&2;exit 1;fi
Puis, des checks côté KVM, un recap et la demandes des mots de passes :
# Choix du vg en fonction de l'option passée if [[ $stock = 1 ]];then vol_kvm=${vol_kvm_main};vg=${vg_main} else vol_kvm=${vol_kvm_back};vg=${vg_back} fi # Verif espace libre free=$(virsh pool-info ${vol_kvm} | grep "Available" | awk '{ split($2,v,","); print v[1]; }') if [ $(($size+$size_swap+$size_var)) -ge ${free%.*} ];then info "Va falloir faire du ménage avant..." 31; exit 1 fi # Check nom vm et id if virsh dominfo ${name}> /dev/null 2>&1;then info "Une VM porte deja ce joli nom..." 31;exit 1;fi if grep -w "10.99.1.${id}" /etc/hosts;then info "ID deja pris..." 31;exit 1;fi # Affichage recap : info "Creation de la VM ${name} avec CPU: ${cpu} / RAM: ${ram}MB IP: 10.X.1.${id}" 32 info "Tailles: /:${size}G - /var: ${size_var}G - SWAP: ${size_swap}G -> vol :${vol_kvm}" 32 info "Entrée pour valider, CTRL-C pour quitter" 32 read # Lecture des mdp info "Saisir mot de passe root" 33 input_pwd pass=$pwd info "Saisir mot de passe $user" 33 input_pwd puser=$pwd
On a terminé avec les inputs et les vérifications.
B – Création des volumes et mise en place du template
Bon, arrivé la, on sait que tout est bon, on peut donc enfin y aller.
# Prep Vol dans LVM info "Préparations Disques" 32 if ! virsh vol-create-as ${vol_kvm} ${name} ${size}G ; then info "Erreur /" 31; exit 1; fi if ! virsh vol-create-as ${vol_kvm} ${name}-var ${size_var}G ; then info "Erreur /var" 31; exit 1; fi if [ $size_swap -ne 0 ]; then if ! virsh vol-create-as ${vol_kvm} ${name}-swap ${size_swap}G ; then info "Erreur Swap" 31; exit 1; fi fi # Partitionnement disque / if ! parted --script /dev/${vg}/${name} 'mklabel msdos' ; then info "Oups: parted" 31;exit 1; fi if ! parted --script /dev/${vg}/${name} 'mkpart primary ext4 1MiB 100%'; then info "Oups: parted" 31;exit 1; fi # Generation nom partition xxx1 ou xxx1p1 if [[ ${name: -1} =~ $re ]]; then part="${vg}-${name}p1"; else part="${vg}-${name}1";fi # Création FS mkfs.ext4 /dev/mapper/${part} -q -F > /dev/null mkfs.ext4 /dev/${vg}/${name}-var -q -F > /dev/null mkswap /dev/${vg}/${name}-swap > /dev/null # Montages mount /dev/mapper/${part} ${dir_mount} mkdir ${dir_mount}/var mount /dev/mapper/${vg}-${name}--var ${dir_mount}/var # Decompression template info "Decompression template ${template}" 32 tar -C ${dir_mount} -xvf ${template} --strip-components=1 > /dev/null # Montages mount -t sysfs /sys ${dir_mount}/sys mount -t proc /proc ${dir_mount}/proc mount --bind /dev ${dir_mount}/dev mount -t devpts /dev/pts ${dir_mount}/dev/pts mount --bind /tmp ${dir_mount}/tmp info "Mise à jour" 32 chroot ${dir_mount} /bin/bash -c "apt-get update && apt-get upgrade -y" > /dev/null
On créé les volumes, on colle dedans notre image préparée, le tout avec l’ensemble des montages qui va bien. On termine avec une petite update, ça ne mange pas de pain !
C – Copie Skelette
On va copier des fichiers templates dans nos VMs :
# Copie Squelette cp skel/etc/* ${dir_mount}/etc/ -r
Et on va les créer de suite !
D’abord l’arborescences :
mkdir skel mkdir skel/etc
Et ensuite créez chacun des fichiers suivants :
1 – skel/etc/default/grub
Les réglages pour Grub, je passe le nom des interfaces réseau en ethX et j’active la console.
GRUB_DEFAULT=0 GRUB_TIMEOUT=1 GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian` GRUB_CMDLINE_LINUX="console=tty0 console=ttyS0,115200n8 net.ifnames=0 biosdevname=0" GRUB_TERMINAL="console serial" GRUB_SERIAL_COMMAND="serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1"
2 – skel/etc/fstab
La future définition des volumes
UUID=rootuuid / ext4 errors=remount-ro 0 1 UUID=varuuid /var ext4 errors=remount-ro 0 1 UUID=swapuuid none swap sw 0 0
3 – skel/etc/hostname
debian
4 – skel/etc/hosts
127.0.0.1 localhost 127.0.1.1 debian
5 – skel/etc/network/interfaces
Je déclare mes interfaces Vlan 99 et 250. IP sera remplacé lors de l’execution du script.
auto lo iface lo inet loopback auto eth0.99 iface eth0.99 inet static address 10.99.1.IP netmask 255.255.255.0 auto eth0.250 iface eth0.250 inet static address 10.250.1.IP netmask 255.255.255.0
6 – skel/etc/resolv.conf
nameserver IP.DNS
Pour le moment, le serveur DNS interne n’étant pas en production, on renseignera un DNS temporaire.
7 – skel/etc/apt/sources.list
Je remarque que le sources.list d’une VM n’est pas optimal. On va donc utiliser celui de l’hyperviseur comme modele.
# mkdir skel/etc/apt # cp /etc/apt/sources.list skel/etc/apt/sources.list
Voila qui termine les fichiers du template.
D – Setup VM
Revenons dans notre script et passons à la configuration de la VM :
# Configuration de GRUB echo -e "\e[32mConfig GRUB\e[39m" chroot ${dir_mount} /bin/bash -c "grub-mkconfig -o /boot/grub/grub.cfg" > /dev/null 2>&1 chroot ${dir_mount} /bin/bash -c "grub-install /dev/mapper/${vg}-${name}" > /dev/null 2>&1 # Configuration des fichiers template echo -e "\e[32mSetup VM\e[39m" sed -i "s/IP/${id}/g" ${dir_mount}/etc/network/interfaces sed -i "s/debian/${name}/g" ${dir_mount}/etc/hostname sed -i "s/debian/${name}/g" ${dir_mount}/etc/hosts rootuuid=$(blkid -s UUID -o value "/dev/mapper/${part}") varuuid=$(blkid -s UUID -o value "/dev/mapper/${vg}-${name}--var") if [ $size_swap -ne 0 ]; then swapuuid=$(blkid -s UUID -o value "/dev/mapper/${vg}-${name}--swap"); fi sed -i "s/rootuuid/$rootuuid/g" ${dir_mount}/etc/fstab sed -i "s/varuuid/$varuuid/g" ${dir_mount}/etc/fstab if [ $size_swap -ne 0 ]; then sed -i "s/swapuuid/$swapuuid/g" ${dir_mount}/etc/fstab else sed -i '/swapuuid/d' ${dir_mount}/etc/fstab fi # Interdiction de connexion en root par ssh sed -i "s/#PermitRootLogin prohibit-password/PermitRootLogin no/g" ${dir_mount}/etc/ssh/sshd_config # Ajout user et changement pwd user et root chroot ${dir_mount} sh -c "adduser --disabled-password --gecos \"\" ${user}" > /dev/null chroot ${dir_mount} sh -c "usermod -aG sudo ${user}" chroot ${dir_mount} sh -c "echo 'root:${pass}' | chpasswd" chroot ${dir_mount} sh -c "echo '${user}:${puser}' | chpasswd" # Clé SSH, config SSH, ntp, ipv6... mkdir ${dir_mount}/home/${user}/.ssh rm ${dir_mount}/etc/ssh/ssh_host_* chroot ${dir_mount} /bin/bash -c "dpkg-reconfigure --frontend=noninteractive openssh-server" > /dev/null 2>&1 echo "net.ipv6.conf.all.disable_ipv6 = 1" >> ${dir_mount}/etc/sysctl.conf echo "server 10.99.1.250" >> ${dir_mount}/etc/ntp.conf cat /root/.ssh/id_rsa.pub > ${dir_mount}/home/${user}/.ssh/authorized_keys # Recup fingerprint rsa=$(ssh-keygen -l -f "${dir_mount}"/etc/ssh/ssh_host_ecdsa_key | cut -d' ' -f2) # Démontage final umount ${dir_mount}/{sys,proc,dev/pts,dev,tmp};umount ${dir_mount}/var;umount ${dir_mount}
E – Enregistrement VM dans KVM
Ne reste plus qu’à enregistrer la VM dans KVM. Pour se faire, on va générer un fichier xml avec virt-install et le commutateur –print-xml. Ensuite, on le retouche pour ajouter le driver vhost et on utilise virsh define avec le xml pour générer la VM. On termine en ajoutant l’IP dans le VLAN 99 dans le fichier /etc/hosts de l’hyperviseur.
info "Enregistrement VM" 32 # Param Disques if [ $size_swap -ne 0 ]; then virtswap=" --disk vol=${vol_kvm}/${name}-swap,bus=virtio"; fi virtdisks="--disk vol=${vol_kvm}/${name},bus=virtio --disk vol=${vol_kvm}/${name}-var,bus=virtio${virtswap}" # Param Nets netdef="${kvmnet},portgroup=${vlandef}" virtnets="--network network:${netdef},model=virtio,target=${name}.0" # Generation XML virt-install -n ${name} --vcpus ${cpu} --ram ${ram} --cpu host --import ${virtdisks} ${virtnets} \ --graphics none --console pty,target_type=serial --os-variant ${os} --print-xml > ${name}.xml # Petite retouche sed -i -e "/<\/interface>/i \\ \\<driver name=\"vhost\"/>" ${name}.xml # Enregistrement dans KVM virsh define ${name}.xml > /dev/null rm ${name}.xml info "Terminé !" 31 echo -e "VM prête à demarrer : virsh start ${name} --console \n" info "Fingerprint: ${rsa}" 32 echo -e "10.99.1.${id}\t${name}" >> /etc/hosts
Et voila !
F – Chasse à la faute de frappe dans le fichier
On va enfin pourvoir admirer le résultat.
Avant tout, et si vous ne l’avez jamais fait sur votre serveur :
# ssh-keygen
et ce afin de générer les clés necessaire à la connexion depuis l’hyperviseur vers les VMs.
Et pour ENFIN créer une VM :
./create_vm.sh -n mavmquirox -i 69 -s 1
Ainsi, j’aurais une VM nommée mavmquirox avec des IPs en 69 (10.x.x.69), sur le VG Master. Bien évidement, on aura adapté CPU, RAM au besoin.
Et vous le voyez, le temps de création est minime ! Bon, en meme temps, vu le temps passé sur le script, faut bien le rentabiliser !
III – Suppression de VM
Clairement la, le script n’est pas terminé, n’est pas aussi « poussé » que create.sh. Il faut triturer les options dedans pour tapper le bon VG.
Ca ne checke pas non plus si la machine tourne… Je retravaillerai dessus à l’occasion.
#!/bin/bash # A remplacer par le bon pool="lvm0" # Ou "lvm1" vg="vg0" # ou "vg1" while getopts n: flag do case "${flag}" in n) name=${OPTARG};; esac done if [ ! -n "$name" ]; then echo 'Name (-n) manque' >&2;exit 1;fi echo -e "Pas de regrets ?\nEntrée pour valider, CTRL-C pour quitter" read echo "Suppression de $name" partprobe > /dev/null 2>&1 re='^[0-9]+$' if [[ "${name: -1}" =~ $re ]]; then p="p";fi wipefs -a -f /dev/${vg}/${name} wipefs -a -f /dev/${vg}/${name}-swap wipefs -a -f /dev/${vg}/${name}-var dmsetup remove ${vg}-${name}${p}1 dmsetup remove ${vg}-${name} virsh vol-delete ${name} ${pool} > /dev/null 2>&1 virsh vol-delete ${name}-swap ${pool} > /dev/null 2>&1 virsh vol-delete ${name}-var ${pool} > /dev/null 2>&1 virsh undefine $name > /dev/null 2>&1 sed -i "/${name}/d" /etc/hosts ssh-keygen -f "/root/.ssh/known_hosts" -R "$name" echo "VM ${name} supprimée..."
IV – Conclusion
Et voila, vous avez des scripts permettant de mettre en place vos VMs de maniere très rapide. Sur mon serveur, create.sh prend moins de 20 secondes pour me créer une VM fonctionnelle.
Pour information, je suis également en train d’en faire une version python que je vous soumettrais quand ce sera opérationnel 😉