Giới thiệu

Khi hệ thống của bạn phát triển, một máy chủ cơ sở dữ liệu duy nhất (Single Node) sẽ nhanh chóng trở thành nút thắt cổ chai về hiệu năng và tiềm ẩn rủi ro sập toàn bộ hệ thống (Single Point of Failure – SPOF). Trong bài viết này, chúng ta sẽ cùng xây dựng một cụm cơ sở dữ liệu PostgreSQL có tính sẵn sàng cao (High Availability), tích hợp cân bằng tải (Load Balancing) và gộp kết nối (Connection Pooling) bằng cách sử dụng Pgpool-II trên môi trường Docker Swarm.

1. Tổng quan Kiến trúc

Pgpool là gì?

Trong bài này, chúng ta sử dụng Pgpool-II đóng vai trò là một phần middleware proxy đứng giữa ứng dụng và cơ sở dữ liệu.

  • Connection Pooling: Pgpool-II giữ sẵn các kết nối tới PostgreSQL, giúp ứng dụng không phải liên tục mở/đóng kết nối, tiết kiệm đáng kể tài nguyên RAM và CPU.
  • Load Balancing: Pgpool-II tự động phân tích câu lệnh SQL. Lệnh Ghi (INSERT/UPDATE/DELETE) sẽ được đẩy vào máy chủ Primary. Lệnh Đọc (SELECT) sẽ được chia đều sang các máy chủ Standby.
  • Tự động Failover: Khi máy chủ Primary sập, hệ thống tự động thăng cấp máy chủ Standby lên thay thế mà không làm gián đoạn ứng dụng.

Kiến trúc và Thành phần của Postgres Cluster

  • PostgreSQL Primary: Nhận lệnh ghi (INSERT, UPDATE, DELETE) và đồng bộ dữ liệu sang nút phụ qua Streaming Replication.
  • PostgreSQL Standby: Nhận dữ liệu đồng bộ từ Primary và xử lý các lệnh đọc (SELECT).
  • Pgpool-II: Đóng vai trò là cổng giao tiếp (proxy) cho ứng dụng, phân tách truy vấn đọc/ghi và theo dõi trạng thái cụm.
  • Docker Swarm Overlay Network: Giúp các container trên các node khác nhau giao tiếp an toàn với nhau.

Tham khảo: Pgpool-II + Watchdog Setup

2. Chuẩn bị môi trường Docker Swarm

Bạn cần một cụm Docker Swarm gồm 3 máy chủ. Để đảm bảo dữ liệu của PostgreSQL không bị trôi nổi khi container khởi động lại, chúng ta cần pin từng container vào một máy chủ cố định thông qua labels.

Chuẩn bị cụm Docker Swarm

Đứng từ Docker node bất kỳ, khởi tạo cụm Swarm và join các node worker bằng lệnh docker swarm join

docker swarm init

Trên node Manager của Docker Swarm, chạy các lệnh sau để dán nhãn cho 3 máy chủ:

docker node update --label-add db=node-1 <tên_node_1>
docker node update --label-add db=node-2 <tên_node_2>
docker node update --label-add db=node-3 <tên_node_3>

Chuẩn bị mạng Overlay

Tạo mạng chung cho các dịch vụ trong Swarm:

docker network create --driver overlay <overlay-network>

3. Cấu hình docker-compose.yml

Chúng ta sẽ sử dụng các image từ kho lưu trữ di sản của Bitnami (bitnamilegacy) để đảm bảo tính ổn định và tương thích lâu dài. Lưu ý đặc biệt ở dịch vụ Pgpool, chúng ta bật biến PGPOOL_FAILOVER_ON_BACKEND_ERROR=yes để đảm bảo cơ chế tự động chuyển đổi dự phòng hoạt động khi có máy chủ báo lỗi.

Tạo tệp docker-compose.yml với nội dung sau:

x-env-postgresql: &env-postgresql
  POSTGRESQL_POSTGRES_PASSWORD: adminpassword
  POSTGRESQL_USERNAME: customuser
  POSTGRESQL_PASSWORD: custompassword
  POSTGRESQL_DATABASE: app_db
  REPMGR_PASSWORD: repmgrpassword
  REPMGR_PRIMARY_HOST: pg-0
  REPMGR_PARTNER_NODES: pg-0,pg-1,pg-2

services:
  pg-0:
    image: bitnamilegacy/postgresql-repmgr:16
    environment:
      <<: *env-postgresql
      REPMGR_NODE_NAME: pg-0
      REPMGR_NODE_NETWORK_NAME: pg-0
    volumes:
      - pg-0-data:/bitnami/postgresql
    deploy:
      placement:
        constraints:
          - node.labels.db == node-1

  pg-1:
    image: bitnamilegacy/postgresql-repmgr:16
    environment:
      <<: *env-postgresql
      REPMGR_NODE_NAME: pg-1
      REPMGR_NODE_NETWORK_NAME: pg-1
    volumes:
      - pg-1-data:/bitnami/postgresql
    deploy:
      placement:
        constraints:
          - node.labels.db == node-2

  pg-2:
    image: bitnamilegacy/postgresql-repmgr:16
    environment:
      <<: *env-postgresql
      REPMGR_NODE_NAME: pg-2
      REPMGR_NODE_NETWORK_NAME: pg-2
    volumes:
      - pg-2-data:/bitnami/postgresql
    deploy:
      placement:
        constraints:
          - node.labels.db == node-3

  pgpool:
    image: bitnamilegacy/pgpool:4
    environment:
      - PGPOOL_BACKEND_NODES=0:pg-0:5432,1:pg-1:5432,2:pg-2:5432
      - PGPOOL_SR_CHECK_USER=customuser
      - PGPOOL_SR_CHECK_PASSWORD=custompassword
      - PGPOOL_ENABLE_LOAD_BALANCING=yes
      - PGPOOL_POSTGRES_USERNAME=customuser
      - PGPOOL_POSTGRES_PASSWORD=custompassword
      - PGPOOL_ADMIN_USERNAME=admin
      - PGPOOL_ADMIN_PASSWORD=adminpassword
      - PGPOOL_ENABLE_WATCHDOG=yes
      - PGPOOL_WATCHDOG_NODES=0:pgpool:9000
      - PGPOOL_FAILOVER_ON_BACKEND_ERROR=yes
    ports:
      - "5432:5432"
    deploy:
      replicas: 3
      endpoint_mode: dnsrr

volumes:
  pg-0-data:
  pg-1-data:
  pg-2-data:

4. Triển khai Hệ thống

Sử dụng lệnh sau để triển khai stack lên Docker Swarm:

docker stack deploy -c docker-compose.yml pgha

Đợi khoảng 1-2 phút để các container khởi động, tiến trình Repmgr tự động nhận diện Master và sao chép dữ liệu (Base Backup) sang các node Standby.

Kiểm tra trạng thái các dịch vụ:

docker service ls

5. Các Kịch bản Kiểm thử (Test Cases)

Kịch bản 1: Kiểm tra trạng thái cụm bằng lệnh PCP

Bạn có thể kiểm tra trực tiếp trạng thái định tuyến của cụm cơ sở dữ liệu bằng cách truy vấn qua Pgpool:

# Kết nối vào một container Pgpool bất kỳ
docker exec -it <ID_CONTAINER_PGPOOL> psql -h localhost -p 5432 -U customuser -d app_db

# Chạy truy vấn trạng thái
app_db=> SHOW pool_nodes;

Kết quả trả về sẽ cho thấy node_id 0, 1, và 2 đang ở trạng thái up.

Kịch bản 2: Kiểm thử Cân bằng tải (Load Balancing)

Mở một terminal để xem log của Pgpool theo thời gian thực:

docker service logs -f pgha_pgpool

Từ terminal dùng DBeaver hoặc psql, thực thi liên tục lệnh đọc:

SELECT * FROM pg_stat_activity;

Quan sát trên màn hình log Pgpool, bạn sẽ thấy nó báo hiệu các truy vấn được đẩy luân phiên qua node_id 1node_id 2, trong khi node_id 0 (Master) không phải xử lý lệnh đọc này.

Kịch bản 3: Giả lập thảm họa (Crash Test)

Chúng ta sẽ mô phỏng tình huống máy chủ Primary bị sập bất ngờ (task kill) như hỏng phần cứng.

# Tìm container pg-0
docker ps | grep pg-0

# Tắt đột ngột container
docker kill <ID_CONTAINER_PG-0>

Quan sát hệ thống:

  1. Tiến trình Watchdog của Pgpool phát hiện lỗi và đồng thuận ngắt kết nối pg-0.
  2. Chạy lại lệnh SHOW pool_nodes;, bạn sẽ thấy pg-0 mang trạng thái down.
  3. Lưu lượng hệ thống sẽ tự động được chuyển hướng sang pg-1 hoặc pg-2 làm Master mới mà ứng dụng không bị rớt mạng.

Kịch bản 4: Phục hồi lại Node bị lỗi (Failback)

Sau khi pg-0 đã được sửa chữa và khởi động lại (docker start <ID_CONTAINER_PG-0>), bạn cần đưa nó trở lại cụm thông qua giao thức điều khiển PCP (Pgpool Control Protocol) bên trong container Pgpool:

# Truy cập bash của container Pgpool
docker exec -it <ID_CONTAINER_PGPOOL> bash

# 1. Tách node an toàn (graceful ngắt các kết nối cũ)
pcp_detach_node -h localhost -p 9898 -U admin -w -n 0 -g

# 2. Gắn lại node vào cluster
pcp_attach_node -h localhost -p 9898 -U admin -w -n 0

Sau bước này, pg-0 sẽ chính thức quay trở lại làm Standby phục vụ cho việc đọc dữ liệu.

Kết luận

Chỉ với một tệp cấu hình Docker duy nhất, chúng ta đã có thể thiết lập một hệ thống PostgreSQL hội tụ đủ 3 yếu tố: Cân bằng tải, Gộp kết nối và Tự động chuyển đổi dự phòng. Hãy cẩn thận khi cấu hình môi trường Production, đặc biệt chú ý đến thuật toán Quorum (Watchdog) phải luôn sử dụng số lượng node lẻ (3, 5, v.v.) để tránh thảm họa Split-brain.

Leave a Reply

This site uses cookies to offer you a better browsing experience. By browsing this website, you agree to our use of cookies.