Оглавление

Этот выпуск «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