Systemd-юниты, которые я копирую из проекта в проект
Каждый раз, поднимая новый сервис, я начинаю с одного и того же юнита. Он не идеален, но закрывает базовые ситуации: падение процесса, утечка памяти, слишком долгие попытки подняться, отсутствие записей в журнале.
Шаблон
[Unit]
Description=My service
Documentation=file:///opt/myservice/README.md
After=network-online.target postgresql.service
Wants=network-online.target
[Service]
Type=notify
User=svc
Group=svc
WorkingDirectory=/opt/myservice
EnvironmentFile=-/etc/myservice/env
ExecStart=/opt/myservice/bin/service
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=3s
StartLimitIntervalSec=60
StartLimitBurst=5
TimeoutStartSec=30
TimeoutStopSec=20
# --- лимиты ---
MemoryMax=1G
MemoryHigh=768M
CPUQuota=150%
TasksMax=512
LimitNOFILE=8192
# --- изоляция ---
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/var/lib/myservice /var/log/myservice
ProtectKernelTunables=yes
ProtectControlGroups=yes
RestrictSUIDSGID=yes
# --- журнал ---
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myservice
[Install]
WantedBy=multi-user.target
Что здесь важно
StartLimitBurst — без него сервис в цикле падения
бесконечно дёргает процессор и засоряет журнал. Пять попыток за минуту,
дальше systemd останавливается и оставляет юнит в failed — это заметно.
MemoryHigh и MemoryMax — мягкий и жёсткий
потолок. Soft limit заставляет ядро активнее вытеснять страницы процесса,
hard limit убивает. Утечка становится видна сразу, а не через неделю,
когда свободной памяти на хосте не осталось.
ProtectSystem=strict плюс ReadWritePaths —
сервис физически не может писать куда попало. Если его взломают, ущерб
ограничен двумя каталогами.
Type=notify требует от приложения сообщить о готовности.
Если приложение этого не умеет, ставьте simple, но тогда
After= не гарантирует, что база уже принимает соединения.
Проверка
systemd-analyze verify /etc/systemd/system/myservice.service
systemctl daemon-reload && systemctl restart myservice
systemctl status myservice --no-pager -l
journalctl -u myservice -f
Отдельно стоит проверить, что сервис действительно поднимется после перезагрузки
хоста, а не только после ручного restart:
systemctl is-enabled myservice должен отвечать enabled.