Perché riscrivere un Container Runtime in C
Prova a eseguire Docker o Podman con un limite di memoria di 512 kilobyte. Il runtime standard runc semplicemente andrà in crash con un errore a un simile limite. Non avrà abbastanza memoria nemmeno per configurare le strutture di processo di base e leggere i file descriptor.
Il motivo risiede nell'architettura. La stragrande maggioranza dello stack container moderno è scritta in Go. Per le utility di alto livello questo è ottimo, ma al livello più basso Go porta con sé il proprio runtime, garbage collector e overhead di memoria. Per configurare i namespace del kernel Linux prima che il processo principale inizi, runc deve riavviarsi e ricorrere a workaround in C.
Il progetto crun dell'organizzazione Containers affronta questo problema direttamente. È un'implementazione leggera della specifica OCI (Open Container Initiative), scritta interamente in C puro.
Cosa offre il passaggio al C puro
Il concetto fondamentale è semplice: rimuovere tutto ciò che è non necessario dal percorso critico della creazione del container. Quando il runtime non è appesantito da livelli extra, il consumo di risorse e la velocità di risposta migliorano drasticamente.
Consumo di memoria e avvio ai limiti
I benchmark degli sviluppatori mostrano una differenza significativa:
# Запуск через 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 container con crun si avvia in modo affidabile anche con un rigoroso limite di memoria di 512 KB, mentre runc inciampa già a 4 MB. Per i pesanti microservizi cloud, una differenza di un megabyte potrebbe sembrare trascurabile, ma sui dispositivi IoT, router e server edge, questo risparmio libera memoria preziosa per i carichi di lavoro effettivi.
Velocità di avvio sequenziale
Quando è necessario avviare frequentemente attività di breve durata (ad esempio, nelle piattaforme serverless o nei runner CI/CD), il tempo di inizializzazione del container diventa un collo di bottiglia. In un test che esegue 100 container sequenzialmente con il comando /bin/true, la differenza di tempo è quasi del doppio:
| Runtime | Tempo di esecuzione (100 container) | Differenza | | :--- | :--- | :--- | | crun | 0:01.69 | -49.4% | | runc | 0:03.34 | baseline |
L'implementazione in C si avvia due volte più velocemente grazie all'assenza di overhead di inizializzazione del runtime Go e a chiamate di sistema non necessarie ridotte.
Utilizzo come libreria
La maggior parte dei runtime esiste solo come eseguibili standalone. Se hai bisogno di avviare un container OCI dal tuo programma, in genere devi fare fork() e invocare un'utilità esterna.
In crun, puoi creare una libreria condivisa libcrun. Questo apre un'API C diretta per lavorare con i container:
- incorporare l'avvio di ambienti isolati nei tuoi daemon
- nessun overhead dal spawning di processi separati
- supporto out-of-the-box per binding Python e Lua
Compilazione e installazione
L'utilità è disponibile nella maggior parte delle distribuzioni tramite i gestori di pacchetti standard, ma anche la compilazione dal codice sorgente è semplice.
La compilazione su Ubuntu richiede i file header di 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
Il processo di compilazione stesso è standard:
$ ./autogen.sh
$ ./configure
$ make
$ sudo make install
Se prevedi di utilizzare la libreria nel tuo codice, il flag configure cambierà in ./configure --enable-shared.
Per ambienti riproducibili e server senza dipendenze extra, il progetto supporta build statiche tramite Nix Flakes:
$ nix --extra-experimental-features "nix-command flakes" build "path:.#crun-static-amd64"
$ ./result/bin/crun --version
Il risultato è un binario statico compatto per x86_64 con glibc, pronto per essere eseguito su qualsiasi macchina di destinazione.
A chi è rivolto
Passare a crun ha senso in diversi scenari:
- Edge computing e IoT. Quando un dispositivo ha solo 256 MB o 512 MB di RAM saldati, cedere megabyte al runtime è inaccettabile.
- Serverless e Functions-as-a-Service. In ambienti con creazione e distruzione frequente di container isolati, un miglioramento del 50% nella velocità di avvio riduce direttamente la latenza dei cold start.
- Dense container packing. Se centinaia di piccoli worker vengono eseguiti su un singolo host, il risparmio totale di RAM si accumula fino a gigabyte.
Cambiare il runtime in Podman è semplice come un singolo flag --runtime /usr/bin/crun o modificare un parametro in /etc/containers/containers.conf. Non sono necessarie modifiche ai manifest o alle immagini.
Progetti correlati