Ako postoji potreba za testiranjem PHP aplikacija na različitim verzijama PHP-a, to je lako moguće učiniti pomoću Docker kontejnera. Naime, na računalu na kojem se razvija PHP aplikacija može se podići više različitih Docker kontejnera, koristeći za svaki kontejner različitu verziju PHP-a.
U ovom članku prikazat će se kako iskoristiti postojeće specifikacije za Docker kontejnere za posluživanje PHP aplikacija, kako koristiti jedan izvorni kod aplikacije za posluživanje na različitim kontejnerima te kako definirati lokalne domene za lak pristup aplikacijama na različitim kontejnerima.
Instalacija Docker-a
Za potrebe ovog članka korišten je operacijski sustav Microsoft Windows 10. Na njemu je instaliran Docker CE (Docker Community Edition) prema uputama u službenoj dokumentaciji za Docker https://docs.docker.com/install/.
Debian, Apache, PHP (DAP) Docker kontejneri
Na poveznici https://store.docker.com može se pronaći mnoštvo specifikacija za Docker kontejnere koji koriste PHP. Također, na poveznici https://store.docker.com/images/php nalazi se službena specifikacija za Docker kontejner koji sadrži PHP instalaciju s osnovnim PHP postavkama i ekstenzijama.
Te Docker specifikacije možemo preuzeti i prilagoditi svojim potrebama. Na primjer, možemo definirati instalaciju dodatnih PHP ekstenzija koji su potrebni za našu aplikaciju ili podesiti postavke web-poslužitelja koji će se koristiti u kontejneru i slično.
Za potrebe ovog članka iskoristit ćemo postojeću Docker specifikaciju iz Git repozitorija na poveznici https://github.com/cicnavi/dockers. Ona se oslanja na službeni PHP Docker kontejner te definira dodatne postavke za instalaciju PHP ekstenzija potrebnih za pokretanje Laravel PHP aplikacija.
Repozitorij 'cicnavi/dockers' možemo klonirati lokalno tako da unesemo sljedeću naredbu (potrebno je imati instaliran Git):
git clone https://github.com/cicnavi/dockers.git dockers
Repozitorij 'cicnavi/dockers' sadrži mapu 'dap' (označava Debian, Apache, PHP) u kojoj se nalazi nekoliko dodatnih mapa. Svaka ta dodatna mapa označava verziju PHP-a koja se koristi u kontejneru. Tako postoji mapa '5.6' koja se odnosi na verziju 5.6 PHP-a, mapa '7.1' koja se odnosi na verziju 7.1 PHP-a i tako dalje. Dakle, svaka ta mapa sadrži specifikaciju jednog Docker kontejnera s određenom verzijom PHP-a.
U samoj mapi 'dockers/dap' postoji datoteka 'docker-compose.yml' u kojoj je navedena specifikacija za pokretanje svih definiranih kontejnera. Ovako izgleda dio za definiranje kontejnera verzije 7.2 PHP‑a:
Iz ove specifikacije može se iščitati da će se kontejner zvati '72.dap.test', da će koristiti sliku (engl. image) 'cicnavi/dap:7.2', da će koristiti lokalni port 8072 i da će ga mapirati na port 80 u samom kontejneru te da će montirati određene lokalne putanje na definirane putanje u kontejneru.
U nastavku datoteke nalazi se specifikacija svih ostalih kontejnera, a također se nalazi i specifikacija za kontejner MySQL jer se ta baza često koristi u PHP aplikacijama.
Pokretanje DAP Docker kontejnera
Nakon kloniranja repozitorija 'cicnavi/dockers' možemo ući u mapu 'dockers/dap' pomoću naredbe:
cd dockers/dap
Budući da se u tom direktoriju nalazi datoteka 'docker-compose.yml', sve definirane kontejnere možemo pokrenuti pomoću naredbe:
docker-compose up -d
Ova naredba će podići nekoliko kontejnera za posluživanje PHP aplikacija te jedan kontejner za posluživanje MySQL baze podataka.
Prvo pokretanje trajat će nekoliko minuta, a svako iduće pokretanje tek nekoliko sekundi. Dakle, nakon završetka imat ćemo nekoliko podignutih kontejnera koje možemo iskoristiti za posluživanje naših PHP aplikacija. U našem slučaju imamo tri kontejnera koji koriste verzije 5.6, 7.1 i 7.2 PHP-a te jedan kontejner s MySQL serverom.
U datoteci 'docker-compose.yml' za primjer smo vidjeli da kontejner '72.dap.test' koristi lokalni port 8072 za mapiranje na kontejner.
To znači da možemo iskoristiti taj port za pristup Apache poslužitelju u kontejneru (port 8072 je mapiran na port 80). Dakle, u lokalnom pregledniku jednostavno možemo unijeti adresu (zajedno s portom) http://localhost:8072/ i prikazat će nam se početna stranica poslužitelja (prema početnim DAP postavkama prikazat će se informacije o PHP instalaciji na poslužitelju).
Da bismo zaustavili podignute kontejnere, možemo unijeti naredbu (moramo se nalaziti u mapi u kojoj se nalazi datoteka 'docker-compose.yml'):
docker-compose down
Posluživanje PHP aplikacija na kontejneru
U datoteci 'docker-compose.yml' možemo vidjeti lokalne putanje koje su montirane na određene putanje u samom kontejneru. Na primjer, za kontejner '72.dap.test' imamo sljedeću specifikaciju za putanje:
Dakle, lokalna putanja '. /7.2/src' je mapirana na putanju u kontejneru '/var/www/src'. Lokalna putanja ' ./7.2/html' je mapirana na putanju u kontejneru '/var/www/html'. Lokalna putanja ' ./7.2/apache-config' je mapirana na putanju u kontejneru '/etc/apache2/config-from-host'.
Za početak nas zanimaju prva dva mapiranja, dakle, 'src' i 'html' mapiranja. Zamisao je da mapa 'src' sadrži izvorni kod same PHP aplikacije, a mapa 'html' sadrži javni dio PHP aplikacije, tj. onaj izvorni kod koji će posluživati web-poslužitelj.
Recimo da imamo jednu PHP aplikaciju koju želimo testirati na različitim verzijama PHP-a. Teoretski bismo mogli kopirati sav izvorni kod u 'src' mapu svih kontejnera na kojima želimo testirati našu aplikaciju. Ali, to rješenje bi značilo da bismo svaku promjenu u izvornom kodu aplikacije morali ručno sinkronizirati na svim mjestima na kojima imamo kopiju aplikacije.
Zbog toga želimo imati samo jedan izvor izvornog koda koji poslužujemo na više kontejnera. To možemo postići korištenjem simboličnih linkova (da, čak i u operacijskom sustavu Windows).
Recimo da imamo sljedeći scenarij:
- želimo testirati aplikaciju na verzijama 5.6 i 7.2 PHP-a
- PHP aplikacija nalazi se na putanji E:\projects\laravel-test
- putanje do montiranih mapa 'src' i 'html' za kontejnere su:
- za PHP 5.6:
- E:\dockers\dap\5.6\src
- E:\dockers\dap\5.6\html
- za PHP 7.2:
- E:\dockers\dap\7.2\src
- E:\dockers\dap\7.2\html.
- za PHP 5.6:
U ovom trenutku možemo otvoriti CMD i iskoristiti naredbu 'mklink' za stvaranje simboličnih poveznica koje će pokazivati na našu PHP aplikaciju u svakom kontejneru. Prvo ćemo stvoriti simboličnu poveznicu za kontejner dap\5.6. Ako pogledamo sintaksu naredbe, vidjet ćemo da je potrebno definirati tip poveznice, samu poveznicu te putanju na koju će poveznica pokazivati.
Za naše potrebe stvorit ćemo poveznicu 'Directory Junction' (simbolički link na mapu), sama poveznica bit će 'E:\dockers\dap\5.6\src\laravel-test', a pokazivat će na putanju 'E:\projects\laravel-test'. Čitava naredba izgleda ovako:
mklink /J E:\dockers\dap\5.6\src\laravel-test E:\projects\laravel-test
Isto možemo napraviti i za kontejner '72.dap.test', samo prilagodimo poveznicu:
mklink /J E:\dockers\dap\7.2\src\laravel-test E:\projects\laravel-test
U naredbenom retku to izgleda ovako:
U ovom trenutku naši kontejneri imaju dostupan izvorni kod naše aplikacije u svojim 'src' mapama, ali im još nismo rekli koji dio koda će se posluživati javno. Dakle, sljedeći korak je u samim kontejnerima definirati putanju koja će se posluživati javno u 'html' mapi.
Da bismo to mogli napraviti moramo ući u naredbeni redak samih kontejnera. To možemo napraviti pomoću naredbe 'docker exec'. Naredbe je potrebno pokrenuti za svaki kontejner u kojem želimo posluživati aplikaciju. Krenimo s kontejnerom '56.dap.test' koji koristi verziju 5.6 PHP-a.
docker exec -it 56.dap.test bash
Ova naredba će pokrenuti naredbu 'bash' na kontejneru '56.dap.test' i omogućiti nam daljnju interakciju s kontejnerom:
Primijetite da smo odmah pozicionirani u mapu '/var/www/html'. U ovom trenutku možemo unijeti naredbu za stvaranje simboličkog linka na javni dio naše aplikacije. U našem slučaju imamo aplikaciju Laravel koja u svojoj korijenskoj mapi ima mapu 'public' za posluživanje aplikacije. Dakle, trebamo stvoritii simboličku poveznicu na mapu 'public'.
Puna putanja do te mape je /var/www/src/laravel-test/public. Neka se poveznica zove kao naša aplikacija, dakle 'larevel-test'. Kao operacijski sustav kontejnera koristi se Debian, pa možemo iskoristiti naredbu 'ln' za stvaranje simboličke poveznice. Sintaksa naredbe je:
ln [OPTION]... [-T] TARGET LINK_NAME
Kao opciju ćemo koristiti '-s', što označava simboličku poveznicu. Kao metu poveznice koristit ćemo putanju '/var/www/src/laravel-test/public', a naziv same poveznice će biti 'laravel-test'. Čitava naredba izgleda ovako (moramo se nalaziti u mapi '/var/www/html'):
ln -s /var/www/src/laravel-test/public laravel-test
Nakon uspješnog stvaranja poveznice možemo izaći iz kontejnera pomoću naredbe:
exit
Istu stvar moramo napraviti za kontejner '72.dap.test', pa možemo unijeti sljedeće naredbe:
docker exec -it 72.dap.test bash
ln -s /var/www/src/laravel-test/public laravel-test
exit
To je to, u ovom trenutku možemo pristupiti našoj aplikaciji na različitim verzijama PHP-a. Pomoću oznake porta određujemo koji kontejner ćemo koristiti za posluživanje aplikacije. Na primjer, na adresi http://localhost:8056/laravel-test/ je aplikacija koja se poslužuje na kontejneru '56.dap.test', a na adresi http://localhost:8072/laravel-test/ je aplikacija koja se poslužuje na kontejneru '72.dap.test'.
Na ovaj način možemo testirati neograničen broj aplikacija na istim kontejnerima, a u URL-u definiramo kojoj aplikaciji želimo pristupiti (u našem slučaju je to bio 'laravel-test').
Definiranje imena poslužitelja
U ovom trenutku za pristup lokalnoj IP adresi koristimo ime 'localhost', a pomoću porta definiramo na koji port se želimo spojiti. Kako bismo jasnije odredili na koji kontejner se želimo povezati, možemo unijeti lokalnu definiciju imena i IP adrese koja će se koristiti. U operacijskom sustavu Windows to se može napraviti u datoteci 'C:\Windows\System32\drivers\etc\hosts'. U našem slučaju u toj datoteci definirat ćemo sljedeća mapiranja imena na IP adrese:
127.0.0.56 56.dap.test
127.0.0.71 71.dap.test
127.0.0.72 72.dap.test
127.0.0.100 mysql.dap.test
Ovime smo definirali da će se kod unosa određenog imena poslužitelja to ime prevesti na definiranu IP adresu. Dakle, ako unesemo adresu http://56.dap.test/ povezat će nas na IP adresu 127.0.0.56, ako unesemo adresu http://72.dap.test/ povezat će nas na IP adresu 127.0.0.72 i tako dalje.
Ali, ako želimo doći do kontejnera i dalje moramo unositi port u adresu. To je zato jer se prema početnim postavkama koristi port 80, ako ga ne definiramo drugačije u URL-u. Dakle, ako želimo doći do kontejnera '56.dap.test', moramo i dalje definirati port 8056 u adresi: http://56.dap.test:8056/. Budući da port definira na koji kontejner se spajamo, unosom adrese http://72.dap.test:8056/ bismo se spojili na kontejner '56.dap.test', jer smo u adresi koristili port 8056 koji se mapira na kontejner '56.dap.test', a ne '72.dap.test' kako smo definirali u URL-u.
Ovaj problem možemo riješiti jednostavnim mapiranjem lokalnih portova na portove koje koriste kontejneri.
Mapiranje portova
Dakle, želja nam je da se kad unesemo adresu http://56.dap.test/ automatski spojimo na kontejner '56.dap.test' (da ne moramo definirati port 8056 u adresi). To možemo napraviti tako da kada stigne zahtjev na određenu IP adresu mapiramo port 80 (jer se prema početnim postavkama koristi taj port ako ga ne navedemo u adresi) na port koji koristi sam kontejner.
Na primjer, za kontejner '56.dap.test' u datoteci 'hosts' definirali smo ime '56.dap.test' koje će se prevesti u IP adresu 127.0.0.56. Kada stigne zahtjev na toj adresi, na portu 80, prebacit ćemo zahtjev na port 8056 koji koristi kontejner '56.dap.test'. Čitava naredba izgleda ovako:
netsh interface portproxy add v4tov4 listenport=80 listenaddress=127.0.0.56 connectport=8056 connectaddress=127.0.0.1
Istu stvar možemo napraviti i za ostale kontejnere. Za '71.dap.test':
netsh interface portproxy add v4tov4 listenport=80 listenaddress=127.0.0.71 connectport=8071 connectaddress=127.0.0.1
Za '72.dap.test':
netsh interface portproxy add v4tov4 listenport=80 listenaddress=127.0.0.72 connectport=8072 connectaddress=127.0.0.1
Za 'mysql.dap.test':
netsh interface portproxy add v4tov4 listenport=3306 listenaddress=127.0.0.100 connectport=33306 connectaddress=127.0.0.1
U ovom trenutku možemo koristiti samo naziv kontejnera u URL-u i bit ćemo povezani s točno određenim kontejnerom! Na primjer, ako unesemo adresu http://56.dap.test/, preglednik će se spojiti s kontejnerom '56.dap.test', ako unesemo adresu http://72.dap.test/, preglednik će se spojiti s kontejnerom '72.dap.test' i tako dalje.
Naša aplikacija je sada dostupna adresama http://56.dap.test/laravel-test/ (koristi se kontejner s verzijom 5.6 PHP-a) i http:/72.dap.test/laravel-test/ (koristi se kontejner s verzijom 7.2 PHP-a).
Definiranje pod-domene za aplikaciju
Da bismo pristupili našoj 'laravel-test' aplikaciji, do sada smo naziv naše aplikacije morali stavljati u nastavku URL-a: http://72.dap.test/laravel-test/. Ako želimo, možemo definirati dodatne postavke za Apache poslužitelj i definirati posebnu poddomenu koja će se koristiti za našu aplikaciju. Kod Apache poslužitelja to možemo napraviti tako da definiramo dodatnu VirtualHost konfiguraciju.
U svakoj mapi kontejnera nalazi se mapa 'apache-config'. U toj mapi možemo definirati dodatne '.conf' datoteke koje će učitati Apache poslužitelj u kontejneru. U spomenutoj mapi već se nalazi primjer datoteke (ima nastavak .conf-example) u kojoj je definiran dodatni VirtualHost, pa tu datoteku možemo iskoristiti za podešavanje naše aplikacije.
Na primjer, u mapi 'E:\dockers\dap\5.6\apache-config' imamo datoteku '100-php-app.56.dap.test.conf-example'.
Nju ćemo kopirati i preimenovati u ' 001-laravel-test.56.test.dev.conf' (obavezno moramo maknuti dio s kraja datoteke '-example', inače je Apache neće učitati).
Tu novu datoteku ćemo sada urediti tako da ćemo otkomentirati dio koji definira VirtualHost (maknuti '#' na početku linije) i podesiti putanje do naše 'laravel-test' aplikacije:
<VirtualHost *:80>
ServerAdmin mivanci@srce.hr
DocumentRoot "/var/www/html/laravel-test"
ServerName laravel-test.56.dap.test
ErrorLog "${APACHE_LOG_DIR}/laravel-test.56.dap.test-error.log"
CustomLog "${APACHE_LOG_DIR}/laravel-test.56.dap.test-access.log" common
<Directory "/var/www/html/laravel-test/">
Options Indexes FollowSymLinks MultiViews
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
U samoj datoteci to izgleda ovako:
Istu stvar ćemo napraviti u mapi ' E:\dockers\dap\7.2\apache-config'. Kopirat ćemo datoteku '100-php-app.72.dap.test.conf-example' i imenovati je '001-laravel-test.72.dap.test.conf'. U njoj ćemo definirati VirtualHost:
<VirtualHost *:80>
ServerAdmin mivanci@srce.hr
DocumentRoot "/var/www/html/laravel-test"
ServerName laravel-test.72.dap.test
ErrorLog "${APACHE_LOG_DIR}/laravel-test.72.dap.test-error.log"
CustomLog "${APACHE_LOG_DIR}/laravel-test.72.dap.test-access.log" common
<Directory "/var/www/html/laravel-test/">
Options Indexes FollowSymLinks MultiViews
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
Nakon toga možemo ponovno pokrenuti naše kontejnere pomoću naredbe kako bi se primijenile nove Apache postavke (moramo se nalaziti u mapi u kojoj se nalazi datoteka 'docker-compose.yml'):
docker-compose restart
U ovom trenutku imamo definirane virtualne hostove, ali ako u naš preglednik pokušamo unijeti URL http://laravel-test.56.dap.test/ ili http://laravel-test.72.dap.test/, dobit ćemo poruku da stranica nije dostupna. To je zato jer trenutno ne postoji mapiranje imena 'laravel-test.56.dap.test' i 'laravel-test.72.dap.test' u određene IP adrese.
Dakle, u 'hosts' datoteku trebamo dodati sljedeće linije (koristit ćemo iste IP adrese koje smo koristili za imena samih kontejnera):
127.0.0.56 laravel-test.56.dap.test
127.0.0.72 laravel-test.72.dap.test
Sada možemo pristupiti našoj aplikaciji tako da u preglednik unesemo adrese:
- http://laravel-test.56.dap.test/ - za aplikaciju koja se poslužuje pomoću verzije 5.6 PHP-a.
- http://laravel-test.72.dap.test/ - za aplikaciju koja se poslužuje pomoću verzije 7.2 PHP-a.