Pull to refresh

Переосмысление PID 1. Часть 4

Reading time13 min
Views9.3K
Original author: Lennart Poettering




Касательно Upstart


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

Я это сказал, но я не могу сказать, что в целом согласен с подходом используемом в Upstart. Но для начала, чуть больше об Upstart.

Upstart не разделяет код с sysvinit, и его функциональность представляет из себя надстройку и он предоставляет некоторую степень совместимости с хорошо известными скриптами SysV. Его главная фишка — это событийно-ориентированный подход: запуск и остановка процессов привязывается к событиям в системе, где событием может выступать множество разных вещей, такие как: доступность сетевого интерфейса или запуск какой-либо программы.

Upstart сериализует службы через эти события: если был послан сигнал syslog-started он используется как сигнал чтобы запустить D-Bus чтобы Syslog мог использовать его. Далее когда будет послан сигнал dbus-started, будет запущен NetworkManager, так как теперь он может использовать D-Bus и так далее и так далее.

Некоторые могли бы сказать, что таким способ текущие реальные зависимости которые существуют и понятны администраторам просто были конвертированы и закодированы в набор событий и правил поведения: каждое логическое правило "a нуждается в b", о котором беспокоится администратор/разработчик превращается в «запустить a когда запустится b» плюс «остановить a когда будет остановлен b». В некоторых ситуациях это просто упрощение вещей: для кода в самом Upstart. Тем не менее я бы поспорил, что это упрощение вещей на самом деле является пагубным. Прежде всего, логически зависимости никуда не деваются, человек, который пишет Upstart файлы теперь вынужден вручную конвертировать эти логические зависимости в набор событий и правил поведения (в действительности два правила для каждой зависимости). В итоге, вместо того чтобы позволить машине выяснить, что ему делать, основываясь на зависимостях, пользователь должен вручную конвертировать зависимости в простой набор событий/правил поведения. Также, из-за того, что информация о зависимости никогда не кодируется, то она не доступна во время выполнения, что означает, что администратор, который пытается выяснить, почему произошло то или иное, например, почему a запустилось, когда запустилось b, то у него нет шансов выяснить это.

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

Проще говоря: факто того, что пользователь запустил D-Bus не является индикатором того, что надо запускать NetworkManager (но это то, что на самом деле сделал бы Upstart). В обратном случае данное заявление тоже справедливо: когда пользователь запрашивает NetworkManager, это однозначно является индикатором того, что должен быть запущен D-Bus (что в основном ожидают увидеть большинство пользователей, верно?).

Хорошая система загрузки должна запускать только то, что необходимо и только по требованию. И либо используя «ленивую» загрузку, либо используя параллелизм заблаговременно. Тем не менее он не должен запускать больше чем необходимо, в частности не все, что установлено и может использовать какую-либо службу должно быть запущено.

Наконец, я не вижу реальной пользы использования логики событий. Мне представляется, что большинство событий, которые представлены в Upstart на самом деле не являются пунктуальными по природе, а имеют некоторое время выполнения: служба запускается, выполняется и останавливается. Когда какое-либо устройство подключено, доступно и снова отключено. Точка монтирования в процессе подключения, полностью подключена и отключена. Кабель питания подключен, система питается от сети и кабель питания отключен. Система загрузки или процесс наблюдатель должен управлять минимальным количеством событий чтобы соблюдать пунктуальность, большинство из которых последовательность из запуска, условия и остановки. И снова эта информация не доступна в Upstart, потому что он фокусируется на единичных событиях и игнорирует зависимости времени выполнения.

Теперь, я осведомлен, что некоторые проблемы, которые я озвучил выше некоторым способом были решены последними изменениями в Upstart, в частности синтаксис основанный на условиях таких как start on (local-filesystem and net-device-up IFACE=lo) в файлах правил Upstart-а. Тем не менее, для меня эта кажется больше попыткой исправить систему, в коде которого есть жесткий изъян.

Оставив все это позади, Upstart является хорошей «сиделкой» для служб, даже если некоторые решения кажутся неоднозначными (читайте выше), и имеет много не реализованных возможностей (также читайте выше).

Есть еще другие системы загрузки кроме Upstart, sysvinit и launchd. Большинство из них предлагают немного больше чем Upstart или sysvinit. Наиболее интересный соперник — это Solaris SMF, который поддерживает правильные зависимости между службами. Тем не менее, во многих случаях они чересчур сложные и позвольте сказать слегка академичные в их чрезмерном использовании XML и новой терминологии для хорошо известных вещей. Он также тесно связан с специфичными особенностями Solaris таким как контрактная система.

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


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

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

systemd запускает и управляет всей системой (обратите внимание на имя...). Он реализует весь фунционал обозначенный выше и еще несколько. Все основано на понятии юнитов. Юнит имеют тип и имя. Так как юниты обычно загружают свою конфигурацию с файловой системы, следовательно, именами юнитов являются имена файлов. Например: юнит avahi.service считывает свой конфигурационный файл с таким же именем, и конечно же одновременно является юнит инкапсулирующим демон Avahi. Существует несколько типов юнитов:

  1. service: это самый очевидный тип юнитов: демоны, которые могут быть запущены, остановлены, перегружены и вновь инициализированы. Для совместимости с SysV мы не только поддерживаем свои собственные конфигурационные файлы, но еще имеем возможность считать классические скрипты загрузки SysV, в частности мы парсим LSB заголовок, если он присутствует. Следовательно /etc/init.d ни что иное как еще один источник конфигурационных файлов
  2. socket: этот тип юнита инкапсулирет сокет в файловой системе или в Интернете.
    На данный момент мы поддерживаем AF_INET, AF_INET6, AF_UNIX сокетов следующих типов: потоковые, датаграммы и последовательных пакетов. Мы также поддерживаем классические FIFO как транспорт. Каждый socket юнит имеет соответствующий service юнит, который запускается, как только будет получено первое соединение на сокет или на FIFO. Например: nscd.socket запускает nscd.service на входящем соединении.
  3. device: этот тип юнита инкапсулирует устройство в Linux системе. Если устройство помечается через udev правило, то оно появится в как device юнит в systemd. Свойства, устанавливаемые с помощью udev могут быть использованы как источник конфигурации для установления зависимостей device юнита.
  4. mount: этот тип юнита инкапсулирует точку монтирования в файловой системе. systemd наблюдает за всеми точками монтирования в то время как они подключается и далее на их жизненном пути, и может быть также использован для подключения и отключения точек монтирования. /etc/fstab используется как дополнительный источник для точек монтирования, точно также как скрипты загрузки SysV могут быть использованы как дополнительный источник конфигурации для юнитов типа service.
  5. automount: это тип юнита инкапсулирует автоматическую точку монтирования в файловой системе. Каждый automount юнит имеет соответствующий mount юнит, который запускается (т.е. подключается) как только будет сделана попытка доступа к папке авто-подключения.
  6. target: этот тип юнита используется для логической группировки юнитов: вместо того чтобы на самом деле делать что-то полезное он просто ссылается на другие юниты, вследствие чего они могут управляться вместе. Примером может служить: multi-user.target, который играет роль уровня запуска 5 в классической системы SysV, или bluetooth.target, который запрашивается как только к системе подключается какой-либо bluetooth донгл и который просто запускает все службы связанные с bluetooth, которые в ином случае не были бы запущены: bluetoothd и obexd как пример
  7. snapshot: этот тип юнита похож на target и ничего не делают по своей сути и единственной его целью является ссылка на другие юниты. Снапшоты могут быть использованы чтобы сохранить/откатывать состояние всех сервисов и юнитов системы загрузки. В основном он предназначен для использования в двух случаях: позволить пользователю временно войти в определенное состояние такое как «Экстренная оболочка», т.е. прерывание текущих служб и простой способ вернуться в предыдущему состоянию, т.е. запуск всех служб, которые были временно остановлены. Также он может использоваться как простой способ приостановки системы (сон): существует много сервисов, которые ведут себя не корректно, когда система уходит в режим сна, и частенько было бы хорошей идеей просто остановить эти службы до сна и запустить после выхода система из режима сна.

Все эти юниты могут иметь зависимости между собой (как положительные, так и негативные, т.е. «Requires» и «Conflicts»): устройство может иметь зависимость к службе, имея в виду, что как только устройство станет доступным соответствующий сервис запуститься. Точки монтирования имеют неявные зависимости к устройствам, с которых они подключаются. Точки монтирования также имеют неявные зависимости к другим точкам монтирования, которые их предвещают (например: точка монтирования /home/lennart неявно имеет зависимость к точке монтирования /home) и т.д.

А вот короткий список особенностей:

  1. Для каждого процесса, который порождается, вы можете контролировать: среду, ограничения ресурсов, рабочую и корневую директорию, umask, настройки OOM киллера, уровень nice, класс IO и приоритетность, политики CPU и приоритетность, притяжение к CPU, id пользователя, id группы, id побочных групп, папки для чтения/записи и папки без права доступа, общие/частные/вторичные флаги монтирования, набор возможностей/ограничений, атрибуты безопасности, установки планировщика CPU, приватные /tmp пространство, cgroup для различных подсистем. Также вы можете легко соединить stdin/stdout/stderr службы к syslog, /dev/kmsg или любой TTY. Если подсоединен к TTY для ввода, то systemd убеждается, что процесс имеет эксклюзивный доступ к TTY, либо ждет доступа или форсирует его.
  2. Каждый запущенный процесс получает свой cgroup (на данный момент только в подсистеме отладки, так как подсистема отладки не используется в других целях и делает не больше чем просто группировка процессов) и очень легко настроить systemd чтобы каждому сервису был присвоен свой cgroup, настроенный вне systemd, скажем через утилиты libcgroups.
  3. Файлы конфигурации использует синтаксис, который максимально следует хорошо известным .desktop файлам. Это простой синтаксис, парсер к которому присутствует во многих библиотеках. Также, он позволяет нам сослаться на существующие инструменты для i18n в описаниях служб. Администраторам и разработчикам не придется учить новый синтаксис.
  4. Как было упомянуто, мы поддерживаем совместимость с скриптами загрузки SysV. Мы пользуемся преимуществом LSB и Red Hat chkconfig заголовков если они присутствуют. Если они не представлены мы пытаемся выжать лучшее из доступной информации, типа приоритеты запуска в /etc/rc.d. Скрипты загрузки SysV используется как дополнительный источник конфигурации, следовательно, упрощается путь миграции на systemd. Опционально мы можем считать классические PID файлы для служб чтобы идентифицировать мастер процесс службы. Заметьте, что мы берем LSB заголовки и конвертируем их в систему зависимостей systemd. Отвлеченная заметка: Upstart не может собирать и использовать такого рода информацию. Загрузка с помощью Upstart на системе где преобладают LSB SysV скрипты будет не распараллеленной, хотя на той же системе с systemd будет. В действительности, для Upstart все скрипты загрузки SysV выполняются вместе как одно задание, тогда как для systemd они являются как еще один источник конфигурации и все они управляются и обозначаются индивидуально, как нативная служба systemd.
  5. Похожим способом мы считываем существующий /etc/fstab, и считаем его как дополнительный источник конфигурации. Используя fstab опцию comment= мы можем даже пометить элемент в /etc/fstab как контролируемую systemd точку автомонтирования.
  6. Если один и тот же юнит настроен в нескольких источниках конфигураций (например, есть файл /etc/systemd/system/avahi.service и /etc/init.d/avahi) тогда приоритет получит нативный файл конфигурации, игнорируя устаревший файл конфигурации, позволяя пакет использовать как SysV скрипт так и файл конфигурации systemd еще некоторое время.
  7. Мы поддерживаем простой механизм шаблонов/экземпляров. Например: вместо того чтобы поддерживать шесть конфигурационных файлов для шести getty, мы просто поддерживаем один getty@.service экземпляр которого будет создан для getty@tty2.service и так далее. Интерфейсная часть может быть даже унаследована выражениями зависимостей, т.е. легко закодировать что служба dhcpd@eth0.service запускает avahi-autopid@eth0.service
    при этом оставляя часть строки — eth0 — замаскированным.
  8. Для активации сокета мы поддерживаем полную совместимость с традиционными режимами inetd, так же как очень простой режим, который пытается имитировать метод активации launchd и это рекомендованный метод для новых служб. Режим inetd позволяет передать только один сокет демону, тогда как нативно поддерживаемый режим позволяет передать сколько угодно файловых дескрипторов. Мы также поддерживаем один экземпляр на соединение, также как один экземпляр на все соединения. В первом режиме мы называем cgroup службы, который будет запущен, с параметрами соединения, и используем логику шаблонизации упомянутую выше. Например: sshd.socket может породить службы sshd@192.168.0.1-4711-192.168.0.2-22 с названием cgroup sshd@.service/192.168.0.1-4711-192.168.0.2-22 (т.е. IP адрес и номера портов используются в названии экземпляра. Для сокета AF_UNIX мы используем PID и идентификатор клиентского соединения). Такой механизм предоставляет администраторам хороший способ идентифицировать различные экземпляры службы или контролировать их рантайм индивидуально. Нативный режим передачи сокетов очень легко реализовать в приложениях: если установлена переменная $LISTEN_FDS, то он будет содержать количество переданных сокетов и демон сможет найти их отсортированными как указано в .service файле, начиная с дескриптора 3 (хорошо написанный демон может также использовать fstat() и getsocketname() чтобы идентифицировать каждый из сокетов в случае если их передано больше одного). В дополнение мы устанавливаем переменную $LISTEN_PID в значение PID демона, который должен получить файловые дескрипторы, потому что переменные окружения как правило наследуется дочерним процессом и, следовательно, могли бы ввести в заблуждение следующие ниже по цепочке. Более того такая логика передачи сокета очень легко реализовать в демонах. Мы будем предоставлять BSD лицензированную эталонную реализацию, которая показывает, как работать с этим. Мы портировали парочку демонов реализующую данную схему.
  9. В некоторой степени мы предоставляем совместимость с /dev/initctl. Эта совместимость в действительности реализована с помощью FIFO-активированных служб, которые просто конвертируют эти старые запросы в запросы D-Bus. В сущности, это означает, что старые shutdown, poweroff и похожие команды из Upstart и sysvinit продолжают работать и с systemd.
  10. Мы также предоставляем совместимость с utmp и wtmp. Возможно даже немного более лучшую версию чем существующие utmp и wtmp.
  11. systemd поддерживает несколько типов зависимостей между юнитами. After/Before может быть использована чтобы вмешаться в порядок активации юнитов. Также полностью ортогональные Requires и Wants, которые выражаются в позитивно требующую зависимость, либо обязательную, либо не обязательную. Также, существует Conflicts, который выражается в негативно требующую зависимость. Наконец, также существует еще три менее используемых типов зависимостей.
  12. systemd имеют минимальную систему транзакций. Это значит: если юнит хотят запустить или остановить мы добавим службу и все её зависимости в временную транзакцию. Далее мы убедимся, что транзакция целостна (т.е. сортировка через After/Before все юнитов свободна от цикличности). Если это не так, попытаемся исправить это, и удалить не существенные задания из транзакции, который может убрать рекурсию. Также, systemd пытается сдерживать не существенные задания, которые могут помещать запуску службы. Не существенные запросы это те, которые напрямую не вовлечены, но которые подтягиваются через зависимости типа Wants. Наконец мы проверяем есть ли задания, противоречащие заданиям, которые уже были добавлены в очередь, что в последствии может прервать транзакцию. Если все гуд и транзакция консистентна (целостна) и её влияние минимизировано, тогда она будет слита с уже предстоящими заданиями и будет добавлена в очередь запуска. В действительности это означает, что перед выполнением запрошенной операции, мы проверим что имеет ли смысл её вообще выполнять, исправим её если возможно и «сдадимся» если на самом деле произойдет не разрешимая ситуация.
  13. Мы записываем время запуска/остановки, как и PID и код выхода каждого процесса, который мы запускаем и за которым мы следим. Эти данные мы можем использовать чтобы построить перекрестные связи служб и их данных в abrtd, auditd и syslog. Представьте интерфейс пользователя, который подсвечивает падения демона, и предоставляет вам легкую навигацию к соответствующему пользовательскому интерфейсу для syslog, abrtd и auditd, который покажет сгенерированную информацию для этого демона по текущему запуску.
  14. Мы поддерживаем повторное выполнение процесса загрузки самим собой в любое время. Состояние демона сериализуется до повторного выполнения и десериализуется после выполнения. Таким способом мы предоставляем простой способ облегчить обновления системы загрузки, а также передачу демона загрузки до конечного демона. Открытые сокеты и точки монтирования autofs правильно сериализуются, так что позволяют соединиться к ним все время, таким образом, что клиенты даже не заметят, что система загрузки перезапускает сама себя. Также факт того, что большая часть состояния служб закодирована в виртуальной ФС cgroup не позволяет нам продолжить выполнения без доступа к сериализованным данным. Путь кода повторного запуска в действительности очень похож на путь кода повторной загрузки конфигурации для системы загрузки, который гарантирует повторное выполнение (который реже запускается) тестируется также как перезагрузка конфигурации (который вероятно запускается более часто)
  15. Начиная работу по удалению скриптов загрузки из системы загрузки, мы записали часть базовой настройки системы в С и перенесли его напрямую в systemd. Среди этого также подключение API файловых систем (т.е. виртуальные файловые системы типа /proc, /sys и /dev) и установка имени хоста.
  16. Состояние сервера инспектируется и контролируется через D-Bus. Оно еще не завершено, но продвинулось далеко вперед.
  17. Пока мы хотим подчеркнуть сокет-основанную и основанные на имени шины активацию, и, следовательно, поддержка зависимостей между сокетами и службами. Мы также поддерживаем несколько способов как такого рода сервисы могут сигнализировать о своей готовности: путем форкания (by forking) и имея статус остановки запускающего процесса (т.е. традиционное поведение daemonize()) также как наблюдение за шиной пока появится сконфигурированное имя службы.
  18. Существует интерактивный режим, который спрашивает каждый раз подтверждения, когда процесс порождается systemd. Вы можете включить его передав systemd.confirm_spawn=1 в аргументах запуска ядра.
  19. С параметром ядра systemd.default= вы можете указать с какого юнита начнется загрузка systemd. Обычно вы указываете что-то типа multi-user.target, но вы можете даже указать одну единственную службу вместо таргета. Для примера из коробки мы предоставляем emergency.service, который похож по своей полезности на init=/bin/bash, тем не менее имея преимущество запущенной системы загрузки, следовательно предоставляя возможность загрузки полноценной системы из экстренной оболочки.
  20. Также присутствует минимальный пользовательский интерфейс, который позволяет запускать/останавливать/инспектировать службы. Он далек от полноценного пользовательского интерфейса, но полезен как инструмент отладки. Он написан на Vala (уу еее!!) и имеет имя systemadm


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

Продолжение следует…
Tags:
Hubs:
+13
Comments9

Articles

Change theme settings