FreeBSD dans Firecracker
(usenix.org)- Faire démarrer le noyau FreeBSD 14 dans Firecracker VMM, une microVM centrée sur Linux pour AWS Lambda, a mis en évidence les hypothèses implicites et les goulets d’étranglement du chemin d’initialisation de l’OS dans un environnement de virtualisation minimal
- Le premier démarrage a été bloqué par des différences de format d’ELF Note en mode de boot PVH, par des hypercalls spécifiques à Xen et par des différences de disposition mémoire dans Firecracker ; le problème a été résolu en modifiant le code PVH de FreeBSD
- Comme Firecracker ne fournit pas d’ACPI, le chemin pour obtenir les informations CPU et interruptions a changé ; une option
MPTABLE_LINUX_BUG_COMPATa été ajoutée pour s’aligner sur le bug de traitement MPTable de Linux - Lors de la connexion de la console série et des périphériques Virtio, des contraintes sont apparues autour du fonctionnement de l’UART, de l’analyse de la ligne de commande du noyau et des E/S disque non alignées ; FreeBSD a appliqué
hw.broken_txfifo, un contournement de vidage FIFO et un traitement bounce basé surbusdma - Avec des correctifs non encore intégrés, le noyau FreeBSD démarre en moins de 20 ms dans une VM à 1 CPU et 128 Mo de RAM ; les tâches restantes portent sur l’intégration du support PVH, la séparation du code Xen, une configuration de noyau plus petite et l’étude d’un portage de Firecracker vers FreeBSD
Pourquoi essayer d’exécuter FreeBSD sur Firecracker
- Firecracker est un VMM qui crée et gère des microVM à faible surcharge sur Linux KVM pour des environnements d’exécution serverless comme AWS Lambda
- Le travail de portage vers FreeBSD a commencé en juin 2022
- La motivation était de mettre en évidence simultanément les limites de FreeBSD et de Firecracker
- En poursuivant le travail d’amélioration du temps de démarrage de FreeBSD, l’objectif était de voir jusqu’où il pouvait aller sur un hyperviseur minimal
- Porter FreeBSD sur une nouvelle plateforme révèle des bugs à la fois dans FreeBSD et dans la plateforme concernée
- AWS Lambda ne prend actuellement en charge que Linux ; indépendamment d’une éventuelle adoption de Lambda, le support de Firecracker par FreeBSD est une condition préalable nécessaire
- Firecracker est en lui-même une plateforme intéressante, et il fallait vérifier son fonctionnement réel
Les premiers obstacles avant l’exécution du noyau
- Firecracker a été conçu à l’origine pour exécuter des noyaux Linux, mais un correctif ajouté en 2020 apportait la prise en charge du mode de boot PVH en plus de
linuxboot - FreeBSD prenant déjà en charge le démarrage PVH sur Xen, la même voie a été tentée sur Firecracker
- Le premier problème était que Firecracker, après avoir chargé le noyau FreeBSD en mémoire, ne trouvait pas le point d’entrée du noyau
- Le protocole de boot PVH stocke cette valeur dans une ELF Note
- Il existe des ELF Note
PT_NOTEetSHT_NOTE, et FreeBSD ne fournissait pas le format que Firecracker recherchait - Une petite modification du script de l’éditeur de liens du noyau FreeBSD a permis à Firecracker de lancer l’exécution du noyau FreeBSD
- L’exécution du noyau s’est de nouveau arrêtée environ 1 microseconde plus tard
Débogage initial et suppression de la dépendance à Xen
- Quand le noyau plante avant l’initialisation du débogueur noyau et de la console série, les capacités de débogage de FreeBSD sont d’une aide très limitée
- Les seules informations fournies par le processus Firecracker étaient que l’invité FreeBSD avait provoqué une triple fault
- Une « kernel bisection » a été réalisée en insérant des instructions
hltau milieu du code de démarrage du noyau afin de localiser le point de crash- Si l’exécution atteignait
hlt, Firecracker continuait de tourner mais l’utilisation CPU côté hôte tombait à 0 % - Si Firecracker s’arrêtait, on pouvait en déduire que le crash s’était produit avant ce point
- Si l’exécution atteignait
- La première cause trouvée était un hypercall Xen
- Le point d’entrée PVH de FreeBSD était en réalité du code destiné à démarrer sur Xen, avec l’hypothèse qu’il s’exécutait dans Xen
- Le KVM utilisé par Firecracker ne fournit pas d’hypercalls Xen, donc leur appel faisait planter la VM
- Au départ, les hypercalls Xen ont été commentés, puis modifiés ensuite pour n’être appelés qu’après vérification de la signature Xen dans
CPUID
- La récupération de la carte mémoire physique était une fonction essentielle assurée jusque-là par un hypercall Xen
- À partir de la version 1 de PVH, le pointeur vers la carte mémoire est transmis via la page
start_info - FreeBSD a été modifié pour utiliser la carte mémoire PVH version 1 à la place de l’hypercall Xen
- À partir de la version 1 de PVH, le pointeur vers la carte mémoire est transmis via la page
- Les différences de disposition mémoire entre Firecracker et Xen ont aussi posé problème
- Xen charge d’abord le noyau puis place la page
start_infoà la fin - Firecracker place la page
start_infoà une adresse basse fixe puis charge ensuite le noyau - Le code PVH de FreeBSD supposait que l’espace juste après
start_infopouvait servir de scratch space, ce qui écrasait la pile noyau initiale sur Firecracker - Le problème a été résolu en allouant l’espace de travail après toutes les zones mémoire initialisées par l’hyperviseur
- Xen charge d’abord le noyau puis place la page
Absence d’ACPI et compatibilité MPTable
- Sur x86, FreeBSD obtient généralement les informations sur les disques, les cartes réseau, les CPU et les contrôleurs d’interruptions via ACPI
- Firecracker, par choix de minimalisme, ne fournit pas ACPI
- FreeBSD peut à la place utiliser les structures MPTable de l’ancienne Intel MultiProcessor Specification
- Elles ne sont pas incluses par défaut dans la configuration de noyau
GENERIC - Dans une configuration de noyau allégée pour Firecracker, il a été possible d’ajouter
device mptable
- Elles ne sont pas incluses par défaut dans la configuration de noyau
- La MPTable fournie par Firecracker n’était pas strictement conforme au standard, mais à ce que Linux accepte
- Linux comportait un bug dans sa manière de trouver et d’analyser la MPTable
- Firecracker ayant été conçu pour démarrer Linux, il fournit une disposition non standard que Linux prend en charge
- FreeBSD, avec son implémentation indépendante conforme au standard, ne trouvait pas la MPTable mal placée et, même en la trouvant, ne pouvait pas analyser une MPTable invalide
- Une option de noyau
options MPTABLE_LINUX_BUG_COMPATa été ajoutée à FreeBSD- Elle sert lorsqu’une compatibilité bug pour bug avec le traitement Linux de la MPTable est nécessaire
- Grâce à cette option, le démarrage de FreeBSD sur Firecracker a pu aller plus loin
Prise en charge de la console série et des périphériques Virtio
- L’un des rares périphériques émulés fournis par Firecracker est le port série
- Dans une configuration typique, l’entrée et la sortie standard du processus Firecracker deviennent l’entrée et la sortie du port série de la VM
- Le noyau FreeBSD a démarré avec le disque racine intégré dans l’image noyau, et il a été possible de lire la sortie console du noyau
- Lors du passage au démarrage de l’espace utilisateur, la sortie console s’est arrêtée au bout de 16 caractères
- Le symptôme rappelait un ancien bug UART de QEMU
- Aucune interruption n’arrivait lorsque la FIFO d’émission de l’UART était vide, si bien que FreeBSD ne pouvait plus écrire après 16 octets
- Le problème a été résolu en compilant dans les variables d’environnement noyau le contournement existant
hw.broken_txfifo="1"
- L’entrée console ne fonctionnait pas non plus
- Firecracker estimait que la FIFO de réception de l’UART émulé était pleine et ne lisait donc pas la console
- Lors de l’initialisation de l’UART, FreeBSD remplissait la FIFO de réception avec des valeurs parasites pour mesurer sa taille, puis la vidait via le FIFO Control Register
- Firecracker n’implémentait pas le FIFO Control Register, donc la FIFO restait pleine en permanence
- FreeBSD a été modifié pour lire et jeter les caractères un par un afin de vider la FIFO si
LSR_RXRDYrestait présent après la tentative de vidage
- Pour utiliser le disque et le réseau, il fallait des périphériques Virtio block/network
- Firecracker les expose comme des périphériques
mmio - La configuration de noyau FreeBSD pour Firecracker a reçu
device virtio_mmio
- Firecracker les expose comme des périphériques
- FreeBSD s’attendait à l’origine à découvrir les périphériques
mmiovia FDT, mais Firecracker transmet des directives commevirtio_mmio.device=4K@0x1001e000:5dans la ligne de commande du noyau- FreeBSD a ajouté du code pour analyser ces directives et créer des nœuds de périphériques
virtio_mmio - Une fois ces nœuds créés, le processus habituel de détection des périphériques de FreeBSD détermine le type de périphérique Virtio et attache le pilote approprié
- FreeBSD a ajouté du code pour analyser ces directives et créer des nœuds de périphériques
- Avec plusieurs périphériques Virtio, un problème d’analyse de la ligne de commande du noyau est apparu
- Firecracker transmet plusieurs paires
key=valueà la manière de Linux - FreeBSD analyse la ligne de commande du noyau comme des variables d’environnement, donc si deux
virtio_mmio.device=portent le même nom, un seul est conservé - Le code d’analyse initial de l’environnement noyau a été modifié pour préserver les variables en doublon avec un suffixe numéroté, comme
virtio_mmio.device=etvirtio_mmio.device_1=
- Firecracker transmet plusieurs paires
- Après un arrêt anormal, si
fscks’exécutait au démarrage suivant, cela provoquait une panique noyaufsckest l’un des rares cas dans FreeBSD à générer des E/S disque non alignées sur les pages- L’implémentation Virtio de Firecracker n’accepte qu’un seul tampon de données et ne prend pas en charge l’approche Virtio classique consistant à découper un tampon franchissant une frontière de page en plusieurs segments
- Le pilote Virtio block de FreeBSD a été modifié pour utiliser
busdma, et les requêtes non alignées ont été adaptées à la contrainte de Firecracker via un traitement bounce passant par un tampon temporaire
Les optimisations de démarrage mises au jour par Firecracker
- Une fois FreeBSD capable de fonctionner sur Firecracker, les points à améliorer pour réduire le temps de démarrage et l’usage mémoire sont devenus très visibles
- Dans une VM avec 128 Mo de RAM, près de la moitié de la mémoire système était à l’état
wired, et les processus se terminaient fréquemment- L’enquête a montré que
busdmaréservait 32 Mo pour les bounce pages - Avec les limitations de Firecracker, chaque E/S disque n’avait besoin au maximum que d’une seule bounce page de 4 Kio
- Un correctif limitant la réservation de bounce pages pour les périphériques ne prenant en charge qu’un petit nombre de segments d’E/S a réduit l’usage mémoire à 512 Kio
- L’enquête a montré que
- L’optimisation du générateur de nombres aléatoires du noyau a réduit le temps de démarrage
- Dans une VM, l’entropie issue des périphériques matériels peut être peu efficace
- x86 utilise
RDRANDcomme source d’entropie de secours, mais avec peu d’entropie par requête et une seule requête toutes les 100 ms - En demandant suffisamment d’entropie pour amorcer complètement le générateur Fortuna, 2,3 s ont été gagnées
- Le traitement du Host ID a aussi été accéléré
- En général, le chargeur de démarrage définit
smbios.system.uuidà partir des informations BIOS ou UEFI - Firecracker n’ayant pas de chargeur de démarrage, aucun identifiant n’était fourni
- Le comportement a été modifié pour attendre 2 secondes avec avertissement si le matériel fournit un identifiant erroné, mais poursuivre silencieusement et rapidement s’il n’y a pas d’identifiant du tout
- En général, le chargeur de démarrage définit
- La condition d’attente de la DAD IPv6 a été resserrée
- FreeBSD attendait la Duplicate Address Detection dès qu’IPv6 était activé sur une interface réseau
- IPv6 était toujours activé sur l’interface loopback
- En attendant la DAD uniquement lorsqu’une interface autre que loopback possède IPv6, 2 s ont été gagnées
- Les attentes fixes au redémarrage et à l’arrêt ont été supprimées
- Au redémarrage, l’attente d’1 seconde après le message
Rebooting...pour laisser le temps auprintfde finir et d’être lu a été remplacée par la sysctlkern.reboot_wait_time, dont la valeur par défaut est 0 - Lors de l’arrêt ou du redémarrage, l’attente supplémentaire d’1 seconde après que le BSP a reçu le signal d’arrêt des autres CPU a également été supprimée
- Au redémarrage, l’attente d’1 seconde après le message
- TSLOG a été utilisé pour analyser un flame chart du démarrage
- L’environnement minimal de Firecracker produisait peu de bruit, ce qui facilitait l’identification des goulets d’étranglement restants
- L’exécution de la VM était si rapide qu’il était souvent possible de compiler un nouveau noyau, l’exécuter et générer un flame chart en moins de 30 secondes
- L’analyse avec TSLOG a permis de réduire plusieurs goulets d’étranglement de l’ordre de la milliseconde
- Réduire la boucle de calibration de
lapic_initde 100000 à 1000 itérations a fait gagner 10 ms - Modifier
ns8250_drain, qui appelaitDELAYpour chaque caractère, afin de ne retarder qu’en cas de besoin après vérification deLSR_RXRDY, a fait gagner 27 ms - Faire en sorte que Firecracker implémente la feuille
CPUIDindiquant la fréquence du TSC et de l’horloge APIC locale a fait gagner 20 ms - Remplacer la valeur fixe de
kern.nswbufà 256 par32 * mp_ncpusa fait gagner 5 ms dans une VM à 1 CPU - Remplacer le bubblesort de
mi_startuppar un quicksort permettrait de gagner 2 ms, mais ce n’était pas encore intégré au 22 août 2023 - Remplacer l’initialisation immédiate par
vm_memdes structuresvm_pagepour toute la mémoire physique par une initialisation paresseuse permettrait de gagner 2 ms, mais ce n’était pas encore intégré à la même date - Ajouter
MAP_POPULATEaummapde la mémoire invitée de Firecracker réduirait le coût, côté Linux, de création des structures de page lors du premier accès et ferait gagner 2 ms, mais ce n’était pas encore intégré à la même date
- Réduire la boucle de calibration de
État actuel et travail restant
- FreeBSD démarre sur Firecracker et fonctionne très rapidement
- Avec les correctifs non intégrés côté FreeBSD et Firecracker, le noyau FreeBSD peut démarrer en moins de 20 ms dans une VM à 1 CPU et 128 Mo de RAM
- Le travail restant se concentre sur la mise au propre du support PVH et sur la réduction de la configuration du noyau
- Les correctifs mentionnés ci-dessus doivent être intégrés
- La prise en charge du mode de boot PVH doit être fusionnée dans la branche principale de Firecracker
- Le code de démarrage PVH est mêlé au support Xen et doit en être séparé
- Le noyau FreeBSD arm64 ne peut actuellement pas être compilé sans support PCI ou ACPI ; supprimer ces dépendances incorrectes permettrait de produire un noyau FreeBSD/Firecracker plus petit
- La vérification de la nécessité d’une réservation mémoire pour les GPU Intel prend 25 µs ; une configuration de noyau plus petite pourrait donc encore réduire le temps de démarrage de quelques microsecondes
- À plus long terme, il pourrait aussi être envisagé de porter Firecracker pour qu’il s’exécute sur FreeBSD
- Firecracker a été écrit en supposant l’usage de Linux KVM
- Il ne semble pas y avoir de raison fondamentale empêchant de l’adapter pour utiliser la partie noyau de l’hyperviseur bhyve de FreeBSD
- Pour expérimenter, on peut compiler le noyau FreeBSD 14.0 pour amd64 avec la configuration de noyau
FIRECRACKER, et utiliser la branchefeature/pvhdu projet Firecracker- Si cette branche n’existe plus, cela signifie que le code a été fusionné dans l’arbre principal de Firecracker
1 commentaires
Avis de Hacker News
Je ne savais pas vraiment que les VM Firecracker n’étaient pas une simple technologie de conteneurs Linux, mais des machines virtuelles complètes.
Au premier abord, cela peut sembler inefficace, mais quand on regarde des cas d’usage réels comme fly.io, il est étonnant de voir à quel point les micro-VM peuvent être à la fois très petites et suffisamment puissantes.
Grâce à KVM et à une prise en charge matérielle minimale (pas de PCI, pas d’ACPI, etc.), le code source de Firecracker reste assez simple et relativement lisible même pour des non-spécialistes.
C’est précisément la raison d’être de Firecracker.
ACPI, cité dans l’article, en est un exemple. Cela dit, il virtualise bien le matériel plutôt que le noyau, et son initialisation est tellement rapide que la plupart des utilisateurs ne remarqueraient probablement pas la différence en remplaçant un containerd classique par firecracker-containerd.
Outre Firecracker, plusieurs micro-VM comme crosvm, cloud-hypervisor ou Dragonball de Kata sont actuellement développées au-dessus de KVM.
Je n’ai pas l’impression qu’implémenter une nouvelle cible pour les langages de programmation soit si difficile ; en créant une cible pseudo-OS du type WASI/WASM et en envoyant des PR aux langages pris en charge, on pourrait probablement supprimer la majeure partie de l’overhead. La partie la plus difficile serait sans doute d’émuler l’espace utilisateur Linux avec suffisamment de précision, mais comme sa surface est très large, la voie d’une cible pseudo-OS semble finalement la meilleure.
Quand les correctifs de Colin arriveront dans FreeBSD et Firecracker, le temps de démarrage complet du noyau passera sous les 20 ms.
Nous vivons vraiment une époque difficile à croire.
Une recherche rapide donne quelques chiffres, mais ils datent de plusieurs années, et il n’est pas clair que la méthode de mesure du temps de démarrage, ni même la définition de « temps de démarrage », soient les mêmes, donc je ne sais pas si la comparaison est valable.
C’est la récente présentation BSDCan de Colin, publiée il y a quelques jours.
https://youtu.be/MT3cdeuRTzs?si=l6baNriUjcvy0ZOE
Après la présentation BSDCan, le FreeBSD Journal lui a demandé : « C’était une bonne présentation, pourriez-vous la transformer en article ? », puis, après la parution de l’article dans le FreeBSD Journal, ;login: a demandé s’il pouvait le republier.
qemu dispose d’une machine microvm inspirée de firecracker.
https://qemu.readthedocs.io/en/latest/system/i386/microvm.ht...
Bien sûr, certains sont des corrections de bugs FreeBSD, donc ils restent indispensables.
Il est intéressant de voir qu’une bonne partie de cette attente d’une seconde s’avère en fait inutile.
Je me demande combien d’administrateurs système ont réellement pris une mesure pertinente quand le système s’est arrêté à cause d’un UUID machine incorrect.
En revanche, le cas « afficher à l’utilisateur un message indiquant qu’on va redémarrer, attendre une seconde pour qu’il puisse lire la console, puis redémarrer » est un peu différent.
Sans vouloir paraître condescendant, je me demande à quels cas d’usage correspondent des choses comme les instances Firecracker.
J’utilise FreeBSD partout, des serveurs en colocation à mon PC personnel, et je suis davantage un vieil administrateur Unix qu’un développeur. Je préfère le bare metal, mais j’accueille favorablement les technologies d’avenir qui contribuent au système d’exploitation. Cela dit, même si j’entends des mots à la mode comme Lambda ou Firecracker, je ne vois pas très bien à quoi ils servent concrètement. Je comprends Docker et les conteneurs, et je comprends à peine k8s, mais je ne vois pas pourquoi il faudrait lancer une VM puis la supprimer aussitôt, alors qu’on peut simplement lancer une VM et l’utiliser quand on en a besoin. Est-ce uniquement une question d’expérience cloud ou de réduction des coûts ?
Ainsi, n’importe quel nœud du plan de calcul peut traiter le trafic de n’importe quelle application. Une application peut grossir dynamiquement pour consommer les ressources de calcul disponibles du plan en fonction de l’évolution des schémas de trafic, et ne consomme pas de ressources lorsqu’elle ne traite pas de trafic. Augmenter la capacité du plan de calcul revient à mettre davantage de nœuds en ligne. À part gérer beaucoup de déploiements à grande échelle, je ne vois pas vraiment d’autre cas d’usage ; dans un environnement qui n’est pas à « l’échelle », c’est une technologie qui restera cachée sous la frontière du fournisseur.
Si vous exploitez un service dont l’API n’est pas appelée souvent, mais qui doit répondre rapidement quand elle l’est, Lambda ou une approche similaire convient bien. En pratique, une bonne partie des API pour applications mobiles entrent dans cette catégorie, et on n’a pas envie de garder une machine inactive 99 % du temps simplement pour répondre à ces appels d’API.
La plupart ne le font pas parce que l’effort dépasse les économies, mais le potentiel existe. Des choses comme Lambda et Firecracker rendent cela beaucoup plus facile.
Il est dommage qu’AWS comme macOS sur ARM ne prennent pas en charge la virtualisation imbriquée.
Si c’était le cas, développer et déployer des technologies basées sur Firecracker serait beaucoup plus facile.
En fait, la virtualisation est possible sur n’importe quel type d’instance, mais je crois que seules les instances .metal permettent d’utiliser l’accélération matérielle.
Firecracker est impressionnant, mais il y a beaucoup de cas particuliers qui mériteraient d’être documentés.
Un grand merci à Colin Percival d’avoir partagé cela. J’aime particulièrement la phrase « après avoir cueilli tous les fruits à portée de main » : pour Colin, cela désigne les correctifs bus_dma personnalisés. Désormais, tout le monde peut profiter gratuitement de « le noyau FreeBSD démarre en moins de 20 ms avec 1 CPU et 128 Mo de RAM ». Si vous êtes habitué au DevOps avec des clusters k8s ou beaucoup de Docker, c’est vraiment étonnant.
J’ai un peu essayé Firecracker : le temps de démarrage est bien conforme aux promesses, mais l’expérience d’utilisation est assez rude.
Par exemple, après avoir réussi à démarrer et crié victoire, j’ai été refroidi en découvrant qu’il fallait encore suivre un long tutoriel pour mettre en place le réseau.
Ce serait vraiment bien de télécharger et lancer un binaire unique qui démarre à la fois une interface web et une API, permette une configuration rapide et télécharge automatiquement ce dont on a besoin.
« Un noyau FreeBSD peut démarrer en moins de 20 ms dans une machine virtuelle avec 1 CPU et 128 Mo de RAM »
Mon Dieu, comment obtenir la même chose sur du vrai matériel, sans VM ? ;)
Ce qui est lent, c’est tout le reste. Par exemple, sur ma machine :
Startup finished in 14.552s (firmware) + 2.885s (loader) + 741ms (kernel) + 23.116s (initrd) + 11.191s (userspace) = 52.488s.