Почему мой системный таймер срабатывает только один раз, когда объект является целью?

У меня есть пара служб (генераторов статических сайтов), которые я хочу регулярно запускать с помощью одного и того же таймера systemd. Я нашел этот вопрос/ответ , который описывает именно то, что я хочу сделать, и описывает настройку, при которой.targetфайл, которыйWants=несколько служб запускается соответствующим таймером. Звучит здорово, но я обнаружил, что когда я на самом деле настраиваю это, оно срабатывает только один раз , а затем отключается!

Я подготовил минимальный рабочий пример (он не запускает несколько служб, но демонстрирует ту же проблему):

:

      [Unit]
Description=A test timer

[Timer]
OnCalendar=*-*-* *:*:30
Unit=test-timer.target

[Install]
WantedBy=timers.target

:

      [Unit]
Description=Target unit
Wants=test-timer.service
After=test-timer.service

[Install]
WantedBy=timers.target

test-timer.service:

      [Unit]
Description=Run test

[Service]
ExecStart=/usr/bin/bash -c "date --rfc-3339='seconds' >> /tmp/test-timer-output"

[Install]
Also=test-timer.target

Включите таймер:

      $ sudo cp test-timer.* /etc/systemd/system/
$ sudo systemctl enable --now test-timer.timer
Created symlink /etc/systemd/system/timers.target.wants/test-timer.timer → /etc/systemd/system/test-timer.timer.

Затем, когда я смотрю на выводsystemctl list-timers --all, перед первым запуском я получаю (игнорируя другие таймеры):

      NEXT                        LEFT       LAST     PASSED       UNIT                ACTIVATES
Fri 2021-10-08 10:38:30 EDT 21s left   n/a      n/a          test-timer.timer    test-timer.target

После первого запуска,NEXTиLEFTбыли заменены наn/a:

      NEXT     LEFT    LAST                        PASSED        UNIT                  ACTIVATES
n/a      n/a     Fri 2021-10-08 10:38:32 EDT 1min 5s ago   test-timer.timer      test-timer.target

Я также попробовал добавитьPersistent=trueи явно разрешаяtest-timer.target, но ни один из них не работает. Каждый раз, когда я делаюsystemctl restart test-timer.timer, он перезапускается, но запускает только один запуск, а затем никогда не повторяется.

Если я удалю слой косвенности, изменивUnit=линияtest-timer.timerкUnit=test-timer.service, служба с радостью запускается каждую минуту, как и ожидалось.

Я пропустил какой-то этап настройки или установки?

2 ответа

Получив помощь в Твиттере, мне удалось решить эту проблему. Проблема в том, что таймер systemd активирует только неактивные службы, а поведение цели по умолчанию активировать и оставаться активным, если что-то не заставит ее отключиться (это не привязано к сроку службы модулей). Чтобы заставить целевое устройство стать неактивным, если какая- либо из активируемых им служб становится неактивной, используйтеBindsTo=на местеWants=в моем примере выше. Итак, для этого минимального примера:

:

      [Unit]
Description=Target unit
BindsTo=test-timer.service
After=test-timer.service

[Install]
WantedBy=timers.target

:

      [Unit]
Description=Run test

[Service]
ExecStart=/usr/bin/bash -c "date --rfc-3339='seconds' >> /tmp/test-timer-output"

[Install]
Also=test-timer.target

Вы сможете увидеть это, как толькоtest-timer.serviceзакончил бег,test-timer.targetтакже станетinactive(и таким образом таймер сможет активировать его снова):

      $ sudo systemctl list-units --all test-timer.target test-timer.service
  UNIT               LOAD   ACTIVE   SUB  DESCRIPTION
  test-timer.service loaded inactive dead Run test   
  test-timer.target  loaded inactive dead Target unit

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

      $ sudo systemctl list-units --all test-timer.target test-timer.service
  UNIT               LOAD   ACTIVE   SUB    DESCRIPTION
  test-timer.service loaded inactive dead   Run test   
  test-timer.target  loaded active   active Target unit

Использование имеет некоторые недостатки:

  • Незначительным недостатком является то, что вы игнорируете назначение целевого юнита: целевые юниты служат хорошо известными точками синхронизации и, следовательно, не должны содержать никакой логики сами по себе (т. е. целевые юниты не должны статически зависеть от каких-либо других юнитов, кроме других целевых юнитов). единицы измерения). Статически добавляя зависимости к целевому блоку (это очень сильная зависимость), вы перемещаете часть логики сервисного модуля в указанный целевой модуль, что делает невозможным понять, что делает сервисный модуль, просто взглянув на сервисный модуль. Вот почему сервисные единицы должны «зарегистрироваться» как зависимые целевые единицы, используя методраздел. Однако из-за этой инверсии логики вы больше не сможете ее использовать - потому что ее инверсия,, не может быть задано напрямую.

  • В любом случае, более серьезным недостатком является необходимость принудительного параллелизма. Несмотря на то, что вы упоминаете только один сервисный модуль, я предполагаю, что вы на самом деле пытаетесь запустить несколько сервисных модулей с помощью одного таймера - в противном случае целевой блок не будет иметь никакой пользы. Если вы попытаетесь привязать несколько сервисных модулей к целевому блоку, для запуска целевого модуля в равной степени необходимо, чтобы все сервисные модули работали. Это означает, что все сервисные модули должны запускаться одновременно (т. е. принудительный параллелизм), а также означает, что если какой-либо из указанных сервисных блоков остановится, ваш целевой блок тоже остановится. Это может привести к неожиданному поведению при повторном запуске целевого объекта (вручную или по таймеру), в то время как некоторые сервисные модули все еще работают.

Чтобы избежать этих недостатков, я рекомендую следующее:

  1. Создайте блок таймера, который запускается. Не забудьте включить таймер:systemctl enable test-timer.timer

    :

            [Unit]
    Description=A test timer
    
    [Timer]
    OnCalendar=*-*-* *:*:30
    Unit=test-timer.target
    
    [Install]
    WantedBy=timers.target
    
  2. Создайте указанный . Вам не нужно будет указывать здесь необходимые сервисные единицы, сервисные единицы скорее «зарегистрируются» как зависимости, используя. Поскольку мы не хотим, чтобы целевой модуль работал бесконечно, мы добавляем . Затем Systemd остановит целевой модуль, как только он выполнит свою задачу, т. е. когда Systemd поставит сервисные модули в очередь на запуск.

    :

            [Unit]
    Description=Target unit
    StopWhenUnneeded=true
    
  3. Создайте свои сервисные подразделения так, как вы это делаете в любом случае. Можете добавить,, , , и все остальные (например,) директивы, как обычно. Также добавьте, позволяя сервисному блоку «зарегистрироваться» как требование целевого блока при включении. После включения сервисного модуля Systemd запустит сервисный модуль, как только запустится целевой модуль, т. е. когда сработает таймер.

    Однако из-за , Systemd остановит целевой модуль, как только сервисный модуль будет поставлен в очередь на запуск (т. е. немедленно). Это никому не помешает (ни вручную, ни по таймеру) запустить целевой объект заново. Поскольку сервисные модули могут по-прежнему работать, вы можете столкнуться с неожиданным поведением, как и в случае с . Таким образом, мы также добавляем и. Эти директивы заставляют Systemd гарантировать, что целевой модуль работает во время работы сервисного модуля. Однако это не остановит целевой отряд; этоэто работа. Вместе они эффективно заставляют Systemd поддерживать работу целевого модуля до тех пор, пока все сервисные модули сstop (напротив, целевой блок останавливается, как только останавливается какой-либо сервисный блок).

    В общем, я рекомендую использовать сервисы вместо настроек по умолчанию в контексте сервисных единиц, запускаемых таймерами. Это связано с тем, что Systemd будет считать сервисный модуль запущенным немедленно и мертвым после завершения команды. В Systemd скорее будет считаться, что сервисный модуль «запускается» во время выполнения команды, и сразу переключается на мертвый после ее завершения. Это основное различие для параллелизма и/директивы: Скоманда сервисного модуля не будет ждать команды другого сервисного модуля, который был объявлен для запусказаканчивать. СSystemd ждет.

    • Хорошо, теперь давайте предположим, что мы хотим запустить два сервисных модуля, и . Они могут работать параллельно, но это не обязательно. Поэтому Systemd должен запустить блок таймера, затем запустить целевой блок, а затем оба сервисных блока параллельно. Целевой блок не должен останавливаться до того, как остановятся оба сервисных блока, как и блок таймера. Для этого создайте следующие два сервисных блока. Не забудьте включить сервисные модули ().

      :

       

      :

                [Unit]
      Description=Run test 2
      After=test-timer.target
      Upholds=test-timer.target
      
      [Service]
      Type=oneshot
      ExecStart=/usr/bin/bash -c "sleep 3 ; date --rfc-3339='seconds' >> /tmp/test-timer-output-2"
      
      [Install]
      WantedBy=test-timer.target
      
    • Если вы предпочитаете начать не раньше, чем закончите, просто добавьтек . Затем Systemd запускает модуль таймера, затем запускает целевой модуль, а затем только первый сервисный модуль. Когда первый сервисный модуль завершится, Systemd запустит второй сервисный модуль.Systemd должен остановить целевой модуль, как только завершатся все сервисные модули (здесь: только второй сервисный модуль), что также приведет к остановке таймерного модуля. Это было бы невозможно с. Опять же, не забудьте включить сервисные модули.

      :

                [Unit]
      Description=Run test 1
      After=test-timer.target
      Upholds=test-timer.target
      
      [Service]
      Type=oneshot
      ExecStart=/usr/bin/bash -c "sleep 3 ; date --rfc-3339='seconds' >> /tmp/test-timer-output-1"
      
      [Install]
      WantedBy=test-timer.target
      

      :

                [Unit]
      Description=Run test 2
      After=test-timer.target test-timer-1.service
      Upholds=test-timer.target
      
      [Service]
      Type=oneshot
      ExecStart=/usr/bin/bash -c "sleep 3 ; date --rfc-3339='seconds' >> /tmp/test-timer-output-2"
      
      [Install]
      WantedBy=test-timer.target
      

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

Другие вопросы по тегам