У суботу зранку я запустив apt upgrade на одному з двох своїх серверів і перезавантажив його. Уже за хвилину він відповідав на пінги – і до кінця дня відхиляв будь-яке з’єднання.
У мене дві машини. На першій – інфраструктура, яку я не готовий втратити, і в неї власний безперебійник, бо там, де я живу, світло вимикають за графіком. Друга формально – пісочниця, хоча це слово сильно применшує те, що на ній наросло: сім віртуальних машин, серед них DNS-сервер, до якого звертається кожен пристрій у будинку, файловий сервер із сімейним архівом і зворотний проксі, що ставить справжній HTTPS-сертифікат перед півдесятком внутрішніх інструментів. Без перезавантаження вона пропрацювала тридцять два дні. Саме вона – героїня цієї історії.
Ця лабораторія – робочий інструмент. Тут не іржавіє те операційне чуття, за яке мене й наймають: такі інциденти ніхто не планує, і піднімати все доводиться мені.
На мене чекали дві несправності, обидві цілком буденні. А от що я досі прокручую в голові – то це, як склався той ранок. Я провів ретельне розслідування, здебільшого копав разом із Claude Code, і кожну гіпотезу ми висували й відкидали в розумному порядку. А причиною виявився порт на моєму роутері, у який я сам увімкнув сервер того ж таки ранку.
Усе, що ми з ним оглядали, лежало всередині межі, яку я накреслив, коли формулював задачу. Розслідування шукає в тому світі, який йому дали, а межі цього світу креслить той, хто ставить питання, – зазвичай ще до того, як знає достатньо, щоб поставити його добре.
Сплячий збій ховається за останньою зміною
Машина завантажилася, відповідала на пінги й відмовляла в усьому іншому: SSH, вебінтерфейс, участь у кластері. Відкритим лишився один порт, лише один. Порт 111.
Цим єдиним відкритим портом машина казала мені, як далеко вона встигла зайти. Порт 111 належить rpcbind, і systemd сам тримає цей сокет відкритим від самого початку завантаження, ще до того, як запуститься програма за ним. Усьому іншому потрібно, щоб завантаження завершилося. А воно не завершилося.
Я змусив себе висмикнути цю штуку з розетки й перетягти на свій стіл, щоб підключити до неї монітор і клавіатуру. За чотири секунди після перезавантаження екран сказав усе: машина не могла дістатися до диска, якого вже не існувало, втомилася чекати й упала в emergency mode – аварійний режим, у якому Linux майже нічого не запускає й чекає на людину.
Звідси й відповіді на порту 111 – і більше ніде.
Диска не було, бо 27 червня я переформатував ті накопичувачі з exFAT на ZFS, а старий запис із fstab так і не прибрав. fstab – це перелік сховищ, без яких машина не вважатиме себе завантаженою. Його читають під час завантаження і більше ніколи, тож помилка тихо пролежала там тридцять два дні, не давши жодного симптому. А тоді вистрелила на першому ж перезавантаженні.
І це загальний принцип, що стосується далеко не лише fstab: збій, який може виринути тільки під час перезапуску, завжди вирине поряд із тим, через що перезапускали. А перезавантаження йдуть слідом за оновленнями. Тож дві години вину носило на собі оновлення – на підставі доказу, який зводився до одного: воно змінилося останнім.
Рядкові бракувало одного слова. nofail каже системі, що зникнення диска – це прикро, але не смертельно. Без нього накопичувач, який від’єднали або який просто повільно прокинувся, кладе всі сім віртуальних машин у чорний екран. Я дописав слово, перезавантажив – і машина піднялася.
Другу несправність я створив сам, розслідуючи першу
На цьому все мало б і скінчитися. А виявилося, що це приблизно середина.
Обидва сервери входять у кластер Proxmox, тобто ділять між собою невелику розподілену файлову систему, де лежать налаштування кожної віртуальної машини, і на обох вона мусить бути однаковою. Читати з неї вдавалося. А запис зависав назавжди – на обох машинах одразу. Нічого не могло запуститися, бо щоб запустити будь-що, спершу треба записати файл блокування.
Логи точно описували, що відбувається, і мовчали про те, чому. Один вузол повідомив про 17 оновлень у черзі й оголосив пару синхронізованою. Другий сказав, що чекає на оновлення, – і більше не сказав нічого й ніколи. Жоден не спробував ще раз.
Тоді я ще не знав, але спричинив це сам, двадцятьма хвилинами раніше: переніс сервер на стіл і ввімкнув його в мережу в іншому місці. Саме розслідування першої несправності й породило другу. У хронології подій цього не було видно, бо переставити машину – це ніби й не змінити її.
Три години правильних відповідей
Я розбирався з цим разом із Claude Code. Частину гіпотез висунув я, частину – він; він запускав команди, читав вивід і тиснув на результати, а не хапався за перший заспокійливий. Години за три ми перевірили й зняли з підозри ось що:
- Склад кластера: правильний на обох вузлах, рівно два учасники, жодних застарілих записів.
- Кворум: є, три голоси з трьох.
- З’єднання між ними: працює в обидва боки, нуль помилок передачі, нуль повторних спроб.
- Розмір пакета: пінг повного розміру пройшов з’єднанням і повернувся.
- Годинники: синхронізовані з точністю до пів секунди.
- Файл налаштувань кластера: однаковий до байта на обох вузлах, та сама контрольна сума.
- База даних під спільною файловою системою: перевірку цілісності пройшла.
- Та сама база, видалена й зібрана з нуля, щоб синхронізуватися наново. Зламалася точнісінько так само.
Кожна відповідь була правильною
Кожну з цих перевірок було розумно запустити, і кожна повернулася чистою. Я перезапускав служби по одній, потім усі разом, потім перезавантажив обидві машини, а тоді вдруге потягнув залізо через усю квартиру, щоб підключити екран до другого вузла.
Щоб помітити абсурд, мені знадобився майже весь ранок, а щоб розв’язати – жодної хвилини: кластер був цілком здоровий і зовсім непридатний. Два учасники, кворум є, годинники зведені, налаштування однакові, база ціла, з’єднання працює, і в кожній колонці помилок – нуль. Система, яка в усьому згодна сама з собою, крім одного: чи може вона записати файл.
Потім мені треба було в місто до кінця дня, і поки мене не було, усе так і стояло зламане.
Чужа гілка на форумі
Повернувся я до цього ввечері, без жодної ідеї, і зробив те, що мав зробити ще в першу годину: пішов читати, що знайшли інші люди з таким самим симптомом. Одна гілка на форумі описувала мій збій до дрібниць, тільки на чужому кластері. У них винним був поганий кабель. Трафік кабель пропускав, але мережева карта через нього скидала швидкість із гігабіта до десятої частини.
Дві команди, по одній на кожному вузлі. Здоровий показав
1000Mb/s. Той, що на моєму столі, – 100Mb/s.
Коли я вранці переносив машину, то ввімкнув її в найближчий вільний порт – той самий, про який я вже знав, що він поганий. Він погоджується тільки на 100 Мбіт і ні на що більше, тому зазвичай у ньому сидить настільний IP-телефон – пристрій, якому треба десь тисячну частку того, що йому дають, і який жодного разу не поскаржився. Я розв’язав проблему з монітором, власними руками змайстрував набагато гіршу, а тоді три години допитувався, чому зламався софт.
Якщо ви тут, бо ваш кластер каже, що чекає оновлень від вузла, який божиться, що вже їх надіслав, а кожна перевірка, що спадає на думку, зелена, – запустіть ethtool на обох кінцях і порівняйте швидкість. Якщо один із них каже 100Mb/s, кидайте читати й ідіть переставляти кабель.
Збій, що проходить кожен тест, який вам спав на думку
Мертве з’єднання падає гучно, бо чи воно доступне – перше, що спадає на думку перевірити, і єдине питання, на якому мертве з’єднання може завалитися. А з’єднання, яке просто повільне, це питання проходить – і всі наступні з тієї самої родини теж.
- пінг не проходить, тож уже перша перевірка називає проблему
- вузол випадає з кластера, і обидва логи кажуть чому
- інтерфейс сам повідомляє, що лежить
- будь-яка система моніторингу, яку будь-коли написали, помітить це з першого ж опитування
- за п’ять хвилин кабель уже у вас у руках
- пінг проходить, тож усі перевірки доступності зелені
- кластер збирається, кворум тримається, обидва вузли згодні, що з ними все гаразд
- інтерфейс показує, що працює, з нулем помилок: він домовився про допустиму швидкість і чесно її тримає
- дрібні службові повідомлення ходять цілий день; а пакет, який справді важить, так і не доходить
- ніщо в цьому збої не вказує на мережу, тож ви налагоджуєте софт
З погляду мережевої карти нічого не сталося, і в цьому вся пастка. Мої два вузли цілий день бездоганно перекидалися дрібними службовими повідомленнями: привіт, я тут, ти лідер, надішли мені оновлення. А самі оновлення так і не прийшли. Той самий дріт, різні розміри пакетів, протилежні результати.
У такій конкретній формі це рідкість, а в загальній – трапляється на кожному кроці: компонент, що почав здавати, але ще не відмовив, пройде тести, написані на відмову. Ці тести мали рацію щодо того збою, який мали ловити. Вони закладають уявлення про те, як річ ламається, а річ знайшла інший спосіб.
[макс]Порт не полагодиш. Приїздив мережевий інженер, спробував і залишив усе, як було: один порт на домашньому роутері може почати здавати й так і лишитися, а через один порт ніхто не міняє робочий роутер. Наклеюєш на нього папірець і обходиш стороною.
Рамку креслить той, хто ставить задачу
Того ранку Claude Code провів справжнє розслідування. Він пропонував перевірки, до яких я сам у такому порядку не додумався б, уважно читав вивід і не погоджувався вважати зелений результат відповіддю. Не міг він одного – запідозрити роутер.
Потрібне число весь час було за одну команду від нас. ethtool сказав би 100Mb/s будь-коли після десятої ранку. Але в рамці були кластер, спільна файлова система, два вузли, їхні логи і питання, яке я набрав, – і роутера там не було ніде. Запускаєш ті команди, до яких тебе підводить твоя рамка.
І ця рамка була моя. Я збудував її, коли описував задачу: два вузли, кластер, файлова система, яка не хоче писати. Усередині неї ми обидва шукали ретельно й правильно. А причина була зовні: на один фізичний рівень нижче й на один пристрій убік, у залізяці, яка не пише жодних логів і не належить до жодної системи, на яку ми дивилися.
Тож це мислити поза рамками в найпростішому, буквальному сенсі, а не в дусі мотиваційного плаката. Рамка – реальний предмет. У неї є край, цей край накреслив той, хто ставив задачу, і накреслив тоді, коли знав найменше, – ще до того, як стало відомо, де несправність. Міркування всередині можуть бути бездоганними й однаково нікуди не привести, бо відповіді там просто немає.
І ця частина лишається правдою, навіть якщо прибрати з історії ШІ. Колега, якому ви віддали баг, підрядник, якому ви дали ТЗ, команда, якій ви поставили ціль на квартал, і модель, якій ви написали промпт, – усі в однаковому становищі: вони шукатимуть у тому світі, який ви їм дали, і шукатимуть краще, ніж ви чекаєте. Ніхто всередині рамки не може помітити її краю. Це лишається роботою того, хто її накреслив. Це не задача на міркування, і жодне більше вікно контексту її не розв’яже, бо відсутньої деталі ніхто ніде не записав – її просто нема звідки прочитати.
Тепер я роблю дві речі інакше. Коли зависає розподілена система, я спершу перевіряю фізичний рівень і лише потім торкаюся служб: це одна команда, а я витратив цілий ранок, доводячи невинуватість купи ні в чому не винного софту. І на початку будь-якого розслідування я тепер уголос, прямо в сесію, відповідаю на одне питання: що змінилося фізично або поза цією системою відтоді, як вона востаннє працювала. Факт про той порт я знав від самого початку. Але він був у мене в голові, а факт у вашій голові – ще не факт у розслідуванні.
Порт і досі там, і досі здає, і досі тримає телефон. Чого в мене немає – то це способу дізнатися, коли таке станеться знову: з’єднання, що перейшло на 100 Мбіт, на кожній моїй панелі моніторингу виглядає точнісінько як здорове, і перевірку, яка б це впіймала, я ще не написав.