SystemD

Загрузка

Материал упоминает устаревшие версии и утилиты: старая версия Ubuntu, старая версия Debian, init.d (сейчас — systemd). Проверьте актуальность для вашей системы.

systemd — система инициализации и менеджер служб Linux. Это первый процесс в системе (PID 1): ядро запускает именно его, а он поднимает всё остальное — службы, точки монтирования, сокеты, таймеры.

Слово «инициализация» тут узкое: systemd управляет не только запуском демонов, но и монтированием файловых систем, сетевыми сокетами, устройствами, расписанием задач, журналом и учётными сессиями. Это и главная причина споров вокруг него.

Зачем его придумали

Как было до него: SysVinit

Традиционная инициализация (init из System V) работала так:

  • скрипты лежали в /etc/init.d/;
  • порядок запуска задавался символическими ссылками в /etc/rc3.d/: S20nginx, S21postgresql, а остановка — K20nginx;
  • номера в именах (S20, S21) и были «зависимостями» — «запусти меня после того, у кого номер меньше»;
  • всё это были shell-скрипты, и запускались они по очереди.

Отсюда — проблемы, которые чувствовал каждый:

  • медленно: пока не завершится одна служба, следующая не начнётся, хотя они не зависят друг от друга;
  • нет настоящих зависимостей: «после» вместо «после и потому что»; служба могла стартовать, когда нужная ещё не готова;
  • нет перезапуска: упавший демон никто не поднимал;
  • нет учёта процессов: служба, оставившая «детей», при остановке не убивала их;
  • нет ограничений ресурсов: сказать «этой службе не больше 512 МБ» было негде;
  • нет единого формата: каждый скрипт писался как хотел автор.

Попытка исправить это — Upstart (Ubuntu, 2006): события («появилось устройство», «поднялся сетевой интерфейс») вместо номеров. Идея хорошая, но реализация ограниченная, и её не приняли вне Ubuntu.

systemd

В 2010 году Леннарт Пёттеринг (Red Hat) представил systemd. Идеи, которые он принёс:

  • параллельный запуск: службы стартуют одновременно, а порядок задаётся зависимостями, а не номерами;
  • активация по требованию: служба поднимается, когда к её сокету пришло соединение, а не «на всякий случай при загрузке»;
  • юнит-файлы — один декларативный формат вместо сотен скриптов;
  • cgroups — ядро отслеживает все процессы службы, поэтому остановка действительно останавливает всё, а лимиты ресурсов становятся возможными;
  • перезапуск при падении (Restart=on-failure);
  • журнал (journald) вместо разрозненных текстовых логов.

Приняли его быстро: Fedora 15 (2011), RHEL 7, Debian 8 (2015), Ubuntu 15.04, Arch, openSUSE. Сегодня systemd стоит практически везде — даже там, где его когда-то не хотели.

Чем недовольны

Честно говоря, критика обоснована: systemd занял области, которые раньше были отдельными программами (udev, journald, logind, networkd, resolved, timesyncd). Кому-то это «монолит», кому-то — «слишком много прав у PID 1». Плюс журнал в двоичном формате и объём конфигурации. Но взамен получены скорость, надёжность и единый способ управлять системой.

Как systemd запускается

  1. Ядро запускает /sbin/init (в большинстве дистрибутивов это ссылка на systemd) — процесс получает PID 1.
  2. systemd читает юниты и строит граф зависимостей.
  3. Достигается default.target — обычно ссылка на graphical.target или multi-user.target.
  4. Дальше работают пользовательские сессии, службы и всё остальное.

Цели загрузки (targets) выстроены в цепочку:

sysinit.target → basic.target → multi-user.target → graphical.target
                                    │
                          rescue.target / emergency.target
  • sysinit.target — самое необходимое: монтирование, устройства;
  • basic.target — базовые службы и сокеты;
  • multi-user.target — обычная система без графики (серверный режим);
  • graphical.target — то же плюс графический вход;
  • rescue.target — однопользовательский режим (корень на запись, минимум служб);
  • emergency.target — только оболочка (корень только для чтения).

Юниты: из чего состоит systemd

Юнит — это описание одного ресурса: что запустить, от чего зависит, что делать при сбое. Юнит-файл состоит из секций:

[Unit]
Description=Мой сервис
After=network.target

[Service]
ExecStart=/usr/local/bin/my-service
Restart=on-failure

[Install]
WantedBy=multi-user.target

[Unit] — общие свойства и зависимости, [Service] — что и как запускать (у других типов — своя секция), [Install] — в какую цель включается.

Типы юнитов: назначение и что было бы без них

service — службы

Самый частый тип: демоны и одноразовые задачи. Пример — nginx.service, ssh.service.

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

Без него: демоны пришлось бы поднимать руками — скриптом, из rc.local или по крону. Пользователь получил бы: после перезагрузки сайт не поднялся (забыли запустить), упавший веб-сервер лежит до утра (никто не перезапустил), а остановка «вручную» оставляет висеть дочерние процессы.

socket — сокеты

Юнит, который держит сокет и запускает службу при первом подключении (активация по требованию). Классический пример — sshd.socket.

Что даёт: служба не занимает память, пока её не позвали; соединение не теряется, пока служба стартует; если служба упала — следующее подключение поднимет её заново.

Без него: службы пришлось бы запускать всегда при загрузке, «на всякий случай». На сервере с десятками служб это лишняя память и лишнее время загрузки; а упавшая служба так и осталась бы лежать — подключиться к ней уже нельзя.

target — цели (группы)

Не действие, а точка синхронизации: «система дошла до этого состояния». Юниты включаются в цели, а цель может зависеть от других целей.

Что даёт: понятные состояния системы (multi-user, graphical, rescue) и точку, к которой привязываются остальные юниты.

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

device — устройства

Юниты устройств systemd получает от udev, когда ядро сообщает о появлении оборудования. Имя — по шине и адресу, например dev-sda1.device.

Что даёт: можно сказать «запусти это после появления диска» или «останови, если диск исчез» (BindsTo=).

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

mount и automount — монтирование

mount — точка монтирования (systemd сам создаёт такие юниты из /etc/fstab), automount — монтирование по требованию: раздел подключается, когда к нему впервые обратились.

Что даёт: зависимости и порядок для файловых систем, монтирование по обращению, автоматический перезапуск неудачного монтирования.

Без него: /etc/fstab всё равно монтировал бы разделы, но без порядка и зависимостей: служба могла стартовать раньше, чем смонтирован её каталог данных, и упасть. Пользователь видел бы «случайные» падения при загрузке, зависящие от скорости диска. А монтирование по обращению (например, сетевой папки) пришлось бы настраивать через autofs.

swap — подкачка

Активация файлов и разделов подкачки (тоже генерируется из /etc/fstab).

Что даёт: подкачка включается в нужный момент и учитывается в зависимостях.

Без него: swap пришлось бы включать вручную (swapon) — то есть после перезагрузки система осталась бы без подкачки, и при нехватке памяти процессы убивались бы ядром (OOM) вместо ухода в swap.

path — слежение за путями

Юнит, который следит за файлом или каталогом и запускает что-то при изменении: PathExists=, PathChanged=, DirectoryNotEmpty=.

Что даёт: реакция на событие сразу, а не по расписанию.

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

timer — таймеры

Расписание для юнитов. Умеет не только календарное время (OnCalendar=), но и относительное: «через 5 минут после загрузки» (OnBootSec=).

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

Без него: остался бы cron. Он работает, но не умеет «через 5 минут после загрузки», не знает зависимостей (задача может стартовать, когда нужная служба ещё не поднялась), не пишет в журнал systemd, а на выключенной машине пропущенная задача просто не выполнится — тогда как Persistent=true у таймера догоняет её при следующем включении.

slice — срезы (cgroups)

Иерархия групп для ограничения ресурсов: system.slice, user.slice, а внутри — свои срезы.

Что даёт: «этой группе служб — не больше 2 ГБ памяти и 50 % CPU»; видно, кто сколько потребляет.

Без него: ограничивать можно было бы только отдельные процессы (ulimit), и то по одному: служба с десятком дочерних процессов осталась бы неуправляемой, и один «прожорливый» сервис мог бы утянуть всю машину.

scope — области

Группа процессов, запущенных извне systemd (например, сессия SSH или контейнер), но учитываемая в его дереве.

Что даёт: такие процессы тоже видны в cgroups и подчиняются общим правилам.

Без него: процессы, запущенные вручную, жили бы «сами по себе»: их нельзя было бы учесть в лимитах, а при остановке сессии они могли остаться висеть.

snapshot — снимок

Сохранение текущего состояния юнитов, чтобы потом вернуться к нему.

Что даёт: быстрый откат набора запущенных служб.

Без него: пришлось бы вручную записывать, что было запущено, и так же вручную возвращать. Используется редко — но без него такой возможности нет вовсе.

Практика

Посмотреть, что в системе:

# все активные юниты
systemctl list-units

# только службы / только таймеры
systemctl list-units --type=service
systemctl list-timers

# все установленные юниты (включая выключенные)
systemctl list-unit-files

# чем загружена система и что её тормозит
systemctl get-default
systemd-analyze blame
systemd-analyze critical-chain

# зависимости юнита
systemctl list-dependencies nginx.service

Управление:

systemctl status nginx
sudo systemctl start nginx
sudo systemctl stop nginx
sudo systemctl restart nginx
sudo systemctl enable nginx     # включать при загрузке
sudo systemctl disable nginx
sudo systemctl mask nginx       # запретить запуск вообще
systemctl cat nginx             # показать юнит-файл
systemctl show -p MemoryMax nginx

Свой сервис и таймер:

# /etc/systemd/system/my-backup.service
[Unit]
Description=Резервная копия
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh

[Install]
WantedBy=multi-user.target
# /etc/systemd/system/my-backup.timer
[Unit]
Description=Резервная копия каждую ночь

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now my-backup.timer
systemctl list-timers

Правка существующего юнита — через drop-in, не изменяя файл пакета:

sudo systemctl edit nginx

это создаст /etc/systemd/system/nginx.service.d/override.conf; после правки — sudo systemctl daemon-reload.

Что запомнить

  • systemd — первый процесс (PID 1) и менеджер всего, что поднимается после ядра.
  • Его придумали, чтобы уйти от последовательных shell-скриптов SysVinit: параллельный запуск, зависимости, перезапуск, единый формат, журнал.
  • Юнит — описание ресурса; типы: service, socket, target, device, mount, automount, swap, path, timer, slice, scope, snapshot.
  • Отсутствие любого типа — это не «мелкое неудобство»: без socket теряется запуск по требованию, без timer — задачи «после загрузки», без slice — лимиты ресурсов, без device — реакция на железо.
  • systemd-analyze blame показывает, что именно тормозит загрузку.

Смотрите также

  • Ядро — что запускает systemd первым
  • Загрузка — весь процесс по шагам
  • Splash — заставка, которую завершает запуск служб
Оценка статьи 0

Комментарии

Пока комментариев нет.