Warum einen Container-Runtime in C neu schreiben
Versuchen Sie, Docker oder Podman mit einem Speicherlimit von 512 Kilobyte auszuführen. Der Standard-Runtime runc wird bei einem solchen Limit einfach mit einem Fehler abstürzen. Es wird nicht genügend Speicher haben, um selbst grundlegende Prozessstrukturen einzurichten und Dateideskriptoren zu lesen.
Der Grund liegt in der Architektur. Die überwältigende Mehrheit des modernen Container-Stacks ist in Go geschrieben. Für Top-Level-Utilities ist das großartig, aber auf der untersten Schicht bringt Go seinen eigenen Runtime, Garbage Collector und Speicheroverhead mit. Um Linux-Kernel-Namespaces einzurichten, bevor der Hauptprozess startet, muss runc sich selbst neu starten und auf C-Workarounds zurückgreifen.
Das crun-Projekt der Containers-Organisation packt dieses Problem direkt an. Es ist eine leichtgewichtige Implementierung der OCI-Spezifikation (Open Container Initiative), die vollständig in reinem C geschrieben ist.
Was der Umstieg auf reines C bringt
Das Kernkonzept ist straightforward: Alles Unnötige aus dem kritischen Pfad der Container-Erstellung entfernen. Wenn der Runtime nicht durch zusätzliche Schichten belastet wird, verbessern sich Ressourcenverbrauch und Reaktionsgeschwindigkeit drastisch.
Speicherverbrauch und Starten an den Grenzen
Die Benchmarks der Entwickler zeigen einen deutlichen Unterschied:
# Запуск через 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
Ein Container mit crun startet zuverlässig sogar mit einem strengen Speicherlimit von 512 KB, während runc bereits bei 4 MB scheitert. Für schwere Cloud-Microservices mag ein Megabyte Unterschied trivial erscheinen, aber auf IoT-Geräten, Routern und Edge-Servern gibt dieser Sparvorteil wertvollen Speicher für tatsächliche Workloads frei.
Sequentielle Startgeschwindigkeit
Wenn Sie häufig kurzlebige Tasks starten müssen (zum Beispiel in serverlosen Plattformen oder CI/CD-Runnern), wird die Container-Initialisierungszeit zum Flaschenhals. In einem Test mit 100 sequentiell gestarteten Containern mit dem /bin/true-Befehl ist der Zeitunterschied nahezu zweifach:
| Runtime | Ausführungszeit (100 Container) | Unterschied | | :--- | :--- | :--- | | crun | 0:01.69 | -49.4% | | runc | 0:03.34 | Baseline |
Die C-Implementierung startet doppelt so schnell dank fehlender Go-Runtime-Initialisierung und weniger unnötiger Systemaufrufe.
Als Bibliothek verwenden
Die meisten Runtimes existieren nur als eigenständige ausführbare Dateien. Wenn Sie einen OCI-Container aus Ihrem Programm starten müssen, müssen Sie typischerweise fork() aufrufen und ein externes Utility starten.
In crun können Sie eine Shared Library libcrun erstellen. Dies eröffnet eine direkte C-API für die Arbeit mit Containern:
- Einbettung des isolierten Umgebungsstarts in Ihre eigenen Daemons
- Kein Overhead durch das Erzeugen separater Prozesse
- Out-of-the-box-Unterstützung für Python- und Lua-Bindings
Kompilierung und Installation
Das Utility ist in den meisten Distributionen über Standard-Paketmanager verfügbar, aber auch der Build aus dem Quellcode ist straightforward.
Der Build auf Ubuntu erfordert grundlegende Header-Dateien:
$ 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
Der Kompilierungsprozess selbst ist Standard:
$ ./autogen.sh
$ ./configure
$ make
$ sudo make install
Wenn Sie planen, die Bibliothek in Ihrem Code zu verwenden, ändert sich das Configure-Flag zu ./configure --enable-shared.
Für reproduzierbare Umgebungen und Server ohne zusätzliche Abhängigkeiten unterstützt das Projekt statische Builds über Nix Flakes:
$ nix --extra-experimental-features "nix-command flakes" build "path:.#crun-static-amd64"
$ ./result/bin/crun --version
Das Ergebnis ist eine kompakte statische Binärdatei für x86_64 mit glibc, bereit, auf jeder Zielmaschine zu laufen.
Für wen ist es gedacht
Der Umstieg auf crun macht in mehreren Szenarien Sinn:
- Edge Computing und IoT. Wenn ein Gerät nur 256 MB oder 512 MB RAM aufgelötet hat, ist es inakzeptabel, Megabyte an den Runtime abzugeben.
- Serverless und Functions-as-a-Service. In Umgebungen mit häufiger Erstellung und Zerstörung isolierter Container reduziert eine 50%ige Startgeschwindigkeitsverbesserung direkt die Cold-Start-Latenz.
- Dichte Container-Packung. Wenn Hunderte kleiner Worker auf einem einzigen Host laufen, summieren sich die gesamten RAM-Einsparungen zu Gigabytes.
Das Austauschen des Runtimes in Podman ist so einfach wie ein einzelnes Flag --runtime /usr/bin/crun oder das Ändern eines Parameters in /etc/containers/containers.conf. Keine Änderungen an Manifesten oder Images erforderlich.
Ähnliche Projekte