Ви впізнаєте запах: панелі сповнені червоним, pager пищить, а сервіс «запускається» сотні разів за хвилину, виконуючи при цьому нуль корисної роботи. На Ubuntu 24.04 systemd не драматизує. Він робить те, що ви йому наказали — перезапускати процес, що падає — поки не досягне ліміту чи не перетворить ваші логи на конфетті.
Ось як зупинити цикл не втрачаючи доказів, витягти першу реальну помилку (не 500-ту), і потім виправити базову причину. Ми робитимемо це як оператори: мінімум геройства, максимум сигналу та зміни, які ви зможете захистити в постмортемі.
Що насправді означає цикл перезапуску в systemd
«Цикл перезапуску» — це просто петля зворотнього зв’язку між політикою перезапуску юніту і процесом, який повертається до systemd надто швидко. Процес завершується (креш, неправильна конфігурація, відсутня залежність, проблема прав), systemd застосовує правила Restart= з файлу юніту і пробує знову. Повторюйте. Якщо це відбувається достатньо швидко, systemd зрештою каже «достатньо» через обмеження частоти запуску (StartLimitIntervalSec + StartLimitBurst) і позначає юніт як failed.
Два ключові операторські моменти:
- Корінна помилка майже завжди в перших кількох спробах. Пізніше ви в основному отримуєте повторюваний шум: «Main process exited» і «Scheduled restart job.»
- systemd не знає «здоровий», він знає «процес завершився». Сервіс може бути «running» і все одно бути марним, або «failed», коли насправді він успішно завершився і неправильно демонайзнувся.
Є поширений анти-приклад: «вилікувати» цикл, встановивши Restart=no і закривши питання. Це не виправлення; це вимкнення пожежної тривоги, бо вона голосна.
Швидкий план діагностики (робіть це в першу чергу)
Це порядок дій, який заощаджує час під тиском. Оптимізовано під «знайти вузьке місце швидко», а не для теоретичної повноти.
1) Підтвердіть, що маєте справу з циклом перезапуску і зафіксуйте його темп
- Перевірте, чи systemd активно перезапускає його, і з якою швидкістю.
- Шукайте обмеження частоти («Start request repeated too quickly»), що означає: ви пропустили найраніше повідомлення і потрібно тягнути журнали за часовим вікном.
2) Витягніть найраніші рядки помилок, а не останні
- Використовуйте
journalctlз--sinceі--reverse, щоб знайти перший поганий рядок після старту. - Отримайте статус виходу та інформацію про сигнал з повідомлень systemd.
3) Заморозьте пацієнта (зупиніть цикл) без видалення доказів
- Замість видалення створіть mask або зупиніть юніт, або тимчасово встановіть
Restart=noчерез override. - Не чистіть журнал. Не перезавантажуйте «щоб очистити». Саме так ви втрачаєте єдині докази.
4) Класифікуйте режим відмови
Більшість циклів потрапляють до одного з цих кошиків:
- Миттєвий вихід: неправильна конфігурація, відсутня змінна середовища, неправильний робочий каталог, невірний прапор, відсутній бінарник.
- Креш: segfault, abort, illegal instruction (часто несумісність бібліотеки або особливостей CPU), OOM.
- Невідповідність готовності: сервіс каже, що стартував, але systemd очікує
Type=notify, або несподівано форкується. - Залежність: мережа не готова, DNS зламаний, монтування відсутнє, проблеми з правами/SELinux/AppArmor.
- Обмеження ресурсів: дескриптори файлів, memlock, tasks, відмінності ulimit під systemd.
5) Відтворіть у тій самій оточенні, яке використовує systemd
- Запустіть той самий ExecStart у чистому середовищі або використайте
systemd-runдля емулювання. - Перевірте опції sandboxing юніту, які можуть блокувати доступ до файлів (наприклад,
ProtectSystem=,PrivateTmp=).
Як безпечно зупинити цикл (і зберегти докази)
Якщо сервіс «флапає», він шкодить системі: спалює CPU, засмічує логи, повторно відкриває сокети, перемелює кеші і може спровокувати зовнішні обмеження по частоті. Ваше перше завдання — зупинити кровотечу, не знищивши місце події.
Опція A: Зупинити юніт (короткочасна пауза)
Це швидка кнопка паузи. systemd все ще може намагатися перезапустити, якщо інші юніти залежать від нього і тягнуть його назад — перевірте після зупинки.
Опція B: Mask юніт (жорстке зупинення)
Mask забороняє запуск (вручну або через залежності). Це правильний рух, коли цикл завдає побічної шкоди і потрібен стабільний хост для розслідування.
Опція C: Тимчасовий override: відключити політику перезапуску
Це чистіше, ніж редагувати файли вендора. Створіть drop-in override, щоб змінити Restart= і додати довший RestartSec=, щоб ви могли читати логи між спробами.
Жарт №1: Цикл перезапуску — як інтерн з безкінечною енергією й нульовим контекстом — швидкий, наполегливий і якимось чином робить усе гірше.
Виловити корінну помилку: журнали, коди виходу та coredump
systemd добре каже вам, що померло. Вам потрібно, щоб він сказав вам, чому. «Чому» зазвичай знаходиться в одному з цих місць:
- журнали journal для юніту та його залежностей
- метадані виходу systemd: код статусу, сигнал, нотатки про core dump
- лог файли додатку: файлові логи, stderr/stdout, структуровані логи
- coredump: для реальних крашів
- логи ядра: OOM killer, segfault, помилки монтування
Одна цитата, навколо якої варто будувати інцидент-реакцію:
«Сподіватися — не стратегія.» — перефразована ідея, часто приписувана в інженерному менеджменті
Переклад: перестаньте гадати. Збирайте докази. Робіть по одній зміні.
Практичні завдання: команди, виходи, рішення (12+)
Це ті завдання, які я реально виконую, коли сервіс флапає на Ubuntu 24.04. Кожне включає, що означає вихід і яке рішення з нього випливає.
Завдання 1: Підтвердіть поточний стан, недавні відмови та лічильники перезапусків
cr0x@server:~$ systemctl status myapp.service --no-pager -l
● myapp.service - MyApp API
Loaded: loaded (/etc/systemd/system/myapp.service; enabled; preset: enabled)
Active: activating (auto-restart) (Result: exit-code) since Mon 2025-12-29 10:14:07 UTC; 2s ago
Process: 18422 ExecStart=/usr/local/bin/myapp --config /etc/myapp/config.yml (code=exited, status=1/FAILURE)
Main PID: 18422 (code=exited, status=1/FAILURE)
CPU: 38ms
Dec 29 10:14:07 server systemd[1]: myapp.service: Main process exited, code=exited, status=1/FAILURE
Dec 29 10:14:07 server systemd[1]: myapp.service: Failed with result 'exit-code'.
Dec 29 10:14:07 server systemd[1]: myapp.service: Scheduled restart job, restart counter is at 17.
Що це означає: Він завершується з статусом 1 швидко й був перезапущений 17 разів. Це не «повільний старт», а детермінована відмова.
Рішення: Зупиніть цикл і витягніть найраніші логи навколо першого перезапуску. Не витрачайте час на налаштування таймаутів поки що.
Завдання 2: Негайно зупиніть цикл (пауза)
cr0x@server:~$ sudo systemctl stop myapp.service
cr0x@server:~$ systemctl is-active myapp.service
inactive
Що це означає: systemd його зупинив. Якщо він знову перейде в «activating», щось інше його підтягує.
Рішення: Якщо він перезапускається через залежності, замаскуйте його (наступне завдання).
Завдання 3: Замаскуйте, щоб уникнути перезапусків, викликаних залежностями
cr0x@server:~$ sudo systemctl mask myapp.service
Created symlink /etc/systemd/system/myapp.service → /dev/null.
cr0x@server:~$ systemctl status myapp.service --no-pager -l
● myapp.service
Loaded: masked (Reason: Unit myapp.service is masked.)
Active: inactive (dead)
Що це означає: Його не можна запустити поки не розмаскувати. Це оборотна дія і залишає журнали цілими.
Рішення: Діагностуйте спокійно. Розмаскуйте тільки коли будете готові до тесту.
Завдання 4: Витягніть журнали конкретного юніту за останній завантаження, новіші першими
cr0x@server:~$ journalctl -u myapp.service -b --no-pager -n 200
Dec 29 10:13:59 server myapp[18391]: FATAL: cannot read config file: open /etc/myapp/config.yml: permission denied
Dec 29 10:13:59 server systemd[1]: myapp.service: Main process exited, code=exited, status=1/FAILURE
Dec 29 10:14:00 server systemd[1]: myapp.service: Failed with result 'exit-code'.
Dec 29 10:14:00 server systemd[1]: myapp.service: Scheduled restart job, restart counter is at 1.
Що це означає: Це золото: «permission denied» при читанні конфігу.
Рішення: Виправте права/власність або перевірте опції жорсткого режиму, які змінили видимість файлової системи.
Завдання 5: Визначте точний ExecStart і налаштування юніту
cr0x@server:~$ systemctl cat myapp.service
# /etc/systemd/system/myapp.service
[Unit]
Description=MyApp API
After=network-online.target
Wants=network-online.target
[Service]
User=myapp
Group=myapp
ExecStart=/usr/local/bin/myapp --config /etc/myapp/config.yml
Restart=always
RestartSec=1
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
[Install]
WantedBy=multi-user.target
Що це означає: ProtectSystem=strict може зробити великі частини файлової системи доступними лише для читання; ProtectHome=true блокує доступ до /home. Якщо конфіг під захищеним шляхом або потрібні допоміжні файли, ви можете зламати сервіс навіть «покращуючи безпеку».
Рішення: Якщо права виглядають нормальними, підозрюйте жорстке обмеження юніту. Або підлаштуйте шляхи, або пом’якшіть конкретні опції через вибіркові override.
Завдання 6: Перевірте права та обмеження SELinux/AppArmor (в Ubuntu зазвичай AppArmor)
cr0x@server:~$ namei -l /etc/myapp/config.yml
f: /etc/myapp/config.yml
drwxr-xr-x root root /
drwxr-xr-x root root etc
drwx------ root root myapp
-rw------- root root config.yml
Що це означає: Каталог /etc/myapp має права 700, а файл — 600, власник root. Якщо юніт працює від користувача myapp, він не зможе його прочитати.
Рішення: Змініть власника або ACL. Не запускайте сервіс як root «бо так працює». Таким чином наступного тижня отримаєте інший інцидент.
Завдання 7: Обережно виправте власність (приклад) і перевірте
cr0x@server:~$ sudo chown -R root:myapp /etc/myapp
cr0x@server:~$ sudo chmod 750 /etc/myapp
cr0x@server:~$ sudo chmod 640 /etc/myapp/config.yml
cr0x@server:~$ sudo -u myapp test -r /etc/myapp/config.yml && echo OK
OK
Що це означає: Користувач сервісу тепер може читати файл.
Рішення: Розмаскуйте і запустіть один раз; слідкуйте за логами. Якщо все ще падає, ви виключили одну корінну причину і залишили зміну мінімальною.
Завдання 8: Розмаскуйте, запустіть один раз і слідкуйте за логами в реальному часі
cr0x@server:~$ sudo systemctl unmask myapp.service
Removed "/etc/systemd/system/myapp.service".
cr0x@server:~$ sudo systemctl start myapp.service
cr0x@server:~$ journalctl -u myapp.service -f --no-pager
Dec 29 10:22:41 server myapp[19012]: INFO: loaded config /etc/myapp/config.yml
Dec 29 10:22:41 server myapp[19012]: INFO: listening on 0.0.0.0:8080
Що це означає: Він стартує і тримається вгорі (більше перезапусків не спостерігається).
Рішення: Якщо стабільно, увімкніть оповіщення та перевірте залежності (БД, сховище, upstream).
Завдання 9: Якщо журнали зашумлені, знайдіть перше вікно помилки за часом
cr0x@server:~$ journalctl -u myapp.service --since "2025-12-29 10:10:00" --until "2025-12-29 10:15:00" --no-pager
Dec 29 10:13:59 server myapp[18391]: FATAL: cannot read config file: open /etc/myapp/config.yml: permission denied
Dec 29 10:13:59 server systemd[1]: myapp.service: Main process exited, code=exited, status=1/FAILURE
Dec 29 10:14:00 server systemd[1]: myapp.service: Scheduled restart job, restart counter is at 1.
Що це означає: Ви можете відокремити найранішу помилку, навіть якщо перезапуски засмічують журнал.
Рішення: Завжди обмежуйте час при запитах журналів під час штормів перезапусків. Це зберігає здоровий глузд і робить нотатки інциденту більш переконливими.
Завдання 10: Перевірте ланцюжок залежностей і порядок
cr0x@server:~$ systemctl list-dependencies --reverse myapp.service
myapp.service
● multi-user.target
Що це означає: На нього мало що залежить (добре). Якщо зворотний список великий, зупинка може зламати інші юніти.
Рішення: Для великих дерев залежностей маскуйте обережно і повідомляйте про вплив. «Я зупинив це» — не повний план.
Завдання 11: Виловити краші: перевірте coredump
cr0x@server:~$ coredumpctl list myapp
TIME PID UID GID SIG COREFILE EXE
Mon 2025-12-29 09:58:12 UTC 17021 1001 1001 11 present /usr/local/bin/myapp
cr0x@server:~$ coredumpctl info 17021
PID: 17021 (myapp)
UID: 1001 (myapp)
GID: 1001 (myapp)
Signal: 11 (SEGV)
Timestamp: Mon 2025-12-29 09:58:12 UTC (17min ago)
Command Line: /usr/local/bin/myapp --config /etc/myapp/config.yml
Executable: /usr/local/bin/myapp
Control Group: /system.slice/myapp.service
Unit: myapp.service
Message: Process 17021 (myapp) of user 1001 dumped core.
Що це означає: Якщо ви бачите SIGSEGV/SIGABRT, ви в зоні реальних крашів: пошкоджений парсинг конфігу, несумісна бібліотека або баг у пам’яті.
Рішення: Перестаньте робити випадкові правки юніту. Зберіть core, build ID та версії пакетів; потім відтворіть у staging або зверніться до вендора.
Завдання 12: Перевірте логи ядра на OOM і сегфолти
cr0x@server:~$ journalctl -k -b --no-pager -n 200
Dec 29 10:01:44 server kernel: Out of memory: Killed process 17555 (myapp) total-vm:812340kB, anon-rss:612220kB, file-rss:1100kB, shmem-rss:0kB, UID:1001 pgtables:1872kB oom_score_adj:0
Dec 29 10:01:44 server kernel: oom_reaper: reaped process 17555 (myapp), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
Що це означає: Ядро вбило процес. systemd перезапускає його. Цикл досягнуто.
Рішення: Усуньте тиск пам’яті (ліміти, витік, конкурентність) або додайте контролі пам’яті systemd (MemoryMax=), щоб падати швидко і передбачувано. Також перевірте swap і сумісні навантаження на хості.
Завдання 13: Перевірте ліміти частоти запуску (чому він «перестає пробувати»)
cr0x@server:~$ systemctl show myapp.service -p StartLimitIntervalUSec -p StartLimitBurst -p NRestarts -p Restart -p RestartUSec
StartLimitIntervalUSec=10s
StartLimitBurst=5
NRestarts=17
Restart=always
RestartUSec=1s
Що це означає: Дозволяє 5 стартів за 10 секунд. З RestartSec=1 ви швидко вдарите по лімітеру.
Рішення: Не «виправляйте», піднімаючи burst до 10 000. Збільшіть RestartSec, щоб уповільнити шторм і зберегти ресурси під час дебагу.
Завдання 14: Відтворіть під systemd-run, щоб відповідало середовищу сервісу
cr0x@server:~$ sudo systemd-run --unit=myapp-debug --property=User=myapp --property=Group=myapp \
/usr/local/bin/myapp --config /etc/myapp/config.yml
Running as unit: myapp-debug.service
cr0x@server:~$ journalctl -u myapp-debug.service --no-pager -n 50
Dec 29 10:30:10 server myapp[19411]: FATAL: cannot open /var/lib/myapp/state.db: permission denied
Що це означає: Ваш тест «виконується нормально, коли я запускаю вручну» ймовірно запускався як root або в іншому робочому каталозі чи з іншими змінними середовища.
Рішення: Завжди відтворюйте з користувачем сервісу в контексті systemd. Це заощадить години та зменшить фольклор.
Завдання 15: Перевірте та виправте власність RuntimeDirectory/StateDirectory (сучасний патерн systemd)
cr0x@server:~$ systemctl show myapp.service -p StateDirectory -p RuntimeDirectory
StateDirectory=myapp
RuntimeDirectory=myapp
cr0x@server:~$ ls -ld /var/lib/myapp /run/myapp
ls: cannot access '/var/lib/myapp': No such file or directory
drwxr-xr-x 2 root root 40 Dec 29 10:12 /run/myapp
Що це означає: Якщо налаштовано StateDirectory=, systemd мав би створити його під /var/lib з правильною власністю — якщо лише юніт старий, неправильно вказаний або перевизначений. Тут він відсутній, а runtime-каталог належить root.
Рішення: Виправте юніт, щоб systemd керував директоріями, або створіть їх з правильною власністю. Помилки власності директорій — класична причина для циклу перезапусків.
Коли сервіс падає лише під systemd
Це дезорієнтує: ви запускаєте бінарник вручну і він працює. Під systemd він відразу падає. Це не надприродне. Це середовище.
Ось що під systemd змінюється і зазвичай має значення:
- Користувач/група та додаткові групи: ручні тести часто запускають як root або як увійшовший користувач.
- Робочий каталог: systemd за замовчуванням використовує
/, якщо не встановленоWorkingDirectory=. - Змінні середовища: ваша оболонка підвантажує профілі; systemd — ні. Якщо додатку потрібні налаштування PATH або
JAVA_HOME, вкажіть їх явно. - Ліміти дескрипторів файлів: systemd може встановлювати інші значення за замовчуванням, і ваш сервіс може вдарити по
EMFILE(занадто багато відкритих файлів). - Sandboxing/жорсткий режим:
ProtectSystem,PrivateTmp,RestrictAddressFamiliesта інші опції можуть тонко ламати додатки. - Тип запуску: якщо додаток демонайзується чи форкується, а юніт має
Type=simple(за замовчуванням), systemd може трактувати батька як вихід і перезапустити.
Жарт №2: systemd не «ненавидить» ваш додаток. Він просто відмовляється брати участь у вашому інтерпретативному танці навколо PID 1.
Пошкодження зберігання та файлової системи, що виглядають як “помилки додатку”
Як людина, що працює зі сховищами, скажу голосно: величезна кількість циклів перезапуску — це проблеми зі сховищем, запаковані в оболонку додатку.
Перемикання на режим лише для читання файлової системи
ext4 і подібні можуть перемонтуватися в режим лише для читання після певних I/O помилок, щоб запобігти подальшому пошкодженню. Ваш сервіс тоді падає при створенні PID-файлів, записі стану, ротації логів або оновленні SQLite. systemd перезапускає його вічно, бо процес продовжує виходити «з якоїсь причини».
Диск заповнений (або таблиця інодів заповнена)
Сервіс, що не може записати в /var або /tmp, часто помирає на ранніх стадіях. Гірше: лог-спам займає решту місця й повалює інші сервіси. Перевіряйте і блоки, і іноди.
Порядок монтувань і мережеві файлові системи
Сервіси, які очікують наявності /mnt/data, впадуть, якщо він не змонтовано. Це ускладнюється з NFS, iSCSI та зашифрованими томами: монтування може бути «присутнє», але не готове; або мережа вгору, але DNS не працює, або навпаки.
Права на шляхи стану
Після відновлення, rsync чи «поліпшення» власність може зійти з шляху. Багато демонів вийдуть, якщо виявлять неправильні права на чутливі файли (SSH, Postgres тощо). Це функція, але вона створює цикли перезапусків, якщо ви не бачите першого рядка журналу.
Мережа та DNS- пастки
Ubuntu 24.04 зазвичай використовує systemd-resolved і Netplan. Цикл перезапуску часто відбувається, коли сервіс намагається резолвити ім’я або підключитися до upstream під час старту, швидко падає і виходить. Вітаємо: ваша нестабільність DNS тепер CPU-бенчмарк.
Поширені мережеві тригери:
- Сервіс стартує раніше, ніж мережа «online» (link up — не те ж саме, що routable).
- DNS вказує на мертвий резольвер;
resolv.confкерується, і ваші старі припущення ламаються. - Firewall блокує вихід; додаток трактує це як фатальну помилку замість спроби повтору.
- Проблеми з IPv6: додаток прив’язується лише до v6 або намагається v6 першим і чекає таймаут.
Порядок залежностей і готовність
Цикли перезапуску не завжди про невдачу додатку. Іноді додаток стартує правильно, але systemd думає, що ні, або запускає його в невірний час.
Неправильний Type сервісу
Якщо ваш демон форкується у фоні, але юніт має Type=simple, systemd може інтерпретувати вихід батька як відмову (або успіх, залежно), і тоді перезапустити. Зазвичай виправлення — Type=forking з PIDFile=, або краще: налаштуйте додаток, щоб він залишався в передньому плані і використовуйте Type=simple.
Невідповідність нотифікації готовності
Type=notify очікує, що процес викличе sd_notify. Якщо цього не відбувається, systemd чекає, таймаутить, вбиває, перезапускає. Цей цикл виглядає як краш, але насправді це порушення контракту.
Монтажні та мережеві таргети не магічні
After=network-online-target допомагає, але працює тільки якщо відповідна служба «wait online» увімкнена для вашого стеку мережі. Для монтувань використовуйте RequiresMountsFor=, щоб прив’язати юніт до шляху, який має бути змонтований.
Ліміти швидкості, політика перезапуску та як налаштувати без обману
Політика перезапуску — це не просто «зробити доступнішим». Це «заявити, як система поводиться при відмові». У продакшені ця поведінка має бути обдуманою.
Restart=always рідко те, що вам потрібно за замовчуванням
Restart=always перезапускає навіть після чистого виходу. Це добре для воркерів, що мають працювати вічно; це жахливо для oneshot-завдань і демонів, які навмисно виходять після успішної міграції.
Кращі патерни:
- Restart=on-failure для більшості демонів.
- Restart=on-abnormal коли чистий вихід — легітимний.
- Без перезапуску для пакетних завдань; нехай планувальник обробляє ретраї.
Використовуйте RestartSec, щоб уникнути самонанесеної відмови в обслуговуванні
Якщо сервіс буде падати повторно, зробіть так, щоб він падав повільніше, щоб люди могли читати логи, а машина — перевести подих. Збільшення RestartSec з 100ms до 5s може бути різницею між «дрібним інцидентом» і «хост непридатний для роботи».
StartLimit… це запобіжник, а не виправлення
Ліміти запуску зупиняють негайний шторм, але не вирішують корінну причину. Розглядайте повідомлення «start request repeated too quickly» як вічливе повідомлення systemd: ви діагностуєте надто пізно в часовій шкалі.
Поширені помилки: симптом → корінна причина → виправлення
Ця частина — те, що ви впізнаєте за десять секунд, бо вже це бачили.
1) Симптом: «Active: activating (auto-restart)» зі статусом=1/FAILURE
Корінна причина: детермінована помилка запуску (парсер конфігу, відсутній файл, permission denied).
Виправлення: stop/mask, потім journalctl -u по часовому вікну. Виправте права або конфіг. Якщо юніт працює не від root, перевірте доступ через sudo -u.
2) Симптом: «Start request repeated too quickly» і потім залишається failed
Корінна причина: спрацював ліміт частоти; ранні помилки прокотилися.
Виправлення: запитуйте логи з --since/--until або -b і шукайте першу відмову. Розгляньте збільшення RestartSec під час діагностики, а не StartLimitBurst.
3) Симптом: сервіс працює при ручному запуску, падає під systemd
Корінна причина: невідповідність середовища (користувач, робочий каталог, PATH, ulimits, sandboxing).
Виправлення: відтворіть через systemd-run з користувачем сервісу. Перевірте systemctl cat на предмет жорсткого режиму та директорій. Встановіть WorkingDirectory= і явні Environment=/EnvironmentFile=.
4) Симптом: статус показує «status=203/EXEC»
Корінна причина: ExecStart вказує на відсутній бінарник або той, що не виконуваний, або невідповідна архітектура/формат.
Виправлення: перевірте шлях і права; перевірте shebang в скриптах. Переконайтеся, що бінарник є на хості, а не тільки у вашій пам’яті.
5) Симптом: статус показує «code=killed, signal=KILL» з повідомленнями TimeoutStartSec
Корінна причина: systemd вбив після таймауту старту; часто невідповідність готовності або повільна залежність.
Виправлення: виправте Type=, нотифікацію готовності або зробіть старт негроміздким щодо залежностей. Якщо старт реально займає більше часу, підвищіть TimeoutStartSec з обґрунтуванням.
6) Симптом: логи ядра показують записи OOM killer для сервісу
Корінна причина: тиск пам’яті або витік; може бути спільне використання ресурсів або неконтрольована конкурентність.
Виправлення: зменште використання пам’яті, додайте swap за потреби або застосуйте MemoryMax=. Потім знайдіть і виправте витік. Не додавайте просто RAM і називайте це «capacity planning».
7) Симптом: «permission denied» при записі в /run або /var/lib
Корінна причина: директорії runtime/state відсутні або належать root після розгортання чи відновлення.
Виправлення: використовуйте RuntimeDirectory= і StateDirectory=, або виправляйте власність через tmpfiles.d. Уникайте ad-hoc mkdir в ExecStartPre, якщо вам не подобаються race conditions.
8) Симптом: відмови корелюють з перезавантаженнями; шлях монтування відсутній при завантаженні
Корінна причина: порядок залежностей і готовність монтувань; мережеві FS не готові в момент старту.
Виправлення: додайте RequiresMountsFor=/path і скоригуйте After=. Для network-online переконайтеся, що відповідна служба wait-online увімкнена.
Три корпоративні міні-історії (біль, уроки, висновки)
Міні-історія 1: Інцидент через неправильне припущення
Команда мала невелике внутрішнє API як systemd сервіс. Нічого складного. Він читає YAML з /etc/company/app.yml і пише стан у /var/lib/app. Так це працювало місяцями.
Потім прийшла спринт з жорсткого підвищення безпеки. Файл юніту «покращили» з ProtectSystem=strict і NoNewPrivileges=true. Усi кивнули. Безпека за замовчуванням — добре. Сервіс перезапускався що секунду і викликав оповіщення по всьому середовищу.
Неправильне припущення було тонким: конфіг «очевидно читабельний», бо завжди працював. Але права файлу були ліниво залишені root-only з першого дня, і сервіс працював як root. Спринт з жорсткої безпеки також змінив модель привілеїв на невигідну. Безпека покращилась — і все зламалось одночасно.
Виправлення було нудним: виправили власність і права, а потім залишили жорсткі налаштування. Урок не в тому, щоб не жорсткити. Урок — не поєднувати зміни безпеки з моделлю привілеїв без плану тестування. На щастя, сервіс став безпечніший, і команда припинила вважати root за фічу.
Міні-історія 2: Оптимізація, що відбилася у відповідь
Платформна група хотіла швидші відновлення. Хтось знизив RestartSec по флоту з 5s до 100ms. Ідея: якщо процеси падають — відновлювати негайно. В лабораторії це виглядало круто. В продакшені маленька відмова перетворилась на системну проблему.
Один сервіс почав падати через ротацію секретного файлу з неправильними правами. Замість одного падіння кожні кілька секунд з читабельними логами, він намагався рестартитись десять разів на секунду на десятках нод. Журнали роздулися. Запис на диск посилився. Ноди почали повідомляти про затримки. Інші сервіси на тих же хостах уповільнились. Шторм рестартів став інцидентом.
Постмортем був неприємним, бо початкова відмова була локальною й незначною. Оптимізація додала гігантський мегафон і податкове навантаження на ресурси. Повернули агресивні таймінги, ввели затримки на рівні сервісу і вимагали обґрунтування для дуже низьких RestartSec. Найважливіше — перестали вважати рестарт ремедіацією. Це інструмент стійкості лише якщо система може ефективно показати корінну причину.
Міні-історія 3: Нудна, але правильна практика, що врятувала день
Інша організація запускала сервіс, пов’язаний з платежами, зі строгим процесом змін. Нечарівно. Кожен юніт мав: drop-in override політику в системі керування конфігурацією, явні користувачі/групи, явну власність директорій через StateDirectory=, і стандартний «debug override», що міг встановити Restart=no і тимчасово збільшити деталізацію логів.
Під час апгрейду Ubuntu один вузол почав флапати демон через несумісність бібліотеки після часткового оновлення пакету. On-call не робив фрістайлів правок на ноді. Вони застосували відомий debug override: зупинили рестарти, зібрали логи, взяли версії пакетів і відновили вузол, завершивши транзакцію оновлення.
Чому це спрацювало? Бо інцидент не розрісся. Нода залишалась стабільною настільки, щоб зібрати докази. Флот не отримав хвилі несумісних хотфіксів. Команда отримала одне чисте виправлення: виправити автоматизацію оновлень, щоб уникати часткових апгрейдів і гарантувати, що сервіс стартує лише після наявності потрібних пакетів.
Нудна практика працює, бо зменшує кількість нових змінних, які ви вводите, коли система в огні. Це не захопливо. Це надійно.
Контрольні списки / покроковий план
Покроково: зупиніть цикл, потім діагностуйте
- Зафіксуйте поточний стан:
systemctl status -lі скопіюйте рядки з кодами виходу до нотаток інциденту. - Зупиніть або замаскуйте: якщо сервіс спричиняє ресурсний шум — замаскуйте його.
- Витягніть найраніші логи відмови: використовуйте
journalctl -uз--since/--untilнавколо першого відомого часу відмови. - Класифікуйте режим відмови: права/конфіг, краш, залежність, таймаут, OOM.
- Перевірте файл юніту на пастки: користувач/група, робочий каталог, жорсткий режим, Type, таймаути, політика перезапуску.
- Відтворіть у контексті systemd:
systemd-runабо запустіть як користувач сервісу. - Перевірте шар платформи: місце на диску/іноди, монтування, DNS, логи ядра на OOM.
- Зробіть одну зміну: найменшу можливу, що усуває корінну причину.
- Протестуйте раз: розмаскуйте і запустіть; слідкуйте за логами.
- Відновіть політику перезапуску усвідомлено: не лишайте в режимі налагодження.
- Запишіть, що змінилося: майбутнє «ви» не пам’ятає, а аудитори точно не забудуть.
Жорсткі правила, коли ви недосипаєте
- Не редагуйте файл вендора в
/lib/systemd/system. Використовуйте drop-ins. - Не «виправляйте» запуском сервісу як root, якщо сервіс не потребує привілеїв (і навіть тоді мінімізуйте область).
- Не підвищуйте StartLimitBurst як перший крок. Краще уповільніть рестарти.
- Не перезавантажуйте лише щоб журнали зникли. Це не гігієна; це знищення доказів.
Цікаві факти та історичний контекст
- systemd запровадив агресивні, явні семантики перезапуску в порівнянні з традиційними init-скриптами, що зробило «флапання» більш помітним—і частішим при неправильних налаштуваннях.
- Ліміти частоти запуску існують частково тому, що ранні демони могли fork-bomb PID 1 швидкими циклами падіння/перезапуску; ліміт — запобіжник.
- Перехід Ubuntu до systemd-resolved змінив управління DNS; старі припущення щодо статичного
/etc/resolv.confчасто ламаються під час оновлень. - Обробка coredump перемістилась від «core файлів скрізь» до централізованого збору через
systemd-coredump, що покращило дебаг на флоті, але збило з пантелику тих, хто чекавcoreв робочому каталозі. - Сучасний systemd додав декларативне управління директоріями (
StateDirectory,RuntimeDirectory,LogsDirectory), щоб зменшити крихкість ExecStartPre скриптів. - Опції жорсткого режиму як ProtectSystem і PrivateTmp стали мейнстрімом, бо вони дешево зменшують класи компрометацій — але також виявляють неакуратні припущення про файлову систему.
- «Працює в оболонці» — брехня з часів Unix; середовища демона завжди відрізнялися (інший PATH, немає TTY, інші ulimits). systemd просто робить контракт явнішим.
- Ліміти частоти і політики перезапуску — це інструменти надійності, а не косметика; ранні HA-патерни намагалися «рестартувати все миттєво», і потім боляче дізналися про каскадні відмови.
Поширені питання
1) Як зупинити цикл перезапуску сервісу, не видаляючи нічого?
Замаскуйте його: sudo systemctl mask myapp.service. Це забороняє як ручні, так і залежні запуски. Розмаскуйте, коли будете готові.
2) Чому systemd продовжує перезапускати сервіс, що очевидно не стартує?
Тому що юніт так налаштований. Шукайте Restart=always або Restart=on-failure. systemd вважає, що перезапуск бажаний, якщо не сказано інакше.
3) Де знаходиться «справжня помилка», якщо systemctl status показує лише повідомлення про перезапуск?
Зазвичай у journalctl -u myapp.service, часто в найраніших рядках після першої спроби старту. Використовуйте --since/--until, щоб націлити початок циклу.
4) Що означає «status=203/EXEC»?
systemd не зміг виконати команду в ExecStart=. Поширені причини: невірний шлях, відсутній бінарник, не виконуваний файл або скрипт з поганим shebang.
5) Що означає «Start request repeated too quickly»?
Тригернувся ліміт запуску. Юніт запустився і впав занадто багато разів в межах StartLimitIntervalSec, перевищивши StartLimitBurst. Це запобіжник, а не корінна причина.
6) Як зрозуміти, що це краш (segfault), а не чистий вихід?
У systemctl status та журналі шукайте виходи за сигналами (SIGSEGV, SIGABRT). Потім перевірте coredumpctl list і coredumpctl info.
7) Сервіс працює при ручному запуску. Чому не під systemd?
Інше середовище: інший користувач, інші ліміти, інший робочий каталог та, можливо, sandboxing. Відтворіть через systemd-run з користувачем сервісу.
8) Чи варто збільшувати TimeoutStartSec, щоб виправити цикл?
Тільки якщо ви довели, що сервіс здоровий, але повільно готовий. Якщо він падає швидко, довший таймаут тільки затягне неминуче і витратить час.
9) Чи нормально ставити Restart=always для всього, щоб «самовідновлювалося»?
Ні. Це може створити шторм перезапусків і приховати детерміновані помилки. Використовуйте Restart=on-failure для більшості демонів і проєктуйте додатки так, щоб вони самі повторювалися для залежностей.
10) Як убезпечити диск від заповнення логами під час шторму перезапусків?
Замаскуйте сервіс, потім розгляньте уповільнення рестартів (RestartSec) перед повторним увімкненням. Якщо journald вже зайняв багато місця, робіть ротацію/вакуум лише після того, як ви зафіксували критичне вікно.
Висновок: наступні кроки, щоб не потрапляти в халепу
Цикли перезапуску здаються хаотичними, але вони зазвичай детерміновані: відсутня права, зламана залежність, невідповідність готовності або реальний краш. Ваше завдання — зупинити метушню, захопити першу значущу помилку і змінити найменшу річ, яка змусить сервіс поводитися правильно.
Зробіть наступне, у порядку:
- Замаскуйте сервіс, якщо він флапає і завдає побічної шкоди.
- Витягніть найранішу помилку з журналу за часовим вікном.
- Класифікуйте режим відмови (права/конфіг, краш, таймаут, залежність, ресурс).
- Відтворіть у контексті systemd з правильним користувачем.
- Застосуйте мінімальне виправлення, розмаскуйте, запустіть один раз і слідкуйте за логами.
- Відновіть розумну політику перезапуску (
on-failure, з розумнимRestartSec), щоб майбутні відмови були стійкими і діагностованими.
Якщо взяти з собою одне: не боріться з циклом, роблячи systemd тихішим. Зробіть відмову гучнішою, ранньою і легшою для доведення. Так інциденти припиняють повторюватись.