Мечтали ли вы стать лучшей версией себя? Моложе, умнее, злее. Вот примерно с такими мыслями я пришел к задаче с планировщиком.
Случилось страшное: cron’а стало не хватать. Нужно запускать один скрипт каждые 20 секунд. Даже если первый поток не завершился, нужно запускать второй параллельно, без терминации первого. Cron прекрасно справляется с запуском кучи одинаковых процессов, но ему не хватает гибкости временных интервалов. Его минимум - минута, а нам нужно срабатывание в 3 раза чаше.
А значит - самое время еще глубже погружаться в systemd таймеры.
Создаем простейший таймер
Повторение - мать учения. Что такое systemd таймер, простыми словами? Это запуск systemd демона по определенному расписанию.
Когда мы создавали своего собственного демона systemd, мы выяснили, что демон - это запуск команды, либо скрипта с определенными параметрами.
Итак, чтобы что-то запустить по расписанию, нужно то, что мы будем запускать.
- Пишем простенький скрипт /root/create_files.sh
#!/bin/bash
date=$(date '+%Y-%m-%d-%H:%M:%S')
touch /root/files/$date
Скрипт будет создавать пустой файл с именем метки времени. Как раз то, что нам нужно, чтобы проверить работу нашего таймера.
- Создаем демона systemd /etc/systemd/system/new_file.service
[Unit]
Description="touch files service"
[Service]
Type=oneshot
ExecStart=/root/create_files.sh
Сервис будет запускать наш скрипт create_files.sh. Тип oneshot означает, что демон завершит свою работу сразу после отработки нашего скрипта
- Наконец, создаем таймер с таким же именем, как и демон /etc/systemd/system/new_file.timer
[Unit]
Description="touch file every 20 sec"
[Timer]
OnCalendar=*:*:0/20
AccuracySec=1s
[Install]
WantedBy=timers.target
- Запускаем проверку наших systemd конфигураций:
systemd-analyze verify /etc/systemd/system/new_file*
Если вывод данной команды пустой - мы все сделали правильно.
- Запускаем наш таймер:
systemctl start new_file.timer
- Проверяем, что наш таймер начал работу:
- При этом видим, что сам сервис выключен:
- Проверяем, что файлы создаются раз в 20 секунд, как мы и указали:
Мы великолепны! Можем редактировать скрипт и расписание под свои нужды и забросить cron.
Или все-таки нет? Что может пойти не так?
Давайте снова простыми словами. Что происходит в системе при активации таймера? Таймер временно запускает демона, который в свою очередь запускает скрипт, либо команду из директивы ExecStart.
Все время, пока скрипт работает, демон остается в состоянии active. Вроде ничего такого серьезного. И тут мы можем столкнуться с двумя проблемами:
1. Вы установите слишком частое срабатывание таймера.
В прошлой статье я писал, что не стоит так делать. Что же произойдет, если мы выставим слишком частое срабатывание?
- Меняем настройки нашего таймера, чтобы он запускался каждую секунду:
- Уведомляем systemd, что нужно перечитать конфигурации демонов:
systemctl daemon-reload
- Перезапускаем наш таймер:
systemctl restart new_file.timer
- Проверяем папку с файлами:
Система создает файлы каждую секунду, но иногда зависает на 6-7 секунд. Поэтому считаем работу данного таймера некорректной.
2. Ваш скрипт не успеет выполниться до следующего шага таймера.
В документации сказано:
For each timer file, a matching unit file must exist, describing the unit to activate when the timer elapses.
Systemd демон должен успеть завершить свою работу до следующей активации его таймера.
Система не сможет запустить два одинаковых systemd демона одновременно.
- Давайте посмотрим, сколько выполняется наш скрипт:
Практически моментально, 20 секунд шага хватает с головой.
- А теперь давайте немного поправим наш скрипт:
Вроде ничего серьезного, мы просто добавили задержку в 40 секунд перед созданием файла.
- Смотрим, к чему это приведет:
А приведет это к тому, что наши файлы будут создаваться не раз в 20 секунд, а раз в 40.
- Что происходит:
- Наш таймер отрабатывает первый раз, запускает демона.
- Демон запускает скрипт, который выполняется 40 секунд, в это время сам демон активен.
- Через 20 секунд таймер отрабатывает снова, но уже не может запустить демона, т.к. тот уже активен.
- Как только скрипт завершается, система запускает отложенный таймер, который не смог запуститься на шаге 3.
- В тот же момент отрабатывает новый таймер, который снова не может запустить сервис, и так до бесконечности.
Как же решить нашу проблему?
- С первым случаем - никак.
У системы есть лимиты, ей нужно обрабатывать и другие процессы. Чтобы точнее ответить на вопрос "почему так?", нужно сильно углубиться в работу ядра linux и systemd. Сейчас мы этого делать не будем. Разберемся лучше со вторым случаем.
- Немного перепишем наш скрипт, чтобы отлавливать ошибку:
Скрипт должен создавать 2 файла: первый в момент срабатывания (имя - timestamp старта), а второй - через 40 секунд (имя - timestamp старта и timestamp конца)
- Проверяем, что файлы создаются, но неправильно:
- Создадим отдельный скрипт worker.sh, который будет запускать наш скрипт с задачей:
Амперсанд в конце позволяет запустить скрипт отвязанным от родительского терминала. После запуска скрипт worker.sh сразу же завершается и не мешает повторному запуску.
- Правим файл сервиса /etc/systemd/system/new_file.service
[Unit]
Description="touch files service"
[Service]
Type=oneshot
ExecStart=/root/worker.sh
RemainAfterExit=no
KillMode=process
- Снова уведомляем systemd, что нужно перечитать конфигурации демонов и перезапускаем таймер:
systemctl daemon-reload
systemctl restart new_file.timer
- Проверяем папку с файлами:
- Вот теперь у нас все идеально точно отрабатывает.
Да, решение выглядит немного костыльным, так как для него пришлось создавать еще один лишний скрипт. Однако, вероятность того, что вы столкнетесь с подобной проблемой крайне мала, так что данный вариант имеет свое право на жизнь.