Aurora Kursy

Kursy Docker od zera do bohateraKontener to nie mała wirtualka

Kontener to nie mała wirtualka

Lekcja 1 z 19 Podgląd

Kontener to nie mała wirtualka

Większość ludzi przychodzi do Dockera z modelem mentalnym z VirtualBoksa: kontener to pewnie taka mniejsza, szybsza maszyna wirtualna. Ten model jest wygodny, intuicyjny i błędny — a każda jego konsekwencja będzie cię prowadzić w złą stronę przy debugowaniu. Zacznijmy więc od wyprostowania, bo na tym jednym zdaniu stoi cała reszta kursu.

Zwykły proces w przebraniu

Maszyna wirtualna emuluje sprzęt. Ma własny BIOS, własne jądro, bootuje się kilkadziesiąt sekund i z punktu widzenia hosta jest czarną skrzynką. Kontener nie ma nic z tych rzeczy. Kontener to zwykły proces uruchomiony przez jądro hosta — taki sam jak twój bash czy przeglądarka — tyle że jądro założyło mu klapki na oczy. Robią to dwa mechanizmy:

  • namespaces decydują, co proces widzi: własną numerację procesów (w środku twój proces ma PID 1 i jest święcie przekonany, że jest sam na maszynie), własny stos sieciowy z własnym localhost, własny widok systemu plików, własny hostname.
  • cgroups decydują, ile proces może: limity pamięci, procesora, operacji dyskowych. To cgroups egzekwują limit pamięci — i to one stoją za tajemniczym kodem wyjścia 137, do którego jeszcze wrócimy.

Namespaces to „co widzisz", cgroups to „ile możesz". Cała reszta Dockera to wygodne opakowanie na te dwa mechanizmy jądra.

Wspólny kernel i jego konsekwencje

Skoro kontener to proces jądra hosta, to jądro jest jedno i wspólne — dla hosta i dla wszystkich kontenerów naraz. Z tej jednej obserwacji wynika kilka rzeczy, które warto poukładać sobie pierwszego dnia.

Kontener nie ma własnego systemu operacyjnego. „Obraz ubuntu" to nie Ubuntu z jądrem — to spakowany zestaw plików przestrzeni użytkownika: bash, apt, biblioteki, układ katalogów. Jądro zawsze dostajesz od hosta. Dlatego „ubuntu" w kontenerze na hoście z Debianem to żaden paradoks: pliki z Ubuntu, wywołania systemowe do jądra Debiana.

Nie uruchomisz kontenera z Windowsem na Linuksie ani odwrotnie. Binarka windowsowa woła jądro Windows, a jądro jest tu linuksowe — i nie ma czego emulować, bo kontener niczego nie emuluje. Jeśli Docker Desktop na Windowsie czy macOS „jakoś" odpala linuksowe kontenery, to dlatego, że po cichu trzyma pod spodem maszynę wirtualną z Linuksem. Kontenery są linuksowe aż do dna.

Start w milisekundach. Nie ma bootowania — jądro po prostu tworzy proces. Dlatego na jednym hoście spokojnie działa kilkaset kontenerów, a kilkaset wirtualek to już projekt budżetowy.

Izolacja jest słabsza niż w maszynie wirtualnej. Wspólne jądro to wspólna powierzchnia ataku: błąd w kernelu dotyczy wszystkich naraz. Kontener to granica wygody, nie granica bezpieczeństwa klasy maszyny wirtualnej. Miej to z tyłu głowy, zanim ktoś na spotkaniu zaproponuje „odpalimy niezaufany kod klientów w kontenerach i będzie bezpiecznie".

Skąd to się wzięło — i dlaczego „u mnie działa" umarło

Kontenery są starsze niż Docker. W 1979 roku pojawił się chroot — więzienie plikowe: proces widzi tylko wycinek systemu plików, ale procesy i sieć nadal dzieli ze wszystkimi. Około 2008 roku LXC po raz pierwszy złożyło namespaces i cgroups w całość, którą dało się nazwać kontenerem — tyle że używało się jej jak lekkiej maszyny: postaw, wejdź, konfiguruj ręcznie. Przenoszenie takiego środowiska między maszynami pozostawało rzemiosłem.

Docker w 2013 roku nie wynalazł kontenerów. Wynalazł opakowanie: format obrazu, w którym aplikacja jedzie razem ze wszystkimi zależnościami, oraz rejestr, z którego ten obraz pobierasz jednym poleceniem na dowolnej maszynie. „U mnie działa" przestało być wymówką nie dlatego, że izolacja się poprawiła — tylko dlatego, że środowisko zaczęło podróżować razem z aplikacją. Ten sam obraz na laptopie, w CI i na produkcji to ten sam zestaw plików co do bajta.

Kto z kim gada

Polecenie docker to nie Docker. To cienki klient, który po API rozmawia z demonem:

docker (CLI) ──> dockerd ──> containerd ──> runc ──> twój proces
  • dockerd wystawia API i ogarnia całą resztę: obrazy, sieci, wolumeny.
  • containerd zarządza cyklem życia kontenerów: tworzeniem, startem, zatrzymywaniem.
  • runc wykonuje brudną robotę: tworzy namespaces, ustawia cgroups, uruchamia proces… i kończy działanie. Nie siedzi przy kontenerze jak niańka — odpala go, zostawia malutki proces-łącznik (shim) i wychodzi.

Konsekwencja praktyczna: kontener nie jest „w" Dockerze. Jest procesem jądra, a Docker go tylko założył. Dlatego restart demona nie musi oznaczać restartu twoich kontenerów — przy włączonym live-restore przeżyją go bez mrugnięcia.

Dowód: ps nie kłamie

docker run -d --name baza redis:7-alpine
ps aux | grep redis

Na hoście zobaczysz redis-server z normalnym, hostowym PID-em. Wejdź do środka kontenera — ten sam proces ma tam PID 1. Jeden proces, dwa punkty widzenia, zero magii.

I to jest model mentalny, z którym debuguje się kontenery: kontener widać w ps na hoście, bo to zwykły proces. Możesz go ubić killem, podejrzeć mu otwarte pliki, policzy ci się do obciążenia systemu. Mała wirtualka niczego takiego by nie pozwoliła — i całe szczęście, że kontener nią nie jest.

Ćwiczenia praktyczne

0 / 1

Sprawdzian

5 pytań · próg 70%

Zaloguj się, żeby rozwiązać sprawdzian i zapisać postęp.