Система инициализации Systemd. Часть II

Это продолжение начатого тут.

Собираем все вместе — systemd

Выше я объяснил, что должен делать хороший процесс с PID 1 и как работают существующие системы инициализации. Перед тем как перейти к самому главному, давайте сделаем еще паузу. Сходите налейте себе еще кружечку кофе. Это того стоит.

Как вы, наверное, уже догадались, те требования и возможности для идеальной системы инициализации, что я предложил выше, существуют уже сейчас в системе инициализации, названной нами systemd, которую я и хочу тут представить. Здесь ее код. Ниже приведен краткий список ее особенностей и их обоснование.
Поскольку systemd запускает и контролирует всю систему, отсюда и ее название. Она реализует все возможности, указанные выше, и еще кое-что. Система построена на концепции модулей (units). Модули имеют имя и тип. Поскольку их конфигурация обычно загружается из файловой системы — названия модулей на самом деле представляют собой имена файлов. Например: модуль avahi.service считывается из конфигурационного файла с тем же именем и естественно, что он реализует работу с демоном Avahi. Существует несколько видов модулей: 
  • service/сервис: наиболее очевидный тип модуля: это демоны, которые могут быть запущены, остановлены, перезапущены или перезагружены. Для совместимости с SysV в systemd помимо собственных файлов конфигурации для различных сервисов имеется возможность чтения классических скриптов инициализации SysV, а также она умеет разбирать заголовок LSB, если он существует. /etc/init.d является, следовательно, не более, чем просто еще одним источником конфигурации.
  • socket/сокет: данный модуль реализует сокет, расположенный в файловой системе или в Интернете. В настоящее время поддерживаются сокеты AF_INET, AF_INET6, AF_UNIX типов stream, datagram и последовательных пакетов (sequential packet). Также поддерживаются классические буферы FIFO. Каждый модуль типа «сокет» имеет соответствующий ему модуль «сервис», который запускается при попытке установки соединения с сокетом или буфером FIFO. Пример: nscd.socket при установке соединения запускает nscd.service.  
  • device/устройство: этот модуль реализует устройство в дереве устройств Linux. Если устройство описано через правила udev, оно будет представлено в systemd как модуль устройство. Набор параметров устройства, установленный udev, будет использоваться systemd как исходный в определении зависимостей для этого типа модулей. 
  • mount/точка монтирования: модуль реализует точку монтирования в файловой системе. systemd контролирует все точки монтирования (их подключение и отключение), а также может быть использована для монтирования и размонтирования отдельных файловых систем. Файл /etc/fstab используется как дополнительный источник конфигурации для них, подобно тому, как сценарии инициализации SysV могут быть использованы в качестве дополнительного источника конфигурации для модулей service. 
  • automount/точка монтирования с а
    втоматическим подключением
    :
    модуль реализует точку монтирования с автоматическим подключением файловой системы. Каждый такой модуль имеет соответствующий ему модуль типа mount, который запускается (т.е. подключается), как только монтируемая файловая система становится доступной. 
  • target/указатель: данный тип модулей используется для логической группировки других модулей: на самом деле сам по себе он ничего не делает, он просто указывает на другие модули, которыми таким способом можно управлять вместе. В качестве примера можно привести модули multi-user.target, который играет роль 5-го уровня запуска в классической схеме SysV, и bluetooth.target, активируемый, как только становится доступен Bluetooth-адаптер, и запускающий сервисы, имеющие отношение к Bluetooth (которые обычно не запущеныbluetoothd и obexd) (т. е. по сути это придет на смену традиционным уровням запуска SysV - прим. перев.). 
  • snapshot/снимок: подобно предыдущему типу модулей снимки также ничего не делают сами, и их единственное преданзначение заключается в ссылке на другие модули. Снимки могут быть использованы для сохранения состояния и возможности отката назад состояния всех служб и модулей системы инициализации. Он, главным образом, предназначен для двух случаев. Первый, чтобы позволить пользователю временно перевести систему в какое-то специфичное состояние, например, однопользовательский режим с остановкой всех работающих сервисов, а затем легко вернуться в предыдущее состояние с одновременным запуском тех сервисов, которые были перед эти
    м запущены. В
    торой вариант его использования — поддержка режима suspend: достаточно много сервисов не могут корректно работать с этой системой, и зачастую их лучше остановить перед засыпанием, а потом просто запустить.
Приведенные модули могут иметь зависимости друг от друга (как положительные, так и отрицательные, т. е. бывает, что одни без других не могут обойтись, а другие, наоборот, не могут терпеть друг друга): например, модуль устройство может зависеть от какого-то модуля сервис, т.е. как только устройство становится доступным — запускается определенный сервис. Модули mount имеют неявную зависимость от устройства, которое они пытаются смонтировать. Также они наследуют неявные зависимости от префиксов путей к точкам монтирования (например, модуль, подключающий /home/lennart, неявно зависит от модуля, подключающего /home)и так далее.

Краткий перечень остальных функциональных возможностей:

  1. Для каждого порожденного процесса вы можете контролировать: среду исполнения, ограничения ресурсов, рабочую и корневую директории, umask, настройки OOM killer, параметр nice, класс и приоритет операций ввода-вывода, политику и приоритеты использования процессора, привязку к процессору, таймер, идентификаторы пользователя, основной и дополнительных групп, списки директорий, доступных для чтения/записи, список директорий, доступ к которым запрещен, флаги монтирования, биты безопасности, параметры, относящиеся к планировщику процессора CPU, области видимости /tmp, привязку к cgroup для различных подсистем. Также можно присоединить stdin/stdout/stderr сервисов к syslog, /dev/kmsg, произвольным TTY. Если вы присоединяете TTY ко входу systemd — удостоверьтесь в том, что процесс получает эксклюзивный доступ.
  2. Каждый запущенный процесс получает собственную cgroup (в текущем состоянии разработки по умолчанию они создаются в подсистеме debug, поскольку она все равно не используется). С помощью systemd также легко помещать отдельные сервисы в различные cgroups, причем, это можно сделать из пользовательского пространства, например, посредством утилит libcgroups.
  3. Файлы конфигурации systemd имеют синтаксис, аналогичный используемому в файлах .desktop, который прекрасно разбирается (parse) многими имеющимися утилитами и имеет все необходимое для интернационализации. Поэтому администраторам и разработчикам не нужно учить новый синтаксис.
  4. Как упоминалось выше, мы (Здесь и далее под «мы» понимаются разработчики и сама systemd, надо смотреть по контексту. Обычно это нормально, когда автор осознает свое единство со своим творением 🙂. Список основных разработчиков приведен в конце этой статьи. Прим. перев.) обеспечиваем совместимость со скриптами SysV, дополнительно к этому обрабатываются заголовки LSB и утилиты chkconfig RedHat. Если их нет, просто используется любая доступная информация (такая, как приоритеты запуска сервисов) из /etc/rc.d. Поскольку эти скрипты начинают просто читать другой источник конфигурации, обновиться с SysV на systemd достаточно легко. Дополнительно мы можем читать классические PID-файлы сервисов, чтобы определить PID главного демона. Также мы можем использовать информацию о зависимостях из LSB-заголовка скрипта и транслировать ее в «родной» формат описания зависимостей для systemd. Важное замечание: Upstart не может использовать эту информацию. Во время запуска Upstart, в отличие от systemd, не распараллеливает запуск большей части скриптов LSB и/или SysV. Фактически, для Upstart все скрипты SysV — это одно исполняемое задание (Тут опять автор немного лукавит. В Upstart просто оставлен слой совместимости со скриптами SysV, который действительно работает, как описано. Но это именно что слой совместимости с теми службами, управляющие скрипты которых по каким-то причинам пока не отконвертированы в «родной« формат для Upstart, а не «злостная недоработка» разработчиков Upstart. Прим. перев.).
  5. Аналогичным образом, существующий файл /etc/fstab читается и используется как еще один источник конфигурации. А с использованием опции comment= fstab вы даже можете отметить отдельные записи в этом файле, чтобы передать их под контроль systemd (для обеспечения работы функционала автоматического монтирования).
  6. Если для одного сервиса существует несколько файлов конфигурации (например, есть два файла /etc/systemd/system/avahi.service и /etc/init.d/avahi), тогда "родной" формат файлов имеет преимущество. Устаревший формат файлов игнорируется, позволяя легко провести обновление.
  7. Мы также поддерживаем механизм использования шаблонов. Например, вместо того, чтобы иметь шесть одинаковых конфигурационных файлов для шести getty, у нас есть только один файл getty@.service, который используется сервисом getty@tty2.service и аналогичными ему. Интерфейсная часть также может наследоваться выражениями, описывающими зависимости, т. е. легко понять, что сервис dhcpcd@eth0.service запускается сервисом avahi-autoipd@eth0.service.
  8. Для активации сервисов посредством сокетов, мы поддерживаем полную совместимость с традиционной моделью inetd, а также простой режим, имитирующий работу launchd, который рекомендуется к использованию для вновь создаваемых сервисов. Режим совместимости с inetd позволяет передавать запускаемому демону только один сокет, в то время как «родной» режим работы позволяет передавать произвольное количество дескрипторов файлов. Мы поддерживаем как режим с одним экземпляром сервиса на одно соединение, так и с одним экземпляром на все соединения. Например: sshd.socket может запускать сервис sshd@192.168.0.1-4711-192.168.0.2-22.service с cgroup sshd@.service/192.168.0.1-4711-192.168.0.2-22 (т. е. IP-адрес и номера портов используются в качестве имен). Для сокетов AF_UNIX, используется PID и идентификатор пользователя присоединяющегося клиента. Это предоставляет администратору прекрасный инструмент для определения различных экземпляров одного и того же демона и контроля за ним. «Родной режим» передачи сокета легко реализовать в приложениях: переменная $LISTEN_FDS, если она установлена, содержит количество передаваемых сокетов, и демон может найти их в файле .service, начиная с файлового дескриптора 3 (хорошо написанный демон также может использовать fstat() и getsockname() для идентификации сокетов в случае, если их больше одного). В дополнение мы устанавливаем переменную $LISTEN_PID, содержащую PID главного демона, который получает сокеты, потому что переменные среды обычно наследуются дочерними процессами, что может несколько запутать процессы, находящиеся далее в цепочке. Поскольку логика передачи сокетов очень проста, мы предоставляем примерную реализацию этого процесса под лицензией BSD. Также мы портировали множество существующих демонов на эту схему. 
  9. Мы предоставляем совместимость с интерфейсом /dev/initctl. Эта совместимость фактически реализована с помощью сервиса, активируемого посредством FIFO, который просто транслирует устаревшие запросы в запросы D-Bus. На практике это означает, что старые команды shutdown , poweroff и аналогичные им из Upstart и sysvinit будут работать с systemd. 
  10. Мы предоставляем совместимость с utmp и wtmp. Код, который это делает, выглядит гораздо более жизнеспособным, чем эти файлы :).
  11. systemd поддерживает несколько типов зависимостей между модулями. After/Before могут использоваться для определения последовательности запуска. Requires/Wants определяет статус зависимости, является она обязательной или опциональной. И наконец, Conflicts определяет отрицательный характер зависимости (т. е. две и более службы, у которых в зависимостях указана Conflicts, не смогут быть запущены одновременно — прим. перев.). Есть также еще три, менее часто используемые типы-зависимостей. 
  12. systemd построена как система с минимумом транзакций. Это означает: если модуль запросил запуск или остановку, мы добавляем его и все его зависимости во временную транзакцию. Затем проверяем целостность этой транзакции, т.е. последовательности обработки зависимостей After/Before для всех модулей на возможность появления циклических зависимостей. Если они есть, systemd пытается исправить ситуацию путем удаления из транзакции «несущественных» (non-essential) заданий. Также systemd пытается убрать из транзакции такие из «несущественных» заданий, которые могут остановить какой-либо другой сервис (не имеющий отношение к останавливаемому модулю). «Несущественными» являются такие задания, которые не относятся к оригинальному запросу, но при этом помещены в очередь на основе зависимостей Wants. В заключение проверяется, могут ли задания в транзакции проти
    воречить заданиям, которые уже находятся в очереди и, если возникла такая ситуация, транзакция отменяется. Если все сработало корректно, транзакция целостна и минимизирована по количеству операций, она ставится в очередь на исполнение. В сухом остатке это означает, что перед запуском запрошенной операции мы проверяем, имеет ли смысл ее вообще выполнять, если возможно, исправляем возникшие ошибки, и затем ничего не делаем, если операцию выполнить невозможно. 
  13. Мы записываем время запуска/остановки, PID и статус завершения для каждого из порождаемых и/или контроллируемых процессов. Эти данные позволяют увязать демоны с их данными в abrtd, auditd и syslog и создать интерфейс, который выделял бы упавшие демоны и предоставлял бы всю необходимую о них информацию.
  14. Мы также реализовали самостоятельный перезапуск процесса init в любое время по требованию. Состояние демона замораживается перед этим и размораживается после. Таким образом мы упрощаем онлайнового обновления с SysV init на systemd, а также передачу полномочий от остановленного к перезапущенному демону. Запросы к открытым сокетам и точкам монтирования autofs, как уже отмечалось выше, будут корректно упорядочены и поставлены в очередь ядром, поэтому клиенты даже не почувствуют, что что-то вообще произошло. Поскольку большая часть информации о состоянии обслуживаемых systemd процессов хранится в виртуальной файловой системе cgroup, это позволяет нам не прерывать их исполнения из-за невозможности доступа к данным init. Код, реализующий перечитывание конфигурации init, является общим с кодом его перезапуска.
  15. Избавляясь от shell-скриптов в процессе запуска системы, мы переписали основную их часть на C и поместили непосредственно в systemd. В частности, это код таких операций как монтирование виртуальных файловых систем /proc, /sys and /dev и установка имени хоста.
  16. Состояние сервиса доступно и может контроллироваться через D-Bus (за это Upstart не пинал только самый ленивый — прим. перев.). Правда, эта возможность пока не реализована и находится в стадии активной разработки. 
  17. Мы придаем особое значение активации сервисов посредством сокетов либо через D-Bus (и, следовательно, поддерживаем обработку соответсвующих зависимостей). В то же время мы поддерживаем традиционные зависимости только между сервисами. Также поддерживается несколько способов, которыми сервис может сообщить о своей готовности: путем вызова fork() и завершением родительского процесса (традиционное поведение daemonize()) и посредством публикации своего имени на D-Bus.
  18. Существует интерактивный режим, который запрашивает