systemd это PID 1 почти во всех современных дистрибутивах: Ubuntu, Debian, RHEL и их клонах. Он запускает сервисы, следит за ними, перезапускает упавшие, собирает логи и заменяет cron таймерами.
Юниты
Всё, чем управляет systemd, это юниты. Тип виден по расширению:
.service: процесс или демон..socket: сокет, при обращении к которому стартует сервис..timer: расписание, запускает связанный.service..target: группа юнитов, точка синхронизации (аналог runlevel):multi-user.target,graphical.target..mount,.path,.sliceи другие.
Где лежат файлы: /usr/lib/systemd/system (или /lib/systemd/system) из пакетов, /etc/systemd/system для своих и для переопределений. Файлы в /etc важнее.
Свой сервис
# /etc/systemd/system/api.service
[Unit]
Description=Orders API
After=network-online.target postgresql.service
Wants=network-online.target
[Service]
Type=simple
User=api
WorkingDirectory=/srv/api
EnvironmentFile=/etc/api/env
ExecStart=/usr/bin/node /srv/api/server.js
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload # перечитать файлы юнитов
sudo systemctl enable --now api # автозапуск + старт сейчас
systemctl status api # состояние и последние строки лога
sudo systemctl restart api
sudo systemctl reload nginx # перечитать конфиг без остановки (если поддерживается)
systemctl list-units --failed
enable создаёт симлинк в multi-user.target.wants/, то есть «запускать при загрузке». start запускает прямо сейчас. Это разные действия: часто сервис запущен, но не включён и не поднимется после ребута.
Зависимости и порядок
Wants=/Requires=говорят, что запустить вместе.Requiresжёстче: при остановке или провале зависимости юнит тоже остановится.After=/Before=говорят, в каком порядке. БезAfterюниты изWantsстартуют параллельно.
Классическая ошибка: написать Requires=postgresql.service без After= и удивляться, что приложение стартует раньше базы.
Типы сервиса
simple(по умолчанию): главный процесс это то, что вExecStart, считается запущенным сразу.exec: то же, но запущенным считается после успешного exec.forking: классический демон, который форкается и выходит; нуженPIDFile.oneshot: команда, которая выполняется и завершается (миграции, настройка).notify: процесс сам сообщает о готовности черезsd_notify.
Демон, который сам уходит в фон, под Type=simple systemd посчитает упавшим. Лучше запускать приложение на переднем плане.
Рестарты и лимиты
Restart=on-failure или always, RestartSec= пауза. Если сервис падает слишком часто (по умолчанию больше 5 раз за 10 секунд, StartLimitBurst и StartLimitIntervalSec), systemd перестаёт пытаться: статус start-limit-hit. Сброс: systemctl reset-failed api.
Ресурсы и безопасность прямо в юните: MemoryMax=, CPUQuota=, LimitNOFILE=, NoNewPrivileges=yes, ProtectSystem=strict, PrivateTmp=yes. Каждый сервис живёт в своей cgroup, поэтому systemd видит и останавливает все его процессы.
Переопределение без правки пакета
sudo systemctl edit nginx
Создаёт /etc/systemd/system/nginx.service.d/override.conf. Файл пакета не трогаем: обновление его не затрёт. Чтобы заменить, а не дополнить ExecStart, сначала пишут пустой ExecStart=.
Таймеры вместо cron
# backup.timer
[Timer]
OnCalendar=*-*-* 03:30:00
Persistent=true
[Install]
WantedBy=timers.target
Persistent=true запустит пропущенный запуск после включения машины. Плюсы перед cron: логи в журнале, зависимости, лимиты ресурсов, systemctl list-timers показывает следующий запуск.
journalctl
journalctl -u api # логи сервиса
journalctl -u api -f # следить
journalctl -u api --since "1 hour ago"
journalctl -u api -b # с последней загрузки
journalctl -p err -b # только ошибки и хуже
journalctl -k # сообщения ядра (как dmesg)
journalctl --disk-usage
sudo journalctl --vacuum-size=500M
Всё, что сервис пишет в stdout и stderr, попадает в журнал с меткой юнита, поэтому писать логи в файлы приложению под systemd не обязательно. Журнал хранится постоянно, если существует /var/log/journal или стоит Storage=persistent в journald.conf. Иначе он в /run и пропадает после перезагрузки.
logrotate
Для файловых логов (/var/log/nginx/*.log) есть logrotate: ротация по размеру или времени, сжатие, хранение N копий. После ротации процесс должен открыть новый файл: postrotate с сигналом (nginx: USR1) или copytruncate, который копирует и обнуляет файл, но может потерять строки.
Частые ошибки
- Забыли
daemon-reloadпосле правки юнита. startбезenable: после ребута сервис не поднялся.RequiresбезAfter.- Правка файла в
/lib/systemd/systemвместоsystemctl edit. - Сервис форкается в фон под
Type=simple.
Что спросят на собесе
- Чем
enableотличается отstart. - Как написать юнит для своего приложения с автоперезапуском.
- Разница
Wants,RequiresиAfter. - Как посмотреть логи сервиса за последний час.
- Почему таймеры systemd удобнее cron.