Перейти к содержанию

Устройство systemd: юниты, targets и управление сервисами

systemd — система инициализации (PID 1) практически в каждом mainstream-дистрибутиве Linux сегодня (Ubuntu, Debian, семейство RHEL/Fedora/CentOS, SUSE, Arch). Она запускает сервисы, управляет зависимостями между ними и первой попадает под подозрение — справедливо или нет — когда «сервер не поднялся после перезагрузки».

Как это устроено

systemd организует всё в юниты. Самые распространённые типы юнитов:

Тип юнита Назначение
.service Долгоживущий демон или разовая команда
.target Именованная точка синхронизации / группа юнитов (замена старых SysV runlevels)
.socket Сервисы с активацией по сокету — systemd слушает сокет и запускает сервис при первом подключении
.timer Замена cron от самого systemd
.mount / .automount Монтирование файловых систем

Юниты — обычные текстовые файлы, обычно в /etc/systemd/system/ (локальные переопределения/ собственные юниты) или /lib/systemd/system/ (юниты, установленные пакетами дистрибутива).

Targets заменяют runlevels

$ systemctl list-units --type=target --no-pager
UNIT                    LOAD   ACTIVE SUB    DESCRIPTION
basic.target            loaded active active Basic System
getty.target            loaded active active Login Prompts
graphical.target        loaded active active Graphical Interface
local-fs.target         loaded active active Local File Systems
...

graphical.target и multi-user.target — современные аналоги старых SysV runlevel 5 и 3. systemctl get-default показывает, какой из них загружается по умолчанию; systemctl set-default multi-user.target переключает сервер на загрузку без графического интерфейса.

Просмотр состояния сервиса

$ systemctl status systemd-journald --no-pager -l
● systemd-journald.service - Journal Service
     Loaded: loaded (/lib/systemd/system/systemd-journald.service; static)
     Active: active (running) since Thu 2026-10-15 06:31:47 EEST; 5 days ago
   Main PID: 3446425 (systemd-journal)
     Status: "Processing requests..."
      Tasks: 1 (limit: 19046)
     Memory: 40.8M
        CPU: 1min 19.843s

Как читать: Loaded показывает, был ли файл юнита успешно разобран и где он лежит; Active — реальное состояние во время работы (active (running), active (exited) для одноразовых юнитов, failed, inactive); Main PID — это то, за чем реально следит systemd — если этот процесс умрёт, а в юните указана директива Restart=, systemd перезапустит его автоматически.

Минимальный service-юнит

# /etc/systemd/system/myapp.service
[Unit]
Description=My Application
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/bin/myapp
Restart=on-failure
RestartSec=5
User=myapp

[Install]
WantedBy=multi-user.target
  • After=network.target — это зависимость только по порядку. Она не гарантирует, что сеть уже реально пригодна для использования (DHCP может ещё согласовываться) — для этого нужен After=network-online.target и Wants=network-online.target, что на дистрибутивах с корректной реализацией является более сильной гарантией.
  • Restart=on-failure — перезапуск только при ненулевом коде выхода или сигнале, не при чистом exit 0 (Restart=always перезапускает безусловно, даже после чистого exit 0; явная остановка через systemctl stop автоматически не перезапускается).
  • WantedBy=multi-user.target — именно на это воздействует systemctl enable: создаётся симлинк, чтобы юнит запускался автоматически при достижении multi-user.target на загрузке.

После создания или правки файла юнита systemd не видит его автоматически:

sudo systemctl daemon-reload
sudo systemctl enable --now myapp.service

daemon-reload перечитывает файлы юнитов с диска; enable --now одновременно включает автозапуск на будущее и запускает сервис немедленно.

Чтение логов через journalctl

journalctl -u myapp.service --since "10 min ago"
journalctl -u myapp.service -f          # следить за логом, как tail -f
journalctl -p err -b                    # только ошибки, текущая загрузка

Различия Ubuntu/Debian и RHEL-подобных систем

Механика systemd идентична во всех дистрибутивах — это один и тот же upstream-проект. Различия — в окружающих инструментах:

Ubuntu/Debian RHEL-подобные
Юнит файрвола ufw.service (обёртка над nftables) firewalld.service
Настройка сети netplan генерирует конфиг для systemd-networkd/NetworkManager NetworkManager по умолчанию
Target по умолчанию graphical.target на desktop-установках, multi-user.target на server то же самое

Практическое задание

  1. Создайте юнит myapp.service из примера выше, но укажите в ExecStart безопасный для тестирования /usr/bin/sleep infinity вместо реального приложения. Сначала удалите строку User=myapp (или создайте такую учётную запись командой sudo useradd -r -s /usr/sbin/nologin myapp), иначе юнит упадёт с status=217/USER.
  2. Включите и запустите его, убедитесь, что он работает, и посмотрите его Main PID.
  3. Убейте процесс напрямую командой kill -9 <PID> и понаблюдайте в другом терминале за journalctl -u myapp -f, как systemd обнаруживает сбой и перезапускает сервис (благодаря Restart=on-failure).

Как проверить, что всё сделано правильно

  • systemctl status myapp должен показать Active: active (running) с новым PID после kill -9, в течение RestartSec (5 секунд в примере).
  • journalctl -u myapp должен показать строку о завершении процесса по сигналу, за которой следует новая запись «Started».

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

  • Правка файла юнита без последующего daemon-reload. systemd продолжает использовать старую, закэшированную версию юнита, пока вы не перечитаете конфигурацию.
  • Использование Restart=always без разумного RestartSec. По умолчанию это 100 мс, поэтому падающий в цикле сервис перезапускается десять раз в секунду, нагружая CPU и забивая журнал, пока не сработает лимит частоты запусков systemd (по умолчанию 5 запусков за 10 секунд) и юнит не перейдёт в failed. Всегда задавайте RestartSec= явно и настройте StartLimitBurst=/StartLimitIntervalSec=, чтобы прекратить попытки после N сбоев за промежуток времени.
  • Предположение, что enable запускает сервис. enable только настраивает запуск сервиса при следующей загрузке — он не запускает его сейчас. Используйте enable --now или отдельный start.

⚠ Опасные команды

  • systemctl set-default <target> на удалённом сервере: если задать неверный или GUI-ориентированный target на безголовой машине, после перезагрузки можно остаться без доступного приглашения входа — проверьте, что target существует и уместен (multi-user.target, не graphical.target, для сервера), прежде чем применять.
  • systemctl mask юнита, от которого зависит другой юнит, может породить запутанные каскадные сбои — маскировка (в отличие от disable) делает запуск юнита невозможным даже как зависимости, пока явно не выполнить unmask.

Связанные статьи

Контрольные вопросы

  1. В чём практическая разница между Restart=on-failure и Restart=always?
  2. Почему правка файла .service не вступает в силу сразу, и какая команда это исправляет?
  3. Что гарантирует After=network.target, а что — нет, в отличие от After=network-online.target?
  4. Как узнать, почему сервис не запустился, используя только systemctl и journalctl?