Простое ускорение Drupal с превентивной защитой от DDoS

Автор: | 27.08.2016

Пятница. Вечер. Хорошая погода. Сижу, отдыхаю, никого не трогаю… Приходит SMS от системы мониторинга: «Хватит расслабляться! Сайт компании лежит». Небольшое отступление: сайт использует CMS Drupal 7 и работает на самом дешёвом VPS с 1 ядром, 1 ГиБ памяти и 10 ГиБ на SSD. Ему по сути больше и не надо, ведь 500 посещений в день просто несерьёзно. Быстрое расследование показало, что зависшие процессы PHP-FPM отобрали всю память и MariaDB от такой наглости решила взять выходной. Быстро приводим сайт в чувство:

# systemctl restart php-fpm
# systemctl start mariadb

Допустим, с кривым PHP-кодом программисты когда-нибудь разберутся. Но что я могу сделать со своей стороны в этот прекрасный вечер пятницы? Полагаю, что более чем на 10 строк меня не хватит. 😉

Для начала с помощью ApacheBench проверим, на что вообще способен сайт в текущем состоянии. Здесь и далее под словом «сайт» следует понимать всю совокупность программно-аппаратных средств, которые позволяют отобразить в окне браузера необходимую информацию. Для 1000 запросов при 100 одновременных подключений получаем следующий результат:

Concurrency Level:      100
Time taken for tests:   33.492 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      63059272 bytes
HTML transferred:       62486000 bytes
Requests per second:    29.86 [#/sec] (mean)
Time per request:       3349.179 [ms] (mean)
Time per request:       33.492 [ms] (mean, across all concurrent requests)
Transfer rate:          1838.70 [Kbytes/sec] received

30 запросов в секунду. Интересно, во сколько раз можно улучшить этот показатель изменив не более 10 строк? Обратимся к мощи nginx и попробуем это выяснить. Но для начала в админке Drupal зайдём в раздел «Конфигурация → Разработка → Производительность» и задействуем кэширование страниц для анонимных пользователей. Время жизни кэша выбираем исходя из своих потребностей. Я поставил 1 день, так как на сайте довольно редко происходят изменения.

Строка первая

Для ускорения будем использовать механизм кэширования ответов FastCGI. Для этого потребуется задать путь для хранения кэша и его параметры. У меня получилось так:

http {
	fastcgi_cache_path /var/cache/nginx levels=1 keys_zone=drupal:1m inactive=1d;
}

Первый параметр директивы fastcgi_cache_path задаёт путь для хранения кэша (/var/cache/nginx). Так как на сайте менее 100 страниц, то я не стал раздувать уровни иерархии кэша и ограничился levels=1. Если предполагается значительно большее количество страниц в кэше, то можно добавить ещё один уровень записав levels=1:2. Параметром keys_zone задаётся имя зоны разделяемой памяти для хранения активных ключей и её размер. Одного мегабайта хватает примерно на 8000 ключей. Если у вас больше, то следует увеличить этот параметр, а в моём случае и его более чем достаточно. Один из ключевых параметров — inactive — задаёт время, по истечению которого данные из кэша будут удаляться при отсутствии обращений. По умолчанию 10 минут, я поставил в соответствии с настройками кэша Drupal (1 день) — это позволит сайту продолжать работать даже если остановить PHP-FPM.

Строка вторая и третья

У нас практически всё готово и можно задействовать кэширование:

http {
	fastcgi_cache_path /var/cache/nginx levels=1 keys_zone=drupal:1m inactive=1d;

	server {
		location ~ \.php$ {
			fastcgi_cache drupal;
			fastcgi_cache_key $request_uri;
		}
	}
}

Директивой fastcgi_cache указываем заданную ранее зону разделяемой памяти, которая будет использоваться для кэширования ответов. Директивой fastcgi_cache_key задаём уникальный ключ для кэширования, переменные можно добавить по вкусу и в зависимости от задач. Не забываем применять внесённые изменения:

# nginx -s reload

Строки с четвёртой по восьмую

Кэширование для анонимных пользователей это конечно же хорошо. Но у нас есть авторизованные пользователи и если ничего не предпринять, то они не смогут полноценно пользоваться сайтом. Добавим для них специальную переменную, которая будет отслеживать наличие активной сессии:

http {
	fastcgi_cache_path /var/cache/nginx levels=1 keys_zone=drupal:1m inactive=1d;

	map $http_cookie $no_cache {
		default 0;
		"~SESS" 1;
	}

	server {
		location ~ \.php$ {
			fastcgi_cache drupal;
			fastcgi_cache_bypass $no_cache;
			fastcgi_cache_key $request_uri;
		}
	}
}

Что происходит: директивой map объявляется пользовательская переменная $no_cache, значение которой зависит от значения встроенной переменной $http_cookie. По умолчанию $no_cache = 0, но если в переменной $http_cookie встречается строка «SESS», то $no_cache = 1. Директива fastcgi_cache_bypass задаёт условие, при котором ответ не будет браться из кэша. Собственно на этом можно было закончить, но я же обещал 10 строк.

Строка девятая

На тот случай, если на сервере откажут все службы (кроме nginx), можно какое-то время скрывать это от пользователей за счёт кэша:

http {
	fastcgi_cache_path /var/cache/nginx levels=1 keys_zone=drupal:1m inactive=1d;

	map $http_cookie $no_cache {
		default 0;
		"~SESS" 1;
	}

	server {
		location ~ \.php$ {
			fastcgi_cache drupal;
			fastcgi_cache_bypass $no_cache;
			fastcgi_cache_key $request_uri;
			fastcgi_cache_use_stale error timeout invalid_header updating http_500 http_503;
		}
	}
}

Директивой fastcgi_cache_use_stale указываем nginx в каких случаях можно использовать устаревший закэшированный ответ, если при работе с FastCGI-сервером возникла ошибка.

Строка десятая

PHP-FPM нам практически не нужен, так что сэкономим оперативную память и не будет держать его процессы в боевой готовности:

pm = ondemand

Всё, 10 строк. Проверим результат:

Concurrency Level:      100
Time taken for tests:   6.191 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      63162000 bytes
HTML transferred:       62589000 bytes
Requests per second:    161.53 [#/sec] (mean)
Time per request:       619.074 [ms] (mean)
Time per request:       6.191 [ms] (mean, across all concurrent requests)
Transfer rate:          9963.53 [Kbytes/sec] received

161 запрос в секунду против 30 до внесения изменений. На мой взгляд вполне достойно. Бонусом снизилось потребление оперативной памяти с ≈700 МиБ до ≈300 МиБ, так что можно переходить на более дешёвый тариф 😀

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *