Содержание статьи
Системные демоны — одна из ключевых подсистем UNIX. От того, насколько хорошо и правильно они написаны, зависят не только возможности операционной системы, но и такие параметры, как удобство использования и даже скорость работы. В этой статье мы рассмотрим четыре примера правильной реализации демонов, которые способны сделать работу в системе удобнее, эффективнее и быстрее.
Systemd: быстрее света
Схема загрузки типичного Linux-дистрибутива выглядит примерно так: ядро инициализирует железо и запускает процесс /sbin/init, который, в свою очередь, запускает инициализационные скрипты. Скрипты монтируют файловые системы, настраивают сеть, различные устройства и начинают последовательный запуск демонов: syslogd, cron, cups и прочих, которые перечислены в конфигурационных файлах. В самом конце init запускает менеджер входа в систему: getty или xdm (kdm, gdm). Просто и логично, не так ли? Однако такая схема довольно примитивна, а в сравнении с Windows и Mac OS X так и вообще архаична. Их системы инициализации запускают задачи параллельно, не дожидаясь завершения одной, чтобы передать управление следующей. Если одна из них застопоривается на операции ввода-вывода, управление сразу получает другая, так что общее время загрузки сокращается, да так существенно, что традиционная система оказывается далеко позади.
В мире Linux так ведет себя только Ubuntu, да и то только последние два года. Все остальные продолжают по старинке последовательно грузить систему или используют самосборные костыли, которые распараллеливают процесс загрузки неумело и часто ошибочно (Gentoo и Arch, привет!). По-настоящему универсальное решение не найдено до сих пор, поэтому над идеей параллельной системы инициализации работают многие программисты.
Леннарт Поттеринг, сотрудник Red Hat и автор PulseAudio, один из них. Его последнее достижение — демон systemd, очередной претендент на звание убийцы /sbin/init, мимо которого можно было бы спокойно пройти, если бы идея, заложенная в его основу, не оказалась столь интересной и правильной.
Systemd отличается от любой другой системы инициализации тем, что намеренно делает сложные вещи простыми. 99% всех остальных параллельных систем инициализации провалились просто потому, что в сравнении с простым и понятным даже дикарю /sbin/ init они выглядели тяжеловесными монстрами. Чтобы обеспечить возможность параллельного запуска, не введя ОС в противоречивое состояние (которое может возникнуть, если, например, пытаться настроить сеть до загрузки сетевых драйверов или запустить демоны, не смонтировав нужную ФС), использовались различные методы синхронизации. В основном это были своеобразные «метки зависимостей», которые не давали очередному шагу инициализации отработать, если не был пройден шаг, описанный в его зависимостях.
Например, cron зависит от syslog, потому что ему надо вести логи; syslog зависит от настойки сети, потому что он способен принимать логи от удаленных машин и так далее. Из-за этого инициализационные скрипты превращались в запутанную вереницу блоков, а их составление значительно усложнялось. Systemd организован намного проще, он не следит за зависимостями, а просто запускает все, что есть, одновременно.
Я не шучу. Systemd использует механизмы контроля зависимостей только на самых ранних этапах инициализации, которые так или иначе должны происходить последовательно (монтирование корневой файловой системы, подключение swap, загрузка модулей и так далее). Когда же дело доходит до демонов, на запуск которых уходит 90% всего времени инициализации ОС, systemd забывает о зависимостях и стартует их всех сразу, показывая просто потрясающую скорость.
Это работает благодаря тому, что systemd знает об особенности работы демонов и их связи между собой.
Ведь на самом деле демонам нужны вовсе не другие демоны, а только «коммуникационные каналы», обеспечивающие обмен данными: cron не зависит от syslog, ему нужен сокет /dev/log, в который он сможет записывать свои логи, это же справедливо и в отношении любого другого демона. Все они общаются через сокеты, и единственная причина, почему демон A должен быть запущен раньше демонов B, C и D, заключается в том, что демон A должен создать сокет, который им нужен. Systemd учитывает эту особенность, поэтому его механизм параллельного запуска основан на сокетах, которые он создает для каждого демона заблаговременно, а затем запускает демоны одновременно. При этом ответственность за синхронизацию и «отслеживание зависимостей» теперь перекладываются на ядро, в рамках которого и реализован механизм сокетов.
Если, например, cron получит управление раньше syslog, ничего страшного не произойдет — cron найдет свой любимый /dev/log и даже сможет писать в него сообщения (если захочет, конечно), которые будут, нет, не выброшены, а буферизированы в сокете, но только до тех пор, пока cron не захочет записать в сокет сообщение, способное его переполнить. В этом случае ядро заблокирует процесс cron и передаст управление следующему процессу в очереди на исполнение (следующему демону). Вскоре (а может быть и сразу) очередь дойдет и до syslog, который запустится, прочитает сообщения, скопившиеся в /dev/log, обработает их и сам на чем-нибудь заблокируется (либо истратит отведенное ему время), и управление перейдет следующему демону. Типичная многозадачность без лишних костылей.
Более того, благодаря такой схеме большинство демонов могут быть запущены только тогда, когда в них возникнет реальная необходимость. Так, например, CUPS вовсе не обязательно запускать во время инициализации ОС, когда нагрузка на систему и без того высока. Логичнее стартануть его, когда на печать будет отправлен первый документ. Systemd позволяет сделать такое, следя за активностью вокруг сокетов, и применяет похожий подход для монтирования файловых систем, подключая их к точкам монтирования только при попытке получить доступ к файлам (также демоны могут быть запущены при появлении в системе определенного файла-устройства).
Справедливости ради стоит сказать, что столь гениальное решение проблемы зависимостей было придумано и реализовано в Mac OS X с самого начала ее существования, но до автора systemd почему-то никто не обращал на это внимания.
Кстати, у самого systemd есть другая и явно уникальная для Linux характеристика: он умеет группировать процессы с помощью cgroups с установкой различных лимитов среды исполнения на всю группу (ограничения ресурсов, рабочий и корневой каталоги, umask, настройки OOM killer, параметр nice, приоритет операций ввода-вывода, приоритеты использования процессора и многое другое). То есть демонов теперь можно помещать в виртуальные окружения без использования какого бы то ни было дополнительного ПО, просто прописав в файл настроек systemd несколько строк.
Systemd уже доступен для скачивания и возможно будет включен в один из будущих релизов Fedora в качестве альтернативной системы инициализации. Инструкции по установке в другие дистрибутивы можно найти на официальной страничке: freedesktop.org/wiki/Software/systemd.
Установить Systemd в Ubuntu можно, выполнив следующие команды:
$ sudo add-apt-repository ppa:andrew-edmunds/ppa
$ sudo apt-get update
$ sudo apt-get install systemd
Далее следует отредактировать /boot/grub/grub.cfg, добавив к параметрам ядра строчку init=/sbin/systemd. После перезагрузки дистрибутив будет работать с новой системой инициализации, в чем можно убедиться с помощью команды:
$ sudo systemctl units-list
Для проверки состояния, запуска, остановки и включения служб используются аргументы status, start, stop и enable.
Ulatencyd: мгновенная реакция
Какой, на твой взгляд, самый важный параметр десктопной операционной системы? Хороший графический интерфейс? Количество доступных приложений? Простота использования?
Да, все это имеет значение, но сегодня, когда этими свойствами легко наделить даже серверные ОС, решающими становятся совсем другие факторы, важнейший из которых — отзывчивость системы.
Хорошая десктопная ОС должна жертвовать всем в угоду высокой скорости реакции. Неважно, какую скорость она показывает при записи файлов на диск, сколько десктопных эффектов предлагает пользователю, правильно ли реагирует на внезапное извлечение флешки, все это не будет иметь никакого значения, если ОС заставляет пользователя ждать.
Для разработчиков операционных систем это прописная истина, поэтому во все времена они стремились сделать свои ОС более интерактивными. У одних это получалось хорошо (привет BeOS), у других плохо (куда же без MS), но были и такие, у кого это не получалось вообще. Долгое время разработчики Linux совершенно не интересовались темой отзывчивости Linux на десктопах. Кон Коливас множество раз указывал им на проблемы, говорил о неповоротливости и медлительности Linux, писал патчи, ночами кодил новые планировщики задач, добивался их включения в ядро. Все напрасно, раз за разом патчи отвергали, а самого автора грубо отстраняли от дел.
Однако со временем труды Кона Коливаса окупились. Инго Молнар начитался его исходников и написал планировщик CFS (Completely Fair Scheduler), а Линукс незамедлительно включил его в ядро 2.6.23. После этого положение дел на десктопах существенно улучшилось, и Linux стал намного быстрее (при этом реализация Кона все равно продолжала показывать более впечатляющие результаты).
Второй важной вехой на пути Linux к десктопу стало включение знаменитого 200-страничного патча в ядро 2.6.38, а также появление его аналога на языке bash. Так Linux научился отделять интерактивные процессы от всех остальных демонов, серверов и bash-скриптов и наделять их более высокими приоритетами. Это событие еще больше улучшило ситуацию и сделало ее практически идеальной: теперь Linux не тормозил даже тогда, когда в фоне шла пересборка ядра в несколько потоков.
Наконец, третьим важным шагом для десктопного Linux (здесь я подхожу к самому главному) стало появление демона ulatencyd, способного регулировать отзывчивость системы динамически, подстраивая ее под изменяющиеся обстоятельства.
Как и ядерный патч, ulatencyd использует механизм cgroups для группировки интерактивных процессов и изменения их приоритетов, но на этом его работа не заканчивается. Демон использует эвристические алгоритмы для выявления «наиболее интерактивных» процессов, а также явных вредителей системы, таких как форк-бомбы и программы с большими утечками памяти. При этом первые получают еще больший приоритет, а вторые жестко урезаются в возможностях (получая низкий приоритет, ограничения на доступную память), изолируются или уничтожаются. Но что самое главное, в любой момент демон можно обучить новым правилам отбора процессов, так что мы теперь можем назначать самые высокие (даже реалтаймовые) приоритеты любимым играм, видеоплеерам и браузерам.
Демон поддерживает плагины, поэтому его функциональность может быть расширена до просто фантастических возможностей. Например, уже сейчас доступен плагин (причем в стандартной комплектации), который следит за работой пользователя в иксах и назначает самые высокие приоритеты вновь открытым приложениям и процессам, окна которых находятся на переднем плане.
Чтобы установить ulatencyd, необходимо скачать его исходники со страницы https://github.com/poelzi/ulatencyd и собрать с помощью стандартных cmake и make:
$ cmake
$ make
$ sudo make install
Далее демон можно запустить:
$ sudo /usr/local/sbin/ulatencyd -v 2
И понаблюдать за тем, как он группирует процессы по приоритетам:
$ ps xaf -eo pid,session,args,cgroup
Никаких настроек производить не нужно. По умолчанию демон отдает предпочтение мультимедийным и наиболее используемым интерактивным приложениям, оставляя фоновые задачи спокойно работать, не мешая основной системе.
relayd: по трем фронтам
Какая связь между мониторингом сетевых хостов, балансировкой нагрузки и прокси-сервером? Все это функции одной машины? Да, вполне возможно. Но что если я скажу, что все эти три функции тесно связаны между собой и должны быть реализованы в рамках одного универсального приложения? Бред? Никак нет.
Возьмем, к примеру, достаточно распространенную в узких кругах функцию сервера под названием «распределение нагрузки между несколькими DNS-серверами». Что нужно для ее решения? Во-первых, умение перенаправлять DNS-трафик на другой хост (от балансировщика к одному из реальных DNS-серверов).
Это можно сделать с помощью брандмауэра или особым образом настроенного BIND (несколько тяжеловесный вариант). Вовторых, умение выбирать наиболее подходящего кандидата для обработки запроса из списка DNS-серверов. Это уже сложнее, и здесь может понадобиться специализированное ПО или опять же брандмауэр (но очень хороший). В-третьих, умение проверять DNS-сервера на доступность и удалять упавших из списка. Нужен скрипт, пингующий хосты и управляющий списками, либо особые возможности брандмауэра (а такой есть?). В общем, долгое нудное велосипедостроение или покупка специализированного решения для конкретной задачи по нереальным ценам. А что если завтра вдруг понадобится сделать нечто подобное для SMTP?
Все править или вновь открывать кошелек? Не стоит, содержимое кошелька все-таки лучше приберечь, а костыли с велосипедами оставить спортсменам. Демон relayd, появившийся в OpenBSD 4.3, позволяет решить эту и еще огромное количество других, подобных и не очень, задач всего за несколько минут.
Он включает в себя возможности балансировщика нагрузки для протоколов уровней 3, 4 и 7, прокси уровня 7 (релей) и сервиса проверки доступности сетевых узлов (из которого и вырос).
На основе relayd можно строить самые разные конфигурации, начиная от простых прокси-серверов или SSL-акселераторов и заканчивая сложными решениями вроде прозрачных web-прокси с фильтрацией запросов и распределением нагрузки между несколькими web-серверами. И все это с помощью простого конфигурационного файла, длина которого даже в самых сложных конфигурациях редко превышает 50 строк.
Да, конечно, без примера все это только слова. Так что вот рабочий конфиг, полностью удовлетворяющий требованиям, выдвинутым в начале раздела:
# vi /etc/relayd.conf
Переменные и макросы
Адрес и порт нашего релея
relayd_addr="127.0.0.1"
relayd_port="8053"
Адреса трех DNS-серверов, которые будут обрабатывать
запросы
table <dns_servers> { 192.168.1.1, 192.168.1.2, 192.168.1.3 }
Общие настройки
Интервал между проверками хостов на доступность
(10 секунд)
interval 10
Таймаут для проверки хостов на доступность методом TCP
(если хост не отвечает дольше 200 мс — он в дауне)
timeout 200
Разделяем сервер на 5 процессов для более эффективной
обработки запросов
prefork 5
Логировать результаты проверки хостов на доступность
log updates
Настройки DNS-протокола
Параметры оптимизации соединения
dns protocol "dnsfi lter" { tcp { nodelay, sack, socket buffer 1024, backlog 1000 } }
Настройки релея
relay dnsproxy {
Прослушиваемый адрес и порт
listen on $relayd_addr port $relayd_port
Работаем с описанным ранее протоколом
protocol "dnsfi lter"
Оправляем DNS-пакеты одному из перечисленных в таблице
DNS-серверов, предварительно проверив его на доступность
forward to <dns_servers> port 53
mode loadbalance check tcp
}
Наиболее важные части этого конфига находятся в теле директив dns protocol и relay. Первая представляет собой нечто вроде шаблона, который используется для того, чтобы не повторять одни и те же настройки протоколов в других частях конфигурационного файла (relayd поддерживает три протокола: HTTP, DNS и TCP). Вторая — это настройка релея, в котором указаны прослушиваемый порт, проксируемый протокол, его настройки и информация о том, каким образом и какому хосту должны быть перенаправлены пакеты. В нашем случае прокси должен отправить DNS-запрос одному из трех серверов с предварительной проверкой на доступность (здесь используется проверка методом TCP-рукопожатия, но relayd поддерживает множество других методов, начиная от ping и заканчивая попыткой установить SSLсоединение). При этом, если один из DNS-серверов окажется в дауне, relayd автоматически исключит его из списка до тех пор, пока плановая проверка на доступность (интервал между проверками указан в опции interval) не покажет его работоспособность.
Для тестирования конфигурации можно использовать следующую форму запуска relayd:
# relayd -d -vv -f /etc/relayd.conf
Так демон не уйдет в фон и будет вести подробную распечатку всех своих действий. После отладки конфигурации можно настроить запуск демона во время загрузки системы. Для этого достаточно поместить строку relayd_flags=»» в файл /etc/rc.conf.local.
FreeBSD fscd: красота минимализма
Этого раздела не должно было быть в статье. Демон fscd настолько простой инструмент, что писать о нем отдельно мне казалось излишним. С другой стороны, не писать о нем нельзя, потому как это один из ярчайших примеров правильного решения задачи в стиле UNIX. А задача у разработчиков FreeBSD была следующая.
Различные системные и не очень демоны могут время от времени падать (или начинать вести себя как идиоты, что еще хуже). На домашней машине это не страшно, упавшего можно перезапустить руками или отправить комп в перезагрузку. Но что делать на сервере, где админ бывает редко?
Сервисы надо мониторить и перезапускать по мере необходимости. Как это сделать? Естественно, встроить эту функциональность в систему инициализации (ведь именно она занимается запуском демонов). И, например, в Solaris так и сделано, да настолько экстравагантно, что сам Линус Торвальдс ногу сломит, пока разберется с его настройкой. Разработчики FreeBSD поступили проще. Они написали отдельный демон, который способен работать со скриптами инициализации FreeBSD, оставаясь совершенно независимой системой. Вся соль в том, что fscd получился настолько простым, что им можно пользоваться, не читая man-страниц и не беспокоясь о том, что он может упасть. Посуди сам, чтобы заставить fscd следить за, например, sshd, нужно ввести всего одну команду:
# fscadm enable sshd /var/run/sshd.pid
Все, fscd запомнит этот выбор и автоматически включит мониторинг после перезагрузки машины. Единственное условие: у подконтрольного демона должен быть инициализационный файл в каталоге /etc/rc.d (или /usr/local/etc/rc.d) и запись в файле /etc/rc.conf, включающая его (это очевидно).
Демон fscd будет доступен только во FreeBSD 9.0, но его вполне можно скачать с официальной странички (people.freebsd.org/~trhodes/fsc) и собрать для восьмерки.
Выводы
Каждый день в мире UNIX появляется что-то новое, но очень редко это новое оказывается чем-то стоящим нашего внимания. В этой статье я рассказал о четырех системных компонентах UNIX, которые не только заслуживают особого внимания, но и несут реальную пользу. Кто знает, возможно в будущем они будут такой же неотъемлемой частью UNIX, как команда grep или демон syslog.
Links
- дом systemd под крылом: freedesktop.org/wiki/Software/systemd; freedesktop.org
- исходные тексты systemd: cgit.freedesktop.org/systemd;
- код ulatencyd: github.com/poelzi/ulatencyd;
- исходники fscd: people.freebsd.org/~trhodes/fsc.
Info
- В долгосрочной перспективе автор собирается превратить systemd в полноценный менеджер сессий, способный заменить gnomesession и kdeinit.
- Кроме всего прочего, systemd обладает функциями монитора для демонов, так что возможности fscd встроены в него от рождения.
- Отказ от использования скриптов — один из методов ускорить процесс загрузки. Многие задачи инициализации systemd способен произвести через прямой вызов нужных команд, без использования скриптов.
- Прежнее название relayd — hostated (от слов host state, «состояние хоста»), было изменено на теперешнее в связи с расширением функционала.