Data publikacji: Apr 24, 2011 10:8:50 AM
Problem można streścić w kilku podpunktach:
potrzebuję uruchomić serwis oparty na Kohana na produkcyjnym serwerze opartym o Debian 6.0 i jako serwer WWW użyć nginx,
chcę skorzystać z php-fpm (najlepiej) lub z php-cgi,
chcę mieć możliwość użycia xdebug do zdalnego odpluskwiania aplikacji,
chcę aby moduł xdebug był ładowany tylko wtedy gdy odpluskwiam aplikację - nie wcześniej, utrata wydajności związana z użyciem xdebug jest zbyt duża i nie można sobie pozwolić na nią na produkcyjnym środowisku,
chcę w czymś podłączyć się i debugować aplikację - VIM, Komodo, Eclipse. Preferowane coś ze składnią VIM (Komodo ma tryb emulacji, do Eclipse jest eclim).
Debian 6.0 nie ma domyślnie php5-fpm (jest tylkp php5-cgi) - muszę skorzystać z repozytorium http://www.dotdeb.org/. Repozytorium zawiera oprócz najnowszego PHP 5.3 również nginx 1.0. Zaczynajmy od instalacji, wg. http://www.dotdeb.org/instructions/. Później apt-get update.
Następnie instalujemy to co potrzebujemy:
# apt-get install nginx-full
# apt-get install php5-fpm php5-dev php-pear
# pecl install xdebug
Chcę stworzyć dwie konfguracje (pool) do php-fpm - jeden z wyłączonym xdebug, drugi z włączonym. Produkcyjne będę korzystał z tego pierwszego. W przypadku włączenia odpluskwiania (parametr ?XDEBUG_SESSION_START=, ciasteczko XDEBUG_SESSION) chcę aby nginx automatycznie przełączył się na drugiego. W ten sposób uniknę strat wydajności z jakimi wiąże się włączenie xdebug na stałe. Początkowo myślałem, że wystarczy stworzyć dwie konfiguracje - produkcyjną:
# cat /etc/php5/fpm/pool.d/www.conf
[www]
...
i drugą będącą kopią powyższej ale z włączonym xdebug:
# cp /etc/php5/fpm/pool.d/www.conf /etc/php5/fpm/pool.d/debug.conf
# vim /etc/php5/fpm/pool.d/debug.conf
[debug]
...
php_admin_value[extension] = "xdebug.so"
Niestety nie działało to jak chciałem - druga konfiguracja segfaultowała. Doczytałem, że xdebug musze właczyć jako zend_extension. php-fpm nie pozwala na dopisanie:
php_admin_value[zend_extension] = /usr/lib/php5/20090626+lfs/xdebug.so
Myślałem o dopisaniu do php-fpm obsługi zend_extension, lecz doczytałem, że php-fpm zaczyna "swoją rolę" po załadowaniu wszystkich zend_extensions - więc ich doładowanie w konfiguracji i tak się na nic nie zda.
Wymyśliłem drugie rozwiazanie - dwie osobne instancje php-fpm - nie jako poole, lecz dwa skrypty w /etc/init.d/php-fpm oraz dwa pliki /etc/php5/fpm/php-fpm.conf.
Zauważyłem, że php-fpm idzie mi na ręke udostępniając opcję wywołania -y do określenia innej lokalizacji php-fpm.conf oraz -d pozwalającą mi na załadowanie xdebug poprzez: -dzend_extension=/usr/lib/php5/20090626+lfs/xdebug.so:
# php-fpm --help
Usage: php [-n] [-e] [-h] [-i] [-m] [-v] [-t] [-p <prefix> ] [-c <file>] [-d foo[=bar]] [-y <file>]
-d foo[=bar] Define INI entry foo with value 'bar'
-y, --fpm-config <file>
Specify alternative path to FastCGI process manager config file.
Stworzyłem nowy /etc/init.d/php-fpm-debug na bazie /etc/init.d/php-fpm. Skrypty zmodyfikowałem tak aby poprawnie działały funkcje start i stop (szczególnie stop, która potrafiła zabić obie instancje php-fpm choć zamykałem tylko jedną z nich). Koniecznie musiałem stworzyć sobie kopię pliku wykonywalnego /usr/sbin/php5-fpm poprzez twardy link (chciałem aby start-stop-daemon wyraźnie widział różnice pomiędzy oboma instancjami php-fpm):
# ln /usr/sbin/php5-fpm /usr/sbin/php5-fpm-debug
Dodatkowo dopisałem poprawną obsługę funkcji reload.
Zduplikowałem konfigurację php-fpm:
# cp /etc/php5/fpm/php-fpm.conf /etc/php5/fpm/php-fpm-debug.conf
Na potrzebę skryptów init.d (w nazwie mają php5-fpm), stworzyłem stosowne symlinki:
# cd /etc/php5/fpm
# ln -s php-fpm.conf php5-fpm.conf
# ln -s php-fpm-debug.conf php5-fpm-debug.conf
Plik /etc/php5/fpm/php-fpm-debug.conf odpowiednio zmodyfikowałem:
--- /etc/php5/fpm/php-fpm.conf 2011-04-04 14:58:03.000000000 +0200
+++ /etc/php5/fpm/php-fpm-debug.conf 2011-04-26 20:28:49.181371333 +0200
@@ -22,12 +22,12 @@
; Pid file
; Note: the default prefix is /var
; Default Value: none
-pid = /var/run/php5-fpm.pid
+pid = /var/run/php5-fpm-debug.pid
; Error log file
; Note: the default prefix is /var
-; Default Value: log/php-fpm.log
-error_log = /var/log/php5-fpm.log
+; Default Value: log/php-fpm-debug.log
+error_log = /var/log/php5-fpm-debug.log
; Log level
; Possible Values: alert, error, warning, notice, debug
@@ -69,5 +69,5 @@
; To configure the pools it is recommended to have one .conf file per
; pool in the following directory:
-include=/etc/php5/fpm/pool.d/*.conf
+include=/etc/php5/fpm/pool-debug.d/*.conf
Stworzyłem plik /etc/php5/conf.d/xdebug.ini o następującej treści:
xdebug.profiler_append = 0
xdebug.profiler_enable = 1
xdebug.profiler_enable_trigger = 0
xdebug.remote_enable=1
xdebug.remote_autostart = 0
xdebug.remote_handler = "dbgp"
xdebug.remote_connect_back = 1
xdebug.remote_mode = "req"
xdebug.remote_port = 9000
W ramach dwóch instancji mam dwa poole - jeden "www", który jest uruchamiany poprzez php-fpm (/etc/php5/fpm/pool.d/www.conf) oraz jego kopię - również nazywającą się "www", która jest uruchamiana poprzez php-fpm-debug (/etc/php5/fpm/pool-debug.d/www.conf). Podstawowa różnica to port, na którym nasłuchują: php-fpm / www nasłuchuje na 9000 a php-fpm-debug / www na 9001. Dodatkowo, w przypadku php-fpm / www pozostawiłem dynamiczną liczbę procesów:
pm = dynamic
pm.max_children = 50
pm.start_servers = 20
pm.min_spare_servers = 5
pm.max_spare_servers = 35
a dla php-fpm-debug / www zdecydowałem się na sztywną liczbę procesów:
pm = static
pm.max_children = 5
Całą konfigurację załączam do niniejszego postu w postaci załacznika php-fpm.tar.gz.
Całość uruchamiamy i sprawdzamy czy wszystko działa:
# /etc/init.d/php-fpm start
# /etc/init.d/php-fpm-debug start
# ps auxf
root 2061 0.0 0.5 52880 2764 ? Ss 20:29 0:00 php-fpm: master process (/etc/php5/fpm/php-fpm.conf)
www-data 2062 0.0 0.4 52872 2384 ? S 20:29 0:00 \_ php-fpm: pool www
www-data 2063 0.0 0.4 52872 2384 ? S 20:29 0:00 \_ php-fpm: pool www
...
www-data 2081 0.0 0.4 52872 2384 ? S 20:29 0:00 \_ php-fpm: pool www
root 2090 0.0 0.5 53080 2780 ? Ss 20:29 0:00 php-fpm: master process (/etc/php5/fpm/php-fpm-debug.conf)
www-data 2091 0.0 0.4 53080 2412 ? S 20:29 0:00 \_ php-fpm: pool www
www-data 2092 0.0 0.4 53080 2412 ? S 20:29 0:00 \_ php-fpm: pool www
www-data 2093 0.0 0.4 53080 2412 ? S 20:29 0:00 \_ php-fpm: pool www
www-data 2094 0.0 0.4 53080 2412 ? S 20:29 0:00 \_ php-fpm: pool www
www-data 2095 0.0 0.4 53080 2412 ? S 20:29 0:00 \_ php-fpm: pool www
# netstat -ltpn
tcp 0 0 127.0.0.1:9000 0.0.0.0:* LISTEN 800/php-fpm.conf)
tcp 0 0 127.0.0.1:9001 0.0.0.0:* LISTEN 942/php-fpm.conf)
Następnym krokiem było stworzenie konfiguracji nginx:
stworzyłem /etc/nginx/includes/fastcgi_params_real
stworzyłem /etc/nginx/conf.d/backends.conf - zwiera on definicję backendów php-fpm-www (czyli php-fpm / www) oraz php-fpm-debug-www (czyli php-fpm-debug / www)
zmodyfikowałem /etc/nginx/sites-enabled/debug (formalnie powinien utworzyć nowy plik a nie modyfikować definicji default:80, lecz potrzebowałem szybko przetestować konfigurację)
Wszystkie pliki załączyłem jako nginx.tar.gz.
W tym ostatnim pliku najważniejsza jest sekcja odpowiedzialna za obsługę plików *.php:
location ~ ^(.+\.php)(.*)$ {
...
# Domyślny backend
set $fastcgi_pass php-fpm-www;
# Czy sesja debugera? Jeżeli tak, to zmieniamy backend na
# php-fpm-debug-www.
if ($cookie_XDEBUG_SESSION) {
set $fastcgi_pass php-fpm-debug-www;
}
if ($request_uri ~ "XDEBUG_SESSION_START=") {
set $fastcgi_pass php-fpm-debug-www;
}
if ($request_uri ~ "XDEBUG_SESSION_STOP") {
set $fastcgi_pass php-fpm-www;
}
# Czy wogóle zezwalamy na podłączenie debugera?
set $debug "no";
# Zezwalamy, jak jest ustawione określone ciasteczo ...
if ($cookie_XDEBUG = "0x123456789") {
set $debug "yes";
}
# ... lub żądanie jest z określonego adresu.
if ($remote_addr ~ ^192\.168\.56\.1$) {
set $debug "yes";
}
# Jeżeli nie zezwolono na podłączenie debugera, to wymuszenie backendu
# spowrotem na php-fpm-www.
if ($debug = "no") {
set $fastcgi_pass php-fpm-www;
}
fastcgi_pass $fastcgi_pass;
...
}
Już wyjaśniam "algorytm":
zmienna $fastcgi_pass zawiera backend, z którym się połączymy - domyślnie przyjmuje że jest to "php-fpm-www",
jeżeli stwierdzono, że podano parametr ?XDEBUG_SESSION_START lub jest ustawione ciasteczko XDEBUG_SESSION to wybierany jest backend "php-fpm-debug-www",
jeżeli stwierdzono, że podano parametr ?XDEBUG_SESSION_STOP to wymuszany jest backend "php-fpm-www",
nie chcę aby dowolna osoba podała ?XDEBUG_SESSION_START i rozpoczęła odpluskwianie mojej aplikacji na serwerze produkcyjnym - dlatego wprowadziłem zabezpieczenia: aby odpluskwianie było możliwe musi być dodatkowo ustawione ciasteczo XDEBUG z odpowiednią wartością lub połaczenie musi nastąpić z określonego adresu IP. Jeżeli którykolwiek z tych warunków jest spełniony zmienna $debug ma wartość "yes", jeżeli nie - to wartość "no",
jeżeli wartość zmiennej $debug to "no" to wymuszany jest backend "php-fpm-www",
ostatecznie przekazujemy żądanie do backendu ze zmiennej $fastcgi_pass.
Stworzyłem plik phpinfo.php z wywołaniem phpinfo(). W przypadku startu bez jakiegokolwiek parametru otrzymuję:
This program makes use of the Zend Scripting Language Engine:Zend Engine v2.3.0, Copyright (c) 1998-2011 Zend Technologies
with Suhosin v0.9.32.1, Copyright (c) 2007-2010, by SektionEins GmbH
Gdy dodam parametr ?XDEBUG_SESSION_START (i oczywiście łaczę się z uprawnionego IP lub ustawiłem dodatkowo ciasteczko XDEBUG=0x123456789) otrzymuję:
This program makes use of the Zend Scripting Language Engine:Zend Engine v2.3.0, Copyright (c) 1998-2011 Zend Technologies
with Xdebug v2.1.1, Copyright (c) 2002-2011, by Derick Rethans
with Suhosin v0.9.32.1, Copyright (c) 2007-2010, by SektionEins GmbH
xdebug support
Version
enabled
2.1.1
Teraz, gdy ciasteczko XDEBUG_SESSION jest ustawione - xdebug jest cały czas właczony. Dodanie parametru ?XDEBUG_SESSION_STOP wyłącza xdebug i powracamy do stanu początkowego.
Mamy już wszystko skonfigurowane po stronie serwera - teraz czas na klienta xdebug.
Pobieramy plugin z adresu: http://www.vim.org/scripts/script.php?script_id=2508. Aktualnie dostępna jest wersja 1.0.2. Pliki rozpakowujemy i umieszczamy w katalogu .vim/plugin w katalogu domowym użytkownika. Skrypt nie jest perfekcyjny i potrzebuje łatek:
możliwość określania mapowań: zdalny plik -> lokalny plik,
możliwość szybkiego ustawienia/usunięcia bezwarunkowego breakpointu,
możliwość zobaczenia gdzie zostały ustawione breakpointy.
Łatki są w załączniku vim.diff. Należy je nałożyć na rozpakowane pliki:
cd .vim/plugins
cat vim.diff | patch -p0
Po uruchomieniu edytora należy ustawić mapowania:
:let g:debuggerMaps ="{'/home/quaker/www':'/home/quaker/Projekty/remote'}"
gdzie:
/home/quaker/www to katalog bazowy aplikacji po stronie serwera,
/home/quaker/Projekty/remote to lokalny katalog bazowy aplikacji - tutaj podmountowałem przy pomocy sshfs katalog zdalny
Rozpoczynamy debugowanie poprzez wciśnięcie klawisza F5. Następnie w przeglądarce wybieramy adres z ustawionym ?XDEBUG_SESSION_START= i powinniśmy dostać informację o rozpoczęciu debugowania:
Obsługa została opisana w okienku HELP__WINDOW.
W pierwszej kolejności właczamy tryb kompatybilności z vim. Z menu wybieramy Edit > Preferences. Następnie z drzewa Category wybieramy Editor > Key Bindings. Później wybieramy Vi z rozwijanej listy Key Binding Schemes.
Tryb vi jest ubogi, lecz zapobiega ciągłemu pojawianiu się litery "i" jeżeli zaczynami pisać :-)
Uruchomienie debugera jest równie proste. Najpierw należy poinformować Komodo, aby debuger słuchał na porcie 9000. Z menu wybieramy Edit > Preferences. Następnie z drzewa Category wybieramy Debuger > Connection. Później wybieramy port 9000.
Następnie w przeglądarce wybieramy odnośnik i podajemy parametr ?XDEBUG_SESSION_START=dowolny_ciag_znaków. Jeżeli po raz pierwszy rozpoczęliśmy sesję debugera dla tego projektu Komodo pokaże następujący komunikat:
W skrócie - informuje on nas, że nie można otworzyć pliku, którego nazwa została właśnie przesłana przez debuger i pyta czy utworzyć mapowanie (czyli powiązanie zdalnego pliku z lokalnym). Wybieramy Yes. Otworzy się następujący dialog, w którym musimy określić pole Maps to:
Zawartość katalogu /home/quaker/www z serwera 192.168.56.101 podmountowałem przy pomocy sshfs do katalogu /home/quaker/Projekty/remote. Pozostaje wprowadzić tylko tą ścieżkę i wybrać OK. Wybieram pozostawiam Save mapping to: Global Preferences - pozwoli mi to debugować aplikację bez konieczności otwierania już istniejącego projektu.
W efekcie otrzymuję ekran z sesją debugera:
Na poczatek trzeba zainstalować stosowny pakiet. Dodatkowo potrzebujemy narzędzia spawn-fcgi:
# apt-get install php5-cgi spawn-fcgi
Musimy postąpić podobnie jak w przypadku php-fpm: czyli dwie instancje. Skrypt /etc/init.d/php5-cgi (i bazujący na nim /etc/ini.d/php5-cgi-debug) musimy stworzyć sami. Pliki załaczam jako php-cgi.tar.gz.
W przypadku konfiguracji nginx drobne zmiany:
dla "formalności" ciąg "php-fpm-www" zastępujemy przez "php-cgi" a ciąg "php-fpm-debug" ciągiem "php-cgi-debug",
zmieniamy porty z 900x na 800x w definicjach backendów (/etc/nginx/conf.d/backends.conf).