Cài đặt & cấu hình MongoDB Replica Set: Tự động Failover chuẩn Production

Database tutorial - IT technology blog
Database tutorial - IT technology blog

1. Bài học xương máu: Khi MongoDB đơn độc giữa đêm

3 giờ sáng, điện thoại réo liên tục vì server MongoDB sập nguồn. Toàn bộ API tê liệt, khách hàng không thể thanh toán. Khi cân nhắc kiến trúc giữa SQL vs NoSQL và lựa chọn MongoDB, nếu từng rơi vào cảnh này, bạn sẽ hiểu cảm giác bất an khi phó mặc môi trường production cho một node standalone duy nhất.

Một thanh RAM lỗi hay ổ cứng đầy 100% là đủ đánh sập toàn bộ dịch vụ (SPOF). Giải pháp duy nhất: phân tán dữ liệu sang nhiều node độc lập, tương tự nguyên lý của PostgreSQL Streaming Replication để đồng bộ liên tục giữa các server.

Mô hình tối thiểu chuẩn production cần 3 node:

  • 1 Primary Node: Xử lý toàn bộ lệnh ghi (write) và mặc định gánh luôn thao tác đọc (read). Mọi thao tác thay đổi dữ liệu sẽ được lưu tuần tự vào một log nội bộ có tên oplog (operations log).
  • 2 Secondary Nodes: Liên tục kéo (pull) oplog từ Primary về áp dụng vào data của mình. Nếu Primary đột tử, cụm chỉ mất 2 đến 12 giây để bầu một Secondary có dữ liệu mới nhất lên thay thế.

Tại sao luôn là số lẻ như 3, 5, hoặc 7? Khi xảy ra sự cố đứt cáp mạng giữa các datacenter (network partition), cụm cần đạt quá bán số phiếu (n/2 + 1) để bầu Primary. Nếu chỉ có 2 node, khi đứt kết nối mỗi bên chỉ giữ đúng 50% số phiếu. Kết quả: không node nào đủ điều kiện lên làm Primary, hệ thống kẹt cứng. Cơ chế bầu chọn theo đa số (quorum) này cũng tương tự như khi triển khai Redis Sentinel để đạt tính sẵn sàng cao.

2. Chuẩn bị hạ tầng & Cài đặt

Trong bài lab này, chúng ta dùng 3 VPS Ubuntu 22.04 LTS (cấu hình khuyến nghị từ 2 vCPU / 4GB RAM trở lên) thuộc cùng dải mạng riêng tư:

  • node1.db.internal – 10.0.0.11 (Node chính)
  • node2.db.internal – 10.0.0.12 (Node phụ 1)
  • node3.db.internal – 10.0.0.13 (Node phụ 2)

Bước 2.1: Đồng bộ phân giải hostname

Đừng hardcode IP trực tiếp vào cấu hình database. Hãy map hostname qua file /etc/hosts trên cả 3 máy để dễ bảo trì khi đổi hạ tầng mạng:

sudo bash -c 'cat <<EOF >> /etc/hosts
10.0.0.11 node1.db.internal node1
10.0.0.12 node2.db.internal node2
10.0.0.13 node3.db.internal node3
EOF'

Bước 2.2: Cài đặt MongoDB 7.0

Chạy cụm lệnh sau lần lượt trên cả 3 server để lấy bản chính thức từ kho lưu trữ của MongoDB:

# Import GPG key chính thức
curl -fsSL https://www.mongodb.org/static/pgp/server-7.0.asc | \
   sudo gpg -o /usr/share/keyrings/mongodb-server-7.0.gpg --dearmor

# Thêm repository cho Ubuntu 22.04 (Jammy)
echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-7.0.gpg ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/7.0 multiverse" | \
   sudo tee /etc/apt/sources.list.d/mongodb-org-7.0.list

# Cập nhật và cài đặt
sudo apt update && sudo apt install -y mongodb-org

# Kích hoạt service chạy cùng hệ điều hành
sudo systemctl enable mongod

3. Thiết lập bảo mật Keyfile và Khởi tạo cụm

Tuyệt đối không chạy Replica Set mà để trống bảo mật. Các node cần một chìa khóa chung (Keyfile) để xác thực lẫn nhau trước khi cho phép trao đổi dữ liệu.

Bước 3.1: Sinh Keyfile xác thực nội bộ

Đứng trên node1, tạo chuỗi ngẫu nhiên 756 bytes rồi phân quyền nghiêm ngặt:

sudo mkdir -p /var/lib/mongodb-key
openssl rand -base64 756 | sudo tee /var/lib/mongodb-key/mongo-keyfile
sudo chown -R mongodb:mongodb /var/lib/mongodb-key
sudo chmod 400 /var/lib/mongodb-key/mongo-keyfile

Quyền 400 (chỉ cho phép user mongodb đọc) là bắt buộc. Nếu để quyền quá mở như 777, tiến trình mongod sẽ từ chối khởi động ngay lập tức.

Dùng scp đẩy file này sang node2 và node3:

# Copy sang node2 và node3
scp /var/lib/mongodb-key/mongo-keyfile [email protected]:/tmp/
scp /var/lib/mongodb-key/mongo-keyfile [email protected]:/tmp/

Sau đó trên cả node2 và node3, di chuyển file vào đúng thư mục rồi gán lại quyền chuẩn:

sudo mkdir -p /var/lib/mongodb-key
sudo mv /tmp/mongo-keyfile /var/lib/mongodb-key/
sudo chown -R mongodb:mongodb /var/lib/mongodb-key
sudo chmod 400 /var/lib/mongodb-key/mongo-keyfile

Bước 3.2: Chỉnh sửa file mongod.conf

Mở file /etc/mongod.conf trên từng máy. Sửa lại 3 khối cấu hình sau:

# /etc/mongod.conf

net:
  port: 27017
  bindIp: 127.0.0.1,node1.db.internal # Thay bằng hostname tương ứng của từng node

security:
  keyFile: /var/lib/mongodb-key/mongo-keyfile
  authorization: enabled

replication:
  replSetName: "rs0"

Nhớ giữ lại 127.0.0.1 trong bindIp để công cụ dòng lệnh nội bộ kết nối được. Sửa xong, khởi động lại dịch vụ trên cả 3 node:

sudo systemctl restart mongod

Bước 3.3: Kích hoạt cụm và tạo tài khoản Root

Nhờ cơ chế Localhost Exception của MongoDB, bạn vẫn có thể truy cập từ chính node1 để tạo user đầu tiên mà chưa cần đăng nhập:

mongosh --port 27017

Kích hoạt cụm bằng lệnh rs.initiate():

rs.initiate({
  _id: "rs0",
  members: [
    { _id: 0, host: "node1.db.internal:27017", priority: 2 },
    { _id: 1, host: "node2.db.internal:27017", priority: 1 },
    { _id: 2, host: "node3.db.internal:27017", priority: 1 }
  ]
});

Chỉ số priority: 2 báo cho cụm ưu tiên đưa node1 lên làm Primary nếu nó còn sống. Sau 2-3 giây bấm Enter, prompt sẽ đổi từ rs0 [direct: other] test> sang rs0 [direct: primary] test>.

Lúc này, hãy tạo ngay tài khoản quản trị tối cao:

use admin;
db.createUser({
  user: "siteRootAdmin",
  pwd: "MatKhauSieuKho123!@#",
  roles: [ { role: "root", db: "admin" } ]
});

Kể từ bước này, mọi phiên mở shell đều bắt buộc phải đăng nhập:

mongosh -u siteRootAdmin -p --authenticationDatabase admin

Bước 3.4: Bơm dữ liệu mẫu kiểm tra đồng bộ

Khi dựng môi trường staging hoặc test hiệu năng, team dev thường cần import sẵn vài chục ngàn dòng dữ liệu từ file Excel/CSV của khách hàng. Nếu cần chuyển đổi nhanh CSV sang JSON, bạn có thể ghé toolcraft.app/vi/tools/data/csv-to-json — tool này parse trực tiếp trên trình duyệt bằng JavaScript nên không sợ rò rỉ dữ liệu nhạy cảm ra ngoài internet.

Có file JSON trong tay, dùng mongoimport để nạp nhanh vào database analytics:

mongoimport --uri="mongodb://siteRootAdmin:MatKhauSieuKho123!%40%[email protected]:27017/analytics?authSource=admin" \
  --collection=users --file=data.json --jsonArray

4. Đánh giá sức khỏe và Diễn tập Failover

Bước 4.1: Kiểm tra độ trễ nhân bản (Replication Lag)

Lệnh kinh điển nhất để khám bệnh cho cụm là rs.status(). Tuy nhiên để kiểm tra nhanh lag giữa các node, hãy gõ:

rs.printSecondaryReplicationInfo();

Màn hình sẽ hiển thị số giây lệch giữa Secondary và Primary. Nếu con số này nhảy lên hơn 5-10 giây, bạn cần rà soát lại băng thông mạng hoặc kiểm tra xem ổ cứng có đang bị quá tải I/O (IOPS bottleneck) hay không. Hiện tượng này cũng tương tự như replication lag khi cấu hình MySQL Replication với GTID trên môi trường production.

Ngoài ra, chú ý trường stateStr trong lệnh rs.status(). Nếu node nào bị kẹt ở trạng thái STARTUP2 hay RECOVERING, node đó đang bận đồng bộ dữ liệu ban đầu và chưa thể tham gia bầu cử hay nhận truy vấn.

Bước 4.2: Diễn tập ngắt điện Primary

Hệ thống có thực sự tự đứng dậy được không? Cách duy nhất để biết là tự tay “rút phích cắm”.

Hãy hạ dịch vụ MongoDB trên node1:

# Thực hiện trên node1
sudo systemctl stop mongod

Ngay lập tức, mở terminal ở node2 và đăng nhập vào shell:

mongosh -u siteRootAdmin -p --authenticationDatabase admin

Chỉ mất chừng 2 đến 4 giây, prompt trên node2 đã chuyển thành rs0 [direct: primary] test>. Quá trình bầu cử diễn ra hoàn toàn tự động (tương tự như cụm MySQL Group Replication tự động failover), ứng dụng vẫn ghi dữ liệu bình thường mà không cần kỹ sư can thiệp bằng tay.

Giờ hãy bật lại node1:

# Thực hiện trên node1
sudo systemctl start mongod

Vừa khởi động lại, node1 sẽ tự động hạ mình xuống làm SECONDARY và kéo bù những bản ghi bị thiếu. Nhờ có priority: 2, sau khi dữ liệu bắt kịp hoàn toàn, node1 sẽ chủ động kích hoạt một cuộc chuyển giao êm đềm (graceful stepdown) từ node2 để giành lại vai trò Primary.

Bước 4.3: Viết Connection String chuẩn cho Application

Sai lầm phổ biến nhất của lập trình viên là trỏ kết nối vào đúng một IP đơn lẻ. Chuỗi kết nối của bạn trong code (Node.js, Go, Python, Spring Boot…) phải liệt kê đủ các thành viên kèm theo tham số cụm:

mongodb://siteRootAdmin:[email protected]:27017,node2.db.internal:27017,node3.db.internal:27017/analytics?replicaSet=rs0&authSource=admin&w=majority&readPreference=primaryPreferred

Hai cờ quan trọng bạn cần nhớ:

  • w=majority: Bắt buộc quá bán số node (ít nhất 2/3 node) xác nhận đã ghi dữ liệu vào disk trước khi driver trả về response thành công. Cờ này bảo vệ bạn khỏi mất dữ liệu khi Primary đột tử giữa chừng.
  • readPreference=primaryPreferred: Mặc định đọc từ Primary để đảm bảo tính nhất quán cao nhất, nhưng nếu Primary tạm thời ngắt kết nối thì tự động route truy vấn đọc sang Secondary thay vì trả lỗi 500 cho người dùng.
Share: