Nginx - Apache killer

Opublikowane przez Jarosław Zabiełło Tue, 07 Nov 2006 23:47:00 GMT

W ostatnim artykule (Railsy: Lighttpd czy Apache 2.2.x?) porównywałem najbardziej popularne serwery HTTP dla Railsów. Zaintrygowany paroma wpisami w blogu postanowiłem przyjrzeć się dosyć mało znanemu serwerowi HTTP który zaczyna zdobywać coraz więcej uwagi na Zachodzie. Chodzi o ultraszybki serwer nginx napisany przez rosyjskiego programistę Igora Sysojewa.

Na pierwszy bój poszedł prosty test wyświetlenia “Hello World!” Na używanym przeze mnie serwerze dedykowanym (Athlon 64 3000+, 1GB RAM, Linux Ubuntu 64bit) dla 100 tys. zapytań (musiałem użyć aż tyle, bo serwer jest za szybki na mniejszą liczbę zapytań) uzyskałem następujące wyniki (dla polecenia “ab -n 100000 -c 1 http://localhost”):

  • Apache 2.2.3 = 4439 req/s
  • Lighttpd 1.4.11 = 7150 req/s
  • Nginx 0.4.12 = 8700 req/s

Co prawda Nginx wykazuje miażdzącą przewagę wydajności nad Apachem ale, z racji tego, że używam Lighttpd, który co prawda odstaje od Nginxa ale nie aż tak, postanowiłem na razie zaczekać z ewentualną migracją.

Okazało się jednak, że będę musiał przeprowadzić taką migrację szybciej niż bym chciał. Coś złego zaczęło się dziać z Ligthttpd. Po paru godzinach pracy, przestawał odpowiadać na zapytania a nawet w ogóle proces znikał z pamięci. Ki diabeł? Trudno powiedzieć, nie mam czasu aby analizować dokładniej problem. Jedyne co pomagało to regularny restart Lighttpd. Trochę głupie rozwiązanie. Postanowiłem zatem zrobić wcześniejszą migrację do Nginxa. Wg statystyk Netcraftu z Nginxa korzysta już ponad 90 tys. domen. Wydaje się to wystarczającą ilością aby można było uznać ten serwer za stabilny.

Jednakże moja migracja ma pewną trudność. Używam bowiem równocześnie PHP, Django, Rails i Zope (ściślej: Plone). Czyli całkiem niezła mieszanka aplikacji. Z PHP i Railsami było najmniej problemów, bo przykłady konfiguracji są z grubsza podane w Wiki.

Z Plone było troszkę gorzej. Musiałem bowiem znaleźć odpowiednik mniej więcej takiego kodu w Apache:

RewriteRule "^/(.*)$" 
"http://88.198.15.160:6001/VirtualHostBase/http/apologetyka.com:80/app/VirtualHostRoot/$1"  [P,L]

To nie jest zwykły rewrite, to jest połączenie proxy z rewrite.

W Lighttpd (też się swego czasu namęczyłem aby to uzyskać) uzyskuje się to tak:

url.rewrite-once = (
  "^/(.*)$" => "/VirtualHostBase/http/apologetyka.com:80/plone/VirtualHostRoot/$1"
)
proxy.server = (
   "/VirtualHostBase/" => (
     ( "host" => "88.198.15.160" , "port" => 6001 ) )
)
Trochę prób i się udało. Nginx potrzebował takiego wpisu:
location / {
  rewrite ^/(.*)$ /VirtualHostBase/http/apologetyka.com:80/plone/VirtualHostRoot/$1;
}
location /VirtualHostBase/ {
  include /opt/nginx/conf/proxy.conf;
  proxy_pass http://88.198.15.160:6001;
}

Najtrudniej było z konfiguracją Django bo opisu do Django na Nginx nie ma ani w dokumentacji do Django, ani w dokumentacji do NGinxa. Zmęczony eksperymentowaniem napisałem na listę dyskusyjną Django i dostałem całkiem pożyteczną wskazówkę odnośnie strony http://www.python.rk.edu.pl/w/p/django-pod-serwerem-nginx/. Niestety miałem pecha, bo akurat trafiłem na zmianę wpisów w DNS i artykuł był niedostępny. Udało mi się na szczęście wyłuskać jego kopię z cache Googli. Autor międzyczasie podesłał mi też pliki z artykułami. Zauważyłem że napotkał pewne problemy ze zmuszeniem Django do wyświetlania statycznej treści. Trochę podłubałem w kodzie i problem rozwiązałem. :)

Zauważyłem że Django wyświetlał mi pliki statyczne w trybie debug. Wynikało to pewnie z tego, że w urls.py stosuję zawsze taki wpis:

...
if DEBUG:
  urlpatterns += patterns('',
    (r'^images/(?P<path>.*)$', 'django.views.static.serve', {'document_root': MEDIA_ROOT+'/images', 'show_indexes': True}),
    (r'^stylesheets/(?P<path>.*)$', 'django.views.static.serve', {'document_root': MEDIA_ROOT+'/stylesheets', 'show_indexes': True}),
    (r'^javascripts/(?P<path>.*)$', 'django.views.static.serve', {'document_root': MEDIA_ROOT+'/javascripts', 'show_indexes': True}),
    )

Dla trybu produkcyjnego (settings.DEBUG=False) trzeba zmusić serwer HTTP aby się zajmował plikami statycznymi. Django ma tylko przetwarzać Pythona. Mozna to zrobić np. tak:

...
location ~* ^.+\.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|doc|xls|exe|pdf|ppt|txt|tar|mid|midi|wav|bmp|rtf|js|mov) {
  access_log   off; # po co mi logi obrazków :)
  expires      30d; 
}
location / {
  include /opt/nginx/conf/fastcgi.conf;
  fastcgi_pass 127.0.0.1:6002;
  fastcgi_pass_header Authorization;
  fastcgi_intercept_errors off;
}

Gdzie plik fastcgi.conf:

fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
fastcgi_param  SERVER_SOFTWARE    nginx;

fastcgi_param  QUERY_STRING       $query_string;
fastcgi_param  REQUEST_METHOD     $request_method;
fastcgi_param  CONTENT_TYPE       $content_type;
fastcgi_param  CONTENT_LENGTH     $content_length;

fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
fastcgi_param  REQUEST_URI        $request_uri;
fastcgi_param  DOCUMENT_URI       $document_uri;
fastcgi_param  DOCUMENT_ROOT      $document_root;
fastcgi_param  SERVER_PROTOCOL    $server_protocol;

fastcgi_param  REMOTE_ADDR        $remote_addr;
fastcgi_param  REMOTE_PORT        $remote_port;
fastcgi_param  SERVER_ADDR        $server_addr;
fastcgi_param  SERVER_PORT        $server_port;
fastcgi_param  SERVER_NAME        $server_name;

fastcgi_param  PATH_INFO          $fastcgi_script_name;

Plik startowy napisałem sobie już w Pythonie:

#!/usr/bin/env python

import os, sys, time
DEBUG = True
# All Django project are inside /home/app/django/

path = '/home/app/django'

projects = {
    'biblia' : {
        'project': 'searchers',
        'port':6002,
        'pidfile': '/var/run/django_searchers.pid',
        'children': 4,
        },
    'koran': {
        'project': 'django_project',
        'port':6003,
        'pidfile': '/var/run/django_koran.pid',
        'children': 4
        },
    }

def start(name):
    project = projects[name]['project']
    os.chdir('%s/%s/' % (path, project))
    appl = './manage.py runfcgi host=127.0.0.1'
    cmd = '%s port=%s minspare=1 maxspare=%s pidfile=%s --settings=%s.settings' \
          % (appl, projects[name]['port'], projects[name]['children'], projects[name]['pidfile'], project)
    if DEBUG: print cmd
    os.system(cmd)

def stop(name):
    pidfile = projects[name]['pidfile']
    if os.path.exists(pidfile):
        cmd = '/bin/kill -TERM %s' % open(pidfile).read()
        if DEBUG: print cmd
        os.system(cmd)
        os.unlink(pidfile)

def restart(name):
    stop(name)
    time.sleep(1)
    start(name)

if __name__ == '__main__':
    try:
        action, project = sys.argv[1], sys.argv[2]
        if action in ['start','stop', 'restart'] and project in projects:
            if action == 'start':
                start(project)
            elif action == 'stop':
                stop(project)
            elif action == 'restart':
                restart(project)
            else:
                raise IndexError
        else:
            raise IndexError
    except IndexError:
        print "Usage: %s {start|stop|restart} {%s}" % (sys.argv[0], '|'.join(projects.keys()))

Migracja się udała. Plone, PHP, Django i Rails śmigają mi teraz na ultraszybkim (i zajmującym mało pamięci!) serwerze Nginx. Acha, zapomniałem dodać: Nginx to nie tylko duża wydajność i oszczędność pamięci. Nginx ma dużo modułów. Może nie tyle, co Apache, ale znacznie lepiej niż Lighttpd.

BTW, ciekawie wygląda także serwer Cherokee. Nginx działa tylko pod systemami POSIX (Unix, MacOS-X, Linux, FreeBSD). Cherokee natomiast posiada… binarną instalację pod Windows! Ale o tym może napiszę coś innym razem. :)

Posted in ,  | Tagi , , , ,  | 16 comments

Railsy: Lighttpd czy Apache 2.2.x?

Opublikowane przez Jarosław Zabiełło Sun, 29 Oct 2006 14:53:00 GMT

Odnośnie użycia Railsów w celu produkcyjnym, dużo się zmieniło od czasu pojawienia się Mongrela – małego, ale dosyć szybkiego serwera napisanego w Ruby i C. Następuje generalne odejście od podpinania kilku procesów FastCGI do Rubiego z różnych powodów. W drugim wydaniu Agile Web Development in Rails autorzy przyznali, że próbowali różnych opcji użycia modułu FastCGI. Zawsze ostatecznie były jakieś problemy.

Aktualnie zalecanym podejściem jest użycie szybkiego serwera HTTP (Lighttpd, Apache lub LiteSpeed) na froncie do obsługi statycznych plików (głównie obrazki, style kaskadowe i pliki Javascript). Aplikację RoR należy uruchomić za pomocą kilku procesów Mongrela (nie jak wcześniej: kilka procesów FastCGI). A tym, co zepnie je z serwerem HTTP jest moduł rozkładający ruch (load balancing).

Niestety, okazało się że w wypadku Lighttpd ten moduł zawiera błędy. Z kolei nowy Apache 2.2 posiada dobrze działający moduł load balancingu. Spowodowało to zrezygnowanie przez wielu z Lighttpd na rzecz nowego Apache 2.2.x.

Z racji tego, że mam trochę swobody w doborze technologii w pracy, postawiłem serwer na nowym Apache’u. Aplikacje Railsów podpinamy za pomocą mongrela i wbudowanego w Apache modułu load_balancer. Działa faktycznie sprawnie.

Czy to znaczy, że Lighttpd nie ma sensu używać? Skąd! W wersji 1.5 mają już mieć naprawiony moduł load balancingu. Ale nawet i bez tego, istnieje bardzo dobry, mały i szybki load balancer – Pound. Skuszony jednak potrzebą eksperymentowania oraz nabytym niedawno serwerem dedykowanym (w miejsce wcześniejszego VPS’a) postanowiłem przenieść wszystkie swoje prywatne serwisy (a jest tego sporo) z Lighttpd do Apache 2.2.3. Musiałem przenieść dwie aplikacje Rails, dwie aplikacje Django, dwie aplikacje PHP 5.1 oraz jedną duża aplikację Zope/Plone. I jak wrażenia?

Otóż, po 2 tygodniach używania, stwierdziłem, że to nie ma sensu. Apache pożera za dużo pamięci. Dzieje się dokładnie tak, jak pisałem wcześniej. Nie mogłem użyć trybu wielowątkowego, nie tyle ze względu na PHP ale ze względy na Django! Napisali bowiem, że nie należy tego trybu używać w wypadku ich frameworka. Django nie jest wielowątkowy. Musiałem więc przełączyć Apache z trybu worker na prefork. Zgodnie z obawami, każdy fork musiał marnować pamięć na moduł mod_php i mod_python nawet jak to nie ma sensu (np. podczas obsługi obrazków). Na dodatek nie mogłem coś uruchomić jednej aplikacji Django. Kompletnie nie wiem dlaczego. Druga była prawie identycznie skonfigurowana i działała.

Wróciłem zatem z powrotem do Lighttpd , Pounda i Mongrela (dla RoR) i FastCGI (dla Django i PHP). Efekty były bardzo widoczne: zwolniło mi się ponad 200 MB pamięci. Może to nie ma znaczenia w pracy, gdzie mamy 2GB ale ja mam tylko 1GB i muszę jeszcze uruchomić zasobożerny Plone.

Dokładniej wszystkie sprawdzone i działające konfiguracje opiszę w powstającej książce o Railach. Moja ostateczna konlkluzja jest taka: jeśli zależy ci na oszczędzeniu pamięci – wybieraj Lighttpd. Jeśli nie jest to kluczowa sprawa, a potrzebujesz jakieś specjalne, dodatkowe moduły (których Apache ma pełno) nowy Apache 2.2 spełni twe oczekiwania.

Posted in  | Tagi , , , ,  | 9 comments

Django na Lighttpd + FastCGI

Opublikowane przez Jarosław Zabiełło Wed, 31 May 2006 21:16:00 GMT

Okazuje się, że uruchomienie Django na serwerze Lighttpd i FastCGI nie jest wcale trudne. Taki zestaw pożera znacznie mniej zasobów komputera niż Apache i mod_python. Efekt dostępny jest tutaj. Projekt Django został stworzony w /home/django_project. Użyłem Django w wersji SVN (pozbawionej “magii”), MySQL 5, Python 2.4.3 oraz system to Debian 3.1. Cała konfiguracja to:

Plik lighttpd.conf (aktualny):

$HTTP["host"] == "koran.apologetyka.com" {
  server.document-root = "/home/django_project/koran/public"
  url.rewrite-once = (
    "^/(media|stylesheets|images|javascripts).*" => "$0",
    "^/admin/(.*)$" => "/django-admin.fcgi/admin/$1",
    "^/(.*)$" => "/django.fcgi/$1"
  )
  fastcgi.server = (
    "/django.fcgi" => (
      (
        "bin-path" => "/home/django_project/django.fcgi",
        "socket" => "/tmp/django.sock",
        "check-local" => "disable",
        "min-procs" => 1,
        "max-procs" => 1,
      )
    ),
    "/django-admin.fcgi" => (
      (
        "bin-path" => "/home/django_project/django-admin.fcgi",
        "socket" => "/tmp/django-admin.sock",
        "check-local" => "disable",
        "min-procs" => 1,
        "max-procs" => 1,
      )
    )
  )
  server.error-handler-404 = "/django.fcgi"
}
Plik django.fcgi:
#!/usr/local/bin/python2.4
import os, sys
sys.path += ['/home'] 
from flup.server.fcgi import WSGIServer
from django.core.handlers.wsgi import WSGIHandler
os.environ['DJANGO_SETTINGS_MODULE'] = 'django_project.settings'
WSGIServer(WSGIHandler()).run()

Później opiszę dokładniej sam kod Django.

Posted in  | Tagi , ,  | 4 comments

Lightpd & FastCGI vs. Apache & mod_php,mod_python...

Opublikowane przez Jarosław Zabiełło Mon, 24 Apr 2006 17:40:00 GMT

Serwer Apache jest bardzo popularnym i solidnym serwerem www. Nie dziwi więc powszechne używanie jego modułow do obsługi Perla, PHP czy Pythona. Uzywanie mod_php jest wręcz nagminne (cały serwis sourceforge.net stoi na mod_php). Podobnie twórcy pythonowego frameworku Django zalecają używanie mod_pythona. W dokumentacji piszą, że:

“jeśli chcesz używać Django na serwerze produkcyjnym, uzywaj Apache’a z mod_pythonem (...) Django wymaga Apache 2.x oraz mod_python 3.x.”

Zupełnie inne podejście zalecają twórcy frameworka Ruby on Rails. Nie dość, że zalecają użycie modułu FastCGI to na dodatek, zalecają (o grozo:) używanie innego serwera www: Ligttpd. Które podejście jest lepsze?

Problem z uniwersalnością

Korzystanie z modułów Apache’a wiąże nas na dobre i złe z tym serwerem. korzystanie z fastcgi daje nam większe pole manewru. Możemy korzystać z Apache, Lighttpd, IIS i innych serwerów www.

Problem stabilności serwera

Jakikolwiek błąd w skryptach pracujących w trybie mod_php czy mod_python odbija się bardzo niebezpiecznie na stabilności całego serwera. Jeden błędnie działający skrypt może zawiesić cały serwer! Dotyczy to nie tylko Apache, ale także windowsowego IIS i modułów ISAPI. Co ciekawe, moduł ISAPI dla języka PHP jest wiecznie w fazie niestabilnej i testowej. Co z tego, że jest szybszy niż klasyczny CGI, skoro jeden drobny błąd w skrypcie PHP potrafi zawiesić cały serwer i unieruchomić cała sieć intranetową na nim opartą? To jeden z powodów dla których PHP nie nadaje się najlepiej do pracy pod Windowsami (wiem, że istnieje Apache/win32 ale z różnych powodów, których tu nie będę rozwijał, korporacyjne sieci intranetowe oparte na Windows Server wolą używać IIS)

Procesy FastCGI są izolowane od serwera. Jak któryś się zawiesi, to serwerowi nic się nie stanie. Proces można ubić, zrestartowac. Daje to znacznie większą stabilność pracy serwera.

Problem bezpieczeństwa

Każdy skrypt odpalany w trybie mod_php na Apache’u jest odpalany w kontekście tego samego uzytkownika. Tworzy to dosyć poważną dziurę bezpieczeństwa na serwerach hostingowych. Każdy użytkownik na serwerze za pomocą prostego kodu PHP może przeglądać źródła dowolnego pliku innych użytkowników. Wszyscy mają dostęp do wszystkich! Napisałem nawet swego czasu prosty skrypt wedrujący po całym sourceforge.net i przeglądający źródła wszystkich dostępnych tam projektów. :) Każdy, średnio zaawansowany programista z łatwością napisze sobie taki kod. Nie ma znaczenia, czy to będzie Perl (mod_perl), Python (mod_python) czy PHP (mod_php).

W FastCGI tego problemu nie ma. Każdy użytkownik może pracować na swoich prawach w pełnej izolacji. Wystarczy każdemu przydzielić po procesie FastCGI na prawach danego użytkownika.

Marnowanie możliwości Apache’a 2.x

To może nie jest najważniejszy problem. Apache 2.x potrafi pracować w trybie wielowątkowym (worker) lub wieloprocesowym (prefork) który jest zgodny ze starym Apache 1.x . Ten pierwszy jest nowocześniejszy, szybszy i pożera mniej pamięci. Problem jednak w tym, że najbardziej popularny język skryptowy nie posiada poprawnie zaimplementowanej wielowątkowości. Jeśli więc chcesz używać PHP, to zapomnij o trybie workera. Musisz pracować w starym trybie prefork. Zajmuje to więcej pamięci, ale za to nie będzie problemów z różnymi bibliotekami PHP.

Problem zasobożerności

Gdy uruchamiamy Apache 1.x lub Apache 2.x w trybie prefork, uruchamiamy szereg podprocesów. Każdy z nich zajmuje określoną ilość pamięci. Każdy z zainstalowanych modułów… również. A to przecież nie ma sensu! W praktyce nie chcemy wykorzystywać wszystkich procesów do przetwarzania mod_pythona. Większość procesów jest zajęta podawaniem statycznej wartości (obrazki, pliki ze stylami kaskadowymi, skryptami JavaScript itp).

FastCGI pozwala na ograniczenie ustalenie ilości swoich procesów w sposób niezależny od wszystkich procesów serwera www. Np. możemy sobie zadecydować, aby 100 procesów zajmowało się wartością statyczną, a tylko 10 wartością dynamiczną. Takie coś jest niemożliwe wypadku korzystania z mod_php czy mod_pythona. A oszczędności na zużyciu pamięci będą kolosalne!

Np. można załozyć, że pojedyńczy proces Apache podczas serwowania statycznych stron zajmie nie więcej niż ok 5 MB pamięci. Natomiast w wypadku użycia mod_ruby może wzrosnąć nawet do ok. 20-30MB na proces. Użycie zatem 100 procesów Apache’a z tylko 10 procesami FastCGI zużyje ok. 800MB pamięci. Zaś użycie mod_ruby bez problemu pożre nawet i 3GB!

Dlaczego Lighttpd a nie Apache?

Na końcu chciałbym jeszcze zastanowić się czy twórcy Railsów nie mają racji i czy w ogóle nie warto zrezygnować z Apache na rzecz Lighttpd? Są generalnie 3 powody za i jeden przeciw.

Za: lepiej dopracowany moduł FastCGI

Moduł FastCGI do Apache’a ma opinię nie do końca stabilnego i dopracowanego. To powoduje obawę większości użytkowników od używania go dla serwera Apache. Z kolei serwer Lighttpd od samego początku kładł nacisk na dobrą implementację swego modułu FastCGI. Jest on stabilniejszy i lepiej dopracowany niż pod Apachem.

Za: Wbudowana możliwość rozkładania obciążenia

Lighttpd ma wbudowany mechanizm rozkładania ruchu na wiele serwerów. Jeśli więc nawet ktoś upiera się przy Apache’u, to w wypadku kiedy jeden serwer nie jest wystarczający do obsługi ruchu, może warto rozważyć aby postawić na froncie jeden serwer Lighttpd który by rozkładał ruch pomiędzy kilka serwerów?

(Najnowszy Apache 2.2 ma posiadać podobny mechanizm. Jednak jest to jeszcze bardzo świeża wersja, która (w momencie pisania tego tekstu) nie ma nawet prekompilowanej instalacji dostępnej dla Windowsów oraz nie wiadomo jak jest ze stabilnością wszystkich modułów.)

Za: Lighttpd jest szybszy

O ile najnowszy Apache 2.2 nie zmieni sytuacji, to wszystko wskazuje na to, ze Lighttpd jest po prostu szybszy. Nie tylko zdecydowanie szybciej podaje statyczne dane ale także szybciej działa PHP pod Lighttpd niż mod_php pod Apachem. To nie są drobne różnice. To są różnice rzędu 2-3 razy!

Przeciw: Apache ma więcej modułów

Właściwie jedynym powodem aby używać Apache zamiast Lighttpd jest to, że do Apache napisano znacznie więcej różnych modułów. Ale szczerze mówiąc, w praktyce to rzadko ma znaczenie. Większość osób uzywa głównie kilku modułów. Zaś Lighttpd posiada praktycznie wszystko, co potrzeba: mod_fastcgi, mod_cgi, mod_redirect, mod_access, mod_auth, mod_compress, mod_webdav, mod_ssl, mod_alias, mod_proxy, mod_rewrite, itp. itd.

Lighttpd + FastCGI. To działa!

Na szczęście, dzięki standardowi WSGI, użytkownicy Pythona nie muszą słuchać zaleceń developerów Django. Teraz praktycznie wszystkie frameworki Pythona pozwalają dzięki WSGI korzystać z dobrodziejstw FastCGI. Ja również na swoim serwerze wyrzuciłem Apache’a i zastapiłem go Lighttpd. Podaje zarówno strony statyczne jak i obsługuje ten blog, a nawet PHP 5.1. Nawet Plone mi udało mi się uruchomić z Lighttpd na przodzie.

Posted in , ,  | Tagi , , ,  | 11 comments