Перейти до вмісту

systemd

те, що запускає все інше в Linux

Що це таке

Коли ядро Linux завершує власний старт, воно передає керування одному процесові з ідентифікатором 1, і майже в усіх поширених дистрибутивах цим процесом є systemd. Це менеджер системи та служб: він піднімає машину під час завантаження, запускає служби й наглядає за ними, поки вона працює, а під час вимкнення зупиняє все в правильному порядку. Проєкт пише свою назву малими літерами, без варіантів на кшталт SystemD чи System D, а кінцева d — давня позначка демона в Unix. Він належить до родини систем ініціалізації, поруч зі скриптами SysV init та Upstart, які він замінив у більшості дистрибутивів, з OpenRC і runit, які досі обирають деякі менші дистрибутиви, і з launchd, що виконує ту саму роботу в macOS.

Усе, чим керує systemd, — це юніти, описані в коротких текстових файлах із секціями та парами «ключ — значення». Посібник налічує 11 типів: service, socket, target, device, mount, automount, timer, swap, path, slice і scope. Юніт типу service запускає програму, а timer за розкладом запускає інший юніт. Socket тримає порт, що слухає з’єднання, і запускає свою службу, коли надходить перше з них, а target об’єднує юніти в стан, якого машина може досягти, наприклад багатокористувацьку систему з мережею. Вимоги й порядок задають окремі параметри, тож «цьому потрібне те» і «це стартує після того» — два різні рядки, а менеджер збирає їх в одну транзакцію завдань запуску й зупинки, яку можна перевірити ще до виконання.

Від скриптових систем ініціалізації, що були до нього, його відрізняють два рішення. Кожна служба працює у власній контрольній групі ядра, тож менеджер знає кожен процес, який служба будь-коли породила, і може зупинити їх усі, обмежити їхню пам’ять і врахувати витрати процесора. А ще проєкт — це цілий набір компонентів, а не лише менеджер: його власний сайт називає демон журналювання, journal, і компоненти для входу користувачів, налаштування мережі, синхронізації часу та розв’язання імен. Які з них увімкнути, дистрибутиви вирішують самі; менеджер на PID 1 — та частина, яка є майже скрізь.

Чому це важливо

На сервері з Linux питання рідко звучить так, чи запускати systemd: дистрибутив уже зробив цей вибір. Справжнє рішення — чи використовувати його як супервізор і планувальник, чи нашаровувати поверх нього інший менеджер процесів, політику перезапуску контейнерного демона або cron.

Якщо використовувати його напряму, порядок запуску, політика перезапуску, журнали, обмеження ресурсів і пісочниця опиняються в одному файлі на службу, який читає процес, що вже працює й не потребує додаткової пам’яті. Юніт може обмежити пам’ять через MemoryMax=, зробити системні каталоги доступними службі лише для читання через ProtectSystem= і запускати її від тимчасового користувача через DynamicUser=, а systemd-analyze security оцінює, наскільки відкрита кожна служба, за шкалою від 0,0 до 10,0. Завдання, яке запускає таймер, на відміну від рядка в crontab, пише в журнал, отримує ту саму пісочницю, що й будь-яка служба, а systemctl list-timers показує його останній і наступний запуск. Навичка ще й переноситься: інженер, який уміє читати юніт-файл, може обслуговувати більшість серверів Linux незалежно від дистрибутива.

Ціна теж реальна. Юніти працюють лише в Linux, тож команда, яка постачає ще й на macOS, пише для launchd файли property list, а та, що постачає на BSD, пише rc-скрипти. У більшості контейнерів systemd немає взагалі: PID 1 там — сама програма, а політика перезапуску належить тому, що запускає контейнер. Там, де контейнери й звичайні служби ділять один хост, Quadlet від Podman скорочує цю відстань: він перетворює опис контейнера на звичайний юніт служби, тож обидва види навантаження стоять під одним супервізором.

Як це виглядає на практиці

Власна платформа компанії спирається на systemd у трьох місцях.

Робочий хост із роботи вся компанія на одному хості з 512 МБ має одне ядро й близько 464 МБ придатної пам’яті, а на ньому працюють вебсервер для семи доменів, git-forge, onion-служба Tor, сервери Gemini і Gopher, зашифровані резервні копії та блокування спроб вторгнення. За такого розміру кожен постійний процес мусить виправдати свою пам’ять, тож супервізором є systemd без другого процесу-супервізора над ним, а контейнери працюють як юніти Quadlet під тим самим менеджером, а не під контейнерним демоном зі своїм власним наглядом. Запас, який лишає така схема, виміряли, коли вирішували, чи додати onion-службу: за добу мінімум доступної пам’яті склав близько 310 МБ із 464.

Зашифровані резервні копії з щомісячною перевіркою відновлення працюють за таймером systemd: дамп бази даних, якщо вона є, потім зашифрований дедуплікований знімок, надісланий через SFTP у сховище в іншого провайдера, потім очищення за правилами зберігання і перевірка цілісності. Сигнал dead man’s switch отримує пінг лише після успішного прогону. Ця робота зачепила й один із гостріших кутів systemd, про який ідеться нижче: парольна фраза лежить у файлі, який читають юніти, бо у значенні, переданому через середовище юніта, менеджер обробляє екранування, і зворотна скісна риска непомітно змінила б парольну фразу.

Сторінка технологій проводить загальну межу: звичайні юніти systemd як основний вибір, а Docker і Kubernetes — для навантажень, які на них заслуговують.

Де виникають проблеми

Відредагований юніт працює зі старими налаштуваннями. Менеджер тримає юніт-файли в пам’яті, тож зміна на диску не діє, доки systemctl daemon-reload не перебудує його уявлення про них, а systemctl cat може показати файл, який відрізняється від того, чим користується запущений менеджер. Вносьте зміни через systemctl edit, який сам перечитує конфігурацію, або перечитайте її вручну й потім перезапустіть службу.

Секрет доходить до служби без одного символу. Значення в Environment= проходять через власні правила лапок systemd: екранування в стилі C обробляється, а буквальну зворотну скісну риску треба подвоювати. Документація також попереджає, що змінні середовища видимі непривілейованим клієнтам через D-Bus. Кладіть секрети у файл і передавайте їх через LoadCredential=: служба отримає копію лише для читання, доступну тільки її користувачеві та root.

Оновлення пакета тихо скасовує локальне виправлення. Зміни у файлі постачальника в /usr/lib/systemd/system/ наступне оновлення пакета перезапише. Натомість кладіть їх у drop-in-файл у /etc/systemd/system/<unit>.d/, який накладається поверх оригіналу й переживає оновлення. Списки команд доповнюються, а не замінюються, тож drop-in, що змінює команду запуску, спершу очищає її порожнім присвоєнням:

# /etc/systemd/system/app.service.d/override.conf
[Service]
ExecStart=
ExecStart=/usr/local/bin/app --config /etc/app/app.toml
Restart=on-failure

Нічне завдання пропускає ніч, коли сервер був вимкнений. Календарний таймер, чий час настав, поки машина стояла, просто чекає наступного слоту, бо Persistent= типово має значення false. Задайте Persistent=true для резервних копій та інших обов’язкових завдань, і пропущений запуск відбудеться, щойно таймер знову стане активним.

Служба, що впала, так і лишається лежати. Restart= типово має значення no, тож служба, яка аварійно завершилася, лишається мертвою, доки хтось це помітить. Задайте Restart=on-failure для служб, які мають працювати постійно, і пам’ятайте: коли юніт досягає ліміту запусків, менеджер відмовляється від нього, а ручний запуск доведеться відкласти, доки мине інтервал або хтось виконає systemctl reset-failed.

Що почитати далі

  • systemd(1) — сам менеджер: типи юнітів, послідовність завантаження і сигнали, на які він реагує.
  • systemd.unit(5) — юніт-файли, залежності, ліміти запусків і каталоги drop-in, що їх перевизначають.
  • systemd.service(5) — типи служб, політика перезапуску і те, як скидаються списки команд.
  • systemd.exec(5) — середовище виконання, пісочниця і передавання облікових даних службам.
  • systemd.timer(5) — календарні та монотонні таймери, зокрема Persistent=.
  • systemctl(1) — командний рядок для запуску, редагування, перечитування й перегляду юнітів.

Словник