Přejít k navigační liště

Zdroják » PHP » Deploy PHP aplikací, bez výpadku a pod (velkou) zátěží

Deploy PHP aplikací, bez výpadku a pod (velkou) zátěží

Články PHP

V tomto krátkém článku bych se s vámi rád podělil o pár poznatků a slepých uliček, kterými jsme si prošli při přípravě nasazování naší PHP aplikace a které by se vám mohly hodit při nasazování té vaší.

Nálepky:

Tento článek původně vyšel na blogu Forest79.

V následujících pár odstavcích bych se s vámi rád podělil o zkušenosti, které jsme nasbírali při implementaci „neprůstřelného“ deploye naší PHP aplikace na jednom z nejnavštěvovanějších českých serverů. Pokud provozujete malý webík, který obslouží jednotky requestů za sekundu, nedozvíte se pravděpodobně nic potřebného. Jestli však provozujete větší službu s cca 80 requesty za vteřinu (ve špičce) a ještě aplikace běží na více webových serverech, tak jako ta naše, nasazujete často a rádi a naopak neradi přicházíte o requesty, tak jako my, pak věřím, že by pro vás následující text mohl být zajímavý.

Jak jsme to dělali dříve

Nejprve trochu uvedu do děje. Naše aplikace je napsaná v Nette, běží na více webových serverech, na každém serveru je k dispozici user cache a samozřejmě opcode cache (pro oboje používáme XCache). Plus aplikace využívá sdílenou cache pro všechny servery (memcache a Redis). Jak jsem psal v úvodu, tak novou verzi aplikaci nasazujeme, kdykoli je třeba. Nečekáme na slabší návštěvnost a nasazujeme vždy za provozu. Až do nedávna nám pokaždé při nasazení aplikace skončilo několik requestů chybou. Při největší návštěvnosti jich bylo v řádu jednotek, ale i tak jsme chtěli daný stav vyřešit a nepřijít ani o jeden request.

V minulosti jsme aplikaci nasazovali způsobem, který většina z vás asi zná a používá. Root webu, nastavený ve virtual hostu Apache, je symbolický odkaz, který vede vždy do adresáře s aktuálními zdrojáky k aplikaci. Samotné nasazení nové verze pak vypadá tak, že se soubory nahrají do nového adresáře, symlink se změní, aby odkazoval do tohoto adresáře, promaže se nějaká ta cache a je nasazeno. Pokud máte více serverů, uděláte buď stejný postup na každém z nich nebo je sesynchronizujete nějakým jiným způsobem (u nás to probíhá pomocí rsync). Podobným způsobem to na našich serverech fungovalo do té doby, než jsme upgradovali operační systém Debian 6 „Squeeze“ na Debian 7 „Wheezy“ kvůli přechodu na PHP 5.4.

Problémy

Od té doby (pouze při velké zátěži, za malého provozu fungovalo vše dobře) přestal Apache nějak zvládat změnu symlinku a klidně po dobu až několika minut vracel prázdné odpovědi. Nejdříve jsme z tohoto chování podezírali XCache, kterou jsme začali používat až po upgradu PHP na 5.4, ale stejně se vše chovalo i s jinou opcode cachí a dokonce i bez ní. Podařilo se nám vygooglit, že několik uživatelů má stejné problémy jako my, ale nenašli jsme žádný recept co s tím a dodnes jsme nepřišli na to, kde byl problém. Nakonec jsme tuto situaci vyřešili tak, že místo změny symlinku jsme výměnu root adresáře provedli pomocí přejmenování adresářů (nová verze aplikace se nahrála do adresáře www_new, aktuální www se přejmenoval na www_old a www_new na www – toto bohužel na rozdíl od změny symlinku nelze udělat atomicky, takže vždy pár requestů skončí chybou), což kupodivu Apache zkousnul. Takto jsme fungovali asi rok, než jsme si našli čas, zkusit tuto ne zrovna ideální situaci nějak vyřešit.

Naše první nápady se upínaly k možnosti restartovat Apache takovým způsobem, kdy se staré requesty nechají dokončit a nové se zavolají s případnou změněnou konfigurací (graceful restart v Debianu známější jako reload). Pro nás by to znamenalo změnit document_root na adresář s novou verzí zdrojových kódů aplikace. Vše fungovalo hezky, pokud se greaceful restart prováděl mimo špičku. Ve špičce se Apache nenechal zahanbit a pro jistotu po restartu na pár minut umřel. Tohle byl pro Apache na našich webových serverech poslední hřebíček do rakve. Už dlouho jsme pro statická data na jiném serveru používali k plné spokojenosti nginx a rozhodli jsme se jím nahradit Apache všude.

Zkoušíme Nginx

S nginxem už začalo pracovat nahrazení symlinku podle očekávání, nastal však jiný problém. Nedokážu si vysvětlit, proč se stejné chování neprojevilo, když jsme používali PHP jako modul s Apachem, ale jde o to, že PHP si interně cachuje realpath pro všechny soubory, které používá (cache se používá i pro magické konstanty __DIR__ a __FILE__) a tuto cache si drží defaultně 2 minuty. Pokud použijete PHP s nginxem (přes fastcgi, php-fpm) a s rootem webu jako symlink, tak se toto chování hned projeví, a to tak, že po nahrazení symlinku s rootem webu dostáváte ještě po dvě minuty (maximálně) starou verzi aplikace. Tato cache se sice dá v php.ini vypnout, ale kvůli údajnému zhoršení výkonu se to nedoporučuje. Bohužel tato cache nelze globálně vymazat, lze deaktivovat pouze pro jeden request (další request už zase cache použije). Všechny tutoriály pro zprovoznění ngixu s PHP přes fastcgi, co jsem našel, předávají cestu k PHP souboru, který se má zpracovat s nepřeloženými symbolickými odkazy. Může za to tento řádek, který velice pravděpodobně máte i ve své konfiguraci:

fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

Dejme tomu, že máme root webu jako symbolický odkaz /var/www/app/www, který ukazuje do adresáře s aktuálními zdrojovými kódy aplikace /var/www/app/releases/v1 a server přijme request na soubor index.php. Nginx zavolá PHP a předá mu cestu k php souboru skrze parametr SCRIPT_FILENAME jako /var/www/app/www/index.php. PHP si při zpracování zjistí, že adresář /var/www/app/www je symbolický odkaz a uloží si do cache přeloženou cestu k souboru index.php jako /var/www/app/releases/v1/index.php, tento soubor také zpracuje a vrátí výsledek. Teď nahrajeme na server novou verzi aplikace do adresáře /var/www/app/releases/v2 a změníme symlink /var/www/app/www, aby ukazoval do tohoto adresáře. Server přijme nový požadavek opět na soubor index.php, opět zavolá PHP a opět mu předá parametr SCRIPT_FILENAME jako /var/www/app/www/index.php. PHP přijme požadavek a podívá se, jestli nemá tuto cestu v cachi. A ouha, má a cache říká, že soubor /var/www/app/www/index.php je ve zkutečnosti odkaz na soubor /var/www/app/releases/v1/index.php. No a tak zpracuje a vrátí výstup ze souboru starší verze aplikace.

Abychom tomuto chování co nejelegantněji předešli, chtělo by to umět PHP předávat soubor ke zpracování už s přeloženou cestou a ne jako symlink. A nginx toto naštěstí umožňuje. Tato funkčnost není nikde moc prezentována (přes modul mod_realdoc to umí i Apache, kde by to podle mě mohlo být potřeba, pokud budete PHP provozovat jako fastcgi) a špatně se nám hledala, ale nakonec se podařilo. Celé je to docela jednoduché a stačí nahradit proměnnou $document_root za $realpath_root. Takže celý řádek konfiguratce bude vypadat následovně:

fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;

Pro jistotu přidáme ještě řádek:

fastcgi_param DOCUMENT_ROOT $realpath_root;

To pokud by někdo v kódu používal $_SERVER['DOCUMENT_ROOT'] a nezapomeneme tyto řádky zakomentovat v konfiguračním souboru nginx fastcgi_params , nebo je uvedeme až po řádku include fastcgi_params.

Nyní už máme připravený webový server a poslední věc, na kterou si musíme dát pozor, je, že pokud v linuxu nahrazujeme symbolický odkaz (často příkazem ln -snf new current), není tato operace atomická a symbolický odkaz na nějakou tu mili/mikro sekundu nebude existovat. Pro atomické nahrazení se musí použít malý trik. Vytvoří se dočasný nový symbolický odkaz a tento nový symbolický odkaz se přesune na původní, který chceme nahradit. Celá operace pak vypadá následovně:

ln -s new current_tmp && mv -Tf current_tmp current

Tak a to je celé to kouzlo, díky kterému jsme schopni nasazovat nové verze aplikace i při největší zátěži a nepřijít přitom ani o jeden request.

Shrnutí a doplnění

  • nebojte se nahradit Apache za nginx! Pokud máte společný server pro PHP a statické soubory, určitě pocítíte i výkonové zlepšení. U nás máme statické soubory na jiném serveru a tak ve výkonu moc velký rozdíl nepozorujeme, ale i tak máme s nginx samé pozitivní zkušenosti. Hodně lidí se bojí, že přijde o .htaccess soubory, ale ruku na srdce, jak často je upravujete? Nginx sice nic podobného nepodporuje (prý je to žrout výkonu, kdyby někdo věděl o nějakém konkrétním měření, rádi se na něj podíváme :-)), ale všechno jde napsat přímo do konfigurace.
  • Nastavte nginx tak, aby rozpoznával symbolické odkazy (někde na fóru jsme se dočetli, že by volání do systému na rozpoznání symbolického odkazu mělo sebrat nějaký ten výkon, ale na našich serverech jsme nic podobného nenaměřili). Nastavení fastcgi může vypadat nějak následovně:
    location ~ \.php$ {
        try_files     $uri =404;
    
        fastcgi_pass         unix:/var/run/php5-fpm.sock;
    
        include  fastcgi_params;
    
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT $realpath_root;
    }
    
  • změnu symbolického odkazu na root webu provádějte atomicky, přes vytvoření dočasného symbolického odkazu na adresář s novou verzi aplikace a přesunutím tohoto nového symlinku přes původní:
    ln -s app_new_version www_tmp && mv -Tf www_tmp www
    
    • pokud máte více webových serverů, musíte tento příkaz zavolat na každém z nich! U nás například používáme pro synchronizaci souborů rsync, ale kdybychom nechali synchronizovat i symbolický odkaz na root adresář, nenahradil by se atomicky!
  • aby vše fungovalo, je potřeba, aby každá verze aplikace měla vlastní temp adresář, kam se vám budou ukládat jednotlivé cache pro konrétní verzi aplikace (šablony, robot loader, …)
    • pokud používáte na serveru user cahe, kde jsou data závislá na verzi aplikace (může se jejich struktura měnit s novou verzí aplikace), tak je vhodné klíče v cachi prefixovat verzí aplikace nebo použít namespace, pokud je to možné
    • takto by aplikace nemusela dobře fungovat v době mezi změnou symlinky a promazáním user cache!
    • databázové změny pro nové verze se musí dělat zpětně kompatibilní se starší verzí (aby na databázi mohla v pořádku fungovat stará i nová verze aplikace)
    • celé to má i tu výhodu, že pokud by nová verze z nějakého důvodu nefungovala dobře, lze se jednoduše vrátit k původní prostou změnou symlinku rootu webu na adresář se starší verzí aplikace
  • možné problémy při takovémto kontinuálním způsobu nasazování mohou nastat se sdílenou cachí. Tam se často uchovávají objekty, které nechceme přegenerovat s každou novou verzí aplikace a pokud je pro správnou funkčnost aplikace potřeba cache promazat (pokud se třeba mění její struktura), nelze to udělat atomicky s nasazením aplikace. A právě v čase mezi změnou symlinku a promazáním cache nemusí aplikace fungovat dobře.

Komentáře

Subscribe
Upozornit na
guest
18 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
MichalKleiner

Diky za pekne shrnuti. Jsem rad, ze takrka stejny system pouzivame pro deploy nasich aplikaci. Stale ale pouzivame Apache. Zmenu symlinku provadime atomicky presne jak je uvedeno v clanku, coz funguje na Debianu nebo podobnych Linux distribucich (stejneho chovani nelze dosahnout na MACu – pri vyvoji nastroje pro deploy me to trochu matlo, samozrejme pro produkci mac nepouzivame).

O kompletnim prechodu na nginx uvazujeme stale intenzivneji, musime ale podporovat i .htaccess pro starsi weby, nekde jsou desitky pravidel. Vi nekdo o ceste, jak .htaccess pravidla prelozit nebo zprovoznit pro nginx, treba pres nejakou proxy nebo wrapper? Pouzivame treba mod_proxy pravidla pro nacteni obsahu z jine url bez presmerovani. Diky za pripadne tipy.

karel

zkusenost prechodu z apache na nginx, prave kvuli htaccess byl prvni zpusob prechodu takovy napul nginx sem pouzival jako revers proxy pro apache kde bezela aplikace, ale staticka data uz serviroval nginx.
postupem casu jsem upravil pravidla z htaccess na pravidla v nginxu a php zpustil pres php-fpm a zabavil se apache uplne.
jinak revers proxy je v nginxu v zakladu vcetne pekne fungujicich cachy, pozot pro ty co by to ladili na windows nevim jak ted ale jeste donedavna cachovani na win moc dobre nejelo.

Luk83

Spíš bych uvažoval o přepisu pravidel do configu Nginx a použít moduly přímo vyvynuté pro něj. I když se to může zdát jako složitější jednorázový řešení a něco jako wrapper pro moduly apache by existovalo (to nevim a silně pochybuju) tak se do budoucna vyvaruješ nekompatibility při upgradu sw, což by ve finále vyšlo mnohem dráž.

Jinak po menšim googlování jsem našel minimálně jeden converter pro rewrite pravidla.

http://www.usethefuckinggoogle.com/?q=nginx+htaccess

David Kudera

Některé ty převodníky jsem zkoušel, ale u jen trošku složitějších pravidel už to nefungovalo. Nakonec bylo fakt lepší dát si trochu času s ručním přepsáním pro nginx

alesroubicek

Zero Downtime Deployment jde poměrně jednoduše vyřešit bez zbytečných hrátek s file systémem na úrovni reverzní proxy. Chvíli vám pobřeží dvě verze vedle sebe, ale to je cena za opravdový ZDD. Náměty viz třeba Google https://www.google.cz/search?q=reverse+proxy+deployment+zero+Downtime&oq=reverse+proxy+deployment+zero+Downtime&espv=1&ie=UTF-8

alesroubicek

Ne. :) Myslím takové řešení, kdy mi na jednom nodu běží dva virtuální servery, jeden se starou verzí, druhej s novou a v reverzní proxy změním nasměrování na port nového serveru. Samozřejmě je potřeba mít synchronizované sessions. Krom reverzní proxy se nic jiného nemění, jen se vytváří nové weby a staré následně mažou. Umožňuje to i snadný rollback v případě zjištěných kritických defektů v produkci.

Luk83

Ve finále, to že Nette nemá odlazený a jednoduchý deployovací nástroje mě nepřekvapuje. Pro Symfony je tu klon Capistrana známý jako Capifony s jednoduchým configem nebo konkrétně teď používáme Webistrano. Tam očekávám, že podobný probs vyřešila o dost větší komunita a nebudu hodiny a dny řešit byť minimální výpadek přehozením symlinků. Oba zmíněné nástroje používají stejný mechanizmus.

Oldřich Vetešník

Otázka je, jestli Capistrano řeší něco navíc a jestli to má cenu. Plus musíte mít všude Ruby (na serverech i na klientech), což je někdy, é, lehce problematické. Mně je sympatický „git push production“ s hooky jako má Heroku, ale zatím jsem to nezkoušel nastavovat u sebe, tak nevím, jak je to náročné.

tomaspolz

To o čem tu píšete mi ani jako deploy metoda nepripadá. Osobne si pod tim predstavuji neco sofistikovanejsiho.

My napřiklad na jednom projektu v praci pouzivame pro deplay aplikace CI nastroj Jenkins od Jetbrains.
Mame specialni deploy server, ktery is z svn stahne produkcni vetvi a pomoci rsync nahraje data na konrektni nody.
Dale umi nahat DB patche, minifikovat CSS a JS? vytvorit konfiguracni skripty dle prostredi, provadet ruzne testy atd. To vse na jedno kliknuti, pokud si date praci s uvodnim nastavenim.

Rozhodne bych vyuzitit CI nastroje doporucil kvli siroke skale moznych tasku, které v ramci buildu muze vykonat .

Jakym zpusobem resite databazove patche, testovani apod ?

tomaspolz

Díky za odpoved.
Mate pravdu, s tím JetBrainsem jsem se spletl. Uvazujeme o TeamCity pro deploy ostatnich projektu, tak proto ta zamena :)

radek

Jeste pridam zajimavy odkaz, jak to delaji v disqus, kde je ta zatez fakt nechutna:
http://highscalability.com/blog/2014/4/28/how-disqus-went-realtime-with-165k-messages-per-second-and-l.html

radek

Sorry k PHP je to vlastne OT.

Jan Forman

.htaccess a i ty symlinky jsou zásadní brzda, už jen proto, že server musí vyhledat všechny soubory v cestě.
v případě symlinku to pořád probíhá stylem sáhnu na symlink, sáhnu na soubor.
.htaccess tam je to zase stylem: sáhnu na jeden soubor a on načte dalších třeba pět v cestě (i když tam nejsou musí je zkusit).

Je to prostě 2x a víckrát pomalejší, ale poznat to jde jen na opravdu zatíženém systému – dochází mu zdroje.
A další CPU ani RAM mu už tak moc nepomůžou… místo aby otevřel třeba 10tisíc souborů musí jich otevřít třeba 50tisíc najednou.

NGINX se snaží otevírat co nejméně souborů a používat co nejméně paměti. Tam kde Apache sežere 8GB RAM a začne tuhnout, NGINX v pohodě používá klidně 512MB RAM a ani nehne brvou.

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.

Pocta C64

Za prvopočátek své programátorské kariéry vděčím počítači Commodore 64. Tehdy jsem genialitu návrhu nemohl docenit. Dnes dokážu lehce nahlédnout pod pokličku. Chtěl bych se o to s vámi podělit a vzdát mu hold.