В systemd, в отличие от классической системы инициализации sysv initd, вместо shell-скриптов для запуска сервисов используются специальные service-файлы. service-файлы являются подтипом более общих файлов инициализации systemd - юнит-файлов. юнит-файлы позволяют запускать не только файлы сервисов, но и создавать Unix-сокеты, обрабатывать события от устройств и т.п.
Безусловно, shell-скрипты обладают большей гибкостью, однако большинство операций, выполняемых shell-скриптами инициализации сервисов, не отличаются оригинальностью и довольно громоздки. service-файлы позволяют описывать типовые операции во много раз проще.
Системные service-файлы, установленные средствами пакетного менеджера, располагаются в каталоге /lib/systemd/system/. Пользовательские service-файлы располагаются в каталоге /etc/systemd/system/. Они имеют приоритет над системными. Если нужно изменить системный service-файл, достаточно скопировать его в каталог пользовательских service-файлов и отредактировать необходимым образом.
service-файл является файлом в формате INI и состоит из трёх разделов, имена которых берутся в квадратные скобки. После строки с именем раздела следуют строки с параметрами, специфичными для данного раздела. Рассмотрим основные параметры каждого раздела.
Этот раздел содержит общую информацию о сервисе, устройстве, точке монтирования и т.п.:
Содержит информацию, специфичную для описания сервиса:
Содержит рекомендации по установке юнита. Описывает, в каких случаях юнит должен быть запущен:
Пример простейшего service-файла уже был приведён в прошлой заметке:
[Unit] Description=openntpd After=network.target [Service] Type=simple ExecStart=/usr/sbin/ntpd -dsf /etc/openntpd/ntpd.conf ExecStartPost=/bin/chown ntpd /var/lib/openntpd/ntpd.drift ExecStop=/sbin/hwclock -w [Install] WantedBy=multi-user.target
Большинство сервисов запускается в единственном экземпляре, однако существуют и сервисы, которые необходимо запустить в нескольких экземплярах. Для того, чтобы не создавать отдельный service-файл для каждого экземпляра сервиса, в systemd была реализована поддержка шаблонов сервисов. Шаблон многоэкземплярного сервиса содержит символ @ в имени service-файла. Перед этим символом в имени service-файла указывается имя многоэкземплярного сервиса.
Когда возникает необходимость создать дополнительный экземпляр сервиса на основе шаблона, в каталоге /etc/systemd/system/*.wants создаётся символическая ссылка на шаблон. Имя символической ссылки совпадает с именем шаблона, но после символа @ добавляется идентификатор экземпляра сервиса. Для примера создадим экземпляр сервиса serial-getty на последовательном порту /dev/ttyS2 (команда systemctl enable для экземпляров сервисов в Debian Wheezy не поддерживается):
# systemctl start serial-getty@ttyS2.service # mkdir /etc/systemd/system/getty.target.wants/ # ln -s /lib/systemd/system/serial-getty@.service /etc/systemd/system/getty.target.wants/serial-getty@ttyS2.service
Идентификатор экземпляра сервиса подставляется в шаблон service-файла на место следующих символов-заменителей:
Что делать, если один из экземпляров сервиса нужно запустить с какими-то индивидуальными настройками, не совпадающими с шаблонными? В этом случае можно просто создать для экземпляра сервиса индивидуальный service-файл, в котором и указать необходимые настройки. systemd, прежде чем использовать шаблоны сервисов, пытается найти service-файл с указанным именем. И только если найти такой файл не удалось, пытается воспользоваться шаблоном.
Например, если необходимы какие-то индивидуальные настройки для getty на tty2, достаточно создать service-файл /etc/systemd/system/getty@tty2.service и прописать в него индивидуальные настройки. Символы-заменители продолжают действовать и внутри этого файла. Их полный список можно найти на странице руководства systemd.unit(5).
Пример шаблона service-файла serial-getty@.serice:
[Unit] Description=Serial Getty on %I BindTo=dev-%i.device After=dev-%i.device systemd-user-sessions.service [Service] ExecStart=-/sbin/agetty -s %I 115200,38400,9600 Restart=always RestartSec=0
Полную версию файла можно посмотреть в реальной системе.
Раз уж мы коснулись темы многоэкземплярных сервисов на примере getty, разберёмся в этом вопросе подробнее.
В systemd имеется два шаблона service-файлов для терминалов:
Главное отличие systemd от SysV init заключается в том, что терминалы запускаются по запросу, а не при загрузке системы. Имеется только два исключения: консоль tty1 запускается автоматически, в графическом режиме на ней запускается дисплейный менеджер, а в текстовом - getty, консоль tty6 тоже запускается автоматически и на ней всегда запускается getty, чтобы всегда имелась хотя-бы одна текстовая консоль для решения различных проблем.
Настройки по умолчанию можно изменить в файле /etc/systemd/systemd-logind.conf (в руководстве был указан файл /etc/systemd/logind.conf):
Если необходимо настроить последовательную консоль в качестве системной, нужно добавить к строке загрузки ядра дополнительную опцию: console=ttyS0.
Для автоматической настройки других последовательных консолей systemd использует программу systemd-getty-generator, которая обнаруживает доступные последовательные консоли и запускает на каждой из них по экземпляру сервиса serial-getty@.service. Это, в том числе, могут быть консоли, предоставляемые системами виртуализации. Для запуска на какой-либо последовательной консоли терминала, нужно воспользоваться уже известными командами:
Сокет-активация - это техника запуска сервисов по факту обращения к соответствующему сокету, подобная той, что реализуется в демонах inetd и xinetd. systemd поддерживает три схемы запуска сервисов, использующие сокет-активацию:
Для примера настроим сокет-активацию SSH. Для этого необходимо создать два юнит-файла: юнит-файл, описывающий сокет, и юнит-файл, описывающий сервис.
Содержимое файла sshd.socket приведено ниже:
[Unit] Description=SSH Socket for Per-Connection Servers [Socket] ListenStream=22 Accept=yes [Install] WantedBy=sockets.target
Директива ListenStream задаёт номер TCP-порта, на котором следует ожидать подключений.
Директива Accept указывает, как systemd должен поступать с поступающими запросами. В случае значения yes, systemd самостоятельно принимает соединение и передаёт сокет-файл соединения в сервис, что соответствует третьей схеме запуска сервисов. В случае значения no, systemd передаёт прослушивающий сокет-файл сервису, не принимая соединения, что соответствует второй схеме запуска.
В свою очередь, файл sshd@.service выглядит следующим образом:
[Unit] Description=SSH Per-Connection Server for %I [Service] ExecStart=-/usr/sbin/sshd -i StandartInput=socket
Минус перед строкой команды ExecStart означает, что systemd не будет запоминать код завершения каждого процесса. Сбросить коды завершения всех сервисов можно с помощью команды systemctl reset-failed.
Директива StandartInput указывает, что на стандартный ввод команды будут подаваться данные, принятые через сокет-файл соединения. Директива StandartOutput, если она не указана, принимает то же значение, что и директива StandartInput. В данном случае это означает, что стандартный вывод процесса будет перенаправляться в сокет-файл соединения.
Для запуска сервиса используется файл, описывающий сокет сервиса:
# systemctl enable sshd.socket # systemctl start sshd.socket
Как и обычно, статус сервиса можно проверить с помощью команды systemctl status sshd.socket. Среди прочей информации в статусе будут присутствовать счётчик количества соединений, принятых с момента запуска сервиса (Accepted) и счётчик количества активных соединений (Connected).
Список активных сеансов можно увидеть с помощью команды:
# systemctl --full | grep sshd
А любой из экземпляров сервиса можно принудительно завершить, указав его идентификатор:
# systemctl kill sshd@171.31.0.52:22-172.31.0.4:47779.service
Подобным образом можно активировать только те сервисы, которые поддерживают получение сокет-файла. Сервисы, открывающие сокет-файл самостоятельно, активировать таким образом не получится.
Стоит отметить, что systemd не реализует полный набор возможностей, предоставляемых демонами inetd и xinetd. Однако, большинство из них либо устарели, либо легко реализуются другими средствами. Устаревшими можно считать, например, поддержку TCPMUX, RPC и встроенных сервисов типа echo, time, daytime, discard. Примером возможности, которую можно реализовать другими средствами, являются списки управления доступом с IP-адресов: её можно реализовать с помощью пакетного фильтра iptables. При этом, iptables позволяет организовать более тонкое и эффективное ограничение доступа, например, блокировать IP-адреса, превышающие лимит попыток подключения за единицу времени и т.п.
Описанная в предыдущем разделе возможность активации сервисов по требованию оказалась особенно интересна компаниям, предоставляющим услуги веб-хостинга, поскольку позволяет экономить ресурсы сервера за счёт редко посещаемых сайтов. К стати пришлась и возможность создать один шаблон service-файла для всех сайтов клиентов, а также возможности по увеличению безопасности и ограничению использования системных ресурсов (эта тема будет подробно рассмотрена в следующей заметке).
Следующим логичным шагом в этом направлении является запуск по требованию не только сервисов, но и контейнеров с виртуальными серверами клиентов. Схема активации в этом случае выглядит так: процесс systemd, работающий на хост-системе, ожидает подключений к сокету. При поступлении запроса на подключение, запускается контейнер, а сокет передаётся на обслуживание процессу systemd, работающему внутри контейнера. Тот, в свою очередь, действует по схеме, описанной в предыдущем разделе, запуская необходимый сервис с помощью сокет-активации. Всё происходит совершенно прозрачно для клиента, разве что задержка между подключением и началом обслуживания может оказаться больше, чем обычно.
Для создания виртуального окружения в настоящее время в systemd используется утилита systemd-nspawn, которую, как надеются авторы, вскоре заменит libvirt-lxc (чуть более подробное описание systemd-nspawn появится в следующей заметке).
Внутри контейнера может быть установлен дистрибутив Linux, отличный от дистрибутива на хост-системе. Например, для установки Fedora и Debian можно воспользоваться соответствующими командами:
# yum --releasever=19 --nogpg --installroot=/srv/mycontainer/ --disablerepo='*' --enablerepo=fedora install systemd passwd yum fedora-release vim-minimal # debootstrap --arch=amd64 unstable /srv/mycontainer/
После того, как вы убедились, что контейнер загружается и работает, можно приступить к настройке service-файла для активации контейнера /etc/systemd/system/mycontainer.service:
[Unit] Description=My little container [Service] ExecStart=/usr/bin/systemd-nspawn -jbD /srv/mycontainer 3 KillMode=process
В полученный контейнер пока что нельзя попасть, поэтому нужно настроить на нём SSH-сервер. Для начала, настроим ожидание подключения к SSH-серверу контейнера на хост-системе в файле /etc/systemd/system/mycontainer.socket:
[Unit] Description=The SSH socket of my little container [Socket] ListenStream=23
Для подключения по SSH к контейнеру будет использоваться 23 порт хост-системы. При желании можно использовать и 22 порт, ожидающий подключения на отдельном IP-адресе.
Теперь нужно настроить сокет-активацию сервера SSH внутри контейнера. Для этого можно воспользоваться файлами sshd.socket и ssh@.service из предыдущего раздела, нужно только не забыть поменять 22 порт на 23. Осталось лишь создать правильные символические ссылки для включения сервиса внутри контейнера. Поскольку команда systemctl enable не поддерживается в ранних версиях systemd, можно сделать это следующим образом:
# chroot /srv/mycontainer ln -s /etc/systemd/system/sshd.socket /etc/systemd/system/sockets.target.wants
В более свежих версиях systemd это можно сделать проще:
# systemctl --root=/srv/mycontainer enable sshd.socket
Теперь, при попытке подключиться к 23 TCP порту на хост-системе, автоматически будет запущен контейнер, внутрь него будет передан сокет, а systemd, находящийся внутри контейнера, запустит экземпляр сервиса SSH для обслуживания подключения.
Аналогичным образом можно добавить внутрь контейнера дополнительные сервисы, добавляя дополнительные директивы ListenStream в service-файл контейнера на хост-системе. При запуске контейнера, в него будут переданы все сокеты, описанные в его service-файле. Те сокеты, обслуживание которых не настроено внутри контейнера, будут закрыты.
В команде запуска systemd-nspawn из примера service-файла контейнера, указана опция -j, которая предписывает команде создать на хост-системе символическую ссылку на файл журнала journald, работающего внутри контейнера. При вводе команды systemd-journalctl с опцией -m на хост-системе, можно просматривать объединённый журнал хост-системы и контейнеров.
В настоящее время у такого подхода к активации контейнеров есть только один существенный недостаток - после обслуживания клиента контейнер продолжает работать, вхолостую используя ресурсы хост-системы. Над исправлением этого недостатка уже ведутся работы.
Некоторые сценарии должны выполнять запуск сервиса только внутри виртуального окружения, или наоборот - только на хост-системе. Для этого в сценарии инициализации добавляются соответствующие средства обнаружения виртуального окружения и определения его типа. Чтобы упростить эти типовые действи, в systemd были добавлены несколько средств определения виртуальных окружений, каждое из которых может пригодиться в соответствующей ситуации. Эти средства позволяют определить конкретный тип виртуализации одного из следующих типов:
С помощью директивы ConditionVirtualization можно указать условие запуска сервиса: yes - сервис будет запускаться внутри виртуального окружения, no - сервис будет запускаться только на хост-системе. Можно указать значение vm или container, если сервис должен быть запущен только при обнаружении виртуального окружения из одной из этих групп. Можно указать и конкретный тип виртуального окружения. Для инверсии значения опции используется восклицательный знак в начале значения. Например, значение "!xen" предписывает запуск сервиса на хост-системе и внутри любого виртуального окружения, за исключением xen.
С помощью команды systemd-detect-virt можно определить наличие виртуального окружения и его тип из скриптов. Если команда обнаружит, что она была запущена из виртуальной среды, она вернёт код завершения 0 и ненулевое значение, если виртуальная среда не обнаружена. В случае обнаружения виртуальной среды команда выводит её тип на стандартный вывод. С помощью опции -q можно отключить вывод типа виртуальный среды. Опции -c и -v позволяют обнаруживать только средства контейнерной виртуализации или только средства полной виртуализации.
Программы, работающие с шиной DBus, могут узнать тип виртуального окружения, запросив соответствующее свойство у системной шины:
$ gdbus call --system --dest org.freedesktop.systemd1 --object-path /org/freedesktop/systemd1 --method org.freedesktop.DBus.Properties.Get org.freedesktop.systemd1.Manager Virtualization
Если виртуальное окружение не обнаружена, это свойство содержит пустую строку. (Команда очень проста для запоминания, не правда-ли? Слава роботам!)
В комплекте с Debian Wheezy поставляется версия systemd, в которой отсутствует поддержка проверки на виртуальность. В man systemd.unit встречается упоминание директивы ConditionVirtualization, но нет команды systemd-detect-virt, а запрос свойства DBus возвращает ошибку.
Файлы с описаниями сервисов могут содержать директивы со ссылками на документацию, относящуюся к данному сервису. Например, раздел Unit service-файла может выглядеть следующим образом:
[Unit] Description=openntpd Documentation=man:openntpd(8) http://www.openntpd.org After=network.target
Поддерживаются схемы man/info и http/https. При наличии в service-файле ссылок на документацию, в выводе информации о состоянии сервиса с помощью команды systemctl status можно будет увидеть строки Docs и ссылки на документацию. Если используется терминал, работающий в графическом режиме, ссылки будут подсвечены и при щелчке по ним откроется документация.
При отсутствии графического терминала, открыть ссылки на документацию можно с помощью следующей команды:
$ systemctl help openntpd.service
К сожалению, эта возможность не поддерживается в systemd, поставляющемся в комплекте с Debian Wheezy.
systemd поддерживает два вида сторожевых таймеров - аппаратные и программные.
Аппаратный сторожевой таймер принимает сигналы от программного обеспечения, работающего на компьютере и позволяет автоматически перезагрузить компьютер, если в течение определённого интервала времени от программного обеспечения не поступали сигналы.
Программные сторожевые таймеры представляют собой отдельную программу, принимающую сигналы от других программ и выполняющую определённые действия, если эти сигналы не поступают в течение указанного интервала времени.
В совокупности оба вида сторожевых таймеров позволяют обнаруживать как аппаратные сбои компьютера, так и сбои в работе сервисов, уведомляя о сбоях администратора системы или выполняя заранее определённые действия. systemd выступает центральным звеном этой системы. Он способен принимать сигналы от сервисов, выполняя определённые действия при их сбоях, отправляя, в свою очередь, аппаратному таймеру сигналы о собственной работоспособности.
Аппаратный сторожевой таймер представлен в системе файлом устройства /dev/watchdog. Настройки systemd, связанные с аппаратным таймером, находятся в файле /etc/systemd/system.conf.
Директива RuntimeWatchdogSec по умолчанию принимает нулевое значение, что отключает использование аппаратного таймера systemd. При указании ненулевого значения systemd будет отправлять в аппаратный сторожевой таймер сигналы с периодом, равным половине указанного.
Директива ShutdownWatchdogSec задаёт длительность, отводимую на процесс перезагрузки системы. После сработки сторожевого таймера, пока система загружается, сторожевой таймер не будет предпринимать никаких действий в течение этого периода времени. Если по истечение этого периода времени сигналы не начнут поступать, аппаратный сторожевой таймер предпримет повторную попытку перезагрузки системы, посчитав что система зависла в процессе загрузки. По умолчанию эта директива принимает значение 10min.
Определить наличие в системе аппаратного сторожевого таймера можно с помощью команды wdctl из пакета util-linux (в Debian Wheezy такой команды в этом пакете нет).
Если сервис поддерживает периодическую отправку сигналов с помощью системного вызова sd_notify, можно настроить его перезапуск средствами systemd в случае длительного отсутствия сигналов от него. Значение периода ожидания сигналов сообщается сервису с помощью переменной окружения WATCHDOG_USEC и задаётся в микросекундах. Сервис должен отправлять сигналы с периодичностью, равной половине указанной. При отсутствии этой переменной в окружении сервиса, он не должен отправлять сигналы с помощью sd_notify.
Настройки в service-файле позволяют определить реакцию systemd на отсутствие сигналов от сервиса в течение периода ожидания.
Директива WatchdogSec задаёт период ожидания сигналов от сервиса. Это значение передаётся сервису в переменной окружения WATCHDOG_USEC.
Директива Restart задаёт настройки условий перезапуска сервиса. Значение on-failure соответствует перезапуску сервиса в случае его сбоя и включает в себя в том числе ситуацию, когда сработал сторожевой таймер.
С помощью трёх следующих директив можно задать действия, предпринимаемые в случае слишком частых сбоев сервиса.
Директива StartLimitBurst задаёт количество сбоев, при достижении которого будет выполнено действие.
Директива StartLimitInterval задаёт интервал времени, за который должно произойти указанное количество сбоев, чтобы действие было выполнено.
Директива StartLimitAction задаёт выполняемое действие. Значение none не задаёт никаких действий. Значение reboot указывает на необходимость перезагрузить всю систему в штатном режиме. Значение reboot-force отправит систему на перезагрузку без корректной остановки сервисов. Значение reboot-immediate отправит систему в немедленную перезагрузку без корректной остановки сервисов и размонтирования файловых систем.
Директива OnFailure позволяет указать юнит, инициируемый в случае сбоя сервиса. В этом юните может быть настроен, например, скрипт отправки уведомлений системному администратору по электронной почте или по SMS.
Директива OnFailureIsolate позволяет перевести систему в другое состояние, например, остановив некоторые другие сервисы.