PHP alkalmazás fejlesztése Docker konténerekkel

php elefánt kép pixabay.com-ról

Az előző részben megmutattam, hogyan lehet statikus tartalmakat, kliensoldali webes alkalmazásokat fejleszteni és közzétenni Docker konténerben, most viszont a szerveroldali programokon a sor. Ebben az esetben az komplikálja a feladatot, hogy már nem elég a kliensnek odaadni a programfájlokat, azokat szerveroldalon kell értelmezni. Ez persze nem feltétlen jelentene önmagában problémát, mivel az értelmező és a szerver lehet egy konténerben is. PHP alkalmazásnál viszont érdemes külön konténerben futtatni a PHP értelmezőt és a webszervert. Az alábbiakban ennek mikéntjéről fogok írni.

Tartalom

Miért két konténer?

[Tartalom]

Az első kérdés biztos az, hogy mégis mi a fenének két konténer külön HTTPD-vel és PHP FPM-mel. Az Apache HTTPD szervernek van PHP modulja, tehát közvetlenül a modulon keresztül tudná értelmeztetni a PHP programot egyetlen konténerben. Ezzel pedig egy sor komplikációtól megkímélve. Nézzük az érveket.

Szokták mondani, hogy a PHP FPM jobb, mert gyorsabb. Azért ez így nem igaz. FPM esetén a HTTPD webszerver a PHP kéréseket továbbítja a PHP felé például TCP kapcsolaton keresztül, majd fogadnia kell a választ, amit majd visszaküldhet a kliensnek. Mindez nyilván időbe telik. Akkor hogy is van ez?

A HTTPD-ben a PHP modul feltétel nélkül betöltődik, akkor is, ha statikus tartalmat kell kiszolgálni. Stílusfájlokat, képeket, és így tovább. Ez persze nem azt jelenti, hogy értelmezni is fogja őket, de a modul legalábbis betölt. Ez is időbe telik, még ha önmagában ez minimális is, de minél több a statikus tartalmak forgalma, annál inkább számíthat.

Az FPM-nek van egy olyan előnye is, hogy a HTTPD szervertől független felhasználó nevében tud indulni, így a jogosultságokat is lehet egy kicsit finomítani.

A HTTPD webszervernek igazából nincs túl sok memóriára szüksége általában. Ezzel szemben a PHP-nek már sokkal inkább, de ugyanez igaz a processzorigényre is. Külön konténerben, akár külön hoszton sokkal könnyebb lehet elosztani az erőforrásokat és emellett az Apache HTTPD és PHP processzmenedzserét külön be lehet állítani az igényeknek megfelelően.

Mindehhez hozzájön a tény, hogy a külön konténerek miatt a frissítés is egymástól függetlenül történhet, vagy cserélni lehet a komponenseket. A PHP-ra nyilván szükség lesz, de hogy HTTPD vagy NginX lesz előtte, az már akár opcionális is lehet.

Gyakorlatilag szeretjük a PHP FPM-et, ezért szükséges a két konténer, de önmagában a külön konténer is praktikus, és akkor csak a PHP FPM jöhet szóba.

A HTTPD és PHP közti kommunikáció

[Tartalom]

A HTTPD-nek tudnia kell, hogy URL alapján milyen kéréseket továbbítson a PHP-nek, majd azokat TCP vagy Unix socketen keresztül szépen megbeszélik egymással. De ha már Dockernél tartunk, a Unix socketet rögtön felejtsük is el, és maradjunk a TCP-nél. Amúgy is fel kell készülni arra, hogy a fejlesztés után majd éles szerver jön, ahol az ég tudja, hol lesz az egyik vagy másik konténer.

Bár nem hálózati kommunikációval összefüggő kérdés, de nyilván nem szeretnénk, ha a PHP nem tudna válaszolni a HTTPD-nek egy kérésre, ezért komolyabb esetekben érdemes elmélyedni a processzmenedzserek lelkivilágában, amiről korábban a Processzkezelőkkel szemtől szemben: HTTPD MPM és PHP FPM változatok linux környezetben című cikkben írtam.

Az, hogy a HTTPD értse a PHP kéréseket és megfelelően továbbítsa a PHP-nek, például az alábbi konfigurációval érhető el:

  1. LoadModule proxy_module modules/mod_proxy.so
  2. LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
  3. <FilesMatch "\.php(/.*)?$">
  4.     SetHandler  "proxy:fcgi://host-vagy-ip-cim:9000"
  5. </FilesMatch>
  6.  
  7. <Proxy "fcgi://hoszt-vagy-ip-cim:9000">
  8. </Proxy>

A konténerek kommunikációja a gyakorlatban

[Tartalom]

Az előzőekben csak a PHP-ről és webszerverről volt szó, de konténereknél egy kicsit komplikáltabb az ügy. Ilyenkor a két komponenst úgy kell elképzelni, mint két külön gépen futó szolgáltatást, amik között hálózati kapcsolat van. A hálózatokról az "A Docker hálózatkezelése" részben írtam bővebben.

Fejlesztéskor is jó eséllyel vagy Docker Swarm-ot vagy Docke Compose-t fogsz használni, de ugye mindkettőnél nagyon hasonló yaml fájlt kell szerkeszteni. Most maradnék a Docker Compose-nál. Alább egy nagyon minimális váz látható a compose fájlra.

  1. version: "3"
  2.  
  3. services:
  4.   php:
  5.    # ide jönnek a php konténer paraméterei
  6.   httpd:
  7.    # ide jönnek a httpd konténer paraméterei

A services alatt persze nem lett volna muszáj a szolgáltatást is php-nek és httpd-nek nevezni. Az teljesen tetszőleges, de így a legegyszerűbb. Később majd szó lesz a paraméterekről is. Minden Compose projekt automatikusan saját hálózatot kap, így jól el lehet különíteni az egyes projekteket egymástól. Ám ahogy azt a hálózatoknál említettem, az egyedi Docker networkökre jellemző, hogy a konténerek nevei hosztnévként is funkcionálnak a hálózat konténerei között. Docker Compose-nál pedig a szolgáltatás neve is, amire később oda kell figyelni, amikor már nem elégszel meg egyetlen közös hálózattal, például reverse proxy esetén, de egy MySQL szervert is el lehet szeparálni az azt amúgy sem használó konténerektől.

Megjegyzés: Docker Compose-zal azért működnek hosztnévként a szolgáltatások nevei, mert a szolgáltatás minden hálózatában, annak minden konténere megkapja hálózati álnévként. Ezt a "docker network connect" utasítással bárki megteheti bármilyen konténer és hálózat esetén Compose nélkül is. A hálózatban azonos álnévvel rendelkező konténerek között pedig működik a load balancing is. MySQL-ről és proxy-ról majd később lesz szó részletesebben, de mivel élesben szinte biztos, hogy lesz reverse proxy, fontos megjegyezni, hogy a proxy hálózatához nem szabad a PHP konténereket is hozzáadni, csak a HTTPD-t, ami így a PHP-vel közös hálózaton keresztül csak a helyi PHP-t látja "php" néven. Már amennyiben így kívánsz hivatkozni a PHP konténerekre és nem fix IP-vel vagy egy speciális névvel

Mivel tehát a PHP service neve "php", ez felhasználható a korábban írt fcgi proxy konfigurációnál:

  1. LoadModule proxy_module modules/mod_proxy.so
  2. LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
  3. <FilesMatch "\.php(/.*)?$">
  4.     SetHandler  "proxy:fcgi://php:9000"
  5. </FilesMatch>
  6.  
  7. <Proxy "fcgi://php:9000">
  8. </Proxy>

Ezt akár be is lehetne másolni egy saját Docker image-ben a httpd.conf-ba vagy külön fájlba is, vagy lehet keresni olyan image-et, amiben ezt már megoldották, ahogy én az itsziget/httpd24-ben. Így a következőképpen folytathatod a Compose fájlt.

  1. version: "3"
  2.  
  3. services:
  4.   php:
  5.     image: php:7.1-fpm
  6.     volumes:
  7.       - .:/var/www/html
  8.   httpd:
  9.     image: itsziget/httpd24:1.1
  10.     volumes:
  11.       - .:/var/www/html
  12.     environment:
  13.       SRV_DOCROOT: /var/www/html
  14.       SRV_PHP: 1

Ez a konfiguráció abban az esetben működik, ha docker-compose.yml fájl mellett vannak a dokumentum gyökérbe való fájlok. Ez nyilván nem mindig van így. Olyankor az SRV_DOCROOT változó értékét és a volumes szekciókban a forrás mappát át kell írni. Van ráadásul olyan eset is, amikor a projekt gyökérben egy almappa a dokumentum gyökér. Ilyennel lehet találkozni egyes keretrendszereknél is, mint például a Symfony. A következőkben pedig egy komplett példát mutatok egy Symfony projekt beüzemelésére Docker Compose-zal.

Symfony projekt Docker Compose-zal

Symfony telepítése

[Tartalom]

Már régóta dolgozom Symfony keretrendszerrel, de ha nem tenném, akkor is nehéz lenne elkerülni a komponenseit, amit más keretrendszerek és alkalmazások is előszeretettel vetnek be. Egyik jellemzője, hogy létezik egy mappa a projekten belül, amire a szerver dokumentum gyökerét kell állítani. Ez régen "web" volt, ma már "public". A PHP látni fogja az efelett levő fájlokat, de a webszerveren keresztül azok elérhetetlenek lesznek. Ez pedig azt is jelenti, hogy ezen kívül más mappát nem is kell felcsatolni a webszerver konténerébe. Most készítsünk el egy egyszerű Symfony 3.4 projektet. Ez az LTS verzió jelenleg. Ha pedig hosszan támogatott verziót szeretnél, akkor érdemes ilyent választani.

Telepíteni több módon is lehetne a rendszert. Egy részt van hozzá egy telepítő szkript. Más részt az Composer-rel is lehet telepíteni. És a legújabb verziókat már nem is támogatja a Symfony installer. A telepítőnél apró bökkenő, hogy a szkript egy Phar fájl igazából, tehát már ahhoz is PHP-re van szükség. Ez viszont konténerbe is felcsatolható. Az alábbi példában a dokumentációban mutatott helyre másoltam a programot. Nincs nagy jelentősége, mivel úgyis konténerben fog működni.

  1. sudo mkdir -p /usr/local/bin
  2. sudo curl -LsS https://symfony.com/installer -o /usr/local/bin/symfony
  3. sudo chmod a+x /usr/local/bin/symfony
  4. # A fenti sorokat csak egyszer kell futtatni. Újabb symfony projekteknél már nem szükséges.
  5. docker container run --rm -it -u $(id -u):$(id -g)  -v /usr/local/bin/symfony:/usr/local/bin/symfony -v $PWD:$PWD --workdir $PWD --name php php:7.1 symfony new php 3.4

Megjegyzés: Látszik, hogy nem az FPM verziót kértem a PHP konténerből, de ezen a ponton még nincs is szükség rá. Ellenben a konténerben a userid beállítására igen, hogy a fájlok ne root felhasználóval jöjjenek létre.

A Composeres verzió egy kicsit rövidebb. Mivel létezik hivatalos Composer image, ami tartalmazza a php-t is, felcsatolni sem kell semmit a munkakönyvtáron kívül.

  1. docker container run --rm -it -u $(id -u):$(id -g) -v $PWD:$PWD --workdir $PWD --name php composer create-project symfony/skeleton php 3.4

Ezt akár ki is lehet próbálni webszerver nélkül az alábbi utasítással, ahol még mindig nincs PHP FPM, hanem a PHP beépített szerverét használjuk. Ez inkább csak tesztelésre ajánlott viszont.

  1. docker container run --rm -it -v $PWD:$PWD --workdir $PWD --name php php:7.1 -S 0.0.0.0:80 -t public

CTRL+C -vel ki lehet lépni. Erre már nem lesz szükség, jöhet a HTTPD szerver beállítása.

Fájlok felcsatolása

[Tartalom]

Akkor most már tényleg legyen webszerver is a projekthez.

  1. version: "3"
  2.  
  3. services:
  4.   php:
  5.     image: php:7.1-fpm
  6.     volumes:
  7.       - .:/var/www/html
  8.   httpd:
  9.     image: itsziget/httpd24:1.1
  10.     volumes:
  11.       - ./public:/var/www/html/public
  12.     environment:
  13.       SRV_DOCROOT: /var/www/html/public
  14.       SRV_PHP: 1
  15.     depends_on:
  16.      - php

Ez már indítható is az alábbi módon:

  1. docker-compose up -d

A webszerver elérésére több módot is találhatsz a HTTPD-ről és statikus tartalmakról szóló részben.

A példában látható, hogy a PHP konténer megkapta a teljes dokumentum gyökeret, de a HTTPD csak a public almappát. Persze fejlesztésnél ennek nagyjából semmi jelentősége nincs, így ebben a fázisban, ha úgy tetszik, nyugodtan fel lehet csatolni a teljes könyvtárat. Amit még észre kell venni, az a "depends_on", amivel a httpd-nél megadtam, hogy függ a "php"-től, tehát előbb a php szolgáltatásnak kell elindulnia. Érdemes mindig meghatározni a sorrendet, bár nem mindig számít.

Bár a docker-compose.yml-ben a "volumes" alatt van mindkettő, megkülönböztetünk volume-ot és felcsatolt hoszt könyvtárat, illetve fájlt (bind mount). Egy volume-ot a services blokkon kívül külön definiálni kell és nem is feltétlenül az adott hoszton lesz fizikailag. A bind mount-nál viszont arra kell figyelni, hogy vagy abszolút útvonal legyen a forrás vagy Docker Compose-nál legalábbis "./" vagy "../"-rel kezdődjön. De mi történik, ha a felcsatolandó könyvtár vagy fájl nem is létezik, például, mert hibás a hivatkozás, vagy nem lett létrehozva? A régi működés szerint ilyenkor automatikusan létrejön a felcsatolandó útvonal könyvtárként, ráadásul root jogosultságokkal. A program pedig ezután vagy működik vagy nem. Ez igazából a Docker működésének következménye a "-v" és a "--mount" opciók különbsége miatt. "--mount" esetén a nem létező forrás hibaüzenetet okoz. A Docker Compose ezt a funkcionalitást csak a 3.3-as Compose fájl verziótól támogatja, ami a Docker Compose 1.14-től elérhető. A verzió viszont még nem elég. Ilyenkor az alábbi módon kell a mount-ot definiálni a yml fájlban például egy, a projekten kívüli, a hosztra is felcsatolt mappa esetén:

  1.    volumes:
  2.       - .:/var/www/html
  3.       - type: bind
  4.         source: /mnt/data/files
  5.         target: /var/www/files

Figyelni kell a megfelelő indentálásra, mert itt a kötőjel utáni kulcs-érték pár miatt egy asszociatív tömb (map) keletkezik, aminek a source és a target is része. Alternatív formátum a JSON-hoz hasonló:

  1.    volumes:
  2.       - .:/var/www/html
  3.       - {type: bind, source: /mnt/data/files, target: /var/www/files}

Tudni kell, hogy a Docker image-ek gyakran saját volume definícióval készülnek, ami azt jelenti, hogy ha te nem is sorolod fel a volumes blokkban, attól az még létre fog jönni volume-ként, csak valami ilyesmi névvel fog szerepelni a "docker volume ls" utasítást futtatva:

d5d57d6d660641b561c57c2dfc153d56ec1c56b492e63a18661cd31c5eff5460

Amennyiben letörlöd a compose projektet a "docker-compose down" -nal, az újabb projektindításkor új volume fog létrejönni egy újabb, hasonlóan szép névvel. Ez viszont olyan esetben nem jó, amikor a létrejött adatoknak meg is kellene maradni. Ráadásul, ha valamiért nagyobb mennyiségű adat kerül ide, az a tárterületet is eltelíti lassan. Bár az eddigi image-ekben nem volt ilyen definíció, a teljesség kedvéért ki fogom tenni a Symfony "var" alkönyvtárát ilyen volume-ra, ami a cache-t és a logokat tartalmazza. Így az nem kerül a hosztra akkor sem, ha egyébként a dokumentum gyökeret felcsatoltam, aminek része a "var", mert a volume-ok a bind mount-ok után vannak felcsatolva.

  1. version: "3.3"
  2.  
  3. volumes:
  4.   var:
  5.  
  6. services:
  7.   php:
  8.     image: php:7.1-fpm
  9.     volumes:
  10.       - .:/var/www/html
  11.       - var:/var/www/html/var
  12.       - type: bind
  13.         source: /mnt/data/files
  14.         target: /var/www/files
  15.   httpd:
  16.     image: itsziget/httpd24:1.1
  17.     volumes:
  18.       - ./public:/var/www/html/public
  19.     environment:
  20.       SRV_DOCROOT: /var/www/html/public
  21.       SRV_PHP: 1
  22.     depends_on:
  23.      - php

Itt már egy "volumes" blokk is megjelent külön a verziószám után. Az alapértelmezett volume-okat itt elég felsorolni paraméterek nélkül, csak indexként. Majd ugyanazzal a névvel lehet rá hivatkozni a szolgáltatásoknál. Tudni kell, hogy ezek a volume-ok annak a konténernek az adataival töltődnek fel, amelyik előbb elindul. Ha valamiért létezne a webszerver konténerben is a mappa, amit a php konténer adataival kellett volna feltölteni, a "depends_on" nélkül előfordulhat, hogy ez nem történik meg.

Olyan eset is lehetséges, amikor egy mappát elérhetővé kell tenni a webszerveren keresztül, de a PHP-nak is el kell érnie, például, mert az másolja bele a fájlokat feltöltésnél. Ekkor először is mind a két szolgáltatásnak fel kell csatolni a mappát, más részt adná magát, hogy a dokumentum gyökérbe történjen a csatolás, hiszen ott elérhető lesz közvetlenül a httpd szerveren keresztül. Ha viszont a dokumentum gyökér is fel van csatolva mindkét szolgáltatásnak bind mount-tal, akkor a hoszton is létre fog jönni üres mappaként a felcsatolt célkönyvtár. Ez persze nem biztos, hogy zavar, ugyanakkor felesleges is. Ám, ha ezt még a "shared" opcióval is ötvözöd valamilyen okból, akkor nem üres mappa jön létre, hanem a hoszton is fel lesz csatolva a dokumentum gyökérbe a mappa. Az ráadásul ott is marad, amíg az "umount" utasítással rendszergazdaként le nem csatolod. Ezt aztán elkezdi beolvasni az IDE (ha nincs kivételként beállítva) és a hoszton végzett műveleteknél is figyelembe kell venni, nehogy véletlenül letöröld vagy egy tartalomban keresés műveletet feleslegesen egy több gigabájtnyi képkönyvtáron is lefuttass.

Megjegyzés: A "shared" opció bind mount-nál adható meg a célkönyvtár után pl. "source:target:shared" alakban, ami annyit tesz, hogy amennyiben az ezzel megjelölt mount célkönyvtárába a konténerben fel lesz csatolva egy új fájl vagy mappa, az ugyanúgy a hoszton is felcsatolódik a mount forráskönyvtárában. volume-oknál pedig nem lehet bind opciókat megadni.

A webszerveren egy alias beállításával az egyébként dokumentum gyökéren kívülre felcsatolt mappát is elérhetővé lehet tenni az alábbi szerverkonfigurációval, amennyiben az "alias" modul engedélyezett:

  1. Alias /files /var/www/files
  2. <Directory /var/www/files>
  3. Require all granted
  4. </Directory>

Ahol az Alias után az első paraméter a böngészőben beírandó, a második a fájlrendszeren levő útvonal. Erre a mappára viszont jó eséllyel a webszerveren nincs hozzáférés engedélyezve, ezért ezt is be kell állítani a fent látható módon. Gond persze akkor van, ha a PHP program sem támogatja, hogy tetszőleges helyen lehessen a feltöltések mappája.

Fájlrendszer jogosultságok

[Tartalom]

Eddig csak a webszerver konfigurációjában megadható jogosultságról volt szó, de a fájlrendszer még ennél is gyakoribb problémát okozhat.

A Symfony 3 "var" nevű almappájáról már volt szó, ami tartalmazza a naplófájlokat és a cache-t. Alapértelmezésként a PHP a www-data felhasználóval működik, tehát a fájlokat is azzal menti. Ha volume-ként lettek létrehozva, ez nem gond, mert úgyis be kell lépni a konténerbe, ahol jó eséllyel nem lesz gond a hozzáféréssel. Ha azonban a gazda rendszerről szeretnéd elérni a fájlokat az egyszerűség kedvéért, majd akár törölni őket, csak root-ként lehet. Olvasni bármilyen felhasználóval tudod, de előfordulhatna olyan eset, amikor egy PHP program, vagy épp a fejlesztő/megrendelő speciális jogokat igényel. Például olvasási joga csak a tulajdonosnak lehet. Egy ilyen fájlt még olvasni sem fogsz tudni a hosztról, ami főleg akkor probléma, amikor Docker image-et szeretnél készíteni, és a Docker sem tudja beolvasni a fájlokat, amikor összegyűjti a .dockerignore-ban nem szereplő elemeket a fájlrendszerről.

Ilyenkor meg lehetne adni a PHP FPM konfigurációban, hogy a PHP-t futtató felhasználó azonosítói megegyezzenek a sajátjaiddal (user, group). Más részt viszont, ahogy a Symfony telepítésénél történt, a konténer felhasználója is felülbírálható egy nem root felhasználói azonosítóval, ami után a PHP nem fog tudni más nevében futni, hiszen nincs hozzá joga. Bármi is van az FPM konfigurációban, az figyelmen kívül lesz hagyva. Ilyenkor így néz ki egy compose fájl:

  1. version: "3.3"
  2.  
  3. services:
  4.   php:
  5.     image: php:7.1-fpm
  6.     volumes:
  7.       - .:/var/www/html
  8.       - type: bind
  9.         source: /mnt/data/files
  10.         target: /var/www/files
  11.     user: ${USER_ID:-33}:${GROUP_ID:-33}
  12.   httpd:
  13.     image: itsziget/httpd24:1.1
  14.     volumes:
  15.       - ./public:/var/www/html/public
  16.     environment:
  17.       SRV_DOCROOT: /var/www/html/public
  18.       SRV_PHP: 1
  19.     depends_on:
  20.      - php

A USER_ID és GROUP_ID környezeti változók, amiket majd be kell állítani indításkor. Ha nem teszed, akkor az alapértelmezett 33-as azonosítókkal fog működni a konténer. Az indítás pedig a következőképpen történne:

  1. USER_ID=$(id -u) GROUP_ID=$(id -g) docker-compose up -d

Persze fixen is bele lehet írni az azonosítókat a compose fájlba, ha csak te fejlesztesz és tuti nem lesz futtatva más felhasználóval. Vagy akár egy "docker-compose.override.yml" nevű fájlban mindenki beállítja a saját azonosítóit, amit a Docker Compose automatikusan összefűz a "docker-compose.yml" fájllal.

Egyedi igények

[Tartalom]

Általában a hivatalos Docker image-ekből érdemes kiindulni, de van, amikor nem elég, és kellenek a PHP-hez további modulok. Ez lehet akár egy memcached vagy redis kiterjesztés is a session-ök tárolására. Ilyenkor saját Docker image-et kell készíteni. Ezt fejlesztési időben nem muszáj feltölteni sehova. Valójában a projektben is lehet tárolni a Dockerfile-t, ami a telepítést leírja. Majd a projekt első indításakor lefut a build. Ebben az esetben az "image" helyett a docker-compose.yml-ben a "build"-et kell megadni.

  1. # ...
  2.   php:
  3.     build:
  4.       context: .
  5.       dockerfile: php.Dockerfile
  6. # ...

Ha csak egy Dockerfile-ra van szükség és nem kell egyedi név hozzá, akkor a "dockerfile" sor ki is hagyható. A build leírása a php.Dockerfile-ban pedig így néz ki:

  1. FROM php:7.1-fpm
  2.  
  3. RUN apt-get update \
  4.  && apt-get install -y --no-install-recommends libmemcached-dev \
  5.  && pecl install memcached \
  6.  && docker-php-ext-enable memcached \
  7.  && apt-get remove --purge -y libmemcached-dev libhashkit-dev libsasl2-dev

Ezzel egy rétegben történik a telepítés és a pecl install után már felesleges programkönyvtárak törlése. Hogy mik feleslegesek, azokat nagyjából teszteléssel lehet megtudni. De "-dev" -re végződő csomagokat jó eséllyel biztonságosan ki lehet törölni. A telepítéskor pedig kiírja az "apt", hogy miket telepített a kért csomagokon kívül. Azokat is figyelni kell.

Megjegyzés: A hivatalos PHP image-ek adnak ugyan a modulok engedélyezéséhez és telepítéséhez szkripteket, de a függőségeket már neked kell megfejteni és előre telepíteni. Ehhez próbáltam segítséget adni a saját verziómmal, amiben előre leteszteltem több telepíthető modult, és a függőségeik telepítéséhez elkészítettem a szkripteket. A kész, minden általam támogatott modult telepítő verzió letölthető a Docker Hub-ról is.

A memcached konténer elindítása pedig már csak egy újabb sor a yaml fájlban. Ezután a PHP-t kellene konfigurálni, hogy a session-t a memcached-del kezelje, de a Symfony-ban definiálható kódból is egyedi Memcached session handler.

docker-compose.yml

  1. version: "3.3"
  2.  
  3. services:
  4.   php:
  5.     build:
  6.       context: .
  7.       dockerfile: php.Dockerfile
  8.     volumes:
  9.      - .:/var/www/html
  10.       - type: bind
  11.         source: /mnt/data/files
  12.         target: /var/www/files
  13.     user: ${USER_ID:-33}:${GROUP_ID:-33}
  14.   httpd:
  15.     image: itsziget/httpd24:1.1
  16.     volumes:
  17.      - ./public:/var/www/html/public
  18.     environment:
  19.       SRV_DOCROOT: /var/www/html/public
  20.       SRV_PHP: 1
  21.   memcached:
  22.     image: memcached:1.5-alpine

Session handler Symfony-ban

  1. namespace App\Controller;
  2.  
  3. use Symfony\Component\HttpFoundation\Response;
  4. use Symfony\Component\HttpFoundation\Session\Session;
  5. use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
  6. use Symfony\Component\HttpFoundation\Session\Storage\Handler\MemcachedSessionHandler;
  7. use Memcached;
  8.  
  9. class DefaultController
  10. {
  11.     public function index()
  12.     {
  13.         $memcached = new Memcached();
  14.         $memcached->addServer('memcached', '11211');
  15.         $session = new Session(new NativeSessionStorage([], new MemcachedSessionHandler($memcached)));
  16.  
  17.         $c = $session->get('c') ?? 1;
  18.         $response = new Response('Request counter: ' . $c);
  19.         $session->set('c', $c + 1);
  20.         return $response;
  21.     }
  22. }

Ez persze megint egy olyan dolog, amire fejlesztéskor kisebb valószínűséggel lesz szükség, de ha élesben kell, akkor már fejlesztéskor is érdemes azzal dolgozni. Azt tudni kell a Memcached-ről, hogy skálázni csak úgy lehet, ha a php programban mindegyik példány címét felveszed, majd egy algoritmus alapján hol egyik, hol másik szerverre kerülnek az adatok. Ha viszont valamelyik szerver kiesik, a többi nem tudja átvenni a helyét, az azon levő adatok elvesznek. Ha ezt elkerülnéd, akkor a Redis-szel próbálkozhatsz. Ez a fejezet részletesen azt viszont már nem tárgyalja.

Ha egyedi szerverkonfigurációra is szükség van, akkor azt szintén bele lehet tenni saját Docker image-be. Persze, amíg folyamatosan szerkesztgetni kell, fel is lehet csatolni a konténerbe. Végül pedig, amire még valószínűleg szükséged lesz, az alkalmazás debugolása. az XDebug erre egy elterjedt eszköz. Ez viszont érezhetően lassíthatná a programot, ezért csak fejlesztői környezetben ajánlott telepíteni, ami a Dockerfile-hoz való következő sorok hozzáadásával történhetne:

  1. RUN yes | pecl install xdebug \
  2.  && echo "zend_extension=$(find /usr/local/lib/php/extensions/ -name xdebug.so)" > /usr/local/etc/php/conf.d/xdebug.ini \
  3.  && echo "xdebug.remote_enable=on" >> /usr/local/etc/php/conf.d/xdebug.ini \
  4.  && echo "xdebug.remote_autostart=off" >> /usr/local/etc/php/conf.d/xdebug.ini \
  5.  && echo "xdebug.remote_connect_back=on" >> /usr/local/etc/php/conf.d/xdebug.ini \
  6.  && echo "xdebug.idekey=XDEBUG" >> /usr/local/etc/php/conf.d/xdebug.ini

Itt még az lehet probléma, hogy amennyiben kódsoronként léptetve debugolod a programot valamilyen klienssel, a HTTPD szerveren a PHP FPM-hez definiált proxy nem fogja megvárni a program végét, ezért a böngészőben már csak hibaüzenet fogsz látni. Ez elkerülhető a timeout megnövelésével az alábbi módon:

  1. <Proxy "fcgi://php:9000">
  2.     ProxySet timeout=3600
  3. </Proxy>

Befejezés

Nehéz minden kérdést lefedni, mert a problémák és így a megoldásaik egyediek lehetnek, de annyit kijelenthetek, hogy sokszor a speciálisabb igényekre is viszonylag egyszerű megoldás. Talán csak rájönni nem az. Viszont minél többet foglalkozol a Dockerrel, annál könnyebb lesz. A speciálisnak tűnő igények pedig gyakran nem is olyan speciálisan, mint hinnéd, így valaki már talán fel is tette a kérdést egy fórumon, amit kis kereséssel meg is találhatsz. Azt nyilván nem állíthatom, hogy minden megoldható Dockerben, hiszen mindent még én sem próbáltam ki. Az Chuck Norris feladata... Félni ugyanakkor nem szabad a Dockerrel fejlesztéstől és üzemeltetéstől, mert az esetek többségében eddigi tapasztalataim szerint többet segít, mint amennyit esetleg akadályoz és tanulásra kényszerít.

Források

[Tartalom]

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

Új hozzászólás