Ubuntu 24.04: «Не вдалося запустити …» — найшвидший робочий процес триажу systemd (випадок №62)

Було корисно?

02:13. Pager каже «служба впала», панель моніторингу — рівна лінія, а консоль вітає вас повідомленням: Не вдалося запустити …. Це системдове «щось сталося». Так само корисно, як печиво з передбаченням майбутнього.

Ось робочий процес, який я застосовую на Ubuntu 24.04, коли потрібно швидко отримати відповідь: що впало, чому впало, від чого це залежить і чи варто виправляти, відкатувати чи ізолювати. Жодної містики. Лише найкоротший шлях від симптома до рішення.

Ментальна модель: що насправді означає «Не вдалося запустити»

Systemd — це оркестратор, а не провидець. Коли він виводить «Не вдалося запустити», він повідомляє про зміну стану: юніт перейшов з «activating» у «failed», або так і не дійшов до «active», або потрапив під обмеження рестартів, або одна з його залежностей впала раніше, і systemd тут лише месенджер.

На Ubuntu 24.04 ви бачитимете ту саму низку базових причин:

  • Невірне визначення юніта: опечатка, неправильний шлях, відсутні лапки, недійсна директива, неправильний Type=.
  • Помилка під час виконання: виконуваний файл повертає ненульовий код, падає, вичерпується час або не може забіндити порт.
  • Збій залежності: не змонтовано диск, мережа не онлайн, секрети недоступні, база даних недоступна.
  • Невідповідність середовища: переміщений конфіг, змінений користувач, змінені права, конфлікт профілю SELinux/AppArmor.
  • Тиск на систему: диск заповнений, нестача пам’яті (OOM), ліміти дескрипторів файлів, CPU-квоти, обмеження cgroup.
  • Тротлінг запусків: «Start request repeated too quickly», тобто сервіс зациклився і systemd перестав його часто перезапускати.

Ключ — ставитись до systemd як до графового двигуна. Юніти мають порядок (After=, Before=) і залежності (Requires=, Wants=). Порядок — це «коли», вимоги — це «має існувати». Змішування цих понять — класичний генератор відмов.

Цікаві факти й історичний контекст (бо минуле постійно з’являється у ваших інцидентах)

  1. systemd з’явився в основному Linux близько 2010–2012 років і замінив зоопарк init-скриптів єдиним менеджером з облікoм залежностей.
  2. Ubuntu перейшла з Upstart на systemd у 15.04; через десятиліття багато «припущень щодо init-скриптів» і досі ховаються в кастомних сервісах.
  3. journald за замовчуванням бінарний (структуровані метадані, швидка фільтрація), що чудово — доки ви не забудете зберегти логи на диск і перезавантаження не зріже сліди.
  4. Юніти — це не тільки служби: mounts, sockets, timers, paths, scopes, targets — більшість «failed service» подій починаються з невдалого mount або socket.
  5. Тротлінг запусків існує, щоб захистити хост; без нього сервіс у циклі падіння може DOSнути власну машину процес-штормом.
  6. «network-online» у systemd хиткий: це означає «менеджер мережі каже, що мережа онлайн», а не «ваш SaaS-ендпоінт досяжний».
  7. За замовчуванням таймаути змінювались з часом; старі копіпастові юніт-файли іноді встановлюють таймаути, які занадто короткі для сучасних шляхів завантаження або надто довгі для виробничих очікувань.
  8. cgroups v2 тепер стандарт; контролі ресурсів і поведінка OOM можуть відрізнятись від старих хостів, дивуючи сервіси, які «завжди працювали».

І ще одна оперативна цитата, варта закріпити на терміналі:

«Надія — це не стратегія.» — генерал Гордон Р. Салліван

Триаж systemd — протиотрута від надії.

Швидкий план діагностики (перші/другі/треті перевірки)

Якщо запам’ятати тільки одну річ: не починайте з редагування файлів. Почніть зі збору доказів, потім оберіть найменш ризикове втручання, яке безпечно відновить службу.

Перше: точно визначте, що впало і як

  • Перевірте стан юніта і останній результат (systemctl status).
  • Витягніть релевантні логи з поточного завантаження (journalctl -u ... -b).
  • Підтвердіть, чи це пряма помилка, чи каскад залежностей (systemctl list-dependencies і systemctl show).

Друге: класифікуйте відмову за 60 секунд

  • Exec/exit: шукайте код виходу, сигнал, дамп ядра, відсутній файл.
  • Таймаут: «start operation timed out», часто через очікування на mount, network-online або повільний диск.
  • Дозвіл: «permission denied», «cannot open» або відмови AppArmor.
  • Ресурси: OOM kill, ENOSPC, надто багато відкритих файлів.
  • Тротлінг рестартів: «start-limit-hit» або «request repeated too quickly».

Третє: оберіть найменш ризикову корекцію

  • Відомий хороший відкат: відкотіть останню зміну конфігурації/пакету, якщо таймлайн співпадає.
  • Тимчасова ізоляція: зупиніть залежні юніти, замаскуйте флапаючі, або відключіть таймер для стабілізації хоста.
  • Хірургічне виправлення: відкоригуйте юніт-файл, права, порядок монтування або файл середовища — і перезапустіть із чистого стану.

Короткий жарт, бо він вам знадобиться: логи systemd як детективні романи — усі підозрювані, а винуватець зазвичай «відсутній файл».

Більше ніж дванадцять практичних завдань з командами, значенням виводу та рішеннями

Ось ходи, які я роблю на Ubuntu 24.04, коли юніт падає. Кожне завдання містить команду, реалістичний уривок виводу, що це означає та яке рішення приймати далі.

Завдання 1: Підтвердити поточний стан юніта, останній код виходу та першу підказку

cr0x@server:~$ systemctl status nginx.service
× nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/lib/systemd/system/nginx.service; enabled; preset: enabled)
     Active: failed (Result: exit-code) since Mon 2025-12-30 02:11:02 UTC; 1min 4s ago
   Duration: 83ms
       Docs: man:nginx(8)
    Process: 2191 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=1/FAILURE)
        CPU: 72ms

Dec 30 02:11:02 server nginx[2191]: nginx: [emerg] open() "/etc/nginx/snippets/tls.conf" failed (2: No such file or directory)
Dec 30 02:11:02 server systemd[1]: nginx.service: Control process exited, code=exited, status=1/FAILURE
Dec 30 02:11:02 server systemd[1]: nginx.service: Failed with result 'exit-code'.
Dec 30 02:11:02 server systemd[1]: Failed to start nginx.service - A high performance web server and a reverse proxy server.

Значення: Помилка в ExecStartPre при тесті конфігурації; nginx відмовився стартувати через відсутній файл.

Рішення: Не перезапускайте наосліп. Виправте відсутнє включення або відкотіть зміну конфігурації. Перевірте з nginx -t після виправлення.

Завдання 2: Витягніть весь журнал для юніта з поточного завантаження

cr0x@server:~$ journalctl -u nginx.service -b --no-pager -n 80
Dec 30 02:11:02 server nginx[2191]: nginx: [emerg] open() "/etc/nginx/snippets/tls.conf" failed (2: No such file or directory)
Dec 30 02:11:02 server systemd[1]: nginx.service: Control process exited, code=exited, status=1/FAILURE
Dec 30 02:11:02 server systemd[1]: nginx.service: Failed with result 'exit-code'.
Dec 30 02:11:02 server systemd[1]: Failed to start nginx.service - A high performance web server and a reverse proxy server.

Значення: Journald підтверджує ту саму корінну причину; немає сенсу шукати залежності поки що.

Рішення: Виправте шлях до файлу конфігурації або відновіть фрагмент; потім протестуйте та запустіть.

Завдання 3: Якщо це каскад залежностей, запитайте у systemd, що спричинило зупинку

cr0x@server:~$ systemctl show -p Id -p ActiveState -p SubState -p Result -p ExecMainStatus -p ExecMainCode -p NRestarts nginx.service
Id=nginx.service
ActiveState=failed
SubState=failed
Result=exit-code
ExecMainCode=1
ExecMainStatus=1
NRestarts=0

Значення: Це чиста помилка «процес вийшов з кодом 1», не сигнал убивства, не цикл рестартів.

Рішення: Виправляйте базову помилку в виконуваному файлі/конфігурації; не чіпайте ліміти старту чи політику рестарту.

Завдання 4: Показати залежності юніта і шукати «мертві» вимоги

cr0x@server:~$ systemctl list-dependencies --reverse nginx.service
nginx.service
● nginx.service
○ systemd-user-sessions.service
○ multi-user.target

Значення: Ніхто особливий не залежить від nginx, окрім таргету; зворотні залежності не заблокують перезапуск.

Рішення: Після виправлення конфігу безпечно перезапускати; низьке радiус ураження.

Завдання 5: Перевірити коректність юніт-файлу і override (впіймати «він не використовує те, що ви думаєте»)

cr0x@server:~$ systemctl cat nginx.service
# /lib/systemd/system/nginx.service
[Unit]
Description=A high performance web server and a reverse proxy server
After=network-online.target remote-fs.target nss-lookup.target
Wants=network-online.target

[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t -q -g 'daemon on; master_process on;'
ExecStart=/usr/sbin/nginx -g 'daemon on; master_process on;'
ExecReload=/usr/sbin/nginx -g 'daemon on; master_process on;' -s reload
ExecStop=/sbin/start-stop-daemon --quiet --stop --retry QUIT/5 --pidfile /run/nginx.pid
TimeoutStopSec=5
KillMode=mixed

[Install]
WantedBy=multi-user.target

Значення: Drop-in не виявлено; цей юніт використовує пакований визначник. Якщо ви очікували кастомні оверрайди — ви дебагите не той файл.

Рішення: Якщо потрібна кастомізація, створіть drop-in через systemctl edit, а не редагуйте файли під /lib.

Завдання 6: Перевірити drop-in override і підозрілі невідповідності

cr0x@server:~$ systemctl status myapp.service
× myapp.service - My App API
     Loaded: loaded (/etc/systemd/system/myapp.service; enabled; preset: enabled)
    Drop-In: /etc/systemd/system/myapp.service.d
             └─override.conf
     Active: failed (Result: exit-code) since Mon 2025-12-30 02:08:19 UTC; 2min 53s ago
Dec 30 02:08:19 server systemd[1]: myapp.service: Failed to run 'start' task: No such file or directory
cr0x@server:~$ systemctl cat myapp.service
# /etc/systemd/system/myapp.service
[Service]
ExecStart=/opt/myapp/bin/myapp --config /etc/myapp/config.yaml
User=myapp
Group=myapp
EnvironmentFile=/etc/myapp/myapp.env

# /etc/systemd/system/myapp.service.d/override.conf
[Service]
ExecStart=/opt/myapp/bin/myappd --config /etc/myapp/config.yaml

Значення: Drop-in оверрайд замінив ExecStart. Якщо /opt/myapp/bin/myappd не існує — systemd не зможе його виконати.

Рішення: Виправте шлях в override або видаліть drop-in; потім systemctl daemon-reload і перезапустіть.

Завдання 7: Виявити «start-limit-hit» (тротл рестартів) і правильно його скинути

cr0x@server:~$ systemctl status myapp.service
× myapp.service - My App API
     Loaded: loaded (/etc/systemd/system/myapp.service; enabled; preset: enabled)
     Active: failed (Result: start-limit-hit) since Mon 2025-12-30 02:09:02 UTC; 2min 10s ago
Dec 30 02:09:02 server systemd[1]: myapp.service: Scheduled restart job, restart counter is at 5.
Dec 30 02:09:02 server systemd[1]: myapp.service: Start request repeated too quickly.
Dec 30 02:09:02 server systemd[1]: myapp.service: Failed with result 'start-limit-hit'.

Значення: Сервіс флапає і systemd його обмежив. Зазвичай це симптом, а не хвороба.

Рішення: Виправте причину крашів. Після цього скиньте тротл:

cr0x@server:~$ sudo systemctl reset-failed myapp.service

Значення: Очищає стан помилки, дозволяючи подальші спроби запуску.

Рішення: Запускайте сервіс лише після усунення корінної причини, інакше ви повторно згенеруєте тротлінг.

Завдання 8: Визначити таймаут чи реальний краш

cr0x@server:~$ systemctl status postgresql.service
× postgresql.service - PostgreSQL RDBMS
     Loaded: loaded (/lib/systemd/system/postgresql.service; enabled; preset: enabled)
     Active: failed (Result: timeout) since Mon 2025-12-30 02:05:41 UTC; 6min ago
Dec 30 02:04:11 server systemd[1]: Starting postgresql.service - PostgreSQL RDBMS...
Dec 30 02:05:41 server systemd[1]: postgresql.service: start operation timed out. Terminating.
Dec 30 02:05:41 server systemd[1]: postgresql.service: Failed with result 'timeout'.

Значення: Воно не вийшло швидко; процес завис під час старту. Поширені підозрювані: повільне сховище, відновлення WAL, заблокований каталог даних, затримка DNS або залежність монтування.

Рішення: Перевірте логи, стан сховища та чи каталог даних знаходиться на монтуванні, яке ще не готове.

Завдання 9: Зіставити час старту юніта з готовністю сховища/монтувань

cr0x@server:~$ systemd-analyze critical-chain postgresql.service
postgresql.service +1min 28.122s
└─local-fs.target @12.405s
  └─mnt-data.mount @11.902s +1min 15.701s
    └─systemd-fsck@dev-disk-by\x2duuid-3a1c...service @3.211s +8.614s
      └─dev-disk-by\x2duuid-3a1c....device @2.983s

Значення: Реальна затримка — mnt-data.mount, яка зайняла 75 секунд. Postgres просто чекає на файлову систему.

Рішення: Виправте продуктивність монтування (мережеве сховище? fsck? помилки пристрою?). Або розв’яжіть порядок старту, якщо це безпечно, але не приховуйте несправний диск.

Завдання 10: Проінспектувати невдалі mount-юніти та помилки у fstab

cr0x@server:~$ systemctl status mnt-data.mount
× mnt-data.mount - /mnt/data
     Loaded: loaded (/etc/fstab; generated)
     Active: failed (Result: exit-code) since Mon 2025-12-30 02:03:22 UTC; 8min ago
Dec 30 02:03:22 server mount[612]: mount: /mnt/data: wrong fs type, bad option, bad superblock on /dev/sdb1, missing codepage or helper program.
Dec 30 02:03:22 server systemd[1]: mnt-data.mount: Mount process exited, code=exited, status=32/n/a
Dec 30 02:03:22 server systemd[1]: mnt-data.mount: Failed with result 'exit-code'.

Значення: Монтування зламане; будь-який сервіс, що залежить від нього, впаде або зависне. «wrong fs type» може означати неправильний тип у fstab, відсутній пакет для файлової системи або реальну корупцію.

Рішення: Підтвердіть пристрій і тип файлової системи. Якщо це продакшен-дані, припиніть трясця з пристроєм і перевірте блоковий пристрій перед повторними спробами монтування, щоб не погіршити ситуацію.

Завдання 11: Підтвердити, яку файлову систему вважає ядро (і чи пристрій присутній)

cr0x@server:~$ lsblk -f
NAME   FSTYPE FSVER LABEL UUID                                 FSAVAIL FSUSE% MOUNTPOINTS
sda
├─sda1 vfat   FAT32       7C1A-3F2B                             510M     2% /boot/efi
├─sda2 ext4   1.0         2d4b2b3c-5a62-4b2f-8f87-1b9e8f8a0c19  14G    61% /
└─sda3 swap   1           7d3e0c7c-5d48-4d1b-9b8b-2a5d0f3b9e21                [SWAP]
sdb
└─sdb1 xfs                9a1e3f1f-6a71-4bb5-8a45-2e7a5bb1c5b2

Значення: /dev/sdb1 — XFS. Якщо у fstab вказано ext4 — ось ваша помилка. Якщо у fstab вказано xfs і все одно не монтується — підозрюйте необхідність відновлення XFS або відсутність xfsprogs (рідко для Ubuntu, але можливо у мінімальних збірках).

Рішення: Виправте тип/опції у fstab або виконайте перевірку файлової системи у режимі обслуговування. Не застосовуйте «примусові» опції монтування на хворому диску в продакшені, якщо не хочете несподівано втратити дані.

Завдання 12: Перевірити, чи сервіс убитий OOM (тихий кілер)

cr0x@server:~$ journalctl -b -k --no-pager | tail -n 12
Dec 30 02:07:12 server kernel: Out of memory: Killed process 3310 (myapp) total-vm:3128456kB, anon-rss:1452032kB, file-rss:132kB, shmem-rss:0kB
Dec 30 02:07:12 server kernel: oom_reaper: reaped process 3310 (myapp), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
Dec 30 02:07:12 server systemd[1]: myapp.service: Main process exited, code=killed, status=9/KILL
Dec 30 02:07:12 server systemd[1]: myapp.service: Failed with result 'signal'.

Значення: Його вбило ядро, а не systemd. Systemd лише звітує про наслідки: SIGKILL.

Рішення: Це питання місткості/лімітів: тонка настройка пам’яті, зменшення споживання, додавання swap (обережно), виправлення витоків або встановлення адекватних обмежень cgroup. Проста дія «перезапустити» — тимчасова брехня.

Завдання 13: Перевірити «адреса вже використовується» (конфлікт портів) і знайти винуватця

cr0x@server:~$ systemctl status myapp.service
× myapp.service - My App API
     Loaded: loaded (/etc/systemd/system/myapp.service; enabled; preset: enabled)
     Active: failed (Result: exit-code) since Mon 2025-12-30 02:12:48 UTC; 10s ago
Dec 30 02:12:48 server myapp[4021]: listen tcp 0.0.0.0:8080: bind: address already in use
cr0x@server:~$ sudo ss -ltnp | grep ':8080'
LISTEN 0      4096         0.0.0.0:8080      0.0.0.0:*    users:(("nginx",pid=2101,fd=12))

Значення: Nginx (або щось інше) вже займає порт. Ваш додаток не може забіндити його.

Рішення: Визначте, хто повинен володіти портом. Переведіть додаток на інший порт, оновіть зворотний проксі або зупиніть конфліктуючу службу. Не вбивайте випадкові PID без розуміння, навіщо вони там.

Завдання 14: Підтвердити, чи AppArmor заблокував доступ (поширено в Ubuntu)

cr0x@server:~$ journalctl -b --no-pager | grep -i apparmor | tail -n 6
Dec 30 02:10:02 server kernel: audit: type=1400 audit(1735524602.112:91): apparmor="DENIED" operation="open" profile="/usr/sbin/nginx" name="/etc/ssl/private/my.key" pid=2191 comm="nginx" requested_mask="r" denied_mask="r" fsuid=0 ouid=0

Значення: Сервіс заблоковано від читання потрібного файлу. Це виглядає як проблема з правами, але не вирішується chmod 777 (не робіть так).

Рішення: Відкоригуйте профіль AppArmor або перемістіть секрети в дозволені шляхи; потім перезавантажте профіль і перезапустіть службу.

Завдання 15: Перевірити, чи юніт бачить очікуване середовище

cr0x@server:~$ systemctl show myapp.service -p Environment -p EnvironmentFiles
Environment=
EnvironmentFiles=/etc/myapp/myapp.env (ignore_errors=no)
cr0x@server:~$ sudo test -r /etc/myapp/myapp.env && echo readable || echo not_readable
not_readable

Значення: Файл середовища недоступний для читання root (або для systemd під час завантаження), тому юніт може не стартувати або не отримати потрібні змінні.

Рішення: Виправте власність/права. Для секретів: читаємі root, або лише сервісним користувачем за потреби; уникайте файлів з глобальним доступом.

Режими відмов, що мають значення в Ubuntu 24.04

1) Плутанина залежностей: After= — це не Requires=

After=network-online.target означає «запуститися після цього таргету», але не «впасти, якщо network-online не вдалося». Якщо ваш сервіс справді вимагає мережі, заявіть це явно. І навпаки, якщо ви вкажете Requires= для ненадійної речі, ви будете тягнути свою службу вниз щоразу, коли ця залежність захитнеться.

2) Одноразові юніти, що прикидаються довготривалими службами

Багато внутрішніх сервісів фактично «запустити скрипт для налаштування» і потім виходять. Якщо юніт визначено як Type=simple з короткоживучим процесом, systemd вважає, що він впав. Використовуйте Type=oneshot з RemainAfterExit=yes, коли це доречно.

3) Network-online — пастка (і не завжди ваш друг)

На хмарних інстансах «network online» може стати правда раніше, ніж працює DNS, раути збалансуються або ваш оверлей мережі функціональний. Якщо сервіс потребує доступу до віддаленої бази під час старту, віддавайте перевагу явній логіці повторних спроб у додатку. Порядок systemd не врятує від ненадійних upstream.

4) Затримки зі сховищем: ваш сервіс невинний, винен монтування

Коли сховище повільне — служби таймаутяться. Коли сховище зламане — служби падають. Юніт, який отримав провину, рідко є тим, хто спричинив затримку.

На Ubuntu 24.04 звертайте увагу на:

  • записи fstab, що генерують mount-юніти
  • поведінку remote-fs (NFS, CIFS)
  • затримки fsck
  • перенумерацію пристроїв після змін апаратури

5) Міф «вчора працювало»: дрейф пакування і конфігурацій

Ubuntu 24.04 приносить новіший systemd, нові OpenSSL за замовчуванням, нові Python, нові ядра. Сервіси, що раніше працювали за невизначеної поведінки, можуть правильно зламатися. Не боріться з цим — виправляйте припущення.

Три міні-історії з корпоративного світу (анонімізовано, реальні шаблони)

Міні-історія 1: Аутейдж через неправильне припущення

Команда мігрувала флот на Ubuntu 24.04 і «стандартизувала» юніт-файли. Хтось припустив, що After=network-online.target означає, що сервіс не стартує, поки база даних не буде досяжна. У стенді це виглядало нормально. В продакшені частина хостів піднялась із затримкою DNS після зміни мережі.

Сервіс спробував під’єднатися до бази при старті, провалився один раз і вийшов. Політика рестарту була Restart=on-failure з коротким циклом. Systemd зробив те, для чого його створили: перезапускав, потім throttled з start-limit-hit. Тепер сервіс впав і відмовлявся стартувати навіть після стабілізації DNS.

Початкова реакція була передбачувана: люди піднімали StartLimitIntervalSec і StartLimitBurst, щоб «дозволити йому продовжувати спроби». Це породило нову проблему: цикл крашів бив по DNS і базі даних штормами підключень з кожним хостом при перезавантаженні. Так локальна помилка перетворюється на масштабний інцидент.

Виправлення було нудним і правильним: додали експоненційний бекофф і логіку повторних спроб на рівні додатку, старт став толерантним до початкових upstream-помилок, і ordering у systemd спростили. Вони залишили After=network-online.target для чистоти, але перестали вважати це гарантією доступності мережі.

Міні-історія 2: Оптимізація, що обернулась проти

Ініціатива з економії витрат прагнула швидшого завантаження і меншого споживання пам’яті. Хтось встановив агресивні таймаути systemd глобально і звузив ліміти пам’яті для групи обробних юнітів. В тестах завантаження все було швидше, і з’явилась слайд-презентація.

У реальності частина вузлів мала повільніше приєднане сховище (не зламане, просто іноді повільніше). Postgres на тих вузлах іноді потребував більше часу для відновлення після некоректного вимкнення. Нові таймаути убивали його посеред відновлення. Сервіс не лише не запустився—повторні перервані відновлення уповільнювали старт ще більше, а цикл рестарту множив проблему.

Операції звинувачували базу. База звинувачувала ядро. Ядро звинувачувало гіпервізор. Тим часом корінна причина була в «покращенні продуктивності», що прибрало запас, який системі був потрібен.

Відкат провели негайно: відновили адекватні таймаути, потім встановили специфічні для сервісів значення на основі реальних часів відновлення. Довгострокове виправлення — політика: таймаути належать сервісам, а не глобальним бажанням. І якщо ви хочете швидше завантаження — виправте вузькі місця, зазвичай сховище і мережу, а не секундомір.

Міні-історія 3: Нудна практика, що врятувала день

Сервіс, пов’язаний з платежами, працював у кластері зі строгим контролем змін. Це не було гламурно. Кожен systemd-юніт мав drop-in у системі контролю версій і кожне розгортання включало «smoke reboot» у канарці, щоб впіймати проблеми порядку завантаження та залежностей.

Одного дня рутинний патч додав запис у fstab для нового звітувального монтування. Сервер монтування був доступний, але один вузол мав застарілу конфігурацію DNS. Після перезавантаження mount-юніт упав. Головний сервіс мав RequiresMountsFor= на той шлях, бо хтось вирішив, що це «приємно мати». Вузол не повернувся в робочий стан.

Оскільки у них був канарковий smoke reboot, проблему спіймали до того, як патч дійшов до флоту. Виправлення було хірургічним: монтування змінили на nofail і вимогу видалили. Основна служба не потребувала того монтування для обробки платежів; воно було потрібно лише для звітності. Це має значення.

Ніяких героїзмів, ніяких war room. Просто чекліст, канарка і принцип відокремлення критичних від опціональних залежностей. Нудне виявилось переможним.

Типові помилки: симптом → корінна причина → виправлення

Тут частина, де ви припиняєте повторювати той самий інцидент щокварталу.

1) «Start request repeated too quickly»

Симптом: Юніт падає з Result: start-limit-hit, не перезапускається.

Корінна причина: Краш-луп або миттєвий вихід (помилка конфігурації, відсутній бінарник, конфлікт порту). Тротл — це systemd, що захищає систему.

Виправлення: Визначте, чому він виходить, виправте це, потім systemctl reset-failed UNIT. Уникайте «вирішення» підвищенням StartLimit, якщо не любите, коли одна проблема породжує багато інших.

2) «Failed to run ‘start’ task: No such file or directory»

Симптом: systemd не може виконати команду.

Корінна причина: Неправильний шлях у ExecStart=, відсутній виконуваний файл, неправильна архітектура або переопреділений ExecStart у drop-in, який ви забули.

Виправлення: systemctl cat UNIT, підтвердіть остаточний ExecStart, перевірте наявність і виконуваність файлу. Потім daemon-reload, якщо змінили юніт-файли.

3) Служба «active (exited)», але функціональність відсутня

Симптом: systemctl показує успіх, але сервіс не працює.

Корінна причина: Type=oneshot скрипт, який завершується; або неправильно оголошений юніт, де головний процес форкається і systemd втрачає його.

Виправлення: Використовуйте правильний Type= (simple, forking, notify, oneshot) і вкажіть PIDFile= за потреби. Перевірте з systemctl show -p MainPID.

4) «Dependency failed for …» під час завантаження

Симптом: Таргет чи служба падає, бо інший юніт упав.

Корінна причина: Жорстка залежність (Requires=) на монтуванні/мережевому юніті, який фактично опціональний.

Виправлення: Перекласифікуйте залежності. Використовуйте Wants= для опціональних компонентів. Для монтувань розгляньте nofail в fstab і/або видаліть RequiresMountsFor=, якщо воно не критичне.

5) «Permission denied» навіть як root

Симптом: Сервіс не може читати ключі/серти/конфіги.

Корінна причина: AppArmor відмова або юніт запускається як некореневий користувач і не має доступу до файлів.

Виправлення: Підтвердіть ефективного користувача (User=), власність файлів і логи AppArmor. Відкоригуйте профіль або перемістіть файли у очікувані шляхи.

6) Таймаути на сервісах, що залежать від сховища після перезавантаження

Симптом: База даних, черга або додаток таймаутять старт; ланцюжок монтувань показує великі затримки.

Корінна причина: Повільний fsck, затримки віддалених монтувань, деградований диск або некоректний запис у fstab, що викликає повтори.

Виправлення: Виправте монтування першочергово. Перевірте стан блочного пристрою, коректність типу/опцій файлової системи та переконайтесь, що сервіси не залежать від опціональних монтувань.

7) Плутанина «Unit file changed on disk»

Симптом: Ви відредагували юніт, але зміни не застосовуються.

Корінна причина: Забули systemctl daemon-reload, або редагували невірний файл (пакетний юніт проти override).

Виправлення: Використовуйте systemctl cat, щоб побачити змерджений юніт; перезавантажте менеджер; перезапустіть юніт.

Другий короткий жарт, потім повертаємось до роботи: якщо ви дебажите збій юніта, перезавантажуючись знову і знову, вітаю — ви винайшли chaos engineering, але без навчання.

Чеклісти / покроковий план

Чекліст A: Коли служба впала прямо зараз (життєвий інцидент)

  1. Захопіть статус і логи (не змінюйте стан першим). Збережіть systemctl status і journalctl -u UNIT -b.
  2. Класифікуйте відмову: exit-code, signal, timeout, dependency, start-limit-hit.
  3. Підтвердьте остаточне визначення юніта за допомогою systemctl cat UNIT.
  4. Перевірте блокатори залежностей: монтування, network-online, секрети, порти.
  5. Визначте шлях відновлення:
    • Якщо регресія конфігурації: відкат конфігурації.
    • Якщо регресія пакета: відкат пакету або фіксація версії.
    • Якщо інфраструктурна залежність: відкрити відмову за потреби або деградувати безпечно.
  6. Стабілізуйте: зупиніть флапаючі юніти; вимкніть таймери; використайте reset-failed після усунення кореня проблеми.
  7. Перевірте: health-ендпоінти, відкриття сокетів, синтетичний запит і systemctl is-active.

Чекліст B: Триаж для відновлення хоста (boot triage)

  1. Визначте юніти, що впали:
cr0x@server:~$ systemctl --failed
  UNIT                LOAD   ACTIVE SUB    DESCRIPTION
● mnt-data.mount      loaded failed failed /mnt/data
● postgresql.service  loaded failed failed PostgreSQL RDBMS

LOAD   = Reflects whether the unit definition was properly loaded.
ACTIVE = The high-level unit activation state.
SUB    = The low-level unit activation state.

Значення: Помилка монтування ймовірно спричиняє падіння бази даних.

Рішення: Виправте монтування спочатку; потім повторіть спробу запуску бази даних.

  1. Отримайте контекст порядку:
cr0x@server:~$ systemd-analyze blame | head -n 10
1min 15.701s mnt-data.mount
12.233s cloud-init.service
8.614s systemd-fsck@dev-disk-by\x2duuid-3a1c...service
3.812s systemd-networkd-wait-online.service
2.115s snapd.service
1.998s apt-daily.service
1.233s systemd-resolved.service
1.101s ufw.service
981ms systemd-journald.service
742ms systemd-logind.service

Значення: Горло завантаження — це монтування і wait-online. Це підкаже вам наступну годину дій.

Рішення: Якщо це проблема флоту, не витрачайте час на дебаг додатку; виправляйте сховище й semantics network-online.

Чекліст C: Безпечні редагування юніт-файлів (щоб не вивести хост з ладу)

  1. Використовуйте drop-in: systemctl edit UNIT (не редагуйте /lib/systemd/system напряму).
  2. Перевірте синтаксис і змерджену конфігурацію: systemctl cat UNIT.
  3. Перезавантажте менеджер: systemctl daemon-reload.
  4. Перезапустіть: systemctl restart UNIT.
  5. Перевірте логи і MainPID: systemctl status UNIT і systemctl show -p MainPID UNIT.
  6. Лише після цього вмикайте/вимикайте: systemctl enable --now UNIT, якщо потрібно.

Чекліст D: Триаж, орієнтований на сховище (бо «Не вдалося запустити» часто означає «диск сказав ні»)

  1. Перевірте монтування: systemctl status *.mount для невдалих.
  2. Зіставте ланцюжок завантаження: systemd-analyze critical-chain SERVICE.
  3. Перевірте пристрої: lsblk -f і blkid.
  4. Перевірте простір і іноди:
cr0x@server:~$ df -h /
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda2        20G   19G  200M  99% /

cr0x@server:~$ df -i /
Filesystem      Inodes   IUsed   IFree IUse% Mounted on
/dev/sda2      1310720 1310102     618  100% /

Значення: Можна «закінчитися інодами», маючи ще місце. Це ламає логування, PID-файли, сокети — служби падають дивним чином.

Рішення: Очистіть директорії з великою кількістю інодів (зазвичай кеш/тимчасові), проведіть ротацію логів або розширте файлову систему. Потім перезапустіть уражені сервіси.

Поширені питання

1) Чому systemctl каже «failed», але процес насправді працює?

Тому що systemd відстежує те, що вважається «головним процесом». Якщо сервіс несподівано форкається, пише невірний PID-файл або використовує неправильний Type=, systemd може його втратити. Перевірте systemctl show -p MainPID UNIT і зіставте з ps. Виправте Type= та трекінг PID.

2) Яка різниця між «exit-code», «signal» і «timeout» у Result?

exit-code означає, що процес повернув ненуль. signal — був убитий сигналом (OOM часто показує SIGKILL). timeout — systemd чекав довше, ніж дозволено, і завершив його. Кожен випадок вимагає різного підходу: помилки конфігу/рантайму vs вбивання ресурсами vs затримки залежностей/продуктивності.

3) Чому я бачу «Unit file changed on disk» після редагування служби?

Бо systemd не перезчитує юніт-файли автоматично при кожному старті. Виконайте systemctl daemon-reload, потім перезапустіть юніт. Якщо ви редагуєте пакований юніт, краще використовуйте drop-in.

4) Який найшвидший спосіб знайти справжнє горло під час завантаження?

Використайте systemd-analyze blame, щоб знайти часові «провали», потім systemd-analyze critical-chain SERVICE, щоб побачити ланцюжок порядку, що важливий для вашого юніта.

5) Чи слід підвищувати StartLimitBurst і StartLimitIntervalSec, щоб уникнути відмов?

Рідко. Ліміти запусків захищають від циклів падінь, що плавлять хости і downstream-залежності. Якщо сервіс миттєво падає, хочеться, щоб він швидко зупинився і про це голосно повідомив. Виправте падіння, додайте бекофф у додаток і застосуйте розумні політики рестарту.

6) Як бачити логи з попереднього завантаження?

Використайте journalctl -b -1 для попереднього завантаження і journalctl -u UNIT -b -1 для юніта у тому завантаженні. Якщо логів немає — можливо journald не налаштований на збереження на диску.

7) Коли використовувати «mask» проти «disable»?

disable забороняє старт при завантаженні або по wants. Сервіс все ще можна стартувати вручну або як залежність. mask робить його неможливим для запуску (символічне посилання на /dev/null). Використовуйте mask для флапаючого юніта, який потрібно тимчасово заборонити під час стабілізації системи.

8) Як переконатися, що відмова через права чи AppArmor?

Права проявляються як «permission denied» в логах додатку і перевіряються через namei -l PATH та перевірки власності. AppArmor показує audit-відмови ядра в журналі. Шукайте в журналі «apparmor=DENIED» і зіставляйте профіль із сервісом.

9) Чому «network-online.target» уповільнює завантаження?

Тому що systemd-networkd-wait-online.service (або еквівалент NetworkManager) може чекати, поки інтерфейси не будуть налаштовані. Це корисно для сервісів, яким дійсно потрібна налаштована мережа, але шкідливо, якщо ви зробили залежними від цього усе підряд. Тримайте залежності вузькими.

10) Що робити, коли юніт впав через відсутній файл в /etc?

Спочатку вирішіть, чи це баг розгортання, чи видалений конфіг пакета. Відновіть з CM або бекапу, або зробіть відкат зміни. Додайте юніт-рівневу перевірку, якщо потрібно (наприклад, ConditionPathExists=), щоб відмова була явною і швидкою.

Висновок: наступні кроки, які можна зробити вже сьогодні

«Не вдалося запустити …» — це не діагноз. Це стартовий пістолет. Ваше завдання — класифікувати це у одну з кількох конкретних категорій: помилка виконання, таймаут, каскад залежностей, права/AppArmor, тиск ресурсів або тротл рестартів.

Зробіть наступні кроки, поки не горить сервер:

  1. Зробіть journald постійним на серверах, де важлива судова експертиза після перезавантаження, щоб помилки не зникали після ребуту.
  2. Аудитуйте кастомні юніт-файли на предмет правильного Type=, явних залежностей і розумних таймаутів.
  3. Розділіть опціональні та критичні залежності (монтування, шляхи для звітності, телеметрія). Використовуйте Wants= або деградуйте безпечно.
  4. Додайте канаркове перезавантаження для змін, що торкаються fstab, мережі, сховища або systemd-юнітів. Помилки порядку завантаження люблять продакшен.
  5. Документуйте «перші три команди», які ваша команда виконує (systemctl status, journalctl -u ... -b, systemctl cat) і застосовуйте це під час інцидентів.

Systemd детерміністичний. Якщо ставитися до нього як до чорної скриньки, він здаватиметься коробкою з гріним. Використайте наведений вище робочий процес — і ви витрачатимете менше часу на здогадки й більше — на відновлення.

← Попередня
Ubuntu 24.04: Налаштування обмеження швидкості Nginx, що не блокуватиме реальних користувачів — як його тонко налаштувати
Наступна →
MCM-графіка: що може піти не так і як постачальники це виправляють

Залишити коментар