Doporučuji si přečíst přepis zprávy Alexandra Sigacheva z Inventos "Proces vývoje a testování s Docker + Gitlab CI"
Ti, kdo právě začínají zavádět proces vývoje a testování na bázi Docker + Gitlab CI, se často ptají na základní otázky. Odkud začít? Jak to uspořádat? Jak testovat?
Tato zpráva je dobrá tím, že strukturovaně popisuje proces vývoje a testování s využitím Docker a Gitlab CI. Samotná zpráva je z roku 2017. Myslím, že z této zprávy lze odvodit základy, metodologii, myšlenku a zkušenosti z používání.

Kdo má zájem, prosím, pokračujte dál.
Jmenuji se Alexander Sigachev. Pracuji ve společnosti Inventos. Podělím se o své zkušenosti s používáním Dockeru a o tom, jak ho postupně zavádíme do projektů ve firmě.
Téma zprávy: Proces vývoje s využitím Docker a Gitlab CI.

Toto je má druhá zpráva o Dockeru. V době první zprávy jsme používali Docker pouze v Developmentu na strojích vývojářů. Počet zaměstnanců, kteří Docker používali, byl asi 2-3 osoby. Postupně jsme nasbírali zkušenosti a pokročili jsme dál. Odkaz na naši .
Co bude v této zprávě? Podělíme se o zkušenosti o tom, jaké problémy jsme potkali, a jak jsme je vyřešili. Ne všude to bylo jednoduché, ale umožnilo nám to pokročit dál.
Náš slogan: dokeryzujte vše, co se vám dostane do rukou.

Jaké problémy řešíme?
Když je ve firmě několik týmů, stává se programátor sdíleným zdrojem. Jsou fáze, kdy je programátor vytržen z jednoho projektu a dán nějakou dobu do jiného projektu.
Aby se programátor rychle zorientoval, musí si stáhnout zdrojový kód projektu a co nejrychleji spustit prostředí, které mu umožní pokračovat v řešení úkolů tohoto projektu.
Obvykle, pokud začínáme od nuly, je v projektu málo dokumentace. Informace o nastavení znají jen staří pracovníci. Zaměstnanci si obvykle nastavují své pracovní místo za jeden nebo dva dny. Abychom to urychlili, použili jsme Docker.
Dalším důvodem je standardizace nastavení v prostředí Development. Z mé zkušenosti si vývojáři vždy dávají záležet. V každém pátém případě se zavádí vlastní doména, například vasya.dev. Vedle sedí soused Petya, který má doménu petya.dev. Vyvíjejí web nebo nějakou komponentu systému pomocí této domény.
Když se systém rozrůstá a tyto domény se začínají objevovat v konfiguracích, vzniká konflikt vývojových prostředí a přepisuje se cesta na web.
Totéž se děje s nastavením databází. Někdo si nedává pozor na bezpečnost a pracuje s prázdným heslem pro root. Někomu při instalaci MySQL požadovalo heslo a to bylo 123. Často se stává, že konfigurace databáze se neustále měnila v závislosti na komitech vývojáře. Někdo to opravil, někdo ne. Existovaly kličky, kdy jsme vyváděli nějakou testovací konfiguraci do .gitignore a každý vývojář si musel nastavovat databázi. To ztěžovalo zahájení procesu. Je třeba kromě všech ostatních věcí myslet na databázi. Databázi je nutné inicializovat, je třeba napsat heslo, vytvořit uživatele, vytvořit tabulku a tak dále.
Dalším problémem jsou různé verze knihoven. Často se stává, že vývojář pracuje na různých projektech. Je tu Legacy projekt, který začali před pěti lety (od roku 2017 — pozn. red.). Na začátku startovali s MySQL 5.5. Existují i moderní projekty, kde se snažíme zavádět již modernější verze MySQL, například 5.7 nebo novější (v roce 2017 — pozn. red.)
Ten, kdo pracuje s MySQL, ví, že tyto knihovny táhnou za sebou závislosti. Je poměrně problematické spustit 2 databáze současně. Minimálně je problematické připojit staré klienty k nové databázi. To zase vyvolává několik problémů.
Další problém nastává, když vývojář pracuje na místním počítači, používá místní zdroje, místní soubory a místní RAM. Veškerá interakce během vývoje řešení se odehrává v rámci toho, jak to funguje na jednom zařízení. Například, když máme v prostředí produkce tři backendové servery a vývojář ukládá soubory do kořenového adresáře, odkud nginx načítá soubory pro odpověď na dotazy. Když se takový kód dostane do produkce, vyplývá to, že soubor je přítomen na jednom ze tří serverů.
Nyní se rozvíjí oblast mikroslužeb. Když dělíme naše velké aplikace na malé komponenty, které mezi sebou komunikují. To nám umožňuje zvolit technologie specifické pro konkrétní úkoly. Také to umožňuje rozdělit práci a zónu odpovědnosti mezi vývojáři.
Frontendový vývojář, pracující na JS, prakticky neovlivňuje backend. Backendový vývojář naopak vyvíjí, v našem případě, pomocí Ruby on Rails, a nezasahuje do frontendu. Interakce probíhá pomocí API.
Jako bonus nám pomocí Dockeru podařilo efektivně využít zdroje na Stagingu. Každý projekt vyžadoval určité nastavení kvůli jeho specifice. Fyzicky bylo nutné vyčlenit buď virtuální server a nastavit jej samostatně, nebo rozdělit nějaké proměnné prostředí, aby projekty mohly vzájemně ovlivňovat jedna druhou v závislosti na verzích knihoven.

Nástroje. Co používáme?
- Přímo samotný Docker. V Dockerfile jsou popsány závislosti jednoho aplikace.
- Docker-compose - to je spojení, které spojuje několik našich Docker aplikací.
- GitLab používáme pro uložení zdrojového kódu.
- GitLab-CI používáme pro systémovou integraci.

Prezentace se skládá ze dvou částí.
První část se zaměří na to, jak jsme spouštěli Docker na počítačích vývojářů.
Druhá část se zaměří na to, jak komunikovat s GitLabem, jak spouštíme testy a jak nasazujeme na Staging.

Docker je technologie, která umožňuje (použitím deklarativního přístupu) popsat potřebné komponenty. To je příklad Dockerfile. Zde deklarujeme, že dědíme z oficiálního Docker obrazu Ruby:2.3.0. Obsahuje nainstalovaný Ruby verze 2.3. Instalujeme potřebné knihovny pro sestavení a NodeJS. Popisujeme, že vytváříme adresář. /appUrčujeme adresář app jako pracovní adresář. Do tohoto adresáře umístíme potřebné minimální soubory Gemfile a Gemfile.lock. Poté provedeme build projektů, které instalují tento obraz závislostí. Uvádíme, že kontejner bude připraven naslouchat na externím portu 3000. Poslední příkaz je příkaz, který přímo spouští naši aplikaci. Pokud spustíme příkaz pro spuštění projektu, aplikace se pokusí vykonat a spustí uvedený příkaz.

Toto je minimální příklad souboru docker-compose. V tomto případě ukazujeme, jak probíhá propojení dvou kontejnerů. To je přímo ve službě databáze a službě web. Naše webové aplikace většinou vyžadují jako backend nějakou databázi pro ukládání dat. Protože používáme MySQL, máme příklad s MySQL — ale nic nebrání použití jiné databáze (PostgreSQL, Redis).
Bereme oficiální obraz MySQL 5.7.14 z Docker hubu bez změn. Obraz, který odpovídá naší webové aplikaci, budujeme z aktuálního adresáře. Při prvním spuštění tedy vytvoříme obraz. Poté spustíme příkaz, který zde vykonáváme. Pokud se vrátíme zpět, uvidíme, že byla určena příkaz pro spuštění přes Puma. Puma je služba napsaná v Ruby. Ve druhém případě přepisujeme. Tento příkaz může být libovolný podle našich potřeb nebo úkolů.
Také popisujeme, co je potřeba pro forward port na naší hostitelské mašině vývojáře z portu 3000 na port 3000 kontejneru. To se provádí automaticky pomocí iptables a vlastního mechanismu, který je přímo zakotven v Dockeru.
Vývojář se také může, stejně jako dříve, obrátit na jakoukoli dostupnou IP adresu, například 127.0.0.1 místní nebo externí IP adresu stroje.
Poslední řádek říká, že kontejner web závisí na kontejneru db. Když spustíme kontejner web, docker-compose předtím spustí naši databázi. Již po startu databáze (ve skutečnosti — po spuštění kontejneru! To nezaručuje připravenost DB) se spustí naše aplikace, náš backend.
To umožňuje vyvarovat se chyb, když databáze není spuštěna, a šetří zdroje, když zastavíme kontejner databáze, uvolňující tak zdroje pro jiné projekty.

Co nám přináší použití dockerizace databází v projektu. U všech vývojářů evidujeme verzi MySQL. To nám umožňuje vyhnout se některým chybám, které mohou nastat při nesouladu verzí, když se mění syntaxe, konfigurace a výchozí nastavení. To nám umožňuje specifikovat společné hostname pro databázi, login a heslo. Odloučíme se od toho chaosu názvů a konfliktů v konfiguračních souborech, které byly dříve.
Máme možnost použít optimalizovanou konfiguraci pro vývojové prostředí, která se bude lišit od výchozí. MySQL je ve výchozím nastavení nastaveno na slabší stroje a jeho výkon je z boxu velmi nízký.

Docker nám umožňuje používat interpretery Python, Ruby, NodeJS, PHP ve potřebných verzích. Zbavujeme se tak potřeby používat nějakého správce verzí. Dříve jsme pro Ruby používali rpm balíček, který umožňoval měnit verzi v závislosti na projektu. Díky Docker kontejneru také hladce migrujeme kód a verzionujeme ho spolu se závislostmi. Nemáme problém rozlišit verzi jak interpreta, tak kódu. Pro aktualizaci verze je třeba zastavit starý kontejner a spustit nový. Pokud něco nevyjde, můžeme zastavit nový kontejner a spustit starý.
Po sestavení obrazu budou kontejnery jak v developmentu, tak v produkci totožné. To je obzvlášť důležité pro velké instalace.
Na frontendu používáme JavaScript a NodeJS.
Náš poslední projekt běží na ReactJS. Vývojář spouštěl všechny kontejnery a vyvíjel pomocí hot-reload.
Dále se spouští úloha na sestavení JavaScriptu a kód, který byl sestaven do statiky, se poskytuje přes nginx, čímž se šetří zdroje.

Zde jsem předložil schéma našeho posledního projektu.
Jaké úkoly jsme řešili? Nastala potřeba vybudovat systém, který komunikuje s mobilními zařízeními. Ta získávají data. Jednou z možností je posílat push notifikace do těchto zařízení.
Co jsme pro to udělali?
Rozdělili jsme aplikaci na komponenty jako: administrační část v JS, backend, který funguje přes REST rozhraní pod Ruby on Rails. Backend komunikuje s databází. Výsledky, které se generují, se předávají klientovi. Administrační rozhraní komunikuje s backendem a databází přes REST rozhraní.
Také jsme potřebovali odesílat push oznámení. Předtím jsme měli projekt, ve kterém byl realizován mechanismus, který odpovídá za doručování oznámení na mobilní platformy.
Vyvinuli jsme takovou schéma: operátor z prohlížeče interaguje s administračním rozhraním, administrační rozhraní interaguje s backendem, a je položena úloha, že je potřeba poslat push oznámení.
Push oznámení interagují s jinou komponentou, která je realizována na NodeJS.
Budují se fronty a dále probíhá odesílání oznámení podle svého mechanismu.
Zde jsou znázorněny dvě databáze. V současné době používáme pomocí Dockeru 2 nezávislé databáze, které nejsou nijak propojené. Kromě toho mají společnou virtuální síť, a fyzická data se ukládají v různých adresářích na stroji vývojáře.

Totéž, ale v číslech. Zde je důležité opětovné použití kódu.
Pokud jsme dříve mluvili o opětovném použití kódu ve formě knihoven, v tomto příkladu se náš servis, který odpovídá za push oznámení, opětovně používá jako kompletní server. Poskytuje API. A s ním již interaguje naše nová vývoj.
V té době jsme používali 4 verzi NodeJS. Nyní (v roce 2017 — pozn. red.) při nových projektech používáme 7 verzi NodeJS. Není problém v nových komponentách používat nové verze knihoven.
V případě potřeby je možné provést refaktoring a zvýšit verzi NodeJS u služby push oznámení.
A pokud se nám podaří zachovat kompatibilitu podle API, bude možné ji nahradit v jiných projektech, které byly dříve použity.

Co je potřeba k přidání Dockeru? Přidáváme do našeho repozitáře Dockerfile, který popisuje nezbytné závislosti. V tomto příkladu jsou komponenty rozděleny podle logiky. To je minimální soubor pro backendového vývojáře.
Při vytváření nového projektu vytvoříme Dockerfile, popíšeme potřebnou ekosystém (Python, Ruby, NodeJS). V docker-compose popisujeme nezbytnou závislost — databázi. Uvádíme, že potřebujeme databázi takové verze, ukládat data tam a tam.
Používáme k poskytování statiky samostatný třetí kontejner s nginx. Je zajištěna možnost nahrávání obrázků. Backend je ukládá do předem připraveného svazku, který je také namontován v kontejneru s nginx, který poskytuje statiku.
Aby uchovat konfiguraci nginx a mysql, vytvořili jsme složku Docker, ve které uchováváme potřebné konfigurační soubory. Když si vývojář provede git clone repozitáře na svůj stroj, získá již projekt připravený pro lokální vývoj. Nevyvstává otázka, který port nebo jaká nastavení aplikovat.

Dále máme několik komponent: administrátor, informační API, push notifikace.
Abychom to všechno spustili, vytvořili jsme další repozitář, který jsme nazvali dockerized-app. V současné době používáme několik repozitářů pro každou komponentu. Ty se logicky liší — v GitLabu to vypadá jako složka, a na stroji vývojáře jako složka pro konkrétní projekt. Na nižší úrovni se nacházejí komponenty, které se budou spojovat.

To je příklad obsahu dockerized-app. Také jsme sem umístili adresář Docker, ve kterém plníme konfigurace potřebné pro interakce všech komponent. Je zde README.md, ve kterém je krátce popsáno, jak spustit projekt.
Zde jsme použili dva soubory docker-compose. To bylo uděláno, aby bylo možné spouštět je postupně. Když vývojář pracuje s jádrem a nepotřebuje push notifikace, jednoduše spustí pouze soubor docker-compose, čímž se šetří systémové zdroje.
Pokud je potřeba integrovat push notifikace, spustí se docker-compose.yaml a docker-compose-push.yaml.
Protože docker-compose.yaml a docker-compose-push.yaml jsou umístěny ve složce, automaticky se vytvoří jednotná virtuální síť.

Popis komponent. To je podrobnější soubor, který odpovídá za shromáždění komponent. Co je tu pozoruhodné? Zde zavádíme komponent vyvážeč.
To je hotový Docker obraz, ve kterém se spouští nginx a aplikace, která naslouchá Docker socketu. Dynamicky, při zapínání a vypínání kontejnerů, regeneruje konfiguraci nginx. Komunikaci s komponentami rozdělíme podle doménových jmen třetí úrovně.
Pro prostředí vývoje používáme doménu .dev — api.informer.dev. Aplikace s doménou .dev je dostupná na lokálním stroji vývojáře.
Pak se přenášejí konfigurace do každého projektu a všechny projekty se spouští společně najednou.

Pokud bychom to graficky znázornili, dostal by se klient jako náš prohlížeč nebo jiný nástroj, kterým provádíme dotazy na vyvážeč.
Vyvážeč na základě doménového jména určuje, ke kterému kontejneru se má obrátit.
Může to být nginx, který poskytuje JS admin panelu. Může to být nginx, který dodává API nebo statické soubory, které nginx zasílá jako načítání obrázků.
Na schématu je vidět, že kontejnery jsou propojeny virtuální sítí a skryty za proxy.
Na vývojářském stroji je možné přistoupit ke kontejneru, pokud znáte IP, ale v zásadě to nepoužíváme. Potřeba přímého přístupu prakticky nenastává.

Jaký příklad bych měl sledovat, abych dockerizoval svou aplikaci? Podle mého názoru je dobrým příkladem oficiální docker obraz pro MySQL.
Je to dost složité. Existuje mnoho verzí. Ale jeho funkčnost pokrývá množství potřeb, které mohou vzniknout během dalšího vývoje. Pokud strávíte čas a pochopíte, jak vše spolu souvisí, myslím, že v samostatném nasazení nebudete mít žádné problémy.
Na hub.docker.com obvykle naleznete odkazy na github.com, kde jsou uvedena přímo syrová data, z nichž si můžete sami sestavit obraz.
Dále v tomto repozitáři je skript docker-endpoint.sh, který zajišťuje primární inicializaci a další zpracování spuštění aplikace.
Tento příklad také nabízí možnost konfigurace pomocí proměnných prostředí. Určením proměnné prostředí při spuštění jednotlivého kontejneru nebo prostřednictvím docker-compose můžete říci, že potřebujeme nastavit prázdné heslo pro docker pro uživatele root na MySQL nebo nějaké, které chceme.
Existuje možnost vytvořit náhodné heslo. Říkáme, že potřebujeme uživatele, je nutné nastavit heslo pro uživatele a vytvořit databázi.
Ve svých projektech jsme poněkud unifikovali Dockerfile, který zajišťuje inicializaci. Oprava byla provedena pro naše potřeby, aby bylo možné jednoduše rozšířit práva uživatele, které aplikace používá. To umožnilo později jednoduše vytvořit databázi z konzole aplikace. V Ruby aplikacích existují příkazy pro vytváření, změnu a mazání databází.

Tento příklad ukazuje, jak vypadá konkrétní verze MySQL na github.com. Dockerfile lze otevřít a podívat se, jak tam probíhá instalace.
docker-endpoint.sh je skript, který slouží jako vstupní bod. Při prvotní inicializaci je potřeba provést určité akce na přípravu, a všechny tyto akce jsou vyčleněny právě do init skriptu.

Přejdeme k druhé části.
Pro uložení zdrojových kódů jsme přešli na gitlab. Je to dostatečně výkonný systém, který má vizuální rozhraní.
Jednou z komponent Gitlabu je Gitlab CI. Umožňuje popsat posloupnost příkazů, které budou následně použity pro organizaci systému distribuce kódu nebo spuštění automatických testů.
Prezentace o Gitlab CI 2 — prezentace z Ruby Russia klubu — dostatečně podrobná a možná vás zaujme.

Nyní se podíváme, co je potřeba pro aktivaci Gitlab CI. K jeho spuštění stačí umístit soubor .gitlab-ci.yml do kořene projektu.
Zde popisujeme, co chceme provádět, posloupnost stavů typu testu a nasazení.
Provádíme skripty, které přímo volají docker-compose pro sestavení naší aplikace. To je příklad backendu.
Dále říkáme, že je třeba spustit migrace pro změny databáze a provést testy.
Pokud skripty probíhají správně a nevrací kód chyby, systém přechází do druhé fáze nasazení.
Fáze nasazení je v aktuálním okamžiku realizována pro staging. Nenosili jsme bezproblémový restart.
Násilně ukončujeme všechny kontejnerové služby a poté je všechny znovu spouštíme, sestavené v prvním kroku při testování.
Zpracováváme migrace databáze pro aktuální proměnné prostředí, které napsali vývojáři.
Je poznámka, že to použít pouze pro větev master.
Při změně jiných větví se nevykonává.
Existuje možnost organizovat nasazení podle větví.

Pro další organizaci je potřeba nainstalovat Gitlab Runner.
Tato utilita je napsána v Golangu. Je to jediný soubor, jak je běžné ve světě Golangu, který nevyžaduje žádné závislosti.
Při spuštění registrujeme Gitlab Runner.
Získáváme v webovém rozhraní Gitlab klíč.
Poté spouštíme příkaz pro inicializaci v příkazovém řádku.
Nastavujeme Gitlab Runner v dialogovém režimu (Shell, Docker, VirtualBox, SSH).
Kód na Gitlab Runner bude spouštěn při každém commitu v závislosti na nastavení .gitlab-ci.yml.

Jak to vizuálně vypadá v Gitlabu ve webovém rozhraní. Po připojení Gitlab CI se nám objeví vlajka, která ukazuje, v jakém stavu se aktuálně nachází build.
Vidíme, že před 4 minutami byl proveden commit, který prošel všemi testy a nezpůsobil žádné problémy.

Můžeme se podívat podrobněji na buildu. Zde vidíme, že už proběhly dva stavy. Stav testování a stav nasazení na staging.
Pokud klikneme na konkrétní build, uvidíme konzolový výstup příkazů, které byly spuštěny během procesu podle .gitlab-ci.yml.

Takto vypadá historie našeho produktu. Vidíme, že byly úspěšné pokusy. Když testy selžou, tak se na další krok nepřejde a kód na staging se neaktualizuje.

Jaké úkoly jsme řešili na staging, když jsme implementovali docker? Náš systém se skládá z komponent a my jsme potřebovali restartovat pouze ty komponenty, které byly aktualizovány v repozitáři, a ne celý systém jako celek.
Proto jsme museli vše rozdělit do jednotlivých složek.
Po dokončení této změny jsme čelili problému, že Docker-compose vytváří pro každou složku vlastní prostor sítě, což zabraňuje komponentám vidět sousedy.
Abychom to obešli, vytvořili jsme síť v Dockeru ručně. V Docker-compose jsme uvedli, aby pro tento projekt používal tuto síť.
Takto každý komponent, který se spouští s touto sítí, vidí komponenty v jiných částech systému.
Další problém – to je dělení staging mezi několika projekty.
Aby to všechno vypadalo hezky a co nejblíže produkci, je dobré používat port 80 nebo 443, které se běžně používají ve webu.

Jak jsme to vyřešili? Přiřadili jsme jednoho Gitlab Runnera všem velkým projektům.
Gitlab umožňuje spustit několik distribuovaných Gitlab Runnerů, které budou jednoduše po řadě v náhodném pořadí brát všechna zadání a provádět je.
Abychom předešli chaosu, omezili jsme skupinu našich projektů na jednoho Gitlab Runnera, který se při našich objemech bez problémů vypořádává.
Vyčlenili jsme nginx-proxy do zvláštního spouštěcího skriptu a do něj jsme zapsali sítě všech projektů.
Náš projekt má jednu síť, zatímco vyrovnávací server má několik sítí podle názvů projektů. Může na základě doménových jmen dále proxy.
Příchozí požadavky jsou směrovány na doménu přes port 80 a jsou spravovány skupinou kontejnerů, která tuto doménu obsluhuje.

Jaké další problémy se objevily? To, že ve výchozím nastavení všechny kontejnery běží pod uživatelským účtem root. Tento root není stejný jako root hostitelského systému.
Pokud se však přihlásíte do kontejneru, budete mít root a soubor, který vytvoříme v tomto kontejneru, získá práva root.
Pokud se vývojář přihlásil do kontejneru a vykonal nějaké příkazy, které generují soubory, a poté se odhlásil z kontejneru, ve své pracovní složce má soubor, ke kterému nemá přístup.
Jak to lze vyřešit? Můžeme přidat uživatele, kteří budou v kontejneru.
Jaké problémy nastaly, když jsme přidali uživatele?
Při vytváření uživatele se často neshodují ID skupiny (UID) a ID uživatele (GID).
Abychom tento problém vyřešili, používáme v kontejneru uživatele s ID 1000.
V našem případě to odpovídalo skutečnosti, že prakticky všichni vývojáři používají systém Ubuntu. A v systému Ubuntu má první uživatel ID 1000.

Jaké máme plány?
Přečíst si dokumentaci k Dockeru. Projekt se aktivně vyvíjí, dokumentace se mění. Údaje, které byly získány před dvěma až třemi měsíci, už začínají být zastaralé.
Některé problémy, které jsme řešili, jsou už možná vyřešeny standardními prostředky.
Velmi bych chtěl pokračovat dál a zaměřit se přímo na orchestrace.
Jedním z příkladů je vestavěný mechanismus Dockeru nazvaný Docker Swarm, který je dodáván v základu. Rád bych spustil něco v produkci na základě technologie Docker Swarm.
Generování kontejnerů ztěžuje práci s protokoly. Teď jsou protokoly izolované. Jsou rozptýlené mezi kontejnery. Jedním z úkolů je udělat pohodlný přístup k protokolům prostřednictvím webového rozhraní.

Zdroj: habr.com
