Je m’occupe depuis quelque temps de l’infrastructure de l’Amicale des Informaticiens de l’Université de Strasbourg (AIUS, pour faire court), qui est une association étudiante de l’UFR de Mathématique et Informatique à l’Université de Strasbourg.
On a donc un local, avec un canapé, des snacks et de la bonne humeur ; mais aussi une baie1 avec 2 serveurs et un switch. Sur ces serveurs on y héberge des boîtes mail, des listes de diffusion, divers sites internets et autres machines virtuelles à disposition des étudiants.
Je me suis dit qu’il serait intéressant de détailler quelques travaux et expérimentations que j’ai pu y faire. Je ne sais pas si je ferais toute une série de billets, ou si celui-ci sera le seul que j’arriverais à écrire, mais je me lance.
-
Je la prendrai en photo à l’occas. ↩
- Petit aperçu de libvirt
- Petit aperçu de mon infrastructure
- virt-install, mon amour
- Le script complet
Petit aperçu de libvirt
J’ai donc deux machines sous Debian 9, tic
et tac
.
Aucun service ne tourne directement sur ces machines, mis à part libvirt
, pour faire tourner un certain nombre de machines virtuelles.
libvirt
est une couche d’abstraction autour de divers systèmes de virtalisation tels que qemu
(kvm
), Virtual Box, Xen, LXC (conteneurs), HyperV…
Le but est de proposer une API unifiée pour tous ces outils.
Aussi, personne ne veut écrire de commande qemu à la main.
Exemple de commande QEMU que libvirt lance pour moi:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | qemu-system-x86_64 -enable-kvm -name guest=devstack,debug-threads=on -S -object secret,id=masterKey0,format=raw,file=/var/lib/libvirt/qemu/domain-5-devstack/master-key.aes -machine pc-i440fx-2.8,accel=kvm,usb=off,dump-guest-core=off -cpu Opteron_G3 -m 2048 -realtime mlock=off -smp 4,sockets=4,cores=1,threads=1 -uuid 6a938435-d5e3-4d01-95e7-4a2a250b0b95 -display none -no-user-config -nodefaults -chardev socket,id=charmonitor,path=/var/lib/libvirt/qemu/domain-5-devstack/monitor.sock,server,nowait -mon chardev=charmonitor,id=monitor,mode=control -rtc base=utc,driftfix=slew -global kvm-pit.lost_tick_policy=delay -no-hpet -no-shutdown -global PIIX4_PM.disable_s3=1 -global PIIX4_PM.disable_s4=1 -boot strict=on -device ich9-usb-ehci1,id=usb,bus=pci.0,addr=0x3.0x7 -device ich9-usb-uhci1,masterbus=usb.0,firstport=0,bus=pci.0,multifunction=on,addr=0x3 -device ich9-usb-uhci2,masterbus=usb.0,firstport=2,bus=pci.0,addr=0x3.0x1 -device ich9-usb-uhci3,masterbus=usb.0,firstport=4,bus=pci.0,addr=0x3.0x2 -device virtio-serial-pci,id=virtio-serial0,bus=pci.0,addr=0x4 -drive file=/var/lib/libvirt/images/devstack.qcow2,format=qcow2,if=none,id=drive-virtio-disk0 -device virtio-blk-pci,scsi=off,bus=pci.0,addr=0x5,drive=drive-virtio-disk0,id=virtio-disk0,bootindex=1 -netdev tap,fd=28,id=hostnet0,vhost=on,vhostfd=30 -device virtio-net-pci,netdev=hostnet0,id=net0,mac=52:54:00:cd:8a:3f,bus=pci.0,addr=0x2 -chardev pty,id=charserial0 -device isa-serial,chardev=charserial0,id=serial0 -chardev socket,id=charchannel0,path=/var/lib/libvirt/qemu/channel/target/domain-5-devstack/org.qemu.guest_agent.0,server,nowait -device virtserialport,bus=virtio-serial0.0,nr=1,chardev=charchannel0,id=channel0,name=org.qemu.guest_agent.0 -device usb-tablet,id=input0,bus=usb.0,port=1 -device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x6 -msg timestamp=on |
libvirt
se présente comme un démon, libvirtd
, qui tourne sur l’hyperviseur offrant une API utilisée ensuite par une multitude d’outils.
virt-manager
par exemple propose une interface graphique permettant de se connecter à plusieurs démons libvirtd
.
virsh
est un outil en ligne de commande pour gérer les machines tournant via libvirtd
.
Tout ce qui est possible (ou presque) avec virsh
l’est aussi avec virt-manager
et vice-versa. Ce sont les deux outils de référence développés par l’équipe de libvirt
.
On va parler plus loin de virt-install
, qui est un outil permettant de scripter facilement l’installation de machines virtuelles en ligne de commande.
Petit aperçu de mon infrastructure
Je vais m’étendre un chouilla sur l’aspect surtout réseau de mon infrastructure. C’est assez spécifique à mon utilisation, donc si ça vous intéresse pas, vous pouvez sauter complètement cette partie.
À l’inverse, si mes aventures réseau vous intéresse, je peux complètement détailler ça dans un autre billet.
La DI de ma fac nous laisse à disposition tout le sous-réseau 130.79.49.192/27 en v4 (donc 32 IPs)1 + un /64 en v6.
J’aurais pû assigner ces IPs directement au machines virtuelles via un bridge, mais je préfère avoir un réseau privé avec mes machines virtuelles (10.0.0.0/16
, pour faire dans l’originalité), et une machine qui sert de NAT + DHCP + serveur DNS interne.
Cette machine s’appelle gateway
, et tourne sous OpenBSD (parce que OpenBSD (et pf
) ça roxxe). C’est la seule machine qui a accès aux deux réseaux, le réseau publique et privé.
Elle a un certain nombre d’IP, et redirige différents ports vers différentes VMs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | gateway# ifconfig -A lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 32768 … vio0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 1500 lladdr 52:54:00:11:6e:e9 index 1 priority 0 llprio 3 groups: egress media: Ethernet autoselect status: active inet 130.79.49.198 netmask 0xffffffc0 broadcast 130.79.49.255 inet 130.79.49.208 netmask 0xffffffff inet 130.79.49.209 netmask 0xffffffff … inet 130.79.49.214 netmask 0xffffffff inet 130.79.49.215 netmask 0xffffffff … vio1: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 1500 lladdr 52:54:00:d7:77:65 index 2 priority 0 llprio 3 media: Ethernet autoselect status: active inet 10.0.0.1 netmask 0xffff0000 broadcast 10.0.255.255 … |
Extrait de /etc/pf.conf
:
1 2 3 4 5 6 | pass in inet proto tcp from any to { ($int_if) (egress) } port { 80 443 } rdr-to $vm_www pass in on egress inet proto tcp from any to (egress) port { 25 143 465 587 993 } rdr-to $vm_mail pass in on egress inet proto tcp from any to $r1 port { 80 443 } rdr-to $vm_chef pass in on egress inet proto tcp from any to $r2 port { 80 443 } rdr-to $vm_monitor pass in on egress inet proto tcp from any to $r4 port { 80 443 } rdr-to $vm_rmll2018 |
Cette machine fait aussi tourner un serveur DHCP pour les machines virtuelles, ainsi qu’un serveur DNS local (unbound
), pour la zone int.aius.u-strasbg.fr
.
Un petit extrait de la configuration unbound
pour vous donner une idée de son utilité:
1 2 3 4 5 6 7 8 9 | local-zone: "int.aius.u-strasbg.fr." static local-data: "gateway.int.aius.u-strasbg.fr. IN A 10.0.0.1" local-data: "switch.int.aius.u-strasbg.fr. IN A 10.0.0.2" local-data: "tic.int.aius.u-strasbg.fr. IN A 10.0.0.3" local-data: "tac.int.aius.u-strasbg.fr. IN A 10.0.0.4" local-data: "ldap.int.aius.u-strasbg.fr. IN A 10.0.1.1" local-data: "www.int.aius.u-strasbg.fr. IN A 10.0.1.2" local-data: "mysql.int.aius.u-strasbg.fr. IN A 10.0.1.3" local-data: "mail.int.aius.u-strasbg.fr. IN A 10.0.1.4" |
Le serveur DHCP annonce comme domaine de recherche int.aius.u-strasbg.fr
.
De cette manière, toutes les machines sont accessibles « entre elles » avec leur nom court.
Je peux donc faire à tout moment ssh www
pour me connecter sur la VM www
depuis n’importe quelle VM (modulo la clé SSH/le mot de passe).
De même, aucuns de mes fichiers de configuration n’utilise d’IP en dur.
La machine www
va contacter la machine mysql
simplement par son hostname réduit.
Bref, ayez un serveur DNS local, et des noms courts et reconnaissables pour vos machines: ça rend le déplacement et les réorganisations (genre, changement d’IP d’une machine) beaucoup plus simples.
-
Ouais, ils ont beaucoup d’IPv4 à l’Unistra. ↩
virt-install, mon amour
virt-install
permet donc de créer une machine virtuelle en ligne de commande.
Donnez-lui quelques infos et une image ISO, il va allouer les ressources nécessaires et démarrer la machine virtuelle sur l’image que vous lui avez donné.
1 2 3 4 5 6 7 | virt-install \ --name debian9 \ --ram 1024 \ --disk pool=default,size=10,format=qcow2,bus=virtio \ --vcpus 1 \ --network bridge=virbr0 \ --location https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-9.3.0-amd64-netinst.iso |
Cela va donc créer une machine virtuelle debian9
en utilisant l’image d’installation réseau de Debian (on peut lui donner aussi un chemin vers une image locale si besoin), 1 Go de RAM, 10 Go d’espace disque et 1 vCPU.
Sauf que par défaut, il va donner un écran à la machine, et il va donc falloir y accéder via virt-viewer
pour pouvoir faire quoi que ce soit. Sauf que moi j’aime bien mon terminal, et j’aimerai pouvoir y rester dedans encore un peu.
La console série
On a dû me dire en cours1 à un moment qu’à l’époque, on utilisait des terminaux avec des liasons séries. Des trucs donc, avec que du texte. Chouette.
Linux prend bien sûr encore en charge cette console série, et l’installateur de Debian fonctionne aussi très bien uniquement avec une console série.
1 2 3 4 5 6 7 8 9 10 | virt-install \ --name debian9 \ --ram 1024 \ --disk pool=default,size=10,format=qcow2,bus=virtio \ --vcpus 1 \ --network bridge=virbr0 \ --location="http://ftp.fr.debian.org/debian/dists/stable/main/installer-amd64/" \ --extra-args="auto console=ttyS0,115200n8 serial" \ --graphics none \ --console pty,target_type=serial |
Et voilà, virt-install
va afficher tout seul la console série, avec l’installeur Debian:
Vous noterez aussi que l’URL a changé: on ne démarre plus sur une image disque (ISO) mais directement sur un kernel (que libvirt va trouver je ne sais comment).
C’est important pour injecter des arguments au démarrage (le --extra-args
).
Le preseed
Ça c’est la partie fun.
Comme beaucoup de distribtions, Debian propose une solution pour automatiser les réponses de l’installeur.
On retrouve par exemple kickstart chez Ubuntu, Fedora ou encore CentOS.
Sur Debian, il s’agit simplement d’un fichier de réponses pour debian-installer
.
1 2 3 4 5 6 7 8 9 | $ cat preseed.cfg d-i debian-installer/locale select en_US d-i console-setup/ask_detect boolean false d-i keyboard-configuration/xkb-keymap select fr(latin9) $ virt-install \ # … --location="http://ftp.fr.debian.org/debian/dists/stable/main/installer-amd64/" \ --initrd-inject=preseed.cfg |
Avec ces trois options, l’installeur va autoconfigurer la langue et la disposition du clavier. Toutes les options de l’installeur sont auto-configurables.
Le fichier de preseed complet que j’utilise.
La clé HOSTNAME
et PASSWORD
sont remplacés à la volée par un moteur de template.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | d-i debian-installer/locale select en_US d-i console-setup/ask_detect boolean false d-i keyboard-configuration/xkb-keymap select fr(latin9) d-i netcfg/choose_interface select auto d-i netcfg/get_hostname string HOSTNAME d-i netcfg/get_domain string int.aius.u-strasbg.fr d-i netcfg/disable_dhcp boolean false d-i mirror/country string manual d-i mirror/http/hostname string ftp.fr.debian.org d-i mirror/http/directory string /debian d-i mirror/http/proxy string d-i netcfg/wireless_wep string d-i clock-setup/utc boolean true d-i time/zone select Europe/Paris d-i partman-auto/disk string /dev/vda d-i partman-auto/method string lvm d-i partman-auto/purge_lvm_from_device boolean true d-i partman-lvm/device_remove_lvm boolean true d-i partman-lvm/device_remove_lvm_span boolean true d-i partman-lvm/confirm boolean true d-i partman-auto/choose_recipe select atomic d-i partman/confirm_write_new_label boolean true d-i partman/choose_partition select finish d-i partman/confirm boolean true d-i partman/confirm_nooverwrite boolean true d-i passwd/root-login boolean true d-i passwd/make-user boolean false d-i passwd/root-password password PASSWORD d-i passwd/root-password-again password PASSWORD tasksel tasksel/first multiselect standard ssh-server # Workaround for https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=666974 d-i grub-installer/only_debian boolean false d-i grub-installer/bootdev string /dev/vda # d-i apt-setup/non-free boolean true # d-i apt-setup/contrib boolean true d-i apt-setup/use_mirror boolean true d-i apt-setup/security_host string security.debian.org d-i apt-setup/services-select multiselect security, updates d-i debian-installer/add-kernel-opts string console=ttyS0,115200n8 serial popularity-contest popularity-contest/participate boolean false d-i finish-install/reboot_in_progress note |
-
Je suis en 3è année de license d’informatique. ↩
Le script complet
Plus qu’à enrober ça dans un petit script shell.
Dans mon infrastructure, il faut rajouter la machine au niveau du serveur DHCP pour qu’elle ait la bonne IP assignée. Pour ça, il me faut l’adresse MAC de l’interface réseau de la machine virtuelle pour pouvoir l’ajouter à la configuration de mon serveur DHCP.
1 2 3 4 5 6 7 | $ virsh domiflist --domain debian9 Interface Type Source Model MAC ------------------------------------------------------- vnet2 bridge virbr0 virtio 52:54:00:f5:6f:a1 $ virsh domiflist --domain debian9| awk '/bridge/ { print $5 }' 52:54:00:f5:6f:a1 |
Pour l’instant, je me contente d’afficher les instructions à l’écran. À l’avenir j’aimerai que ces changements soient fait automatiquement.
Le script complet:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | #!/usr/bin/zsh DOMAIN=$1 if [ -z "$DOMAIN" ]; then echo "usage: $0 hostname" >&2 exit 1 fi # On génère le fichier de preseed en remplaçant `HOSTNAME` par le nom de la machine. m4 -DHOSTNAME=$DOMAIN < preseed.cfg.in > preseed.cfg OPTIONS=( --name=$DOMAIN --vcpus=4 --ram=2048 --location="http://ftp.fr.debian.org/debian/dists/stable/main/installer-amd64/" --initrd-inject=preseed.cfg --extra-args="auto console=ttyS0,115200n8 serial" --graphics none --console pty,target_type=serial --network bridge=br1 --os-type=linux --os-variant=debian9 --disk=pool=default,size=50,format=qcow2,bus=virtio ) echo "|> Running virt-install" virt-install $OPTIONS --noreboot reset MAC=`virsh domiflist --domain $DOMAIN | awk '/bridge/ { print $5 }'` cat - <<EOF |> Install complete. Please configure dhcpd and unbound on gateway - Choose an IP (10.0.1.X) - in /var/unbound/etc/unbound/conf: local-data: "$DOMAIN.int.aius.u-strasbg.fr. IN A 10.0.1.X" - in /etc/dhcpd.conf: host $DOMAIN { hardware ethernet $MAC; fixed-address 10.0.1.X; option host-name "$DOMAIN"; } - run: rcctl restart dhcpd rcctl reload unbound EOF read -p "Press enter when done." echo "|> Starting domain \`$DOMAIN'" virsh start --domain $DOMAIN |
Le fichier preseed.cfg.in
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | d-i debian-installer/locale select en_US d-i console-setup/ask_detect boolean false d-i keyboard-configuration/xkb-keymap select fr(latin9) d-i netcfg/choose_interface select auto d-i netcfg/get_hostname string HOSTNAME d-i netcfg/get_domain string int.aius.u-strasbg.fr d-i netcfg/disable_dhcp boolean false d-i mirror/country string manual d-i mirror/http/hostname string ftp.fr.debian.org d-i mirror/http/directory string /debian d-i mirror/http/proxy string d-i netcfg/wireless_wep string d-i clock-setup/utc boolean true d-i time/zone select Europe/Paris d-i partman-auto/disk string /dev/vda d-i partman-auto/method string lvm d-i partman-auto/purge_lvm_from_device boolean true d-i partman-lvm/device_remove_lvm boolean true d-i partman-lvm/device_remove_lvm_span boolean true d-i partman-lvm/confirm boolean true d-i partman-auto/choose_recipe select atomic d-i partman/confirm_write_new_label boolean true d-i partman/choose_partition select finish d-i partman/confirm boolean true d-i partman/confirm_nooverwrite boolean true d-i passwd/root-login boolean true d-i passwd/make-user boolean false d-i passwd/root-password password pas-le-vrai d-i passwd/root-password-again password pas-le-vrai tasksel tasksel/first multiselect standard ssh-server # Workaround for https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=666974 d-i grub-installer/only_debian boolean false d-i grub-installer/bootdev string /dev/vda # d-i apt-setup/non-free boolean true # d-i apt-setup/contrib boolean true d-i apt-setup/use_mirror boolean true d-i apt-setup/security_host string security.debian.org d-i apt-setup/services-select multiselect security, updates d-i debian-installer/add-kernel-opts string console=ttyS0,115200n8 serial popularity-contest popularity-contest/participate boolean false d-i finish-install/reboot_in_progress note |
Petit bonus: installation automatique d’une clé SSH
C’est un peu crade, mais l’installeur debian permet de lancer une commande en post-installation. Chez moi, ça rajoute la clé SSH que j’utilise partout:
1 | d-i preseed/late_command string in-target sed -i '/cdrom/d' /etc/apt/sources.list; chroot /target sh -c "mkdir -pm 700 /root/.ssh && echo 'ssh-ed25519 AAAAC3Nza...US4uhq553XgYq8ltBP6 root@tac' > /root/.ssh/authorized_keys && chmod 0600 /root/.ssh/authorized_keys && chown -R root:root /root/.ssh" |
J’espère que ce premier billet vous a plu. J’écrit ça à l’arrache en partie dans le train ; n’hésitez pas à me le signaler si vous trouvez des coquilles, ou si je ne suis pas clair.
Quelques liens en vrac:
https://raymii.org/s/articles/virt-install_introduction_and_copy_paste_distro_install_commands.html