Кроме домашнего сервера у меня есть небольшой арендованный сервер за пределами дома: на нём сервис, которым пользуются домашние устройства, и почта. Неделями он «падал» по вечерам: то работает, то нет. Я искал виноватого у хостера, в маршруте, в домашнем канале. А причина лежала на самом сервере и была банальной до обидного — кончилось место на диске. Плюс вторая история: защита от шторма переподключений, которая через четыре дня заперла систему изнутри.

Это тринадцатая часть цикла «Домашняя инфраструктура».

Симптом, который сбивал с толку

Сеть до сервера была чистой: 0% потерь, 56 мс. Мелкие запросы через сервис проходили. А объёмные — например, отправка картинки — вставали намертво, 0 КБ/с. Такая картина очень похожа на проблемы с сетью: «мелкое пролезает, крупное нет». Поэтому я и копал в сторону маршрута и MTU.

А на сервере:

$ df -h /
Filesystem  Size  Used Avail Use% Mounted on
/dev/vda1    15G   14G  367M  98% /

Сервис писал строку журнала на каждый запрос (уровень info) плюс журнал DNS-запросов. JSON-журнал его контейнера разросся до 928 МБ. Когда место почти кончилось, запись в журнал начала блокироваться — а вместе с ней вставала и обработка запросов, которые эту запись порождали. Мелкие запросы успевали проскочить, объёмные — нет.

После чистки — 5368 КБ/с (около 44 Мбит/с) вместо нуля. И задним числом это объяснило всю историю: 70 записей «сервер не отвечает» за неделю при чистой сети и по 12–14 срабатываний в сутки в предыдущий месяц.

Что занимало 15 ГБ и что с этим сделал

ЧтоСколькоЧто сделано
JSON-журналы контейнеров1,6 ГБ (928 + 230 + 169 + 96 МБ)обнулены, последние 2000 строк сохранены
журнал systemd713 МБ без лимитаSystemMaxUse=200M
кэш apt и старые ревизии snap—apt-get clean, удалены
хранилище образов containerd5 ГБне трогал: там рабочие образы

Итог: занято 82% вместо 98%, свободно 2,6 ГБ. На диске в 15 ГБ это и есть разумный запас.

Грабля 1: ротация журналов Docker не действует на старые контейнеры

Лимит в /etc/docker/daemon.json ("log-opts": {"max-size": "50m", "max-file": "3"}) применяется только к контейнерам, созданным после правки. Уже работающие продолжают писать без ограничений, пока их не пересоздать. До пересоздания помогает logrotate — но только с copytruncate: без него Docker продолжает писать в переименованный файл, и место не освобождается.

Грабля 2: vacuum журнала освободил ноль

$ journalctl --vacuum-size=200M
Vacuuming done, freed 0B of archived journals.

Все 713 МБ лежали в активном файле журнала, а vacuum чистит только архивные. Помогло systemctl restart systemd-journald (активный файл закрывается и становится архивным) и повтор команды.

Грабля 3: уровень журнала откатывается сам

Уровень журнала сервиса я понизил до warning в его файле настроек — и после перезапуска он вернулся на info. Файл генерировался панелью управления из её базы и перетирался при каждом старте. Править пришлось в самой панели. Результат: было около 900 МБ журнала в сутки, стала одна строка раз в 12 секунд.

Сторожа за местом на диске не было — а именно он предотвратил бы всю историю. Проверка свободного места раз в час с тревогой при 85% — самый дешёвый мониторинг, который только бывает. Единственный подвох: если тревога уходит почтой, а почта живёт на том же сервере, она не уйдёт тогда, когда нужнее всего.

Правило «сначала самое глупое»

Через пару недель сервер лёг целиком — на полтора часа: ни пинга, ни портов, и из дома, и с другого сервера в дата-центре. В панели хостера «всё нормально». Девять падений за сутки — проблема была в сети хостера, и вместе с сервером, конечно, легла почта. Из обоих случаев вышло правило проверки при «всё легло», по порядку:

  1. жив ли домашний интернет (открывается ли что угодно ещё);
  2. жив ли сервер — с двух разных точек, не только из дома;
  3. есть ли место на диске и память — df -h, free -m;
  4. и только потом — свои настройки, маршруты и версии.

Защита, которая заперла систему изнутри

Вторая история связана с тем же сервисом. Однажды пропавший канал до внешнего узла устроил шторм: соединения устанавливались, но данных по ним не шло, и закрывать их было некому — по умолчанию ядро проверяет мёртвые соединения раз в два часа. Устройства в сети переспрашивали каждые несколько секунд, и одновременных соединений набралось 3149.

Первое лекарство — ограничить число одновременных соединений и скорость создания новых:

iptables -A OUTPUT -p tcp --syn -m connlimit --connlimit-above 300 --connlimit-mask 0 -j REJECT --reject-with tcp-reset
iptables -A OUTPUT -p tcp --syn -m hashlimit --hashlimit-above 40/sec --hashlimit-burst 80 \
         --hashlimit-name syn -j REJECT --reject-with tcp-reset

Через четыре дня сервис встал при живом сервере. Характерный признак — ровно 300 установленных соединений и 21 815 отказов в счётчике правила. --connlimit-mask 0 значит «считать все соединения вместе, не разбирая источников», и обычная нагрузка домашней сети упёрлась в потолок. Поднял потолок до 1000 — и снова: 827 отказов за час при трёх реальных соединениях с моей стороны. Обычная рабочая точка сети оказалась около 954 одновременных соединений, то есть любой разумный потолок был ниже нормы.

Итог: connlimit удалён совсем. Осталось только ограничение скорости создания новых соединений — от шторма оно защищает, а нормальную работу не трогает. А мёртвые соединения теперь закрываются быстро: в настройках сервиса включён keepalive на исходящих соединениях — проверка через 30 секунд простоя, повтор через 10. Системные net.ipv4.tcp_keepalive_* тоже задают эти интервалы, но действуют только на сокеты, где программа сама включила SO_KEEPALIVE, — поэтому смотреть надо в настройки сервиса.

Keepalive лучше, чем урезать время простоя: мёртвое соединение умирает за минуту, а живое, но просто молчащее, никто не рвёт. И имя счётчика hashlimit держите коротким: у модуля есть ограничение на длину имени.

И урок, который стоит повторить: прежде чем чинить «нестабильность» удалённого сервера, проверьте, не ваша ли защита её создаёт. 12 записей «сервер мёртв» за сутки сгрудились ровно в окне, когда работал ограничитель.

Сервер «иногда не отвечает», а причина не находится?

Пришлите df, free и правила файрвола — проверю место, журналы и ограничители, которые могли запереть систему.

· полезно?
АЧ
Алексей Чикун веб-разработчик, команда TRIOHEAD

Делаю сайты и сервисы целиком: от вёрстки до сервера. Пишу о том, что проверил на своих проектах.

Комментарии

// загружаю комментарии…

Комментарии публикуются после проверки. Ссылки и HTML не принимаются.