O aplikaci, která nikdy nespadne

posted 04.04.2016

Posledních pár měsíců pracuju na automatizovaném dispečinku našich kurýrů ve foodpandě. Je to první projekt, na kterém jsem se rozhodl opustit náš typický setup s autoškálovanou flotilou EC2 instancí, které jsou spravovány puppetem, a vydal jsem se cestou AWS služeb, které sice dávají méně možností přizpůsobení k obrazu svému, zato přinášejí spolehlivost a jednoduchost.

Způsoby jak rozběhat kód v Amaazonu

EC2, Elastic Beanstalk, ECS a Lambda jsou čtyři produkty, které má Amazon v sekci Compute. Zleva doprava je v nich jistý trend, kterým se Amazon ubírá, postupně se čim dál tím méně zabýváte tím, na jakém serveru aplikace běží.

Čim dal tím méně starostí

Pokud spustíte novou výchozí EC2, najdete prázdnou linuxovou mašinu, kterou musíte sami naplnit vším potřebným: web serverem, běhovým prostředím pro aplikaci (ve zkratce nainstalovat PHP, Javu, NodeJS nebo cokoliv jiného je potřeba), logováním, správou konfigurace atd atd.

S Elastic Beanstalkem se jde o krok dál, Amazon už má předpřipravené konfigurace pro nejčastější platformy a vy jen nahráváte kód aplikace a předáte konfigurace proměnnými prostředí. Všechno to sice běží na EC2 instancích, pokud si ale o to nepožádáte, nedostanete žádný SSH přístup. AWS dodá monitoring, deployment tooling a automaticky nakonfigurované load balancery.

EC2 Container Service je pak managovaný hosting Docker kontejnerů. Tam si sice volíte jaký operační systém chcete, vyberete si základní image a nainstalujete všechny závislosti, jakmile ale jednou kontejner postavíte, už se ho nikdy nedotknete.

No a nakonec je Lambda. S Elastic Beanstalkem má společné to, že nahráváte pouze kód aplikace. Zásadním rozdílem ale je to, že nahráváte kód, který pouze reaguje na události a nedrží žádný stav. Už neplatíte za čas běhu serveru, protože žádný server nemáte. Amazon si rozhází váš kód na spoustu serverů a automaticky se postará o škálování. Platíte pouze za spotřebovaný čas a pamět pro výpočet odpovědi.

Život bez serverů

Lambdu jsme začali používat na všechno, na co jsme si předtím rezervovali kapacitu nebo měli frontu zpracovávající workery, Ukážu to na pár konkrétní příkladech.

  1. Generování PDF exportů byla jedna z aktivit, která nám nerovnoměrně zatěžovala servery a hrozně obtížně se škálovala. Než stačily naběhnout nové instance, bylo po dávce a celá škálovací skupina se pak flákala. Místo toho teď na Lambě hostujeme generátor exportů, kterému se pošle JSON s názvem šablony a daty pro šablonu. Jako odpoveď dostanu odkaz na S3, kde je uložené vygenerované PDFko.

  2. SMS a mail brány mají typicky hrozně nevyrovnané doby odezvy. Dlouho jsme to řešili frontou a workerem, který postupně zprávy odesílal. Nyní zavoláme Lambda funkci s kódem pro odesílání SMSek a hotovo. Odpadla konfigurace fronty a především nutnost spravovat démona, který ty zprávy bude konzumovat. Navíc lze Lambdu zavolat asynchronně, takže nezdržujeme uživatele, pokud ji voláme z webových transakcí.

  3. Nakonec jádro projektu, o kterém jsem mluvil na začátku. Hlavní součástí dispečinku je pro nás to, čemu říkáme algoritmus. Pokaždé když nám přijde nová objednávka, musíme ji přidělit jednomu z našich kurýru. Do hry vstupuje spousta parameterů jako jsou časová okna pro vyzvednutí a doručení, velikost objednávky, priorita a v neposlední řadě existující trasy kurýrů. Pro každou objednávku pošleme algoritmu snapshot současné polohy všech kurýrů, jejich tras a informace o objednávce. Výsledkem je seznam všech obodovaných kurýrů s informací, kde po své trase je pro ně nejlepší vyzvednout a doručit objednávku.

    Algoritmus je black box: pošlete mu celý popis situace na vstupu a zpracujete výsledek, což je naprosto ideální využití pro Lambdu. Výpočty jsou docela složité, protože počítáme permutace zastávek, pro každou počítáme vzdálenosti a časy a celé to seřazujeme. To nás ale netrápí, protože Lambda v případě špičky automaticky rozdistribuuje kód na tolik serverů, kolik bude potřeba. Jakmile přestaneme počítat, už nečekáme na žádný downscaling a neplatíme nic dál.

Jednou běží, navždý běží

Pro mě osobně má Lambda jednu killer feature. Můžete mít všechny verze funkce nadeployované najednou bez jakýchkoliv dalších nákladů. U klasických aplikací většinou nahrazujete jednu verzi verzí novější. U Lambdy nic nenahrazujete, ale pokaždé nahráváte novou. Silnou kombinací jsou pak následující tři body:

  1. Můžete mít nahrano současné libovolné množství verzí kódu
  2. Neplatíte za nevyužívané vteřiny a megabyty paměti
  3. Funkčnost Lambda funkce se nemění v čase (protože nemá žádný stav)

Když tohle spojíte, zjistíte, že vám stačí mít dobré end-to-end testy, které budete pouštět při každém buildu aplikace. Místo simulování AWS služeb offline jsme se rozhodli mít přesnou kopii produkčního stacku jak pro vývoj, tak pro continuous integration (opět, neplatíte, pokud zrovna nepoužíváte). Tím pádem pokud naší dispečinkové aplikace prošli end to end testy na Travisu, můžeme se spolehnout, že nám na produkci poběží jak teď, tak i v budoucnosti.

Dispečinková aplikace by se mohla rozbít pouze v tom případě, že by algoritmus přestal fungovat, nebo by někdo nahrál nějakou neočekávanou verzi. První jsme vyloučili výše. Druhý případ vylučujeme tím, že verze Lambda funkce je součástí buildu. Pokud by ji někdo chtěl zvýšit, musel by vytvořit nový build, ale tím i projít všemy end to end testy.

Jednou z největších úlev v poslední době pro mě bylo přestat pouštět funkční testy oproti produkci, ale pouze testovat celé prostředí při buildu. Díky Lambdě nemusí běžet žádné před-produkční servery a díky Dockeru lze pustit věrnou kopii aplikací na build serveru. Testy např. v Seleniu jsou proti produkci zoufale nestabilní a akorát žerou čas testerům i vývojářům. Celý tým si zvykne na to, že je v pořádku že denně padá zhruba 5% testů.

Místo toho máme kombinaci toho, že bez zelených testů není release, a jistotu že pokud jednou testy prošly, aplikace bude v pořádku i v budoucnosti. Všem najednou zbývá o dost víc času na vývoj.