Пара 9: systemd, граф запуска и журналы

90 минут · 3 курс, ML.

Содержание и результат

Запустить пользовательскую службу, проверить её статус и найти причину сбоя в journal.

План занятия

0–8: почему процесс пропал после закрытия shell. 8–30: PID 1, units, граф и зависимости. 30–45: start/enable и готовность. 45–60: журнал, рестарты, область --user. 60–80: исходная практика StudyPulse с проверкой HTTP; лабораторный граф зависимостей — для быстро завершивших. 80–90: объяснить After/Requires и доказательство готовности.

Практика выполняется в своей учебной папке и на localhost. Подготовка окружения описана в lab/README.md.

Менеджер служб

Запуск, остановка и журнал должны иметь одного понятного владельца.

systemd управляет службами через unit-файлы. ExecStart задаёт команду, WorkingDirectory — рабочую папку, Environment — настройки. Для курса используем пользовательскую службу: она не требует редактирования системных unit-файлов. absolute path интерпретатора надёжнее зависимости от активированного venv. systemctl --user daemon-reload перечитывает описание, start запускает, status показывает состояние. enable настраивает запуск согласно unit, но сам по себе не стартует без --now. Пользовательская служба зависит от пользовательского менеджера; не обещаем работу после выключения ноутбука или завершения WSL.

Аналогия: заведующий сменой знает, кого запустить и где искать отчёт.

Работает и готов — разные состояния

active не заменяет запрос к /healthz.

Процесс может существовать и не принимать запросы: ещё загружает модель, слушает другой порт или застрял. После systemctl status проверяем curl и ss. Restart=on-failure помогает после аварийного выхода, но не устраняет причину. Неправильная конфигурация может вызвать цикл рестартов; нужны пауза и ограничение попыток. На демонстрации ошибочный MODEL_PATH вызывает ранний отказ. Рестарт — инструмент восстановления, а журнал помогает понять, почему он понадобился. Не оцениваем сдачу только скриншотом active (running).

Аналогия: сотрудник пришёл на смену, но касса ещё не открыта.

journalctl: временная линия

Причину ищем рядом с моментом отказа.

journalctl --user -u studypulse выводит записи конкретной службы. -n ограничивает количество, -f показывает новые события. Полезны время, имя службы, сообщение об ошибке и код завершения. Наше приложение пишет структурированные JSON-строки в stdout: их собирает менеджер процессов. Не сохраняем содержимое чувствительных запросов в лог. Перед исправлением запишите точное наблюдение: не «не работает», а «служба завершилась, MODEL_PATH не найден». В WSL сначала проверьте, используется ли systemd; отсутствие менеджера — ограничение среды, а не ошибка приложения.

Аналогия: бортовой журнал объясняет последовательность, а не только итог.

Подробный разбор: systemd: от графа units до исправного сервиса; Логи ядра, служб и приложений

Что делает systemd и что остаётся ядру

systemd организует пользовательские службы, ядро управляет ресурсами.

Что делает systemd и что остаётся ядру

В Ubuntu systemd обычно является PID 1 системы: он запускает и наблюдает объекты пользовательской среды, собирает завершения процессов и организует остановку. Он не заменяет ядро и не планирует инструкции на CPU вместо Linux. Ядро предоставляет процессы, сигналы, файловые системы и cgroups; менеджер использует эти механизмы. Через cgroups можно учитывать не только один MainPID, но и потомков службы. Отдельный systemd --user управляет units конкретного пользователя. PID 1 имеет особую роль, поэтому не демонстрируем на нём kill. На схеме показаны связи управления, а не обещание, что каждый рабочий процесс навсегда остаётся прямым ребёнком PID 1: приложения могут создавать потомков и промежуточные процессы. Сервис запускать через менеджер удобнее, когда нужны наблюдение, журнал, политика остановки и воспроизводимая конфигурация.

Аналогия: диспетчер организует смены, инженерная система здания обеспечивает ресурсы. Менеджер не является электрической сетью.

Практическое следствие: Планировщик ядра. systemd задаёт организацию служб и может использовать ограничения ресурсов.

Unit — объект управления; service — процессная работа

target группирует, timer активирует, mount подключает файловую систему.

Unit — объект управления; service — процессная работа

Unit — общий формат описания управляемого объекта. Service задаёт способ запуска и наблюдения процесса или задачи, target объединяет units в логическую группу, timer планирует активацию, mount представляет монтирование. Есть также socket, path, device, slice и scope. Target не является отдельной программой, которая должна висеть в ps. Журнал — отдельная инфраструктура: stdout и stderr службы могут поступать в journal согласно её настройкам. В нашем сервисе оставляем процесс в foreground: менеджеру не нужен самодельный фон через &. Секция [Unit] содержит общие связи, [Service] — параметры выполнения, [Install] — инструкции создания связей при enable. Показать эти три секции полезнее, чем просить запомнить десятки директив без их роли.

Аналогия: расписание включает отделы, дежурства и события, но не каждое поле расписания — отдельный работник.

Практическое следствие: Нет. Это группирующий объект менеджера, а не обязательный процесс.

Requires и After отвечают на разные вопросы

Нужен ли другой unit — и в каком порядке выполнять задания.

Requires и After отвечают на разные вопросы

Requires=db.service включает db в транзакцию активации; After=db.service задаёт порядок, если оба unit участвуют. After сам по себе не запускает db. Requires без ordering не требует сначала закончить запуск db, поэтому обе службы могут стартовать параллельно. Для учебного обязательного подготовительного шага используем Requires вместе с After. Если подготовительный unit не сможет активироваться, упорядоченная зависимая служба не стартует; однако автоматическое обнаружение последующего «зависания» внешней БД этими директивами не обеспечивается. Wants слабее: запросить другую службу, но не делать её неуспешный запуск достаточной причиной провала своего старта. Реальное поведение также зависит от типа службы и условий; не превращаем две строки в обещание полной health-оркестрации. На доске рисуем разные цвета для включения в граф и для порядка.

Аналогия: «есть только после приготовления» не означает, что кто-то уже поручил повару приготовить.

Практическое следствие: Нет. Ordering ограничивает порядок присутствующих заданий; активация задаётся отдельно.

Загрузка — граф, а не один длинный shell-скрипт

Независимые работы можно выполнять одновременно.

Загрузка — граф, а не один длинный shell-скрипт

При запросе target менеджер собирает транзакцию заданий из зависимостей. Ordering задаёт допустимый порядок; независимые ветви могут выполняться параллельно. Поэтому полезно различать общую длительность запуска и критическую цепочку. systemd-analyze critical-chain показывает цепь задержек согласно данным менеджера, blame ранжирует длительности активации units, но не является автоматическим доказательством причины общей медленной загрузки. Служба может стартовать долго параллельно и не задерживать интересующий target. Граф на слайде намеренно учебный: состав реальных targets зависит от дистрибутива, режима и настроек. Уточняйте systemctl list-dependencies и эффективные unit-файлы вместо заучивания универсальной последовательности. Для WSL результаты не равны времени холодной загрузки физического ноутбука.

Аналогия: ремонт кухни и уборка кабинета идут одновременно; общий срок зависит от необходимых зависимостей, а не суммы всех работ.

Практическое следствие: Нет. Нужны зависимости и критический путь, а работа могла выполняться параллельно.

active ещё не значит «готов отвечать»

Событие старта зависит от Type; готовность проверяется по контракту.

active ещё не значит «готов отвечать»

Для Type=simple менеджер считает службу запущенной после создания основного процесса, до подтверждения успешного exec. Type=exec ждёт успешного выполнения exec, поэтому лучше обнаруживает ошибки запуска исполняемого файла. Ни один из этих типов не доказывает, что приложение закончило инициализацию и открыло порт. Type=notify ждёт уведомления READY=1 от поддерживающего это протокол приложения; нельзя просто поменять Type на notify у произвольного Python и ожидать успеха. Даже готовность менеджера не гарантирует правильный ответ на пользовательский запрос. В лабораторной используем статус, журнал, затем curl /healthz и реальный запрос /predict. Аналогично network-online.target зависит от настроенного wait-online механизма и не обещает бесконечную доступность внешнего API или Интернет-соединения.

Аналогия: работник пришёл, инструмент запущен и заказ успешно выполнен — три разные проверки.

Практическое следствие: Нет. Это свидетельство состояния менеджера; нужен запрос, который проверяет выбранный контракт.

start и enable не взаимозаменяемы

Сейчас работать и участвовать в будущей активации — разные свойства.

start и enable не взаимозаменяемы

Start запросит запуск unit сейчас. Enable использует [Install], чтобы создать необходимые связи, например target.wants/demo.service. Без --now enable не обязан запустить процесс немедленно, а start без enable не обеспечивает автоматический запуск после следующей обычной загрузки. Enable --now совмещает действия. Disabled не означает «невозможно запустить»: unit может быть запущен явно, через зависимость, socket или timer. Disable удаляет связи установки, но не обязан остановить уже работающую службу; --now дополнительно запрашивает остановку. После редактирования файла нужен daemon-reload, чтобы менеджер перечитал описание; это не рестарт приложения. В курсе избегаем изменения системного default target: достаточно пользовательского сервиса и проверки связей.

Аналогия: записать работника в завтрашнее расписание и вызвать его сейчас — отдельные операции.

Практическое следствие: Нет. Без --now установка связей не равна немедленному start; смотрим статус и журнал.

Restart не лечит причину сбоя

Пауза и лимит защищают от бесконечного быстрого цикла.

Restart не лечит причину сбоя

Restart=on-failure позволяет повторный запуск после заданных категорий неуспешного завершения. RestartSec задаёт задержку, а start rate limiting ограничивает слишком частые попытки. Если ExecStart указывает на несуществующий файл или конфигурация неверна, повторение не делает файл правильным. Сначала читаем journal и код завершения. Штатный systemctl stop обычно не активирует автоматический Restart даже при Restart=always. При остановке systemd обычно посылает TERM, ждёт TimeoutStopSec и при необходимости применяет финальный KILL согласно настройкам; KillMode определяет охват процессов, обычно control-group. Приложение обязано реализовать собственное корректное завершение. Не демонстрируем общий restart сервера: останавливаем только свой учебный unit. Восстановление должно подтверждаться запросом, а не только новым PID.

Аналогия: диспетчер снова вызывает мастера, но одинаково неверный адрес продолжит приводить к отказу.

Практическое следствие: Нет. Это может быть цикл падений; журнал и пользовательский путь покажут причину.

Состояние → журнал → конкретная проверка

Собираем доказательства одной причины, затем проверяем исправление.

Состояние → журнал → конкретная проверка

Systemctl status даёт удобный снимок, но его короткий журнал может обрезать важную строку. Systemctl show позволяет получить MainPID, ExecMainStatus, Result и другие свойства. Journalctl -u отбирает сообщения системной службы, для user-unit используем journalctl --user -u. -b ограничивает текущую загрузку, -n количество записей, --no-pager удобно для короткой демонстрации. Причины старта часто прозаичны: неверный абсолютный путь, отсутствие WorkingDirectory, права на файл, занятый порт или окружение, которое раньше задавал интерактивный shell. ExecStart не является обычной строкой Bash: pipe, > и && без shell не работают как ожидает новичок. Не добавляем sh -c без необходимости; конфигурируем рабочую папку и окружение явно. Перед публикацией журнала удаляем секреты и личные пути.

Аналогия: письменная инструкция должна включать то, что в привычной мастерской делалось молча.

Практическое следствие: Окружение, cwd, пользователь и способы разбора команды отличаются; нужно сравнить именно эти факты.

systemctl и systemctl --user управляют разными менеджерами

Системный сервер, сеанс пользователя и контейнер — разные области.

systemctl и systemctl --user управляют разными менеджерами

Пользовательский manager запускает units пользователя из ~/.config/systemd/user; systemctl --user обращается к нему. Системный manager использует системные пути и другой набор units. Перепутанный флаг часто даёт unit not found, хотя файл существует. Время жизни user-manager связано с сеансом и настройками lingering; включение lingering меняет поведение и не требуется бездумно для каждой лабораторной. В WSL дополнительно учитываем время жизни самой среды. В контейнере PID 1 может быть приложение или маленький init; Docker restart policy относится к контейнеру, а не автоматически к systemd-unit внутри него. В macOS аналог задач постоянных служб — launchd с plist и launchctl, в Windows — SCM с Windows service; обычный shell-скрипт ещё не превращается в нативную Windows service.

Аналогия: диспетчер здания и координатор вашей команды ведут разные расписания, даже когда называют задания одинаково.

Практическое следствие: Проверяется системный manager; для пользовательской службы нужен --user.

Логи нужно искать по источнику

kernel journal, journal службы и файл приложения — разные потоки.

Логи нужно искать по источнику

Dmesg читает сообщения ring buffer ядра, доступ может ограничиваться политикой системы. Journald собирает записи с metadata, включая службу, PID, boot и время; journalctl умеет фильтрацию по этим признакам. Journal хранится в бинарном формате: его не читают cat как текст. /run/log/journal предназначен для временного хранения; /var/log/journal используется для постоянного согласно Storage и конфигурации. Текстовые /var/log/syslog и /var/log/auth.log существуют при соответствующей syslog-конфигурации, а не обязаны присутствовать в каждой Ubuntu/container. Приложение может писать свой файл или stdout; Docker logs показывает stdout/stderr контейнера, но не любой файл внутри него. Ротация и retention ограничивают размер и срок хранения. Для расследования сохраняют контекст времени, версии и request_id, не раскрывая секреты. Смена timezone или неверные часы усложняет сопоставление.

Практическое следствие: journalctl -k -b; journalctl -u имя.service -b; journalctl --user -u studypulse.service; ls /var/log — каждый инструмент смотрит свою область.

Опыт: связать рисунок с наблюдением

ps -p 1 -o pid,comm,args
systemctl --user cat studypulse.service
systemctl --user show studypulse.service -p MainPID -p Result -p ExecMainStatus
journalctl --user -u studypulse.service -n 20 --no-pager
curl --fail http://127.0.0.1:8000/healthz
# Optional dependency exercise
python3 lab/experiments/systemd_graph.py

journalctl -k -b -n 20 --no-pager
journalctl --user -u studypulse.service -n 20 --no-pager
ls -lah /var/log
systemctl --user show studypulse.service -p StandardOutput -p StandardError

Используйте исходный unit StudyPulse из lab/README.md. Покажите [Unit], [Service], [Install] и объясните каждую уже использованную директиву. Сравните start с enable; после ошибки прочитайте journal, исправьте путь и подтвердите HTTP. Дополнительный опыт systemd_graph.py создаёт только уникальные временные user-units, сравнивает After с Requires+After и удаляет их после завершения. Запускается только в Ubuntu с доступным systemd --user. На Mac обсуждаем схему и изучаем launchctl list; чужие plist не меняем.

Найдите сообщение запуска StudyPulse по unit и времени, затем request_id в JSON-логе. Укажите, что переживёт reboot в текущей конфигурации, а не в предполагаемом универсальном Linux.

Источники подробного разбора

Команды и наблюдения

Окружение: Ubuntu / Bash с systemd.

ps -p 1 -o comm=
mkdir -p ~/.config/systemd/user
cp ~/devops-course/lab/studypulse.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user start studypulse
systemctl --user status studypulse --no-pager
journalctl --user -u studypulse -n 20 --no-pager
curl -i http://127.0.0.1:8000/healthz

Пример unit использует %h/devops-course/lab; путь к скопированному проекту должен совпасть. Предыдущий ручной запуск остановите, чтобы освободить порт.

Ожидаемый результат: PID 1 = systemd; служба active; HTTP healthz = 200. Если systemd недоступен, сначала исправляется конфигурация WSL.

Вариант для macOS

macOS использует launchd, не systemd. Unit-файл .service и journalctl не переносимы; у launchd plist, LaunchAgents/LaunchDaemons и другой lifecycle. На схеме сравниваем механизмы, а обязательную лабораторную systemd выполняем в Ubuntu. Учебный launchd-пример с проверкой приведён в отдельной странице сравнения ОС.

launchctl print gui/$(id -u)
# Linux-specific exercise: use an Ubuntu VM or the partner WSL2 setup

Практика: Сломанный запуск

  1. Запустите предоставленный user-unit и подтвердите ответ HTTP.
  2. В учебной копии unit задайте MODEL_PATH=/missing/model.json.
  3. Выполните daemon-reload и restart, найдите причину в журнале.
  4. Восстановите значение, повторите запуск и проверку healthz; затем stop.

Результат: Ошибка из журнала, исправление и HTTP-доказательство восстановления.

Проверка: Служба восстанавливается без root; студент различает start, restart и daemon-reload.

Неисправность для разбора: После правки unit забыли daemon-reload или порт занят ручным запуском.

Решение и диагностика

При отсутствии systemd следуйте официальной инструкции WSL: в /etc/wsl.conf секция [boot], systemd=true; затем wsl --shutdown в PowerShell останавливает все WSL-дистрибутивы, поэтому сначала сохраните работу. Для этой пары допускается демонстрация преподавателя, если среда не готова. После исправления unit: systemctl --user daemon-reload; systemctl --user restart studypulse; journalctl --user -u studypulse -n 20; curl .../healthz. В завершение systemctl --user stop studypulse.

Основные выводы

Самостоятельная работа

Добавить в README управление user-службой и диагностику занятого порта.

Источники