Ускорение Drupal. Настраиваем Nginx + Php-fpm + Apc на Debian

По "многочисленным" просьбам комментаторов статьи Настройка веб-сервера Nginx на Debian Squeeze как front-end к Apache cегодня рассмотрим очередной вариант ускорения Drupal путем настройкм веб-сервера без Apache. Сделаем это за счет применения связки Nginx + Php-fpm и задействуем APC (eaccelerator позже тоже попробуем).

В предыдущей статье мы настраивали Nginx как front-end к Apache для раздачи статики. С этой задачей Nginx справляется на отлично, но, к сожалению, nginx не имеет поддержки php, поэтому, чтобы польностью отказаться от Apache, мы будем использовать php-fpm (FastCGI Process Manager).
Принципе работы нашего веб-сервера будет следующим:

  1. Nginx принимает запросы от клиентов
  2. Статику nginx отдает самостоятельно
  3. Динамические запросы Nginx передает на php-fpm, который запущен локально на порту 9000.

Настраивать, как уже наверное все догадались, будем на Debian Squezze. Тестовый сайт называется testdrupal.it-oblako.ru.

Первоначальная установка всех компонентов

Обновляем локальный кэш пакетов:

apt-get update

Устанавливаем репозиторий dotdeb.org, вписываем в /etc/apt/sources.list следующую строку:

deb http://packages.dotdeb.org squeeze all

и выполняем команду

wget http://www.dotdeb.org/dotdeb.gpg && cat dotdeb.gpg | apt-key add -

Устанавливаем все компоненты:

 apt-get install nginx php-fpm mysql-server php5-gd php5-mysql php5-apc

Настройка nginx

Конфигурация nginx выглядит следующим образом:

# Редирект с www.testdrupal.it-oblako.ru на testdrupal.it-oblako.ru
server {
  server_name www.testdrupal.it-oblako.ru;
  rewrite (.*) http://testdrupal.it-oblako.ru$1;
}

server {
  listen 80;
  server_name testdrupal.it-oblako.ru;

  access_log /home/webmaster/domains/testdrupal.it-oblako.ru/logs/access.log;
  error_log /home/webmaster/domains/testdrupal.it-oblako.ru/logs/error.log;

  root /home/webmaster/domains/testdrupal.it-oblako.ru/html;
  
  location / {
	try_files $uri @drupal;
  }

  location ~ \.php$ {
     fastcgi_pass   127.0.0.1:9000;
     fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
     include        fastcgi_params;
  }

  location @drupal {
	fastcgi_pass 127.0.0.1:9000;
        fastcgi_index  index.php;
	fastcgi_read_timeout 600;
        fastcgi_param  SCRIPT_FILENAME      $document_root/index.php;
        fastcgi_param  QUERY_STRING         q=$uri&$args;
        fastcgi_param  REQUEST_METHOD       $request_method;
        fastcgi_param  CONTENT_TYPE         $content_type;
        fastcgi_param  CONTENT_LENGTH       $content_length;
        fastcgi_param  REDIRECT_STATUS      200;
        fastcgi_param  SCRIPT_NAME          /index.php;
        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;  
  } 	
}

Файл параметров тоже оставил дефолтный /etc/nginx/fastcgi_params

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;

# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param  REDIRECT_STATUS    200;

Настройка php-fpm

В файле /etc/php5/fpm/php-fpm.conf устанавливаем следующие параметры:

syslog.facility = daemon
syslog.ident = php-fpm
log_level = error
emergency_restart_interval = 12h
events.mechanism = epoll

Теперь переходим к настройке пула /etc/php5/fpm/pool.d/www.conf

user = webmaster
group = webmaster
pm = dynamic
pm.max_requests = 1500
security.limit_extensions = .php
php_admin_value[error_log] = /home/webmaster/domains/drupal-admin.ru/logs/fpm-php.www.error.log
php_admin_flag[log_errors] = on
php_admin_value[memory_limit] = 256M
php_admin_value[open_basedir] = "/home/webmaster:."
php_admin_value[upload_tmp_dir] = "/home/webmaster/tmp"
php_admin_value[session.save_path] = "/home/webmaster/tmp"

В данном случае тюнингом php-fpm я не занимался, поэтому использовал конфигурацию по умолчанию. В файле конфигурации /etc/php5/fpm/php-fpm.conf все достаточно подробно написано. Также можно обратиться к официальной документации.

Настройка APC

Для настройки apc я использовал следующие параметры в файле /etc/php5/conf.d/apc.ini:

extension=apc.so
; Включаем APC
apc.enabled=1    
; Количество сегментов памяти
apc.shm_segments=1      
; Размер одного сегмента памяти
apc.shm_size=64            

Результат

Я провел небольшое тестирование скорости отдачи главной страницы сайта с 10000 нодов для зарегистрированных пользователей с помощью ab. Результат достаточно неплохой 17 страниц в секунду сервер делает без проблем.
Конфигурация сервера: VDS на хостинге it-oblako.ru, виртуализация KVM.
Оперативная память: 1 Гб
Процессор: Xeon 3 Ггц
Полное описание тестирования можно посмотреть здесь.

Комментарии

Читал, что связка Nginx + Php-fpm + Apc выигрышная только на тарифах с ограниченной оперативной памятью , если памяти хватает то Nginx + Apache + mod_php + Apc будет тоже хорошим решением т.е. не уступающим.

В чем плюс в остальном Php-fpm перед апачем ?

Я вижу разницу в том, что Nginx + php-fpm + APC будет быстрее, чем Nginx + Apache + mod_php + APC, потому что не используется дополнительное звено Apache, который на каждом запросе будет тратить время на VirtualHost, на .htaccess, до того как обработать динамический запрос.
В целом для повышения производительности можно применять затюненный Apache в режиме Worker MPM. Кстати неплохо было бы сравнить на одном и том же железе Apache Worker MPM + mod_php vs Nginx + php-fpm. На abmysite.com пока что нет такого теста. Обязательно добавим.

Вроде как drupal.ru работает на apache http://www.drupal.ru/about/hostings ...

Да, вижу используется Nginx + Apache. Подозреваю, что это особенность хостинга, где они сейчас размещаются. Я так понимаю этот хостинг использует именно связку Nginx + Apache в продакшн.
Думаю, что нужно проводить тесты разных конфигураций на одном и том же железе, чтобы найти оптимальную конфигурацию. Видимо drupal.ru нашел эту конфигурацию на этом хостинге.

Спасибо за статью, помогло ускорить работу.

Привет!
А друпал ни как не увидит что включен APC? Отчет статусов в Друпале выводит такую запись

Upload progress Not enabled
Your server is not capable of displaying file upload progress. File upload progress requires an Apache server running PHP with mod_php.

С апачем он вроде писал что включен APC.

Попробуйте включить в apc.ini строчку apc.rfc1867 = On и перезапустите php-fpm

Добрый день! У меня nginx выдает 502 ошибку :( В логах

[error] 13413#0: *1 connect() failed (111: Connection refused) while connecting to upstream, client: мой ip адрес, server: somehost.kz, request: "GET / HTTP/1.1", upstream: "fastcgi://127.0.0.1:9000", host: "somehost.kz"

нужно через ps aux проверить что php-fpm запущен, и netstat -an глянуть что порт 9000 занят. Также посмотреть конфиг php-fpm на каком порту он запущен.

Судя по всему, он не запущен. При запуске ничего не сообщает, но статус показывает failed.
В логах php-fpm ничего нет :(

[09-Aug-2013 12:00:54] NOTICE: fpm is running, pid 8601
[09-Aug-2013 12:00:54] NOTICE: ready to handle connections
[09-Aug-2013 12:00:57] NOTICE: configuration file /etc/php5/fpm/php-fpm.conf test is successful

[09-Aug-2013 12:00:57] NOTICE: Finishing ...
[09-Aug-2013 12:00:57] NOTICE: exiting, bye-bye!
[09-Aug-2013 12:00:57] NOTICE: fpm is running, pid 9301
[09-Aug-2013 12:00:57] NOTICE: ready to handle connections
[09-Aug-2013 12:21:48] NOTICE: Finishing ...
[09-Aug-2013 12:21:48] NOTICE: exiting, bye-bye!

скиньте конфиг php-fpm: php-fpm.conf и www.conf

php-fpm.conf:
[global]
pid = /var/run/php5-fpm.pid
error_log = /var/log/php5-fpm.log
syslog.facility = daemon
syslog.ident = php-fpm
log_level = error
emergency_restart_interval = 12h
events.mechanism = epoll
include=/etc/php5/fpm/pool.d/*.conf

www.conf:
[www]
user = dark
group = dark
listen = /var/run/php5-fpm.sock
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
pm.max_requests = 1500
max_execution_time = 30
request_terminate_timeout = 30
chdir = /
security.limit_extensions = .php
php_admin_value[error_log] = /home/dark/domains/somehost.kz/logs/fpm-php.www.error.log
php_admin_flag[log_errors] = on
php_admin_value[memory_limit] = 256M
php_admin_value[open_basedir] = "/home/dark:."
php_admin_value[upload_tmp_dir] = "/home/dark/tmp"
php_admin_value[session.save_path] = "/home/dark/tmp"

Пробовал ставить в www.conf "listen = 127.0.0.1:9000", но это не помогло

Вы настроили php-fpm на сокете listen = /var/run/php5-fpm.soc, а из nginx подключаетесь по порту. Нужно вместо сокета прописать 127.0.0.1:9000.

Я пробовал ставить вместо сокета айпишник, не помогло:
2013/08/14 12:25:59 [error] 4444#0: *1 connect() failed (111: Connection refused) while connecting to upstream, client: ip-адрес, server: somehost.kz, request$
P.S: отключить бы еще каптчу для авторизованных пользователей

Проблема в была в строке 319 файла www.conf: max_execution_time = 30
Этот параметр для php.ini. Чтобы задать его для php-fpm используйте php_admin_value