Возможно эта статья ниочем, подумал я. Но тут намедни вопросы появились насчет разделения front-back отдачи контента. И я понял что есть еще люди…
И они всегда будут. А значит есть о чем написать.
Все знают наш любимый и почитаемый веб сервер Apache. К сожалению время его сделало до такой степени «навороченым», что порой поражаешься сколько памяти и ресурсов вообще он может кушать на довольно простых задачах. И иногда сервер просто не справляется с нагрузкой (в основном не хватает памяти).
Нужно констатировать факт – Apache совершенно неэкономно расходует память при отдаче статического контента (картинки, HTML файлы, стили и т.д. – любой контент, который не содержит в себе серверного кода).
Благо есть с чем сравнить. Уже давно появился легкий веб-сервер Nginx. Он написан изначально оптимизированным под отдачу статики и, в сравнении с Apache, совершенно не расходует память. И везде, где только можно кусок памяти временно скинуть на диск, он это делает не выедая лишних системных ресурсов.
Но Nginx не содержит каких либо модулей для обработки динамического контента (кроме SSI). Он может только «проксировать» (передавать) обработку контента «бекенду» – собранному на заднем плане обработчику динамики Apache (или любому другом веб серверу). Так же Nginx умеет общаться с Fast-CGI сервером на бекенде.
Вот здесь и начинается разделение на FrontEnd и BackEnd отдачи. Смысл очень простой – мы каждому предоставляем делать только то, что он хорошо умеет делать и ничего больше. Nginx стоит на фронте и принимает запросы от клиентов. Из них он ловит запросы к статическому содержимому и немедля это содержимое отдает. Запросы же к динамическому (или потенциально динамическому контенту), Nginx проксирует на Бекенд, на котором предположительно стоит Apache и обрабатывает запросы к динамическому контенту (например, PHP-скриптам). Результат отдается обратно к Nginx, которую в свою очередь, возвращает ответ клиентам.
Вырисовывается такая схема:

Таким образом, еще говорят, можно «облегчить апач», т.е. существующий Apache поставить на бекенд, а перед ним на фронте поставить Nginx. При этом обычно апач переводят слушать какой то порт на 127.0.0.1 и туда направляют проксирующие запросы Nginx.
Если ваш одинокий Apache до этого сильно грузил сервер и у вас действительно есть что «облегчить» (т.е. много статического содержимого), то разница будет ощутима визуально и нагрузка сервера серъезно просядет.
Теперь к практике.
Облегчение нагрузки Apache с помощью Nginx.
Допустим, уже стоит и работает Apache, который испытывает перегрузки.
Первое что нужно сделать, это перевести VirtualHost’ы апача куда нибудь на 127.0.0.1. Например, было:
Listen 123.123.123.123
NameVirtualHost 123.123.123.123
<VirtualHost 123.123.123.123>
ServerName foobar.com
ServerAlias www.foobar.com
DocumentRoot /home/www/foobar.com
…..SOME STUFF…..
</VirtualHost>
Теперь переводим апач на бекенд, а на фронт ставим Nginx. Таким образом, для внешнего клиента ничего якобы не меняется.
Listen 127.0.0.1
NameVirtualHost 127.0.0.1
<VirtualHost 127.0.0.1>
ServerName foobar.com
ServerAlias www.foobar.com
DocumentRoot /home/www/foobar.com
…..SOME STUFF…..
</VirtualHost>
Стоит заметить, что если другие виртуалхосты так же слушают на айпи 123.123.123.123, то их либо нужно тоже переместить на бекенд, либо поместить на другой айпи, поскольку nginx и apache не могут слушать на одном и том же айпи на 80 порту.
Далее, предполагается, что nginx уже установлен – не вижу ничего сложного в этой процедуре.
Помещаем nginx на фронт:
nginx.conf:
….
server {
listen 123.123.123.123;
server_name foobar.com www.foobar.com;
# устанавливаем отдачу статики
# сюда можно добавить другие расширения файлов
# которые будет отдавать nginx
location ~* \.(jpg|jpeg|gif|png|ico|css|bmp|js|swf)$ {
root /home/www/foobar.com;
}
# Не забываем закрыть .htacces’ы
location ~ /\.ht {
deny all;
}
location / {
#Все остальное отдаем апачу
proxy_pass http://127.0.0.1:80;
proxy_redirect off;
proxy_set_header Connection close;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass_header Content-Type;
proxy_pass_header Content-Disposition;
proxy_pass_header Content-Length;
}
}
Для FreeBSD так же можно указать ацепт фильтр в директиве listen:
listen 123.123.123.123 default accept_filter=httpready;
Это делается для одного сервера на один айпи. На все остальные так же распространяется. Указанный сервер будет помечен как дефолтный (если клиент указывает левый «Host:» хидер или не указывает его вообще – прямое обращение по айпи).
Зачем нужен accept filter? Когда ядро принимает новый коннекшин без ацепт фильтра, оно сразу же передает коненкшин веб серверу с помощью системного вызова accept(). С ацепт фильтром accept() происходит только тогда, когда клиентом были посланы все HTTP-заголовки. Помогает при SYN-флуде. Требует модуль ядра accf_http:
# kldload accf_http
Корректная обработка айпи клиента.
Все бы хорошо, только скрипты апача будут всегда видеть айпи клиента как 127.0.0.1 – айпи, с которого проксирует nginx. Для фикса этой проблемы можно использовать mod_realip для apache v1 или mod_rpaf для apache любой версии .
Оба этих модуля делают одно и тоже – смотрят в хидер «X-Real-IP: «, который передает nginx и меняют на соотв. айпи env переменную REMOTE_ADDR.
При использовании mod_rpaf необходимо прописать в виртуалхост на бекенде:
RPAFenable On
RPAFsethostname On
RPAFproxy_ips 127.0.0.1
RPAFheader X-Real-IP
В принципе, все. Теперь скрипты будут работать корректно и в логах апача будет корректный айпи клиента. Можно пошаманить с другими параметрами http и listen Nginx’а.
Так же не рекомендуется использовать заголовок «X-Forwarded-For» для передачи айпи, т.к. некоторые скрипты предпринимают попытку определения прокси клиента. В этом случае для них все клиенты будут якобы с прокси.
Использование Fast-CGI сервера в Nginx.
Существует определенная категория анти-фанатов Apache, которая вообще принципиально отказывается от Apache. PHP-скрипты отдаются на обработку FastCGI серверу, который заменяет собой Apache.
Наиболее популярным FastCGI сервером для PHP я считаю PHP-FPM.
PHP-FPM обладает некоторыми преимуществами перед апачем:
- Можно настроить обработку динамики каждого Vhost’а из под другого юзера
- Можно настроить прием соединений через Unix socket (о! не пробуйте это делать на нагруженой файловой системе – время отклика серъезно пострадает)
- Можно настроить chroot в заданную директорию для каждого виртуалхоста
- Каждому Vhost’у отделить фиксированное количество воркеров. Не больше. Но и не меньше.
С точки зрения производительности, очевидность преимущества FPM перед Apache, на мой взгляд, сомнительна. К тому же, как показывает практика, FPM ведет себя весьма плохо, если max_children выставлено неоптимально. А динамически ее конфигурировать FPM на данный момент не умеет.
Советую ставить FPM, если вы точно знаете что делаете и уже потестировали и «поигрались» с ним.
Итак, конфигурация nginx для обработки PHP-скриптов (вместо location / в предыдущем примере):
location / {
# Передавать соединения через UNIX socket
fastcgi_pass unix:/tmp/php.sock;
# Так же можно передавать через TCP:
# fastcgi_pass localhost:9000;
fastcgi_param SCRIPT_FILENAME /home/www/foobar.com$fastcgi_script_name;
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 REMOTE_ADDR $remote_addr;
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 REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
fastcgi_read_timeout 31;
fastcgi_send_timeout 31;
}
Надеюсь, я ничего не забыл из fastcgi_param
Хотя обычно этого достаточно. Вообще конечно желательно fastcgi настроить прямо на локейшин «location ~* \.php$». Ну в общем тут нужно покумекать в зависимости от запросов сайта.
Далее, установите патч PHP-FPM. На данном этапе лучше компилировать php в какой то свой $PREFIX:
./configure \
–prefix=/usr/local/fpm \
–enable-fastcgi \
–enable-fpm \
…другие параметры…
Содержимое $PREFIX/fpm/etc/php-fpm.conf обычно подходит стандартное с некоторыми поправками. Предполагается что будет только 1 пул воркеров. Директивы, на которые стоит обратить внимание:
#Для приема соединений на UNIX socket
<value name=»listen_address»>/tmp/php.sock</value>
#Для приема соединений на TCP socket
<value name=»listen_address»>127.0.0.1:9000</value>
# Кхе… Директива listen_address может быть только одна в пуле… Хотя могу и ошибаться.
# Юзер и группа пула
<value name=»user»>nobody</value>
<value name=»group»>nobody</value>
# Тип IPC – статический
<value name=»style»>static</value>
# Количество воркеров
<value name=»max_children»>5</value>
На остальное можно обращать внимание, а можно и не обращать )
Запускается это все дело командой
# $PREFIX/fpm/bin/php-cgi –fpm
Ну в принципе, бекенд готов. Можно запускать и тестировать. Таким же образом можно поставить любой другой FastCGI обработчик «за спиной» у nginx. Смысл в любом случае простой – каждый делает то, что он умеет хорошо делать, в результате все делается так как надо
Посты
Добрый день.
Очень полезная статься, спасибо!
Достаточно давно использую nginx в качестве frontend на своем сайте. Всегда мучися вопросом, как лучше сконфигурировать связку с Apache:
1.
location / {
proxy_pass http://127.0.0.1:80;
}
location ~* \.(jpg|jpeg|gif|png|ico|css|bmp|js|swf)$ {
root /var/www;
}
или
2.
location / {
root /var/www;
}
location ~ \.php$ {
proxy_pass http://127.0.0.1;
}
У меня пока настроен первый вариант, но рассмотрев контент своего сайта, location для статики разросся до неприличных размеров (avi|bmp|class|css|doc|flv|gif|ico|jpg|jpeg|js|mov|mp3|pdf|png|ppt|rtf|swf|txt|xls|xml)
Если не ошибаюсь, при каждом запросе к сайту, обрабатывается regexp поиск по location. Во втором случае он значительно короче и, соответственно, должен быть бысрее.
Я использую Debian Linux. В ранних версиях пакета nginx default host был настроен по первому варианту, а в последней версии уже по второму.
А как Вы считаете?
С уважением, Алексей.
Да, второй вариант более еффективный, но там надо думать. Если у вас вся динамика сосредодочена собсно в .php файлах, то тут думать нечего:
===================
location / {
root /var/www;
index index.php index.htm index.html; # или как там у вас
}
location ~ \.php$ {
proxy_pass http://127.0.0.1:80;
}
===================
индексы должны корректно передаваться на бекенд.
Если там например, HTML файлы стоят на обработке ПХП, то нужно тоже это предусмотреть.
> Зачем нужен accept filter? Когда ядро принимает новый коннекшин без ацепт фильтра, оно сразу же передает коненкшин веб серверу с помощью системного вызова accept(). С ацепт фильтром accept() происходит только тогда, когда клиентом были посланы все HTTP-заголовки. Помогает при SYN-флуде.
Чепуха. accept фильтры никак не помогают при SYN флуде.
> Можно настроить прием соединений через Unix socket (о! не пробуйте это делать на нагруженой файловой системе – время отклика серъезно пострадает)
Опять чепуха. unix socket имеет только *адрес* в фаловой системе. Этот адрес фигурирует только при bind() и connect(), никакой нагрузки на файловую систему ввод-вывод через такой сокет не создает.
anight : нет, проверено на FreeBSD. Загрузка дисковой системы на 99% замедляла отклик от PHP-FCGI где то секунд на 5. В то время как TCP работал нормально.
насчет SYN-флуда, возможно мы понимаем под этим разные вещи. Если делать connect-close на 80 порт в больших масштабах, думаю без acceppt-фильтра машине будет хуже
> насчет SYN-флуда, возможно мы понимаем под этим разные вещи.
Это точно. Почитайте: http://en.wikipedia.org/wiki/Syn_flood
> Если делать connect-close на 80 порт в больших масштабах, думаю без acceppt-фильтра машине будет хуже
Вот это правильно. А syn-flood тут не при чем.
> anight : нет, проверено на FreeBSD. Загрузка дисковой системы на 99% замедляла отклик от PHP-FCGI где то секунд на 5. В то время как TCP работал нормально.
Мне легче поверить в то, что вы не учли какие-то побочные явления при тестировании, чем в то, что во freebsd нагрузка на ФС влияет на скорость ввода-вывода unix sockets
Да нет… Поднимаю FCGI на сокете – тормозит, меняю на TCP/IP – все нормально. На разгруженой дисковой системе через сокет работает нормально.
Вообще-то в unix системах, если файловая система загружена прилично (IO постоянно занят + прерывания) то открытие сокета, это есть ни что иное, как создание нового файла, отсюда и побочный эффект.
Согласен, конечно радует перспектива изабвиться от апача на совсем всязи с появление nginx, но провозившись часов 10 с PHP-FPM я понял, что не для меня этот продукт. Во всяком случае с апачворкером все летает гораздо активнее и, главное экономичнее для ресурсов. Пока в топку. И кстати, замечу, что последние версии апача очень бодрят, не то что раньше. Так что nginx-apache-worker пока оптимальная связка.
А статья полезная, жаль раньше не нашел