Bonjour à tous ! Ce week-end, j’ai découvert un concept dont je n’avais jamais entendu parler auparavant : les microVMs.

C’est très connu dans le petit monde des fournisseurs cloud (cloud providers), mais pour nous, simples mortels, c’est un peu plus obscur. La quantité de contenu disponible en français sur le sujet est assez faible, à l’exception de cet excellent article de Quentin Joly qui va assez vite sur la théorie et passe directement à la pratique.

C’est pourquoi je vais essayer de vous faire un tour d’horizon complet sur ce sujet : le pourquoi, le comment et comment on peut les utiliser.

Qu’est-ce qu’une microVM ?

Avant de répondre à cette question, revenons aux bases. Quelle est la différence entre une VM (Machine Virtuelle) et un conteneur ?

Comparaison architecture VM vs Conteneur

Comme vous voyez dans cette image, une VM est un systeme d’exploitation complet qui tourne sur un hyperviseur ou VMM (Virtual Machine Monitor). Il a son propre noyau, ses propres drivers et tout le reste. On peut dire qu’il est independant du systeme hote. L’avantage des VM’s est evident, c’est tres securisé, on peut faire tourner n’importe quel OS. Mais un des incovenients, c’est que c’est lourd. Il faut beaucoup de ressources pour faire tourner une VM, et c’est plutot lent (le temps de boot).

Un conteneur, par contre, s’appuie sur un moteur de conteneurs (Docker, podman, systemd…) et partage le noyau de l’hôte. Il n’a pas besoin de ses propres pilotes ni d’un système d’exploitation complet embarqué. Il n’embarque qu’un minimum du système d’exploitation nécessaire à l’application. C’est pourquoi c’est beaucoup plus rapide et léger. Plus besoin de virtualiser le matériel (hardware) ; on isole plutôt les processus au niveau de l’OS (via des namespaces, cgroups… c’est un sujet à part entière).

Le catch des conteneurs, c’est le risque de failles de sécurité. Et comme le conteneur partage le noyau de l’hôte, si un conteneur est compromis, il peut potentiellement compromettre l’hôte lui-même. Et par conséquent, tous les autres conteneurs qui tournent sur cet hôte…

Alors, dans tout ça, les microVMs, c’est quoi exactement ? D’abord, commençons par dire que ce sont des machines virtuelles vraiment très légères. Elles sont construites sur le même principe que les VMs classiques. Donc avec leur propre noyau, leur propre système de fichiers, etc.

Mais pourquoi sont-elles plus légères que les VMs classiques ? Parce que dans une VM classique, on prépare la VM pour qu’elle puisse faire tourner un système complet avec tous ses composants : pilotes (drivers), périphériques variés, ports USB, série, etc. Dans une microVM, on va à l’essentiel. Le VMM (l’hyperviseur) n’émule que les composants matériels strictement nécessaires à son fonctionnement : CPU, mémoire, un réseau minimaliste (souvent via virtIO), mais pas de carte graphique émulée, pas d’USB, pas de BIOS traditionnel… C’est réduit au strict minimum.

Les avantages sont donc évidents. Une microVM démarre en quelques centaines de millisecondes (parfois à peine 200ms selon la configuration), contre plusieurs secondes pour une VM classique. Et comparé aux conteneurs, c’est beaucoup plus sécurisé. Chaque microVM est isolée comme une VM classique : elle a son propre espace d’adressage mémoire et une isolation matérielle forte. Elles n’ont aucune ‘conscience’ de tourner sur un hôte mutualisé. Donc, si jamais une microVM est compromise, seule elle est affectée, pas l’hôte ni les autres microVMs. C’est donc beaucoup plus sécurisé que les conteneurs.

Le monde des VMs est sécurisé mais lent. Le monde des conteneurs est rapide mais potentiellement vulnérable. Il fallait quelque chose entre les deux, capable de démarrer en quelques millisecondes, tout en restant hermétique au reste du système. C’est exactement ce que les microVMs promettent.

Et ce besoin pressant, c’est justement ce qu’AWS a rencontré avec Lambda…

L’origine des microVMs - AWS Lambda

AWS Lambda est un service de calcul serverless qui vous permet d’exécuter du code sans avoir à gérer l’infrastructure sous-jacente. C’est ce qu’on appelle de la FaaS (Function as a Service). Donc, moi, le client, je ne paie que le temps de calcul utilisé par ma fonction (mon code) et je n’ai pas à me soucier de l’infrastructure sous-jacente ; c’est une sorte de boîte noire (‘black box’) pour moi.

Si vous comprenez un peu le fonctionnement, vous réalisez qu’il faut que le code s’exécute très rapidement : dès qu’un événement déclenche la fonction, le serveur doit répondre quasi instantanément.

C’est là qu’intervient le concept de ‘cold start’ (démarrage à froid). C’est le délai nécessaire pour que l’environnement d’exécution de la fonction soit prêt et que le code commence à s’exécuter lorsqu’elle n’était pas déjà ‘chaude’. Donc, plus ce temps de démarrage (‘cold start’) est court, plus l’utilisateur final reçoit sa réponse rapidement.

Vous vous en doutez, AWS faisait face à un défi de taille : comment minimiser ce ‘cold start’ sans compromettre la sécurité de l’infrastructure mutualisée ?

Quand j’ai découvert ce problème, ma première réaction a été : “Mais c’est simple, utilisez des conteneurs, et c’est réglé, non ?” Pas si simple que ça… Si vous avez suivi, vous savez que les conteneurs partagent le noyau de l’hôte. Or, sur les serveurs où tourne AWS Lambda, s’exécute le code de milliers de clients différents. Si l’un d’eux trouvait une faille dans l’isolation du conteneur (via un ‘zero-day’ par exemple), il pourrait potentiellement compromettre l’hôte. Et par conséquent, compromettre le code et les données de tous les autres clients tournant sur cet hôte. AWS avait donc un gros problème de sécurité potentiel.

C’est pourquoi, au début d’AWS Lambda, ils utilisaient des VMs classiques. Mais, comme nous l’avons vu, les VMs sont plus lourdes et leur temps de démarrage (donc le ‘cold start’) était trop long. C’était un vrai problème pour l’expérience utilisateur et l’efficacité du service.

Donc après une dure réflexion, et après un premier projet réussi fait par Google, AWS a forké le projet et a créé Firecracker. Firecracker est un VMM (Virtual Machine Monitor) léger, spécifiquement conçu pour créer et gérer des microVMs sécurisées et rapides.

Notre première microVM avec Firecracker

Lancer notre première microVM avec Firecracker est en fait très simple. Firecracker n’est qu’un simple binaire exécutable utilisable sur Linux (x86_64 ou aarch64/ARM64). Il a très peu de dépendances externes…

Pour commencer, vous pouvez utiliser une VM Debian ou Ubuntu, ou directement votre machine physique si elle tourne sous Linux. Il faut d’abord télécharger le binaire fourni par AWS (qui, par ailleurs, offre une excellente documentation : Getting Started with Firecracker).

release_url="https://github.com/firecracker-microvm/firecracker/releases"
latest=$(basename $(curl -fsSLI -o /dev/null -w  %{url_effective} ${release_url}/latest))
arch=`uname -m`
# Make sure to adjust the architecture if needed (e.g., aarch64 instead of x86_64)
if [ "$arch" == "x86_64" ]; then
    curl -L ${release_url}/download/${latest}/firecracker-${latest}-${arch}.tgz | tar -xz
    mv ./release-${latest}-$(uname -m)/firecracker-${latest}-$(uname -m) ./firecracker
elif [ "$arch" == "aarch64" ]; then
    curl -L ${release_url}/download/${latest}/firecracker-${latest}-${arch}.tgz | tar -xz
    mv ./firecracker-${latest}-${arch} ./firecracker
else
    echo "Unsupported architecture: $arch"
    exit 1
fi
# Clean up the extracted directory if it exists
rm -rf ./release-${latest}-$(uname -m)

On télécharge donc le binaire et on le décompresse. Ensuite, déplaçons l’exécutable firecracker dans un dossier inclus dans notre PATH, par exemple /usr/local/bin :

sudo mv ./firecracker /usr/local/bin/

Firecracker utilise KVM (Kernel-based Virtual Machine) pour la virtualisation matérielle. Avant de créer notre première microVM, il faut donc s’assurer que KVM est bien activé et fonctionnel sur notre machine hôte.

Pour cela, sur Debian/Ubuntu, on peut installer les paquets nécessaires et vérifier les permissions :

sudo apt update
sudo apt install -y qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils cpu-checker

(Note : tous ces paquets ne sont pas strictement nécessaires pour Firecracker seul, notamment libvirt-*. qemu-kvm peut être utile pour les outils ou pour vérifier KVM. L’essentiel est que le module KVM soit chargé et que l’utilisateur exécutant Firecracker ait les droits sur /dev/kvm).

On peut vérifier que le module KVM est bien chargé et que notre utilisateur a les permissions nécessaires (il doit faire partie du groupe kvm) :

# Vérifie si KVM est supporté et activé
sudo kvm-ok 
# Vérifie les permissions (l'utilisateur courant doit pouvoir lire/écrire)
ls -l /dev/kvm 
# Ajoute l'utilisateur courant au groupe kvm si nécessaire (déconnexion/reconnexion requise après)
# sudo usermod -aG kvm $USER 

Maintenant que Firecracker est installé et que KVM est prêt, créons notre première microVM ! D’abord, lançons le processus Firecracker dans un terminal. Il attendra des instructions via une API REST sur un socket Unix :

# Créez un socket pour l'API
rm -f /tmp/firecracker.socket 
# Lancez Firecracker
firecracker --api-sock /tmp/firecracker.socket

(Laissez ce terminal ouvert)

Ensuite, dans un deuxième terminal, nous allons configurer et démarrer notre microVM en envoyant des requêtes à cette API. Il existe deux façons principales d’interagir avec l’API de Firecracker : en envoyant directement des requêtes HTTP (via curl par exemple) ou en lui fournissant un fichier de configuration JSON au démarrage. J’ai trouvé l’approche par appels API directs (avec curl) plus didactique pour commencer.

Préparons d’abord les répertoires et téléchargeons un noyau et un système de fichiers minimalistes fournis par l’équipe Firecracker :

# Créez un répertoire pour les fichiers de la microVM
mkdir -p /var/lib/firecracker/hello && cd /var/lib/firecracker/hello

# Téléchargez l'image du kernel et le rootfs (système de fichiers racine)
arch=$(uname -m)
kernel_path="hello-vmlinux.bin"
rootfs_path="hello-rootfs.ext4"

if [ "$arch" = "x86_64" ]; then
    curl -o $kernel_path https://s3.amazonaws.com/spec.ccfc.min/img/quickstart_guide/x86_64/kernels/vmlinux.bin
    curl -o $rootfs_path https://s3.amazonaws.com/spec.ccfc.min/img/quickstart_guide/x86_64/fsfiles/hello-rootfs.ext4
elif [ "$arch" = "aarch64" ]; then
    curl -o $kernel_path https://s3.amazonaws.com/spec.ccfc.min/img/quickstart_guide/aarch64/kernels/vmlinux.bin
    curl -o $rootfs_path https://s3.amazonaws.com/spec.ccfc.min/img/quickstart_guide/aarch64/fsfiles/hello-rootfs.ext4
else
    echo "Unsupported architecture: $arch"
    exit 1
fi

Maintenant, configurons la source de démarrage (le noyau) via l’API (toujours depuis le deuxième terminal) :

# Chemin absolu vers les fichiers
kernel_full_path=$(realpath $kernel_path)
rootfs_full_path=$(realpath $rootfs_path)

# Configure la source de boot
curl --unix-socket /tmp/firecracker.socket -i \
    -X PUT 'http://localhost/boot-source' \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json' \
    -d "{
        \"kernel_image_path\": \"${kernel_full_path}\",
        \"boot_args\": \"console=ttyS0 reboot=k panic=1 pci=off\"
    }"

Configurons ensuite le disque racine (rootfs) :

# Configure le disque racine
curl --unix-socket /tmp/firecracker.socket -i \
    -X PUT 'http://localhost/drives/rootfs' \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json' \
    -d "{
        \"drive_id\": \"rootfs\",
        \"path_on_host\": \"${rootfs_full_path}\",
        \"is_root_device\": true,
        \"is_read_only\": false
    }"

Script inspiré de celui de Quentin Joly, qui est très bien fait !

Et enfin, démarrons l’instance :

# Démarre la microVM
curl --unix-socket /tmp/firecracker.socket -i \
    -X PUT 'http://localhost/actions' \
    -H  'Accept: application/json' \
    -H  'Content-Type: application/json' \
    -d '{
        \"action_type\": \"InstanceStart\"
    }'

Et voilà ! En quelques millisecondes (regardez le premier terminal où tourne firecracker), vous devriez voir les messages de démarrage du noyau Linux :

Terminal affichant le démarrage de la microVM Firecracker

Une microVM fonctionnelle !

Conclusion

Voilà, c’est tout pour cette introduction aux microVMs. J’espère que ce tour d’horizon vous a permis de mieux comprendre ce que sont les microVMs, pourquoi elles existent, et comment faire vos premiers pas avec Firecracker.

Dans un prochain article, j’appliquerai cette idée de microVM dans un projet plus pratique qui, je pense, vous plaira.

D’ici là, n’hésitez pas à me faire part de vos retours ou questions, par exemple via LinkedIn