systemd: init и process manager¶
systemd — PID 1 на большинстве современных Linux-дистрибутивов. Его задача — запустить, остановить и поддерживать
жизнь всех сервисов в системе, от первой инициализации железа до выключения. От старого sysvinit его отличает не
только реализация (написан на C, не на shell-скриптах), но и сама модель: вместо последовательного выполнения
скриптов в /etc/init.d/ он строит граф зависимостей unit-файлов и параллельно поднимает всё, что не блокирует
друг друга.
«systemd» — это не один бинарь, а семейство демонов: собственно systemd (PID 1), systemd-journald (логи),
systemd-logind (сессии), systemd-resolved (DNS), systemd-networkd (сеть), systemd-timesyncd (NTP),
systemd-oomd (OOM-killer на PSI), systemd-nspawn (контейнеры). Это и есть главный источник споров: одни видят
в этом «всё в одном» цельный пакет управления системой, другие — нарушение Unix-философии. Технически systemd —
просто эволюция, которую без переписывания init из C сделать было нельзя.
Зачем¶
sysvinit (1992) systemd (2010)
┌────────────────────────────┐ ┌────────────────────────────┐
│ /etc/init.d/* — shell │ │ unit-файлы (декларативные)│
│ /etc/rc<N>.d/SXX → KXX │ │ граф зависимостей │
│ последовательный запуск: │ │ параллельный запуск: │
│ │ │ │
│ rcS (single) │ │ sysinit.target │
│ └─▶ rc2 (multi) │ │ ╱ │ │ ╲ │
│ ├─ S10sysklog │ │ srv srv srv srv │
│ ├─ S20networking │ │ ╲ │ │ ╱ │
│ ├─ S30sshd │ │ basic.target │
│ └─ S99crond │ │ │ │ │
│ │ │ srv srv │
│ boot 30..60 сек │ │ ╲ ╱ │
│ │ │ multi-user.target │
│ │ │ │
│ ad-hoc backgrounding, │ │ boot 2..10 сек │
│ никакого socket act. │ │ │
│ │ │ socket activation, │
│ │ │ on-demand mount, │
│ │ │ journald, cgroups, ... │
└────────────────────────────┘ └────────────────────────────┘
Главные новшества по сравнению с sysvinit:
- Параллельная загрузка. Зависимости описываются явно (
After=,Requires=), всё остальное стартует параллельно. Boot за 2–5 секунд против 30–60. - Socket activation. systemd сам слушает сокет, поднимает сервис при первом подключении и передаёт ему готовый fd. Сервисы могут зависеть от сокетов друг друга без знания «кто стартует первым».
- cgroup tracking. Каждый сервис автоматически попадает в свою cgroup, что даёт точный учёт процессов и потомков (никаких потерянных daemon'ов, которые «отвязались через fork»).
- journald. Бинарный лог с индексами и метаданными (UID, PID, unit, cgroup) — фильтр
journalctl -u nginx --since todayработает безgrepпо терабайтам. - on-demand mount, timers, paths, devices — единая модель для разнотипных событий.
Unit types¶
Unit — базовая единица systemd. Каждый файл *.<type> описывает один объект. Все unit-файлы лежат в стандартных
путях: /usr/lib/systemd/system/ (от пакетов), /etc/systemd/system/ (от админа, override), ~/.config/systemd/user/
(для user instance).
| Тип | Файл | Что описывает |
|---|---|---|
| service | *.service |
сервис: daemon, oneshot-команда, forking-сервер |
| socket | *.socket |
listener для socket activation |
| timer | *.timer |
расписание запуска (cron-replacement) |
| mount | *.mount |
точка монтирования (генерится из /etc/fstab) |
| automount | *.automount |
lazy mount: монтировать при первом доступе |
| path | *.path |
watcher: реагировать на изменения файла/директории |
| swap | *.swap |
swap-устройство |
| device | *.device |
udev-устройство (генерится автоматически) |
| target | *.target |
группа units (аналог runlevel) |
| slice | *.slice |
узел иерархии cgroup для группировки services/scopes |
| scope | *.scope |
группа внешних процессов в cgroup (создаётся через API) |
systemctl list-unit-files --type=service # все service-файлы в системе
systemctl list-units --type=service --state=running
systemctl list-units --type=timer
Unit-файл: устройство¶
Unit-файл — INI-подобный текст с тремя стандартными секциями:
# /etc/systemd/system/myapp.service
[Unit]
Description=My application server
Documentation=https://example.com/docs
After=network-online.target postgresql.service
Wants=network-online.target
Requires=postgresql.service
ConditionPathExists=/etc/myapp/config.yaml
[Service]
Type=notify # сервис сам говорит "готов" через sd_notify()
ExecStartPre=/usr/bin/myapp --check-config
ExecStart=/usr/bin/myapp --config /etc/myapp/config.yaml
ExecReload=/bin/kill -HUP $MAINPID
ExecStop=/usr/bin/myapp --graceful-shutdown
Restart=on-failure
RestartSec=5s
User=myapp
Group=myapp
WorkingDirectory=/var/lib/myapp
Environment="LOG_LEVEL=info" "WORKERS=4"
EnvironmentFile=/etc/myapp/env
# Ресурсы (cgroup)
MemoryMax=2G
MemoryHigh=1.5G
CPUWeight=200
TasksMax=512
# Безопасность
NoNewPrivileges=true
ProtectSystem=strict # / read-only, кроме перечисленных путей
ProtectHome=true # /home, /root, /run/user — недоступны
PrivateTmp=true # свой /tmp, /var/tmp (mount ns)
PrivateDevices=true # урезанный /dev
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
SystemCallFilter=@system-service # seccomp-фильтр (whitelist)
SystemCallArchitectures=native
[Install]
WantedBy=multi-user.target
Секции¶
| Секция | Что задаёт |
|---|---|
[Unit] |
метаданные, зависимости, условия запуска |
[Service] |
как запускать процесс (Type, ExecStart, User, лимиты, sandbox) |
[Install] |
как unit включается через systemctl enable (symlinks в .wants) |
[Socket] |
для .socket: что слушать (ListenStream, ListenDatagram), сколько fd |
[Timer] |
для .timer: OnCalendar, OnBootSec, OnUnitActiveSec |
[Path] |
для .path: PathChanged, PathExists |
[Mount] |
для .mount: What, Where, Type, Options |
Service Type¶
Этот параметр определяет, когда systemd считает сервис «запущенным»:
| Type | Когда «started» |
|---|---|
simple |
сразу после fork+exec (default; для большинства не подходит) |
exec |
сразу после успешного execve (точнее, чем simple) |
forking |
когда parent-процесс exit'нул (классический Unix daemon с fork+detach) |
oneshot |
когда процесс exit'нул успешно (для скриптов и init-команд) |
notify |
когда сервис послал sd_notify("READY=1") |
dbus |
когда сервис захватил указанное D-Bus имя |
idle |
как simple, но запуск откладывается до завершения active jobs |
notify — рекомендуемый тип для долгоживущих сервисов: даёт точный момент готовности и поддерживает WATCHDOG=1
для health-check'ов. Современные сервисы (systemd-resolved, NetworkManager, sshd с подходящим патчем) делают
sd_notify напрямую.
Зависимости и порядок¶
Главное запутывание systemd для новичков — разделение наличия зависимости и порядка запуска. Это разные свойства.
| Директива | Означает |
|---|---|
Requires=B |
A зависит от B. Если B не запустился — A не запустится. B остановлен → A тоже |
Wants=B |
A хочет B, но переживёт его отсутствие (мягкое требование) |
Requisite=B |
A требует, чтобы B уже был запущен; не запускает B сам |
BindsTo=B |
как Requires, но: B упал → A тоже немедленно остановлен |
Conflicts=B |
A и B взаимоисключают друг друга |
After=B |
если оба запускаются — A после B (только порядок, не зависимость) |
Before=B |
если оба запускаются — A раньше B |
Типичная ошибка — указать Requires=network.target, но забыть After=network.target. systemd запустит оба
параллельно, и приложение полетит до того, как сеть готова.
правильно: типичная ошибка:
[Unit] [Unit]
Requires=postgresql.service Requires=postgresql.service
After=postgresql.service (нет After!)
[Service] [Service]
ExecStart=/usr/bin/myapp ExecStart=/usr/bin/myapp
↓ запуск: ↓ запуск:
postgresql → myapp postgresql и myapp параллельно
myapp падает: connection refused
Граф зависимостей¶
graph TB
DT["default.target<br/>(alias на graphical / multi-user)"]
DT -- wants --> DM["display-manager.service"]
DT -- wants --> MU["multi-user.target"]
DT -- wants --> GT["getty@tty[1-6]"]
MU -- wants --> NG["nginx.service"]
MU -- wants --> SS["sshd.service"]
MU -- wants --> CR["cron.service"]
NG -- after --> NT["network.target"]
SS -- after --> NT
CR -- after --> NT
NT -- after --> BT["basic.target"]
BT -- after --> ST["sysinit.target"]
# Вывести граф в Graphviz dot-формате
systemd-analyze dot multi-user.target | dot -Tpng > graph.png
# Список After/Before для unit'а
systemctl list-dependencies nginx.service
systemctl list-dependencies --before nginx.service
Targets vs runlevels¶
Target — это unit без процессов, единственная цель которого — группировать другие units через Wants= /
Requires=. Это замена runlevels sysvinit:
| sysvinit runlevel | systemd target | Что означает |
|---|---|---|
| 0 | poweroff.target |
выключение |
| 1, S | rescue.target |
single-user, root shell без сети |
| 2, 3, 4 | multi-user.target |
multi-user, no GUI |
| 5 | graphical.target |
multi-user + display manager |
| 6 | reboot.target |
перезагрузка |
| — | emergency.target |
минимум: только initrd-like среда |
# Переключиться в режим (без перезагрузки)
systemctl isolate multi-user.target # выйти из GUI
systemctl isolate graphical.target # обратно
# Установить default
systemctl set-default multi-user.target
# Specialty targets
systemctl rescue # rescue mode
systemctl emergency # emergency shell
Socket activation¶
Один из ключевых дизайн-выборов systemd: позволить ему слушать сокет от имени сервиса. Когда приходит соединение,
systemd поднимает сервис и передаёт ему уже открытый fd через переменные окружения LISTEN_FDS, LISTEN_PID,
LISTEN_FDNAMES.
sequenceDiagram
participant U as Пользователь
participant K as Kernel
participant S as systemd
participant C as cups.service
Note over S: T=0 — systemd парсит cups.socket<br/>(ListenStream=/run/cups/cups.sock, Service=cups.service)
S->>K: socket(AF_UNIX), bind, listen на /run/cups/cups.sock
Note over C: cups.service — НЕ запущен
Note over S,C: T=t1 — bootup завершён, никто не подключался,<br/>экономим память и время старта
U->>K: T=t2 — lp document.pdf<br/>connect(/run/cups/cups.sock)
K-->>S: poll() сигнализирует о connection
S->>C: fork+exec cupsd<br/>env: LISTEN_FDS=1, LISTEN_PID=<cupsd-pid>, LISTEN_FDNAMES=cups.sock<br/>fd 3 = слушающий socket
C->>C: sd_listen_fds(0) → fd 3
C->>K: accept()
K-->>U: connect() возвращает, всё проходит дальше
Что это даёт¶
-
Параллельный boot без явного порядка. Сервисы могут зависеть от сокетов друг друга, не указывая
After=. Например, syslog.socket доступен с самого начала; logger в любом сервисе подключается, даже если syslog.service ещё не успел стартовать (kernel-side queue хранит соединения). -
Lazy startup. Сервис не запускается, пока не нужен. cups, atd, multipathd могут спокойно ждать первого запроса.
-
Rolling restart. При обновлении сервиса соединения не теряются — systemd держит сокет открытым, новые подключения буферизуются в kernel queue.
Пример unit'а с socket activation¶
# /etc/systemd/system/echo.socket
[Unit]
Description=Echo socket
[Socket]
ListenStream=12345
Accept=no # один экземпляр сервиса на все соединения
# (Accept=yes — отдельный процесс на каждое)
[Install]
WantedBy=sockets.target
# /etc/systemd/system/echo.service
[Unit]
Requires=echo.socket
[Service]
ExecStart=/usr/local/bin/echod
StandardInput=socket # systemd подключит socket к stdin сервиса
Сервис может вообще не знать о сокетах: systemd подключает socket к stdin (как делает inetd). Современные демоны
используют API библиотеки sd_notify: sd_listen_fds() для перечисления переданных fd, sd_notify() для
сигналов о готовности и health-check.
journald¶
Логи в systemd — отдельная подсистема. journald собирает stdout/stderr всех unit'ов, плюс данные из syslog API
(syslog(3)), kernel-log (/dev/kmsg), audit, и хранит их в бинарном формате *.journal в
/var/log/journal/ (persistent) или /run/log/journal/ (только в RAM, до перезагрузки).
Каждая запись — это структура с произвольным набором полей:
MESSAGE="Started session 5 of user endor"
PRIORITY=6
SYSLOG_FACILITY=3
SYSLOG_IDENTIFIER=systemd
_SYSTEMD_UNIT=systemd-logind.service
_SYSTEMD_CGROUP=/system.slice/systemd-logind.service
_UID=0
_GID=0
_PID=812
_COMM=systemd-logind
_HOSTNAME=workstation
_BOOT_ID=4c5e...
_MACHINE_ID=abc...
_TRANSPORT=stdout
__REALTIME_TIMESTAMP=1737367800123456
__MONOTONIC_TIMESTAMP=234567890123
Поля с префиксом _ (один underscore) ставит journald автоматически, доверять им можно. Поля с __ (два) —
служебные. Произвольные поля от приложения — без подчёркивания.
journalctl¶
journalctl # весь лог, от старого к новому
journalctl -f # tail -f
journalctl -r # обратный порядок
journalctl -n 100 # последние 100 строк
journalctl -u nginx.service # только nginx
journalctl -u nginx -u postgresql # несколько unit'ов
journalctl -u nginx --since "2 hours ago"
journalctl -u nginx --since today --until "1 hour ago"
journalctl -p err # priority >= err
journalctl -k # только kernel (dmesg replacement)
journalctl _PID=1234 # по любому полю
journalctl _COMM=ssh _UID=1000 # пересечение условий
journalctl _SYSTEMD_UNIT=nginx.service + _SYSTEMD_UNIT=php-fpm.service
# объединение (плюс — OR)
journalctl --boot # текущая загрузка
journalctl --boot -1 # предыдущая загрузка
journalctl --list-boots # список загрузок
journalctl --disk-usage # сколько места занято
journalctl --vacuum-time=7d # удалить старше 7 дней
journalctl --vacuum-size=500M # удалить до достижения размера
Хранение¶
/var/log/journal/<machine-id>/
├── system.journal ← активный, в него идёт запись
├── system@xxxx-yyyy.journal ← архивный (после rotate)
├── system@xxxx-zzzz.journal
└── user-1000.journal ← логи user@1000.service
Persistent storage включается созданием /var/log/journal/ (если директории нет — journald пишет только в RAM).
По умолчанию journald держит лимит: 10% от размера диска или 4 GB (что меньше). Настраивается в
/etc/systemd/journald.conf (SystemMaxUse=, SystemKeepFree=, SystemMaxFileSize=, MaxRetentionSec=).
Forwarding¶
journald может дублировать логи: ForwardToSyslog=yes отправляет в rsyslog, ForwardToKMsg=yes — в /dev/kmsg,
ForwardToConsole=yes — на консоль. Это нужно для интеграции с внешними сборщиками (Loki, Elasticsearch,
Splunk) — обычно стоит rsyslog-омнибас на host'е, который форвардит куда нужно.
cgroup integration¶
Каждый unit систем автоматически получает свой cgroup в иерархии (см. подробно cgroups: углублённо). Дерево строится по принципу «слайсы группируют, сервисы и скоупы содержат процессы»:
/sys/fs/cgroup/
├── init.scope/ ← сам systemd (PID 1)
├── system.slice/ ← system-сервисы
│ ├── nginx.service/ ← все процессы nginx
│ ├── postgresql.service/
│ └── ...
├── user.slice/ ← пользовательские сессии
│ └── user-1000.slice/
│ ├── user@1000.service/ ← user systemd instance
│ │ ├── app.slice/
│ │ └── ...
│ └── session-3.scope/ ← TTY/SSH сессия
└── machine.slice/ ← systemd-nspawn контейнеры, VMs
systemd-cgls / systemd-cgtop¶
systemd-cgls # дерево cgroup как ps-tree
# Control group /:
# ├─user.slice
# │ └─user-1000.slice
# │ └─session-3.scope
# │ ├─2103 sshd: endor [priv]
# │ ├─2118 sshd: endor@pts/0
# │ ├─2119 -bash
# │ └─2435 bpftrace -e ...
# └─system.slice
# ├─nginx.service
# │ ├─1234 nginx: master process
# │ ├─1235 nginx: worker process
# │ └─1236 nginx: worker process
# └─...
systemd-cgtop # top по cgroups
# Control Group Tasks %CPU Memory Input/s Output/s
# / 412 12.3 3.2G 1.5M 800K
# user.slice 87 8.1 1.8G ... ...
# system.slice 325 4.2 1.4G ... ...
Resource limits через unit-файл¶
[Service]
MemoryMax=2G # → /sys/fs/cgroup/<unit>/memory.max
MemoryHigh=1.5G # → memory.high
CPUWeight=200 # → cpu.weight
CPUQuota=50% # → cpu.max (50000 100000)
IOWeight=200 # → io.weight
TasksMax=512 # → pids.max
IOReadBandwidthMax=/dev/sda 50M
# Изменить лимит на лету
systemctl set-property nginx.service MemoryMax=4G
systemctl set-property nginx.service MemoryMax=4G --runtime=false # и в drop-in
# Запустить процесс в transient scope с лимитом
systemd-run --scope -p MemoryMax=512M -p CPUWeight=50 ./myscript.sh
# Создать transient service (с автоматической cgroup, рестартом и логами)
systemd-run --unit=myjob --slice=batch.slice ./long_running_task
systemd-oomd¶
Когда memory.pressure (PSI) внутри slice'а превышает порог, systemd-oomd выбирает самую тяжёлую cgroup в этом
slice и убивает её до того, как сработает kernel OOM-killer. Это превентивный механизм для интерактивных
систем, где kernel-OOM реагирует слишком поздно — когда уже всё тормозит из-за swap-storm'а.
Конфиг в slice'е:
# /etc/systemd/system/user-1000.slice.d/oomd.conf
[Slice]
ManagedOOMMemoryPressure=kill
ManagedOOMMemoryPressureLimit=50%
ManagedOOMSwap=kill
«Если memory pressure some avg10 в этом slice превышает 50% — выбрать и убить cgroup внутри». Подробно — см. cgroups, раздел PSI.
systemd-nspawn¶
systemd-nspawn — минимальный container runtime от systemd. Под капотом — chroot + полный набор namespaces +
своя cgroup. Используется как «легковесный VM» для тестов, как контейнерная замена chroot для сборок, и как
основа для образов в machinectl.
# Создать chroot директорию с минимальной системой
debootstrap --include=systemd,dbus stable /var/lib/machines/debian
# Запустить как контейнер с интерактивной оболочкой
sudo systemd-nspawn -D /var/lib/machines/debian
# Запустить как боксированную систему (со своим systemd внутри)
sudo systemd-nspawn -bD /var/lib/machines/debian
# внутри: запускается systemd-PID-1, виден полный multi-user.target
# С сетью (bridge на host, veth внутри)
sudo systemd-nspawn -bD /var/lib/machines/debian --network-bridge=br0
# Управление через machinectl
machinectl list
machinectl shell debian
machinectl poweroff debian
systemd-nspawn ближе всего к philosophy «контейнер = full OS», в отличие от Docker'а с philosophy «один процесс на контейнер». Для долгоживущих сред разработки и для тестирования systemd-сервисов — идеален.
Boot sequence: что делает systemd¶
graph TB
BIOS["BIOS/UEFI → GRUB → kernel → initrd/initramfs"]
BIOS -- pivot_root / switch_root --> SD["/sbin/init = systemd (PID 1)"]
SD -- читает default.target (alias) --> MU["multi-user.target (цель)"]
MU -- requires --> BT["basic.target"]
BT -- requires --> ST["sysinit.target"]
ST --> LF["local-fs.target<br/>(mount /, /var, /home, ...)"]
ST --> SW["swap.target<br/>(активация swap)"]
ST --> CS["cryptsetup.target<br/>(LUKS / dm-crypt)"]
LF --> EARLY["udev.service — создаёт device-nodes, .device units<br/>systemd-journald<br/>systemd-tmpfiles — /run, /tmp правила<br/>systemd-sysctl — применяет /etc/sysctl.d/<br/>..."]
После sysinit.target — basic.target (sockets, timers, paths).
После basic.target — sequence Wants= для multi-user.target:
sshd, cron, network manager, dbus, getty@tty1, ...
Анализ загрузки¶
systemd-analyze # время загрузки
# Startup finished in 3.124s (kernel) + 4.821s (userspace) = 7.945s
# multi-user.target reached after 4.819s in userspace
systemd-analyze blame # самые медленные unit'ы
# 2.142s NetworkManager-wait-online.service
# 1.234s docker.service
# 0.456s systemd-journal-flush.service
# 0.321s snapd.service
# 0.198s lvm2-monitor.service
systemd-analyze critical-chain # критический путь зависимостей
# multi-user.target @4.819s
# └─NetworkManager-wait-online.service @2.677s +2.142s
# └─NetworkManager.service @1.234s +1.443s
# └─basic.target @1.231s
# └─sysinit.target @1.218s +12ms
# └─...
systemd-analyze plot > boot.svg # визуализация в SVG
NetworkManager-wait-online.service — классический виновник медленной загрузки на ноутбуках: он ждёт реального
соединения с сетью, и если WiFi не сразу подключается, sleep'ит до 30 секунд.
systemctl: основные команды¶
# Управление unit'ом
systemctl start nginx # запустить (этот раз)
systemctl stop nginx # остановить
systemctl restart nginx # рестарт
systemctl reload nginx # SIGHUP (если ExecReload= задан)
systemctl reload-or-restart nginx # reload если возможно, иначе restart
systemctl status nginx # состояние + последние логи + cgroup
# Автозапуск
systemctl enable nginx # symlink в .wants — запуск при boot
systemctl disable nginx # удалить symlink
systemctl enable --now nginx # enable + start сразу
systemctl is-enabled nginx # enabled / disabled / static / masked
systemctl is-active nginx # active / inactive / failed
systemctl mask nginx # symlink на /dev/null — невозможно
# запустить даже руками
systemctl unmask nginx
# Конфигурация
systemctl daemon-reload # после правки unit-файлов
systemctl edit nginx # создать drop-in (override.conf)
systemctl edit --full nginx # редактировать копию unit-файла
systemctl cat nginx # показать актуальный unit-файл + drop-ins
systemctl show nginx # все свойства unit'а с дефолтами
# Информация
systemctl list-units --type=service --state=running
systemctl list-units --failed # упавшие
systemctl list-dependencies nginx # дерево зависимостей
systemctl list-jobs # незавершённые операции
systemctl get-default # default target
systemctl set-default multi-user.target
Другие компоненты systemd¶
systemd-timesyncd¶
Минимальный SNTP-клиент. Заменяет ntpd/chrony, когда не нужна высокая точность (например, на ноутбуке). Простая
конфигурация в /etc/systemd/timesyncd.conf. Для серверов и точности < 1 ms — лучше chrony.
systemd-resolved¶
Caching DNS resolver. Слушает 127.0.0.53:53, конфигурируется через /etc/systemd/resolved.conf или per-interface
через resolvectl. Поддерживает DNSSEC, DNS-over-TLS, mDNS, LLMNR. Один из самых спорных компонентов: ломает
конфигурации, привыкшие к простому /etc/resolv.conf. Может быть полностью отключён без последствий.
systemd-networkd¶
Network manager в духе systemd — декларативные .network, .netdev, .link файлы. Хорош для серверов, где
конфигурация фиксирована. Не для laptop'ов (там — NetworkManager).
systemd-logind¶
Управляет user-сессиями: учёт login'ов, lock/unlock screen, suspend по закрытию крышки, polkit-проверка доступа
к hardware. Каждая сессия — это session-N.scope в cgroup иерархии.
systemd-tmpfiles¶
Создаёт и удаляет файлы и директории по правилам в /etc/tmpfiles.d/, /usr/lib/tmpfiles.d/. Применяется при
boot (создание /run-структуры), периодически по таймеру (очистка /tmp старше N дней).
systemd-coredumpctl¶
Перехватывает coredump'ы через kernel.core_pattern = |/usr/lib/systemd/systemd-coredump, складывает в
/var/lib/systemd/coredump/. Просмотр через coredumpctl list, coredumpctl debug <PID> (открывает gdb с
core'ом).
User instance¶
systemd --user — отдельный экземпляр systemd, работающий от имени каждого пользователя. Запускается через
user@<UID>.service в system instance. Управляет пользовательскими unit'ами:
systemctl --user start myapp.service
systemctl --user enable --now myapp.timer
journalctl --user -u myapp.service
Unit-файлы лежат в:
- ~/.config/systemd/user/ (пользовательские)
- /etc/systemd/user/ (от админа)
- /usr/lib/systemd/user/ (от пакетов)
User instance запускается при первом login'е пользователя. Если нужно, чтобы сервис работал и без login'а
(например, на сервере): loginctl enable-linger <user> — это даёт user instance lifetime «с boot до shutdown».
Подводные камни¶
Service Type=simple. ExecStart-процесс отвязывается через fork+exec child + parent exit. systemd считает
сервис «started» сразу после fork, видит parent exit и думает, что сервис умер. Решение: Type=forking с
указанием PIDFile= или, лучше, переписать без forking — современные daemon'ы должны быть foreground (Type=notify
или Type=simple).
daemon-reload после правки unit-файла обязателен. systemd кэширует распарсенные units. systemctl edit сам
делает reload, но прямое редактирование файла в /etc/systemd/system/ — нет. Признак — поведение не соответствует
изменённому файлу.
After= без Requires= — только порядок. Если зависимый сервис упал или не сконфигурирован, ваш всё равно
запустится (и упадёт). Для жёсткой связи нужен и Requires=, и After=.
ProtectHome=true ломает Docker bind-mounts из /home. Sandbox-директивы создают свой mount namespace — host
mounts становятся недоступны. Для сервисов с volume'ами из /home нужно явно отключить ProtectHome= или
использовать ReadOnlyPaths= для тонкой настройки.
EnvironmentFile с многострочными значениями. systemd parser простой: KEY=value, без shell-семантики. Никаких
$(command), многострочных значений, escape-quoting. Сложные конфиги — через ExecStartPre скрипт или
Environment="KEY=value" напрямую.
Timer'ы и накопление missed runs. Если timer был остановлен (suspended laptop), при возобновлении systemd по
умолчанию не запускает все пропущенные runs — только следующий. Persistent=true в [Timer] сохраняет
last-trigger и запускает один пропущенный run при boot.
journald теряет сообщения под нагрузкой. При rate >10000 msg/s journald начинает дропать с warning «Suppressed
N messages from RateLimitBurst= и RateLimitIntervalSec= в journald.conf.
Restart=always с быстро падающим сервисом. systemd рестартует, сервис падает, рестартует, падает — start-rate
limiter (StartLimitBurst=, StartLimitIntervalSec=) после 5 рестартов за 10 секунд переводит unit в
failed (start-limit-hit). Чтобы убрать ограничение — StartLimitBurst=0 или StartLimitIntervalSec=0.
systemctl stop ждёт TimeoutStopSec= (по умолчанию 90s). Если сервис игнорирует SIGTERM, systemd ждёт
полтора минуты, потом шлёт SIGKILL. Для медленных shutdown-процедур — TimeoutStopSec=10min. Для быстрого
прерывания — KillMode=mixed или SendSIGKILL=no.
Связанные темы¶
- Основы процессов — systemd как PID 1, дерево процессов, дочерние сессии
- Сигналы —
KillSignal=,ExecStop=,SIGTERM→SIGKILLtimeout - cgroups: углублённо — каждый unit получает свою cgroup; лимиты через unit-файл
- Linux namespaces —
PrivateTmp,ProtectHomeсоздают mount ns - seccomp —
SystemCallFilter=применяет seccomp-bpf - Внутреннее устройство контейнеров — systemd-nspawn как минимальный container runtime; интеграция с containerd через scope
Источники¶
- systemd documentation — freedesktop.org
man systemd,man systemd.unit,man systemd.service,man systemd.socket,man systemd.exec,man systemd.resource-control,man journalctl,man systemctl- Lennart Poettering, «systemd for Administrators» — серия из 21 поста, эталонная документация дизайна
- systemd by example — интерактивные примеры
- The Biggest Myths — Poettering об основных заблуждениях
- Rethinking PID 1 — оригинальный анонс проекта (2010)
- systemd source —
src/core/,src/journal/