Fájlok megosztása konténerek között volume-on keresztül Docker Compose-zal

SHARE felirat szókirakóban a pixabay.com-ról heinzremyschindler felhasználótól

Az előző két cikkben szó volt egyszerű konténer indításáról felcsatolt mappákkal, fájlokkal és Docker image készítéséről. A Docker Compose-zal a felcsatolt mappát is a "volumes" blokkban definiáltuk, de valójában volume-ként inkább arra hivatkozunk, amikor a Dockerre bízzuk a mappa létrehozását, és nem is feltétlenül csak lokális fájlrendszerben lehet gondolkodni. A cikkben és a mellékelt videóban viszont csak lokális volume-okról lesz szó az előző részekben elkészült webszervert kiegészítve egy fájlfeltöltő alkalmazással, ám továbbra is a hoszt rendszer hálózatán futtatva a szolgáltatásokat. Ezért egy rettentő egyszerű HTTPD Proxy-t is konfigurálnunk kell, amivel még egy lépéssel közelebb kerülünk ahhoz a szemlélethez, amit előbb-utóbb mindenkinek meg kell ismernie, aki egyszerre több webes szolgáltatással dolgozik konténerekbe szervezve.

Tartalomjegyzék

Törölhető konténerek

[Tartalom]

Docker konténereknél az is szempont, hogy a konténer bármikor letörölhető legyen és ez a törlés ne okozzon maradandó kárt az alkalmazásban. Természetesen, amíg a konténerek nem élnek, addig a szolgáltatások sem érhetők el (a magas rendelkezésre állású, skálázott szolgáltatásoktól tekintsünk el), de a következő indításkor onnan lehet folytatni, ahol abbahagytuk. Ezt többféle módon is el lehet érni.

  • Állapotmentes alkalmazások: Ilyen lehet akár egy statikus HTML oldal vagy egy webes számológép, aminek nincs tárolandó adata, csak az inputot várja és visszaadja az eredményt.
  • Nem érdekel az állapot: Lehet az alkalmazásnak állapota, de nem érdekel az aktuális állapot, illetve nem fontos, hogy a törlések között is megtartsuk. Ilyen lehet egy demo alkalmazás, amit egyszer elindítok, dolgozom benne, törlöm, és legközelebb mindent előlről akarok kezdeni.
  • Perzisztens tárhely: Ha fontos az állapotok megtartása, akkor valamilyen perzisztens tárhelyről kell gondoskodni. Ez lehet akár egy külső adatbázis szerver, vagy az alkalmazás konténerében egy volume definíciója.

A továbbiakban pedig a volume lesz a téma.

Volume-ok áttekintése

[Tartalom]

A volume tulajdonképpen arról szól, hogy a konténerben kijelölünk egy mappát, aminek a tartalmát akkor is szeretnénk megtartani, ha a konténert törüljük. Többféle volume definíció létezik. Lehet akár távoli fájlrendszert is felcsatolni a hálózaton keresztül, de használhatunk lokális volume-okat, amik fizikailag is azon a szerveren vannak, ahol a konténer is fut. Első körben a lokális volume-okkal éppen elég megismerkedni.

Ezek a lokális volume-ok viszont hasonlók a bind mounttal felcsatolt mappákhoz olyan értelemben, hogy mindkét mappa a hoszt operációs rendszer fájlrendszerén lesz. A bind mounttal felcsatolt mappát viszont többnyire mi hozzuk létre, mi menedzseljük. A Docker is létrehozza, ha a mappa nem létezett, de a jogosultságok nem feltétlenül lesznek jók. A lokális volume viszont mindenképpen a Dockeren keresztül lesz létrehozva és fizikailag a fájlrendszeren alapértelmezés szerint valahol a /var/lib/docker mappa alatt jön létre. Mivel ebben az esetben a létrehozott mappa mindig üres, szintén alapértelmezés szerint, de opcionálisan a Docker kimásolja az image-ből annak a mappának a tartalmát, amit a konténer számára volume-ként megjelöltünk.

Bár a volume-ok minden szempontból tárgyalása számunkra most feleslegesen bonyolítaná a téma megértését, de megkülönböztetünk még névtelen és nevesített volume-okat is. Valójában a névtelen volume-nak is van neve, de ezt nem mi adjuk neki, hanem automatikusan kap egy generált hasht. Ez alapján viszont nehezebben azonosítható be, hogy a volume melyik konténerhez tartozik és mi van rajta, amikor a volume-ok listáját böngésszük. Névtelen volume például a Dockerfile-ban is definiálható már az image fejlesztője által, így azokat a mappákat, amiknek feltétlenül szükséges maradandó tárhely, a biztonság kedvért előre fel lehet sorolni. Én mégis azt javaslom, hogy amikor csak lehet, és ha nem csak egy gyors kipróbálásról van szó, mindig definiáljuk névvel a volume-okat. Ezt persze mindenkinek a saját helyzetében kell mérlegelni, de a következőkben mindkettőre lesz példa.

Mappastruktúra optimalizálása

[Tartalom]

Mielőtt viszont volume-okat definiálnánk, át kell alakítanunk a korábbi példát úgy, hogy egy kicsit logikusabban szervezzük a konfigurációs fájlokat és forráskódokat az új célnak is megfelelően, ami egy PHP alapú fájlfeltöltő és egy, a feltöltött fájlokat listázó szolgáltatás lesz.

Mappa struktúra VSCode-ban

  • A HTTPD-hez szükséges fájlokat mind betesszük egy "httpd" nevű mappába.
  • A gyökérben levő Dockerfile-t egyből lehet is mozgatni ebbe a mappába.
  • A két konfigurációs fájl a gyökérből átkerül a "httpd/conf" mappába, a "httpd-vhosts.conf" fájl viszont a konténerbeni helyéhez hasonlóan a "httpd/conf/extra" almappába.
  • A "vhosts" mappa úgyszintén bekerül a "httpd" alá
  • A "vhosts/blog" mappa pedig átnevezhető "downloads"-ra, tehát a "httpd/vhosts/downloads" néven lesz ezentúl elérhető.

A "downloads" mappában levő “index.html” fájl új tartalma viszont egy link lesz, ami majd a letöltések mappájára mutat

<p style="font-weight: bold; font-size: 26pt;">
  <a href="files">Downloads</a>
</p>

HTTPD konfiguráció

[Tartalom]

A korábban két COPY-val másolt konfigurációs fájlokat a Dockerfile-ban már lehet egy utasítással másolni, vagyis az alábbi két sor

COPY ./httpd.conf /usr/local/apache2/conf
COPY ./httpd-vhosts.conf /usr/local/apache2/conf/extra

változik erre:

COPY conf /usr/local/apache2/conf

Alapértelmezetten a HTTPD szerver a konténer standard outputjára és error stream-be logol, de a virtuális hostoknak a logjait fájlba irányítottuk, ezért ezt érdemes legalább egy névtelen volume-ra kitenni. Ehhez a Dockerfile-ba bekerül a végére a következő sor:

VOLUME [ "/usr/local/apache2/logs" ]

A teljes Dockerfile

FROM httpd:2.4

COPY conf /usr/local/apache2/conf
COPY vhosts /var/vhosts

VOLUME [ "/usr/local/apache2/logs" ]

A virtuális hostok konfigurációját is változtatni kell, ahol viszont majd használjuk a “ProxyPass“ és “ProxyPassReverse” direktívákat. Ezeket viszont előbb engedélyeznünk kell a “httpd.conf” -ban. A következő két sort kell beilleszteni a fájlba még a “http-vhosts.conf” betöltése előtt, vagy a már benne levő, de kikommentelt sorok elől kivenni a kettőskeresztet:

LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so

A "httpd-vhosts.conf"-ban az aldomaineket kell korrigálni, azaz a "blog" helyett mindenhol "downloads" lesz, ahogy a log fájlok neveiben és a mappa útvonalakban is. Ezen kívül az előző részekben bemutatott "xip.io" szolgáltatás egy ideje nem érhető el, ezért lecseréltem "nip.io"-ra. A PHP feltöltőt viszont nem tudja kiszolgálni a HTTPD, mert a PHP modult nem engedélyeztük. A PHP-t egyébként is általában külön konténerben érdemes futtatni, ezért most egy, az egyébként élesben ajánlott, de komplikáltabban konfigurálható megoldás helyett használjuk a proxy funkcióját a HTTPD-nek. Ehhez a "127.0.0.1.nip.io virtuális host végén az alábbi két sort kell beilleszteni, ami minden kérést átirányít a virtuális hosztról a PHP konténerbe, ami majd a 8080-as porton lesz indítva a host network-ön.

    ProxyPass "/" "http://127.0.0.1:8080/"
    ProxyPassReverse "/" "http://127.0.0.1:8080/"

A "files" almappára, amit majd volume-ként fogunk felcsatolni, a HTTPD-ben engedélyezni kell a fájlok listázását "index.html" hiányában. Ehhez a "Directory" blokkba kerül be a Options +Indexes

Teljes httpd-vhosts.conf:

<VirtualHost *:80>
    DocumentRoot "/usr/local/apache2/htdocs"
</VirtualHost>

<VirtualHost *:80>
    ServerAdmin webmaster@127.0.0.1.nip.io
    DocumentRoot "/var/vhosts/downloads"
    ServerName downloads.127.0.0.1.nip.io
    ErrorLog "logs/downloads-error_log"
    CustomLog "logs/downloads-access_log" common

    <Directory "/var/vhosts/downloads">
    Require all granted
    Options +Indexes
    </Directory>
</VirtualHost>

<VirtualHost *:80>
    ServerAdmin webmaster@127.0.0.1.nip.io
    DocumentRoot "/var/vhosts/www"
    ServerName 127.0.0.1.nip.io
    ErrorLog "logs/www-error_log"
    CustomLog "logs/www-access_log" common

    <Directory "/var/vhosts/www">
    Require all granted
    </Directory>

    ProxyPass "/" "http://127.0.0.1:8080/"
    ProxyPassReverse "/" "http://127.0.0.1:8080/"
</VirtualHost>

Fájlfeltöltő készítése

[Tartalom]

A fájlfeltöltőhöz a "httpd" almappához hasonlóan létrehozunk egy "uploader"nevű mappát a projekt gyökerében. Ebben lesz a Dockerfile, amiben leírjuk a PHP-val indított szerver működését, illetve egy "index.php", ami bekerül majd szintén az image-be.

Dockerfile

FROM php:8.0.5-cli

COPY index.php /var/www/index.php

CMD [ "php", "-S", "0.0.0.0:8080", "-t", "/var/www" ]

A PHP image-nek több változata létezik. A 8.0.5-ös PHP verzióból a "cli", azaz parancssori PHP értelmező változatot használjuk, de demonstrálásra egy egyszerű szervert azzal is tudunk indítani. Ezt a szervert pedig a konténer indításakor el kell tudnunk indítani. A "CMD" kulcsszóval definiálható egy ilyen parancs. A parancsnak minden részét idézőjelbe kell tenni és vesszőkkel elválasztva, szögletes zárójelek között megadni tulajdonképpen JSON formátumban. A -S után a szolgáltatás IP-je és portja áll, a -t után pedig a gyökérkönyvtár útvonala. Az index.php másolása pedig már biztosan nem okoz meglepetést a Dockerfile-ban, aminek a forráskódja az alábbi 5 soros PHP kódból és utána következő rövid HTML-ből áll:

<?php
if (!empty($_FILES['file'])) {
    move_uploaded_file($_FILES['file']['tmp_name'], __DIR__ . '/files/' . $_FILES['file']['name']);
}
?>
<!DOCTYPE html>
<html>
<head>
    <title>Uploader</title>
    <meta charset="utf-8">    
</head>
<body>

<form enctype="multipart/form-data" method="post">
    <input type="file" name="file"><br>
    <input type="submit" value="Upload">
</form>

</body>
</html>

Ez a feltöltő kód is csak a demonstrációra szolgál. Élesben azért ennél sokkal összetettebb lenne a kód.

Docker Compose fájl módosítása

[Tartalom]

Már használatra kész lenne a forráskód, de a Docker Compose-t még fel kell készítenünk az új szolgáltatásra és a volume használatára, ahol a feltöltött fájlokat fogjuk tárolni. A feltöltő része a "services" blokkon belül a "docker-compose.yml" fájlban a következő:

 uploader:
    build
:
      context
: uploader
      dockerfile
: Dockerfile
    network_mode
: host

A volume-ot is definiálni kell a "services" blokkal egy szinten (sorban alatta vagy felette), ahol csak a volume nevét kell megadni egy kettősponttal a végén.

volumes:
  files:

Majd mindkét service blokkján belül hozzá kell adni a volume csatolásának definícióját, ahol a mappa elérések különbözni fognak. Ezen kívül a HTTPD-ből nem kell tudni írni a fájlokat, így ezt csak olvashatóra korlátozzuk a HTTPD konténerben, valamint a "type"-nál a "bind" helyett, amit a bind mounttal felcsatolásnál megadtunk, most a "volume" érték lesz. A teljes új fájl most a következő:

version: "3.9"

volumes
:
  files
:

services
:
  httpd
:
    build
:
      context
: httpd
      dockerfile
: Dockerfile
    network_mode
: host
    volumes
:
      - type
: volume
        source
: files
        target
: /var/vhosts/downloads/files
        read_only
: true
 
  uploader
:
    build
:
      context
: uploader
      dockerfile
: Dockerfile
    network_mode
: host
    volumes
:
      - type
: volume
        source
: files
        target
: /var/www/files

Böngésző teszt

[Tartalom]

A teszteléshez most el kell indítani a konténereket, illetve az image-eket újra kell buildelni. A HTTPD image már az előző részek után ott lehet a gépen, ezért a "--build" paraméter hozzáadásával indítjuk a konténereket, hogy biztosan lefusson az új build.

docker-compose up -d --build

Ezek után a böngészőből megnyitva a "127.0.0.1.nip.io" weboldalt a fájlfeltöltő űrlapon betallózunk egy fájlt.

Feltöltő űrlap

Majd a "downloads.127.0.0.1.nip.io" weboldalon a "Downloads" linkre kattintva már látható is a feltöltött fájl annak ellenére, hogy a szolgáltatások elzárva, külön konténerben futnak.

Letöltések listája

Bár ez már túlmutat a cikken, de ugyanez a logika akár távoli fájlrendszereket felcsatoló volume-okkal is alkalmazható és így akár fizikailag több gépet is lehet használni a terhelés elosztására, amíg a volume mindegyik gépen elérhető.

Releváns, hasznos parancsok

[Tartalom]

Volume-ok listázásával látható a "httpd-vhosts_files" nevű volume, ami tartalmazza a projekt nevét éppúgy, mint a konténerek. Valamint egy névtelen volume is megjelenik egy hosszú hash-sel, amit nem illesztek be, mert úgyis mindenkinél más.

docker volume ls

A volume-ok adatai is megnézhetők az inspect paranccsal:

docker volume inspect httpd-vhosts_files

Ami egy json kimenetet ad, amiben a "Labels" kulcsszóra érdemes felfigyelni, ahol a Docker Compose-zal kapcsolatos adatok vannak, mivel abban definiáltuk.

[
    {
        "CreatedAt": "2021-05-04T20:13:27+02:00",
        "Driver": "local",
        "Labels": {
            "com.docker.compose.project": "httpd-vhosts",
            "com.docker.compose.version": "1.28.5",
            "com.docker.compose.volume": "files"
        },
        "Mountpoint": "/var/lib/docker/volumes/httpd-vhosts_files/_data",
        "Name": "httpd-vhosts_files",
        "Options": null,
        "Scope": "local"
    }
]

A névtelen volume-nál is meg lehet nézni ugyanezt, viszont ott hiányozni fog a "Labels", mivel annak semmi köze nem volt a Compose-hoz.

A virtuális hoszt logokat a konténerekbe belépve lehet megnézni, de akár a belépést kihagyva közvetlenül a fájl tartalmát is ki lehet listázni

docker-compose exec httpd more logs/downloads-access_log

A nem virtuális hoszthoz köthető logokat, mint például a konfigurációs fájl elírását, amitől a konténer el sem indul, az alábbi paranccsal lehet kiírni.

docker-compose logs --tail 200 httpd

Arra az esetre, ha már nagyon hosszú lenne a log, érdemes a "--tail" paraméterben limitálni a kiírandó legfrissebb sorok számát.

A névtelen volume-okat az alábbi paranccsal JSON-ban le lehet kérni:

docker container inspect httpd-vhosts_httpd_1 --format '{{ json .Config.Volumes }}'

De az összes felcsatolt mappa, beleértve a bind mountokat és a volume-okat, listázható JSON-ban az alábbi paranccsal:

docker container inspect httpd-vhosts_httpd_1 --format '{{ json .Mounts }}'

Végszó

[Tartalom]

Bár az előző részekhez képest ez most egy nagyobb ugrás volt, de a fókusz a névtelen és nevesített volume-okon volt. Ezeket mindenképp érdemes megismerni és használni. Ha mindeközben olyan akadályba ütköznél a volume-okkal kapcsolatban, amit nem értesz, mert látszólag működnie kellene, egy részt ne aggódj, időnként a tapasztaltabbak is belefutnak speciális helyzetekbe, amiket ilyenkor ki kell nyomozni. Más részt pedig bátran írj kommentet a hiba minél részletesebb leírásával és segítek. Igény esetén pedig egy újabb cikkben és videóban egy konkrét problémára is kitérek.

A cikk, a videó és az oldal like-olásával, valamint a Youtube csatornára feliratkozással is támogathatod az ingyenes tartalmak mögötti munkát, amit töretlenül igyekszem készíteni az igényeid alapján is. Ezeket az igényeket, észrevételeket is várom bármilyen csatornán továbbra is.

Kategóriák: 
Megosztás/Mentés