Как стать автором
Обновить

Systemd, интерактивные скрипты и таймеры

Время на прочтение 5 мин
Количество просмотров 19K


Введение


При разработке под linux возникают задачи создания интерактивных скриптов, выполняемых при включении или завершении работы системы. В system V это делалось легко, но с systemd вносит коррективы. Зато оно умеет свои таймеры.


Зачем нужны target


Часто пишут, что target служат аналогом runlevel в system V -init. В корне не согласен. Их больше и можно разделять пакеты по группам и, к примеру, запускать одной командой группу сервисов, выполнять дополнительные действия. Кроме того, у них нет иерархии, только зависимости.


Пример target при включении(обзор возможности) с запуском интерактивного скрипта


Описание самого target:


cat installer.target
[Unit]
Description=My installer
Requires=multi-user.target 
Conflicts=rescue.service rescue.target
After=multi-user.target rescue.service rescue.target 
AllowIsolate=yes
Wants=installer.service

Данный target запустится, когда будет запущен multi-user.target и вызовет installer.service. При этом таких сервисов может быть несколько.


cat installer.service
[Unit]
# описание
Description=installer interactive dialog

[Service]
# Запустить один раз, когда остальное будет запущенно
Type=idle
# Команда запуска - вызов скрипта
ExecStart=/usr/bin/installer.sh
# Интерактивное взаимодействие с пользователем через tty3
StandardInput=tty
TTYPath=/dev/tty3
TTYReset=yes
TTYVHangup=yes

[Install]
WantedBy=installer.target

И наконец, пример выполняемого скрипта:


#!/bin/bash
# Переходим в tty3
chvt 3
echo "Install, y/n ?"
read user_answer

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


Итоговый запуск выглядит так:


  1. Стартует загрузчик
  2. Загрузчик начинает запуск прошивки, передавая параметр final.target
  3. Systemd начинает запуск системы. Последовательно идёт к installer.target или work.target от basic.target через их зависимости (например,multi-user.target). Последние и приводят систему к работе в нужном режиме

Подготовка прошивки к запуску


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


Systemd запускает процессу в одном таргете параллельно. Есть зависимости, которые позволяют определить последовательность запуска скриптов.


Как это работает у меня в проекте ( https://habr.com/ru/post/477008/ https://github.com/skif-web/monitor)


  1. Система стартует
  2. Запускается сервис settings_restore.service.Он проверяет наличие файла settings.txt в разделе с данными. Если его нет, то на его место кладётся эталонный файл.Далее происходит восстановление настроек системы:
    • пароля администратора
    • hostname,
    • часового пояс
    • локаль
    • Определение, весь ли носитель используется. По умолчанию размер образа небольшой — для удобства копирования и записи на носитель. При старте проверяется — есть ли ещё неиспользуемое место. Если есть — диск переразбивается.
    • Генерация machine-id из MAC-адреса. Это важно для получения одного и того же адреса по DHCP
    • Настройки сети
    • Ограничивается размер логов
    • Подготавливается к работа внешний диск(если включена соответствующая опция и диск новый)
  3. Запускаться postgresq
  4. запускается сервис restore. Он нужен для подготовки самого zabbix и его базы данных:
    • Проверяется, есть ли уже база данных zabbix. Если нет — создается из инициализирующих дампов(идут в поставке zabbix)
    • создается список часовых поясов (нужно для их отображения в web-интерфейсе)
    • Находится текущий IP, он выводится в issue (приглашение для входа в консоли)
  5. Меняется приглашение — появляется фраза Ready to work
  6. Прошивка готова к работе

Важны файлы сервисов, именно они выставляют последовательность их запуска


[Unit]
Description=restore system settings
Before=network.service prepare.service postgresql.service systemd-networkd.service systemd-resolved.service

[Service]
Type=oneshot
ExecStart=/usr/bin/settings_restore.sh

[Install]
WantedBy=multi-user.target

Как видно, я поставил зависимости, что бы сначала отработал мой скрипт, а только потом поднималась сеть и стартовала СУБД.


И второй сервис(подготовка zabbix)


#!/bin/sh
[Unit]
Description=monitor prepare system
After=postgresql.service settings_restore.service
Before=zabbix-server.service zabbix-agent.service

[Service]
Type=oneshot
ExecStart=/usr/bin/prepare.sh

[Install]
WantedBy=multi-user.target

Здесь немного сложнее.Запуск так же в multi-user.target, но ПОСЛЕ запуска СУБД postgresql и моего setting_restore. Но ПЕРЕД запуском служб zabbix.


Сервис с таймером для logrotate


Systemd может заменить CRON. Серьезно. Причем точность не до минуты, а до секунды(а вдруг понадобится).А можно создать монотонный таймер, вызываемый по таймауту от события.
Именно монотонный таймер, считающий время от запуска машины, я и создал.
Для этого потребуется 2 файла
logrotateTimer.service — собственно описание сервиса:


[Unit]
Description=run logrotate

[Service]
ExecStart=logrotate /etc/logrotate.conf
TimeoutSec=300

Всё просто — описание команда запуска.
Второй файл logrotateTimer.timer — вот он и задает работу таймеров:


[Unit]
Description=Run logrotate

[Timer]
OnBootSec=15min
OnUnitActiveSec=15min

[Install]
WantedBy=timers.target

Что здесь есть:


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

Интерактивный скрипт при выключении и свой таргет выключения


В другой разработке мне пришлось делать более сложный вариант выключения машины — через собственный таргет, что бы выполнить множество действий. Обычно рекомендуется создать сервис oneshot с опцией RemainAfterExit, но это не дает создать интерактивный скрипт.


А дело в том, что команды, запускаемые опцией ExecOnStop выполняются вне TTY! Проверить просто — вставьте команду tty и сохраните её вывод.


Поэтому я реализовал выключение через свой таргет. На 100% правильность не претендую, но это работает!
Как это делалось(в общих чертах):
Создал таргет my_shutdown.target, который ни от кого не зависел:
my_shutdown.target


[Unit]
Description=my shutdown
AllowIsolate=yes
Wants=my_shutdown.service 

При переходе в этот таргет(через systemctl isolate my_shutdwn.target), он запускал сервис my_shutdown.service, задача которого простая — выполнить скрипт my_shutdown.sh:


[Unit]
Description=MY shutdown

[Service]
Type=oneshot
ExecStart=/usr/bin/my_shutdown.sh
StandardInput=tty
TTYPath=/dev/tty3
TTYReset=yes
TTYVHangup=yes

WantedBy=my_shutdown.target

  • Внутри этого скрипта я выполняю нужные действия. Можно в таргет добавить много скриптов, для гибкости и удобства:

my_shutdown.sh


#!/bin/bash --login
if [ -f /tmp/reboot ];then
    command="systemctl reboot"
elif [ -f /tmp/shutdown ]; then
    command="systemctl poweroff"
fi
#Вот здесь нужные команды
#Например, cp /home/user/data.txt /storage/user/
    $command

Примечание. Использование файлов /tmp/reboot и /tmp/shutdown. Нельзя вызвать target с параметрами. Можно только service.


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


Однако, самое интересное было потом. Машину же надо выключить/перезагрузить. И тут есть 2 варианта:


  • Заменить команды reboot,shutdown и прочие(они все равно являются симлинками на systemctl) на свой скрипт.Внутри скрипта — переход в my_shutdown.target. А скрипты внутри таргета потом вызывают напрямую systemctl, например, systemctl reboot
  • Более простой, но мне не нравящийся вариант. Во всех интерфейсах вызывать не shutdown/reboot/прочие, а напрямую вызывать таргет systemctl isolate my_shutdown.target

Я выбрал первый вариант. В systemd reboot(как и poweroff) являются симлинками на systemd.


ls -l /sbin/poweroff 
lrwxrwxrwx 1 root root 14 сен 30 18:23 /sbin/poweroff -> /bin/systemctl

Поэтому их можно заменить на свои скрипты:
reboot


#!/bin/sh
    touch /tmp/reboot
    sudo systemctl isolate my_shutdown.target
fi
Теги:
Хабы:
+10
Комментарии 11
Комментарии Комментарии 11

Публикации

Истории

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн