Systemd для адміністратора, 11

11. Служби з активацією у стилі inetd

В одному з попередніх розділів (гл. 3) я розповідав, як можна перетворити SysV init-скрипт на юніт-файл systemd. У цьому розділі ми розглянемо, як провести аналогічне перетворення служб inetd.

Почнемо з невеликого екскурсу до історії питання. Вже багато років inetd вважається однією з базових служб Unix-систем. Працюючи як суперсервер, він слухає мережеві сокети від імені різних служб, та активує відповідні служби під час вступу на ці сокети вхідних з'єднань. Таким чином, він забезпечує механізм активації на запит (on-demand activation). Подібний підхід позбавляє необхідності тримати постійно працюючими всі серверні процеси, що дозволяє підтримувати безліч служб навіть на системах з дуже обмеженими ресурсами. У дистрибутивах Linux можна зустріти кілька різних реалізацій inetd. Найбільш популярні з них – BSD inetd та xinetd. Хоча inetd у багатьох дистрибутивах досі встановлюється за замовчуванням, зараз він уже рідко використовується для запуску мережевих служб – тепер більшість із них запускаються автономно під час завантаження системи (основний аргумент на користь такої схеми – вона дозволяє забезпечити більш високу продуктивність служб). Однією з ключових можливостей systemd (а також launchd від Apple) є сокет-активація - той самий механізм, давним-давно реалізований inetd, проте в даному випадку ключові цілі трохи інші. Сокет-активація в стилі systemd насамперед орієнтована на локальні сокети (AF_UNIX), хоча підтримуються також мережні сокети (AF_INET). І важливіша відмінність — сокет-активація в systemd забезпечує не лише економію системних ресурсів, а й ефективну паралелізацію роботи (оскільки вона дозволяєзапускати клієнтські та серверні процеси одночасно, безпосередньо після створення сокету), спрощення процесу конфігурації (відпадає необхідність явно вказувати залежності між службами) та збільшення надійності (перезапуск служби, службовий чи екстрений – у разі падіння – не призводить до недоступності сокету). Тим не менш, systemd нітрохи не гірше inetd може запускати служби у відповідь на вхідні мережеві з'єднання. Будь-яка сокет-активація вимагає відповідної підтримки з боку самої служби, що запускається. systemd надає дуже простий інтерфейс, який можна використовувати службами для забезпечення сокет-активації. В основі цього простого та мінімалістичного механізму лежить функція sd_listen_fds(). Однак, інтерфейс, що традиційно використовується в inetd, ще простіше. Він дозволяє передавати службі, що запускається, тільки один сокет, який формується з потоків STDIN і STDOUT запущеного процесу. Підтримка цього механізму також присутня в systemd - для забезпечення сумісності з безліччю служб, у яких сокет активація реалізована тільки в стилі inetd.

Перш ніж ми перейдемо до прикладів, розглянемо три різні схеми, які використовують сокет-активацію:

1. Сокет-активація, орієнтована на паралелізацію, спрощення і надійність: сокети створюються на ранніх стадіях завантаження, і єдиний екземпляр служби відразу починає обслуговувати всі запити. Така схема підходить для системних служб, що часто використовуються — очевидно, що такі служби краще запускати якомога раніше, і по можливості паралельно. Як приклад можна навести D-Bus та Syslog.

2. Сокет-активація для одиночних служб: сокети створюються на ранніх стадіях завантаження, і єдиний екземпляр служби запускається при отриманні запитів. Така схемабільше підходить для служб, що рідко використовуються, дозволяючи забезпечити економію системних ресурсів і зменшити час завантаження, просто відклавши активацію служби до того моменту, коли вона дійсно знадобиться. Приклад - CUPS.

3. Сокет-активація для служб, що запускають по екземпляру на кожне з'єднання: сокети створюються на ранній стадії завантаження, і при кожному вхідному з'єднанні запускається екземпляр служби, якому передається сокет з'єднання (сокет, що слухає, при цьому залишається у суперсервера, inetd або systemd). Ця схема підходить для служб, що рідко використовуються, не критичних за продуктивністю (кожен новий процес займає порівняно небагато ресурсів). Приклад: SSH.

Описані схеми не еквівалентні з погляду продуктивності. Перша і друга схема, після завершення запуску служби, забезпечують таку саму продуктивність, як і у випадку з незалежною (stand-alone) службою (тобто службою, запущеною без використання суперсервера і сокет-активації), оскільки сокет, що слухає, передається безпосередньо процесу служби, і подальша обробка вхідних з'єднань відбувається так само, як і в незалежній службі. У той же час, продуктивність третьої із запропонованих схем часом залишає бажати кращого: кожне вхідне з'єднання породжує ще один процес служби, що при великій кількості з'єднань може призвести до значного споживання системних ресурсів. Втім, ця схема має свої переваги. Зокрема забезпечується ефективна ізоляція обробки клієнтських запитів і, крім того, вибір такої схеми активації значно спрощує процес розробки служби. У systemd найбільша увага приділяється першій із цих схем, проте решта двох теж чудово підтримується. Свого часу, я розповідав, які зміни вкод служби потрібно зробити для забезпечення роботи за другою схемою, на прикладі сервера CUPS. Що ж до inetd, то він призначений насамперед для роботи за третьою схемою, хоча підтримує і другу (але не першу). Саме через специфіку цієї схеми inetd отримав репутацію «повільного» (що, насправді, трохи несправедливо).

Отже, вивчивши теоретичну сторону питання, перейдемо до практики та розглянемо, як inetd-служба може бути інтегрована з механізмами сокет-активації systemd.

Як приклад візьмемо SSH, відому та широко поширену службу. На більшості систем, де вона використовується, частота звернень до неї не перевищує одного разу на годину (як правило, вона значно менша за цю величину). SSH вже дуже давно підтримує сокет-активацію в стилі inetd згідно третьої схеми. Так як необхідність у цій службі виникає порівняно рідко, і кількість одночасно працюючих процесів зазвичай невелика, вона добре підходить для використання за цією схемою. Перевитрата системних ресурсів має бути незначним: більшу частину часу така служба фактично не виконується і не витрачає ресурси. Коли хтось починає сеанс віддаленої роботи, вона запускається і зупиняється негайно після завершення сеансу, звільняючи ресурси. Що ж, подивимося, як у systemd можна скористатися режимом сумісності з inetd та забезпечити сокет-активацію SSH. Так виглядає рядок із конфігурацією служби SSH для класичного inetd:

ssh stream tcp nowait root /usr/sbin/sshd sshd -i

Аналогічний фрагмент конфігурації для xinetd:

service ssh socket_type = stream protocol = tcp wait = no user = root server = /usr/sbin/sshd server_args = -i >

Більшість опцій інтуїтивно зрозуміла, крімтого, неважко помітити, що обидва ці тексти оповідають про те саме. Однак деякі моменти не цілком очевидні. Наприклад, номер порту, що прослуховується (22) не вказується в явному вигляді. Він визначається шляхом пошуку імені служби у базі даних /etc/services. Подібний підхід був колись дуже популярним у Unix, але зараз він вже поступово виходить з моди, і тому в сучасних версіях xinetd підтримується можливість явної вказівки номера порту. Одна з найцікавіших налаштувань названа не зовсім очевидно - nowait (wait = no). Вона визначає, чи буде служба працювати за другою (wait) або третьою (nowait) схемою. І нарешті, ключ -i активує inetd-режим у SSH-сервері (без цього ключа він працює у незалежному режимі). Мовою systemd ці фрагменти конфігурації перетворюються на два файли. Перший з них, sshd.socket, містить інформацію про сокет, що прослуховується:

[Unit] Description=SSH Socket for Per-Connection Servers [Socket] ListenStream=22 Accept=yes [Install] WantedBy=sockets.target

Сенс більшості опцій цілком очевидний. Зроблю лише кілька зауважень. Accept=yes відповідає режиму nowait. Сподіваюся, запропонована мною назва більш точно відображає сенс опції - в режимі nowait суперсвервер сам викликає accept() для сокету, що слухає, в той час як в режимі wait ця робота лягає на процес служби. Опція WantedBy=sockets.target забезпечує активацію даного юніту у потрібний момент під час завантаження системи. Другий із цих файлів — [email protected]:

[Unit] Description=SSH Per-Connection Server [Service] ExecStart=-/usr/sbin/sshd -i StandardInput=socket

Більшість представлених опцій, як завжди, зрозумілі інтуїтивно. Особливу увагу варто звернути лише на рядок StandardInput = socket,яка, власне, і включає для даної служби режим сумісності з активацією inetd. Опція StandardInput= дозволяє вказати, куди буде підключений потік STDIN процесу цієї служби (подробиці див. на сторінці посібника). Задавши для неї значення socket, ми забезпечуємо підключення цього потоку до сокету з'єднання, як і вимагає механізм inetd-активації. Зауважимо, що явно вказувати опцію StandardOutput= у разі необов'язково — вона автоматично встановлюється у значення, як і StandardInput, якщо явно не зазначено щось інше. Крім того, можна відзначити наявність "-" перед ім'ям бінарного файлу sshd. Таким чином, ми вказуємо systemd ігнорувати код виходу процесу sshd. За промовчанням systemd зберігає коди виходу для всіх екземплярів служби, що завершилися з помилкою (скинути цю інформацію можна командою systemctl reset-failed). SSH часто завершується з ненульовим кодом виходу, і ми дозволяємо systemd не реєструвати подібні «помилки». Служба [email protected] призначена для роботи у вигляді незалежних екземплярів (такі служби ми розглядали у попередньому розділі 10). Для кожного вхідного з'єднання systemd буде створювати свій екземпляр цієї служби, причому ідентифікатор екземпляра формується на основі реквізитів з'єднання. Можливо, ви запитаєте: чому для налаштування inetd-служби в systemd потрібно два конфігураційні файли, а не один? Відповідаємо: щоб уникнути зайвого ускладнення, ми забезпечуємо максимально прозорий зв'язок між юнітами, що працюють, і відповідними юніт-файлами. Такий підхід дозволяє незалежно оперувати юнітом сокету та юнітами відповідних служб при формуванні графа залежностей та при управлінні юнітами. Зокрема, ви можете зупинити (видалити) сокет, не торкаючись екземплярів, що вже працюють.відповідної служби, або зупинити будь-який з цих екземплярів, не чіпаючи інші екземпляри та сам сокет. Побачимо, як наш приклад працюватиме. Після того, як ми помістимо обидва запропоновані вище файли в каталог /etc/systemd/system, ми зможемо включити сокет (тобто забезпечити його активацію при кожному нормальному завантаженні) і запустити його (тобто активувати в поточному сеансі роботи):

Отже, наш сокет вже прослуховується, але вхідних з'єднань на нього поки не надходило (лічильник Accepted: показує кількість прийнятих з'єднань з моменту створення сокету, лічильник Connected: кількість активних з'єднань на поточний момент). Підключимося до нашого сервера з двох різних хостів, і подивимося на список служб:

Ось, мабуть, і все, що вам потрібно знати про портування inetd-служб у systemd та подальше їх використання.

Стосовно SSH, в більшості випадків схема з inetd-активацією дозволяє заощадити системні ресурси і тому виявляється більш ефективним рішенням, ніж використання звичайного service-файлу sshd, що забезпечує запуск одиночної служби без використання сокет-активації (поставляється у складі пакета і може бути включений при необхідності). Найближчим часом я збираюся надіслати відповідний запит щодо нашого пакету SSH у багтрекер Fedora.

З іншого боку, systemd також надає ряд можливостей, відсутніх у xinetd, зокрема, індивідуальний контроль над кожним екземпляром служби (див. вище), і значний набір налаштувань контролю оточення, в якому запускаються екземпляри. Я сподіваюся, що можливостей systemd має бути достатньо для вирішення більшості завдань, а в тих поодиноких випадках, коли вам потрібні специфічні опції xinetd — ніщо не заважає вам запустити його на додаток доsystemd. Таким чином, вже зараз у більшості випадків xinetd можна викинути з обов'язкових системних компонентів. Можна сказати, що systemd не просто повертає функціональність класичного юніксового inetd, але ще й відновлює її ключову роль у Linux-системах.

Тепер, озброєні цими знаннями, ви можете портувати свої служби з inetd на systemd. Але, звичайно, буде краще, якщо цим займуться розробники з апстріму додатка або супроводжують вашого дистрибутива.