Следующий выпуск «Linux под капотом»: от SysV init к systemd и зачем это знать

Этот выпуск «Linux под капотом» про PID 1: как пришли от SysV init к systemd, и почему от этого зависит половина типичных «не стартует после ребута». Ниже ориентир на Debian/Ubuntu и systemd как основной init. Если у вас OpenRC или runit, смысл про «первый процесс» тот же, но команды и конфиги будут другими.

Важно: если строки в код-блоках «склеиваются» в одну строку, это почти всегда минификация HTML (кэш-плагин или оптимизатор). Для страниц со статьями лучше отключить HTML-minify или исключить обработку содержимого внутри <pre>.

Контекст и зачем это знать

Когда «сломался Linux», обычно сломалось ожидание: сеть ещё не поднялась, диск ещё не примонтирован, сокет ещё не слушает, конфиг не появился, права не те, окружение другое. Сервис может быть исправен, но стартовать не там и не тогда.

В Linux есть один процесс, который важнее остальных: PID 1. Он стартует первым в userspace и дальше управляет запуском сервисов и переходом системы в рабочее состояние. Исторически это был init из SysV-модели. Сейчас у большинства дистрибутивов PID 1 — systemd.

Если понимать, как работает PID 1, пропадает привычка «перезагрузить ещё раз на удачу». Вместо этого быстро закрываются вопросы: кто кого тянет, кто чего ждёт, и на каком шаге система застряла.

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

SysV init это линейная модель. Есть скрипты в /etc/init.d. На разных стадиях загрузки выполняются симлинки из каталогов вида /etc/rcS.d и /etc/rc?.d, а порядок задаётся именами ссылок (S01…, S02…, S99…).

Плюс такой схемы: простота: «вот список, вот порядок». Минус: слабое описание зависимостей. «Мне нужна сеть» превращается в «надеюсь, что сетевой скрипт успел раньше». Чем больше сервисов, тем больше скрытых ожиданий и случайностей.

systemd это менеджер юнитов. Юнит: ini-файл, который описывает объект: service, socket, timer, mount, device, target и другие. Юниты пакетов обычно лежат в /lib/systemd/system, локальные — в /etc/systemd/system. Правки без ломания пакетов делаются через drop-in overrides: /etc/systemd/system/имя.service.d/override.conf.

Самая частая ошибка в логике systemd: путать «зависимость» и «порядок». Wants=/Requires= отвечают за «подтянуть/требовать». After=/Before= отвечают только за «кто раньше/кто позже», но сами по себе никого не запускают. Если сервис должен и подтянуть зависимость, и подождать её — часто нужны обе группы директив.

Targets это ориентиры состояния, которые заменили runlevel’ы. Это не «режимы по магии», а юниты, которые собирают набор зависимостей. Например, multi-user.target обычно означает «система в рабочем режиме без GUI», а graphical.target — «с GUI».

journal (journald) это журнал событий, который удобно читать по загрузкам и по конкретным юнитам. Вместо «ищи в /var/log и гадай» смотрится история ровно того сервиса, который интересует.

Как посмотреть это на живой системе

Проверь PID 1 через ps. Одна команда, без «хвостов».

ps -p 1 -o pid,comm,args

Если хочется вообще не зависеть от ps, посмотри имя процесса PID 1 через /proc.

cat /proc/1/comm

Посмотри, с какими аргументами запущен PID 1 (тоже через /proc).

tr '\0' ' ' < /proc/1/cmdline; echo

Проверь, на что указывает /sbin/init.

readlink -f /sbin/init

Проверь, какой бинарник реально запущен у PID 1.

readlink -f /proc/1/exe

Посмотри целевой target по умолчанию (аналог «куда система должна прийти после загрузки»).

systemctl get-default

Посмотри активные targets прямо сейчас.

systemctl list-units --type=target --state=active

Проверь, какие юниты в ошибке (быстрый «что горит»).

systemctl --failed

Открой предупреждения и ошибки текущей загрузки.

journalctl -b -0 -p warning..alert --no-pager

Разбери конкретный сервис: статус и последние строки лога (пример: ssh).

systemctl status ssh.service --no-pager

Посмотри фактическую конфигурацию юнита, включая overrides.

systemctl cat ssh.service

Проверь зависимости сервиса и обратные зависимости (кто его тянет при старте).

systemctl list-dependencies ssh.service
systemctl list-dependencies --reverse ssh.service

Типовые проблемы и симптомы

Сервис запускается руками, но не стартует при загрузке. Частая причина: окружение. В интерактивной shell один PATH и набор переменных, а у systemd среда строже. В итоге ExecStart падает из-за «не нашёл бинарник», «не видит конфиг», «нет прав».

Добавили After=, но ничего не поменялось. Это классика: After= не подтягивает зависимости. Он только задаёт порядок, если оба юнита запускаются. Для «подтянуть» нужны Wants= или Requires=.

«Сеть есть, но не для сервиса». Некоторым приложениям нужна не настройка интерфейса, а реально доступная сеть. Тут всплывают ожидания вокруг network-online.target и то, кто в системе отвечает за «online».

Зависание на «A start job is running…». Обычно ждут монтирование (mount unit), устройство (device unit) или таймаут. Это диагностируется через journal по текущей загрузке: видно, что именно ждут и сколько осталось.

И отдельная боль, которую легко поймать на практике: две команды случайно запускаются как одна. Shell честно передаёт «вторую команду» как аргументы первой, и ошибки выглядят так, будто сломалась утилита. Лекарство простое: команды в инструкции показывать отдельными блоками и запускать отдельными строками.

Мини-лабораторка

Сделаем лабораторку без демонов и вечных циклов: таймер systemd, который раз в 2 минуты пишет строку в journal через logger. Получится связка timer → service → journal, и её можно увидеть в диагностике так же, как реальные сервисы.

Создай файл юнита сервиса /etc/systemd/system/kapotom-hello.service через sudoedit.

sudoedit /etc/systemd/system/kapotom-hello.service

Вставь в файл содержимое и сохрани.

[Unit]
Description=Linux pod kapotom: hello to journal

[Service]
Type=oneshot
ExecStart=/usr/bin/logger -t kapotom-hello hello_from_systemd_timer

Создай файл таймера /etc/systemd/system/kapotom-hello.timer через sudoedit.

sudoedit /etc/systemd/system/kapotom-hello.timer

Вставь содержимое таймера и сохрани.

[Unit]
Description=Linux pod kapotom: periodic hello

[Timer]
OnBootSec=30s
OnUnitActiveSec=2min
Unit=kapotom-hello.service

[Install]
WantedBy=timers.target

Перечитай конфигурацию systemd и включи таймер.

sudo systemctl daemon-reload
sudo systemctl enable --now kapotom-hello.timer

Проверь, когда таймер сработает и что он активен.

systemctl status kapotom-hello.timer --no-pager
systemctl list-timers --all

Посмотри записи в журнале по тегу и по юниту.

journalctl -t kapotom-hello -b --no-pager -n 50
journalctl -u kapotom-hello.service -b --no-pager -n 50

Удали лабораторку аккуратно: выключи таймер, удали файлы, перечитай конфигурацию.

sudo systemctl disable --now kapotom-hello.timer
sudo rm -f /etc/systemd/system/kapotom-hello.service /etc/systemd/system/kapotom-hello.timer
sudo systemctl daemon-reload

Что попробовать руками

Собери «паспорт» PID 1 на своей системе.

ps -p 1 -o pid,comm,args
readlink -f /sbin/init

Сделай то же самое через /proc, чтобы не зависеть от утилит.

cat /proc/1/comm
tr '\0' ' ' < /proc/1/cmdline; echo
readlink -f /proc/1/exe

Возьми один важный для тебя сервис и разложи его «по полочкам»: конфиг, зависимости, логи. Для примера можно nginx или docker.

systemctl status nginx.service --no-pager
systemctl cat nginx.service
systemctl list-dependencies nginx.service
journalctl -u nginx.service -b --no-pager -n 120

Если что-то не стартует, начни с двух быстрых вопросов: «что упало» и «что в журнале этой загрузки».

systemctl --failed
journalctl -b -0 -p warning..alert --no-pager -n 200

Источники и ссылки


Комментарии загружаются…