Как лучше всего сделать защиту от denial of service при исчерпании свободного места на диске большими по объему лог-файлами nginx?
Gena Makhomed
gmm на csdoc.com
Вс Фев 11 23:15:53 UTC 2024
On 11.02.2024 23:02, Evgeniy Berdnikov wrote:
> Вполне возможно, что товарищ никого не обманывает, и ему действительно
> обсуждать постановку задачи скучно, неинтересно, и даже "бесполезно"
> в том смысле, что он не готов к обсасыванию этой темы: то ли слишком
> непривычно и оттого голова напрягается, то ли по ещё какой причине...
> Товарищу хочется улучшать решение Y, и не хочется обсуждать альтернативы.
Из его собщения от 5 февраля однозначно следует, что он
уже пытался настроить запись логов напрямую в файл но не смог
получить рабочего решения при 200-250 тысячах подключений в секунду
и необходимости делать ротацию лога каждые 30 секунд. И даже предлагает
мне самому попробовать и убедиться, что это не работает и что таким
образом запись и ротацию логов в файл самим nginx при такой большой
нагрузке и при таком интервале ротации - настроить невозможно,
потому что если делать ротацию через SIGHUP и не использовать
директиву worker_shutdown_timeout - то через некоторое время
будет очень много старых рабочих процесов, которые еще продолжают
обрабатывать подключения - особенно, если используются вебсокеты,
стриминг, или идет отдача каких-то больших по объему файлов.
И в сервере просто закончится оперативная память и придет OOM killer
или если есть своп - сервер начнет делать swapping с плавным переходом
в thrashing и - переходом всех сайтов в состояние denial of service.
И даже просит поделиться опытом и говорит, что будет рад меня выслушать.
Но через несколько дней причины, по котороым он начал создавать
костыли из syslog, unix socket`ов и скриптов на Python - уже резко
изменились и теперь ему стало долго, скучно и неинтересно рассказывать
о том, что но просто не внимательно читал документацию и не нашел там
информации о практически мгновенном способе ротации логов, с помощью
сигнала SUGUSR1, когда не будет всех тех проблем, что есть при 200-250
тысячах подключений в секунду и ротации лог-файлов раз в 30 секунд
с использованием SIGHUP
> Но по сути дела изначальный вопрос был "почему пропадают записи в syslog?"
> Причина, скорее всего, была в том, что syslogd не успевал их принимать.
Да, и Максим же это подтвердил и объяснил, что тогда происходит - тогда
те запииси, которые syslog не смог не смог принять - nginx дропает
и пишет об этом собщение в error.log - и данной ситуации это есть самое
лучшее решение проблемы, потому что оде альтернативы еще хуже - первая
альтернатива - это блокироваться на операции записи в syslog и ничего
не делать, пока syslog не станет способен принимать сообщения от nginx,
вторая альтернатива - это продолжать работу и накапливать сообщения
в оперативной памяти, пока не придет Out Of Memory killerи не уничтожит
процесс по kill -9.
Теоретически - возможен еще вариант, создаватьна диске временный файл
и временно хранить записи там, пока syslog не придет в себя и не начнет
снова нормально принимать записи от nginx. Но в таком случае - если
syslog просто лежит и не работает - этот временный файл может стать
очень большим, даже использовав все свободное место на дике - так что
это тоже не очень хороший вариант решения, да и добавляет лишней
сложности. Лишней, потому что основная задача nginx - это не логи
писать, а обрабатывать запросы от клиентов.
Ротация логов делается с помощью программы logrotate, которая делает
ротацию только по времени и никак не смотрит на количество свободного
места на диске и на размер лог-файла. С помощью той программы logrotate,
которая идет в составе дистрибутива - неочевидно, как настроить более
быструю ротацию логов nginx, если они начинают занимать слишком много
места на диске и когда свободного места на диске остается слишком мало.
Хотя бы потому, что программа logrotate запускается раз в сутки
и в таком случае dateformat будет иметь вид -%Y%m%d - год-месяц-день.
И только если указывается параметр ротации hourly - только тогда формат
будет -%Y%m%d%H - год-месяц-день-час.
Директива dateext присутствует только в файле /etc/logrotate.conf
но онаотсутствует в файле /etc/logrotate.d/nginx - в результате -
получается такая ситуация, что если хочется сделать принудительную
ротацию лог-файла с помощью команды
# logrotate --force /etc/logrotate.d/nginx
тогда access.log переименовывается в access.log.1
тем более, что access.log-20240211 уже существует на диске.
и если в тот же день делать еще принудительную ротацию, прописав
внутри файла /etc/logrotate.d/nginx директиву dateext - тогда тоже
не будет ничего хорошего, потому что тогда будет ошибка:
error: destination /var/log/nginx/access.log-20240211 already exists,
skipping rotation
error: destination /var/log/nginx/error.log-20240211 already exists,
skipping rotation
возможно было бы лучшим решением, если бы вместо сообщения про ошибку
и отказа отротации логов в этой ситуации - когда файл с таким именем
на диске уже есть - программа logrotate сама бы динамически изменяла
бы строку dateformat добавляя в нее все новые и новые поля, до тех пор,
пока конфликта не будет.
Например, значение dateformat по умолчанию равно -%Y%m%d
если конфликт имен и такой файл на диске уже существует -
тогда добавить %H если и тогда есть конфликт, тогда добавить
%M если и сейчас есть конфликт - тогда добавить %S и если и в такой
ситуации будет конфликт - тогда подождать 0.5 секунды и снова повторить
попытку.
В таком случае - никогда не будет проблем с отказом от ротации логов,
и принудительную ротацию
# logrotate --force /etc/logrotate.d/nginx
можно будет запускать как угодно часто, например, раз в час или даже раз
в минуту, проверяя предварительно количество свободного места на диске
или размер файла или по каким-то другим условиям. Или - встроить,
например, lua прямо внутрь бинарника logrotate и расширенную логику
принятия решения о том, надо делать ротацию или нет - задавать на lua.
но это нереальный вариант. Проще будет наверное, по крону раз 5-10 минут
запускать скрипт на Python, вся логика приятия решения о необходимости
принудительной ротации будет запрограммирована на Python - это и
проверка свободного места на диске и проверка размера лог-файла
и все что угодно. И если ротация нужна - тогда этот скрипт просто
вызывает logrotate и запрашивает принудительную ротацию.
Скрипт может выглядеть примерно так:
#!/usr/bin/python3 -u
from invoke import run
def need_nginx_logs_rotation():
# TODO необходимо ли делать ротацию?
return False
if need_nginx_logs_rotation():
run("logrotate --force /etc/logrotate.d/nginx")
но чтобы это все работало нормально - необходимо, чтобы
в https://github.com/logrotate/logrotate была добавлена логика
такого динамически изменяемого значения директивы dateformat
и после этого - чтобы в /etc/logrotate.d/nginx была добавлена
строка dateext - тогда принудительная ротация лог-файлов nginx
станет возможной.
Для чего может быть нужна принудительная частая ротация лог-файлов?
Чтобы система автоматически следила за тем, чтобы лог-файлы nginx
не занимали очень много места на диске и автоматически удаляла бы
более старые лог-файлы, оставляя в каталоге с логами только самые новые.
Это может быть полезно в случае DDoS-атак, чтобы не не было
таких неожиданных ситуаций, что место на сервере внезапно закончилось,
и база данных MySQL прекратила работать, потому что логи nginx заняли
все свободное место на диске, и тогда все пользователи сайта будут
получать 5хх статус ошибки на свои запросы вместо содержимого сайта.
А это, фактически, будет означать, что DDoS-атака достигла своей цели
и для всех новых запросов клиентов действительно происходит
отказ в обсуживании, denial-of-service.
Но пока в бинарнике logrotate нет такой логики по динамическому
изменению значения dateformat и пока нет в файле /etc/logrotate.d/nginx
директивы dateext - такая простая и автоматически работающая защита
от DDoS-атак с помощью скрипта на python - пока что невозможна.
А никакой лучший вариант защиты от такого вида Denial-of-service attack
я придумать пока что не могу. Разве что полностью на Python
реализовывать всю эту логику и делать автоматическую ротацию
логов только скриптом на Python. Более красивое решение -
это внести изменения прямо в бинарник logrotate,
чтобы dateformat изменялся динамически. Тогда - в 99.999% случаев
он будет вида -%Y%m%d, в остальное время расширяясь
только при необходимости.
Или, как workaround - добавить в файл /etc/logrotate.d/nginx
две строчки
dateformat -%Y%m%d-%H%M%S
dateext
и за несколько минут написать в скрипте на Python необходимую
логику ротации - по количеству свободного места на диске и т.п.
тогда - для себя я смогу таким способом решить проблему защиты
от DDoS-атак с помощью исчерпания места на диске логами nginx.
или же я зря пытаюсь это автоматизировать и вместо автоматической защиты
от исчерпания места на диске необходимо чтобы был настроен мониторинг
и алерты, чтобы системный администратор мог бы проснуться среди ночи,
зайти на сервер по ssh и вручную удалить старые лог-файлы, чтобы не было
ситуации полного исчерпания свободного вместа и прекращения работы сайта
вот не знаю, какой из этих трех вариантов будет лучше всего:
1) настроить автоматическую ротацию логов скриптом на Python, внеся
необходимые изменения в logrotate, чтобы dateformat динамически
изменялся при необходимости от -%Y%m%d до -%Y%m%d-%H%M%S
и чтобы никогда не было ошибок при --force ротации логов.
2) добавить в /etc/logrotate.d/nginx две строчки:
dateformat -%Y%m%d-%H%M%S
dateext
и написать за несколько минут скрипт на Python, который будет делать
принудительную ротацию, когда остается мало свободного места на диске.
для себя я эту проблему смогу решить, получив только визуально некоторое
ухудшение ситуации, потому что логи nginx будут выглядеть не так красиво
в тех случаях, когда принудительная ротация логов не нужна и суффикс
в виде -%H%M%S будет просто избыточным в этих ситуациях.
3) не делать автоматической защиты от DDoS через исчерпание свободного
места на диске - потому что идеологически правильно чтобы эту проблему
вручную устранял системный администратор, потому что старые логи важны
и нужны.
Учитывая, что по умолчанию logrotate в системе запускается раз в сутки
и по умолчанию dateformat расчитан только на daily и hourly ротацию -
и вообще не рассчитан на автоматическую ротацию по причине исчерпания
свободного места - это значит третий вариант есть самый правильный?
потому что именно он по умочанию и реализован и настроен для nginx
в операционной системе и в пакете nginx из официального репозитория?
А значит никакой автоматической защиты от DDoS по причине исчерпания
свободного места на диске быть не должно, а вместо этого должен быть
настроен мониторинг и должно быть только ручное вмешательство
системного администратора и ручное устранение им этой проблемы?
Или я чего-то не понимаю?
--
Best regards,
Gena
Подробная информация о списке рассылки nginx-ru