A legegyszerűbb Docker fejlesztési munkafolyamat több rendszerből álló Laravel projektekhez
Címkék
PHP
Megosztás

A legegyszerűbb Docker fejlesztési munkafolyamat több rendszerből álló Laravel projektekhez

Kocsis József

Bevezető

Szoftverfejlesztési projekteken dolgozva sok olyan faktorral kell számolni, amelyek döntő jelentőségűek lehetnek a végcél szempontjából. Az egyik legfontosabb ezek közül az idő. Általános tévhit, hogy ezeknél a projekteknél (a Webcapital esetében webes alkalmazások fejlesztése) a munka nagy részét a programozás, avagy a kód megírása teszi ki. Természetesen ez projektenként változhat, de többször előfordult már, hogy a kódírással közvetetten kapcsolatos feladatok emésztettek fel - akár indokolatlanul - sok időt.

Ebben a bejegyzésben ezek közül az előkészítés, azaz az “onboarding” fázissal foglalkozunk, amelynek során kiválasztjuk, hogy milyen fejlesztői környezetben, milyen eszközökkel fogunk dolgozni.

Mit nevezünk lokális fejlesztési környezetnek

Egy weboldal fejlesztése során, kivételes esetektől eltekintve, nem közvetlenül az éles (a látogatók által nyilvánosan elérhető szerveren található) kódbázissal dolgozunk. A felhasználók számára lényegtelen a háttérben zajló folyamatok működése, számukra egyértelműen a végeredmény a fontos. A fejlesztők oldalán viszont kiemelten értékes, ha az éles rendszer minden működésbeli sajátosságát reprodukálni tudják a saját gépükön is. Az informatika hajnala óta kutatott és vitatott kérdése a fejlesztésnek, hogy milyen módon tudja egy szakember azt garantálni, hogy az applikáció, amely a specifikáció által elvárt módon működik a saját gépén, az ügyfél számára is megfelelő legyen.

Ennek a kihívásnak a megoldására használatos szakmai berkekben az éles rendszer leképezése a fejlesztői eszközökön, és esetünkben értjük alatta a webszerver és az azt futtató környezet beállításait, az adatbázismotor, filerendszer felépítését stb. Ezek összeségéből áll össze a fejlesztői környezet, melynek lokális (a fejlesztői gépen) történő beállítására és finomhangolására számtalan megoldás létezik.

Beállítás lépései és tapasztalatok egyszerű multitenant rendszerek esetében

Multitenant rendszerekről akkor beszélünk, amikor egy kódbázis szolgál ki több felhasználót egy szerveren. A Webcapital tipikusan ilyen rendszerekkel dolgozik. Ezeknél alapesetben 2, de esetenként 3 vagy több összetevő együttműködése adja a rendszer egészét. A leggyakoribb alkotóelemek a következők:

  • Kódbázis (Ez írja le az üzleti működés logikáját)
  • Adatbázis (Itt tárolja a rendszer az adatokat)
  • File alapú tartalmak (képek, dokumentumok) - ez opcionális

A szerver operációsrendszer verseny két legnagyobb résztvevője a Linux és a Windows. Mi kivétel nélkül Linux operációs rendszerrel futó szerverekkel dolgozunk. Az ezeken futó applikációkon végzett munka azonban nem zárja ki, hogy lokálisan Windows vagy macOS rendszerre építse fel a fejlesztő a környzetet.

A leggyakoribb fejlesztéshez használt eszközök elérhetőek mindhárom operációs rendszerre, így általában a fejlesztő preferenciája dönti el, hogy melyiket választja. Ebben a posztban nem térünk ki az egyes rendszerek sajátosságaira, de fontos megjegyezni, hogy még a webes fejlesztések terén is előfordulhatnak problémák a gyártók egyedi megoldásai miatt.

Jelenleg elérhető megoldások a többrendszeres projektek kezelésére lokális környezetben

A microservice architectúrák térnyerése következtében az elmúlt években gyakorivá vált, hogy egy projekt keretében a fő kódbázis mellett kisebb-nagyobb microrendszerek támogatják a fő rendszer működését. Kisebb fejlesztőcsapatok esetében gyakori, hogy a tagok az összes résztvevő kódbázison dolgoznak egyidejűleg, ezért fontos, hogy azonnal lássák, a rendszerek milyen pontokon befolyásolják egymás működését.

Korábban az ilyen rendszerek kezelése nehézkessen ment a hiányzó vagy kisszámú elérhető szoftveres megoldás miatt, manapság azonban a virtualizáció és a docker térnyerésével jelentősen csökkent a fejlesztés során a beállításra fordított idő. Vegyük sorra, hogy milyen megoldásokhoz nyúlhat egy fejlesztő, amennyiben többrendszeres projekten kell dolgoznia, és szeretné egy gépen kezelni az összes rendszert, a szerverbeállítások megtartásával.

Operációs rendszer-specifikus megoldások

Amennyiben a legnépszerűbb webszerverek közül az nGinx-et és az Apache-ot vesszük példának, ezekből létezik mindhárom említett operációs rendszerre telepíthető változat. A Webcapitalnál használt leggyakoribb programnyelv a PHP. Szerencsére mindkét webszerverhez, mindhárom operációs rendszerhez találunk megfelelő illesztést vagy telepíthető verziót a nyelvből.

A fejlesztők általában már kész csomagokkal dolgoznak, melyek tartalmazzák a webszerver, a programnyelv és az adatbázis motor, esetleg egy levelező kliens forrásfile-jait is. Ezekre jó példa a Laragon vagy a XAMPP. Előnyük, hogy a fejlesztők könnyen telepíthetik és beállíthatják saját ízlésük és igényeik szerint, viszonylag gyorsan.

Hátrányuk ezzel szemben, hogy amennyiben egy projektnek speciális környezeti függőségei vannak, akkor minden új telepítésnél ezeket a környezeti változókat is be kell állítani a lokális környezetben. Ha ráadásul ezek a függőségek nincsenek kellően dokumentálva, akkor értékes órák, szélsőséges esetben akár napok is rámehetnek egy új telepítés konfigurálására.

Virtualizáció

A Docker térnyerése előtt, haladó szakemberek számára, megoldás volt komplett szerverek futtatása lokális környezetben a virtualizáció segítségével. Ezekre kialakított applikációk a Virtualbox, a Multipass vagy a Vagrant.

Előnyük, hogy a beállított rendszerek könnyen költöztethetőek és másolhatóak, de első beállításuk sok időt igényel és használatukhoz haladó tudás szükséges, ezért junioroknak és gyakornokoknak nem ajánljuk. Erőforrásigényük is jóval nagyobb, ezért a régebbi eszközök csak nehézkesen tudták teljesíteni a követelményeket és gyakran bővítésre szorultak az akadálymentes munkavégzéshez.

Egy univerzális megoldás: Docker

A Docker maga számtalan külön posztot megérne, ebben azonban két aspektusára térnénk ki: a már említett XAMPP és Laragon csomagokhoz hasonlóan valamennyi operációs rendszerre telepíthető illetve, hogy használatával a szükséges függőségek leírása részévé válik a projektünknek. Ilyen módon tehát a szükséges futtatási környezet dokumentálásra kerül (a Dockerfile segítségével).

A Docker szintén virtualizált környezetben futtatja az alkalmazásunkat, azzal a különbséggel, hogy úgynevezett konténereket, kisebb virtualizált egységeket használ, és ezek összehangolásával teremti meg a számára fontos környezeti feltételeket. Fontos különbség azonban a korábban említett virtualizációs megoldásokkal szemben, hogy egyszerű paranccsori utasításokkal beállítható és elindítható. Egy egyszerű példa: PHP 8.1 használatával futtatunk egy parancsot (telepítjük egy Laravel projekt függőségeit, forrás itt):

docker run --rm 
    -u "$(id -u):$(id -g)" \
    -v $(pwd):/var/www/html \
    -w /var/www/html \
    laravelsail/php81-composer:latest \
    composer install --ignore-platform-reqs

A parancs futtatásával a Docker automatikusan biztosít számunkra minden alapvető feltételt, ami ahhoz szükséges, hogy a PHP script-ünk működjön, beleértve az operációs rendszert is.

Bár a művelet lényegesen egyszerűbb, mint kézzel telepíteni és beállítani egy virtuális szervert, azért egyedi parancsok megírása haladó feladatnak tekinthető. Alapesetben a Docker bázisú fejlesztéseknél a Dockerfile-ok írják le az egyes környezeti beállításokat. Ezeknek és az egyéb szolgáltatások összehangolását a docker-compose parancs segítségével végezzük.

A docker-compose

A Docker parancsok és egyéb szolgáltatások újrahasznosítására és dokumentálhatóvá, verziózhatóvá tételére ideális eszköz a docker-compose. A lényege, hogy a fenti parancsot leírhatjuk egy egyszerű konfigurációs fájlban, yaml formátumban. Egy ilyen docker-compose.yml fájban egy Node parancs futtatása például ennyire egyszerű:

version: '3'
services:
  node:
    image: node:latest
    command: "node -e 'console.log(123)'"

A fenti példán természetesen nem túl részletes, mindössze azt szemléltetjük vele, hogy mennyire egyszerű egy javascript parancsot megfuttatni még akkor is, ha nincs a gépünkön Node.js telepítve. Ha rövid példánkat elhelyezzük egy docker-compose.yml nevű fájban, a futtatás során a docker telepíti a Node.js-t a konténerünkbe.

A fejlesztők számára a legfontosabb szempont, hogy a létrehozott docker-compose.yml fájl ettől kezdve része lehet a projektünknek, hozzáadhatjuk a verziókövető rendszerünkhöz (pl. GIT). Ilyen módon a projektünk futtatókörnyezete automatikusan dokumentálva lesz és az egész fejlesztői csapat ugyanazon a környezeten dolgozhat.

Laravel Sail

A Webcapital projektjeinek nagy része a Laravel keretrendszert használja, ezért számunkra kiemelten örömteli volt a Laravel 8-as verziója, amelyben már megjelent a Laravel Sail nevű plugin. A Sail a docker-compose működését viszi még egy lépéssel tovább, és - kicsit leegyszerűsítve - a rendelkezésünkre bocsájt egy kimondottan Laravel projektek számára megírt docker-compose.yml fájlt, amit akár azonnal futtathatunk.

Több rendszerből álló Laravel projektek beállítási lépései és problémái

A Docker és a docker-compose megismerése után vizsgáljuk meg, hogy milyen problémák adódhatnak egy több rendszer együttműködéséből felépülő Laravel projekt beállítása során.

Egy egyszerű példa: adott egy CRM termék. A szolgáltatást az előfizető felhasználók kedvükre használhatják, azonban az előfizetések érvényességét egy másik rendszerben tartjuk számon. A példánkban az egyszerűség kedvéért a két rendszert egy szerveren futtatjuk, de külön adatbázist használnak.

Multi-system Docker Setup

Így tehát a docker-compose használata esetén két docker-compose fájlunk lesz, mindkét fájl az adott alkalmazás számára szükséges környezetet írja le.

Ilyenkor fellép egy nehézség: a Docker a projekt egyes szolgáltatásait hálózatokba csoportosítja, és így szervezi meg azok kommunkikációját egymással. A két docker-compose.yml fájl tartalmát ezért össze kell hangolni, hogy az alkalmazásaink képesek legyenek egymással kommunikálni munka közben.

Esetünkben három feltételnek kell teljesülnie:

  • a két kódbázisnak tudnia kell kommunkálni egymással, tehát pl. az egyik alkalmazás szkriptjeinek meg kell tudniuk hívni a másik alkalmazás szkriptjeit paranccsorból
  • a két kódbázisnak hozzá kell férnie a saját adatbázisához
  • a két projekt egymástól függetlenül is kell, hogy működjön, anélkül, hogy a docker-compose.yml fájlt ezért módosítani kelljen.

A két docker-compose-yml fájl ide vágó része alaphelyzetben:

# CRM: (tartalmazó mappa neve: crm)
version: '3'
...
services:
    laravel.test:
        networks:
            - sail
        volumes:
            - '.:/var/www/html'
mysql:
    networks:
        - sail
networks:
    sail:
        driver: bridge
...
# Előfizetések: (tartalmazó mappa neve: elofiz)
version: '3'
...
services:
    laravel.test:
        networks:
            - sail
        volumes:
            - '.:/var/www/html'
mysql:
    networks:
        - sail
networks:
    sail:
        driver: bridge
...

A fájlok ide vágó része tökéletesen egyezik, de ezzel az alapbeállítással a Docker két külön hálózatot fog létrehozni, amelyek nevét az általunk megadott névből, és a projektet tartalmazó mappa nevéből fogja generálni. A létező hálózatokat listázhatjuk a docker network ls paranccsal:

webcapital@webcapital:~$ docker network ls
NETWORK ID     NAME                            DRIVER    SCOPE
0ff495b6d149   crm_sail                        bridge    local
3fa732044205   elofiz_sail                     bridge    local

A Webcapital megoldása egy több rendszerből álló project kezelésére Laravel Sail-lel

A fenti példát alapul véve, az első feltétel teljesítése nagyon egyszerű. A Docker lehetővé teszi, hogy egy filerendszer mappát két container-be is felcsatoljunk egyidejűleg. Itt a csatolás iránya mindegy, mi az előfizetéseket kezelő rendszert fogjuk felcsatolni a CRM-be.

Fontos, hogy valamennyi módosítást követően le kell állítani és újraindítani a már futó konténereinket a docker-compose up, illetve Laravel Sail esetén a sail up paranccsal.

# CRM: (tartalmazó mappa neve: crm)
version: '3'
...
services:
    laravel.test:
        volumes:
            - '.:/var/www/html'
            - '/absolute/path/to/elofiz:/var/www/elofiz'
...
# Előfizetések: (tartalmazó mappa neve: elofiz)
version: '3'
...
services:
    laravel.test:
        volumes:
            - '.:/var/www/html'
...

Ilyen módon a két kódbázis képessé válik a kommunikációra a CRM konténer laravel.test image-ében.

Azonban, a második kitétel még nem teljesül. Az előfizetéseket kezelő alkalmazás ilyen módon nem fogja elérni a saját adatbázisát, mivel az egy másik hálózaton működik. Ha valamennyi szolgáltatást egy hálózatba pakoljuk, figyelnünk kell, hogy ne legyen név illetve port egyezés, mindegyik kódbázisnak ismernie kell például, hogy melyik adatbázist keresse:

# CRM: (tartalmazó mappa neve: crm)
version: '3'
...
services:
    laravel.test-crm:
        networks:
            - sail
mysql_crm:
    networks:
        - sail
    ports:
        - '3307:3306'
networks:
    sail:
        name: 'kozos-halozat'
...
# Előfizetések: (tartalmazó mappa neve: elofiz)
version: '3'
...
services:
    laravel.test-elofiz:
        networks:
            - sail
mysql_elofiz:
    networks:
        - sail
    ports:
        - '3306:3306'
networks:
    sail:
        name: 'kozos-halozat'
...

Ezzel a módosítással, mindegyik szolgáltatás saját név alatt fog futni. Ha most ellenőrizzük a hálózatokat láthatjuk, hogy bár fut mindkét container, csak egy közös hálózat jelenik meg:

webcapital@webcapital:~$ docker network ls
NETWORK ID     NAME                            DRIVER    SCOPE
0ff495b6d149   crm_sail                        bridge    local
3fa732044205   elofiz_sail                     bridge    local
caed1f17fc3e   kozos-halozat                   bridge    local

(A korábbi hálózatok törölhetőek a docker network rm paranccsal)

Az alkalmazásaink környezeti változói között be kell állítani az új DB szolgáltatás neveket (mysql-crm és mysql-elofiz) és máris mindkét oldal eléri a saját adatbázisát, bármelyik image-ből is futtassuk őket.

Már csak egy feladatunk van. A fenti példánál maradva, amennyiben csak a CRM rendszeren szeretnénk dolgozni, és az előfizetés alkalmazás kódja nincs is behúzva a projektbe (pl. egy junior fejlesztő számára delegált kisebb feladat miatt), a docker-compose hibát fog dobni, mert nem fogja találni a megadott /absolute/path/to/elofiz mappát.

Ezt a docker-compose alapértelmezett beállításaival és környezeti változókkal tudjuk áthidalni. A Laravel Sail automatikusan parse-olni fogja alkalmazásunk környezeti változóit, így szabadon létrehozhatunk egy elérési utat az előfizetéses alkalmazáshoz, pl. ELOFIZ_APP_VOLUME.

A docker-compose.yml fájl módosítása után, amennyiben a fenti környezeti változó nincs jelen, akkor is lesz egy alapértelmezett fallback értéke a mappának:

# CRM: (tartalmazó mappa neve: crm)
version: '3'
...
services:
    laravel.test:
        volumes:
            - '.:/var/www/html'
            - '${ELOFIZ_APP_VOLUME-/dev/null}:/var/www/elofiz'
...

Mivel a beállítási lehetőségek kombinációja magas szintű komplexitást eredményezhet a projektben, ezért azt javasoljuk, hogy a fokozatosság elvének jegyében csak a minimális beállításokat végezzük el kezdetben.

Ez a poszt csak egy rövid bemutató a több rendszeres projektek kezelési lehetőségeire a Laravel Sail segítségével. Léteznek sokkal bonyolultabb eszközök, de egyszerűbb projektek esetén, ahol fő a sebesség a docker-compose kényelmesen meg tudja oldani az összetettebb konfigurációs feladatokat is. Bármilyen észrevétel javaslat esetén írjatok nyugodtan a contact@webcapital.hu címre!