15. Сторожевые таймеры

Разрабатывая systemd, мы ориентируемся на три основных области его применения: мобильные/встраиваемые устройства, настольные системы и промышленные серверы. Мобильные и встраиваемые системы, как правило, располагают очень скромными ресурсами и вынуждены очень экономно расходовать энергию. Настольные системы уже не так сильно ограничены по мощности, хотя все же проигрывают в этом плане промышленным серверам. Но, как ни странно, существуют возможности, востребованные в обоих крайних случаях (встраиваемые системы и серверы), но не очень актуальные в промежуточной ситуации (десктопы). В частности, к ним относится поддержка сторожевых таймеров (watchdogs), как аппаратных, так и программных.
Во встраиваемых системах часто используются аппаратные сторожевые таймеры, выполняющие сброс системы, когда программа перестает отвечать (точнее, перестает периодически посылать таймеру сигнал «все в порядке»). Таким образом, обеспечивается повышение надежности системы: что бы ни случилось, система обязательно попытается привести себя в рабочее состояние. На десктопах такая функциональность практически не востребована. С другой стороны, она весьма актуальна для высокодоступных серверных систем.
Начиная с версии 183, systemd полностью поддерживает аппаратные сторожевые таймеры (доступные через интерфейс /dev/watchdog), а также обеспечивает программный сторожевой контроль системных служб. В целом эта схема (если она задействована) работает так. systemd периодически посылает сигналы аппаратному таймеру. В том случае, если systemd или ядро зависают, аппаратный таймер, не получив очередного сигнала, автоматически перезапустит систему. Таким образом, ядро и systemd защищены от бесконечного зависания — на аппаратном уровне. В то же время, сам systemd предоставляет интерфейс, реализующий логику программных сторожевых таймеров для отдельных служб. Таким образом можно обеспечить, например, принудительный перезапуск службы в случае ее зависания. Для каждой службы можно независимо настроить частоту опроса и задать соответствующее действие. Соединив оба звена (аппаратный сторожевой таймер, контролирующий ядро и systemd, и программные сторожевые таймеры, посредством которых systemd контролирует отдельные службы), мы получаем надежную схему надзора за всеми системными компонентами. Чтобы задействовать аппаратный таймер, достаточно задать ненулевое значение параметра RuntimeWatchdogSec= в файле /etc/systemd/system.conf. По умолчанию этот параметр равен нулю (т.е. аппаратный таймер не задействован). Установив его равным, например, «20s», мы включим аппаратный сторожевой таймер. Если в течение 20 секунд таймер не получит очередного сигнала «все в порядке», система автоматически перезагрузится. Отметим, что systemd отправляет такие сигналы с периодом, равным половине заданного интервала — в нашем случае, через каждые 10 секунд. Собственно, это все. Просто задав один параметр, вы обеспечите аппаратный контроль работоспособности systemd и ядра.
Небольшой совет: если вы занимаетесь отладкой базовых системных компонентов — не забудьте отключить сторожевой таймер. Иначе ваша система может неожиданно перезагрузиться, когда отладчик остановит процесс init и тот не может вовремя отправить сообщение таймеру.
Заметим, что с аппаратным сторожевым таймером (/dev/watchdog) должна работать только одна программа. Этой программой может быть либо systemd, либо отдельный демон сторожевого таймера (например, watchdogd) — выбор остается за вами.
Стоит упомянуть здесь еще одну опцию из файла /etc/systemd/system.conf — ShutdownWatchdogSec=. Она позволяет настроить аппаратный сторожевой таймер для контроля процесса перезагрузки. По умолчанию она равна «10min». Если в процессе остановки системы перед перезагрузкой она зависнет, аппаратный таймер принудительно перезагрузит ее по истечении данного интервала. Это все, что я хотел сказать об аппаратных таймерах. Двух вышеописанных опций должно быть вполне достаточно для полноценного использования их возможностей.
А сейчас мы рассмотрим логику программных сторожевых таймеров, обеспечивающих контроль работоспособности отдельных служб. Прежде всего отметим, что для полноценной поддержки сторожевого контроля, программа должна содержать специальный код, периодически отправляющий таймеру сигналы «все в порядке». Добавить поддержку такой функциональности довольно просто.
Для начала, демон должен проверить переменную окружения WATCHDOG_USEC=. Если она определена, то ее значение задает контрольный интервал в микросекундах, сформатированный в виде текстовой (ASCII) строки. В этом случае демон должен регулярно выполнять вызов sd_notify("WATCHDOG=1") с периодом, равным половине указанного интервала. Таким образом, поддержка программного сторожевого контроля со стороны демона сводится к проверке значения переменной окружения, и выполнении определенных действий в соответствии с этим значением.
Если интересующая вас служба обеспечивает поддержку такой функциональности, вы можете включить для нее сторожевой контроль, задав опцию WatchdogSec= в ее юнит-файле. Эта опция задает период работы таймера (подробнее см. systemd.service(5)). Если вы зададите ее, то systemd при запуске службы передаст ей соответствующее значение WATCHDOG_USEC= и, если служба перестанет своевременно отправлять сигналы «все в порядке», присвоит ей статус сбойной (failure state). Очевидно, что одного только присвоения статуса недостаточно для обеспечения надежной работы системы. Поэтому нам также пригодятся настройки, определяющие, нужно ли перезапускать зависшую службу, количество попыток перезапуска, и дальнейшие действия, если она все равно продолжает сбоить. Чтобы включить автоматический перезапуск службы при сбое, нужно задать опцию Restart=on-failure в ее юнит-файле. Чтобы настроить, сколько раз systemd будет пытаться перезапустить службу, воспользуйтесь настройками StartLimitBurst= StartLimitInterval= (первая из них определяет предельное количество попыток, вторая — интервал времени, в течение которого они подсчитываются). В том случае, если достигнут предел количества попыток за указанное время, выполняется действие, заданное параметром StartLimitAction=. По умолчанию он установлен в none (никаких дополнительных действий не будет, службу просто оставят в покое со статусом сбойной). В качестве альтернативы можно указать одно из трех специальных действий: reboot, reboot-force и reboot-immediate. reboot соответствует обычной перезагрузке системы, с выполнением всех сопутствующих процедур (корректное завершение всех служб, отмонтирование файловых систем и т.д.). reboot-force действует более грубо — даже не пытаясь корректно остановить службы, оно просто убивает все их процессы, отмонтирует файловые системы и выполняет принудительную перезагрузку (в результате, перезагрузка происходит быстрее, чем обычно, но файловые системы остаются неповрежденными). И наконец, reboot-immediate даже не пытается отдать дань вежливости (убить процессы и отмонтировать файловый системы) — оно немедленно выполняет жесткую перезагрузку системы (это поведение практически аналогично срабатыванию аппаратного сторожевого таймера). Все перечисленный настройки подробно описаны на странице руководства systemd.service(5)).
Прим. перев.: Автор упускает из виду одну полезную опцию, непосредственно относящуюся к обсуждаемому вопросу — OnFailure=, задающую юнит, который будет активирован в случае сбоя исходного юнита. Таким образом можно обеспечить, например, запуск скриптов, отправляющих администратору уведомление о сбое в сочетании с дополнительной информацией (для сбора которой целесообразно задействовать команды systemctl status и journalctl). Кроме того, комбинирование данной настройки с опцией OnFailureIsolate= позволяет при сбое юнита перевести систему в определенное состояние (например, остановить некоторые службы, перейти в режим восстановления, выполнить перезагрузку).

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

Данная служба будет автоматически перезапущена, если она не передаст системному менеджеру очередной сигнал «все в порядке» в течение 30 секунд после предыдущего (кроме того, перезапуск будет произведен и в случае любого другого сбоя, например, завершения основного процесса службы с ненулевым кодом выхода). Если потребуется более 4 перезапусков службы за 5 минут — будет предпринято специальное действие, в данном случае, быстрая перезагрузка системы с корректным отмонтированием файловых систем.
Это все, что я хотел рассказать о сторожевых таймерах в systemd. Мы надеемся, что поддержки аппаратного отслеживания работоспособности процесса init, в сочетании с контролем функционирования отдельных служб, должно быть достаточно для решения большинства задач, связанных со сторожевыми таймерами. Разрабатываете ли вы встраиваемую либо мобильную систему, или работаете с высокодоступными серверами — вам определенно стоит попробовать наши решения!
(И да, если у вас возникнет вопрос, почему с аппаратным таймером должен работать именно init, и что мешает вынести эту логику в отдельный демон — пожалуйста, перечитайте эту главу еще раз, и попытайтесь увидеть всю цепочку сторожевого контроля как единое целое: аппаратный таймер надзирает за работой systemd, а тот, в свою очередь, следит за отдельными службами. Кроме того, мы полагаем, что отсутствие своевременного ответа от службы нужно рассматривать и обрабатывать так же, как и любые другие ее сбои. И наконец, взаимодействие с /dev/watchdog — одна из самых тривиальных задач в работе ОС (обычно, она сводится к простому вызову ioctl()), и для ее решения достаточно нескольких строк кода. С другой стороны, вынос данной функции в отдельный демон потребует организации сложного межпроцессного взаимодействия между этим демоном и процессом init — очевидно, реализация такой схемы предоставит значительно б´ольший простор для ошибок, не говоря уже о повышенном потреблении ресурсов.)
Отметим, что встроенная в systemd поддержка аппаратного сторожевого таймера в конфигурации по умолчанию отключена, и поэтому никак не мешает работе с этим таймером из других программ. Вы без лишних проблем можете выбрать внешний сторожевой демон, если он лучше подходит для вашей задачи.
Да, и еще: если у вас возникнет вопрос, имеется ли в вашей системе аппаратный таймер — скорее всего да, если ваш компьютер не очень старый. Чтобы получить точный ответ, вы можете воспользоваться утилитой wdctl, включенной в последний релиз utillinux. Эта программа выведет всю необходимую информацию о вашем аппаратном сторожевом таймере.
И в завершение, я хотел бы поблагодарить ребят из Pengutronix, которым приндалежит основная заслуга реализации сторожевого контроля в systemd.

Содержание
Вперед - Запуск getty на последовательных (и не только) консолях
Назад - Самодокументированный процесс загрузки