Les machines virtuelles sont fort pratiques pour utiliser efficacement les serveurs en les découpant en tranche de la bonne taille. Un partage sans trop de soucis de sécurité (la surface d’attaque est restreinte), permettant d’avoir du multitenant (différents clients, potentiellement agressifs, partagent une machine).
À cause de Moore (enfin, de la fin de sa loi), les processeurs (génériques, aka CPU) prennent du retard face aux progrès du réseau, du stockage, et l’arrivée d’unités de calculs spécialisés (GPU, NPU, TPU…).
Le noyau a la charge de gérer le matériel et d’arbitrer leur accès.
Quand le CPU pédalait plus vite que le réseau et le stockage, les latences du noyau était de différent ordre de grandeurs inférieures à celle du matériel, et donc anecdotiques. Mais avec le réseau en 100G, le NVMe, et tous plein de nouveaux jouets qui essayent de saturer le PCIe, les latences du noyau deviennent hors de prix. L’article fondateur, Attack of the Killer Microseconds, explique très bien tout ça, et j’en ai déjà parlé dans le billet tcpless.
Avec une machine virtuelle, on empile les noyaux : celui de l’hôte (qui gère effectivement le matériel), et celui de l’invité (qui fait un peu de la figuration).
Avec deux noyaux en cascade, il y a le risque de faire deux fois le travail (ou de prendre deux fois une décision sur un même point).
Grâce à virtio, le noyau invité se fait le plus discret possible, avec un accès le plus simple et le plus direct avec le matériel, parfois même sans passer par le noyau de l’hôte (en utilisant les fameux rings et du zero copy).
Le noyau invité a la charge de gérer les utilisateurs et les process dans la machine virtuelle. Avec des micromachines virtuelles, comme dans un conteneur, il n’y aura qu’un seul utilisateur, une seule application (peut être même asynchrone, sans threads).
La tentation est donc grande de continuer d’élaguer et de réduire la place du noyau dans les machines virtuelles.
On peut réduire un kernel générique au maximum, comme le propose smolbsd (avec NetBSD) ou comme le suggère Firecracker (avec Linux), mais on peut aussi faire le grand pas vers l’unikernel.
unikernel
Le concept, audacieux, d’un unikernel, est d’embarquer l’application dans un kernel minimaliste, taillé sur mesure. Une application classique a son code, ses bibliothèques, ses appels système. Dans un unikernel, tout est bibliothèque, l’application (compilé en statique) comme les appels système nécessaires, et forme un seul binaire, bootable.
Isolation
Comme l’hôte assure l’isolation de la machine virtuelle, celle-ci peut se permettre de ne rien isoler (pas d’espace mémoire, pas de process, pas d’utilisateur…), comme il n’y a qu’une seule application, elle n’a rien à craindre de quiconque.
Minimalisme
Les unikernel ciblent la virtualisation (bien que des foufous fassent booter des Raspberry Pi avec des unikernels), ce qui simplifie tout.
Pour la gestion des drivers, il suffit de mettre du virtio à tous les étages (ou du Xen).
Cette simplicité permet un temps de boot réduit (qui pousse à préférer Firecracker à Qemu, trop long à démarrer), des images légères, une consommation de mémoire moindre, et de très bonnes performances.
Quand on parle de simplicité, on est au niveau “Linux embarqué”, ces gens considèrent que musl est une grosse libc.
Toutefois, les unikernels commencent par promettre un minimalisme absolu, des APIs uniques et parfaites, puis exposent une couche de compatibilité POSIX, pour finalement lancer des applications au format elf (Le format Linux).
Multicœur
Le SMP (la gestion des multiples processeurs et cœurs) est un sujet complexe, surtout la partie coordination pour les accès exclusifs. La virtualisation peut aisément aggraver le problème : si vCPU pose un verrou sur un CPU physique et que l’hyperviseur le gèle pour le confier à une autre machine virtuelle (en cas de sur allocation), il va bloquer le verrou pour un temps imprévisible (et pourrir la gigue, jitter en VO).
Le papier How to Deal with Lock Holder Preemption explique le problème et propose une solution.
Pour un OS spécifiquement conçu pour la virtualisation (comme l’est un unikernel), il est possible de se passer de ce genre de verrou.
Système de fichiers
Autre sujet chaud, les systèmes de fichier, les unikernels détestent ça, il faut implémenter VFS, gérer les descripteurs de fichiers, puis les spécificités du système de fichiers, et paraphraser LVM si on est ambitieux.
Pour avoir un ordre de grandeur, e2fsprogs, qui permet de gérer ext[2-4] en espace utilisateur, son dossier lib
contient 197 fichiers en C pour 65 426 lignes de code seul (du sloc
en jargon), sans tirer de dépendance tierce (d’après le paquet Debian libext2fs2). En comparaison pour Nanos, il y a 1457 lignes pour TFS, leur FS maison et 814 lignes pour 9pfs (utilisé pour accéder à un disque de l’hôte).
Sinon, un ramfs suffit pour avoir un /tmp
est le minimum pour assurer une compatibilité.
La réponse fantasmatique ultime serait que l’application parle directement le NVMe, en écrivant ses blocs, sans l’abstraction d’un système de fichier. Voici les spécifications, bon courage.
Utiliser un stockage clef/valeur peut être une abstraction plus légère qu’un système de fichier (le stockage, lui, causera aux blocs). DPDK a le blobstore et Ceph a confg-key (bien moins ambitieux que le précédant, mais ils ont aussi exploré base clef/valeur comme LevelDB) par exemple, mais l’intégration à unikernel reste théorique.
La solution raisonnable serait plutôt d’utiliser virtio-fs, qui expose un dossier de l’hôte (qui, lui, va gèrer un système de fichiers) via le protocole zero copy de virtio. Basé sur FUSE, mais avec le daemon en espace utilisateur sur l’hôte, utilisé depuis l’invité, avec les commandes FUSE qui utilisent le protocole de virtio, à base de ring et de mmap. L’unikernel Hermit, par exemple, peut utiliser du virtio-fs.
Virtio-fs est plébiscité par Kata, parfait pour du Serverless (et utilisé par AWSLambda), mais nécessite de la collaboration depuis l’hôte, chose impossible si l’on souhaite s’appuyer sur l’API VM de son Cloud préféré qui va se contenter de connecter un disque de blocs à une machine virtuelle.
Techniquement, du CephFS pourrait faire le job, mais je n’ai aucune idée du surcout pour une image (code, mémoire, processeur), et du temps de connexion. Une recherche rapide ne donne pas grand-chose : soit c’est trop novateur, soit c’est une mauvaise idée.
En attendant OSv embarque ZFS (qu’on aurait du mal à qualifier de minimaliste), Nanos a son système de fichier maison (nommé TFS) et Unikraft a la flemme.
La tradition reste la mauvaise foi en affirmant “les VMs sans état, c’est la pureté”. Cette approche radicale est tout à fait légitime, c’est un des piliers du dogme Serverless.
Temps de démarrage
Démarrer une machine virtuelle classique prends du temps : Il faut rapatrier l’image de référence sur le serveur qui va instancier la machine avec différentes stratégies : téléchargement sur le disque local, puis éventuelle création d’une instance en COW (copy on write), ou utilisation en flot depuis un disque distant depuis un SAN, avec là aussi un potentiel COW.
La taille compte, plus l’image sera de taille raisonnable, plus vite on pourra commencer à l’utiliser.
Ensuite vient la partie matérielle, que la virtualisation simplifie grandement, le BIOS/UEFI a déjà fait le travail.
Le démarrage du kernel, là aussi, la partie drivers est normalisée par la virtualisation, et enfin l’init, avec systemd, un bon gros cloud-init qui va mettre à jour des paquets, et enfin la ribambelle de services qui vont accompagner l’application métier.
Allez jeter un œil au billet de depot.dev qui bataille pour booter en 5s, sans utiliser de pool d’images qui explique comme passer d’un correct 40s pour démarrer à un excellent 5s.
Pour un temps de démarrage qui se mesure en dizaines de secondes, les VMM (gestionnaires de machines virtuelles) sont tous suffisamment rapides.
Sauf qu’en travaillant le temps de démarrage, pour du serverless par exemple, on passe à un temps en dixième de secondes avec Firecracker, comme le rappel Julia Evans qui souhaite instancier des VMs à la demande pour ses joueurs. Il y a ensuite d’autres stratégies comme le stream d’image de AWS Lambda, comme j’en avais parlé dans mon billet Flot d’images disque.
Un unikernel a tous les atouts pour démarrer super rapidement : une taille ascétique, une couche système minimaliste, et un seul service.
Tous les efforts des unikernels pour démarrer rapidement peuvent être ruinés par les autres services requis pour instancier une machine virtuelle.
Sécurité
Les unikernels vantent la sécurité de leur minimalisme et l’isolation forte de la virtualisation.
Ils ne s’arrêtent pas en si bon chemin et implémentent au mieux les fonctions de sécurité disponible sur les processeurs. Pour ensuite pinailler sur le cout en performance, et le bienfondé de cette protection dans le cadre précis d’une machine virtuelle avec un process, un utilisateur, un espace mémoire.
Certains unikernels peuvent même cibler des architectures exotiques dédiées à la sécurité, comme les enclaves Intel SGX.
Débug
Le service devient une fonction dans le noyau. Tout le rangement d’un UNIX classique avec des process et des espaces mémoire réservé, des appels systèmes saute. Quand un bug apparait, il va falloir pouvoir déterminer qui accuser, le code système, ou le code métier.
Le problème de la virtualisation, c’est que ça isole fort. Pour que l’invité puisse raconter ses soucis, il va falloir passer par un port série (aka la console) ou du réseau.
Par le réseau, on peut faire passer le débugueur gdb
, et profiter de l’interactivité (ça marche même depuis un IDE).
Normalement, on commence par vérifier que son application fonctionne en local (ou dans un conteneur), hors contexte unikernel, puis on teste dans l’unikernel.
Le debug unikernel ne devrait être utilisé uniquement quand on tripote des paramètres de l’unikernel, pas pour du code métier. Ou alors, c’est qu’il y a un bug dans l’unikernel ou une de ses bibliothèques système, et qu’il faut comprendre l’incident pour faire un ticket circonstancié.
Tous les outils permettant d’envoyer la stack trace d’une erreur, comme Sentry, fonctionneront dans un unikernel.
Certains unikernels ont des options lors de la compilation pour que la fonction contenant le code métier soit emballée dans un try/catch et déclenche une action en cas de crash (comme afficher un dernier log dans la console série).
Les cas justifiant un debug en prod doivent être rare, et si ça arrive, bah ce sera pénible, comme un bug de qemu. Il reste toujours la possibilité de router une partie du trafic sur une instance qui sera outillée pour un debug dans les conditions de la prod, soit un gdb depuis l’hôte, soit, euh, l’application hors du contexte unikernel ?
Les offres serverless ne proposent pas de debug live (pas de serveur : pas de debug), ça doit juste marcher. Avec Firecracker, AWS a dû assumer tous les bugs de virtualisation, mais la machine virtuelle est utilisé sans privilège (sans root) par le client, avec une distribution Linux maison. Là, avec les unikernels, l’utilisateur va devoir assumer la partie système de la virtualisation, avec la CI pour recetter.
Observabilité
L’unikernel, par principe, méprise la notion de process et son /proc
qui va avec, tout comme les syscall (et son strace
).
L’analyse de traces dynamiques avec, DTrace, uprobe et ebpf ne sont évidement pas disponible, mais les unikernels peuvent proposer la notion de traces et de hooks avec des API spécifiques.
Les échantillonneurs, et leur fameux flamegraph, ne vont pas fonctionner du premier coup, car ils sont souvent un outil tiers qui va analyser un process, comme py-spy. Les profileurs sous forme de bibliothèque, eux, vont fonctionner normalement, comme pprof, dans sa version Golang ou Rust.
Globalement, tous les outils embarqués dans le code vont fonctionner dans un unikernel : les journaux, traces, métriques, tout ce que propose opentelemetry.
Pour avoir des informations en boite noire, il faudra forcément passer par l’hôte, et l’on pourra voir ce que consomme la machine virtuelle, mais guère plus. Techniquement, tout ce qui cible qemu devrait fonctionner, comme Cannoli
Unikernels monolangage
Pour pousser un cran plus loin la sécurité, certains unikernels sont dédiés à un langage rigoriste :
D’autres ciblent des langages plus fantaisistes
- FreePascal avec Toro
- Javascript avec runtime.js ✞
Cette hyperspécialisation est étrange, car un unikernel basé sur du C pourra lier n’importe quel langage compilé, ou embarquer l’interpréteur de n’importe quel langage de script. Utiliser un langage avec une forte abstraction par rapport à l’OS permet de masquer à l’utilisateur les efforts (fort couteux) de portage.
Dans l’autre sens, des unikernels spécialisés peuvent s’ouvrir à d’autres langages : Hermit, dédié au calcul haute performance (HPC), peut gérer du C/C++, Fortran, Go et discuter en OpenMP.
Pour les fans de méta, les “one ring to rule them all”, il y a Solo5, commencé comme un portage de Mirage pour KVM (depuis Xen), puis devient une abstraction imaginée comme pérenne pour utiliser différents unikernels. IRL, Solo5 fonctionne avec Mirage et IncludeOS (fan de C++). Les benchmarks ne sont pas flatteurs pour Solo5.
Unikernels généralistes
Dans les unikernels généralistes, beaucoup sont cités (et comparer), mais peu sont encore bien vivants. La short list semble être Osv, Nanos et Unikraft, mais d’autres conservent leur aura, comme Lumpin, Rump ou Solo5. Hermit est encore très jeune, mais prometteur.
Prêt pour la prod
Les unikernels étaient une fantaisie pour chercheurs qui bootent, mais ils commencent à intéresser les entreprises.
Les unikernels ne sont pas apparus ex nihilo, ils sont la suite logique des µ conteneurs, qui sont la suite des conteneurs (qui sont la suite des WAR de Java, l’hypothèse d’une parenté avec les chroots est trop banale).
Les unikernels s’insèrent facilement dans les workflows actuels, ils ne sont pas si disruptifs que ça.
Une CI/CD avec moult tests, puis de la métrologie (journaux, métriques, traces, rapports d’erreurs) sont indispensables, mais bon, comme n’importe quel service depuis au moins une décennie.
Pour l’instant, 3 usages types émergent :
- Serverless
- HPC (pour profiter du gain en performance)
- Hébergement dense avec scale to zero (du mutu moderne, quoi)
OSv
OSv a envoyé du rêve, en proposant un unikernel vantant sa compatibilité avec les binaires Linux.
OSv documente ses choix (et citent des white papers), et a fait le choix hétérodoxe d’un système de fichier luxueux : ZFS.
Petit souci, l’entreprise derrière, Cloudius, a lâché l’hébergement cloud pour tout miser sur ScyllaDB (une base de données orientée colonnes, libre, clone de Cassandra, concurrente de DynamoDB).
Le projet est dorénavant maintenu par sa communauté, avec un rythme bien plus mou.
Unikraft remercie OSv pour les bouts de code qu’ils ont repris.
Nanos
NanoVMs affirme avec aplomb que les unikernels vont buter les conteneurs d’ici 5 ans, mais aussi qu’ils permettent de se passer de devops.
De manière très classique, leur offre SAAS, propriétaire, s’appuie sur du code plus bas niveau, libre : un kernel et un cli permettant le build et le déploiement.
Nanos est l’unikernel (sous licence Apache-2), avec sa chaine de build, ops (sous licence MIT).
En local
De gros efforts ont été fournis pour faciliter la prise en main de Nanos : la documentation est conséquente, et ops
rends bien des services à toutes les étapes du projet, que ce soit le build, le run (en local) et le déploiement.
Les langages compilés sont utilisés comme avant, il suffit de produire un binaire pour Linux. La doc s’amuse à fournir des exemples exotiques (forth, haskell, pascal, crystal, ada, nim, odin…).
Pour les langages interprétés, il suffit d’utiliser un paquet disponible (une image dans un registre, quoi), pour construire son image. La doc, de nouveau, s’amuse avec des exemples exotiques, comme PHP ou Perl.
L’objectif est clairement de déployer efficacement son code métier, et non de batailler/pinailler avec les problématiques des unikernels.
Les images Nanos ciblent KVM (qemu et firecracker), mais aussi Bhyve (sur FreeBSD), Xen, Hyper-V (sur Windows), VSphere, VMware ou même Virtualbox.
Un beau travail d’intégration a été fait pour débuguer avec gdb
, soit en cli (depuis la VM vers l’hôte, donc), soit intégré dans son éditeur préféré (comme VSCode).
Génération Cloud
Nanos cible les différentes offres Cloud du marché pour déployer ses images. Il cible les gros du Cloud (AWS, GCE, Azure), mais aussi les hébergeurs pour budgets serrés : Vultr, Linode, DigitalOcean, OpenStack (et donc OVH)…
Le cli, ops
est capable de lister, pousser, instancier les images.
Il est possible de spécialiser ses images pour un hébergeur spécifique, pour utiliser correctement le service de machines virtuelles, avec les klibs, permettant ainsi d’émettre des métriques ou du log, ou même profiter de cloud-init pour avoir de la config au boot du service.
Ops sait manipuler les volumes distants (en utilisant les APIs de l’hébergeur), et les brancher/débrancher, même à chaud sur une instance de nanoVM.
Pour pouvoir utiliser des disques en mode bloc, Nanos a fait l’effort de créer son propre système de fichier, TFS. Un utilitaire permettant de monter les images avec FUSE est fourni, permettant de trifouiller le contenu.
TFS est un système de fichier nébuleux, en lisant les sources, on comprend qu’il est journalisé (src/fs/tlog.c), et ne possède pas les fonctionnalités ambitieuses d’un système de fichier moderne, comme le RAID, ou la création d’instantanés.
Étonnement, Nanos n’est pas super à l’aise avec Kubernetes; oui, il va fonctionner avec KubeVirt, mais il estime que ça va dégrader son niveau de sécurité.
Nanos a une approche granboutienne, il considère qu’un unikernel est une petite VM, et non un super conteneur, et qu’il doit s’intégrer dans l’écosystème des machines virtuelles, et surtout pas se compromettre avec K8s (qui lui est dans la team conteneur).
Avec cette approche, Nanos s’interdit les joies du serverless, ou alors il devra proposer un service avec un pool de serveurs bare-metal pour y arriver.
Avec son système de fichiers maison, TFS, Nanos fait un choix pragmatique pour gérer la persistance maintenant, partout, sur les offres Cloud majeures.
Sauf qu’un système de fichier est par définition terrifiant, en dessous de 10 ans d’âge, on ne le considère même pas, et ensuite, il faut assumer des pinaillages incessants sur les performances dont vont se plaindre les développeurs de bases de données, les ext4 vs XFS, les BtrFS vs ZFS.
virtio-fs en confiant la persistance à l’hôte contourne le problème, mais est-ce bien raisonnable d’exiger qu’un service HTTP gère les blocs de son disque dur alors que le reste de la gestion matérielle est abstraite par la virtualisation?
Unikraft
Unikraft a une approche plus universitaire que Nanos, ce qui permet d’avoir de chouette white papers à lire, comme Unikraft: Fast, Specialized Unikernels the Easy Way.
Le projet rassemble plusieurs universités, et bénéficie de l’aide du Google Summer Of Code. Unikraft a été adoubé par la prestigieuse fondation Linux (c’est comme le CNCF, mais en plus grand).
Dans la tradition BSD, une startup complète la partie recherche, en proposant une offre SAAS: Unikraft.io basé en Allemagne (en zone RGPD, donc, même si peu de détails apparaissent sur la matérialité de l’hébergement).
Unikraft cible pour l’instant Xen et KVM (qemu et Firecracker), pour les architectures x86_64, arm64 et arm.
Il est possible de créer des images pour des Clouds existants, fort pratiques pour faire des benchmarks, mais la documentation ne dit pas comment le faire. Rien n’est prévu pour avoir de la persistance hors virtio (pas de iscsi ni de NVMe-OF, pas de systèmes de fichiers en stock), impossible donc d’utiliser les volumes distants.
Unikraft ne croit pas à l’intégration aux offres Cloud actuelles, il souhaite repartir sur un socle simple et pour ça cible le bare metal, sans se soucier des offres existantes.
Unikraft a une relation avec les Clouds du marché opposée à celle de Nanos.
Construire une image
Unikraft a bien sûr son cli à tout faire, kraft
, un gros binaire autonome, qui, en plus d’une ressemblance assumée à docker
pour lancer des instances, permet de construire une image.
Pour l’instant, la seule cible est KVM (via QEMU ou Firecracker), d’autres cibles sont prévus : Xen, VMWare et Hyper-V.
La présentation d’Unikraft insiste bien sur les bienfaits de la compilation spécifique (et minimaliste), mais la documentation commence par expliquer comment créer un unikernel à partir d’un binaire Linux, en utilisant un loader comme app-elfloader.
Le plus simple est de cannibaliser une image Docker, basée sur Debian ou Alpine selon l’inspiration (ou la compatibilité avec musl
plutôt que glibc
). Avec du multi-FROM, on recopie le strict nécessaire d’une image officielle (pour déléguer la maintenance du binaire à un tiers de confiance), puis le build va recopier l’arborescence minimaliste. Oui, tout le travail se fait dans le Dockerfile, ce qui simplifie grandement la CI.
Le build va embarquer ce petit bout d’arborescence dans le noyau, avec un “embeded initrd” en VO, einitrds pour les intimes, qui sera exposé comme ramfs (en lecture seule) lors du démarrage de l’image.
Pour des langages compilés traditionnels, c’est un peu plus sport, il faut entrer dans le conteneur, strace
le service pour découvrir ce qu’il openat
, puis ldd
pour connaitre les bibliothèques liées, et avec ces indices créer une image ascétique.
Le fichier Kraftfile
, qui malgré son nom hommage à (Make|Docker)file
ne décrit aucune actions et ne contient que de la configuration, la magie se passant soit dans des options de compilation mystique pour du natif, ou un lien vers un Dockerfile qui aura tout préparé.
Allez voir le catalogue officiel, il est rempli d’exemple, et vous pourrez juger de la lisibilité du combo Dockerfile
+ Kraftfile
. Vous verrez aussi que la gestion des images arm n’est pas encore systématique, bien que la compilation croisée soit documentée.
Pour du C, C++, rust, vous pouvez aussi construire un vrai unikernel de puriste, dit natif : votre application devient une bibliothèque de votre noyau. Allez voir le dossier native dans le catalogue.
Bien sûr, il est possible d’activer le mode verbeux, puis, en désespoir, d’utiliser l’indispensable debug avec gdb
pour comprendre pourquoi ça résiste.
Pour gérer des images dans des formats spécifiques, Unikraft fournit un module pour Packer.
Écrire des fichiers
Unikraft reconnait que les systèmes de fichiers peuvent être utiles, pour une base de données par exemple. Pour l’instant, unikraft ne gère que ramfs (pour avoir un /
ou /tmp
) et 9pfs, qui est un service réseau issu du mythique projet plan 9 du laboratoire Bell, dont on retrouve des petits bouts un peu partout.
Un ticket github évoque la gestion de systèmes de fichiers classique comme ext4 : ouvert en 2019, à part du rangement, il ne s’est rien passé. Cette indécision sur l’intégration d’un système de fichier dans un unikernel est légitime. Déléguer ce point à l’hôte est tellement plus simple, surtout que 9pfs (déjà disponible dans unikernel) le fait déjà, passer à virtio-fs semble logique, avec un gain en performance à la clé.
En attendant, si vous voulez profiter d’un volume distant, ce sera via l’hôte, qui devra le monter, puis le partager.
KraftCloud expose des volumes, précise qu’ils ont un système de fichier à leur création, en se gardant bien de préciser lequel. Comme tout est compilé hors de Kraftcloud, point d’arme secrète, ce devrait être du 9pfs.
Exploitation
Les fans des 12 facteurs seront rassurés
- Le build est prédictif, avec le combo
Dockerfile
+Kraftfile
, et adapté à une CI/CD - Le contrat avec l’hôte est clair : KVM ou Xen
- Déploiement sans admin : presque, ça reste une image de VM, par contre, la compatibilité cloud n’est pas une priorité. L’intégration avec Packer permet au moins de gérer les différents formats d’images.
- Pour le scale, on reste dans les clous : service sans états que l’on peut multiplier pour gérer la charge.
- Configuration via les ENVs avec le module posix-environ.
- Pour les logs, tout écrire dans STDOUT/STDERR ne va pas suffire. Cette recommandation est ringardisée par l’arrivée d’opentelemtry, votre application va devoir communiquer à un service tiers ses journaux, traces, rapports d’erreurs et diverses métriques. Le bon moment pour essayer Quickwit ?
kraft
Fournis les commandes équivalents à ce que propose Docker pour le cycle de vie des machines virtuelles, et il gère même les fichiers docker-compose.yml
.
Pour des architectures multi serveurs, il faudra passer par leur offre KraftCloud, ou Kubernetes.
Écosystème containerd et K8s
Unikraft fournit son propre runtime compatible OCI (Containerd ou cri-o) : runu
.
En créant des images d’unikernel au format OCI, il est possible de déployer des unikernels Unikraft dans un cluster Kubernetes, et même de panacher avec des conteneurs classiques.
Il est techniquement possible d’envisager du serverless via k8s (Knative et ses amis), mais pour l’instant, c’est qemu qui est utilisable, pas Fireworks, il faut donc bien surveiller les temps de démarrage.
As A Service
Unikraft.io est une offre SAAS, Unikraft ayant fait le choix opposé de Nanos, en daubant la compatibilité historique, préférant cibler le bare metal, en gardant la main sur l’hôte et la coordination. D’après ce que laisse transparaitre la doc, Unikraft.io est basé sur Firecracker, k8s, crossplane et Prometheus.
- API REST, utilisable depuis le cli en local ou une CI
- Loadbalancer
- Autoscale
- Scale to zero
- Déploiement continu (les outils sont conçus pour fonctionner depuis unne CI)
- Mise à jour progressive (déploiement bleu/vert…)
- Sondes Prometheus
- Terraform
- Déploiement de certificats, ou création à la demande avec Lets Encrypt
- Volumes
- Registre d’images
- Gestion de docker-compose.yml
Certains services paraissent pour l’instant frustes (comme le load balancer).
Il manque des bouts :
- Stockage sans fond de type S3
- Load balancing interne
Par contre, Unikraft.io reprend la promesse d’élasticité du Cloud : tu payes ce que les utilisateurs utilisent, de 0 à beaucoup. Ce qui signifierait que les load balancers attendent gratuitement. C’est gentil, mais les crawlers assurent un trafic minimum constant.
Les Unikernels sont-ils universels ?
L’assonance est belle, mais la technologie commence à peine à émerger des laboratoires après une petite décennie de gestation.
On va dire que les étoiles s’alignent, ce qui permet l’émergence de cette technologie.
KVM normalise l’accès à l’hôte avec virtio, avec des services en zero copy, baissant encore le surcout de la virtualisation. Rust s’engouffre dans ce domaine, permettant l’arrivée de Firecracker, fasciné par le démarrage rapide de machines virtuelles.
Moore se fait distancer par le stockage, le réseau et les *PU : le CPU devient le boulet des architectures contemporaines, ce qui remet en avant les langages compilés, domaine qui a aussi fait des efforts pour proposer des langages mieux bordés, avec un cycle de développement plus raisonnable.
La télémétrie a fait un bon en avant (merci Grafana), tout comme la notion de CI/CD : il est maintenant normal de ne plus pouvoir corriger une prod avec un vim
en ssh
après une analyse rapide avec tail
.
Webassembly, conçu pour pouvoir recompiler Doom et lancer dans un navigateur web, s’avère être finalement un redoutable concurrent aux unikernels, avec son bac à sable rigoureux et ses bonnes performances.
Le cloud a promis l’élasticité il y a fort longtemps, et il est possible que ce soit les unikernels qui pourront tenir cette vieille promesse.
Les machines virtuelles en cascades fonctionnent mal, pour profiter des unikernels, il faut du bare metal, et les serveurs contemporains, pour rentabiliser la place dans les datacenters, sont très souvent énormes. OVH sait rester raisonnable avec des processeurs spécifiques aux dédiés (AMD a une gamme pour ça), avec juste 12 cœurs délicats, alors qu’AWS tabasse directement avec des machines à 128 cœurs. La moindre maquette va manger 3 serveurs, pour avoir de la redondance et des élections non ambigües.
La virtualisation, que ce soit KVM ou Xen a réussi l’exploit de devenir transparent, on sait que ça existe, mais on ne l’accuse jamais de bug ou de brimer la performance. Pour les bugs, on accuse le kernel et on est confiant qu’une mise à jour corrigera, pour les performances, bah, on a la surallocation et le CPU steal comme bouc émissaire. Les unikernels n’ont pas encore ce totem d’immunité, la virtualisation, OK, c’est la même qu’une bonne VM à l’ancienne, mais le peu de code système de l’unikernel, qui va-t-on blâmer? Pas les devs Linux, ils n’y sont pour rien. Comment gérer un éventuel incident en prod?
La suite
Dans un monde parfait, la recherche de l’efficience, et donc de la densification serait un objectif, mais bon, le dédié a encore beaucoup de succès, tout comme les technologies avec un cout CPU/utilisateur désastreux.
Ce n’est pas une raison pour lâcher l’affaire, la densification avance, ne serait-ce que pour des histoires de foncier ou de puissance disponible, le minimalisme est une des pistes.
La notion d’Unikernel date des années 90, mais il a fallu la déferlante des conteneurs (et son incapacitant à faire du multitenant), puis le renouveau de la paravirtualisation (avec Firecracker) poussé le Serverless pour que des produits matures émergent enfin.
Il manque l’épreuve du feu aux Unikernel pour gagner la confiance des utilisateurs, et pour ça, il faut que des utilisateurs utilisent pour trouver les bugs les plus vicieux, et surtout polissent les workflows de compilation. Unikernel est presque prêt pour le grand public, et peut déjà servir d’arme secrète pour les aventureux.
Ma prédiction est que les unikernels vont être utilisés pour des services en interne, très denses, et sans états : comme des load balancers, des services liés à la télémétrie, du memcache.
Première interaction avec les utilisateurs : du serverless, sans stockage, sans débug, avec pour commencer des runtimes prêt-à l’emploi (python, ruby, node…), puis de l’image unikernel issu d’une CI/CD.
On peut imaginer que de gros utilisateurs veuillent faire baisser leur facture d’hébergement en profitant des gains en performance et en densité des unikernels. Comme ce qu’a permis, sans trop d’effort, les processeurs ARMs, plus efficients et aux performances stables (sans variations de fréquence pour gérer la chaleur).
Le stockage est pour l’instant un caillou dans la chaussure d’unikernel. Soit l’unikernel gère le disque distant (NVMeOF ?) et un système de fichier, soit l’hôte mount un volume distant dans un namespace partagé avec la VM et l’expose avec virtio (choix que fait Lambda avec Firecracker), soit un portage de CephFS pour déporter le souci sur Ceph.
Les API clouds pour les machines virtuelles ne sont pas entièrement adaptés aux unikernel, il manque une optimisation hyper agressive sur le temps de démarrage et une stratégie plus interventionniste sur l’hôte s’il doit gérer les systèmes de fichiers pour l’invité.
Les unikernels vont dans le sens de la simplification des architectures système, ce qui est toujours une bonne idée. Il est temps que le droit d’inventaire de la (para)virtualisation soit enfin effectué. Je trouve personnellement réjouissant de pouvoir se passer du tentaculaire systemd sans revenir à l’infâme initd, même si supprimer la notion même de kernel semble un peu excessif pour y arriver.
Small is beautiful.