niedziela, 9 czerwca 2013

Konfiguracja Apache z php-fpm na CentOS


    Zapewne większość osób siedząca choć trochę w IT miała styk z PHP - ma sporo zarówno zalet jak i wad. By "dobrze" pisać w PHP trzeba mieć już trochę doświadczenia, z czego wiele osób nie zdaje sobie sprawy. Na pewno jest to coś, co każdy początkujący webmaster przerabiał instalując serwer Apache wraz z mod_php. Taka konfiguracja jest ekstremalnie prosta, oraz oczywiście niewydajna przy zastosowaniach bardziej poważnych. Czemu tak jest, to temat na inną okazje.



    Faktem jest, że przez wiele lat z powodu słabej wydajności i dużego zapotrzebowania na pamięć konfiguracji Apache+mod_php, bardzo często używany był trochę inny zestaw, czyli Apache+mod_fastcgi+php-cgi. Natomiast ostatnio pojawił się nowy gracz - php-fpm.

    W skrócie, php-fpm umożliwia między innymi całkowite rozdzielenie aplikacji PHP od serwera www (dzięki czemu nginx+php-fpm zdobywa rzesze fanów), mniejszy narzut na pamięć w porównaniu do mod_fastcgi+php-cgi (to zależy od konfiguracji, ale zwykle tak) oraz o wiele łatwiejszą i elastyczniejszą konfiguracje.

    To co dzisiaj zrobimy to konfiguracja wydajnego serwera dla aplikacji PHP opartego o zestaw Apache+mod_fastcgi+php-fpm+apc+memcache w oparciu o system CentOS 6.4.

Zaczynamy

Ponieważ mod_fastcgi nie ma w standardowych repozytoriach CentOS, zainstalujemy go z repozytorium RPMforge

# wget http://pkgs.repoforge.org/rpmforge-release/rpmforge-release-0.5.3-1.el6.rf.x86_64.rpm
# yum install rpmforge-release-0.5.3-1.el6.rf.x86_64.rpm
# yum install httpd mod_fastcgi php php-fpm memcached php-pecl-memcache php-pecl-apc

Ważna rzecz, to wyłącznie mod_php, edytujemy plik /etc/httpd/conf.d/php.conf i zakomentowujemy linie ładujące moduł mod_php:

#<IfModule prefork.c>
#  LoadModule php5_module modules/libphp5.so
#</IfModule>
#<IfModule worker.c>
#  LoadModule php5_module modules/libphp5-zts.so
#</IfModule>


Resztę pliku zostawiamy bez zmian. Jeżeli mamy dedykowaną maszynę pod nasz serwis to nie mamy również potrzeby używania suexec, ustawiamy więc "FastCgiWrapper Off" w pliku /etc/httpd/conf.d/fastcgi.conf. Ostatnia ważna rzecz to włączenie modułu mpm_worker poprzez odkomentowanie odpowiedniej linii w pliku /etc/sysconfig/httpd.

Tworzymy virtual host dla naszego testowego serwisu - nazwiemy go test.pnet. Zawartość pliku /etc/httpd/conf.d/test.pnet.conf

<VirtualHost *:80>

    ServerAdmin     iz0@o2.pl
    ServerName      test.pnet
    ServerAlias     *.test.pnet
    DocumentRoot    /var/www/test.pnet
    ErrorLog        logs/test.pnet-error.log
    CustomLog       logs/test.pnet-access.log combined
    LogLevel        warn

    <Directory /var/www/test.pnet>
        Options FollowSymLinks Indexes +ExecCGI
        AllowOverride None
        Order deny,allow
        Allow from all
    </Directory>


    Alias /php5.fastcgi /var/www/fastcgi/php5.fastcgi
    FastCGIExternalServer /var/www/fastcgi/php5.fastcgi -socket /var/run/php-fpm/test.pnet_fpm.sock -pass-header Authorization

    AddHandler php5-script .php
    Action php5-script /php5.fastcgi virtual

    <Location /php-fpm>
        Options +ExecCGI
        SetHandler php5-script
    </Location>
</VirtualHost>


Bystre oko, zauważy że używam gniazda plikowego, zamiast połączenia TCP. Gniazda plikowe, (przynajmniej w teorii) mają mniejszy narzut na obsługę jak połączenia TCP, ale co ważniejsze ustawiając odpowiednio uprawnienia na pliku /var/run/php-fpm/test.pnet_fpm.sock chronimy naszą aplikacje przed intruzami zarówno z zewnątrz maszyny, jak i wewnątrz.

Dodatkowo, dyrektywa AllowOverride zawsze powinna być ustawiona na None - chyba że mamy bardzo ważny powód by poświęcić wydajność serwera (nawet do 50%) na rzecz wygody użytkowników.

Powinniśmy również przestawić tryb selinux na mniej restrykcyjną politykę przy pomocy polecenia "setenforce Permissive" (domyślnie ustawiona jest w trybie Enforcing). Jeżeli tego nie zrobimy, to prawdopodobnie selinux będzie powodować problemy typu odmowa dostępu, np:

 (13)Permission denied: FastCGI: failed to connect to server "/var/www/fastcgi/php5.fastcgi": connect() failed

Tworzymy teraz katalog domowy aplikacji /var/www/test.pnet:

# mkdir /var/www/test.pnet
# cat > /var/www/test.pnet/index.php
<?php
phpinfo();
phpinfo(INFO_MODULES);
?>
^C

# mkdir /var/www/test.pnet/apc
# cp /usr/share/doc/php-pecl-apc-3.1.9/apc.php /var/www/test.pnet/apc/index.php

By przetestować konfiguracje utworzyliśmy plik index.php wywołujący funkcje phpinfo. Dodatkowo skopiowaliśmy skrypt pokazujący statystyki APC. Gdy już całość będzie działać skopiujemy do katalogu /var/www/test.pnet naszą aplikacje.

Od strony Apache, to już wszystko. Teraz szybka konfiguracja php-fpm:

# cd /etc/php-fpm.d
# mv www.conf test.pnet.conf

W pliku konfiguracyjnym jest sporo komentarzy, które dosyć dobrze opisują znaczenie samych zmiennych. By się specjalnie nie powtarzać podaje poniżej same przykładowe ustawienia:

[test.pnet]
listen=/var/run/php-fpm/test.pnet_fpm.sock

listen.owner = apache
listen.group = apache
listen.mode = 0660

user = apache
group = apache

pm = dynamic
pm.max_children = 15
pm.start_servers = 3
pm.min_spare_servers = 3
pm.max_spare_servers = 7
pm.max_requests = 5000
pm.status_path = /php-fpm/status

ping.path = /php-fpm/ping
request_terminate_timeout = 300s
request_slowlog_timeout = 240s

slowlog = /var/log/php-fpm/test.pnet-slow.log
 

rlimit_files = 10240
 

chdir = /var/www/test.pnet

php_admin_value[error_log] = /var/log/php-fpm/test.pnet-error.log
php_admin_flag[log_errors] = on
php_value[session.save_handler] = memcache
php_value[session.save_path] = "unix:/var/run/memcached/php_sesscion.sock"


Jak widać do przechowywania sesji używamy memcached poprzez gniazdo plikowe /var/run/memcached/php_sesscion.sock - z tych samych powodów co wcześniej. Konfiguracja mamcached w /etc/sysconfig/memcached:

PORT="11211"
USER="memcached"
MAXCONN="1024"
CACHESIZE="64"
OPTIONS=" -s /var/run/memcached/php_sesscion.sock -a 666 "


Nie pozostaje nam już nic innego jak uruchomić wszystkie usługi:

# service memcached start
# service php-fpm start
# service httpd start

Parę uwag końcowych

Jeżeli sprawdzicie listę modułów Apache poleceniem "apachectl -t -D DUMP_MODULES" okaże się, że sporo z nich jest nam niepotrzebnych. Warto wyłączyć niepotrzebne moduły jeżeli zależy nam na ograniczeniu zużycia pamięci oraz wydajności - szybszym tworzeniu nowych procesów. Szczególnie jeżeli używamy Apache z mod_prefork - przy konfiguracji opisanej tutaj zalecany jest mod_worker.

Sprawdzić status serwisu php-fpm możemy poprzez uri /php-fpm/status, a memcache przy pomocy polecenia:

# echo stats | nc -U  /var/run/memcached/php_sesscion.sock