345 lines
12 KiB
ReStructuredText
345 lines
12 KiB
ReStructuredText
Construction de l'image qcow2
|
|
=============================
|
|
|
|
Toutes les VM du cluster — ``intel``, PostgreSQL, route reflector et les suivantes — partent
|
|
d'une même image qcow2 « golden », construite une fois puis réutilisée. Cette page décrit la
|
|
procédure en service.
|
|
|
|
.. important::
|
|
|
|
Cette image est un **artefact redistribuable** : tout ce qui s'y trouve se retrouve dans
|
|
chaque VM qui en dérive. Les étapes de nettoyage de la fin ne sont pas une commodité, ce sont
|
|
des exigences.
|
|
|
|
Principe
|
|
--------
|
|
|
|
La construction se fait dans une **VM jetable**, et non par montage de l'image sur l'host : le
|
|
chroot a besoin d'un noyau et d'un espace utilisateur cohérents avec la distribution cible, ce
|
|
que l'host ne fournit pas nécessairement.
|
|
|
|
Cette VM de construction démarre sur un overlay de l'image du fournisseur et voit deux disques
|
|
supplémentaires : le futur disque « golden », et un espace de travail.
|
|
|
|
.. mermaid::
|
|
|
|
graph LR
|
|
ISO["seed.iso<br/>cloud-init NoCloud"] --> BVM
|
|
OVL["<os>-tmp.qcow2<br/><i>overlay, jetable</i>"] --> BVM["VM de construction"]
|
|
BVM --> ROOT["<os>-root.qcow2<br/><b>image golden</b>"]
|
|
BVM --> WORK["tmp.qcow2<br/><i>espace de travail</i>"]
|
|
BASE["image du fournisseur<br/>(qcow2)"] -.backing file.-> OVL
|
|
|
|
.. list-table::
|
|
:header-rows: 1
|
|
:widths: 26 20 54
|
|
|
|
* - Disque
|
|
- Vu dans la VM
|
|
- Rôle
|
|
* - ``<os>-tmp.qcow2``
|
|
- ``vda`` (virtio-blk)
|
|
- système de la VM de construction ; overlay de l'image du fournisseur, jeté à la fin
|
|
* - ``<os>-root.qcow2``
|
|
- ``sda`` (SCSI)
|
|
- **le résultat** : l'image golden, écrite en brut depuis la VM
|
|
* - ``tmp.qcow2``
|
|
- ``sdb`` (SCSI)
|
|
- espace de travail : téléchargement et conversion
|
|
|
|
Variables
|
|
---------
|
|
|
|
.. code-block:: bash
|
|
|
|
export os=<nom_os>
|
|
export os_link=<url_du_qcow2_fournisseur>
|
|
export os_file=<nom_du_fichier_qcow2>
|
|
export os_dir=<repertoire_de_telechargement>
|
|
export disk_dir=<repertoire_des_disques>
|
|
|
|
Étape 1 — Le seed cloud-init de la VM de construction
|
|
------------------------------------------------------
|
|
|
|
Ce seed ne concerne **que la VM de construction**. Il n'a aucun rapport avec la configuration
|
|
cloud-init de l'image produite, qui est posée plus loin en chroot. Son seul rôle est de donner
|
|
un accès à la VM le temps du build.
|
|
|
|
.. code-block:: bash
|
|
|
|
mkdir -p "${os_dir}" && cd "${os_dir}"
|
|
mkdir -p /opt/seed/${os}
|
|
|
|
cat << 'ENDFILE' > /opt/seed/${os}/meta-data
|
|
instance-id: iid-local01
|
|
local-hostname: my-vm-01
|
|
ENDFILE
|
|
|
|
cat << 'ENDFILE' > /opt/seed/${os}/network-config
|
|
version: 2
|
|
renderer: networkd
|
|
ethernets:
|
|
eth0:
|
|
dhcp4: true
|
|
ENDFILE
|
|
|
|
cat << 'ENDFILE' > /opt/seed/${os}/user-data
|
|
#cloud-config
|
|
users:
|
|
- name: <utilisateur>
|
|
lock_passwd: false
|
|
passwd: "<hash du mot de passe>"
|
|
sudo: ALL=(ALL) NOPASSWD:ALL
|
|
ssh_authorized_keys:
|
|
- <clé publique ssh>
|
|
ENDFILE
|
|
|
|
mkisofs -o /opt/seed/${os}_seed.iso -V cidata -J -r /opt/seed/${os}/
|
|
|
|
Le label de volume ``cidata`` n'est pas décoratif : c'est ce qui fait reconnaître l'ISO comme une
|
|
source NoCloud par cloud-init.
|
|
|
|
.. warning::
|
|
|
|
``passwd`` attend un **hash**, et ``ssh_authorized_keys`` une clé publique personnelle : ces
|
|
deux valeurs sont des données à ne pas recopier hors de l'host de construction. Elles ne
|
|
figurent volontairement pas dans cette documentation.
|
|
|
|
``openssl passwd -5`` pour generer un hash
|
|
|
|
Étape 2 — Les disques
|
|
---------------------
|
|
|
|
.. code-block:: bash
|
|
|
|
curl "${os_link}" -O
|
|
|
|
qemu-img create -f qcow2 "${disk_dir}/${os}-root.qcow2" 10G
|
|
qemu-img create -f qcow2 "${disk_dir}/tmp.qcow2" 50G
|
|
qemu-img create -f qcow2 -b "${os_dir}/${os_file}" -F qcow2 "${disk_dir}/${os}-tmp.qcow2" 10G
|
|
|
|
.. important::
|
|
|
|
``-F qcow2`` est **obligatoire** sur qemu récent : sans lui, le format du backing file n'est
|
|
pas figé dans l'en-tête de l'overlay.
|
|
|
|
La taille de ``<os>-root.qcow2`` (10 Gio ici) borne l'image produite : elle doit être au moins
|
|
égale à la taille **virtuelle** de l'image du fournisseur, pas à la taille de son fichier.
|
|
|
|
Étape 3 — Lancer la VM de construction
|
|
--------------------------------------
|
|
|
|
.. code-block:: bash
|
|
|
|
qemu-system-x86_64 \
|
|
-enable-kvm \
|
|
-cpu host \
|
|
-m 2048 \
|
|
-smp 2 \
|
|
-nographic \
|
|
-serial mon:stdio \
|
|
-monitor unix:/tmp/vm-build.mon-sock,server,nowait \
|
|
-drive file=/opt/seed/${os}_seed.iso,media=cdrom,if=ide \
|
|
\
|
|
-drive file=${disk_dir}/${os}-tmp.qcow2,format=qcow2,if=none,id=vda \
|
|
-device virtio-blk-pci,drive=vda,bootindex=0 \
|
|
\
|
|
-device virtio-scsi-pci,id=scsi0 \
|
|
\
|
|
-drive file=${disk_dir}/${os}-root.qcow2,if=none,id=hd0 \
|
|
-device scsi-hd,drive=hd0,bus=scsi0.0 \
|
|
\
|
|
-drive file=${disk_dir}/tmp.qcow2,if=none,id=hd1 \
|
|
-device scsi-hd,drive=hd1,bus=scsi0.0 \
|
|
\
|
|
-netdev tap,id=net0,ifname=tap0,script=no,downscript=no \
|
|
-device virtio-net-pci,netdev=net0,mac=00:22:33:00:00:01
|
|
|
|
La répartition virtio-blk pour le système / SCSI pour les disques supplémentaires est la même que
|
|
celle qu'impose l'agent — voir :doc:`/architecture/contraintes`. Le tap ``tap0`` doit exister et
|
|
être raccordé à un réseau qui donne un accès sortant : la suite télécharge l'image du
|
|
fournisseur depuis la VM.
|
|
|
|
Étape 4 — Écrire l'image du fournisseur sur le disque cible
|
|
------------------------------------------------------------
|
|
|
|
Les commandes suivantes s'exécutent **dans la VM de construction**. Identifier d'abord les
|
|
disques : le disque de travail et le disque cible ne doivent pas être confondus.
|
|
|
|
.. danger::
|
|
|
|
``qemu-img convert`` écrase intégralement le disque cible. Vérifier les noms avant, avec
|
|
``lsblk``, plutôt que de supposer l'ordre d'énumération.
|
|
|
|
.. code-block:: bash
|
|
|
|
work_disk=/dev/sdb
|
|
os_disk=/dev/sda
|
|
|
|
mkdir /work
|
|
mkfs.xfs ${work_disk}
|
|
mount ${work_disk} /work
|
|
cd /work
|
|
|
|
curl "${os_link}" -O
|
|
qemu-img convert ./*.qcow2 -O raw "${os_disk}"
|
|
|
|
L'image du fournisseur est écrite **en brut** directement sur le disque cible : le qcow2 obtenu
|
|
côté host contient donc une image disque complète et amorçable, sans backing file.
|
|
|
|
.. code-block:: bash
|
|
|
|
partprobe
|
|
echo 1 > /sys/block/sda/device/rescan
|
|
sleep 2
|
|
|
|
# La partition racine est la plus grande du disque
|
|
root_partition=$(fdisk -lo device,size "${os_disk}" | grep -E '^/dev/' | tr -s ' ' \
|
|
| sort -rhk2 | head -n1 | cut -d ' ' -f1)
|
|
|
|
mount -o nouuid $root_partition /mnt
|
|
mount -o bind /dev /mnt/dev
|
|
mount -o bind /proc /mnt/proc
|
|
mount -o bind /sys /mnt/sys
|
|
|
|
cp /etc/resolv.conf /mnt/etc/resolv.conf
|
|
|
|
``-o nouuid`` est nécessaire parce que le système de fichiers qui vient d'être écrit porte le
|
|
même UUID que celui déjà monté par la VM de construction. Le ``resolv.conf`` est copié pour que
|
|
les commandes en chroot aient la résolution DNS ; il est supprimé au nettoyage.
|
|
|
|
Étape 5 — Personnaliser l'image
|
|
-------------------------------
|
|
|
|
**Accès SSH**
|
|
|
|
.. code-block:: bash
|
|
|
|
yum install -y augeas
|
|
|
|
echo "The default user for Syonad VMs is 'syonad'." > /mnt/etc/banner
|
|
|
|
augtool -r /mnt -s <<'EOF'
|
|
set /files/etc/ssh/sshd_config/X11Forwarding no
|
|
set /files/etc/ssh/sshd_config/PermitTunnel no
|
|
set /files/etc/ssh/sshd_config/PermitRootLogin no
|
|
set /files/etc/ssh/sshd_config/RSAAuthentication yes
|
|
set /files/etc/ssh/sshd_config/PubkeyAuthentication yes
|
|
set /files/etc/ssh/sshd_config/PasswordAuthentication no
|
|
set /files/etc/ssh/sshd_config/UseDNS no
|
|
set /files/etc/ssh/sshd_config/ChallengeResponseAuthentication no
|
|
set /files/etc/ssh/sshd_config/GSSAPIAuthentication no
|
|
set /files/etc/ssh/sshd_config/Match[1]/Condition/User "root,centos,ubuntu,debian,ec2-user"
|
|
set /files/etc/ssh/sshd_config/Match[1]/Settings/Banner "/etc/banner"
|
|
EOF
|
|
|
|
``PasswordAuthentication no`` vaut pour toutes les VM dérivées : l'accès se fait par clé, et le
|
|
champ ``password`` de l'API de l'agent ne sert donc **pas** à ouvrir une session SSH.
|
|
|
|
**Utilisateur par défaut et source de metadata**
|
|
|
|
.. code-block:: bash
|
|
|
|
cat << 'ENDFILE' > /mnt/etc/cloud/cloud.cfg.d/20_user.cfg
|
|
system_info:
|
|
default_user:
|
|
name: syonad
|
|
ENDFILE
|
|
|
|
cat << 'ENDFILE' > /mnt/etc/cloud/cloud.cfg.d/99_metadata.cfg
|
|
datasource_list: [ NoCloud ]
|
|
datasource:
|
|
NoCloud:
|
|
seedfrom: 'http://169.254.169.254:80/'
|
|
timeout: 5
|
|
max_wait: 10
|
|
ENDFILE
|
|
|
|
C'est ce second fichier qui raccorde l'image au serveur de metadata de l'agent : ``NoCloud`` est
|
|
la seule source retenue, et elle pointe sur ``169.254.169.254``. La route ``/32`` vers cette
|
|
adresse est indispensable côté agent — voir :doc:`/concepts/modes-reseau`.
|
|
|
|
**Services et durcissement**
|
|
|
|
.. code-block:: bash
|
|
|
|
chroot /mnt/ systemctl enable fstrim.timer
|
|
|
|
chroot /mnt/ systemctl disable rpcbind.service
|
|
chroot /mnt/ systemctl disable rpcbind.socket
|
|
|
|
augtool -r /mnt -s set /files/etc/selinux/config/SELINUX disabled
|
|
|
|
chroot /mnt/ dnf remove -y 'cockpit*'
|
|
chroot /mnt/ rm -rf /run/cockpit
|
|
|
|
Étape 6 — Nettoyer, puis éteindre
|
|
---------------------------------
|
|
|
|
.. code-block:: bash
|
|
|
|
rm -f /mnt/etc/resolv.conf
|
|
rm -rf /mnt/var/cache/yum
|
|
rm -rf /mnt/root/.ssh
|
|
rm -rf /mnt/root/.bash_history
|
|
rm -rf /mnt/tmp/*
|
|
rm -rf /mnt/var/lib/dhcp/*
|
|
rm -rf /mnt/var/tmp/*
|
|
find /mnt/var/log ! -type d -exec rm '{}' \;
|
|
rm -rf /mnt/var/lib/cloud/*
|
|
|
|
poweroff
|
|
|
|
``/mnt/var/lib/cloud/*`` est le nettoyage le plus important : c'est lui qui fait que cloud-init
|
|
considère chaque VM dérivée comme une instance neuve. Sans lui, l'image embarque l'identité de
|
|
l'instance de construction et le user-data n'est pas appliqué — même mécanisme que la
|
|
recréation d'une VM sous un nom déjà utilisé, cf. :doc:`/concepts/metadata-cloud-init`.
|
|
|
|
Une fois la VM éteinte, ``${disk_dir}/${os}-root.qcow2`` est l'image golden.
|
|
``${os}-tmp.qcow2`` et ``tmp.qcow2`` sont jetables.
|
|
|
|
Points de vigilance
|
|
-------------------
|
|
|
|
.. warning::
|
|
|
|
**SELinux est désactivé** dans l'image. C'est une couche de protection en moins sur toutes les
|
|
VM qui en dérivent, y compris celles qui portent des fonctions sensibles comme le route
|
|
reflector ou la base de données. Décision à assumer explicitement, et à réévaluer : le mode
|
|
``permissive`` permettrait au minimum de savoir ce qui serait bloqué.
|
|
|
|
.. note::
|
|
|
|
``fstrim.timer`` est activé dans l'image, mais l'agent lance QEMU **sans** ``discard=unmap``
|
|
ni ``detect-zeroes=unmap`` sur les disques. Le ``fstrim`` du guest ne rend donc aujourd'hui
|
|
aucun espace à l'host : les qcow2 ne se rétractent pas. L'activation reste utile pour le jour
|
|
où l'option sera ajoutée côté agent, mais ne pas compter dessus pour la place disque.
|
|
|
|
.. warning::
|
|
|
|
**La barre oblique finale de** ``seedfrom`` **est indispensable avant cloud-init 23.1.**
|
|
cloud-init construit l'URL des documents en concaténant ``seedfrom`` avec ``meta-data``,
|
|
``user-data`` et ``vendor-data`` (``util.read_seeded``). Jusqu'à la 22.4 — celle de Debian 12,
|
|
22.4.2 —, sans barre oblique finale il demande ``http://169.254.169.254:80meta-data`` : la
|
|
source échoue, la VM démarre en ``DataSourceNone``, sans nom d'hôte ni user-data. À partir de
|
|
23.1, un drapeau actif par défaut (``NOCLOUD_SEED_URL_APPEND_FORWARD_SLASH``) ajoute la barre
|
|
oblique manquante, ce qui explique qu'une image récente fonctionne sans elle.
|
|
|
|
Vérifié le 2026-10-04 dans le lab (#50), sur une VM Debian 12 lancée par l'agent : sans barre
|
|
oblique, ``Datasource DataSourceNone`` ; avec, ``DataSourceNoCloudNet
|
|
[seed=…http://169.254.169.254…]`` et le nom d'hôte de la VM. Avec la barre oblique, la
|
|
configuration fonctionne quelle que soit la version de cloud-init.
|
|
|
|
.. note::
|
|
|
|
**Provenance de l'image du fournisseur** — tout ce qui est construit en hérite. Vérifier la
|
|
somme de contrôle, et la signature quand elle existe, avant de construire dessus. La
|
|
procédure actuelle télécharge l'image deux fois, une fois sur l'host et une fois dans la VM,
|
|
sans vérification.
|
|
|
|
.. note::
|
|
|
|
**À rédiger** — la procédure ne dit pas encore comment l'image produite est nommée, versionnée
|
|
et distribuée aux hyperviseurs, ni quelles variantes existent par rôle (``intel``, PostgreSQL,
|
|
route reflector) : image unique personnalisée au démarrage par cloud-init, ou images
|
|
dérivées ?
|