Sự cố lệch 3,5 giây làm sập chuỗi thanh toán
Khoảng 6 tháng trước, cụm microservices bên mình gặp ca trực nhớ đời. Hàng trăm giao dịch thanh toán bị từ chối liên tục vì token JWT vừa tạo ra đã báo hết hạn ngay lập tức. Đội trực mở Kibana trace log thì thấy sự kiện nhảy loạn xạ giữa các node, hoàn toàn mất dấu luồng xử lý.
Nguyên nhân không nằm ở code hay database. Máy chủ API và worker bị lệch nhau đúng 3,5 giây. Với các hệ thống chạy Cassandra, CockroachDB hay Kubernetes etcd, chỉ cần lệch vài chục mili-giây là thuật toán đồng thuận đã bắt đầu lỗi, dẫn đến hỏng toàn vẹn dữ liệu.
Tại sao máy chủ Linux luôn bị trôi giờ?
Đồng hồ phần cứng (RTC) trên mainboard dùng tinh thể thạch anh. Tần số dao động của nó thay đổi theo nhiệt độ phòng máy, điện áp và tuổi thọ linh kiện. Hiện tượng trôi xung nhịp này (clock drift) khiến server vật lý nhanh hoặc chậm khoảng 1 đến 2 giây mỗi ngày.
Trên môi trường ảo hóa như KVM, VMware hay AWS EC2, tình trạng này còn nặng hơn. Khi CPU ảo bị tranh chấp tài nguyên (CPU steal) hoặc VM chuyển host, xung nhịp gián đoạn tức thì. Độ lệch có thể vọt lên hàng phút chỉ sau vài giờ.
Nếu để từng máy chủ tự sync ra NTP public ngoài Internet, đường truyền quốc tế chập chờn sẽ gây jitter cao. Ngoài ra, việc mở cổng UDP 123 bừa bãi ra ngoài còn tiềm ẩn nguy cơ bị lợi dụng tấn công NTP Amplification.
Nhìn lại các giải pháp đồng bộ thời gian cũ
Trước khi chuẩn hóa hạ tầng, mình từng thử qua nhiều cách:
1. Chạy lệnh ntpdate qua Cronjob
Nhiều nơi vẫn đặt cron chạy ntpdate mỗi 15 phút. Cách này rất rủi ro vì ntpdate chỉnh giờ bằng cách nhảy bước (step change). Khi đồng hồ bị giật lùi, các tiến trình ghi timestamp liên tục như MySQL replication binlog hay scheduled job dễ bị treo cứng.
2. Dùng daemon NTPd truyền thống
ntpd đã phục vụ thế giới Linux hàng chục năm. Điểm trừ lớn nhất là nó bắt nhịp rất chậm. Khi server khởi động lại hoặc rớt mạng, ntpd mất từ 20 đến 40 phút mới kéo giờ về chuẩn. Lượng RAM tiêu thụ cũng cao hơn so với nhu cầu thực tế.
3. Dùng systemd-timesyncd mặc định
Công cụ này tích hợp sẵn trên Ubuntu và Debian, hoạt động cực nhẹ. Nếu chỉ cần đồng bộ cho một máy trạm độc lập, nó làm rất tốt. Tuy nhiên, systemd-timesyncd chỉ là client thuần túy, không thể làm server cấp phát giờ cho các máy khác trong mạng nội bộ.
Giải pháp thực chiến: Dựng Chrony NTP Server nội bộ
Sau thời gian dài vận hành thực tế, Chrony là lựa chọn tin cậy nhất. Daemon này kéo giờ chuẩn cực nhanh khi boot, tự bù trôi xung nhịp mượt mà (slew) mà không làm giật lùi thời gian của ứng dụng, đồng thời ăn chưa tới 15MB RAM.
Mô hình triển khai
Mô hình chuẩn gồm 1 đến 2 máy chủ Chrony Master đặt tại dải DMZ hoặc Management. Các máy này lấy giờ trực tiếp từ nguồn Stratum 1/2 uy tín. Toàn bộ máy chủ database, backend và switch/router trong mạng nội bộ sẽ trỏ về cụm Master này.
Bước 1: Cài đặt Chrony
Với hệ điều hành Debian và Ubuntu:
sudo apt update
sudo apt install chrony -y
Với RHEL, AlmaLinux, Rocky Linux hoặc CentOS Stream:
sudo dnf install chrony -y
Bước 2: Cấu hình Chrony làm Local NTP Server
File cấu hình nằm tại /etc/chrony/chrony.conf (Ubuntu/Debian) hoặc /etc/chrony.conf (RHEL/CentOS). Mở file bằng quyền root:
sudo nano /etc/chrony/chrony.conf
Nội dung cấu hình tối ưu cho server Master:
# Khai báo các upstream NTP gần khu vực địa lý để giảm độ trễ
server 0.asia.pool.ntp.org iburst
server 1.asia.pool.ntp.org iburst
server 2.asia.pool.ntp.org iburst
server time.google.com iburst
server time.cloudflare.com iburst
# Lưu trữ thông số sai số tần số đồng hồ để bù trừ khi khởi động lại
driftfile /var/lib/chrony/chrony.drift
# Chỉ cho phép nhảy bước giờ nếu lệch trên 1 giây trong 3 lần sync đầu tiên
makestep 1.0 3
# Tự động đồng bộ giờ hệ điều hành vào chip RTC phần cứng
rtcsync
# Cho phép các dải subnet nội bộ kết nối lấy giờ
allow 192.168.10.0/24
allow 10.10.0.0/16
# Giữ vai trò máy chủ thời gian nội bộ ngay cả khi đứt mạng Internet
local stratum 10
Mẹo tính mạng: Khi chia subnet cho nhiều cụm server, bạn có thể dùng toolcraft.app/vi/tools/developer/ip-subnet-calculator để lấy nhanh network range và broadcast chuẩn, tránh mở nhầm dải IP trong dòng allow.
Bước 3: Mở Firewall và kích hoạt dịch vụ
Giao thức NTP hoạt động trên cổng UDP 123. Cần mở port này trên firewall máy Master.
Nếu dùng UFW (Ubuntu/Debian):
sudo ufw allow 123/udp
sudo ufw reload
Nếu dùng Firewalld (RHEL/CentOS/Rocky):
sudo firewall-cmd --add-service=ntp --permanent
sudo firewall-cmd --reload
Kích hoạt để Chrony tự chạy cùng hệ thống:
sudo systemctl enable --now chrony
sudo systemctl restart chrony
Bước 4: Kiểm tra trạng thái đồng bộ
Gõ lệnh sau để xem danh sách nguồn thời gian upstream:
chronyc sources -v
Khi nguồn nào có tiền tố ^*, nghĩa là Chrony đã chọn nguồn đó làm mốc chuẩn chính:
MS Name/IP address Stratum Poll Reach LastRx Last sample
===============================================================================
^* time.cloudflare.com 3 6 377 25 -122us[ -150us] +/- 14ms
^+ 118.69.177.108 2 6 377 24 -450us[ -478us] +/- 22ms
^+ time.google.com 1 6 377 23 +85us[ +57us] +/- 18ms
Kiểm tra độ trôi chi tiết của hệ thống:
chronyc tracking
Bốn thông số cần lưu ý:
- Reference time: Thời điểm đồng bộ thành công gần nhất.
- Stratum: Khoảng cách tới đồng hồ nguyên tử (server Master của bạn thường là Stratum 2 hoặc 3).
- System time: Độ lệch so với chuẩn quốc tế. Mức tốt là dưới 0.0005 giây (0.5ms).
- Root delay: Tổng độ trễ mạng tới máy chủ gốc.
Xem danh sách các máy client nội bộ đang kết nối:
chronyc clients
Bước 5: Cấu hình Client trỏ về Master nội bộ
Trên các máy node ứng dụng trong mạng LAN, bạn cài Chrony rồi sửa file cấu hình, xóa các pool public và chỉ trỏ về IP của Master:
# Trỏ trực tiếp về IP Chrony Master nội bộ
server 192.168.10.10 iburst
makestep 1.0 3
rtcsync
Restart lại dịch vụ trên client:
sudo systemctl restart chrony
chronyc sources
Kinh nghiệm vận hành cần nhớ
Khi đưa Chrony vào production, bạn nên lưu ý 3 điểm kỹ thuật quan trọng sau:
- Tắt hẳn các service thời gian khác: Tuyệt đối không để
systemd-timesyncdhayntpdchạy cùng Chrony. Hãy tắt triệt để bằng lệnhsudo systemctl disable --now systemd-timesyncdđể tránh xung đột quyền điều khiển đồng hồ nhân kernel. - Kiểm soát tham số makestep: Chỉ cho phép nhảy bước giờ ở 3 lần sync đầu lúc khởi động máy. Nếu ép đồng hồ giật lùi khi ứng dụng đang chạy tải cao, các thuật toán lock phân tán và database transaction sẽ bị lỗi ngay.
- Cấu hình tối thiểu 3 đến 4 nguồn upstream: Thuật toán Marzullo của Chrony cần ít nhất 3 nguồn độc lập để so khớp chéo. Nếu một server trả về mốc giờ sai, Chrony sẽ tự động cô lập nguồn lỗi đó ra khỏi phép tính.

