Живая миграция прошла успешно, а виртуалка потеряла три минуты времени
Плановая живая миграция виртуалки на другой гипервизор — рутинная операция, которую делают ради обслуживания хоста без простоя сервиса. В нашем случае virsh migrate отчитался об успехе, downtime уложился в обычные доли секунды, ни один алерт не сработал в момент переезда. А спустя пару минут посыпались совсем не связанные с миграцией на первый взгляд ошибки: TLS-хендшейки между внутренними сервисами, странные скачки в графиках Prometheus и cron-задача, которая, кажется, выполнилась не в своё окно. Разбираемся, как виртуалка «потеряла» три минуты при технически безупречной миграции и при чём тут гипервизор, на который её перевезли.
Содержание
Что сломалось
Хост, на котором крутилась одна из виртуалок с внутренними сервисами (mTLS-шлюз, очередь задач и Prometheus-экспортёры), ушёл в плановое обслуживание — обновление ядра. Планировщик выбрал для эвакуации виртуалки соседний узел в том же пуле, virsh migrate --live --persistent отработал штатно, лог миграции между гипервизорами на исходном и целевом хостах не содержал ни одной ошибки или предупреждения.
Первые тревожные сигналы появились через 2-3 минуты после завершения миграции:
- mTLS-шлюз стал массово ронять соединения от одного конкретного бэкенда с ошибкой вида
certificate is not yet valid— как будто клиентские сертификаты пришли из будущего; - очередь задач показала резкий скачок «времени в очереди» для задач, поставленных этой виртуалкой — метрика внезапно ушла в отрицательные значения на графике;
- в системном логе самой виртуалки нашёлся разрыв: последняя запись перед миграцией и первая запись после неё отличались по временной метке не на секунды простоя, а на величину, которая явно не билась с реальной длительностью паузы.
Разница между тем, что показывали часы виртуалки, и реальным временем на остальных нодах кластера составила ровно около трёх минут — виртуалка отставала. Никакого краха, перезагрузки или потери данных не было: сервис продолжал работать, просто с неправильным системным временем.
Что показывали логи и метрики
Первым делом проверили самое очевидное — синхронизацию времени внутри самой виртуалки:
chronyc tracking
Вывод показал большое расхождение (System time в районе трёх минут) и Leap status: Normal — то есть служба синхронизации была жива, видела источники, но ещё не успела скорректировать настолько большой скачок. Посмотрели на источники:
chronyc sources -v
Внешние NTP-серверы отвечали нормально, задержки в пределах обычных значений, никаких Reachability register с нулями — сеть до NTP работала исправно и во время миграции, и после неё.
Дальше подняли journalctl на самой виртуалке за окно миграции:
journalctl --since "2026-08-24 03:10:00" --until "2026-08-24 03:20:00" -o short-iso
Метки времени в логе действительно «прыгнули» ровно в момент, когда QEMU на целевом хосте поднял виртуалку после финальной фазы stop-and-copy. До этого момента время шло линейно и правильно, после — сразу с отставанием.
Проверили источник времени в самой ОС:
cat /sys/devices/system/clocksource/current_clocksource
dmesg | grep -i -E 'clocksource|tsc'
Клоксорс как был kvm-clock, так и остался, никаких сообщений о переключении на hpet или пометке TSC как unstable в логе не нашли — сам механизм отсчёта времени внутри гостя не деградировал и не терял тики во время работы.
Затем посмотрели на конфигурацию домена и лог миграции на обоих гипервизорах:
virsh dumpxml <domain> | grep -A5 '<clock'
virsh domtime <domain>
journalctl -u libvirtd --since "2026-08-24 03:00:00"
Клок в XML был настроен стандартно (offset='utc' с таймером kvmclock), лог libvirtd подтверждал: миграция прошла за ожидаемое время, без повторных итераций pre-copy и без принудительного увеличения допустимого downtime — то есть сама механика живой миграции виртуалок отработала штатно, без долгой заморозки гостя. Отдельно свериться с чек-листом, как проверить, что миграция прошла успешно, тоже не помешало — по этим критериям переезд выглядел безупречно.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверПервые гипотезы — и почему они отпали
Первая версия — нестабильный TSC после переезда на другой физический CPU. У живой миграции между гипервизорами с разными моделями процессоров такое бывает: если на целевом хосте не включено TSC scaling, гость может получить рассинхронизацию счётчика. Отпала быстро: dmesg не показал ни единого предупреждения про TSC, а сам сдвиг был не «дрожащим» (как при нестабильном источнике), а одним ровным шагом ровно в момент возобновления виртуалки.
Вторая версия — потеря доступа к NTP-серверам из-за смены сетевого пути на новом хосте (другой влан, другое правило файрвола на выходе). Тоже не подтвердилась: chronyc sources показывала стабильную связь с источниками времени всё это время, отставание не росло, а было зафиксировано одним разовым скачком.
Третья версия — сглаживание секунды координации (leap smear) на upstream NTP-пуле. Проверили расписание — ближайшая коррекция секунды не планировалась ни в эту неделю, ни в этот месяц, версия отпала сразу.
Четвёртая версия — сама виртуалка была настолько нагружена во время stop-and-copy, что что-то не успело провзаимодействовать корректно (например, гостевой планировщик «проспал» тики). Тоже не сходится: пауза гостя при stop-and-copy на этой миграции была короткой, лог libvirtd это подтверждал, а сдвиг в три минуты на пару порядков больше типичной паузы финальной фазы копирования.
Круг сузился до одного места, которое мы изначально не проверяли, — не самой виртуалки и не процесса миграции, а часов физического хоста, на который она переехала.
Что нашли на самом деле
На целевом гипервизоре проверили его собственное системное время и статус синхронизации:
chronyc tracking
systemctl status chronyd
Служба chronyd на хосте была в состоянии inactive (dead). Хост не синхронизировал время сам с собой уже давно — по логам systemd-юнит не запускался с момента последней переустановки этого узла несколько недель назад: при вводе хоста в кластер сетевые настройки и подключение к пулу гипервизоров прогнали через плейбук, а роль, которая включает и разблокирует chronyd, в тот прогон не попала — узел добавили в инвентарь отдельным, «быстрым» способом в обход полного плейбука, чтобы срочно вернуть мощности в пул.
Без синхронизации аппаратные часы хоста продолжали жить по собственному кварцу материнской платы, который у недорогих серверных плат в проде нередко уходит на несколько секунд в сутки. За несколько недель без коррекции набежало отставание, которое на момент инцидента как раз было в районе трёх минут — сходится с тем, что увидели на виртуалке.
Механика простая: когда QEMU поднимает гостя на целевом хосте после финальной фазы миграции, начальное состояние паравиртуального источника времени (kvmclock) синхронизируется с текущим временем хост-системы в момент запуска. Если время на хосте само по себе отстаёт, гость наследует это отставание при старте — для гипервизора это не ошибка и не сбой, это ожидаемое поведение: он честно взял «своё» текущее время и передал его гостю. Проблема была не в миграции и не в гипервизоре как таковом, а в том, что у источника, из которого гипервизор брал время, эталонное значение было неверным.
Почему разошлись именно на три минуты, а не скорректировались сразу
Логичный вопрос: если внутри виртуалки работал chronyd и видел живые внешние источники, почему он не поправил трёхминутный сдвиг за секунды? Ответ — в поведении по умолчанию. chrony, как и классический ntpd, старается не делать резких скачков системного времени в уже работающей системе: большие расхождения он предпочитает гасить плавной подстройкой хода часов (slew), а не мгновенным шагом (step), потому что резкий скачок времени назад или вперёд может сломать логику приложений, которые полагаются на монотонность времени, файловые метки или блокировки с TTL.
Порог, после которого демон вообще разрешает себе шаг, а не сглаживание, задаётся директивой makestep в /etc/chrony/chrony.conf (базовые принципы такой синхронизации времени сервера полезно держать в голове ещё до того, как что-то сломается):
# makestep <порог в секундах> <количество первых обновлений>
makestep 1.0 3
В конфигурации виртуалок в этом кластере makestep был выставлен консервативно и разрешал резкий шаг только в первые несколько обновлений после старта службы — а после «устаканивания» синхронизации на старте новый резкий скачок в три минуты, случившийся уже во время работы системы, демон обязан был гасить медленным дрейфом хода часов, растягивая коррекцию на заметное время. Именно поэтому расхождение не исчезло само за секунды, а продержалось достаточно долго, чтобы его увидели зависимые сервисы: и mTLS-проверка сертификатов (у которой есть допуск на рассинхрон часов, но не на минуты), и логика очереди задач, считавшая длительность ожидания по системным меткам времени.
Что изменили после инцидента
Разбор дал три направления доработок — на уровне хостов, на уровне гостевых систем и на уровне процесса ввода узлов в кластер.
На хостах:
- вернули
chronydво все узлы пула через полный плейбук провижининга, без «быстрых» веток в обход инвентаризации; - добавили в мониторинг гипервизоров отдельную метрику рассинхрона времени самого хоста, а не только гостевых систем — раньше отслеживали дрейф времени внутри виртуалок, но не собственное время узлов, на которые эти виртуалки едут;
- в чек-лист перед вводом хоста в пул миграции добавили обязательную проверку
chronyc trackingсо статусомNormalи отставанием в пределах разумного порога — без зелёного статуса узел не попадает в список доступных целей планировщика.
На гостевых системах:
- пересмотрели
makestepв шаблоне виртуалок так, чтобы демон синхронизации мог сделать резкий шаг и после старта, если расхождение превышает заметный порог, а не только в первые обновления — это не устраняет причину, но резко сокращает время жизни рассинхрона, если он всё-таки случится; - добавили в mTLS-проверку сертификатов более информативное логирование самого расхождения времени в момент отказа рукопожатия, чтобы при повторном похожем случае причина была видна сразу в логе шлюза, а не требовала отдельного расследования по
chronyc.
На уровне процесса:
- завели алерт по метрике рассинхрона времени хостов через
node_exporter(node_timex_offset_seconds) с достаточно жёстким порогом и коротким интервалом проверки — раньше такой алерт существовал только для внешних, «видимых снаружи» серверов, но не для внутренних узлов кластера виртуализации; заодно свели её в общий дашборд рядом с остальными метриками из статьи про мониторинг гипервизора; - добавили короткий пункт в постмортем-шаблон для миграций: явно фиксировать состояние синхронизации времени целевого хоста как часть чек-листа готовности узла, наравне с проверкой свободной памяти и места на диске.
Отдельно стоит сказать, зачем вообще проверять время хоста, а не только гостя, если раньше проблем не было: пока виртуалки живут на одном и том же хосте годами, дрейф аппаратных часов гасится штатной синхронизацией внутри самой виртуалки и остаётся незаметным. Риск проявляется именно в момент переезда на другой физический узел — и чем реже конкретный хост участвует в живых миграциях, тем выше шанс, что накопленный на нём дрейф никто вовремя не заметил.
Если вы держите нагруженные сервисы с внутренней mTLS-аутентификацией, очередями задач или другой логикой, чувствительной к точному времени, и планируете миграции виртуальных машин между узлами — стоит заранее убедиться, что мониторинг покрывает не только гостевые системы, но и хосты-цели. Разбор подобных инцидентов проще, когда инфраструктура прозрачна и под контролем: если вы арендуете сервер и хотите переложить обслуживание гипервизорного слоя (включая плановые миграции без сюрпризов с часами) на провайдера, это стоит учитывать при выборе тарифа.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Могла ли эта проблема возникнуть без живой миграции, просто со временем?
Да, дрейф аппаратных часов хоста без работающей синхронизации накапливается постоянно — вопрос только в том, когда он станет заметен. Живая миграция делает его видимым сразу, потому что гость одномоментно перенимает текущее время нового хоста.
Почему chronyc tracking внутри виртуалки не показал проблему заранее?
Он показывал корректное состояние ровно потому, что виртуалка синхронизировалась с внешними NTP-серверами и её собственное время было верным до миграции. Источником ошибки был не гость, а хост, на который её перевезли, — эту связь chronyc внутри гостя увидеть не может.
Помогло бы включение NTP непосредственно на самом гипервизоре предотвратить инцидент?
Да, именно включённый и рабочий chronyd на хосте — базовое условие. Проблема была не в отсутствии инструмента, а в том, что служба оказалась выключена из-за пробела в процессе ввода узла в кластер, а не из-за отсутствия конфигурации как таковой.
Стоит ли всегда настраивать makestep на резкий скачок вместо плавной подстройки?
Не универсально: резкий шаг времени назад может сломать приложения, которые полагаются на монотонность часов или короткие TTL. Разумный компромисс — разрешить шаг для заметных расхождений (единицы минут и больше), но не для мелких, где плавная подстройка безопаснее.
Как быстро вручную скорректировать время виртуалки, если ждать нельзя?
Можно принудительно шагнуть временем сразу, не дожидаясь автоматической логики: chronyc makestep. Это разовая ручная команда для экстренного случая, а не замена правильной настройки конфигурации.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →