Таймеры systemd вместо crond: различия между версиями

Материал из ALT Linux Wiki
 
(не показано 88 промежуточных версий 2 участников)
Строка 1: Строка 1:
=Предмет статьи=
=Предмет статьи=
Речь о периодических задачах, которые следует исполнять еже<…>но, без указания сложных конструкций из даты:времени/периодичности (сиречь, о том, чем занимаются скрипты из <code>/etc/cron.<…>ly/</code>, плюс не охваченные ими ежеминутные, ежеквартальные и ежесеместровые запуски).
Речь о задачах, которые следует исполнять периодически, без указания сложных конструкций из даты:времени/периодичности (сиречь, о том, чем занимаются скрипты из {{path|/etc/cron.***ly/}}, плюс не охваченные ими ежеминутные, ежеквартальные и ежесеместровые запуски).
 
=Варианты=
=Варианты=
В части руководств встречаются рекомендации «пилить» таймеры под каждый отдельный юнит, существуют и генераторы таймеров на основе crontab &mdash; проекты некоторых можно глянуть [https://github.com/systemd-cron здесь].
В части руководств встречаются рекомендации «пилить» таймеры под каждый отдельный юнит, существуют и генераторы таймеров на основе crontab &mdash; проекты некоторых можно глянуть [https://github.com/systemd-cron здесь].
Строка 7: Строка 8:
Но посредством шаблонов можно сделать проще.
Но посредством шаблонов можно сделать проще.
=Реализация=
=Реализация=
==Запилить пару шаблонов==
===1) Создать пару шаблонов===
Для периодических таймеров:
Для периодических таймеров:
<source lang="ini">
{|class="mw-collapsible mw-collapsed wikitable"
# cat /lib/systemd/system/doit@.timer
!/lib/systemd/system/doit@.timer &nbsp;
 
|-
|<source lang="ini">
[Unit]
[Unit]
Description = %i timer
Description = %i timer
Строка 23: Строка 25:
WantedBy = timers.target
WantedBy = timers.target
</source>
</source>
|}
Для их «целей»:
Для их «целей»:
<source lang="ini">
{| class="mw-collapsible mw-collapsed wikitable"
# cat /lib/systemd/system/doit@.target
!/lib/systemd/system/doit@.target &nbsp;
 
|-
|<source lang="ini">
[Unit]
[Unit]
Description = %i timer target
Description = %i timer target
StopWhenUnneeded = yes
StopWhenUnneeded = yes
</source>
</source>
doit - мол, «делать это еже…»
|}
<pre>
'''doit''' здесь &mdash; это про:
{| class="mw-collapsible mw-collapsed wikitable"
!«делать еже…» &nbsp;
|-
|<pre>
minutely      …минутно,
minutely      …минутно,
hourly        …часно,
hourly        …часно,
Строка 42: Строка 50:
yearly        …годно.
yearly        …годно.
</pre>
</pre>
Само собой, «doit» что в именах, что в шаблоне таймера и периодически запускаемых сервисах можно заменить на «do», «run», «cron», «timers» или что угодно иное.
|}
==Включить и запустить периодические таймеры==
Само собой, «doit» что в именах, что в шаблоне таймера и периодически запускаемых сервисах можно заменить на «do», «run», «cron», «launch» или что угодно иное.
<source lang="bash"># systemctl enable --now doit@{minute,hour,dai,week,month,quarter,semi-annual,year}ly.timer</source>
===2) Включить и запустить периодические таймеры===
==Создать сервисы, требующие периодического исполнения==
# systemctl enable --now doit@{minute,hour,dai,week,month,quarter,semi-annual,year}ly.timer
Пример найден на просторах интернетов, нужную периодичность указывать через секцию [Install]:
===…или оба шага одним скриптом:===
<source lang="ini">
{| class="mw-collapsible mw-collapsed wikitable"
# cat /lib/systemd/system/logrotate.service
!doit.make &nbsp;
|-
|<source lang="ифыр">
#!/bin/sh
 
SVC=/lib/systemd/system/doit@
 
target="[Unit]
Description = %i timer target
StopWhenUnneeded = yes"
 
timer="[Unit]
Description = %i timer
 
[Timer]
OnCalendar = %i
Persistent = true
Unit = doit@%i.target
 
[Install]
WantedBy = timers.target"
 
for TYPE in timer target; do
    TGT=$SVC.$TYPE
    [ -f $TGT -a ! -s $TGT ] || echo "${!TYPE}" >$TGT
done


systemctl enable --now doit@{minute,hour,dai,week,month,quarter,semi-annual,year}ly.timer
</source>
|}
===3) Создать сервисы, требующие периодического исполнения===
Пример найден на просторах интернетов, нужную периодичность (можно сразу для нескольких интервалов) указывать через секцию '''[Install]''':
{| class="mw-collapsible mw-collapsed wikitable"
!/lib/systemd/system/logrotate.service &nbsp;
|-
|<source lang="ini">
[Unit]
[Unit]
Description = Rotate system logs
Description = Rotate system logs
Строка 55: Строка 97:
[Service]
[Service]
Nice = 19
Nice = 19
Type = simple
IOSchedulingClass = 2
IOSchedulingClass = 2
IOSchedulingPriority = 7
IOSchedulingPriority = 7
Строка 63: Строка 104:
WantedBy = doit@daily.target
WantedBy = doit@daily.target
</source>
</source>
|}


==И, наконец, включить их==
===4) И, наконец, включить их===
<source lang="bash"># systemctl enable --now <список таких сервисов></source>
# systemctl enable <список таких сервисов>
…напоследок проверив, что юниты нужных сервисов активированы:
…напоследок проверив, что юниты нужных сервисов активированы:
<source lang="bash"># ls /etc/systemd/system/doit@*ly.target.wants
{| class="mw-collapsible mw-collapsed wikitable"
 
!<code># ls /etc/systemd/system/doit@*ly.target.wants</code> &nbsp;
|-
|<pre>
/etc/systemd/system/doit@daily.target.wants:
/etc/systemd/system/doit@daily.target.wants:
logrotate.service
logrotate.service
Строка 77: Строка 121:
/etc/systemd/system/doit@monthly.target.wants:
/etc/systemd/system/doit@monthly.target.wants:
unbound-anchor.service
unbound-anchor.service
</source>
</pre>
 
|}
=Собственно, готово &mdash; работает=
…и удостоверившись, что всё заработало:
<source lang="bash"># systemctl list-timers
{| class="mw-collapsible mw-collapsed wikitable"
 
!<code># systemctl list-timers</code> &nbsp;
|-
|<pre>
Wed 2017-12-20 12:28:00 IRKT  22s left            Wed 2017-12-20 12:27:00 IRKT  37s ago              doit@minutely.timer          doit@minutely.target
Wed 2017-12-20 12:28:00 IRKT  22s left            Wed 2017-12-20 12:27:00 IRKT  37s ago              doit@minutely.timer          doit@minutely.target
Wed 2017-12-20 13:00:00 IRKT  32min left          Wed 2017-12-20 12:00:00 IRKT  27min ago            doit@hourly.timer            doit@hourly.target
Wed 2017-12-20 13:00:00 IRKT  32min left          Wed 2017-12-20 12:00:00 IRKT  27min ago            doit@hourly.timer            doit@hourly.target
Строка 94: Строка 140:
9 timers listed.
9 timers listed.
Pass --all to see loaded but inactive timers, too.
Pass --all to see loaded but inactive timers, too.
</source>
</pre>
|}
Можно выключить крон:
Можно выключить крон:
<source lang="bash"># systemctl disable --now crond</source>
# systemctl disable --now crond
И не беда, что реально работает меньше половины таймеров: один предустановленный и три добавленных, а остальные пять &mdash; пустышки. Лишние можно отключить либо рассматривать их как заготовки &mdash; вроде пустых каталогов среди <code>/etc/cron.<…>ly/</code>.
Не важно, что реально работает только часть таймеров, а остальные &mdash; пустышки: лишние можно отключить либо рассматривать их как заготовки вроде пустых каталогов из <code>/etc/cron.<…>ly/</code>.
=Примечание=
 
Само собой, для сложных конструкций взамен кронтабов из <code>/etc/cron.d/</code> придётся создавать отдельные таймеры, которые, однако, справятся и там, где крон нужно подпирать костылём &mdash; вроде проверки программного RAID-массива:
=Другие примеры=
<source lang="bash">57 0 * * 0 root [ -x /usr/share/mdadm/checkarray ] && [ $(date +\%d) -le 7 ] && /usr/share/mdadm/checkarray --cron --all --idle --quiet</source>
Само собой, для сложных конструкций взамен кронтабов из <code>/etc/cron.d/</code> придётся создавать отдельные таймеры, которые, однако, справятся и там, где крон нужно подпирать костылём &mdash; вроде чистки ZFS по вторым воскресеньям ежемесячно:
Данный пример его автор комментирует так: ''Запускать задачу следует по воскресеньям, но не позже 7-го числа &mdash; то есть, лишь в первое воскресенье каждого месяца, в каковой ситуации кронтаб беспомощен, так что приходится костылить хак.''
<source lang="ini">24 0 8-14 * * root [ $(date +\%w) -eq 0 ] && [ -x /usr/lib/zfs-linux/scrub ] && /usr/lib/zfs-linux/scrub</source>
или проверки программного RAID-массива:
<source lang="ini">57 0 * * 0 root [ $(date +\%d) -le 7 ] && [ -x /usr/share/mdadm/checkarray ] && /usr/share/mdadm/checkarray --cron --all --idle --quiet</source>
Из чего видно, что в кроновском «моменте запуска» нельзя сочетать диапазон чисел месяца с днями недели, так что без скрипта никак. Вот и второй пример его автор в вольном переводе комментирует так:


И вот наипростейший пример таймера, который будет запускаться пусть не в первое, а в последнее воскресенье месяца, но без плясок с бубном из скриптов:
''<strong>Запускать задачу следует по воскресеньям, но не позже 7 числа &mdash; то есть, лишь в первое воскресенье каждого месяца, в каковой ситуации кронтаб беспомощен, так что приходится костылить хак.</strong>''
<source lang="ini">
 
А вот пример таймера для запуска в последнее воскресенье каждого месяца без скриптов:
{|class="mw-collapsible mw-collapsed wikitable"
!/lib/systemd/system/last-sunday.timer &nbsp;
|-
|<source lang="ini">
[Unit]
[Unit]
Description = Last Sunday of month timer
Description = Last Sunday of month timer


[Timer]
[Timer]
OnCalendar = Sun *~07/1
OnCalendar = Sun *~07/1
Persistent = true
AccuracySec = 2h
Persistent = true
Unit        = last-sunday.target
 
[Install]
WantedBy    = timers.target
</source>
|}
…и его таргета:
{|class="mw-collapsible mw-collapsed wikitable"
!/lib/systemd/system/last-sunday.target &nbsp;
|-
|<source lang="ini">
[Unit]
Description = Last Sunday timer target
StopWhenUnneeded = yes
</source>
|}
Остаётся создать сервис-юниты с соответствующими строками запуска:
{|class="mw-collapsible mw-collapsed wikitable"
!/lib/systemd/system/some-name.service &nbsp;
|-
|<source lang="ini">
[Unit]
Description = в зависимости от запускаемой службы, к примеру "ZFS Scrub" или "MDAdm Check"
 
[Service]
# Для чистки ZFS (zfs-scrub.service):
ExecStart = /usr/lib/zfs-linux/scrub
# Для проверки рейд-массива (mdadm-check.service):
ExecStart = /usr/share/mdadm/checkarray --cron --all --idle --quiet


[Install]
[Install]
WantedBy = timers.target
WantedBy = last-sunday.target
</source>
|}
Всё это может выглядеть громоздко, однако:
*таймер с таргетом создаются однократно, а для каких-то сервисов таргет не обязателен &mdash; достаточно, чтоб имена таймера и сервиса совпадали;
*юниты нужны лишь под отдельные службы;
*всегда можно посмотреть результат отработки таких служб через <code>journalctl -u <service>.service</code>.
==Таймеры рабочего времени==
Недавно нашёл [https://habr.com/ru/company/ruvds/blog/512868/ пару] [https://habr.com/ru/post/535930/ статей] на хабре, куда не приняли ту, что вы сейчас читаете. :) Там во многом подробнее, но ни одного примера интервалов подобного рода.
 
Например, для выполнения задачи каждые полчаса с утра до вечера, подходит такой:
<source lang="ini">
OnCalendar = 08..18:15,45:00
</source>
А для более длительных промежутков — например, двухчасовых — можно сделать так:
<source lang="ini">
OnCalendar = 9..17/2:05:00
</source>
</source>
Остаётся создать сервис-юнит для этого таймера с такой строкой запуска:
Ещё из этих интервалов можно исключить выходные дни, а если чуток извернуться — то и праздники. :)
<source lang="ini">ExecStart = /usr/share/mdadm/checkarray --cron --all --idle --quiet</source>
 
=Обратная связь=
*[https://t.me/gbIMoBou @gbIMoBou]
*[[Участник:Дым#Заметки|Другие статьи]]
{{Category navigation|title=Системному администратору|category=Admin|sortkey={{SUBPAGENAME}}}}
{{Category navigation|title=Системному администратору|category=Admin|sortkey={{SUBPAGENAME}}}}
[[category:systemd]]
[[category:systemd]]

Текущая версия от 08:51, 7 августа 2024

Предмет статьи

Речь о задачах, которые следует исполнять периодически, без указания сложных конструкций из даты:времени/периодичности (сиречь, о том, чем занимаются скрипты из /etc/cron.***ly/, плюс не охваченные ими ежеминутные, ежеквартальные и ежесеместровые запуски).

Варианты

В части руководств встречаются рекомендации «пилить» таймеры под каждый отдельный юнит, существуют и генераторы таймеров на основе crontab — проекты некоторых можно глянуть здесь. Но мне они показались несколько выморочными, чересчур навороченными — ведь всё необходимое уже наличествует в systemd, и следует признать: находились таки примеры с таймерами/таргетами на отдельные периоды — часы/дни/недели и т.п.

Но посредством шаблонов можно сделать проще.

Реализация

1) Создать пару шаблонов

Для периодических таймеров:

/lib/systemd/system/doit@.timer  
[Unit]
Description = %i timer

[Timer]
OnCalendar = %i
Persistent = true
Unit = doit@%i.target

[Install]
WantedBy = timers.target

Для их «целей»:

/lib/systemd/system/doit@.target  
[Unit]
Description = %i timer target
StopWhenUnneeded = yes

doit здесь — это про:

«делать еже…»  
minutely      …минутно,
hourly        …часно,
daily         …дневно,
weekly        …недельно,
monhly        …месячно,
quarterly     …квартально,
semi-annually …семестрово,
yearly        …годно.

Само собой, «doit» что в именах, что в шаблоне таймера и периодически запускаемых сервисах можно заменить на «do», «run», «cron», «launch» или что угодно иное.

2) Включить и запустить периодические таймеры

# systemctl enable --now doit@{minute,hour,dai,week,month,quarter,semi-annual,year}ly.timer

…или оба шага одним скриптом:

doit.make  
#!/bin/sh

SVC=/lib/systemd/system/doit@

target="[Unit]
Description = %i timer target
StopWhenUnneeded = yes"

timer="[Unit]
Description = %i timer

[Timer]
OnCalendar = %i
Persistent = true
Unit = doit@%i.target

[Install]
WantedBy = timers.target"

for TYPE in timer target; do
    TGT=$SVC.$TYPE
    [ -f $TGT -a ! -s $TGT ] || echo "${!TYPE}" >$TGT
done

systemctl enable --now doit@{minute,hour,dai,week,month,quarter,semi-annual,year}ly.timer

3) Создать сервисы, требующие периодического исполнения

Пример найден на просторах интернетов, нужную периодичность (можно сразу для нескольких интервалов) указывать через секцию [Install]:

/lib/systemd/system/logrotate.service  
[Unit]
Description = Rotate system logs

[Service]
Nice = 19
IOSchedulingClass = 2
IOSchedulingPriority = 7
ExecStart = /usr/sbin/logrotate /etc/logrotate.conf

[Install]
WantedBy = doit@daily.target

4) И, наконец, включить их

# systemctl enable <список таких сервисов>

…напоследок проверив, что юниты нужных сервисов активированы:

# ls /etc/systemd/system/doit@*ly.target.wants  
/etc/systemd/system/doit@daily.target.wants:
logrotate.service

/etc/systemd/system/doit@minutely.target.wants:
unbound-stats.service

/etc/systemd/system/doit@monthly.target.wants:
unbound-anchor.service

…и удостоверившись, что всё заработало:

# systemctl list-timers  
Wed 2017-12-20 12:28:00 IRKT  22s left            Wed 2017-12-20 12:27:00 IRKT  37s ago              doit@minutely.timer          doit@minutely.target
Wed 2017-12-20 13:00:00 IRKT  32min left          Wed 2017-12-20 12:00:00 IRKT  27min ago            doit@hourly.timer            doit@hourly.target
Thu 2017-12-21 00:00:00 IRKT  11h left            Wed 2017-12-20 00:00:18 IRKT  12h ago              doit@daily.timer             doit@daily.target
Thu 2017-12-21 11:12:16 IRKT  22h left            Wed 2017-12-20 11:12:16 IRKT  1h 15min ago         systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service
Mon 2017-12-25 00:00:00 IRKT  4 days left         Mon 2017-12-18 00:00:00 IRKT  2 days ago           doit@weekly.timer            doit@weekly.target
Mon 2018-01-01 00:00:00 IRKT  1 weeks 4 days left Fri 2017-12-01 00:00:00 IRKT  2 weeks 5 days ago   doit@monthly.timer           doit@monthly.target
Mon 2018-01-01 00:00:00 IRKT  1 weeks 4 days left n/a                           n/a                  doit@quarterly.timer         doit@quarterly.target
Mon 2018-01-01 00:00:00 IRKT  1 weeks 4 days left n/a                           n/a                  doit@semi-annually.timer     doit@semi-annually.target
Mon 2018-01-01 00:00:00 IRKT  1 weeks 4 days left Tue 2017-10-24 18:17:11 IRKT  1 months 26 days ago doit@yearly.timer            doit@yearly.target

9 timers listed.
Pass --all to see loaded but inactive timers, too.

Можно выключить крон:

# systemctl disable --now crond

Не важно, что реально работает только часть таймеров, а остальные — пустышки: лишние можно отключить либо рассматривать их как заготовки вроде пустых каталогов из /etc/cron.<…>ly/.

Другие примеры

Само собой, для сложных конструкций взамен кронтабов из /etc/cron.d/ придётся создавать отдельные таймеры, которые, однако, справятся и там, где крон нужно подпирать костылём — вроде чистки ZFS по вторым воскресеньям ежемесячно:

24 0 8-14 * * root [ $(date +\%w) -eq 0 ] && [ -x /usr/lib/zfs-linux/scrub ] && /usr/lib/zfs-linux/scrub

или проверки программного RAID-массива:

57 0 * * 0 root [ $(date +\%d) -le 7 ] && [ -x /usr/share/mdadm/checkarray ] && /usr/share/mdadm/checkarray --cron --all --idle --quiet

Из чего видно, что в кроновском «моменте запуска» нельзя сочетать диапазон чисел месяца с днями недели, так что без скрипта никак. Вот и второй пример его автор в вольном переводе комментирует так:

Запускать задачу следует по воскресеньям, но не позже 7 числа — то есть, лишь в первое воскресенье каждого месяца, в каковой ситуации кронтаб беспомощен, так что приходится костылить хак.

А вот пример таймера для запуска в последнее воскресенье каждого месяца без скриптов:

/lib/systemd/system/last-sunday.timer  
[Unit]
Description = Last Sunday of month timer

[Timer]
OnCalendar  = Sun *~07/1
AccuracySec = 2h
Persistent  = true
Unit        = last-sunday.target

[Install]
WantedBy    = timers.target

…и его таргета:

/lib/systemd/system/last-sunday.target  
[Unit]
Description = Last Sunday timer target
StopWhenUnneeded = yes

Остаётся создать сервис-юниты с соответствующими строками запуска:

/lib/systemd/system/some-name.service  
[Unit]
Description = в зависимости от запускаемой службы, к примеру "ZFS Scrub" или "MDAdm Check"

[Service]
# Для чистки ZFS (zfs-scrub.service):
ExecStart = /usr/lib/zfs-linux/scrub
# Для проверки рейд-массива (mdadm-check.service):
ExecStart = /usr/share/mdadm/checkarray --cron --all --idle --quiet

[Install]
WantedBy = last-sunday.target

Всё это может выглядеть громоздко, однако:

  • таймер с таргетом создаются однократно, а для каких-то сервисов таргет не обязателен — достаточно, чтоб имена таймера и сервиса совпадали;
  • юниты нужны лишь под отдельные службы;
  • всегда можно посмотреть результат отработки таких служб через journalctl -u <service>.service.

Таймеры рабочего времени

Недавно нашёл пару статей на хабре, куда не приняли ту, что вы сейчас читаете. :) Там во многом подробнее, но ни одного примера интервалов подобного рода.

Например, для выполнения задачи каждые полчаса с утра до вечера, подходит такой:

OnCalendar = 08..18:15,45:00

А для более длительных промежутков — например, двухчасовых — можно сделать так:

OnCalendar = 9..17/2:05:00

Ещё из этих интервалов можно исключить выходные дни, а если чуток извернуться — то и праздники. :)

Обратная связь