Skip to main content

Настройка скрипта автозапуска через SystemD-юнит (серверный подход)

Предисловие

Какие существуют подходы к автозагрузке в Linux?

Есть 3 самых популярных подхода решения задачи "я хочу, чтобы скрипт запускался после перезагрузки сервера":

  1. Через файл rc.local - устаревший подход, да и правка системных конфигов, даже таких, всегда чревата;
  2. @reboot в cron - это явный костыль, хотя многие со мной не согласятся;
  3. Скрипты в духе "sleep 60 && ..." - это зло. Они не дают контроля и работают буквально "на шару". Слишком много ответственности для простого bash-скрипта.

ИМХО автора
Для серверов единственно правильный путь решения этой проблемы сейчас - systemd unit

Почему это лучше, чем альтернативные подходы?

Подход автора лучше, потому что он:

  1. Дает значительно больше контроля над сервисом: его остановка, внеплановый запуск или сбор информации о его работе становятся значительно проще для администратора - для этого будут использоваться стандартные утилиты, к которым все привыкли (systemctl и journalctl);  
  2. Разделяет ответственность: логика скрипта находится отдельно от логики, которая ответственна за его запуск;
  3. Позволяет менять логику скрипта отдельно, не меняя любой внешней логики (атомарность в подходе).   

Пример настройки скрипта для перезапуска сервисов

Пишем сервисный файл (SystemD-юнит)

Я редактирую файл юнита. Допустим, он будет называться post-reboot-tasks, тогда:

nano /etc/systemd/system/post-reboot-tasks.service

 В самом юните я делаю следующее:

[Unit]
Description=Post reboot tasks
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/opt/scripts/post-reboot.sh
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

Почему это работает?

Тут у читателя может возникнуть вопрос: А с чего ты взял, что оно сработает только при перезагрузке? Где ты здесь указываешь, что он будет выполнен именно и только после ребута системы? 

Отвечаю

В SystemD это делается не отдельной строкой в духе "AfterReboot=true", а самим фактом, что юнит включён в target, который достигается при загрузке. В этом примере вот эта строчка и говорит, "запускать при старте системы":

WantedBy=multi-user.target

Когда вы делаете:

systemctl enable post-reboot-tasks

SystemD создаёт symlink юнита в:

/etc/systemd/system/multi-user.target.wants/post-reboot-tasks.service

А multi-user.target - это стандартная "нормальная загрузка сервера без GUI" (условно, runlevel=3). Каждый раз, когда система доходит до этого Target'а  (то есть после ребута/бут-цикла), SystemD подхватит юнит и запустит его.

Здесь "после ребута" = "после каждой загрузки", т.е.  юнит будет выполняться каждый boot.

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

Здесь все просто. Я:

  1. Создаю директорию, которую ранее указывал в сервисном файле;
  2. Создаю и заполняю скрипт внутри нее (см. пример ниже);
  3. Выдаю необходимые права для запуска скрипта;
  4. Применяю изменения для SystemD, чтобы юнит появился в списке и попал в пул активных.

image.png

Команда, которой я это сделал:

mkdir -p /opt/scripts/ && \
nano /opt/scripts/post-reboot.sh && \
chmod +x /opt/scripts/post-reboot.sh && \
systemctl daemon-reexec && systemctl daemon-reload && \
systemctl enable --now post-reboot-tasks

Теперь, когда создан скрипт и юнит-файл попал в реестр SystemD, он будет запускаться при каждой перезагрузке

Пример наполнения скрипта

Перейдем к примерам скриптов. В моем случае, я бы хотел автоматический перезапуска ряда сервисов после того, как система загрузилась и получила IP-адрес. Именно это я и делаю здесь:

image.png

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