NTP и синхронизация времени на игровом сервере
Если вы уже проверяли timedatectl status и видели заветное synchronized: yes, велик соблазн закрыть тему навсегда — часы идут, и ладно. Но NTP не бинарный переключатель "работает / не работает": у синхронизации есть погрешность, и есть сценарии, где именно эта погрешность в сотни миллисекунд рвёт корреляцию логов между узлами, сдвигает окно бэкапа или роняет подписанный запрос к внешнему API с невнятной ошибкой авторизации. Разберём NTP не с позиции "включено/выключено", а с позиции того, что реально ломается, когда точность важна не абстрактно, а конкретно — для конкретной интеграции.
Содержание
Как на самом деле работает NTP
NTP (Network Time Protocol) — не просто "спросить время у сервера и выставить его". Клиент отправляет запрос, замеряет время до ответа, и на основе задержки round-trip вычисляет не только расхождение часов (offset), но и оценивает погрешность этого расхождения. Источники времени организованы иерархией страт (stratum): stratum 0 — сами эталонные часы (атомные, GPS-приёмники), stratum 1 — серверы, напрямую к ним подключённые, stratum 2 — серверы, синхронизирующиеся со stratum 1, и так далее. Обычный VPS обычно синхронизируется с публичным пулом на уровне stratum 2-3, и этого более чем достаточно для игрового сервера — гнаться за stratum 1 нет смысла.
Ключевой момент: NTP-клиент не переставляет часы рывком при каждой синхронизации. Если расхождение небольшое (обычно до ~128 мс), демон плавно "подтягивает" системные часы, слегка ускоряя или замедляя их ход (slewing) — это специально сделано, чтобы не было скачков времени назад, которые ломают приложения, полагающиеся на монотонность времени. Если же расхождение большое (например, после долгого простоя виртуалки или восстановления из старого снапшота), демон может пойти на резкий скачок (step) — именно поэтому после разворачивания сервера из образа стоит форсировать синхронизацию, как описано в статье про базовую синхронизацию времени, а не полагаться на плавную подстройку.
Погрешность в несколько десятков-сотен миллисекунд — это норма для NTP через интернет, а не брак. Для cron и человеческого расписания это ничто. Но дальше в статье разберём три сценария, где именно эти миллисекунды и секунды становятся проблемой.
systemd-timesyncd против chrony: когда нужен апгрейд
systemd-timesyncd, который стоит по умолчанию на Ubuntu/Debian — простой SNTP-клиент. Он умеет главное: получать время из пула и плавно подгонять часы. Для одиночного игрового сервера с типовой нагрузкой этого действительно хватает в 9 случаях из 10.
chrony стоит выбирать осознанно, а не "на всякий случай", в трёх ситуациях:
- Нестабильный канал. VPS с периодическими просадками сети или у хостера с не самой стабильной магистралью — chrony быстрее восстанавливает точную синхронизацию после разрыва связи и лучше фильтрует "шумные" измерения задержки.
- Несколько игровых серверов на одной машине. Если у вас несколько инстансов на одном хосте и вы хотите поднять локальный NTP-сервер для внутренней сети (например, в связке с прокси или BungeeCord/Velocity), chrony умеет работать не только клиентом, но и полноценным сервером для других машин в вашей подсети.
- Нужна детальная диагностика.
chronyc trackingиchronyc sources -vдают куда больше данных для разбора инцидента, чемtimedatectl, — offset в секундах с точностью до микросекунд, оценку дрейфа частоты часов (skew), root delay до источника.
Установка и базовая настройка:
sudo apt update && sudo apt install chrony
sudo systemctl enable --now chronyd
Конфиг лежит в /etc/chrony/chrony.conf. Для игрового сервера имеет смысл проверить и при необходимости расширить список источников — по умолчанию там обычно пул вашего дистрибутива:
pool 2.pool.ntp.org iburst
pool 2.ru.pool.ntp.org iburst maxsources 2
Флаг iburst заставляет chrony при старте отправить несколько запросов подряд вместо одного — это ускоряет первичную синхронизацию с типичных 5-10 минут до нескольких секунд, что особенно важно на серверах, которые часто перезагружаются или живут в контейнерах с коротким временем жизни.
Посмотреть текущее состояние синхронизации детальнее, чем даёт timedatectl:
chronyc tracking
Строка System time в выводе покажет реальное расхождение (например, 0.000342 seconds fast of NTP time) — если это значение регулярно скачет за пределы десятков миллисекунд, стоит смотреть на сеть, а не на конфиг демона.
Нужен игровой сервер?
MAATRIX GAMES — 45+ игр, деплой за минуты, DDoS-защита, автобэкапы. Тарифы от 399 ₽/мес.
Создать серверРассинхрон и корреляция логов между узлами
Базовая проблема — "метки времени не совпадают" — уже разбиралась в статье про синхронизацию времени в общем. Здесь пойдём глубже: что происходит, когда у вас не один сервер, а несколько компонентов, разбросанных по разным машинам.
Типовая современная схема игрового хостинга — это не один процесс на одной машине. Это игровой сервер плюс, возможно, база данных для плагинов, плюс централизованный сбор логов через что-то вроде Loki, плюс стек мониторинга типа Prometheus/Grafana или Zabbix. Каждый из этих компонентов пишет собственные таймстампы, и если часы на разных узлах разошлись даже на пару секунд — при разборе инцидента вы получаете логи, которые формально идут не в том порядке, в котором события реально происходили.
Это особенно болезненно для систем вроде Loki, где запросы часто фильтруются именно по временному окну: если лог-агент на игровом сервере отстаёт на 90 секунд от таймстампа, который выставляет сама Loki при приёме, событие может "выпасть" за границы окна поиска, и вы физически не найдёте строку, хотя она есть в хранилище — просто ищете не в том диапазоне.
Практический вывод: если у вас распределённая инфраструктура из нескольких серверов, стоит явно проверить синхронизацию на каждом узле, а не только на игровом сервере. Один нюанс, о котором часто забывают, — контейнеры Docker обычно используют системное время хост-машины напрямую (через /etc/localtime и системные часы ядра), поэтому если вы гоняете стек через Docker Compose, синхронизировать нужно именно хост, а не пытаться настраивать NTP внутри каждого контейнера отдельно — это не сработает так, как ожидается, и просто добавит лишний слой сложности.
Окна бэкапов и коллизии расписания
С автоматическими бэкапами по расписанию рассинхрон времени создаёт не только банальный сдвиг "сработало не в ту минуту", но и менее очевидную проблему — коллизию окон между несколькими задачами cron, которые специально разнесены во времени, чтобы не создавать пиковую нагрузку одновременно.
Классическая схема: рестарт сервера в 5:00, снятие бэкапа мира в 5:05, выгрузка в облако через rclone в 5:15. Если часы плавают на десятки секунд из-за нестабильной синхронизации, при неудачном стечении обстоятельств задача бэкапа может стартовать до того, как рестарт полностью завершил запись файла мира на диск — и вы получите бэкап недописанного состояния. На практике это редкий сценарий, но именно "редкий и трудновоспроизводимый" — то, что убивает часы отладки, когда бэкап изредка оказывается битым без видимой причины в логах бэкап-скрипта.
Второй момент — ротация бэкапов по дате. Скрипты, которые удаляют бэкапы старше N дней, обычно сравнивают дату создания файла с текущей датой системы. Если часы резко скакнули (например, после восстановления снапшота с "замороженным" временем), логика ротации может либо разом удалить пачку свежих бэкапов, посчитав их устаревшими, либо наоборот — не удалить старые, раздув хранилище. Проверка timedatectl set-ntp false && timedatectl set-ntp true для форсированной пересинхронизации сразу после восстановления из снапшота — не паранойя, а разумная привычка именно из-за этого риска.
Подписанные запросы к внешним API
Вот сценарий, который редко всплывает в общих статьях про время, но регулярно ломает интеграции: множество внешних API защищают запросы не только ключом, но и временной меткой в самой подписи запроса — чтобы перехваченный запрос нельзя было воспроизвести позже (replay-атака). Это касается платёжных API, части Steam Web API, вебхуков от панелей управления и любых интеграций через API хостинга.
Механизм обычно такой: клиент формирует HMAC-подпись запроса, куда входит текущий unix-таймстамп. Сервер API проверяет подпись и одновременно смотрит, не "протух" ли таймстамп — обычно допустимое окно от 30 секунд до 5 минут в зависимости от сервиса. Если часы разошлись сильнее этого окна, запрос отклоняется — причём текст ошибки почти никогда прямо не говорит "у вас часы врут", это обычно generic 401 Unauthorized или Invalid signature, что уводит разбор в сторону конфигурации ключа, хотя он абсолютно корректный.
Та же логика касается вебхук-уведомлений о критичных событиях — если вы сами подписываете исходящие вебхуки (например, чтобы принимающая сторона могла проверить, что запрос действительно от вашего сервера), а часы разошлись, принимающая сторона может отбраковывать ваши уведомления как невалидные или подозрительно старые.
Практический чек-лист при загадочных ошибках авторизации с внешним API, который выглядит настроенным правильно:
# Сверить системное время с эталонным источником напрямую
curl -sI https://www.google.com | grep -i '^date:'
date -u
Если разница между выводом date -u и заголовком Date от внешнего сервиса больше пары секунд — это первый подозреваемый, ещё до того как копаться в самом API-клиенте, ключах и логике подписи.
Мониторинг синхронизации как часть общего мониторинга
Раз рассинхрон обычно проявляется не постоянно, а эпизодически (после сетевых просадок, миграций, рестартов виртуалки), имеет смысл не проверять его руками время от времени, а включить в общий стек мониторинга, если он у вас уже есть — через Zabbix или Prometheus/Grafana.
Для Prometheus есть готовый экспортер node_exporter, отдающий метрики node_timex_sync_status и node_timex_offset_seconds — можно построить алерт на превышение допустимого offset:
- alert: ClockDrift
expr: abs(node_timex_offset_seconds) > 0.5
for: 5m
labels:
severity: warning
annotations:
summary: "Часы сервера разошлись более чем на 500 мс"
Для Zabbix похожая метрика собирается через встроенный агент или простым UserParameter, дёргающим chronyc tracking и парсящим значение offset. Порог в полсекунды — ориентир, а не универсальная константа: если у вас есть интеграции с жёстким окном подписи в 30 секунд, есть смысл насторожиться заметно раньше, чем часы уйдут в проблемную зону.
Локальный NTP-сервер для нескольких машин в сети
Если у вас не один сервер, а небольшая частная сеть — например, несколько игровых нод плюс отдельная машина под прокси и базу данных, — синхронизировать каждую машину отдельно с публичным пулом не обязательно оптимально: лишний внешний трафик и отдельная зависимость от доступности внешних NTP-серверов на каждой машине.
Chrony умеет работать локальным сервером времени для остальных машин подсети. На одной машине (обычно той, что и так синхронизирована с публичным пулом) добавляете в chrony.conf:
allow 10.0.0.0/24
local stratum 10
Директива allow открывает доступ для указанной подсети, local stratum 10 разрешает демону раздавать время даже если временно потерян внешний источник (полезно на случай кратковременных проблем с интернет-каналом у всей сети). На остальных машинах в chrony.conf прописываете адрес этой машины как источник:
server 10.0.0.1 iburst
Такая схема не обязательна для типового одиночного сервера, но оправдана, если вы администрируете небольшой кластер и хотите единую точку контроля времени внутри своей инфраструктуры, а не полагаться на то, что каждая нода по отдельности одинаково хорошо достучится до внешнего пула.
Нужен игровой сервер?
MAATRIX GAMES — 45+ игр, деплой за минуты, DDoS-защита, автобэкапы. Тарифы от 399 ₽/мес.
Создать серверЧастые вопросы
NTP уже настроен по умолчанию — зачем вообще ставить chrony?
Для одиночного сервера без строгих интеграций — не обязательно, systemd-timesyncd справится. Chrony имеет смысл при нестабильной сети, нескольких серверах на одной машине или когда нужна детальная диагностика через chronyc.
Как быстро понять, из-за времени ли отваливается конкретный запрос к API?
Сравните вывод date -u с HTTP-заголовком Date от целевого сервиса (или любого стабильного внешнего хоста) — если расхождение больше пары секунд, это первый кандидат на причину, ещё до разбора ключей и подписи.
Может ли резкий скачок времени (step) сломать что-то в работающем игровом процессе?
Теоретически да, если плагин держит таймеры на абсолютном системном времени, а не монотонных часах — скачок назад иногда путает такую логику. На практике современные движки чаще используют монотонные таймеры для игровых тиков, но после ручной коррекции времени стоит понаблюдать за поведением.
Нужен ли отдельный мониторинг времени, если сервер один и без внешних интеграций?
Не критично, но дешёвая страховка — особенно если планируете добавлять API-интеграции или расширяться до нескольких машин позже.