Fedora livre son système d'exploitation comme un conteneur OCI. Kernel, bootloader, drivers, tout est dans l'image. Voici comment ça marche.
J'ai découvert bootc par accident, en installant Bazzite sur une vieille machine.
Par défaut l'OS est livré avec des drivers génériques. Pas de nvidia-smi pour votre GPU.
Donc je regarde et je comprends rapidement qu'il me faut la version de l'OS dédiée à mon matériel. Sur le coup j'étais découragé car je sais que ça prend du temps.
Mais en me renseignant je comprends qu'en fait la réinstallation complète avec drivers dédiés consiste juste à remplacer le container de l'OS par un autre. 🤯
Et c'est l'expérience à l'usage qui m'a fait remonter à la techno.
Une mise à jour spécifique à mon matériel qui passe en 7 minutes, drivers compris. C'est mon record personnel. Je n'ai jamais installé un OS plus ses drivers en moins de 30 ou 40 minutes.
Donc je suis allé lire la documentation Fedora. Et l'approche est excellente.
Un OS livré comme une image de conteneur
Le principe tient en une phrase. La documentation bootc résume les conteneurs bootables comme des mises à jour transactionnelles, en place, du système d'exploitation, en utilisant des images de conteneur OCI ou Docker.
Ce n'est pas un conteneur applicatif qui tourne au-dessus d'un OS. Le kernel Linux, le bootloader et les drivers font partie de l'image, ce qui la rend bootable.
Concrètement, ça veut dire que vous manipulez votre système avec l'outillage que vous connaissez déjà :
- un Containerfile pour décrire l'OS
- Podman, Docker ou buildkit pour le construire
- un registry pour le distribuer
- un tag pour versionner
Vous construisez un conteneur bootable exactement comme un conteneur applicatif, avec les technologies existantes.
Comment on construit et on déploie ça
Le point de départ, c'est une image de base préparée pour cet usage. Elle contient un kernel, un bootloader et OSTree, et il en existe pour AlmaLinux, CentOS Stream, Fedora et Red Hat Enterprise Linux.
Ensuite vous dérivez :
FROM quay.io/fedora/fedora-bootc:42
RUN dnf install -y nginx && dnf clean all
COPY config/ /etc/
RUN systemctl enable nginx
RUN bootc container lint
Vous buildez, vous poussez sur un registry. Et pour provisionner une machine neuve, vous convertissez l'image en image disque.
bootc sait s'installer par dessus un système existant, mais le plus courant est de passer par une image disque au format ISO, raw ou qcow2 pour provisionner un nouveau système.
C'est le rôle de bootc-image-builder, qui est lui même un conteneur.
Après, sur la machine déployée, tout se joue en trois verbes :
- bootc upgrade, qui va chercher la nouvelle image et la met en attente pour le prochain boot
- bootc switch, qui fait la même chose en changeant l'image suivie
- bootc rollback, qui remet l'ancien déploiement en tête du bootloader
C'est un modèle A/B, porté aujourd'hui par ostree. Et bootc switch a exactement le même effet que bootc upgrade, la seule différence étant l'image de conteneur suivie.
C'est là que se joue mon histoire de 7 minutes. Une image validée en amont, un pull, un reboot.
Ce qui est en lecture seule, et ce qui persiste
C'est la partie qu'il faut comprendre avant de se lancer.
Avec le backend composefs activé, la racine entière est un système de fichiers en lecture seule, ce qui est important pour obtenir les bonnes garanties. Deux répertoires échappent à la règle.
/etc d'abord. Il contient par défaut un état mutable persistant, et il hérite de la sémantique OSTree qui applique une fusion à trois voies lors des mises à jour. En clair :
- le nouveau /etc par défaut sert de base
- le diff entre l'actuel et le précédent est réappliqué dessus
- vos fichiers modifiés localement sont conservés
/var ensuite. Son contenu persiste par défaut, il n'en existe qu'un seul, et il est partagé entre les déploiements. La logique est celle d'un volume Docker : un downgrade de postgresql ne doit pas toucher la base physique dans /var/lib/postgres, et une mise à jour ou un rollback bootc non plus.
Attention à un piège. Le contenu que vous mettez dans /var au build se comporte comme un VOLUME Docker : il n'est décompressé qu'à partir de l'image initiale, les changements ultérieurs ne sont pas appliqués automatiquement. Pour créer des arborescences, on passe par systemd tmpfiles.d.
Pourquoi je trouve ça révolutionnaire
Parce que ça supprime la dérive de configuration.
Votre parc ne tourne plus sur des machines qui ont chacune leur historique de dnf update. Il tourne sur un digest. Le même partout, ou pas du tout.
Et parce que la boucle de feedback devient celle d'un dev, pas celle d'un sysadmin :
- vous versionnez votre OS dans un dépôt Git
- votre CI le build et le signe
- vous testez l'image avant de la pousser
- vous déployez avec un pull
- vous revenez en arrière avec une entrée de bootloader
C'est exactement ce que Universal Blue fait avec Bazzite. Chaque projet Universal Blue est basé sur des images bootc, et comme Bazzite est construit avec bootc, il est possible de créer sa propre image avec une sélection de logiciels différente.
Voilà pourquoi mon installation a pris 7 minutes. Quelqu'un avait déjà construit et testé l'image pour mon matériel, et je n'ai fait que la récupérer.