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:
Magas Szintű Felhasználói Eszközök: Ezek a parancssori felületek, mint adockervagy apodman, 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.Magas Szintű Futtatókörnyezetek: Ezen démonok, mint acontainerd(a Docker alapértelmezett futtatókörnyezete) vagy aCRI-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.Alacsony Szintű Futtatókörnyezetek: Az olyan eszközök, mint arunc, 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. Ayoukiés acrunhasonlóan OCI-kompatibilis, csereszabatos alternatívák, amelyek megerősítik ezt a moduláris felépítést.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.
- 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
- 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
- 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 -aparanccsal, 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
- 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
- Indítsd el az Nginx konténert a háttérben:
docker run -d --name test-net nginx - Kérdezd le a konténer folyamat-azonosítóját (PID) a hoszton:
PID=$(docker inspect -f '{{.State.Pid}}' test-net) - Használd az
nsenterparancsot 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:
rootfs: A konténer gyökér fájlrendszerét tartalmazó alkönyvtár.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:
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.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.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.