Docker - 2. rész: Első használatba vétel

Attila | 2022. 12. 26. 10:40 | Olvasási idő: 4 perc

Címkék: #Container #Docker #Image #node.js #Virtualizáció (Virtualization)

Az előzőekben talpra állítottuk a Docker virtualizációs platformot Windows operációs rendszeren. Most pedig rátérünk a Docker világ részleteire. Ez is kvázi tekinthető egy kezdésnek, mivel most vagyunk azon a ponton, hogy már simán fut a legfrissebb Docker Desktop a gépünkön. Itt létrehozzuk a saját image-ünket (amit már nem az internetről töltünk le), feltöltjük tartalommal, ami egy alkalmazás struktúrája lesz, megfuttatjuk a konténerjét, és ki is tudjuk próbálni a böngészőnkben. Ráadásként mindezt publikáljuk a Docker Hub-ra, és egy nyilvános webhelyen elérhetővé is tesszük a konténerizált webes alkalmazásunkat! Ez így nem semmi! De aggódni nem kell, mert mindent pontról-pontra végigveszek! Vágjunk is bele!
docker-getting-started

Bevezetés, emlékeztetőül

A Docker Desktop teljeskörű dokumentációja itt érhető el: https://docs.docker.com/desktop/

A docker-tutorial konténer elindítása a Docker Desktop-ban:

Eredmény megtekintése a böngészőben:

A folyamatot végigcsináljuk, hogy megismerjük a Docker működését részleteiben és egy Node.js alapú webes alkalmazás futtatását tudjuk szemléltetni. A Node.js-ről eddig még nem esett szó, de nem kell megijedni, nem azt fogjuk megtanulni ebben a leírásban, hanem csak felhasználjuk az abban a környezetben írt webes alkalmazást. Mi is az a Node.js? Ha a weboldalát nézzük, akkor azt írják róla, hogy ez egy aszinkron, eseményvezérelt JavaScript futtatóprogram, amelyet skálázható hálózati alkalmazások építésére terveztek. Hú, ez elég összetetten hangzik, de megijedni nem kell tőle. A Javascript napjaink egyik legnépszerűbb programozási nyelve, és ha már ez így van, akkor a fejlesztők közösségének az volt az ötlete, hogy ne csak a webes alkalmazások kliens oldalán használjuk, hanem szerver oldalon is hasznos lenne az alkalmazása, két legyet üthetünk ezzel egy csapással (egy programozási nyelv részletes megismerésével kliens, és így, szerver oldalon is tudunk majd operálni). Jól hangzik, ugye? De mi most nem fogunk belemenni a részleteiben, csak használunk egy ilyen alkalmazást a példáinkban és csak minimálisan, gyakorlatilag statikus tartalmak, feliratok átírásán túl, nem fogunk belenyúlni és módosítani az alkalmazás működésébe.

Ezért térjünk is vissza rögtön a Docker-re és futtassuk a következő parancsot a Windows Terminal-unkban, ami létrehozza és futtatja a konténerünket:

docker run -d -p 80:80 docker/getting-started

A parancs nem teljesen pontos, lásd az alábbi felsorolásban a megjegyzés részt. A parancs a konténer elindítását fogja eredményezni. A futtatási parancs paraméterezése:

  • -d: a konténert a háttérben futtatjuk
  • -p 80:80: a 80-as port-ot nyitjuk meg a gazda „gépen”, ami kvázi a konténer, így érjük el a webszerverét, hiszen a 80-as porton tipikusan a webszerverek szólíthatók meg és így tudjuk ezt a böngészőben megtekinteni a localhost/tutorial URL-en. Ez egy nginx webszerver.
  • docker/getting-started: az image neve, amit a konténer használ alapjául fájlrendszerként.
    • Megjegyzés: ez 2022.12.26-án már nem a docker/getting-started image, hanem a docker101tutorial nevű image (annak is a legutóbbi – latest – verziója), de valószínűleg ezt a dokumentációs részt nem frissítették megfelelően. Ez nem okoz majd gondot, mivel itt én mindenre magyarázatot fogok adni, hogy megfelelően tudjuk kezelni a Docker-t.
  • A konténer neve ez lesz: docker-tutorial, ami a docker101tutorial nevű image-re épül, azt használja fájlrendszerként (ez még nem egy random generált név a konténernél, de a későbbiekben majd ilyenekkel fogunk találkozni).

Mi az a "container image"?

A container (konténer) definíciójáról már van némi fogalmunk, de mi is az az image (kép)? Amikor futtatunk egy konténert, akkor az egy elszigetelt (izolált) fájlrendszert használ, amit a "container image" biztosít a számára. Mivel az image tartalmazza a konténer fájlrendszerét, annak tartalmaznia kell mindent, ami az alkalmazás futtatásához szükséges – minden függőséget, beállítást, szkripteket, bináris programokat és sok egyéb mást is. Az image ezen felül még a konténer egyéb konfigurációját is tartalmazza, például a környezeti változókat, egy alapértelmezett futtatandó parancso(ka)t és egyéb metaadatokat is.

A későbbiekben mélyebben belemerülünk az image-ek témakörébe, és olyan témákkal foglalkozunk, mint a rétegezés, a legjobb gyakorlatok (best practices) és még sok egyéb mással is, úgyhogy minden érthetővé válik majd.


Az alkalmazásunk

A bemutató további részében egy alkalmazást fogunk építeni és működtetni, ami egy egyszerű "todo", vagyis elvégzendő tevékenységi lista menedzselő alkalmazás lesz. Ez a Node.js keretrendszerben működik, de az se ijedjen most meg, aki még sosem használta a Node.js-t, mert nem lesz hozzá szükségünk különösebb Javascript tudásra. Mi most csak egy MVP (Minimum Viable Product) terméket fogunk használni, amellyel tudjuk szemléltetni a Docker környezet működését és hogy mire vagyunk vele képesek.

Az alkalmazás kezdeti teendői

Az alkalmazás maga (forráskódja) egy-az-egyben "letölthető" saját magunktól, a saját konténerünk kiszolgálása által a böngészőnkben: http://localhost/assets/app.zip (ha esetleg futna már alapértelmezetten webszerver a gépünkön, például egy Apache a XAMPP vagy WAMP által, akkor ezt érdemes leállítani, mielőtt most a Docker-rel dolgoznánk).

A zip fájl tartalmát bárhova kicsomagolhatjuk a gépünkön, majd utána a mappáját nyissuk meg a kedvenc kódszerkesztőnkkel. Én a Visual Studio Code-ot fogom ehhez használni.

A projektnek az app mappanevet adtam és ide másoltam be a zip fájlban lévő app mappa tartalmát, így tehát bekerült ide két további almappa (spec és src), valamint van itt egy package.json és egy yarn.lock fájl is.

Építsük fel az alkalmazás "container image"-ét (izolált fájlrendszerét)

Ehhez szükségünk van a projekt gyökerében egy új fájlra: Dockerfile néven (kiterjesztés nélkül!).

A VSCode meg is kérdezi rögtön (jobb alul), hogy akarjuk-e telepíteni a Docker kiterjesztést. Én telepítem, mivel még többször fogom ezt használni a jövőben.

Ezután visszatérhetünk a Dockerfile tartalmához és feltölthetjük kóddal:

FROM node:18-alpine
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "src/index.js"]

Most építsük fel a "container image"-t a következő paranccsal a terminal-ban (terminal-t tudunk indítani a VSCode-ban is, ha úgy kényelmesebb):

docker build -t getting-started .

Ez a parancs felhasználja az iménti Dockerfile-t az építkezéshez. Ehhez letölti majd a node:18-alpine image-t kiindulásnak. Beállítja a munkakönyvtárat az /app mappának. Átmásolja oda a teljes tartalmát, telepíti az alkalmazás függőségeit a yarn csomagkezelő segítségével. A -t kapcsoló után pedig simán elneveztük itt az image-t getting-started néven. A parancs végén lévő . (pont) mondja meg a Docker-nek, hogy hol kell keresnie a Dockerfile-t, ami a "receptúra" az image felépítéséhez.

Meg is jelent az új image a listában:

Indítsuk az alkalmazás konténerjét!

Az image-ünk így már megvan, futtathatjuk az alkalmazás konténerét.

Adjuk ki a parancsot a terminal-ban:

docker run -dp 3000:3000 getting-started

Megjegyzés: a -d kapcsoló együttesen is használható a -p kapcsolóval (-dp). Így a konténer elszigetelten fog működni, és a portot összekapcsoljuk: a kiszolgáló gép 3000-es portját csatlakoztatjuk a konténer 3000-es portjához.

A parancs futtatásának eredménye egy véletlenszerűen generált angol név, ami a neve lesz az új konténerünknek (azonosítója is egyben, hiszen, amíg ilyen nevű konténer létezik a listában, addig ilyet nem generál újra). A Docker Desktop konténer listájában pedig meg is jelent ez.

Mivel a konténer neve egy véletlenszerűen generált név lesz, szóval biztosan más a neve nálam, mint nálatok. Böngészőben pedig így néz ki az alkalmazás: http://localhost:3000/

Nyugodtan tesztelhetjük a működését: adhatunk hozzá új elemeket és majd el is végezhetjük őket: ha a bal oldali checkbox-okra kattintunk (áthúzásra kerül a nevük), vagy törölhetjük is őket, ha a jobb oldalukon lévő kuka ikonra kattintunk.

Így tehát van egy "dockerizált" (más néven, konténerizált) alkalmazásunk, aminek a fájlrendszerét az új, felépített image-ünk biztosítja, a futása pedig egy elszigetelt konténerben történik meg, amit mi egyszerű felhasználóként a böngészőnk segítségével érünk el a saját gép (localhost) 3000-es portján keresztül. Itt van a két jelenleg futó konténerünk a Docker Desktop vezérlőpultjában:


Frissítsük az alkalmazásunkat

Megnézzük azt is, hogy milyen könnyű frissíteni az alkalmazásunk forráskódját. Egy nagyon egyszerű módosítást hajtunk végre, ami az üres lista állapotában kerül kiírásra. Jelenleg ez a szöveg olvasható: "No items yet! Add one above!", cseréljük le erre: "You have no todo items yet! Add one above!".

Keressük meg a VSCode-ban az src / static / js / app.js fájlt és annak is az 56. sorát, ott kell átírni a fenti szöveget az újra, majd mentsük el a fájlt. Ha most ellenőrizzük a böngészőnkben az alkalmazást és kukázzuk az eddig felvitt feladatokat, akkor viszont még azt tapasztaljuk, hogy a korábbi szöveg maradt meg, nem került frissítésre tehát a szöveg. Ez azért van, mivel a változások érvényesítéséhez újra kell építenünk először az image-et…

docker build -t getting-started .

… aztán pedig a konténert is:

docker run -dp 3000:3000 getting-started

Erre viszont hibaüzenetet kapunk, hiszen ezzel a névvel már van futó konténerünk, ami ráadásul be is van kötve a 3000-es portokra. Ezt viszont nagyon egyszerűen tudjuk orvosolni: először állítsuk le, majd töröljük a régi konténert a Docker Desktop vezérlőpultján, majd futtassuk újra az iménti parancsot.

Ha most frissítjük a böngészőnkben az alkalmazás lapját, akkor a frissített eredményt láthatjuk:

Két problémát tudunk azonosítani a fenti műveletsor kapcsán:

  1. A felvitt adataink eltűnnek (törlődnek), amikor egy új konténert hozunk létre és indítunk el.
  2. Egy aprócska változtatáshoz (szöveg átírása) túl sok mindent kellett újra végrehajtanunk, újraépítenünk, ami nem túl kényelmes így.

Úgyhogy a következő bejegyzésben fogjuk majd orvosolni ezeket a kellemetlenségeket. De előtte egy rövid kitérőt teszünk és megnézzük, hogy hogyan lehet megosztani az elkészített image-eket másokkal.


Alkalmazás megosztása

Docker image-eket a Docker Hub-on lehet megosztani a legegyszerűbben.

Készítsünk egy repository-t!

Regisztráljunk aztán jelentkezzünk be a Docker Hub oldalra (https://hub.docker.com/).

  1. Kattintsunk a Create Repository gombra.
  2. Adjuk a projektünknek ezt a nevet és állítsuk publikusra: getting-started
  3. Kattintsunk a Create gombra.

Image feltöltése a Repo-ba

Ha futtatjuk ezt a parancsot:

docker push docker/getting-started

Akkor egy hibaüzenetet kapunk vissza, ami azt jelzi a számunkra, hogy nem létezik ilyen nevű (docker/getting-started) image a gépünkön.

Ki tudjuk listázni viszont az image-einket (vagy pedig meg tudjuk nézni őket a Docker Desktop-ban is):

docker image ls

Ez eddig rendben is van, látjuk a listában az image-ünket. Viszont össze kellene kötni az alkalmazásunkat a Docker Hub-bal, úgyhogy adjuk ki a parancsot a terminálban:

docker login -u <felhasználóneved>

(Sárga háttérszínnel jelölöm, amit ki kell cserélni a parancsban, mert mindenkinek a saját Docker Hub-os felhasználónevét kell oda beírnia <>-k nélkül.) Utána kérni fogja a jelszavadat is, amit ha jól adsz meg, vissza is igazolja egy "Login Succeeded" üzenettel.

Ezután nevezzük át az image-ünket úgy, hogy belevesszük a Docker Hub-os felhasználónevünket. A docker tag parancs használható erre:

docker tag getting-started <felhasználóneved>/getting-started

Ekkor egy új image jön létre a getting-started mintájára, csak bekerül elé a Docker Hub-os felhasználónevünk is.

Akár a Docker Desktop Image-eket tartalmazó ablakából is tudjuk push-olni az image-ünket:

(A felhasználónevemet kékkel kitakartam és a későbbiekben is kitakarom majd, ha nem felejtem el 😊 )

Vagy pedig a VSCode termináljában kiadhatjuk a parancsot:

docker push <felhasználóneved>/getting-started

Alapértelmezetten a latest tag-et fogja hozzáadni a rendszer.

Az eredmény a Docker Hub-on látható, ha megnyitjuk az új Repo-nkat (korábban ez a "Tags and scans" szekció üres volt):

A sikeres futtatás után én a korábbi (felhasználónév nélküli) image-emet töröltem (előtte a kapcsolódó konténert érdemes leállítani).

Játék a publikus Image-ünkkel

Kipróbálhatjuk, hogy a publikus image-ünket meg tudjuk-e futtatni egy nyilvános helyen (persze, csak korlátozott ideig áll majd rendelkezésre, mivel csak kipróbálás céljára használjuk).

Keressük fel ezt a weboldalt és jelentkezzünk be: https://labs.play-with-docker.com/ A Docker Hub-os felhasználónk fog kelleni hozzá. A bejelentkezés elfogadása után indíthatjuk is a "játékot":

4 óráig használhatjuk, bal felül el is indult a visszaszámlálás.

Kattintsunk itt rá, az "+ ADD NEW INSTANCE" gombra, aminek következtében a fő tartalmi részen meg fog nyílni nekünk egy terminál. Írjuk be oda ezt a parancsot:

docker run -dp 3000:3000 <felhasználóneved>/getting-started

Az eredménye itt látható:

Ahogy az látható: én egy "no space left on device" hibaüzenetet kaptam, ami a parancs újra- és újrafuttatásával sem oldódott meg, emiatt én vártam egy picit és utána, ha megint futtattam, akkor már jó lett:

Ha most az oldalon felül rákattintok az "OPEN PORT" gombra, majd beírom a felugró ablakba a "3000"-est (idézőjelek nélkül csak a számot), akkor egy új lapon meg kell jelennie az alkalmazásnak.

A generált webcím megnyílik, ami kiadja az alkalmazást, és már vittem is fel két teszt feladatot, ahogy az az iménti képen látszódik is.

Itt most megismertük, hogy hogyan lehet megosztani az image-einket azáltal, hogy feltöltöttük egy nyilvános repo-ba. De ennyi kitérő után, majd a következő bejegyzésben visszatérünk az előző szekcióban jelzett problémákhoz, elsőként az adatok (beírt feladatok a todo listában) elvesztését fogjuk kiküszöbölni.

Kövessetek a Facebook-on, és ha tetszik a munkám, akkor támogassátok néhány euróval a blog és az oldal fennmaradását a "buymeacoffee" (kávé) ikon útmutatásait követve.