Розробляємо systemd-генератор для динамічних unit-файлів: шаблони, drop-in, тестування і безпечний відкат

Розробляємо systemd-генератор для динамічних unit-файлів: шаблони, drop-in, тестування і безпечний відкат
Категорії: Скрипти та автоматизація

Динамічні unit-файли з systemd-генератором дозволяють автоматично будувати служби на льоту — залежно від конфігів, середовища або даних. Це чистіше за хаос у /etc, гнучкіше за статичні шаблони та зручно для автоматизація задач на сервер Linux. Сьогодні покажу, як створити свій генератор, використати шаблони, drop-in, протестувати все без паніки і мати безпечний відкат у разі чого. 🚀

Як працюють systemd-генератори

systemd-генератори — це невеликі програми (часто bash скрипти), які запускаються під час старту systemd або після daemon-reload. Вони читають вашу конфігурацію та генерують unit-файли у /run/systemd/generator (або .../generator.late). Важливі факти:

  • Місце розташування генератора: /usr/lib/systemd/system-generators або /etc/systemd/system-generators.
  • Вихід генератора — тільки в /run/systemd/generator (+ підкаталоги *.wants для залежностей).
  • Генератори мають бути дуже швидкими: ніяких довгих мережевих запитів, тільки читання файлів і створення unit-ів.

Покроковий How-to: динамічні служби з конфігів

Створимо генератор, який зчитує .env-файли з /etc/dynunits та для кожного з них збирає інстанс myrunner@name.service з автозапуском у multi-user.target.

1) Підготовка каталогу конфігів

sudo install -d -m 0755 /etc/dynunits
sudo tee /etc/dynunits/hello.env >/dev/null <<'EOF'
# Команда, яку треба виконати як сервіс
COMMAND=/usr/bin/bash -lc 'echo "Hello from myrunner@hello"; date; sleep 2'
EOF
sudo chmod 600 /etc/dynunits/*.env

2) Створення генератора

Розміщуємо генератор у /etc/systemd/system-generators/ (адмінський рівень). Він читає кожен *.env і генерує інстанс:

sudo install -d -m 0755 /etc/systemd/system-generators
sudo tee /etc/systemd/system-generators/dynunits-generator >/dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefail

OUT="${1:-/run/systemd/generator}"
CONFIG_DIR="/etc/dynunits"

# Kill-switch для безпечного відкату
[[ -f "$CONFIG_DIR/.disable" ]] && exit 0

mkdir -p "$OUT" "$OUT/multi-user.target.wants"
shopt -s nullglob

for f in "$CONFIG_DIR"/*.env; do
  inst="$(basename "$f" .env)"
  cat >"$OUT/myrunner@${inst}.service" <<UNIT
[Unit]
Description=Dynamic runner for ${inst}
After=network.target

[Service]
Type=simple
EnvironmentFile=$f
ExecStart=/usr/bin/bash -lc "\${COMMAND}"
Restart=on-failure

[Install]
WantedBy=multi-user.target
UNIT

  ln -sf "../myrunner@${inst}.service" \
    "$OUT/multi-user.target.wants/myrunner@${inst}.service"

done
EOF
sudo chmod 0755 /etc/systemd/system-generators/dynunits-generator

3) Перевантаження systemd та перевірка

sudo systemctl daemon-reload
systemctl list-units 'myrunner@*.service'
# Запуск інстансу
sudo systemctl start myrunner@hello.service
# Логи інстансу
journalctl -u myrunner@hello.service -b --no-pager
# Валідація синтаксису юніта
systemd-analyze verify /run/systemd/generator/myrunner@hello.service

Побачили «Hello…» і дату в журналі? Вітаю, генератор працює! 🔧

Шаблони unit-файлів та drop-in

У прикладі вище ми генеруємо повні інстанси. Так само можна згенерувати лише базовий шаблон myrunner@.service і окремі drop-in. Drop-in — це конфіг-заплатки у каталозі /etc/systemd/system/<unit>.d/*.conf або у /run/systemd/generator.late. Наприклад, щоб обмежити ресурси для всіх інстансів:

sudo install -d -m 0755 /etc/systemd/system/myrunner@.service.d
sudo tee /etc/systemd/system/myrunner@.service.d/override.conf >/dev/null <<'EOF'
[Service]
CPUAccounting=yes
MemoryMax=200M
EOF
sudo systemctl daemon-reload
sudo systemctl restart myrunner@hello.service

У ролі «джерела правди» для інстансів можна використовувати файли .env, а для політик — drop-in. Це дає чисту модель керування і добре масштабується на сервер Linux.

Альтернативні способи без генератора

  • Класичні шаблони: створіть статичний /etc/systemd/system/myrunner@.service, а параметри підсовуйте через /etc/systemd/system/myrunner@name.service.d/env.conf. Увімкнути так: systemctl enable --now myrunner@name.service.
  • Періодичні задачі: замість сервісів згенеруйте *.timer + *.service пари. Це добре лягає на сценарії «cron та systemd timers» для регулярних бекапів чи синків.

GUI-спосіб (через Cockpit)

Генератор з GUI не намалюєте, але тестувати зручно. Установіть Cockpit, увімкніть сокет і керуйте службами у веб-інтерфейсі: старт/стоп, логи, автозапуск.

# Debian/Ubuntu-подібні
sudo apt update && sudo apt install -y cockpit
sudo systemctl enable --now cockpit.socket
# Далі відкрийте https://<IP>:9090 і перейдіть у Services -> myrunner@hello.service

Тестування і безпечний відкат

Перевірки перед продом

  • systemd-analyze verify для кожного юніта у /run/systemd/generator.
  • Запуск у «пісочниці»: systemd-run --unit=probe --pty bash і ручний виклик команд з COMMAND=....
  • Тестовий вузол або VM, якщо у вас чутливий прод.

Механіки відкату

  • Kill-switch: створіть файл /etc/dynunits/.disable і зробіть systemctl daemon-reload — генератор нічого не згенерує.
  • Видаліть або перемістіть проблемний *.env, перезавантажте daemon.
  • Drop-in скасовуються командою: sudo systemctl revert myrunner@.service.
  • Якщо система не вантажиться, у GRUB додайте параметр ядра systemd.unit=rescue.target, або замаскуйте сервіс: systemd.mask=myrunner@hello.service.

FAQ

Чому мій генератор не запускається після daemon-reload?

Перевірте шлях і права: файл має бути виконуваним у /etc/systemd/system-generators/ або /usr/lib/systemd/system-generators/. Далі подивіться журнал: journalctl -b -u systemd або помилки stderr генератора у загальному журналі.

Де правильно зберігати згенеровані файли?

Тільки у /run/systemd/generator або /run/systemd/generator.late. Ніколи не пишіть у /etc/systemd/system з генератора.

Чи можна викликати мережеві утиліти у генераторі?

Ні, уникайте довгих або ненадійних дій. Генератор має бути миттєвим. Усі важкі речі — у самих сервісах.

Що робити, якщо інстанс не з’являється у списку?

Переконайтеся, що *.env існує, не порожній, має права читання root-ом, і немає .disable. Потім: systemctl daemon-reload і systemd-analyze verify.

Чи безпечно підставляти команди з .env у ExecStart?

Так, якщо ви контролюєте вміст і права доступу. Ставте chmod 600 на .env, не приймайте дані від недовірених користувачів, додавайте NoNewPrivileges=yes, PrivateTmp=yes тощо у drop-in для захисту.

Порада від Kernelka

Я завжди тримаю маленький «стоп-кран»: файл .disable для генератора і альтернативний користувацький доступ по консолі, щоб у разі збою швидко відкотитися. Також рекомендую вести git у /etc/dynunits — зміни видно, відкат в один клік. 🌟

Підсумок

  • systemd-генератор дає гнучкі динамічні unit-файли без сміття у /etc.
  • Комбінуйте шаблони і drop-in для чистої архітектури.
  • Тестуйте: systemd-analyze verify, журнали, пісочниці.
  • Тримайте kill-switch і план безпечного відкату.
  • Для періодики краще підійдуть «cron та systemd timers».

Прокоментувати

На сайті відображається лише твоє ім'я та коментар. Електронна пошта використовується виключно для зв'язку з тобою за потреби.