GitLab jako Continuous Integration (nejen) pro PHP

V článku si ukážeme, jak kromě hostování a správy git repositářů můžeme GitLab používat pro CI (Continuous Integration). Zajímavé na něm je, že na rozdíl od konkurence to nabízí i v hostované verzi zadarmo.
Znáte GitLab? Dlouho jsem si myslel, že to je jen nástroj pro pohodlnější správu git repositářů na vlastním serveru. Už to dávno není pravda, teď je to celá platforma pro řízení vývoje. Kromě hostování git repositáře umí issues a merge requesty pro code review. Jako nejdůležitější součást bych vyzdvihl integrované CI (Continuous Integration), které je podobné TravisCI. Ale na rozdíl od něj je zadarmo i pro private repositáře. Tím je odbouraný další důvod, proč na menších projektech nepoužívat CI.
GitLab nabízí tři varianty:
- hostovanou na Gitlab.com – zdarma neomezeně private repositářů, uživatelů a CI buildů (+možnost objednání lepšího supportu)
- self-hosted community edition (open-source)
- placenou Enterprise variantu (self-hosted), která navíc obsahuje další integrace a vychytávky pro větší týmy
Nám bude stačit ta první, tedy hostovaná varianta – funguje podobně jako GitHub, ale má navíc právě CI.
Continuous Integration
Jen stručně, co Continuous Integration (CI) je:
Kontinuální integrace je technika používaná při vývoji software, kdy členové týmu integrují svůj kód často, obvykle aspoň jednou za den, což vede k mnoha integracím každý den. Každá integrace je ověřena automatickým sestavením (včetně testů), aby byly případné problémy objeveny co nejdříve. Mnoho týmů zjistilo, že tento přístup vede k výraznému snížení problémů při integraci a umožňuje jim rychleji dodávat
kvalitnější software.
Přeloženo z článku Continuous Integration od Martina Fowlera
Zjednodušeně řečeno, pokud používáte branche a rebase před merge (což je vlastně integrace změn z masteru do vaší branche), tak se to budete snažit dělat co nejčastěji a průběžně budete spouštět testy a automatizované kontroly.
Jak vše nastavit?
Výhodou CI serveru je, že spouští build automaticky pro každou změnu (v tomto případě pro každý merge request nebo push do repositáře), takže se nestane, že by na to někdo mohl zapomenout. Aby bylo co spouštět, potřebujeme nejdříve build skript, který spustí různé nástroje – php lint, PHPUnit, kontrolu coding standards apod.
V článku vás provedu nastavením CI buildu na GitLabu pro PHP aplikaci. V první polovině článku se zaměřím na samotný build skript, který je na CI serveru nezávislý. V druhé polovině článku vám předvedu nastavení GitLab CI, které použije předtím vytvořený build skript.
Vytvoření build scriptu
Pro vytvoření build scriptu použijeme scripts
přímo z Composeru. Pro komplexnější skripty můžete zvolit Phing, ale na začátku bych doporučoval dát šanci skriptům v Composeru. (Koneckonců v JS světě se teď také vracejí od buildovacích nástrojů Gulp/Grunt zpátky k npm skriptům.)
Nejjednodušší skript může vypadat nějak takto (předpokládá, že máme vytvořený phpunit.xml.dist
s konfigurací PHPUnitu, aby ho šlo spouštět jen příkazem vendor/bin/phpunit
):
{
"require-dev": {
"phpunit/phpunit": "5.6.1"
},
"scripts": {
"test": "phpunit"
}
}
A pokud zavoláme composer test
(případně composer run-script test
, pokud by se název skriptu překrýval s nativním příkazem Composeru), spustí se dle očekávání PHPUnit.
Pozn.: Composer automaticky přidá do cesty adresář
vendor/bin/
takže ho není potřeba do příkazů vypisovat.
V dalším kroku si přidáme kontrolu coding standards:
{
"require-dev": {
...
"squizlabs/php_codesniffer": "2.7.1",
},
"scripts": {
...
"phpcs": "phpcs --standard=PSR2 src tests"
}
}
Jak jste se už jistě dovtípili, tak kontrolu můžeme zavolat pomocí composer phpcs
.
Síla skriptů v Composeru je v možnosti volat je navzájem pomocí @
. Vytvoříme tedy meta příkaz, který bude volat ostatní dva:
{
"scripts": {
"build": [
"@test",
"@phpcs"
],
...
}
}
A composer build
spustí jak testy, tak kontrolu coding standards.
Ještě se mi osvědčilo přidat si do buildu i volání composer install
, aby build vždy proběhl se správnými verzemi balíčků. Výsledný build skript může vypadat následovně:
{
"scripts": {
"build": [
"@composer install --no-progress --no-interaction --no-suggest",
"@test",
"@phpcs"
],
"test": "phpunit",
"phpcs": "phpcs --standard=PSR2 src tests"
}
}
Při každém spuštění composer build
se sice dozvíme, jestli testy a kontrola coding standards procházejí, ale nemáme jistotu, že na to některý vývojář nezapomene. A tady přichází ke slovu CI server, v našem případě GitlabCI.
Za domácí úkol doporučuji na začátek buildu přidat ještě PHP Parallel Lint, který kontroluje zda
.php
soubory jsou vůbec validní.
Nastavení buildu na GitlabCI
Registrací na GitLabu, vytvořením projektu a souvisejícími úkoly se tu zabývat nebudu, neměl by v tom být žádný problém. Pustíme se rovnou do konfigurace CI.
Pro konfiguraci se používá soubor .gitlab-ci.yml
v rootu projektu, který v minimalistické podobě může vypadat takto:
image: geertw/docker-php-ci:7.0
my_app:
script:
- composer build
První řádek říká, na jakém image se build spustí (můžeme použít jakýkoliv image z Docker Hubu). Dále máme zapsaný úkol my_app
, pro který se spustí příkaz composer build
(proč je to takhle krkolomněji na dalším řádku, uvidíme později).
V GitLabu není potřeba nic nastavovat, pokud pushnete branch se souborem .gitlab-ci.yml
, spustí se build automaticky. Detaily najdete v projektu na GitLabu v záložce Pipelines. Pokud se vám povede spustit tento skript, tak už máte vyhráno – dále už tu konfiguraci budeme jen vylepšovat.
Tip: Pro kontrolu validity konfigurace můžete použít jejich linter (je potřeba být přihlášen). Výhodou je, že nemusíte špinit git historii a čekat na failnutí buildu.
Výběr image a konfigurace
Nevybral jsem oficiální image s čistým PHP, ale jiný, který už je připravený pro použití v GitLabCI – obsahuje navíc git, Composer a některá rozšíření. Ještě místo geertw/docker-php-ci:7.0
, který je v ukázce, může být vhodnější použít geertw/docker-php-ci:7.0-no-xdebug
`, který mu tam před několika dny poslal Tomáš Fejfar. Pokud nepotřebujete sbírat code coverage z testů, Xdebug nedává v CI smysl a jen bude build zpomalovat.
(V oficiální dokumentaci ukazují postup instalace závislostí do základního image v rámci buildu, což v během mého testování klidně minutu nebo dvě přidalo. Proto používám odlišný postup – už hotový image.)
Pokud pro svůj projekt máte specifické požadavky na rozšíření nebo nainstalované aplikace, tak vám nic nebrání vytvořit vlastní image a používat ten. Nicméně to už přesahuje rozsah článku, třeba se tématu chopí někdo povolanější.
Pokud se vám nechce vytvářet vlastní image a v rámci buildu potřebujete něco nastavit, tak je možné použít hook before_script
. Pro ukázku jsem do něj přidal jen příkaz na vypsání verze PHP, což se nám bude hodit později. Jen pozor, že pokud budete něco instalovat v každém buildu, tak to zbytečně prodlouží jeho běh.
before_script:
- php -v
Kde vlastně buildy běží? GitLab se domluvil s DigitalOcean a ten mu zadarmo poskytujete VPS pro běh buildů. (Případně by mělo být možné přidat vlastní VPS, ale to jsem nezkoušel.)
Spouštění na různých verzích PHP
Pokud potřebujete build spouštět na různých verzích PHP (ať už proto, že musíte podporovat i starší verze, nebo naopak chystáte přechod na novou), tak to v GitLabCI není problém. Stačí upravit konfiguraci takto (a odebrat původní image:
):
my_app:5.6:
image: geertw/docker-php-ci:5.6-no-xdebug
script:
- composer build
my_app:7.0:
image: geertw/docker-php-ci:7.0-no-xdebug
script:
- composer build
Build se automaticky spustí na obou image.
Cachování
Zbytečně mnoho času stráví build na stahování závislostí přes Composer. Lze to snadno vylepšit pomocí nastavení cachování vybraných adresářů (v případě Composeru to bude vendor
).
cache:
paths:
- vendor/
Po úspěšném buildu se vybraný adresář uloží mimo Docker container a při příštím běhu je obnoven. Přináší to docela znatelné zrychlení, tak to určitě nevynechejte.
Services
Pokud budete v rámci buildu potřebovat jakoukoliv další aplikaci (databázi, Redis, cokoliv), tak se to řeší prostřednictvím tzv. services – k hlavnímu kontejneru můžete napojit libovolné další.
Například pro MySQL by to vypadalo takto:
services:
- mysql:latest
variables:
# Configure mysql environment variables (https://hub.docker.com/r/_/mysql/)
MYSQL_DATABASE: zdrojak_demo
MYSQL_ROOT_PASSWORD: super_secure_password
mysql:latest
říká, že chceme použít oficiální image pro MySQL v poslední verzi. Dále pak nastavíme konfigurační proměnné pro konkrétní image. Obdobně si můžeme nastavit jakoukoliv další service (tzn. připojit si jakýkoliv image).
Výsledek
Výsledná konfigurace .gitlab-ci.yml
pro buildování aplikace, která pro testy potřebuje MySQL databázi, může vypadat takto:
before_script:
- php -v
services:
- mysql:latest
variables:
MYSQL_DATABASE: "zdrojak_demo"
MYSQL_ROOT_PASSWORD: "123456"
my_app:5.6:
image: geertw/docker-php-ci:5.6-no-xdebug
script:
- composer build
my_app:7.0:
image: geertw/docker-php-ci:7.0-no-xdebug
script:
- composer build
cache:
paths:
- vendor/
Závěr
Pro mě osobně je GitLab a jejich CI překvapení a své miniprojekty, které mám aktuálně na Bitbucketu bez CI, začnu postupně převádět na Gitlab a nastavovat k nim CI. A samozřejmě se nemusíte omezovat jen na projekty v PHP.
Teď už opravdu nevidím žádný důvod, proč CI nepoužívat. Pokud na nějaký přijdete, tak vám ho v komentářích rád zkusím vyvrátit. Případně vám pomohu s nastavením buildu.
Mě by se i líbil, jen ta jeho rozežranost se mi nezdá. 4GB RAM? Jakože na git!?
Tak je to ruby aplikace a PostgreSQL databáze, takže to zas tolik není.
Díky za zajímavý článek.
Bitbucket nedávno spustil https://bitbucket.org/product/features/pipelines, předpokládám, že jde v podstatě o totéž. Má někdo zkušenost?
Mam zkusenost, zkousel jsem to pred casem. Bylo to docela slozite rozchodit, ale nakonec to funguje v pohode. Nemuzes na to mit prilis velke naroky. Obcas potrebuju spoustet nejake buildy v navaznosti na jine a obcas mam i jinak slozite triggery a eventy, coz se moc od Pipelines neda ocekavat. Na jednoduche veci typu: push, testy, deploy to ale funguje dobre.
Vypadá to hezky, akorát v mezičase podobné Pipelines implementoval i Bitbucket, takže se stačí mrknout na jejich nastavení (co jsem se díval na GitLabCI soubor, tak je to hodně podobné) a nemusíte vlastně nic migrovat :-)
jj, zachytil jsem to, ale od příštího roku to budou mít placené a ve free verzi bude 50buildminut/měsíc viz https://bitbucket.org/product/pricing/upcoming?tab=host-in-the-cloud
A zdá se, že to pořádně neumí databáze: https://confluence.atlassian.com/bitbucket/test-with-databases-856697462.html
A Bitbucket je zadarmo jen do 5 uživatelů v repositáři.
Super. Škoda, že jsme nedávno nastavili bitbucket + wercker. Nebo teda neni to asi škoda :-). Taky zadarmo. Docela by mě zajímaly zkušenosti z obou.
Vdaka za zlanok, gitlab je naozaj lakavy.
Uz spominany Bitbucket + wercker pouzivam taktiez a celkom mi vyhovuje. Plus bitpucket poslednu dobu zavadza Pipelines co je nieco ako plugin system, cize sa da integrovat s viacerymi rozlicnymi sluzbami.
Co sa stane v pripade ze nastane chyba?
Chapem to spravne, ze workflow funguje nasledovne:
vytvoris pull request, ten sa zvaliduje, nasledne spravca cez web UI uvidi ze CI to oznacilo za ok, tak sa to mergne do main branche?
Ako by si nastavil CI + automaticky deploy bez rucnych kontrol? Teda ak niekto commitne a CI zbehne bez chyby, tak aby sa to automatickly merglo a nasledne deploylo?
Drobnost:
PHP_CodeSniffer verzia 2.7.1 neexistuje. posledna verzia z vetvy 2 je 2.7.0
Ty Pipelines budou ve free od příštího roku docela omezené, viz odpověď na komentář výše.
Ad. workflow – ano, a ještě bych tam přidal code-review po tom, co je CI build OK. Pak fixy z review a když to bude OK, tak merge.
Celé automatizované workflow by mohlo fungovat pro staging, GitLab by to měl umět, ale nemám s tím zkušenost. Pro produkci to bude nebezpečné a pro staging složité (ve chvíli, kdy nějaké migrace DB mohou být nerevertnutelné)
Díky za nalezení chybky, opravíme.
Vdaka za super clanok, mam vsak jeden dotaz. Ako riesite cely tento buildovaci proces v pripade ze sa aplikacia nahrava na jednoduchy php hosting ktory ma len ftp a nic viac? Mate po ruke nejaky dalsi dodatocny server ktory ma rovnake nastavenia ako ostre ftp kde sa to cele zbuildi otestuje a potom sa to len 1:1 preklopi alebo sa to ma riesit nejakym inym sposobom? Ide to cele zautomatizovat ze sa tenťo zbuildeny projekt rovno aj nahra na konkretne ostre ftp?
CI může klidně fungovat nezávisle, jen jako „kontrola kvality“. Ten deploy by případně mohl jít v rámci Gitlabu automatizovat pomocí dg/ftp-deployment.
Ale jako nejlepší řešení mi přijde zbavit se FTP.
Lze nějak zapsat konfiguraci CI, které se vztahuje ke konkrétní branch? Aby se deploy na produkci provedl pouze při změně v master branch a deploy na stage se spustil při změně v beta branch?
Omezit jednotlivé joby podle branchí by mělo být možné pomocí atributů
only
aexcept
.Jsem lama, ale uz jsem cosi zaslechl o CI i dockeru. Mame tady nainstalovany gitlab lokalne na nasem serveru a chci se zeptat, jestli ten navod plati i pro tuto situaci.
Musi se tam zprovoznit docker, nebo to jde i bez dockeru, popripade vubec nejde a je nutne pouzit gitlab.com?
(Mam urcite zbrany nahravat firemni zdrojaky na gitlab.com, i kdyz je mozne pouzit privatni repository)
Zkušenost s tím přímo nemám, ale Docker podle mě potřeba není. Buildy klidně můžou běžet na stejném serveru, jen to není doporučené.
Docker má tu výhodu, že by pak mělo jít používat různé image, jak jsem ukazoval v článku. Jinak to poběží na stejných verzích SW jako jsou na serveru.
Ano CI je stejne pro vsechny verze Gitlabu.
Samotny job Gitlab CI jde sestavit pouze z command line prikazu. Jestli se to bude zpracovavat primo nebo pres docker zalezi na uzivateli.
Rozdil v self-hsoted variante je ze si proste urcite kde a na kolika strojich build probehne a mate je plne pod kontrolou.