LINUX.ORG.RU
решено ФорумAdmin

Динамическая генерация юнитов systemd

 , ,


0

1

Захотел реализовать с помощью systemd автоматический запуск скриптов в директории с определённой иерархией при их изменении или добавлении.

Структура директории такая:

test
├── test1
│   └── script.sh
├── test2
│   └── script.sh
└── test3
    └── script.sh

Есть шаблоны test@.service и test@.path. Юниты, созданные по первому шаблону, запускают скрипты по условию ConditionPathExists=|/srv/test/%i/script.sh. Path-юниты следят за состоянием тех же скриптов через PathChanged и при изменении дёргают соответствующие сервис-юниты.

Юниты создаются по шаблонам генератором, который читает указанную директорию, присваивает им имена типа test@test01.service и test@test01.path и делает симлинки в test.service.wants.

Сам test.service просто выполняет /bin/true и позволяет запускать все сгенерированные юниты скопом (хотя, наверное, стоило бы просто использовать .target).

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

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

If you need to generate dynamic configuration for other services, do so in normal services you order before the service in question.

На этом месте у меня кончается фантазия, а никаких подробностей маны systemd не сообщают.

По идее нужно бы отслеживать директорию на предмет появления новых файлов, но среди проверок пути для path-юнитов, кажется, нет подходящей. Возможно, что здесь требуются и не они. С другой стороны, хотелось бы избежать перезапуска всех остальных скриптов при добавлении нового, но сам этот скрипт запустить.

Буду благодарен, если кто-нибудь натолкнёт на мысль куда копать дальше. Наверняка ведь упускаю из вида что-то существенное.

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

Напиши .path-юнит, который запускает systemctl daemon-reload.

По идее нужно бы отслеживать директорию на предмет появления новых файлов, но среди проверок пути для path-юнитов, кажется, нет подходящей.

PathChanged= на каталоге? По смыслу. Создание нового файла подразумевает модификацию каталога (и mtime обновляется, да).

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

If you need to generate dynamic configuration for other services, do so in normal services you order before the service in question.

Нет, тут о другом. Тут написано, что генераторы не должны генерировать ничего, кроме юнитов (т. е. не конфиги). Под «dynamic configuration» понимаются какие-то сторонние конфиги для третьих демонов.

intelfx ★★★★★ ()
Последнее исправление: intelfx (всего исправлений: 1)
Ответ на: комментарий от intelfx

Напиши .path-юнит, который запускает systemctl daemon-reload.

Я думаю, там релодить демон не нужно. Хватит команды systemctl enable --now test@testnew.service test@testnew.path

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

PS Народ очень не рекомендует релодить демона в автомате.

Oleg_Iu ()
Последнее исправление: Oleg_Iu (всего исправлений: 2)
Ответ на: комментарий от Oleg_Iu

Я думаю, там релодить демон не нужно. Хватит команды systemctl enable –now test@testnew.service test@testnew.path

Я продолжаю идею ТСа с генератором. Ручной разбор существующей конфигурации и её точечная правка звучит как переусложнение и огромный простор для налажать.

PS Народ очень не рекомендует релодить демона в автомате.

Почему же это?

intelfx ★★★★★ ()
Последнее исправление: intelfx (всего исправлений: 1)
Ответ на: комментарий от intelfx

Разбор конфигурации придется делать, если директории могут не только появляться, но и исчезать, если я правильно понял идею тс. Автоматизировать этот разбор не сложно. К тому же не сложно предусмотреть ситуацию, когда юниты будут сгенерированы ошибочно.

Почему же это?

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

Oleg_Iu ()
Ответ на: комментарий от intelfx

Создание нового файла подразумевает модификацию каталога

То чувство, когда не сложил два и два. Спасибо.

После того как я добавил path-юнит, отслеживающий директорию, и сервис, релодящий демона, по первому впечатлению всё заработало: симлинки стали добавляться, добавляемые скрипты отрабатывали.

По этому поводу попробовал переписать конфигурацию с target-юнитом, но тут уткнулся в другую проблему. Те path-юниты, которые уже существуют на момент запуска таргета, включаются и остаются активными. А те, которые добавляются потом, оказываются мертворождёнными (has successfully entered the 'dead' state) и запускаются только при рестарте таргета вместе с остальными.

Попробовал вернуть ту конфигурацию, которая у меня была до упражнений с target-юнитом, но проблема осталась. Надо, наверное, привыкать использовать контроль версий в любых ситуациях.

ivanov17 ()
Ответ на: комментарий от ivanov17

На всякий случай приведу конфигурацию

test@.service:

[Unit]
Description=run %i script
PartOf=test-master.service
ConditionPathExists=|/srv/test/%i/script.sh

[Service]
Type=oneshot
WorkingDirectory=/srv/test/%i
ExecStart=/usr/bin/bash script.sh

test@.path:

[Unit]
Description=detect changes in %i script
PartOf=test-master.service

[Path]
PathChanged=/srv/test/%i/script.sh
Unit=test@%i.service

test-master.service:

[Unit]
Description=test-master service
Wants=test-reload.path

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/true
ExecReload=/bin/true

[Install]
WantedBy=default.target

test-reload.path:

[Unit]
Description=test-reload path unit

[Path]
PathChanged=/srv/test
Unit=test-reload.service

[Install]
WantedBy=default.target

test-reload.service:

[Unit]
Description=test-reload service

[Service]
Type=oneshot
ExecStart=/usr/bin/systemctl --user daemon-reload

[Install]
WantedBy=default.target

В случае, когда я использую таргет-юнит, в шаблонах вместо PartOf=test-master.service я указываю PartOf=test.target, ну и сам test.target содержит только Wants=test-reload.path в секции [Unit].

ivanov17 ()

Эту задачу я так и не решил, точнее решил другим образом.

В процессе мне показалось, что я изобретаю очередной велосипед, и лучше бы поискать готовое решение, подходящее для моей задачи. В конце концов остановился на Laminar CI, который в целом как раз и занимается тем, что запускает баш-скрипты, которые положили в специальную директорию :3

Кроме того, он позволяет запускать их не только все скопом, но и по очереди, или не более указанного количества одновременно. Также можно заставить выполняться отдельные скрипты до и после каждого задания, передавать различные переменные разным группам скриптов и прочая, прочая. До кучи он ещё и собирает статистику в sqlite-базу и рисует html-страничку.

А для того, чтобы смотреть какой из скриптов у меня обновился и сообщать какое задание теперь нужно запускать, я, почесав затылок, написал и заюзал гит-хук :3

Так что если вдруг кто-то зайдёт сюда из гугла с целью навелосипедить себе что-нибудь такое с systemd, рекомендую сначала попробовать Laminar CI.

С systemd, емнип, я упёрся в то, что в любом случае нужно знать заранее имена всех файлов, чтобы создавать отслеживающие их path-юниты с помощью target-юнита. А знать их заранее невозможно.

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

Так что помечаю вопрос как решённый.

ivanov17 ()