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