Как структурировать systemd unit-файл

Как структурировать systemd unit-файл


1.Начните с понятного блока [Unit]

[Unit]
Description=My Custom Web App
After=network.target
Wants=network-online.target
  • Description - это то, что вы увидите при systemctl status, так что пусть будет понятно, что за сервис.
  • After задаёт порядок запуска: этот сервис стартует после того, как поднимется сеть.
  • Wants - мягкая зависимость. Полезна, например, если у вас есть связанные сервисы вроде логгирования или баз данных.
Совет: избегайте Requires=, если не уверены, что падение зависимого сервиса действительно должно валить и ваш.

2. Осмысленно заполняйте блок [Service]

Это, по сути, ядро вашего unit-файла. Вот минимальный, но вполне рабочий пример:

[Service]
ExecStart=/usr/local/bin/myapp
WorkingDirectory=/var/www/myapp
Restart=on-failure
RestartSec=5s
User=webuser
Group=webgroup
Environment=NODE_ENV=production

Разберёмся, что тут к чему:

  • ExecStart - команда запуска вашего сервиса. Только абсолютный путь.
  • WorkingDirectory - задаёт директорию, относительно которой будут интерпретироваться относительные пути и в которую будут писаться логи.
  • Restart и RestartSec - чтобы сервис не умирал от случайного сбоя. Без этого в проде никуда.
  • User/Group - сбрасываем привилегии (не запускайте от root, если можно избежать).
  • Environment - задавать переменные окружения тут чище и нагляднее, чем через .env и shell-обёртки.

Если приложению нужно что-то почистить при остановке:

ExecStop=/usr/local/bin/myapp-stop

А если сервис умеет принимать сигнал на перезагрузку конфигурации (например, как у Nginx):

ExecReload=/bin/kill -HUP $MAINPID

3.Выберите подходящий Type=

Type=simple

Типов вообще пять, но в реальности чаще всего используются вот эти:

  • simple - значение по умолчанию. Подходит, если ваше приложение не уходит в фон самостоятельно.
  • forking - для старых демонов, которые сами себя форкают в фоне.
  • notify - если приложение умеет общаться с systemd через sd_notify().
  • oneshot - для коротких задач, например инициализаторов или скриптов-хуков.
  • idle - редкий зверь. Запускается, когда все активные задачи systemd завершились.

Не заморачивайтесь: если ваш сервис не форкается сам, Type=simple - ваш выбор.

4.Умело используйте StandardOutput и StandardError

StandardOutput=journal
StandardError=inherit

По умолчанию вывод уходит в journald, что удобно для просмотра через journalctl. Обычно именно это и нужно.

Хотите писать логи в файл? Можно так:

StandardOutput=append:/var/log/myapp.log

Но честно? Лучше оставьте как есть и используйте журнал - если только у вас нет веской причины делать иначе.

5.Защитите систему (используйте sandbox-настройки)

Если ваш сервис торчит наружу (например, API или веб-приложение) - стоит ограничить ему доступ куда не надо:

ProtectSystem=full
ProtectHome=yes
NoNewPrivileges=true
PrivateTmp=true

Эти параметры не дают вашему приложению лазить туда, где ему делать нечего.

Хотите устроить жёсткий режим? Добавьте:

CapabilityBoundingSet=

И после этого явно укажите только те Linux-способности (capabilities), которые реально нужны вашему приложению.

6.Не забудьте про блок [Install]

[Install]
WantedBy=multi-user.target

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

  • WantedBy=multi-user.target - означает запуск при обычной загрузке системы (наиболее частый случай).
  • WantedBy=default.target - для десктопных сессий.
  • Alias=myapp.service - необязательный алиас, чтобы можно было запускать под другим, более дружелюбным именем.

Без этого блока systemctl enable просто ничего не сделает.

Пример чистого и годного к употреблению unit-файла:

[Unit]
Description=My Production Web App
After=network.target
Wants=network-online.target

[Service]
ExecStart=/usr/local/bin/myapp
WorkingDirectory=/srv/myapp
Restart=on-failure
RestartSec=5s
User=web
Group=web
Environment=NODE_ENV=production
StandardOutput=journal
StandardError=journal
ProtectSystem=full
ProtectHome=yes
PrivateTmp=true
NoNewPrivileges=true

[Install]
WantedBy=multi-user.target


Report Page