Bài 03

High Availability: Keepalived VRRP + MySQL Replication

11 tháng 5, 2026 · 120 phút đọc

Bài 03 — High Availability: Keepalived VRRP + MySQL Replication

Đến bài này, hệ thống của bạn đã có:

  • Bài 01: 1 site WordPress trên 1 server.
  • Bài 02: 1 nginx proxy load balance đến 2 backend.

Vẫn còn 2 điểm chết (single point of failure):

  1. Proxy chỉ có 1 con — proxy die thì cả site sập. DNS chỉ trỏ 1 IP, không biết chuyển đi đâu.
  2. Database chỉ có 1 con — DB die thì backend cũng vô dụng. Có 10 backend cũng không cứu được.

Bài này là phần cuối — biến hệ thống thành thực sự HA: proxy có cả Master + Backup tự động chuyển khi 1 con die, DB có replica đứng sẵn nhận traffic khi DB chính die.

Đây là phần khó nhất của chặng này. Đọc xong là bạn dựng được kiến trúc mà mọi web service production thực sự đang chạy.


Mô hình HA tổng thể

Sau bài này, hệ thống sẽ trông như sau:

                          ┌──────────────┐
                          │    Client    │
                          └──────┬───────┘
                                 │ https://test.com
                                 │ DNS trỏ về VIP
                                 ▼
                       ┌──────────────────┐
                       │ VIP 192.168.32.100│  ← IP ảo
                       └─────────┬─────────┘
                                 │ (Keepalived VRRP)
              ┌──────────────────┴──────────────────┐
              │                                      │
              ▼                                      ▼
   ┌────────────────────┐                ┌────────────────────┐
   │  Proxy MASTER      │ ◄── VRRP ────► │  Proxy BACKUP      │
   │  192.168.32.135    │  (multicast)   │  192.168.32.136    │
   │  - nginx           │                │  - nginx           │
   │  - keepalived      │                │  - keepalived      │
   └─────────┬──────────┘                └─────────┬──────────┘
             │                                      │
             └──────────────┬───────────────────────┘
                            │ load balance
              ┌─────────────┴────────────┐
              ▼                          ▼
   ┌────────────────────┐    ┌────────────────────┐
   │  Backend 1         │    │  Backend 2         │
   │  192.168.32.133    │    │  192.168.32.134    │
   │  - nginx           │    │  - nginx           │
   │  - php-fpm         │    │  - php-fpm         │
   │  - WordPress       │    │  - WordPress       │
   └─────────┬──────────┘    └─────────┬──────────┘
             │                          │
             └──────────────┬───────────┘
                            │ MySQL connections
              ┌─────────────┴────────────┐
              ▼                          ▼
   ┌────────────────────┐    ┌────────────────────┐
   │  DB MASTER         │ ──►│  DB SLAVE          │
   │  192.168.32.137    │binlog 192.168.32.138    │
   │  - MariaDB         │    │  - MariaDB         │
   │  (write + read)    │    │  (read only)       │
   └────────────────────┘    └────────────────────┘
            (replication async)

3 lớp HA:

  1. Lớp VIP (Keepalived VRRP) — 1 IP ảo, 2 proxy luân phiên giữ.
  2. Lớp Backend (nginx load balance) — đã có ở Bài 02.
  3. Lớp Database (MySQL Replication) — Master ghi, Slave đọc + standby.

Bài này dạy từ trên xuống. Phần A: Keepalived VRRP. Phần B: MySQL Replication. Phần C: gắn cả 3 lớp.


Phần A — Keepalived VRRP

Tổng quan về Keepalived

Keepalived là phần mềm dùng để cung cấp tính sẵn sàng cao (High Availability — HA) và cân bằng tải (Load Balancing) trong các hệ thống máy chủ. Triển khai trên Linux để đảm bảo các dịch vụ quan trọng luôn hoạt động ngay cả khi xảy ra lỗi.

Keepalived ban đầu được thiết kế để bảo vệ các dịch vụ LVS (Linux Virtual Server) bằng cách sử dụng giao thức VRRP (Virtual Router Redundancy Protocol). Tuy nhiên, nó đã phát triển để hỗ trợ nhiều tính năng:

  • Failover: đảm bảo dịch vụ luôn sẵn sàng bằng cách chuyển đổi giữa Master và Backup.
  • Load Balancing: phân phối lưu lượng truy cập đến nhiều backend.
  • Health Check: theo dõi tình trạng của dịch vụ backend và loại bỏ node không hoạt động.

3 thành phần chính của Keepalived

  • VRRP (Virtual Router Redundancy Protocol): quản lý IP ảo (Virtual IP — VIP) giữa các máy chủ để thực hiện failover.
  • Health Checkers: kiểm tra tình trạng các dịch vụ backend.
  • Configuration Scripts: thực thi các hành động tùy chỉnh dựa trên trạng thái hệ thống.

Ở bài này tập trung dùng Keepalived VRRP để làm HA, kết hợp với nginx proxy đã làm Load Balancing ở Bài 02.


VRRP là gì

VRRP (Virtual Router Redundancy Protocol) là giao thức được sử dụng để đảm bảo tính khả dụng và dự phòng cho các router hoặc gateway trong mạng.

Keepalived sử dụng VRRP để cung cấp tính năng dự phòng gateway cho các máy chủ trong mạng, đảm bảo rằng khi một router (hoặc máy chủ) gặp sự cố, một router khác sẽ thay thế và tiếp tục cung cấp dịch vụ mà không có gián đoạn.

Các thành phần chính trong VRRP

  • Virtual Router (Router ảo): đại diện cho một địa chỉ IP ảo (VIP). Các máy chủ trong mạng sẽ sử dụng IP ảo này làm gateway. VRRP đảm bảo IP ảo luôn có một router "chủ" để xử lý lưu lượng. Nếu router chính gặp sự cố, một router "phụ" sẽ thay thế.

  • Master Router: router đảm nhận vai trò cổng mặc định (gateway) cho các máy chủ trong mạng. Đây là router chính giữ VIP.

  • Backup Router: router phụ, luôn giám sát tình trạng của router chính. Khi router chính gặp sự cố, router phụ sẽ trở thành router chủ và tiếp quản VIP.

  • Virtual IP Address (VIP): địa chỉ IP ảo mà các thiết bị mạng sử dụng làm gateway. Địa chỉ này không gắn với bất kỳ máy chủ hoặc router cụ thể nào mà được chia sẻ giữa các router trong nhóm VRRP.

  • Priority: mỗi router trong nhóm VRRP có một mức độ ưu tiên. Router với priority cao hơn sẽ trở thành Master. Nếu hai router có cùng priority, một trong hai sẽ được chọn dựa vào địa chỉ MAC.


Cách hoạt động của VRRP

Truyền thông giữa các router

Các router gửi các gói tin VRRP định kỳ để thông báo về tình trạng của chúng. Gói tin chứa: trạng thái (Master/Backup), priority, thông tin VIP.

Trạng thái bình thường:

┌──────────────────┐          ┌──────────────────┐
│  MASTER          │          │  BACKUP          │
│  192.168.32.135  │          │  192.168.32.136  │
│  priority=101    │          │  priority=90     │
│                  │          │                  │
│  ┌────────────┐  │  VRRP    │                  │
│  │ Có VIP     │  │  packet  │                  │
│  │ 32.100     │  │ ────────►│                  │
│  └────────────┘  │  mỗi 1s  │  (lắng nghe)    │
│                  │          │                  │
└──────────────────┘          └──────────────────┘
        ▲
        │ traffic vào VIP
        │
   ┌────┴────┐
   │ Client  │
   └─────────┘

Cơ chế truyền gói VRRP

  • Mặc định: IP multicast (Layer 3) — gói tin VRRP được gửi qua multicast IP 224.0.0.18. Hiệu quả khi nhiều router cùng nhóm, không cần biết IP từng con.

  • Unicast: VRRP cũng hỗ trợ gửi trực tiếp đến từng router. Cần cấu hình IP cụ thể. Tốn ít băng thông hơn và bảo mật hơn (không broadcast ra mạng).

  • Failover trigger: Nếu BACKUP không nhận được gói VRRP từ MASTER trong thời gian bằng 3 lần khoảng quảng bá (advert_int), nó sẽ coi MASTER đã gặp sự cố và khởi động quy trình failover.

Chuyển giao quyền điều khiển (Failover)

1. MASTER chết (server down, network die):

┌──────────────────┐          ┌──────────────────┐
│  MASTER (X)      │          │  BACKUP          │
│  192.168.32.135  │          │  192.168.32.136  │
│  ☠️ DEAD          │          │  priority=90     │
│                  │ no VRRP  │                  │
│                  │ ─ ─ ─ ─ ─│  (chờ 3s)        │
│                  │          │                  │
└──────────────────┘          └──────────────────┘
                                        │
                                        ▼
2. Sau 3 × advert_int (= 3s default):

┌──────────────────┐          ┌──────────────────┐
│  MASTER (X)      │          │  ✨ NEW MASTER   │
│  192.168.32.135  │          │  192.168.32.136  │
│  ☠️ DEAD          │          │  ┌───────────┐  │
│                  │          │  │ Có VIP    │  │
│                  │          │  │ 32.100    │  │
│                  │          │  └───────────┘  │
└──────────────────┘          └──────────────────┘
                                        ▲
                                        │ traffic redirect
                                   ┌────┴────┐
                                   │ Client  │
                                   └─────────┘

Preempt — quay về master khi sống lại

Mặc định: máy có priority cao hơn sẽ giành quyền quản lý VIP. Khi MASTER bị lỗi và hoạt động trở lại, VIP sẽ được đưa trả về máy có priority cao hơn.

3. MASTER cũ sống lại:

┌──────────────────┐          ┌──────────────────┐
│  MASTER (revive) │          │  BACKUP again    │
│  192.168.32.135  │          │  192.168.32.136  │
│  priority=101    │          │  priority=90     │
│  ┌───────────┐   │  VRRP    │                  │
│  │ VIP 32.100│   │  ────────►                  │
│  │ TRỞ VỀ    │   │ priority │                  │
│  └───────────┘   │ cao hơn  │                  │
└──────────────────┘          └──────────────────┘

⚠️ Để tắt tính năng preempt (giữ VIP ở backup, không trả về master) — dùng nopreempt trong cấu hình. Hữu ích khi không muốn "flap" 2 lần (1 lần fail over, 1 lần fail back).


Cài đặt và cấu hình Keepalived VRRP

Yêu cầu

  • 2 server nginx proxy (đã thông mạng), đã cài đặt và cấu hình proxy load balancing (kết quả của Bài 02).
  • IP: master 192.168.32.135, backup 192.168.32.136.
  • VIP dự kiến: 192.168.32.100.

Cài đặt

Trên cả 2 server (master và backup):

sudo dnf install -y keepalived nginx
sudo systemctl enable --now keepalived

Cấu hình Keepalived trên MASTER

File cấu hình mặc định: /etc/keepalived/keepalived.conf

sudo vim /etc/keepalived/keepalived.conf

Nội dung:

vrrp_instance VI_1 {
    state MASTER                # Router hiện tại là Master
    interface ens33             # Giao diện mạng sử dụng
    virtual_router_id 51        # ID nhóm VRRP, phải giống nhau giữa các router cùng nhóm
    priority 101                # Mức độ ưu tiên (cao hơn = ưu tiên làm Master)
    advert_int 1                # Thời gian giữa các lần quảng bá VRRP (giây)
    authentication {
        auth_type PASS          # Loại xác thực (PASS = mật khẩu)
        auth_pass 1111          # Mật khẩu xác thực
    }
    virtual_ipaddress {
        192.168.32.100          # Địa chỉ IP ảo chia sẻ giữa các router
    }
}

Giải thích từng dòng:

  • state MASTER — thiết lập router này là Master. Nếu Master ngừng hoạt động, một router khác có thể trở thành Master.
  • interface ens33 — chỉ định giao diện mạng mà VRRP sẽ chạy. Phải đúng tên interface (kiểm tra bằng ip a).
  • virtual_router_id 51 — ID nhóm VRRP. Các router cùng nhóm phải có ID này giống nhau. Khác nhóm phải khác ID.
  • priority 101 — Master nên có priority cao hơn Backup.
  • advert_int 1 — tần suất gửi gói VRRP để thông báo trạng thái (mỗi 1 giây).
  • authentication — phương thức xác thực, ngăn router lạ xâm nhập nhóm.
  • virtual_ipaddress — VIP mà nhóm router chia sẻ.

Cấu hình Keepalived trên BACKUP

sudo vim /etc/keepalived/keepalived.conf
vrrp_instance VI_1 {
    state BACKUP                # Router hiện tại là Backup
    interface ens33
    virtual_router_id 51        # Cùng ID với MASTER
    priority 90                 # Priority THẤP HƠN MASTER (101 vs 90)
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111          # Cùng password với MASTER
    }
    virtual_ipaddress {
        192.168.32.100          # Cùng VIP với MASTER
    }
}

Chỉ khác MASTER ở 2 dòng:

  • state BACKUP
  • priority 90 (thấp hơn 101 của Master)

Restart và test

Trên cả 2 server:

sudo systemctl restart keepalived
sudo systemctl status keepalived

Kiểm tra VIP trên MASTER (192.168.32.135):

ip a | grep 192.168.32.100
# inet 192.168.32.100/32 scope global ens33

VIP đã gắn vào interface. Trên BACKUP, không thấy VIP — vì BACKUP đang chờ.

Test failover

Trên MASTER, dừng keepalived hoặc shutdown server:

# Trên master
sudo systemctl stop keepalived

Trên BACKUP (192.168.32.136), kiểm tra:

ip a | grep 192.168.32.100
# inet 192.168.32.100/32 scope global ens33   ← VIP đã chuyển sang!

Failover diễn ra trong 3 giây (3 × advert_int = 3 × 1s).

Khởi động lại MASTER:

sudo systemctl start keepalived

VIP tự quay về MASTER (do priority cao hơn). Đây là behavior preempt mặc định.

Cấu hình unicast (thay multicast)

Mặc định keepalived chạy multicast 224.0.0.18. Nếu mạng không cho phép multicast (cloud VPC, một số switch) — chuyển sang unicast:

vrrp_instance VI_1 {
    state MASTER
    interface ens33
    virtual_router_id 51
    priority 101
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        192.168.32.100
    }

    unicast_peer {
        192.168.32.136     # Địa chỉ IP của router BACKUP
    }
}

Trên BACKUP, set unicast_peer trỏ về MASTER (192.168.32.135).

Phân biệt nhiều nhóm Keepalived

Nếu trong cùng một mạng có nhiều nhóm keepalived khác nhau (vd: 1 nhóm cho web proxy, 1 nhóm cho DB), phân biệt bằng:

  • virtual_router_id — các router cùng nhóm dùng chung ID, khác nhóm khác ID.
  • VIP — mỗi nhóm có VIP riêng.
  • authentication — mỗi nhóm dùng password khác nhau để đảm bảo chỉ router cùng nhóm giao tiếp với nhau.
  • Sử dụng card mạng khác nhau — physical isolation.

HA cho multi-service trong một server

Trong bối cảnh hiện tại, một server thường chạy nhiều service độc lập. Khi cấu hình HA cho server nhưng:

  • Service A chết, Service B vẫn hoạt động → VIP bị chuyển đi → các dịch vụ khác (B, C, D) bị ảnh hưởng oan.

Giải pháp: cấu hình keepalived cho TỪNG dịch vụ, mỗi dịch vụ quản lý một VIP riêng. Nếu dịch vụ đó chết → kích hoạt failover cho VIP đó, các dịch vụ khác vẫn hoạt động bình thường.

Mô hình multi-VIP

┌─────────────────────────────────────┐
│  Server: 192.168.32.135 (MASTER)    │
│                                      │
│  ┌────────────┐    ┌────────────┐  │
│  │ Service A  │    │ Service B  │  │
│  │ VIP: .100  │    │ VIP: .101  │  │
│  │ VRID: 50   │    │ VRID: 51   │  │
│  └─────┬──────┘    └─────┬──────┘  │
│        │                  │         │
│  ┌─────┴──────────────────┴──────┐  │
│  │   keepalived (cả 2 instance) │  │
│  └──────────────────────────────┘  │
└─────────────────────────────────────┘
              │            │
              │ VRRP       │ VRRP
              ▼            ▼
┌─────────────────────────────────────┐
│  Server: 192.168.32.136 (BACKUP)    │
└─────────────────────────────────────┘

Service A chết → chỉ VIP .100 chuyển → Service B (VIP .101) vẫn ở master

Cấu hình multi-instance trong file config

# VRRP Instance for Service A
vrrp_instance VI_A {
    state MASTER
    interface ens33
    virtual_router_id 50             # VRID duy nhất cho service A
    priority 100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass serviceA_pass      # Password riêng
    }
    virtual_ipaddress {
        192.168.32.100               # VIP cho service A
    }
    track_script {
        check_serviceA               # Theo dõi trạng thái service A
    }
}

# VRRP Instance for Service B
vrrp_instance VI_B {
    state MASTER
    interface ens33
    virtual_router_id 51             # VRID KHÁC cho service B
    priority 100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass serviceB_pass
    }
    virtual_ipaddress {
        192.168.32.101               # VIP cho service B
    }
    track_script {
        check_serviceB
    }
}

vrrp_script check_serviceA {
    script "/etc/keepalived/check_serviceA.sh"   # Đường dẫn tới script
    interval 2                                    # Kiểm tra mỗi 2 giây
    weight 2                                      # Giảm độ ưu tiên đi 2 nếu lỗi
}

vrrp_script check_serviceB {
    script "/etc/keepalived/check_serviceB.sh"
    interval 2
    weight 2
}

Tạo script kiểm tra dịch vụ

Tạo script kiểm tra trạng thái dịch vụ:

sudo vim /etc/keepalived/check_serviceA.sh
#!/bin/bash
if systemctl is-active --quiet serviceA; then
    exit 0  # Trả về 0 nếu service đang chạy
else
    exit 1  # Trả về 1 nếu service gặp sự cố
fi

Tương tự cho check_serviceB.sh. Cấp quyền execute:

sudo chmod +x /etc/keepalived/check_serviceA.sh
sudo chmod +x /etc/keepalived/check_serviceB.sh

Hiểu cơ chế weight

Khi script trả về 1 (service chết):

Trước khi service chết:
  MASTER:  priority=100,  weight không trừ → effective = 100
  BACKUP:  priority=90                      → effective = 90
  → MASTER thắng (100 > 90)

Service chết, script return 1:
  MASTER:  priority=100, weight -2          → effective = 98
  BACKUP:  priority=90                      → effective = 90
  → MASTER vẫn thắng (98 > 90)   ← KHÔNG failover!

⚠️ weight phải đủ lớn để priority sau khi trừ < priority backup. Ví dụ trên weight 2 chưa đủ. Nên đặt:

vrrp_script check_serviceA {
    script "/etc/keepalived/check_serviceA.sh"
    interval 2
    weight -20      # ⚠️ NEGATIVE để trừ vào priority master khi script fail
}

Lúc này:

Service chết:
  MASTER:  100 - 20 = 80
  BACKUP:  90
  → BACKUP thắng → VIP chuyển sang BACKUP ✓

(Một số tài liệu dùng weight 2 với nghĩa "tăng priority backup khi script trên backup OK". Cách dùng phụ thuộc context — đọc kỹ doc keepalived.)

Notification email

Có thể cấu hình gửi email khi xảy ra failover:

global_defs {
    notification_email {
        admin@example.com
    }
    notification_email_from keepalived@example.com
    smtp_server 192.168.1.1
    smtp_connect_timeout 30
    router_id SERVER_1              # Đặt tên định danh cho server này
}

Các điểm quan trọng về track_script

  • Mỗi vrrp_instance quản lý một VIP. Router quảng bá các VIP mình đang quản lý. Gói VRRP với virtual_router_id được gửi đi theo từng service.

  • Nếu có sự cố trên service nào → Keepalived vẫn tự động chuyển trạng thái mà KHÔNG cần track_script (chỉ dựa vào VRRP heartbeat).

  • Track_script để tách biệt về mặt dịch vụ. Nếu chỉ muốn HA cho service A (không cho service B) thì bắt buộc phải có track_script để theo dõi tình trạng service A.

  • Track_script khi nào dùng:

    • Muốn Keepalived không chỉ dựa vào trạng thái VRRP mà còn kiểm tra trạng thái thực của dịch vụ trước khi thay đổi trạng thái VIP.
    • Khi có yêu cầu phục hồi dịch vụ (service failover) khi dịch vụ gặp sự cố mà không chỉ dựa vào trạng thái mạng.
    • Đây là cơ chế health check mà Keepalived cung cấp.
  • Trên BACKUP: cấu hình tương tự, chỉ cần thay đổi priority. Có thể KHÔNG cần track_script trên backup.


Vấn đề kinh điển: network die nhưng service vẫn sống

Trường hợp không kích hoạt được failover khi sử dụng track_script:

Scenario nguy hiểm:
┌─────────────────────┐         ┌─────────────────────┐
│  MASTER             │         │  BACKUP             │
│  - nginx UP ✓       │   ⚡    │  - nginx UP ✓       │
│  - keepalived UP    │ network │  - keepalived UP    │
│  - track_script OK  │  DIE!   │  - track_script OK  │
│                     │ ────❌──│                     │
└─────────────────────┘         └─────────────────────┘
        ▲                                ▲
        │                                │
        │ Client không tới được          │ Client không tới được
        │ (nếu route qua network die)    │

Khi sử dụng track_script mà network giữa các server gặp vấn đề nhưng health check service vẫn hoạt động:

  • Track_script trên MASTER vẫn return 0 (service nginx OK)
  • Failover không được kích hoạt → user vẫn không truy cập được

Lý do: khi dùng track_script, failover dựa trên trạng thái dịch vụ (weight bị giảm xuống và bị đẩy xuống làm backup). KHÔNG dựa vào network. Còn nếu không dùng track_script thì failover phụ thuộc vào network (VRRP heartbeat fail).

2 cách khắc phục:

  1. Cấu hình track_script CHỈ trên MASTER, không cấu hình trên BACKUP — backup không tự coi mình "không cần lên master".

  2. Thêm script check network trên master để ping tới backup:

#!/bin/bash
# /etc/keepalived/check_network.sh
ping -c 2 -W 1 192.168.32.136 > /dev/null
exit $?

Trong config, thêm vrrp_script cho check_network với weight đủ lớn để force giảm priority khi network die.

Xử lý lỗi cấu hình

Keepalived ghi log vào file log hệ thống (thường là /var/log/messages hoặc /var/log/syslog) nếu gặp lỗi trong file cấu hình. Khi keepalived không start được:

sudo journalctl -u keepalived -n 50
sudo tail -f /var/log/messages | grep -i keepalived

Phần B — MySQL Replication

Phần A đã giải quyết HA cho proxy. Còn DB? Bài 01-02 chỉ có 1 DB → die là sập tầng DB → backend vô dụng. Phần này dạy MySQL Replication để có DB backup sẵn sàng.

MySQL Replication là gì

MySQL Replication là quá trình sao chép dữ liệu từ một database server (gọi là Master hoặc Source) sang một hoặc nhiều database server khác (gọi là Slave hoặc Replica) một cách tự động và liên tục.

┌─────────────────────┐                   ┌─────────────────────┐
│  MASTER (Source)    │ ─── replicate ──► │  SLAVE (Replica)    │
│  192.168.32.137     │                   │  192.168.32.138     │
│                     │                   │                     │
│  - Chấp nhận write  │                   │  - Read-only        │
│  - Chấp nhận read   │                   │  - Standby cho HA   │
│  - Ghi binlog       │                   │  - Đọc binlog từ    │
│                     │                   │    master và apply  │
└─────────────────────┘                   └─────────────────────┘
       │                                          ▲
       │ binary log (binlog)                      │
       └──────────────────────────────────────────┘
                  copy + replay

Lợi ích của replication

  • Read scaling — read query đẩy sang slave, master chỉ xử lý write.
  • Backup — backup từ slave, không impact master.
  • Geographic distribution — slave ở data center khác cho latency thấp.
  • High Availability — master die → promote slave thành master mới.
  • Phân tích / report — chạy query nặng trên slave, không ảnh hưởng production.

Async vs Semi-sync vs Sync

3 mode replication, đánh đổi giữa performancedata safety:

ASYNC (mặc định):
  Master commit ──► trả OK cho client ──► gửi binlog cho slave (sau)
  Risk: master die trước khi gửi → slave thiếu data ⚠️
  Lợi: nhanh, không ảnh hưởng latency master

SEMI-SYNC:
  Master commit ──► gửi binlog cho slave ──► slave ack ──► trả OK cho client
  Risk: slave die → master treo chờ ack
  Lợi: đảm bảo ít nhất 1 slave có data trước khi confirm

SYNC (Galera Cluster, không phải replication thuần):
  Master prepare ──► tất cả node ack ──► tất cả commit cùng lúc
  Risk: chậm hơn nhiều, network requirement cao
  Lợi: zero data loss, multi-master write

Bài này dùng async (mặc định MySQL/MariaDB) — đơn giản, đủ cho HA cơ bản.


Cách hoạt động — Binary Log và 3 Thread

Binary Log (binlog) là gì

Binary log (binlog) là file log ghi lại mọi thay đổi dữ liệu (INSERT, UPDATE, DELETE, DDL) trên master. Đây là "nguồn sự thật" mà slave dùng để replay.

Master ghi vào binlog mỗi statement/transaction:
  /var/lib/mysql/mysql-bin.000001
  /var/lib/mysql/mysql-bin.000002
  ...

Mỗi event có:
  - Position (offset trong file)
  - Timestamp
  - Statement / row change

3 dạng binlog format:

  • Statement-Based Replication (SBR) — ghi SQL statement gốc. Nhỏ gọn nhưng có vấn đề với non-deterministic (NOW(), UUID()).
  • Row-Based Replication (RBR) — ghi row thay đổi cụ thể. An toàn nhất. Default modern.
  • Mixed — auto chọn giữa SBR và RBR.

3 thread của replication

┌──────────────────────────┐         ┌──────────────────────────┐
│       MASTER              │         │        SLAVE             │
│                          │         │                          │
│  Client INSERT/UPDATE    │         │                          │
│         │                │         │                          │
│         ▼                │         │                          │
│  ┌──────────────┐        │         │                          │
│  │ MySQL engine │        │         │                          │
│  └──────┬───────┘        │         │                          │
│         │ write          │         │                          │
│         ▼                │         │                          │
│  ┌──────────────┐        │  TCP    │  ┌──────────────────┐   │
│  │   binlog     │ ──[1]──┼─────────┼─►│   IO Thread      │   │
│  │ (mysql-bin)  │        │         │  │ (đọc binlog từ    │   │
│  └──────────────┘        │         │  │  master qua mạng) │   │
│         ▲                │         │  └────────┬─────────┘   │
│         │ [Dump Thread]  │         │           │ write        │
│         │                │         │           ▼              │
│         │                │         │  ┌──────────────────┐   │
│                          │         │  │   relay log      │   │
│                          │         │  │  (mysql-relay)   │   │
│                          │         │  └────────┬─────────┘   │
│                          │         │           │              │
│                          │         │           ▼              │
│                          │         │  ┌──────────────────┐   │
│                          │         │  │   SQL Thread     │   │
│                          │         │  │ (replay từng     │   │
│                          │         │  │  event vào DB)   │   │
│                          │         │  └────────┬─────────┘   │
│                          │         │           ▼              │
│                          │         │  ┌──────────────────┐   │
│                          │         │  │  Slave DB        │   │
│                          │         │  │  (data synced)   │   │
│                          │         │  └──────────────────┘   │
└──────────────────────────┘         └──────────────────────────┘

3 thread:

  1. Master: Dump Thread — đọc binlog và gửi qua mạng cho slave.
  2. Slave: IO Thread — nhận binlog từ master, ghi vào relay log (local file ở slave).
  3. Slave: SQL Thread — đọc relay log, replay từng event vào database của slave.

Tại sao tách 2 thread ở slave? IO thread chỉ chép network bytes, nhanh. SQL thread mới là cái chạy actual SQL — chậm hơn. Tách ra để binlog không bị stuck nếu SQL replay chậm.

Position-based vs GTID

2 cách track replication position:

Position-based (legacy):

slave dõi theo: file mysql-bin.000003, position 12345

Failover khó: nếu chuyển slave sang master khác, phải tìm đúng file + position.

GTID (Global Transaction ID) — modern, recommend:

mỗi transaction có ID unique: SERVER_UUID:TXN_NUMBER
slave dõi theo: đã apply transaction X, Y, Z...

Failover dễ: slave chỉ cần biết "đã apply tới GTID nào", không cần file/position.

MariaDB từ 10.0+ dùng GTID-style riêng (khác MySQL). Bài này dùng position-based cho đơn giản, MariaDB có hỗ trợ GTID nhưng setup phức tạp hơn.


Setup Master-Slave với MariaDB

Yêu cầu

  • 2 server MariaDB (đã cài như Bài 01).
  • Master: 192.168.32.137.
  • Slave: 192.168.32.138.
  • Network thông giữa 2 server, port 3306 mở.

Bước 1: Config trên MASTER

Sửa file config MariaDB:

sudo vim /etc/my.cnf.d/server.cnf

Trong block [mysqld]:

[mysqld]
server-id = 1                       # ID duy nhất, master = 1
log-bin = /var/lib/mysql/mysql-bin  # Bật binary log
binlog_format = ROW                  # Row-based (an toàn nhất)
bind-address = 0.0.0.0              # Listen mọi interface
expire_logs_days = 7                 # Auto-xoá binlog cũ sau 7 ngày

Restart MariaDB:

sudo systemctl restart mariadb

Verify binlog đã bật:

mariadb -u root -p -e "SHOW VARIABLES LIKE 'log_bin';"
# +---------------+-------+
# | Variable_name | Value |
# +---------------+-------+
# | log_bin       | ON    |
# +---------------+-------+

Bước 2: Tạo user replication trên MASTER

mariadb -u root -p
CREATE USER 'repl'@'192.168.32.138' IDENTIFIED BY 'ReplPass123!';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'192.168.32.138';
FLUSH PRIVILEGES;

User repl chỉ có quyền REPLICATION SLAVE — đủ để đọc binlog, không có quyền khác.

Bước 3: Lock master + lấy binlog position

-- Lock toàn bộ DB để không có write mới trong khi snapshot
FLUSH TABLES WITH READ LOCK;

-- Ghi lại position hiện tại
SHOW MASTER STATUS;
-- +------------------+----------+--------------+------------------+
-- | File             | Position | Binlog_Do_DB | Binlog_Ignore_DB |
-- +------------------+----------+--------------+------------------+
-- | mysql-bin.000001 |   12345  |              |                  |
-- +------------------+----------+--------------+------------------+

LƯU LẠI file và position này — cần khi config slave.

Bước 4: Dump DB từ MASTER

Trên terminal khác (không thoát mariadb session đang lock):

mysqldump -u root -p --all-databases --master-data=2 > /tmp/master_dump.sql

--master-data=2 ghi binlog position vào dump file (dưới dạng comment, tham khảo).

Sau khi dump xong, unlock master:

UNLOCK TABLES;
EXIT;

Bước 5: Copy dump sang SLAVE

scp /tmp/master_dump.sql root@192.168.32.138:/tmp/

Bước 6: Config trên SLAVE

Sửa config:

sudo vim /etc/my.cnf.d/server.cnf
[mysqld]
server-id = 2                       # ID khác master (master=1)
relay-log = /var/lib/mysql/mysql-relay-bin
read_only = ON                       # Slave read-only (safety)
bind-address = 0.0.0.0

Restart:

sudo systemctl restart mariadb

Bước 7: Import dump vào SLAVE

mariadb -u root -p < /tmp/master_dump.sql

Bước 8: Setup replication trên SLAVE

mariadb -u root -p
CHANGE MASTER TO
    MASTER_HOST='192.168.32.137',
    MASTER_USER='repl',
    MASTER_PASSWORD='ReplPass123!',
    MASTER_LOG_FILE='mysql-bin.000001',     -- File từ bước 3
    MASTER_LOG_POS=12345;                    -- Position từ bước 3

START SLAVE;

Verify:

SHOW SLAVE STATUS\G

2 dòng quan trọng phải đều là Yes:

Slave_IO_Running: Yes          IO thread đang chạy
Slave_SQL_Running: Yes         SQL thread đang chạy
Seconds_Behind_Master: 0       Replication lag (giây)
Last_IO_Error:                 rỗng = OK
Last_SQL_Error:                rỗng = OK

Nếu 1 trong 2 không Yes → có lỗi. Xem error message ở Last_IO_Error hoặc Last_SQL_Error.

Test replication

Trên MASTER:

USE wordpress;
INSERT INTO wp_options (option_name, option_value) VALUES ('test_repl', 'hello');

Trên SLAVE (chờ vài giây):

USE wordpress;
SELECT * FROM wp_options WHERE option_name = 'test_repl';
-- Phải thấy row 'hello'

✓ Replication chạy.


Failover khi MASTER die

Quan trọng: MySQL replication open source KHÔNG tự động failover. Khi master die, phải làm manually (hoặc dùng tool như MHA, Orchestrator, ProxySQL).

Quy trình promote slave thành master mới

Trước failover:
  ┌────────────┐      ┌────────────┐
  │ Master ☠️   │ ───► │ Slave UP ✓ │
  │ (DEAD)     │      │ read-only  │
  └────────────┘      └────────────┘

Bước 1: Stop replication trên slave
  SLAVE> STOP SLAVE;
  SLAVE> RESET SLAVE ALL;

Bước 2: Bỏ read-only
  SLAVE> SET GLOBAL read_only = OFF;

Bước 3: Update app config trỏ về slave IP mới
  Backend wp-config.php:
    define('DB_HOST', '192.168.32.138');   ← IP slave (giờ là master mới)

Sau failover:
  ┌────────────┐      ┌──────────────┐
  │ Master ☠️   │      │ NEW MASTER ✓ │
  │ (DEAD)     │      │ writable     │
  └────────────┘      └──────────────┘

Sau khi MASTER cũ sống lại

Không thể đơn giản start lại master cũ — vì lúc đó có 2 master cùng write → conflict.

Quy trình rebuild:

  1. Treat master cũ như slave mới — dump từ new master, import vào master cũ.
  2. Setup replication từ new master → master cũ.

Sau khi sync xong, có 2 lựa chọn:

  • Giữ topology mới (slave cũ → master mới, master cũ → slave mới).
  • Switch back — thêm 1 lần failover nữa để về topology ban đầu (tốn thêm downtime).

Tự động hoá failover

Production thật thường không làm thủ công. Các tool phổ biến:

  • ProxySQL — proxy ở giữa app và DB, tự động re-route khi master die.
  • MaxScale (MariaDB) — proxy chuyên cho MariaDB, tự failover.
  • MHA (Master High Availability) — tool cũ nhưng vẫn dùng nhiều.
  • Orchestrator — quản lý topology, auto-failover, GUI đẹp.

Bài này không cover các tool này — quá phức tạp cho 1 bài. Production lớn nên đầu tư thời gian học 1 trong số đó.


Common issues với replication

1. Replication lag

Lag = slave chậm hơn master bao lâu (giây). Xem qua Seconds_Behind_Master.

Master: tx 1, 2, 3, 4, 5, 6, 7, 8, 9, 10  (đang ở tx 10)
Slave:  tx 1, 2, 3, 4, 5                  (đang ở tx 5)
                                ▲
                          Lag = 5 transactions

Nguyên nhân lag:

  • Slave hardware yếu hơn master.
  • SQL thread single-threaded (default) → write nhiều không kịp replay.
  • Network slow giữa master và slave.
  • Slow query trên slave block SQL thread.

App đọc từ slave có thể thấy stale data — vừa write vào master, đọc lại từ slave chưa thấy. Đây là vấn đề kinh điển của eventual consistency.

Fix lag:

  • Bật parallel replication (multi-thread SQL apply) — MariaDB 10.0+ hỗ trợ.
  • Tách read query không critical sang slave, critical read về master.
  • Upgrade slave hardware.

2. Broken replication

Replication broken khi slave gặp lỗi và Slave_SQL_Running: No:

  • Duplicate key: slave có row mà master đang INSERT (data drift).
  • Missing table: master DROP TABLE chưa replicate, slave query → fail.
  • Binlog gap: master xoá binlog cũ trước khi slave đọc.

Fix tạm thời (skip error):

STOP SLAVE;
SET GLOBAL sql_slave_skip_counter = 1;
START SLAVE;

⚠️ Skip error che bug, không sửa. Production cần investigate root cause + rebuild slave nếu data drift lớn.

3. Master bị bottleneck binlog

Binlog ghi mọi write → master IO tăng. Mitigation:

  • Bật sync_binlog = 0 (nguy hiểm, mất data nếu crash).
  • Move binlog sang disk riêng (SSD).
  • Compression binlog (MySQL 8+).

Phần C — Kết hợp HA Full Stack

Mô hình hoàn chỉnh

Tổng hợp cả 3 lớp HA:

                             ┌──────────────┐
                             │    Client    │
                             └──────┬───────┘
                                    │ https://test.com
                                    │ DNS → VIP
                                    ▼
                          ┌─────────────────┐
                          │ VIP 32.100      │
                          └────────┬────────┘
                                   │
              ┌────────────────────┴────────────────────┐
              │            (Keepalived VRRP)             │
              ▼                                          ▼
   ┌──────────────────┐                       ┌──────────────────┐
   │ Proxy MASTER     │                       │ Proxy BACKUP     │
   │ 192.168.32.135   │ ◄─── VRRP ────────►   │ 192.168.32.136   │
   │ - nginx LB       │     (multicast)       │ - nginx LB       │
   │ - keepalived ⭐  │                       │ - keepalived     │
   └────────┬─────────┘                       └────────┬─────────┘
            │                                          │
            └────────────┬─────────────────────────────┘
                         │ (nginx load balancing)
            ┌────────────┴────────────┐
            ▼                          ▼
   ┌──────────────────┐       ┌──────────────────┐
   │ Backend 1        │       │ Backend 2        │
   │ 192.168.32.133   │       │ 192.168.32.134   │
   │ - nginx          │       │ - nginx          │
   │ - php-fpm        │       │ - php-fpm        │
   │ - WordPress      │       │ - WordPress      │
   └────────┬─────────┘       └────────┬─────────┘
            │                          │
            │ DB connections           │
            │ (writemaster,         │
            │  read → có thể slave)    │
            ▼                          ▼
   ┌──────────────────┐       ┌──────────────────┐
   │ DB MASTER ⭐     │ ────► │ DB SLAVE         │
   │ 192.168.32.137   │binlog │ 192.168.32.138   │
   │ - MariaDB        │       │ - MariaDB        │
   │ - writable       │       │ - read-only      │
   │ - server-id=1    │       │ - server-id=2    │
   └──────────────────┘       └──────────────────┘
        (replication async)

Failover scenarios

Scenario 1: Proxy MASTER die

1. Proxy master die (server tt / nginx crash / network die)
   
2. Backup không nhận VRRP packet trong 3s
   → tự promote thành Master
   → tiếp quản VIP 32.100
   
3. Client tiếp tục access https://test.com
   → DNS vẫn trả VIP 32.100
   → traffic giờ tới proxy backup (192.168.32.136)
   → nginx LB ở backup forward tới backends

⏱️ Downtime: ~3 giây (= 3 × advert_int)

Scenario 2: 1 Backend die

1. Backend 1 die
   
2. Proxy MASTER (nginx LB) phát hiện qua passive health check
   max_fails=3 lần fail liên tiếp trong fail_timeout=30s
   → mark backend 1 = DOWN
   
3. Mọi request tiếp theo → backend 2
   
4. Sau fail_timeout, nginx test lại backend 1
   → nếu UP, mark UP
   → nếu vẫn DOWN, tiếp tục giữ DOWN

⏱️ Downtime: ~vài request (tới khi đủ max_fails)

Scenario 3: DB MASTER die

1. DB master (137) die
   
2. Backend WordPress connect 137 → connect refused / timeout
   → user thấy "Error connecting to database"
   
3. ⚠️ KHÔNG auto failover — phải manual:
   a) SSH vào slave (138)
   b) STOP SLAVE; RESET SLAVE ALL;
   c) SET GLOBAL read_only = OFF;
   d) Update wp-config.php trên cả 2 backend:
      DB_HOST = '192.168.32.138'
   e) Restart php-fpm để pickup config mới
   
⏱️ Downtime: phút (tùy thao tác manual của admin)

Đây là điểm yếu của setup hiện tại. Production cần ProxySQL hoặc MaxScale để auto failover DB.

Scenario 4: Network split-brain

                  ⚡ Network giữa 2 proxy bị cắt ⚡
   ┌──────────────────┐                     ┌──────────────────┐
   │ Proxy 1          │ ────────❌─────────►│ Proxy 2          │
   │ (cũ là master)   │                     │ (cũ là backup)   │
   │ Vẫn nghĩ mình    │                     │ Không nhận VRRP  │
   │ là MASTER        │                     │ → tự promote     │
   │ → giữ VIP 32.100 │                     │ → cũng giành     │
   │                  │                     │   VIP 32.100     │
   └──────────────────┘                     └──────────────────┘
            │                                         │
            │ traffic                                 │ traffic
            ▼                                         ▼
        ⚠️ 2 server cùng claim VIP                   ⚠️
        → ARP conflict
        → traffic chia ngẫu nhiên hoặc fail

Split-brain hiếm nhưng nguy hiểm. Mitigation:

  • Dùng unicast VRRP qua interface độc lập (không qua switch chính).
  • Thêm shared storage hoặc quorum (etcd, ZooKeeper) — overkill cho setup nhỏ.
  • Monitoring chặt chẽ, alert ngay khi phát hiện 2 node cùng có VIP.

Câu chuyện thực tế

Cuối 2024, mình deploy hệ thống e-commerce có HA cho client. Setup đúng như mô hình ở trên: 2 proxy + keepalived, 2 backend, master-slave MySQL.

Demo cho khách: kill proxy master → VIP chuyển sang backup trong 3 giây → site không sập. Khách hài lòng, đưa lên production.

Một tuần sau, alert lúc 3h sáng: site sập hoàn toàn. SSH vào, mọi service đều UP — nginx OK, php-fpm OK, keepalived OK. Nhưng vào https://example.com → timeout.

Check VIP:

ip a | grep 32.100

Cả 2 proxy đều không có VIP! Đây là kịch bản chưa nghĩ tới.

Debug:

journalctl -u keepalived -n 100

Thấy log:

VRRP_Instance(VI_1) Received advert with higher priority 200
VRRP_Instance(VI_1) Entering BACKUP STATE

Có một con server khác trong cùng VLAN đang gửi VRRP với virtual_router_id = 51 và priority 200! Cả master và backup của mình đều trở thành backup, không ai giữ VIP.

Tìm hiểu: team khác trong công ty deploy keepalived cho project khác, dùng trùng virtual_router_id 51 (giống nhau là default trong tutorial). VLAN là shared → VRRP multicast tới tất cả → conflict.

Fix:

  1. Đổi virtual_router_id thành số khác (ví dụ 151) cho project mình.
  2. Đổi auth_pass thành unique string.
  3. Báo team kia cũng chọn VRID khác.
  4. Document VRID đã dùng trong wiki nội bộ.

Bài học:

  1. VRID phải unique trong cùng broadcast domain. 51 là số ai cũng dùng theo tutorial → dễ trùng. Production: dùng VRID đặc biệt như 151, 199.

  2. Authentication không chỉ là security — là cách phân biệt nhóm. Password unique giúp 2 nhóm khác nhau không "nói chuyện" với nhau qua VRRP.

  3. Test failover định kỳ. Không thì lúc cần thật mới biết keepalived bị break.

  4. Monitoring VIP — alert ngay khi không có server nào giữ VIP, hoặc 2 server cùng giữ.


Lab — dựng full HA stack

Setup 6 server:

  • proxy-master: 192.168.32.135 (đã có nginx từ Bài 02)
  • proxy-backup: 192.168.32.136 (cài nginx + keepalived)
  • backend-1: 192.168.32.133 (đã có từ Bài 01-02)
  • backend-2: 192.168.32.134 (đã có từ Bài 02)
  • db-master: 192.168.32.137 (đã có MariaDB)
  • db-slave: 192.168.32.138 (cài MariaDB mới)

Phần A: Setup Keepalived

Trên proxy-master:

sudo dnf install -y keepalived
sudo tee /etc/keepalived/keepalived.conf <<'EOF'
vrrp_instance VI_LEMP {
    state MASTER
    interface ens33
    virtual_router_id 151
    priority 101
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass LempHA2024
    }
    virtual_ipaddress {
        192.168.32.100
    }
    track_script {
        check_nginx
    }
}

vrrp_script check_nginx {
    script "/etc/keepalived/check_nginx.sh"
    interval 2
    weight -20
}
EOF

sudo tee /etc/keepalived/check_nginx.sh <<'EOF'
#!/bin/bash
if systemctl is-active --quiet nginx; then
    exit 0
else
    exit 1
fi
EOF
sudo chmod +x /etc/keepalived/check_nginx.sh

sudo systemctl enable --now keepalived
ip a | grep 32.100   # Phải thấy VIP

Trên proxy-backup (192.168.32.136):

# Cài lại nginx + cấu hình proxy giống Bài 02
sudo dnf install -y nginx keepalived
# ... cấu hình nginx proxy giống proxy-master ...

sudo tee /etc/keepalived/keepalived.conf <<'EOF'
vrrp_instance VI_LEMP {
    state BACKUP
    interface ens33
    virtual_router_id 151
    priority 90
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass LempHA2024
    }
    virtual_ipaddress {
        192.168.32.100
    }
}
EOF

sudo systemctl enable --now keepalived
ip a | grep 32.100   # KHÔNG thấy VIP (đang là backup)

Phần B: Setup MySQL Replication

Trên db-master (137):

sudo tee -a /etc/my.cnf.d/server.cnf <<'EOF'

[mysqld]
server-id = 1
log-bin = /var/lib/mysql/mysql-bin
binlog_format = ROW
bind-address = 0.0.0.0
expire_logs_days = 7
EOF

sudo systemctl restart mariadb
sudo firewall-cmd --permanent --add-port=3306/tcp
sudo firewall-cmd --reload

# Tạo user replication
mariadb -u root -p <<EOF
CREATE USER 'repl'@'192.168.32.138' IDENTIFIED BY 'ReplPass123!';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'192.168.32.138';
FLUSH PRIVILEGES;
FLUSH TABLES WITH READ LOCK;
SHOW MASTER STATUS;
EOF
# LƯU LẠI File và Position!

# Dump data
mysqldump -u root -p --all-databases --master-data=2 > /tmp/master.sql

# Unlock
mariadb -u root -p -e "UNLOCK TABLES;"

# Copy sang slave
scp /tmp/master.sql root@192.168.32.138:/tmp/

Trên db-slave (138):

sudo dnf install -y mariadb-server

sudo tee -a /etc/my.cnf.d/server.cnf <<'EOF'

[mysqld]
server-id = 2
relay-log = /var/lib/mysql/mysql-relay-bin
read_only = ON
bind-address = 0.0.0.0
EOF

sudo systemctl enable mariadb --now

# Import dump
mariadb -u root -p < /tmp/master.sql

# Setup replication
mariadb -u root -p <<EOF
CHANGE MASTER TO
    MASTER_HOST='192.168.32.137',
    MASTER_USER='repl',
    MASTER_PASSWORD='ReplPass123!',
    MASTER_LOG_FILE='mysql-bin.000001',
    MASTER_LOG_POS=<Position từ SHOW MASTER STATUS>;
START SLAVE;
SHOW SLAVE STATUS\G
EOF
# Verify: Slave_IO_Running=Yes, Slave_SQL_Running=Yes

Test toàn bộ

  1. Test VIP failover:

    # Trên proxy-master
    sudo systemctl stop keepalived
    
    # Trên proxy-backup
    ip a | grep 32.100   # Phải thấy VIP đã chuyển
    
    # Client vẫn vào được https://test.com
  2. Test DB replication:

    -- Master
    USE wordpress;
    INSERT INTO wp_options (option_name, option_value) VALUES ('test', 'hello');
    
    -- Slave (chờ vài giây)
    SELECT * FROM wp_options WHERE option_name = 'test';
  3. Test DB failover manual:

    -- Trên slave
    STOP SLAVE;
    RESET SLAVE ALL;
    SET GLOBAL read_only = OFF;
    
    -- Update wp-config.php trên cả 2 backend: DB_HOST = '192.168.32.138'
    -- Restart php-fpm

Pitfalls

  1. VRID trùng nhau giữa nhiều nhóm keepalived trong cùng VLAN → 2 nhóm "tranh" VIP. Dùng VRID unique (vd 151, 199 thay 51).

  2. weight quá nhỏ trong track_script → service die nhưng priority master vẫn cao hơn backup → không failover. Tính toán: weight phải đủ để priority_master - weight < priority_backup.

  3. auth_pass trùng giữa các nhóm → xác thực không phân biệt được. Mỗi project unique password.

  4. Interface name sai (eth0 vs ens33 vs enp0s3) → keepalived không gắn VIP. Check ip a trước.

  5. Multicast bị block trong cloud VPC → VRRP fail. Chuyển sang unicast_peer.

  6. Network die nhưng track_script vẫn OK → không failover dù user không access được. Cấu hình track_script chỉ trên master, hoặc thêm script ping backup.

  7. MySQL replication không có GTID → failover phải tìm đúng file + position, khó manual. Cân nhắc dùng GTID cho production.

  8. Slave có write nhầm → data drift với master → replication broken. Luôn read_only = ON trên slave.

  9. Master xoá binlog cũ trước khi slave đọc → slave fail với "binlog không còn". Tăng expire_logs_days đủ lớn hoặc dùng compressed binlog.

  10. Lag replication không monitor → user write rồi đọc lại không thấy data → bug khó debug. Monitor Seconds_Behind_Master qua Prometheus.

  11. Master die, manual promote slave → quên đổi DB_HOST ở app → site vẫn fail. Tự động hoá qua ProxySQL hoặc DNS update.

  12. Master cũ sống lại không sync → 2 master cùng write → data divergence. Luôn rebuild master cũ thành slave mới.


Tóm tắt

  • Keepalived VRRP quản lý VIP giữa nhiều server. VIP nằm trên Master, chuyển sang Backup khi Master die.
  • Failover ~3 giây (= 3 × advert_int) khi mất VRRP heartbeat.
  • VRID unique trong cùng broadcast domain. Auth password unique cho từng nhóm.
  • Multi-VIP per service với track_script, mỗi service một VIP.
  • weight âm trong track_script để giảm priority master khi service die.
  • MySQL Replication: Master ghi binlog → Slave IO thread đọc → SQL thread replay.
  • Async (default), Semi-sync (đảm bảo data), Sync (Galera, multi-master).
  • Position-based vs GTID — GTID dễ failover hơn, recommend cho production.
  • Master die → manual promote slave (open source). Auto failover cần ProxySQL/MaxScale/Orchestrator.
  • Replication lag = slave chậm hơn master. Monitor Seconds_Behind_Master.
  • Mọi failover phải test định kỳ. Không thì lúc cần thật không biết broken từ lúc nào.

🎓 Tổng kết — Chặng 4: Web Stack & High Availability

Đi qua 3 bài:

  • Bài 01 — LEMP Stack: dựng web service đầu tiên (1 server, 1 stack).
  • Bài 02 — Nginx Reverse Proxy: 1 proxy → N backend, load balance + headers + SSL.
  • Bài 03 — High Availability: Keepalived VRRP + MySQL Replication.

Từ "1 server WordPress chạy" → kiến trúc multi-tier production thật. Mọi pattern bạn vừa học sẽ gặp lại ở K8s — chỉ khác cách config (manifest YAML thay nginx.conf), nguyên lý y hệt:

  • Service / Ingress trong K8s = nginx reverse proxy + load balancer.
  • VIP của LoadBalancer Service = VRRP/Keepalived (cloud LB dưới hood chính là pattern này).
  • StatefulSet với PVC + operator = MySQL replication tự động.
  • HPA, PDB = patterns để đảm bảo HA cho stateless workload.

Học chặng này xong, đọc K8s sẽ thấy quen.

Lúc chưa rõ thì đọc thêm

  • ProxySQL / MaxScale / Orchestrator — auto failover DB.
  • HAProxy — reverse proxy alternative, mạnh ở Layer 4.
  • CARP / pfSync — VRRP alternative trên BSD/pfSense.
  • Galera Cluster — multi-master sync replication cho MariaDB.
  • MySQL Group Replication — version mới của MySQL hỗ trợ multi-master.
  • etcd, Consul — distributed key-value store, dùng cho quorum/leader election.
  • Patroni — auto failover cho PostgreSQL (đáng học song song với MySQL replication).

Lời khuyên cuối

  1. Test failover định kỳ. Mỗi tháng kill 1 server, xem hệ thống có chuyển đúng không. Không thì lúc thật xảy ra mới biết keepalived broken từ tháng trước.

  2. Document VRID, VIP, replication topology. Wiki nội bộ. Đặt vào runbook ai cũng đọc được lúc 3h sáng.

  3. Monitoring là bắt buộc. Prometheus + Grafana monitor:

    • VRRP transition (master → backup).
    • Replication lag (Seconds_Behind_Master).
    • Service uptime ở cả master và backup.
    • 2 server cùng giữ VIP (split-brain detection).
  4. Auto failover phức tạp hơn nhiều so với HA passive. Đầu tư đúng tool (Orchestrator/Patroni) khi project lớn — không tự viết script auto failover trừ khi hiểu sâu.

  5. Backup là cứu cánh cuối cùng. Replication không phải backup — nếu master DROP TABLE, slave cũng mất bảng đó ngay. Backup full + incremental, restore test định kỳ.

Chúc bạn không bao giờ phải nghe khách báo "website tao sập rồi" lúc 3h sáng! 🚀

Minh Hưng

← Bài trước
Bài 02
Bài tiếp theo →
Bài 04
Zalo tư vấn