Dlaczego przepisać runtime kontenerów w C
Spróbuj uruchomić Docker lub Podman z limitem pamięci wynoszącym 512 kilobajtów. Standardowy runtime runc po prostu zawiedzie z błędem przy takim limicie. Nie będzie miał wystarczającej ilości pamięci nawet na skonfigurowanie podstawowych struktur procesu i odczytanie deskryptorów plików.
Przyczyna leży w architekturze. Zdecydowana większość nowoczesnego stosu kontenerów jest napisana w Go. Dla narzędzi najwyższego poziomu jest to świetne rozwiązanie, ale na samym najniższym poziomie Go wnosi ze sobą własny runtime, garbage collector i narzut pamięciowy. Aby skonfigurować przestrzenie nazw jądra Linux przed uruchomieniem procesu głównego, runc musi uruchomić się ponownie i uciec się do obejść w C.
Projekt crun organizacji Containers radzi sobie z tym problemem bezpośrednio. Jest to lekka implementacja specyfikacji OCI (Open Container Initiative), napisana całkowicie w czystym C.
Co daje przejście na czyste C
Główna koncepcja jest prosta: usuń wszystko niepotrzebne z ścieżki krytycznej tworzenia kontenera. Gdy runtime nie jest obciążony dodatkowymi warstwami, zużycie zasobów i szybkość reakcji dramatycznie się poprawiają.
Zużycie pamięci i uruchamianie na granicach limitów
Testy porównawcze developerów pokazują wymowną różnicę:
# Запуск через 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
Kontener z crun niezawodnie startuje nawet przy restrykcyjnym limicie pamięci 512 KB, podczas gdy runc zawodzi nawet przy 4 MB. Dla ciężkich mikroserwisów chmurowych różnica jednego megabajta może wydawać się błaha, ale na urządzeniach IoT, routerach i serwerach brzegowych ta oszczędność zwalnia cenną pamięć na rzeczywiste obciążenia robocze.
Szybkość sekwencyjnego uruchamiania
Gdy musisz często uruchamiać krótkotrwałe zadania (na przykład na platformach serverless lub runnerach CI/CD), czas inicjalizacji kontenera staje się wąskim gardłem. W teście uruchamiającym 100 kontenerów sekwencyjnie za pomocą polecenia /bin/true, różnica czasowa jest niemal dwukrotna:
| Runtime | Czas wykonania (100 kontenerów) | Różnica | | :--- | :--- | :--- | | crun | 0:01.69 | -49.4% | | runc | 0:03.34 | baseline |
Implementacja w C startuje dwukrotnie szybciej dzięki brakowi narzutu inicjalizacji runtime Go i mniejszej liczbie niepotrzebnych wywołań systemowych.
Używanie jako biblioteki
Większość runtime'ów istnieje tylko jako samodzielne pliki wykonywalne. Jeśli musisz uruchomić kontener OCI ze swojego programu, zazwyczaj musisz użyć fork() i wywołać zewnętrzne narzędzie.
W crun możesz zbudować bibliotekę współdzieloną libcrun. Otwiera to bezpośrednie API w C do pracy z kontenerami:
- osadzanie uruchamiania izolowanego środowiska we własnych demonach
- brak narzutu związanego z tworzeniem osobnych procesów
- wbudowana obsługa powiązań Pythona i Luy
Budowanie i instalacja
Narzędzie jest dostępne w większości dystrybucji poprzez standardowe menedżery pakietów, ale budowanie ze źródeł jest również proste.
Budowanie na Ubuntu wymaga podstawowych plików nagłówkowych:
$ 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
Sama kompilacja jest standardowa:
$ ./autogen.sh
$ ./configure
$ make
$ sudo make install
Jeśli planujesz używać biblioteki w swoim kodzie, flaga konfiguracji zmieni się na ./configure --enable-shared.
Dla powtarzalnych środowisk i serwerów bez dodatkowych zależności, projekt obsługuje statyczne budowanie przez Nix Flakes:
$ nix --extra-experimental-features "nix-command flakes" build "path:.#crun-static-amd64"
$ ./result/bin/crun --version
Rezultatem jest kompaktowy statyczny plik binarny dla x86_64 z glibc, gotowy do uruchomienia na dowolnej maszynie docelowej.
Dla kogo to jest
Przejście na crun ma sens w kilku scenariuszach:
- Edge computing i IoT. Gdy urządzenie ma przylutowane tylko 256 MB lub 512 MB RAM, oddawanie megabajtów runtime'owi jest nie do przyjęcia.
- Serverless i Functions-as-a-Service. W środowiskach z częstym tworzeniem i niszczeniem izolowanych kontenerów, 50% przyspieszenie uruchamiania bezpośrednio redukuje opóźnienie zimnego startu.
- Gęste pakowanie kontenerów. Jeśli setki małych workerów działa na jednym hoście, łączne oszczędności RAM sumują się do gigabajtów.
Zamiana runtime'a w Podman jest tak prosta jak jedna flaga --runtime /usr/bin/crun lub zmiana parametru w /etc/containers/containers.conf. Żadne zmiany w manifestach czy obrazach nie są potrzebne.
Powiązane projekty