Домашний сервер, который зависал только в простое
Задача
Пет-проектов накопилось столько, что каждый новый упирался в один и тот же вопрос: где это будет крутиться. Держать всё на маке нельзя – мак уходит со мной и закрывается крышкой. Разносить по облачным VPS – дорого на десяток мелких сервисов, и половина задач вообще не про интернет: распознавание речи с рабочих встреч, домашний DNS, слушатель весов по Bluetooth. Такое место должно стоять дома.
Требования вышли простые: коробка размером с книгу, работающая круглосуточно и тихо, с запасом по памяти под локальные модели и без вентилятора величиной с турбину.
Взял Beelink SER6 PRO: Ryzen 7 6800H, 32 ГБ памяти, NVMe на 512 ГБ. Мини-ПК вместо классического самосбора – потому что мне нужен был работающий сервер, а не хобби по подбору корпуса.
Забегая вперёд: хобби всё равно случилось, просто не то, которое я планировал.
Первый симптом: Windows намертво зависает
Машина приехала с Windows, и первые дни я даже не успел ничего на неё поставить – она вставала колом. Не синий экран, не перезагрузка, а именно намертво: курсор не двигается, по сети не отвечает, помогает только кнопка питания.
Дальше был честный набор проверок, который в итоге не дал почти ничего – и это тоже результат.
Память. memtest, 17 полных прогонов. Ноль ошибок.
Диск. SMART чистый.
Температуры. stress-ng двадцать минут: пик 90 °C, без троттлинга и без единой ошибки. То есть под полной нагрузкой машина не просто выживала – ей было хорошо.
Периферия. А вот здесь нашлось: часть «зависаний» оказалась глючным кабелем клавиатуры. Система была жива, просто перестала слушать. Я честно вычеркнул эти эпизоды из статистики и на какое-то время поверил, что проблема решена.
Она не была решена. Заодно выяснилось, что Wi-Fi-модуль в машине мёртв – не «плохо ловит», а не определяется вообще. Модуль я физически извлёк, сервер посадил на провод, а Bluetooth завёл через USB-адаптер. Тогда это выглядело как отдельная мелкая неприятность. Позже это стало важной строчкой в анамнезе.
Windows я снёс и поставил Ubuntu 26.04 LTS Server. Не потому, что подозревал Windows – мне в любом случае был нужен Linux под Docker. Но втайне я надеялся, что вместе со старой системой уедут и фризы.
Что на нём живёт
Пока машина вела себя прилично, я успел собрать всё, ради чего её покупал. Правило
было одно: никаких сервисов, установленных в систему руками. Всё в контейнерах,
каждый стек – отдельная папка с compose.yml, чтобы переезд на другое железо сводился
к копированию одного каталога.
- Gitea – свой git с приватными репозиториями, туда уехало всё из
~/dev. Плюс Actions-раннер: сборка, тесты и деплой контейнера происходят на той же машине, куда я пушу. - AdGuard Home как DNS всей квартиры – заодно он же раздаёт локальные имена
*.home. - Caddy перед всем этим: новый сервис – это плюс один блок в конфиге, а не возня с портами в закладках.
- Uptime Kuma – мониторинг того, что снаружи: VPS, туннели, NAS.
- Homepage – дашборд со статусами контейнеров и температурой, стартовая страница в браузере.
- Ollama – локальные модели для задач, где текст не должен уезжать наружу.
- Backrest поверх restic – ночной бэкап на Synology, который стоит в той же сети.
Ради честности: Jellyfin и Open WebUI я снёс. Первый – потому что «домашний кинотеатр» оказался красивой идеей, которой я не пользовался, второй – потому что локальная модель в чате медленная, и как собеседник она мне не нужна (как API – вполне). Хороший домашний сервер меряется не числом плиток на дашборде.
Фризы никуда не делись
Дальше начинается собственно история болезни.
Под Linux машина зависла дважды за первый день. Оба раза – в простое. Я тогда не придал этому значения, а зря: это и был диагноз, просто я его ещё не умел читать.
Первое, что я сделал, – завёл чёрный ящик. Раз в минуту скрипт дописывает в
/var/log/health.log строку: температура, load average, память, аптайм. Стоило это
десять минут работы, а дало всё остальное расследование. Когда машина висит намертво,
у вас нет ни логов паники, ни дампа – журнал просто обрывается на полуслове. Единственное,
что у вас есть, – последняя строка, записанная до обрыва. И она говорила одно и то же:
37 °C, load 0.29, тишина.
Второе – аппаратный сторож. Я не рассчитывал им что-то вылечить, мне нужно было, чтобы сервер поднимался сам, пока я на работе. С watchdog вышло три отдельные грабли, и я перечислю их, потому что в интернете они разбросаны по разным форумам:
- Ubuntu блеклистит все watchdog-модули.
sp5100_tcoне грузится сам, пришлось завести юнит, который делает явныйmodprobe. RuntimeWatchdogSecв systemd не работает – к моменту старта менеджера модуля ещё нет. Пришлось ставить обычный демонwatchdogс таймаутом 60 секунд.- Бут-луп. После срабатывания сторожа таймер продолжает тикать и на следующей загрузке. Модуль поднимается с большим heartbeat, боевые 60 секунд выставляет уже демон при открытии устройства – и если перепутать порядок, машина уходит в бесконечную перезагрузку. Один раз я это словил.
И четвёртая, уже про Docker: после жёсткой перезагрузки контейнеры не поднимались
сами, хотя у всех стоял restart: unless-stopped. Оказалось, это ожидаемое поведение,
если контейнер был остановлен не штатно. Лечится юнитом, который через пятнадцать секунд
после старта docker делает docker start всем подряд.
Два ложных диагноза подряд
Первая гипотеза была красивая и подтверждалась интернетом: глубокие C-states. Процессор в простое уходит в энергосберегающие состояния и не возвращается – классика для Ryzen в мини-ПК. Отключил C2 и C3 через sysfs, оставил C1. Настроек C-states в BIOS у этой машины нет вообще, а сам BIOS – последний публичный для 6800H.
Помогло ровно до вечера. Фриз номер три случился уже с отключёнными C-states, снова в простое. Сторож отработал, машина поднялась сама.
Вторая гипотеза была не менее красивая: amdgpu и GFXOFF. На безголовой машине
встроенная графика уходит в энергосбережение, и на этом поколении это известный
убийца. Добавил amdgpu.gfxoff=0 и amdgpu.runpm=0 в параметры ядра.
А ночью случился каскад. Около четырнадцати фризов подряд, с 22:44 до 05:05, интервалы сжимались от часа до нескольких минут. Сторож честно поднимал машину каждый раз. Последний фриз оставил в pstore kernel oops – единственный за всю историю след в логах, и тот оборванный: журнал потерял 935 сообщений.
А потом наступил день, и машина отработала четырнадцать часов без единого фриза.
Вот здесь я чуть не закончил расследование с неверным ответом. Четырнадцать часов
стабильности после применения фикса – это же и есть подтверждение? Я почти записал
amdgpu в причину.
Спасли те самые ежеминутные строчки. Я посмотрел, чем эти четырнадцать часов отличались от предыдущей ночи, и отличались они не параметром ядра. Весь день машину нагружали: гонялся CI, пересобирались образы, я лазил по ней руками. Как только вечером я закончил и сервер ушёл в простой – каскад повторился: шесть фризов за два с половиной часа, интервалы от пяти до шестидесяти пяти минут. Все – при load около нуля и 36 °C.
Итого за двое суток: семнадцать перезагрузок. И две гипотезы, каждая из которых «подтвердилась» и каждая из которых была неверной.
Эксперимент, который закрыл вопрос
Когда стало видно, что фризы строго в простое, а под нагрузкой машина стабильна, гипотеза сформулировалась сама: дело не в конкретном C-state и не в графике, а в самом переходе процессора в состояние покоя. Любом.
Проверяется это одной строчкой – параметром ядра idle=poll, который запрещает
процессору засыпать вообще: вместо сна ядра крутят пустой цикл.
Важно, что это был не фикс, а эксперимент, и я заранее знал, что он должен показать.
Если фризы – от перехода в простой, то с idle=poll машина, оставленная без единой
задачи, обязана простоять сколько угодно. До этого она в таком состоянии валилась
каждые 5–65 минут.
Она простояла четыре часа одиннадцать минут в полном простое без единого разрыва в health.log. Дальше – полтора месяца непрерывного аптайма.
Диагноз: дефект железа, вылезающий на переходах в idle. Память чистая, температуры чистые, под нагрузкой всё идеально – а в анамнезе уже есть мёртвый Wi-Fi-модуль на этой же плате.
Цена лечения честная и неприятная: с idle=poll процессор всегда занят, машина
постоянно держит около 82 °C и слышно вентилятор. Вместо тихой коробки под столом
получился маленький обогреватель. Это не решение, это протез.
Что ответил производитель
Письмо в поддержку Beelink я отправил ещё в разгар каскада, с логами и статистикой. Ответ пришёл такой:
- Wi-Fi-модуль признан гарантийным случаем – отказ железа.
- Фризы – это, по их версии, не железо, а драйвер: «GFXOFF hang – это проблема
драйвера Linux, параметр
amdgpu.gfxoff=0решает её окончательно». Плюс ссылка на свежий BIOS и рекомендация поставить ядро посвежее.
То есть вендор назвал окончательным решением ровно ту гипотезу, которую я уже проверил
и отверг: amdgpu.gfxoff=0 стоял в моих параметрах ядра во время вечернего каскада
из шести фризов. Совет про свежее ядро тоже мимо – он написан для Ubuntu 22.04,
а у меня 26.04, ядро и так новее.
Спорить я не стал и ничего делать не стал тоже – по очень практичной причине: сервер работает. Обновление BIOS лежит в кармане как план на первый следующий фриз, а пока машина держит аптайм, лезть в прошивку ради того, чтобы поспорить с поддержкой, у меня нет ни одной причины.
Вторая болезнь: сервер, который был ни при чём
Через месяц спокойной жизни я обнаружил, что Gitea и Uptime Kuma лежат уже две недели. С 6 по 20 августа. Никто не заметил – я в это время не пушил код, а Kuma сама себя мониторить не может по определению.
Вместе с ними две недели не делалось ни одного бэкапа.
Цепочка оказалась такая. У ночного бэкапа были хуки: перед снапшотом остановить Gitea
и Kuma, чтобы базы не копировались в момент записи, после снапшота – запустить обратно.
Шестого августа посреди снапшота отвалился NAS. Шары примонтированы жёстко (hard),
и restic не получил ошибку – он завис навсегда. Хук «стоп» отработал, хук «старт»
не наступил никогда.
Дальше выяснилось, что NAS отваливался не разово: 1719 событий «server not responding» за 37 суток. Тот же жёсткий монтаж дал и второй эффект – контейнер со сборщиком метрик опрашивал NFS-точки, потоки залипали намертво, рантайм плодил новые, и load average дорос до 6550 на машине, где реально ничего не происходило. Один контейнер держал 6753 потока из 7443 на всей системе.
А причина всего этого – деградировавший блок питания сетевого маршрутизатора, в который воткнуты и NAS, и сервер. Заменили блок питания вечером 20 августа – отвалы прекратились в тот же час и больше не повторялись.
Разбор полётов вышел дороже самой замены:
- Хуки бэкапа переписаны. Больше никаких «останови – запусти»: базы снимаются горячим дампом SQLite прямо внутри работающего контейнера. Класс отказа «зависший хук оставил сервисы выключенными» исчез вместе с остановкой сервисов.
- Дыру закрыл ручной прогон, а
restic check --read-dataпо всем двенадцати снапшотам показал, что обрыв 6 августа репозиторий не повредил. - Мониторинг научился следить за собой – раньше падение Kuma означало тишину, а тишина выглядела как норма.
Что в итоге
Сервер стоит под столом, гудит вентилятором, держит 82 °C и работает без перерывов с середины июля. На нём живут свой git с CI, DNS всей квартиры, локальное распознавание речи, слушатель весов, мониторинг и ночные бэкапы. Ни один из этих сервисов я не хочу отдавать в облако, и ни один из них не требует моего внимания.
У него дефектное железо, известный диагноз, костыль вместо лечения и план на случай, когда костыль перестанет держать. Меня это устраивает больше, чем красивая машина с непонятным поведением.
Выводы
Чёрный ящик заводят до аварии, а не после. Ежеминутная строчка с температурой и нагрузкой – десять минут работы. Именно она дала корреляцию «фризы только в простое», которую я не увидел бы никогда: в момент зависания система не успевает записать о себе вообще ничего.
Гипотеза, которая «подтвердилась» тишиной, не подтвердилась. Дважды подряд я менял параметр, получал часы стабильности и почти записывал причину. Оба раза стабильность объяснялась чем-то другим. Проверять надо не отсутствием симптома, а экспериментом, который делает симптом максимально вероятным: если гипотеза про простой – оставьте машину в простое специально.
Самовосстановление меняет класс проблемы. Сторож не вылечил ни одного фриза, но превратил «сервер лежит, пока я не приду с работы» в «сервер моргнул на минуту». Это разница между аварией и неудобством, и стоит она одного модуля ядра.
Автоматика из двух половинок ломается ровно посередине. «Останови сервис – сделай дело – запусти обратно» падает в единственном месте, где падение необратимо. Если операцию можно сделать без остановки – её нужно делать без остановки.
Симптом почти никогда не там, где причина. «Сдохли Gitea и Kuma» и «сервер под нагрузкой 6000» – это на самом деле «у роутера сел блок питания». Домашняя инфраструктура – это цепочка, и её самое слабое звено обычно не то, на которое вы смотрите.
Тишина – это не «всё хорошо», это отсутствие информации. Две недели без бэкапов и без единого сервиса контроля версий не породили ни одного уведомления. Система, которая сломалась молча, будет молчать столько, сколько вы ей позволите.