News & Blog

Docker alapok

News & Blog

Bevezető

Nem túlzunk talán, ha azt mondjuk, az elmúlt évek, évtizedek az informatikában a virtualizációs technikák fejlődéséről is szóltak. Mind hardver, mind szoftver oldalon számos olyan fejlesztés történt, amely lehetővé teszik az ilyen  módszerek minél jobb kihasználását. Ezeket a fejlesztéseket két nagy csoportba sorolhatjuk, az elsőbe a Hypervisor alapú, míg a másodikba a konténer alapú virtualizáció tartozik. A két rendszer alapvető különbségét az alábbi ábrán keresztül szemléltethetjük:

Jól látható, hogy a hypervisor alapú virtualizáció esetén a gépünkön futó operációs rendszerre installált hypervisor program (például a VMvare, VirtualBox, stb.) nyújt egy olyan felületet, amelyen keresztül újabb vendég operációs rendszereket installálhatunk fel, és ezeknek a fájlrendszerére újabb alkalmazásokat tudunk telepíteni. A konténer alapú virtualizáció esetén azonban a hypervisor helyett egy jóval egyszerűbb konténer modult (engine) használunk, amely az alkalmazások számára csak a host operációs rendszer fájlrendszerét (illetve annak is csak az alkalmazás számára szükséges részét) nyújtja, és úgy mondjuk, hogy az alkalmazásokat egységbe zárja.  Úgy is mondhatjuk, hogy míg hypervisor a hardvert virtualizálja, addig a konténer technológia csak a fájlrendszert. Ez utóbbi lényegesen kevesebb erőforrást igényel, és gyorsabb használatot tesz lehetővé. Ha például egy szoftverfejlesztő ugyanazon programjának különböző verzióit szeretné kipróbálni, a hypervisor használata esetén több operációs rendszert is fel kell installálnia, addig a konténerek használatával csupán több konténert kell létrehoznia. Egy ilyen a Linux konténer alapú virtualizációs technológiájára alapuló nyílt forráskódú megoldás a Docker. (Ha valaki még emlékszik, a Linux alatti chroot-ból nőtte ki magát.) Mindenképpen meg kell említenünk, hogy a Docker használatával elsősorban karakteres felületen érhetjük el az alkalmazásokat (terminálból), a grafikus elérés nehézkes, ha mindenképpen szükséges, akkor leginkább valamilyen VNC-vel próbálkozzunk. Egy konténer fájlrendszerébe a host operációs rendszeréből belelátunk, de onnan kifelé nem. (Természetesen valamilyen hálózatos szolgáltatással (pl. FTP) elérhető a host fájlrendszere is.)

A Docker dokumentumainak olvasgatásakor gyakran előfordulhat a Kubernetes kifejezés is. A Kubernetes egy olyan konténer alapú alkalmazáskezelő szoftver, amely a Docker konténereket is tudja kezelni. (A Docker már natívan támogatja.) Eredetileg a Google fejlesztette, amely konténerek millióit futtatja, így szüksége volt egy olyan rendszerre, mely ezek működését szervezi, kezeli. Mi  nem foglalkozunk a Kubernetes-el ebben a cikkben.

Docker Desktop for Windows

A Docker több operációs rendszert is támogat, vagyis futtathatjuk Windows, Linux vagy OSX alapú gépeken is. Mindegyik rendszeren a konténer technika működtetéséhez valamilyen virtuális gép kezelő rendszert használunk. (Még akkor is, ha nem virtuális gépeket hozunk létre.) Mivel ebben a cikkben a Windows 10-re telepített Docker-t fogjuk használni, így a Windows megoldását vesszük szemügyre. A Windows a Hyper-V rendszerét használja virtuális gépek készítésekor, így a kezdetekben a Docker is ezt használta. A legújabb kiadásban viszont erről áttért a WSL2-re (Windows Subsystem for Linux 2), mely lehetővé teszi, hogy virtuális gépek installálása nélkül közvetlenül Linux disztribúciókat futtassunk a Windows-on. A gondot az okozza, hogy ha mind a két rendszer aktív a gépünkön, akkor a Docker indításakor hibaüzenetet kapunk. Erre majd mutatok egy lehetséges megoldást, amely ugyan az én gépemen működött, de nem garantálhatom, hogy mindenkién működni fog. Ilyenkor a különböző fórumokban keresgethetünk tovább.

Első lépésként frissítsük a Windows 10-et, hogy a legújabb WSL legyen rajta. Ezután töltsük le a Docker Desktop programot erről a linkről: https://www.docker.com/products/docker-desktop/, majd installáljuk fel. És készen is vagyunk. Amennyiben indítás után a Hardware assisted virtualization and data execution protection must be enabled in the BIOS hibaüzenetet kapnánk, akkor amennyiben a BIOS-ban engedélyezve van a virtualizáció, indítsunk rendszergazdaként egy Power Shell parancssort, és adjuk ki a bcdedit /set hypervisorlaunchtype auto parancsot, majd indítsuk újra a gépet. A hibajelzés remélhetőleg megszűnik. (Itt meg kell jegyeznem, hogy ezután a beállítás után ugyan a Docker Desktop elindult, de a VMware-ben a virtuális gépeimnél ki kellett kapcsolnom a processzor beállításnál a Virtualize Intel VT-x/EPT or AMD-V/RVI beállítást. Ilyenkor viszont a beágyazott virtuális gépek nem indultak el. Ha erre szükség lenne, akkor az előző parancsot vissza kell vonni a bcdedit /set hypervisorlaunchtype off paranccsal, ilyenkor persze a Docker Desktop nem indul el.)

A Docker Desktop program ugyan lehetőséget ad arra, hogy rajta keresztül kezeljük a konténereinket, de mi a következőkben a parancssoros megoldásokat fogjuk átnézni.

Lemezképek és konténerek (images és containers)

A Docker használatakor tisztában kell lennünk a lemezképek (images) és konténerek (containers) fogalmával. Mint az előző ábrán is láthatjuk, egy konténer egy egységbe zárt alkalmazást tartalmaz. Ennek az alkalmazásnak a kiinduló kódját tartalmazza a lemezkép. Ilyen lemezképeket egyrészt az Internetről tudunk letölteni (például a Docker HUB-ról), de saját magunk is elkészíthetjük azokat. Minden lemezkép kiindulási alapja egy ős-lemezkép, és amennyiben változtatunk rajta valamit, akkor ez a változtatás egy rétegként kerül rá. Ezért mondhatjuk, hogy a lemezképek is réteges szerkezetűek. (Azt, hogy mi az ős lemezkép, és milyen változtatásokat kell rajta végrehajtani, egy Docker fájl írja le.) Kezdetekben mi csupán előre elkészített lemezképeket fogunk használni, amelyek különböző alkalmazások kódjait és szükséges fájljait tartalmazzák, így a lemezképek készítésével még nem kell foglalkoznunk. Egy lemezképet alapvetően a nevével azonosítjuk, de mivel a benne található alkalmazásnak lehet több verziója is, ezért kiegészíthető a név egy tag-gel, mely így alapvetően a verzióra utal. (Később még egy felhasználói névvel is kiegészítjük.) Amennyiben csak a nevet adjuk meg, a legutolsó verzió töltődik le. Kiindulásként töltsük le a Docker HUB-ról a hello-world képfájlt. Ehhez a docker pull parancsot használjuk:

C:\Users\User>docker pull hello-world
Using default tag: latest
latest: Pulling from library/hello-world
2db29710123e: Pull complete
Digest: sha256:faa03e786c97f07ef34423fccceeec2398ec8a5759259f94d99078f264e9d7af
Status: Downloaded newer image for hello-world:latest
docker.io/library/hello-world:latest

Ellenőrizzük le, hogy a képfájl megjelent-e a gépünkön helyileg. Erre szolgál a docker images parancs:

C:\Users\User>docker images
REPOSITORY    TAG       IMAGE ID       CREATED         SIZE
hello-world   latest    feb5d9fea6a5   14 months ago   13.3kB

Számunkra a két legfontosabb információ a képfájl neve (hello-world) és az azonosítója (feb5d9fea6a5), mivel ezekkel tudunk a képfájlra a későbbiekben hivatkozni. Az azonosító használatakor azt a rugalmasságot tapasztalhatjuk, hogy egy azonosítót csak addig kell megadni, amíg egyértelmű nem lesz, vagyis jelen esetben maga az f karakter is azonosítja már a hello-world képfájlt.

Most, hogy a képfájl már megtalálható a gépünkön, indítsuk el, vagyis készítsünk belőle egy konténert. Ehhez a docker run parancsot használjuk, melyben vagy a képfájl nevét, vagy az azonosítóját (illetve annak első pár karakterét) kell megadnunk:

C:\Users\User>docker run hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.

Miután elindítottuk a képfájlt, a Docker készít belőle egy konténert, mely a memóriába töltődik be. Azt is mondhatjuk, hogy egy konténer egy képfájl egy futó példánya. (A folyamatot példányosításnak is nevezzük.) Gyakorlatilag az történik, hogy a docker run parancs a képfájlon létrehoz egy írható réteget, és ebben lesz tárolva minden változtatás. Bármilyen változtatást hajtunk végre a konténerben (amit a benne futó program megenged), az nem lesz hatással az eredeti képfájlra, vagyis az gyakorlatilag csak olvasható módon van letárolva a rendszerben. A fenti konténer miután kiírta a köszöntő szöveget a terminálba, le is állt. Később találkozunk olyan konténerekkel is, amelyek futó állapotban maradnak.

Fontos tudnunk, hogy ha egy konténer leáll, attól még megmarad, és szükség esetén újraindítható. (A későbbiekben részletesebben is megvizsgáljuk egy konténer életciklusát.) Jelenítsük meg, hogy milyen konténerek vannak a rendszerben. Erre szolgál a docker ps parancsa:

C:\Users\User>docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

És hol van a konténer? Ez a parancs alapesetben csak a futó konténereket listázza ki. Ahhoz, hogy lássuk a leálltakat is, használjuk a -a kapcsolót. (A legtöbb kapcsolónak van rövid és hosszú formája, a -a hosszú formája a −−all.)

C:\Users\User>docker ps -a
CONTAINER ID IMAGE       COMMAND  CREATED        STATUS                PORTS NAMES
87bbef8af2b3 hello-world “/hello” 24 minutes ago Exited (0) 24 minutes ago   romantic_shaw

Itt már láthatjuk a leállított konténert. A megjelenített adatok között talán a név lehet érdekes, hiszen mi nem adtunk ilyen furcsa nevet (romantic_shaw) a konténernek. Ezt a nevet a Docker generálta, és természetesen van rá módunk, hogy mi nevezzük el a konténert. Erre való a −−name kapcsoló:

C:\Users\User>docker run −−name hello hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.

Most jelenítsük meg újra a konténereinket. (A leálltakat is, hiszen a hello-world leáll a futása után.)

C:\Users\User>docker ps -a
CONTAINER ID IMAGE       COMMAND  CREATED           STATUS             PORTS     NAMES
b048200d436f hello-world “/hello” 27 seconds ago    Exited (0) 26 seconds ago    hello
87bbef8af2b3 hello-world “/hello” About an hour ago Exited (0) About an hour ago romantic_shaw

Jól látható, hogy megjelent a hello nevű hello-world konténerünk. Ezután már nem csak az azonosítóval, hanem ezzel a névvel is hivatkozhatunk a konténerre. Természetesen a másik is érintetlenül ott maradt, hiszen a docker run parancs a lemezképből készíti el a konténert, így a hello-world-nek már két példánya létezik.

Arra is van lehetőség, hogy átnevezzünk egy konténert a docker rename paranccsal:

C:\Users\User>docker rename romantic_shaw hello_old

C:\Users\User>docker ps -a
CONTAINER ID IMAGE       COMMAND  CREATED           STATUS             PORTS     NAMES
b048200d436f hello-world “/hello” 27 seconds ago    Exited (0) 49 seconds ago    hello
87bbef8af2b3 hello-world “/hello” About an hour ago Exited (0) About an hour ago hello_old

Próbáljuk most letörölni a hello-world képfájlt. Erre a docker rmi parancsot használhatjuk. A parancs után vagy a képfájl nevét, vagy az azonosítóját (annak egy részét) adhatjuk meg. Próbáljuk ki:

C:\Users\User>docker rmi hello-world
Error response from daemon: conflict: unable to remove repository reference “hello-world” (must force) – container 9abba1e94559 is using its referenced image feb5d9fea6a5

A megjelenő hibaüzenet jelzi, hogy nem törölhetjük a képfájlt, mivel létező konténerek is vannak belőle.  Először a konténereket kell törölnünk a docker rm paranccsal. (Szintén névvel vagy azonosítóval jelöljük ki a törlendő konténereket.) A példában az egyik konténert névvel, a másikat az azonosítója első pár karakterével töröljük le:

C:\Users\User>docker rm hello
hello
C:\Users\User>docker rm 87b
87b

Ezután már törölhető a képfájl is:

C:\Users\User>docker rmi hello-world
Untagged: hello-world:latest
Untagged: hello-world@sha256:faa03e786c97f07ef34423fccceeec2398ec8a5759259f94d99078f264e9d7af
Deleted: sha256:feb5d9fea6a5e9606aa995e879d862b825965ba48de054caab5ef356dc6b3412
Deleted: sha256:e07ee1baac5fae6a26f30cabfe54a36d3402f96afda318fe0a96cec4ca393359

Amennyiben futó konténerre adjuk ki a törlés parancsot, hibajelzést kapunk. Ilyenkor vagy le kell állítani előbb a konténer futását (később látjuk erre a docker stop parancsot), vagy a -f (−−force) kapcsolót kell használni. Ha nagyszámú leállított konténerünk van már, és mindegyiket törölni szeretnénk, akkor ezt a docker container prune paranccsal tehetjük meg:

C:\Users\User>docker container prune
WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N] y
Deleted Containers:

Ugyanígy a képfájlokat is tudjuk egyszerre törölni a docker image prune paranccsal. (A -a kapcsolóval írjuk elő, hogy minden képfájlt töröljön, persze csak azokat, amelyekből nem indítottunk konténert.)

C:\Users\User>docker image prune -a

Amennyiben olyan képfájlból akarunk konténert indítani, amelyik nincsen letöltve, akkor a Docker automatikusan letölti azt a Docker HUB-ról, és el is indítja. (Vagyis indítás előtt nem kötelező a pull-lal lehúzni.) Próbáljuk elindítani a hello-world-öt azután, hogy az előbb letöröltük:

C:\Users\User>docker run hello-world
Unable to find image ‘hello-world:latest’ locally
latest: Pulling from library/hello-world
2db29710123e: Pull complete
Digest: sha256:faa03e786c97f07ef34423fccceeec2398ec8a5759259f94d99078f264e9d7af
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.

Az első üzenetből láthatjuk, hogy nem találja meg helyben a képfájlt, így elmegy érte a HUB-ra, letölti és elindítja.

Felmerülhet a kérdés, hogy lehet-e úgy konténert készíteni, hogy nem indítjuk el rögtön. Természetesen igen, a docker create paranccsal:

C:\Windows\System32>docker create −−name hello-world-stop hello-world
0e68d97b0115f377913a73b42662873a87d02e53c8b2a3b223e5c6a82424cfb2

Ha lekérdezzük a konténereket, láthatjuk hogy megtalálható közöttük a hello-world-stop nevű leállt konténer.

C:\Users\User>docker ps -a
CONTAINER ID IMAGE       COMMAND  CREATED       STATUS   PORTS NAMES
0e68d97b0115 hello-world “/hello” 5 seconds ago Created        hello-world-stop

Interaktív konténerek, képfájl készítése konténerből

Húzzunk le most egy olyan képfájlt, amely elindítva interaktív módon kommunikál a felhasználóval. Erre egy jó példa az ubuntu Linux disztribúció:

C:\Users\User>docker pull ubuntu
Using default tag: latest
latest: Pulling from library/ubuntu
e96e057aae67: Pull complete
Digest: sha256:4b1d0c4a2d2aaf63b37111f34eb9fa89fa1bf53dd6e4ca954d47caebca4005c2
Status: Downloaded newer image for ubuntu:latest
docker.io/library/ubuntu:latest

C:\Users\User>docker images
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
ubuntu       latest    a8780b506fa4   4 weeks ago   77.8MB

A konténert az interaktív használathoz a -it kapcsolókkal ubuntu-cont néven indítjuk el. (A -it a -i és a -t kapcsolók összevont alakja. Ezekkel érjük el, hogy a konténer a terminálból fogadja a billentyűleütéseket és ide írja ki a kimenetét.)

C:\Users\User>docker run -it −−name ubuntu-cont ubuntu
root@8453898cfdaf:/#

A megjelenő prompt már az ubuntu promptja. Próbáljuk ki, hogy felinstalláljuk rá a Midnight Commander-t:

apt-get update
apt-get install mc

Tökéletesen sikerül. A konténerünk ezek után már nem teljesen azonos a kiindulási képfájllal, hiszen abban nincsen benne az mc, míg a konténerben igen. Maga a konténer is réteges szerkezetű, alaprétege a képfájl tartalmazza (csak olvasható módon), és erre épülnek rá a módosítások. Ha magában az alaprétegben történik változtatás, akkor ez a változtatás az új rétegekbe kerül át. Mi ebből annyit látunk, hogy a konténer már tetszőlegesen változtatható. Lépjünk ki az ubuntu -ból az exit paranccsal. Ilyenkor a konténer le is áll.

Próbaképpen futtassunk egy új konténert az ubuntu képfájlból, és nézzük meg, hogy benne van-e az mc. Természetesen nem, hiszen az eredeti képfájlban sem volt. Vagyis most van két ubuntu konténerünk, az egyikben van mc, a másikban nincs.

Mint írtuk, az ubuntu konténerből az exit paranccsal léptünk ki. (Ez nem docker parancs, ezt a Linux hajtja végre.) Természetesen van lehetőség egy konténer docker paranccsal történő leállítására is. Amennyiben újra elindítjuk az ubuntu konténert, és átváltunk egy másik terminálra, ott a docker stop paranccsal leállíthatjuk:

C:\Users\User>docker run -it ubuntu
root@d20d72cebaa5:/#

C:\Users\User>docker ps
CONTAINER ID IMAGE  COMMAND CREATED        STATUS    PORTS NAMES
d20d72cebaa5 ubuntu “bash”  53 seconds ago Up 52 seconds   awesome_golick

C:\Users\User>docker stop d20
d20

Amennyiben gond adódna egy konténer futásában, és valamilyen oknál fogva nem állítható le, akkor a docker kill paranccsal ki is lőhető a rendszerből:

C:\Users\User>docker kill d20
d20

Nagyon egyszerű egy leállított konténerből képfájlt készíteni docker commit paranccsal. Csináljunk most a docker-cont konténerből egy docker-mc nevű képfájlt, jelezve a névvel, hogy már megtalálható benne az mc. A parancsban kapcsoló nélkül adjuk meg először annak a konténernek a nevét, amelyből készítjük (ubuntu-cont), majd az új képfájl nevét (ubuntu-mc), majd :

C:\Users\User>docker commit ubuntu-cont ubuntu-mc
sha256:f2fe86484b624e3cea85f25622b756a574381278e9f86e2956c4f3394cddfb39

Ezután ha elindítjuk ezt a képfájlt, már az mc-vel bővített ubuntu indul el. Ilyen egyszerű. (Három dolgot azonban meg kell jegyeznünk: Először azt, hogy a képfájlok készítésekor érdemes azt az elvet követni, hogy egy képfájl csak egy szolgáltatást nyújtson. Másodszor egy létező képfájlt csak akkor módosítsunk, ha tudjuk, mit miért teszünk. Természetesen ilyen egyszerű segédprogramok telepítése nem okoz gondot. Harmadszor pedig azt, hogy képfájlok létrehozására a legtisztább mód a Docker fájl használata, amelyről a későbbiekben még szó lesz.)

Amennyiben egy már futó konténerhez szeretnénk interaktívan csatlakozni, akkor erre a docker attach parancsot használhatjuk. (Ez vagy akkor történhet meg, ha eleve nem interaktív módban indítottuk a konténert, vagy ugyan abba, de egy másik terminálból is csatlakozni szeretnénk hozzá.)

C:\Users\User>docker run -it –name mc1 ubuntu-mc

Váltsunk át egy másik parancsorra, és csatlakozzunk a futó konténerhez:

C:\Users\User>docker attach mc1

Ilyenkor, amit az egyik terminálban csinálunk, az megjelkenik a másikban is.

A konténer életciklusa

Az előzőekben láthattuk, hogyan kell elindítani, leállítani, törölni, elkészíteni a konténereket. Foglaljuk most ezeket össze, és vezessük be a konténer állapotának a fogalmát. Ezeket az állapotokat, és az állapotokat előidéző parancsokat tartalmazza a következő ábra:

Egy konténernek ezek szerint 5 állapota lehet. Ezek közül az állapotok közül csupán a szüneteltetett (PAUSED) állapot jelenthet újdonságot. Egy futó konténernél megtehetjük azt, hogy futását felfüggesztjük a docker pause paranccsal. A szüneteltetett konténer futása ezután a docker unpause paranccsal folytatható. A kipróbálásához indítsuk el az ubuntu-mc konténert, és indítsuk el benne az mc-t, majd egy másik terminálban szüneteltessük a futását (docker pause). Ekkor azt tapasztalhatjuk, hogy ekkor az mc nem reagál a billentyűzetre. A konténer futásának folytatása után (docker unpause), az mc működése is folytatódik. (Természetesen nem csak az mc futása lett szüneteltetve, hanem a teljes Linuxé.)

C:\Users\User>docker run -it ubuntu-mc
root@e3e3c951bd71:/#

C:\Users\User>docker ps
CONTAINER ID IMAGE     COMMAND CREATED        STATUS   PORTS  NAMES
e3e3c951bd71 ubuntu-mc “bash”  20 seconds ago Up 19 seconds   affectionate_yonath

C:\Users\User>docker pause e3e
e3e

C:\Users\User>docker ps
CONTAINER ID IMAGE     COMMAND CREATED            STATUS           PORTS     NAMES
e3e3c951bd71 ubuntu-mc “bash”  About a minute ago Up About a minute (Paused) affectionate_yonath

C:\Users\User>docker unpause e3e
e3e

Hálózati szolgáltatások konténeren belül

Számos olyan képfájlt találunk a Docker HUB-on, melyek valamilyen hálózati szolgáltatást nyújtanak. Ilyenek lehetnek a webszerverek, FTP szerverek, SSH szerverek, stb. A kérdés csak az, hogy a host gépről hogyan lehet elérni ezeket a szolgáltatásokat? A megoldás nagyon egyszerű: A konténer indításakor a -p kapcsolóval megadjuk, hogy a konténeren belül futó szolgáltatás portja a localhost mely portján keresztül legyen elérhető.

Első példakánt indítsuk el (automatikusan le fog töltődni) a httpd képfájlt, mely egy Apache webszert tartalmaz:

C:\Users\User>docker run -d -p 8080:80 −−name apache2 httpd
fd46c18a070a3982ab0b7212eee5c4e2c8f2755327346acd0d29c95630667a3c

A -p kapcsoló után először a hoston használatos portot adjuk meg, majd utána kettősponttal elválasztva a dockeren belül futó szolgáltatás portját. Mivel belül egy webszerver fut, így ez a 80-as port lesz. Magyarázatra szorul a -d kapcsoló. Mivel a webszolgáltatást futtató konténer nem áll le automatikusan, ezért elfoglalja azt a terminált, amiben elindítottuk.  A -d kapcsolóval azt adjuk meg, hogy a konténer a háttérbe fusson, vagyis kapjuk vissza a promptot.

Ezután már csak az a dolgunk, hogy a hoszton megnyissunk egy böngészőt, és behívjuk a localhost:8080 címről az alapértelmezett oldalt.

A következő példában egy igazán nagyágyú képfájlt fogunk elindítani, ez pedig a felhőalapú fájltároló szolgáltatást nyújtó  nyílt forráskódú, ingyenesen használható owncloud alkalmazás. Letöltése és használata teljesen megegyezik az előzőével. (Ügyeljünk arra, hogy ezt ne ugyanarra a portra képezzük le, mint az előzőt, mert akkor hibajelzést kapunk!)

C:\Users\User>docker run -d -p 8081:80 -d owncloud
e3bda9f23a2b1bbac7cbc5813dd321a071f97488964d403ae47cc44dbd94ddc4

Nyissunk meg ezután egy böngészőt, és hívjuk be az alkalmazás nyitóoldalát a http://localhost:8081 hivatkozással.

Parancsvégrehajtás konténeren belül

Az előző két példa mindegyike egy webes szolgáltatást nyújtott számunkra. Felvetődhet azonban a kérdes, hogyan tudjuk a szolgáltatásokat konfigurálni. (Itt most olyan alapkonfigurációra gondolok, mint a httpd.conf módosítása.) Mivel a konténeren belül nem indult el a shell, így nem sok esélyünk van bármilyen fájlt módosítani. Szerencsére van arra megoldást, hogy a konténeren kívülről elindítsuk a konténeren belüli shell-t. (Illetve bármilyen parancsot kiadjunk.) Erre szolgál a docker exec parancs. Indítsuk el az apache2 nevű futó konténerünkön a bash shell-t. (Természetesen interaktív módban kell.)

C:\Users\User>docker exec -it apache2 bash
root@c8cd0ec334f2:/usr/local/apache2#

Ezután az alapparancsokkal módosíthatjuk a konfigurációs fájlt, de a korábban látott módon akár a Midnight Commander fájlkezelőt is felinstallálhatjuk, hogy könnyebb legyen a dolgunk. Ha most exit-tel kilépünk, akkor nem a konténert állítjuk le, hanem csak a shell-ből lépünk ki.

Persze nem csak a shell-t indíthatjuk el. Például kiadhatunk egy ls parancsot is, és megjelenik a terminálban az aktuális könyvtár tartalma.

C:\Users\User>docker exec apache2 ls -l

Lehetőség van arra, hogy a konténer elindításába helyezzük el azt a parancsot, amelyet a konténeren belül kell végrehajtani. (Mivel interaktív módban indítjuk, nem használhatjuk a -d kapcsolót is.)

C:\Users\User>docker run -it -p 8080:80 −−name apache2 httpd /bin/bash

Környezeti változók beállítása

Vannak olyan konténerek, amelyekben futó szolgáltatások számára különböző paramétereket kell beállítani. Ilyen például a MySQL, melynek a MYSQL_ROOT_PASSWORD környezeti változó értéke adja meg, hogy milyen jelszó tartozik az adatbázisszerver root felhasználójához. Töltsük le most a mysql képfájlt, és indítsuk el úgy hogy ezt a környezeti változót beállítjuk a -e kapsoló után:

C:\Users\User>docker pull mysql
C:\Users\User>docker run −−name mysql-server -e MYSQL_ROOT_PASSWORD=pass123 -d mysql

Ahhoz, hogy kipróbálhassuk a szervert, indítsuk el a konténeren belül a bash shell-t, majd csatlakozzunk az adatbázisszerverhez.

C:\Users\User>docker exec -it mysql-server bash
bash-4.4# mysql -u root -p
Enter password:

Konténerek összekapcsolása

Ahogyan ezt az előző fejezetben láttuk, a MySQL szervert parancssorosan már tudjuk kezelni, de mi lenne, ha azt a phpMyAdmin-on keresztül is megtehetnénk. Ehhez töltsük le a phpmyadmin képfájlt, indítsuk el, majd a konténert az előző csatoljuk a mysql konténerhez a −−link kapcsolóval. (Tehát a phpmyadmin konténerében nem kell benne lennie a mysql szolgáltatásnak.) A hozzákapcsolt konténer után kettősponttal elválasztva a link alias nevét adjuk meg.

C:\Users\User>docker pull phpmyadmin/phpmyadmin
C:\Users\User>docker run −−name phpmyadmin -d −−link mysql-server:db -p 80:80 phpmyadmin/phpmyadmin

Ezután már csak egy böngészőben be kell hívnunk a phpmyadmin nyitóoldalát a http://localhost hivatkozással.

Konténer fájlrendszerének elérése, kötetek

Térjünk vissza a httpd konténerhez, és változtassuk meg a nyitóoldalt úgy, hogy feltöltünk egy új fájlt a webszerverre. Ehhez először a host gépen létrehozunk egy index.html állományt tetszőleges tartalommal. Most már csak ezt a fájlt kellene az webszerver gyökérkönyvtárába másolni. Erre szolgál a docker cp parancsa.

c:\Users\User>docker run -d -p 80:80 −−name apache2 httpd
edeb74e8139b02ce05bbecf8a22b02de20d185b69216faa49348d02282149d21
c:\Users\User>docker cp index.html apache2:usr/local/apache2/htdocs

A böngészőben már ez jelenik meg nyitóoldalként. Ahelyett, hogy egyesével bemásoljuk a szükséges fájlokat a dockeren belülre, egy egész könyvtárat is felcsatolhatunk a host gépről a -v (−−volume) kapcsolóval. Ezt megosztott könyvtárat nevezzük a Docker használatában kötetnek (volume). (A −−mount kapcsolóval is gyakorlatilag ugyanezt a műveletet tudjuk elvégezni, itt a -v-t használjuk.) Készítsünk el a felhasználói könyvtárunkban egy web alkönyvtárat, és helyezzünk el benne egy index.html fájlt. Csatoljuk fel ezt a könyvtárat a tartalmával együtt a konténer belsejében a /mnt/test könyvtárba, és rögtön indítsunk egy bash shell-t is, hogy leellenőrizhessük az eredményt.

c:\Users\User>docker run -it -p 8080:80 -v c:/users/user/web:/mnt/test httpd /bin/bash

A parancs kiadása után ha bármit másolunk a web könyvtárba értelemszerűen az a /mnt/test könyvtárban is megjelenik. Természetesen ezt a könyvtárat az apache szerver gyökérkönyvtárába is becsatolhatjuk, így már innen veszi a nyitóoldalt. (Most shell nélkül a háttérben indítsuk el a konténert.)

c:\Users\User>docker run -d -p 80:80 -v c:/users/user/web:/usr/local/apache2/htdocs httpd

Így most bármit másolunk a hoszton a web könyvtárba, azt felhasználhatjuk a weblapunkon. Természetesen a folyamat fordítva is igaz, ha a konténerben futó alkalmazás bármit másol a felcsatolt könyvtárba, az megjeleni a hoszton. És ezek a fájlok akkor is megmaradnak, ha a konténert leállítjuk vagy törölöljük. Amennyiben olyan könyvtárat csatolunk fel a hosztról a -v kapcsolóval, amely nem létezik, a Docker létrehozza azt. (A −−mount ilyenkor hibajelzéssel leáll.)

A csatolást csak olvasható módon is elvégezhetjük, ha a csatolást kiegészítjük a :ro jelzővel. (Ilyenkor a konténerben futó alkalmazás nem tudja módosítani a felcsatolt könyvtár tartalmát, de a hoszton természetesen újabb fájlok még bemásolhatók.)

c:\Users\User>docker run -d -p 80:80 -v c:/users/user/web:/usr/local/apache2/htdocs:ro httpd

Képfájl készítése Dockerfile segítségével

Emlékezzünk vissza, korábban egy létező képfájlból elindított ubuntu konténerben felinstalláltuk a Midnight Commander fájlkezelőt, és ebből a konténerből készítettünk el egy új képfájlt. Ennek szebb módja, ha az alapoktól mi építjük fel a képfájlt. Ehhez a Dockerfile-t használhatjuk. A Dockerfile egy olyan rögzített szintaktikájú szöveges fájl, amely lépésről lépésre leírja, hogyan készüljön el a képfájl. Ezt az mc-vel bővített ubuntu elkészítését például a következő sorok írják le:

FROM ubuntu:latest
RUN apt-get update && apt-get upgrade -y
RUN apt-get install mc -y

Az eslő sor előírja, hogy induljunk ki az ubuntu utolsó kiadásából. A második sor ennek frissítését írja elő, míg a harmadik a Midnight Commander felinstallálását. (Ezek gyakorlatilag azok a parancsok, amelyeket az ubuntu-ban adnánk ki.) Korábban már írtunk arról, hogy egy Docker konténer réteges felépítésű. A fenti fájlban a FROM parancs hozza létre az alapréteget, és utána minden RUN parancs egy -egy újabb réteggel egészíti azt ki.

Írjuk be ezeket a sorokat egy Dockerfile nevű szöveges állományba, majd adjuk ki a docker build parancsot:

C:\Users\User>docker build -t ubuntu-mc2 .

A -t kapcsoló után a képfájlt nevét adjuk meg, míg utána a Dockerfile elérési útvonalát. (Itt az aktuális könyvtárban van a Dockerfile.) A parancs legenerálja a képfájlt,amit a szokásos módon el is indíthatunk. Próbáljuk ki, benne lesz az mc!

c:\Users\User>docker run -it ubuntu-mc

A képfájl készítésének lehetőségeit a következő ábra foglalja össze:

Második példában csináljunk egy olyan httpd-index nevű képfájlt, amely tartalmaz egy webszervert, melynek lecseréljük az alapértelmezett index.html oldalát. A Dockerfile és az új index.html az aktuális könyvtárban legyen.

FROM httpd:latest
COPY ./index.html /usr/local/apache2/htdocs

Magyarázatra talán az utolsó sor szorul csak: Az aktuális könyvtárból az index.html fájlt bemásolja az webszerver gyökérkönyvtárába.

Készítsük el a képfájlt a korábban megismert paranccsal:

C:\Users\User>docker build -t httpd-index .

A futtatása már rutinszerűen megy:

C:\Users\User>docker run -d -p 80:80 httpd-index

És a böngészőben már az új nyitóoldal jelenik meg.

A harmadik példában úgy fogunk egy python programot lefuttatni, hogy a teljes futási környezete a konténerben lesz. A futatandó python program két díszítősor között kiírja a programnak paraméterként átadott weboldal címét:

print("-----------------------------------------------")
import socket
import sys
nev = sys.argv[1]
ip = socket.gethostbyname(nev)
print(nev,"=> ",ip)
print("-----------------------------------------------")

Mentsük el ezt a fájlt getip.py néven, és készítsük el mellé a az alábbi Dockerfile-t:

FROM python:3
ADD getip.py /
CMD [ "python", "./getip.py", "www.iktblog.hu"]

A Dockerfile első sora írja le, hogy python3-ban szeretnénk futtatni a programot. A második sorban az ADD paranccsal bemásoljuk a Python programot tartalmazó fájlt a képfájl gyökerébe. (Az ADD parancs a COPY-hoz hasonlóan másolja be a fájlt.) A harmadik sorban a CMD paranccsal adjuk meg, hogy a konténer indulásakor milyen parancs hajtódjon végre. A CMD után idézőjelek között először a futtatandó parancsot, utána a parancs paramétereit adjuk meg. Itt lefuttatjuk a python-t, átadjuk neki paraméterül a python programot, annak pedig a feloldandó oldal nevét. Eredményül  megkapjuk a feloldott IP címet. (Minden konténer indulásakor csak egyetlen parancs hajtódhat végre, tehát ha több CMD-t is talál a Docker fájlban, csak a legutolsót értékeli ki.)

Jó lenne, ha nem lenne bedrótozva egyetlen oldal a képfájlba, hanem át lehetne adni azt paraméterként a konténernek annak az oldalnak a címét, amelynek az IP címét keressük. Ehhez módosítsuk a Dockerfile-t a következő módon:

FROM python:3
ADD getip.py /
ENTRYPOINT [ "python", "./getip.py" ]
CMD [ "www.iktblog.hu" ]

Az ENTRYPOINT parancs abban hasonlít a CMD-hez, hogy ebben is egy végrehajtandó utasítást a paramétereivel adunk meg. A különbség többek között abban van, hogy az ENTRYPOINT-ot nem lehet felülírni egy parancssori paraméterrel, míg a CMD-t igen. (A paramétert a docker run parancs végén adjuk meg.) Ha A docker talál egy ENTRYPOINT-ot és egy CMD-t is a Dockerfile-ban, akkor a végrehajtandó parancsot (és első paramétereit) az ENTRYPOINT-ból veszi, míg a többi paramétert a CMD-ből. A CMD után álló paraméterek viszont felülírhatók a docker run parancs végén álló parancssori paraméterekkel. Ez ad lehetőséget nekünk arra, hogy tetszőleges paramétert átadjunk a Python programnak.

Készítsük el az új képfájlt:

C:\Users\User>docker build -t python-getip2 .

Indítsuk el a konténert, és láthatjuk, hogy megjelent az iktblog.hu IP címe.

C:\Users\User>docker run python-getip2

Most indítsuk el úgy, hogy egy másik oldal (www.index.hu) nevét adjuk meg paraméterként, és már ennek az IP címét adja vissza. (Ha nemlétező nevet adunk meg, hibajelzést kapunk. A program áttekinthetősége miatt itt most nem foglalkoztunk a hiba lekezelésével.)

C:\Users\User>docker run python-getip2 www.index.hu

Az előző példákban csak olyan Python programot tudtunk futtatni konténerben, amelyekhez szükséges könyvtárakat a Python beépítve tartalmazta. Amennyiben külső könyvtárat is használni szeretnénk, a Dockerfile-on belül elő kell írnunk a feltelepítését. Ha például a numpy külső könyvtárat is szeretnénk beépíteni, akkor egészítsük ki a Dockerfile-t a következő sorral:

RUN pip install numpy

Ezután pedig már használhatjuk a könyvtár beépített függvényeit.

Docker képfájlok közzététele

Ahogyan mások által készített képfájlokat mi is beszerezhetünk a Docker HUB-ról, ugyanúgy a sajátjainkat is fel tudjuk tölteni oda. Ehhez először is regisztrálnunk kell a https://hub.docker.com/ oldalon, és a feltöltendő képfájlunkat el kell látnunk a felhasználói nevünkkel, ahogyan a következő példában is bemutatjuk. Legyen a regisztrált felhasználói nevünk dockeruser, és ezzel jelentkezzünk is be a docker login paranccsal. (Meg kell adni a felhasználói nevet és a jelszót.)

C:\Users\User>docker login

Mielőtt tovább lépnénk, ismerjük meg a képfájlok címkézésének módját. Egy képfájl teljes azonosítása a következőképpen történhet:

felhasználó_neve/képfájl_neve:cimke

A felhasználó neve azonosítja, hogy a docker HUB-on melyik felhasználóhoz tartozik ez a képfájl. A címke megadása általában a verziót jelzi, ha nem adjuk meg, automatikusan a latest használja. Fűzzük az utoljára elkészített képfájlunkhoz a dockeruser felhasználói nevet, és az 1.0 verziószámot:

C:\Users\User>docker tag python-getip2 dockeruser/python-getip2:1.0

Már csak fel kell tölteni a Docker HUB-ra docker push paranccsal:

C:\Users\User>docker push dockeruser/python-getip2:1.0

A Docker Desktop programban már láthatjuk is, hogy a képfájlunk megjelent a Remote Repositories oldalon. Ha letöröljük helyileg a képfájlt, akkor innen lehet már lehúzni:

C:\Users\User>docker pull dockeruser/python-getip2:1.0

Lehetőség van arra is, hogy a képfájlból tömörített állományt készítsünk, és így már más csatornákon is közzétehető. (Emailen, FTP-n, stb.) A tömörített állomány elkészítése a docker save, visszaállítása a docker load paranccsal történik.

C:\Users\User>docker save -o c:\Users\User\python-getip.tar python-getip2

C:\Users\User>docker load -i c:\Users\User\python-getip.tar

Hasonló feladatot látnak el a docker export és docker import parancsok, de ezek nem a képfájl, hanem a konténer teljes fájlszerkezetét mentik le egy tar tömörített fájlba, illetve onnan töltik azt vissza. Az alábbi ábra ezeket a lehetőségeket foglalja össze:

Hálózati kapcsolatok

Azt már láthattuk, hogy a konténereink automatikusan kapsolódnak a hoszton keresztül az Internethez, hiszen szabadon frissíthettük a bennük lévő alkalmazásokat. Sőt, ugyan nem próbáltuk ki, de a különálló hosztok egymást is el tudják érni. Ennek leellenőrzésére készítsünk el egy olyan képfájlt, mely egy httpd képfájlra épül, de tartalmazza az IP címek lekérdezésére és a pingetésre szolgáló segédprogramokat. A Dockerfile így fog kinézni:

FROM httpd:latest
RUN apt-get update -y
RUN apt-get install iproute2 -y
RUN apt-get install iputils-ping -y

Készítsünk belőle egy apache2-net képfájlt:

C:\Users\User>docker build -t apache2-net .

Indítsunk el két példányt belőle web1 és web2 néven, majd indítsunk bennük két külön terminálban interaktív módon egy-egy bash shell-t:

C:\Users\User>docker run -d -p 8081:80 –name web1 apache2-net
C:\Users\User>docker run -d -p 8082:80 –name web2 apache2-net
C:\Users\User>docker exec -it web1 bash
C:\Users\User>docker exec -it web2 bash

Kérjük le a két konténer IP címét az ip a paranccsal:

root@fcc1429156c4:/usr/local/apache2# ip a show dev eth0 | grep inet
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0

root@8b308495c6d6:/usr/local/apache2# ip a show dev eth0 | grep inet
inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0

Jól látható, hogy minden konténer példány rendelkezik saját IP címmel, és ezeken keresztül el is érik egymást. Pingessük meg web1-ről web2-t:

root@fcc1429156c4:/usr/local/apache2# ping 172.17.0.3
PING 172.17.0.3 (172.17.0.3) 56(84) bytes of data.
64 bytes from 172.17.0.3: icmp_seq=1 ttl=64 time=0.129 ms

Mi ez a hálózat? Kezdjük kicsit messzebbről. A Docker az egyes konténerek közözött különböző típusú hálózatokat tud létrehozni. Ezek a következők:

bridge Ez az alapértelmezett hálózati meghajtó. Ha nem specifikálunk másikat, ezt hozza létre a Docker a konténerekben futó applikációk számára. Több ilyen bridge típusú hálózat is létrehozható. Alapértelmezetten az ugyanahhoz a bridge-hez tartozó konténerek képesek egymással kommunikálni, míg a különbözőkhöz tartozók nem. Ezek segítségével az ugyanazon a hoszton futó konténerek.
host Host típusú hálózatnál a konténer közvetlenül használja a hoszt hálózatát, és nem kap külön IP címet. (Ha például a konténerben futó alkalmazás a 80-as portjához kapcsolódik, akkor a hoszt gép IP címén és 80-as portján lesz elérhető.)
overlay Overlay típusú hálózatoknál a különböző hosztokon futó konténerek is tudnak egymással kommunikálni.
ipvlan Az ilyen típusú hálózatban a telejs körű IPv4 és IPv6 feldolgozás mellett a VLAN cimkék kezezelése is rendelkezésünkre áll.
macvlan Lehetőséget teremt arra, hogy a konténerekhez MAC címet is rendeljünk.
none Ezzel a meghajtóval a konténert leválasztjuk mindenféle hálózatról.
network plugins Lehetőségünk van más gyártók hálózati meghatóprogramjait is beintegrálni.

 

Ezek szerint a konténereink az alapértelmezett bridge hálózathoz csatlakoznak. A rendelkezésre álló hálózatainkat a docker network ls paranccsal jeleníthatjük meg:

C:\Users\User>docker network ls
NETWORK ID    NAME    DRIVER SCOPE
7652463cb164  bridge  bridge local
990888a2fe9c  host    host   local
f0d9a7d832f2  none    null   local

A docker network inspect parancs egy adott hálózat részleteit írja ki. (Itt csak a parancs kimenetének egy részletét láthatjuk.)

C:\Users\User>docker network inspect bridge
“Containers”:
“Name”: “web2“,
“IPv4Address”: “172.17.0.3/16“,
“Name”: “web1“,
“IPv4Address”: “172.17.0.2/16“,

Azt tapasztalhatjuk, hogy a konténerekben korábban lekérdezett IP címeket láthatjuk itt is. Válasszuk le a konténereket most erről a bridge hálózatról a docker network disconnect paranccsal:

C:\Users\User>docker network disconnect bridge web1
C:\Users\User>docker network disconnect bridge web2

Ha most lekérdezzük a web1 és web2 konténereken belül az IP címet, azt tapasztaljuk, hogy a korábban látott eth0 hálókártyák eltűntek.Ha teljesen szabályosan akarjuk a konténereket leválasztani a hálózati szolgáltatásokról, akkor kapcsoljuk őket a none hálózathoz a docker network connect paranccsal:

C:\Users\User>docker network connect none web1
C:\Users\User>docker network connect none web2

Ha a két konténert egy önálló bridge típusú hálózathoz szeretnénk kapcsolni, akkor ezt a hálózatot először hozzuk létre a docker network create paranccsal, majd kapcsoljuk az új hálózathoz a konténereket. (Természetesen előtte le kell azokat választani a none hálózatról.)

C:\Users\User>docker network create officeNET
C:\Users\User>docker network connect officeNET web1
C:\Users\User>docker network connect officeNET web2

Mivel nem adtuk meg a létrehozandó hálózat típusát, bridge típusút hozott létre. Ha másikra lenne szükségünk, azt a -d kapcsolóval adhatjuk meg. Ha most újra lekérdezzük a konténereken belül az IP címet, akkor már újra megjelenik egy ethernet hálózati kártya új IP címmel.

Docker compose

Ahogyan korábban láttuk, a Dockerfile egy képfájl létrehozásának lépéseit írja le. A Docker azonban lehetőséget ad arra is, hogy több konténer futtatásával egy komplexebb feladatot is végrehjatsunk (ahogyan ezt a phpMyAdmin és a MySQL konténer esetében láttuk is). A Docker Compose segédprogram ebben nyújt segítséget. Központi eleme egy YAML fájl. (A YAML egy olyan adatok sorozatát leíró fájl, amelyet általában konfigurációs állományok létrehozására használnak.) Mivel ennek a bejegyzésnek a célja csupán a Docker alapjainak bemutatása, így csak egy példafájl elemzésén keresztül fogjuk megismerni ennek a technikának az alapjait. A példában egy alap wordpress-t fogunk telepíteni. (A példában bemutatásra kerülő yaml fájl a docker.com oldalról lett letöltve.) Az ehhez szükséges docker-compose.yaml fájl tartalma:

version: "3.3"

services:
  db:
    image: mysql:5.7
    volumes:
      - db_data:/var/lib/mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: somewordpress
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress
    
  wordpress:
    depends_on:
      - db
    image: wordpress:latest
    volumes:
      - wordpress_data:/var/www/html
    ports:
      - "8000:80"
    restart: always
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpress
      WORDPRESS_DB_NAME: wordpress
volumes:
  db_data: {}
  wordpress_data: {}

A version kulcsszóval adjuk meg, hogy melyik verziójú Docker Compose-t használjuk. A services szakaszban adjuk meg, milyen szolgáltatásokat (konténereket) fogunk telepíteni. Jelen esetben kettőt, a MySQL5.7-es verzióját db néven , és a legutolsó wordpress-t wordpress néven. Az image kulcsszó a letöltendő képfájlt azonosítja, míg a volumes-ben adjuk meg, mely könyvtárakat csatoljuk fel a hoszt-ról az adott konténerbe. Ezeket a külső becsatolandó könyvtárakat adjuk meg a legkülső volumes szakaszban. Ha még nem léteznek, a Docker létrehozza azokat.  Erre azért van szükség, hogy a konténer törlését követően az adataink (az adatbázis és a wordpress könyvtárai) megmaradjanak. A restart: always segítségével adjuk meg, hogy a konténer újraindításakor az adott szolgáltatás is induljon újra. Az environment szakaszban az adott szolgáltatások számára szükséges környezeti változók kerülnek beállításra. A depends_on kulcsszó után adjuk meg annak a konténernek a nevét, amelyet az adott szolgáltatás előtt el kell indítani. (Itt a wordpress előtt kell a db-t elindítani.) Végül a ports adja meg, hogy a konténerben futó szolgáltatás portja a hoszt mely portján jelenjen meg.

A docker-compose.yaml fájlból a docker-compose up paranccsal készíthetjük el a képfájlokat. (A compose fájl abban a könyvtárban legyen, ahol kiadjuk a parancsot.)

C:\Users\User>docker-compose up

Ha mindent jól csináltunk, a parancs lefutása után a futó konténereink között már látjuk is a két konténert, és a http://localhost:8000 hivatkozást kiadva egy böngészőben, elindul a WordPress telepítője.