Cài đặt và cấu hình Chrony trên CentOS Stream 9: Thiết lập NTP Server và Client đồng bộ thời gian chính xác

CentOS tutorial - IT technology blog
CentOS tutorial - IT technology blog

2 giờ sáng, log không khớp, alert loạn xạ

Chuyện xảy ra vào một đêm thứ Sáu. Mình đang ngủ thì nhận được call từ team dev: “Anh ơi, transaction log trên DB server với app server lệch nhau gần 5 phút, không biết lỗi xảy ra lúc nào nữa.”

Mở laptop lên check, quả thật — DB server đang chạy đúng giờ, còn app server thì trễ hơn 4 phút 37 giây. Đủ để khiến SSL certificate handshake fail trên một số service, Kerberos authentication timeout, và cái đau nhất là distributed tracing hoàn toàn vô nghĩa khi timestamp không đồng bộ giữa các node.

Con server đó đang dùng ntpd cũ, pool NTP bị unreachable từ mấy tuần trước, và không có monitoring nào cảnh báo. Từ đó mình chuyển hẳn sang Chrony cho toàn bộ fleet. Công ty mình vẫn còn vài con server chạy CentOS 7, và việc migrate sang AlmaLinux là bài toán mình đã xử lý rồi — nhưng dù là CentOS 7 hay CentOS Stream 9, Chrony đều là giải pháp time sync đúng đắn nhất hiện tại.

Tại sao đồng hồ server bị lệch?

Đồng hồ phần cứng (RTC) của server bị drift theo thời gian — có thể lệch vài giây đến vài phút mỗi ngày. Trong môi trường ảo hóa (KVM, VMware), tình trạng này còn tệ hơn vì VM bị suspend/resume liên tục khiến đồng hồ nhảy loạn.

Các vấn đề thực tế mình từng gặp khi thời gian server lệch:

  • TLS/SSL handshake fail: Certificate bị coi là chưa valid hoặc đã expired vì thời gian server sai
  • Kerberos auth timeout: Kerberos chỉ chấp nhận clock skew tối đa 5 phút, lệch quá là auth fail toàn bộ
  • Database replication báo lag giả: Master-slave timestamp lệch nhau khiến monitor báo replication delay trong khi thực ra không có gì
  • Log forensics vô nghĩa: Khi incident xảy ra, không thể correlate log từ nhiều server nếu timestamp không đồng bộ
  • Cron job chạy sai giờ: Job backup chạy lúc peak hour thay vì 3am như cấu hình

Phân tích nguyên nhân: ntpd cũ và tại sao Chrony thay thế hoàn toàn

CentOS Stream 9 (và RHEL 9) đã bỏ hẳn ntpd, chỉ ship Chrony mặc định. Nhưng nhiều admin vẫn cố cài ntpd từ repo ngoài vì quen tay — đó là sai lầm cần tránh.

Chrony vượt trội hơn ntpd ở hai điểm mấu chốt:

  • Đồng bộ nhanh hơn sau khi boot: Chrony dùng thuật toán riêng, có thể step-adjust đồng hồ ngay lập tức thay vì phải slew dần dần (ntpd mặc định không step nếu lệch dưới 1000 giây)
  • Hoạt động tốt trong môi trường không ổn định: Laptop bật/tắt, VM suspend/resume, kết nối mạng chập chờn — Chrony xử lý tốt hơn nhiều nhờ theo dõi độ lệch theo thời gian thực

Kiểm tra xem service nào đang chạy trước khi làm gì:

systemctl status chronyd ntpd 2>/dev/null
timedatectl status

Nếu cả hai đều enabled thì đang có xung đột — phải tắt ntpd trước.

Các cách giải quyết: Cài đặt và cấu hình Chrony

Bước 1: Cài đặt Chrony (thường đã có sẵn trên CentOS Stream 9)

dnf install -y chrony
systemctl enable --now chronyd
systemctl status chronyd

Nếu server đang chạy ntpd, tắt và loại bỏ trước:

systemctl stop ntpd
systemctl disable ntpd
systemctl mask ntpd  # Ngăn service khác enable lại
dnf remove -y ntp

Bước 2: Cấu hình NTP Client cơ bản

File cấu hình chính nằm tại /etc/chrony.conf. Mặc định CentOS Stream 9 dùng pool 2.centos.pool.ntp.org. Nếu server đặt ở Việt Nam hoặc Nhật, đổi sang pool châu Á để giảm latency:

cat > /etc/chrony.conf << 'EOF'
# NTP pools gần nhất để giảm latency
pool 0.asia.pool.ntp.org iburst
pool 1.asia.pool.ntp.org iburst
pool 2.asia.pool.ntp.org iburst
pool 3.asia.pool.ntp.org iburst

# Lưu drift file để track độ lệch đồng hồ theo thời gian
driftfile /var/lib/chrony/drift

# Step-adjust đồng hồ ngay khi startup nếu lệch hơn 1 giây (tối đa 3 lần)
makestep 1.0 3

# Sync lại đồng hồ hardware (RTC) định kỳ
rtcsync

logdir /var/log/chrony
EOF
systemctl restart chronyd
chronyc sources -v

Output của chronyc sources trông như này khi hoạt động bình thường:

MS Name/IP address         Stratum Poll Reach LastRx Last sample
===============================================================================
^* 103.145.x.x                   2   6   377    51   +123us[ +456us] +/- 5ms
^+ time.cloudflare.com            3   6   377    52   -234us[ -789us] +/- 8ms

Ký hiệu ^* là server đang được chọn làm nguồn chính, ^+ là backup. Nếu tất cả đều hiện ^? thì server chưa reach được pool nào — check firewall port 123/UDP.

Bước 3: Dựng NTP Server nội bộ (quan trọng cho doanh nghiệp)

Thay vì để 50 server tự ra internet sync NTP, bạn nên dựng 1-2 NTP server nội bộ, các server còn lại sync vào đó. Lợi ích rõ ràng: giảm traffic ra ngoài, toàn bộ hạ tầng dùng cùng một time source, và khi incident xảy ra log correlation chính xác.

Trên máy NTP Server (ví dụ IP 192.168.1.10), cấu hình thêm phần allow:

cat > /etc/chrony.conf << 'EOF'
# Sync từ pool bên ngoài
pool 0.asia.pool.ntp.org iburst
pool 1.asia.pool.ntp.org iburst
pool 2.asia.pool.ntp.org iburst

driftfile /var/lib/chrony/drift
makestep 1.0 3
rtcsync
logdir /var/log/chrony

# Cho phép các subnet nội bộ query NTP từ server này
allow 192.168.1.0/24
allow 10.0.0.0/8

# Nếu mất internet, vẫn serve time cho client dựa trên đồng hồ local
# stratum 10 = "tôi không chắc lắm nhưng vẫn tốt hơn không có gì"
local stratum 10
EOF
systemctl restart chronyd

# Mở firewall port NTP
firewall-cmd --permanent --add-service=ntp
firewall-cmd --reload

# Kiểm tra client đang sync vào server này
chronyc clients

Trên tất cả các server còn lại (NTP Client), đổi config trỏ vào NTP server nội bộ:

cat > /etc/chrony.conf << 'EOF'
# Trỏ vào NTP server nội bộ
server 192.168.1.10 iburst prefer
server 192.168.1.11 iburst  # Backup NTP server nếu có

driftfile /var/lib/chrony/drift
makestep 1.0 3
rtcsync
EOF

systemctl restart chronyd
sleep 10
chronyc tracking

Troubleshoot nhanh các lỗi hay gặp

# Xem trạng thái đồng bộ chi tiết
chronyc tracking

# Buộc sync ngay lập tức (hữu ích sau khi vừa đổi config)
chronyc makestep

# Xem log real-time
journalctl -u chronyd -f

Lỗi “No NTP sources selected” — thường do firewall block port 123/UDP:

firewall-cmd --list-services | grep ntp
# Nếu không có, mở:
firewall-cmd --permanent --add-service=ntp && firewall-cmd --reload

SELinux deny chronyd — hay gặp sau khi đổi đường dẫn log hoặc drift file:

ausearch -m avc -ts recent | grep chrony
restorecon -Rv /var/lib/chrony /etc/chrony.conf

Cách tốt nhất: Setup bền vững cho production

Sau nhiều lần bị wake-up call lúc đêm khuya, mình đúc kết ra checklist này để không tái diễn:

  1. Dựng ít nhất 2 NTP server nội bộ — một master, một backup. Chrony client tự failover khi master down
  2. NTP server nội bộ sync từ ít nhất 3 pool bên ngoài — Chrony tự loại bỏ pool có độ tin cậy kém
  3. Bật rtcsync — sync đồng hồ hardware để server reboot không bị lệch ngay từ đầu
  4. Monitor offset bằng Prometheus — metric node_timex_offset_seconds sẽ alert trước khi lệch quá 1 giây, đủ thời gian xử lý trước khi thành incident
  5. Đưa vào Ansible playbook — mọi server mới deploy đều có chrony config đúng từ ngày đầu, không phải làm tay từng con

Đây là Ansible task mình đang dùng, đơn giản nhưng đủ dùng:

- name: Install chrony
  dnf:
    name: chrony
    state: present

- name: Deploy chrony config
  template:
    src: chrony.conf.j2
    dest: /etc/chrony.conf
    owner: root
    mode: '0644'
  notify: restart chronyd

- name: Enable chronyd
  systemd:
    name: chronyd
    state: started
    enabled: yes

- name: Open NTP port (NTP server nodes only)
  firewalld:
    service: ntp
    permanent: yes
    state: enabled
  when: inventory_hostname in groups['ntp_servers']

Từ khi áp dụng setup này cho toàn bộ fleet — bao gồm cả những con server đang chờ migrate sang AlmaLinux — mình chưa bị call lúc 2 giờ sáng vì time sync nữa. Và khi có incident thật sự xảy ra, log từ tất cả server đều correlate được chính xác, tiết kiệm được nhiều giờ debug không cần thiết.

Share: