Systemd: полное руководство для админов + примеры
АВТОР: (с) eternaladm
2 сентября 2025 г. 13:00
В мире Linux и Unix-подобных систем исторически доминировала система инициализации SysVinit. Её процесс загрузки был последовательным и понятным: она запускала скрипты (обычно расположенные в /etc/rc.d/ или /etc/init.d/) по одному, в строгом порядке, определяемом симлинками в каталогах rcN.d.
Но с развитием Linux и увеличением сложности систем - недостатки SysVinit стали вполне очевидны:
Последовательный запуск: скрипты выполнялись один за другим. Если служба не зависела от другой - она всё равно ждала своей очереди, что замедляло загрузку.
Отсутствие контроля за процессами. Init только запускал скрипты, но не мог отслеживать состояние запущенных служб (упала? требует перезагрузки?).
Разрозненность логирования. Каждая служба вела логи "по-своему" (в /var/log/), не было какого-то единого централизованного интерфейса для просмотра логов.
Полагаю, именно эти проблемы привели к появлению альтернатив (Upstart, OpenRC), но именно systemd, представленный Леннартом Поттерлингом в 2010 году, стал неким "де-факто" стандартом для множества крупных дистрибутивов (Fedora, Debian, Ubuntu, Arch и др.). Он был разработан как кардинально новое решение для преодоления ограничений.
Технические аспекты systemd
1. Архитектура и основные компоненты
Systemd - не единая бинарная программа, а набор связанных компонентов, образующих "каркас" системы. Его ядро - демон systemd (PID 1), который является прямым потомком ядра и управляет всеми остальными процессами.
Ключевые технические компоненты:
systemd (PID 1): главный демон. Отвечает за запуск и поддержание юнитов, отслеживание процессов (через cgroups), обработку сигналов от ядра.
systemctl: основной CLI-инструмент для взаимодействия с демоном. Не просто запускает скрипты, а отправляет D-Bus-сообщения демону systemd, который выполняет запрошенные действия.
journald: демон журналирования. Интегрирован напрямую с демоном systemd, что позволяет захватывать stdout/stderr каждого сервиса и "обогащать" записи лога метаданными (например, SYSTEMDUNIT=ssh.service).
udev менеджер устройств. Управляет устройственными узлами в /dev, загружает firmware и модули ядра. Тесно интегрирован с systemd для активации устройств.
networkd, timedated, logind: специализированные демоны для управления сетью, системным временем и сеансами пользователей (логином).
2. Юниты
Юниты - абстракции системных ресурсов. Их конфигурация описывается в декларативных файлах (обычно в /usr/lib/systemd/system/, переопределяются в /etc/systemd/system/).
Основные типы юнитов:
Service (.service): cамый распространённый тип. Описывает процесс (демон) и команды для управления им (ExecStart=, ExecStop=).
Socket (.socket): описывает сокет (сетевой, IPC или FIFO). Механизм "socket-based activation" - ключевая особенность: демон systemd прослушивает сокет и запускает соответствующий сервис (.service) только при поступлении первого подключения. Это экономит ресурсы для редко используемых служб.
Timer (.timer): альтернатива cron. Позволяет планировать задачи на основе календарных выражений (OnCalendar=*-*-* 00:00:00) или относительных интервалов (OnBootSec=, OnUnitActiveSec=). Главное преимущество - полная интеграция с зависимостями и журналированием, как у любого другого юнита.
Mount (.mount) и Automount (.automount): точки монтирования. Automount обеспечивает отложенное монтирование в момент первого обращения к каталогу.
Path (.path): активирует сервис при изменении файловой системы (например, появлении файла в определённом каталоге). Аналог incron.
Target (.target): аналог runlevel из SysVinit, но более гибкий. Это группа юнитов, которую нужно достичь (например, multi-user.target — многопользовательский режим без GUI).
Структура файла юнита
Каждый файл разбит на секции. [Unit] содержит общие метаданные (описания, зависимости), а специализированная секция (например, [Service] или [Timer]) — параметры конкретного типа.
Пример:
/etc/systemd/system/app.service
[Unit] Description=My Custom Application After=network.target # Запускать после активации сети Requires=postgresql.service # Жёсткая зависимость Wants=redis.service # Мягкая зависимость [Service] Type=simple ExecStart=/usr/bin/myapp --serve Restart=on-failure # Автоматически перезапускать при падении RestartSec=5 User=myappuser Group=myappgroup [Install] WantedBy=multi-user.target # В какой "уровень" включить этот юнит при активации через `systemctl enable`
3. Зависимости и порядок запуска
Systemd строит ориентированный граф зависимостей между юнитами. Анализируя директивы After, Before, Requires, Wants, он вычисляет оптимальный порядок активации, позволяя запускать максимальное количество независимых юнитов параллельно. Не слепой параллелизм, а управляемое распараллеливание на основе явно заданных правил.
4. Cgroups и отслеживание процессов
Это фундаментальное техническое отличие от SysVinit. Systemd помещает каждый запущенный сервис в свою собственную cgroup (control group). Это позволяет:
Точно отслеживать все процессы службы: даже если главный процесс (ExecStart) породил множество дочерних процессов (fork), они все останутся внутри cgroup. Команда systemctl status точно покажет все процессы, принадлежащие сервису.
Изолировать и ограничивать ресурсы: через директивы юнита (MemoryMax=, CPUQuota=) можно легко установить лимиты на потребление памяти, CPU, I/O сервисом.
Гарантированно завершать все процессы службы: при остановке сервиса systemd гарантированно завершает все процессы в его cgroup, устраняя проблему "зомби"-процессов.
Теория реализуется на практике с помощью двух инструментов (systemd-cgls и systemd-cgtop), которые предоставляет systemd для визуализации и мониторинга контрольных групп.
Практическое использование: инструменты systemd-cgls и systemd-cgtop.
Systemd-cgls - показывает иерархическое дерево всех существующих cgroup в системе, аналогично тому, как ls показывает файлы. Это лучший способ понять и увидеть структуру, которую создаёт systemd. Посмотрим на примере:
Показать всё древо cgroup для сервиса nginx:
systemd-cgls /system.slice/nginx.service
Будут показаны все процессы в данной cgroup:
1234 /usr/sbin/nginx -g daemon off:
1235 nginx: worker process
1236 nginx: cache loader process
systemd-cgtop - показывает потребление ресурсов (CPU, память, ввод/вывод) всеми cgroup в реальном времени, аналогично тому, как top показывает это для процессов. Посмотрим пример вывода:
Control Group Tasks %CPU Memory Input/s Output/s /25715.8 3.9G 0B 0B /system.slice/nginx.service3 0.130.0M 0B 0B /system.slice/mysql.service 4512.5 1.1G 0B 0B /user.slice/user-1000.slice 120 3.2 800.0M 0B 0B
5. Журналирование (journald)
Бинарный формат логов: в отличие от текстовых файлов syslog, journald по умолчанию использует структурированный бинарный формат для хранения логов (обычно в /var/log/journal/). Это позволяет эффективно индексировать и запрашивать логи по метаданным.
Метаданные: каждая запись в журнале содержит не только сообщение, но и массу системной информации: PID, UID, SYSTEMDUNIT, EXE, CMDLINE, _HOSTNAME и т.д.
Запросы: инструмент journalctl позволяет строить сложные запросы, используя эти метаданные как поля для фильтрации.
Логи для nginx с PID 1234 и логи для php-fpm:
journalctl _SYSTEMD_UNIT=nginx.service _PID=1234 + _SYSTEMD_UNIT=php-fpm.service
6. D-Bus
Взаимодействие между systemctl, другими утилитами (journalctl, hostnamectl) и демоном systemd происходит через D-Bus (системную шину сообщений). Это обеспечивает стандартизированный, безопасный и гибкий IPC-механизм. Например, когда вы выполняете systemctl start nginx, systemctl отправляет D-Bus-сообщение методу StartUnit на системную шину, которое перехватывает и обрабатывает демон systemd.
Практическое применение
Теория - всегда хорошо, но куда без практики? Далее рассмотрим практические примеры применения "всего и вся".
1. Управление состоянием службы: запуск, остановка, перезагрузка
Данные команды управляют текущим, активным состоянием службы. Действуют немедленно, не влияют на автозагрузку при следующей перезагрузке.
Запускает службу немедленно:
systemctl start <service_name>
Например:
systemctl start nginx
Останавливает службу немедленно:
systemclt stop <service_name>
Например:
systemctl stop sshd
Полная перезагрузка службы. Последовательно выполнит stop, затем start. Применяется, когда необходима загрузка с "чистого" состояния:
systemctl restart <service_name>
Например:
systemctl restart apache2
Перезагружает конфигурацию службы без остановки основного процесса. Важно! Это не перезапускает дочерние службы. Сервис продолжает работать и обрабатывать запросы, лишь обновляя свои настройки. Применяется, когда необходимо внести изменения в конфиг для минимизации простоев:
systemctl reload <service_name>
Например:
systemctl reload nginx # обновится конфиг без разрыва соединений
Показывает подробный статус службы: # активен/неактивен, добавлен ли в автозагрузку, последние записи в лог, а также инфо о процессе:
systemctl status <service_name>
Например:
systemctl status cron
2. Управление автозагрузкой
Данные команды управляют тем, будет ли служба запущена автоматически при загрузке системы или при определенной цели (target). Не запускают и не останавливают службу немедленно.
Включает автозагрузку службы при старте системы:
systemctl enable <service_name>
Что она делает? Создает символические ссылки (симлинки) из юнит-файла службы (обычно в /usr/lib/systemd/system/) в соответствующие каталоги .wants/ для целей (например, multi-user.target.wants/ или graphical.target.wants). При запуске цели система видит эти ссылки и запускает все необходимые службы.
Отключает автозагрузку службы:
systemctl disable <service_name>
Что делает? Удаляет символические ссылки, тем самым исключая саму службу из процесса автозагрузки.
Показывает статус автозагрузки службы. # Например: enabled, disabled, static, masked:
systemctl is-enabled <service_name>
Использование в скрипте:
if ! systemctl is-enabled --quiet nginx; then systemctl enable nginx; fi
Также, хочу упомянуть про "маскировку", которая является "абсолютным запретом". Это альтернатива disable. Disable лишь убирает симлинки, но сервис все еще можно запустить вручную через start. Mask создает симлинк на /dev/null, что делает запуск сервиса невозможным в принципе. Это критически важно для сервисов, которые конфликтуют между собой (например, разные сетевые менеджеры NetworkManager и systemd-networkd).
Полностью запрещает запуск службы (любым способом):
systemctl mask <service_name>
Разрешает запуск службы (обратное действие):
systemctl unmask <service_name>
Например: если Вы используете networkd, стоит замаскировать NetworkManager:
systemctl mask NetworkManager.service
3. Просмотр состояния системы и служб
Эти команды помогают получить общую картину о состоянии всех служб в системе.
Показывает краткий обзор состояния системы: список загруженных юнитов, состояние по умолчанию, журналы:
systemctl status # без аргументов
Возвращает состояние службы (active, inactive, activating, failed). Вывод краткий, подходит для скриптов:
systemctl is-active <service_name>
Например:
systemctl is-active docker # вернет active или inactive
Проверяет, находится ли служюа в состоянии сбоя. Возвращает failed (если не запустилась) или active/inactive. # Полезно для автоматического оповещения:
systemctl is-failed <service_name>
Показывает все запущенные в данный момент сервисы. Полезная команда для получения списка того, что работает на сервере:
systemctl list-units --type=service --state=running
Альтернативный короткий синтаксис:
systemctl --type=service --state=running
Покажет все доступные сервисы и статус их автозагрузки (enabled, disabled, static, masked). Команда показывает, будет ли запущена служба при загрузке, а не в данный момент:
systemclt list-unit-files --type=service
Альтернативный короткий синтаксис:
systemctl --type=service --all
Статус static означает, что юнит-файл не имеет секции [Install], поэтому его нельзя "enable". Обычно запускается как зависимость другого сервиса.
Далее, в этом же блоке, стоит обратить внимание на возможность просмотра зависимостей.
Показывает, от каких юнитов зависит данный сервис:
systemctl list-dependencies <service_name>
Показывает, какие юниты зависят от данного сервиса (кто его требует):
systemctl list-dependencies --reverse <service_name>
Показывает дерево зависимостей в виде древовидной структуры:
systemctl list-dependencies --tree <service_name>
Например:
systemctl list-dependencies graphical.target --tree | head -20
А также:
--all - показать все зависимости (включая неактивные)
--reverse или -r - показать, какие юниты зависят от указанного (кто использует эту службу)
--before / --after - показать юниты, которые запускаются "до или после" указанного
Приведу конкретный пример для лучшего понимания. Например, необходимо проверить, какие службы зависят от сетевого менеджера:
systemctl list-dependencies --reverse NetworkManager.service
Это поможет понять, какие службы перестанут работать корректно, если остановить NetworkManager.
4. Просмотр логов конкретной службы
Основной инструмент для работы с журналами в systemd - journalctl. Все команды обычно требуют для просмотра полного лога.
Показывает все записи в журнале (логи) для указанной службы за всё доступное время:
journalclt -u <service_name>
Показывает логи службы с указанного времени:
journalctl -u <service_name> --since today
Например:
journalctl -u sshd --since "1 hour ago" # что происходило с SSH за последний час?
Фильтрация по времени позволяет сузить ввод, дабы не листать массивные файлы журналов. Вместо today можно использовать yesterday, "2025-01-01 00:00:00" и тд.
"Режим" реального времени (follow). Команда выводит текущие логи и продолжает показывать новые записи по мере их появления:
journalctl -u <service_name> -f
Например:
journalctl -u apache2 -f # наблюдать за логами Apache в реальном времени
5. Поиск причин проблем с загрузкой
Эти команды помогают быстро найти службы, которые не смогли запуститься, и понять общую картину здоровья системы.
Быстрый просмотр всех юнитов (не только служб), которые находятся в failed:
systemctl --failed
Покажет сообщения журнала с уровнем важности "ошибка" и выше (crit, alert...):
journalctl -p err..alert
Показывает все логи с момента последней загрузки системы. Полезно для анализа проблем, которые могли возникнуть в момент старта системы:
journalclt -b
Например:
journalctl -b -1 # Что привело к падению системы в прошлый раз?
6. Анализ времени загрузки
Следующий блок - инструменты для анализа производительности и поиска "узких мест" в процессе загрузки.
Показывает общее время, затраченное на загрузку ядра и пользовательского пространства. Полезно для формирования общего представления о скорости загрузки:
systemd-analyze
Пример вывода:
Startup finished in 4.567s (kernel) + 1min 12.345s (userspace) = 1min 16.912s
Демонстрирует скисок всех сервисов, которые дольше всего грузились. Сортирует по времени их инициализации:
systemd-analyze blame
Команда очень полезная, тк позволяет сразу увидеть, какая служба задерживает старт системы (к примеру, медленный сервис или антивирус). Помогает в оптимизации времени загрузки.
Визуализирует цепочку запуска для конкретного сервиса:
systemd-analyze critical-chain <service_name>
Например:
systemd-analyze critical-chain graphical.target # Почему граф. интерфейс так долго грузится?
Данная команда показывает зависимости: какие службы должны были запуститься до него, сколько времени занял каждый шаг. Выделяет "узкие места", задерживающие старт целевой службы.
КРАТКАЯ ШПАРГАЛКА ДЛЯ АНАЛИЗА
Посмотреть логи службы:
journalctl -u <имя_службы>
Логи в реальном времени:
journalctl -u <имя_службы> -f
Кто не запустился?
systemctl --failed
Критические ошибки:
journalctl -p err..alert
Логи прошлой загрузки:
journalctl -b -1
Что грузилось дольше всех?
systemd-analyze blame
Почему грузился X?
systemd-analyze critical-chain
РАЗБОР ПРИМЕРОВ
1. Создание и настройка простого сервиса
Итак, задача следующая: создать службу, которая запускает простой эхо-сервер на Python и гарантирует его работу (рестартит после падения).
Создаем скрипт сервиса (/usr/local/bin/echo-server.py):
#!/usr/bin/env python3
import socket
HOST = '0.0.0.0'
PORT = 12345
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((HOST, PORT))
s.listen()
print(f"Echo server listening on {HOST}:{PORT}")
while True:
conn, addr = s.accept()
with conn:
print(f"Connected by {addr}")
data = conn.recv(1024)
if not data:
break
conn.sendall(data)
Создаем юнит-файл (/etc/systemd/system/echo.service):
[Unit] Description=Simple Echo Server Documentation=https://example.com # Запускаться после того, как сеть будет готова After=network.target [Service] Type=simple # Указываем полный путь к интерпретатору и скрипту ExecStart=/usr/bin/python3 /usr/local/bin/echo-server.py # Перезапускать сервис всегда, если он завершился не так, как ожидалось Restart=on-failure # Ждать 5 секунд перед перезапуском RestartSec=5 # Запускать от имени непривилегированного пользователя для безопасности User=nobody Group=nogroup # Важные настройки безопасности для изоляции сервиса # Запрещаем любые права root NoNewPrivileges=yes # Ограничиваем возможности процесса CapabilityBoundingSet= # Запрещаем запись в большинство файловых систем ProtectSystem=strict ProtectHome=read-only PrivateTmp=yes [Install] WantedBy=multi-user.target
Применяем конфигурацию и управляем сервисом. Перезагружаем конфигурацию systemd для чтения нового юнита:
systemctl daemon-reload
Включаем автозагрузку сервиса:
systemctl enable echo.service
Запускаем сервис:
systemctl start echo.service
Проверяем статус и логи:
systemctl status echo.service
journalctl -u echo.service -f
Тестируем работу эхо-сервера:
echo "Hello, Habr!" | nc localhost 12345
Проверяем, что сервис перезапускается. Симулируем падение:
kill -TERM $(systemctl show echo.service --property=MainPID --value)
Через 5 секунд проверяем статус снова - сервис должен быть активен:
systemctl status echo.service
В данном примере я продемонстрировал создание сервиса с нуля, настройку политик, перезапуск и некоторые настройки безопасности, которые следует использовать по умолчанию.
2. Ограничение ресурсов сервиса с помощью cgroups
Задача: необходимо ограничить потребление памяти эхо-сервером до 50МБ и ограничить использование CPU, к примеру, до 50% одного ядра.
Модифицируем юнит-файл сервиса (/etc/systemd/system/echo.service), добавляя в секцию [Service]:
[Service] # Memory Limits # Жесткий лимит памяти. Процесс будет убит (OOM Killer), если превысит его. MemoryMax=50M # Мягкий лимит. Systemd начнет агрессивно пытаться освободить память (например, сбрасывая кеши), # если процесс превысит этот лимит, но не убьет его сразу. MemoryHigh=40M # CPU Limits # Ограничивает использование CPU до 50% одного ядра. CPUQuota=50%
После изменения файла не забываем выполнить:
systemctl daemon-reload
systemctl restart echo.service
Проверяем ограничения:
Смотрим, в какую cgroup помещен наш сервис:
systemd-cgls /system.slice/echo.service
Мониторим потребление ресурсов этой cgroup в реальном времени:
systemd-cgtop -p /system.slice/echo.service
Можно посмотреть параметры cgroup напрямую:
cat /sys/fs/cgroup/system.slice/echo.service/memory.max
Должно быть: 52428800 (50 МБ в байтах)
3. Поиск и устранение проблемы с помощью journalctl
Задача: найти причину падения службы my-app.service (запускается, но сразу падает).
1. Смотрим статус - видим, что сервис в состоянии failed:
systemctl status my-app.service
В выводе часто уже есть последняя строка ошибки.
2. Смотрим логи службы за последний запуск:
journalctl -u my-app.service -n 20 --no-pager
3. Если лога нет или его недостаточно, смотрим логи с момента последней загрузки с подробным выводом:
journalctl -u my-app.service --since "10 min ago" -o verbose
Ключ `-o verbose` покажет все метаданные (например, UID, GID, командную строку), что может быть критически важно для диагностики.
4. Допустим, в логах видна ошибка подключения к порту 5432. Ищем, что происходило с PostgreSQL (который должен предоставлять этот порт) в то же время:
journalctl -u postgresql.service --since "10 min ago" | tail -20
5. Или ищем все сообщения об ошибках (уровень ERR и выше) в системе за этот период:
journalctl -p err..alert --since "10 min ago"
6. Если проблема в разрешении зависимостей, смотрим, что мешает запуску. Включаем подробный вывод systemd при запуске сервиса:
systemctl status my-app.service -l --no-pager
Или пытаемся запустить вручную с выводом в консоль:
systemctl start my-app.service
Journalctl с правильными ключами фильтрации (-u, -p, -since, -o) - главный и невероятно полезный инструмент диагностики проблем со службами.