Pourquoi réécrire un runtime de conteneur en C
Essayez d'exécuter Docker ou Podman avec une limite de mémoire de 512 kilo-octets. Le runtime standard runc va simplement planter avec une erreur à une telle limite. Il n'aura pas assez de mémoire même pour configurer les structures de processus de base et lire les descripteurs de fichiers.
La raison réside dans l'architecture. La grande majorité de la pile de conteneurs moderne est écrite en Go. Pour les utilitaires de haut niveau c'est formidable, mais à la couche la plus basse, Go apporte son propre runtime, son ramasse-miettes et sa surcharge mémoire. Pour configurer les espaces de noms du noyau Linux avant que le processus principal ne démarre, runc doit se redémarrer et recourir à des contournements en C.
Le projet crun de l'organisation Containers s'attaque à ce problème de front. C'est une implémentation légère de la spécification OCI (Open Container Initiative), écrite entièrement en C pur.
Ce que le passage au C pur vous apporte
Le concept central est simple : supprimer tout ce qui est inutile du chemin critique de création du conteneur. Lorsque le runtime n'est pas encombré par des couches supplémentaires, la consommation de ressources et la vitesse de réponse s'améliorent considérablement.
Consommation de mémoire et lancement aux limites
Les benchmarks des développeurs montrent une différence éloquente :
# Запуск через runc падает
$ podman --runtime /usr/bin/runc run --rm --memory 4M fedora echo it works
Error: container_linux.go:346: starting container process caused "process_linux.go:327: getting pipe fds for pid 13859 caused \"readlink /proc/13859/fd/0: no such file or directory\"": OCI runtime command not found error
# Запуск через crun отрабатывает без запинки
$ podman --runtime /usr/bin/crun run --rm --memory 512k fedora echo it works
it works
Un conteneur avec crun démarre de manière fiable même avec une limite de mémoire stricte de 512 Ko, tandis que runc trébuche même à 4 Mo. Pour les lourds microservices cloud, une différence d'un mégaoctet peut sembler triviale, mais sur les appareils IoT, les routeurs et les serveurs edge, cette économie libère de la mémoire précieuse pour les workloads réels.
Vitesse de démarrage séquentiel
Lorsque vous devez fréquemment lancer des tâches de courte durée (par exemple, dans les plateformes serverless ou les runners CI/CD), le temps d'initialisation du conteneur devient un goulot d'étranglement. Dans un test exécutant 100 conteneurs séquentiellement avec la commande /bin/true, la différence de temps est presque twofold :
| Runtime | Temps d'exécution (100 conteneurs) | Différence | | :--- | :--- | :--- | | crun | 0:01.69 | -49.4% | | runc | 0:03.34 | baseline |
L'implémentation en C démarre deux fois plus vite grâce à l'absence de surcharge d'initialisation du runtime Go et à moins d'appels système inutiles.
Utilisation comme bibliothèque
La plupart des runtimes n'existent que comme exécutables autonomes. Si vous devez lancer un conteneur OCI depuis votre programme, vous devez généralement faire un fork() et invoquer un utilitaire externe.
Dans crun, vous pouvez construire une bibliothèque partagée libcrun. Cela ouvre une API C directe pour travailler avec les conteneurs :
- intégration du démarrage d'environnement isolé dans vos propres démons
- aucune surcharge liée à l'apparition de processus séparés
- support prêt à l'emploi pour les liaisons Python et Lua
Compilation et installation
L'utilitaire est disponible dans la plupart des distributions via les gestionnaires de paquets standard, mais la compilation depuis les sources est également simple.
La compilation sur Ubuntu nécessite les fichiers d'en-têtes de base :
$ sudo apt-get install -y make git gcc build-essential pkgconf libtool \
libsystemd-dev libprotobuf-c-dev libcap-dev libseccomp-dev libjson-c-dev \
go-md2man autoconf python3 automake
Le processus de compilation lui-même est standard :
$ ./autogen.sh
$ ./configure
$ make
$ sudo make install
Si vous prévoyez d'utiliser la bibliothèque dans votre code, le flag de configuration deviendra ./configure --enable-shared.
Pour des environnements reproductibles et des serveurs sans dépendances supplémentaires, le projet supporte les builds statiques via Nix Flakes :
$ nix --extra-experimental-features "nix-command flakes" build "path:.#crun-static-amd64"
$ ./result/bin/crun --version
Le résultat est un binaire statique compact pour x86_64 avec glibc, prêt à fonctionner sur n'importe quelle machine cible.
À qui cela s'adresse
Passer à crun a du sens dans plusieurs scénarios :
- Edge computing et IoT. Lorsqu'un appareil n'a que 256 Mo ou 512 Mo de RAM soudés, renoncer à des mégaoctets pour le runtime est inacceptable.
- Serverless et Functions-as-a-Service. Dans les environnements avec une création et destruction fréquentes de conteneurs isolés, un démarrage 50% plus rapide réduit directement la latence de cold start.
- Densité de conteneurs. Si des centaines de petits workers s'exécutent sur un seul hôte, les économies totales de RAM s'additionnent en gigaoctets.
Changer le runtime dans Podman est aussi simple qu'un seul flag --runtime /usr/bin/crun ou la modification d'un paramètre dans /etc/containers/containers.conf. Aucun changement de manifestes ou d'images n'est nécessaire.
Projets similaires