Как лучше всего сделать защиту от 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