Portál AbcLinuxu, 30. dubna 2025 18:20
Repost z mého osobního blogu, aby se článku dostalo alespoň nějakého ohlasu. Aneb jak jsem uspěl při náhradě squidu za statický webserver.
ještě lehký úvod pro čtenáře na ABCLinuxu - tento zápis je psán formou nenáročnou pro čtenáře neznalého problematiky. Pro čtenáře znalého to může být velice únavné. Mě se ale nechce článek ještě jednou upravovat, když jsem se s ním psal. Takže je to prosět takhle.
Webserver je od toho, aby obsluhoval požadavky návštěvníků. Nejrozšířenějším webserverem je Apache. Apache ale není nejlepší webserver pro všechny účely — když ho dost zatížíte, žere hodně RAM, nestíhá, špatně nebo nestandardně nakonfigurovaný občas i vytuhává a padá. Jedním problémem hned z návrhu je model process-per-request, tedy že pro každý obsluhovaný požadavek se vytvoří vlastní proces, který má s mod_php
třeba i 8 MB. A to je dost, od serveru se nečeká, že bude mít pod zátěží několik stovek MB. Dalším problémem je to, že když má třeba klient pomalé připojení k Internetu a rozhodne se stáhnout si velký soubor, proces se s ním musí celou tu dobu zahazovat – takže tady je člověk tahající statický obsah (třeba 10MB video) a na něj je vázáno klidně 1-2 % systémové RAM (třeba můj server má 512MB RAM) — prostě do té doby, než si to video ten člověk stáhne. Že to není nic strašného? A co když je na stránce galerie s 25 obrázky po 30kB – uživatel začne načítat stránku a … buď je najednou server zahlcen (málo RAM), nebo se nedostává na ostatní uživatele, nebo se stránka zase pomalu načítá tomu jednomu … a co se dál s tím dělá, chcete asi vědět.
Prvním nabízeným řešením (a cestou nejnižšího odporu, alespoň na první pohled) je reverse caching proxy (nelíbí se mi české přepisy slova cache …) — nejlepší bude, když si o ní přečtete na wiki, ve stručnosti je to proxy „obrácená dovnitř” — chová se jako webserver a stojí mezi skutečným webserverem a návštěvníkem. Výše naznačené problémy řeší následujícím způsobem:
Pound
).Dá se na to ale jít ještě líp ;)
Pro nastavení reverse caching proxy toho admin udělat moc nemusí — nastaví port a IP, na které má proxy naslouchat a kam má posílat požadavky, v případě Squidu ještě úložiště pro cache a velikost cache — tady bych si dovolil připomínku, že pokud vám následující řešení vyhovovat nebude, určitě použijte místo Squidu Varnish, je prostě lepší, rychlejší, no, přečtětěte si sami na jeho webu. Dál už se vlastně admin o nic starat nebude. Pořád ale platí, že každý požadavek, který jde na server poprvé, musí nejdřív obsloužit hlavní webserver (Apache) a ani není jisté, že se objekt dostane do cache, tam taky nevydrží věčně … nebylo by lepší pro takový účel použít malý monolitický webserver (nevětví se na další procesy, zabírá tedy i pod zátěží maximálně kolem 15MB, teď už mluvím konkrétně o nginxu), který se o statický obsah postará a ten dynamický hodí (jako reverse proxy) na Apache? Přesně tohle umí nginx (a mnohem víc, ale o tom příště, až to sám uvedu ve skutek).
Komplikace je v tom, že webserver musí vědět, kde hledat soubory, které má posílat klientovi, a taky v tom, že vůbec ne každý webserver podporuje tzv. Mass hosting, tedy řešení, že se v konfiguráku nedefinuje každý virtual host zvlášť, ale nadefinuje se tam nějak pomocí proměnné Host
, která je součástí HTTP hlavičky. Nginx to umí a dokonce velice jednoduše. Další problém byl, že se soubory nacházejí na disku ve zdánlivě nesouvisejícím pořádku (nesouvisejícím s tou proměnnou Host
, tedy tím, co posílá klient a co nginx zajímá), jako třeba /var/www/webs/klient1/
je DocumentRoot
pro web www.domena.cz
— data se totiž tahají z databáze, ale jak to sdělit nginxu (tenhle problém bude nejspíš jen můj, ale dlouho jsem nevěděl co s tím)?
Jako správce sítě ve škole jsem se proslavil svou symlinkovou magií (radši nebudu rozebírat), když jsem v linuxu symlinky objevil (po migraci z Windows), hned jsem si je zamiloval, takže jsem je použil jako řešení i teď. Do skriptu, který generuje konfigurák pro Apache (a tahá data právě z databáze a tudíž ví, který adresář odpovídá které doméně), jsem přidal jednoduchý příkaz, který nalinkuje DocumentRoot
do /webs/$host
, kde $host
je Host
předávané klientem. Takže nginx má jako root
nastaveno /webs/$host
a kouká se správně vždycky do toho adresáře, na jaký se klient ptá, není nutno nastavovat pro každou doménu zvlášť. Nginx taky umí obsah rozlišovat pomocí nějakého regulárního výrazu, ty sice neumím, co ale umím, je hezky něco opsat z manuálu, takže jsem si nastavil regexp pro některé významné přípony statického obsahu — videa, obrázky, archivy a podobně. Už vás nebudu dál napínat, můj nginx.conf
je takovýhle (většina toho je výchozí konfigurák, všímejte si hlavně nastavení proxy
a location
):
user nginx nginx; worker_processes 1; error_log /var/log/nginx/error_log info; events { worker_connections 8192; use epoll; }http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main ‘$remote_addr – $remote_user [$time_local] ‘ ‘”$request” $status $bytes_sent ‘ ‘”$http_referer” “$http_user_agent” ‘ ‘”$gzip_ratio”’; client_header_timeout 10m; client_body_timeout 10m; send_timeout 10m; connection_pool_size 256; client_header_buffer_size 1k; large_client_header_buffers 4 2k; request_pool_size 4k; gzip on; gzip_min_length 1100; gzip_buffers 4 8k; gzip_types text/plain; output_buffers 1 32k; postpone_output 1460; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 75 20; ignore_invalid_headers on; index index.html; server { listen moje.veřejná.ip.blah:80; server_name _ *; access_log /var/log/nginx/localhost.access_log main; error_log /var/log/nginx/localhost.error_log info; location / { proxy_pass http://localhost:80/; #Kde běží Apache proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location ~* \.(jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|pdf|ppt|txt|tar|mid|midi|wav|bmp|rtf)$ { root /webs/$host; } } }
Takže tak. Teď už se o 90% všech požadavků stará nginx, cache nežere paměť, Apache více méně taky ne, já jsem připraven na modernosti typu Ruby on Rails a Turbo Gears (které se častěji realizují s „alternativními” webservery — lighttpd, nginx) a mám volnější ruce co se konfigurace týče. Snad to někomu k něčemu bude, s dotazy neváhejte a ptejte se v komentářích, třeba si toho někdy všimnu.
Tiskni
Sdílej:
No nevím, takové RubyOnRails jsem ještě na Apachi neviděl (pokud není Apache použit jen jako reverzní proxy a na statický obsah). U TurboGears je tohle řešení (Apache jako reverzní proxy) taky myslím oficiální dokumentované a to už mi přijde o dost rozumější na takovýhle úkol použít lighttpd nebo nginx. Docela jsem se o tohle zajímal (chtěl jsem mít všechno po kupě s Apachem) a vím, že k Apachi to nijak zvlášť přátelské není (respektive jde to, ale v takové podobě, že jestli je tam Apache nebo něco 100x menšího, tak to nejde poznat). Hmm.
Jde mi o to, že v některých předchozích článcích jsem tu řešil. jak mít všechno "pod jednou střechou" - chci, aby se aplikace (ať už PHP, nebo Python nebo Ruby) každého klienta spouštěly pod jeho UID/GID a pak nebyla nutná různá složitá zabezpečovací řešení. K čemuž se FastCGI použít dá, ale nedá se konfigurovat z databáze nebo nějak dynamicky, jako třeba mod_ruid (o tom je taky na blogu nebo v předchozích zápiscích, nejsme si jistý, kdyžtak to najdete googlem). S lighttpd to jde AFAIK napíchnout na MySQL, nejsem si jistý. Neříkám, že něco nejde, ale pokud budete mít Apache + *CGI, tak kde je ten rozdíl proti nginxu nebo lighttpd (které jsou podle benchmarků rychlejší a žerou méně RAM)?
No, třeba se přes FastCGI dá ovlivnit, s jakým UID/GID ta spouštěná aplikace pojede - můžete pro každého klienta nastavit zvlášť. FastCGI je v tomhle ohledu lepší a univerzálnější, řekl bych.
Jak můžu i HTTP aplikace ovlivnit (ve sdíleném prostředí, kdy jeden webserver obsluhuje více hostů, které mají být odděleny) za jaký UID/GID se bude spouštět? U Apache třeba pomocí mod_ruid, u jiných webserverů nevím, reverzní proxy s tím nemá co dělat. Takže to není ani trochu stejné, jestli budete z webserveru posílat požadavky přes FastCGI nebo jako reverzní proxy - v případě FastCGI můžete ovlivnit věci, které s reverzní proxy ovlivnit nemůžete. A myslím, že v tom mám dost jasno na to, abyste mi neodpovídal "LOL" :/
Hmm :/ Já opravdu stojím o komentáře a diskusi, jen mám pocit, že kvůli tomu, že jsem napsal blogpost ve stylu, který vám nesedne, se ze mě snažíte udělat blba, což není příjemné asi nikomu, i když máte samozřejmě ve většíně věcí pravdu (nejsou ale v rozporu s tím, co jsem psal v postu). A ke konkrétnímu případu - spouštět Apache pod speifickým uživatelem Vám přijde efektivní? Tak, aby se nepřemnožil a zároveň zvládal vykrýt requesty, by to bylo vážně dost těžké (protože si vydržuje pro každou instanci nějaký počet procesů == zbyteně vyplýtvaná RAM). I pro menší webservery (lighttpd je to plýtvání ve srovnání s tím, jak je efektivní řešení s FastCGI, které udělá suid až potom, co mu přidje request a webserver mu oznámí, na jaké uid:gid má ten suid udělat. V případě nginxu to beru, ten je tak malý, že tyhle funkce nepotřebuje a můžete si jich pustit v systému dostatek bez toho, aby to mělo podobný efekt jako s Apachem. Já ten nadpis snad kvůli Vám ještě přepíšu :D
Ale já nechtěl jen reverse proxy. "Bulvární" nadpis možná trochu je, původně to bylo v mém blogu, kde si toho dovoluju víc (protože to nikdo nečte), ale prostě to tak je, no. A že bych nevěděl, co si vybrat ... tak to taky nebylo. Ten server už běží delší dobu, nginx je záležitost poměrně nová, řešení s pouze reverzní proxy (Pound) bylo náročnější než řešení s reverzní cachovací proxy, takže tady bych s Vámi dost nesouhlasil ... aneb, ne, já vím, co chci :)
Ale já jsem se vůbec Squid shazovat nesnažil, ach jo. Jen jsem měl radost, že se mi podařilo rozjet řešení, které není tak obecně použitelné, jako je squid (protože musíte provádět nějakou tu magii s documentrooty) a chtěl jsem se o to podělit. Ale pokud už do sebe rýpeme, tak bych Vám doporučil se mrknout na stránky Varnishe, protože tam se o Squidu dost mluví a také o tom, k čemu byl a nebyl udělaný - Squid jako reverzní proxy primárně dělán nebyl a neodvádí jako reverzní (a to ani cachovací) proxy tu nejlepší práci. Ale to jen na okraj.
Lidi (klienti) jsou zvyklí na Apache, tím myslím především jeho rewrite. I já bych byl trochu naštvaný, když bych nainstaloval redakční systém s nachystaným .htaccess pro Apache, aby fungovali SEF (Search Engine Friendly) URL a ono to nejelo. A další věci na to jsou navázané. Ale máte pravdu, kdybych nedělal mass hosting, ale jenom nějaký jeden specifický projekt, tak bych Apache už asi nepoužil.
Jak jinak než rewritem chcete řešit Search Engine Friendly URLs (třeba to co je vidět tady na Ábíčku, ale v PHP aplikacích na Apachi)?
A co když je na stránce galerie s 25 obrázky po 30kB – uživatel začne načítat stránku a … buď je najednou server zahlcen (málo RAM), nebo se nedostává na ostatní uživatele, nebo se stránka zase pomalu načítá tomu jednomuSlušní weboví klienti (prohlížeče, proxy, roboti) mají nastaven limit pro maximální počet souběžných spojení k jednomu hostname, který se zpravidla pohybuje v řádu jednotek (např. můj Firefox má nastaveno 8, což je u něj výchozí hodnota). Může být nastaven vyšší limit pro persistentní spojení (u mne je nastaveno 12), což u modelu process-per-connection může trochu vadit, protože ten proces musí existovat, i když zrovna nic nedělá. Jetty s SelectChannelConnectorem tohle umí řešit tak, že spojení, které je otevřené, ale nic se na něm zrovna neděje, nemá přiřazené ani svoje vlákno, takže těch prostředků vázaných takovým spojením je opravdu minimum. Taky předpokládám, že většina paměti, kterou zabírají procesy Apache, je ve skutečnosti sdílená paměť – kód Apache a případně modulů. Takže by to s tou žravostí paměti nemělo být zas tak špatné.
No nevím, rozhodně když porovnám paměťovou náročnost situací "pouze Apache", "Apache+Squid" a "Apache+Nginx", tak z toho první vychází nejhůř a poslední nejlíp. Co dělají slušní klienti je jedna věc, druhá je, že na různých tunerských stránkách jsou tipy na zrychlení načítání stránek založené právě na zvyšování těchhle hodnot. Ale zas tak moc jsem to nezkoumal, vím jen, že ve srovnání s řešením přes squid mám teď místo loadu 0.3-0.5 asi 0.02-0.1.
Jo, místo Squidu už jsem poměrně dlouhou dobu používal Varnish, rozdíl značný a problémy popisované v blogu taky úplně neřešil (neříkám, že jsem to od něj čekal, neshazuju cachovací reverzní proxy, prostě to bylo řešení, které jsem použil - to abyste mě zas neobviňoval...). Apache s nginx a bez budu moct brzo otestovat (přijde nový server, takže si budu mít s čím hrát), ale už jen když se nad tím zamyslím, tak by to dávalo docela smysl, ne? Navíc testování v lokální síti, kde přenos větších souborů bude řádově rychlejší (tedy bude muset viset méně procesů Apache) taky úplně neodpovídá reálnému provozu, takže nevím, jak to objektivně otestovat, ale co už ...
Jisté ale je to, že když mám Apache MPM Prefork (což musím mít kvůli mod_ruid a PHP) a _jeden_ proces Apache už bere sám o sobě asi 20x víc než proces nginxu (který se nedělí), tak to bude paměťově mnohem náročnější (pouze Apache) a výpočetně určitě taky (vytvoření procesu a jeho zánik je - i když nejsem ani trochu expert - myslím docela náročné, ne?). Nemám pravdu?
Na webu nginxu tuším nějaké je, vychází to pokud vím přibližně na stejno, v blogu jakéhosi člověka jsem četl, že lighttpd leakuje a má dost bezpečnostních problémů (což je pravda, ale opravují je).
Ale určitě, proto taky jásám nad tím, že jsem mohl odstranit Squid/Varnish a nahradit je nginxem, což si myslím že je už jen z hldiska návrhu lepší řešení. Apache je pořád v mém systému nenahraditelný a prozatím tam zůstane a na něj taky nenadávám ... :)
A abychto ještě upřesnil, já nenadávám ani na reverzní cachovací proxy, vyřešila dočasně můj problém, prostě ne ideálně a teď to (myslím) ideální je. Takže ten příspěvek je vpodstatě jen o těch pár posledních odstavcích (hlavně ten konfigurák :D), aby se z toho mohl někdo (až dospěje do podobné situace) poučit. Díky za pochopení :)
ISSN 1214-1267, (c) 1999-2007 Stickfish s.r.o.