Cấu hình sao lưu tự động cho toàn bộ cụm PostgreSQL phân tán
Chúng ta cần đảm bảo backup không chỉ bao gồm dữ liệu trong database mà còn bao gồm cả metadata của Raft (log file) và khóa mã hóa TDE.
Tạo script backup tập trung vào Node Leader để đảm bảo tính nhất quán của dữ liệu tại thời điểm snapshot.
1. Tạo script backup an toàn cho Leader Node
Tạo file script tại /usr/local/bin/cluster_backup.sh trên Node Leader. Script này sẽ khóa ghi tạm thời (nếu cần), backup dữ liệu, backup log Raft, và backup file key TDE.
#!/bin/bash
# /usr/local/bin/cluster_backup.sh
# Backup toàn bộ cluster: Data, Raft Log, TDE Key
BACKUP_DIR="/backup/cluster"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_NAME="cluster_backup_${DATE}"
PGDATA="/var/lib/postgresql/data"
RAFT_LOG_DIR="/var/lib/raft/log"
TDE_KEY_FILE="/etc/ssl/private/tde_key.pem"
# Tạo thư mục backup nếu chưa có
mkdir -p "${BACKUP_DIR}/${BACKUP_NAME}"
echo "Bắt đầu backup vào: ${BACKUP_DIR}/${BACKUP_NAME}"
# 1. Backup dữ liệu PostgreSQL (chế độ hot backup)
echo "Đang thực hiện hot backup PostgreSQL..."
pg_basebackup -h 127.0.0.1 -D "${BACKUP_DIR}/${BACKUP_NAME}/pgdata" -Ft -Xs -P -z
# 2. Backup Log Raft (cần thiết để khôi phục trạng thái cluster)
echo "Đang backup log Raft..."
tar -czf "${BACKUP_DIR}/${BACKUP_NAME}/raft_log.tar.gz" -C "${RAFT_LOG_DIR}" .
# 3. Backup Key TDE (BẮT BUỘC phải có để giải mã dữ liệu)
# Lưu ý: Đảm bảo quyền truy cập chỉ dành cho root hoặc backup-admin
echo "Đang backup key TDE..."
cp "${TDE_KEY_FILE}" "${BACKUP_DIR}/${BACKUP_NAME}/tde_key.pem"
chmod 400 "${BACKUP_DIR}/${BACKUP_NAME}/tde_key.pem"
# 4. Backup file cấu hình cluster (postgresql.conf, raft_config.json)
echo "Đang backup cấu hình..."
cp /etc/postgresql/16/main/postgresql.conf "${BACKUP_DIR}/${BACKUP_NAME}/"
cp /etc/raft/raft_config.json "${BACKUP_DIR}/${BACKUP_NAME}/"
echo "Backup hoàn tất. Thư mục: ${BACKUP_DIR}/${BACKUP_NAME}"
Kết quả mong đợi: Một thư mục được tạo với timestamp hiện tại, chứa file tarball của pgdata, file tar.gz của log raft, và file key TDE.
2. Cấu hình Cron để chạy tự động
Thêm task vào crontab của root để chạy script backup mỗi ngày lúc 2 giờ sáng.
crontab -e
Thêm dòng sau vào file crontab:
0 2 * * * /usr/local/bin/cluster_backup.sh >> /var/log/cluster_backup.log 2>&1
Kết quả mong đợi: Task được lưu và cron sẽ tự động thực thi vào thời gian đã định.
3. Kiểm tra và Verify backup
Chạy script thủ công để kiểm tra tính toàn vẹn trước khi vào lịch trình tự động.
/usr/local/bin/cluster_backup.sh
Kiểm tra cấu trúc thư mục:
ls -R /backup/cluster/$(ls -t /backup/cluster | head -n 1)
Kết quả mong đợi: Thấy rõ các file pgdata, raft_log.tar.gz, tde_key.pem và các file config.
Quy trình khôi phục dữ liệu từ backup đã mã hóa
Khôi phục trong môi trường TDE đòi hỏi phải đưa file key mã hóa vào đúng vị trí trước khi khởi động PostgreSQL, nếu không service sẽ không thể mở file dữ liệu.
1. Chuẩn bị môi trường khôi phục
Giả sử toàn bộ cluster bị mất và cần khôi phục từ backup mới nhất trên một server mới (hoặc server cũ đã xóa sạch).
# Xóa dữ liệu cũ (nếu có)
rm -rf /var/lib/postgresql/data/*
rm -rf /var/lib/raft/log/*
rm -rf /etc/postgresql/16/main/postgresql.conf
Tạo lại thư mục cần thiết:
mkdir -p /var/lib/postgresql/data
mkdir -p /var/lib/raft/log
mkdir -p /etc/postgresql/16/main
mkdir -p /etc/ssl/private
2. Khôi phục Key TDE (BƯỚC QUAN TRỌNG NHẤT)
Trước khi giải nén dữ liệu, phải đặt lại file key TDE vào đúng đường dẫn cấu hình.
# Giả sử backup mới nhất là thư mục cluster_backup_20231027_020000
LATEST_BACKUP=$(ls -t /backup/cluster | head -n 1)
cp "/backup/cluster/${LATEST_BACKUP}/tde_key.pem" /etc/ssl/private/tde_key.pem
chmod 600 /etc/ssl/private/tde_key.pem
chown postgres:postgres /etc/ssl/private/tde_key.pem
Kết quả mong đợi: File key tồn tại tại /etc/ssl/private/tde_key.pem với quyền 600 và owner là postgres.
3. Khôi phục dữ liệu PostgreSQL và cấu hình
Giải nén dữ liệu và file cấu hình vào đúng vị trí.
cd /backup/cluster/${LATEST_BACKUP}
# Khôi phục cấu hình
cp postgresql.conf /etc/postgresql/16/main/
cp raft_config.json /etc/raft/
# Giải nén dữ liệu (pg_basebackup tạo ra tarball)
tar -xzf pgdata/base.tar.gz -C /var/lib/postgresql/data/
tar -xzf pgdata/global.tar.gz -C /var/lib/postgresql/data/
# Lưu ý: Cần giải nén tất cả các folder trong pgdata, ví dụ:
for dir in pgdata/*/; do
tar -xzf "${dir}base.tar.gz" -C /var/lib/postgresql/data/ 2>/dev/null
done
# Đảm bảo quyền sở hữu
chown -R postgres:postgres /var/lib/postgresql/data/
4. Khôi phục Log Raft
Giải nén log để khôi phục trạng thái của cluster.
tar -xzf raft_log.tar.gz -C /var/lib/raft/log/
chown -R postgres:postgres /var/lib/raft/log/
Kết quả mong đợi: Thư mục /var/lib/raft/log chứa các file log của cluster.
5. Khởi động lại dịch vụ
Khởi động PostgreSQL. Với TDE, nếu key đúng, database sẽ mở bình thường. Nếu sai, service sẽ crash.
systemctl restart postgresql@16-main
systemctl status postgresql@16-main
Kết quả mong đợi: Service chạy (active (running)) và log không có lỗi "decryption failed" hay "invalid key".
Mô phỏng mất mát nút và khôi phục Node mới
Khi một Node trong cụm Raft bị hỏng hoàn toàn (mất disk, mất máy), ta cần thêm Node mới vào cụm và đồng bộ dữ liệu từ các Node còn lại.
1. Mô phỏng mất Node (Node 3)
Xóa Node 3 khỏi cấu hình cluster (thực hiện trên Leader hoặc Node còn lại có quyền vote).
# Trên Node Leader (ví dụ Node 1)
# Giả sử sử dụng tool quản lý Raft hoặc command line tương ứng
# Ví dụ: raftctl remove-node 3
# Nếu dùng pg_hba.conf để hạn chế, cập nhật file đó
sed -i '/node3_ip/d' /etc/postgresql/16/main/pg_hba.conf
systemctl reload postgresql@16-main
Kiểm tra trạng thái cluster:
psql -h localhost -U postgres -c "SELECT * FROM raft_status();"
Kết quả mong đợi: Node 3 không còn xuất hiện trong danh sách active nodes, cluster vẫn hoạt động với 2 nodes còn lại (nếu quorum vẫn đạt).
2. Cài đặt Node mới (Node 3 mới)
Cài đặt PostgreSQL và Raft trên máy mới, nhưng chưa khởi động tham gia vào cluster ngay.
# Trên Node 3 mới
apt update && apt install postgresql postgresql-contrib
# Tạo cấu trúc thư mục
mkdir -p /var/lib/raft/log
# Copy file TDE Key từ backup sang Node mới
scp /etc/ssl/private/tde_key.pem root@node3_new:/etc/ssl/private/
3. Thêm Node mới vào Cluster Raft
Thực hiện lệnh join cluster từ Node Leader để Node mới đồng bộ dữ liệu (Snapshot) từ Leader.
# Trên Node Leader
# Command giả định để thêm node vào cluster Raft
# Cú pháp thường là: raftctl add-node
raftctl add-node 3 192.168.1.103
# Nếu dùng cơ chế replication của PostgreSQL (Stream Replication) để sync
# Cấu hình replication slot trên Leader
psql -c "SELECT pg_create_physical_replication_slot('slot_node3');"
# Cấu hình trên Node 3 mới để bắt đầu sync
# File recovery.conf hoặc postgresql.conf trên Node 3
Trên Node 3 mới, cấu hình postgresql.conf:
primary_conninfo = 'host=192.168.1.101 port=5432 user=replicator password=strong_password'
Khởi động Node 3 mới:
systemctl start postgresql@16-main
Kết quả mong đợi: Node 3 bắt đầu quá trình streaming hoặc snapshot từ Leader, trạng thái chuyển từ "initializing" sang "promoted" hoặc "follower".
4. Xác nhận Node mới đã đồng bộ
Kiểm tra lại trạng thái cluster để đảm bảo Node 3 đã tham gia và dữ liệu nhất quán.
psql -h localhost -U postgres -c "SELECT node_id, role, status FROM raft_nodes;"
Kết quả mong đợi: Node 3 xuất hiện với vai trò "Voter" hoặc "Follower" và trạng thái "Online/Synced".
Kiểm tra tính toàn vẹn dữ liệu sau khi khôi phục
Sau khi khôi phục backup và thêm node mới, cần đảm bảo dữ liệu không bị hư hỏng (corruption) do quá trình mã hóa/giải mã hoặc đồng bộ.
1. Kiểm tra checksum của dữ liệu
Chạy công cụ kiểm tra tính toàn vẹn của PostgreSQL.
pg_checksums --data-directory=/var/lib/postgresql/data --check
Nếu bạn đã bật checksum trong cấu hình ban đầu (data_checksums = on), lệnh này sẽ báo lỗi nếu có bit nào bị thay đổi.
Kết quả mong đợi: Không có dòng lỗi nào xuất hiện, thông báo "data checksum verification complete".
2. Kiểm tra dữ liệu thực tế
Chạy query kiểm tra số lượng bản ghi và tổng giá trị (checksum logic) của bảng quan trọng.
psql -h localhost -U postgres -d your_database -c "
SELECT
table_name,
COUNT(*) as row_count,
MD5(string_agg(column_name, '')) as data_hash
FROM information_schema.tables
JOIN your_table ON table_name = your_table.table_name
WHERE table_schema = 'public'
GROUP BY table_name;
"
So sánh kết quả này với báo cáo backup trước khi sự cố xảy ra.
3. Kiểm tra tính nhất quán của Raft
Đảm bảo mọi node trong cụm đều có cùng một Commit Index (mức độ đồng bộ).
psql -h localhost -U postgres -c "
SELECT node_id, last_log_index, last_log_term
FROM raft_status
ORDER BY node_id;
"
Kết quả mong đợi: Tất cả các node (bao gồm node mới khôi phục) có cùng giá trị last_log_index và last_log_term.
4. Kiểm tra khả năng đọc ghi với TDE
Thực hiện thao tác INSERT và SELECT để đảm bảo việc mã hóa/giải mã hoạt động bình thường trên dữ liệu mới đồng bộ.
psql -h localhost -U postgres -d your_database -c "
INSERT INTO your_table (id, secret_data) VALUES (999, 'test_encryption_data');
SELECT * FROM your_table WHERE id = 999;
DELETE FROM your_table WHERE id = 999;
"
Kết quả mong đợi: Chạy thành công không có lỗi, dữ liệu hiển thị đúng (hoặc dạng hash nếu view đã được mã hóa) và không bị lỗi "decryption error".
Điều hướng series:
Mục lục: Series: Xây dựng Database phân tán an toàn với PostgreSQL, Raft và TDE
« Phần 6: Bảo mật nâng cao: Quản lý truy cập và Audit trong hệ thống phân tán
Phần 8: Giám sát, Troubleshooting và tối ưu hiệu năng thực tế »