Kihagyás

Kernel

1. Bevezetés

A konténerizáció a modern szoftverfejlesztés és telepítés sarokkövévé vált, lehetővé téve az alkalmazások platformfüggetlen, konzisztens futtatását. A leggyakrabban használt eszközök, mint például a Docker, egy magas szintű, felhasználóbarát absztrakciós réteget biztosítanak, elrejtve a háttérben zajló bonyolult technikai részleteket.

A Docker motorháztetője alatt a Linux kernel mélyen gyökerező, alapvető funkciói dolgoznak, amelyek lehetővé teszik a konténerek számára az elszigetelt és erőforrás-korlátozott működést. Ezen absztrakció mögött azonban a Linux rendszermag alapvető mechanizmusai állnak, amelyek a konténerek valódi izolációját és erőforrás-menedzsmentjét végzik. A két fő pillér a namespaces és a cgroups.

A konténerek működését a Linux kernel alacsony szintű mechanizmusai teszik lehetővé. A névterek (namespaces) biztosítják az izolációt, míg a csoportok (control groups, cgroups) az erőforrások korlátozását és nyomon követését. Ezekre épül a konténerizáció szabványosítása, amelyet az Open Container Initiative (OCI) határoz meg, egységesítve az image-formátumokat és a futtatási környezeteket. Az OCI egyik alapvető komponense a runc, egy alacsony szintű futtatókörnyezet, amely a kernel szolgáltatásait közvetlenül használva indítja el a konténereket. A technológia gyakorlati demonstrációja során egy konténer akár a Docker megkerülésével, kézi lépésekkel is létrehozható és futtatható.

1.1. A rétegzett konténer futtatókörnyezet

A konténeres ökoszisztéma az elmúlt években a monolitikus architektúrától egy modulárisabb, interoperábilisabb modell felé mozdult el. A korábbi egységes Docker démon, amely az összes feladatot elvégezte – a képek építésétől és kezelésétől a konténerek futtatásáig – mára több, szakosodott komponensre bomlott. Ez a széttöredezés a szabványosítási igényre adott válasz, amelynek célja a szállítófüggőség elkerülése és az innováció ösztönzése a konténerizáció különböző területein. A felhasználói lekérdezés, amely a Docker kikerülésére fókuszál, tökéletesen illeszkedik ebbe a modern, rétegzett modellbe. A konténer futtatókörnyezet hierarchiája ma már egyértelműen elkülönül:

  1. Magas Szintű Felhasználói Eszközök: Ezek a parancssori felületek, mint a docker vagy a podman, a felhasználói élményt biztosítják. Feladataik közé tartozik a konténerképek építése, letöltése és a futtatási parancsok delegálása egy alacsonyabb szintű futtatókörnyezetnek.
  2. Magas Szintű Futtatókörnyezetek: Ezen démonok, mint a containerd (a Docker alapértelmezett futtatókörnyezete) vagy a CRI-O, a konténerek teljes életciklusát kezelik, beleértve a képátvitelt, a tárolást és a hálózati beállításokat. Ők a magas szintű felhasználói eszközök és az alacsony szintű futtatókörnyezetek közötti interfész. A Kubernetes például egy CRI-kompatibilis futtatókörnyezetet használ a konténerek kezelésére.
  3. Alacsony Szintű Futtatókörnyezetek: Az olyan eszközök, mint a runc, a hierarchia legalján helyezkednek el, és közvetlenül a rendszermag primitívumait, a neveket és cgroups-okat használva hozzák létre és futtatják a konténerfolyamatokat. A runc az Open Container Initiative (OCI) futtatókörnyezeti specifikációjának referenciamegvalósítása, ami kulcsfontosságú szerepet játszik az interoperabilitásban. A youki és a crun hasonlóan OCI-kompatibilis, csereszabatos alternatívák, amelyek megerősítik ezt a moduláris felépítést.
  4. Linux Rendszermag Primitívumok: A hierarchia alapját a kernel szintű technológiák, a névterek és a cgroups-ok képezik, amelyek a valódi izolációt és erőforrás-korlátozást biztosítják.

Ez a moduláris architektúra a szabványosítás eredménye. Az Open Container Initiative (OCI) által kidolgozott, deklaratív szabványok, mint a futtatókörnyezeti specifikáció, lehetővé teszik a komponensek közötti kommunikációt. A runc mint OCI referenciamegvalósítás létezése a legfontosabb példája ennek a filozófiának: a futtatókörnyezet lecserélhető, ha az OCI specifikációt követi, ami felnyitja az utat új, optimalizált vagy speciális futtatókörnyezetek számára. Ez az elv volt az alapja a monolitikus Docker daemon helyett a mai, rugalmasabb és versengő ökoszisztéma kialakulásának.

2.Namespaces

A névterek (namespaces) a Linux rendszermag egyik alapvető funkciója, amely az elszigetelésért felel, és logikai partíciókat hoz létre a globális rendszermag-erőforrások számára. Ennek a mechanizmusnak a lényege, hogy egy folyamat vagy folyamatcsoport számára egy saját, izolált nézetet biztosít a rendszer erőforrásairól, mintha azok külön gépen futnának. A névterek teszik lehetővé a konténerek könnyűsúlyú virtualizációját, amely különbözik a teljes virtualizációtól (pl. egy virtuális gép esetében), mivel nem egy teljes operációs rendszert emulálnak, hanem csak a futó folyamatokat izolálják a gazdagéptől és egymástól.

A koncepció jobban megérthető egy lakóház analógiájával. A ház maga a fizikai gép (a gazdagép). A névterek az egyes lakások, amelyekben a lakók (a konténerfolyamatok) élnek. Bár minden lakás ugyanabban a fizikai épületben található, mindegyiknek megvan a maga izolált környezete, saját bejárata és címe. A lakásokban zajló tevékenységek (pl. zenehallgatás) nem zavarják a többi lakót, hacsak nincs egy szándékos kapcsolódási pont. A névterek pontosan ezt a funkciót látják el a folyamatok számára: létrehozzák az elszigetelt környezetet, amely megakadályozza a folyamatok közötti interferenciát, és védelmet nyújt a gazdagép számára.

2.1. A konténerizációban használt namespaces

A modern Linux rendszerekben nyolc különböző névtértípus létezik a rendszermag 5.6-os verziója óta, amelyek mindegyike a rendszer egy specifikus erőforrását izolálja. A konténerizációban a következőek a legfontosabbak:

  • PID (Process ID) Névtér: Izolálja a folyamatazonosítókat. Ez azt jelenti, hogy a konténeren belüli folyamatok a saját PID-fa szerint vannak számozva, a konténeren kívüli folyamatokról pedig nincsenek tudomások. A konténeren belüli első folyamat PID-je mindig 1 lesz, ami az init folyamat szerepét tölti be, kezelve az árván maradt alfolyamatokat és gondoskodva a konténer megfelelő leállításáról. Névtér nélkül a folyamatazonosítók ütközhetnének.
  • MNT (Mount) Névtér: Ez az a mechanizmus, amely elkülöníti a fájlrendszer csatolási pontjait. Ennek köszönhetően minden konténernek saját, független nézete van a fájlrendszer hierarchiájáról. A névtéren belüli változtatások nem terjednek át a gazdagép fájlrendszerére, ami alapvető fontosságú a biztonság szempontjából. Ez az alapja annak az union filesystemnek is, amely a Docker image rétegeit kombinálja a futtatható konténer fájlrendszerének létrehozásához.
  • UTS (Unix Timesharing System) Névtér: Izolálja a gazdagép és a domain nevet, lehetővé téve, hogy a konténernek saját, független hostname-je legyen, a host rendszer hosztnevének megváltoztatása nélkül.
  • NET (Network) Névtér: Virtualizálja a hálózati stacket. Ez a namespace egy külön hálózati környezetet biztosít, saját IP-címmel, útválasztási táblával, aljzathalmazzal és tűzfalszabályokkal. Ez teszi lehetővé, hogy a konténereknek saját virtuális hálózati interfészeik legyenek, és ez az alapja a Docker hálózati modelljének. Új névtér létrehozásakor az alapértelmezett konfiguráció csak egy loopback interfésszel rendelkezik.
  • IPC (Inter-process Communication) Névtér: Izolálja a folyamatok közötti kommunikációs erőforrásokat, mint a szemafort, az üzenetsorokat és a megosztott memóriát. Ez megakadályozza, hogy a konténerek egymás között kommunikáljanak IPC-n keresztül, hacsak nem konfigurálták őket így.
  • USER (Felhasználó) Névtér: Izolálja a felhasználó és csoport azonosítókat. Ez a névtér lehetővé teszi, hogy egy konténeren belül futó folyamat root (UID 0) jogosultságokkal fusson, de a gazdagépen egy nem root felhasználóhoz legyen leképezve. Ez kritikus biztonsági funkció, mivel egy potenciális container escape esetén a támadó nem kapna root jogosultságokat a host rendszeren.

A következő táblázat összefoglalja a legfontosabb névtereket és azok szerepét a konténerizációban:

Névtér Kernel Név Izolált Erőforrás Analógia Hatása a Konténerre
PID CLONE_NEWPID Folyamatazonosítók, folyamatfa A szoba lakói listája A konténer csak a saját folyamatait látja, és az első folyamat PID 1-ként fut.
NET CLONE_NEWNET Hálózati verem, interfészek, IP-címek A lakás hálózati kábelezése Minden konténer saját IP-címmel, portokkal és útválasztási táblákkal rendelkezik.
MNT CLONE_NEWNS Csatolási pontok, fájlrendszer A szoba bútorai és elrendezése A konténer a saját, független fájlrendszer-hierarchiáját látja.
UTS CLONE_NEWUTS Hostname, domain név A lakás neve A konténernek lehet saját, a gazdagéptől független hostname-je.
IPC CLONE_NEWIPC Folyamatok közötti kommunikáció A szomszédok közötti beszélgetések Megakadályozza az azonos gazdagépen lévő konténerek közös memóriájának vagy üzenetsorainak elérését.
USER CLONE_NEWUSER Felhasználói és csoport azonosítók Különböző felhasználói azonosítók A konténeren belüli root felhasználó a gazdagépen egy alacsonyabb jogosultságú felhasználóra képezhető le.

2.2. Névterek létrehozása és kezelése unshare-rel

Az unshare parancs egy olyan alacsony szintű eszköz, amely lehetővé teszi a futó folyamat számára, hogy "leválassza" a gazdagépről a megosztott erőforrásokat, és új névteret hozzon létre számukra. Ez a parancs tökéletesen alkalmas a névtér-izoláció alapjainak demonstrálására.

2.2.1. UTS névtér létrehozása

$ sudo unshare --uts /bin/bash
# A prompt megváltozik, jelezve, hogy új névtérben vagyunk.
$ hostname my-container
$ hostname
my-container
$ exit
# Visszatérve a gazdagéphez, a hostname eredeti maradt.
$ hostname
<eredeti_hostname>

2.2.2. PID és MNT névtér létrehozása

Az izolált PID névtér létrehozásához kulcsfontosságú, hogy egy új /proc fájlrendszert csatoljunk, amely az új névtérhez tartozó folyamatokat tartalmazza. Ehhez a unshare parancsot a --pid és --mount-proc kapcsolókkal együtt kell használni, amelyek egy új MNT névteret is létrehoznak.

$ sudo unshare --fork --pid --mount-proc /bin/bash
# Új bash shellben vagyunk, immár egy új PID névtérben.
$ ps aux
USER     PID   %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root       1  0.0  0.0  21980  5628 pts/1    S+   14:30   0:00 /bin/bash
root       8  0.0  0.0  27544  6628 pts/1    R+   14:30   0:00 ps aux
# Látható, hogy a bash parancsunk PID 1-ként fut az új névtérben.
$ exit

2.2.3. NET névtér létrehozása

$ sudo unshare --net /bin/bash
$ ip addr
1: lo: <LOOPBACK,UP,LOWER_UP>...
# Csak a loopback interfész van jelen, a gazdagép hálózati interfészei nem.
$ exit

2.2.4. Hosztnév elkülönítése (UTS)

root@container:# hostname cont1
root@container:# hostname
cont1

A host rendszeren továbbra is az eredeti hostname látszik.

2.2.5. /proc elkülönítése

A --mount-proc hatására a /proc az új, izolált PID namespace alapján jelenik meg, nem a gazdagép folyamatai látszanak. Ez létfontosságú, hogy a ps, top, stb. működjön izoláltan.

2.2.6. Komplett izolált környezet létrehozása

sudo unshare \
  --uts \
  --pid \
  --mount \
  --net \
  --ipc \
  --user \
  --map-root-user \
  --fork \
  --mount-proc bash
  • --uts, --pid, --mount, --net, --ipc, --user: létrehozza a megfelelő Linux-névtereket\
  • --map-root-user: a user namespace-ben root-nak tűnsz, a gazdán nem vagy root
  • --fork: létrehoz egy gyermek folyamatot, ami init lesz a PID namespace-ben – nélküle nem lennél ténylegesen izolált
  • --mount-proc: újra mountolja a /proc-ot, hogy a ps és más eszközök helyesen működjenek az új PID namespace-ben

Ezzel kapsz egy shellt, ami úgy viselkedik, mint egy egyszerű konténer:

root@your-shell:# ps -ef
UID        PID  PPID C STIME TTY          TIME CMD
root         1     0  0 ...   pts/...    0:00 bash
root         5     1  0 ...   pts/...    0:00 ps -ef

Láthatod, hogy csak azok a folyamatok jelennek meg, amelyek ebbe a névtérbe tartoznak.

2.2.7. További névterek

Hálózati namespace ip netns segítségével:

sudo ip netns add testns
sudo ip netns exec testns ip addr

Felhasználói namespace:

sudo unshare --user --map-root-user bash

3. Cgroups

A cgroups (Control Groups) a Linux kernel egyik alapvető funkciója, amely lehetővé teszi a folyamatok hierarchikus csoportokba rendezését, valamint az erőforrás-felhasználásuk (CPU, memória, lemez I/O, hálózat stb.) korlátozását, elszámolását és prioritizálását. A cgroups teszi lehetővé, hogy a konténer motorok – például a Docker – pontosan szabályozzák, mennyi erőforrást kap egy-egy konténer, megakadályozva ezzel, hogy egyetlen konténer túl sok erőforrást fogyasszon, ami negatívan befolyásolná a host rendszert vagy más konténereket.

A cgroupok szerveződésében a következő kulcsfogalmak merülnek fel: * Alrendszer (subsystem / controller): Egy modul, amely a cgroupok csoportosítási funkcióit kihasználva egy adott erőforrásra vonatkozóan korlátozásokat vagy szabályokat alkalmaz. Például a memory alrendszer a memória, a cpu alrendszer a CPU, a blkio pedig a blokk I/O erőforrásokat kezeli. * Hierarchia: A cgroupok egy fa-struktúrában, hierarchikusan rendeződnek, ahol minden a rendszerben futó folyamat pontosan egy cgrouphoz tartozik a hierarchián belül. A szülő cgroupokra vonatkozó korlátozások érvényesek az összes leszármazott cgroupra is.

A Docker a cgroups segítségével a következő erőforrás-korlátozásokat képes kikényszeríteni:

  • CPU
    • --cpuset-cpus: egy vagy több meghatározott CPU magot lehet hozzárendelni a konténerhez.
    • --cpu-shares: prioritizálja a konténerek közötti CPU idő elosztását. Példa: ha két konténer fut, az egyiknek 1024, a másiknak 512 cpu-shares értéke van, akkor a CPU időt 2:1 arányban kapják meg.
  • Memória
    • --memory: kemény (hard) memóriakorlát, amit a konténer nem léphet túl.
    • --memory-reservation: puhább (soft) limit, ami akkor lép életbe, ha a rendszer memóriahiányt érzékel.
    • --memory-swap: finomhangolja a konténer memóriájának és a swap memóriának a felhasználását.

3.1. Cgroups v1 vs. Cgroups v2

A cgroupoknak két verziója létezik, amelyek jelentősen eltérnek egymástól a felépítés és a kezelés szempontjából. A cgroups v1, a kezdeti megvalósítás, külön, párhuzamos hierarchiát használ minden egyes alrendszerhez. Ez a megközelítés bonyolulttá és következetlenné tette a menedzsmentet, mivel a fejlesztések nagyrészt koordinálatlanul zajlottak. Ezen problémák miatt indult el a cgroups v2 fejlesztése, amely a modern Linux disztribúciók alapértelmezett beállítása.

A cgroups v2 a v1 problémáira adott válasz. Egyetlen, egységes hierarchiát hoz létre, amely leegyszerűsíti a folyamatok erőforrás-korlátainak beállítását. Bár a v2 célja a v1 teljes felváltása, a két rendszer egyszerre is működhet egy gépen, feltéve, hogy egy adott alrendszer nem fut egyszerre mindkét hierarchiában. A user által kért cgcreate és cgexec parancsok a korábbi, v1-re épülő libcgroup csomag részei, és bár elavultnak számítanak, továbbra is hasznosak a manuális cgroup-kezelés elveinek megértéséhez.

A következő táblázat összehasonlítja a két verziót.

Tulajdonság Cgroups v1 Cgroups v2 Következmény
Hierarchia Több, párhuzamos hierarchia, egy-egy alrendszerenként Egyetlen, egységes hierarchia Bonyolultabb, potenciálisan konfliktusos vs. egyszerűbb, koherensebb kezelés.
Folyamat-hozzárendelés Egy folyamat több cgroupban is lehet, külön hierarchiákban Egy folyamat egyetlen cgroupban van a hierarchián belül Fragmentált és nehezen követhető vs. letisztult, egyértelmű struktúra.
API Fájlrendszer-interfész (/sys/fs/cgroup) Fájlrendszer-interfész, de egységesebb vezérlőfájlokkal Különböző vezérlőfájlok formátuma a v1-ben vs. konzisztens API a v2-ben.
Integráció libcgroup eszközök, manuális mkdir parancsok systemd integráció, systemd-run parancsok A modern disztribúciókon elavult, de a koncepció megértéséhez hasznos vs. a modern rendszerek ajánlott megközelítése.

3.2. Erőforrás-korlátok bevezetése cgcreate-tel és cgexec-kel

A libcgroup eszközök lehetővé teszik a cgroupok és erőforrás-korlátok kézi létrehozását a parancssorból. Bár a modern rendszerek a systemd-t használják erre a célra, ez a labor kiválóan bemutatja a cgroupok alapvető működését. Létrehozunk egy memóriakontrollert használó cgroupot, korlátozzuk a memória felhasználást, és ellenőrizzük, hogy a korlátok érvényesülnek.

  1. lépés: libcgroup eszközök telepítése Ubuntu/Debian alapú rendszereken telepítse a cgroup-tools csomagot.
$ sudo apt-get update
$ sudo apt-get install cgroup-tools
  1. lépés: Cgroup létrehozása Hozz létre egy új cgroupot a memory alrendszerhez. Az cgcreate parancs automatikusan létrehozza a könyvtárat és a vezérlőfájlokat.
$ sudo cgcreate -g memory:my_cgroup
  1. lépés: Memória korlát beállítása Lépj a cgroup könyvtárába, és állítsd be egy 5 MB-os (5242880 bájt) memória korlátot a memory.limit_in_bytes fájlba írással. Kapcsold ki a swap-ot a sudo swapoff -a paranccsal, hogy a memória korlát közvetlenül a RAM-ra vonatkozzon, és ne használja a rendszer a swap-ot.
$ cd /sys/fs/cgroup/memory/my_cgroup
$ echo "5242880" | sudo tee memory.limit_in_bytes
  1. lépés: Program futtatása a korlátozott cgroupban Hozz létre egy egyszerű C programot, amely megpróbál több memóriát lefoglalni, mint a megengedett 5 MB.

mem_limit.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    size_t size = 10 * 1024 * 1024; // 10 MB
    void *ptr = malloc(size);
    if (ptr == NULL) {
        printf("Sikertelen memória allokáción");
        return 1;
    }
    printf("Sikeres memória allokáción");
    memset(ptr, 0, size);
    printf("Sikeres memória inicializáció\n");
    getchar(); // Vár a felhasználói bevitelre
    free(ptr);
    return 0;
}

Fordítás:

  $ gcc mem_limit.c -o mem_limit

Futtasd a programot a cgexec paranccsal a my_cgroup-on belül.

  $ sudo cgexec -g memory:my_cgroup./mem_limit

A program futása a korlát elérésekor hibával leáll. Az /var/log/syslog vagy a dmesg kimenete az OOM Killer (Out of Memory Killer) üzenetet fogja tartalmazni, jelezve, hogy a rendszermag kényszerűen leállította a folyamatot a beállított memória korlát túllépése miatt.

Ez a gyakorlat bemutatja, hogy a cgroupok manuális vezérlése miként történik. A modern megközelítés azonban az, hogy a systemd kezeli a cgroupok életciklusát, a rendszeregységek (services, scopes, slices) hierarchiájához rendelve a folyamatokat. Ez a trend a rendszermag-primitívumok vezérlésének a manualitásból egy magasabb szintű, központosított rendszerbe való átkerülését jelzi.

3.3 A konténer-elszigetelés

A konténerek elszigetelésének alapját a Linux Namespaces (névterek) és a Cgroups adja. A névterek felelősek a rendszer erőforrásainak elszigeteléséért, mint például a folyamatfák (PID), a fájlrendszer (Mount) és a hálózati stack (Network). A Cgroups, ahogy korábban tárgyaltuk, az erőforrás-gazdálkodásért felel. A hálózati névtér elszigetelésének demonstrálása

  1. Indítsd el az Nginx konténert a háttérben: docker run -d --name test-net nginx
  2. Kérdezd le a konténer folyamat-azonosítóját (PID) a hoszton: PID=$(docker inspect -f '{{.State.Pid}}' test-net)
  3. Használd az nsenter parancsot a konténer hálózati névtérébe való belépéshez és a hálózati interfészek listázásához: sudo nsenter --net=/proc/$PID/ns/net ip a

A parancs kimenete csak a konténer belső hálózati interfészeit mutatja, bizonyítva, hogy a konténernek saját, a hosztétól elkülönített hálózati stack-je van. A hoszt és a konténer közötti kommunikáció a veth (virtuális Ethernet) párral valósul meg. A veth pár egyik vége a konténer elszigetelt hálózati névterében, a másik vége pedig a hoszt docker0 hídján van. Ez a "virtuális kábel" biztosítja a csomagok áramlását a konténer és a hoszt között, ezzel demisztifikálva a konténerhálózat alapjait.

4. Namespaces és Cgroups

A namespaces és a cgroups nem ugyanazt a funkciót látják el, hanem egymást kiegészítve biztosítják a konténerek izolációját és erőforrás-kezelését. A namespaces az elszigetelést biztosítja, elválasztva a konténer folyamatfáját, fájlrendszerét, hálózatát és egyéb azonosítóit a host rendszertől. A cgroups ezzel szemben az erőforrás-korlátozást valósítja meg, megakadályozva, hogy a konténer túl sok erőforrást emésszen fel. Egy folyamat futhat a saját namespace-ében anélkül, hogy erőforrás-korlátok vonatkoznának rá, és fordítva. A Docker ereje abban rejlik, hogy mindkét technológiát kombinálja, így hozva létre egy hatékony és biztonságos futtatókörnyezetet.

Kategória Linux Namespaces Cgroups (Control Groups)
Fő Funkció Elszigetelés (Isolation) Erőforrás-limitálás és elszámolás (Resource Limitation)
Cél A folyamat számára a rendszer erőforrásairól egy egyedi, elkülönített nézet biztosítása. A folyamatok csoportosítása és erőforrás-felhasználásuk szabályozása.
Főbb Erőforrások PID, Mount, Network, UTS, IPC, User CPU, Memória, I/O, Hálózati sávszélesség
Példa a Dockerben A docker run parancs automatikusan létrehozza a konténerhez szükséges namespaces-eket, például a különálló network namespace-t. A docker run --memory=1g --cpus=1 parancs korlátozza a konténer erőforrás-felhasználását.
Analógia A konténer egy saját, zárt szoba, ahol saját szabályok vannak. A szoba légkondicionálója, amely korlátozza a szoba hőmérsékletét vagy a villanyszámlát.

5. Az OCI Szabvány és a runc

5.1. Az OCI specifikáció

Az Open Container Initiative (OCI) egy, a Linux Foundation által támogatott, nyílt együttműködési erőfeszítés, amelynek célja a konténer formátumok és futtatókörnyezetek szabványosítása. Az OCI megalapításának mozgatórugója a konténerek különböző futtatókörnyezetek és platformok közötti hordozhatóságának és interoperabilitásának javítása volt, megelőzve a szállítófüggőség kialakulását a piacon.

Az OCI három fő specifikációt dolgozott ki:

  • Futtatókörnyezeti Specifikáció (runtime-spec): Leírja, hogyan kell futtatni egy "fájlrendszeri csomagot" (filesystem bundle), amely egy diszkre van kicsomagolva.
  • Kép Specifikáció (image-spec): Meghatározza a konténerképek formátumát és tárolását, beleértve a fájlrendszer rétegeket és a konfigurációs metaadatokat.
  • Disztribúciós Specifikáció (distribution-spec): Szabványosítja a konténerképek regiszterek közötti terjesztésére szolgáló API protokollt.

5.2. Az OCI bundle

Az OCI futtatókörnyezeti specifikációja egy "csomag" (bundle) nevű alapvető egységet definiál. Ez egy olyan könyvtár, amelyet egy OCI-kompatibilis futtatókörnyezet (például a runc) fogyaszt, és amely minden szükséges információt tartalmaz a konténer elindításához. Egy OCI csomag két fő komponensből áll:

  1. rootfs: A konténer gyökér fájlrendszerét tartalmazó alkönyvtár.
  2. config.json: A konténer konfigurációját leíró deklaratív manifest fájl. Ez a JSON dokumentum részletezi, hogy milyen folyamatot kell elindítani a konténerben, milyen környezeti változókat kell beállítani, és milyen rendszermag erőforrás-korlátokat és névtereket kell használni.

Az OCI config.json fájl egy klasszikus példája a deklaratív konfigurációnak. A fájl leírja a kívánt állapotot (mit kell elérni), anélkül, hogy megmondaná, hogyan (hogyan kell elérni). Ezt a feladatot az imperatív végrehajtó, a runc végzi el. Ez az elválasztás a "mi" és a "hogyan" között az modern infrastruktúra-menedzsment egyik alapvető tervezési mintája, amely a Kubernetes-tól a Terraform-ig számos eszközben megtalálható.

A következő táblázat a config.json fájl kulcsfontosságú elemeit mutatja be.

JSON Mező Típus Kötelező? Leírás Példa Érték
ociVersion string Igen Az OCI futtatókörnyezeti specifikáció verziója. "1.0.1"
root object Igen A konténer gyökér fájlrendszerét határozza meg. {"path": "rootfs", "readonly": true}
root.path string Igen A rootfs könyvtár elérési útja, relatív a csomaghoz képest. "rootfs"
process object Igen A konténerben futtatandó folyamatot írja le. {"terminal": false, "args": ["/bin/sh"]}
process.args array Igen A futtatandó végrehajtható fájl és annak argumentumai. A PID 1 folyamatként indul a konténerben. ["/bin/sh"]
process.env array Opcionális Környezeti változók a konténerhez. ``
linux object Opcionális Linux-specifikus konfiguráció. Itt vannak beállítva a névterek és a cgroupok. {"namespaces": [...]}
linux.namespaces array<object> Opcionális A konténerhez használandó névterek listája. [{"type": "pid"}]
linux.resources.cpu object Opcionális A CPU erőforrás-korlátok beállítása. {"shares": 1024}

Table 3: Az OCI Bundle config.json Referencia

5.3. runc

A runc egy könnyűsúlyú, hordozható és állapot nélküli parancssori eszköz, amely az OCI futtatókörnyezeti specifikációjának referenciamegvalósítása. A Docker belsőleg a containerd-et használja, amely a runc-ra támaszkodik a tényleges konténer-végrehajtás során. A runc a „séf” a konténeres konyhában: ő az, aki ténylegesen elkészíti az ételt a konyhafőnök (containerd) utasításai alapján. Feladata, hogy a deklaratív OCI bundle-t beolvassa, és a szükséges rendszermag-hívásokat (syscall-okat) végrehajtva beállítsa a névtereket és a cgroups-okat, majd elindítsa a konténerfolyamatot.

A runc parancs-felülete a konténer-életciklus granularitására fókuszál. A runc run parancs a leggyakoribb, mivel leegyszerűsíti a folyamatot, egyetlen paranccsal létrehozza és elindítja a konténert. Ugyanakkor a runc create és a runc start parancsok szétválasztják a konténer létrehozását (a névterek és erőforrások beállítása) és a tényleges folyamat elindítását, ami nagyobb kontrollt biztosít a felhasználónak.

A következő táblázat a legfontosabb runc parancsokat és szerepüket foglalja össze.

Parancs Leírás Életciklus Szakasz Fő Funkció
runc spec Létrehoz egy alapértelmezett config.json fájlt az aktuális könyvtárban. Bundle Előkészítés Deklaratív manifest sablonjának generálása.
runc create Létrehozza a konténer példányt a bundle-ből, és beállítja a névtereket, de nem indítja el a folyamatot. Létrehozva (created) A rendszermag primitívumainak lefoglalása és inicializálása.
runc start Elindítja a config.json-ban definiált folyamatot egy már létrehozott konténerben. Futásban (running) A konténer főfolyamatának végrehajtása.
runc run Egy lépésben létrehozza és elindítja a konténert. Létrehozva -> Futásban A runc create és runc start kombinációja a kényelem érdekében.
runc list Listázza az összes, runc által kezelt konténert, állapotukkal együtt. Ellenőrzés Az aktív, szüneteltetett vagy leállított konténerek állapotának megtekintése.
runc delete Törli a konténert, és felszabadítja a lefoglalt erőforrásokat. Takarítás A konténerhez rendelt erőforrások, mint a névtér és a cgroups, felszabadítása.
runc exec Egy parancsot hajt végre egy már futó konténeren belül. Menedzsment A konténeres környezetbe való belépés, hibakeresés vagy további parancsok végrehajtása.

6. Konténer építése és futtatása runc-kal

A cél egy minimális konténer kézi felépítése és futtatása a runc segítségével, a Docker magas szintű funkcióinak kihagyásával.

6.1. A Labor beállítása és előfeltételei (Ubuntu)

A laborhoz a runc binárisra lesz szükség. Telepítése a hivatalos GitHub kiadási oldalról, vagy az Ubuntu tárolóiból is lehetséges. A fájlrendszer (rootfs) beszerzéséhez ideiglenesen használni fogjuk a Dockert, de fontos megjegyezni, hogy ez a lépés is végrehajtható kézi módszerekkel is.

# Runc telepítése
$ sudo apt-get update
$ sudo apt-get install runc

# Docker telepítése (opcionális, csak a rootfs beszerzéséhez)
$ sudo apt-get install docker.io

6.2. A fájlrendszer (rootfs) előkészítése

Egy OCI bundle elsődleges eleme a konténer gyökér fájlrendszere. A legegyszerűbb módja egy minimalista rootfs beszerzésének, ha exportálunk egy meglévő Docker képet, például a busybox vagy alpine nevűt.

# 1. Hozz létre egy új könyvtárat a konténer-csomagnak
$ mkdir mycontainer
$ cd mycontainer

# 2. Hozz létre egy rootfs alkönyvtárat
$ mkdir rootfs

# 3. Exportáld a busybox Docker képet a rootfs könyvtárba
$ sudo docker pull busybox
$ sudo docker export $(sudo docker create busybox) | sudo tar -C rootfs -xvf -

A rootfs könyvtár mostantól tartalmazza a busybox minimális fájlrendszerét, amely elégséges a konténer futtatásához.

6.3. A config.json fájl generálása és módosítása

Az OCI futtatókörnyezet a config.json fájlban leírtak alapján hozza létre a konténert. A runc egy kényelmes parancsot biztosít egy alap sablon generálásához.

$ sudo runc spec

Ez a parancs létrehoz egy config.json fájlt az aktuális mycontainer könyvtárban. A fájlban el kell végezni a következő módosításokat, hogy az illeszkedjen a létrehozott környezethez:

  1. A root.path beállítása: Győződj meg róla, hogy a root objektumban a path értéke rootfs. Ez a mező relatív utat ad meg, ami a leggyakoribb megközelítés.
  2. A process.args beállítása: Módosítsd a process objektumon belül az args tömböt, hogy a konténer a sh shellt futtassa. A busybox egy minimális shellt, az /bin/sh-t tartalmazza, amelyet a konténer futtatásakor fogunk használni.
  3. A linux.namespaces beállítása: A fájlban lévő linux.namespaces objektum listázza a használandó névtereket. Ellenőrizd, hogy az alapértelmezett listán szerepel-e a pid, network, mount és uts névtér, ami egy teljesen izolált környezetet eredményez.

6.4. A konténer futtatása runc-kal

Most, hogy a bundle elkészült (rootfs és config.json), elindíthatjuk a konténert. A runc run parancs a legegyszerűbb módja ennek, mivel a create és a start funkciókat egyesíti.

$ sudo runc run my-first-container

A konténer elindul, és egy shell promptot kell látnod. Itt a következőket ellenőrizheted:

  • A prompt sh-ra vagy bash-ra változott, jelezve, hogy a konténerben van.
  • Az id parancs futtatásával a felhasználó valószínűleg root-ként fog megjelenni.
  • Az hostname parancs a konténer hostname-ét mutatja.
  • A ps aux parancs a konténerben futó folyamatokat listázza, amelyből látható, hogy az ön által futtatott shell folyamat a PID 1.
  • Az ip addr parancs csak a loopback interfészt mutatja.
  • Az ls -la / parancs a konténer rootfs-ét listázza, amely elkülönül a gazdagép fájlrendszerétől.

6.5. Ellenőrzés és takarítás

A konténer futásának ellenőrzéséhez a runc list parancsot használhatod egy új terminál ablakból. A runc exec parancs lehetővé teszi, hogy egy másik parancsot futtass egy már futó konténeren belül. A konténerből való kilépéshez írd be az exit parancsot a konténer shelljében. A konténer ekkor stopped állapotba kerül, de a rendszermag erőforrásai még lefoglalva vannak.

# Ellenőrzés egy új terminálban
$ sudo runc list

ID                   PID         STATUS      BUNDLE           CREATED                         OWNER
my-first-container   ...         running     /home/user/...  ...                              root

# Parancs végrehajtása a futó konténeren belül
$ sudo runc exec my-first-container ps aux

# A konténer törlése és az erőforrások felszabadítása
$ sudo runc delete my-first-container

A runc delete parancs véglegesen felszabadítja az összes, a konténerhez rendelt rendszermag erőforrást, és eltávolítja a konténerből a runc list kimenetéből is.