16. Konténerek

A virtuális gépek nagyszerű lehetőséget biztosítanak arra, hogy egy fizikai hardveren különböző gépekre válasszuk szét az egyes szoftver rendszereket. Ideális pl. a Neptun számára, mely így önálló operációs rendszeren futhat és annak teljes működését a Neptun igényei szerint lehet beállítani. A virtualizáció során számos példát láthattál annak alkalmazására, a régi rendszerek archiválásától az időkiszolgáló szerverig. A virtuális gépeket másnéven szerver szintű virtzualizációként is szokták említeni, mivel a virtualizáció tárgya esetükben a teljes operációs rendszer. Kis alkalmazások számára viszont nem célszerű ezt a megoldást választani, hiszen minden virtuális gép igényel egy alap infrastruktúrát, szükségszerűen futtatja a rendszermagot és tartalmaz számos, az operációs rendszerhaz tartozó egyéb szoftvert a rendszerkönyvtáraktól a beépített alap alkalmazásokig. Csupán egy webszervizen alapuló API működtetésére ez a megoldás tehát rendkívül pazarló lenne, ezért az ilyen feladatokhoz operációs rendszer szintű virtualizációt szokás alkalmazni, melynek egyik legismertebb (de nem az egyetlen) változata a Docker. A konténerizáció során az alkalmazott operációs rendszer közös, és abban csak mikroszervizek futnak, melyek tipikusan tartalmazzák az alkalmazást és annak futtató környezetét is. Egy webalkalmazás esetén például a webszervert, az ehhez az alaklmazáshoz tartozó adatbázis-szervert és pl. a php értelmezőt is. Ebben a kontextusban a virtualizált környezeteket konténereknek nevezzük, ezek egymástól teljesen elválasztva futnak, de egymással együttműködő konténer csoportok is létrehozhatók, melyek saját belső névtérrel is rendelkeznek.

Konténer és virtuális gép

A konténerizáció alkalmazása számos más előnyt hoz. Amellett, hogy a kis alkalmazások felhőszolgáltatásokban (AWS, Google Cloud stb.) történő futtatásának tipikus technológiája, erre alapozva egyszerűen lehet ún. hibatűrő (HA - High Availability) rendszereket is felépíteni. Egy ilyen esetén a konténerek több példányban, több szerveren működnek, így az egyik kiesése esetén a teljes rendszer még képes ellátni a feladatát. Újabb előnyt jelent, hogy a konténerekben alkalmazott szoftverek verziója állandó, így elkerülhetők a fejlesztés és a publikált végtermék futtató környezetének különbségei - a fejlesztés során alkalmazott környezet a konténerbe lesz “becsomagolva”, így az pontosan meg fog egyezni a végleges változatban alkalmazottal.

16.1. A Docker

A Docker az egyik legnépszerűbb konténerizációs rendszer, melynek alapját a Linux kernelben már régóta jelen levő konténerizációs képesség adja. A Docker a legelterjedtebb szoftver, amely erre alapulva egy modern, kényelmes környezetet nyújt a fejlesztők és az üzemeltetők számára Windowson, MacOS-en és Linuxon is. Maga a szoftver ingyenes, de a grafikus felület kezelőszoftveréért, a Docker Desktopért egy bizonyos éves árbevétel felett (ez e sorok írásakor 10M dollár) fizetni kell.

Ebben a fejezetben a konténerizációs feladatokat a Dockeren keresztül ismerjük meg.

16.1.1. A Dockerhub

A konténerek népszerűségéhez nagyban hozzájárult a Dockerhub létrehozása és publikussá tétele. Ez a szolgáltatás nem csak számos kész konténert tartalmaz, hanem különböző könyvtárakban a saját konténerünk felépítéséhez szükséges image-eket is. Rengeteg alkalmazás konténerizált változata tölthető le innen, pl. különéle Linuxok, webszerverek, adatbázis-kezelő rendszerek, futtató környezetek. Az egyes változatok különböző változatban is rendelkezésre állhatnak. A Dockerhub webes felületének címe https://hub.docker.com, itt tudod megkeresni a szükséges kész konténereket. A Dockerhubra a saját készítésű konténereidet is feltöltheted, és amennyiben publikussá teszed, az mások számára elérhetővé válik (ehhez viszont regisztrálnod kell).

16.1.2. A docker telepítése

Linuxon a telepítés nagyon egyszerű, az alábbi módon hajthatod végre:

koczka@columbo:~# sudo apt install docker.io

A telepítés sikerességét egyszerűen a docker verziószámának megjelenítésével ellenőrizheted:

koczka@columbo:~# docker -v
Docker version 20.10.12, build 20.10.12-0ubuntu2~20.04.1

Windowson a telepítés bonyolultabb, a részletes útmutatót a Docker weboldalán találod. Fontos, hogy Windows rendszerű gépeken Windows és Linux konténerek is futtathatók, mi az utóbbival dolgozunk, ezért egy Linux kernelre is szükséged lesz, melyet a Docker indítása után egy üzenetablakban a program jelezni is fog. A Linux kernel letöltéséhez látogass el a Microsoft weboldalára, és a WSL2 Linux kernel update package for x64 machines link mentén töltsd le az aktuális verziót. Valószínűleg újra kell indítanod a gépedet, és újra elindítanod a Docker Desktop alkalmazást. A program ablakának bal alsó sarkában levő docker ikon zöld háttere jelzi, hogy a rendszer működésre kész (esetenként ez 20-30 másodpercig is eltart).

A Docker indulása

16.2. A konténerek kezelése

Ahhoz, hogy a konténerekkel hatékonyan tudj dolgozni, meg kell ismerkedned azokkal a parancsokkal, amelyekkel azokat kezelni lehet. Enélkül, különösen egy fejlesztési folyamat során, a felesleges konténerek és image-ek előbb-utóbb rengeteg erőforrást foglalnak el a gépeden. Kezdjünk egy egyszerű példával, egy olyan konténer letöltésével és elindításával, amely csak egy egyszerű szöveget ír ki. Az első konténerünk a hello-world lesz, amelyet Dockerhubról fogunk letölteni és elindítani a docker run paranccsal.

koczka@columbo:~# docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
2db29710123e: Pull complete
Digest: sha256:18a657d0cc1c7d0678a3fbea8b7eb4918bba25968d3e1b0adebfa71caddbc346
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/

For more examples and ideas, visit:
https://docs.docker.com/get-started/

Elég sok szöveg jelent meg a képernyőn, lássuk, mi történt! Először is, a konténer még nem áll rendelkezésre a helyi gépen (Unable to find image ‘hello-world:latest’ locally) ezért azt először a Dockerhubról le kell tölteni. Ez a folyamat a következő néhány sorban követhető, majd annak végén elindul a konténerben levő program, és megjeleníti a Hello from Docker kezdetű szöveget.

Az egyes konténereknek több változata is elérhető lehet a Dockerhubon. Amennyiben nem határozod meg a szükséges verziót, automatikusan a legutolsó verzióval fogsz dolgozni. Egy eltérő változat kiválasztása a konténer:verzió formában történik, pédául php:7.4, python:3, ubuntu:16.04 vagy ubuntu:latest (a latest értelemszerűen az elérhető legfrissebb változatot jelenti).

Tip

A későbbiekben, amikor a konténer technológiát egy pontosan meghatározott és tesztelt környezet előállításának érdekében építed fel, pontos verziókat érdemes meghatároznod. A :latest megadásával előfordulhat, hogy egy későbbi időpontban, amikor a konténeredet újraépítik, már egy új, nem pedig az általad használt és tesztelt környezet épül fel újra.

Ahhoz, hogy egy konténert csak letölts, de ne indítsd el, a pull paramétert kell használnod. Az alábbi példában a docker letölt egy másik, busybox nevű konténert, de nem indítja azt el (az alábbi kimenet akkor lenne látható, ha ezt korábban még nem töltöttem volna le):

root@columbo.uni-eszterhazy.hu:~#  docker pull busybox
Using default tag: latest
latest: Pulling from library/busybox
f5b7ce95afea: Pull complete
Digest: sha256:9810966b5f712084ea05bf28fc8ba2c8fb110baa2531a10e2da52c1efc504698
Status: Downloaded newer image for busybox:latest
docker.io/library/busybox:latest

A már letöltött konténert elindítani szintén a docker run paranccsal lehet.

Az egyszer elindított konténerek alapesetben nem tűnnek el a rendszerből, hanem lefutásuk után is megmaradnak, így a megfelelő parancsokkal újra elindíthatók. Az újbóli elindításukhoz először meg kell ismerkedned egy újabb paranccsal, ami a rendszerben levő konténereket listázza ki. Az alábbi példában a docker ps -a megjeleníti az összes, akár futó, akár már lefutott konténerrel kapcsolatos információkat. A docker ps önmagában csak azokat jelenítené meg, amelyek még futnak:

root@columbo.uni-eszterhazy.hu:~#  docker ps -a
CONTAINER ID   IMAGE         COMMAND    CREATED          STATUS                          PORTS     NAMES
6b9d1f725626   hello-world   "/hello"   33 minutes ago   Exited (0) About 7 minutes ago             silly_albattani

Minden konténernek van egy belső azonosítója (a példában 6b9d1f725626) és egy neve, amit ha a létrehozásakor nem határoztad meg, a rendszer generál (ez a példában silly_albattani). Emellett látható a konténer alapjául szolgáló, korábban letöltött image neve, a létrehozás ideje és a státusza is, esetünkben az alkalmazás már 7 perccel ezelőtt kilépett (Exited) a visszatérési értéke pedig 0 volt. A belső azonosítónak kiemelt szerepe van a konténerek kezelésében, például a konténer újbóli elindításában, ahogy az alábbi példában látható.

root@columbo.uni-eszterhazy.hu:~#  docker start -a 6b9d1f725626

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/

For more examples and ideas, visit:
https://docs.docker.com/get-started/

Tip

A konténer azonosítók elég hosszú véletlenszámok, amelyeket nem szükséges pontosan begépelni, elég csupán annyit, amelyből az már egyértelműen azonosítható. A fenti parancs ez alapján így lett volna rövidíthető: docker start -a 6, mivel egyetlen más konténer azonosító sem kezdődik 6-ossal.

Hogy miért nem a docker run parancsot használtam? Azért, míg a docker start a meglevő konténert indítja el újra, a docker run a konténer egy újabb példányát hozza létre. Ennek következtében az alábbi ábrán látható módon a már lefutott konténerek “felszaporodnak”, így a fejlesztés során időről időre takarítod kell (vagy gondoskodnod róla, hogy a leállt konténerek automatikusan törlődjenek).

root@columbo.uni-eszterhazy.hu:~#  docker run hello-world

Hello from Docker!
This message shows that your installation appears to be working correctly..
.
.
root@columbo.uni-eszterhazy.hu:~#  docker ps -a
CONTAINER ID   IMAGE         COMMAND    CREATED          STATUS                     PORTS     NAMES
f87cb9248b50   hello-world   "/hello"   6 seconds ago    Exited (0) 9 seconds ago             brave_hawking
6b9d1f725626   hello-world   "/hello"   45 minutes ago   Exited (0) 4 minutes ago             silly_albattani

Ha a docker run parancsot úgy szeretnéd végrehajtani, hogy a konténer futásának befejeződésekor azt a rendszer automatikusan törölje, használd a --rm paramétert.

root@columbo.uni-eszterhazy.hu:~#  docker run --rm hello-world

Hello from Docker!
This message shows that your installation appears to be working correctly.
.
.
CONTAINER ID   IMAGE         COMMAND    CREATED          STATUS                     PORTS     NAMES
f87cb9248b50   hello-world   "/hello"   4 minutes ago    Exited (0) 4 minutes ago             brave_hawking
6b9d1f725626   hello-world   "/hello"   50 minutes ago   Exited (0) 9 minutes ago             silly_albattani

A konténer azonosítót vagy annak nevét egaránt használhatod arra, hogy egy futó konténert leállíts. A docker stop brave_hawking egyenértékű a docker stop f87cb paranccsal, és leállítja a meghatározott konténert. A docker kill brave_hawking az operációs rendszer kill -9 parancsához hasonlóan fogja a konténert leállítani, amit abban az esetben érdemes használnod, ha a konténer a stop hatására nem fejezi be a futását.

A meglevő, már szükségetelen konténereket a docker rm paranccsal lehet törölni, paraméterként a konténer azonosítóját kell megadni. Egy lépésben több konténer is törölhető:

root@columbo.uni-eszterhazy.hu:~#  docker rm f8 6b
f8
6b

A docker ps -a kimenete most már üres, azaz nincs egy konténerünk sem:

root@columbo.uni-eszterhazy.hu:~#  docker ps -a
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

Tip

Az összes leállított konténer törlésére egy másik paramétert is kínál a docker, ez a docker container prune. Amennyiben Unixon dolgozol, ezzel ekvivalens a docker rm $(docker ps -a -q -f status=exited).

16.2.1. Konténer image-ek

A konténerek mellett a letöltött image-ek kezelésére is szükség van. Fontos, hogy lásd a különbséget: az eddig bemutatott parancsok alanyai az elindított, és (valaha) futó konténerek példányai voltak, és nem voltak hatással azokra a fájlokra, amelyekből “példányosítottuk” őket. Ezeket a docker image kezdetű parancsokkal tesszük meg. A gépeden levő image-ek listáját a docker image list paranccsal ellenőrizheted. Ebben a két eddig használt konténer látható.

root@columbo.uni-eszterhazy.hu:~#  docker image ls
REPOSITORY    TAG       IMAGE ID       CREATED         SIZE
busybox       latest    ff4a8eb070e1   2 weeks ago     1.24MB
hello-world   latest    feb5d9fea6a5   13 months ago   13.3kB

Az image-eknek is vannnak ID-ik, és bár ezeket elsősorban a törlésükkör használjuk, ugyanerre lesz szükséged akkor is, amikor a saját konténeredet a Dockerhubra akarod majd feltölteni. A felesleges image-ek törléséhez a docker image rm parancs paramétereként fel kell sorolnod azok ID-it.

root@columbo.uni-eszterhazy.hu:~#  docker image rm ff fe
Untagged: busybox:latest
Untagged: busybox@sha256:9810966b5f712084ea05bf28fc8ba2c8fb110baa2531a10e2da52c1efc504698
Deleted: sha256:ff4a8eb070e12018233797e865841d877a7835c4c6d5cfc52e5481995da6b2f7
Deleted: sha256:0b16ab2571f4b3e0d5a96b66a00e5016ddc0d268e8bc45dc612948c4c95b94cd
Untagged: hello-world:latest
Untagged: hello-world@sha256:18a657d0cc1c7d0678a3fbea8b7eb4918bba25968d3e1b0adebfa71caddbc346
Deleted: sha256:feb5d9fea6a5e9606aa995e879d862b825965ba48de054caab5ef356dc6b3412
Deleted: sha256:e07ee1baac5fae6a26f30cabfe54a36d3402f96afda318fe0a96cec4ca393359

Tip

  • Ha egy image, pl. a hello-world több verzója is jelen van a rendszerben, a docker rmi hello-world -f paranccsal az összeset törölheted.
  • Az imege-ek nem törölhetők addig, amíg abból létrehozott konténer létezik a rendszerben még akkor sem, ha annak futása már véget ért. Azokat a korábban ismertetett docker rm paranccsal először törölni kell. (A teljesség kedvéért, ha minden kötél szakad, a --force segítségével megkerülheted ezt a korlátot.)

Egy konténer nem feltétlenül zárt, abba szükség esetén be lehet lépni, és a futó környezeteben tudod ellenőrizni, tesztelni a működését. Ehhez a konténert interaktív terminál módban kell elindítanod a -it paraméter megadásával. Az alábbi példában egy Ubuntu Linux 20.04-es konténert indítok el úgy, hogy az egy parancsértelmezőt (bash) futtasson. A --restart=always paraméter hatására a kontner futása a kilépéskor automatkusan újraindul, ennek hatásaként működőképes marad akkor is, amikor a parancssorból a Ctrl-D megnyomásával vagy az exit paranccsal kilépsz.

root@columbo.uni-eszterhazy.hu:~# docker run --restart=always -it ubuntu:20.04 bash
Unable to find image 'ubuntu:20.04' locally
20.04: Pulling from library/ubuntu
eaead16dc43b: Pull complete
Digest: sha256:450e066588f42ebe1551f3b1a535034b6aa46cd936fe7f2c6b0d72997ec61dbd
Status: Downloaded newer image for ubuntu:20.04
root@a023792c965b:/#

A promptban levő véletlenszám, mint host név (ez egyébként a konténer ID-je) jelzi, hogy most már a konténer belsejében vagy, és parancsaid abban a környezetben futnak le.

Amennyiben a konténerből kilépsz, ahhoz utólag az attach paraméterrel kapcsolódhatsz újra. Értelemszerűen meg kell adnod a kiválasztott konténer valamelyik azonosítóját azonosítóját, a konténer ID-t vagy a nevét:

root@columbo.uni-eszterhazy.hu:~#  docker ps -a
CONTAINER ID   IMAGE          COMMAND   CREATED         STATUS         PORTS     NAMES
a023792c965b   ubuntu:20.04   "bash"    3 minutes ago   Up 3 minutes             stoic_beaver

root@columbo.uni-eszterhazy.hu:~#  docker attach a0
root@a023792c965b:/#

Ha a --restart=always paraméterrel indított konténert le akarod állítani, használd a docker stop <konténer_id> parancsot, ha a kilépés nem történik meg szabályosan, erőltetheted azt a docker kill <konténer_id> paranccsal.

root@columbo.uni-eszterhazy.hu:~#  docker stop a0
a0

Tip

  • Az operációs rendszereknél tanult screen-szerű működés a konténerek esetén is elérhető a Ctrl-p, majd a Ctrl-q megnyomásával.

16.3. Saját konténer készítése

A fenti példákban kész konténereket használtam, amelyeket a Dockerhub-ról töltöttem le. Természetesen lehetőség van saját konténerek készítésére is (persze, hiszen a Dockerhub konténerei is így készülnek). Egy új ilyen létrehozásához egy fájlt kell készíteni, a célszerűség érdekében ennek Dockerfile a neve, amit egy önálló könyvtárban érdemes elkészíteni. Készítsünk egy nagyon egyszerű konténert, ami egy üdvözlő szöveget ír ki!

FROM ubuntu:latest
MAINTAINER Koczka Ferenc <koczka.ferenc@uni-eszterhazy.hu>
RUN echo "Üdvözlet a konténerből!" >/hello.txt
CMD ["cat", "hello.txt"]

Az egyes sorok jelentése a következő:

1. FROM:Szinte alig van olyan eset, hogy egy konténert “a semmiből” éíptünk fel, szinte mindig valamilyen alap rendszerből indulunk ki. Ez tipikusan valamilyen, a konténer programja számára szükséges környezet, pl. php, python stb., de lehet egy erősen leegyszerűsített operációs rendszer is. Ez ebben a példában az Ubuntu Linux. A paraméter rendszerint tartalmaz egy verziószámot is, az 16.04, 20.04 különböző változatokat határoz meg. A már említett latest az utolsó. legfrissebb változatot jelenti, de ennek használatával kapcsolatban érdemes óvatosnak lenni: ha egy konténer szoftverének kifejlesztését és tesztelését egy adott környezetetben végezted el, a működése során célszerű mindvégig megtartani ezt.
2. MAINTAINER:A Dockerfile készítőjének adatai, összetett rendszerek építésekor ez az adat fontos lehet. Ezt idővel különféle META tagek fogják leváltani.
3. RUN:Az itt megadott parancsok a konténer felépítése során futnak le. Ebben a példában egy hello.txt nevű fájlt hozok létre a konténer gyükérkönyvtárában azért, hogy annak tartalmát a konténer futásakor majd megjelenítsem.
4. CMD:A konténer elindulásakor indítandó programot és paramétereit is meg kell határozni, erre szolgál a CMD. Paramétereként egy tömböt kell megadni, ennek első eleme a végrehajtandó parancs, a továbbiak pedig az egyes paraméterek.

Tip

  • Egyes Linux változatok kifejezetten alkalmasak a konténerben való futtatásra elsősorban a nagyon kis méretük miatt. Ilyen pl. az Alpine Linux.

A konténer felépítése a docker build paranccsal történik. A -t kapcsolóval meg kell adni a konténer nevét és verziószámát, ez a példában hello:1. Meg kell adni a Dockerfile helyét is, mivel példánkban ez az aktuális könyvtár, egyszerűen egy pontot tettem a sor végére. Az Enter megnyomásával elindul a konténer felépítése, melyhez először letölti a szükséges fájlokat a Dockerhubról (ubuntu:latest), majd nyomon követhető a Dockerfile feldolgozása. Az elkészített konténer nem indul el, csupán az image jön létre, amit a docker images paranccsal lehet ellenőrizni.

root@columbo.uni-eszterhazy.hu:~/Docker/1#  docker build -t hello:1 .
Sending build context to Docker daemon  2.048kB
Step 1/4 : FROM ubuntu:latest
latest: Pulling from library/ubuntu
301a8b74f71f: Pull complete
Digest: sha256:7cfe75438fc77c9d7235ae502bf229b15ca86647ac01c844b272b56326d56184
Status: Downloaded newer image for ubuntu:latest
---> cdb68b455a14
Step 2/4 : MAINTAINER Koczka Ferenc <koczka.ferenc@uni-eszterhazy.hu>
---> Running in 0bd52d7aca59
Removing intermediate container 0bd52d7aca59
---> 2d4f265b2f3c
Step 3/4 : RUN echo "Üdvözlet a konténerből!" >/hello.txt
---> Running in 4f83294b2435
Removing intermediate container 4f83294b2435
---> b8842a25e223
Step 4/4 : CMD ["cat", "hello.txt"]
---> Running in c2504067af12
Removing intermediate container c2504067af12
---> d019f405528e
Successfully built d019f405528e
Successfully tagged hello:1

A konténer indítása a már lassan megszokott docker run paranccsal történik, melyhez hozzáadtam a fejlesztés során hasznos --rm paramétert is, hogy elkerüljem a tesztelés során létrejövő újabb és újabb példányok felhalmozódását a rendszerben. A megjelenő üzenet szerint lefut az első konténerem:

root@columbo.uni-eszterhazy.hu:~/Docker/1#  docker run --rm hello:1
Üdvözlet a konténerből!

Lássunk egy újabb példát! Készítsünk egy konténert, amelyben egy már ismert játékprogram, a Ninvaders fut! Lehet, hogy találnánk kész ilyet, de inkább készítsük el a sajátunkat!

FROM ubuntu:16:04
MAINTAINER Koczka Ferenc <koczka.ferenc@uni-eszterhazy.hu>
RUN apt-get update && apt-get -y install ninvaders
WORKDIR /usr/games
CMD ["./ninvaders"]

Ebben a konténerben a RUN paramétereként lefutó parancs telepíti a játékprogramot, a WORKDIR pedig aktuális könyvtárként a /usr/games-t állítja be. A játékprogram telepítését a RUN parancs paramétereként megadott telepítő parancsok végzik. A CMD végül elindítja az aktuális könyvtár ninvaders programját. A konténer felépítése a docker build -t ninvaders:1 ., az indítása a docker run --rm ninvaders:1 paranccsal történik.

16.4. Hálózati kapcsolatok

A konténerek egyik jellemző alkalmazási területe a webszervizek létrehozása, melynek már hálózati kapcsolatra is szüksége van. Egy ilyen alkalmazás esetén mind a Dockerfile-nak, mind a konténer indítását végző parancsnak további paraméterek megadására van szüksége. Az alábbi példában egy mini webszolgáltatást, egy web alapú Hello World konténert hozunk létre. Hogy a megoldás minél egyszerűbb legyen, nem foguk önálló webszervert alkalmazni, hanem a Php beépített webszerverét fogjuk alkalmazni. Két fájlra lesz szükségünk, az első neve index.php, a másodiké pedig a már ismert Dockerfile kell, hogy legyen.

Az index.php file tartalma:

<?php echo "Hello World";

A Dockerfile pedig:

FROM php:7.4
MAINTAINER Koczka Ferenc <koczka.ferenc@uni-eszterhazy.hu>
WORKDIR /var/www/html
ADD index.php .
EXPOSE 80:80
CMD ["php", "-S", "0.0.0.0:80"]

Ebben néhány új direktívát használtam, a jelentésük az alábbi:

WORKDIR:A konténer belsejében levő aktuális könyvtárat állítja be, amit az ADD és a CMD is kihasznál majd.
ADD:Egy fájlt vagy könyvtárat másol be a konténerbe. A szintaxisa a szokásos forrás-cél sorrend. A példában az index.php fájlt másoljuk be a WORKDIR által meghatározott aktuális könyvtárba, amit a . szimbolizál. Ennek hatására a már elkészített index.php a konténer /var/www/html könyvtárába kerül.
EXPOSE:A konténerben futó webszerver belső portja a 80-as, a konténeren belül ezt ezen a porton lehetne elérni. Amennyiben tehát a konténer belsejében futó egyéb alkalmzásnak erre szüksége lenne, erre a portra kellene csatlakoznia. A külső elérésre azonban egy másik portot szokás használni azért, mert egy gépen számos más konténer is futhat, és az ütközések elekerülése érdekében azok nem használhatnak azonos portokat. Ezért a belső portszám mellett meg kell adnod egy külső portot is, melynek forgalmát a Docker a konténer belsejében futó alkalmazás portjára irányítja. Az EXPOSE 80:80 hatására a konténer a külvilág számára a 80-as porton lesz elérhető, amit a Docker a konténer belsejébe, szintén a 80-as porton működő php szerver számára továbbít.

A konténer felépítését a már szokásos paranccsal végezzük:

root@columbo.uni-eszterhazy.hu:~/Docker/2# docker build -t miniweb:1 .

A konténer indítása azonban egy extra paraméter is igényel minden olyan esetben, amikor az abban futó szoftvernek hálózati kapcsolatokra is szüksége van, ezt a -p külső_port:belső_port formában kell megadni.

root@columbo.uni-eszterhazy.hu:~/Docker/2# docker run -it --rm -p 80:80 --name miniweb miniweb:1

A futó konténer által előállított weblapot a konténert futtató gép nevével vagy IP címével érheted el egy böngészőben. Amennyiben a konténert a saját gépeden futtatod, a cím a http://localhost legyen.

Php konténerben

Bár a 80-as port alkalmazása általános a webszolgáltatások működtetése során, könnyen lehetséges, hogy a gépeden ezt a portot már valamilyen más alkalmazás használja, ezért sem az, sem a konténer nem képes kommunikálni. Ilyen esetekben a külső portot meg kell változtatni, és egy másikat kell alkalmazni.

Amennyiben a konténer külső portját a 8080-as portra szeretnéd áthelyezni, a következőket kell tenned:

  • A Dockerfile-ban a EXPOSE 80:80-at módosítanod kell EXPOSE 8080:80-ra.
  • A konténer indításakor a -p 8080:80 paramétert kell megadnod.
  • A böngésző címsorában is meg kell adnod az új portot, így az így módosul: http://localhost:8080.

16.5. Megosztott könyvtárak

Az eddig látott konténerekkel kapcsolatban nem hangsúlyoztam ki, hogy azok egy “felépített” egészet alkotnak, azaz minden változás, amely a futásuk során azok belsejében történik, a konténer lebontásával és újraépítésével elvész, ami egyaránt lehet előny és hátrány is. Előnyt jelent pl. egy sérülékeny website esetén, mert az ellene végrehajtott sikeres támadás után elég a konténert újraépíteni, és minden újra az eredeti (igaz, továbbra is sérülékeny) állapotában érhető el. Ez a működés ugyanakkor súlyos adatvesztés forrása is lehet: ha pl. az előző példánkban szereplő webszerverre annak felhasználói fájlokat töltenek fel, vagy abban adatokat rögzítenek, a konténer az újraépítése során ezeket nem fogja megtartani.

A megoldást egy olyan könyvtár adja, amely fizikailag a konténert futtató operációs rendszer fájlrendszerében van, de azt összerendeljük a konténer egy belső könyvtárával. Egy ilyen összerendelést alkalmazva az adott könyvtár tartalma tehát a konténeren kívül, a valódi fájlrendszerben lesz, annak tárolása a konténertől függetlenül történik, így “túléli” teljes újraépítés folyamatát is.

Egy ilyen összerendeléshez a tulajdonképpen csak a konténer indításának parancsát kell módosítanunk, maga a Dockerfile nem tartalmaz erre vonatkozó beállításokat. Az előző példához képest annyit kell változtatnunk, hogy a weblapunk alapjául szolgáló index.php fájlt most nem másoljuk be a konténerbe, hanem azt egy új könyvtárban, az src-ben helyezzük el. A könyvtárunk struktúrája tehát az alábbi lesz:

root@columbo.uni-eszterhazy.hu:~/Docker/2#  tree
.
├── Dockerfile
└── src
    └── index.php

A Dockerfile-ból eltávolítottam az ADD direktívát:

FROM php:7.4
MAINTAINER Koczka Ferenc <koczka.ferenc@uni-eszterhazy.hu>
WORKDIR /var/www/html
EXPOSE 80:80
CMD ["php", "-S", "0.0.0.0:80"]

A konténer indításához tehát egy újabb paraméterre, a --mount-ra van szükség. Ennek további három adatot kell megadni: a kötés típusát, a külső (forrás) és a belső (cél) könyvtárakat. Esetünkben a kötés típusa bind lesz, ezt abban az esetben használd, ha egy valós könyvtárat egy konténerben levővel szeretnél összekapcsolni. (Más típusú összekapcsolások is alkalmazhatók, de ezekkel jelen jegyzetben nem foglalkozunk). A source a fizikai könyvtár, a target pedig a konténerben levő könyvtár abszolút elérési útját adja meg. Az alábbi példában az előző konténert így indítom el, ennek eredményeként a webszerver fájljait a konténer futása közben, a src könyvtár tartalmának szerkesztésével lehet módosítani.

docker run -it --rm -p 80:80 --mount type=bind,source="$(pwd)"/src,target=/var/www/html  --name miniweb miniweb:1

Tip

A forrás könyvtár abszolút elérési útvonalának megadása nemcélszerű abban az esetben, ha a konténert egy másik, jelenleg ismeretlen gépen szeretnénk futtatni, hoszen nem ismerjük annak könyvtárszerkezetét. Ezért használjuk a $(pwd) kiértékelést a parancssorban, ami az aktuális könyvtár elérési útját adja vissza. Erről a shell scripteknél tanultunk bővebben.

Most tehát egy olyan webszerver konténert készítettem, amely az src könyvtár tartalmát szolgáltatja a kliensek felé. Lássuk, mi történik, ha az index.php tartalmát módosítom az alábbira:

<?php phpinfo();

A böngésző tartalmát frissítve abban az új tartalom jelenik meg annak ellenére, hogy a konténert most nem indítottam újra.

Php konténerben