Több konténer együttese¶
Amikor több konténert együttesen szeretnénk kezelni, a Docker Compose eszköztárával definiálhatjuk és automatizálhatjuk a teljes alkalmazás infrastruktúráját egy YAML fájlban. Ez a módszer rendkívül hasznos az egyes szolgáltatások közötti kommunikáció kialakításában és a függőségek kezelésében is. Ebben a fejezetben bemutatjuk, hogyan építhetünk fel komplex, több összetevőből álló rendszereket úgy, hogy azok elszeparált konténerek együtteseként működjenek. Ehhez megismerjük a Docker Compose alapjait, az ehhez szükséges fájlstruktúrákat, és gyakorlati példákon keresztül próbáljuk ki a többkonténeres alkalmazások felépítését. A fejezetben az alábbiakról lesz szó:
A docker-compose elvi működése.
A docker-compose fájl tartalma, felépítése és szabályai.
A docker-compose használata és különböző paraméterei.
Gyakorlati példák.
Szükséges eszközök: |
Windows, MacOS vagy Linux operációs rendszerű számítógép, telepített Docker szoftverrel. |
Feldolgozási idő: |
kb. 3 óra. Gyakorlati feladatok megoldására további 1 óra. |
Az eddigiek alapján már szinte tetszőleges konténert fel tudunk építeni, vagy már kész konténereket is használatba tudunk venni. Egy feladat megoldásához a gyakorlatban viszont nem mindig elég egyetlen konténert használni. Egy website-nak a webszerver mellett adatbáziskezelő-rendszerre is szüksége lehet, és a megoldást nem a minden alkalmazást „egybegyúró” komplex konténerek, hanem elemi konténerek együttműködése jelenti. Ebben a felépítésben tehát a webalkalmazást futtató konténer mellett egy másik, az adatbáziskezelőt működtető konténert is el kell indítanunk és biztosítanunk kell a kommunikációt is köztük. A konténereket futtató host gép tehát nem egyetlen nagy adatbázis motort működtet, amely minden webkonténer számára adatbázis szolgáltatást nyújt, hanem minden egyes webhez önálló, konténerben futó adatbáziskezelő tartozik.
Konténerek együttese¶
Felmerülhet a kérdés, hogy miért nem egyetlen konténerben tároljuk az összes olyan komponenst, amely egy adott szolgáltatás működtetéséhez szükséges összes összetevőt egyben tartalmazza? Miért nem egyetlen konténer tartalmazza a webszervert és az adatbázist is? A választ a konténerek tervezési architektúrája adja: az egyes szolgáltatások önálló konténerbe helyezésével az egyes alkotóelemek könnyen cserélhetők, frissítésük esetén önálló egységet alkotva nincsenek kihatással a rendszer más részeire.
Egy komplex rendszert alkotó konténerek együttesét multi-konténereknek nevezik. Ebben az egyes konténerek egymással is kommunikálnak, ehhez azonban egy nyilvánosan nem elérhető, csak a konténerek közt működő belső hálózatot használnak. Az együttműködő konténerek közötti belső kapcsolatokat ezért deklarálni kell. Mivel a Dockerfile csak egyetlen konténer leírására szolgál, ezért egy több konténert tartalmazó egység leírását egy másik típusú fájlban írjuk le, a felépítésükre pedig a docker-compose parancs szolgál majd.
Amikor egy több konténer együtteséből álló konténer együttest kell indítanunk, a docker-compose programot kell használnunk. Ez alapértelmezésben egy docker-compose.yaml nevű fájlt keres, és az ebben leírtak szerint építi fel az egyes konténereket és alakítja ki kapcsolataikat. Ha esetleg nem találkoztál még .yaml fájllal, ezek a .json fájlokhoz hasonló strukturált fájlok, amelyek egy sajátos szintaxis mentén adatok szervezett leírását teszik lehetővé. Bár ezek az egyszerű szövegfájlok tetszőleges text editorral szerkeszthetők, fontos szerepe van a behúzások helyes írásának – ez a .yaml fájlok szerkesztésének egyik neuralgikus pontja. A helyzetet tovább nehezíti, hogy a docker-compose.yaml fájlnak a docker fejlődésével több verziója is megjelent – ezek sajnos nem feltétlenül kompatibilisek egymással, így a .yaml fájl első sora rendszerint az alkalmazott verzió számát írja le. Azt, hogy az éppen rendelkezésre álló docker-compose program melyik .yaml verziót támogatja, a dokumentáció tartalmazza.
Kezdjünk megint egy egyszerű példával! Az első docker-compose.yaml fájlunk csak egyetlen konténert definiál majd, ebben először a .yaml fájl előállítására koncentrálunk. A későbbiekben ezt fogjuk újabb és újabb konténerekkel bővíteni. Először tehát készítsünk egy konténert egy egyszerű PHP alkalmazással, amelyhez használjuk a Php-be épített webszervert! A web könyvtárunk helyezkedjen el a konténeren kívül, a webroot mappában! A projekt szerkezete így az alábbi lesz:
php-webserver
├── webroot
│ ├── index.php
├── docker-compose.yaml
└── run
A webroot/index.html fájl most is csak egyetlen index.php fájlt tartalmaz, amely a már ismert phpinfo() függvénnyel a használt PHP verziószámát és kapcsolódó információkat jelenít meg:
<?php
phpinfo();
?>
A konténer felépítésére szolgáló docker-compose.yaml tartalma az alábbi lesz:
version: "3.0"
services:
php:
container_name: columbo.uni-eszterhazy.hu_www
image: php:8.1
ports:
- 8080:80
command: php -S 0.0.0.0:80 -t /var/www/html
volumes:
- ./webroot:/var/www/html
Ebben az egyes elemek jelentése a következő:
- version:
A
docker-compose.yamlverziószáma, ez példánkban 3.0.- services:
Ebben a blokkban soroljuk fel a kialakítandó konténereket. Minden konténert névvel kell ellátni, és a leírását egy egységgel (tabbal) beljebb kell kezdeni.
- php:
A létrehozandó konténer neve, amelyet egy kettőspont követ. A példában egyetlen szolgáltatást (service-t) definiáltunk, amelynek a
phpnevet adtuk.- container_name:
az egyes konténereket el kell nevezni, amit ez a direktíva tesz lehetővé. Bár a név ezen a ponton nem tűnik túlságosan fontosnak, egy olyan szerver esetén, ami számos konténert futtat, ez teheti egyértelművé, hogy melyek tartoznak egy szolgáltatáshoz. A webszervert futtató konténer neve végződhet pl.
_www-re, az adatbázis szerveré_mysql-re. Így az egy szolgáltatáshoz tartozó konténerek üzem közben is egyértelműen azonosíthatók, áthelyezésükkor, törlésükkor a módosítandó rendszerelemek egyértelműen meghatározhatók. A futó konténerek nevei most is a docker ps paranccsal jeleníthetők meg:CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 05d37de60c5a php:8.1 "docker-php-entrypoi…" About a minute ago Up About a minute 0.0.0.0:8080->80/tcp columbo.uni-eszterhazy.hu_www
- image:
ez a direktíva határozza meg, hogy melyik kész, a DockerHubról származó konténert szeretnénk felhasználni. Ez példánkban a php 8.1-es változata, amit a weblap kimenete is megmutat majd.
- port:
Ahogyan az a Hálózatkezelés a konténerekben c. fejezetben már láttuk, a konténerek hálózati kommunikációjának engedélyezéséhez meg kell adnunk, hogy a konténer melyik porton fogadjon kéréseket, és azokat melyik belső portra továbbítsa. A
8080:80beállítás azt eredményezi, hogy a „külvilágtól” a 8080-as porton jövő kéréseket a konténer belsejében működő alkalmazás 80-as portjára kell továbbítani. Ezen a porton a Php-be épített webszerver „hallgatózik”, az oda küldött kéréseket fogadja, feldolgozza, és a megfelelő tartalommal válaszol.- command:
a konténer elindítása után ezt a programot kell elindítani. A
docker-compose.yamlfájlban nem kell korábban látott a tömbszerű megadást használni, a parancs és paraméterei egymás után sorolhatók fel. A Php esetében a-S-t követően a webszerver által használni kívánt IP cím és portszám szerepel, a-tután pedig a web fájlokat tartalmazó könyvtár neve olvasható. A0.0.0.0azt jelenti, hogy a szolgáltatás minden rendelkezésre álló IP címen elérhető lesz, használata pedig azért célszerű, mert így nem kell megadni azt a jövőbeni, jelenleg még ismeretlen IP címet, amit majd a konténer futtatásakor a felhőben kapni fog.- volumes:
itt lehet leírni a konténer egy belső könyvtárának, és a fizikai fájlrendszer könyvtárainak vagy fájljainak összerendelését. Mindkét paraméter megadásakor abszolút elérési utat kell használni. A példa webszervere ennek az összerendelésnek köszönhetően képes hozzáférni a fizikai fájlrendszerben levő webroot könyvár tartalmához.
A konténer felépítését a már említett docker-compose up paranccsal lehet elvégezni, amennyiben démonként, a háttérben szeretnénk azt futtatni, egy -d kapcsolóval kell azt kiegészíteni. Ekkor értelemszerűen a konténer üzenetei sem látszanak majd, így ezt a módot inkább a production változatban célszerű választani.
koczka@columbo:~/docker-php$ docker-compose up
Starting columbo.uni-eszterhazy.hu_www ... done
Attaching to columbo.uni-eszterhazy.hu_www
columbo.uni-eszterhazy.hu_www | [Thu May 16 14:40:09 2024] PHP 8.1.28 Development Server (http://0.0.0.0:80) started
A futtatáshoz szükséges parancsokat esetleg egy scriptben is összegezhetjük, ez a későbbiekben kényelmesebbé teszi a fejlesztés alatt működő konténer újraindítását azzal, hogy az indítást megelőzően leállítja a korábbi futó változatot is.
#!/bin/bash
docker stop columbo.uni-eszterhazy.hu_www
docker rm columbo.uni-eszterhazy.hu_www
for I in $(docker image ls -q)
do docker image rm $I --force
done
docker-compose up
A következő példában ezt a web konténert kiegészítjük egy mySql adatbázis szerverrel is.
Php szerver mySql adatbázissal¶
A docker-compose igazi ereje tehát azokban az esetekben mutatkozik meg, amikor egy szolgáltatást több konténer együttes működése valósít meg. Ilyenkor a docker-compose.yaml nem csak egy service blokkot tartalmaz, hanem többet is: minden egyes szolgáltatást megvalósító konténerhez tartozik majd egy.
A példánkban a Php és mySql együttműködését valamelyest megnehezíti a már bemutatott korlát: a php értelmező a mySql adatbázisokhoz csak egy extensionön (bővítményen) keresztül tud kapcsolódni, emiatt a Dockerhubból letöltött változat önmagában most sem lesz használható. Ezért most a kész konténer helyett egy Dockerfile-lal felépített egyedi konténert fogunk alkalmazni, ezt ugyanúgy fogjuk felépíteni, ahogyan azt már a Saját konténer készítése fejezetben láttuk. Az egyetlen különbséget a konténer .yaml fájlba történő beépítése jelenti.
A projektünk struktúrája most az alábbiak szerint épül fel. A webroot könyvtárban továbbra is a webszerver fájljait tároljuk. A mySql-képes php motor előállítását egy alkalmas Dockerfile alapján végezzük, amit a php könyvtárban helyezünk el. A mySql adatbázis fájljait nem tárolhatjuk a konténer belsejében, hiszen akkor a konténer eldobásakor és újraépítésekor az abban tárolt adatok elvesznének – ezért ezeket a host gép fájlrendszerében, a mysql-data könyvtárban helyezzük el. (Ezt nem kell manuálisan létrehozni, a folyamat során automatikusan elkészül majd.) Az initdb.sql a kezdeti adatbázis tartalmát definiálja majd, a neve tetszőlegesen megválasztható. A docker-compose.yaml pedig a projekt könyvtár gyökerébe kerül.
php-webserver
├── webroot
│ └── index.php
├── php
│ └── Dockerfile
├── mysql-data
├── initdb.sql
├── docker-compose.yaml
└──run
Először foglalkozzunk a mySql-képes PHP értelmező előállításával! Az ehhez szükséges Dockerfile felépítése meglehetősen egyszerű, de érdemes odafigyelni arra, hogy azt az erre a célra létrehozott php könyvtárban helyezzük el. Most a php 8.1-es változatából indulunk ki, és az abban levő docker-php-ext-install programmal elvégezzük a mysqli extension hozzáadását. Az így létrejövő konténerben működő php interpreter már képes lesz kapcsolódni egy mySql adatbázishoz.
FROM php:8.1
RUN docker-php-ext-install mysqli
A docker-compose.yaml most két konténert definiál, a már ismert php -t és a mysql-t. A php esetében viszont most nem használhatjuk a korábban látott image direktívát, mert az nem adná hozzá a mysql extensiont az értelmezőhöz. Ehelyett most a build: php direktívát használjuk, ennek hatására a build folyamat belép a php könyvtárba, és az ott található Dockerfile könyvtár alapján felépíti a php konténert.
A mySql szerver konténerének előállítása is tartalmaz néhány új elemet. A konténer neve, az image és a használt port jelentése megegyezik az eddig látottakkal. Az image beállítására itt is szükség van: a konténer belsejében szereplő /var/lib/mysql könyvtár fájljait a konténeren kívül, a mysql-data könyvtárban kell tárolnunk azért, hogy az a konténertől függetlenül megőrizhető legyen.
version: "3.0"
services:
php:
container_name: columbo.uni-eszterhazy.hu_www
build: php
ports:
- "8080:80"
command: php -S 0.0.0.0:80 -t /var/www/html
volumes:
- ./webroot:/var/www/html
mysql:
container_name: columbo.uni-eszterhazy.hu_mysql
image: mysql:8.0
ports:
- "3306:3306"
volumes:
- ./mysql-data:/var/lib/mysql
- ./initdb.sql:/docker-entrypoint-initdb.d/initdb.sql
environment:
MYSQL_USER: webdb
MYSQL_PASSWORD: webdb
MYSQL_DATABASE: webdb
MYSQL_ROOT_PASSWORD: nagyonTitKosJelszo
MYSQL_ROOT_HOST: "0.0.0.0/0"
A konfigurációs fájl új eleme az environment blokk, amely a konténeren belül létrehozandó környezeti változókat definiálja. Mivel a változók definiálásának ez meglehetősen egyszerű módszere, gyakran használjuk különféle kezdeti paraméterek meghatározására, esetünkben az adatbázis hozzáférés paramétereinek beállítására. A használható változók neveit a konténer dokumentációja tartalmazza, a MySql esetében ezek a következők:
- MYSQL_USER:
a konténer az első indulásakor létrehozza ez a felhasználót.
- MYSQL_PASSWORD:
definiálja a
MYSQL_USERfelhasználó jelszavát.- MYSQL_DATABASE:
a konténer az első indulásakor létrehozza az itt megadott adatbázist, és hozzáférést biztosít az imént létrehozott felhasználó számára.
- MYSQL_ROOT_PASSWORD:
amennyiben szükséges, ebben a változóban lehet megadni az adatbázis szerver rendszergazdai jelszavát, de erre a legtöbb esetben nincs szükség.
- MYSQL_ROOT_HOST:
a root hozzáférés az itt megadott hálózatra korlátozható, így más hálózatokból még érvényes név és jelszó birtokában sem lehetséges a hozzáférés – ennek az adatbázis védelmében van szerepe.
A konténer számos más, egyéb környezeti változón keresztül beállítható paramétert fogadhat, ezekről részletes információ a DockerHub mySql oldalán található.
Szintén új elem az initdb.sql fájl összerendelése a konténeren belül levő docker-entrypoint-initdb.d/initdb.sql fájllal. A konténer az első indulásakor ellenőrzi a docker-entrypoint-initdb.d/ könyvtár tartalmát, és az ott található összes fájlt a frissen létrehozott adatbázison lefuttatja. Amennyiben a példában szereplő db.sql fájlban SQL mondatok, tábla definíciók, tipikusan INSERT sorok vannak, ezekkel a webdb adatbázis táblái létrehozhatók és kezdeti adatokkal feltölthetők. Ezt a módszert kell tehát alkalmazni ahhoz, hogy a konténer már a létrehozásakor egy előre előkészített és adatokkal feltöltött adatbázist tartalmazzon. Példánkban az initdb.sql fájl egy tábla definíciót és egy rekord beszúrását tartalmazza:
CREATE TABLE people (
id serial,
name VARCHAR(64) NOT NULL
);
INSERT INTO people (name) VALUES ('Szabo Andras');
INSERT INTO people (name) VALUES ('Kiss Lajos');
Már csak egy feladat van hátra, egy olyan webalkalmazás készítése, amely kapcsolódik a mysql adatbázisunkhoz és képes azon műveleteket végezni. Az alábbi program, a webroot/index.php fájlban van, és a webdb adatbázis people táblájából kéri le és írja ki a name oszlop mezőit:
<?php
$dbhost = "mysql";
$username = "webdb";
$password = "webdb";
$db = "webdb";
$conn = new mysqli($dbhost, $username, $password, $db);
if ($conn->connect_error) {
die("Kapcsolódási hiba: " . $conn->connect_error);
}
$sql = "SELECT * FROM people";
$result = $conn->query($sql);
echo "<table border='1'>";
echo "<tr><th>Name</th>";
while($row = $result->fetch_assoc()) {
echo "<tr><td>".$row["name"]."</td>";
}
echo "</table>";
$conn->close();
?>
Fontos tudni, hogy ebben a struktúrában nem tudhatjuk előre, hogy az adatbázis szervert tartalmazó konténer milyen IP címet kap majd akkor, amikor azt a saját gépünk helyett a szolgáltató szerverén működtetjük. Az adatbázis kapcsolat kiépítéséhez ezért nem használhatjuk a korábban esetleg megszokott localhost-ot sem, mert az a webszerver címét jelenti, az adatbázis viszont nem ezen van. Azért, hogy a konténerek közti kapcsolat ne függjön attól, hogy azok éppen milyen IP címeket kaptak, azok a konténercsoporton belül a szolgáltatás nevével kell hivatkozhatniuk egymásra. A fenti php programban ezért adtuk a $dbhost változónak értékül a mySql szerver docker-compose.yaml-ban használt szolgáltatás nevét, a mysql-t.
A konténer felépítését alapjában véve ismét a docker-compose up paranccsal végezzük. A tesztelés során azonban hasznos lehet, ha a javításokat követően leállítjuk, majd töröljük a konténereket, töröljük az adatbázis fájlokat a mysql-data könyvtár eltávolításával:
#!/bin/bash
docker stop columbo.uni-eszterhazy.hu_www
docker stop columbo.uni-eszterhazy.hu_mysql
docker rm columbo.uni-eszterhazy.hu_www
docker rm columbo.uni-eszterhazy.hu_mysql
for I in $(docker image ls -q)
do docker image rm $I --force
done
rm -r mysql-data
docker-compose up
A docker-compose parancs számos egyéb paraméterrel is rendelkezik, ezeket érdemes lehet a dokumentációban áttekinteni. A teljesség igénye nélkül lássunk még néhány példát!
A docker-compose build parancs újraépíti az egyes konténer elemeket. A --scale web=3 paraméter a web nevű szolgáltatásból három példányt indít el, ami akkor hasznos, ha a szolgáltatásunkat több konténerrel szeretnénk kiszolgálni. A docker-compose down pedig a docker-compose.yaml fájl alapján leállítja az abban létrehozott konténereket. Ezt a folymatot az alábbi példa egyetlen sorban végzi el. Emlékeztetőül: a && karakterpár után írt programok csak akkor indulnak el, ha az őket megelőző program visszatérési értéke 0, azaz azok hiba nélkül futottak le.
1 docker-compose build && docker-compose up -d --scale web=3 && docker-compose down
Wordpress konténerizálása¶
A Wordpress az egyik legeltejedtebb CMS (Content Management System), mely bizonyos mértékig szintén jól konténerizálható. Az alábbi docker-compose.yaml fájl erre mutat példát, amit egyfajta „Wordpress-gyárként” használhatunk, ezért annak paramétereit egy .env file-ba emeltük ki. Ennek tartalma VALTOZO=ertek formájú sorokból áll, a konténerek felépítése előtt a docker-compose parancs ezt automatikusan betölti. Az .env fájlunk tartalma példánkban az alábbi:
EXTERNAL_PORT=8013
WEBHOST_URL=www.erlauni.hu
MYSQL_DATABASE=wordpress
MYSQL_USER=wordpress
MYSQL_PASSWORD=geeThu9aiR0u
MYSQL_ROOT_PASSWORD=aeT0phahboo7
WP_HOME="https://{$WEBHOST_URL}"
WP_SITEURL="https://{$WEBHOST_URL}"
STORAGE_LIMIT=2G
MEM_LIMIT=512M
MEM_SWAP_LIMIT=512M
A docker-compose.yaml fájl felépítése az alábbi:
Összefoglalás¶
Ebben a fejezetben a komplex feladatokat megoldó, több konténerrel működő alkalmazás létrehozását tanultuk meg. Ennek központi eleme a docker-compose.yaml fájl volt, melyben az egyes konténereket definiáltuk a már megszokott módon. Megismertük azokat a parancsokat, amelyekkel a konténerek felépíthetők, indíthatók és leállíthatók. Láttuk, hogy a konténerek belső hálózatában az egyes elemekre a szolgáltatás nevével kell hivatkozni, ennek figyelmen kívül hagyása tipikus probléma a gyakorlati alkalmazás során.
Feladatok¶
Tekintsd át az alábbi
docker-compose.yamlfájlt, és adj választ a következőkre:
Milyen szolgáltatást valósít meg ez a file?
Milyen célt szolgálhat a
depends_ondefiníció?Milyen célt szolgálhat a
deploydefiníció?version: '3.8' services: db: image: mysql:latest environment: MYSQL_ROOT_PASSWORD: dbuser MYSQL_DATABASE: webdb volumes: - db_data:/var/lib/mysql web: image: nginx:latest ports: - "8080:80" depends_on: - db deploy: replicas: 3 volumes: db_data:
Hozz létre egy
docker-compose.yamlfájlt, amely egy egyszerű webalkalmazást indít egy nginx konténerben! Helyezz el benne egy weblapot, és biztosítsd, hogy az a 80-as porton legyen elérhető!Készíts egy
docker-compose.yamlfájlt, amely egy web és egy db szolgáltatást indít. A web a működéséhez szükséges adatokat az adatbázisból nyeri. Használd az php és mysql image-eket.Módosítsd a
docker-compose.yamlfájlt, hogy a web szolgáltatás használjon egy kötetet az adatbázis fájlok megőrzéséhez.Készíts egy
docker-compose.yamlfájlt, amely a web szolgáltatás három példányát és egy db szolgáltatást.Módosítsd a
docker-compose.yamlfájlt, hogy a db szolgáltatás egyedi belépési parancsot használjon az indításkor.Hozz létre egy
docker-compose.yamlfájlt, amelyben a web szolgáltatás csak akkor indul el, ha a db service már fut, és fogadja a kapcsolati kéréseket. Használd a depends_on-t!Az előző feladatban létrehozott szolgáltatást indítsd el daemon módban (háttérben futó szolgáltatásként)!
Állítsd le előző feladatban létrehozott szolgáltatást!