воскресенье, 24 февраля 2013 г.

systemd. Часть 2. service-файлы

В systemd, в отличие от классической системы инициализации sysv initd, вместо shell-скриптов для запуска сервисов используются специальные service-файлы. service-файлы являются подтипом более общих файлов инициализации systemd - юнит-файлов. юнит-файлы позволяют запускать не только файлы сервисов, но и создавать Unix-сокеты, обрабатывать события от устройств и т.п.

Безусловно, shell-скрипты обладают большей гибкостью, однако большинство операций, выполняемых shell-скриптами инициализации сервисов, не отличаются оригинальностью и довольно громоздки. service-файлы позволяют описывать типовые операции во много раз проще.

Системные service-файлы, установленные средствами пакетного менеджера, располагаются в каталоге /lib/systemd/system/. Пользовательские service-файлы располагаются в каталоге /etc/systemd/system/. Они имеют приоритет над системными. Если нужно изменить системный service-файл, достаточно скопировать его в каталог пользовательских service-файлов и отредактировать необходимым образом.

1. Формат service-файла

service-файл является файлом в формате INI и состоит из трёх разделов, имена которых берутся в квадратные скобки. После строки с именем раздела следуют строки с параметрами, специфичными для данного раздела. Рассмотрим основные параметры каждого раздела.

  • Раздел Unit

    Этот раздел содержит общую информацию о сервисе, устройстве, точке монтирования и т.п.

    Description - строка описания.

    After - указывает, что этот сервис должен запускаться после указанного. Эта директива не описывает зависимость, для описания зависимостей используется директива Require.
  • Раздел Service

    Содержит информацию, специфичную для описания сервиса.

    ExecStart - путь к демону и аргументы демона для его запуска. При указании минуса перед строкой команды systemd не будет запоминать код завершения процесса.

    Type - определяет, каким образом systemd будет отслеживать, запустился ли сервис.

    • forking - запущенный процесс порождает другой процесс, а сам завершается. systemd считает сервис запущенным, когда завершается запущенный процесс. Главным процессом сервиса считается порождённый процесс.
    • dbus - после запуска сервис зарегистрируется на шине Dbus под именем, указанным в настройке BusName. systemd сможет отслеживать состояние сервиса через Dbus.

    BusName - имя сервиса, под которым он зарегистрируется на шине Dbus. Эта директива используется в том случае, когда директива Type принимает значение dbus.

    Restart - директива указывает на способ перезапуска сервиса в случае его падения.

    • restart-always - при падении перезапускать сервис всегда.

    EnvironmentFile - позволяет задать файл с переменными окружения для сервиса. В файле допускается указывать только определения переменных.
  • Раздел Install

    Содержит рекомендации по установке юнита. Описывает, в каких случаях юнит должен быть запущен.

    WantedBy - описывает, в каких случаях юнит должен быть активирован.
Пример простейшего 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
2. Экземпляры сервисов

Большинство сервисов запускается в единственном экземпляре, однако существуют и сервисы, которые необходимо запустить в нескольких экземплярах. Для того, чтобы не создавать отдельный 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-файла на место следующих символов-заменителей:
%I - идентификатор экземпляра сервиса подставляется в шаблон как есть,
%i - идентификатор экземпляра сервиса подставляется в шаблон в экранированном виде. Это бывает нужно, если идентификатором экземпляра сервиса является путь к устройству, содержащий косые черты. Чтобы использовать этот путь в имени юнит-файла устройства (dev-%i.device), нужно заэкранировать все специальные символы.

Что делать, если один из экземпляров сервиса нужно запустить с какими-то индивидуальными настройками, не совпадающими с шаблонными? В этом случае можно просто создать для экземпляра сервиса индивидуальный 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
Полную версию файла можно посмотреть в реальной системе.

3. Запуск getty на консолях

Раз уж мы коснулись темы многоэкземплярных сервисов на примере getty, разберёмся в этом вопросе подробнее.

В systemd имеется два шаблона service-файлов для терминалов:
getty@.service - шаблон для запуска виртуальных терминалов, в переменной TERM используется значение linux,
serial-getty@.service - шаблон для запуска терминалов на последовательных портах. в переменной TERM используется значение vt102. В этом шаблоне, в отличие от предыдущего, отсутствуют настройки для очистки буфера прокрутки, поскольку он попросту отсутствует.

Главное отличие systemd от SysV init заключается в том, что терминалы запускаются по запросу, а не при загрузке системы. Имеется только два исключения: консоль tty1 запускается автоматически, в графическом режиме на ней запускается дисплейный менеджер, а в текстовом - getty, консоль tty6 тоже запускается автоматически и на ней всегда запускается getty, чтобы всегда имелась хотя-бы одна текстовая консоль для решения различных проблем.

Настройки по умолчанию можно изменить в файле /etc/systemd/systemd-logind.conf (в руководстве был указан файл /etc/systemd/logind.conf):
NAutoVTs - количество терминалов, запускаемых автоматически (по умолчанию - 6),
ReserveVT - номер зарезервированной виртуальной консоли (по умолчанию - 6).

Если необходимо настроить последовательную консоль в качестве системной, нужно добавить к строке загрузки ядра дополнительную опцию: console=ttyS0.

Для автоматической настройки других последовательных консолей systemd использует программу systemd-getty-generator, которая обнаруживает доступные последовательные консоли и запускает на каждой из них по экземпляру сервиса serial-getty@.service. Это, в том числе, могут быть консоли, предоставляемые системами виртуализации. Для запуска на какой-либо последовательной консоли терминала, нужно воспользоваться уже известными командами:

4. Сокет-активация

Сокет-активация - это техника запуска сервисов по факту обращения к соответствующему сокету, подобная той, что реализуется в демонах inetd и xinetd. systemd поддерживает три схемы запуска сервисов, использующие сокет-активацию:

1. Схема, ориентированная на параллельный запуск сервисов и упрощение зависимостей между ними. Например, syslog и dbus могут запускаться параллельно с сервисами, использующими их. systemd создаёт файл сокета и запускает одновременно и сервисы, обслуживающие файл сокета, и сервисы, пользующиеся им. Если сервис-потребитель запустится раньше сервиса-поставщика, то запрос попадёт в сокет-файл и будет ожидать обработки до того момента, когда запустится сервис-поставщик.

2. Схема, ориентированная на активацию по запросу одиночных редко используемых сервисов. Примером такого сервиса может быть демон печати CUPS. systemd создаёт сокет-файл, который прослушивает самостоятельно. При поступлении запроса в сокет-файл, systemd запустит CUPS и передаст сокет-файл ему.

3. Схема, ориентированная на активацию по запросу многоэкземплярных сервисов, в которых каждое соединение или запрос обрабатываются отдельным процессом. Например, сервер ssh. 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-адреса, превышающие лимит попыток подключения за единицу времени и т.п.

5. Сокет-активация контейнеров

Описанная в предыдущем разделе возможность активации сервисов по требованию оказалась особенно интересна компаниям, предоставляющим услуги веб-хостинга, поскольку позволяет экономить ресурсы сервера за счёт редко посещаемых сайтов. К стати пришлась и возможность создать один шаблон 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 на хост-системе, можно просматривать объединённый журнал хост-системы и контейнеров.

В настоящее время у такого подхода к активации контейнеров есть только один существенный недостаток - после обслуживания клиента контейнер продолжает работать, вхолостую используя ресурсы хост-системы. Над исправлением этого недостатка уже ведутся работы.

6. Проверка на виртуальность

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

1. Полная виртуализация (vm): quemu, kvm, vmware, microsoft, oracle, xen, bochs,

2. Контейнерная виртуализация (container): chroot, openvz, lxc, lxc-libvirt, systemd-nspawn.

С помощью директивы 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 возвращает ошибку.

7. Документация сервисов

Файлы с описаниями сервисов могут содержать директивы со ссылками на документацию, относящуюся к данному сервису. Например, раздел 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.

8. Сторожевые таймеры

systemd поддерживает два вида сторожевых таймеров - аппаратные и программные.

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

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

В совокупности оба вида сторожевых таймеров позволяют обнаруживать как аппаратные сбои компьютера, так и сбои в работе сервисов, уведомляя о сбоях администратора системы или выполняя заранее определённые действия. systemd выступает центральным звеном этой системы. Он способен принимать сигналы от сервисов, выполняя определённые действия при их сбоях, отправляя, в свою очередь, аппаратному таймеру сигналы о собственной работоспособности.

8.1. Аппаратный сторожевой таймер

Аппаратный сторожевой таймер представлен в системе файлом устройства /dev/watchdog. Настройки systemd, связанные с аппаратным таймером, находятся в файле /etc/systemd/system.conf.

Директива RuntimeWatchdogSec по умолчанию принимает нулевое значение, что отключает использование аппаратного таймера systemd. При указании ненулевого значения systemd будет отправлять в аппаратный сторожевой таймер сигналы с периодом, равным половине указанного.

Директива ShutdownWatchdogSec задаёт длительность, отводимую на процесс перезагрузки системы. После сработки сторожевого таймера, пока система загружается, сторожевой таймер не будет предпринимать никаких действий в течение этого периода времени. Если по истечение этого периода времени сигналы не начнут поступать, аппаратный сторожевой таймер предпримет повторную попытку перезагрузки системы, посчитав что система зависла в процессе загрузки. По умолчанию эта директива принимает значение 10min.

Определить наличие в системе аппаратного сторожевого таймера можно с помощью команды wdctl из пакета util-linux (в Debian Wheezy такой команды в этом пакете нет).

8.2. Программные сторожевые таймеры

Если сервис поддерживает периодическую отправку сигналов с помощью системного вызова 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 позволяет перевести систему в другое состояние, например, остановив некоторые другие сервисы.

Ссылки:
1. systemd.directives — Index of configuration directives - указатель на страницы руководства, описывающие директивы юнит-файлов
2. Deploying Node.js with systemd - использование сокет-активации на веб-хостинге с сайтами на node.js

Комментариев нет: