Таймеры systemd вместо crond: различия между версиями
Дым (обсуждение | вклад) |
Дым (обсуждение | вклад) |
||
(не показано 68 промежуточных версий этого же участника) | |||
Строка 1: | Строка 1: | ||
=Предмет статьи= | =Предмет статьи= | ||
Речь о задачах, которые следует исполнять периодически, без указания сложных конструкций из даты:времени/периодичности (сиречь, о том, чем занимаются скрипты из | Речь о задачах, которые следует исполнять периодически, без указания сложных конструкций из даты:времени/периодичности (сиречь, о том, чем занимаются скрипты из {{path|/etc/cron.***ly/}}, плюс не охваченные ими ежеминутные, ежеквартальные и ежесеместровые запуски). | ||
=Варианты= | =Варианты= | ||
Строка 8: | Строка 8: | ||
Но посредством шаблонов можно сделать проще. | Но посредством шаблонов можно сделать проще. | ||
=Реализация= | =Реализация= | ||
== | ===1) Создать пару шаблонов=== | ||
Для периодических таймеров: | Для периодических таймеров: | ||
{|class="mw-collapsible mw-collapsed wikitable" | |||
!/lib/systemd/system/doit@.timer | |||
|- | |||
|<source lang="ini"> | |||
[Unit] | [Unit] | ||
Description = %i timer | Description = %i timer | ||
Строка 24: | Строка 25: | ||
WantedBy = timers.target | WantedBy = timers.target | ||
</source> | </source> | ||
|} | |||
Для их «целей»: | Для их «целей»: | ||
{| class="mw-collapsible mw-collapsed wikitable" | |||
!/lib/systemd/system/doit@.target | |||
|- | |||
|<source lang="ini"> | |||
[Unit] | [Unit] | ||
Description = %i timer target | Description = %i timer target | ||
StopWhenUnneeded = yes | StopWhenUnneeded = yes | ||
</source> | </source> | ||
doit - | |} | ||
<pre> | '''doit''' здесь — это про: | ||
{| class="mw-collapsible mw-collapsed wikitable" | |||
!«делать еже…» | |||
|- | |||
|<pre> | |||
minutely …минутно, | minutely …минутно, | ||
hourly …часно, | hourly …часно, | ||
Строка 43: | Строка 50: | ||
yearly …годно. | yearly …годно. | ||
</pre> | </pre> | ||
Само собой, «doit» что в именах, что в шаблоне таймера и периодически запускаемых сервисах можно заменить на «do», «run», «cron», | |} | ||
==Включить и запустить периодические таймеры== | Само собой, «doit» что в именах, что в шаблоне таймера и периодически запускаемых сервисах можно заменить на «do», «run», «cron», «launch» или что угодно иное. | ||
===2) Включить и запустить периодические таймеры=== | |||
== | # systemctl enable --now doit@{minute,hour,dai,week,month,quarter,semi-annual,year}ly.timer | ||
===…или оба шага одним скриптом:=== | |||
<source lang=" | {| class="mw-collapsible mw-collapsed wikitable" | ||
# | !doit.make | ||
|- | |||
|<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 | |||
|- | |||
|<source lang="ini"> | |||
[Unit] | [Unit] | ||
Description = Rotate system logs | Description = Rotate system logs | ||
Строка 56: | Строка 97: | ||
[Service] | [Service] | ||
Nice = 19 | Nice = 19 | ||
IOSchedulingClass = 2 | IOSchedulingClass = 2 | ||
IOSchedulingPriority = 7 | IOSchedulingPriority = 7 | ||
Строка 64: | Строка 104: | ||
WantedBy = doit@daily.target | WantedBy = doit@daily.target | ||
</source> | </source> | ||
|} | |||
==И, наконец, включить их== | ===4) И, наконец, включить их=== | ||
# systemctl enable <список таких сервисов> | |||
# systemctl enable <список таких сервисов | |||
…напоследок проверив, что юниты нужных сервисов активированы: | …напоследок проверив, что юниты нужных сервисов активированы: | ||
{| class="mw-collapsible mw-collapsed wikitable" | |||
!<code># ls /etc/systemd/system/doit@*ly.target.wants</code> | |||
|- | |||
|<pre> | |||
/etc/systemd/system/doit@daily.target.wants: | /etc/systemd/system/doit@daily.target.wants: | ||
logrotate.service | logrotate.service | ||
Строка 79: | Строка 121: | ||
/etc/systemd/system/doit@monthly.target.wants: | /etc/systemd/system/doit@monthly.target.wants: | ||
unbound-anchor.service | unbound-anchor.service | ||
</ | </pre> | ||
|} | |||
…и удостоверившись, что всё заработало: | |||
{| class="mw-collapsible mw-collapsed wikitable" | |||
!<code># systemctl list-timers</code> | |||
|- | |||
|<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 | ||
Строка 96: | Строка 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. | ||
</ | </pre> | ||
|} | |||
Можно выключить крон: | Можно выключить крон: | ||
# systemctl disable --now crond | |||
Не важно, что реально работает только часть таймеров, а остальные — пустышки: лишние можно отключить либо рассматривать их как заготовки вроде пустых каталогов из <code>/etc/cron.<…>ly/</code>. | |||
=Другие примеры= | =Другие примеры= | ||
Само собой, для сложных конструкций взамен кронтабов из <code>/etc/cron.d/</code> придётся создавать отдельные таймеры, которые, однако, справятся и там, где крон нужно подпирать костылём — вроде чистки ZFS по вторым воскресеньям ежемесячно: | Само собой, для сложных конструкций взамен кронтабов из <code>/etc/cron.d/</code> придётся создавать отдельные таймеры, которые, однако, справятся и там, где крон нужно подпирать костылём — вроде чистки ZFS по вторым воскресеньям ежемесячно: | ||
Строка 105: | Строка 151: | ||
или проверки программного RAID-массива: | или проверки программного 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> | <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 | ''<strong>Запускать задачу следует по воскресеньям, но не позже 7 числа — то есть, лишь в первое воскресенье каждого месяца, в каковой ситуации кронтаб беспомощен, так что приходится костылить хак.</strong>'' | ||
А вот пример таймера | А вот пример таймера для запуска в последнее воскресенье каждого месяца без скриптов: | ||
<source lang="ini"> | {|class="mw-collapsible mw-collapsed wikitable" | ||
!/lib/systemd/system/last-sunday.timer | |||
|- | |||
|<source lang="ini"> | |||
[Unit] | [Unit] | ||
Description = Last Sunday of month timer | Description = Last Sunday of month timer | ||
Строка 121: | Строка 170: | ||
[Install] | [Install] | ||
WantedBy = timers.target | WantedBy = timers.target | ||
</source> | </source> | ||
|} | |||
<source lang="ini"> | …и его таргета: | ||
{|class="mw-collapsible mw-collapsed wikitable" | |||
!/lib/systemd/system/last-sunday.target | |||
|- | |||
|<source lang="ini"> | |||
[Unit] | [Unit] | ||
Description = Last Sunday timer target | Description = Last Sunday timer target | ||
StopWhenUnneeded = yes | StopWhenUnneeded = yes | ||
</source> | </source> | ||
|} | |||
Остаётся создать сервис-юниты с соответствующими строками запуска: | Остаётся создать сервис-юниты с соответствующими строками запуска: | ||
<source lang="ini"> | {|class="mw-collapsible mw-collapsed wikitable" | ||
!/lib/systemd/system/some-name.service | |||
|- | |||
|<source lang="ini"> | |||
[Unit] | [Unit] | ||
Description = | Description = в зависимости от запускаемой службы, к примеру "ZFS Scrub" или "MDAdm Check" | ||
[Service] | [Service] | ||
# Для чистки ZFS (zfs-scrub.service): | |||
# Для | |||
ExecStart = /usr/lib/zfs-linux/scrub | ExecStart = /usr/lib/zfs-linux/scrub | ||
# Для | # Для проверки рейд-массива (mdadm-check.service): | ||
ExecStart = /usr/share/mdadm/checkarray --cron --all --idle --quiet | ExecStart = /usr/share/mdadm/checkarray --cron --all --idle --quiet | ||
Строка 146: | Строка 200: | ||
WantedBy = last-sunday.target | WantedBy = last-sunday.target | ||
</source> | </source> | ||
|} | |||
Всё это может выглядеть громоздко, однако: | Всё это может выглядеть громоздко, однако: | ||
*таймер с таргетом создаются однократно; | *таймер с таргетом создаются однократно, а для каких-то сервисов таргет не обязателен — достаточно, чтоб имена таймера и сервиса совпадали; | ||
*юниты нужны лишь под отдельные службы; | *юниты нужны лишь под отдельные службы; | ||
*всегда можно посмотреть результат отработки таких служб через <code>journalctl -u <service>.service</code>. | *всегда можно посмотреть результат отработки таких служб через <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> | |||
Ещё из этих интервалов можно исключить выходные дни, а если чуток извернуться — то и праздники. :) | |||
=Обратная связь= | |||
*[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
Ещё из этих интервалов можно исключить выходные дни, а если чуток извернуться — то и праздники. :)