Оптимизация сервера с WHM/cPanel - mpm-itk, nginx и пр.

Небольшие заметки из личного (и не только) опыта, рекомендации
Ответить
Аватара пользователя
Raven
Бородатый сис
Бородатый сис
Сообщения: 2800
Зарегистрирован: 03 мар 2010, 15:12
ОС: RHEL 8
Откуда: Из серверной

Оптимизация сервера с WHM/cPanel - mpm-itk, nginx и пр.

Сообщение Raven » 25 янв 2013, 17:25

Попальсь мне в распоряжение хостинг-сервера под CentOS 5 c WHM/cPanel на борту. Серверы в достаточно мощные, но клиентов немало, причем все хостят только один софт, довольно тяжелый. Встал вопрос о оптимизации этих самых серверов. Сама по себе WHM - весьма убогое ПО с кучей ручек, считающее себя выше админа и ОС - предпочитает все делать по своему, не спрашивая как нужно админу и ломая всю идею ОС на которой стоит.

Чтож, будем переучивать. Начнем с малого.

Не использовать bind.

Bind - это DNS-сервер давно считающимся штатным DNS-сервером в любой *nix системе. Сервер сочетает в себе 2 компонента - авторитативный сервер и кеширующий резольвер + обладает весьма гибким конфигом. Однако вместе со всеми его достоинствами он также обладает кучей недостатков - использование одновременно резольвера и авторитативного сервера (компоненты, которые в bind и так весьма ресурсоемки) приводит к повышеному расходу ресурсов ( связка резольвера pdns-recursor и авторитативного сервера pdns обычно расходует в 3 раза меньше ресурсов нежели 1 bind). Так как на сабжевых серверах используется только авторитативная его часть, необходимость обработки рекурсивных запросов отсутствует, то целесообразно заменить его на что-либо полегче. MyDNS - сервер использующий бекэндом БД mysql, расходует весьма мало ресурсов, но к сожалению последний его релиз был выпущен в далеком 2006-м году. Есть его форк mydns-ng, но он ушел не намного дальше своего родителя - последний апдейт в svn - от 2010-го года. Такие серверы как PowerDNS и djbdns авторами панели очевидно не рассматриваются. Остается один вариант - NSD. На сегодняшний день среди авторитативных это один из быстрейших и легковесных серверов, использует файлы описаний зон в стиле BIND и может использовать файлы BIND без изменений, благодаря чему смена DNS-сервера проходит безболезнено.

Лезем в панель, следуем по пути Service Configuration » Nameserver Selection и выбираем NSD. Пара минут и -30 (в моем случае) метров из обьема используемой памяти - немного, но мне так спокойнее - нету named в top'е, а тестовая машинка с 2 тестовыми сайтами уж очень слабенькая.

Тюнинг MySQL
Понитие достаточно абстрактное, так как для каждой машины обычно приходится притягивать за уши баланс между ресурсоемкостью и быстродействием. Дефолтная сборка MySQL притаскиваемая панелью показала не самое лучшее быстродействие, поэтому я предпочел избавиться от нее в пользу чего поновее/побыстрее. Лезем в Server Configuration » Update Preferences и выставляем MySQL - never, дабы панель не пыталась управлять установкой/обновлением MySQL. Затем грохаем ее сборку и ставим что нам больше нравится. Я предпочитаю Percona-server или MariaDB, в зависимости от задач. Так как на тестовике все таблицы в InnoDB, то улучшеная поддержка MyISAM, а соответственно и MariaDB нам не нужна. Ставим Percona.

Код: Выделить всё

yum -y erase MySQL-*
rpm -Uhv http://www.percona.com/downloads/percona-release/percona-release-0.0-1.x86_64.rpm
yum -y install Percona-Server-shared-55 Percona-Server-devel-55 Percona-Server-client-55 Percona-Server-shared-compat Percona-Server-server-55
service mysql restart
Далее читаем логи незапустившегося mysql-сервера (а это случается процентах в 90 случаев) и идем править ему конфиг устраняя проблемы описаные им в логах. Обычно это устаревшие/отсутствующие функции.

Конфиг... Какая интересная штука... А что если вот тут... АААА!!! Ну ладно, шутки в сторону, советую перед правкой почитать этот сайт - много интересного. Не буду расписывать что да как, скажу только что для машинки с 1-ядерным процом и 512 мб оперативки оптимальнейшим образом показала себя данная конфигурация:
[client]
port = 3306
socket = /var/lib/mysql/mysql.sock

[mysqld]
port = 3306
socket = /var/lib/mysql/mysql.sock
log-error=/var/lib/mysql/error.log
datadir=/var/lib/mysql
skip-external-locking
skip-networking
symbolic-links=0
character-set-server=utf8
collation-server=utf8_general_ci
wait_timeout = 300
tmpdir=/tmp

query_cache_limit=2M
query_cache_size = 16M
query_cache_type=1
tmp_table_size=128M
max_heap_table_size=128M

table_cache = 512
thread_cache_size=128
thread_stack = 64K
long_query_time = 5
slow-query-log
slow-query-log-file = /var/lib/mysql/slow.log

myisam-recover=BACKUP
max_connections = 20
key_buffer_size = 16M
max_allowed_packet = 1M
table_open_cache = 512
sort_buffer_size = 512K
net_buffer_length = 8K
read_buffer_size = 256K
read_rnd_buffer_size = 512K
myisam_sort_buffer_size = 8M
join_buffer_size = 4M

innodb_data_home_dir = /var/lib/mysql
innodb_data_file_path = ibdata1:10M:autoextend
innodb_log_group_home_dir = /var/lib/mysql
innodb_buffer_pool_size = 16M
innodb_additional_mem_pool_size = 2M
innodb_log_file_size = 5M
innodb_log_buffer_size = 8M
innodb_flush_log_at_trx_commit = 1
innodb_lock_wait_timeout = 50
innodb_file_per_table

[mysqld_safe]
log-error=/var/lib/mysql/error.log
nice = 0

[mysqldump]
quick
quote-names
max_allowed_packet = 16M

[mysql]
no-auto-rehash
safe-updates

[myisamchk]
key_buffer_size = 20M
sort_buffer_size = 20M
read_buffer = 2M
write_buffer = 2M
Учим корову летать.

В дефолтном виде конфиг apache генерируемый скриптами WHM работоспособен, но во-первых небезопасен, во-вторых не оптимален - при попытке выбрать с сайта страницу с 500 результатами на страницу сервер упал. Попробуем приблизить его к оптимальному варианту. Лезем в панель и выставляем следующие опции приближенные к тем что ниже:

Код: Выделить всё

Timeout 300 # у нас есть очень тяжелые запросы
TraceEnable Off # Это нам ни к чему
ServerSignature Off # Зачем светить интимными местами?
ServerTokens ProductOnly # См. выше
FileETag None # Эт тоже фтопку
StartServers 5 # Сколько процесс-потомков запускать при старте
<IfModule prefork.c>
MinSpareServers 5 # Минимальное количество ждущих процессов
MaxSpareServers 10 # Максимальное количество
</IfModule>
ServerLimit 35 # Максимальное количество порождаемых потомков
MaxClients 35 # Максимальное количество клиентов для которых заведется свой процесс
MaxRequestsPerChild 500 # Сколько запросов обработает потомок прежде чем убьется
KeepAlive On # Возможность подавать несколько запросов в 1 соединении
KeepAliveTimeout 5 # Слишком долго ждать запросов тоже не стоит...
MaxKeepAliveRequests 60 
Выборка описаной выше страницы прошла успешно. Увеличиваем количество результатов на страницу до 1000 - сервер в дауне. :-[

По дефолту апач работает в режиме модели мультипроцессинга prefork и имеет возможность подключения php как dso, cgi, fastcgi и suphp. Как разграничитель прав доступа он использует mod_suexec либо mod_ruid2 либо все тот же suphp. Пройдемся по каждому пункту:
  • DSO - динамически подключаемая библиотека, она же пресловутый mod_php. По скорости уступает только fastcgi поднимаемому внешним приложением (php-fpm, spawn-fcgi, но их к сожалению панель использовать не умеет).
  • cgi - достаточно устаревшая технология, раз в 7 медленнее dso. Суть заключается в том, что апач сам поднимает процесс интерпретатора php и убивает его после обработки скрипта.
  • fastcgi - без внешнего спаунера - примерно то же что и cgi (скорость также примерно одинакова), однако расходует меньше оперативной памяти.
  • suphp - прослойка между веб-сервером и интерпретатором php. php запускается как отдельный процесс от имени указаного пользователя не обгоняет по скорости cgi, ибо сам по сути запускает обработку php как cgi, только от указаного ему пользователя.
  • mod_suexec, mod_ruid2 - модули обеспечивающие работу виртуалхоста от имени указаного пользователя. Имеют особенность оставлять огрехи в виде сгенерированных динамическим контентом файлов принадлежащих корневому пользователю apache и недоступных для модификации/удалению пользователем виртуалхоста.
Как видим самый быстрый из доступных вариантов обработчиков php бесполезен в при использовании панели в shared-хостинге, так как самый оптимальный вариант его использования в этих условиях - mod_suexec не обеспечивает должного уровня изоляции пользователя.
К сожалению я не нашел в панели нативной поддержки apache как mpm-itk. Однако нашелся модуль "впиливающий" такую функциональность в EasyApache. Пляшем от радости, катаемся по полу, разгоняемся, качаем, ставим:

Код: Выделить всё

wget http://docs.cpanel.net/twiki/pub/EasyApache3/CustomMods/MPMitk.tar.gz
 tar -C /var/cpanel/easy/apache/custom_opt_mods -xzf MPMitk.tar.gz
залетаем в панель, компилим apache и тут же наступаем на грабли! При сборке itk панель "забывает" собирать php как dso, поддержка fastcgi в itk отсутствует by design. Остаются cgi и suphp, но тогда теряется смысл установки itk - сам по себе этот mpm немного медленнее prefork, а с тормозным с cgi это будет полный ахтунг, ну а с suphp получится "масло масленое", да и опять же cgi.

Рвем волосы, крошим зубами столешницу... Изучаем механизмы EasyApache и находим интересный кусок кода в файле /var/cpanel/perl/easy/Cpanel/Easy/Utils/PHP.pm :

Код: Выделить всё

                        $self->run_system_cmd( [ '/usr/local/apache/bin/apxs', '-q', 'MPM_NAME' ], 0, 1 );
                        if ( $self->{'last_run_system_cmd'} !~ /^prefork$/m ) {

                            # Don't generate CGI/FCGI binary on first pass (unless PHP 5.4 or later)
                            $self->add_to_configure( { '--disable-cgi' => '' }, "Cpanel::Easy::PHP5" ) unless $v5_4_or_later;

                            # Don't generate mod_php; we don't support Zend Thread Safety (ZTS)
                            $self->delete_from_configure( { '--with-apxs' => undef, '--with-apxs2' => undef }, "Cpanel::Easy::PHP5" );
                        }
То есть панель спрашивает у apxs каков MPM установленого apache и если ответ не prefork отказывается собирать php как DSO. Сделано было это ради безопасности - помимо prefork и event панель по дефолту поддерживает еще и модель worker - thread-safe или "нитевую". То есть если prefork и itk порождают процессы-потомки, то worker попрождает так называемые "нити" - потоки. И рухнувший поток запросто укладывает весь сервер. Но мы - то не собираемся собирать worker. Поэтому можем смело экспериментировать. Разберем файл apxs - это на самом деле perl-овый скрипт и нужная нам секция выглядит в нем следующим образом:

Код: Выделить всё

if ($opt_q) {
    ##       
    ##  QUERY INFORMATION 
    ##                    
    my $result = get_vars(@args);                      
    print "$result\n";
}
Чтож, есть с чем поиграться. Скопируем его в /root и немного подправим:

Код: Выделить всё

if ($opt_q) {
    ##       
    ##  QUERY INFORMATION 
    ##                    
    my $result = get_vars(@args);
    if ($result =~ "itk") { 
    print "prefork\n";           
    }                            
    else {                       
    print "$result\n";           
    }                            
}
То есть таким образом, во время сборки php вывод команды apxs -q MPM_NAME c собраным mpm-itk всегда будет "prefork". *SCARE*

Чтобы не дергать файлик туда-сюда вручную воспользуемся поддержкой хуковых скриптов EasyApache.
Создадим 2 файла - /scripts/after_apache_make_install куда запишем:

Код: Выделить всё

#!/bin/bash
if [ -f /root/apxs ]; then
mv -f /usr/local/apache/bin/apxs /usr/local/apache/bin/apxs.orig
cp -f /root/apxs /usr/local/apache/bin/apxs
fi


и
/scripts/posteasyapache

Код: Выделить всё

#!/bin/bash
if [ -f /usr/local/apache/bin/apxs.orig ]; then
rm -f /usr/local/apache/bin/apxs
mv /usr/local/apache/bin/apxs.orig /usr/local/apache/bin/apxs
fi
то есть логика следующая - собрался и установился апач, его файлик подменяется нашим, EasyApache приступает к сборке модулей, интерпретаторов и опрашивет скриптец, получает "правильный" ответ, а дальше начинают собираться модули, в т.ч. php))

Топаем в панель и собираем апач и php, включив при сборке MPMitk и отключив MPMprefork, suPHP, mod_ruid2 и кучу других ненжных модулей (вырубаем все, что не используется нами или панелью - лишний модуль - лишняя память). После сборки с учетом вышеизложеных кастомизаций у нас должно остаться 2 пункта в списке доступных типов подключения php - cgi и dso. Включаем пых как dso и перезапускаем апач. Проверяем права в папках пользователей, и приступим к написанию правильного конфига, через правку шаблона для cPanel, ибо панелька не признает никаких правок кроме как ею же созданых или дописаных в предзаготовленные ей внешние файлы и в случае обнаружения вмешательства чьих-либо шаловливых рук с радостью перезаливает все из шаблона.

Для этого скопируем файл /var/cpanel/templates/apache2/main.default в /var/cpanel/templates/apache2/main.local и открываем целевой файл для редактирования. Ищем в нем секцию:

Код: Выделить всё

# These can be set in WHM under 'Apache Global Configuration'
[% IF main.timeout.item.timeout.length %]Timeout [% main.timeout.item.timeout %][% END %]
[% IF main.traceenable.item.traceenable.length %]TraceEnable [% main.traceenable.item.traceenable %][% END %]
[% IF main.serversignature.item.serversignature.length %]ServerSignature [% main.serversignature.item.serversignature %][% END %]
[% IF main.servertokens.item.servertokens.length %]ServerTokens [% main.servertokens.item.servertokens %][% END %]
[% IF main.fileetag.item.fileetag.length %]FileETag [% main.fileetag.item.fileetag %][% END %]
[% IF main.startservers.item.startservers.length %]StartServers [% main.startservers.item.startservers %][% END %]
<IfModule prefork.c>
    [% IF main.minspareservers.item.minspareservers.length %]MinSpareServers [% main.minspareservers.item.minspareservers %][% END %]
    [% IF main.maxspareservers.item.maxspareservers.length %]MaxSpareServers [% main.maxspareservers.item.maxspareservers %][% END %]
</IfModule>
[% IF main.serverlimit.item.serverlimit.length %]ServerLimit [% main.serverlimit.item.serverlimit %][% END %]
[% IF main.maxclients.item.maxclients.length %]MaxClients [% main.maxclients.item.maxclients %][% END %]
[% IF main.maxrequestsperchild.item.maxrequestsperchild.length %]MaxRequestsPerChild [% main.maxrequestsperchild.item.maxrequestsperchild %][% END %]
[% IF main.keepalive.item.keepalive.length %]KeepAlive [% main.keepalive.item.keepalive %][% END %]
[% IF main.keepalivetimeout.item.keepalivetimeout.length %]KeepAliveTimeout [% main.keepalivetimeout.item.keepalivetimeout %][% END %]
[% IF main.maxkeepaliverequests.item.exists('maxkeepaliverequests') %]MaxKeepAliveRequests [% main.maxkeepaliverequests.item.maxkeepaliverequests || 0 %][% END %]
и добавляем после нее:

Код: Выделить всё

<IfModule mpm_itk_module>
    [% IF main.minspareservers.item.minspareservers.length %]MinSpareServers [% main.minspareservers.item.minspareservers %][% END %]
    [% IF main.maxspareservers.item.maxspareservers.length %]MaxSpareServers [% main.maxspareservers.item.maxspareservers %][% END %]
    NiceValue -2  # приоритет процесса. Зададим ему -2 (чуть выше дефолтного). Как возможно вы заметили выше, mysql у нас запускается с нулевым приоритетом (nice = 0 ), чуть более высший приоритет будет иметь веб-сервер, дабы строилась логическая цепочка - фронтэнд-бекэнд, посредством которой процессы фронтэнда не вытеснялись процессами бекэнда. 
    AssignUserID [% main.user.item.user %] [% main.group.item.group %] # запускаем дочерние процессы, обслуживающие корневые вирты и/или вирты без явно назначеного владельца от имени пользователя и группы апача, в случае с cPanel это nobody.
</IfModule>

# привнесем в скудный конфиг немного секурности и быстродействия:
ContentDigest Off 
AddDefaultCharset Off
BufferedLogs Off
LimitInternalRecursion 5
LimitRequestBody 0
ListenBacklog 1024

<IfModule mod_headers.c>
        RequestHeader unset Range
        RequestHeader unset Request-Range
</IfModule>

<IfModule reqtimeout_module>
        RequestReadTimeout header=20-40,minrate=500
        RequestReadTimeout body=10,minrate=500
</IfModule>
 
<IfModule mod_deflate.c>
        AddOutputFilterByType DEFLATE text/html text/plain text/xml
        AddOutputFilterByType DEFLATE text/css
        AddOutputFilterByType DEFLATE application/x-javascript application/javascript application/ecmascript
        AddOutputFilterByType DEFLATE application/rss+xml
        DeflateCompressionLevel 1
</IfModule>
Далее находим 2 блока

Код: Выделить всё

    [%- IF supported.mod_suphp %]
    <IfModule mod_suphp.c>
        suPHP_UserGroup nobody nobody
    </IfModule>
    [%- END %]
и после каждого дописываем:

Код: Выделить всё

    <IfModule itk.c>
        AssignUserID [% main.user.item.user %] [% main.group.item.group %]
        NiceValue -2
        MaxClientsVHost 10 # Ограничиваем количество запускаемых процессов на вирт.
    </IfModule>
Копируем файл /var/cpanel/templates/apache2/vhost.default в /var/cpanel/templates/apache2/vhost.local и правим /var/cpanel/templates/apache2/vhost.local - ищем

Код: Выделить всё

[% IF vhost.user != 'nobody' -%]
    <IfModule !mod_disable_suexec.c>
        <IfModule !mod_ruid2.c>
            SuexecUserGroup [% vhost.user %] [% vhost.group %]
        </IfModule>
    </IfModule>
    <IfModule mod_ruid2.c>
        RUidGid [% vhost.user %] [% vhost.group %]
    </IfModule>

[% END -%]
и заменяем на:

Код: Выделить всё

[% IF vhost.user != 'nobody' -%]
    <IfModule !mod_disable_suexec.c>
        <IfModule !mod_ruid2.c>
            SuexecUserGroup [% vhost.user %] [% vhost.group %]
        </IfModule>
    </IfModule>
    <IfModule mod_ruid2.c>
        RUidGid [% vhost.user %] [% vhost.group %]
    </IfModule>
    <IfModule itk.c>
        AssignUserID [% vhost.user %] [% vhost.group %]
        NiceValue -2
        MaxClientsVHost 10
    </IfModule>

[% END -%]
Также я заменил у себя блок

Код: Выделить всё

[% IF supported.mod_suphp -%]
    <IfModule mod_suphp.c>
        suPHP_UserGroup [% vhost.user %] [% vhost.group %]
    </IfModule>
[% END -%]
на

Код: Выделить всё

[% IF supported.mod_suphp -%]
    <IfModule !itk.c>
    <IfModule mod_suphp.c>
        suPHP_UserGroup [% vhost.user %] [% vhost.group %]
    </IfModule>
    </IfModule>
[% END -%]
дабы не было соблазна запускать suphp под mpm-itk *CRAZY*

блок

Код: Выделить всё

[% IF !vhost.hascgi -%]
	Options -ExecCGI -Includes
	RemoveHandler cgi-script .cgi .pl .plx .ppl .perl
[% END -%]
я перенес ниже, разместив его перед самым

Код: Выделить всё

[% IF supported.mod_alias -%]
а перед ним добавил

Код: Выделить всё

<Directory "[% vhost.documentroot %]">
    Options ExecCGI IncludesNOEXEC Indexes SymLinksIfOwnerMatch -FollowSymLinks
    AllowOverride All
</Directory>
Для чего? Семен Семеныч... В дефолтном шаблоне виртуалхоста не обозначены права и обязаности директории - они наследуются по иерархии из того, что определено в main.local, а давать разрешающие права на корень - не есть безопасно. Поэтому лучше там урезать, а здесь отпустить.
Сохраняем, пробуем пересобрать конфигу:

Код: Выделить всё

/usr/local/cpanel/bin/build_apache_conf
Если все прошло без ошибок - можно плясать дальше, ежели нет - ищем где накосячили. Причем искать придется методом исключения добавленого.

Пусть лошадь думает, у нее голова большая...
Если все предыдущие шаги выполнены, у вас ничего не сломалось, а наоборот - работает и даже немного лучше чем раньше - самое время отбросить шлак от апача. Для этого к панели уже целый модуль написали - будем ставить nginx. Следуя инструкции на сайте скачиваем модуль, распаковываем и... Ээ, нет! Я привык сначала смотреть с чем сталкиваюсь! Рассмотрим его конфиги и все что нам предлагается. Порывшись в папке /usr/src/publicnginx я все-таки обнаружил что конкретно меня не устраивало.
1. Конфиг nginx. Это конечно дело вкуса, но мой конфиг мне больше нравится нежели тот, что идет в поставке модуля. Я заменил содержимое файла nginx.conf на свой шаблончик:

Код: Выделить всё

user nobody nobody;
worker_processes 4;
worker_rlimit_nofile 20480;
worker_priority -5;
timer_resolution 100ms;
error_log  /var/log/nginx/error.log info;

events {
 worker_connections 2048;
 use epoll;
}

http {
 access_log off;
 server_name_in_redirect off;
 server_names_hash_max_size 10240;
 server_names_hash_bucket_size 1024;
 include    mime.types;
 default_type  application/octet-stream;
 server_tokens off;
 sendfile on;
 sendfile_max_chunk  128k;
 tcp_nopush on;
 tcp_nodelay on;
 keepalive_requests 100;
 keepalive_timeout 50s 35;
 keepalive_disable msie6;
 ignore_invalid_headers on;
 client_header_timeout  3m;
 client_body_timeout 3m;
 send_timeout 3m;
 reset_timedout_connection on;
 connection_pool_size  256;
 client_header_buffer_size 256k;
 large_client_header_buffers 4 256k;
 client_max_body_size 200M; 
 client_body_buffer_size 128k;
 request_pool_size 32k;
 output_buffers 4 32k;
 postpone_output 1460;
 client_body_in_file_only on;
 log_format bytes_log "$msec $bytes_sent .";
 underscores_in_headers on;
 lingering_time 30;
 lingering_timeout 10;
 
 proxy_temp_path  /tmp/nginx/nginx_proxy/;
 proxy_cache_path /tmp/nginx/proxy_temp levels=2 keys_zone=pagecache:5m inactive=10m max_size=50m;
 
 proxy_cache pagecache;
 proxy_headers_hash_bucket_size 6400;
 proxy_headers_hash_max_size 51200;
 proxy_connect_timeout 90;
 proxy_send_timeout 90;
 proxy_read_timeout 320;
 proxy_buffer_size 64k;
 proxy_buffers 16 32k;
 proxy_busy_buffers_size 64k;
 proxy_temp_file_write_size 64k;
 proxy_redirect off;
 proxy_hide_header Vary;
 proxy_set_header Accept-Encoding '';
 proxy_ignore_headers Cache-Control Expires;
 proxy_ignore_client_abort off;
 proxy_intercept_errors off;
 proxy_set_header Referer $http_referer;
 proxy_set_header Host   $host;
 proxy_set_header Range "";
 proxy_set_header Request-Range "";
 proxy_set_header Cookie $http_cookie;
 
 gzip off;
 gzip_vary on;
 gzip_disable "MSIE [1-6]\.";
 gzip_proxied any;
 gzip_http_version 1.1;
 gzip_min_length  1000;
 gzip_comp_level  6;
 gzip_buffers  16 8k;
 gzip_types    text/plain text/xml text/css application/x-javascript application/xml application/xml+rss text/javascript application/atom+xml;

 include "/etc/nginx/vhosts/*";
}
Далее мне не понравился конфиг виртуалхостов. Опять же он в принципе не так уж и плох, но мне мой шаблон роднее - я заменил в файле createvhosts.py

Код: Выделить всё

def writeconfded(user, domain, docroot, passedip, alias):
        user = user
        domain = domain
        passedip = passedip
        dedipvhost = """server {
          error_log /var/log/nginx/vhost-error_log warn;
          listen %s:80;
          server_name %s %s %s;
          access_log /usr/local/apache/domlogs/%s bytes_log;
          access_log /usr/local/apache/domlogs/%s combined;
          root %s;
          location / {
          location ~.*\.(3gp|gif|jpg|jpeg|png|ico|wmv|avi|asf|asx|mpg|mpeg|mp4|pls|mp3|mid|wav|swf|flv|html|htm|txt|js|css|exe|zip|tar|rar|gz|tgz|bz2|uha|7z|doc|docx|xls|xlsx|pdf|iso)$ {
          expires 1d;
          try_files $uri @backend;
          }
          error_page 405 = @backend;
          add_header X-Cache "HIT from Backend";
          proxy_pass http://%s:8081;
          include proxy.inc;
          }
          location @backend {
          internal;
          proxy_pass http://%s:8081;
          include proxy.inc;
          }
          location ~ .*\.(php|jsp|cgi|pl|py)?$ {
          proxy_pass http://%s:8081;
          include proxy.inc;
          }
          location ~ /\.ht {
          deny all;
          }
        }""" % (passedip, domain, alias, passedip, domain + "-bytes_log", domain, docroot, passedip, passedip, passedip)
        if not os.path.exists( '/etc/nginx/vhosts'):
                os.makedirs('/etc/nginx/vhosts')
        if os.path.exists( '/etc/nginx/staticvhosts/' + domain):
                pass
        else:
                domainvhost = open ('/etc/nginx/vhosts/' + domain, 'w')
                domainvhost.writelines( dedipvhost )
                domainvhost.close()

def writeconfshared(user,domain,docroot,passedip, alias):
        sharedipvhost = """server {
          error_log /var/log/nginx/vhost-error_log warn;
          listen %s:80;
          server_name %s %s;
          access_log /usr/local/apache/domlogs/%s bytes_log;
          access_log /usr/local/apache/domlogs/%s combined;
          root %s;
          location / {
          location ~.*\.(3gp|gif|jpg|jpeg|png|ico|wmv|avi|asf|asx|mpg|mpeg|mp4|pls|mp3|mid|wav|swf|flv|html|htm|txt|js|css|exe|zip|tar|rar|gz|tgz|bz2|uha|7z|doc|docx|xls|xlsx|pdf|iso)$ {
          expires 1d;
          try_files $uri @backend;
          }
          error_page 405 = @backend;
          add_header X-Cache "HIT from Backend";
          proxy_pass http://%s:8081;
          include proxy.inc;
          }
          location @backend {
          internal;
          proxy_pass http://%s:8081;
          include proxy.inc;
          }
          location ~ .*\.(php|jsp|cgi|pl|py)?$ {
          proxy_pass http://%s:8081;
          include proxy.inc;
          }
          location ~ /\.ht {
          deny all;
          }
        }""" % (passedip, domain, alias, domain + "-bytes_log", domain, docroot, passedip, passedip, passedip)
        if not os.path.exists( '/etc/nginx/vhosts'):
                os.makedirs('/etc/nginx/vhosts')
        if os.path.exists( '/etc/nginx/staticvhosts/' + domain):
                pass
        else:
                domainvhost = open ('/etc/nginx/vhosts/' + domain, 'w')
                domainvhost.writelines( sharedipvhost )
                domainvhost.close()
на

Код: Выделить всё

def writeconfded(user, domain, docroot, passedip, alias):
        user = user
        domain = domain
        passedip = passedip
        dedipvhost = """server {
          listen %s:80;
          server_name %s %s %s rcvbuf=8192 sndbuf=16384 backlog=32000;
          access_log /usr/local/apache/domlogs/%s bytes_log;
          access_log /usr/local/apache/domlogs/%s combined;
          location ~* ^.+\.(3gp|gif|jpg|jpeg|png|ico|wmv|avi|asf|asx|mpg|mpeg|mp4|pls|mp3|mid|wav|swf|flv|html|htm|txt|js|css|exe|zip|tar|rar|gz|tgz|bz2|uha|7z|doc|docx|xls|xlsx|pdf|iso)$ {
                    root %s;
                    expires 1d;
                    error_log /var/log/nginx/vhost-error_log warn;
                    error_page 403 = @fallback;
                    error_page 404 = @fallback;
                    error_page 405 = @fallback;
                    try_files $uri $uri/ @fallback;
          }
          location / {
                    proxy_pass http://%s:8081;
                    proxy_redirect http://%s:8081/ /;
                    proxy_set_header Host $host;
                    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                    proxy_set_header X-Forwarded-Proto $scheme;
                    proxy_set_header X-Real-IP $remote_addr;
          }
 
          location @fallback {
                    proxy_pass http://%s:8081;
                    add_header X-Cache "Jump to backend";
                    proxy_set_header Host $host;
                    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                    proxy_set_header X-Forwarded-Proto $scheme;
                    proxy_set_header X-Real-IP $remote_addr;
          }
 
          location ~ /\.ht {
                    deny all;
          }
        }""" % (passedip, domain, alias, passedip, domain + "-bytes_log", domain, docroot, passedip, passedip, passedip)
        if not os.path.exists( '/etc/nginx/vhosts'):
                os.makedirs('/etc/nginx/vhosts')
        if os.path.exists( '/etc/nginx/staticvhosts/' + domain):
                pass
        else:
                domainvhost = open ('/etc/nginx/vhosts/' + domain, 'w')
                domainvhost.writelines( dedipvhost )
                domainvhost.close()

def writeconfshared(user,domain,docroot,passedip, alias):
        sharedipvhost = """server {
          listen %s:80;
          server_name %s %s;
          access_log /usr/local/apache/domlogs/%s bytes_log;
          access_log /usr/local/apache/domlogs/%s combined;
          location ~* ^.+\.(3gp|gif|jpg|jpeg|png|ico|wmv|avi|asf|asx|mpg|mpeg|mp4|pls|mp3|mid|wav|swf|flv|html|htm|txt|js|css|exe|zip|tar|rar|gz|tgz|bz2|uha|7z|doc|docx|xls|xlsx|pdf|iso)$ {
                    root %s;
                    expires 1d;
                    error_log /var/log/nginx/vhost-error_log warn;
                    error_page 403 = @fallback;
                    error_page 404 = @fallback;
                    error_page 405 = @fallback;
                    try_files $uri $uri/ @fallback;
          }
          location / {
                    proxy_pass http://%s:8081;
                    proxy_redirect http://%s:8081/ /;
                    proxy_set_header Host $host;
                    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                    proxy_set_header X-Forwarded-Proto $scheme;
                    proxy_set_header X-Real-IP $remote_addr;
          }
 
          location @fallback {
                    proxy_pass http://%s:8081;
                    add_header X-Cache "Jump to backend";
                    proxy_set_header Host $host;
                    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                    proxy_set_header X-Forwarded-Proto $scheme;
                    proxy_set_header X-Real-IP $remote_addr;
          }
 
          location ~ /\.ht {
                    deny all;
          }
        }""" % (passedip, domain, alias, domain + "-bytes_log", domain, docroot, passedip, passedip, passedip)
        if not os.path.exists( '/etc/nginx/vhosts'):
                os.makedirs('/etc/nginx/vhosts')
        if os.path.exists( '/etc/nginx/staticvhosts/' + domain):
                pass
        else:
                domainvhost = open ('/etc/nginx/vhosts/' + domain, 'w')
                domainvhost.writelines( sharedipvhost )
                domainvhost.close()
В файле nginxinstaller заменяем

Код: Выделить всё

--http-client-body-temp-path=/tmp/nginx_client  --http-proxy-temp-path=/tmp/nginx_proxy  --http-fastcgi-temp-path=/tmp/nginx_fastcgi
на

Код: Выделить всё

--http-client-body-temp-path=/tmp/nginx/nginx_client  --http-proxy-temp-path=/tmp/nginx/nginx_proxy  --http-fastcgi-temp-path=/tmp/nginx/nginx_fastcgi
(не люблю беспорядок)

ищем:

Код: Выделить всё

proc = subprocess.Popen("rm -f /etc/nginx/proxy.inc > /dev/null 2>&1", shell=True)
output = proc.communicate()

proc = subprocess.Popen("cp /" + currentdir + "/proxy.inc /etc/nginx/proxy.inc", shell=True)
output = proc.communicate()
и заменяем на

Код: Выделить всё

proc = subprocess.Popen("mkdir -p /tmp/nginx/nginx_proxy", shell=True)
output = proc.communicate()

proc = subprocess.Popen("mkdir -p /tmp/nginx/nginx_fastcgi", shell=True)
output = proc.communicate()

proc = subprocess.Popen("mkdir -p /tmp/nginx/nginx_client", shell=True)
output = proc.communicate()

proc = subprocess.Popen("chown -R nobody:nobody /tmp/nginx", shell=True)
output = proc.communicate()
Можно ставить. В принципе у меня все завелось с первого раза, один только момент - откройте любой сайт на этом сервере в браузере Opera (можно Chrome, Firefox но я не заморачивался как в них это делать), скопируйте ссылку на любой рисунок на странице, в контекстном меню выберите "Проинспектировать элемент". Запустится Opera Dragonfly, где на вкладке "Сеть" откройте подвкладку "Создать запрос" и вставьте скопированную ссылку на изображение в поле "URL" и нажмите "Отправить запрос". Почитайте поле "Ответ" и если там обнаружилась строчка "Jump to backend", значит у нас не все гладко - статику отдает apache. Время почитать vhost-error.log nginx'а, где вы скорее всего обнаружите кучу "Permission denied". Не стоит кататься по полу - скорее всегоу вас nginx просто не может войти в папку к пользователю. Помните - права на папки всегда должны быть 0755. Исправляем ситуёвину:

Код: Выделить всё

find /home -type f -exec chmod 0755 {} \;
Идем в панель и доводим тюнинг до логического завержения. Я в частности применил сл. параметры для apache:

Код: Выделить всё

Timeout 300
TraceEnable Off
ServerSignature Off
ServerTokens ProductOnly
FileETag None
StartServers 5

MinSpareServers 5
MaxSpareServers 10

ServerLimit 35
MaxClients 25
MaxRequestsPerChild 100
KeepAl% END %ive Off
KeepAliveTimeout 5
MaxKeepAliveRequests 60
Попробовал подать тот же запрос которым мучал его раньше, увеличив вывод страницы до 10 000 результатов - выгреб как родное!

Результаты нагрузочного теста via siege:

Код: Выделить всё

Transactions: 487 hits
Availability: 97.40 %
Elapsed time: 839.42 secs
Data transferred: 3.63 MB
Response time: 7.61 secs
Transaction rate: 0.58 trans/sec
Throughput: 0.00 MB/sec
Concurrency: 4.42
Successful transactions: 487
Failed transactions: 13
Longest transaction: 29.60
Shortest transaction: 1.31
До тюнинга Availability был всего 74% :)

Теперь можно с гордостью собирать eAccelerator или Xcache - с нашим апачем они будут работать (с suphp они не дружат). Кстати я предпочитаю использовать phpexpress ;)
Я не злопамятный, я просто часто ковыряю логи
Аватара пользователя
Raven
Бородатый сис
Бородатый сис
Сообщения: 2800
Зарегистрирован: 03 мар 2010, 15:12
ОС: RHEL 8
Откуда: Из серверной

Re: Оптимизация сервера с WHM/cPanel - mpm-itk, nginx и пр.

Сообщение Raven » 12 фев 2013, 12:30

А теперь бонусом - актуальные конфиги для сервака в 4 Гб оперы и 8 ведер.

my.cnf

Код: Выделить всё

[mysqld]                                               
datadir=/var/lib/mysql                                 
socket=/var/lib/mysql/mysql.sock                       
log-error=/var/lib/mysql/error.log               
slow-query-log-file=/var/lib/mysql/slow.log      
log-queries-not-using-indexes                          
slow-query-log                                         
long_query_time=5                                      

skip-external-locking
max_connections=50   
max_connect_errors=20
symbolic-links=0     
character-set-server=utf8
collation-server=utf8_general_ci
wait_timeout=300                
low-priority-updates            
old_passwords=1                 

tmp_table_size=128M
max_heap_table_size=128M
tmpdir=/tmp             

# Timeouts
interactive_timeout=28800
wait_timeout=50          
connect_timeout=10       
delayed_insert_timeout=50

# Threads
thread_concurrency=8
thread_cache_size=8 
thread_stack=256K   

# MyISAM tuning
max_allowed_packet=16M
myisam_sort_buffer_size=64M
myisam-recover=BACKUP      
table_cache=256            
key_buffer_size=32M        
sort_buffer_size=4M        
read_buffer_size=4M        
read_rnd_buffer_size=16M   
join_buffer_size=4M        
net_buffer_length=2K       


# Queries cache tunning
query_cache_limit=2M   
query_cache_size=128M  
query_cache_type=1     

# InnoDB tuning
innodb_file_per_table
innodb_data_home_dir = /var/lib/mysql
innodb_data_file_path = ibdata1:10M:autoextend:max:15G
innodb_log_group_home_dir = /var/lib/mysql
innodb_buffer_pool_size=16M
innodb_additional_mem_pool_size=2M
innodb_log_file_size=5M
innodb_log_buffer_size=8M
innodb_flush_log_at_trx_commit=0
innodb_lock_wait_timeout=50
innodb_autoinc_lock_mode=2
innodb_thread_concurrency = 16
innodb_doublewrite=0
innodb_checksums=0
innodb_support_xa=0
innodb_max_dirty_pages_pct=15

innodb_open_files=1000
innodb_buffer_pool_instances=2
innodb_read_io_threads=8
innodb_write_io_threads=8
innodb_io_capacity=2000

[mysqldump]
quick
quote-names
max_allowed_packet=128M

[mysql]
no-auto-rehash
safe-updates
max_allowed_packet=128M

[myisamchk]
key_buffer_size=20M
sort_buffer_size=20M
read_buffer=2M
write_buffer=2M

[mysqlhotcopy]
interactive-timeout

[mysqld_safe]
log-error=/var/lib/mysql/error.log
open_files_limit=20000
nice=0r
nginx.conf

Код: Выделить всё

user nobody nobody;                                                
worker_processes 16;                                               
worker_rlimit_nofile 20480;                                        
worker_priority -5;                                                
timer_resolution 100ms;                                            
error_log  /var/log/nginx/error.log warn;                          

events {
   worker_connections 2048;
   use epoll;              
}                          

http {
   access_log off;
   log_format bytes_log "$msec $bytes_sent .";
   include    mime.types;                     
   default_type  application/octet-stream;    
   server_name_in_redirect off;               
   server_names_hash_max_size 10240;          
   server_names_hash_bucket_size 1024;        
   server_tokens off;                         
   ignore_invalid_headers on;                 
   reset_timedout_connection on;              
   underscores_in_headers on;                 
   sendfile off;                              
   sendfile_max_chunk  128k;                  
   keepalive_requests 100;                    
   keepalive_timeout 50s 35;                  
   keepalive_disable msie6;                   
   tcp_nopush on;                             
   tcp_nodelay on;                            
   client_header_timeout  3m;                 
   client_body_timeout 3m;                    
   send_timeout 3m;                           
   connection_pool_size 512;                  
   client_header_buffer_size 256k;            
   large_client_header_buffers 16 1024k;      
   client_max_body_size 200M;                 
   client_body_buffer_size 256k;              
   request_pool_size 32k;                     
   output_buffers 4 32k;                      
   postpone_output 1460;                      

   lingering_time 30;
   lingering_timeout 10;

   proxy_temp_path  /tmp/nginx/proxy/;
   proxy_cache_path /tmp/nginx/proxy/temp levels=2 keys_zone=pagecache:5m inactive=10m max_size=50m;

   proxy_headers_hash_bucket_size 6400;
   proxy_headers_hash_max_size 51200;  
   proxy_connect_timeout 90;           
   proxy_send_timeout 90;              
   proxy_read_timeout 320;             
   proxy_buffer_size 64k;              
   proxy_buffers 16 32k;               
   proxy_busy_buffers_size 64k;        
   proxy_temp_file_write_size 64k;     
   proxy_redirect off;                 
   proxy_hide_header Vary;             
   proxy_set_header Accept-Encoding '';
   proxy_ignore_headers Cache-Control Expires;
   proxy_ignore_client_abort off;             
   proxy_intercept_errors off;                
   proxy_pass_header Set-Cookie;              
   proxy_set_header Referer $http_referer;    
   proxy_set_header Range "";                 
   proxy_set_header Request-Range "";         
   proxy_set_header Cookie $http_cookie;      

   gzip on;
   gzip_vary on;
   gzip_disable "MSIE [1-6]\.";
   gzip_proxied any;           
   gzip_http_version 1.1;      
   gzip_min_length  1000;      
   gzip_comp_level  6;         
   gzip_buffers  16 8k;        
   gzip_types    text/plain text/xml text/css application/x-javascript application/xml application/xml+rss text/javascript application/atom+xml;

    server {
          listen 0.0.0.0:80 rcvbuf=8192 sndbuf=16384 backlog=32000 default;
          server_name $hostname http://www.$hostname;     
          access_log /usr/local/apache/domlogs/$hostname-bytes_log bytes_log;
          access_log /usr/local/apache/domlogs/$hostname.com combined;
          location / {
                    proxy_pass http://127.0.0.1:8081;
                    proxy_redirect http://127.0.0.1:8081/ /;
                    proxy_set_header Host $host;
                    proxy_set_header X-Forwarded-Host $host;
                    proxy_set_header X-Forwarded-Server $host;
                    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                    proxy_set_header X-Forwarded-Proto $scheme;
                    proxy_set_header X-Real-IP $remote_addr;
                    proxy_cache pagecache;
          }
    }
  include "/etc/nginx/vhosts/*";
}
createvhost.py

Код: Выделить всё

def writeconfded(user, domain, docroot, passedip, alias):
        user = user                                      
        domain = domain                                  
        passedip = passedip                              
        dedipvhost = """server { 
          listen %s:80 ;
           server_name %s %s; #%s 
           access_log /usr/local/apache/domlogs/%s bytes_log;
           access_log /usr/local/apache/domlogs/%s combined;
           # Tuning
           client_header_buffer_size 256k;  
           client_body_buffer_size 256k;
           large_client_header_buffers 16 256k; 
           request_pool_size 32k;
           location ~* ^.+\.(3gp|gif|jpg|jpeg|png|ico|wmv|avi|asf|asx|mpg|mpeg|mp4|pls|mp3|mid|wav|swf|flv|html|htm|txt|js|css|exe|zip|tar|rar|gz|tgz|bz2|uha|7z|doc|docx|xls|xlsx|pdf|iso)$ { 
                     root %s;
                     expires 1d;
                     error_log /var/log/nginx/vhost-error_log warn; 
                     error_page 403 = @fallback;
                     error_page 404 = @fallback;
                     error_page 405 = @fallback; 
                     try_files $uri $uri/ @fallback; 
           } 
           location / {
                     proxy_pass http://%s:8081; 
                     proxy_redirect http://%s:8081/ /; 
                     proxy_cache pagecache; 
                     proxy_set_header Host $host; 
                     proxy_set_header X-Forwarded-Host $host; 
                     proxy_set_header X-Forwarded-Server $host;
                     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 
                     proxy_set_header X-Forwarded-Proto $scheme;
                     proxy_set_header X-Real-IP $remote_addr; 
           } 

           location @fallback {  
                     proxy_pass http://%s:8081; 
                     proxy_redirect off;
                     add_header X-Cache "Jump to backend"; 
                     proxy_cache pagecache;
                     proxy_set_header Host $host; 
                     proxy_set_header X-Forwarded-Host $host; 
                     proxy_set_header X-Forwarded-Server $host; 
                     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 
                     proxy_set_header X-Forwarded-Proto $scheme; 
                     proxy_set_header X-Real-IP $remote_addr; 
           }
 
           location ~ /\.ht { 
                     deny all; 
           }
        }""" % (passedip, domain, alias, passedip, domain + "-bytes_log", domain, docroot, passedip, passedip, passedip)                                       
        if not os.path.exists( '/etc/nginx/vhosts'):
                os.makedirs('/etc/nginx/vhosts')
        if os.path.exists( '/etc/nginx/staticvhosts/' + domain): 
                pass
        else: 
                domainvhost = open ('/etc/nginx/vhosts/' + domain, 'w') 
                domainvhost.writelines( dedipvhost )
                domainvhost.close() 

def writeconfshared(user,domain,docroot,passedip, alias):
        sharedipvhost = """server {  
          listen %s:80;
           server_name %s %s;
           access_log /usr/local/apache/domlogs/%s bytes_log;
           access_log /usr/local/apache/domlogs/%s combined; 
           # Tuning
           client_header_buffer_size 256k;
           client_body_buffer_size 256k;
           large_client_header_buffers 16 256k;
           request_pool_size 32k; 
           location ~* ^.+\.(3gp|gif|jpg|jpeg|png|ico|wmv|avi|asf|asx|mpg|mpeg|mp4|pls|mp3|mid|wav|swf|flv|html|htm|txt|js|css|exe|zip|tar|rar|gz|tgz|bz2|uha|7z|doc|docx|xls|xlsx|pdf|iso)$ {
                     root %s;
                     expires 1d;
                     error_log /var/log/nginx/vhost-error_log warn; 
                     error_page 403 = @fallback;
                     error_page 404 = @fallback;
                     error_page 405 = @fallback;
                     try_files $uri $uri/ @fallback;
           } 
           location / { 
                     proxy_pass http://%s:8081; 
                     proxy_redirect http://%s:8081/ /; 
                     proxy_cache pagecache; 
                     proxy_set_header Host $host;
                     proxy_set_header X-Forwarded-Host $host; 
                     proxy_set_header X-Forwarded-Server $host;
                     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 
                     proxy_set_header X-Forwarded-Proto $scheme; 
                     proxy_set_header X-Real-IP $remote_addr;
           }
   
           location @fallback { 
                     proxy_pass http://%s:8081;
                     proxy_redirect off;
                     add_header X-Cache "Jump to backend";
                     proxy_cache pagecache; 
                     proxy_set_header Host $host;
                     proxy_set_header X-Forwarded-Host $host;
                     proxy_set_header X-Forwarded-Server $host;
                     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                     proxy_set_header X-Forwarded-Proto $scheme;
                     proxy_set_header X-Real-IP $remote_addr;
           } 

           location ~ /\.ht { 
                     deny all;
           } 
        }""" % (passedip, domain, alias, domain + "-bytes_log", domain, docroot, passedip, passedip, passedip) 
        if not os.path.exists( '/etc/nginx/vhosts'): 
                os.makedirs('/etc/nginx/vhosts')
        if os.path.exists( '/etc/nginx/staticvhosts/' + domain): 
                pass 
        else: 
                domainvhost = open ('/etc/nginx/vhosts/' + domain, 'w')  
                domainvhost.writelines( sharedipvhost ) 
                domainvhost.close()
apache.conf

Код: Выделить всё

# В основном конфиге:
Timeout 45
TraceEnable Off
ServerSignature Off
ServerTokens ProductOnly
FileETag None
StartServers 10
<IfModule prefork.c>
MinSpareServers 10
MaxSpareServers 20
</IfModule>
ServerLimit 256
MaxClients 50
MaxRequestsPerChild 400
KeepAlive Off
KeepAliveTimeout 2
MaxKeepAliveRequests 60

<IfModule itk.c>
 MinSpareServers 10
 MaxSpareServers 20
 AssignUserID nobody nobody
 NiceValue 0
</IfModule>

ContentDigest Off
AddDefaultCharset Off
BufferedLogs Off
LimitInternalRecursion 5
LimitRequestBody 0
ListenBacklog 1024

# В виртуалхостах:
    <IfModule itk.c>                       
        AssignUserID $username $groupname       
        NiceValue -3                       
        MaxClientsVHost 20                 
    </IfModule>
Я не злопамятный, я просто часто ковыряю логи
Ответить

Вернуться в «Полезные советы»