Синхронизация времени: chrony и NTP-сервер в локальной сети
Время на серверах кажется мелочью, пока оно не ломается. Потом отваливается Kerberos (допуск по умолчанию пять минут), сертификаты TLS выглядят «ещё не действительными», кластерные базы расходятся в кворуме, а логи с разных хостов невозможно склеить в одну хронологию при разборе инцидента. Лечится всё это одним сервисом, который надо настроить один раз и аккуратно. На современных дистрибутивах для этого используют chrony: он быстрее приходит в синхронное состояние, нормально работает на виртуальных машинах и ноутбуках, где связь и часы нестабильны, и умеет работать как сервер для остальной сети.
Наша цель: внутри лаборатории itpages.lab.local завести собственный NTP-сервер на хосте srv-linux01 (192.168.10.10), который берёт время из надёжных внешних источников, а все остальные хосты (srv-dns01, srv-dhcp01 и прочие) синхронизируются уже с него. Пограничный маршрутизатор rtr-edge01 при этом остаётся только шлюзом и время не раздаёт. Такая схема снижает нагрузку на публичные пулы, даёт единый источник времени и продолжает работать при потере интернета.
Шаг 1. Установка chrony и проверка текущего состояния
Сначала посмотрим, чем время управляется сейчас. На Ubuntu часто работает systemd-timesyncd, и он конфликтует с chrony: пакет chrony сам его отключает, но проверить стоит.
timedatectl
systemctl is-active systemd-timesyncd chronyd chrony
sudo apt install chrony
# RHEL / Rocky: sudo dnf install chrony
В выводе timedatectl обрати внимание на строки «System clock synchronized» и «NTP service». Если часовой пояс неверный, исправь его сразу:
sudo timedatectl set-timezone Asia/Almaty
timedatectl | grep -E 'Time zone|synchronized'
Часовой пояс влияет только на отображение, внутри система хранит UTC. Но путаница с поясом это распространённая причина разговоров «время на сервере неправильное», когда на деле неправильно только отображение.
Имя сервиса различается: в Debian и Ubuntu это chrony, в семействе RHEL это chronyd. Конфигурационный файл лежит в /etc/chrony/chrony.conf (Debian) или /etc/chrony.conf (RHEL).
Шаг 2. Настройка сервера srv-linux01
Сервер берёт время из нескольких внешних источников и раздаёт его локальной сети. Несколько источников нужны, чтобы chrony мог отсеять неверный: при одном источнике он вынужден верить ему слепо, при двух не может понять, кто прав, а три и больше позволяют выбрать большинство.
# /etc/chrony/chrony.conf на srv-linux01
pool 0.pool.ntp.org iburst maxsources 2
pool 1.pool.ntp.org iburst maxsources 2
pool 2.pool.ntp.org iburst maxsources 2
driftfile /var/lib/chrony/chrony.drift
makestep 1.0 3
rtcsync
logdir /var/log/chrony
log tracking measurements statistics
# раздаём время только локальной сети
allow 192.168.10.0/24
allow 192.168.20.0/24
# если интернет пропал, продолжаем раздавать время сети
local stratum 10
# ограничение запросов от клиентов
ratelimit interval 3 burst 8
Разберём директивы. iburst ускоряет первую синхронизацию: вместо одного запроса в минуту отправляется пачка из четырёх. makestep 1.0 3 разрешает при старте резко перевести часы, если расхождение больше секунды, но только в первых трёх обновлениях: дальше время будет подводиться плавно, без скачков, которые ломают приложения. rtcsync периодически записывает системное время в аппаратные часы. Директива allow определяет, каким клиентам сервер отвечает: без неё chrony работает только как клиент и порт 123 наружу не отвечает.
Строка local stratum 10 это страховка. Если внешние источники пропали, сервер продолжит раздавать своё время со стратумом 10, и локальные часы останутся согласованными между собой. Это важнее абсолютной точности: пусть вся сеть уйдёт на пару секунд в сторону вместе, но не разойдётся между собой. Значение 10 выбрано высоким намеренно, чтобы любой настоящий источник был предпочтительнее.
Применяем и открываем порт:
sudo systemctl restart chrony
sudo systemctl enable chrony
sudo ufw allow from 192.168.10.0/24 to any port 123 proto udp
# firewalld: sudo firewall-cmd --permanent --add-service=ntp && sudo firewall-cmd --reload
Шаг 3. Проверка серверной стороны
У chrony есть командный клиент chronyc, который показывает всё нужное. Начнём с источников:
chronyc sources -v
chronyc sourcestats
chronyc tracking
В выводе sources перед источником стоит символ состояния. Звёздочка * означает, что именно с этим источником сейчас идёт синхронизация, плюс + это годный источник-кандидат, а ? означает, что связь не установлена или источник недоступен. Колонка Reach показывает в восьмеричном виде историю последних восьми запросов: значение 377 это все успешные, а 0 это полное отсутствие ответов.
Команда tracking даёт ключевые числа:
Reference ID : A29FC801 (time.example.net)
Stratum : 3
System time : 0.000012345 seconds fast of NTP time
Last offset : +0.000008123 seconds
RMS offset : 0.000021543 seconds
Frequency : 12.345 ppm slow
Leap status : Normal
Здесь самое важное это System time (текущее отклонение, в здоровой системе порядка миллисекунд или меньше) и Leap status: Normal. Если вместо этого написано «Not synchronised», сервер ещё не нашёл источник или его не пускает файрвол на исходящий UDP 123.
Сколько клиентов уже подключено к серверу, показывает отдельная команда (она выполняется только от root):
sudo chronyc clients
sudo chronyc serverstats
Шаг 4. Настройка клиентов
Клиентская конфигурация намного короче. Вместо пула публичных серверов указываем наш внутренний. Лучше завести для него постоянное DNS-имя, например ntp.itpages.lab.local как CNAME на srv-linux01, чтобы при переезде сервера не править конфиги везде.
# /etc/chrony/chrony.conf на srv-dns01, srv-dhcp01 и других
server ntp.itpages.lab.local iburst prefer
server 192.168.10.10 iburst
driftfile /var/lib/chrony/chrony.drift
makestep 1.0 3
rtcsync
logdir /var/log/chrony
Повторим применение и проверим:
sudo systemctl restart chrony
chronyc sources
chronyc tracking | grep -E 'Reference|Stratum|System time'
Клиент должен показать стратум на единицу больше сервера: если у srv-linux01 стратум 3, то у клиентов будет 4. Если стратум 16 или статус «Not synchronised», сервер недоступен, и нужно проверять файрвол и директиву allow.
Принудительно подвести время, не дожидаясь плавной коррекции, можно командой sudo chronyc makestep. Применять её стоит осознанно: резкий скачок времени назад или вперёд на работающем сервере баз данных может вызвать неприятности.
Для DHCP-клиентов и виртуальных машин есть нюансы. Адрес NTP-сервера можно раздавать через DHCP (опция 42), и chrony на дистрибутивах с соответствующими скриптами её подхватит. Мы вернёмся к этому в статье про Kea. На виртуальных машинах гипервизор иногда сам подводит время гостя, и это конфликтует с chrony: отключи синхронизацию времени гостя с хостом в настройках гипервизора, либо используй драйвер PTP (refclock PHC /dev/ptp0), который даёт точность лучше, чем сетевой NTP.
Шаг 5. Мониторинг и безопасность
Сервис, который «настроили и забыли», тоже ломается: меняется сеть, закрывается порт, умирает источник. Поэтому добавь простую проверку, которую можно подключить к любой системе мониторинга:
#!/bin/sh
# /usr/local/bin/check-chrony.sh
OFFSET=$(chronyc tracking | awk '/System time/ {print $4}')
STRATUM=$(chronyc tracking | awk '/Stratum/ {print $3}')
echo "offset=${OFFSET}s stratum=${STRATUM}"
awk -v o="$OFFSET" 'BEGIN { exit (o > 0.1) ? 2 : 0 }'
Скрипт возвращает код 2, когда отклонение превышает 100 мс, и его легко повесить на любой планировщик или мониторинг. Дополнительно имеет смысл оповещать, когда стратум достигает 16 или Reach всех источников падает в ноль.
С точки зрения безопасности держи в голове два момента. Во-первых, ограничивай allow нужными сетями и не открывай UDP 123 в интернет: открытые NTP-серверы использовали для усиления DDoS-атак. Во-вторых, для критичных сред используй аутентификацию NTP (NTS) в клиентах, где источник её поддерживает: в конфиге достаточно добавить опцию nts к строке server, и chrony сам договорится о ключах.
server time.cloudflare.com iburst nts
ntsdumpdir /var/lib/chrony
И последнее: если в сети есть оборудование, которое умеет только древний протокол, оно подключается к тому же серверу, ведь chrony отвечает на обычные запросы NTP версий 3 и 4. Так одна точка времени обслуживает и серверы, и принтеры, и коммутаторы.
Шаг 6. Особые случаи: виртуальные машины, высокая точность и сбои
Обычная настройка подходит большинству серверов, но есть ситуации, где поведение отличается. Первая это виртуальные машины, которые ставились на паузу или переносились между хостами: после возобновления часы гостя отстают на минуты или часы. Chrony при старте сам выправит время благодаря makestep, но если пауза случилась на работающей системе, дай команду принудительно:
sudo chronyc makestep
sudo chronyc waitsync 10 0.1
chronyc tracking | grep 'System time'
Команда waitsync ждёт, пока синхронизация достигнет нужной точности, и годится для скриптов запуска сервисов, которые чувствительны ко времени. Для служб, которым критично, чтобы chrony успел синхронизироваться до их старта, добавь в юнит зависимость от chrony-wait.service.
Вторая ситуация это лабораторная сеть без интернета вообще. Тогда внутренний сервер берёт время у собственных часов или у GPS-приёмника и раздаёт его остальным. Для этого в конфиге остаётся только директива local stratum 8, а при наличии GPS-приёмника с выходом PPS добавляется источник:
refclock SHM 0 refid GPS precision 1e-1 offset 0.0 delay 0.2
refclock PPS /dev/pps0 refid PPS lock GPS
Третья ситуация это проверка расхождения между хостами без остановки сервисов. Быстро собрать смещение всех хостов можно простым циклом:
for h in srv-dns01 srv-dhcp01 srv-linux01; do
echo -n "$h: "
ssh "$h.itpages.lab.local" "chronyc tracking | awk '/System time/ {print \$4, \$5}'"
done
Если один из хостов выбивается, скорее всего, на нём стоит иной синхронизатор или в конфигурации указан не тот сервер. Обрати внимание и на высокий показатель Skew в chronyc sourcestats: он говорит о нестабильности источника или самих локальных часов, что типично для перегруженной виртуальной машины.
Частые ошибки и как их избежать
- Закрытый UDP 123 или отсутствие директивы allow. Клиенты показывают «Not synchronised», а на сервере всё выглядит нормально. Проверь, что
allowперечисляет нужную подсеть, порт 123/udp открыт во входящих правилах сервера, а на самом сервере открыт исходящий UDP 123 для связи с публичным пулом. Диагностика:sudo tcpdump -i eth0 -nn udp port 123. - Одновременная работа нескольких синхронизаторов. Chrony вместе с systemd-timesyncd, ntpd или службой синхронизации гостя гипервизора начинает «бороться» за часы, и время дёргается. Оставь ровно одну службу:
systemctl disable --now systemd-timesyncdи проверь на виртуалках, что интеграция времени с гипервизором выключена. - Один внешний источник и слепая вера ему. При единственном источнике chrony не может понять, что тот ошибается. Используй минимум три источника или пул, а для внутреннего сервера оставь
local stratum, но никогда не давай ему слишком низкий стратум: иначе при сбое интернета клиенты предпочтут локальные неточные часы настоящим.
Что дальше
Администрирование Linux — это набор связанных навыков, вот соседние темы серии: