Programio

Jak vypadá naše vývojové prostředí

Někdy před rokem jsme se rozhodli – snad finálně! – vyřešit problém, že i když používáme multiplatformní technologie jako je Node.JS, tak ve výsledku má každý vývojář mírně odlišný systém, což způsobuje, že kód, který je funkční na jednom počítači, není funkční na jiném. Cestou se na naše řešení nabalila i jiná pozitiva a o tom všem si v článku povíme.

Před rokem trpělo naše vývojové prostředí přibližně těmito neduhy:

  • Stáhli jste si zdrojáky webového UI, rozjeli jste ho, ale nefungovalo vám, protože jste si zapomněli ručně stáhnout jednu chybějící knihovnu.

  • Po stažení se sice UI rozběhlo, ale moc toho nefungovalo, protože jste zapomněli spustit backendový server.

  • Spustili jste backend, ale nefungovaly statistiky, protože jste u sebe měli starou verzi backendu.

  • Napsali jste nějaký kód, vyzkoušeli jste ho, otestovali, commitli&pushli a po chvíli vám přišel email z Jenkinsu, že neprošli unit testy, protože máte lokálně jinou verzi testovacího frameworku než je na Jenskinsu.

Podobných chyb si jistě dokážete představit stovky.

Docker to the rescue!

Docker asi znáte, je teď kolem něj docela hype. Je to něco trochu podobného jako virtualizovaný OS. Pomocí Dockeru můžeme vytvořit kontejner, což je jakýsi sandbox, který je co možná nejvíce odstíněný od hostitelského OS. Tento kontejner si můžeme různě přizpůsobit a nainstalovat tam své aplikace. Z pohledu uživatele se pak Docker kontejner jeví jako samostatný operační systém se svým file systémem a se svými procesy, ale fakticky v rámci Dockeru žádný virtualizovaný OS neběží a vše zkrátka vykonává hostitelský OS.

Můžeme proto na jednom systému spustit třeba deset kontejnerů, v pěti můžeme provozovat Node.JS aplikace, v dalších třech Java aplikace a ve zbývajících můžeme mít třeba nějaký monitoring. Každý kontejner bude jinak nakonfigurovaný, budou tam běžet jiné procesy a přitom nemusíme virtualizovat deset OS. Hlavní výhoda je v úspoře prostředků. Představte si, že na jednom počítači skutečně virtualizujete deset OS.

Námi v současnosti vyvíjený produkt se skládá z několika desítek Node.JS projektů. Některé z nich jsou HTTP servery, další jsou ZeroMQ servery a zbytek jsou sdílené knihovny. Nevím, jestli se vlezeme do definice microservices, nicméně platí, že při běžném vývoji nám běží několik desítek Docker kontejnerů.

Tím, že nám všechny aplikace běží v kontejnerech, získáváme jednotné prostředí pro všechny vývojáře. Už žádné rozdílné verze testovacího frameworku – pro testování používáme framework, který je nainstalovaný v kontejneru. Aplikace se spouští v Nodu, který je opět nainstalovaný v kontejneru. Všechny tyto závislosti se jednou nastaví v konfiguraci daného kontejneru a programátoři už do toho nehrabou.

Veškeré externí závislosti, které se nějak využívají pro běh našich aplikací, máme vždy ve stejných verzích, takže pokud se naše aplikace chová na dvou různých počítačích jinak, obvykle to znamená, že si někdo nesynchronizoval změny v kódu.

No jo, jenže Docker zatím běží pouze na Linuxu a my máme v týmu i Windowsáře a Macaře. Co s tím?

Vagrant to the rescue!

Vagrant je jakýsi wrapper nad virtualizačními programy jako je VirtualBox nebo VMware apod. Umožňuje nám pomocí jednoduchého konfiguračního souboru nakonfigurovat běžící virtualizovaný OS. Vagrant jsme proto využili tak, že v něm virtualizujeme CentOS, ve kterém spouštíme naše Docker kontejnery. Ráno zkrátka přijdeme do práce, spustíme Vagrant, v něm se spustí kontejnery a jedeme. Všem vývojářům, nehledě na OS, běží všechny aplikace v identickém prostředí.

Další příjemný bonus je jednoduchost prvotní instalace. Pokud přijde do týmu nový člověk, jako první si nainstaluje samotný Vagrant, pak si stáhne konfigurační soubory z repozitáře definující naše vývojové prostředí a jedním příkazem vagrant install nainstaluje všechny aplikace potřebné pro vývoj současného produktu. Druhým příkazem vagrant up pak prostředí spustí. To je celé.

Pokud se během toho procesu nic nepokazí, má někdy po dvou hodinách funkční a běžící aplikaci. Může si v prohlížeči zobrazit naše UI psané v Angularu, tam si může zkusit založit novou kampaň, což odchytí backend – HTTP server, který přes ZeroMQ dá vědět ostatním částem systému o nové kampani. Ty přijmou zprávu a aktualizují nějaké údaje v Mongu, které nám také běží v jednom z kontejnerů. A tak dále.

Protože máme v Dockeru nainstalované i Mongo, což je naše hlavní databáze, má každý vývojář na svém počítači úplnou a nezávislou instalaci celého systému. Pokud například v UI naklikám novou kampaň, data o ní bude mít v Mongu jen ten daný vývojář a nebude tím otravovat ostatní.

Stejně snadno lze psát i integrační testy. Po restartu celého prostředí se všechny kontejnery dostanou do původního stavu, takže můžeme snadno napsat nějaké scénáře a otestovat, zda vše proběhlo jak mělo. Tyto integrační testy si pak může pustit kterýkoliv vývojář, když si například chce ověřit, že novou fíčurou nezničil nějakou starou.

A co statistiky?

Z předchozích článků víte, že pro statistiky používáme hromadu různých služeb, které jsou navíc napsané v Javě, takže jsou to docela žrouty paměti. Nakonec se ukázalo, že to zase tak velký problém nebyl a zprovoznili jsme všechny části našich statistik přes Docker kontejnery, takže je opravdu možné provozovat celý náš systém na svém počítači bez toho, aby jakákoliv součást byla sdílená. Stručně k jednotlivým službám:

  • Kafce jsme pouze nastavili, aby nežrala tolik paměti přes -Xmx a -Xms parametry a všechny topicy vytváříme pro jednoduchost pouze s jednou partition a bez replikací.

  • Samzy jsme se báli nejvíce, protože jsme standardně pouštěli přes Hadoop a že by se nám do Vagrantu chtělo instalovat ještě Hadoop, to se říci nedalo. Nicméně Samza lze pro testovací účely spustit jako obyčejný Java proces, což jsme využili a spouštíme ji právě tak.

  • Druid potřebuje k běhu hodně procesů a nějaké ty externí závislosti. Nakonec jsme ale žádnou závislost navíc neinstalovali. Druid je robustní, takže pokud nějaká služba neběží, Druid se snaží stále fungovat dále v omezených podmínkách. To nám pro testovací účely stačí. Segmetny lze buď neukládat, nebo je lze ukládat na lokální disk.

V kontejnerech nám tedy běží light-weight verze statistik, která se ale chová identicky jako na ostrých serverech, což je přesně to, co potřebujeme.

Další vychytávky

  • Všechny Node.JS aplikace se spouští pod supervisorem, který sleduje změny ve zdrojácích a při každé změně restartuje danou službu.

  • Jak jsem psal výše, celý náš systém se skládá z několika desítek běžících aplikací či knihoven. Jedním z problémů je, jak zařídit, aby se změna v jedné knihovně automaticky projevila ve všech aplikacích, které tuto knihovnu používají. V kontejnerech máme projekty nainstalované a prolinkované tak, aby se přesně toto dělo.

  • Protože díky Vagrantu máme všichni všechny zdrojáky na stejném místě, mohli jsme snadno napsat skripty, které třeba kontrolují, jestli jsme něco nezapomněli commitnout, které umí stáhnout všechny změny z repozitářů nebo které umí spustit unit testy pro danou aplikaci, nehledě na to, jestli je to webová aplikace, nebo serverová. Protože se tyto shell skripty vykonávají uvnitř Vagrantu, automaticky fungují všem, nehledě na OS.

  • Díky Dockeru si můžeme snadno zobrazit logy z každé aplikace, tail-efnout se aktuální logy nebo restartovat kontejner, pokud je zrovna nějaký nemocný.

Na jaké problémy jsme narazili

  • Dlouhodobě jsme měli největší problémy s DNS. Všechny naše projekty běží na nějaké doméně, takže třeba Mongo běží na mongo.adserver.dev, Ad Selector běží na adselector.adserver.dev a podobně. Jenomže aby to fungovalo, potřebujeme něco, co bude dané domény překládat. K tomu jsme používali třeba SkyDNS, což sice fungovalo, ale občas se stávalo, že SkyDNS nastartoval později než bylo potřeba a na několik prvních dotazů zkrátka nestihl odpovědět. Částečně to bylo způsobeno i vysokým zatížením systému, viz další bod.

  • Občas jsou problémy s výkonem, hlavně kvůli všem těm watcherům, které sledují zdrojáky. To jsme ze začátku dlouho ladili, než se to vyladilo k plné spokojenosti všech. Na Linuxech jsme nakonec skončili u NFS, se kterým je vše nejrychlejší a nevytěžuje to systém. Při běžném provozu mi celý Vagrant vytěžuje asi 40 % jednoho CPU a přibližně 4 GB RAM.

  • Nebylo úplně jednoduché zařídit, aby se ty desítky kontejnerů spustily ve správném pořadí, ve správném čase, až když běží jiná služba apod.

  • Přesměrování portů. Pokud nějaká aplikace běží v Dockeru a běží na nějakém portu, musíme zařídit, aby se ostatní mohli na tento port dostat. Chceme-li, ať se vidí jednotlivé kontejnery mezi sebou, stačí to uvést v Dockerfilu. My ale občas chceme poslat ze svého lokálního počítače dotaz na nějaký server běžící v kontejneru. Proto musí ještě nastavit, ať je port viditelný i z venku, ne jen z jiného kontejneru. No jo, jenže „z venku“ znamená ve Vagrantu, protože Docker kontejner běží ve Vagrantu. Proto musíme port přesměrovat ještě jednou, mezi naším lokálním strojem a Vagrantem. Jo, je to přesně takový chaos, jaký z tohoto odstavce máte pocit.

  • Co se týče operačních systémů, nejhorší zkušenosti máme s OS X, nejlepší s Linuxem. Windows je tak nějak mezi.