Školení Videa Blog

Nastavení GitLab CI pro produkční aplikaci

Tomáš Jacík  

GitLab CI je dnes moderní. Vychází o něm články s krásnými, jednoduchými příklady, jak jej začít používat. Pak ale člověk narazí. Jak se utkat s překážkami a vyjít z toho jako vítěz?

Už delší dobu jsem uvažoval, že vyzkouším GitLab a jeho CI jako alternativu k Bitbucketu. Prozatím jsem jako CI používal PHPCI, který sice nebyl špatný, ale spokojený jsem s ním zrovna také nebyl. Nakonec mě přesvědčil až článek Martina Hujera na Zdrojáku, který nastavení popisoval velice jednoduše. Tak jednoduché jsem to bohužel neměl. Nakonec jsem se tím ale prokousal a zde je výsledek. Před pokračováním doporučuji si článek přečíst, můj článek na něj reaguje.

Jen pro pořádek

Dodatečná PHP rozšíření

V odkazovaném článku je zmíněn Docker image geertw/docker-php-ci. Většina lidí asi sáhne buď po něm, nebo po čistém PHP image. Po chvíli jsem ale zjistil, že mi v něm spousta rozšíření chybí. Musel jsem tedy řešit jak je doinstalovat. Originální image kompiluje PHP ze zdrojáků a tak když chci rozšíření doinstalovat, musím ty standardní instalovat přes docker-php-ext-install a ty nestandardní přes pecl install. Některé je navíc potřeba nastavit přes docker-php-ext-configure a abych nezapomněl, musím mít nainstalovány potřebné dev knihovny. Občas byl oříšek přijít na to, co dané rozšíření chce. Co je horší, samotný update APT a instalace rozšíření trvá zbytečně dlouho.

Nakonec jsem se tedy rozhodl udělat si image vlastní. Vyšel jsem z phusion/baseimage-docker a vyházel z něj vše co nebylo nutné (je koncipován spíše na produkci). PHP jsem do něj nainstaloval z mého oblíbeného PPA od Ondřeje Surého. Jaké to má výhody?

Výsledek si můžete vyzkoušet použitím image sunfoxcz/php-build.

Spouštění na různých verzích PHP

Věcí, které se stahovaly, instalovaly a spouštěly, bylo hodně. Build pak běžel zbytečně dlouho. Chvíli mi trvalo přijít na to, jak to správně sestavit.

Takto vypadá výsledek:

stages:
    - download
    - test

download:
    stage: download
    image: sunfoxcz/php-build:5.6
    script:
        - composer create-project --no-interaction --no-progress --prefer-dist jakub-onderka/php-parallel-lint temp/php-parallel-lint ~0.9
        - composer create-project --no-interaction --no-progress --prefer-dist nette/code-checker temp/code-checker ~2.5.0
    artifacts:
        paths:
            - temp/php-parallel-lint
            - temp/code-checker
        expire_in: 1 hour

.test_template: &test_template
    stage: test
    services:
        - mongo:latest
        - mysql:latest
    before_script:
        - composer install --no-interaction --no-progress --no-suggest --optimize-autoloader
        - cp app/Config/config.test.neon app/Config/config.local.neon
        - "mysql -h mysql $MYSQL_DATABASE -p$MYSQL_ROOT_PASSWORD < sql/000_structure.sql"
        - "mysql -h mysql $MYSQL_DATABASE -p$MYSQL_ROOT_PASSWORD < tests/data/testdata.sql"
        - vendor/bin/phinx migrate -e production
    script:
        - php temp/php-parallel-lint/parallel-lint.php -e php,phpt -j $(nproc) app tests
        - php temp/code-checker/src/code-checker.php --short-arrays -d app
        - php temp/code-checker/src/code-checker.php --short-arrays -d tests
        - vendor/bin/tester -s -p php -c tests/php.ini tests

test:5.6:
    image: sunfoxcz/php-build:5.6
    <<: *test_template

test:7.0:
    image: sunfoxcz/php-build:7.0
    <<: *test_template

test:7.1:
    image: sunfoxcz/php-build:7.1
    <<: *test_template

test:7.2:
    image: sunfoxcz/php-build:7.2
    <<: *test_template

Další služby

Když přidám services, jejich hostname je vždy jejich jméno. Tohle mi chvíli trvalo dohledat. Co mě dost překvapilo, že služby nejde mezi joby sdílet. Ani když jejich definici dám globálně. Nechal jsem je proto jen v šabloně pro testy, aby ostatní joby zbytečně nespouštěly jejich instance. Přitom by to bylo super, udělat si job, který nakrmí databázi a ostatním jobům tak ušetří spoustu práce. Dokonce je na to založená issue, ale implementace zatím bohužel není.

Volání externích zdrojů

Po skončení buildu jsem chtěl poslat notifikaci na Slack. Moje staré CI na to mělo přímo plugin. Tady jsem musel opět hledat. Nakonec jsem našel, že se notifikace do Slacku dají nastavit ve webovém rozhraní GitLabu, a to pro více věcí než jen build, tak jsem to nastavil tam a neřešil, jak to nastavit v konfiguraci buildu.

Po úspěšném buildu jsem ale chtěl také zavolat Deployer, přes který aktuálně nasazuji na server. Tam už jsem to nakonec řešit musel. Po nějakém tom zkoumání vypadal výsledek takto:

stages:
    - download
    - test
    - deploy

deploy to production:
    stage: deploy
    environment: production
    image: sunfoxcz/php-build:5.6
    script:
        - curl -s -X POST https://deployer.domain.tld/deploy/$DEPLOYER_WEBHOOK_KEY
    only:
        - master@skupina/repozitar

Vlastní runner

I když se mi podařilo čas buildu stáhnout na slušnou úroveň, na free instancích GitLabu mi to stále přišlo dost dlouho. Cvičně jsem si tedy na Digital Ocean vytvořil z šablony 2GB VPS s Dockerem. Instalace runneru je pak na pár minut. Rozdíl v rychlosti je velký a jede to skvěle i při 4 konkurenčních buildech. Za ty prachy to rozhodně stojí.

Pro ukázku uvedu ještě kompletní .gitlab-ci.yml:

variables:
    GIT_DEPTH: "1"
    # Configure mysql environment variables (https://hub.docker.com/r/_/mysql/)
    MYSQL_DATABASE: test_db
    MYSQL_ROOT_PASSWORD: xxx

stages:
    - download
    - test
    - deploy

download:
    stage: download
    image: sunfoxcz/php-build:5.6
    script:
        - composer create-project --no-interaction --no-progress --prefer-dist jakub-onderka/php-parallel-lint temp/php-parallel-lint ~0.9
        - composer create-project --no-interaction --no-progress --prefer-dist nette/code-checker temp/code-checker ~2.5.0
    artifacts:
        paths:
            - temp/php-parallel-lint
            - temp/code-checker
        expire_in: 1 hour

.test_template: &test_template
    stage: test
    services:
        - mongo:latest
        - mysql:latest
    before_script:
        - composer install --no-interaction --no-progress --no-suggest --optimize-autoloader
        - cp app/Config/config.test.neon app/Config/config.local.neon
        - "mysql -h mysql $MYSQL_DATABASE -p$MYSQL_ROOT_PASSWORD < sql/000_structure.sql"
        - "mysql -h mysql $MYSQL_DATABASE -p$MYSQL_ROOT_PASSWORD < tests/data/testdata.sql"
        - vendor/bin/phinx migrate -e production
    script:
        - php temp/php-parallel-lint/parallel-lint.php -e php,phpt -j $(nproc) app tests
        - php temp/code-checker/src/code-checker.php --short-arrays -d app
        - php temp/code-checker/src/code-checker.php --short-arrays -d tests
        - vendor/bin/tester -s -p php -c tests/php.ini tests

test:5.6:
    image: sunfoxcz/php-build:5.6
    <<: *test_template

test:7.0:
    image: sunfoxcz/php-build:7.0
    <<: *test_template

test:7.1:
    image: sunfoxcz/php-build:7.1
    <<: *test_template

deploy to production:
    stage: deploy
    environment: production
    image: sunfoxcz/php-build:5.6
    script:
        - curl -s -X POST https://deployer.domain.tld/deploy/$DEPLOYER_WEBHOOK_KEY
    only:
        - master

cache:
    paths:
        - vendor