Construire un unikernel qui exécute WebAssembly - Partie 1
(flavio.castelli.me)- Lors de la Hackweek 22 de SUSE, un POC d’un unikernel capable d’exécuter des modules WebAssembly a été réalisé, et son implémentation est présentée en plusieurs parties
- Pour porter directement une application classique sur un unikernel, il faut aussi faire correspondre ses dépendances, mais une plateforme WebAssembly définit beaucoup plus clairement la frontière des fonctionnalités que le runtime doit fournir
- Une application Spiderlightning ne demande que des fonctionnalités comme le Key/Value, et que l’hôte l’implémente avec Redis ou Azure Cosmos DB, le même module
.wasmn’a pas besoin de connaître la différence - La base retenue est l’unikernel Rust RustyHermit ; comme Wasmtime et Wasmer ne se compilaient pas, le choix s’est porté sur wasmi, un runtime 100 % Rust
- Après avoir ajouté la prise en charge de wasmi à wit-bindgen pour s’aligner sur le Component Model et WIT, les fonctionnalités Key/Value côté hôte ont été scaffoldées afin d’exécuter
keyvalue-demo
Projet Hackweek et objectifs
- Pendant la Hackweek 22 de SUSE, le projet Build a unikernel that runs WebAssembly a été mené
- L’implémentation complète était trop longue pour tenir dans un seul article, elle a donc été découpée en plusieurs billets, et celui-ci en est la première partie
- Le code du POC est publié à part, mais aucun lien URL effectif n’est fourni dans le texte source
Pourquoi utiliser ensemble unikernels et WebAssembly
- Pour un développeur d’applications, le portage vers un unikernel représente une lourde contrainte
- L’application et toutes ses dépendances doivent être compatibles avec l’unikernel cible
- Des correctifs peuvent être nécessaires dans toute la pile applicative
- De leur côté, les mainteneurs d’unikernels doivent aussi dépenser beaucoup d’énergie pour permettre l’exécution fluide d’applications arbitraires
- Parce qu’il est difficile de prédire quels primitives système utiliseront les applications des utilisateurs
- À l’inverse, viser des plateformes WebAssembly comme Spin ou Spiderlightning clarifie l’ensemble de fonctionnalités que le runtime doit fournir
- Dans un scénario Spiderlightning, l’application peut demander au runtime une fonctionnalité de stockage Key/Value
- Que l’hôte implémente cette fonctionnalité avec Redis ou avec Azure Cosmos DB, cela reste transparent pour l’application
- Le même module
.wasmpeut s’exécuter sur différentes implémentations hôte
Architecture cible
- Si une application unikernel peut exécuter des modules WebAssembly et prendre en charge l’ensemble d’API Spiderlightning, alors la même application Spiderlightning peut fonctionner aussi bien sur le runtime
slightclassique que sur cet unikernel - Le développeur d’applications n’a rien de plus à faire, et le module Wasm n’a pas besoin de savoir où il s’exécute
- La complexité est concentrée chez le développeur de l’unikernel, mais le périmètre à implémenter reste bien plus clair que « prendre en charge l’exécution de toutes les applications »
Implémentation sur la base de RustyHermit
- La base choisie est RustyHermit
- Il s’agit d’un unikernel écrit en Rust
- Il est inclus dans Rust nightly, ce qui offre une expérience de développement proche de celle d’une application Rust classique
- La compilation d’applications RustyHermit est relativement simple
- La documentation est un peu dispersée, mais de bonne qualité, et les exemples sont très utiles
- On ne peut pas supposer que tous les crates Rust fonctionneront tels quels sur RustyHermit, et cette contrainte a influencé le développement du POC
Choix du runtime WebAssembly
- Le runtime préféré, Wasmtime, ne se compile pas sur RustyHermit
- Beaucoup de dépendances supposent la présence de
libcou d’autres bibliothèques bas niveau
- Beaucoup de dépendances supposent la présence de
- wasmer rencontre le même problème
- WebAssembly Micro Runtime a aussi été envisagé, mais le choix a été fait d’utiliser un runtime écrit en Rust afin de conserver une « expérience RustyHermit complète »
- Le choix final s’est porté sur wasmi, un runtime WebAssembly 100 % Rust
- Il fonctionne bien sur RustyHermit
- Sa conception s’inspire de Wasmtime, ce qui a permis de réutiliser une grande partie des connaissances existantes
WebAssembly Component Model et WIT
- Spiderlightning utilise la proposition WebAssembly Component Model
- Pour fournir des fonctionnalités au guest WebAssembly
- Et pour permettre à l’hôte de consommer les fonctionnalités fournies par le guest WebAssembly
- La communication entre l’hôte et le guest utilise des types définis avec Wasm Interface Type
- La démo utilise le Component Model selon le flux suivant
- Le guest demande à l’hôte de démarrer un serveur HTTP et lui transmet les routes HTTP à enregistrer ainsi que les noms des fonctions de gestion internes
- Le type
http-serverest utilisé, et le guest consomme ici une fonctionnalité fournie par l’hôte
- Le type
- L’hôte traite les requêtes HTTP entrantes à partir des informations de routage fournies par le guest
- Le handler HTTP est une fonction exposée par le guest WebAssembly
- Le serveur consomme une fonctionnalité fournie par le guest et communique via le type
http-handler
- Certains handlers HTTP interagissent avec un stockage Key/Value
- Dans ce cas également, le guest utilise une fonctionnalité fournie par l’hôte, définie par le type
keyvalue
- Dans ce cas également, le guest utilise une fonctionnalité fournie par l’hôte, définie par le type
- Le guest demande à l’hôte de démarrer un serveur HTTP et lui transmet les routes HTTP à enregistrer ainsi que les noms des fonctions de gestion internes
Extension de wit-bindgen et exécution de la démo
- Pour chaque type WIT, il faut du code côté guest jouant le rôle de SDK, ainsi que du code d’implémentation côté hôte
- wit-bindgen est un outil CLI qui génère le code hôte/guest à partir de fichiers
.wit - Dans ce POC, il suffisait d’implémenter uniquement l’interface côté hôte à l’intérieur de l’unikernel
- Le code généré par
wit-bindgens’appuie sur le runtime WebAssembly pour réaliser les opérations de bas niveau- Le code généré dépend donc du langage de programmation et du runtime WebAssembly utilisé côté hôte
- Comme
wasmin’était pas pris en charge parwit-bindgen, celui-ci a été étendu pour gérer wasmi- Le code se trouve dans un fork sur la branche wasmi
- Ensuite, le code côté hôte pour la fonctionnalité Key/Value a été scaffoldé, puis une implémentation simple du trait hôte a été ajoutée
- Le code hôte se limitait à afficher des informations de débogage
- À ce stade, il était possible d’exécuter sans modification keyvalue-demo du projet Spiderlightning
Aperçu de la partie suivante
- Il existe un enregistrement montrant l’application unikernel exécuter la démo Spiderlightning
http-server - La partie suivante abordera Rust async, Redis et quelques erreurs étranges
1 commentaires
Commentaires Hacker News
Ça ne vous fait pas immédiatement penser à https://www.destroyallsoftware.com/talks/the-birth-and-death... ?
Pour quelqu’un qui n’est pas hacker de systèmes d’exploitation mais qui veut un unikernel, quelle serait l’approche la plus aboutie ?
Les options qui me viennent à l’esprit sont : transformer l’application en module du noyau Linux et la charger dans un noyau classique en ignorant l’espace utilisateur, réduire drastiquement Linux et y greffer son propre code, partir d’un projet d’unikernel sur GitHub, ou élaguer un autre système d’exploitation comme FreeBSD
J’aime bien l’idée d’une machine x64 dans une VM connectée à une carte réseau, qui se comporte comme une ressource de calcul généraliste, avec des tâches assignées en envoyant des données par le réseau. Comme c’est plus contraignant qu’un daemon en espace utilisateur, ça n’avait pas encore beaucoup de valeur, mais si j’ai un jour du temps, je me demande par où commencer pour bidouiller au niveau OS
Unikernel Linux (UKL) est parti d’une tentative d’exploiter la configurabilité de Linux, avec pour objectif un noyau couvrant aussi bien les systèmes d’exploitation généralistes que les unikernels spécialisés par application et matériel. io_uring et eBPF sont aussi mentionnés comme domaines liés : io_uring répartit le coût des appels système, tandis qu’eBPF est une autre façon, certes limitée, d’exécuter du code en espace noyau
Code : https://github.com/unikernelLinux/ukl
UKL est un petit patch pour Linux et glibc, qui permet de construire de nombreux programmes en unikernels sans modification. Le programme est lié avec le noyau Linux et le vmlinuz final, s’exécute en espace noyau, peut démarrer sur bare metal ou dans une VM, et peut utiliser presque toutes les fonctionnalités et pilotes de Linux
/init, puis de l’embarquer dans le noyau et de démarrer dessusL’app devient alors PID 1 et, en pratique, le seul processus ; à part quelques threads noyau, on peut faire ce qu’on veut
Il prend en charge plusieurs langages et apps, x86/ARM64, QEMU/Firecracker, et peut aussi exécuter en unikernel des ELF construits sous Linux : https://unikraft.org/guides/bincompat
Le Discord est ici : https://unikraft.org/discord
Si vous voulez apprendre OCaml tout en visant un unikernel, c’est une voie possible
On trouve plus de détails dans la documentation d’Unikraft : https://unikraft.org/docs/concepts/design-principles#approac...
Beau projet. J’aime le fait que WASM ait été conçu dès le départ avec le sandboxing et la portabilité en tête
J’aurais aimé que WASM existe dans les années 90 à la place de JavaScript, et j’ai l’impression que WASM va tout dévorer. Ce que je souhaite le plus, c’est la pérennité. Il y a aujourd’hui beaucoup de programmes qu’on ne peut plus exécuter, les vieux jeux en étant l’exemple typique. Une spécification simple a davantage de chances de survivre longtemps, donc l’ajout de nouvelles fonctionnalités m’inquiète un peu, mais l’avenir du binaire semble passionnant
Ce n’est pas pour rien que JS était au départ limité à du scripting de base, comme les gestionnaires de clics ou la validation de formulaires. S’il est devenu autre chose, ce n’est pas seulement à cause de défauts de conception de JS, mais aussi des usages dans lesquels on l’a forcé à entrer. Utiliser le navigateur comme mécanisme de distribution pour ce genre d’apps est très loin de ce qu’imaginaient Tim Berners-Lee ou Marc Andreesen
À l’époque, le camp du « réseau est l’ordinateur » proposait des clients X légers pour des apps plus riches : https://en.wikipedia.org/wiki/Network_Computer
J’ai des sentiments mitigés sur WASM. Pour l’instant, il est entouré d’un grand voile de hype et de nouveauté. Si l’on traite le navigateur web comme un simple viewport pour les fantasmes de langage du moment des designers UI et des développeurs, beaucoup de mauvaises choses arrivent côté accessibilité, lecteurs d’écran, etc.
La tendance à considérer WASM hors du navigateur comme une VM universelle est aussi une voie déjà empruntée il y a 30 ans. C’était ce que la JVM essayait de faire, mais apparemment ce n’est plus « cool » aujourd’hui
Je ne sais pas bien à quoi devrait ressembler l’entre-deux, ni pourquoi on devrait le vouloir. Mais, en pratique, WASM risque aussi d’engloutir le contenu documentaire, et alors les bloqueurs de pub et les modes lecture pourraient être fichus
J’adore vraiment. Parmi les technologies liées, il y en avait plusieurs que je n’avais jamais vues, donc j’ai tout mis en favoris
Ensuite, j’aimerais essayer de configurer une connexion WireGuard vers l’hyperviseur. L’établissement de la connexion pourrait aussi passer par quelque chose comme Tailscale
Ainsi, le WebAssembly de cette machine parlerait directement au WebAssembly de cette autre machine. Ce ne serait pas un processus qui ouvre une connexion TCP vers un emplacement arbitraire, mais une architecture où la communication se fait sur la base de la configuration et des permissions transmises
Avec du retard, quelqu’un a-t-il déjà envisagé d’exécuter Zephyr comme unikernel ? https://docs.zephyrproject.org/latest/boards/x86/acrn/doc/in...
Combien de temps avant l’arrivée d’un matériel dédié à WASM ?
Cela dit, quelqu’un pourrait créer un truc du genre « en surface c’est du wasm, mais en dessous il y a en réalité du RISC-V »
Mais je pense que ce type de matériel restera toujours une niche. Parce qu’exécuter WASM sur du matériel standard sera généralement plus rapide. WASM lui-même a été conçu pour tourner rapidement sur du matériel standard, et les économies d’échelle des processeurs généralistes sont bien meilleures
L’International Conference on Functional Programming s’appelait au départ Functional Programming and Computer Architecture, mais on a ensuite trouvé comment compiler efficacement des langages fonctionnels à évaluation paresseuse comme Haskell sur du matériel existant
C’est pareil pour les machines Lisp et Java. L’une des raisons pour lesquelles on ne voit presque plus ce genre de machines, c’est que la technologie des compilateurs a rattrapé son retard
Quels sont les cas d’usage des unikernels et de WASM ?
À mon sens, la valeur des unikernels tient à 1) la performance : jeter ce qui est inutile et ramener ce qui est nécessaire en « ring 0 » pour grappiller ne serait-ce que quelques cycles, 2) la simplification : la possibilité de réduire la complexité en supprimant les parties inutiles, 3) la sécurité : là encore, la possibilité de modifier la surface d’attaque en réduisant ce qui n’est pas nécessaire
Cela dit, je ne pense pas que ce soit adapté à la façon dont beaucoup de gens sur ce forum écrivent des microservices ou des applis web. Les usages sont plus proches de la création de composants d’infrastructure, comme des bases de données ou des load balancers
C’est pourquoi certains fournisseurs d’edge cloud convertissent les images Docker en micro-VM lorsqu’ils les exécutent
Cela dit, à l’edge, WASM dans une micro-VM peut avoir du mal à rivaliser avec le WASM sandboxé de l’edge. Du point de vue du fournisseur, ce dernier a probablement plus de facilité à ajouter des fonctions de frontière utiles et des intégrations
Comme cela avait été « annoncé » il y a longtemps dans Birth & Death of Javascript, l’idée était qu’un jour apparaîtrait un unikernel capable d’exécuter en espace noyau un runtime à garbage collection sûr, ce qui permettrait alors de supprimer du CPU la prise en charge du mapping de mémoire virtuelle pour le rendre plus rapide.
En 2014, l’auteur pensait à JS et asm.js, mais aujourd’hui WASM semble être cette voie. J’ai hâte, haha
https://www.destroyallsoftware.com/talks/the-birth-and-death...
Mais depuis, on a appris qu’un navigateur monoprocessus est un cauchemar de sécurité, et les navigateurs actuels ne sont plus monoprocessus afin de permettre un sandboxing correct.
Cela dit, il est intéressant de voir à quel point cette vidéo était proche de la bonne réponse, et aussi en quoi elle se trompait.
C’est difficile à formuler précisément, mais cela a quelque chose de familier. Indice : cela commence aussi par un « J ».
https://en.wikipedia.org/wiki/JavaStation
L’utilisation virtuelle d’un processus peut dépasser son RSS même sans que ce soit dû au swapping, et le système d’exploitation comme l’allocateur travaillent ensemble pour gérer cela assez intelligemment dans les cas courants.
Il est donc difficile de dire que s’en débarrasser apporterait automatiquement un gain de performance. D’autant plus si l’on passe par une couche de VM WASM assez lente.
Pour certaines applications, par exemple les bases de données, les exécuter comme unikernel ou les rapprocher davantage du noyau avec un accès direct à la MMU peut être très bénéfique : https://github.com/tuhhosg/exmap & https://github.com/viktorleis/vmcache & https://www.cs.cit.tum.de/fileadmin/w00cfj/dis/_my_direct_up...
Mais pour des applications générales qui supposent le standard POSIX ou partent du principe que leur environnement d’exécution ressemble à un ordinateur généraliste moderne, j’ai des doutes. Au final, on risque de réécrire en code utilisateur une grande partie de ce que faisait la couche VMM.
Les moteurs JS dépendent du VMM, et WASM aussi de plusieurs façons. Presque tous les programmes non triviaux hors embarqué reposent subtilement sur l’existence du VMM. En particulier, certaines technologies de VM autour des micro-VM utilisent aussi le VMM, et les unikernels n’ont vraiment de sens que lorsqu’ils sont utilisés comme VM.