Pull to refresh

Я медленно удаляю apache с сервера

Reading time13 min
Views54K
image
Есть у меня серверок (да, да, именно серверок, сервером его назвать сложно). Железо старенькое (2 гига оперативы, AMD Athlon(tm) 64 Processor 3500+, програмный RAID). Админю я его сам, без особых навыков и познаний. Когда-то давным давно (больше года назад) поставил на него Debian 5.0 Lenny (это была вторая в жизни установка linux-системы, до этого ставил только Ubuntu на рабочий ноутбук) и панель управления ISPConfig3 по мануалу. Держу на нем несколько (штук 40) сайтов друзей и клиентов, Redmine, SVN и еще немного по мелочам.
Периодически все это безобразие падает (load average > 20), и приходится на сервере раз в пару часов перегружать apache или высасывать из пальца очередную попытку оптимизации. В общем полный раздрай и разруха. И вот в одну прекрасную субботу я подумал — а почему бы не решить вопрос раз и… И вот в общем.
Под катом — история убитых выходных + предыстория. Интересна в первую очередь мне, чтобы потом легко вспомнить что именно и зачем я ставил. Может быть интересна новичкам в интересном и нелегком (ох, ...) деле серверной оптимизации постепенным(!) переводом сайтов из-под Apache c его ModRewrite под Nginx (кстати, правильно это слово читается «энжинкс»меня поправили, Сысоев на конференциях не раз говорил, что название сервера стоит читать, как «энжин-икс», спасибо bayandin и DorBer ). Возможно, будет интересна более-менее опытным товарищам, оказавшимся в тех же условиях (Debian Lenny, ISPConfig3, слабое железо, несколько хороших, не сильно хороших и разных сайтов). И более опытным может быть интересно зайти, оставить пару комментариев.


Краткое содержание этой серии:
Вместо предисловия — Новичок и его сервер
1. Слушаем чужие советы и тратим время на чепуху
2. Реверс-прокси и еще один админ (ставим и коряво настраиваем nginx)
3. Наконец-то взялись за ум (nginx + php-fpm + eAccelerator)

Начнем с истории


Сервер ставился по вот этому мануалу — классический LAMP + Хостинг-панель + phpMyAdmin.
Позже на него поставили Redmine, который пользовали по прямому назначению и SVN. И тот, и другой в качестве web-сервера используют Apache. Один через Passenger, второй через mod_dav_svn. Это важно, потому что Apache после этих установок потяжелел.

Оптимизация первая — mpm_worker (лишний шаг)


Через некоторое время пошла первая волна подвисаний. То ли сайтов стало больше, то ли посещаемость их так сильно выросла, то ли активная разработка (и соответственно активное использование Redmine + SVN) стала тому виной. Но ложился сервак постоянно и всерьез. Нашел среди знакомых вроде более-менее адекватного линуксоида, но толкового ответа от него не добился. (Ну не считать же таковым совет поменять сетевую карту!)
Другой админ провел 3-х дневную инспекцию, посоветовал перевести Apache в режим worker. Ок, сели, рукава засучили, сайты в ISPConfig на FastCGI попереводили (там поле типа select в админке:)), вокруг phpMyAdmin c бубном потанцевали, все .htaccess на предмет директив php_* просмотрели. Админ потерялся. Ну ок, своими силами переставили Apache в worker.
Сервер более-менее нормально (зависая не чаще раза в неделю) работал пару месяцев.
Ссылок на манулы не даю, поскольку шаг действительно лишний.

Оптимизация вторая — welcome to nginx (шаг в правильном направлении)


Опять нашли какого-то админа, но на этот раз схитрили — сначала поговорили, он про MPM и не слышал ничего, читать конфиги апача не умеет, уровень квалификации был признан неудовлетворительным.
Правда, пару раз обмолвился о nginx. Я погуглил, почитал Хабр и решил поставить nginx в качестве реверс-прокси перед Apache. Переезжать полностью на nginx было страшно, почему-то был уверен, что PHP-скрипты будут чуствовать себя неудобно (наверное вспомнился какой-то давний случай, когда правил чужой код на чужом сервере без апача, и там то ли расширений не стояло, то ли сам php старенький был). Вот так вот детские травмы влияют на дальнейшую судьбу серверов.
Итак, ставим nginx на Debian (не забываем, что у нас ISPConfig) вот по этому мануалу. В принципе — все стандартно. Сначала переводим Apache на порт :82 (порт :8080 занят — на нем висит ISPConfig):
vi /etc/apache2/ports.conf

После этого меняем порт у всех ранее созданных виртуальных хостов
sed -ie 's/YOUR-IP:80/YOUR-IP:82/g' /etc/apache2/sites-available/*.vhost
mkdir /root/apache2_vhost_backup/
mv /etc/apache2/sites-available/*.vhoste /root/apache2_vhost_backup/

Осталось не забыть сменить порт для наших приложений (в моем случае это были phpMyAdmin, SVN и Redmine), конфиги которых лежат вне /etc/apache2/sites-available/ (в мануале об этом умалчивают).
Следующий шаг — сделать так, чтобы вновь создаваемые через ISPConfig сайты тоже слушали 82-й порт (в мануале тут ошибочка, если делать как у них — ничего не получится. У меня по крайней мере (ISPConfig 3.0.2) вновь создаваемые сайты слушали 80-й. А комментарий мой на эту тему под статьей я почему-то уже не вижу):
cd /usr/local/ispconfig/server/
cp conf/apache_ispconfig.conf.master conf-custom/
cp conf/vhost.conf.master conf-custom/
#открываем оба исходных файла и меняем порт на :82
vi conf/apache_ispconfig.conf.master conf/vhost.conf.master  

Дальше — стандартная установка/настройка libapache2-mod-rpaf (чтобы апач видел IP пользователя) и nginx
apt-get install libapache2-mod-rpaf nginx


Дописываем в конфиг Apache /etc/apache2/apache2.conf 2 строчки
RPAFsethostname On
RPAFproxy_ips 127.0.0.1 YOU_IP_ADDRESS


Заменяем default сервер nginx /etc/nginx/sites-available/default вот этим (это старый плохой конфиг, я его специально для статьи сохранил. Я его ниже переделываю. Отличается от мануального тем, что в мануальном все запросы вида www.site.com перенаправляются на site.com, а у меня обрабатываются — просьба/требование сеошников некоторых сайтов. Сходство с мануальным — каждый виртуальный хост можно найти по адресу /var/www/site.com/web/, статику нужно брать именно отсюда):

server {
    listen   80 default;
    server_name  _;
    server_name_in_redirect  off;
    resolver  127.0.0.1;
#ACHTUNG! так делать не стоит. Смотрите UPD снизу
    if ($host ~* ^(www\.)(.+)) {
        set $host2 $2;
    }
    if ($host !~* ^(www\.)(.+)) {
        set $host2 $host;
    }
    access_log  /var/log/ispconfig/httpd/$host/access.log;
    location ~* ^.+\.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|doc|xls|exe|pdf|ppt|txt|tar|mid|midi|wav|bmp|rtf|js|swf|flv|mp3)$ {
        root   /var/www/$host2/web;
        access_log off;
        expires 30d;
    }
    location / {
        root   /var/www/$host2/web;
        index  index.html index.htm index.php;
        access_log      off;
        proxy_pass http://127.0.0.1:82/;
        proxy_set_header X-Real-IP  $remote_addr;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

Перезапускаем apache и nginx (лучше именно в такой последовательности)
/etc/init.d/apache2 restart
/etc/init.d/nginx restart

Все! Прокси стал. Только вот толку от него немного (на моей конфигурации). Ну хорошо, статику отдает теперь nginx. И че? В общем, через пару месяцев сервак опять плачет и жалуется на жизнь. Симптомы те же — раз в 2-3 дня load average > 20, перезапускаем апач — через минуту все нормализуется.

Оптимизация третья — а зачем нам Apache?


Предыстория закончилась. В эту субботу лежу я на диване, читаю интервью и понимаю — время пришло. Поскольку впереди времени немеряно (целые выходные! никому ничего не обещал! ну почти...), решаю даже сделать это правильно — сначала собираем прототип на рабочем ноутбуке (Debian Squeeze), а только потом лезем на живой сервер.
Сказано — сделано. Тушим апач, ставим nginx, переписываем порты у апача, эмулируем в директории /var/www/ перечень доменов (у ISPConfig именно так), забираем с сервера описанный выше конфиг, запускаем оба сервера — профит, прокси готов. Все гуд, тестовый сайт на wordpress бегает.

Подшаг 3.1 (лишний) — fastcgi-wrapper

Ок, если не передавать запросы на Apache — кому тогда? Гугл-гугл, ты могуч… Ага! Отдай запросы FastCGI через wrapper. Супер! так просто! И мануал на русском! Класс! Так, так. Ага, предлагают поставить lighttpd. Ради 1 скрипта? Хм… Ну ладно, ноутбук, не сервер же. Пробуем.

$ sudo aptitude install lighttpd                
# ок, поставили (кстати, внимательный глаз в процессе установки заметит, что в Squeeze нужный нам скрипт spawn-fcgi идет отдельным пакетом. но от этого только хуже. я его ставил отдельным пакетом, потом сносил, потом опять ставил, но уже весь lighttpd)
$ sudo /etc/init.d/lighttpd stop                  
#ок, остановили
$ sudo update-rc.d -f lighttpd remove    
#ок, убрали из автозагрузки
$ sudo /usr/bin/spawn-fcgi -a 127.0.0.1 -p 9000 \
                  -u www-data -g www-data \
                  -f /usr/bin/php5-cgi \
                  -P /var/run/fastcgi-php.pid
#получаем что-то похожее на ошибку - "child process return 2" - в общем ничего не стало, никто никого на :9000 не слушает, все плохо. А почему? А все просто в системе нет "/usr/bin/php5-cgi"!
$ sudo aptitude install php5-cgi

Все, после установки php5-cgi все сразу станет хорошо — spawn-fcgi запустится, помещенный в /etc/rc.local запустится — в общем все будет хорошо. А нифига! Мануал написан на русском? Жди беды! И точно — на ноутбуке процесс постоянно отваливался, будучи перенесенным на сервер — на тяжелых запросах через два на третий отдавал пустую страницу. Но мы то еще об этом не знаем, nginx на 127.0.0.1:9000 еще ничего не проксирует! Просто не делайте так, я чуть ниже покажу как лучше.

Подшаг 3.2 — настраиваем nginx

На эту тему написано немеряно. На хабре и снаружи. Из интересного —
ветка форума — хорошо видна эволюция понимания настроек nginx — forum.woweb.ru/topic47631.html
как не надо делать — habrahabr.ru/blogs/nginx/74135
«достойно всяческого порицания» — vkurseweba.ru/files/nginx-doc.pdf — улыбнула фраза
за вики простите, но все же прочтите — wiki.nginx.org/NginxHttpCoreModule

Итого, у меня получилось 2 файла конфигурации, один для реверс-прокси к Апачу (назвал его proxy):
server {
#слушаем 80-й порт, это сервер (в терминах конфига nginx) по-умолчанию
    listen   80 default;
#перечень доменов, которые крутятся на этом сервере
    server_name  mysite.com www.mysite.com;
# http://nginx.org/ru/docs/http/ngx_http_core_module.html#server_name_in_redirect
    server_name_in_redirect  off;
http://nginx.org/ru/docs/http/ngx_http_core_module.html#resolver
    resolver  127.0.0.1;
#парочка некрасивых if, предназначены для определения пути к директории виртуального хоста
#ACHTUNG! так делать не стоит. Смотрите UPD снизу
    if ($host ~* ^(www\.)(.+)) {
        set $host2 $2;
    }
    if ($host !~* ^(www\.)(.+)) {
        set $host2 $host;
    }
#надеюсь не нуждается в комментариях
    access_log  /var/log/ispconfig/httpd/$host/access.log;
    access_log off;
#DOCUMENT_ROOT виртуального хоста
    root   /var/www/$host2/web;
#извините, не могу прокомментировать что именно делает данная директива
    index  index.html index.htm index.php;
#пошли разбираться с полученным урлом. Первым делом проверяем на существование файла. Если файл есть - отдаем его. Если нет - отправляем запрос на index.php в корне домена. (Не волнуйтесь, запросы вида /some-file.php сюда не попадут, они будут сразу обрабатываться соответствующим location)
    location / {
        try_files $uri /index.php;
    }
#именно этот location будет обрабатывать запросы вида /some-file.php, и как следствие - на него мы перенаправим запросы на несуществующие файлы из предыдущего location
    location ~* \.php$ {
        proxy_pass http://127.0.0.1:82;
        proxy_set_header X-Real-IP  $remote_addr;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}


И второй сервер (в терминологии nginx), для работы непосредственно с php (я назвал его default):
server {
    listen   80;
    server_name  _;
    server_name_in_redirect  off;
    resolver  127.0.0.1;
#ACHTUNG! так делать не стоит. Смотрите UPD снизу
    if ($host ~* ^(www\.)(.+)) {
        set $host2 $2;
    }
    if ($host !~* ^(www\.)(.+)) {
        set $host2 $host;
    }

    access_log  /var/log/ispconfig/httpd/$host/access.log;
    root   /var/www/$host2/web;
    access_log off;
    index  index.html index.htm index.php;
#работает аналогично соответствующей location из первого файла
    location / {
        try_files $uri $uri/ @fastcgi;
    }
#именованный location, нормальным образом запросы сюда не попадут, только перенаправленные из других частей конфига. В предыдущем proxy я не создавал такого, поскольку внутри named-location нельзя использовать proxy_pass вида http://127.0.0.1:82 - nginx при перезагрузке ругается, хотя это наверное можно обойти с помощью upstream (смотрите еще ниже). Именно сюда мы переправляем запросы с ЧПУ. Можно было обойтись без него и как в предыдущем примере перенаправлять не на @fastcgi, а на /index.php,  но мне почему-то хочется верить, что так будет отрабатывать чуть быстрее.
    location @fastcgi{
#указываем куда передавать запросы
        fastcgi_pass 127.0.0.1:9000;
#инклюдим файл /etc/nginx/fastcgi_params
        include fastcgi_params;
#задаем скрипт-обработчмк
        fastcgi_param  SCRIPT_FILENAME  /var/www/$host2/web/index.php;
    }
#все как в предыдущем примере
    location ~ \.php$ {
        fastcgi_pass   127.0.0.1:9000;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  /var/www/$host2/web/$fastcgi_script_name;
        include fastcgi_params;
    }
}

А теперь самое вкусное. Наличие этих двух серверов включенными (не забываем создавать символические ссылки в папку sites-enabled) у nginx позволяет мне спокойно уйти спать с чуством глубокого внутреннего удовлетворения. Наконец-то я смогу спокойно, один за одним, выбирая самые нагруженные сайты на сервере переводить их на nginx, перепроверять, и в случае проблем за 1 минуту переводить обратно. Можно не самые нагруженные — можно самые массовые. Текущая конфигурация default удовлетворяет правила ModRewrite от Wordpress по умолчанию на 100%. После минимального допиливания — будет удовлетворять еще и джумлу, и Битрикс. А это уже ~80% моего хостинга. К сожалению, ModRewrite для CodeIgniter будет несовместим с этой версией. Но отличаться она будет не сильно. Так что серверов в конфиге nginx будет не 2 — «на Апач» + «на php», а побольше. А разные велосипеды с 48 строками в .htaccess специально на apache оставлю, пусть сволочи помучаются.
Обратите внимание, у первого (proxy) файла стоит listen:80 default; Что это означает? Это означает, что все виртуальные хосты, не указанные в остальных файлах по умолчанию будут отправлены на обработку к Apache. Это-то нам сейчас и нужно. Потом, хочется верить, я перепроверю все сайты и сделаю default сервером по умолчанию (из-за этого и выбрано имя сервера). А на proxy оставлю несколько «счастливчиков».

Вся конфигурация была проверена на ноутбуке и перенесена на сервер без каких-либо неожиданностей. На первый взгляд все работает, крутится, несколько сайтов перевел на nginx, перенастроил default для Joomla, даже Битрикс — и тот завелся!

Конфигурация не идеальная плохонькая (к примеру, .htaccess, .htpasswd, application.ini и файлы с расширением не-php nginx отдаст первому встречному попросившему), использовать ее для production не стоит. Камни и свои примеры в комментариях приветствуются.
В сети было найдено немало упоминаний про онлайн-генератор конфигов под nginx на основе .htaccess.
Все они указывают на одну и ту же, ныне не работающую страницу — http://www.anilcetin.com/convert-apache-htaccess-to-nginx/. Никто не встречал более-менее рабочего варианта? Есть на сервере несколько велосипедов с очень некрасивыми .htaccess. Хотелось бы их преобразовать во что-то похожее на конфиг nginx, пусть не 100% рабочий, можно и напильником допилить.

UPD: В комментариях vbart показал более красивое решение определения имени домена без www.
Для этого в /etc/nginx/nginx.conf в блок http добавляем map
    map $host $host_wo_www {
        default  $host;
        ~^www\.(?P<wo_www>.+)$  $wo_www;
    }

И теперь во всех серверах мы можем избавляться от уродливого двойного if, а вместо переменной $host2 использовать $host_wo_www.

Подшаг 3.3 — избавляемся от fastcgi-wrapper

Воскресным утром обнаружились первые проблемы. Один из 5 переведенных сайтов периодически выдавал по запросу чистую страницу. Не страшно, сайт мой, а значит никто звонить не будет. Но неприятно именно тем, что такая ситуация может повториться и с сайтами клиентов. Оно нам надо?
Где-то на Хабре нашел упоминание php-fpm в этом контексте. Нагуглил очередной мануал. Но вот беда — это возможно для php 5.3. А на сервере стоит 5.2. Система бэкапов у меня работает только для виртуальных хостов и баз данных. Кинул монетку и обновил.
В файл /etc/apt/sources.list добавил строчки
deb http://packages.dotdeb.org lenny all
deb-src http://packages.dotdeb.org lenny all
#в следующих двух строках я вместо stable (из мануала) поставил lenny - при stable apt-get отказался видеть php и php-fpm
deb http://php53.dotdeb.org lenny all
deb-src http://php53.dotdeb.org lenny all

Потом все как всегда
apt-get update
apt-get install php php5-fpm

При установке php5 попытался подняться lighttpd, но не смог — 80-й порт слушал nginx. (Я ставил несколько раз, поэтому пришлось пойти и переключить lighttpg на 81-й, все равно сейчас его удалим)

На удивление — проблем почти не было (short_open_tag On и default_timezone в php.ini не в счет). А, да, еще упала одна Joomla из-за криворуких плагинописателей. Эту подняли уже аж в понедельник.

Чувство update на живом сервере сродни прыжку с парашютом — мозгами понимаешь, что все будет гуд и на самый крайний случай откатимся назад или что-то придумаем, но ладошки потеют и куришь по 2 сигареты за раз.
Потом полностью откатываем назад шаг 3.1 — убираем fastcgi-wrapper из автозагрузки, тушим уже запущенный (можно перезагрузиться, а можно подсмотреть id процесса в /var/run/fastcgi-php.pid и сделать kill), удаляем lighttpd.
/etc/init.d/php-fpm start

И все. И да, после этого все стало совсем хорошо и весело. На ноутбуке порт слушали, никто никуда не падал. На сервере исчез белый экран смерти.
UPD: ниже Nc_Soft (спасибо!) указал на одну уязвимость.
Идем редактируем файл /etc/php5/fpm/php.ini, заменянем/дописываем cgi.fix_pathinfo=0

Подшаг 3.4 — а почему собственно :9000?

И раз уж на то пошло — а почему вообще сетевой интерфейс? Мы ведь в линуксе, правильно? Надо работать через сокеты! Четкого понимания что же такое сокет нет. Опять гуглим. Читаем. Настаиваем php-fpm — /etc/php5/fpm/pool.d/www.conf:
;listen = 127.0.0.1:9000
listen = /var/run/php5-fpm.sock

Настраиваем nginx — /etc/nginx/nginx.conf, заодно делаем upstream — так красивее
...
http{
...
#этот блок должен быть обязательно в контексте http - иначе nginx при перезапуске ругается unknown directive
    upstream php5-fpm-sock {
        server unix:/var/run/php5-fpm.sock;
    }
...
}
...

В настройках наших серверов заменяем 127.0.0.1:9000 на php5-fpm-sock. Сервер proxy, естественно, не трогаем.

Перезапускаем nginx и php-fpm
/etc/init.d/nginx restart
/etc/init.d/php-fpm restart

Возможно я не прав, пусть знающие люди в комментариях осудят, поймут и простят подскажут, но я, тысячу чертей, не понимаю, почему во всех встреченных мною мануалах все back-end'ы nginx слушает именно по сетевому протоколу. И никто словом не обмолвится — мол, в дальнейшем замените сетевой сокет на UNIX-сокет (POSIX или как там его правильно).

Подшаг 3.5 — выеживаемся

Это почти все. После мануала из предыдущего подшага захотелось странного — кешировать опкоды. При попытке установить php-apc apt-get потребовал downgrade php и remove php-fpm. Я показал ему фигу и с помощью checkinstall поставил eAccelerator из исходников вот по этому этому мануалу. После установки появилась одна проблемка — eAccelerator не захотел работать с сайтами, которые сейчас крутятся под Apache (то ли они у меня на самом деле работают в режиме cgi, то ли еще в чем проблема). Вернее сайты работают, но почему-то выдают warning на пустом месте — то файл не найден (хотя на этой строчке include жестко захардкожен и файл реально существует), то переменная не определена (1 строчкой выше приравнена пустому массиву!).
В общем, убрал я eaccelerator.ini из /etc/php5/conf.d и дописал его содержимое в конец /etc/php5/fpm/php.ini
Для сайтов, работающих через php-fpm eAccelerator включен.

В этом месте возникла еще одна проблема — даже при выполнении php -v из командной строки, php ругался на apc.so. Я пошел и удалил apc.ini из /etc/php5/conf.d/. Все вроде стало нормально. И только на следующий день заметил замертво лежащий сайт на Joomla. Расследование показало, что, невзирая на отсутствие в системе php-apc, Joomla упорно вызывала apc_fetch. Я ее конечно в коде закомментировал и сделал вид, что она всегда возвращает false, но хотелось бы побороть проблему чуть более красиво. Или отучить джумлу лезть за кешем именно в apc, или поставить в php фейк apc (Debian Lenny). Если кто-то сможет помочь — добро пожаловать в комментарии.

Итоги


Сегодня сервер проработал целый день, load average выше 2 не поднимался (это только 5 переведенных сайтов, больше не переводил — день длинный и тяжелый, после таких-то выходных). Ни на ноутбуке, ни на сервере самописные сайты на Zend Framework работать отказываются наглухо, по ощущениям даже не создается объект Application — пойдем перепроверять base_path.

PS: я в дальнейшем планирую перевести Redmine и SVN за nginx, поставить Node.js, посмотреть как сращиваются Nginx и git. Если будет на то воля Хабра — могу отписаться. Постараюсь сделать это покороче.

UPD: учел замечание от пользователя VBart, подправил конфиги.
UPD: добавил в описание установки php-fpm дополнение от Nc_Soft.
Tags:
Hubs:
Total votes 167: ↑137 and ↓30+107
Comments184

Articles