Host OS
1. Bevezetés¶
A Linux kernel folyamatelkülönítési primitívjei — a namespaces és a cgroups — részletesen bemutatják, hogyan nyilvánulnak meg a host operációs rendszer virtuális fájlrendszerein. Ezek a mechanizmusok izolált környezeteket hoznak létre, konfigurációjuk pedig közvetlenül ellenőrizhető és kezelhető a /proc és a /sys/fs/cgroup interfészeken keresztül. A működésük megértése kulcsfontosságú a rendszeradminisztrátorok, biztonsági szakemberek és fejlesztők számára, akiknek árnyaltan kell érteniük a konténerizáció mögöttes mechanikáját.
2. A folyamatelkülönítés alapjai Linuxban¶
2.1. A Namespaces és a Cgroups szerepe¶
A Linuxban a folyamatok elszigetelésének modern megközelítése két alapvető kernel mechanizmusra épül: a namespaces-re és a cgroups-ra. Ez a két primitív közötti munkamegosztás kulcsfontosságú a konténerizáció mögötti koncepció megértéséhez. A namespaces feladata, hogy virtualizálja a rendszererőforrásokat, ezáltal azt az illúziót keltve egy folyamat számára, mintha a globális erőforrások (mint például a folyamat-azonosítók, a hálózati interfészek vagy a csatolási pontok) saját, izolált példányával rendelkezne. Más szóval, a namespaces azt szabályozza, hogy egy folyamat mit láthat a rendszerből.
Ezzel szemben a cgroups (control groups) a folyamatok erőforrás-felhasználásának felügyeletére és korlátozására szolgál. Hierarchikus csoportokba szervezi a folyamatokat, és lehetőséget biztosít arra, hogy a kernel szintjén szabályozzák, monitorozzák és korlátozzák a CPU, memória, I/O és egyéb erőforrásokhoz való hozzáférést. A cgroups tehát azt határozza meg, hogy egy folyamat mit használhat.4 A konténer futtatókörnyezetek, mint a Docker vagy a runc, nem különálló technológiák, hanem a kernel beépített namespaces és cgroups képességeit használják fel. Amikor egy felhasználó kiadja a docker run parancsot, a futtatókörnyezet a kernel rendszerhívásait (pl. unshare, clone) használja egy új névterekkel rendelkező folyamat létrehozására, majd a cgroups fájlrendszeren keresztül konfigurálja annak erőforrás-korlátait.
2.2. A Linux virtuális fájlrendszerei¶
A namespaces és a cgroups működésének megértéséhez elengedhetetlen a Linux virtuális fájlrendszereinek ismerete. A /proc és a /sys/fs nem fizikai lemezen lévő fájlokat tartalmaznak, hanem a kernel belső adatszerkezeteinek dinamikus leképzései. Ezek a fájlrendszerek valós idejű betekintést nyújtanak a kernel állapotába, és API-ként szolgálnak a folyamatok, hardverek és az említett elszigetelési mechanizmusok kezeléséhez.
Fontos megkülönböztetni a virtuális fájlokat a hagyományos fájloktól. Például a /proc/[pid]/cgroup fájl megnyitása nem egy statikus fájl tartalmát olvassa be, hanem a kernel egy API-hívásának eredménye. Emiatt a /proc alkönyvtárai és fájljai bármikor eltűnhetnek, ha a hozzájuk tartozó folyamat leáll. Ez a dinamikus jelleg jelentős következményekkel jár a rendszermonitorozó és biztonsági eszközök fejlesztése során a robusztus alkalmazásoknak figyelembe kell venniük az úgynevezett "race condition"-öket (versenyhelyzeteket), és meg kell tudniuk birkózni a fájlok váratlan eltűnésével. Egy lehetséges megoldás a teljes fájl egyetlen művelettel történő beolvasása egy pufferbe ahelyett, hogy soronként olvasnák.
3. A Linux Namespaces¶
3.1. A /proc/[pid]/ns/ interfész¶
A /proc/[pid]/ns/ alkönyvtár a fő interfész a namespaces kezeléséhez a host operációs rendszeren. Minden futó folyamat rendelkezik egy ilyen alkönyvtárral, amely szimbolikus linkeket tartalmaz a folyamat által használt névterek mindegyikére. Ezek a szimbolikus linkek a namespaces típusát és azonosítóját mutatják meg. Például a /proc/[pid]/ns/uts egy szimbolikus link a folyamat UTS (UNIX Time-sharing) névteréhez, amely elszigeteli a hostnevet és a domainnevet. Hasonlóképpen, a /proc/[pid]/ns/cgroup a cgroup névteret reprezentálja, ami a cgroup hierarchia virtualizált nézetét biztosítja. Ez az interfész a kulcs a folyamatok aktuális névtereihez, és az unshare rendszerhívás használja új névterek létrehozására, míg a setns lehetővé teszi a folyamatok csatlakozását egy már létezőhöz.
3.2. Inode számok mint a namespace azonosítók¶
A /proc/[pid]/ns/ szimbolikus linkek mögött rejlő technikai működésben az inode számok játsszák a fő szerepet. A kernel úgy van kialakítva, hogy ha két folyamat ugyanabban a namespace-ben található, akkor a /proc/[pid]/ns/ könyvtáraikban lévő megfelelő szimbolikus linkek device és inode-számai megegyeznek. Ez a tulajdonság egyedi és megbízható módszert kínál annak ellenőrzésére, hogy két folyamat osztozik-e egy adott névtéren. Ez a technika a stat rendszerhívással is ellenőrizhető a st_dev és st_ino mezők segítségével. A /proc linkek maguk is tartalmazzák a névtér típusát és az inode számot. Egy egyszerű readlink parancs a /proc/$$/ns/uts fájlon például uts:[4026531838] kimenetet adhat, ahol a 4026531838 az adott névtér egyedi inode azonosítója.
A névterek a futásidő befejeztével automatikusan megszűnnek, amikor az utolsó folyamat is leáll. A kernel azonban lehetőséget biztosít azok fennmaradására, még akkor is, ha nincsenek aktív folyamatok bennük. Ezt a funkciót egy fájl leíró megnyitásával vagy a /proc/[pid]/ns/\* fájl bind mountolásával lehet elérni egy másik fájlrendszerbeli helyre. Ez a mechanizmus magyarázatot ad arra, hogy az olyan eszközök, mint az ip netns hogyan képesek létrehozni állandó, megnevezett hálózati névtereket (/var/run/netns/router). A parancs létrehozza a névteret, majd egy bind mount-ot hoz létre a /proc/self/ns/net fájlról a /var/run/netns/router útvonalra. Amíg a bind mount létezik, a névtér is fennmarad.
3.3. A Legfontosabb Namespaces részletes nemutatása és fájlrendszerbeli lenyomata¶
A namespaces különböző típusai a rendszererőforrások eltérő aspektusait izolálják. A leggyakrabban használtak és a fájlrendszerbeli lenyomataik a következők:
- PID (Process ID) Namespace (/proc/[pid]/ns/pid): A PID névtér elszigeteli a folyamat azonosítók halmazát, lehetővé téve, hogy a konténeren belül a folyamatoknak független PID-jeik legyenek, amelyek nem láthatók a host rendszerről, vagy más PID névterekből. A konténeren belüli első folyamat megkapja az 1-es PID-t, ami a host rendszer init folyamatához hasonlóan különleges kezelésben részesül.
- Mount (MNT) Namespace (/proc/[pid]/ns/mnt): Ez a névtér izolálja a fájlrendszer csatolási pontjait, így a konténereknek a hostétól elkülönített saját fájlrendszer nézete van. A mount
--make-sharedparanccsal azonban megoszthatóak a csatolási pontok a névterek között. - Network (NET) Namespace (/proc/[pid]/ns/net): Ez a névtér virtualizálja a teljes hálózati vermet, ideértve a hálózati interfészeket, az IP címeket, a tűzfal szabályokat és a routing táblákat. Minden konténer saját, izolált hálózati környezetet kap, ami megakadályozza, hogy az egymástól elkülönített konténerek hálózati forgalma interferáljon.
- Cgroup Namespace (/proc/[pid]/ns/cgroup): Ez egy létfontosságú, de sokszor félreértett névtér. Nem maga a cgroup erőforráscsoport, hanem a cgroup hierarchia virtualizált nézete. Lehetővé teszi, hogy egy konténer a cgroup fa egy alsóbb szintű ágát lássa saját gyökércsoportjának. A tényleges cgroups a
/sys/fs/cgroupfájlrendszerben található.
Az alábbi táblázat összefoglalja a leggyakoribb Linux névterek host operációs rendszeren belüli megjelenését.
| Namespace Típus | Cél | /proc/[pid]/ns/ Fájlútvonal | Leírás |
|---|---|---|---|
| PID | Folyamat azonosítók izolálása | /proc/[pid]/ns/pid | Minden névtérnek saját 1-es PID-je lehet. |
| Mount | Fájlrendszer csatolási pontok izolálása | /proc/[pid]/ns/mnt | Egy folyamat csak a saját csatolási hierarchiáját látja. |
| Network | A hálózati verem izolálása | /proc/[pid]/ns/net | Saját IP cím, routing tábla és interfészek. |
| Cgroup | A cgroup hierarchia virtualizált nézete | /proc/[pid]/ns/cgroup | A folyamat azt hiszi, hogy a cgroup fa gyökerében van. |
| User | Felhasználói és csoportazonosítók izolálása | /proc/[pid]/ns/user | Egy root felhasználó a konténerben a hoston egy nem root felhasználóhoz lehet térképezve. |
| UTS | Hostnév és domainnév izolálása | /proc/[pid]/ns/uts | A konténer saját hostnevet kaphat a hosttól függetlenül. |
| IPC | Inter-process communication erőforrások izolálása | /proc/[pid]/ns/ipc | Izolálja a SysV IPC és a POSIX üzenetsor erőforrásait. |
4. A Linux Cgroups¶
4.1. A /sys/fs/cgroup fájlrendszer¶
Míg a namespaces a folyamatok látóterét korlátozza, a cgroups a felhasználható erőforrások mennyiségét szabályozza. A cgroups funkcionalitása hierarchikus fájlrendszerként van a kernelbe beépítve, amely általában a /sys/fs/cgroup alatt van csatolva. Ez a könyvtár a cgroup gyökérkontroll-csoportként is ismert, és itt találhatók a fő beállítások, valamint a cgroup-fa további ágai.
4.2. Cgroup V1 vs. V2¶
A cgroups két fő verziója létezik, amelyek alapvető különbségeket mutatnak a működésükben és a fájlrendszerbeli elrendezésükben.
- Cgroup V1: A Több-hierarchiás Modell: A V1 verzióban minden egyes erőforrás-típushoz (ún. controller-hez) külön hierarchia tartozik. Például a CPU és a memória controller-ek saját csatolási pontokkal rendelkeznek, mint a
/sys/fs/cgroup/cpués a/sys/fs/cgroup/memory. Ez a megközelítés lehetővé tette, hogy egy folyamat különböző cgroup-okhoz tartozzon a különböző hierarchiákban, ami komplexitást okozott a kezelésben és a felügyeletben. - Cgroup V2: Az Egységesített Hierarchiás Modell: A V2 a cgroups újragondolt, modern verziója, amely a V1 által okozott problémákra ad választ. A V2 kizárólag egyetlen, egységes hierarchiát használ, amelybe minden controller be van kötve. A teljes hierarchia a
/sys/fs/cgroupalatt található, és a különböző controller-ek funkcionalitását a cgroup.subtree_control fájlban lehet engedélyezni. Ez az egységesítés tisztább és konzisztensebb API-t biztosít, megkönnyítve az erőforrás-felügyeletet.
Ez a két verzió nem csupán egy apró változtatás, hanem egy jelentős architekturális váltás a kernelben. Míg a V1 egy gyűjteménye volt független alrendszereknek, a V2 integrált, egységes megközelítést képvisel. Ez a váltás magyarázza a fájlnevek változásait is; például a V1 memory.limit_in_bytes fájlja a V2-ben memory.max-ra változott, ami a közös, következetes elnevezési konvenció része.
| Funkció | Cgroup V1 | Cgroup V2 |
|---|---|---|
| Hierarchia | Több-hierarchiás (egy controller = egy fa) | Egységes, egyetlen fa |
| Feladat-hozzárendelés | tasks, cgroup.procs | cgroup.procs |
| Memórialimit fájl | /sys/fs/cgroup/memory/memory.limit_in_bytes | /sys/fs/cgroup/memory.max (a gyökérben) |
| Processzorlimit fájl | /sys/fs/cgroup/cpu/cpu.cfs_quota_us | /sys/fs/cgroup/cpu.max |
| Fájlrendszerbeli útvonal | / | / |
4.3. A Cgroup V2¶
A V2 architektúrájában a cgroup hierarchia könyvtárakból áll, ahol minden könyvtár egy cgroup. A V2-ben minden cgroupnak van néhány alapvető fájlja, amelyek a magműveleteket szabályozzák:
cgroup.procs: Ez a fájl a cgroup tagjait sorolja fel a PID-jeik alapján. Egy folyamat hozzárendeléséhez ehhez a cgroup-hoz, egyszerűen bele kell írni a PID-jét ebbe a fájlba. Fontos különbség a V1-hez képest, hogy a V2-ben egy folyamat minden szála ugyanahhoz a cgroup-hoz tartozik.cgroup.controllers: Egy csak olvasható fájl, amely felsorolja a cgroup számára elérhető controllereket. Ez a tartalom megegyezik a szülő cgroupcgroup.subtree_controlfájljának tartalmával.cgroup.subtree_control: Ez a fájl a cgroup közvetlen gyermekeire vonatkozó kontrollereket listázza. Egy controller engedélyezéséhez a gyermek cgroup-ok számára, a szülő cgroup cgroup.subtree_control fájljába kell beleírni a controller nevét egy + előjellel.
Controller Interfész Fájlok Miután egy controller engedélyezésre került a cgroup.subtree_control fájlban, a hozzá tartozó interfészfájlok megjelennek a gyermek cgroup könyvtárában. Ezek a fájlok a specifikus erőforrás-korlátok beállítására szolgálnak:
CPU Controller: A V2 CPU controller acpu.maxfájlon keresztül szabályozza a kvótát és a periódust ("quota period" formátumban), acpu.weightfájlon keresztül pedig a relatív CPU részesedést. Például, a 1.5 CPU-nak megfelelő kvóta beállításához a docker run--cpus="1.5"paranccsal, a Docker acpu.maxfájlba írja a megfelelő quota és period értékeket.Memory Controller: A memória controllerben a V2 az egységes elnevezési konvenciót követi, és a V1memory.limit_in_bytesfájlja helyett amemory.maxfájlt használja a kemény memórialimit beállítására. Amemory.highfájl a puha limit beállítására szolgál.
Az alábbi táblázat a leggyakoribb Cgroup v2 controller-fájlokat és funkcióikat foglalja össze.
| Controller | Fájlnév | Funkció |
|---|---|---|
| cpu | cpu.max | Beállítja a maximálisan felhasználható CPU kvótát a megadott perióduson belül (quota és period mikroszekundumban). |
| cpu | cpu.weight | Beállítja a cgroup relatív részesedését a rendelkezésre álló CPU-ból (1 és 10000 közötti érték). |
| memory | memory.max | Kemény memórialimitet állít be a cgroup számára (bájtokban). |
| memory | memory.high | Puha memórialimitet állít be, ami a kernel kényszeríthet (bájtokban). |
| pids | pids.max | A cgroup-hoz tartozó folyamatok maximális számát korlátozza. |
4.4. Manuális Cgroup létrehozása és kezelése a hoston¶
A konténer futtatókörnyezetek automatizálják a cgroups kezelését, de a mélyebb megértés érdekében hasznos lehet a manuális eljárás bemutatása. A következő lépésekkel manuálisan is létrehozható és konfigurálható egy cgroup egy folyamat számára:
Hozza létre a cgroup könyvtárat: Egy új cgroup létrehozása a legegyszerűbb, a mkdir paranccsal történik a/sys/fs/cgrouphierarchiában.$ mkdir /sys/fs/cgroup/mygroupEngedélyezze a controllertA szülő cgroup cgroup.subtree_control fájljába kell írni a használni kívánt controller nevét.$ echo "+cpu" >> /sys/fs/cgroup/cgroup.subtree_controlÁllítsa be az erőforrás-limiteket: A controller interfészfájljába (cpu.max) írva konfigurálhatók a korlátok. Például, a CPU használat 50%-ra korlátozásához.$ echo "50000 100000" > /sys/fs/cgroup/mygroup/cpu.maxAdja hozzá a folyamatot: Végül, a folyamat PID-jét a cgroup.procs fájlba írva rendelhető hozzá a cgroup-hoz.$ echo $$ > /sys/fs/cgroup/mygroup/cgroup.procs
Ez a manuális eljárás pontosan megmutatja a konténer futtatókörnyezetek mögötti logikát, és a docker run parancsok alacsony szintű megfelelőjét.
5. A Konténer futtatókörnyezet absztrakciója¶
5.1. A docker run parancs kibontása¶
Egy végfelhasználó számára a docker run parancs egyetlen, egyszerű műveletnek tűnik, amely elindít egy konténert. A valóságban ez a parancs egy összetett folyamat, amely a namespaces és cgroups primitívjeit használja fel a kernelben. A Docker motor a runc nevű alacsony szintű konténer futtatókörnyezetet hívja meg, amely az OCI (Open Container Initiative) specifikáció szerint végzi a konténer létrehozását és elindítását. Ez a folyamat magában foglalja a következő lépéseket:
- A futtatókörnyezet létrehozza a konténer számára a szükséges namespaces-eket (PID, MNT, NET, UTS, IPC, cgroup) a kernel clone vagy unshare rendszerhívások segítségével.
- A futtatókörnyezet meghatározza a konténer erőforrás-korlátait a konfigurációs fájljai (például config.json) alapján.
- A futtatókörnyezet ezután a
/sys/fs/cgrouphierarchiában létrehozza a konténernek megfelelő könyvtárat, és beírja a korlátozó értékeket a megfelelő controller-fájlokba. - Végül, a futtatókörnyezet elindítja a konténerben lévő fő folyamatot.
5.2. Egy konténer lenyomatának nyomon követése a host fájlrendszeren¶
A szakemberek számára létfontosságú, hogy meg tudják találni és ellenőrizni a konténerhez tartozó fájlrendszerbeli lenyomatot. Ezt a következő lépésekkel lehet megtenni:
A Konténer PID-jének Megszerzése: A konténer futás közbeni PID-jének megállapításához használható a docker inspect parancs.$ docker inspect -f '{{.State.Pid}}'A Namespaces Ellenőrzése: A kapott PID segítségével meg lehet tekinteni a konténer névtereit a/proc/[pid]/ns/könyvtárban. Itt a szimbolikus linkek inode-számai egyértelműen azonosítják a használt névtereket.A Cgroup Elérési Útvonalának Megtalálása: A konténer cgroup tagságát a/proc/[pid]/cgroupfájlban lehet megtekinteni. Ez a fájl megadja a cgroup hierarchián belüli relatív elérési útvonalat.$ cat /proc/[pid]/cgroupA kimenet a0::/docker/longidformátumban lesz a Cgroup v2 esetén, a teljes elérési útvonal a/sys/fs/cgroup/docker/longid
A futtatókörnyezet absztrakciós szintje mögött a felhasználó által beállított egyszerű paraméterek közvetlenül leképeződnek a cgroup fájlrendszerre. Például a docker run --cpus="1.5" parancsban a Docker motor a háttérben kiszámolja a megfelelő kvóta (150000) és periódus (100000) értékeket, majd a cpu.max fájlba írja azokat. Ez a közvetlen kapcsolat mutatja, hogy a high-level parancsok hogyan alakulnak át alacsony-szintű kernel primitív beállításokká, amelyek valós időben szabályozzák a folyamatokat.