Learning Platform
Глоссарий Troubleshooting
Урок 15.03 · 22 мин
Начальный
systemdunitstargetsjournalctlsystemctl

systemd — PID 1, units, targets, journalctl

В уроке 13.1 мы видели, что после switch_root kernel запускает PID 1. На современных Linux (Ubuntu, Debian, RHEL, Fedora, Arch с 2015+) — это systemd. Это не просто init, а целая платформа управления системой: запуск сервисов, dependency-graph, mount-юниты, timers (cron-replacement), journal logs, network management. Понимание systemd — обязательное для всех админов и DE.

В этом уроке: что такое unit (.service, .target, .timer, .mount, .socket), как они зависят друг от друга, что такое target (заменяет old runlevels), как читать journalctl для дебага, и базовые команды systemctl.


systemd как PID 1: 4 главные функции

systemd: чем PID 1 занимается
InitЗапуск всех системных сервисов по графу зависимостей. От mount файловых систем до nginx и getty. Заменяет SysV init + initscripts
Service managerSupervisor: следит за сервисами, перезапускает при failure (Restart=on-failure), enforces resource limits (CPUQuota, MemoryMax), security sandbox (ProtectSystem)
Loggersystemd-journald -- структурированный logging. Собирает stdout/stderr сервисов, kernel-сообщения, syslog. Хранит в бинарном formate с метаданными. journalctl -- query interface
Time, network, etc.systemd-timesyncd (NTP), systemd-resolved (DNS), systemd-networkd, systemd-logind. Не все сервисы systemd обязательны -- networkd часто заменяется NetworkManager
Mount managersystemd-mount: монтирует все ФС из /etc/fstab как mount-units. swapfile тоже -- swap-unit. Может dynamically reload через mount/umount commands
Boot managerTargets для boot stages. multi-user.target -- 'ready for users', graphical.target -- 'ready for X11/Wayland'. systemd-analyze для optimization

systemd — огромный проект (~2 миллиона строк кода), который заменил много отдельных утилит: SysV init, cron, syslog, automount, inetd, и др. Это вызывает споры в Linux-сообществе (“monolithic, too much”), но de-facto стандартизация позволила админам учить один tool вместо десятка.


Units: основной концепт

В systemd всё — unit. Это файл с метаданными о сервисе/mount/timer/socket/target. Файлы в нескольких местах:

/usr/lib/systemd/system/      # ставится пакетами (apt, dnf)
/etc/systemd/system/          # admin custom (приоритет выше /usr/lib)
~/.config/systemd/user/       # user-units (нет root)
/run/systemd/system/          # runtime-сгенерированные (после reboot исчезают)

Типы юнитов (расширение определяет):

.service    # сервисы (nginx.service, postgresql.service)
.target     # группы units, аналог runlevels (multi-user.target)
.socket     # socket activation (start service когда socket-connection пришёл)
.mount      # mount filesystem (генерируется автоматически из /etc/fstab)
.timer      # periodic execution (cron-replacement)
.path       # watch filesystem path (start service когда файл изменился)
.swap       # swap area
.device     # device units (создаются udev'ом)
.slice      # cgroup hierarchies для resource limits
.scope      # external processes managed by systemd

Список загруженных юнитов:

$ systemctl list-units | head
UNIT                                LOAD   ACTIVE SUB     DESCRIPTION
proc-sys-fs-binfmt_misc.automount   loaded active running ...
sys-devices-pci0000:00-...service   loaded active running ...
boot.mount                          loaded active mounted ...
postgresql.service                  loaded active running PostgreSQL
nginx.service                       loaded active running nginx
sshd.service                        loaded active running OpenSSH
multi-user.target                   loaded active active  Multi-User System

ACTIVE — состояние unit: active/inactive/failed. SUB — более точное состояние (running, exited, dead, listening).


.service unit — основа основ

Простой пример service-unit для Python-скрипта:

[Unit]
Description=My ETL Script
After=network-online.target postgresql.service
Wants=network-online.target

[Service]
Type=simple
User=etl
Group=etl
WorkingDirectory=/opt/etl
ExecStart=/opt/etl/venv/bin/python /opt/etl/main.py
Restart=on-failure
RestartSec=10s
StandardOutput=journal
StandardError=journal
Environment="PYTHONUNBUFFERED=1"
EnvironmentFile=/opt/etl/secrets.env

# Security
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true

# Resource limits
MemoryMax=2G
CPUQuota=200%

[Install]
WantedBy=multi-user.target

Три секции:

  • [Unit] — метаданные и зависимости.
  • [Service] — что и как запускать (для .service).
  • [Install] — куда подключать при enable.

Ключевые поля:

  • Description — человеческое описание.
  • After= — запустить после (но не required).
  • Wants=/Requires= — запустить также эти units (Requires строже — если они failed, наш тоже).
  • ExecStart= — АБСОЛЮТНЫЙ путь к команде (PATH не наследуется).
  • User=/Group= — от какого юзера (НЕ root для production).
  • Restart=on-failure — автоперезапуск при exit-code != 0.
  • RestartSec=10s — задержка перед перезапуском.
  • MemoryMax=, CPUQuota= — через cgroups, защита от runaway процесса.
  • WantedBy=multi-user.target — при enable сделать symlink в multi-user.target.wants/.

После создания файла:

# Создаём в /etc/systemd/system/etl.service
sudo systemctl daemon-reload          # перечитать unit-файлы
sudo systemctl start etl.service      # запустить сейчас
sudo systemctl enable etl.service     # автозапуск при boot
sudo systemctl status etl.service     # статус
WARNING

После любой правки unit-файла НУЖЕН sudo systemctl daemon-reload. systemd кэширует юниты в памяти, правка на диске не подхватывается. После daemon-reload restart применит изменения. systemd при restart показывает ‘Warning: unit changed on disk’ — это сигнал что забыли daemon-reload.


Targets — что заменили runlevels

В классическом SysV были runlevels — 0 (halt), 1 (single user), 3 (multi-user no GUI), 5 (multi-user GUI), 6 (reboot). Переключались через init 5.

systemd использует targets — более гибкие группировки. Каждый target — unit-файл, который через Requires= / Wants= подтягивает другие units.

Стандартные targets:

poweroff.target          # выключение (= runlevel 0)
rescue.target            # rescue (= runlevel 1, single user)
multi-user.target        # обычный server, без GUI (= runlevel 3)
graphical.target         # GUI desktop (= runlevel 5)
reboot.target            # перезагрузка (= runlevel 6)

# Кроме того:
emergency.target         # ещё минимальнее rescue (без mount других ФС)
default.target           # symlink на multi-user или graphical

Команды:

# Текущий default-target (загружается по умолчанию):
$ systemctl get-default
multi-user.target

# Сменить (например, после установки GUI):
sudo systemctl set-default graphical.target

# Переключиться сейчас (без reboot):
sudo systemctl isolate rescue.target          # выгоняет всех в rescue
sudo systemctl isolate multi-user.target      # обратно

# Что входит в target:
systemctl list-dependencies multi-user.target

В normal flow boot: systemd запускает default.target, который через зависимости подтягивает все нужные сервисы.


journalctl — просмотр логов

systemd-journald собирает stdout/stderr всех сервисов, kernel-сообщения, syslog — в единый бинарный journal (/var/log/journal/). journalctl — интерфейс query.

Базовые команды:

# Все логи с начала journal:
journalctl

# Live tail (как tail -f):
journalctl -f

# Только текущая загрузка:
journalctl -b

# Предыдущий boot:
journalctl -b -1

# Конкретный сервис:
journalctl -u nginx.service

# Сервис + live:
journalctl -u nginx.service -f

# С момента времени:
journalctl --since "10 minutes ago"
journalctl --since "2026-05-18 09:00"
journalctl --since today --until "1 hour ago"

# Только kernel:
journalctl -k
journalctl -k -b -1     # kernel предыдущей загрузки

# По приоритету (level):
journalctl -p err -b    # только errors с текущего boot
journalctl -p warning   # warnings и выше

# Конкретный PID:
journalctl _PID=1234

# По UID:
journalctl _UID=33

# Полный output (без обрезания строк):
journalctl --no-pager

# Реверс (новейшие первыми):
journalctl -r | head -20

# Только сегодня, по сервису, последние 50 строк:
journalctl -u sshd --since today -n 50

journalctl имеет встроенный pager (less). Поэтому можно листать стрелками, искать через /, и т.д. Для скриптов — --no-pager.


Persistent journal

По умолчанию на некоторых дистрибутивах journal — volatile (только RAM, /run/log/journal/). После reboot всё стирается. Для persistent:

# Создать директорию (systemd сам начнёт писать):
sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo killall -USR1 systemd-journald

# Проверить:
journalctl --disk-usage
# Archived and active journals take up 230.4M in the file system.

Конфиг — /etc/systemd/journald.conf:

[Journal]
Storage=persistent        # auto | persistent | volatile | none
Compress=yes
SystemMaxUse=2G           # лимит на disk
SystemMaxFileSize=128M
MaxRetentionSec=1month

После — sudo systemctl restart systemd-journald.

Для немедленной чистки:

sudo journalctl --vacuum-time=7d    # удалить старше 7 дней
sudo journalctl --vacuum-size=2G    # оставить только 2 ГБ

Dependency graph

systemd при boot строит граф зависимостей и запускает units параллельно где можно. Зависимости задаются:

  • Requires=X — X должен быть active, иначе наш unit fails. Жёсткая связь.
  • Wants=X — запустить X (если не запущен), но если он failed — наш всё равно работает.
  • After=X — запускать после X.
  • Before=X — запускать до X.
  • Conflicts=X — если X active, наш не может быть запущен (и наоборот).

Визуализация:

# Полное дерево зависимостей:
systemctl list-dependencies multi-user.target

# Что зависит от меня:
systemctl list-dependencies --reverse postgresql.service

# Критическая цепочка времени запуска:
systemd-analyze critical-chain

.timer — cron-replacement

Cron-задачи можно (и лучше) делать через systemd-timers. Это пара unit-файлов: .service (что делать) + .timer (когда).

# /etc/systemd/system/backup.service
[Unit]
Description=Database backup

[Service]
Type=oneshot
User=postgres
ExecStart=/usr/local/bin/backup-db.sh

# /etc/systemd/system/backup.timer
[Unit]
Description=Daily backup at 03:00

[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
RandomizedDelaySec=300

[Install]
WantedBy=timers.target

Включить:

sudo systemctl enable backup.timer
sudo systemctl start backup.timer
systemctl list-timers       # все таймеры с next/last activation

Преимущества над cron:

  • Persistent=true — если в 03:00 машина была off, запустится сразу после boot (cron не умеет).
  • RandomizedDelaySec= — стохастика, снимает thundering herd на cron-кластерах.
  • Структурированные logs в journal (journalctl -u backup.service).
  • Зависимости через After=postgresql.service.

.mount и .socket

.mount — генерируется автоматически из /etc/fstab при boot. Можно создать вручную для специфических случаев (NFS с особыми опциями, lazy mount). Имя файла — из mount path: /data/backup -> data-backup.mount.

.socket — socket activation. Когда кто-то подключается на socket, systemd запускает сервис. Используется для inetd-style сервисов:

# foo.socket
[Socket]
ListenStream=8080

[Install]
WantedBy=sockets.target

# foo.service -- запустится при connection
[Service]
ExecStart=/usr/local/bin/foo-server

Это даёт lazy loading (сервис запускается только когда нужен) и atomic upgrade (sockets открыты systemd, переинсталляция сервиса без потери connections).

Пишем systemd .service unit для Python ETL daemon

systemctl: основные команды

# Жизненный цикл:
systemctl start SVC               # запустить
systemctl stop SVC                # остановить
systemctl restart SVC             # рестарт
systemctl reload SVC              # reload config (если поддерживает)
systemctl reload-or-restart SVC   # reload если можно, иначе restart

# Автозапуск:
systemctl enable SVC              # автозапуск при boot
systemctl disable SVC             # убрать автозапуск
systemctl is-enabled SVC          # проверить (вернёт enabled/disabled)
systemctl mask SVC                # запретить полностью (даже manual start)
systemctl unmask SVC

# Статус и информация:
systemctl status SVC              # статус + последние logs
systemctl is-active SVC           # active/inactive/failed
systemctl cat SVC                 # показать unit-файл
systemctl show SVC                # все properties (длинный output)
systemctl edit SVC                # override unit (создаст /etc/systemd/system/SVC.service.d/override.conf)
systemctl edit --full SVC         # full override (копия unit-файла)

# Listing:
systemctl list-units              # все active
systemctl list-units --all        # включая inactive
systemctl list-units --failed     # только failed
systemctl list-unit-files         # все доступные

# Targets:
systemctl get-default
systemctl set-default multi-user.target
systemctl isolate rescue.target

# After config change:
systemctl daemon-reload           # перечитать unit-файлы (после правок)

Попробуй сам

# 1. Все active services сейчас:
systemctl list-units --type=service --state=running | head -15

# 2. Статус критичного сервиса:
systemctl status sshd

# 3. Логи sshd за последний час:
journalctl -u sshd --since "1 hour ago"

# 4. Failed services:
systemctl --failed

# 5. Default target:
systemctl get-default

# 6. Что в multi-user.target:
systemctl list-dependencies multi-user.target | head -20

# 7. Размер journal:
journalctl --disk-usage

# 8. Все таймеры:
systemctl list-timers

# 9. Время boot:
systemd-analyze
systemd-analyze blame | head -10

# 10. (С root) создать минимальный service:
cat <<EOF | sudo tee /etc/systemd/system/hello.service
[Unit]
Description=Hello world
[Service]
ExecStart=/bin/echo "Hello from systemd"
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl start hello
sudo systemctl status hello
# Удалить:
sudo systemctl stop hello
sudo rm /etc/systemd/system/hello.service
sudo systemctl daemon-reload

Проверка знанийKnowledge check
Создал /etc/systemd/system/myapp.service с User=myapp, ExecStart=/opt/myapp/run.sh. systemctl start говорит 'failed'. systemctl status показывает 'Main process exited, code=exited, status=203/EXEC'. Что не так и как исправить?
ОтветAnswer
Exit code 203/EXEC -- стандартный systemd-код для 'executable not found or not executable'. Это означает что ExecStart= не может быть выполнен. Причины: 1. Опечатка в пути -- проверить ls -l /opt/myapp/run.sh. 2. Файл не executable -- chmod +x /opt/myapp/run.sh. 3. Shebang в скрипте указывает на интерпретатор, которого нет (#!/bin/bashh, #!/usr/bin/python -- но python это python3 в новых системах). Решение: head -1 /opt/myapp/run.sh -- проверить, what /usr/bin/python (или нужный). 4. User myapp не существует или не может прочитать /opt/myapp/run.sh. Проверить: id myapp; sudo -u myapp cat /opt/myapp/run.sh. Если permission denied -- chmod o+r или chown myapp. 5. WorkingDirectory= указан, но юзер myapp не имеет доступа -- chmod на директорию. 6. Sandbox-options блокируют. Если есть ProtectSystem=strict или NoNewPrivileges=true и т.д. -- они могут блокировать exec. Для дебага временно убрать эти options. 7. Файл существует, executable, доступен пользователю -- но это shell-скрипт без shebang. Тогда systemd не знает, чем его запускать. Добавить #!/bin/bash в первой строке или вызывать через ExecStart=/bin/bash /opt/myapp/run.sh. Дебаг workflow: 1. Полный status с деталями: systemctl status myapp.service -l journalctl -u myapp.service -n 30 2. Проверить файл: ls -l /opt/myapp/run.sh file /opt/myapp/run.sh head -1 /opt/myapp/run.sh # shebang sudo -u myapp /opt/myapp/run.sh # запустить от того же юзера вручную 3. systemd-analyze verify: systemd-analyze verify /etc/systemd/system/myapp.service Покажет ошибки в самом unit-файле. 4. Если ничего не помогает -- временно упростить unit: [Service] ExecStart=/bin/echo hello Если это запускается -- проблема в run.sh. Если нет -- проблема в unit-настройках. 5. После исправления: sudo systemctl daemon-reload sudo systemctl restart myapp Кстати, /etc/systemd/system/ (где admin кладёт юниты) и /usr/lib/systemd/system/ (системные) -- разные пути, и admin overrides have priority. Если в обоих есть myapp.service -- /etc выиграет. Проверить точно какой загружается: systemctl cat myapp.service -- показывает path.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 6. Какие типы unit-файлов существуют в systemd?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 4