Az image készítésnek több lehetséges módja van. Először nézzük meg a “kézműves” docker image készítést, aztán pedig megnézzük, hogy hogyan lehet Dockerfile segítségével automatizáltan megtenni ugyanazt.
Első körben indítsunk el egy konténert, ami a hivatalos ubuntu imaget fogja használni.
docker run --name imagemaker -ti ubuntu
Ez elindít egy imagemaker
nevű konténert a -ti
kapcsolóval, hogy tudjunk gépelni. Figyeld meg, hogy nem használtuk a --rm
kapcsolót. (Hogy mi ez arról korábban már írtam) Ezt azért tettük, mert a futás után szükségünk lesz majd a leállított konténerre, hisz abból fogjuk elkészíteni az imaget.
Amennyiben kiadod a curl
parancsot a “command not found” üzenetet fogod kapni, hisz az nincs telepítve.
A következő két utasítás fogja a curl alkalmazást telepíteni. Amennyiben nem mozogsz otthonosan a Linux világában ne ijedj meg, nem fogunk most elmélyülni benne. Add ki a következő két parancsot:
apt-get update
apt-get install -y curl
Az első parancs frissíti a csomaginformációkat, amik nincsenek benne az imageben, hogy annak mérete minél kisebb legyen. Az, hogy ez miért is jó, majd később lesz szó. A második parancs a tényleges telepítés. Az -y
kapcsoló fontos, mivel ez a parancs automatán fut és nem interaktívan, ezért a kérdésekre nem fog tudni válaszolni ezzel a kapcsolóval viszont minden kérdésre yes, vagyis igen választ ad majd. Még egyszer megjegyezném: most csak azt kell megérteni, hogy ez a két parancs telepíti a curl
parancsot.
Ki is próbálhatod. Amennyiben futtatod a curl parancsot, már nem a hibaüzenettel fogsz szembesülni, hanem a curl használatáról kapsz információkat. Lépjünk ki a konténerből az exit
paranccsal. Itt igazából nem a konténerből léptünk ki, hanem csak a bash shell-ből, de mivel csak ez futott a konténerbe, így a konténer futása is befejeződött.
Amennyiben kiadjuk a docker ps -a
parancsot, akkor láthatjuk, hogy a imagemaker
nevű konténer létezik.
Ezután adjuk ki a következő parancsot, hogy elkészüljön az image:
docker commit imagemaker local/curl
A commit
parancsnak először a konténer nevét vagy azonosítóját, majd az image nevét kell megadni. Azért, hogy ne akadjon össze a hivatalos image és az általam gyártottak én a tanarurkerem/
előtagot szoktam használni általánosan. Azonban akkor, ha tudom, hogy csak azon az egy gépen használom, amit készítettem, akkor a local/
előtagot. A docker image ls local/curl
parancs segítségével láthatjuk, hogy létre is jött az image.
Indítsunk is el egy konténert a frissen készített image segítségével:
docker run --rm -ti local/curl
Teszteljük, adjuk ki a curl
parancsot. Amennyiben jól dolgoztunk a curl parancs használatáról kapunk némi információt.
Vizsgáljuk meg egy picit jobban ezt az imaget, hisz ideje beszélni a layerekről, mely az egyik erőssége a Docker által megalkotott eszközkészletnek. Adjuk ki a docker image history local/curl
parancsot.
A látott kép értelmezéséhez érdemes tudni azt, hogy a Docker egy úgynevezett naplózó fájlrendszert használ az imagek és konténerek fájlrendszeréhez. Amikor elindítunk egy konténert, akkor az alapjául szolgáló image fájlrendszerét kapja meg, de a futás során az image fájlrendszere változatlan, a változásokat egy külön rétegbe vagyis layerbe menti el a rendszer. Szemben a virtuális gépekkel, ha elindítunk száz konténert egy két gigabájtos image alapján, akkor az a két gigabájt csak egyszer lesz lefoglalva a háttértárakon. Akkor, amikor letöltésre kerül az adott image és függőségei. A száz konténer csak annyi helyet fog foglalni, amennyi adatot módosítanak. Száz virtuális gépnél ilyenkor kétszáz gigányi tárhely kerül lefoglalásra, vagyis százszor annyi.
A fenti commit
parancs nem csinál mást, mint az imagemaker
konténer layerét elmenti local/curl
néven, valamint azt az információt, hogy az adott konéner az ubuntu
image használatával lett elindítva. A Docker nem tárolja el a teljes fájlrendszert újra, hanem csak a különbségeket. Amennyiben megnézzük a history
parancs kimenetét, láthatjuk, hogy az utolsó réteg, amit mi készítettünk az 46MB. A docker image ls local/curl
és a docker image ls ubuntu
parancsok segítségével láthatjuk, hogy a kiinduló ubuntu
image 77.8 MB és az általunk készített local/curl
image pedig 124MB (78+46) méretű.
Ez az a pont, ahol a commit parancsot el is felejthetjük. Nem hinném, hogy szükségünk lesz rá többet. Imaget ugyanis nem így fogunk készíteni, hanem Dockerfile és a build parancs segítségével. A fenti részt azért tartottam fontosnak bemutatni, mert a mögöttes működési mechanizmus megértéséhez feltétlenül szükséges volt.
Készítsünk egy tetszőleges könyvtárat, majd álljunk bele és hozzunk létre ott egy Dockerfile
nevű fájlt a következő tartalommal:
FROM ubuntu
RUN apt-get update
RUN apt-get install -y curl
Majd készítsük el az image-t a következő paranccsal:
docker build -t local/curl .
Amennyiben minden jól ment ez a parancs elkészíti a local/curl
imaget, amit ugyanúgy tudunk használni mint a korábbit. Próbáljuk ki és indítsuk el és teszteljük a curl parancsot:
docker run --rm -ti local/curl
Így persze csak azt fogjuk tudni tesztelni, hogy ugyanúgy működik még az adott image, de nem lehetünk benne biztosak, hogy tényleg egy másik imageről van-e szó. Adjuk ki a docker image history local/curl
parancsot. Ekkor valami ilyesmit kell kapnunk:
Amint látjuk a history parancs már egészen más kimenetet ad mint eddig. Nem csak egy plusz réteget látunk ott, hanem kettőt. Ebből le is vonhatjuk a következtetést, hogy ez bizony már egy másik image, hiába néz ki ugyanúgy.
Most vizsgáljuk meg picit a Dockerfilet. Ne próbáljuk meg hasonlítani semmihez ez egy teljesen egyedi formátum saját szabályokkal. Minden sor egy utasítás és az adott utasítás paraméterei.
[UTASÍTÁS] [paraméterek]
Üres sorokkal tagolhatjuk a fájlt, az nem fogja megzavarni a működést. Amennyiben egy sor végén visszaperjel (\) található, úgy a következő sor hozzáfűzésre kerül. Így több sorba is törhetjük a hosszú parancsokat, amennyiben ez szükséges. # jellel megjegyzéseket tehetünk a fájlba. A Dockerfile parancsok kis-nagybetűre nem érzékenyek, de konvenció szerint mindig csupa nagybetűvel írjuk azokat. Mindig egy FROM
paranccsal kezdünk, amely megmondja, hogy mi lesz a kiinduló, base image. Jelen esetben is, mint ahogyan a parancssori példában az ubuntu
imageből indulunk ki. A RUN
parancs a paraméterül kapott parancsokat lefuttatja és az eredményből készít egy újabb layert. Ezért van itt két layer, míg korábban csak egy volt. A fenti két parancsot összevonhatjuk és egy sorba tehetjük. Ehhez a &&
jel kell, mely egy shell operátor, és a két utasítást egymás után futtatja le. Próbáljuk ki! Módosítsuk a Dockerfile-t, majd futtassuk újra a buildet:
FROM ubuntu
RUN apt-get update && apt-get install -y curl
A history most már csak egy plusz réteget mutat.
Természetesen több sorba is törhetjük a parancsot a \ segítségével. Módosítsuk a Dockerfile-t majd építsük újra:
FROM ubuntu
RUN apt-get update && \
apt-get install -y curl
Amennyiben nem húzzuk beljebb a második apt-get parancsot, úgy image azonosítóra megegyező imaget kapunk, hisz a Docker először “kiegyenesíti” a sort és utána képez belőle egy azonosítót. A behúzás miatt a “kiegyenesített” sor tartalmaz plusz szóközöket és ezért más azonosítót fog kapni, de tartalmilag ugyanaz az image lesz. Ezt a méretén is láthatjuk.
Végezetül beszélnünk kell az image méretéről. Minél kisebb egy image annál rövidebb idő alatt lehet azt szerverek között mozgatni a hálózaton és annál kevesebb helyet foglal a hoston. Ezért törekedni kell a minél kisebb imagek létrehozására. Könnyen belátható, hogy a nagyobb image több kódot tartalmaz. A több kóddal növekszik a sérülékenységek valószínűsége. Ez is amellett szól, hogy az imagek méretét ne engedjük nagyra nőni, ezért se mindegy, hogy mi is van abban az imageben. Épp ezért nincsen benne a kiinduló imageben az apt csomagok metainformációi, ezért kellett futtatnunk az apt-get update
parancsot. Ez azt is jelenti, hogy az általunk készített image tartalmazza ezeket. Módosítsuk hát a Dockerfilet és adjuk hozzá a szükséges rm
parancsot:
FROM ubuntu
RUN apt-get update && \
apt-get install -y curl && \
rm -rf /var/lib/apt/lists/*
Építsük meg az imaget, majd nézzük meg, hogy mi lett az eredmény:
Láthatólag a curl telepítésének lépése lement 46 MByteról 7 MBytera. Ki lehet próbálni, hogy a három parancsot három külön RUN parancsba tesszük és azt fogjuk tapasztalni, hogy az image mérete nem csökkent.
A képet megvizsgálva azt is láthatjuk, hogy az apt-get update
parancs 40MByteos méretnövekedését tudjuk megspórolni, ha a három parancsot egy RUN
utasításba tesszük. Figyeljük meg, hogy a törlés (rm -rf /var/lib/apt/lists/*
) egy igen fontos eleme a méretcsökkentésnek. Pusztán azzal, ha egy RUN
utasításba írjuk a parancsokat nem lesz kisebb az image mérete.
Ne feledd! Amennyiben szeretnél fejlődni keress meg nyugodtan. Csoportos és személyes Docker oktatással és tanácsadással tudlak téged vagy csapatodat, cégedet támogatni.