Images
1. Bevezetés¶
A Docker image nem csupán egy fájl, hanem egy sorban egymásra épülő rétegekből álló, blueprint (tervrajz), amelynek architektúrája döntő fontosságú a Docker hatékonysága és hordozhatósága szempontjából. A Docker központi eleme a Docker-kép (Docker image), amely egy írásvédett sablon egy konténer létrehozásához. A képek rétegekből épülnek fel, ahol minden réteg a fájlrendszer egy-egy változását (hozzáadást, törlést vagy módosítást) tartalmazza. Ezek a rétegek egymásra épülnek, és egy unió fájlrendszer (Union File System) segítségével egyetlen egységes nézetet alkotnak, ami a végső Docker-képet eredményezi.
2. Az image rétegek¶
A Docker image egy read-only (csak olvasható) template, amelyből a konténereket létrehozzák. Minden Dockerfile utasítás (FROM, RUN, COPY, ADD) új, csak olvasható réteget ad a képhez. Ezek a rétegek egymásra épülnek, mint a lencsék a távcsőben, és mindegyik csak a legutóbbi változtatásokat tartalmazza. Ez a rétegelt struktúra egy mélyebb, a Git-re hasonlító logikán alapszik. Minden egyes réteg egy "diff" (különbség) az előző réteghez képest. Ezek a rétegek a létrehozás után megváltoztathatatlanok, ami biztosítja a képek konzisztenciáját és megbízhatóságát.
Ez a megközelítés rendkívül erőteljes előnyöket biztosít:
Gyorsabb Buildelés: A rétegek sorrendje kritikus fontosságú. A Docker a Dockerfile utasításait felülről lefelé dolgozza fel, és a build gyorsítására beépített gyorsítótárat (cache) használ. Ha egy utasítás megváltozik, a gyorsítótára és az összes utána következő réteg is érvénytelen lesz, így újra kell épülniük. Ha viszont csak a későbbi utasítások változnak, a korábbi rétegek a cache-ből töltődnek be, ami jelentősen lerövidíti a buildelési időt.Helytakarékosság: Ha több image osztozik azonos rétegeken (például egy közös alap image-en), akkor a Docker csak egyszer tárolja ezeket a rétegeket a lemezen. Ez óriási lemezterület-megtakarítást eredményezhet, különösen nagy méretű rendszerek esetén.Inkrementális Frissítések: Egy registry-ből történő letöltéskor (pull) vagy feltöltéskor (push) csak az új vagy megváltozott rétegeket kell továbbítani, ami minimálisra csökkenti a hálózati forgalmat és felgyorsítja a telepítéseket.
A build gyorsítótár használatának bemutatására egy Python alkalmazás példáját vizsgáljuk meg.
requirements.txt
flask
app.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello, Docker!'
if __name__ == '__main__':
app.run(host='0.0.0.0')
Dockerfile
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python3", "app.py"]
A rétegek sorrendjének rendkívüli jelentősége van a buildelési hatékonyság szempontjából. A build gyorsítótárazásának kihasználásával jelentősen felgyorsítható az iteratív fejlesztés során az újraépítési folyamat. A Docker ugyanis minden utasításnak a tartalmát ellenőrzi a gyorsítótárában. Ha az utasítás vagy annak kontextusa (például egy fájl, amelyet a COPY parancs másol) nem változott, a Docker a gyorsítótárból tölti be a réteget ahelyett, hogy újraépítené.
A Dockerfile utasításainak stratégiai sorrendje lehetővé teszi, hogy az alkalmazás kódjának változásai ne érvénytelenítsék a függőségeket telepítő rétegeket. A fenti példában a COPY requirements.txt. utasítás a COPY . . előtt helyezkedik el. Ha a fejlesztő csak az app.py fájlon változtat, a COPY requirements.txt réteg gyorsítótára érvényes marad, és a függőségek telepítése (RUN pip install) sem igényel újraépítést. Ezzel szemben, ha a requirements.txt is megváltozna, a Dockernek mindkét réteget újra kellene építenie. Ez a módszer jelentős időt takarít meg a buildelési ciklusban, és demonstrálja, hogy a rétegelési elv nem csupán az adatok tárolásának módja, hanem a buildelési folyamat optimalizálásának kulcsfontosságú eszköze is.
2.1 Alapvető Dockerfile-utasítások a gyakorlatban¶
A Dockerfile egy egyszerű szöveges dokumentum, amely a kép létrehozásához szükséges összes utasítást tartalmazza.4 Az utasítások minden sora egy parancs, amelynek végrehajtásával egy új réteg jön létre.
FROM: Ez az utasítás határozza meg a buildeléshez használt alapképet. Ez általában a Dockerfile első utasítása. Az alapképek lehetnek minimalista operációs rendszerek (pl. amazonlinux:latest) vagy előre konfigurált, alkalmazás-specifikus képek (pl. python:3.9-slim).RUN: Parancsokat futtat a buildelés során. Ezekkel lehet szoftvereket telepíteni, fájlokat létrehozni vagy a környezetet konfigurálni.4 Az utasításnak két formája van: a shell forma (pl. RUN apt-get update) és az exec forma (pl. RUN ["apt-get", "update"]). Az exec formátum lehetővé teszi a parancsok explicit futtatását, ami néha precízebb viselkedést eredményez. Az utasítások kombinálásával (RUN apt-get update && apt-get install -y python3) csökkenthető a rétegek száma, ami kisebb képméretet eredményez.COPY és ADD: Fájlokat másol a build kontextusból a képbe. A COPY egyszerű másolásra szolgál, míg az ADD további funkciókkal is rendelkezik, például tömörített fájlok automatikus kibontásával vagy URL-ekről való letöltéssel. Általánosságban elmondható, hogy a COPY használata javasolt, mivel explicit és kiszámítható, kivéve, ha az ADD speciális funkcióira van szükség.CMD és ENTRYPOINT: Meghatározzák, hogy milyen parancsot kell végrehajtani, amikor a konténer elindul. Ezek az utasítások nem hoznak létre új rétegeket. A CMD állítja be a futtatandó alapértelmezett parancsot, míg az ENTRYPOINT konfigurálja a konténert, hogy végrehajthatóként fusson.
2.2. A docker history parancs¶
A docker history parancs egy alapvető eszköz, amely bemutatja egy Docker-kép rétegeinek történetét. A parancs kimenete részletes információt nyújt minden rétegről, beleértve a létrehozás időpontját, a méretét és a parancsot, amely létrehozta. Használd a docker history parancsot a korábban létrehozott my-python-app:1.0 kép ellenőrzésére:
docker history my-python-app:1.0
Az alap parancs kimenete mutatja a rétegek rövidített azonosítóit és a parancsokat. A teljes parancs megjelenítéséhez használja a --no-trunc flag-et:
docker history --no-trunc my-python-app:1.0
Az auditálásra és optimalizálásra a --format flag használata a legelőnyösebb, mivel lehetővé teszi, hogy csak a releváns információkat jelenítsük meg, például a rétegek méretét és a létrehozó parancsot.
docker history --format "Méret: {{.Size}}, Létrehozó parancs: {{.CreatedBy}}" my-python-app:1.0
A docker history parancs kritikus visszajelzést biztosít a kép optimalizálásához. Azáltal, hogy megjeleníti az egyes rétegek méretét, lehetővé teszi a fejlesztők számára, hogy azonosítsák a legnagyobb rétegeket és optimalizálják a Dockerfile-t a méret csökkentése érdekében. Például, ha egy RUN parancs jelentősen megnöveli a kép méretét, a parancs egyesíthető más, kapcsolódó parancsokkal, hogy egyetlen rétegbe kerüljön. Ez a folyamat nemcsak a végleges kép méretét csökkenti, hanem felgyorsítja a letöltési időt is.
Ezen túlmenően, a parancs az audithoz is hasznos. A docker history segít felderíteni, ha a kép buildelése során felesleges vagy potenciálisan rosszindulatú parancsok (pl. curl vagy wget) futottak le. Ez a rétegről-rétegre történő átláthatóság a biztonsági vizsgálatok alapja, mivel világosan megmutatja, mi került a képbe és miért.
3. Hatékonyság és biztonság¶
A konténerek sikeres használata a produkcióban nemcsak a funkcionalitás biztosítását jelenti, hanem az optimalizálást és a biztonságot is magában foglalja. Az alábbiakban bemutatjuk, hogyan lehet karcsú, gyors és biztonságos Docker-képeket készíteni.
3.1 A többlépcsős buildek ereje¶
A többlépcsős buildek (multi-stage builds) lehetővé teszik a Dockerfile felosztását több szakaszra, mindegyiknek saját alapképe lehet. Ez a módszer különösen előnyös fordított nyelvek (pl. Go, Java, C++) esetében, mivel elválasztja a buildelési környezetet a futásidejű környezettől. A végső kép csak a futtatáshoz szükséges futtatható binárist és a minimális függőségeket tartalmazza, a buildelési eszközöket (fordítók, package managerek) pedig eldobja.
Hozz létre egy main.go nevű fájlt a következő tartalommal:
package main
import "fmt"
func main() {
fmt.Println("Hello, Docker\!")
}
Ezután készíts egy többlépcsős Dockerfile-t:
# 1. szakasz: a buildelési környezet
FROM golang:1.24 AS build-stage
WORKDIR /src
COPY main.go.
RUN go build -o /bin/hello-app main.go
# 2. szakasz: a végső, minimalista kép
FROM scratch
COPY --from=build-stage /bin/hello-app /bin/hello-app
CMD ["/bin/hello-app"]
Futtasd a build parancsot:
docker build -t hello-go:1.0.
A többlépcsős buildek használata nem csak a kép méretét csökkenti, hanem alapvetően alkalmazza a "legkisebb jogosultság elvét" (Principle of Least Privilege) a konténerképekre. Azzal, hogy eldobja a buildelési szakaszt, amely tartalmazza a fordítót, a buildelési rendszereket és az egyéb, futásidejű környezetben szükségtelen eszközöket, a végső képben jelentősen csökken a lehetséges támadási felület.
Egy monolitikus Dockerfile esetén a végső képbe bekerülne a teljes Go SDK a fordítóval és az összes könyvtárral együtt. Bármelyik komponensben lévő biztonsági rés kihasználható lenne, még akkor is, ha az nem közvetlenül az alkalmazás része. A scratch alapú többlépcsős builddel viszont a végső kép mindössze a binárist tartalmazza. Nincsenek felesleges parancssori eszközök, amelyekkel egy támadó jogosultságokat emelhetne vagy a konténeren belül mozoghatna. Ezáltal a megközelítés a biztonsági kockázatot a lehető legalacsonyabbra szorítja.
3.2 A .dockerignore fájl¶
A docker build parancs futtatásakor a Docker kliens alapértelmezés szerint a teljes build kontextust (a jelenlegi könyvtárban található összes fájlt) elküldi a Docker démonnak egy .tar archívumban. A .dockerignore fájl hasonlóan működik, mint egy .gitignore fájl, megadja, mely fájlokat és könyvtárakat kell kizárni ebből a kontextusból.
A .dockerignore fájl elsődleges előnye nem csak a kisebb végső kép, hanem a gyorsabb buildelési folyamat. Azzal, hogy a build kontextusból kizárja a szükségtelen fájlokat és könyvtárakat (pl. node_modules, .git vagy logok), jelentősen csökken az archívum mérete, amit el kell küldeni a démonnak, különösen, ha a démon egy távoli gépen fut.
Hozz létre egy nodeapp nevű könyvtárat, és helyezd el benne a következő fájlokat. A valóságban a node_modules és a .git könyvtárak az npm install és a git init parancsok hatására jönnek létre.
- package.json, app.js
- Egy node_modules nevű, nagy méretű könyvtár
- Egy .git nevű rejtett könyvtár
- Egy .env nevű környezeti fájl
Hozz létre egy .dockerignore nevű fájlt, és tedd bele a következőket:
node_modules
.git
.env
logs
*.log
Futtasd a build parancsot:
docker build -t my-node-app:1.0 .
A .dockerignore fájl működésének megértése kulcsfontosságú a Docker optimalizálásában. A Docker démonhoz küldött build kontextus mérete befolyásolja a buildelési időt. Egy nagy, figyelmen kívül hagyott könyvtár (pl. node_modules) miatt a build kontextus hatalmasra nőhet. A .dockerignore azonban a Docker kliensnél, a .tar archívum létrehozása előtt lép életbe, megakadályozva, hogy a felesleges fájlok egyáltalán eljussanak a démonhoz. Ezáltal optimalizálja a teljes buildelési láncot, nem csak a végső képet. Emellett a biztonsági szempontok is kiemelkedők, mivel megakadályozza, hogy érzékeny információk, mint például a .env fájlok vagy a Git előzmények, bekerüljenek a végső képbe.
3.3 Biztonsági növelés nem root felhasználókkal¶
A konténerbiztonság egyik alapvető elve, hogy a konténerben lévő alkalmazásoknak a lehető legkevesebb jogosultsággal kell futniuk. Alapértelmezés szerint a konténerek root felhasználóként futnak, ami potenciális biztonsági kockázatot jelent, ha egy támadó ki tud lépni a konténerből. Az USER utasítás a Dockerfile-ban lehetővé teszi, hogy a konténer futtatásához egy nem root felhasználót adjunk meg.
Módosítsd a Dockerfile-t egy dedikált felhasználó létrehozásához:
FROM ubuntu:22.04
# Hozzon létre egy rendszerszintű csoportot és felhasználót
RUN groupadd --system appuser && useradd --system -g appuser appuser
WORKDIR /app
COPY . .
# A fájlok tulajdonjogának átadása a létrehozott felhasználónak
RUN chown -R appuser:appuser /app
# Átváltás a nem-root felhasználóra
USER appuser
CMD ["./my-app"]
A fenti Dockerfile példája egyértelműen mutatja, hogyan lehet elválasztani a jogosultságokat a buildelési és a futásidejű folyamatokban. A RUN parancsok még rootként futnak (például a felhasználó és a csoport létrehozásához), de a USER appuser parancs után a konténer a már korlátozott jogosultsággal rendelkező felhasználóként indul el. A docker run --rm -it my-app-image whoami paranccsal ellenőrizhető a futásidejű felhasználó neve.
A konténer felhasználójának biztonsági korlátozása csak az első lépés. Különbséget kell tenni a konténeren belüli felhasználó és a Docker démont futtató felhasználó között. A Docker démon hagyományosan root jogosultságokkal fut, ami egy önálló biztonsági kockázatot jelent a hoszton. A Docker rootless módja megoldja ezt a problémát azáltal, hogy lehetővé teszi a démon futtatását nem root felhasználóként egy felhasználói névtéren belül, elszigetelve mind a konténert, mind a démont a hoszt root jogosultságaitól. Ennek a kettős megközelítésnek az alkalmazása – a nem root konténerfolyamatok és a rootless démon kombinációja – a legbiztosabb védelmet nyújtja a potenciális támadások ellen.
4. Összegzés, optimalizálás és ajánlások¶
A felhasználó egy szilárd alapot kap a Dockerben rejlő teljes potenciál kiaknázásához. Az alábbi táblázat összefoglalja az egy- és többlépcsős buildek közötti különbségeket, majd a végső ajánlások listája segítséget nyújt egy hatékony Docker munkafolyamat kialakításához.
4.1 A buildelési stratégiák összehasonlító elemzése¶
A kép mérete és biztonsága kulcsfontosságú a produkciós környezetekben. A többlépcsős buildek a legfontosabb eszközök az optimalizált, karcsú képek létrehozásához.
| Mutató | Egy-lépcsős build | Többlépcsős build |
|---|---|---|
| Végleges kép mérete | Nagy, mivel a buildelési eszközöket és függőségeket is tartalmazza | Kicsi, mivel csak a futtatáshoz szükséges futtatható binárist tartalmazza |
| Build idő | Hosszabb az első buildnél, de a gyorsítótárazás miatt gyorsabb az iteratív buildnél, ha a függőségek nem változnak. | Hosszabb az első buildnél, de a futásidejű szakaszhoz gyorsabb az újraépítés. |
| Biztonsági profil | Nagyobb támadási felület, mivel tartalmazza a buildelési eszközöket és a szükségtelen fájlokat.10 | Minimális támadási felület, a szükségtelen eszközök kizárása miatt.11 |
| Rétegek száma | Magas lehet, mivel minden RUN parancs új réteget hoz létre. | Kevesebb, mivel csak a futásidejű szakasz rétegei maradnak meg a végső képben. |
| Felhasználási esetek | Egyszerű szkriptek, nem fordított nyelvek (pl. Python, Node.js), vagy gyors fejlesztési és tesztelési célok. | Fordított nyelvek (Go, Java), CI/CD munkafolyamatok, produkciós telepítések.10 |
4.2 Végső ajánlások egy robusztus Docker munkafolyamathoz¶
Egy hatékony és biztonságos Docker munkafolyamat kialakításához az alábbiak beépítése javasolt a napi gyakorlatba:
Minimalista alapképek használata: Válassz hivatalos, karcsú (slim) vagy Alpine-alapú képeket az alkalmazásokhoz. A kisebb alapképek nemcsak a végleges kép méretét csökkentik, hanem a potenciális biztonsági sebezhetőségeket is minimalizálják.Többlépcsős buildek bevezetése: Mindig használj többlépcsős buildeket a buildelési és a futásidejű függőségek szétválasztására, különösen fordított nyelvek esetén. Ez drasztikusan csökkenti a kép méretét és növeli a biztonságot.A .dockerignore fájl következetes használata: Hozd létre .dockerignore fájlt a build kontextusból kizárandó felesleges vagy érzékeny fájlok megadására. Ez felgyorsítja a buildelési folyamatot és megelőzi az adatbiztonsági rések kialakulását.Nem-root felhasználók használata: Módosítsd a Dockerfile-t úgy, hogy az alkalmazások ne root felhasználóként, hanem egy dedikált, korlátozott jogosultságú felhasználóként fussanak. Ez egy alapvető biztonsági intézkedés a privilégiumok emelésének megakadályozására.A build gyorsítótár optimalizálása: Helyezd a Dockerfile utasításait a legritkábban változóktól a leggyakrabban változók felé haladva. Így a gyakori kódmódosítások sem érvénytelenítik a függőségeket telepítő rétegeket, ezzel felgyorsítva az újraépítést.A verziók rögzítése: Mindig rögzítsd az alapképek és a függőségek verzióit a Dockerfile-ban (FROM python:3.9-slim, RUN pip install flask==2.0.0). Ez biztosítja a reprodukálhatóságot, és elkerüli a nem kívánt változásokat a frissítések során.
5. Az Union filesystem¶
Az Union Filesystem egy Linux kernel szolgáltatás, amely lehetővé teszi több, egymás felett lévő könyvtár vagy fájlrendszer – úgynevezett ágak (branches) – transzparens rétegzését, és ezekből egyetlen, koherens virtuális nézet kialakítását. Ezt a folyamatot gyakran union mount-nak nevezik. A technológia lényege, hogy a különböző forrásokból származó tartalmak úgy jelenjenek meg, mintha egyetlen fizikai helyen lennének tárolva: ha azonos útvonalak léteznek több rétegben, a legfelső réteg tartalma élvez prioritást az alatta lévőkkel szemben.
A Docker ezt a mechanizmust használja a konténerek fájlrendszerének felépítéséhez. Az egyesítő fájlrendszerek alapját a rétegek jól meghatározott hierarchiája képezi. Az alapértelmezett overlay2(OverlayFS) tárolómeghajtó az Union Filesystem egy implementációja:
lowerdir (alsó réteg): Ez az alap, írásvédett könyvtár. A virtuális nézetben a fájlok innen származnak, hacsak nincs egy dominánsabb réteg, amely elfedi őket. Egy union mount több lowerdir-t is tartalmazhat, amelyek hierarchikusan egymásra épülnek, és a sorrendben utolsónak megadott réteg élvez prioritást. A Docker esetében a képek (images) összes írásvédett rétege a lowerdir-ben található.upperdir (felső réteg): Ez az egyetlen írható könyvtár, amelyen keresztül minden módosítás és új fájl létrehozása történik. A felhasználó a virtuális nézeten keresztül végzi a műveleteit, amelyek mögött valójában az upperdir áll. A Dockerben ez a konténer saját, izolált, írható rétege.workdir (munkakönyvtár): Ezt a könyvtárat a fájlrendszer belsőleg, ideiglenes scratch space-ként használja. A bonyolultabb műveletek, mint például a fájlok átnevezése vagy áthelyezése, ezen a területen keresztül mennek végbe. A workdir-nek és az upperdir-nek ugyanazon a fizikai fájlrendszeren kell lenniük.merged (egyesített) nézet: Ez az a virtuális mountpont, ahol az upperdir és a lowerdir tartalma egységesen jelenik meg. A fájlhozzáférés során az upperdir élvez elsőbbséget, ha egy fájl mindkét rétegben létezik, az upperdir-ben lévő verzió lesz látható, és az alsó rétegben lévő verzió "átlátszatlanná" válik a felhasználó számára.
Ennek köszönhetően a Docker képes a csak olvasható image rétegeket újrahasznosítani, miközben minden konténer saját, izolált írható réteget kap.
5.1. Miért van rá szükség¶
A egyesítő fájlrendszerek alkalmazása számos területen elengedhetetlen, különösen ott, ahol a hatékonyság, az erőforrás-gazdálkodás és az adatok integritásának megőrzése kritikus szempont.
Live CD/DVD-k és beágyazott rendszerek: Ez a technológia teszi lehetővé, hogy egy alapvetően írásvédett adathordozón (például CD-ROM-on vagy egy Live USB-n) lévő alap fájlrendszer módosíthatónak tűnjön a felhasználó számára. A rendszer egy írható réteget (upperdir) hoz létre, és minden változás, például új fájl mentése vagy egy meglévő módosítása, erre az írható rétegre kerül. Ezáltal az alap fájlrendszer, ami a fizikai médián van, érintetlen marad. Ez az elv különösen hasznos olyan rendszerek esetében, ahol a háttértároló fizikai korlátai miatt (pl. flash memória korlátozott írási ciklusa) nem lenne gazdaságos vagy lehetséges az alap adathalmaz megváltoztatása.Konténerizáció (különösen a Docker): A konténerizációs platformok, mint a Docker, az egyesítő fájlrendszerekre épülnek a rétegelt kép (image) és tároló (container) architektúra megvalósítása érdekében. Egy Docker-kép valójában több egymásra helyezett, írásvédett rétegből áll. Amikor egy konténer elindul, a Docker hozzáad egy új, üres, írható réteget (upperdir) ezekhez az írásvédett alaprétegekhez. A konténeren belül végrehajtott összes módosítás ezen a legfelső, írható rétegen keresztül valósul meg. Ez a megközelítés lehetővé teszi, hogy több konténer is osztozzon ugyanazon az alapképen, ami drámaian csökkenti a lemezterület-felhasználást. A konténerek gyorsan indulnak, mivel csak a saját írható rétegüket kell létrehozni, az alaprétegek már léteznek.
Az egyesítő fájlrendszerek alapvető működése egy ok-okozati lánc eredménye. A technológia nem pusztán a könyvtárak kombinációjáról szól; elsősorban a "copy-on-write" és "transzparens rétegzés" stratégia megvalósítását szolgálja. Ezen stratégia nélkül a Live CD-k, valamint a Docker azon koncepciója, miszerint több konténer osztozhat ugyanazon az alapképen, miközben mindegyiknek saját, izolált állapota van, nem valósulhatna meg. Az elv abból a problémából ered, hogy miként lehet egy alapvetően statikus, írásvédett adathalmazt dinamikussá és módosíthatóvá tenni anélkül, hogy az eredeti forrást megváltoztatnánk. A válasz az, hogy a módosításokat egy különálló, írható rétegbe helyezzük, miközben az eredeti érintetlen marad.
6. A Copy-on-Write (CoW)¶
A másolás-írás (Copy-on-Write, CoW) egy olyan optimalizálási stratégia, amely ahelyett, hogy azonnal duplikálná az adatokat egy másolási kérésre, először csak egy új hivatkozást hoz létre a meglévő adatra. A tényleges másolás csak akkor történik meg, amikor az adatok valamelyik példányát ténylegesen módosítani akarják.
Az egyesítő fájlrendszerek, mint például a Docker overlay2 tárolómeghajtója, hatékonyan alkalmazzák ezt a mechanizmust. Mivel az image rétegek read-only (csak olvashatóak), a futó konténer nem tud közvetlenül írni ezekbe. Ehelyett a Docker a rétegek tetejére egy vékony, írható réteget (upperdir) helyez el.
6.1. Hogyan működik a CoW a Dockerben¶
Olvasási Művelet: Ha egy folyamat olvasni akar egy fájlt, a Docker először a vékony írható rétegben keres. Ha nem találja ott, akkor a lenti, csak olvasható image rétegeket vizsgálja, a legújabb rétegtől lefelé haladva az alapréteg felé. Ha megtalálja a fájlt, közvetlenül onnan olvassa be.Írási Művelet: Amikor egy folyamat egy meglévő fájlt akar módosítani, a Docker egycopy_upműveletet hajt végre. Ez a művelet lemásolja a fájlt a lenti, csak olvasható rétegből a konténer írható rétegébe. A módosítás ezután a másolaton történik, míg az eredeti változat érintetlen marad. Ez a másolási művelet a fájl méretétől függően teljesítményproblémákat okozhat, de a következő módosítások már azupperdir-ben történnek.Új fájl létrehozása: Az új fájl mindig közvetlenül az upperdir-ben jön létre, mivel az alsó rétegek írásvédettek.Törlési Művelet: Ha egy fájlt törölnek a konténerben, egy "whiteout" fájl jön létre az írható rétegben. Ez a speciális fájl elrejti a lenti rétegben lévő fájlt a konténer számára, de az eredeti fájl valójában nem törlődik. Ez a tény fontos a lemezterület-használat szempontjából, mivel egy fájl törlése nem csökkenti az image méretét, csak egy új réteg méretét növeli.Opaque könyvtárak: Alapesetben azonos nevű könyvtárak tartalma a rétegek között egyesül. Ha azonban az upperdir-ben egy könyvtár "opaque" (átlátszatlan) jelölést kap, az teljesen elfedi a lowerdir-ben lévő azonos könyvtár tartalmát.
| Művelet | A Fájl Helye | Mechanizmus |
|---|---|---|
| Létező Fájl Olvasása | Alap image réteg (pl. /etc/hosts) | A fájl közvetlenül a csak olvasható lowerdir-ből kerül beolvasásra, ami minimális teljesítmény-többlettel jár. |
| Létező Fájl Módosítása (első alkalommal) | Alap image réteg | A copy_up művelet lemásolja a fájlt a csak olvasható lowerdir-ből az írható upperdir-be. |
| Létező Fájl Módosítása (további alkalmakkor) | Az írható konténer réteg (upperdir) | A módosítások az upperdir-ben lévő másolaton történnek. A lenti, csak olvasható fájl érintetlen marad. |
| Fájl Törlése | Az írható konténer réteg (upperdir) | A rendszer egy whiteout fájlt hoz létre az upperdir-ben, amely elrejti a lenti rétegben lévő fájlt. Az eredeti fájl nem törlődik. |
| Új Fájl Létrehozása | Az írható konténer réteg (upperdir) | Az új fájl közvetlenül az upperdir-be kerül, mivel nem létezik az alatta lévő rétegekben. |
6.2. A CoW stratégia előnyei és hátrányai¶
-
Előnyök:Helytakarékos: több konténer osztozhat azonos, read-only rétegeken, amíg nem módosítanak rajtuk.Teljesítményfokozó: a duplikáció késleltetése csökkenti az erőforrásigényt, gyorsítva a többkonténeres futtatást.
-
Hátrányok:I/O-intenzív copy_up: nagy fájlok első írása lassú lehet, mivel a teljes fájl másolódik az upperdir-be.Törlés illúziója: a whiteout fájlok miatt a fizikai tárhely nem szabadul fel, az image méret nőhet.Írás-intenzív terhelés: overlay2 kevésbé hatékony, ezért perzisztens adatokhoz inkább Docker volumes ajánlottak.
7. Image optimalizálás¶
Az image méretének minimalizálása kulcsfontosságú a gyors buildelés, a hálózati forgalom csökkentése és a biztonság javítása érdekében.
- A Multi-Stage Build-ek ereje: A multi-stage build (többlépcsős build) minta a legjobb módja a kép méretének csökkentésére. A Dockerfile több FROM utasítást is tartalmazhat, mindegyik egy új buildelési szakaszt jelöl. Az első szakasz (a builder) tartalmazhatja az összes buildelési eszközt és függőséget, mint például a fordítókat. A végső szakasz (a final) pedig csak az elkészült, futtatható binárist vagy a szükséges fájlokat másolja át a builder szakaszból, így a felesleges buildelési függőségek nem kerülnek be a végleges image-be.
- A Helyes Alap Image (Base Image) Kiválasztása: A base image kiválasztása jelentősen befolyásolja a végleges image méretét és biztonságát. Az alpine a legnépszerűbb választás kis mérete miatt (akár 5.59MB), de bizonyos alkalmazások esetében (pl. Python) teljesítményproblémákat okozhat a glibc hiánya miatt. A distroless image-ek még kisebbek, de nem tartalmaznak shell-t vagy csomagkezelőt, ami megnehezíti a hibakeresést. A
slimváltozatok jó kompromisszumot jelentenek. - A Rétegek Számának Minimalizálása és a Build Cache kihasználása: Minden RUN, COPY, és ADD utasítás új réteget hoz létre, ami növeli a végleges image méretét és a buildelési időt. A legjobb gyakorlat az, ha több RUN utasítást egyetlen parancsban kombinálunk a && segítségével. Fontos, hogy a szükségtelen fájlokat és gyorsítótárakat (cache) ugyanabban a RUN utasításban távolítsuk el, amelyben létrehoztuk őket, ezzel elkerülve a felesleges adatot a layerben. Ezenkívül a Dockerfile utasításainak sorrendje is számít. A gyakran változó fájlok (COPY) a Dockerfile végén legyenek, hogy kihasználhassuk a build gyorsítótárát.
- A .dockerignore használata: A .dockerignore fájl lehetővé teszi, hogy kizárjuk a build kontextusból a szükségtelen fájlokat (pl. .git mappák, node_modules), ami gyorsítja a buildelést és csökkenti a végleges image méretét.
| Best Practice | Ráció | Példa |
|---|---|---|
| Multi-Stage Build | A build időbeli függőségeket elválasztja a végső futtatókörnyezettől, drasztikusan csökkentve az image méretét és a támadási felületet. | FROM node:16 as build RUN npm install ... FROM node:alpine as final COPY --from=build /app. |
| Helyes Alap Image | A kisebb base image csökkenti az image méretét, a buildelési időt és a biztonsági kockázatokat. | Válassz a python:3.11-slim helyett a python:3.11-alpine-t (ha nincs glibc probléma) vagy a distroless-t. |
| Rétegek Minimalizálása | Kevesebb réteg gyorsabb buildelést és kisebb image méretet eredményez. | RUN apt-get update && apt-get install -y vim && rm -rf /var/lib/apt/lists/* |
| .dockerignore Használata | Kizárja a build kontextusból a nem szükséges fájlokat. | Hozzon létre egy .dockerignore fájlt a .git, node_modules, és .env bejegyzésekkel. |