pondělí 9. dubna 2018

DevOps a Release management

Release management je proces, který je dnes přímo spojen s věcmi jako je Continuous Integration, Continuous Delivery (ve zkratce CI/CD), Git, Docker, apod. Díky tomu se z Release managementu stává obor, který vyžaduje vcelku hluboké znalosti v IT oboru. V dnešním článku se pokusím rozebrat, jakým způsobem k Release managementu přistupujeme my a jak do tohoto procesu zakomponovat ostatní moderní technologie jako je Docker, apod.

Verzování


Základním předpokladem je určení, jakým způsobem verzovat. První variantou je, že si sami vytvoříme verzování podle svého vlastního uvážení. To ovšem nedělejte. Vymýšlení "kola" je to nejhorší, co můžete udělat. Chápu, že každý z nás chce být průkopníkem ve své oblasti, ale je velice malá pravděpodobnost, že se Vám to podaří. Nicméně, pokud máte ambice, že změníte IT svět a nastavíte novou specifikaci pro verzování, směle do toho.

Druhou a mnohem lepší variantou je, že zvolíte již existující návrh. Tím je specifikace, která se označuje jako Sémantické verzování, zkráceně SemVer. Tuto specifikaci vytvořil Tom Preston-Werner, což je spoluzakladatel GitHubu. Samotná specifikace je vcelku jednoduchá a její osvojení je otázka několika hodin. Výhodou tohoto řešení je zejména fakt, že pokud dodržíte tuto specifikaci, je mnohem větší pravděpodobnost, že konzumenti Vašeho API, knihovny, či celého systému porozumí Vašemu verzování. Nebudete muset nikomu vysvětlovat, že když jste zvýšili MAJOR verzi, že tím automaticky říkáte, že Váš systém je zpětně nekompatibilní, apod.

Vedle toho je SemVer vhodné i pro vývoj, tedy v době, kdy sice v produkci ještě nejste, ale potřebujete si v projektu udělat trochu pořádek a jednoduše zajistit, aby se do vyšších prostředí nedostalo něco, co tam nepatří.

Následující popis počítá se znalostí SemVer.

Verzování a Git


Samotný Git je sice označován jako distribuovaný systém správy verzí, ale to ještě neznamená, že použitím Gitu mám vyřešen Release management. Naopak. Git slouží pouze jako prostředek k tomu, jak Release management uskutečnit.

Pokud dáte do spojení Release management a Git, s největší pravděpodobností skončíte u něčeho, co se jmenuje Gitflow.

Gitflow

Jedná se o specifikaci, která určuje, jakým způsobem řídit Váš projekt od prvního řádku kódu až po produkční verzi. Vedle toho také definuje, jak řešit věci jako je release nové verze, opravy na stávajících verzích, apod.

Gitflow vychází z předpokladu, že díky Gitu máte možnost jednoduše vyrábět větve a ty mezi sebou zase spojovat a štítkovat (tagovat). Díky tomu si můžete ušetřit několik bezesných nocí, kdy budete přemýšlet nad tím, jak se zachovat, když například máte v produkci verzi 1.0.3 a vy už pracujete na verzi 2.0.0, ale zároveň potřebujete opravit něco ve verzi 1.0.3 a danou opravu udělat jak pro produkci, tak i pro stávající vývoj.

Gitflow je robusní řešení, které pokrývá všechny známé problémy při dodávce. Vedle toho je to návrh, který předpokládá, že v produkci máte několik verzí. Gitflow je vcelku rošířená specifikace, se kterou se lze často setkat. Otázkou jen je, jestli skutečně Gitflow potřebujeme.

Důvodů, proč neskočit po tomto řešení je hned několik. Prvním důvodem je to, že toto řešení je příliš komplexní a často se dostáváte do situace, kdy kvůli jedné malé změně musíte "mergovat první ligu". Druhou nevýhodou je to, že pokud vydáváte nové verze agilněji, tedy v rychlých iteracích, tak je Gitflow něco, co skutečně nechcete. Další nevýhodou je to, že je velká pravděpodobnost, že se v tom někteří vývojáři budou ztrácet a samotný Release management tak bude výsadou jen těch, kteří perfektně znají Gitflow. Tady platí pravidlo, že méně je někdy více.

Když jsem řešil samotný Release management, tak po prostudování několika specifikací a po ohlédnutí se zpět, na projekty, na kterých jsem pracoval, tak se mi v hlavě točila pořád a jedna a ta samá otázka. Skutečně Gitflow potřebuji? Co mi to nakonec přinese? Je to pro projekt, na kterém pracuji, vhodná specifikace? Nakonec jsem dospěl k tomu, že nikoli.

Github release

Další variantou, jak vyrábět release je pomocí Githubu. Pokud své projekty máte v Githubu, ať už jako privátní repozitáře či veřejné, máte možnost v určitém momentu udělat release. Onen release nemusí být jen produkční, ale také pre-release, což znamená, že se jedná o určitý milník ve vývoji, kterým si označíte Váš poslední commit. Tento pre-release může být vhodný třeba pro nasazení na vyšší prostředí, než je Vaše vývojové. Třeba pro demo, testy apod.

Samotný Github release nedělá nic jiného, než to, že přidá tag na daný commit. Buď podle větve a nebo na jasně definovaný commit. Je vcelku na Vás, kde daný tag umístíte, jde spíše o to, že ho umístíte.

Další věcí je i to, že při vytvoření releasu Vám Github nabízí možnost napsat Release notes. Tedy k dané verzi sepsat poznámku, ve které jasně specifikujete, co nová verze obsahuje, jaké bugy opravuje, apod. Přiznám se, že Release notes je věc, která je nesmírně důležitá a zároveň nejvíce ignorovaná mezi vývojáři. Je sice hezké, že jste vytvořili novou verzi, ale bez popisu dané verze je to spíše jen tvorba verze pro Vás, nikoli pro ostatní. V případě, že píšete knihovnu, kterou má konzumovat někdo další, tak absence Release notes je kontraproduktivní i pro Vás. Vždy, když použiji nějakou knihovnu, tak se koukám, zda vývojáři píší ony poznámky. Pokud ne, knihovnu ignoruji a jdu jinam. Veřte, že nejsem jediný.

Když jsem si dal na misku vah plusy a mínusy mezi Gitflow vs Github releases, tak jsem nakonec dospěl k závěru, že raději zvolím Github. Jednak pro jeho jednoduchost, ale také pro to, že je plně postačující. Jsem presvědčen, že pro drtivou většinu projektů to tak je. Když jsem přemýšlel, kde by mi Github releases nestačil, nebyl jsem schopný žádný takový příklad najít. Pokud Vás nějaký napadne, budu rád, když se o tento případ podělíte. Samozřejmě v případě absence Githubu můžeme říci, že release je řízen tagováním, což je ve výsledku to samé, akorát často přijdete o ony hezky zobrazené Release notes.

Abyste měli představu, jak takové releases v Githubu vypadají, podívejte se třeba na knihovnu React.

Verzování a Docker


Pokud dnes mluvíme o dodávce softwaru, který je server side charakteru, tak bychom druhým dechem měli říct, že ho dodáváme pomocí Docker image. V případě, že Docker nepoužíváte, ale chtěli byste, věřte, že si tím neskutečně ušetříte problémy při integracích a dodávce směrem k provozu.

Díky tomu, že Docker image je tagována, tak se tím přímo nabízí, aby byla tagována tak, jak jsou tagovány jednotlivé verze. Více se o verzování Docker image zmíním dále, v ukázkovém projektu.

Verzování a NPM


V případě, že píšete aplikace v javascriptu, je vcelku jedno, zda serverové či klientské, tak jistě používáte hromadu knihoven, na kterých jste závislí. Vedle toho se ovšem často dostanete do fáze, kdy byste si i Vy sami chtěli vyrobit vlastní knihovnu, která Vám umožní nasdílet si části kódu mezi jednotlivými systémy.

K tomuto účelu slouží právě NPM repozitář. A světě div se, ale i NPM podporuje verzování. Dokonce používá specifikaci SemVer. Tudíž je vcelku jasné, že i zde použijete stejný přístup. Více opět v ukázce.

Verzování a CircleCI


Pokud mluvíme o Release managementu a dodávce softwaru, tak bychom si měli najít nástroj, který nám pomůže s tím, abychom automatizovali to, co automatizovat lze. Tedy o nástroji, který se označuje jako CI/CD.

Nejznámějším a asi možná i nejpoužívanejším je Jenkins. Hromada lidí má s Jenkinsem zkušenosti a jistě Vám řeknou, že je skvělý. Po pravdě, osobně Jenkins nesnáším. Nesnáším je možná silné slovo, ale nespatřuji v něm příliš velkou výhodu oproti ostatním. Asi tím nejzásadnějším je fakt, že Jenkins lze použít pouze jako On-premises, tedy musíte si ho "někam" nainstalovat.

Moje osobní zkušenost s Jenkinsem je taková, že jsem zažil instance, které byly příšerně pomalé a vytvořit v něm "job", chtělo notnou dávku trpělivosti, která mi scházela. I když s Jenkinsem pracovat umím, vůbec mi nebude vadit, když ho již nepotkám.

V době, když jsem hledal smysluplnou alternativu, která bude navíc fungovat jako služba, tudíž nepotřebujete člověka, co se Vám musí starat o Váš vyšperkovaný Jenkins, nalezl jsem několik variant. Osobně jsem si nejvíce zamiloval CircleCI a to z několika důvodů.

První věcí je tedy to, že funguje jako služba. Další výhodou je to, že má vcelku jednoduché použití. Tedy nemusíte vystudovat 5 vysokých škol, abyste byli schopni napsat jednoduché joby. Dále je to fakt, že každý job, který spouštíte je spuštěn ve vlastním Docker kontejneru a tím se od sebe jednotlivé joby izolujete a můžete je spouštět paralelně. Dané joby pak nadefinujete do workflow a určíte závislosti mezi nimi. Typickým příkladem je, že pokud nedoběhne build projektu, nespustí se deploy, apod.

CircleCI neumožňuje vyrábet Joby bez zdroje. Tedy každa akce je vázána na repozitář v Gitu. Tohle pro někoho může být blokující, ale já to vnímám jako výhodu. Jakým lepším způsobem si verzovat joby, než přímo Gitem? A věřte, jde to i bez toho, jen je třeba občas se nad danou věcí jinak zamyslet. Tím, že CircleCI podporuje i scheduler, tedy opakované spouštění jobů v určitý čas, lze říci, že je to plnohodnotná alternativa.

Poslední věcí, kterou bych chtěl zmínit je to, že CircleCI je spojen s BitBucketem nebo Githubem. Nepotřebujete tedy speciálně pro CD/CI definovat oprávnění a něco složitě nastavovat, ale ono oprávnění je přebíráno právě z těchto systémů pro Git. Pokud jste ve Vaší privátní organizaci na Githubu administrátorem, budete administrátorem i zde, apod.

CircleCI je pro mě osvobozující v tom, že dělá jen to, co dělat má. Tam jeho role začíná i končí.

Ukázkový projekt


Nyní se přesuňme k druhé části, která ma za cíl ukázat, jakým způsobem náš proces úspěšně nastartovat a zároveň provozovat i v produkci.

Nejprve si nadefinujme projekt, který se skládá ze tří částí:

1. Frontend v Next.js
2. Backend v Node.js
3. Knihovna X

Frontend pro svojí činnost potřebuje bežící Node.js a zároveň používá knihovnu z NPM, tedy naší Knihovnu X. Backend také běží nad Node.js a připojuje se k databázi. Poskytuje tedy vrstvu mezi databází a frontendem. Zároveň i backend používá naší Knihovnu X z NPM jako závislost.

Náš projekt bude dodáván formou Docker image a poběží v Azure Cloudu v Kubernetes.

První setup


Prvním krokem je inicizalizace našich systému do Gitu. Budeme mít tři Git repozitáře (Frontend, Backend, KnihovnaX). Inicializaci můžeme udělat v master větvi, ale hned bychom měli vyrobit větev s názvem dev (potažmo devel či develop).

Druhým krokem je definování verze v package.json. Pokud začínáme, označme verzi jako: 0.1.0-alpha.1. Tím, že nám SemVer říká, že do produkce nemůže jít nic, co začíná 0, tak alespoň víme, že v produkci ještě nejsme. Pokud již systém existuje a to i v produkci, tak záleží jak moc jsme verzování ignorovali. Pokud úplně, nastavil bych verzi jako: 2.0.0-alpha.1. Rovnou říkáme, děláme verzi 2 a verze 1 bylo vše předchozí :)

Vývoj


Pokud na daném projektu či repozitáři pracujeme sami, klidně můžeme vše commitovat do naší dev větve. Jsme tam sami, takže proč ne. V případě, že nás pracuje na projektu více, je lepší si vytvořit vlastní větev. Například tak, že si vytvořím novou větev z větve dev a do názvu dám: feature/xxx. Pod názvem xxx by se mělo vyskytovat to, co skutečně děláme. Ve chvíli, kdy svou činnost máme hotovou, zamergujeme naší větev feature/xxx do větve dev. A tak stále dokola. Každopádně nezapomínejte po sobě dané větve mazat.

Vedle toho můžeme rovnou použít CircleCI a říci, že pokud někdo uloží něco do větve dev, spustí se následující scénář (frontend, backend):

1. Build projektu (npm install, tsc, tslint, apod)
2. Test projektu (unit testy, apod)
3. Tvorba Docker image (s tagy: "commit hash" a "dev")
4. Deploy (deploy projektu do Kubernetes)

Krok 1 a 2 je vcelku jasný. Krok 3 říká, že vytvořím Docker image, kde commit hash je unitátní a tudíž bude možné kdykoli nasadit verzi Docker image podle commitu. Tag dev v Docker repozitory se bude neustále přepisovat. Ale zase nám umožní, vytvořit prostředí, které bude z posledních commitů z dev větví.

Krok 4 je deploy, což znamená, že pokud je již systém běžící, tak stačí pouze aktualizovat Docker image tag v prostředí dev. Nejlépe podle commit hash.

Co se týče knihovny, tak workflow by bylo jednodušší:

1. Build projektu
2. Test projektu

První pre-release


Nyní se přesuňme k tomu, že v rámci Githubu vytvoříme svůj první release. V tomhle případě začněme rovnou naší knihovnou, protože tu budete muset releasovat jistě jako první.

Poté, co v Githubu provedete release, tak se do Gitu zapíše tag. Při releasování v Githubu, zapíšete verzi takto: v0.1.0-alpha.1. CircleCI umí zareagovat i pouze na push tag a spustí následující workflow:

1. Build projektu
2. Test projektu
3. Publish do NPM (tag: alpha, verze: 0.1.0-alpha a 0.1.0-alpha.1)

V NPM repozitáři budete verzi 0.1.0-alpha přepisovat každým vydáním nove alpha verze.

Nyní si v naší knihovně (v dev větvi) otevřeme soubor package.json a verzi přepíšeme na 0.1.0-alpha.2. Následuje další vývoj, až do doby, kdy se rozhodneme, že by stálo za to, opět udělat nový pre-release.

Co se týče frontendu a backendu, tak budeme postupovat úplně stejně. Rozdílné bude pouze naše workflow:

1. Build projektu
2. Test projektu
3. Tvorba Docker image (s tagy: "0.1.0-alpha" a "0.1.0-alpha.1")

A opět v dev větvi zvýšíme verzi na: 0.1.0-alpha.2.

Tímto způsobem jsme vytvořili první pre-release.

Je dobré ještě zmínit, že samotné verzování je hodně na Vás. Nicméně byste měli z alpha verze přecházet do beta verze a poté do Release candidate (rc). Tímto způsobem byste měli postupně dojít až do produkce. Použití těchto identifikátorů je následující:

1. Alpha říká, že funkcionalita je nekompletní a rozpracovaná
2. Beta říká, že funkcionalita je již téměř kompletní, ale může obsahovat chyby
3. Release Candidate říká, že funkcionalita je vyvinuta a opravují se jen chyby

Další variantou je, že s jednotným identifikátorem dojdete až před produkci. Určitě ale nemodifikujte samotnou verzi. Je nežádoucí, aby v rámci vývoje došlo k tomu, že z verze 0.1.0 udělám verzi 0.2.0 apod. To patří do produkce.

Pokud jste pečlivě prostudovali SemVer, tak jste jistě narazili i na to to, že v rámci pre-release identifikátoru můžete být více konkrétní. Klasickým příkladem je to, že alpha.x je málo vypovídající. Nic nebrání tomu, aby se verze označila třeba takto: 0.1.0.-alpha.1+api.3. Je jen na Vás, co vše chcete v pre-release identifikátoru evidovat. Samozřejmě bych to s výčtem informací nepřeháněl :)

Pre-release je stav, kdy vývoj říká, že splnil nějaký milník. Něco, kdy stojí za to, aby provedl pre-release a tím nabídnul své dílo vyššímu prostředí (demo, testy, apod). Vedle toho není cílem, aby všechny projekty měly stejné číslo v identifikátoru alpha. Vývoj jen řekne, že pro sestavení prostředí z posledního milníku je třeba:

Frontend: 0.1.0-alpha.7
Backend: 0.1.0-alpha.12
Knihovna: 0.1.0-alpha.4

Popřípadě můžete říct, že použití verze 0.1.0-alpha, vždy použije poslední existující release v alpha fázi.

Určitě doporučuji, aby pre-release vznikal tak často, jak to jen lze. V rámci konce sprintu (jednou za 14) je maximum. Klidně může vznikat častěji a to je žádoucí. Přeci jen, chceme říkat, že jsme "agilní" :)

První produkční release


Pokud již nastal čas, management už je naštvaný, že jsme po termínu, přejdeme k oficiálnímu releasu. Ten uděláme tak, že v našich package.json souborech nastavíme verzi na: 1.0.0 a tento commit zamergujeme do master větve. Poté přes Github uděláme release.

V NPM nám vznikne knihovna s číslem verze 1.0.0. Pro Docker image zvolíme tagy: 1, 1.0, 1.0.0 a latest. Důvod, proč bychom měli opět vyrábet více tagů je ten, že poté si můžeme zvolit, jakým způsobem budete sestavovat produkci či jiné prostředí. Pokud zvolíme verzi 1, tak říkáme, aktualizuj se vždy, když někdo vytvoří produkční release 1.x.x. Pokud 1.0, tak totéž platí pro 1.0.x, apod. Latest je tag, kterým se definuje poslední existující stabilní verze a navíc je pro Docker výchozí.

V rámci Docker tagů můžete použít i kódové označení. Jedním z příkladů je třeba Docker image pro Node.js.

Nyní se vrátíme k naší dev větvi a verzi nastavíme na 1.1.0-alpha.1.

Opravy chyb na produkci


Možná jste si všimli, že jsme v dev větvi použili verzi 1.1.0-alpha.1 a ne 1.0.1-alpha.1. Důvod je ten, že pokud se na produkci vyskytne závažná chyba, která musí být opravena dříve, než bude nový release, tak její oprava bude mít verzi: 1.0.1.

Oprava chyby se provede tak, že se vytvoří nová větev z tagu 1.0.0. Název branche by měl být hotfix/xxx, kde pod xxx se může skrývat identifikace chyby. Poté, co je chyba opravena a otestována, je zamergována zpět do master větve a vydána jako verze: 1.0.1. Současně s tím bychom daný hotfix měli zamergovat do naší dev větve a následně branch hotfix/xxx smazat.

Následný vývoj


Jak už jsme si řekli, tak následný vývoj bude mít verzi 1.1.0-alpha.1. Nicméně může se stát, že nový release bude znamenat nekompatibilitu s předchozí verzí. V tom případě bychom verzi nastavili jako: 2.0.0-alpha.1.

V této chvíli se můžete vrátit ke kapitole Vývoj, protože přesně na tom místě se nyní nacházíme...

Závěr


Jsem si vědom toho, že existuje tisíce patternů, jak řídit samotnou dodávku softwaru. Každopádně cílem bylo Vám ukázat jednu z cest, kterou když se vydáte, tak jistě neselžete.

Stejně tak lze mnohem více věcí automatizovat, viz třeba ruční přepisování package.json souborů. Ale vždy je zatím skryto určité nebezpečí. Tím nebezpečím je fakt, že automatizace je dobrá cesta, ale do určitých limitů. Mohlo by se Vám totiž jednoduše stát, že touha po dokonalé automatizaci končí šílenými bash/python/perl skripty, které jsou často jen náhradou ručního releasu. Za mě, ona automatizace končí přesně ve chvíli, kdy se dostanu do míst, kde musím řešit příliš mnoho výjimek a pravidel.

A co vy? Jak řešíte Release management?


neděle 1. dubna 2018

DevOps s Kubernetes a Helm Charts


V poslední době jsem byl nucen se více zaměřit na integraci mezi vývojem a provozem. Poté, co jsem si prošlapal několik slepých uliček, jsem nakonec skončil u technologie, která se pomalu stává hlavním tématem ve spojení s Cloudem. Kubernetes. Platforma, kterou si zamilujete.

Než se dostaneme ke Kubernetes a Helm Charts, pojďme se podívat na svět, kde tyto technologie neexistují.

Cesta mezi zdrojáky v gitu a provozem je často dlouhá, krkolomná a křehká. Zažil jsem projekty, kde integrace znamenala, že se ručně nakopíroval build na ostrý provoz přes FTP. Projekty, kde díky absenci CI/CD se integrace vlastně nekonala, ale byla tvořena pouze z jednoho lokálního PC. A také projekty, kde integrace byla tak složitá, že bylo třeba zaměstnat několik lidí v roli DevOps, kteří tvořili křehké bash/python/perl skripty, které chápal pouze autor.

V takovém prostředí je vždy nutné mít několik programátorů/administrátorů, kteří jsou fulltime výtíženi jen tím, aby udrželi aktuální stav. Ve chvíli, kdy dochází produkčnímu nasazení, jedná se spíše o ruskou ruletu. Před takovou akcí dochází k modlitbám a lidem v hlavách běží několik otázek: "Bude to fungovat? Jak se jednoduše vrátíme zpět? Cože? Horizontálně škálovat?! Máme správné verze?" Tohle peklo se často ještě násobí počtem systémů, které musíme nasadit.

Vedle samotného nasazení na produkci se DevOps často stará i o to, aby vývoj, potažmo ostatní účastníci projektu, měli svá prostředí. Pokud nejsme naprostí ignoranti, je jasné, že pouze s jedním prostředím typu DEV si nemůžeme vystačit. Poté přichází na řadu prostředí pro testery, prostředí pro demo, prostředí na preprod simulaci, apod. Nedej bože, aby vývoj chtěl prostředí podle git větví. Není neobvyklé, že takových prostředí můžeme mít klidně i několik desítek. Často se nakonec dostáváme do stavu, kdy začneme ohýbat release management, protože jinak bychom do produkce nikdy nic nedostali. Chaos, který se nedá narovnat jinak, než zásadní změnou použitých technologií...

Pojďme se nyní pokusit z takového stavu vymanit a v podstatě se dostat do stavu, kdy samotná role DevOps se stane vlastně zbytečnou, popřípadě umožní nám zredukovat potřebné množství lidí, kteří by nám někde po serverech psali bash skripty. Aby následující popis nebyl příliš abstraktní, pojďme si navrhnout projekt, kterého se to bude týkat.

Návrh projektu


Náš projekt se skládá ze šesti systémů (každý systém je uložen ve vlastním git repozitáři):
  1. Frontend - hlavní UI našeho projektu
  2. Backend - hlavní backend pro UI a současně poskytuje veřejné API
  3. Admin - admin rozhraní
  4. Backend-admin - backend pro administraci
  5. Backend-int - integrační backend mezi naším projektem a ERP systémem pod námi
  6. Tool-ui - pomocný nástroj, který sleduje náš systém, slouží pouze pro vývoj

Běh projektu má následující předpoklady:
  • Databáze
  • Frontend, Backend a Admin musí být přístupný na doméně přes SSL, ostatní systémy nesmí být dostupné na veřejné doméně či IP adrese
  • Certifikáty pro komunikaci mezi Backend-int a ERP systémem
  • Projekt běží v Cloudu (Azure či Google Cloud)
  • CI/CD server - například CircleCI

Součástí vývoje je nutné mít i několik prostředí. Každé prostředí obsahuje vlastní databázi a je plně autonomní. Tvorba prostředí vlastně znamená tvorbu databáze a serverů, na kterých běží náš projekt. Pro naše účely si představme, že máme tyto prostředí:
  • dev - prostředí pro vývoj
  • test[0-9] - až 10 testovacích prostředí
  • uat - prostředí pro uživatelské akceptační testy
  • preprod - protředí simulující produkci, tedy na produkčních datech
  • prod - produkční prostředí

Krok první: Docker


O Dockeru jsem psal v minulém článku Vývoj aplikací přes Docker. V dnešní době si vývoj bez Dockeru neumím ani moc představit. Těch výhod, které Vám Docker nabízí je totiž tolik, že i přes prvotní krkolomné rozeběhnutí se nakonec budete ptát sami sebe: "Proč jsem sakra s Dockerem nezačal už dříve".

Součástí samotné "dockerizace" Vašich systémů je nutné myslet i na správné verzování. K verzování Vám slouží samotné Docker tagy. Zde Vás odkážu na článek, popisující samotné verzování.
Vedle verzování je možné Docker image ukládát i do privátních repozitářů. Ať už v rámci Cloudu, kde často najdete něco jako Container registry, tak třeba i v Docker Hubu.
Každopádně samotný Docker image NESMÍ obsahovat žádné citlivé informace. Pokud do běžícího Docker kontejneru potřebujeme dostat informace o připojení k databázi, nechte toto nastavení na Kubernetes.

Nyní Vám ukážu příklad, jak v CircleCI vytvořit Docker image a ten uložit do privátního Docker registry v Azure:
version: 2
jobs:
  create_docker_image:
    working_directory: ~/docker-image
    machine: true
    steps:
      - checkout
      - attach_workspace:
          at: ~/docker-image
      - run:
          name: Docker login
          command: docker login ${AZURE_DOCKER_URL_SERVER} -u ${AZURE_DOCKER_USERNAME} -p ${AZURE_DOCKER_PASSWORD}
      - run:
          name: Set docker image to workspace
          command: mkdir -p workspace && echo "${AZURE_DOCKER_URL_SERVER}/xxx/${CIRCLE_PROJECT_REPONAME}" > workspace/docker-image
      - run:
          name: Show docker image
          command: cat workspace/docker-image
      - run:
          name: Docker create image
          command: docker build --build-arg GIT_COMMIT=${CIRCLE_SHA1} --build-arg GIT_BRANCH=${CIRCLE_BRANCH} --build-arg BUILD_NUM=${CIRCLE_BUILD_NUM} --build-arg BUILD_AUTHOR=${CIRCLE_USERNAME} -t $(cat workspace/docker-image):latest .
      - run:
          name: Docker create tag CIRCLE_BRANCH
          command: docker tag $(cat workspace/docker-image) $(cat workspace/docker-image):${CIRCLE_BRANCH}
      - run:
          name: Docker create tag CIRCLE_SHA1
          command: docker tag $(cat workspace/docker-image) $(cat workspace/docker-image):${CIRCLE_SHA1}
      - run:
          name: Docker push
          command: docker push $(cat workspace/docker-image)
      - persist_to_workspace:
          root: .
          paths:
            - workspace
            - .circleci

workflows:
  version: 2
  build_and_deploy:
    jobs:
      - create_docker_image:
          context: xxx-context
          filters:
            branches:
              only:
                - dev

Job s názvem create_docker_image provede několik věcí:

  1. Přihlásí se pomocí Docker login do privátního Docker registry
  2. Vytvoří Docker image s tagem latest
  3. Do Docker image uloží informace jako: commit hash, git branch, číslo buildu, autora buildu
  4. Vytvoří další tag podle git větve
  5. Vytvoří další tag s git hash commitem
  6. Pushne dané tagy do privátní Docker registry
V ukázce je navíc použit workspace, což je vlastnost CircleCI, která umožňuje předat hodnoty dalším jobům v rámci workflow.

Výsledkem je, že v Docker repozitory budeme mít Docker image s tagy: latest, dev a commit hash. Důvod, proč verzujeme Docker image více tagy je jednoduchý. Představte si situaci, že používáte Docker image s tagem dev. Co když je ovšem onen image rozbitý a my rychle potřebujeme nasadit jeho předchozí verzi? Není nic jednoduššího, než si dohledat předchozí commit a nasadit image podle commit hash.

V případě releasu jsou Docker image tvořeny z master větve a verzovány podle git tagu. Pokud řeknu, že vytvářím novou verzi například: 2.3.7, tak se vytvoří či přepíší následující tagy: latest, 2, 2.3 a 2.3.7. Ale více se už dozvíte z výše odkazovaného článku o verzování Docker image.
Další variantou verzování vývoje je přes pre-release podle SemVer. Nicméně, zůstaňme u této zjednodušené varianty.

V této chvíli bychom mohli samotný Docker image nasadit a říci, že máme hotovo. Po pravdě, přímé nasazování samotných Docker images nám sice částečně pomůže, ale tím zdaleka nekončíme.

Stačí si jen zodpovědět otázku: "Jak vytvořím celé prostředí, když nasazuji pouze jeden systém?" Onen systém potřebuje databázi, potřebuje i okolní systémy a tím pádem bychom se stále museli zabývat tvorbou prostředí ala bash skripty v DevOps.

Krok druhý: Kubernetes


Když jsem přemýšlel, že napíši článek o Kubernetes, uvědomil jsem si, že vlastně nevím, kde přesně začít. Těch věcí je tolik, že na konci si řeknete: "Uff...". Na druhou stranu si ale zase uvědomíte, že to vlastně není nic složitého a vlastně jde jen o další abstrakci. Pojďme si Kubernetes vysvětlit trochu zjednodušeně.

Jednou větou se dá říci, že Kubernetes slouží na orchestraci běžících Docker kontejnerů. Zkusme si v několika bodech ukázat, kde všude nám může pomoci.

  • Tvorba bezvýpadkového systému
  • Škálování (horizontální i vertikální) za běhu
  • Load balancing
  • Správa celého prostředí
  • Možnost provést rollback, vrátit se k předchozím verzím
  • Uzavřít systémy uvnitř VNETu a vystavit ven pouze některé systémy
  • Globální nastavení proměných jako jsou hesla či hodnoty ovlivňující chod systémů
  • Jednoduše přes interní DNS provázat systémy mezi sebou
  • Před veřejné endpointy nasadit například nginx s automatickým Lets Encrypt SSL certifikáty
  • Automatické horizontální škálování, například v případě zátěže se po 5 minutách začnou automaticky vytvářet nové servery a případě snížení záteže se po dalších 5 minutách začnou mazat až do předem definovaného minimálního množství
  • Změnu provádět vždy deklarativně, tedy nebát se jako v případě imperativního zásahu, že shodím celý systém
  • Oddělit od sebe jednotlivá prostředí
  • Ideální platforma na tvorbu multitenantních systémů
  • Nebýt závislý na Cloud platformě

Kubernetes za Vás nevyřeší to, zda jste kvalitně otestovali svůj systém. Vyřeší ovšem za Vás to, že Vás odstíní od věcí, které byste si museli sami pracně udržovat pomocí různých podpůrných systémů.

Za největší výhodu považuji deklarativní ovládání celé platformy. Pomocí vlastních šablon říkáte, co se má v budoucnu stát. Příkladem je Deployment.

Deployment


Pomocí objektu Deployment definujete, jaký systém a v jaké konfiguraci se má nasadit. Součástí šablony pro Deployment jsou i další věci jako je například informace o tom, jaký Docker image se má spustit, s jakými parametry a třeba kolik se má vytvořit serverů.
Poté, co onen Deployment pošlete do Kubernetes, tak on si ho zařadí do své fronty požadavků a v budoucnu začně zpracovávat.

Pojďme se podívat na příklad Deployment objektu:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend-deployment
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
      - name: frontend
        image: url/frontend-image:1.0.3-dev
        env:
        - name: PORT
          value: "8080"
        - name: NODE_ENV
          value: "production"
        ports:
        - containerPort: 8080
      imagePullSecrets:
      - name: image-secret

Tento plán nám říká, že do Kubernetes nasadíme systém s názvem frontend, který je z Docker image frontend-image:1.0.3-dev. Navíc onen Docker image je z privátní repozitory, k němuž najde přístup v Secret objektu s názvem image-secret. Secret je další objekt, který se definuje podobně jako objekt Deployment

Daný Docker kontejner bude mít dvě proměné a to PORT a NODE_ENV. Systém se nasadí ve třech Podech. Pojem Pod v Kubernetes znamená běžící jednotku. Zjednodušeně řečeno, můžeme na ní koukat jako na běžící Docker kontejner.

Abychom tento Deployment nasadili, stačí spustit následující příkaz:
kubectl apply -f deployment.yaml

V případě, že již takový Deployment existuje a tudíž i existují bežící Pody, tak Kubernetes neudělá to, že by předchozí zastavil a nasadil nové, ale nejprve si vytvoří nový Pod a v ramci něho se pokusí onen Pod spustit. Pokud by se mu to nepovedlo, ať už z důvodu neexistence Docker image, nebo pádu systému při startu, tak to neovlivní aktuální běh Podů.
Pokud se mu to podaří, tak po startu zastaví jeden z předchozích Podů a začne vytvářet další. Tímto způsobem postupně vymění všechny Pody, aniž by muselo dojít k výpadku.

V našem případě jsou systémy postaveny nad Node.js. Díky tomu je start takového Podu často v řádu několika milisekund. Pokud používáte nějaké šílenosti typu Weblogic server, který startuje cca týden, tak právě díky postupné výměně Podů, nemusíte zaznamenat žádný výpadek.

V Kubernetes existuje velká škála objektů. Zde je výčet těch, se kterými sám pracuji a považuji je za ty nejzásadnější:
  • Namespace - jmenný prostor, vhodný například pro určování prostředí
  • Deployment - definuje tvorbu Podů
  • Pod - běžící Docker kontejner
  • Service - služba, sloužící na sloučení Podů pod jeden vstupní bod, může sloužit jako Load Balancer
  • ConfigMap - objekt obsahující konfigurace, nejčastěji se tato konfigurace namapuje do Podů
  • Secret - stejný jako ConfigMap s tím rozdílem, že hodnota není na první pohled viditelná a je v ukládána v base64; nejčastěji se používá na uložení hesel a dalších citlivých informací
  • Ingress - objekt určený pro externí přístup, příkladem je třeba vystavení Vaší aplikace přes nginx server s SSL; Ingress je nad objektem Service a může tedy sloužit také jako Load Balancer


Kubernetes je úžásná technologie, která tady s námi několik let jistě bude. Informace o tom, jak použít Kubernetes třeba v Azure, najdete zde. Pokud byste si chtěli Kubernetes vyzkoušet pouze lokálně, není lepší volby než přes minikube.

Krok třetí: Helm


Pokud jsme zvládli Docker a Kubernetes, tak poslední třešničkou na dortu je Helm. A i když se Vám může zdát, že Helm je technologie, která už není tak potřebná, opak je pravdou Kubernetes bez Helmu je stejné, jako jezdit v rychlém autě se zataženou ruční brzdou.

Co je to tedy Helm? Jedná se o Package Manager pro Kubernetes. Vím, tohle je asi nic neříkající věta a proto pojďme si opět ukázat, kde by se nám Helm mohl hodit.

Vraťme se k našemu projektu. Projekt je složen ze 6ti systémů, které jsou mezi sebou provázané a navíc potřebují několik věcí, jako je ono připojení k databázi, připojení k privátní Docker repozitory, vystavení několik služeb přes nginx SSL, apod. Tím nám vznikají vcelku složité předpoklady k sestavení celého projektu. Co kdybychom ale řekli, že celý náš projekt vytvoříme od nuly a to pouze jedním příkazem?

A zde právě přichází na řadu Helm. Celý náš projekt můžeme zabalit do balíčku, který nainstalujeme jako kdybychom instalovali jakýkoli jiný program.

Zde je ukázka instalace v Helmu, která provede nasazení celého prostředí jako jeden balík:
helm install repo/projekt-x --namespace dev --name projekt-x-dev -f values-dev.yaml 

Tento příkaz vytvoří namespace dev a nainstaluje Kubernetes objekty, které jsou definovány v repozitáři repo/projekt-x. Onen balík bude pojmenován jako projekt-x-dev a navíc do jednotlivých definic vloží hodnoty ze souboru values-dev.yaml.

Stejně jako v případě Docker image bychom NEMĚLI do Helm Chart ukládat citlivé informace. K tomu právě slouží možnost při instalaci připojit soubor, který obsahuje ruzná hesla a další hodnoty, kterým nastavíme celý projekt.

Poté si můžeme nechat vypsat všechny balíčky, které jsme pomocí Helmu nainstalovali:
helm ls

Pomocí Helmu můžeme celý balíček smazat a tím smazat celé prostředí. Můžeme se pomocí Helmu i vracet k různým revizím. Představte si, že nasazujete novou verzi celého vašeho projektu. V testech bylo vše v pořádku a co čert nechtěl, na produkci je problém. Není nic snazšího, než pomocí Helm rollback se vrátit k předchozí verzi:
helm rollback system-x-dev 23

Pro představu si pojďme popsat třeba Wordpress. Wordpress je všeobecně známý redakční systém a na své fungování potřebuje několik věcí: apache, php a mysql. Co kdybychom ale Wordpress pomocí Helmu nainstalovali třeba takto:
helm install --name my-release stable/wordpress
Důvod, proč jsem si zvolil zrovna ukázku pro Wordpress, není náhoda. Existuje totiž oficiální balík Wordpress Chart.

Pojďme se nyní podívat na to, jak se takový Helm Chart tvoří. První základní věcí je soubor Chart.yaml, který obsahuje základní informace o našem balíku:
apiVersion: v1
appVersion: "1.0"
description: Nas pokusny system
name: system-x
version: 0.0.1

Nejdůležitější věcí je atribut name a version. Díky tomu jsme schopni identifikovat náš balík. Další součástí je adresář templates, ve kterém si již tvoříme vlastní definice Kubernetes objektů.

V případě, že máte již balík v Kubernetes nainstalován, máte možnost změnit jeho hodnoty. A to také pouze částečně. Představte si, že máte nainstalováné prostředí dev a chcete změnit verzi Docker image pro frontend. Ideální kandidát v CD/CI:
helm upgrade --reuse-values --wait system-x-dev repo/system-x --namespace dev --set frontend.image.tag=1.0.3-dev

Závěr


Stejně jako Kubernetes, tak i Helm je technologie, která se nedá popsat v rámci jednoho článku. I když se na první pohled může zdát, že je to příliš složité, tak opak je pravdou. Sám jsem toho důkazem. Stačí úvest jeden příklad. Kubernetes a Helm jsem si osvojil během několika dní. Postupnými kroky se dopracoval až k tomu, že dnes pouze vylepšuji současný stav.
V budoucnu se zkusím více zaměřit na samotný release management, který dost ovlivňuje to, jakým způsobem budeme s Dockerem, Kubernetes a Helmem pracovat.

"Happy helming"

neděle 18. března 2018

Jak psát stabilní kód v JavaScriptu?

Pokud se rozhodnete, že v JavaScriptu napíšete větší část kódu, tak narazíte na to, že bez podpůrných nástrojů se z této cesty může stát peklo. Existuje velké množství vývojářů, kteří Vám řeknou, že JavaScript je bastl a nedá se v něm smysluplně napsat nic jiného, než pár drobností na rozhýbání webu. Opak je pravdou. V současné době je JavaScript nejuniverzálnějším jazykem na světě.

Je vcelku jedno, zda v JavaScriptu píšete aplikace typu backend, web, mobil, desktop, příkazy pro konzoli či třeba cloud funkce. Stále byste se měli snažit o to, abyste svůj kód zapsali tím nejlepším možným způsobem.

JavaScript se za posledních několik let výrazně změnil. Dostali jsme se do stavu, kdy existují dvě skupiny programátorů.

První skupinou jsou ti, kteří ignorují nové ECMAScript specifikace a JavaScript zapisují "starým způsobem". Do této skupiny často spadají lidé, kteří potřebují občas rozhýbat určitou část webu a JavaScript chápají jako jazyk, který přímo vykoná webový prohlížeč. O této skupině vývojářů se dá říci, že vlastně neumí v současném JavaScriptu programovat.

Druhou skupinou jsou vývojáři, kteří umí využít věci jako je Node.js, Babel, Typescript, apod. Kód v JavaScriptu zapisují v nových ECMAScript specifikacích a fakticky jsou schopni z tohoto jazyka často vytěžit maximum. Jedná se o naprosto jiný svět, než je tomu v případě první skupiny.

Abych Vám toto předal trochu více exaktněji, podívejte se na následující příklad, který ilustruje rozdíl mezi první a druhou skupinou:
// prvni skupina
function oldWay(options) {
    return {
        name: options.name,
        hello: function () {
            return 'Hello ' + options.name;
        }
    }
}

// druha skupina
const newWay = ({name}) => ({name, hello: () => `Hello ${name}`});

console.log(oldWay({name: 'Old way'}).hello());
console.log(newWay({name: 'New way'}).hello());

Tento kód je založen na využití arrow functions, string interpolation a destructuring assignment. Věřte, že je to jen část vlastností, které nové ECMAScript specifikace nabízí. Teď ruku na srdce, pokud je zde čtenář té první skupiny, poznal by, že ten druhý zápis ve výsledku dělá tu samou věc? Na JavaScript se dá koukat jako na dva jazyky. Na jazyk před ECMAScript 6 a jazyk s ECMAScript 6 a vyšší.

Ale pojďme zpět. Jak zajistit to, abychom v JavaScriptu byli schopni psát stabilní kód, který se nám nerozsype s novou posilou v týmu či tím, že někdo bude psát "starým" a někdo "novým" způsobem?

Airbnb JavaScript Style Guide

První věcí, kterou by každý JavaScript programátor měl začít je, že si přečte a osvojí si style guide, tedy zápis JavaScript kódu. K tomtu účelu se výborně hodí následující příručka Airbnb JavaScript Style Guide.
Dokud si toto neosvojíte, těžko se můžete považovat za seniornějšího JavaScript programátora.

Typescript / Flow

Pokud to s JavaScriptem myslíte skutečně vážně, určitě byste se měli snažit o to, abyste tento jazyk obohatili o statickou typovou kontrolu. Jelikož je JavaScript dynamicky typovaný jazyk, tak v případě, že se Vám projekt v JavaScriptu rozšíří, tak bez typové kontroly je jakýkoli refactoring roven ruské ruletě. Osobně preferuji Typescript a důvody proč, jsem sepsal v článku Proč právě Typescript.

Typescript strict mode

Součástí Typescriptu je i možnost nastavení striktního módu. Toto nastavení se jmenuje přímo "strict". Co se díky tomutu módu vše zapne, najdete v manuálu: https://www.typescriptlang.org/docs/handbook/compiler-options.html

ESLint / TSLint

Další nezbytnou součástí je ESLint (v Typescriptu TSLint). Linting, nebo-li "lustrování" kódu slouží k tomu, aby Vám nadával za to, že jste kód nezapsali zrovna tím nejlepším způsobem. Například Vám řekne, že jste napsali function, místo toho, abyste použili arrow function, že jste string zapsali ve špatných uvozovkách, že jste překročili limit počtu znaků na řádku, apod.
I když možná budete tento linter na začátku nenávidět, tak věřte, že je to dočasné. Časem totiž poznáte, že je to velice dobrý sluha. Dá se říci, že Vás naučí správně zapisovat JavaScript kód.

Prettier

Dalším skvělým pomocníkem je Prettier. Tento nástroj slouží k tomu, že za Vás automaticky formátuje kód. K čemu je to vlastně dobré?
Osobně pracuji tak, že když píšu kód, tak automaticky stále spouštím dvě základní operace nad kódem: "Format code & Optimize imports". Dělám to tak už roky a jsem přesvědčen, že by toto měl dělat každý vývojář. Každý moderní vývojový nástroj (Atom, WebStorm, Visual Studio Code, apod) má tyto dvě operace k dispozici.
Ve chvíli, kdy pracuji na projektu, kde je více lidí, tak bez automatického formátu vzniká problém. Co dokáže naprosto otrávit je, když automaticky formátujete kód jen v editoru a najednou uděláte změny i tam, kde jste vůbec nepracovali. Git Vám poté hlásí, že poslední změnu na daném řádku jste udělali Vy a přitom to není vůbec pravda.
První variantou je, že všem vývojářům přikážete, že musí používat jedno IDE a jeden styl formátování. A věřte, to je to poslední, co chcete dělat. Ne každý chce třeba psát kód v IntelliJ IDEA. Proto zde máme Prettier. Nástroj, který za nás určí pravidla a vy se jim automaticky přizpůsobíte, ať už píšete v poznámkovém bloku či třeba v Atomu.
Druhou důležitou vlastností je to, že Prettier Vám formátuje kód tak, aby správně doplnil závorky, středníky, atd, podle jasně definovaného style guide.

Integrace TSLintu a Prettier s Gitem

Nyní se pojďme podívat na to, jak využít TSLint a Prettier při práci s Gitem. Berme v potaz, že již máte projekt v Gitu. Projekt je v Typescriptu a jedná se o React aplikaci.

Nejprve do projektu přídáme závislost na tslint a tslint-react:
npm i -D tslint tslint-react

Poté do projektu přidáme soubor tslint.json:
{
  "extends": [
    "tslint:recommended",
    "tslint-react"
  ],
  "rules": {
    "max-line-length": [
      false,
      160
    ],
    "semicolon": [
      true,
      "always",
      "ignore-bound-class-methods"
    ],
    "quotemark": [
      true,
      "single",
      "jsx-double"
    ],
    "member-ordering": [
      true,
      "variables-before-functions"
    ],
    "ordered-imports": false,
    "interface-name": false,
    "object-literal-key-quotes": [
      true,
      "as-needed"
    ],
    "object-literal-sort-keys": false,
    "no-object-literal-type-assertion": false,
    "no-empty-interface": false,
    "jsx-no-multiline-js": false,
    "jsx-boolean-value": false,
    "member-access": false
  }
}

Do package.json stačí přidat následující skript:
"scripts": {
    "tslint": "tslint -c tslint.json 'src/**/*'"
}

A poté spustit:
npm run tslint
Hlásí Vám tslint chybu? Pokud ano, tak nezbýbá, než ony chyby opravit :)

Nyní pojďme přidat prettier:
npm i -D husky prettier pretty-quick
Současně s knihovnou prettier nainstalujeme knihovnu husky, která slouží k tomu, že vytvoří git hook.

Dále v projektu vytvoříme soubor .prettierrc.json:
{
  "printWidth": 160,
  "tabWidth": 4,
  "parser": "typescript",
  "singleQuote": true,
  "bracketSpacing": false,
  "trailingComma": "all",
  "arrowParens": "always"
}

Jelikož použijeme pretty-quick pro provedení automatického formátování, je třeba definovat soubor .prettierignore, ve kterém určíme adresáře a soubory, které z automatického formátovaní chceme vynechat:
.circleci/
.next/
dist/
package.json
package-lock.json

Poté, co máme základní konfiguraci hotovou, nezbývá, než obohatit náš package.json:
"scripts": {
    "tslint": "tslint -c tslint.json 'src/**/*'",
    "precommit": "pretty-quick --staged && npm run tslint"
}
A nyní stačí zkusit provést commit do gitu :)

Toto řešení je postavené na tom, že používáte Git. Před tím, než se provede samotný commit do Gitu, tak se nejprve spustí formát kódu a poté se překontroluje pomocí TSLintu. Pokud máte něco špatně, commit se neprovede. Pokud Vám nevyhovuje precommit, můžete využít i možnost prepush, tedy před tím, než se provede push do remote git repozitáře. Nicméně výsledkem je, že se Vám do společného Git repozitáře vždy dostane pouze kód, který je spravné formátovaný a zkontrolovaný pomocí linteru.

Kromě Git hooku je určitě vhodné, abyste toto provedli ještě v CI. Tedy ve vašem nástroji na continuous integration. V našem případě se jedná o CircleCI, kde pouze spustíme samotný příkaz npm run tslint a tím zajistíme, že před nasazením na server nemáme v kódu něco špatně.

Závěr

Zajistit štábní kultruru v JavaScript projektech není složitá záležitost. Celé je to pouze o tom, že čím déle budete věci jako je TSLint či Prettier ignorovat, tím víc práce v budoucnu budete mít.
Také je dobré zmínit, že nelze nekriticky spoléhat na tyto nástroje. Stále je pouze na Vás, zda kód, který napíšete je nakonec funkční. Nejsou to nástroje, které Vás například zbaví nutnosti psát unit testy.
A co vy? Používáte některé ze zmíněných nástrojů?

pátek 2. března 2018

GraphQL na reálném projektu

Pokud při vývoji backend API začnete uvažovat o náhradě RESTu za GraphQL, tak Vám gratuluji, protože jste s největší pravděpodobností zvolili správně :)

Psát o výhodách samotného GraphQL je asi zbytečné. Stačí jen nekriticky říct, že: "Konečně máme smysluplný způsob jak získávat data z backendu na frontend."

GraphQL můžete implementovat v několika jazycích. Počínaje Javascriptem a třeba Javou konče. Nicméně, pokud to s GraphQL myslíte vážně, určitě bych se raději vydal cestou javascriptu. Důvod je čistě pragmatický a to ten, že v javascriptu budete mít nejlepší podporu a i samotní autoři tento jazyk berou jako výchozí.

Reálný projekt s GraphQL

V současné době naše firma ApiTree pracuje na projektu, který vyžaduje tvorbu veřejného API a také tvorbu uživatelského portálu, který používá právě zmíněné GraphQL API.

Pro tento projekt jsme zvolili následující technologie:

  • Typescript
  • Next.js/React
  • Apollo
  • GraphQL
  • Node.js
  • Express
  • CosmosDB/MongoDB
  • Kubernetes

Tím, že na projektu pracuje více vývojářů, tak jsou rozděleni na své sféry vlivu. I přes to, že používáme jeden jazyk, tak je velice náročné nechat vývojáře stále přepínat mezi psaním backendu pomocí Mongoose a frontendu v podobě Next.js.

Pokud bychom vývoj rozdělili na backend a frontend, tak společným prvkem se stává samotné GraphQL schéma. Toto schéma potřebují znát obě strany.

Pro naše účely jsme tedy rozdělili projekt na několik systémů:

  • Backend - Express, GraphQL Apollo server, Mongoose
  • GraphQL - GraphQL schéma, GraphQL Apollo mock server
  • Frontend - Next.js / React, GraphQL Apollo client

Strana backendu a frontendu je asi zřejmá, proto se pojďme spíše podívat, proč jsme zvolili samostatný projekt pro GraphQL API.

Projekt GraphQL schéma

Když jsme přemýšleli jak nejjednodušeji napsat GraphQL schéma, skončili jsme samozřejmě u toho, že píšeme čisté schéma v souborech s příponou *.graphql. Definice tohoto schématu obsahuje vše co potřebujeme. Definuje query, mutace, nabízí i jednoduchou dokumentaci v podobě description nad jednotlivými graphql objekty a atributy.

Toto samotné schéma nic samo neumí, pouze definuje jak API bude vypadat.

Jelikož používáme Typescript, hodilo se nám, abychom z daného GraphQL schéma vygenerovali typovou definici, kterou poté použije jak backend, tak frontend. K tomuto účelu nám dobře posloužila knihovna GraphQL Code Generator. Poté jsme celý projekt vzali a vytvořili z něj NPM balík pomocí npm publish.

Nyní máme k dispozici GraphQL schéma s Typescript definicemi jak pro frontend tak pro backend.

Vedle toho jsme tento projekt využili ještě k jedné věci a tou je tvorba mock serveru. Tvorba tohoto mock serveru byla velice jednoduchá. Vzali jsme samotný soubor graphql a poslali ho do metody addMockFunctionsToSchema z knihovny graphql-tool. Pokud vezmeme samotné schéma, tak tato metoda nám nabízí výchozí mocky v podobě předem definovaného řetězce na string, number, apod. Nicméně tato volba je přepisovatelná, stejně jako implementování metody resolve. Zjednodušeně se dá říci, že napíšeme graphql schéma, pošleme do gitu a CircleCI nám zajistí jak tvorbu NPM balíčku pro frontend a backend, tak nám dále v Kubernetes vystaví mock server, který rovnou můžeme volat.

Závěr

Psát o tom, jak pomocí Apollo frameworku volat GraphQL v React komponentách je asi zbytečné, k tomu stačí jednoduchý tutoriál.

Na straně backend serveru je to stejné. Vezmeme graphql definici a jen dopíšeme resolve metody. Ve spojení s Mongoose a samotnou typovou definicí z GraphQL schématu, máte vlastně hotové kontrolery a jen se dopisuje implementace.

Práce s GraphQL není jen velice rychlá a efektivní, ale i zábavná. Když k tomu připočteme takové věci jako je start Node.js serveru během několika milisekund, tak tvorba GraphQL API vlastně vzniká v realtime čase psaním graphql schéma souboru.

A co vy? Už jste na GraphQL přešli?

úterý 20. února 2018

Nová verze Next.js 5 a Typescript


Před pár dny vyšla nová verze nejpoužívanejšího stacku pro React: Next.js 5.0. Seznam změn lze nalézt na oficiálním blogu. Jednou z klíčových vlastností je i podpora pluginů a to zejména pluginu @zeit/next-typescript.

Samotný example od Next.js bohužel neposkytuje ukázku, jak zprovoznit custom server s Typescriptem. Pokud Vás zajímá, tak jak provozovat next-typescript plugin s custom serverem, tak čtěte dál :)

Custom server a Typescript

V případě, že používáte custom server, je třeba samotnou konfiguraci obohatit o několik dalších kroků.

Začněme nejdříve přípravou skriptů v package.json:
  "scripts": {
    "prebuild": "rimraf dist/ && rimraf .next/",
    "build": "next build && tsc --module commonjs",
    "start": "NODE_ENV=production node dist/index.js",
    "dev": "nodemon server/index.ts"
  }

Dalším krokem je úprava tsconfig.json:
{
  "compileOnSave": false,
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "jsx": "preserve",
    "allowJs": true,
    "moduleResolution": "node",
    "allowSyntheticDefaultImports": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "removeComments": false,
    "preserveConstEnums": true,
    "sourceMap": true,
    "skipLibCheck": true,
    "outDir": "dist",
    "baseUrl": ".",
    "typeRoots": [
      "./node_modules/@types"
    ],
    "lib": [
      "dom",
      "es2015",
      "es2016"
    ]
  },
  "include": [
    "server/**/*.ts"
  ]
}

Soubor nodemon.json:
{
  "watch": [
    "server/**/*.ts"
  ],
  "execMap": {
    "ts": "ts-node --compilerOptions '{\"module\":\"commonjs\"}'"
  }
}

Soubor next.config.js (v ukázce je i custom konfigurace webpacku):
require('dotenv').config();
const withTypescript = require('@zeit/next-typescript');
const webpack = require('webpack');

module.exports = withTypescript({
    webpack(config) {
        config.plugins = [
            ...config.plugins || [],
            new webpack.DefinePlugin({
                'process.env.BACKEND_ENDPOINT': JSON.stringify(process.env.BACKEND_ENDPOINT),
            }),
        ];
        return config;
    },
});

Aby byl projekt validní, je třeba dodržet následujcící adresářovou strukturu:

  • server/index.ts - custom node.js server
  • pages/*.tsx - jednotlivé Next.js / React stránky
  • .next/ - build samotného Next.js (viz npm run build)
  • dist/index.js - zkompilovaný custom server (viz npm run build)

Závěr


Next.js a Typescript je nyní ve skvělé symbióze. Server side renderingu zdar! :)

čtvrtek 1. února 2018

Co je špatně na interních knihovnách a frameworcích?

Než se pustím do rozboru, co je špatně na interních knihovnách a frameworcích, chtěl bych předat jednu radu byznysu a vývojářům.

Rada pro byznys

"Pokud Vám vývoj řekne, že na Váš problém mají připravený vlastní interní framework, tak utečte."

Rada pro vývojáře

"Pokud Vám při pohovoru řeknou, že používají vlastní interní framework, tak utečte."


Téměř každý vývojář se někdy dostal do stavu, kdy si řekl, že by bylo vhodné, aby si napsal vlastní podpůrné knihovny pro potřeby, které zrovna řeší.
Tahle cesta je ovšem často dlážděna takovými překážkami, že je velice málo projektů, které nakonec uspějí. Je to stejné, jako když třeba založíte firmu. Jaká je pravděpodobnost, že přežijete? Že ustojíte první rok? První překážku, první úspěch, který vás může nakonec natolik pohltit, že vás zničí?

Rád se vždy vracím do minulosti a nebude tomu jinak ani teď.

Pamatuji si, když jsem začal s programováním jako hlavní profesí. V té době jsem pracoval jako PHP vývojář. Abyste byli v obraze, tak tenkrát bylo PHP ve verzi 4. Kromě toho, že ten jazyk pořádně neuměl typy a měl divně navržené argumenty metod, tak hlavně trpěl vysokou absencí podpůrných knihoven a frameworků.
Tenkrát se často přistupovalo k řešení, které je sexy pro každého vývojáře. Napsat si vlastní framework! Kdo to tenkrát nedělal, tak buď byl dostatečně chytrý, že věděl, že to není dobrý nápad a nebo nepsal v PHP nic velkého.

Já jsem tenkrát pracoval na informačním systému, který byl složen hlavně z reportingu. Jako každý správný reporting i ten náš obsahoval "sofistikovaný datagrid". Cesta byla jasná. Napsat si vlastní knihovnu na datagrid.
Jenže tím tato cesta nekončí. Kde mám podporu pro psaní dotazů do databáze? Kde mám podporu pro routing? Kde mám podporu pro customizaci datagridu? No, zkráceně řečeno, skončil jsem s nečím, co jsem si dovolil nazvat "framework".

I když to byla zajímavá práce a částečně nakonec ušetřila vývoj, tak nakonec skončila v propadlišti dějin. Tím důvodem nebylo nic jiného, než to, že jsem daný framework nikdy nedal jako opensource. Kdybych to tenkrát udělal, možná by se to setkalo úspěchem, možná ne, ale každopádně to byla chyba.

Jako ponaučení jsem si nakonec odnesl jednu důležitou věc:
"Pokud svou knihovnu nedám k dispozici jako opensource, vždy skončí v propadlišti dějin."

Ponaučen tím, že psát si vlastní framework je jedna z nejtěžších disciplín, jsem si řekl, že touto cestou již nikdy nepůjdu. Jak já se mýlil...

V době, kdy jsem již několik let byl v pozici Java programátora, tak jsem se často setkával s tím, že na každý problém existuje nějaké řešení. V tom je Java dodnes skvělá. Na server straně často najdete dostatek knihoven, které vám pomůžou řešit váš problém. Jenže bylo několik oblastí, kde J2EE stále pokulhávalo.

Jednou z oblastí bylo, že v případě, že jste použili prvotní verze Glassfish a EJB, tak se vám težko psali integrační testy jinak, než že jste museli celou aplikaci nasadit na server a tam testy spustit. Tedy z testů se stala spíše noční můra.
Jenže to bych nebyl já, abych si zase nehledal svou cestu. Tou cestou bylo řešení v podobě vlastního kontejneru.
O co šlo? Když jsem se zamyslel nad tím, co má aplikace na serveru dělá a jak funguje, řekl jsem si, že vlastně to není nic složitého. Přečte anotaci @EJB (v té době skvělá novinka) a kontejner zařizuje dependency injection. K tomu bylo pár dalších vlastností jako je scope, transakce či JNDI.
A jelikož Glassfish neměl embedded režim, tak jsem si řekl, že si to napíšu.
Nakonec jsem skončil s knihovnou, která pomocí Java reflexe přečetla dané anotace. Jinak řečeno, napsal jsem si vlastní DI kontejner :)
Řešení bylo efektivní. Testy startovaly rychle a já mohl nějaké ty testy napsat.
Jenže opět jsem selhal v tom, že jsem si dané řešení nechal jen pro sebe a navíc jsem se pořádně nesnažil použít jiné alternativy.
Po nějaké době jsem nakonec zjistil, že Glassfish vydal embedded variantu a celé mé řešení vlastně bylo k ničemu.

Jako ponaučení jsem si nakonec odnesl následující:
"Než začnu psát vlastní řešení, pořádně se podívám, zda neexistuje jiná alternativa. A i za cenu drobných ztrát to vždy bude lepší varianta."

V následujících letech jsem od takto "kreativních" činností začal upouštět. Jednak to bylo tím, že jsem spíše putoval po projektech, kde jsem vždy byl pouze dočasně a jednak také proto, že jsem byl přesvědčen, že na to, aby člověk vytvořil skutečně dobrou knihovnu, nestačí jí jen napsat, ale také nekonečně podporovat.

S čím jsem se ovšem setkal, byla naprostá zrůdnost v podobě šílených interních legacy knihoven a frameworků. Vždy jsem si představoval, jak něco takového asi vzniklo. Mám pocit, že často stejně jako v mém případě. Je prostě "sexy" si napsat vlastní framework. Jsem chytrejší než zbytek světa, tak si napíšu vlastní knihovnu, která bude dokonalá, obsáhne celý problém, pohltí celý svět a bude jednou stát na piedestalu: "Nejlepší framework světa".

Jenže, co se ve výsledku stane? Takováto interní knihovnička se časem stane největší brzdou celého vývoje. Původní autor už dávno odešel, dokumentace je neaktuální a bugy se řeší workaroundy, místo aby došlo k nápravě.

Nedílnou součástí takovéhle knihovny či frameworku je šílená enterprise struktura, kde jeden objekt má cca tisíc abstraktních předků a to právě kvůli tomu, že je to prostě "cool".

Použití takovéhle knihovny pak často vyžaduje mít IQ alespoň 350 a 10 letou praxi.

Pokud jsem psal, že tvorba knihoven a frameworků je jedna z nejsložitějších disciplín v programování, tak s tím souvisí i určitá seniorita v oboru. Prvním znakem je třeba to, že na místo dědičnosti je vždy lepší upřednostňovat kompozici. Důvodem je totiž fakt, že dědičnost v OOP je předpovídání budoucnosti. To už je menší šarlatánství čtení budoucnosti z ruky.

Jako ponaučení jsem si nakonec odnesl následující:
"Vytvořit univerzální řešení je iluze. Ze stříbrné kulky se nakonec stává zlomená lopata."

Další zajimavou disciplínou je něco, co jsem si interně nazval jako "obalování". Obalování je činnost, kdy vlastně neděláte nic jiného, než že stalé dokola přepisujete API nějaké knihovny do vlastních metod.
Důvod, proč někdo něco takového dělá je často způsobeno tím, že je přesvědčen, že jeho obalující API je lepší než to, které nabízí knihovna. Jenže to není tak úplně pravda. Je mnohem větší pravděpodobnost, že ono "obalování" vývojáři dělají z toho důvodu, že si pak lépe pamatují co ono API dělá. Někdy je totiž časově náročné se dané API učit.

A opět jedno ponaučení:
"Místo obalování API svým vlastním je lepší věnovat čas existujícímu API a to využít na max. Je totiž velká pravděpodobnost, že píšete kód, který je ve výsledku úplně k ničemu."

Když jsem přemýšlel, jak z toho nakonec ven, tak jsem si uvědomil, že to nelze. Nelze to už jen z toho důvodu, že programátory často pohání ona touha po tvorbě dokonalé knihovny. Myslet si, že v budoucnu bude situace jiná je dost lichá. Důvodem není nic jiného, než to, že jsme jen lidé a ti jsou často odsouzeni k opakovaným chybám.

Abych se v budoucnu vyhnul tomu, že bych někdy zklouznul k opětovné tvorbě "vlastního frameworku", tak jsem si vytvořil několik pravidel, které mě vždy dost jasně zastaví:

  1. Pokud vytvořím univerzální knihovnu či framework, vždy musí být opensource.
  2. Musím vytvořit srozumitelnou dokumentaci
  3. Musím knihovnu stále udržovat a je velká pravděpodobnost, že na to budu sám
  4. Kód musí obsahovat testy
  5. Testy musí být udržované
  6. Pokud má knihovna nezíská další konzumenty, je odsouzena k zániku
  7. Hlášené chyby musím opravovat ASAP
  8. Kód dané knihovny musí mít vysokou kvalitu
  9. Musím knihovnu správně verzovat a umět opravovat chyby i ve starších verzích
  10. Napsat vlastní knihovnu či framework je extrémně pracné a nakonec přináší spoustu problémů

Závěr

"Chceš napsat vlastní knihovnu či framework? OK. Vytvoř kód, dej ho na GitHub a až bude mít alespoň 1000 stars, tak se vrať a řekni, že máš knihovnu či framework."



neděle 10. prosince 2017

Vývoj aplikací přes Docker

V dnešní době asi neexistuje vývojář, který by někdy neslyšel o Dockeru. O technologii, která se v posledních letech stala hlavním prostředkem pro moderní vývoj aplikací.

Co je to Docker?

Docker je kontejner, ve kterém běží vaše aplikace. V konečném stavu se jedná o linuxový virtuální server, který obsahuje vše potřebné, co jste si sami definovali. Představte si, že máte Node.js aplikaci, která běží na portu 8080. Takovouto aplikaci můžete velice jednoduše převézt do Dockeru a tu poté nasadit v Cloudu.

Než se pustíme do samotných příkladů, pojďme si nejdříve říci, proč bychom něco takového, jako je Docker vlastně měli chtít.

Proč Docker?

Jednotné prostředí

Vývoj aplikací je často tvořen z několika fází. První fází bude development prostředí, které je určené pro vývojáře. Po určité době se aplikace dostane do stavu testování, kde se provede akceptace, zda je onen softwarový produkt určen pro produkci. Poté dojde k nasazení na prostředí simulující produkci, tedy otestuje se vůči produkčním datům. A nakonec zde máme produkci.

Když to sečteme, zjistíme, že naše aplikace neběží jen na jednom prostředí, ale naopak jsme nuceni, abychom aplikaci jednoduše rozeběhli na více serverech. Každé prostředí by mělo být jednotné a právě s tím nám Docker může pomoci. Díky Dockeru můžeme totiž zajistit, že ono prostředí bude vždy Debian ve verzi x.y, s Node.js ve verzi 8.1 apod.

Lokální vývoj

Samotnou aplikaci musí někdo někde napsat. K tomu nám samozřejmě slouží naše vlastní počítače. A zde je kámen úrazu. Pokud nepoužijeme Docker, tak aplikaci píšeme v prostředí, které je velice často vzdálené produkčnímu prostředí.

Představme si situaci, kdy Pepa používá Windows, Franta má Linux a Jarda jede na MacOS. V takovém případě musíme někam sepsat požadavky typu: "musíte mít Node.js verzi 8.2, musíte mít nainstalovánu technologii A, B, C,....". V případě Dockeru nemusíte. Nemusíte řešit to, kdo jaký má operační systém, kdo jakou má verzi Node.js, apod. A už vůbec nemusíte své vývojáře nutit, aby používali prostředí, které je jim nepřirozené či je složité na nastavení.

Horizontální škálování

Samotná technologie Docker Vám sice neumožňuje používat škálování, ale díky Dockeru se ke škálování můžete snadněji dostat. K tomuto účelu zde existují technologie jako je Kubernetes, Swarm či Mesos. Jedná se o technologie, které běží nad Vašimi dockery a orchestruje jednotlivé kontejnery.

Snažsí nasazení

Pokud přejdeme na myšlenku Dockeru, který říká: "Nasazuj aplikace z předem připravených kontejnerů", tak nám tím současné říká: "Nestarejte se tolik o to, jak konfigurovat server". S tím práve souvisí i to, jak aplikaci nakonec nasadit. V případě, že máte vlastní virtuální či fyzický server, často se z deploymentu stává černá díra, protože nasadit aplikaci umí jen infra tým, popřípadě ten nejšikovnější programátor, který je i administrátorem. Nasazení pomocí Dockeru totiž nakonec může znamenat, že postup se zredukuje na 1 až 2 řádky kódu (docker registr -> server). Většina Cloudových řešení, jako je Azure, AWS či Google Cloud, Vám totiž nabízí možnost, jak jednoduše takové nasazení z Docker image provést.

Node.js a Docker

Nyní se již pojďme podívat na ukázku, jak použít Docker při vývoji Node.js aplikací.

Pro následující ukázky budete potřebovat:
  1. Node.js 8 a vyšší (pro inicializaci projektu, v budoucnu se nejedná o běhové prostředí aplikace)
  2. Docker
  3. Editor kódu (WebStorm, Atom, Visual Studio Code, apod)
Představme si, že máme jednoduchou Node.js aplikaci, která na portu 8080 vrací "Hello World". Jednoduchý návod na vytvoření takove aplikace by byl následující:

Inicizalizace projektu:
npm init

Instalace závislostí:
npm install express

Soubor src/index.js:
const express = require('express');
const app = express();

app.get('/', (req, res) => {
    res.send('Hello world!');
});

app.listen(8080, () => {
    console.log('Running on http://localhost:8080');
});

Aplikaci bychom spustili následujícím příkazem:
node src/index.js

V prohlížeči by nyní na adrese http://localhost:8080 měla běžet naše aplikace. Nyní aplikaci zastavme a pojďme ji převést do Dockeru.

Dockerizování

V kořenovém adresáři projektu vytvořte soubor Dockerfile:
FROM node:carbon

MAINTAINER Ales Dostal <a.dostal@apitree.cz>

# Create app directory
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app

# Install app dependencies
COPY package.json /usr/src/app/
RUN npm install

# Bundle app source
COPY . /usr/src/app

EXPOSE 8080
CMD ["node", "src/index.js"]

Na prvním řádku definuje image, ze kterého budeme vycházet. V našem případě to je node:carbon, což je Node.js verze 8.x. Pokud chcete definovat přesnou verzi Node.js můžete použít například node:8.9.3. Pokud nevíte, jake obrazy jsou k dispozici, není nic jednoduššího, než se podívat na Docker Hub.

Klauzule MAINTAINER je pouze informativní, nicméně je jistě dobré jí definovat, aby samotný image obsahoval metadata o tom, kdo daný image vytvořil a kdo je jeho vlastníkem.

Poté již konkrétně definujeme, co náš Docker image obsahuje. Nejprve vytvoříme adresář /usr/src/app a ten nastavíme jako výchozí adresář. Výchozí při běhu Dockeru.

Dále do výchozího adresáře překopírujeme package.json a spustíme instalaci závislostí.

Posledním příkazem, který se spouští při vytváření Docker image, je překopírování celého projektu do výchozího adresáře.

Na konci je ještě definování portu, který bude z běžícího Dockeru viditelný a také příkaz, který se spustí vždy, když Docker image spustíme. V tom je hlavní rozdíl mezi klauzulí RUN a CMD. V našem případě je to příkaz node src/index.js.

Poslední částí je vytvoření souboru .dockerignore:
node_modules

Do tohoto souboru definujeme adresáře a soubory, které se mají při vytváření docker image ignorovat. V našem případě je to například node_modules, protože tento adresář, který obsahuje závislosti, si docker vytvoří sám (díky RUN npm install).

Vytvoření Docker image

Nyní již nic nebrání tomu, vytvořit si na lokálním stroji vlastní docker image. K tomuto účelu stačí spustit tento příkaz:
docker build -t apitreecz/testapp .

Pozor, nezapoměnte na konci na onu tečku. Jde o to, že tento příkaz spouštíme v kořenovém adresáři projektu a tou tečkou určujeme adresář, kde příkaz docker bude hledat projekt a hlavně soubor Dockerfile a .dockerignore.

Samotný příkaz začně stahovat image node:carbon a současně spustí příkazy jako npm install a kopírování projektu do /usr/src/app (samozřejmě v Dockeru, nikoli ve vašem stroji).

Poté, co příkaz doběhne, měl by napsat něco jako:
Successfully built 46aaa661cef4
Successfully tagged apitreecz/testapp:latest

Abychom se přesvědčili, že náš docker image skutečně existuje, stačí se podívat pomocí příkazu:
docker images

Výstup by měl vypadat nějak takto:
REPOSITORY                    TAG                 IMAGE ID            CREATED             SIZE
apitreecz/testapp             latest              46aaa661cef4        2 minutes ago       751MB

Díky tomu máme ve svém lokálním repozitáři vytvořen Docker image, který stačí jen spustit.

Spuštění Docker image

Pro spuštění docker image stačí následující příkaz:
docker run -p 8080:8080 -d apitreecz/testapp

Pokud jste vše udělali správně, měla by Vaše aplikace běžet na portu 8080. Takže nezbývá, než v browseru vyzkoušet spustit http://localhost:8080.

Abyste byli schopni vůbec sledovat, co se s Vaší aplikací v Dockeru děje, pojďme se podívat na pár příkazů, které Vám můžou pomoci.

Výpis běžících docker kontejnerů:
docker ps

Výstup by měl vypadat nějak takto:
CONTAINER ID        IMAGE               COMMAND               CREATED             STATUS              PORTS
e9321555c9cc        apitreecz/testapp   "node src/index.js"   6 seconds ago       Up 2 seconds        0.0.0.0:8080->8080/tcp

V této chvíli je pro nás duležitý CONTAINER_ID, kterým se daný bežící Docker kontejner identifikuje.

Logy Docker kontejneru:
docker logs <container id>

Vstup do konzole Docker kontejneru:
docker exec -it <container id> /bin/bash

Zastavení Docker kontejneru:
docker stop <container id>

Pokud jste se úspěšně dostali až sem, dá se říci, že máte první krok k "dokerizaci" za sebou. Naučili jsme se, jak vytvořit Docker image, jak daný Docker image spustit, spravovat a nakonec jak ho i zastavit.

Nyní se pojďme podívat na další bod a tím je vývoj vůči Docker kontejneru.

Vývoj na běžícím Docker kontejneru

Jistě jste si všimli, že pokud bychom chtěli vyvíjet aplikaci pomocí Dockeru, tak současný postup by pro nás znamenal: zastavit kontejner, napsat kód, vytvořit kontejner, spustit kontejner. Tento postup by byl samozřejmě možný, ale zároveň i dost zdlouhavý. Proto si pojďme udělat ukázku, jak toto vyřešit elegantnějším zpusobem.

Docker a Next.js

Pro účely ukázky použiji Next.js, který umožňuje hot reload, díky kterému si lépe můžeme ukázat, jak vyvíjet na běžícím docker kontejneru, který bude reagovat na naše změny.

Prvním krokem bude vytvoření jednoduché Next.js aplikace.

Nejprve vytvoříme adresář nextjsdemo a do něj přejdeme:
mkdir nextjsdemo
cd nextjsdemo

Dále provedeme inicializaci pomocí npm:
npm init

Po odklikání všech otázek se nám v projektu vytvoří package.json.

Dále nainstalujeme potřebné závislosti:
npm install --save next react react-dom

Nyní vytvoříme soubor pages/index.js:
export default () => <div>Hello from Next.js!</div>

Poslední částí je zápis scripts do package.json:
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start"
  }

Teď už nic nebrání tomu, abychom si svojí aplikaci zkusili spustit:
npm run dev

Na portu 3000 poběží naše aplikace. Pro test stačí v browseru spustit: http://localhost:3000. Pokud jste vše udělali správně, měli byste vidět webovou stránku s "Hello from Next.js!". Při přepsání textu v pages/index.js, můžete vidět, že díky Next.js máte k dispozici hot reload a hned v browseru vidíte samotnou změnu. Aplikaci zastavte a pojďme ji "dokerizovat".

Nyní vytvoříme Dockerfile, který nám opět bude definovat docker image:
FROM node:carbon

MAINTAINER Ales Dostal <a.dostal@apitree.cz>

# Create app directory
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app

# Install app dependencies
COPY package.json /usr/src/app/
RUN npm install

# Bundle app source
COPY . /usr/src/app

RUN cd /usr/src/app && npm run build

EXPOSE 3000
CMD ["npm", "start"]

Jedná se o velice podobný soubor, který jsme tvořili v první ukázce, pouze zde přidáváme build samotné aplikace a port, na kterém aplikace bude vystupovat je 3000. Samozřejmě port lze změnit i na 8080, pouze bychom doplnili parametr k npm start, na kterém portu se má Next.js aplikace spustit.

Dále opět vytvoříme také soubor .dockerignore:
.next
node_modules

V .dockerignore opět definujeme všechny adresáře a soubory, které nechceme do docker image kopírovat. Tím, že jsme si zkoušeli aplikaci již pustit, můžete si všimnout, že nám Next.js vytvořil adresář .next, který ale z lokálního stroje nechceme kopírovat, protože si ho image vytvoří sám.

Opět vytvoříme docker image:
docker build -t apitreecz/nextjsdemo .

Pro kontrolu si můžeme zkusit náš docker spustit:
docker run -p 8080:3000 -d apitreecz/nextjsdemo

Pokud jste vše udělali správně, měli byste ve webovém prohlížeči, na adrese http://localhost:8080 vidět naší aplikaci. Zde si můžete všimnout, že i když aplikace v Docker kontejneru běží na portu 3000, my jsme jí přemapovali na port 8080 pro náš lokální stroj.

Nicméně nyní jsme ve stavu, kdy nám kontejner spustil aplikaci v produkčním módu (díky npm start) a zároveň nemáme možnost docker image aktualizovat.

Abychom byli schopni za běhu náš běžící Docker kontejner aktualizovat, použijeme k tomu Docker Compose. Nejprve zastavte původní Docker kontejner a pojďme se podívat jak na to.

V root adresáři vytvoříme soubor docker-compose.yml:
version: "2"
services:
  webapp:
    build: .
    command: npm run dev
    ports:
      - "8080:3000"
    volumes:
      - .:/usr/src/app

A nyní spustíme příkaz:
docker-compose up --build

Tento příkaz nám podle Dockerfile a docker-compose.yml nám vytvoří a spustí Docker image, která přepisuje původní chování a tím je spuštění npm start na npm run dev. Současně s tím nám reaguje na změny stejně, jako v případě spuštění z lokálního stroje.

Nyní v browseru stačí přejít znovu na stránku http://localhost:8080, kde byste měli vidět bežící aplikaci. Současně s tím, pokud nyní změníte text v pages/index.js, uvidíte, že Vaše bežící aplikace na toto zareaguje a dojde k hot reloadu.

Výhodou Docker Compose je hlavně v tom, že nemusíte přepisovat Dockerfile pro lokální vývoj vs produkční běh. Docker Compose vám přepíše původní konfiguraci a tím pádem máte k dispozici jak produkční Docker image, tak image pro lokální vývoj.

Závěr

O Dockeru by se dalo napsat spoustu věcí. Jelikož se článek rozrostl, tak jsem nucen ho rozdělit na dvě části. Příště si ukážeme, jak náš Docker image uložit do Docker Hubu a současně, jak ho poté nasadit v Cloudu (Google Cloud, Azure).

I když Docker přidává další abstrakci Vaší aplikaci, tak věřte, že ona abstrakce je nakonec výhodná, protože Vás odstiňuje od nutnosti unifikovat běžící prostředí složitou konfigurací serverů a lokálních strojů.

DevOps a Release management

Release management je proces, který je dnes přímo spojen s věcmi jako je Continuous Integration, Continuous Delivery (ve zkratce CI/CD), G...