Docker alapozó

Aki fejlesztett már bármit szerver oldalra, az tudja, hogy a működési logika csak a dolgok egyik oldala, ott van még emellett a szerver…

Docker alapozó

Forrás: https://www.docker.com/

Aki fejlesztett már bármit szerver oldalra, az tudja, hogy a működési logika csak a dolgok egyik oldala, ott van még emellett a szerver környezet. Az adatbázis, az alkalmazásszerver és minden egyéb. Milyen jó lenne, ha a kód mellett ezt is le tudnánk írni valamilyen formában, amit lefordítva bármilyen szerverkörnyezetbe kirakhatnánk a kis csomagunkat vagy éppen fejlesztés közben egy a véglegeshez hasonló környezetben tesztelhetnénk a kódunkat anélkül hogy bármit kézzel kellene telepítgetni vagy beállítani. A Docker kb. erre a problémára ad megoldást és még talán ennél is többet…

A szerver környezet (izolált módon történő) helyben futtatására az egyik lehetőség, hogy felhúzunk egy virtuális gépet. Ennek nagy hátránya, hogy egy virtuális szerver futtatása erőforrás igényes. Kell a rendszerünk alá egy komplett operációs rendszer, emulált hardverek (pl. virtuális lemezek), dedikált memória, stb. Szerencsére Linuxon létezik egy kevésbé erőforrás igényes megoldás a processek izolálására, de mielőtt erre rátérnék, had tegyek egy rövid kitérőt…

Annak idején még fősulin volt egy Linux biztonságtechnika nevű óránk, ahol az egyik feladatunk chroot jail összeállítása volt. A chroot jail a Linux kernel chroot hívásán alapul, amivel úgy futtathatunk egy porcess-t, hogy számára a root fájlrendszer a valódi fájlrendszer valamelyik könyvtára legyen. Ezzel a technológiával az adott process bezárható a saját kis root fájlrendszerébe, így ha egy támadó betörne a szerverre a processen keresztül, képtelen lesz elérni a teljes fájlrendszert, hiszen csak annyit lát belőle amennyit az adott process. Szokták chroot jailbe zárni a webszervereket, a DNS szervert, és szinte minden FTP szerver támogatja. Ez utóbbi esetben sikeres login után az FTP szerver chroot jailbe zárja a process-t, így az adott felhasználó csak a saját könyvtárát fogja látni és maga a kernel gondoskodik róla, hogy onnan ne tudjon kitörni.

A Docker a Linux-on névtereket használ, ami hasonló a chroot-hoz, de a névterek segítségével nem csak saját root fájlrendszert kap a process, de külön process táblát, hálózati interfészt és minden egyebet. Tehát mikor Dockerben fut mondjuk egy webszerver, akkor nem indul új virtuális gép, de a webszerver nem fogja látni a hoszt gép fájlrendszerét, nem éri el a többi processt és hálózati szempontból is úgy fogja gondolni, hogy egyedül fut a gépen (pl.: indíthatunk több webszervert is a “saját” 80-as portján). Izoláció szempontjából tehát a Docker konténerek olyanok mint a virtuális gépek, ugyanakkor sokkal kevesebb erőforrást fogyasztanak, hiszen nem kell egy komplett OS-t emulálni.

A Docker nagy előnye, hogy nem csak egy toolt kapunk a porcessek izolált futtatásához, hanem egy komplett ökoszisztémát, aminek része egy nagy konténer repository, ahol megtalálható a legtöbb alaprendszer, webszerver, adatbázis szerver, stb. amiket akár egy az egyben használhatunk, vagy kiindulási alapként a saját konténerünkhöz. Ennek szemléltetésére gyorsan mutatok is egy példát:

docker run -it busybox sh

A fenti parancs lehúzza a Docker repoból a busybox image-t, ami egy alap Linux rendszer, elindítja azt, majd lefuttatja az sh parancsot amivel kapunk egy interaktív shell-t. (A Docker rendszer installálásáról nem akartam külön írni, itt megtalálható minden információ az egyes operációs rendszerekhez: https://docs.docker.com/get-docker/)

A konténerben futó shell-ben bármit csinálhatunk. Listázhatjuk a fájlrendszert, körbejárkálhatjuk a könyvtárakat, stb. De ugyanilyen könnyen, egyetlen sorból fel lehet húzni egy MySQL szervert vagy bármilyen más adatbázis szervert minden projektünkhöz külön sajátot, így nem kell ezekkel “szennyezni” a hoszt rendszert. Egyszerűen elindítjuk az éppen aktuálisan fejlesztett projekthez a megfelelő környezetet, majd ha nincs rá szükség egyszerűen leállítjuk.

Amiről még érdemes pár szót ejteni, az a Docker fájlrendszerének felépítése. Adjuk ki a busybox shellben a mount parancsot. A root fájlrendszernél valami ilyesmit fogunk látni:

overlay on / type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/CA5FL4FFRJFLJF36PN4VOQZWNR:/var/lib/docker/overlay2/l/TCJVSWOFYXJ5M3IHQ7GQLRXKIE,upperdir=/var/lib/docker/overlay2/1303a004c26c203f181d96e3179cce1351ee2f14648403dbac315af700352929/diff,workdir=/var/lib/docker/overlay2/1303a004c26c203f181d96e3179cce1351ee2f14648403dbac315af700352929/work,xino=off)

Látható, hogy a fájlrendszer típusa overlay, ami több könyvtárból áll össze. Amikor olvassuk a fájlrendszert, akkor a rendszer felülről lefelé keresi a fájlokat, ha pedig írás történik, a fájlok a legfelső (workdir) könyvtárban jönnek létre, így az alap busybox fájlrendszert sosem piszkítjuk össze. Ez azért jó, mert ha van mondjuk 10 adatbázis konténerünk, nem kell 10 példányban létrehozni a teljes fájlrendszert, elég azokat a fájlokat eltárolni amik az egyes konténerekben különböznek az alap image-től. Ehhez a működéshez persze elég lenne 2 réteg. Egy alap csak olvasható fájlrendszer és egy második réteg ahol az egyedi változásokat tároljuk. Ezzel szemben sok konténer esetén a listában akár 10–20 réteget is látunk. Ez azért van, mert az egyes docker image-ek egymásra épülhetnek, így minden image saját réteget kap. Mondjuk ha van egy MySQL és egy PostgreSQL szerverünk amik ugyanarra az alap Linux image-re épülnek, akkor az alsó szint mindkét esetben a Linux réteg lesz, amit így elég csak egyszer tárolni a hoszt fájlrendszerben. A fentiekből látható, hogy a Docker a számítási kapacitás mellett a fájlrendszerrel is próbál takarékosan bánni. Ha a fenti példa 10 adatbázis szerverét virtuális szerveren futtatnánk, az 10 komplett virtuális disket jelentene operációs rendszerrel és minden egyébbel.

Most, hogy nagyjából túl vagyunk az alapokon, hozzunk is létre egy saját Docker konténer image-t. Ehhez huzzuk le a következő repo-t:

git clone https://github.com/TheBojda/docker-sample.git

Az egész projekt 3 fájlból áll és egy sima statikus index.html fájlt tud kiszolgálni. A docker image leírása a Dockerfile-ban található, ami így néz ki:

FROM nginx
COPY run.sh /
RUN chmod +x /run.sh
COPY html /usr/share/nginx/html
ENTRYPOINT [“/run.sh”]

A Dockerfile felépítése igen egyszerű. A fájl elején a FROM részben adjuk meg a kiinduló image-t, ami jelen esetben egy NGINX szerver. Ezt követi a run.sh másolása a root könyvtárba, amire futtatási jogot adunk. A következő sorban bemásoljuk a html könyvtár tartalmát a /usr/share/nginx/html könyvtárba, majd elindítjuk a run.sh-t, ami futtatja a nginx-et.

Ahogy láthatjuk, a Dockerfile egy egyszerű script szerű leírás arról, hogy hogyan álljon össze a konténer image. Magát az image-t a következő paranccsal tudjuk létrehozni (a repo legyen az aktuális könyvtár):

docker build . -t docker-sample

A fenti parancs lefuttatja a Dockerfile-t, végrehajtja a benne található parancsokat, majd létrehozza az image-t amit a -t kapcsolóval docker-sample-nek fogunk elnevezni. Ha minden jól ment, akkor a

docker images

paranccsal láthatjuk a listában a docker-sample image-t. Futtatni a konténerünket a következő paranccsal tudjuk:

docker run -p 80:80 docker-sample

A -p paraméter a konténer 80-as portját a hoszt 80-as portjára vezeti ki. Erre azért van szükség, mert ahogy korábban említettem a konténerek izoláltan futnak saját fájlrendszerrel, processz listával és hálózattal, így enélkül nem tudnánk elérni a konténer 80-as portját. Ha minden jól sikerült, akkor a http://localhost címen a “Hello from Docker” feliratot fogjuk látni, ami az index.html fájl tartalma.

A gyorstalpalónk végén szeretnék még pár szót szólni a docker-compose parancsról. Ez egy Pythonban írt tool, amivel több konténerből álló docker környezeteket tudunk felhúzni. A környezet leírására a docker-compose.yaml fájl szolgál. Az alábbi minta azt mutatja, hogyan ránthatunk fel pár sorból egy komplett WordPress oldalt:

A yaml fájlban 2 service-t definiálunk ami 2 docker konténer elindítását jelenti. Az első service a db, ami egy mysql szerver. Az environment részben adjuk meg az adatbázis nevét és az alap felhasználót. A volumes részben a /var/lib/mysql könyvtárba felcsatolunk egy virtuális meghajtót (egy könyvtár a lokális gépen), így ha töröljük a konténert, az adatbázis tartalma akkor is megmarad (egészen addig amíg a virtuális meghajtót nem töröljük). A második service maga a wordpress. Ennek a konténernek a konfigurációja hasonlít a db-hez, az egyetlen különbség a ports és a depends_on rész. Az előbbi azt adja meg, hogy a konténer melyik portja a hoszt melyik portjára legyen leképezve, az utóbbi pedig azt, hogy a wordpress service előtt kell elindítani a db-t. A compose fájlt a

docker-compose up

paranccsal futtathatjuk le. Ha minden jól ment, akkor a http://localhost:8000/ címen elérjük az új WordPress site-unkat. Ha egy másik terminálban lefuttatjuk a

docker ps

parancsot, akkor szépen látszik a két docker konténer. Egy a db-nek és egy a wordpress-nek.

A Docker nagy előnye, hogy egyfajta általános konténer szabvánnyá vált, így minden cloud szolgáltató támogatja ezt a formátumot. Ha tehát a saját gépünkön megvagyunk a fejlesztésekkel, az elkészült konténer image-t egy mozdulattal kirakhatjuk a felhőbe, ahol menedzselt módon futtathatjuk azt. Ez utóbbi azt jelenti, hogy a rendszer automatikusan gondoskodik a működésképtelenné vált konténerek újraindításáról, a rendszer fel illetve leskálázásáról, illetve ha legalább két szerveren fut a konténerünk, akkor felváltva frissíthetjük a konténereinket, így a szolgáltatásunk egy másodpercre sem fog leállni.

Remélem a fentiekből nagyjából körvonalazódott, hogy mennyire hasznos a Docker és a konténerek használata. Nyilván a témával könyveket lehetne megtölteni, de úgy gondolom, hogy alapozónak ennyi talán elegendő. Ha valakit mélyebben érdekel a téma, az itt talál egy nagyon jó tutorialt angol nyelven: https://docker-curriculum.com/ . Ezen kívül rengeteg írás elérhető a weben, így aki jobban el akar mélyedni a témában, könnyedén talál hozzá anyagot.