Всем привет! В последнее время я вплотную занимаюсь исследованием возможностей systemd и решил поделиться результатом исследований с сообществом, в виде небольшого (или большого, как пойдёт ;-) цикла статей. Итак первым (уже нет) номером нашей программы будет запуск юнитов по различным событиям происходящим во время работы ОС. В качестве исследовательской платформы будет выступать Manjaro Linux c systemd v247.2. И... да. Некоторые события, вынудили меня написать внеочередную статью, которая взлетела на вершину хит-парада, а опрос показал, что тема актуальна и вызывает интерес, так что погнали!
Пролог
Systemd система инициализации большинства современных систем на основе ядра Linux, обладает просто безграничными возможностями и не ограничивается обычным запуском демонов. Достаточно посмотреть на объёмы штатной документации, описывающей её возможности:
pacman -Ql $(pacman -Qsq systemd|xargs)|egrep '^systemd\s|^systemd-sysvcompat\s'|egrep "man/man[1|5|8]/[[:print:]]*\.gz"|wc -l278
И это только маны описывающие конфиги, пользовательские и администраторские утилиты systemd. Если же не ограничивать поиск практической частью, то цифра будет ещё более устрашающей:
pacman -Ql $(pacman -Qsq systemd|xargs)|egrep '^systemd\s|^systemd-sysvcompat\s'|wc -l1852
Большинство администраторов и разработчиков просто не представлют какие возможности таятся в недрах той системы, в окружении которых приходится работать их сервисам. Ну что-ж, пришлось время узнать, насколько глубока кроличья нора!
Disclamer: Хоть в официальной документации и манах почти не используется такое понятие как триггеры (хотя и используется triggered by), но все те штуки которые описаны в этой и следующей статье, по сути, именно ими и являются. Это сущности которые срабатывают по каким-либо событиям, поэтому не удивляйтесь, если я, авторским произволом, буду использовать этот термин.
Часть первая, очевидная. Таймеры.
Все мы знаем старый, добрый cron
, во всех его
проявлениях. Созданный ещё в 80-х, он, в том или ином виде, дожил
до нашего времени облачных сервисов. Так-же мы все знаем его
ограничения. Например одной строчкой невозможно заставить крон
запускать произвольный бинарник/скрипт раз в полтора часа, начная с
часа ночи, приходится описывать такое событие двумя строчками.
Что-бы обойти ограничения классического крона, в systemd были
придуманы такие триггеры как таймеры (юниты с окончанием
*.timer
) умеющие запускать произвольные сервисы или
группы сервисов (*.target
) периодически; по
наступлении какого-либо времени; по выходу системы из спящего
режима; по календарному событию (наподобие того как это делает
другой ветеран Unix утилит, команда at
), а так-же по
другим событиям, не связанными, напрямую, со временем.
Для начала что запускаем. Возьмём, для примера, таймер man-db.timer из комплекта поставки одноимённого пакета:
$ cat /usr/lib/systemd/system/man-db.timer[Unit]Description=Daily man-db regenerationDocumentation=man:mandb(8)[Timer]OnCalendar=dailyAccuracySec=12hPersistent=true[Install]WantedBy=timers.target
Простой, коротенький таймер. Но в чём-же дело, почему не указано
что мы запускаем? Всё нормально! По умолчанию, если в секции
[Timer]
отсутствует параметр Unit=
, с
указанием запускаемого юнита, systemd будет искать одноимённый
*.service
юнит. Проверяем!
$ cat /usr/lib/systemd/system/man-db.service[Unit]Description=Daily man-db regenerationDocumentation=man:mandb(8)ConditionACPower=true[Service]Type=oneshot# Recover from deletion, per FHS.ExecStart=+/usr/bin/install -d -o root -g root -m 0755 /var/cache/man# Expunge old catman pages which have not been read in a week.ExecStart=/usr/bin/find /var/cache/man -type f -name *.gz -atime +6 -delete# Regenerate man database.ExecStart=/usr/bin/mandb --quietUser=rootNice=19IOSchedulingClass=idleIOSchedulingPriority=7
Да, вот он сервис который ежедневно пересоздаёт базу данных
страниц руководства. Сервис стартует начиная с 00:00
(OnCalendar=daily
) , с точностью 12 часов
(AccuracySec=12h
), то-есть он может сработать в любой
момент между полуночью и полднем, в зависимости от загрузки
системы:
$ systemctl status man-db.timer man-db.timer - Daily man-db regeneration Loaded: loaded (/usr/lib/systemd/system/man-db.timer; disabled; vendor preset: disabled) Active: active (waiting) since Thu 2020-12-31 23:18:59 MSK; 1 day 19h ago Trigger: Sun 2021-01-03 00:00:00 MSK; 5h 30min left Triggers: man-db.service Docs: man:mandb(8)дек 31 23:18:59 dell-lnx systemd[1]: Started Daily man-db regeneration.
Минимальная точность у параметра AccuracySec=
1us!
Чем больше этот параметр, тем меньше нагрузка на систему. Если
параметр отсутствует, то по умолчанию (указано в
/etc/systemd/system.conf
:
DefaultTimerAccuracySec=
) он равен одной минуте.
Ладно, это всё лирика, давайте быстренько пробежимся по другим
возможным параметрам секции [Timer]
, а на сладкое
оставим параметры задания времени в OnCalendar=
и
других временнх параметрах.
Монотонные таймеры, для периодических событий
-
OnBootSec=
Таймер сработает через указанное время после старта системы. -
OnStartupSec=
Для системных таймеров действие аналогично предыдущему, для пользовательских таймеров, это время после первого логина пользователя в систему. -
OnActiveSec=
Один из параметров для периодического запуска. Через какое время, после реального времени срабатывания таймера, запускать юнит. -
OnUnitActiveSec=
Триггер будет ориентироваться на время последнего запуска целевого юнита. -
OnUnitInactiveSec=
Триггер будет ориентироваться на последнее время завершения работы целевого юнита. Хорошо для долгоиграющих сервисов. Бэкапы и вот это вот всё. Все эти таймеры можно комбинировать между собой и с таймеромOnCalendar=
.
Прочие параметры
-
RandomizedDelaySec=
Этакий рандомный джиттер. Перед срабатыванием добавляется случайный таймаут от нуля, до заданного значения. По умолчанию -- отключено. -
OnClockChange=, OnTimezoneChange=
Булевые параметры, определяющие будет-ли таймер реагировать на перевод системных часов или смену временной зоны. По умолчанию, оба параметра,false
. -
Persistent=
Записывать-ли на диск состояние таймера сразу после запуска юнита. Актуально для параметраOnCalendar=
. По умолчаниюfalse
. -
WakeSystem=
Ещё один логический параметр. Действует на монотонные таймеры. По умолчанию отключён. Логика следующая. При отключённом параметре все монотонные таймеры запоминают своё состояние, перед уходом системы в спящий режим и встают на паузу. После выхода системы из спящего режима, отсчёт продолжается с того момента с которого система ушла в спячку. Если-же параметр поставить вtrue
, то таймеры продолжают работать и в спящем режиме (должно поддерживаться и железом) и по наступлении события выводят систему из спячки и запускают юнит. -
RemainAfterElapse=
Последняя крутилка, по умолчаниюtrue
Смысл этого параметра примерно следующий, После срабатывания таймера он остаётся загруженным, но если поставитьfalse
, то после срабатывания таймер выгружается и перестаёт отслеживать время. Хорошо для одноразовых юнитов (Transient Units) о которых мы поговорим в одной из следующих статей. Или для таймеров которые должны сработать один раз, как это делают задания старой, добройat
.
Таймстампы, диапазоны, тестирование, примеры
Ну и наконец зачем это всё. Чем хороши таймеры, так это тем, что всяческие сложные временные метки задаются гораздо проще чем в cron и задача запуска сервиса раз в полтора часа, начиная с часа ночи, выглядит куда приятнее. В самом простом случае она будет выглядеть так:
[Unit]Description=Test timer[Timer]OnCalendar=01:00OnActiveSec=1.5h
Ну это слишком просто. Например мы хотим что-б наш юнит
запускался каждую пятницу 13-е OnCalendar=Fri *-*-13
12:00:00
Полный формат календарной формы выглядит так:
Mon 2025-12-01 00:00:00.000000 Europe/Moscow
Поэтому
мы можем запускать таймер по времени другого часового пояса (по
умолчанию текущий) Например хотим что-б таймер прислал нам
уведомление, что Камчатка уже отпраздновала Новый год:
OnCalendar=yearly Asia/Kamchatka
Нормализованная форма
будет выглядеть так(эти строчки указывают на одно и то-же
время):
OnCalendar=*-01-01 00:00:00 Asia/Kamchatka
Алиасы (и
их эквиваленты в нормализованной форме) могут быть такими:
minutely *-*-* *:*:00 hourly *-*-* *:00:00 daily *-*-* 00:00:00 monthly *-*-01 00:00:00 weekly Mon *-*-* 00:00:00 yearly *-01-01 00:00:00 quarterly *-01,04,07,10-01 00:00:00 semiannually *-01,07-01 00:00:00
Примеры валидных таймстампов:
таймстамп с @ epoch time
Fri 2012-11-23 11:12:13 Fri 2012-11-23 11:12:13 2012-11-23 11:12:13 Fri 2012-11-23 11:12:13 2012-11-23 11:12:13 UTC Fri 2012-11-23 19:12:13 2012-11-23 Fri 2012-11-23 00:00:00 12-11-23 Fri 2012-11-23 00:00:00 11:12:13 Fri 2012-11-23 11:12:13 11:12 Fri 2012-11-23 11:12:00 now Fri 2012-11-23 18:15:22 today Fri 2012-11-23 00:00:00 today UTC Fri 2012-11-23 16:00:00 yesterday Fri 2012-11-22 00:00:00 tomorrow Fri 2012-11-24 00:00:00 tomorrow Pacific/Auckland Thu 2012-11-23 19:00:00 +3h30min Fri 2012-11-23 21:45:22 -5s Fri 2012-11-23 18:15:17 11min ago Fri 2012-11-23 18:04:22 @1395716396 Tue 2014-03-25 03:59:56
Здесь представлены таймстампы как для OnCalendar=
,
так и для монотонных таймеров.
Перечисления и диапазоны:
Боольшой список примеров
Sat,Thu,Mon..Wed,Sat..Sun Mon..Thu,Sat,Sun *-*-* 00:00:00 Mon,Sun 12-*-* 2,1:23 Mon,Sun 2012-*-* 01,02:23:00 Wed *-1 Wed *-*-01 00:00:00 Wed..Wed,Wed *-1 Wed *-*-01 00:00:00 Wed, 17:48 Wed *-*-* 17:48:00 Wed..Sat,Tue 12-10-15 1:2:3 Tue..Sat 2012-10-15 01:02:03 *-*-7 0:0:0 *-*-07 00:00:00 10-15 *-10-15 00:00:00 monday *-12-* 17:00 Mon *-12-* 17:00:00 Mon,Fri *-*-3,1,2 *:30:45 Mon,Fri *-*-01,02,03 *:30:45 12,14,13,12:20,10,30 *-*-* 12,13,14:10,20,30:00 12..14:10,20,30 *-*-* 12..14:10,20,30:00 mon,fri *-1/2-1,3 *:30:45 Mon,Fri *-01/2-01,03 *:30:45 03-05 08:05:40 *-03-05 08:05:40 08:05:40 *-*-* 08:05:40 05:40 *-*-* 05:40:00 Sat,Sun 12-05 08:05:40 Sat,Sun *-12-05 08:05:40 Sat,Sun 08:05:40 Sat,Sun *-*-* 08:05:40 2003-03-05 05:40 2003-03-05 05:40:00 05:40:23.4200004/3.1700005 *-*-* 05:40:23.420000/3.170001 2003-02..04-05 2003-02..04-05 00:00:00 2003-03-05 05:40 UTC 2003-03-05 05:40:00 UTC 2003-03-05 2003-03-05 00:00:00 03-05 *-03-05 00:00:00 hourly *-*-* *:00:00 daily *-*-* 00:00:00 daily UTC *-*-* 00:00:00 UTC monthly *-*-01 00:00:00 weekly Mon *-*-* 00:00:00 weekly Pacific/Auckland Mon *-*-* 00:00:00 Pacific/Auckland yearly *-01-01 00:00:00 annually *-01-01 00:00:00 *:2/3 *-*-* *:02/3:00
Да. Микро и наносекунды тоже поддерживаются, а ещё очень удобная функция конца месяца и счётчик:
-
*-*~01
Первый день с конца каждого месяца (он-же последний день месяца). -
*-05~05
27-e мая каждого года (31-5). -
Mon *-12~07/1
Последний понедельник декабря. -
Mon *-12-01/3
Третий понедельник декабря.
Проверять таймстампы на валидность можно при помощи утилиты
systemd-analyze
:
$ systemd-analyze calendar 'Mon *-12-01/1' Original form: Mon *-12-01/1 Normalized form: Mon *-12-01/1 00:00:00 Next elapse: Mon 2021-12-06 00:00:00 MSK (in UTC): Sun 2021-12-05 21:00:00 UTC From now: 11 months 2 days left$ systemd-analyze timespan 1.5hOriginal: 1.5h s: 5400000000 Human: 1h 30min$ systemd-analyze timestamp 01:00:30.9999 Original form: 01:00:30.9999 Normalized form: Sat 2021-01-02 01:00:30 MSK (in UTC): Fri 2021-01-01 22:00:30 UTC UNIX seconds: @1609538430.999900 From now: 18h ago
Вот так, в принципе, всё просто, логично и красиво. И разумеется напочитать:
man systemd.timerman systemd.timeman systemd-system.confman systemd-analyzeman tzselect