Systemd для начинающих: разбор юнитов и их функций

431c208edcd03f130babea024dc63b87

На связи Егор из блока поддержки платформы F5, сегодня предлагаем вспомнить основы systemd. Поскольку утилит и возможностей в нем много, а новые функции добавляются регулярно, следить за всем этим может быть непросто, так что начнем с основ.

Systemd — это init процесс, который запускается при загрузке ядра операционной системы linux и его задачей является форкать другие процессы. Информацию о том, какие процессы необходимо исполнить он берет из, так называемых «юнитов» (systemd unit — https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html). Юниты бывают различных типов, каждый из которых выполняет определенную задачу.

Имя каждого файла юнита включает соответствующее расширение, например, nginx.service или nginx.socket. Эти файлы размещаются в определенных каталогах, по умолчанию в /etc/systemd/system/, /lib/systemd/system/ и некоторых других. Чтобы узнать точные пути, используйте утилиту systemd-path, которая перечисляет все пути, используемые различными демонами.

Если вы создаете или редактируете юниты самостоятельно, они обычно располагаются в каталоге /etc/systemd/system/. Юниты, идущие в составе пакетов обычно находятся в каталоге /lib/systemd/system/ или /usr/lib/systemd/system/. В последних версиях systemd каталоги /lib, /bin или /sbin являются симлинками на соответствующие каталоги в директории /usr. В некотором роде, это отход от стандарта FHS, который изначально определял роли для каждого каталога в корне дистрибутива.

Основной юнит, с которым работают администраторы, это юнит типа .service — это конфигурационный файл в формате ini, который описывает, какой процесс запустить (путь до бинарного файла) и параметры его запуска, такие как некоторые элементы безопасности, ограничения, параметры окружения и флаги. Параметров может быть много, поэтому изучение документации systemd занимает немало времени, к тому же она является в основном сухим перечислением функционала и не всегда понятно как именно на практике использовать те или иные фичи.

Основные типы юнитов

service: Управляет запуском сервисов при помощи определения команд для старта (ExecStart=, ExecStartPre=, ExecStartPost=), условий для запуска (ExecCondition=), рестарта (Restart=, RestartMode=), остановки (ExecStop=, ExecStopPost=) и релоада конфигурации (ExecReload=), а также параметров изоляции процесса при помощи параметров, которые наследуются в слайс сервиса.

timer: Аналог cron для планирования задач. Его большим преимуществом является предсказуемое поведение stdout и stderr, которые по умолчанию выводятся в journald. Сам по себе он не содержит команды, а только инструкцию для времени срабатывания и указатель на .service, который необходимо запустить. Если сервис не указал, сопоставление может работать по совпадению имени юнита (например dehydrated.timer и dehydrated.service)

slice: Описание группы для управления ограничениями на процесс при помощи cgroup, которая управляется systemd. Важно отметить, что systemd управляет cgroups по умолчанию монопольно, хотя docker и другие инструменты могут использовать свои собственные механизмы управления, однако там появляются нюансы наследования этих cgroup. Вы можете это встретить, например, в настройке cgroup-драйвера в kubelet и containerd, где необходимо синхронно указать кто будет управлять cgroup — systemd или docker. Разработчики systemd настоятельно рекомендуют использовать для управления cgroup именно systemd, если он используется как init-система.

scope: Описание процессов, запущенных не через systemd, но попадающих в определенную cgroup. Например при логине пользователя на его сессию запускается .scope, в который попадают запущенные в ней процессы. Нужно это для того, чтобы иметь возможность управлять этими процессами с точки зрения ограничения ресурсов, квот и тд.

mount/automount: Управляет точками монтирования файловых систем, аналогично правилам в fstab.

•  socket/path: Описание файлового сокета или путь до файла. С помощью сокетов можно реализовать активацию по сокету (socket activation), когда процесс запускается только при первом запросе к сокету. Например, в docker демон запускается через socket activation, когда docker-клиент делает любой запрос. В результате даже если docker демон у вас выключен, введя любой запрос (например docker ps) — docker запустится и ваш клиент получит ответ. Аналогично работает активация по пути (path-based activation)

•  target — это объединение нескольких сервисов: вы можете таким образом настраивать окружение или совокупность юнитов. Например, если у вас есть несколько демонов, работающих вместе (в моем случае, это элементы control plane в kubernetes и woker plane), вы можете объединить их в один target. Затем, используя команду systemctl start .target, вы запускаете все дочерние юниты, входящие в этот target. Это также позволяет вам отслеживать статус выполнения. Также состояние нормального состояния запущенной системы определяется через таргет, например обычная загрузка ОС со всеми сервисами, добавленными в автозагрузку, управляются с помощью multi-user.target.

device — это прокидывание устройств в систему из udev. Как правило, эти юниты не настраиваются вручную, они добавляются автоматически.

Чтобы увидеть все юниты, вы можете использовать утилиту systemctl. Управление этими юнитами происходит через команды systemctl start, systemctl stop, systemctl restart — если это описано в соответствующем юните сервиса. Также с помощью systemctl enable можно добавить юнит в автозагрузку при старте системы. По сути, под капотом создается символьная ссылка на определенный target.wants, который применяется при старте системы. Подробности этого процесса мы сейчас не будем рассматривать.

Все логи отправляются в journald

Это демон systemd, который обычно устанавливается вместе с systemd. Он перехватывает все системные сообщения через /dev/syslog, собственный сокет и некоторые другие пути, и вы можете просматривать все логи через утилиту journalctl.

Сразу отмечу для начинающих, что стоит выучить команду journalctl -efu <имя_сервиса>. Она позволяет перейти в конец лога (флаг -e), чтобы не пролистывать его вручную. Флаг -f (follow) позволяет следить за логами в режиме реального времени, как если бы вы использовали команду tail -f. Вместо -u <имя_сервиса> указывайте имя юнита с расширением. Если ваш тип юнита это .service, то вы можете указывать только имя. Если вы хотите посмотреть логи всех юнитов, используйте опцию -efa. Также доступна опция -t <тег>, которая фильтрует логи по тегу — это полезно, если у вас настроен, к примеру, apparmor, который выводи аудит событий с тегом audit. 

По умолчанию логи в journald настроены так, что они сохраняются только с момента последней загрузки операционной системы. После перезагрузки логи теряются. Чтобы этого избежать, нужно настроить персистентное хранение логов в конфиге /etc/systemd/journald.conf (параметр по-умолчанию Storage=). В случае с auto, персистентность будет работать в случае существования каталога /var/log/journal.

Стоит также упомянуть, что даже если вы не запускаете процессы через systemd, вы можете перенаправлять логи через journald c помощью утилиты systemd-cat. Например, если вы запустите команду systemd-cat с вашим приложением в качестве аргумента и его stdout и stderr будет выводиться в journald, после чего вы сможете просматривать логи через journalctl. Основное преимущество в этом случае — централизованное хранение логов, что исключает их случайную потерю.

Еще один способ работы с systemd — это использование команды systemd-run. Это удобно, если вы не хотите создавать отдельный юнит. systemd-run позволяет запустить процесс с определенными параметрами, которые вы обычно задаете в юнит-файле. Например, я использовал эту функцию, когда мне нужно было запустить процесс с ограничениями по памяти для node.js-сборки, которая потребляла много ресурсов cpu. Таким образом, вы можете управлять приложениями, не создавая дополнительных юнитов. В большинстве случаев этого знания уже достаточно для повседневного использования systemd. Много ли вы знаете способов без проблем и хардкода руками в cgroup управлять потреблением ресурсов запущенными процедурами? По-моему, systemd в этом плане очень помогает.

Про утилиты

Что еще можно сделать с помощью systemd? Например, есть утилита systemd-tmpfiles. Не позволяйте ее имени вводить вас в заблуждение. Недавно был целый холивар на тему того, что эта служба, якобы, удаляла домашние директории при флаге --remove. Однако, этот демон не занимается созданием временных каталогов. Это просто демон, который из конфигурации создает определенные каталоги. Она обычно используется для управления каталогами пользователей в некоторых дистрибутивах.

Вы можете и использовать systemd-userdbd для создания пользователями и группами при помощи json конфигов в каталоге /etc/userdb/ (для этого потребуются правки в /etc/nsswitch.conf). А демон systemd-homed поможет вам использовать защищенные каталоги пользователей, которые будут храниться в зашифрованном образе.Вы можете задавать атрибуты пользователей и управлять ими с помощью команды loginctl или userdbctl.

Также можно монтировать и размонтировать файловые системы с помощью утилиты systemd-mount, которая работает аналогично команде mount в Unix. Важно учитывать что эта команда автоматически при монтировании каталога будет создавать .mount и .automount юниты.

Для настройки корректной работы сети можно и нужно использовать systemd-networkd для настройки сетевых устройств с помощью конфигов в /etc/systemd/system/ расширения .network и .netdev, systemd-resolved для настройки локального резолвера (который в ubuntu, например, используется по умолчанию), а также systemd-timesyncd для синхронизации часов по протоколу ntp.

Клиентские утилиты для этих сервисов позволяют посмотреть текущую конфигурацию: networkctl, resolvectl, timedatectl.

Для определения, работаете ли вы в среде виртуализации, существует утилита systemd-detect-virt. Она помогает определить, находится ли система в контейнере или виртуальной машине.

Наконец, systemd-ask-password используется для интерактивного запроса пароля. Это полезно, когда нужно ввести пароль в скрипте без отображения символов на экране.

Что касается контейнеризации, systemd включает systemd-nspawn, который обеспечивает контейнеризацию и изоляцию процессов (управляется через утилиту machinectl. Также в последней версии systemd научился запускать qemu виртуальную машину через подобный механизм). Это аналог lxc контейнеров, которые обязаны включать в своем образе systemd и dbus. Также для более интегрированных с хостовой системой окружений вы можете использовать portable services (клиентской утилитой в этом случае выступает portablectl). А также использовать extensions images для расширения хостового набора бинарей при помощи systemd-sysext или systemd-confext. Эти инструменты требуют более углубленного разъяснения за пределами этой статьи, особенно учитывая предвзятое отношение к systemd у комьюнити.

Среди интересных механизмов также можно упомянуть systemd-cgtop и systemd-cgls. В них вы можете наблюдать запущенные cgroup в вашей системе. Учитывая что каждый сервис в системе по-умолчанию создает свой собственный слайс в cgroup, то это супер удобно, если необходимо проанализировать потребление ресурсов на хосте. Даже если вы используете docker, весьма вероятно что вам помогут эти утилиты, присмотритесь к ним.

Для проверки настройки изоляции в .service юнитах можно использовать утилиту systemd-analyze. Она покажет, насколько безопасно настроены ваши юниты и изоляция файловой системы, оценивая их в условных единицах. Это полезный инструмент для оценки уровня безопасности и корректности конфигурации. Также эта утилита может приоткрыть вам глаза на то, насколько ментейнеры пакетов в дистрибутивах заботятся о том, чтобы использовать весь набор возможностей изоляции systemd для ваших процессов. Возможно повальное увлечение snap-пакетами и docker-контейнерами не началось, если бы некоторые ответственные люди чуть более скрупулезно читали документацию инструментов, с которыми работают.

Ну и, наконец, есть возможность прокидывать в окружение запущенного через .service юнит процесса дешифрованные на ходу секреты через параметры наподобие (LoadCredential=), а управлять ими через утилиту systemd-creds.

Это все еще не все утилиты и демоны, которые входят в состав systemd. Для того чтобы покрыть большую часть информации о systemd, которая, как правило, остается в тени, мы решили начать помаленьку записывать небольшой набор лекций, первая из которых уже вышла (Плейлист)

Изучайте инструменты, которые вы используете, расширяйте свой кругозор и развивайтесь в профессиональном плане. Всем удачи.

© Habrahabr.ru