Systemd in Action. Part 2

Ivan Shapovalov, Like-all, Pavlo Rudyi

Part 1Part 3 Part 4

systemd In Action, volume 2

В этой части мы сосредоточимся на администрировании системы под управлением systemd.

Для начала краткий повтор и углубление в материал предыдущей серии.

I. “Repetition needs a friend…” (c) Whitechapel

systemd – это универсальный plumbing layer, или набор служебных программ для совершенно разных задач. Основным компонентом этого проекта является одноимённая система инициализации, построенная на основе концепции юнитов. Каждый юнит описывает какой-либо объект в системе (например, программу/демон, устройство или точку монтирования) и характеризуется текущим состоянием и списком зависимостей. На основании этой информации строится дерево зависимостей (ну, естественно, не дерево, а направленный ациклический граф, но суть та же), в котором для активации каждого юнита требуется активировать все его зависимости.

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

Основная команда оценки состояния системы и управления юнитами — systemctl.

Подкоманда Описание
list-units (--failed) (по умолчанию) cписок юнитов (--failed: показывать только сбойные юниты)
status (без аргументов) информация о состоянии системы и список всех процессов в виде дерева по контрольным группам
list-sockets список сокетов
list-timers cписок таймеров
daemon-reload перечитать список юнитов
daemon-reexec перезапустить сам systemd (с сохранением состояния)
show полный список свойств юнита (машиночитаемый)
cat эффективный текст юнит-файла, включая все дополнения
list-dependencies список зависимостей юнита
start запуск сервиса
stop остановка сервиса
status (с аргументами) статус сервиса
kill принудительная остановка всех процессов, принадлежащих юниту
reload перезагрузка конфигурации сервиса (если это понятие к нему применимо)
isolate изолировать юнит (т. е. остановить все юниты, кроме указанного и его зависимостей)
enable “добавить в автозагрузку” (выполнить действия в [Install]-секции юнита)
disable “убрать из автозагрузки” (выполнить обратные действия)
mask запретить запуск юнита любым способом

Здесь подкоманда status имеет два варианта (с аргументами или без них). В первом случае выводится состояние указанных юнитов, а во втором — общая информация о состоянии системы и дерево процессов.

Процессы, соответствующие каждому юниту, помещаются в отдельную контрольную группу (cgroup), что значительно упрощает контроль за процессами в системе. На основе этого становится возможным отслеживать и ограничивать потребление ресурсов отдельными юнитами (а точнее, их процессами). Для этого применяются существующие механизмы в ядре Linux.

Команда, отображающая иерархию контрольных групп или её произвольной части — systemd-cgls.

Команда, показывающая потребление ресурсов по контрольным группам — systemd-cgtop.

Юниты хранятся в виде файлов с ini-подобным синтаксисом и располагаются в трёх базовых директориях, перечисленных здесь в порядке возрастания приоритета:

Директория Описание
/usr/lib/systemd/system системные юниты
/run/systemd/system временные юниты
/etc/systemd/system изменения, внесённые администратором

Если файл с одним и тем же именем встречен в нескольких базовых директориях, из них выбирается наиболее приоритетный, а остальные отбрасываются. К слову, маскировка (systemctl mask) — это переопределение юнита симлинком на /dev/null. (Из этого следует, что замаскировать юнит, находящийся в /etc, нельзя.)

Впрочем, юнит-файлы можно дополнять, и не прибегая к их прямому редактированию. Для любого юнит-файла (например, foo.service) в любой из вышеперечисленных директорий могут быть созданы три поддиректории:

  • foo.service.d
  • foo.service.wants
  • foo.service.requires

В первой из них располагаются файлы с любым именем и расширением .conf. Они считываются вслед за юнитом в алфавитном порядке и обрабатываются так, как будто “подклеены” в конец юнит-файла. Секции объединяются. При этом правила приоритета продолжают действовать: .conf-файлы с одинаковым именем перекрывают друг друга.

Во второй и третьей должны находиться символьные ссылки на другие юнит-файлы. Юниты по этим ссылкам добавляются к Wants= и Requires=-зависимостям рассматриваемого юнита.

К слову, именно таким способом обрабатываются директивы WantedBy= в [Install]-секции. Например, пусть наш юнит foo.service содержит [Install]-секцию следующего вида:

[Install]
WantedBy=multi-user.target

Тогда после выполнения команды systemctl enable foo.service будет создана следующая символьная ссылка (предположим, что foo.service лежит в /etc):

/etc/systemd/system/multi-user.target.wants/foo.service -> /etc/systemd/system/foo.service

Ещё есть юниты-шаблоны. Если существует юнит foo@.service (знак @ в конце имени), то при обращении к foo@bar.service будет использоваться юнит foo@.service, а вхождения спецификаторов %i и аналогичных в его тексте будут заменены на строку bar. Да, в некоторых директивах подстановки запрещены — список спецификаторов и ограничения проще всего посмотреть в мане.

Теперь рассмотрим различные вспомогательные утилиты, которые systemd привносит в наш $PATH. Почти все они начинаются с префикса systemd-. Набираем этот префикс, нажимаем Tab и видим полный список. Итак,

II. “And if you seek vengeance, all you need are instruments of pain.” (c) Dethklok

Для начала (чтобы сразу от них отделаться), systemd-escape и systemd-path.

Про них можно сказать достаточно мало. В systemd есть собственный механизм кодирования произвольных строк в имена файлов. Он применяется в двух местах. Во-первых, имя mount-юнита должно соответствовать пути до точки монтирования (а имя device-юнита — пути до узла устройства в /dev или /sys). Для того, чтобы внедрить в имя юнита произвольный путь со слешами, и применяется эта схема. Например:

Исходная строка Результирующая строка Пояснение
/ - слеши заменяются на дефисы
/mnt/foo mnt-foo первый слеш пропускается, если кроме него в строке ещё что-то есть
/dev/disk/by-label/bar dev-disk-by\x2dlabel-bar символы со специальными значениями передаются с помощью C-подобных escape-последовательностей

Во-вторых, (почти) эта же схема может использоваться в шаблонных юнитах для того, чтобы передавать в качестве параметра произвольные строки. “Почти” заключается в том, что первый слеш не пропускается.

Итак, утилита systemd-escape преобразует строки в описанный формат и обратно. Параметр --path включает описанную выше обработку первого слеша.

systemctl start $(systemd-escape --path /mnt/foo).mount
systemctl start foo@$(systemd-escape "string with 'very' (different) characters").service

Зачем нужна утилита systemd-path в практическом смысле — трудно предположить. Она отображает разные пути из тех, что описаны в file-hierarchy(7), своеобразном обновлённом “аналоге FHS” от разработчиков systemd.

Наверное, предлагается писать $(systemd-path search-binaries) вместо $PATH. Или, например, $(systemd-path user-documents) вместо разбора XDG-шного ~/.config/user-dirs.dirs (а вот это уже удобно).

systemd-run — выполнение произвольных программ под управлением systemd.

Эта команда создаёт временный юнит в /run/systemd, указывает ему в ExecStart= переданную в параметрах команду, опционально дописывает некоторые другие свойства и тут же запускает. Юзкейсов потенциально бесконечное количество.

systemd-run --service-type oneshot /bin/echo "this will be run under systemd"
systemd-run --nice 20 -p CPUQuota=200% /bin/resource-hungry-daemon

Конечно, если юнит для демона уже есть, лучше будет запускать именно его, а свойства типа CPUQuota= устанавливать через systemctl set-property.

Кстати, systemd имеет пользовательский режим работы. Каждый пользователь, залогиненный хотя бы один раз, получает собственный экземпляр systemd, для управления которым не требуются права root (но и запускается он, естественно, от имени пользователя). Соответственно, команды systemd-run, systemctl и прочие имеют параметр --user с очевидным значением.

systemd-run --user /bin/echo "this will be run under systemd user instance"
systemctl --user list-units

С вспомогательными утилитами на этом всё. Рассмотрим теперь команды, относящихся к анализу конфигурации. Помимо systemctl status и systemctl --failed, уже упоминавшихся вначале, существуют ещё две.

systemd-delta — посмотреть список дополнений и переопределений юнитов.

Эта команда отображает сводную табличку всех изменений, внесённых в “системные” юниты (те, что находятся в /usr/lib). Возможны варианты: EXTENDED (расширен с помощью файла в .d-директории), OVERRIDDEN (замещён файлом с тем же названием), MASKED (симлинк на /dev/null), REDIRECTED (симлинк на что-то другое) или EQUIVALENT (симлинк на то же самое).

Есть также параметры --type (фильтр по вышеописанным вариантам) и --diff (показывать дифф между файлами для OVERRIDDEN-записей).

systemd-analyze — разного рода статистика.

Подкоманда Описание
time (по умолчанию) показывает сообщение о времени, затраченном на каждый из этапов запуска системы: прошивка, загрузчик, ядро, initramfs и основная цель (default.target)
blame выводит список юнитов, запуск которых занял наибольшее время (отсортированный по убыванию этого времени)
critical-chain [ЦЕЛЬ] выводит цепочку юнитов, которую пришлось ждать дольше всего (проблемные юниты подсвечены красным и указано время ожидания их запуска)
plot генерирует bootchart-подобную SVG-диаграмму, позволяющую наглядно оценить порядок запуска юнитов и затраченное на каждый из них время
dot [ЮНИТЫ...] генерирует граф зависимостей между юнитами в формате dot
verify проверяет указанный юнит-файл на корректность

Перейдём к средствам конфигурирования системы, коих в systemd тоже значительное количество.

III. “The dissecting begins in the chest” (c) The Faceless

Как нетрудно заметить, в systemd очень активно применяется клиент-серверная архитектура с шиной D-Bus в роли универсального транспорта. Практически каждый компонент (за исключением, наверное, systemd-networkd) имеет свой собственный шинный интерфейс — и отдельную ctl-утилиту в качестве адаптера к этому интерфейсу. Три настроечных утилиты, которые мы сейчас рассмотрим, исключением не являются.

К слову, эта клиент-серверная модель часто вызывает недоумение у тех, кто пытается ознакомиться с systemd: “Как, и для управления хостнеймом свой демон?” Так вот, не всё то висит в памяти, что зовётся демоном. Каждый из этих демонов запускается только при получении по шине какого-либо запроса и автоматически завершается через некоторое (весьма малое) время после его обработки. Преимущества такого подхода перед “обычным”, когда все действия выполняются непосредственно утилитой командной строки, очевидны:

  • проще писать альтернативные UI (например, настройка того же хостнейма в “панели управления” DE)
  • есть возможность более тонкой настройки привилегий, чем с помощью SUID-бита на утилите
  • проще отследить/перехватить появление сообщения на шине, чем вызов бинарника

Здесь мы, соответственно, рассмотрим только ctl-обёртки. Кстати, каждая из них имеет параметр -H, который позволяет выполнить операцию удалённо, подключившись к указанной машине по SSH.

Настройка системных часов, таймзоны и NTP-сервера: timedatectl

Запуск этой утилиты без параметров приводит к отображению “сводки о текущем положении дел”. Под положением дел понимается время (локальное, UTC и то, которое хранится в аппаратных часах), активная таймзона, состояние NTP-сервера, состояние DST и флаг, описывающий, что хранится в аппаратных часах (локальное время или UTC).

      Local time: Пн 2014-12-15 11:13:17 MSK
  Universal time: Пн 2014-12-15 08:13:17 UTC
        RTC time: Пн 2014-12-15 08:13:17
       Time zone: Europe/Moscow (MSK, +0300)
     NTP enabled: yes
NTP synchronized: yes
 RTC in local TZ: no
      DST active: n/a

Следует отметить, что, начиная с версии 216, systemd-timedated управляет только “комплектным” NTP-клиентом (systemd-timesyncd). Предполагается, что основные юзкейсы его функциональность покрывает, а если вам всё же её не хватило и ввиду этого вы сознательно установили другой NTP-сервер/клиент, то вы уже знаете, как им управлять.

Впрочем, на строку NTP synchronized описанное ограничение не распространяется: состояние синхронизации времени берётся из ядра и может быть установлено любым NTP-клиентом.

Подкоманда Описание
status (по умолчанию) показывает вышеописанную сводку
set-time устанавливает локальное время (ожидается аргумент в формате ГГГГ-ММ-ДД ЧЧ:ММ:СС)
set-timezone устанавливает таймзону (ожидается аргумент в формате Europe/Moscow; список таймзон можно получить подкомандой list-timezones)
list-timezones выводит список допустимых таймзон
set-local-rtc включает или отключает хранение локального времени в аппаратных часах (true — локальное время, false — UTC)
set-ntp включает или отключает “комплектный” NTP-клиент systemd-timesyncd

Настройка имени хоста и описания системы: hostnamectl

Запуск без параметров — опять же, вывод достаточно информативного статусного сообщения. В него входит имя хоста, несколько описательных полей вроде физического расположения системы, UUID системы и текущей загрузки, имя дистрибутива, версия ядра и так далее.

   Static hostname: intelfx-laptop
         Icon name: computer-laptop
           Chassis: laptop
        Deployment: development
          Location: road to nowhere
        Machine ID: 2ec6cf64aaa5446b99b67d87f1557c41
           Boot ID: 07fbc3ae2b3a4e0a91b7e8465ae4774d
  Operating System: Arch Linux
            Kernel: Linux 3.18.0-pf0-intelfx-00204-g2766b40
      Architecture: x86-64

Поля “Icon name”, “Chassis”, “Deployment” и “Location” сугубо пассивны: это строки, которые можно установить и узнать по запросу (хранятся они в /etc/machine-info). “Machine ID” и “Boot ID” — это, соответственно, UUID системы (хранится в /etc/machine-id) и UUID текущей загрузки. Наконец, “Operating System” считывается из /usr/lib/os-release.

Два вышеуказанных UUID используются, например, в systemd-journald: первый — при сборе логов с нескольких машин, а по второму можно фильтровать.

Подкоманда Описание
status (по умолчанию) показывает вышеописанную сводку
set-hostname устанавливает имя хоста: --static — основное, которое хранится в /etc/hostname, --transient — получаемое по сети, --pretty — подробное и человекочитаемое
set-icon-name устанавливает поле “Icon name” (допустимы корректные названия пиктограмм согласно Icon Naming Specification)
set-chassis устанавливает поле “Chassis” (допустимы значения из фиксированного списка)
set-deployment устанавливает поле “Deployment” (допустимы значения из фиксированного списка)
set-location устанавливает поле “Location” (допустимы произвольные строки)

Настройка локали и раскладки клавиатуры: localectl

При запуске утилиты без параметров мы получаем текущие значения системной локали, взятые из /etc/locale.conf, а также текущие настройки клавиатуры для подсистемы VT ядра Linux и для X11.

   System Locale: LANG=ru_RU.UTF-8
                  LC_MESSAGES=en_US.UTF-8
       VC Keymap: ruwin_cplk-UTF-8
      X11 Layout: us,ru
     X11 Variant: ,winkeys
     X11 Options: grp:caps_toggle

Здесь всё довольно очевидно. Отметим только, что установка одной из раскладок (для X11 или консоли) автоматически устанавливает другую. Это поведение можно отключить, указав параметр --no-convert.

Подкоманда Описание
status (по умолчанию) показывает вышеописанную сводку
set-locale устанавливает системную локаль (ожидаются один или несколько аргументов в формате locale(7), т. е. LANG=ru_RU.UTF-8)
list-locales выводит список доступных локалей
set-keymap устанавливает раскладку клавиатуры для подсистемы VT (т. е. в консоли)
list-keymaps выводит список доступных раскладок клавиатуры для подсистемы VT
set-x11-keymap устанавливает раскладку и опции клавиатуры для X11 (ожидается от одного до четырёх параметров в формате <раскладка> <модель> <варианты> <опции>)
list-x11-keymap-models выводит список доступных “моделей” клавиатуры для X11
list-x11-keymap-layouts выводит список доступных “раскладок” клавиатуры для X11
list-x11-keymap-variants выводит список доступных “вариантов” раскладок клавиатуры для X11 (опционально допускается указание раскладки, для которой нужно вывести список вариантов)
list-x11-keymap-options выводит список доступных “опций” клавиатуры для X11

Настройка системной локали производится при помощи записи значений в /etc/locale.conf, подсистемы VT — при помощи записи значений в /etc/vconsole.conf (т. е. предполагается использование systemd-vconsole-setup), а настройка X11 — при помощи создания файла /etc/X11/xorg.conf.d/00-keyboard.conf с нужными настройками. Из этого следует, что после задания настроек с помощью localectl требуется перезапустить соответствующие компоненты системы.

Первичная настройка системы: systemd-firstboot

Эта утилита, в отличие от трёх предыдущих, не имеет соответствующего настроечного демона и вносит все изменения напрямую. Это связано с тем, что systemd-firstboot предназначен для использования при установке ОС (для этого предусмотрен параметр --root) или при первом её запуске.

С помощью этой утилиты можно задать системную локаль, таймзону, имя хоста, UUID системы и пароль суперпользователя, причём одним из нескольких способов:

  • установить в неинтерактивном режиме, передав утилите соответствующий параметр (--locale, --locale-messages, --timezone, --hostname, --machine-id, --root-password или --root-password-file)
  • сгенерировать (--setup-machine-id)
  • скопировать из основной системы (параметры --copy-locale, --copy-timezone, --copy-root-password или --copy как синоним комбинации всех вышеперечисленных)
  • спросить у пользователя (--prompt-locale, --prompt-timezone, --prompt-hostname, --prompt-root-password или --prompt как синоним комбинации всех вышеперечисленных)

…И, наконец, взглянем на ассортимент “короткоживущих” утилит, используемых systemd при загрузке системы.

IV. “Let’s drink more… shell shall we?” (c) Unexpect

Все эти программы объединяет следующее: они выполняют то, ради чего до systemd во время загрузки использовались различного рода шелл-скрипты. Показательно то, что типов действий не так и много, и после ознакомления с нижеприведённым списком утилит использование вместо них командного интерпретатора общего назначения (т. е. шелла) действительно начинает выглядеть оверкиллом.

Итак,

systemd-tmpfiles — выполнение произвольных операций с файлами и директориями

Конфигурация этого компонента — набор последовательно считываемых и обрабатываемых .conf-файлов в трёх директориях, имеющих друг над другом приоритет по уже известному принципу. Директории таковы: /usr/lib/tmpfiles.d, /run/tmpfiles.d и /etc/tmpfiles.d.

Сами файлы представляют собой список действий, по одному на строчку. Формат записи действий можно прочесть в tmpfiles.d(5), и на нём мы останавливаться не будем.

systemd-modules-load — загрузка модулей ядра

Здесь всё предельно аналогично. Конфигурационные файлы systemd-modules-load располагаются в трёх директориях (/usr/lib/modules-load.d, /run/modules-load.d и /etc/modules-load.d) и содержат имена модулей, предназначенных к загрузке — опять же, по одному на строчку.

Стоит отметить, что эти директории не имеют ничего общего с modprobe.d(5) и не заменяют их. В modprobe.d перечисляются опции по умолчанию и “чёрный список” автозагрузки, а modules-load.d — это список модулей, которые нужно загрузить принудительно.

systemd-sysusers — добавление системных пользователей и групп

Данный компонент используется для создания отсутствующих системных учётных записей и групп, вплоть до регенерации /etc/passwd, /etc/group и /etc/shadow в случае их отсутствия. Основной юзкейс здесь – это запуск системы с пустым /etc, или (в более общем случае) с пустой корневой ФС, когда /usr с мастер-образом монтируется из внешнего источника (вполне вероятно, что в read-only), а остальное пересоздаётся при первом запуске. Впрочем, такая автоматизация может быть полезной и в других случаях — например, для того, чтобы обеспечить обновление системы без ручного слияния списков пользователей и групп (как это иногда приходилось делать, например, в Arch Linux).

Конфигурируется systemd-sysusers аналогично двум вышеописанным компонентам: с помощью .conf-файлов в директориях /usr/lib/sysusers.d, /run/sysusers.d и /etc/sysusers.d.

systemd-binfmt — регистрация дополнительных форматов исполняемых файлов

В отдельную утилиту это вынесли, надо полагать, для удобства: функциональность systemd-binfmt полностью реализуется с помощью tmpfiles.d-директив, по очереди записывающих нужные строчки в псевдофайл /proc/sys/fs/binfmt_misc/register. Тем не менее, список binfmt-правил теперь можно записывать в .conf-файлы — в директориях, как нетрудно догадаться, /usr/lib/binfmt.d, /run/binfmt.d и /etc/binfmt.d.

Используется такая возможность, например, с Wine: правило :DOSWin:M::MZ::/usr/bin/wine: заставит ядро запускать MZ-исполняемые файлы (*.exe) с помощью Wine. Это правило и приведено в качестве примера в binfmt.d(5).

systemd-vconsole-setup — настройка подсистемы VT ядра Linux

Наконец, последний компонент. Он конфигурируется в /etc/vconsole.conf и занимается тем, что настраивает при загрузке ядерную консоль Linux: в частности, шрифт (FONT=, FONT_MAP= и FONT_UNIMAP=) и раскладку клавиатуры (KEYMAP= и KEYMAP_TOGGLE=).

К слову, команда localectl set-keymap изменяет именно два последних параметра.

Part 1Part 3 Part 4