сеньорчикОткрыть в Telegram
← вся теориятеория к собесу · Linux и bash

systemd: юниты, перезапуск, журнал

systemd: описания, перезапуск, журнал

Заглянул в настоящие настройки на рабочем сервере, systemd версии 255. У службы docker записано: перезапускать всегда, пауза между попытками 2 секунды, не больше 3 попыток за минуту. У службы cron: перезапускать при сбое, пауза 100 миллисекунд, до 5 попыток за 10 секунд. А у служб, где про перезапуск не написано ничего, - Restart=no, то есть упал и лежит.

Стержень: описание сервиса лежит в файле, systemd держит его в памяти, а поведение при выходе целиком задаётся политикой перезапуска и её ограничителем.

// Формулировки: «поправил описание, ничего не изменилось», «Restart=on-failure и код 0», «куда пропали логи после перезагрузки?»

Описание сервиса и перечитывание

systemd управляет службами по текстовым описаниям - их называют юнитами. В таком файле сказано, что запускать, от какого пользователя, с каким окружением, после чего стартовать и как вести себя при выходе.

Деталь, на которой теряют часы: systemd читает эти файлы при старте системы и по отдельной команде перечитывания, а не при каждом обращении. Команда перезапуска сервиса берёт то описание, которое уже загружено в память. Поэтому правильный порядок - сначала правка файла, потом перечитывание, потом перезапуск. Само перечитывание безопасно и работающие службы не трогает, а команда состояния честно предупреждает, что файл на диске изменился.

// Второй практический момент - куда писать правки. Если менять файл, пришедший с пакетом, обновление пакета его перезапишет вместе с вашими изменениями. Поэтому правки кладут отдельным маленьким файлом-дополнением: он лежит рядом, применяется поверх и обновление его не трогает.

юнит
текстовое описание службы: что запускать и как себя вести
перечитывание описаний
отдельная команда; без неё перезапуск берёт старое описание из памяти

Политика перезапуска и её ограничитель

Вернусь к настоящим значениям с сервера. Restart=on-failure считает провалом ненулевой код выхода, обрыв сигналом, истёкшее отведённое на запуск время. А вот чистый выход с кодом 0 при такой политике - успех, и перезапуска не будет. Это регулярная ловушка: сервис «сам себя выключил», вернув ноль, и systemd честно оставил его выключенным. Если служба обязана работать всегда, ставят Restart=always - именно так настроен docker на моём сервере.

Но перезапускать бесконечно нельзя: сервис, который падает мгновенно, съел бы машину. Поэтому у политики есть ограничитель из двух чисел - сколько попыток и за какое окно. У docker это 3 попытки за минуту, у cron - 5 за 10 секунд. Исчерпав лимит, systemd перестаёт пытаться и оставляет службу в состоянии отказа, пока человек не разберётся.

// Пауза между попытками задаётся отдельно: у docker 2 секунды, у cron 100 миллисекунд. Сумма настроек и определяет, как быстро сервис сдастся: три попытки по 2 секунды - это меньше десяти секунд до окончательного отказа, и в мониторинге это надо ловить, а не полагаться, что «systemd поднимет».

Restart=on-failure
перезапуск при сбое; выход с кодом 0 сбоем не считается
ограничитель попыток
сколько перезапусков за какое окно; исчерпан - служба остаётся в отказе

Журнал и расписания

Всё, что служба пишет в вывод, попадает в системный журнал: его смотрят по имени службы, следят в реальном времени, фильтруют по важности и по периоду. Но есть настройка, из-за которой логи исчезают. При режиме хранения «автоматически» журнал пишется на диск, только если существует каталог /var/log/journal; иначе он живёт во временной памяти и после перезагрузки пропадает целиком. На моём сервере каталог есть, журнал занимает 38,5 мегабайта и перезагрузку переживает.

Регулярные задачи systemd умеет запускать сам, без cron: расписание задаётся человекочитаемой строкой. Проверил разбор на живой машине: строка «Mon *-*-* 04:00:00» означает ближайший понедельник в 04:00, «*-*-01 00:00:00» - первое число каждого месяца, а короткое слово «daily» разворачивается в «*-*-* 00:00:00». Систему можно спросить, когда сработает в следующий раз, и она ответит точной датой.

// Преимущество перед cron практическое: код возврата и вывод задачи видны обычными командами, повторный запуск не начнётся, пока предыдущий не закончил, а отдельный флаг заставляет догнать запуск, пропущенный из-за того, что машина была выключена. У cron пропущенное просто теряется.

хранение журнала
без каталога /var/log/journal логи живут в памяти и гибнут при перезагрузке
таймер вместо cron
расписание строкой, вывод в общем журнале, догон пропущенного запуска

Как отвечать: «Поправил описание службы, сделал restart - параметры старые. Почему?»

Потому что systemd держит описания служб в памяти и перечитывает их только по отдельной команде или при старте системы. Команда перезапуска берёт то описание, которое уже загружено, и честно перезапускает службу со старыми параметрами. Правильный порядок такой: правка файла, затем перечитывание описаний, затем перезапуск службы. Само перечитывание безопасно, работающие службы оно не трогает. Кстати, команда состояния службы в такой ситуации прямо предупреждает, что файл на диске изменился, - это первое, на что стоит посмотреть. И отдельно про то, куда писать правки: если менять файл, пришедший с пакетом, следующее обновление пакета его перезапишет вместе с изменениями. Поэтому я всегда кладу правки отдельным файлом-дополнением, который применяется поверх пакетного и обновление его не трогает.

Ответ даёт точный порядок команд, объясняет причину и добавляет практику с отдельным файлом правок, которой нет в учебниках.

На чём валятся

  • Забывают перечитать описания и долго ищут, почему правка не применилась.
  • Ждут перезапуска при политике «только при сбое» после выхода с кодом 0 - это успех.
  • Полагаются на автоперезапуск, не глядя на ограничитель: 3 попытки за минуту, и служба остаётся в отказе.
  • Не создают каталог для журнала и теряют все логи при перезагрузке.

Проверьте себя

Пять вопросов из банка по этой подтеме. Всего их 7, остальные разбираются в тренажёре.

  1. #dvo_linux_systemd1 / 5
    В юните стоит Restart=on-failure. Процесс завершился с кодом 0 — systemd его перезапустит?
    A)Перезапустит: on-failure реагирует на факт выхода, код не важен
    B)Не перезапустит, и поменять это можно лишь через Restart=on-abnormal
    C)Перезапустит при Type=simple и не станет при Type=oneshot
    D)Не перезапустит: нулевой код — успешный выход, а не провал
    показать ответ и разбор
    +D)Не перезапустит: нулевой код — успешный выход, а не провал

    // разбор: on-failure считает провалом ненулевой код, обрыв сигналом, таймаут и срабатывание watchdog; чистый exit 0 — успешное завершение, перезапуска не будет. Если сервис обязан жить постоянно, ставят Restart=always. Оба режима ограничены StartLimitIntervalSec и StartLimitBurst: после серии быстрых падений systemd бросает попытки и оставляет юнит в failed.

  2. #dvo_linux_systemd2 / 5
    После перезагрузки хоста journalctl не показывает логи за прошлую неделю. Что настроено не так?
    A)Логи забирает syslog, journald их у себя не дублирует
    B)Журнал лежит в /run: нет каталога /var/log/journal
    C)Ротация по SystemMaxUse срезала неделю разом
    D)journalctl без --since показывает записи текущей загрузки
    показать ответ и разбор
    +B)Журнал лежит в /run: нет каталога /var/log/journal

    // разбор: При Storage=auto (значение по умолчанию) journald пишет на диск, только если существует каталог /var/log/journal; иначе журнал живёт в /run/log/journal — это tmpfs, то есть память, и чистится при перезагрузке. Лечится созданием каталога или Storage=persistent с рестартом systemd-journald. Отсюда вечное «логи были, после ребута пропали» на минимальных образах.

  3. #dvo_linux_systemd3 / 5
    systemctl start app — сервис работает. После перезагрузки хоста он не поднялся. Что забыли?
    A)Нужен systemctl daemon-reload, чтобы автозапуск подхватился
    B)Юнит для автозапуска надо положить в каталог /etc/init.d
    C)Из-за Type=oneshot сервис поднимается только вручную
    D)systemctl enable — start не ставит автозапуск при загрузке
    показать ответ и разбор
    +D)systemctl enable — start не ставит автозапуск при загрузке

    // разбор: start и enable независимы. start поднимает сервис прямо сейчас, но ничего не помнит про загрузку. enable добавляет юнит в нужный target (симлинк в …/multi-user.target.wants/), чтобы systemd поднял его при следующем boot. Обычно нужны оба: enable --now делает и то и другое разом.

  4. #dvo_linux_systemd4 / 5
    Сервис B зависит от A: в юните B стоит After=A.service. Но B стартует раньше, чем A готов принимать соединения. В чём подвох?
    A)After= надо просто заменить на Before=A.service в юните B
    B)After= задаёт лишь порядок старта, но не готовность A
    C)Порядок в systemd недетерминирован, директиву After он игнорирует
    D)Достаточно вписать sleep 10 в ExecStartPre у сервиса B
    показать ответ и разбор
    +B)After= задаёт лишь порядок старта, но не готовность A

    // разбор: After=A выстраивает очередь: B не стартует, пока A не «активен». Но при Type=simple A считается активным сразу после fork, ещё до того как начал слушать порт, — значит B поднимется раньше готовности. Решения: у A выставить Type=notify и слать sd_notify READY=1 по факту готовности (или Type=forking с корректным PID), у B — ретраи на подключении. И различать After (порядок) от Requires/Wants (тянуть ли A вообще).

  5. #dvo_linux_systemd5 / 5
    Сервис периодически убивается по OOM, хотя на хосте гигабайты свободной памяти. Куда смотреть?
    A)Память хоста сильно фрагментирована, и свободной на деле нет
    B)journald убивает сервисы, которые слишком шумят в логах
    C)У юнита выставлен MemoryMax — systemd убивает сервис по cgroup-лимиту
    D)Это утечка в самом ядре, которая лечится только ребутом
    показать ответ и разбор
    +C)У юнита выставлен MemoryMax — systemd убивает сервис по cgroup-лимиту

    // разбор: systemd кладёт каждый сервис в свою cgroup и умеет ограничивать её память через MemoryMax (v2) или MemoryLimit (v1). При превышении срабатывает cgroup-OOM, убивая процессы именно этого сервиса — независимо от того, что на хосте памяти вагон. В journalctl будет запись про OOM с именем cgroup сервиса. Правят лимит (systemctl show app -p MemoryMax) или причину роста памяти.

дальше

Теорию прочитали. Навык ставится повторением

В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.