- Praktický úvod do Docker a kontejnerů (1) – od instalace po první kontejnery
- Praktický úvod do Docker a kontejnerů (2) – propojování
- Praktický úvod do Docker a kontejnerů (3) – víc najednou aneb něco užitečného s Docker Compose
- Praktický úvod do Docker a kontejnerů (4) – jak z nich získat maximum
- Praktický úvod do Docker a kontejnerů (5) – Docker Machine
- Praktický úvod do Docker a kontejnerů (6) – cluster hostitelů s Docker Swarm
- Praktický úvod do Docker a kontejnerů (7) – scheduler v Docker Swarm
- Praktický úvod do Docker a kontejnerů (8) – váš vlastní registr obrazů
- Praktický úvod do Docker a kontejnerů (9) – multi-host networking
- Praktický úvod do Docker a kontejnerů (10) – Windows kontejnery s Docker API
- Praktický úvod do Docker a kontejnerů (11) – Windows kontejnery s PowerShell
- Praktický úvod do Docker a kontejnerů (12) – Windows Hyper-V kontejner
- Praktický úvod do Docker a kontejnerů (13) – Swarm mode, service, balancing, scaling (v1.12)
Jak vytěžit z kontejnerů maximum
Pro dnešní díl jsem slíbil shrnout určitá doporučení pro úspěšné nasazení Docker a kontejnerových technologií obecně. Kontejnery můžete využívat jako VM (do jisté míry), ale jisté je, že je to mentalita, která nevede k maximálním přínosům. Je to jako jezdit na carving lyžích stylem z rovných – jasně že to jde, ale ti sportovci, co nikdy nepřešli na nový styl, nedokázali nové technologie dobře využít (v důsledku čehož už nikdy nevyhráli). Zjednodušeně řečeno – do kontejnerů ideálně patří cloud-native aplikace. Cloud-native aplikace jsou ty, které dodržují 12 factor pravidla (12factor.net). Pojďme aplikovat principy 12factor na kontejnery a trochu je zjednodušit pro rychlé pochopení i těmi jako já, kteří nejsou programátory. Nejde o dogmata, klidně nemusíte souhlasit se vším, ale faktem zůstává, že řada velmi úspěšných aplikací jako je Netflix, Airbnb, Facebook, Twitter nebo Google tyto principy používá – proto myslím, že to není náhoda 🙂
Jedna činnost per kontejner
Možná nejdůležitější pravidlo, které vám ušetří ze začátku spoustu trápení s mentalitou VM použitou v kontejnerech. Snažte se, aby kontejner plnil jeden účel a to se pokud možno projevilo jako jeden proces – třeba MySQL, MongoDB, Node.js aplikace, Python, Go. Ideální jsou tady self-contained prostředky, ale o tom později. Spouštět více procesů můžete tak, že v kontejneru provozujete Supervisord – jde to, používá se to, ale čím méně to budete dělat, tím lépe pro vás.
Konfigurace
Nejprve co je myšleno konfigurací – jde o něco, co dělá rozdíly v jednotlivých spuštěních, chcete-li instancích. Typicky jde o jména a hesla, IP nebo doménová jména API, umístění log souborů a tak podobně. Někdy je najdete ve formě konstant přímo jako součást kódu (to je proti 12-factor). Jindy jde o konfigurační soubory různých formátů v různých umístěních (ani to není 12-factor). Ideální je, aby byla konfigurace součástí prostředí, ve kterém aplikace poběží – tedy v environmentálních proměnných. Vezměte také v úvahu, že výsledný runtime je tedy vždy kombinace release (výsledného kódu) a nastavení prostředí. Změna jednoho nebo druhého vede (mentálně vzato) k nové verzi v produkci.
Dependencies
Podle zvoleného programovacího jazyka je váš kód více či méně závislý na jiných modulech, knihovnách apod. Při používání kontejnerů byste nikdy neměli předpokládat, že vaše dependencies tam “někdo dal”. Nepracujte způsobem, kdy poskytnete kód a release notes s předpisem všech návazností a operations nasadí (nebo se o to pokusí) vše potřebné. Přímo vývoj je odpovědný za všechny tyto návaznost a nic nepředpokládá – ani přítomnost curl, ani Java VM, ani Python pip nebo konkrétního modulu. Splnění tohoto předpokladu ve světě VM vede na extenzivní používání nástrojů typu Chef, Puppet, Ansible nebo Salt. Ve světě kontejnerů je tohle o způsobu vytvoření image, tedy v případě Dockeru o Dockerfile.
Self-contained
Navažme na předchozí bod a můžeme ho rozvést. Jedna z možností jak zajistit všechny návaznosti je jejich důsledné explicitní definování při vytváření obrazu. Existují programovací jazyky, které mají možnost snížit počet komponent tím, že všechno zabudují do sebe. Pro mne je nejzářivější příklad nejvíc rostoucí, moderní a fascinující jazyk Go. Ten je kompilovaný a produkuje velmi rychlý kód (nemá daleko od C), ale při tom je nádherně jednoduchý a úsporný (proti Java nebo C vám stačí daleko méně řádků) a kompiluje se neuvěřitelně rychle v řádu vteřin (což se o C nebo Java říci rozhodně nedá – a ta rychlost je taková, že ho lidé používají podobným způsobem, jako Python nebo Ruby – tam můžete zdroják jednoduše spustit, protože je to interpretované, ale v Go ho také můžete spustit a ona před tím proběhne extrémně rychlá kompilace). Jednoduchost a úspornost ala Python, rychlost a kompilace (= spoustu bugů odhalí komplilátor, ne až běh jako u Python) ala C. Ale to jsem odbočil – Go obsahuje i zabudovaný web server a i když přidáte hromadu dalších modulů výsledkem je jeden binární soubor – naprosto ideální pro Docker (který je mimochodem psaný v Go) – jeden soubor se vším a jeden proces. Zabudované webové frameworky (nejen pro GUI, ale i pro REST API) jsou čím dál tím populárnější. Python má Bottle a CherryPy, Go má net/http, Node.JS má Express. Jednodušší webové aplikace a jakékoli REST API tak vyřešíte takhle – bez nutnosti integrovat váš kód s Apache nebo Tomcat. Python samozřejmě nevede na jeden binární soubor, ale čím méně komponent je potřeba pro běh mikroslužby, tím lépe.
Backend služby
Vaše aplikace určitě používá některé backend služby – databázi, messaging službu, object store, cache, email nebo SMS službu a tak podobně. Základní doporučené pravidlo kontejnerů, PaaS a 12-factor je důsledně toto všechno chápat jako externí služby. Není to součást kódu a nemá to běžet ve stejném kontejneru. Potřebujete cache? Někde venku (= v jiném kontejneru) nechť běží Redis a vaše aplikace ho bude využívat, k čemuž potřebujete vědět jen IP (nebo doménové jméno) a případně nějaké credentials – mimochodem to je konfigurace a jak už padlo, tuto zprostředkujte environmentální proměnnou. Upozorňuji, že i zapsání souboru do filesystému má být externí služba!
Stateless, share nothing
12-factor říká, že služby nesmí mít state. Vztaženo na kontejnery a rozvedeno do lidštiny – uvnitř kontejneru s nějakou částí aplikace nikdy nesmí být nic, co není někde jinde. Nikdy nesmíte mít důvod zálohovat kontejner a v jeho paměti nemá být nic unikátního. Cloud-native aplikace se nezálohuje z pohledu VM nebo kontejneru, pouze třeba exportujete a archivujete logy nebo data v databázi, ale image VM nebo kontejneru není nijak zajímavá. Tohle je ideální skutečně dotáhnout do konce, takže například webová aplikace může mít nákupní košík – to, jaké zboží si dáte do košíku (= session informace) nesmí být v paměti webového nodu, ale ve datové vrstvě (typicky v Redis clusteru), tedy mimo samotný web server (tzn. v load-balanceru nepotřebujete, vlastně ani nechcete sticky session aneb session persistence!). Současně doba pro provedení nějaké operace by měla být co nejkratší (jde například o čas mezi kliknutím na přidání zboží do košíku a uložením tohoto stavu v Redis – v této krátké době je totiž v nodu state, který není jinde v backend, což by sice měla HTML5 aplikace řešit přes retry, protože u ní ten state stále je, ale – čím kratší dobu to trvá, tím menší je šance, že node odumře zrovna uprostřed této operace a tím lépe pro nás, protože retry chceme co nejméně). Proč je to důležité?
Chcete mít možnost kdykoli za běhu přidat nebo ubrat kontejnery, “přesouvat” zátěž z jednoho DC do druhého tím, že vlevo jich pár zabijete a vpravo nahodíte pár nových (přesně tak – vMotion nemá v cloud-native světě vůbec co dělat). Chcete vědět, že havárie nodu není žádný problém jednoduše proto, že havárie je častá a normální (slyšeli jste o NetFlix a jejich Chaos Monkey? Aby se zaručilo, že vývojáři všechny tyto koncepty dodržují, NetFlix průběžné náhodně sestřeluje produkční servery – schválně a zákeřně).
Neintegrujte přes databázi
Tohle je výjimka v tomto článku, kde chci říct co ne, spíše než co ano. Možná máte aplikace, které jsou integrované tak, že koukají do stejné databáze. Jedna aplikace něco zapíše, druhá si to přečte. Pro cloud-native, DevOps, Agile a kontejnery je to fatální problém. Co když aplikace potřebuje reflektovat nový svět tam venku a přidat funkce, tedy změnit svůj datový model? Jak přidáte nové klíče a tabulky? Velmi často to vede k tomuto: znamená to buď odstávku nebo minimálně velké riziko, protože v tom vašem Oracle běží úplně celá firma a nesahá se na to (= inovace byla zabita nebo významně zpomalena). Co to udělá s ostatními aplikacemi, nemusíme je přepsat taky (= inovace čekají na ostatní aplikace, takže jsou defacto zabity)?. Už nikdy nezměníte svou databázi. Pro situace, kde potřebujete velmi škálovatelnou a jednoduchou databázi typu NoSQL, využíváte (pro tento účel) podstatně méně efektivní a extrémně drahý Oracle. Vztahy (mezi produkty, zákazníky, v logistice) se snažíte vyřešit relačním systémem (což je pomalé a velmi složité), místo nasazení Graph databáze. Zkrátka – integrace přes databázi je asi ten úplně nejhorší způsob. To už je přeci jen lepší integrovat na aplikační vrstvě přes RPC a jiné ošklivosti (o tom co místo toho v dalším odstavci), pořád lepší, než přes datovou vrstvu.
Loosly coupled microservices
Kontejnery vás vedou k mikroslužbám místo monstrózní monolitické aplikace. Oddělte jednotlivé služby uvnitř své aplikace – práce s uživateli, vyhledávání, upload a zpracování dokumentu či obrázu – to všechno jsou mikroslužby. Roztrháním monolitu na mikroslužby získáte flexibilitu tyto provozovat a upgradovat zcela samostatně, svázat je volně a nepřímo (typicky přes API) a pro každou zvolit klidně jiný programovací jazyk a jinou databázi (každá mikroslužba ma svojí DB a takovou, která sedne na její potřeby – třeba Neo4J pro vztahové záležitosti, Cassandru pro velký výkon a škálu, MongoDB pro jednoduchost a škálu ve čtení a MySQL tam, kde potřebujete ACID). Každá mikroslužba něco nabízí jiným – a jak se ti o ní dozvědí? Ideální je, pokud informaci dostanou do svého prostředí – tedy jsme znovu u env proměnných. Kdo to zařídí? Ideálně PaaS (o tom dále) a pokud nemáte, tak nějaké service discovery (etcd, ZooKeeper, Doozerd). Nejpoužívanější volná návaznost je REST API (typicky přes balancer) nebo messaging služba ve stylu worker nodů jako je RabbitMQ (něco jako když si vyzvedáváte pořadový lísteček na poště a jste obslouženi okýnkem, které se první uvolní).
Startuj rychle, vypínej se slušně
Kontejner by měl být disposable, chceme ho kdykoli nahodit a kdykoli zrušit, takže je ideální, pokud startuje opravdu rychle. V závislosti na složitosti kódu a použitém jazyce to může být pár milisekund nebo pár vteřin, ale čím méně, tím lépe. Čím dřív je kontejner nahoře, tím dříve obsluhuje klienty, což se krásně hodí v autoscaling scénáři. Mimochodem dá se jít až tak daleko, že kontejner se spustí až když ho aplikace potřebuje. Totéž platí pro vypínání, které je u kontejnerů daleko častější, než u VM (scale down, přesun workload na jiný fyzický systém apod.). Kontejner by měl korektně reagovat na SIGTERM. Autoscaling (třeba díky PaaS) kontejner upozorní, že bude končit a dá mu pár vteřin na to se rozloučit s blízkými. Měl by tedy dokončit na čem pracuje (PaaS mu v ten okamžik už nové požadavky neposílá) a v případě worker node odpovědět NACK do RabbitMQ (takže úkol, na kterém dělal, si převezme někdo jiný).
Centralizujte logy
Vzpomínáte, jak jsem říkal, že v kontejneru nemá být žádný state, který není jinde? Pokud necháme logování uvnitř, tak jsme toto pravidlo porušili. Měli bychom centrálním způsobem agregovat logy z kontejnerů za běhu, protože třeba už za pár vteřin kontejner i jeho lokální logy nebudou existovat. Docker na úrovni jednoho nodu tuto vlastnost má a orchestrační technologie (Kubernetes, Swarn, Mosos) nebo ještě lépe PaaS (Cloud Foundry, OpenShift) tohle nabízí. Všechny tyto agregované logy je také vhodné spojit s logy z všeho dalšího a globálně agregovat a analyzovat úplně všechno. Tady vám moc rád doporučím ArcSight (ale existují i další možnosti jako je Splunk, ELK nebo Papertrail).
Chcete v kontejnerech uspět? Použijte Platform-as-a-Service
Některá uvedená doporučení jsou o způsobu práce a přemýšlení, tedy řekněme o metodice a procesech. Mnoho jiných je ale ideální doplnit nějakým nástrojem, který s tím pomůže. Chtěl bych vám velmi doporučit použít rovnou Platform-as-a-Service – místo skládání řešení, které vytvoří cluster hostitelů, síťově propojí kontejnery v různých serverech, hledání způsobu, jak korektně a konzistentně vytvářet kontejnery s aplikačním prostředím dle specifik každé služby, zvolte rovnou balík, kde všechny tyhle věci už jsou. Já preferuji Cloud Foundry s využitím Docker kontejnerů pod kapotou a současně doporučuji integrovat tento systém s některými OpenStack komponentami. Docker ve správě CloudFoudry a běží v jednotlivých VM, kteréhé jsou hostované v OpenStack (takže jednoduše přidáte nebo uberete hosty) a OpenStack přidá do Cloud Foundry plně spravované služby na vyžádání a za vás provádí jejich instalaci, replikaci, konfiguraci apod. (OpenStack Trove pro DBaaS a Cue pro Messaging-as-a-Service).
Přesně takovýmto balíčkem je HP Helion Development Platform, která využívá open source CloudFoundry (a krásné GUI získané akvizicí ActiveState Stackato) a integraci s Helion OpenStack právě pro věci jako je DBaaS. Získáte otevřené řešení, které posune váš vývoj hodně dopředu a stane se vaší živou učebnicí cloud-native světa.
Proč HP Helion Development Platform postavenou na Cloud Foundry?
- Zajistí dodržení všech dependencies a díky Heroku buildpack vytvoří kompletní prostředí s aplikací (image kontejneru)
- Napojí backend služby přes env proměnné ať jsou fyzicky kdekoli
- Díky OpenStack zajistí vytvoření, replikaci i zálohování databáze (DBaaS s OpenStack Trove) nebo message služby (OpenStack Cue)
- PaaS spustí kontejnery a sama si vybere kde (spravuje několik VM, kde je Droplet Execution Engine, tedy primárně Docker)
- Zajišťuje automatický URL routing a load balancing, takže jednoduše přidáte a odeberete nody (kontejnery) bez nutnosti měnit něco v síti nebo v DNS
- Přináší versioning, novou verzi aplikace za běhu nasadíte a jedním tlačítkem se můžete vrátit k předchozí verzi (funguje to tak, že PaaS vytvoří nové kontejnery, pak přehodí routing ze starých na nové a nakonec staré vypne)
- Routing můžete namířit k vícero verzím aplikace pro A/B testování, tedy část přístupů je obslouženo verzí A, část verzí B
- Autoscaling – PaaS může monitorovat například zatížení CPU a při překročení určité hranice zvětšit počet balancovaných nodů (a naopak)
- Sbírání logů ze všech nodů
- Timeline pro historii událostí a komunikaci mezi vývojáři, testery a provozáky