Kontejnery

Podman/Docker, namespaces, cgroups.

Co se naučíš: Pochopíš, že kontejner není virtuální stroj, ale izolovaný proces, a co to znamená v provozu.

13 min čtení + cvičeníNavazuje na:⚙️ Procesy a signály

Kontejner je jen izolovaný proces běžící na jádře tvého hostitele, ne malý virtuální počítač. Nemá vlastní jádro, nebootuje, nemá „svůj hardware": sdílí jádro s tebou a od zbytku systému ho oddělují dvě funkce Linuxu, namespaces (co proces vidí) a cgroups (kolik smí spotřebovat). Až tohle pochopíš, přestaneš od kontejnerů čekat věci, které umí jen VM, a naopak oceníš, proč jsou tak rychlé a lehké.


Pár pojmů na úvod

  • Kontejner = izolovaný proces (nebo skupina procesů) na sdíleném jádře hostitele. Tváří se, že má systém sám pro sebe, ale pod ním je pořád tvoje jedno jádro Linux.
  • Image (obraz) = neměnná šablona, ze které kontejner vzniká, zabalený souborový systém + metadata (co spustit). Image se nemění; kontejner je jeho běžící instance.
  • Namespaces = funkce jádra, která procesu izoluje pohled, vidí jen svoje procesy, svou síť, své mounty. Nic „mimo" pro něj neexistuje.
  • cgroups (control groups) = funkce jádra, která omezuje zdroje, kolik CPU a RAM proces dostane. Namespaces řeší co vidí, cgroups řeší kolik sní.
  • Registry = sklad hotových image ke stažení (Docker Hub, quay.io). Odtud pullneš nginx, postgres, python… místo abys je stavěl od nuly.
  • Podman / Docker = nástroje, kterými kontejnery ovládáš. Docker je nejrozšířenější; Podman (rootless, bez démona) je běžný v ekosystému RHEL/Fedora. CLI mají skoro totožné.

Kontejner vs virtuální stroj, hlavní rozdíl

Nejčastější omyl začátečníka je, že kontejner = odlehčená VM. Není. Virtuální stroj emuluje celý počítač: má vlastní jádro, bootuje, běží v něm kompletní operační systém nad hypervizorem. Kontejner nic z toho nemá, je to obyčejný proces, který jádro hostitele jen „schová" do svých namespaces a omezí cgroupami.

Důsledky, které z toho plynou:

  • Rychlost a velikost: kontejner nabootuje za zlomek vteřiny a váží megabajty, ne gigabajty, protože nenese celý OS ani jádro.
  • Hustota: na jednom stroji rozjedeš stovky kontejnerů; VM jsou o řád těžší.
  • Slabší izolace: protože sdílejí jádro, díra v jádře může teoreticky prasknout hranici mezi kontejnery. VM je oddělená silněji (ale za cenu režie). K bezpečnosti se vrátíme níž.

Image vs kontejner

Tenhle rozdíl si zapamatuj natvrdo, protože se v něm pletou i pokročilí:

  • Image je neměnná šablona, jako instalační ISO nebo „recept". Leží v úložišti a nikdy se nemění.
  • Kontejner je běžící (nebo zastavená) instance vytvořená z image, jako uvařené jídlo z receptu. Z jednoho image spustíš klidně deset kontejnerů najednou, každý žije vlastní život.
podman pull nginx          # stáhne image nginx z registry
podman images              # vypíše lokálně dostupné image
podman run -d nginx        # vytvoří a spustí kontejner z image
podman ps                  # vypíše běžící kontejnery
podman ps -a               # i zastavené

💡 Když smažeš kontejner, image zůstane. Když smažeš image, musíš ho příště znovu pullnout nebo postavit. Jsou to dvě různé věci na dvou různých místech.


Podman vs Docker

Docker kontejnery zpopularizoval a jeho CLI se stalo standardem. Má ale dvě vlastnosti, které Podman řeší lépe:

  • Démon: Docker běží jako pořád zapnutá služba (dockerd) pod rootem; všechno jde přes ni. Podman žádného démona nemá, spustí kontejner jako obyčejný podproces a skončí.
  • Rootless: Podman umí běžet bez roota, pod tvým uživatelem. Kontejner tak nemá práva do celého systému, i kdyby se z něj něco „utrhlo".

Nejlepší zpráva: CLI je kompatibilní. Skoro všude, kde návod píše docker, můžeš dát podman:

podman run -d -p 8080:80 nginx    # to samé jako `docker run ...`
alias docker=podman                # a spousta návodů „prostě jede"

V příkladech používáme podman, ale s Dockerem je postup skoro identický (docker run … funguje stejně).


Prakticky: porty a data

Kontejner je izolovaný, takže ven není vidět nic, dokud to sám nepropojíš. Dvě věci potřebuješ skoro pořád, pustit dovnitř síť (port) a přežít data (volume).

Porty (-p): namapuješ port hostitele na port uvnitř kontejneru, jinak se k službě nedostaneš:

podman run -d -p 8080:80 nginx
# host:8080  →  kontejner:80  → v prohlížeči otevřeš http://localhost:8080

Volumes (-v): připojí složku hostitele (nebo pojmenovaný svazek) dovnitř kontejneru. Bez toho je souborový systém kontejneru dočasný:

podman run -d -v ./data:/var/lib/postgresql/data:Z postgres
# cokoli DB zapíše do /var/lib/... skončí ve složce ./data na hostiteli

⚠️ Když na systému běží SELinux (enforcing, typicky Fedora/RHEL), přidej k bind-mountu příznak :Z (privátní label jen pro tenhle kontejner) nebo :z (sdílený mezi kontejnery). Bez něj kontejner do složky nesmí a spadne na „permission denied", i když práva (rwx) vypadají správně.

🚩 Souborový systém kontejneru žije jen po dobu jeho existence. Když kontejner smažeš, jeho data zmizí s ním. Cokoli má přežít (databáze, uploady, logy), MUSÍ jít do volume. Tohle je asi nejčastější bolestivá lekce začátečníků, viz Failure modes.


Containerfile, vlastní image z receptu

Když ti hotový image nestačí, napíšeš si vlastní. Recept se jmenuje Containerfile (u Dockeru Dockerfile, obsah je stejný), je to textový seznam kroků, jak image postavit:

FROM node:22            # z čeho vyjít (základní image)
WORKDIR /app            # pracovní složka uvnitř
COPY . .                # zkopíruj kód z hostitele do image
RUN npm ci              # spusť příkaz při stavbě
CMD ["node", "server.js"]   # co spustit, když kontejner poběží
podman build -t moje-app .   # postaví image podle Containerfile v aktuální složce
podman run -p 3000:3000 moje-app

Kouzlo je v tom, že celé prostředí je popsané v jednom souboru, kdokoli s tímhle receptem postaví bit po bitu stejný image. Tím se dostáváme k tomu, proč vůbec kontejnery používat.


Proč vůbec kontejnery

  • Konzistence dev ↔ prod: image nese aplikaci i její prostředí (verze runtime, knihovny, systémové balíčky). Konec „u mě to jede, na serveru ne", na serveru běží stejný image.
  • Izolace: dvě aplikace, které chtějí různé verze Pythonu nebo knihoven, si nelezou do zelí; každá má svůj kontejner.
  • Rychlé nasazení: nasazení = stáhnout image a spustit. Žádné ruční „doinstaluj tohle, nastav tamto". Rollback = spustit předchozí verzi image.
  • Čistý hostitel: nemusíš na systém instalovat deset různých databází a runtime; všechno žije zabalené v kontejnerech a po smazání nezůstane bordel.

Failure modes, časté omyly začátečníků

  • „Kontejner je bezpečný jako VM." Není, sdílí jádro s hostitelem, takže izolace je slabší. Nespouštěj v něm slepě nedůvěryhodný kód s očekáváním pancéřové hranice. Pomáhá rootless Podman.
  • Root uvnitř kontejneru. U rootful Dockeru proces běží jako root a při špatné konfiguraci (nebo díře v jádře) to může být root i na hostiteli. Rootless Podman naopak root uvnitř mapuje přes user namespace na tvého neprivilegovaného uživatele, takže host-root to není, proto ho preferuj (plus USER bez práv v image).
  • „Smazal jsem kontejner a přišel o databázi.“ Data bez volume jsou dočasná. Vždy si ujasni, co má přežít, a dej to do -v, jinak podman rm = konec dat.
  • „Build funguje u mě.“ Když ostatním pošleš jen zdrojáky bez image nebo Containerfile, postaví si něco jiného. Sdílej image (registry) nebo aspoň Containerfile, ne zabalený stav ze svého disku.
  • Obří image. FROM ubuntu + hromada balíčků = gigabajtové image, které se pomalu stahují. Vyjdi z menšího základu (alpine, -slim), uklízej po sobě, nezanášej do image build nástroje.
  • Tag latest. nginx:latest dnes a za měsíc může být jiná verze, build přestane být reprodukovatelný. Přišpendli konkrétní verzi (nginx:1.27), ať víš, co ti kde běží.

🛠️ Cvičení

  1. Kontejner vs VM vlastními slovy. Vysvětli na dvě věty, proč kontejner nabootuje za zlomek vteřiny a VM ne. Kde je v tom „vlastní jádro"?
  2. Spusť si web. Stáhni image nginx, spusť z něj kontejner a namapuj ho na port hostitele tak, abys ho otevřel v prohlížeči na http://localhost:8080. Jaký -p zápis použiješ?
  3. Image vs kontejner. Spusť ze stejného image nginx dva kontejnery zároveň a vypiš si běžící kontejnery. Kolik je image a kolik kontejnerů? Vysvětli ten poměr.
  4. Ztráta dat. Rozjeď kontejner, vytvoř v něm soubor (bez volume), kontejner smaž a spusť znovu. Je soubor tam? Proč? Jak bys to zařídil, aby přežil?
  5. Přečti si Containerfile. Najdi u libovolného projektu Containerfile/Dockerfile a popiš, co dělá řádek FROM, COPY, RUN a CMD. Který se spouští při stavbě a který až při běhu?
Náčrt řešení: rozbal, až si cvičení zkusíš sám
  1. Kontejner vs VM, kontejner je jen proces na už běžícím jádře hostitele, takže se „spustí" jako každý jiný program, okamžitě. VM musí nastartovat vlastní jádro a celý operační systém nad hypervizorem, což je plnohodnotný boot. Pozor: proto je kontejner malý a rychlý, ale izolovaný slaběji, sdílí jádro.
  2. Spuštění nginx, podman pull nginx, pak podman run -d -p 8080:80 nginx. Zápis -p 8080:80 znamená „port 8080 na hostiteli → port 80 uvnitř kontejneru". V prohlížeči otevřeš http://localhost:8080. Pozor: pořadí je HOST:KONTEJNER, splést si je znamená, že se nikam nedostaneš.
  3. Image vs kontejner, podman run -d nginx spustíš dvakrát, pak podman ps. Máš jeden image a dva kontejnery, image je neměnná šablona, kontejner je běžící instance z ní. Poměr klidně 1 image : N kontejnerů. Pozor: smazání jednoho kontejneru se druhého ani image nedotkne.
  4. Ztráta dat, soubor tam není. Souborový systém kontejneru žije jen po dobu jeho existence; podman rm ho zahodí. Aby soubor přežil, musíš při spuštění připojit volume (-v ./data:/cesta/uvnitr), zapisovat do něj, a data pak leží na hostiteli. Pozor: přežije jen to, co je uvnitř cesty volume, nic jiného.
  5. Containerfile, FROM určuje základní image, ze kterého se staví; COPY nakopíruje soubory z hostitele do image; RUN spustí příkaz při stavbě (např. instalace závislostí) a zapeče výsledek do image; CMD říká, co se spustí až při běhu kontejneru. Pozor: RUN = build-time, CMD = run-time, pletou se nejčastěji.

🧠 Otázky & odpovědi

Jaký je rozdíl mezi kontejnerem a virtuálním strojem?

Virtuální stroj emuluje celý počítač, má vlastní jádro, bootuje a běží v něm kompletní operační systém nad hypervizorem. Kontejner je oproti tomu jen izolovaný proces na sdíleném jádře hostitele: nemá vlastní jádro, nebootuje, jádro ho jen schová do svých namespaces a omezí cgroupami. Proto je kontejner mnohem rychlejší a lehčí (megabajty, start za zlomek vteřiny), ale izolovaný slaběji, protože o jádro se dělí s hostitelem i s ostatními kontejnery.

Co jsou namespaces a cgroups a jak spolu souvisí?

Jsou to dvě funkce jádra Linux, na kterých kontejnery stojí. Namespaces izolují pohled procesu, vidí jen svoje procesy, svou síť, své mounty, jako by byl na systému sám. cgroups izolují zdroje: určují, kolik CPU a paměti proces smí spotřebovat. Zkráceně: namespaces řeší co proces vidí, cgroups řeší kolik toho sní. Teprve dohromady dají dojem samostatného systému, i když je to pořád jen proces na tvém jádře.

Jaký je rozdíl mezi image a kontejnerem?

Image je neměnná šablona, zabalený souborový systém plus metadata o tom, co spustit; leží v úložišti a nikdy se sám nemění (jako recept nebo instalační ISO). Kontejner je běžící instance vytvořená z image (jako uvařené jídlo z receptu). Z jednoho image spustíš klidně deset kontejnerů najednou a každý žije vlastním životem. Když kontejner smažeš, image zůstane; když smažeš image, musíš ho příště znovu stáhnout nebo postavit.

Proč používat Podman místo Dockeru?

Podman řeší dvě slabiny klasického Dockeru. Za prvé nemá démona. Docker běží jako pořád zapnutá root služba, přes kterou jde všechno; Podman jen spustí kontejner jako obyčejný podproces a skončí. Za druhé umí běžet rootless, pod tvým uživatelem, takže kontejner nemá práva do celého systému, i kdyby se z něj něco utrhlo. A protože je CLI kompatibilní, skoro všude, kde návod píše docker, můžeš dát podman a funguje to stejně.

Proč data v kontejneru zmizí a jak je zachovat?

Protože souborový systém kontejneru je dočasný, žije jen po dobu existence toho kontejneru. Jakmile kontejner smažeš (podman rm), zmizí s ním i všechno, co si dovnitř zapsal. Aby data přežila, musíš připojit volume přes -v, buď složku hostitele, nebo pojmenovaný svazek. Cokoli aplikace zapíše do cesty volume, skončí mimo kontejner a přežije jeho smazání i restart. Proto databáze, uploady a logy patří vždy do volume, ne do „vnitřku" kontejneru.