Проверка сервера “на вшивость” или “Как заDoSить Daemony.org?” (Оптимизируем связку Apache+PHP+MySQL)
Вообще, надумал я это все утворить после этого комментария. Самому стало интересно, сколько можно выжать на пределе из собственного сервера. Решил вечером после работы заняться экспериментами, устроить экстрим-тест.
В принципе, за все время существования daemony.org как сервера ну и этого блога на нем, каких-то особых проблем с перегрузками я не замечал. Блог не так уж популярен как, например, тот же Опеннет. И хотя с каждым днем его приходят читать все больше и больше людей, live.daemony.org к большой популярности особо не стремится. Но, что если…?
Что если снова, как когда-то в пятницу, 28 марта 2008 года этот блога надумают навестить одновременно 195 ботов Яху?
Тогда, год назад нашествие было пережито довольно тяжело. Сопровождалось оно немерянным поглощением свопа процессами httpd и mysqld и в итоге сервер на две минуты был потерян из виду. После того, как рухнул mysql и высвободилась память, систему вроде попустило, но массовое скопление пауков продолжалось еще около 10 минут.
Машина тогда работала на третьем Пентиуме и имела в слотах 128 метров физической памяти. Над подгонкой параметров Apache и MySQL я тоже тогда особо не задумывался. Работало как работало. Но, в настоящий момент daemony.org крутится на следующей конфигурации:
- CPU: Intel(R) Celeron-D 2.13GHz (2133.42-MHz 686-class CPU)
- MB: ASUS
- RAM: 1024 MB
- HDD: IDE UDMA100 (Seagate ST3120026A 8.54) 120 GB (как основной, системный)
И еще висит пару винтов поменьше основного для файлопомойки, бекапов и т.п.
Согласен, конфигурация тоже не жирная, однако получше, чем было. Да и платный хостинг я как бы не предлагаю. Тем не менее, это хозяйство, под управлением FreeBSD держит на своих плечах маршрузатор для доступа в сеть Интернет, VPN тоннель (дом <-> работа), прокси сервер, Samba сервер, на котором вместе с FTP сервером крутится файлопомойка, почтовый SMTP+POP3+IMAP4 сервер (не коммерческий, а только для личных целей), DNS сервер, поддерживающий уже почти четыре десятка доменных зон. Ну и конечно же Apache и MySQL для хостинга. В придачу ко всему болтается ml-donkey - клиент для всех известных файлообменных сетей. (Балуемся периодически.
)
В тех же самых комментариях, был брошен “камень в огород” разработчикам Wordpress. Что сказать, движок действительно не самый легкий, какие существуют. Особенно, если учесть, сколько еще кривых плагинов под него написано…
Итак, проводим экстрим-тест сайта на не самом легком движке.
ЦЕЛЬ
Искусственно создав множественные обращения к Wordpress блогу положить Apache сервер и/или MySQL и/или в целом вызвать отказ в обслуживании системы (если повезет).
В качестве инструмента для такого DoS’а я задействовал консоль другой FreeBSD машины, которая стоит на моей работе. От сервера на работе к Daemony.org канал достаточно широкий и короткий и машина ничем особо не нагружена. Вот на ней то я и решил запустить 300 параллельных процессов wget с опциями для тотального выкачивания сайтов.
Делается это так:
wget -b -c -r -l 0 -k --cache=off http://site.com/
Если через точку с запятой повторить эту команду 300 раз и нажать Enter, то произойдет нечто страшное. Wget вообще сам по-себе ужасная штука. Не верите? Проверьте сами. (cуществует также версия wget под Windows). Только в качестве целевого адреса не указывайте live.daemony.org иначе на один час Ваш IP попадет в бан.
После запуска трех сотен копий wget’на удаленной машине с параметрами рекурсивного сохранения страниц сайта, с Daemony.org тут же произошли резкие перемены. top, висевший в консоли, замер и отозвался где-то через полминуты, сигнализируя о высоком уровне потребления swap памяти. Задержки при ответе веб-сервера возросли до 30 секунд… Блог начал открываться с бешенными тормозами. Но открываться продолжал.
И здесь я вспомнил об одном чудном правиле моего фаерволла ipfw, которое прописал когда-то да позабыл о нем. Это правило (лично на мой взгляд) обязательно должен иметь в своем наборе каждый администратор хостинг-сервера для предотвращения DoS атаки с определенного IP, в данном примере, по HTTP прокотолу. Вот оно:
ipfw add allow tcp from any to {$MY_IP} dst-port 80 in via {$Iface} setup limit src-addr 100
Циферка в конце этого правила указывает на количество соединений, которые максимально допустимы с одного любого IP адреса к моему внешнему IP на порт 80. Если клиент с IP адреса XXX.XXX.XXX.XXX откроет на наш порт 80 больше, чем 100 соединений, все лишние соединения будут отрезаны файерволлом, а лог /var/log/security ipfw напишет такое:
... ipfw: 2800 drop session YYY.YYY.YYY.YYY:51672 -> XXX.XXX.XXX.XXX:80, too many entries
Не помню, когда я добавлял это правило в свой ipfw, но здесь пришло время ему себя показать. Я решил его удалить и посмотреть что-же произойдет.
Как итог, с Апачем у меня случился ступор, а с MySQL обморок, в результате которого mysqld ушел в даун. Справа видно, как это выразилось на графиках мониторинга.
Выходит, что ограничение limit src-addr 100 работало и реально помогало. Конечно, от распределенной DoS, когда запросы на подключение приходят с разных IP адресов, это правило не спасет. Но написав умный скрипт, который будет анализировать логи и динамически вносить в тэйблз ipfw адреса нападающих, попытаться уберечь машину от дауна можно, даже если канал в Сеть будет полностью загажен приходящими запросами. Вот только значение - 100 - мне кажется явно завышено. Живой человек единовременно не может браузером генерить столько запросов.
Но есть еще поисковые боты, которые это могут. А еще есть вероятность, что несколько пользователей одной локальной сети, будучи за NAT’ом могут единовременно просматривать сайт, при этом естественно выходя в сеть Интернет с одного “реального” IP адреса.
Следовательно, для веб-сервера следует подобрать некую наиболее оптимальную конфигурацию, чтобы он без особого труда мог обслуживать разумное количество запросов в единицу времени.
В Сети многие рассуждали и рассуждают о выборе этой самой оптимальной конфигурации, но проблема состоит в том, что единого и оптимального решения не существует, поскольку каждый конкретный случай зависит от конфигурации железа, от структуры скриптов на сайтах, от их количества, степени использования БД и т.п.
Когда-то я наталкивался на одну интересную статью на сайте ibm.com которая повествует о выборе оптимальных настроек для Apache. Сегодня, благодаря Google, я нашел ее снова и еще раз перечитал. Вы тоже можете . Она переведена на русский язык и хоть написана она под Linux системы, “перевести” ее на язык FreeBSD не сложно. Автор статьи Шон Волберг (Sean Walberg) - с 1994 года занимающийся администрированием Unix систем. Согласно рекомендациями мистера Волберга, а также с оглядкой на системные требования Wordpress, у меня получились вот что.
sysctl.conf
kernel.shmmax=67108864 net.inet.ip.portrange.first=1024 net.inet.ip.portrange.last=65535
httpd.conf
Timeout 300 KeepAlive On MaxKeepAliveRequests 100 KeepAliveTimeout 2 StartServers 30 MinSpareServers 10 MaxSpareServers 20 MaxClients 200 MaxRequestsPerChild 100
Это основные директивы Apache 1.3.x, которые регламентируют его производительность. Путем ряда экспериментов я выяснил, что немало ресурсов освобождается при занижении KeepAliveTimeout. Убрав его с 30 до 2 я это заметил тут же по top‘у процессов. Кроме этого, при резко возросшей нагрузке помогло увеличение StartServers - количества httpd процессов, постоянно висящих в фоне и готовых обслужить новые запросы. Что касается MaxRequestsPerChild - это значение не рекомендуют делать слишком высоким дабы избежать утечки памяти. Дочерний процесс httpd должен будет обслужить в данном случае 100 запросов, а после этого погибнет. На его место родительский процесс породит новое дитя.
php.ini
max_execution_time = 120 max_input_time = 60 memory_limit = 64M output_buffering = 4096 ... eaccelerator.shm_size="64" # Если используется eaccelerator
Лимит памяти указан такой только благодаря наличию Wordpress. Если на Вашем сервере его нет, значение можно снизить до дефолтового.
Настройки базы данных в конфигурационном файле /etc/my.cnf были взяты из каталога /usr/local/share/mysql/ из примера my-large.cnf Пробовались также настройки из файла my-medium.cnf но с ними mysqld процесс постоянно висит в top’е и кушает процессор.
my.cnf
# #####################################################
# /etc/my.cnf MySQL config file by Daemony
# #####################################################
[client]
port = 3306
socket = /tmp/mysql.sock
[mysqld]
port = 3306
socket = /tmp/mysql.sock
bind-address = 127.0.0.1
skip-locking
key_buffer_size = 256M
max_allowed_packet = 1M
table_cache = 256
sort_buffer_size = 1M
read_buffer_size = 1M
read_rnd_buffer_size = 4M
myisam_sort_buffer_size = 64M
thread_cache = 40
query_cache_size = 16M
thread_concurrency = 2
net_buffer_length = 16K
max_connections = 300
long_query_time=5
skip-federated
skip-innodb
skip-bdb
skip-name-resolve
[mysqldump]
quick
max_allowed_packet = 16M
[mysql]
no-auto-rehash
[isamchk]
key_buffer = 128M
sort_buffer_size = 128M
read_buffer = 2M
write_buffer = 2M
[myisamchk]
key_buffer = 128M
sort_buffer_size = 128M
read_buffer = 2M
write_buffer = 2M
[mysqlhotcopy]
interactive-timeout
# #####################################################
Что касается настроек MySQL сервера, то говорят, очень сильно помогает кеширование тредов. В моем примере это прописано в параметре thread_cache. Чтобы от этой опции был толк, база данных должна быть собрана с параметром WITH_PROC_SCOPE_PTH=yes. Тогда во время работы сервера БД, порожденные треды (соединения) “умирать” не будут, а будут ложиться в кеш, максимально в том количестве, которое задано в thread_cache и далее при следующих запросах серверу уже не будет необходимости порождать новые треды. Он будет брать их из кеша, тем самым не расходуя ресурсы на ненужные действия.
Посмотреть, что сейчас творится с тредами можно утилитой mysqladmin
$ mysqladmin extended-status -u root -p | grep Thread | Threads_cached | 23 | | Threads_connected | 2 | | Threads_created | 25 | | Threads_running | 1 |
Нас в выводе команды больше всего будет интересовать первая и третья строчки. Первая - количество кешированных тредов; третья - количество тредов, которые были созданы с момента запуска MySQL сервера. Количество кешированных тредов не должно слишком отставать от количества созданных. Я для себя подобрал оптимальное (на мой взгляд), значение количества тредов - thread_cache = 40. Но вообще рекомендуется от 8 до 100. Для Вашего случая Вам, возможно, прийдется поиграться настройками в my.cnf. Попробуйте поизменять thread_cache, каждый раз, проверяя через extended-status сколько тредов закешировано и сколько создано. Правда, для проведения такого подбора Вам понадобится чтобы Ваш MySQL сервер поработал некоторое время под своей обычной нагрузкой. Но самое главное, чтобы created на порядки не превышало cached.
Подбор значения thread_cache не даст Вам никакого прироста производительности, если на Вашем сервере мало SQL баз или же они почти не используются.
Вот, собственно, что было сегодня проделано. Напоследок, я все же уменьшил в ipfw максимальное количество открытых соединений с одного IP адреса до 20. Не думаю, что это мало.
Если во время моих экспериментов кому-то из Вас, дорогие читатели, были доставлены неудобства из-за того, что Вы не смогли попасть на сайт - приношу свои извинения.
Результат всей этой оптимизации, я думаю, уже будет (или не будет) отчетливо просматриваться через пару деньков на графиках мониторинга. А если у Вас после прочтения этой публикации возникло желание что-то дополнить, либо поделиться собственным опытом оптимизации Apache и/или MySQL, милости прошу в комментарии.
P.S.: …и вообще, пора бы уже попробовать перейти на nginx…
Похожие публикации
Теги: apache, FreeBSD, mysql, php, защита, настройка, опимизация, полезное, рекомендации, тюнинг


Кроме ipfw можно было бы pf использовать. И тогда проблем с DOS-атаками не было бы даже если бы шло много запросов с разных айпи-адресов. То есть если бы они пошли, то айпи адрес при достижении определенного лимита вносился бы в черный список (таблица) и более не допускался в течение определенного времени (а по прошествие этого времени через крон таблицу черного списка очищаешь). pf умеет делать
StartServers 30
и если вы это сделаете в отдельном посте я думаю еще куча народа будут также благодарные.
06.MinSpareServers 10
07.MaxSpareServers 20
08.MaxClients 200
Вопрос а зачем так много серверов 30 нужно ???
Второе MaxClients 200 вот эта строчка вызвала удивление вы собрались обслуживать 100000 уников в сутки ???
И самое главное
вопрос нумер раз как настраивать апачь 22 ??? где лежат файлы и как что за что отвечает хотябы краткую водную можите дать?
вопрос нумер два кэширование тредов как собрать заново базу можите дать пошаговые действия у меня сейчас в конфиге даже нету упоминания строки
thread_cache = 40 тоетсь собранна она без кэширования . как с меньшими жертвами проделать операцию по сберке.
Я буду очень благодарен
Со вчерашнего дня, 2 Mbps.
Поставил и у себя 30. Понаблюдаю как оно будет.
Это не аватары, а скорее “Граватары”. Идешь на , регаешься. В регистрации указываешь мыло, которое ты обычно используешь, загружаешь свою любимую аватарку.
Везде в блогах, где ты будешь оставлять свои комментарии и где поддерживаются граватары, указывай этот e-mail и твоя граватара будет преследовать тебя от блога к блогу.
Сорри за офтоп, но как же все таки сюда добавить аватару???
У меня стоит 30 сек, хватает с головой. Если по уму делать, то можно, например, ориентироваться на таймаут браузера. По умолчанию в том же IE если не ошибаюсь он равен 1 минуте. То есть нет смысла для back-end процесов на php делать max_execution_time > 60 sec, по той простой причине, что браузер клиента закроет соединение. Исключение на мой взгляд составляет ресурсоемкая пакетная обработка каких-либо данных, (например обработка изображений, больших массивов информации), но только на php такие вещи все равно никто не делает.
Повторюсь, если какой-то скрипт, предназначение которого, прочитать аргументы, выполнить запрос к базе и отформатировать вывод жрет больше 30 секунд - на помойку его.
А сколько бы ты поставил? Я конкретных рекомендаций не нашел, кроме той, что на ibm.com
Но 30 секунд - по-моему маловато…
max_execution_time = 120
Это реально очень много, 2 минуты, любой скрипт, который отрабатывается больше 30 секунд, должен умереть.., (не своей смертью)
С рабочей машины канал 100 мегабит, но у этого сервера на UP всего 1.
По мониторингу, при частых запросах отдельных страниц скорость выше 800-900 Kbps не поднялась. Хотя если бы отдавался один большой файл то 1 Mbps - стабильно.
Разреши полюбопытствовать, а какой у твоего сервака канал UP?