Možno to tiež poznáte. Aplikáciu nahrávate ručne na server za pomoci nejakého FTP nástroja. Zrazu ale potrebujete spraviť nejakú rýchlu zmenu priamo na produkcii a tak nahráte niečo na server, ale zabudnete to dať aj do repozitára. Prípadne na aplikácii pracuje viac ľudí a zabúdajú pushovať všetky zmeny do repozitára.

A tak postupom času zistíte, že aplikácia v repozitári sa už dosť odlišuje od aplikácie na serveri a nasadzovanie nových verzií sa stáva stále komplikovanejšie a komplikovanejšie.

Pred pár rokmi sa nám stal podobný prípad na jednom projekte. Aplikácia síce mala svoj repozitár, ale súbory na serveri sa už veľmi odlišovali od toho, čo bolo reálne na serveri. A tak sa z nasadzovania novej verzie stal zdĺhavý proces, ktorý obnášal prechádzanie jednotlivých upravených súborov samostatne po jednom a kopírovanie len konkrétnych riadkov na server. Nebolo možné kopírovať celé súbory, lebo by mohlo zmiznúť niečo, čo nebolo v repozitári, ale len priamo na serveri. A tak sme si jedného dňa povedali a dosť! Je treba s tým spraviť poriadok, lebo takto to ďalej nejde…

Prvý krok bol zjednotiť stav aplikácie na serveri a v repozitári. Za čas existencie aplikácie sa na nej vystriedali rôzni programátori a už málokto vedel, čo konkrétne sa môže vymazať, aby ostala zachovaná funkčnosť. A tak sme spravili dosť radikálne riešenie. Vytvorenie úplne nového repozitára a to skopírovaním súborov priamo zo servera. Vedeli sme, že na serveri je plne funkčná verzia webu, ktorú je treba zachovať. A tak nám toto prišla ako najlepšia a najjednoduchšia možnosť. Aj za cenu toho, že v repozitári sa možno ešte nachádza nejaká tá zabudnutá vyvinutá funkcionalita, ktorá ešte nebola na produkcii.

Druhým krokom bolo zamedziť tomu, aby sa zase začala rozchádzať verzia v repozitári a verzia na serveri. To sa dalo zabezpečiť jediným spôsobom – automatickým nasadzovaním aplikácie. Už žiadne ručné kopírovanie súborov zo strany programátora… Programátor spraví push do repozitára a tieto zmeny sa automaticky nahrajú na server bez ďalšej nutnej spolupráce programátora. Keďže sme ako repozitár využívali gitlab, tak nám na automatický deploy prišlo najvhodnejšie riešenie použiť nástroj gitlab CI/CD.

Gitlab CI/CD

Gitlab CI/CD je nástroj nachádzajúci sa v gitlabe, ktorý slúži na Continuous Integration, Continuous Delivery, Continuous Deployment. Na nastavenie gitlab CI/CD v konkrétnom projekte sa používa súbor .gitlab-ci.yml, ktorý sa nachádza v root adresári repozitára. Tento súbor vytvorí pipeline, ktorá sa spustí pri pushnutí do nastavenej vetvy. Pipeline spúšta runner. Keď máme projekt na gitlab.com, môžeme využiť zdieľané runnery. Účty zadarmo majú obmedzený počet minút na 2000 minút bežiacich pipeline mesačne. Čo je ale celkom dostačujúce číslo, keď chceme využívať pipeline len na deploy novej verzie webu.

Jednoduchý príklad, ako môže súbor .gitlab-ci.yml vyzerať:

stages:
- build
- deploy
build-job:
stage: build
script:
- echo "Toto je testovaci build job"
deploy-job:
stage: deploy
script:
- echo "Toto je testovaci deploy job"

Tento jednoduchý konfiguračný súbor spustí 2 joby. Prvý je build job, za ktorým nasleduje deploy job. Pipeline sa spustí pri pushnutí do akejkoľvek vetvy, keďže zatiaľ sme neobmedzili pre aké vetvy sa má spúšťať.

V našom prípade chceme pipeline spustiť len pri pushnutí do master. To znamená, keď sa pushne do produkčnej vetvy. To obmedzíme tým, že k jobom doplníme:

only:
- master

Momentálne naše joby nerobia nič, len vypíšu testovací text. Keď chceme vidieť výstup z jobov v gitlabe, treba si otvoriť v menu CI/CD -> Pipelines.

Výpis jobu môže vyzerať napríklad takto:

Build job

Úlohou build jobu je vybuildiť aplikáciu a pripraviť všetky potrebné súbory, ktoré aplikácia používa. Napríklad, keď máme PHP aplikáciu a používame composer, tak build job stiahne potrebné knižnice. Alebo keď používame SCSS na štýlovanie aplikácie, tak build job vygeneruje CSS súbory (napríklad cez gulp). Prípadne môžeme zminifikovať JS súbory. Je toho naozaj veľa, čo môžeme v tomto kroku spraviť.

Tu je ešte veľmi dôležité vybrať vhodný docker image pre spustenú pipeline. V prípade, že sa jedná o PHP aplikáciu a na knižnice používame composer, je dobré zvoliť docker image, ktorý už obsahuje composer a teda nám v našom jobe stačí len spustiť composer install príkaz. Alebo keď máme single page aplikáciu napísanú napríklad v NUXT, tak je potrebné zvoliť docker image, ktorý má nainštalovaný NODE a YARN. A nám už v jobe stačí len spustiť yarn install a následne yarn build. Výber image spravíme doplnením riadku image. Napríklad, keď chceme použiť image s nainštalovaným node 13, tak doplníme riadok:

image: node:10.13-alpine

Deploy job

Úlohou deploy jobu je dostať aplikáciu na produkčný server. Tu máme viacero možností ako to spraviť. Buď môžeme použiť príklaz lftp, ktorý presunie všetky súbory na server. Alebo, keď máme SSH prístup na server, môžeme použiť príkaz rsync, ktorý nám súbory na serveri synchronizuje s pripravenými súbormi na deploy. Nám sa osvedčil práve rsync príkaz.

Použitie rsync v gitlab CI/CD

Najskôr si vygenerujeme dvojicu kľúčov (public + private). To môžeme spraviť na linuxe príkazom ssh-keygen, prípadne na windowse cez program PuTTYgen. Dôležité je aby SSH kľúč nepoužíval žiadny passphrase. Inak by sme ho v gitlab CI/CD nevedeli použiť.

Pridáme vygenerovaný public kľúč do súboru authorized_keys na serveri, na ktorý sa budeme pripájať.

V repozitári projektu si nastavíme privátny kľúč do premennej, ktorú potom využijeme v pipeline. To spravíme cez Settings -> CI/CD -> Variables. Premennú môžeme nazvať napríklad SSH_PRIVATE_KEY.

V súbore .gitlab-ci nastavíme použitie kľúča:

before_script:
- 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'

Následne by sme sa už mali vedieť pripojiť cez príkaz rsync na server. Vhodné je ešte doplniť, aké adresáre sa majú zo synchronizácie vynechať. Napríklad priečinok logs, cache a podobne. Buď ich jednotlivo vymenujeme v príkaze rsync alebo si vytvoríme súbor .rsyncignore v root priečinku projektu a tam ich všetky vypíšeme. Niečo na štýl .gitignore. Príklad, ako môže vyzerať .gitlab-ci.yarn pre build a nasadenie NUXT aplikácie:

image: node:10.13-alpine
before_script:
- 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
stages:
- build
- deploy
build-job:
stage: build
script:
- yarn
- mv .env.prod .env
- yarn build
- yarn generate
deploy-job:
stage: deploy
script:
rsync -avzc --delete --no-times --no-perms --no-owner --no-group --exclude-from=".rsyncignore" ./ nas@server.sk:/www

V krátkosti sme si ukázali ako nastaviť build a deploy aplikácie cez Gitlab CI/CD. Možností, ktoré ponúka gitlab a konfigurácia .gitlab-ci.yarn súboru, je obrovské množstvo. Netreba sa báť listovať v dokumentáciách a experimentovať 🙂

Ako zautomatizovať nasadzovanie webovej aplikácie?