Перейти к содержимому

Создание собственной службы Systemd

Приложение нужно запускать при старте сервера, перезапускать при падении и вести логи. Shell-скрипты в /etc/rc.local не дают ни перезапуска, ни зависимостей. Systemd решает всё это одной декларацией.

Зачем писать свой unit-файл

Вместо скриптов и супервизоров вроде supervisord systemd даёт единый интерфейс управления службами. Он знает о сокетах, зависимостях, ресурсных лимитах и имеет встроенный journald для логов.

Структура unit-файла

Unit-файл — ini-подобный файл в /etc/systemd/system/ или /run/systemd/system/ для runtime. Имя формата имя.service.

[Unit]
Description=My Application
After=network.target

[Service]
Type=simple
ExecStart=/opt/myapp/bin/start.sh
Restart=on-failure
User=myapp

[Install]
WantedBy=multi-user.target

Обязательные секции и директивы

СекцияКлючНазначение
[Unit]DescriptionЧеловеческое описание
[Unit]AfterПорядок запуска относительно других юнитов
[Service]TypeСпособ демонизации
[Service]ExecStartКоманда запуска
[Install]WantedByЦель, при которой включается

Type

  • simple — процесс сам остаётся в foreground, systemd ждёт его
  • forking — процесс форкается, родитель завершается (классический демон)
  • oneshot — однократный запуск, не держит процесс
  • exec — аналог simple, но с ожиданием завершения ExecStartPre

Для современных приложений почти всегда simple.

Пример: простой сервис приложения

[Unit]
Description=Backend API Service
Documentation=https://internal.example.com/docs
After=network-online.target postgresql.service
Wants=network-online.target

[Service]
Type=simple
User=appuser
Group=appgroup
WorkingDirectory=/opt/api
ExecStart=/opt/api/start.sh
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5s
TimeoutStartSec=30s
TimeoutStopSec=60s
Environment=NODE_ENV=production
EnvironmentFile=/etc/default/api
StandardOutput=journal
StandardError=journal
SyslogIdentifier=api-backend

# Hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/api /var/log/api
ProtectKernelTunables=true
ProtectControlGroups=true

[Install]
WantedBy=multi-user.target
Примечание

Флаг --user создаёт пользовательский сервис, который живёт вместе с сессией пользователя, а не системой.

Python: venv и gunicorn

В ExecStart указывайте интерпретатор из venv, а не системный python — зависимости сервиса не смешаются с пакетами хоста:

[Service]
Type=simple
User=appuser
Group=appuser
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/venv/bin/python -m myapp
EnvironmentFile=/etc/myapp/env
Restart=on-failure
RestartSec=5

Для WSGI-приложения:

ExecStart=/opt/myapp/venv/bin/gunicorn --workers 3 --bind 127.0.0.1:8000 myapp.wsgi:application

Секреты держите в EnvironmentFile (KEY=VALUE), права 600, владелец root:appuser. Если юнит не стартует, journalctl -u myapp обычно показывает traceback, отсутствующий WorkingDirectory или пропавший env-файл.

Полезные директивы для продакшена

Перезапуск

Restart=on-failure          # при ненулевом коде завершения
Restart=on-abnormal         # при signal или timeout
Restart=always              # всегда, включая clean stop
RestartSec=5

Зависимости

After=network.target         # после поднятия сети
After=postgresql.service     # после конкретной службы
Wants=network-online.target  # попытка запустить, не критично
Requires=postgresql.service  # жёсткая зависимость

Ограничения

LimitNOFILE=65536
LimitNPROC=4096
MemoryMax=512M
CPUQuota=50%

Логирование

StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp

Читать логи:

journalctl -u myapp -f
journalctl -u myapp --since "1 hour ago"
journalctl -u myapp -p err

Активация и управление

# Перечитать unit-файлы
sudo systemctl daemon-reload

# Запустить
sudo systemctl start myapp

# Проверить статус
sudo systemctl status myapp

# Включить автозапуск
sudo systemctl enable myapp

# Перезагрузить конфигурацию без рестарта
sudo systemctl reload myapp

# Перезапустить
sudo systemctl restart myapp

# Остановить
sudo systemctl stop myapp

# Убрать из автозапуска
sudo systemctl disable myapp

Проверка синтаксиса без применения:

systemd-analyze verify /etc/systemd/system/myapp.service

Типичные ошибки

Missing ExecStart. Unit падает при запуске, в логах Unit entered failed state.

Не указан Type. По умолчанию может быть simple, но если процесс демонизируется сам, нужен forking.

Неправильный путь к скрипту. Проверяйте, что файл существует и имеет бит исполнения.systemd не найдёт ошибку в пути — просто не запустит.

Runtime unit без перезагрузки. Изменили файл, забыли daemon-reload. Systemd работает со старой версией.

WorkingDirectory не существует. Если указана директория, она должна быть.

Restart=always без Exit. Если процесс корректно завершается, always поднимет его снова. Это не всегда ожидаемо при systemctl stop.

Предупреждение

Не редактируйте unit-файлы из пакетов напрямую в /usr/lib/systemd/system/. Они перезаписываются при обновлении. Пользуйтесь drop-in файлами в /etc/systemd/system/<name>.service.d/.

Drop-in пример:

mkdir -p /etc/systemd/system/myapp.service.d
# /etc/systemd/system/myapp.service.d/override.conf
[Service]
Environment=DEBUG=1
RestartSec=10s
sudo systemctl daemon-reload
sudo systemctl restart myapp