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) — командний рядок для запуску, редагування, перечитування й перегляду юнітів.