Zvolte jazyk

Docker je pět kernelových funkcí v plášti

Developer na codeandcake.dev strávil čas stavbou kontejnerového runtime pro zábavu a napsal, co se z toho naučil o Dockeru. Článek je krátký a lepší než většina vysvětlení kontejnerů, které jsem četl, protože začíná tím, co se při čtení dokumentace nikdy úplně neuchytí: kontejner není malá virtuální stroj. Je to proces na vašem kernelu, na který jsou přišroubovány omezení.

Váš kontejner je právě teď v /proc

To je první věc, kterou článek udělá správně. Kernel stále spouští proces kontejneru. Pokud víte, kam se podívat, proces je v /proc uveden jako cokoli jiného na stroji. Žádný hypervisor, žádný druhý kernel, žádná emulovaná hardwarová zařízení. Izolace je reálná, ale je to izolace pohledu a zdrojů, ne zeď.

A neexistuje žádná funkce Linuxu nazvaná kontejner. Jak to článek vyjadřuje, kontejner je ve skutečnosti pět kernelových funkcí v plášti. Namespaces rozhodují, co proces vidí. Cgroups rozhodují, kolik CPU a paměti může použít. Síťování je vlastní hromada mechanismů, které většina z nás, včetně autora, plně nepochopí. Obrazy se ukážou jako složky, které Docker někde uloží a pak předá chroot nebo pivot_root, což z té složky udělá novou kořenovou složku pro proces. Když napíšete docker run s obrazem Ubuntu a shell bash, to je ten trik. Složka, přepnutí kořene a sada omezení aplikovaná dříve, než se váš shell vůbec spustí.

Docker používám roky a stále mi to přijde v užitečném smyslu deflační. Kouzlo je celé v potrubí a potrubí je prozkoumatelné.

Go byl podivná volba pro runc

Nezajímavější částí článku je volba jazyka. Kontejnerový runtime Dockeru, runc, je napsaný v Go. Runtime jazyka Go je nativně vícevlákenný, což je skutečně dobrá vlastnost pro servery, ale nepohodlná pro kontejnerový runtime. Mnoho syscalls, které musí runtime provádět, vyžaduje jednovlákenné prostředí. Proto runc obsahuje C modul, který se spustí před inicializací Go runtime, čistě za účelem provedení těchto volání.

Potom je tu fork. Go ho nemá tak, jak ho runtime potřebuje, protože fork forkne pouze hlavní vlákno. Kontejnerový runtime skutečně chce fork, aplikovat hromadu omezení na potomka a poté exec proces, který jste požadovali. runc se tomuhle vyhýbá tím, že execuje /proc/self/exe se speciálním argumentem příkazového řádku, který ho označí jako potomský proces, což je dostatečně blízká aproximace fork, aby to fungovalo.

Pokud jste se někdy ptali, proč nástroje pro kontejnery místy působí, jako by bojovaly s platformou, to je důvod. Bojují s runtime jazyka, ne s kernelu.

Root je ta část, kterou nikdo nedokázal vyřešit

API Linux kernelu, které runtime potřebuje, buď vyžadují root, nebo je vyžadovaly, proto se démon Dockeru historicky spouští jako root. To je velké množství privilegií soustředěných na jednom místě a článek přímo říká, že bezpečnost kontejnerů musí být velmi náročná práce. A je.

Rootless runtime věci zlepšily. Výjimkou je síťování. Aby vám dal stejné funkce bez rootu, zdá se, že rootless runtime musí reimplementovat síťový stack v userspace. To je vážné množství práce, aby se dostal tam, kde začal, a vysvětluje hodně o tom, proč rootless konfigurace chodí jinak, když se něco pokazí se sítí.

Žádné z toho není argument proti kontejnerům. Je to argument pro to, abyste věděli, která z pěti funkcí je zodpovědná za zaručení, které si myslíte, že máte. Na mých vlastních strojích by upřímná odpověď, než jsem to četl, byla „Docker to udělá“. To není odpověď. Je to pokrčení rameny s navíc kroky.

Článek končí tam, kde končí hodně dobré technické psaní, s autorem stále v rané fázi vlastního runtime a s plány pokračovat. Čtení mi dalo chuť proklepnout /proc na běžícím kontejneru a vidět proces tam sedící. Bude tam. Vždy tam je.