Ósmy artykuł z cyklu Konfigurujemy VPS - kontynuacja opisu konfiguracji nginx-a. Opisuję tu co ciekawego można pisać w location, czyli jak uruchamiać skrypty albo budować reverse-proxy.
Uwaga: przed czytaniem tego artykułu trzeba przeczytać ogólny opis konfiguracji nginxa.
Reverse proxy
Technika reverse-proxy polega na przykryciu nginxem innego serwera HTTP, działającego głębiej. Tym serwerem może być nawet Apache, ale często jest nim specjalizowany serwerek HTTP, na przykład Mongrel (Ruby on Rails), Paster lub CherryPy (wiele stosów pythonowych), Zope/Plone, Tomcat/Coyote (Java)...
Przykrycie takiego programu nginxem zwiększa wydajność (serwer aplikacyjny nie zajmuje się statycznymi obrazkami, nie traci czasu na komunikację - patrz szczegółowy opis w części 5) i bezpieczeństwo (nginx odfiltruje błędne żądania, ograniczy się do wybranych urli, nałoży w razie potrzeby reguły dostępu).
Wiele osób reaguje na reverse-proxy pytaniem: jak to, jeszcze jeden serwer? A jest to kombinacja bardzo podobna do np. FastCGI.
Przy reverse proxy frontendowy program odbiera żądania i przez lokalne połączenie TCP/IP, przy pomocy uzgodnionego protokołu, przekazuje do właściwego serwera aplikacyjnego.
Przy FastCGI frontendowy program odbiera żądania i przez lokalne połączenie TCP/IP, przy pomocy uzgodnionego protokołu, przekazuje do właściwego serwera aplikacyjnego.
Różnicą jest tylko uzgodniony protokół. Przy reverse-proxy jest to po prostu HTTP, przy FastCGI specyficzny własny protokół (wymagający przepakowania danych).
To samo dzieje się przy konektorach do kontenerów Javowych i w wielu innych wypadkach.
Czasami ma się wybór, np. wiki MoinMoin może działać jako FastCGI i może działać jako samodzielny serwer (wart schowania za proxy).
Zapis konfiguracji reverse-proxy w nginx-ie wygląda następująco:
- w ramach bloku
http
umieszczamy dyrektywyupstream
definiujące wykorzystywane proxy, - w odpowiednich
location
umieszczamy właściwe dyrektywy proksujące.
Przykład:
# ... http { # ... upstream moj_backend { server localhost:8080; } server { # ... location /web/ { proxy_pass http://moj_backend/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_read_timeout 90; } } }
Kilka drobnych uwag:
- dyrektywy
upstream
nie są obowiązkowe, mógłbym napisać po prostuproxy_pass http://localhost:8080/;
(zapis zupstream
jest jednak wygodny np. gdyupstream
mam inne na maszynie testowej a inne na produkcyjnej a same dyrektywy proksy pozostają identyczne i mogą należeć do dzielonego includowanego pliku) - w ramach jednego
upstream
można wpisać wiele serwerów,nginx
zrealizuje w takiej sytuacji prosty load-balancing - zarówno dyrektyw
upstream
jak proksującychlocation
może być wiele, możemy proksować wiele backendów albo proksować jeden backend z różnych urli, - w szczególności można przeproksować cały ruch (wpisać dyrektywy
proxy w ramach
location /
).
A teraz rzecz ważna. Z poniższych napisów
# A location /web/ { proxy_pass http://moj_backend; } # B location /web/ { proxy_pass http://moj_backend/; } # C location /web/ { proxy_pass http://moj_backend/web/; } # D location /web/ { proxy_pass http://moj_backend/bip/; }
warianty A i C znaczą to samo, a B i D co innego. Bo kierują do innych
adresów. Żądanie /web/jakis/plik.html
trafi do serwera
aplikacyjnego:
- w wariantach A i C jako
/web/jakis/plik.html
, - w wariancie B jako
/jakis/plik.html
- w wariancie D jako
/bip/jakis/plik.html
Zasada jest prosta: jeśli dyrektywa proxy_pass
zawiera wyłącznie
nazwę serwera, bez URLa (pojedynczy slash w przypadku B to już URL),
to ścieżka przekazywana jest w całości, bez modyfikacji, tak jak
przyszła. Gdy adres proxy zawiera URL, nginx zdejmuje z ścieżki
prefiks dotyczący lokacji w której jest wpisana dyrektywa proxy (a
także czyści ścieżkę np. zastępując dir/dir2/../dir3
przez
dir/dir3
).
Uwaga: nginx nie koryguje URLi w wynikowych HTMLach (ogólnie, w żaden
sposób nie zmienia tekstu HTML). Dlatego trzeba zadbać by proksowana
aplikacja generowała odpowiednie linki (czyli, np. w wariancie B
umieściła w menu link do /web/strona.html
a nie do /strona.html
).
Serwery aplikacyjne i aplikacje webowe zwykle mają opcje
konfiguracyjne pozwalające dopasować urle do takiej sytuacji.
Nginx może za to korygować adresy w zwracanych nagłówkach Location i Refresh (tj. cele redirectów HTTP wygenerowanych przez backend). Aktywujemy to dyrektywą:
proxy_redirect http://localhost:8000/wewnurl/ http://supersajt.pl/web/;
albo bez nazwy maszyny:
proxy_redirect http://localhost:8000/wewnurl/ /web/;
albo wręcz:
proxy_redirect default;
(ostatnia forma odwraca dyrektywę proksy obok której się znajduje)
Fast CGI
Nginx potrafi obsługiwać także programy/skrypty FastCGI. Zapis wygląda następująco:
location ~ /myapp/.*\.fcgi$ { include /etc/nginx/fastcgi_params; fastcgi_pass 127.0.0.1:54217; fastcgi_index index.fcgi; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; }
Plik /etc/nginx/fastcgi_params
czasem jest dostępny w dystrybucji,
czasem nie, jeśli nie - można go zrobić samemu:
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 GATEWAY_INTERFACE CGI/1.1; fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; 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 REDIRECT_STATUS 200;
Znowu: ten include
jest nieobowiązkowy, można
także wpisać te dyrektywy (lub część z nich, nie wszystkie skrypty
potrzebuję kompletu zmiennych) bezpośrednio w ramach location
.
Zależnie od tego jak jest skonstruowane location
, może być
potrzebne inne zapisanie SCRIPT_FILENAME
(czasem można tam
wręcz wpisać jawną ścieżkę).
Nginx sam nie uruchamia żadnych skryptów, jedynie się z nimi
komunikuje. Program obsługujący FastCGI musi zostać uruchomiony
niezależnie, na przykład przy pomocy runita. Często
używany jest programik spawn-fcgi
, o którym ... w następnym
rozdziale.
PHP
Skrypty PHP uruchamiamy via FastCGI. Czyli na przykład:
location ~ \.php$ { include /etc/nginx/fastcgi_params; fastcgi_pass 127.0.0.1:51213; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; }
(oczywiście location
może być bardziej selektywne)
Uwaga: warto pokombinować przy konfiguracji tak, aby do interpretera PHP trafiały wyłącznie żądania wymagające używania skryptów. PHP potrafi też serwować statyczne pliki czy obrazki ale nginx zrobi to szybciej i mniejszym kosztem.
Od strony nginx-a na tym koniec. Jeszcze parę słów o uruchamianiu PHP jako FastCGI.
Tradycyjną metodą jest użycie programu spawn-fcgi
. Program pochodzi
z ... lighttpd (gdzie został napisany w tym samym celu). Na Ubuntu
czy Debianie najprościej go uzyskać po prostu instalując lighttpd:
$ sudo apt-get install lighttpd
a następnie w dowolny sposób blokując jego autouruchamianie (ja
brutalnie wpisałem exit
na początku /etc/init.d/lighttpd
, jest to
nieeleganckie ale za to ładnie pokazywane w czasie późniejszych
aktualizacji pakietów). Można też po prostu skopiować
/usr/bin/spawn-fcgi
do /usr/local/bin
a pakiet lighttpd
usunąć,
ale może to sprawić problemy po upgrade systemu do nowszej wersji.
Następnie trzeba ten programik wykorzystać. Podpierając się runitem
po prostu tworzę katalog /etc/sv/php-fcgi
, a w nim skrypt
run
o takiej treści:
#!/bin/sh CHILD_COUNT=4 PORT=51213 USER=www-data GROUP=www-data exec /usr/bin/spawn-fcgi -f /usr/bin/php-cgi \ -a 127.0.0.1 -p $PORT \ -P /var/run/fastcgi-php.pid \ -u $USER -g $GROUP \ -C $CHILD_COUNT \ -n
gdzie PORT
musi zgadzać się z adresem wpisanym w dyrektywie
fastcgi_pass
. Kluczowe znaczenie ma CHILD_COUNT
czyli ilość
działających procesów PHP, trzeba tu po prostu oszacować,
ile pamięci chcemy dać PHP.
Następnie:
$ sudo chmod a+x /etc/sv/php-fcgi $ sudo ln -s /etc/sv/php-fcgi /etc/service/
i w ciągu paru sekund pojawi się grupa procesów php-cgi
.
Ogromne zyski wydajnościowe w porównaniu do powyższej techniki (a także zwykłych metod uruchamiania PHP pod Apache) zapewnia podobno php-fpm. Nie testowałem jeszcze tego rozwiązania, więc nie opiszę jego konfiguracji, w sieci można znaleźć benchmarki w których
php-fpm
obsługuje 3-4 razy (!) więcej żądań na sekundę niżspawn-fcgi
(które z kolei ma niewielką przewagę nad Apache z mod_php).
Konfiguracja samego PHP: trzeba przepatrzyć /etc/php5/cgi/php.ini
(to ta wersja obowiązuje), z szczególnym uwzględnieniem:
-
bloku
Resource Limits
(w kontekście maszyn o małej pamięci warto wystroić zwłaszczamemory_limit
, potrzebny limit oczywiście zależy od używanych skryptów ale w praktyce można zaczynać od 8MB i podnosić, jeśli pojawiają się błędy) -
bloku
Error handling and logging
(gdzie arcystarannie wyłączamydisplay_errors
, za to włączamylog_errors
i ustawiamy jakiśerror_log
, pamiętając też, że użytkownik na którym działa PHP - czyliUSER
z powyższego skryptu - musi mieć prawo tam pisać)
Wystrojenie interpretera PHP to trochę bardziej złożone zagadnienie, temat jest popularny, a konfiguracja od tej strony ta sama co np. przy Apache, więc nie będę się o tym teraz rozpisywał.
Podsumowanie
Opisałem jak konfigurować Nginxa jako reverse-proxy i jak używać skryptów FastCGI. Ciąg dalszy (min. dyskusje wydajnościowe) nastąpi.