company_banner

Использование таймеров systemd вместо заданий cron

Автор оригинала: David Both
  • Перевод
Сейчас я занимаюсь заменой моих cron-заданий на таймеры systemd. Я пользовался таймерами несколько лет, но обычно в тонкости их применения особо не углублялся, разбираясь лишь с тем, что нужно было для выполнения интересующей меня задачи. Недавно я работал над серией материалов про systemd и узнал о том, что systemd-таймеры обладают некоторыми очень интересными возможностями.



Эти таймеры, как и задания cron, могут, в заданное время, вызывать выполнение различных действий в системе. Например — запуск скриптов командной оболочки или программ. Таймеры могут срабатывать, например, раз в день, причём — только по понедельникам. Ещё один пример — срабатывание таймера каждые 15 минут в рабочее время (с 8 утра до 6 вечера). Но таймеры systemd могут кое-что такое, что недоступно заданиям cron. Например, таймер может вызвать скрипт или программу через заданное время после некоего события. Таким событием может быть загрузка системы или запуск systemd, завершение предыдущей задачи или даже завершение работы сервиса, вызванного ранее по таймеру.

Таймеры, используемые для обслуживания системы


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

В качестве примера приведу здесь сведения о таймерах, которые имеются на виртуальной машине, использованной мной для экспериментов. Здесь, для получения списка всех таймеров, я использовал команду systemctl status *timer. Подстановочный символ в виде звёздочки играет здесь ту же роль, которую он играет в других подобных командах. А именно, он сообщает системе о том, что нас интересуют все таймеры (timer units, их ещё называют «unit-файлы таймера» или «юниты таймера») systemd:

[root@testvm1 ~]# systemctl status *timer
● mlocate-updatedb.timer - Updates mlocate database every day
     Loaded: loaded (/usr/lib/systemd/system/mlocate-updatedb.timer; enabled; vendor preset: enabled)
     Active: active (waiting) since Tue 2020-06-02 08:02:33 EDT; 2 days ago
    Trigger: Fri 2020-06-05 00:00:00 EDT; 15h left
   Triggers: ● mlocate-updatedb.service

Jun 02 08:02:33 testvm1.both.org systemd[1]: Started Updates mlocate database every day.

● logrotate.timer - Daily rotation of log files
     Loaded: loaded (/usr/lib/systemd/system/logrotate.timer; enabled; vendor preset: enabled)
     Active: active (waiting) since Tue 2020-06-02 08:02:33 EDT; 2 days ago
    Trigger: Fri 2020-06-05 00:00:00 EDT; 15h left
   Triggers: ● logrotate.service
       Docs: man:logrotate(8)
             man:logrotate.conf(5)

Jun 02 08:02:33 testvm1.both.org systemd[1]: Started Daily rotation of log files.

● sysstat-summary.timer - Generate summary of yesterday's process accounting
     Loaded: loaded (/usr/lib/systemd/system/sysstat-summary.timer; enabled; vendor preset: enabled)
     Active: active (waiting) since Tue 2020-06-02 08:02:33 EDT; 2 days ago
    Trigger: Fri 2020-06-05 00:07:00 EDT; 15h left
   Triggers: ● sysstat-summary.service

Jun 02 08:02:33 testvm1.both.org systemd[1]: Started Generate summary of yesterday's process accounting.

● fstrim.timer - Discard unused blocks once a week
     Loaded: loaded (/usr/lib/systemd/system/fstrim.timer; enabled; vendor preset: enabled)
     Active: active (waiting) since Tue 2020-06-02 08:02:33 EDT; 2 days ago
    Trigger: Mon 2020-06-08 00:00:00 EDT; 3 days left
   Triggers: ● fstrim.service
       Docs: man:fstrim

Jun 02 08:02:33 testvm1.both.org systemd[1]: Started Discard unused blocks once a week.

● sysstat-collect.timer - Run system activity accounting tool every 10 minutes
     Loaded: loaded (/usr/lib/systemd/system/sysstat-collect.timer; enabled; vendor preset: enabled)
     Active: active (waiting) since Tue 2020-06-02 08:02:33 EDT; 2 days ago
    Trigger: Thu 2020-06-04 08:50:00 EDT; 41s left
   Triggers: ● sysstat-collect.service

Jun 02 08:02:33 testvm1.both.org systemd[1]: Started Run system activity accounting tool every 10 minutes.

● dnf-makecache.timer - dnf makecache --timer
     Loaded: loaded (/usr/lib/systemd/system/dnf-makecache.timer; enabled; vendor preset: enabled)
     Active: active (waiting) since Tue 2020-06-02 08:02:33 EDT; 2 days ago
    Trigger: Thu 2020-06-04 08:51:00 EDT; 1min 41s left
   Triggers: ● dnf-makecache.service

Jun 02 08:02:33 testvm1.both.org systemd[1]: Started dnf makecache –timer.

● systemd-tmpfiles-clean.timer - Daily Cleanup of Temporary Directories
     Loaded: loaded (/usr/lib/systemd/system/systemd-tmpfiles-clean.timer; static; vendor preset: disabled)
     Active: active (waiting) since Tue 2020-06-02 08:02:33 EDT; 2 days ago
    Trigger: Fri 2020-06-05 08:19:00 EDT; 23h left
   Triggers: ● systemd-tmpfiles-clean.service
       Docs: man:tmpfiles.d(5)
             man:systemd-tmpfiles(8)

Jun 02 08:02:33 testvm1.both.org systemd[1]: Started Daily Cleanup of Temporary Directories.

С каждым таймером связано, по меньшей мере, шесть строк, содержащих сведения о нём:

  • Первая строка содержит имя файла таймера и короткое описание цели существования этого таймера.
  • Вторая строка выводит сведения о состоянии таймера. А именно, сообщает о том, загружен ли он, даёт полный путь к файлу таймера, показывает состояние vendor preset (disabled или enabled).
  • В третьей строке показаны сведения об активности таймера, куда входят данные о том, когда именно таймер был активирован.
  • В четвёртой строке содержатся дата и время следующего запуска таймера и примерное время, оставшееся до его запуска.
  • Пятая строка сообщает имя сервиса или события, вызываемого таймером.
  • Некоторые (но не все) файлы юнитов таймеров systemd содержат указатели на документацию. Такие указатели есть, в моём примере, в описаниях трёх таймеров.
  • Последняя строка в описании таймера представляет собой запись журнала, которая связана с самым свежим экземпляром сервиса, вызванного таймером.

Если вы попробуете выполнить на своём компьютере команду systemctl status *timer, то представленный ей набор таймеров вполне может отличаться от моего.

Создание таймера


Хотя мы могли бы разобраться с особенностями работы таймеров, проанализировав какие-нибудь существующие таймеры, я предлагаю создать собственный unit-файл сервиса (файл конфигурации сервиса, service unit) и файл таймера, с помощью которого будет вызываться соответствующий сервис. Мы тут, чтобы не усложнять повествование, приведём довольно тривиальный пример. Но после того как мы с ним разберёмся, вам будет легче разобраться в работе других таймеров.

Для начала создадим простой файл конфигурации сервиса, который будет запускать что-то простое, вроде команды free. Например, это может понадобиться в том случае, если нам надо регулярно мониторить объём свободной памяти. Создадим unit-файл с именем myMonitor.service в папке /etc/systemd/system. Он не обязательно должен быть исполняемым.

# This service unit is for testing timer units
# By David Both
# Licensed under GPL V2
#

[Unit]
Description=Logs system statistics to the systemd journal
Wants=myMonitor.timer

[Service]
Type=oneshot
ExecStart=/usr/bin/free

[Install]
WantedBy=multi-user.target

Этот файл, пожалуй, можно назвать простейшим файлом конфигурации сервиса. Теперь давайте проверим его состояние и протестируем его для того чтобы убедиться в том, что он работает так, как ожидается.

[root@testvm1 system]# systemctl status myMonitor.service
● myMonitor.service - Logs system statistics to the systemd journal
     Loaded: loaded (/etc/systemd/system/myMonitor.service; disabled; vendor preset: disabled)
     Active: inactive (dead)
[root@testvm1 system]# systemctl start myMonitor.service
[root@testvm1 system]#

А почему ничего не выводится в консоль? Дело в том, что по умолчанию стандартный вывод (stdout) от программ, запускаемых systemd с помощью unit-файлов сервисов, перенаправляется в журнал systemd. Благодаря этому, по крайней мере, до тех пор, пока соответствующие записи существуют, эти записи можно проанализировать. Заглянем в журнал и поищем записи, относящиеся к нашему сервису и к дню, когда мы проводили испытания. Соответствующая команда будет выглядеть так: journalctl -S today -u myMonitor.service. Ключ -S — это сокращённый вариант --since. Он позволяет указывать временной период, за который утилита journalctl ищет записи. Дело тут не в том, что нас не интересуют более ранние результаты. В нашем случае таких результатов просто не будет. Этот ключ использован для того чтобы сократить время, которое нужно утилите на поиск данных. Если компьютер работает уже давно, в журнале может накопиться очень много записей.

[root@testvm1 system]# journalctl -S today -u myMonitor.service
-- Logs begin at Mon 2020-06-08 07:47:20 EDT, end at Thu 2020-06-11 09:40:47 EDT. --
Jun 11 09:12:09 testvm1.both.org systemd[1]: Starting Logs system statistics to the systemd journal...
Jun 11 09:12:09 testvm1.both.org free[377966]:               total        used        free      shared  buff/cache   available
Jun 11 09:12:09 testvm1.both.org free[377966]: Mem:       12635740      522868    11032860        8016     1080012    11821508
Jun 11 09:12:09 testvm1.both.org free[377966]: Swap:       8388604           0     8388604
Jun 11 09:12:09 testvm1.both.org systemd[1]: myMonitor.service: Succeeded.
[root@testvm1 system]#

Задача, которая запускается с помощью файла конфигурации сервиса, может быть представлена отдельной программой, последовательностью программ, или скриптом, написанном на любом скриптовом языке. Добавим в unit-файл myMonitor.service ещё одну задачу, включив в него, в конце раздела [Service], следующее:

ExecStart=/usr/bin/lsblk

Снова запустим сервис и проверим журнал. Там должно быть что-то, напоминающее то, что показано ниже. А именно, в журнал должны попасть данные, выводимые обеими командами:

Jun 11 15:42:18 testvm1.both.org systemd[1]: Starting Logs system statistics to the systemd journal...
Jun 11 15:42:18 testvm1.both.org free[379961]:               total        used        free      shared  buff/cache   available
Jun 11 15:42:18 testvm1.both.org free[379961]: Mem:       12635740      531788    11019540        8024     1084412    11812272
Jun 11 15:42:18 testvm1.both.org free[379961]: Swap:       8388604           0     8388604
Jun 11 15:42:18 testvm1.both.org lsblk[379962]: NAME          MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
Jun 11 15:42:18 testvm1.both.org lsblk[379962]: sda             8:0    0  120G  0 disk
Jun 11 15:42:18 testvm1.both.org lsblk[379962]: ├─sda1          8:1    0    4G  0 part /boot
Jun 11 15:42:18 testvm1.both.org lsblk[379962]: └─sda2          8:2    0  116G  0 part
Jun 11 15:42:18 testvm1.both.org lsblk[379962]:   ├─VG01-root 253:0    0    5G  0 lvm  /
Jun 11 15:42:18 testvm1.both.org lsblk[379962]:   ├─VG01-swap 253:1    0    8G  0 lvm  [SWAP]
Jun 11 15:42:18 testvm1.both.org lsblk[379962]:   ├─VG01-usr  253:2    0   30G  0 lvm  /usr
Jun 11 15:42:18 testvm1.both.org lsblk[379962]:   ├─VG01-tmp  253:3    0   10G  0 lvm  /tmp
Jun 11 15:42:18 testvm1.both.org lsblk[379962]:   ├─VG01-var  253:4    0   20G  0 lvm  /var
Jun 11 15:42:18 testvm1.both.org lsblk[379962]:   └─VG01-home 253:5    0   10G  0 lvm  /home
Jun 11 15:42:18 testvm1.both.org lsblk[379962]: sr0            11:0    1 1024M  0 rom
Jun 11 15:42:18 testvm1.both.org systemd[1]: myMonitor.service: Succeeded.
Jun 11 15:42:18 testvm1.both.org systemd[1]: Finished Logs system statistics to the systemd journal.

Теперь, после того как мы уверились в том, что всё работает правильно, создадим, в папке /etc/systemd/system, unit-файл таймера, дав ему имя myMonitor.timer. В файл добавим следующее:

# This timer unit is for testing
# By David Both
# Licensed under GPL V2
#

[Unit]
Description=Logs some system statistics to the systemd journal
Requires=myMonitor.service

[Timer]
Unit=myMonitor.service
OnCalendar=*-*-* *:*:00

[Install]
WantedBy=timers.target

Указатель времени OnCalendar в этом файле, *-*-* *:*:00, должен приводить к тому, что таймер выполняет вызов юнита myMonitor.service каждую минуту. Подробнее об OnCalendar мы поговорим ниже.

А пока мы можем взглянуть на записи журнала, имеющие отношение к запуску сервиса, вызываемого таймером. Мы, кроме того, можем включить режим наблюдения за таймером. Однако наблюдение за сервисом позволит видеть результаты практически в режиме реального времени. Для этого нужно запустить journalctl с ключом -f (follow):

[root@testvm1 system]# journalctl -S today -f -u myMonitor.service
-- Logs begin at Mon 2020-06-08 07:47:20 EDT. --

Запустите таймер, но не включайте его в автозапуск при загрузке системы. 

[root@testvm1 ~]# systemctl start myMonitor.timer
[root@testvm1 ~]#

Понаблюдайте некоторое время за тем, что происходит.

Один из результатов появляется сразу же. А следующие будут выводиться с интервалом примерно в одну минуту. Понаблюдайте за журналом несколько минут.

[root@testvm1 system]# journalctl -S today -f -u myMonitor.service
-- Logs begin at Mon 2020-06-08 07:47:20 EDT. --
Jun 13 08:39:18 testvm1.both.org systemd[1]: Starting Logs system statistics to the systemd journal...
Jun 13 08:39:18 testvm1.both.org systemd[1]: myMonitor.service: Succeeded.
Jun 13 08:39:19 testvm1.both.org free[630566]:               total        used        free      shared  buff/cache   available
Jun 13 08:39:19 testvm1.both.org free[630566]: Mem:       12635740      556604    10965516        8036     1113620    11785628
Jun 13 08:39:19 testvm1.both.org free[630566]: Swap:       8388604           0     8388604
Jun 13 08:39:18 testvm1.both.org systemd[1]: Finished Logs system statistics to the systemd journal.
Jun 13 08:39:19 testvm1.both.org lsblk[630567]: NAME          MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
Jun 13 08:39:19 testvm1.both.org lsblk[630567]: sda             8:0    0  120G  0 disk
Jun 13 08:39:19 testvm1.both.org lsblk[630567]: ├─sda1          8:1    0    4G  0 part /boot
Jun 13 08:39:19 testvm1.both.org lsblk[630567]: └─sda2          8:2    0  116G  0 part
Jun 13 08:39:19 testvm1.both.org lsblk[630567]:   ├─VG01-root 253:0    0    5G  0 lvm  /
Jun 13 08:39:19 testvm1.both.org lsblk[630567]:   ├─VG01-swap 253:1    0    8G  0 lvm  [SWAP]
Jun 13 08:39:19 testvm1.both.org lsblk[630567]:   ├─VG01-usr  253:2    0   30G  0 lvm  /usr
Jun 13 08:39:19 testvm1.both.org lsblk[630567]:   ├─VG01-tmp  253:3    0   10G  0 lvm  /tmp
Jun 13 08:39:19 testvm1.both.org lsblk[630567]:   ├─VG01-var  253:4    0   20G  0 lvm  /var
Jun 13 08:39:19 testvm1.both.org lsblk[630567]:   └─VG01-home 253:5    0   10G  0 lvm  /home
Jun 13 08:39:19 testvm1.both.org lsblk[630567]: sr0            11:0    1 1024M  0 rom
Jun 13 08:40:46 testvm1.both.org systemd[1]: Starting Logs system statistics to the systemd journal...
Jun 13 08:40:46 testvm1.both.org free[630572]:               total        used        free      shared  buff/cache   available
Jun 13 08:40:46 testvm1.both.org free[630572]: Mem:       12635740      555228    10966836        8036     1113676    11786996
Jun 13 08:40:46 testvm1.both.org free[630572]: Swap:       8388604           0     8388604
Jun 13 08:40:46 testvm1.both.org lsblk[630574]: NAME          MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
Jun 13 08:40:46 testvm1.both.org lsblk[630574]: sda             8:0    0  120G  0 disk
Jun 13 08:40:46 testvm1.both.org lsblk[630574]: ├─sda1          8:1    0    4G  0 part /boot
Jun 13 08:40:46 testvm1.both.org lsblk[630574]: └─sda2          8:2    0  116G  0 part
Jun 13 08:40:46 testvm1.both.org lsblk[630574]:   ├─VG01-root 253:0    0    5G  0 lvm  /
Jun 13 08:40:46 testvm1.both.org lsblk[630574]:   ├─VG01-swap 253:1    0    8G  0 lvm  [SWAP]
Jun 13 08:40:46 testvm1.both.org lsblk[630574]:   ├─VG01-usr  253:2    0   30G  0 lvm  /usr
Jun 13 08:40:46 testvm1.both.org lsblk[630574]:   ├─VG01-tmp  253:3    0   10G  0 lvm  /tmp
Jun 13 08:40:46 testvm1.both.org lsblk[630574]:   ├─VG01-var  253:4    0   20G  0 lvm  /var
Jun 13 08:40:46 testvm1.both.org lsblk[630574]:   └─VG01-home 253:5    0   10G  0 lvm  /home
Jun 13 08:40:46 testvm1.both.org lsblk[630574]: sr0            11:0    1 1024M  0 rom
Jun 13 08:40:46 testvm1.both.org systemd[1]: myMonitor.service: Succeeded.
Jun 13 08:40:46 testvm1.both.org systemd[1]: Finished Logs system statistics to the systemd journal.
Jun 13 08:41:46 testvm1.both.org systemd[1]: Starting Logs system statistics to the systemd journal...
Jun 13 08:41:46 testvm1.both.org free[630580]:               total        used        free      shared  buff/cache   available
Jun 13 08:41:46 testvm1.both.org free[630580]: Mem:       12635740      553488    10968564        8036     1113688    11788744
Jun 13 08:41:46 testvm1.both.org free[630580]: Swap:       8388604           0     8388604
Jun 13 08:41:47 testvm1.both.org lsblk[630581]: NAME          MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
Jun 13 08:41:47 testvm1.both.org lsblk[630581]: sda             8:0    0  120G  0 disk
Jun 13 08:41:47 testvm1.both.org lsblk[630581]: ├─sda1          8:1    0    4G  0 part /boot
Jun 13 08:41:47 testvm1.both.org lsblk[630581]: └─sda2          8:2    0  116G  0 part
Jun 13 08:41:47 testvm1.both.org lsblk[630581]:   ├─VG01-root 253:0    0    5G  0 lvm  /
Jun 13 08:41:47 testvm1.both.org lsblk[630581]:   ├─VG01-swap 253:1    0    8G  0 lvm  [SWAP]
Jun 13 08:41:47 testvm1.both.org lsblk[630581]:   ├─VG01-usr  253:2    0   30G  0 lvm  /usr
Jun 13 08:41:47 testvm1.both.org lsblk[630581]:   ├─VG01-tmp  253:3    0   10G  0 lvm  /tmp
Jun 13 08:41:47 testvm1.both.org lsblk[630581]:   ├─VG01-var  253:4    0   20G  0 lvm  /var
Jun 13 08:41:47 testvm1.both.org lsblk[630581]:   └─VG01-home 253:5    0   10G  0 lvm  /home
Jun 13 08:41:47 testvm1.both.org lsblk[630581]: sr0            11:0    1 1024M  0 rom
Jun 13 08:41:47 testvm1.both.org systemd[1]: myMonitor.service: Succeeded.
Jun 13 08:41:47 testvm1.both.org systemd[1]: Finished Logs system statistics to the systemd journal.

Обязательно проверьте и состояние таймера, и состояние сервиса.

Удалось ли вам заметить тут то, что заметил я? Возможно, вы, читая журнал, обратите внимание как минимум на две вещи.

Во-первых — на то, что вам не нужно делать что-то особенное для логирования того, что ExecStart из myMonitor.service пишет в stdout. Эта возможность входит в стандартные функции systemd по запуску сервисов. Но это означает, что вам, вероятно, потребуется проявлять осторожность при запуске скриптов из файлов конфигурации сервисов, обращая внимание на то, каков объём данных, который они пишут в stdout.

Во-вторых, вы могли обратить внимание на то, что таймер запускается не точно в :00 секунд каждой минуты, и даже не в точности через одну минуту после предыдущего запуска. Это — одна из особенностей подобных таймеров, если это нужно (или если это задевает чувства системного администратора), такое поведение таймеров можно изменить, сделав их точнее.

Причина того, что таймер не запускается в :00 секунд каждой минуты, заключается в том, что система таким образом стремится предотвратить одновременный запуск нескольких сервисов. Например, при настройке указателя времени OnCalendar можно пользоваться такими значениями, как Weekly, Daily, да и другими подобными. Эти сокращённые способы именования моментов времени настроены так, чтобы таймеры, в которых они используются, запускались бы в 00:00:00 соответствующего дня. Когда так настроено множество таймеров, высока вероятность того, что все они сработают одновременно.

Именно поэтому таймеры systemd намеренно спроектированы так, чтобы они запускались бы не в точно указанное время, а с некоторым случайным отклонением от него. Это отклонение нельзя назвать совершенно случайным. Таймеры запускаются где-то во временном окне, которое начинается с указанного момента и заканчивается моментом, отстоящим от исходного на одну минуту. Это время, в соответствии с документацией к systemd.timer, поддерживается в стабильном состоянии с учётом всех остальных объявленных в системе таймеров. В вышеприведённом фрагменте журнала можно видеть, что таймер срабатывает сразу же после его запуска, а потом — примерно через 46 или 47 секунд после начала каждой следующей минуты.

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

Но для решения некоторых задач точное время срабатывания таймера чрезвычайно важно. В подобных случаях при настройке таймеров можно указывать более точное время их срабатывания (вплоть до точности, измеряемой микросекундами). Делается это путём добавления в файл описания таймера, в раздел Timer, конструкции, напоминающей следующую:

AccuracySec=1us

Для указания желаемой точности таймера можно пользоваться особыми ключевыми словами. Эти ключевые слова можно применять и при настройке повторяющихся и «одноразовых» событий. Система понимает следующие ключевые слова:

  • Микросекунда: usec, us, µs.
  • Миллисекунда: msec, ms.
  • Секунда: seconds, second, sec, s.
  • Минута: minutes, minute, min, m.
  • Час: hours, hour, hr, h.
  • День: days, day, d.
  • Неделя: weeks, week, w.
  • Месяц: months, month, M (месяц определён как 30,44 дня).
  • Год: years, year, y (год определён как 365,25 дня).

Все стандартные таймеры, которые имеются в /usr/lib/systemd/system, настроены с использованием гораздо более длительных диапазонов, задающих точность их запуска, так как в случае с этими таймерами срабатывание их в строго заданное время не особенно важно. Взгляните на спецификации некоторых таймеров, созданных системой:

[root@testvm1 system]# grep Accur /usr/lib/systemd/system/*timer
/usr/lib/systemd/system/fstrim.timer:AccuracySec=1h
/usr/lib/systemd/system/logrotate.timer:AccuracySec=1h
/usr/lib/systemd/system/logwatch.timer:AccuracySec=12h
/usr/lib/systemd/system/mlocate-updatedb.timer:AccuracySec=24h
/usr/lib/systemd/system/raid-check.timer:AccuracySec=24h
/usr/lib/systemd/system/unbound-anchor.timer:AccuracySec=24h
[root@testvm1 system]#

Для того чтобы лучше познакомиться с внутренним устройством файлов таймеров из директории /usr/lib/systemd/system, вы можете просмотреть их содержимое.

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

[root@testvm1 system]# systemctl enable myMonitor.timer

Файлы таймеров, которые вы будете создавать, не обязательно должны быть исполняемыми. Кроме того, файлы конфигурации сервисов не нужно настраивать так, чтобы они активировались бы при загрузке, так как их вызывают таймеры. Если надо, сервис можно вызвать и вручную, из командной строки. Попробуйте сделать это и загляните в журнал systemd.

Для того чтобы узнать подробности о точности таймеров, о способах указания времени вызова событий, о вызове событий, посмотрите документацию по systemd.timer и systemd.time.

Типы таймеров


Таймеры systemd обладают другими возможностями, которых нет у заданий cron, «одноразовых» или повторяющихся, которые вызываются только с привязкой к реальному времени и к реальным датам. Таймеры systemd можно настроить так, чтобы они вызывались бы на основании изменения состояния других юнитов systemd. Например, таймер можно настроить так, чтобы он срабатывал бы через заданное время после загрузки системы, после входа в неё пользователя, или через заданное время после активации определённого сервиса. Такие таймеры называют монотонными (monotonic). Эти таймеры сбрасываются после каждой перезагрузки системы.

В следующей таблице приведён список монотонных таймеров с краткой характеристикой каждого из них. Там же есть и описание таймера OnCalendar, который монотонным не является и применяется в тех случаях, когда нужно организовать единовременный или повторяющийся запуск чего-либо в будущем. Эта таблица основана на документации systemd.timer.
Таймер Монотонный Описание
OnActiveSec=

X Время срабатывания таймера задаётся относительно момента активации таймера.
OnBootSec=

X Время срабатывания таймера задаётся относительно момента загрузки системы.
OnStartupSec=

X Время срабатывания таймера определяется относительно первого запуска менеджера служб. В случае с системными таймерами это очень похоже на OnBootSec=, так как системный менеджер служб обычно запускается при загрузке очень рано. Подобные таймеры, преимущественно, ценны при использовании их с привязкой к менеджерам служб, относящимся к отдельным пользователям, так как такие менеджеры служб обычно запускаются только при первом входе пользователя в систему, а не в процессе загрузки системы.
OnUnitActiveSec=

X Время срабатывания таймера задаётся относительно того времени, когда таймер, который должен быть активирован, был активирован в последний раз.
OnUnitInactiveSec=

X Время срабатывания таймера определяется относительно того времени, когда таймер, который должен быть активирован, был в последний раз деактивирован.
OnCalendar=

  Такой таймер привязан к реальному времени и ориентируется на события календаря. Подробности о синтаксисе описаний событий календаря можно найти в справке по systemd.time(7). В остальном же семантика описаний таких таймеров похожа на таймеры OnActiveSec=. Это — именно такие таймеры systemd, которые сильнее всего похожи на те механизмы, которые применяются при настройке заданий cron.

При настройке монотонных таймеров могут использоваться те же ключевые слова, что были описаны выше при разговоре об AccuracySec. Но надо отметить, что systemd приводит соответствующие временные промежутки к секундам. Например, вам нужно задать таймер, который срабатывает один раз, через пять дней после загрузки системы. Выглядеть его описание может так: OnBootSec=5d. Если компьютер был загружен 2020-06-15 в 09:45:27, то таймер сработает 2020-06-20 в 09:45:27 (или в пределах 1 минуты после этого момента времени).

Описание событий календаря


Применение событий календаря — это ключевая часть описания таймеров, вызываемых через определённые промежутки времени. Разберём некоторые особенности таких событий, используемых при настройке указателя времени OnCalendar.

В systemd и в соответствующих таймерах используется формат описания времени и дат, отличающийся от того, который принят в crontab. Этот формат является более гибким, чем тот, что используется в crontab. Он позволяет указывать дату и время в упрощённом виде, в стиле команды at. Тому, кто знаком с at, будет несложно разобраться с настройками таймеров systemd.

При использовании OnCalendar= для настройки таймеров используется следующий базовый формат для указания даты и времени:

DOW YYYY-MM-DD HH:MM:SS

DOW (Day Of Week, день недели), это необязательная часть вышеприведённой конструкции. В других полях можно использовать символ звёздочки (*), символизирующий любое значение, которое может находиться в занимаемой им позиции. Все формы указания даты и времени приводятся к нормализованной форме. Если время не указано, предполагается, что это — 00:00:00. Если дата не указана, а время указано, то таймер сработает либо в день его запуска (условно говоря, «сегодня»), либо на следующий день («завтра»). Это зависит от текущего времени. Для указания месяцев и дней недели могут использоваться их названия. Здесь можно использовать списки значений, разделяемые запятой. Диапазоны значений можно разделять тремя точками (), которые ставят между начальным и конечным значением диапазона.

При указании дат в нашем распоряжении есть пара интересных возможностей. Так, тильда (~) может использоваться для указания последнего дня месяца, или для указания даты, на заданное количество дней предшествующей последнему дню месяца. Косая черта (/) может применяться, в виде модификатора, для указания дня недели.

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

Пример представления события календаря DOW YYYY-MM-DD HH:MM:SS
Описание
*-*-* 00:15:30

Каждый день каждого месяца каждого года, через 15 минут 30 секунд после полуночи.
Weekly

Каждый понедельник в 00:00:00.
Mon *-*-* 00:00:00

То же самое, что и Weekly.
Mon

То же самое, что и Weekly.
Wed 2020-*-*

Каждую среду 2020 года в 00:00:00.
Mon..Fri 2021-*-*

Каждый будний день в 2021 году в 00:00:00.
2022-6,7,8-1,15 01:15:00

1 и 15 июня, июля и августа 2022 года в 01:15:00 после полуночи.
Mon *-05~03

В следующий раз, в любой год, когда в мае понедельник будет днём, на 3 дня предшествующим концу месяца.
Mon..Fri *-08~04

В любом году, в 4 будний день, предшествующий концу августа.
*-05~03/2

В 3 день, предшествующий концу мая, и затем — снова, через два дня. Повторяется ежегодно. Обратите внимание на то, что тут использован знак ~.
*-05-03/2

В третий день мая, а затем — каждый второй день этого месяца. Повторяется ежегодно. Обратите внимание на то, что тут использовано тире (-).

Тестирование описаний времени, используемых в событиях календаря


В systemd имеется отличный инструмент для проверки и исследования спецификаций событий календаря. Речь идёт о команде systemd-analyze calendar, которая разбирает описания событий календаря и представляет их в нормализованной форме. Эта команда даёт и другую интересную информацию, такую, как дата и время наступления следующего такого события, и приблизительное время, оставшееся до достижения этого момента.

Для начала взглянем на спецификацию, которая содержит только дату, не содержит сведений о времени (обратите внимание на то, что время в полях Next elapse и (in UTC) различаются, это различие зависит от местного часового пояса):

[student@studentvm1 ~]$ systemd-analyze calendar 2030-06-17
  Original form: 2030-06-17                
Normalized form: 2030-06-17 00:00:00        
    Next elapse: Mon 2030-06-17 00:00:00 EDT
       (in UTC): Mon 2030-06-17 04:00:00 UTC
       From now: 10 years 0 months left    
[root@testvm1 system]#

Теперь добавим в описание сведения о времени. В данном примере дата и время анализируются раздельно, как сущности, не связанные друг с другом:

[root@testvm1 system]# systemd-analyze calendar 2030-06-17 15:21:16
  Original form: 2030-06-17                
Normalized form: 2030-06-17 00:00:00        
    Next elapse: Mon 2030-06-17 00:00:00 EDT
       (in UTC): Mon 2030-06-17 04:00:00 UTC
       From now: 10 years 0 months left    

  Original form: 15:21:16                  
Normalized form: *-*-* 15:21:16            
    Next elapse: Mon 2020-06-15 15:21:16 EDT
       (in UTC): Mon 2020-06-15 19:21:16 UTC
       From now: 3h 55min left              
[root@testvm1 system]#

Теперь рассмотрим пример, в котором дата и время рассматриваются совместно. Для этого их нужно заключить в кавычки. Но если вы будете использовать такую конструкцию в OnCalendar, не забудьте убрать кавычки, иначе вы столкнётесь с ошибками:

[root@testvm1 system]# systemd-analyze calendar "2030-06-17 15:21:16"
Normalized form: 2030-06-17 15:21:16        
    Next elapse: Mon 2030-06-17 15:21:16 EDT
       (in UTC): Mon 2030-06-17 19:21:16 UTC
       From now: 10 years 0 months left    
[root@testvm1 system]#

Теперь проверим что-нибудь из предыдущей таблицы. Мне особенно нравится такое описание из неё:

2022-6,7,8-1,15 01:15:00

Проанализируем его:

[root@testvm1 system]# systemd-analyze calendar "2022-6,7,8-1,15 01:15:00"
  Original form: 2022-6,7,8-1,15 01:15:00
Normalized form: 2022-06,07,08-01,15 01:15:00
    Next elapse: Wed 2022-06-01 01:15:00 EDT
       (in UTC): Wed 2022-06-01 05:15:00 UTC
       From now: 1 years 11 months left
[root@testvm1 system]#

Теперь давайте взглянем на описание Mon *-05~3, но в этот раз запросим у программы сведения о следующих 5 срабатываниях таймера, в котором использованы такие настройки:

[root@testvm1 ~]# systemd-analyze calendar --iterations=5 "Mon *-05~3"
  Original form: Mon *-05~3                
Normalized form: Mon *-05~03 00:00:00      
    Next elapse: Mon 2023-05-29 00:00:00 EDT
       (in UTC): Mon 2023-05-29 04:00:00 UTC
       From now: 2 years 11 months left    
       Iter. #2: Mon 2028-05-29 00:00:00 EDT
       (in UTC): Mon 2028-05-29 04:00:00 UTC
       From now: 7 years 11 months left    
       Iter. #3: Mon 2034-05-29 00:00:00 EDT
       (in UTC): Mon 2034-05-29 04:00:00 UTC
       From now: 13 years 11 months left    
       Iter. #4: Mon 2045-05-29 00:00:00 EDT
       (in UTC): Mon 2045-05-29 04:00:00 UTC
       From now: 24 years 11 months left    
       Iter. #5: Mon 2051-05-29 00:00:00 EDT
       (in UTC): Mon 2051-05-29 04:00:00 UTC
       From now: 30 years 11 months left    
[root@testvm1 ~]#

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

Дополнительные материалы


В интернете есть много публикаций по systemd, но они, в основном, слишком краткие, очень упрощённые, или даже содержат ошибки. В этой статье приведены некоторые хорошие источники информации по systemd. Ниже дан список ссылок ещё на некоторые качественные материалы по этой теме. 

  • Практическое руководство по systemd, подготовленное Fedora Project.
  • Шпаргалка от Fedora Project, в которой сопоставлены команды старой системы SystemV и systemd.
  • Подробности о systemd и о причинах создания этой системы.
  • Материал со сведениями и советами, посвящённый systemd.
  • Материалы Леннарта Поттеринга (Lennart Poettering), архитектора и основного разработчика systemd. Эти материалы предназначены для системных администраторов, они написаны между апрелем 2010 года и сентябрём 2011, но они всё ещё не потеряли актуальности. Практически все другие достойные публикации о systemd основаны на этих статьях.
  • Материал об управлении датой и временем системы с помощью systemd.
  • Статья об управлении запуском компьютера с использованием systemd.

Итоги


Таймеры systemd можно использовать для решения тех же задач, которые решают с помощью заданий cron. Но systemd даёт больше гибкости в плане настройки календарных и монотонных таймеров.

Даже хотя файлы конфигурации сервисов, созданные нами в ходе экспериментов, обычно вызываются с помощью таймеров, для их вызова можно, в любое время, воспользоваться командой вида systemctl start myMonitor.service. Одним таймером может запускаться несколько задач. Это могут быть, например, и Bash-скрипты, и утилиты Linux. Файл конфигурации сервиса можно составить так, чтобы при его вызове выполнялись бы несколько скриптов. Можно сделать и так, чтобы скрипты запускались бы по отдельности.

Если говорить о сосуществовании systemd, cron и at, то хочу отметить, что я пока не видел каких-либо признаков того, чтобы cron или at были бы признаны устаревшими. Я надеюсь, что их продолжат поддерживать, так как at, по крайней мере, гораздо легче, чем systemd, использовать для планирования единовременных задач.

Чем пользуетесь вы? Таймерами systemd или заданиями cron?

RUVDS.com
RUVDS – хостинг VDS/VPS серверов

Комментарии 10

    +2
    Странно, что в статье не упомянут systemd-cron (это замена для cron для тех, кому от таймеров systemd нужна только базовая функциональность cron)
      0
      Да, я тоже удивился, сам пользуюсь… Хотя впринципе это сторонний пакет и, по крайней мере в Manjaro, его нужно доставлять, даже не из репозиториев, а из AUR.
      –7
      bloatd
        0
        Соответствующие таймеры вызывают различные служебные задачи, вроде обновления системных баз данных, очистки временных директорий

        Очистку временных директорий (а так-же их создание/удаление и/или создание/удаление файлов, плюс раздачу прав и владельцев этих файлов и директорий) логичнее и правильнее делать при помощи подсистемы tmpfiles
        man tmpfiles.d
        Но да… Именно очистка делается системным сервисом systemd-tmpfiles-clean.service, который в свою очередь запускается таймером systemd-tmpfiles-clean.timer.
          0
          И тем и тем… Причём задания cron работают через таймеры systemd.
            0
            Материалы Леннарта Поттеринга
            Отсутствует ссылка, увы (

              0

              Пока пилил свой "умный дом" тоже столкнулся с systemd и пересмотрел своё мнение о нём. Тоже была проблема с неточным срабатыванием таймера. Конечно, решил. Но всё равно хочется избавиться от ежеминутного скрипта и заменить чем-то более простым. Побочный эффект от такого таймера — каждую минуту идёт запись в лог факта срабатывания таймера. А если используется Raspberry Pi, то запись идёт на флешку. Лог, по сути, бесполезен, но ресурс флешки убивает.

                0

                В /etc/systemd/journald.conf (или в /etc/systemd/journald.conf.d/whatever.conf):


                [Journal]
                Storage=volatile
                0

                Блин, не знал. Спасибо большое.

                  –1
                  На Centos 7
                  sudo systemctl status *timer

                  выдает пустую строку, а
                  systemctl list-timers

                  работает.
                  При посте переводных статей неплохо бы проверять что там написано, тк вывод команды
                  systemctl status

                  вообще имеет вид несколько отличный от того что опубликовано.

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                  Самое читаемое