PostgreSQL - High Availability Database Concept

PostgreSQL - HA Concept

1. Giới thiệu

2. Các công nghệ sử dụng

Như danh sách trên có thể thấy, chúng ta sẽ kết hợp nhiều thành phần với các vai trò khác nhau để tạo thành 1 solution cần thiết. Mình sử dụng OS Ubuntu server 22.04 LTS vì hiện tại đây là version thông dụng và stable, các bản Ubuntu 23 và 24 hiện tại ý kiến cá nhân mình thấy vẫn còn chưa rộng rãi các package và chưa stable. Trong bài viết này mình sẽ triển khai trên Docker, nếu cần chịu tải cao hơn các bạn có thể triển khai với systemd.

3. Mô hình triển khai PostgreSQL Master-Slave

4. Các bước cài đặt và cấu hình

4.1. Cài đặt Docker service và Docker-compose

Note:

Thực hiện trên cả 2 server

4.2. Cấu hình PostgreSQL Replicate Master/Slave sử dụng Docker-compose

Note:

Thực hiện trên cả 2 server.

Mỗi server đều có 2 file yaml để start container PostgresSQL Master và Slave.

mkdir -p /opt/docker/postgresql/data
chown -R 1001.1001 /opt/docker/postgresql/

touch /opt/docker/postgresql/docker-compose-postgresql-master.yaml
touch /opt/docker/postgresql/docker-compose-postgresql-slave.yaml


# docker-compose-postgresql-master.yaml on PostgreSQL-01

services:

  postgresql:
    image: bitnami/postgresql:latest
    #image: bitnami/postgresql:12.4.0
    container_name: postgresql
    hostname: "postgresql"
    restart: always
    environment:

      ##### start container as Master
      - POSTGRESQL_REPLICATION_MODE=master
      - POSTGRESQL_REPLICATION_USER=repl_user
      - POSTGRESQL_REPLICATION_PASSWORD=replpwd
      - POSTGRESQL_USERNAME=gitlab
      - POSTGRESQL_PASSWORD=7JG9z9L*e*jy5
      - POSTGRESQL_DATABASE=gitlab_production
      - POSTGRESQL_POSTGRES_PASSWORD=5U2vE*18O52JVnpB

    volumes:
      - "/opt/docker/postgresql/data:/bitnami/postgresql/data"
    ports:
      - 5432:5432
    extra_hosts:
           - postgresql-01:192.168.161.11
           - postgresql-02:192.168.161.12


# docker-compose-postgresql-slave.yaml on PostgreSQL-01

services:

  postgresql:
    image: bitnami/postgresql:latest
    #image: bitnami/postgresql:12.4.0
    container_name: postgresql
    hostname: "postgresql"
    restart: always
    environment:

      ##### start container as Slave
      - POSTGRESQL_REPLICATION_MODE=slave
      - POSTGRESQL_REPLICATION_USER=repl_user
      - POSTGRESQL_REPLICATION_PASSWORD=replpwd
      - POSTGRESQL_MASTER_HOST=postgresql-02
      - POSTGRESQL_MASTER_PORT_NUMBER=5432
      - POSTGRESQL_PASSWORD=5U2vE*18O52JVnpB

    volumes:
      - "/opt/docker/postgresql/data:/bitnami/postgresql/data"
    ports:
      - 5432:5432
    extra_hosts:
           - postgresql-01:192.168.161.11
           - postgresql-02:192.168.161.12


# docker-compose-postgresql-master.yaml on PostgreSQL-02

services:

  postgresql:
    image: bitnami/postgresql:latest
    #image: bitnami/postgresql:12.4.0
    container_name: postgresql
    hostname: "postgresql"
    restart: always
    environment:

      ##### start container as Master
      - POSTGRESQL_REPLICATION_MODE=master
      - POSTGRESQL_REPLICATION_USER=repl_user
      - POSTGRESQL_REPLICATION_PASSWORD=replpwd
      - POSTGRESQL_USERNAME=gitlab
      - POSTGRESQL_PASSWORD=7JG9z9L*e*jy5
      - POSTGRESQL_DATABASE=gitlab_production
      - POSTGRESQL_POSTGRES_PASSWORD=5U2vE*18O52JVnpB

    volumes:
      - "/opt/docker/postgresql/data:/bitnami/postgresql/data"
    ports:
      - 5432:5432
    extra_hosts:
           - postgresql-01:192.168.161.11
           - postgresql-02:192.168.161.12


# docker-compose-postgresql-slave.yaml on PostgreSQL-02

services:

  postgresql:
    image: bitnami/postgresql:latest
    #image: bitnami/postgresql:12.4.0
    container_name: postgresql
    hostname: "postgresql"
    restart: always
    environment:

      ##### start container as Slave
      - POSTGRESQL_REPLICATION_MODE=slave
      - POSTGRESQL_REPLICATION_USER=repl_user
      - POSTGRESQL_REPLICATION_PASSWORD=replpwd
      - POSTGRESQL_MASTER_HOST=postgresql-01
      - POSTGRESQL_MASTER_PORT_NUMBER=5432
      - POSTGRESQL_PASSWORD=5U2vE*18O52JVnpB

    volumes:
      - "/opt/docker/postgresql/data:/bitnami/postgresql/data"
    ports:
      - 5432:5432
    extra_hosts:
           - postgresql-01:192.168.161.11
           - postgresql-02:192.168.161.12



docker-compose -f /opt/docker/postgresql/docker-compose-postgresql-master.yaml up -d --force-recreate postgresql
docker-compose -f /opt/docker/postgresql/docker-compose-postgresql-slave.yaml up -d --force-recreate postgresql


psql -U postgres -p 5432

postgres=# select usename,application_name,client_addr,backend_start,state,sync_state from pg_stat_replication ;

4.3. Cài đặt và cấu hình Keepalived tự động chuyển đổi dự phòng cho PostgreSQL Replicate Master/Slave

Note:

Thực hiện trên cả 2 server.

apt install keepalived -y 
#!/bin/bash

# /etc/keepalived/keepalived_control_failover.sh
# keepalived_control_failover.sh On server PostgreSQL-01


STATE=$3
peer='192.168.161.12'
ipaddr=`/sbin/ifconfig ens33 | awk -F ' *|:' '/inet /{print $3}'`
logs='/etc/keepalived/keepalived_control_failover.log'
echo "$(date +'%Y-%m-%d %H:%M:%S') $(hostname) $(hostname -I), STATE: ${STATE} " >>  $logs

sendTelegram(){
        curl -s -X POST --data chat_id=-xxxxx --data text="$1" "https://api.telegram.org/botxxxxxxx:Axxxxx/sendMessage" 
}


case $STATE in
        "MASTER") echo "$(date +'%Y-%m-%d %H:%M:%S') trigger postgresql container to master" >>  $logs
                  /usr/bin/docker exec postgresql touch /tmp/postgresql.trigger.5432
                  echo "$(date +'%Y-%m-%d %H:%M:%S') recreate postgresql container with Master config" >>  $logs
                  /usr/bin/docker-compose -f /opt/docker/postgresql/docker-compose-postgresql-master.yaml up -d --force-recreate postgresql
                  sendTelegram "❗ PostgreSQL Failover Switching ❗ %0A alert on $(hostname) %0A - Hostname : $(hostname) %0A - STATE : ${STATE} %0A - IP : ${ipaddr} "
                  exit 0
                  ;;

        "BACKUP") echo "$(date +'%Y-%m-%d %H:%M:%S') stop postgresql container" >>  $logs
                  /usr/bin/docker stop postgresql
                  echo "$(date +'%Y-%m-%d %H:%M:%S') rsync data from Master " >>  $logs
                  echo 'xxxxx' | rsync -av root@$peer:/opt/docker/postgresql/data/ /opt/docker/postgresql/data/
                  echo "$(date +'%Y-%m-%d %H:%M:%S') recreate postgresql container with Slave config" >>  $logs
                  /usr/bin/docker-compose -f /opt/docker/postgresql/docker-compose-postgresql-slave.yaml up -d --force-recreate postgresql
                  sendTelegram "❗ PostgreSQL Failover Switching ❗ %0A alert on $(hostname) %0A - Hostname : $(hostname) %0A - STATE : ${STATE} %0A - IP : ${ipaddr} "
                  exit 0
                  ;;

        *)        echo "unknown state"
                  echo "$(date +'%Y-%m-%d %H:%M:%S') unknown state to process!!!" >>  $logs
                  exit 0
                  ;;
esac
#!/bin/bash

# /etc/keepalived/keepalived_control_failover.sh
# keepalived_control_failover.sh On server PostgreSQL-02


STATE=$3
peer='192.168.161.11'
ipaddr=`/sbin/ifconfig ens33 | awk -F ' *|:' '/inet /{print $3}'`
logs='/etc/keepalived/keepalived_control_failover.log'
echo "$(date +'%Y-%m-%d %H:%M:%S') $(hostname) $(hostname -I), STATE: ${STATE} " >>  $logs

sendTelegram(){
        curl -s -X POST --data chat_id=-xxxxx --data text="$1" "https://api.telegram.org/botxxxxxxx:Axxxxx/sendMessage" 
}


case $STATE in
        "MASTER") echo "$(date +'%Y-%m-%d %H:%M:%S') trigger postgresql container to master" >>  $logs
                  /usr/bin/docker exec postgresql touch /tmp/postgresql.trigger.5432
                  echo "$(date +'%Y-%m-%d %H:%M:%S') recreate postgresql container with Master config" >>  $logs
                  /usr/bin/docker-compose -f /opt/docker/postgresql/docker-compose-postgresql-master.yaml up -d --force-recreate postgresql
                  sendTelegram "❗ PostgreSQL Failover Switching ❗ %0A alert on $(hostname) %0A - Hostname : $(hostname) %0A - STATE : ${STATE} %0A - IP : ${ipaddr} "
                  exit 0
                  ;;

        "BACKUP") echo "$(date +'%Y-%m-%d %H:%M:%S') stop postgresql container" >>  $logs
                  /usr/bin/docker stop postgresql
                  echo "$(date +'%Y-%m-%d %H:%M:%S') rsync data from Master " >>  $logs
                  echo 'xxxxx' | rsync -av root@$peer:/opt/docker/postgresql/data/ /opt/docker/postgresql/data/
                  echo "$(date +'%Y-%m-%d %H:%M:%S') recreate postgresql container with Slave config" >>  $logs
                  /usr/bin/docker-compose -f /opt/docker/postgresql/docker-compose-postgresql-slave.yaml up -d --force-recreate postgresql
                  sendTelegram "❗ PostgreSQL Failover Switching ❗ %0A alert on $(hostname) %0A - Hostname : $(hostname) %0A - STATE : ${STATE} %0A - IP : ${ipaddr} "
                  exit 0
                  ;;

        *)        echo "unknown state"
                  echo "$(date +'%Y-%m-%d %H:%M:%S') unknown state to process!!!" >>  $logs
                  exit 0
                  ;;
esac
# /etc/keepalived/keepalived.conf 
global_defs {
  enable_script_security
  script_user root 
}

vrrp_script chk_postgresql {
    script "/usr/bin/nc -zv localhost 5432"
    interval 2
    weight 3
}

vrrp_instance VIP_1 {
    interface ens33
    state MASTER
    virtual_router_id 60
    priority 100
    authentication {
        auth_type PASS
        auth_pass 3Bj1KiCoLBYbmUxy
    }
    virtual_ipaddress {
        192.168.161.10/24
    }
    track_script {
        chk_postgresql
    }

    notify "/etc/keepalived/keepalived_control_failover.sh"
}
# /etc/keepalived/keepalived.conf 

global_defs {
  enable_script_security
  script_user root 
}

vrrp_script chk_postgresql {
    script "/usr/bin/nc -zv localhost 5432"
    interval 2
    weight 3
}

vrrp_instance VIP_1 {
    interface ens33
    state BACKUP
    virtual_router_id 60
    priority 100
    authentication {
        auth_type PASS
        auth_pass 3Bj1KiCoLBYbmUxy
    }
    virtual_ipaddress {
        192.168.161.10/24
    }
    track_script {
        chk_postgresql
    }

    notify "/etc/keepalived/keepalived_control_failover.sh"
}
chmod +x /etc/keepalived/keepalived_control_failover.sh

systemctl enable keepalived.service
systemctl restart keepalived.service

journalctl -u keepalived | tail -n 100