Bu yazımda sizlere 2 noktadan HAProxy Load Balancer erişimi ile 3 node Patroni PostgreSQL cluster yapısı kurulumunu anlatacağım.

Kuracağımız yapıda 3 adet database sunucusu, 2 adet HAProxy sunucusu bulunacak, sunucularda Debian 12 işletim sistemi mevcut. Ubuntu ve RPM tabanlı diğer dağıtımlar için komutlarda ufak değişiklikler gerekli olabilir.

ETCD

Patroni key value store olarak etcd vb çözümler kullanmakta, biz etcd ile ilerleyeceğiz.

Node 1 üzerinde /etc/default/etcd dosyasının içeriğini değiştiriyoruz;

ETCD_NAME="node-1"
ETCD_DATA_DIR="/var/lib/etcd"

ETCD_INITIAL_ADVERTISE_PEER_URLS="http://<node-1-ip>:2380"
ETCD_LISTEN_PEER_URLS="http://<node-1-ip>:2380"

ETCD_LISTEN_CLIENT_URLS="http://<node-1-ip>:2379,http://127.0.0.1:2379"
ETCD_ADVERTISE_CLIENT_URLS="http://<node-1-ip>:2379"

ETCD_INITIAL_CLUSTER_TOKEN="<token>"
ETCD_INITIAL_CLUSTER="node-1=http://<node-1-ip>:2380,node-2=http://<node-2-ip>:2380,node-3=http://<node-3-ip>:2380"
ETCD_INITIAL_CLUSTER_STATE="new"

Node 2 üzerinde (Node 3'ü de aynı örüntü ile düzenleyebilirsiniz);

ETCD_NAME="node-2"
ETCD_DATA_DIR="/var/lib/etcd"

ETCD_INITIAL_ADVERTISE_PEER_URLS="http://<node-2-ip>:2380"
ETCD_LISTEN_PEER_URLS="http://<node-2-ip>:2380"

ETCD_LISTEN_CLIENT_URLS="http://<node-2-ip>:2379,http://127.0.0.1:2379"
ETCD_ADVERTISE_CLIENT_URLS="http://<node-2-ip>:2379"

ETCD_INITIAL_CLUSTER_TOKEN="<token>"
ETCD_INITIAL_CLUSTER="node-1=http://<node-1-ip>:2380,node-2=http://<node-2-ip>:2380,node-3=http://<node-3-ip>:2380"
ETCD_INITIAL_CLUSTER_STATE="new"

Son olarak bütün sunucularda etcd servislerini sisteme kaydedelim.

sudo systemctl enable --now etcd-server

Buraya kadar hatasız geldiysek etcdctl member list komutu ile şu şekilde bir çıktı almamız gerekiyor;

http://x.x.x.x:2379 is healthy: successfully committed proposal: took = 12.972548ms
http://x.x.x.x:2379 is healthy: successfully committed proposal: took = 13.542307ms
http://x.x.x.x:2379 is healthy: successfully committed proposal: took = 20.649216ms

PostgreSQL

Gerekli yardımcı paketleri kuruyoruz;

sudo apt install gnupg2 curl -y

GPG keyini ve PostgreSQL reposunu ekliyoruz;

curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc|sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/postgresql.gpg
sudo sh -c 'echo "deb https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'

Paket bilgilerini tazeleyip kurulumu sağlıyoruz;

sudo apt update
sudo apt install postgresql-17 -y

Bu aşamada PostgreSQL servisini durdurup data klasörünü siliyoruz.

PostgreSQL process yönetimini ve config ayarlarını Patroni üzerinden yapacağız.

sudo systemctl disable --now postgresql
sudo rm -rf /var/lib/postgresql/*

Patroni

Mevcutta Debian 12 repolarında bulunduğu için patroniyi her üç node'da da direkt kurabiliriz;

sudo apt install patroni -y

Database verilerini /data klasöründe tutmak için klasörü ve izinlerini sağlayalım;

sudo mkdir -p /data
sudo chown -R postgres:postgres /data
sudo chmod -R 700 /data

Patroni için gerekli config dosyasını /etc/patroni/config.yml yolunda Node 1 üzerinde oluşturalım;

PostgreSQL için parameters kısmında yer alan optimizasyon ayarlarını kurduğunuz sunucunun özelliklerini https://pgtune.leopard.in.ua/ adresinde doldurarak kabaca belirleyebilirsiniz.

scope: postgresql-cluster
name: node-1

restapi:
  listen: 0.0.0.0:8008
  connect_address: <node-1-ip>:8008

etcd3:
  host: <node-1-ip>:2379

bootstrap:
  dcs:
    ttl: 30
    loop_wait: 10
    retry_timeout: 10
    postgresql:
      parameters:
        max_connections: 300
        shared_buffers: 4GB
        effective_cache_size: 12GB
        maintenance_work_mem: 1GB
        checkpoint_completion_target: 0.9
        wal_buffers: 16MB
        default_statistics_target: 100
        random_page_cost: 1.1
        effective_io_concurrency: 300
        work_mem: 6990kB
        huge_pages: off
        min_wal_size: 2GB
        max_wal_size: 8GB
        max_worker_processes: 4
        max_parallel_workers_per_gather: 2
        max_parallel_workers: 4
        max_parallel_maintenance_workers: 2

      use_pg_rewind: true
      use_slots: true

  initdb:
    - encoding: UTF8
    - locale: en_US.UTF-8
    - data-checksums

  pg_hba:
    - host all all 0.0.0.0/0 md5
    - host replication repuser 127.0.0.1/32 md5
    - host replication repuser <node-2-ip> md5
    - host replication repuser <node-3-ip> md5

  users:
    postgres:
      password: "<super-user-pass>"
      options:
        - superuser
        - createdb
    repuser:
      password: "<repuser-pass>"
      options:
        - replication

postgresql:
  listen: 0.0.0.0:5432
  connect_address: <node-1-ip>:5432
  data_dir: /data
  bin_dir: /usr/lib/postgresql/17/bin
  authentication:
    superuser:
      username: postgres
      password: "<super-user-pass>"
    replication:
      username: repuser
      password: "<repuser-pass>"
  parameters:
    wal_level: replica
    hot_standby: "on"
    max_wal_senders: 10
    wal_keep_size: 512MB
    synchronous_commit: "off"

tags:
  nofailover: false
  noloadbalance: false
  clonefrom: false

Node 2 üzerinde de aynı yol üzerinde dosyayı oluşturalım;

scope: postgresql-cluster
name: node-2

restapi:
  listen: 0.0.0.0:8008
  connect_address: <node-2-ip>:8008

etcd3:
  host: <node-2-ip>:2379

bootstrap:
  dcs:
    ttl: 30
    loop_wait: 10
    retry_timeout: 10
    postgresql:
      parameters:
        max_connections: 300
        shared_buffers: 4GB
        effective_cache_size: 12GB
        maintenance_work_mem: 1GB
        checkpoint_completion_target: 0.9
        wal_buffers: 16MB
        default_statistics_target: 100
        random_page_cost: 1.1
        effective_io_concurrency: 300
        work_mem: 6990kB
        huge_pages: off
        min_wal_size: 2GB
        max_wal_size: 8GB
        max_worker_processes: 4
        max_parallel_workers_per_gather: 2
        max_parallel_workers: 4
        max_parallel_maintenance_workers: 2

      use_pg_rewind: true
      use_slots: true

  initdb:
    - encoding: UTF8
    - locale: en_US.UTF-8
    - data-checksums

  pg_hba:
    - host all all 0.0.0.0/0 md5
    - host replication repuser 127.0.0.1/32 md5
    - host replication repuser <node-1-ip> md5
    - host replication repuser <node-3-ip> md5

  users:
    postgres:
      password: "<super-user-pass>"
      options:
        - superuser
        - createdb
    repuser:
      password: "<repuser-pass>"
      options:
        - replication

postgresql:
  listen: 0.0.0.0:5432
  connect_address: <node-2-ip>:5432
  data_dir: /data
  bin_dir: /usr/lib/postgresql/17/bin
  authentication:
    superuser:
      username: postgres
      password: "<super-user-pass>"
    replication:
      username: repuser
      password: "<repuser-pass>"
  parameters:
    wal_level: replica
    hot_standby: "on"
    max_wal_senders: 10
    wal_keep_size: 512MB
    synchronous_commit: "off"

tags:
  nofailover: false
  noloadbalance: false
  clonefrom: false

Node 3 için de aynı örüntüyle dosya oluşturduktan sonra sırasıyla sunucularda Patroni servislerini başlatalım;

sudo systemctl enable --now patroni
sudo systemctl restart patroni

Node 1 üzerinde patronictl -c /etc/patroni/config.yml list komutuyla cluster durumunu kontrol ettiğimizde şöyle bir sonuç almalısınız;

+ Cluster: postgresql-cluster ----+---------+----+-----------+
| Member | Host         | Role    | State   | TL | Lag in MB |
+--------+--------------+---------+---------+----+-----------+
| node-1 | x.x.x.x      | Leader  | running |  1 |           |
| node-2 | x.x.x.x      | Replica | running |  1 |         0 |
| node-3 | x.x.x.x      | Replica | running |  1 |         0 |
+--------+--------------+---------+---------+----+-----------+

Bu aşamada replikasyon kurulumu tamamlandı, psql ile ilk sunucuya bağlanıp bir database oluşturduğunuzda diğer node'lar üzerinde de beliriyor olmalı.

HAProxy

Repolarda HAProxy de mevcut;

sudo apt install haproxy -y

İki node üzerinde de /etc/haproxy/haproxy.cfg dosyasını güncelliyoruz;

global
    maxconn 1000

defaults
    log global
    mode tcp
    retries 2
    timeout client 30m
    timeout connect 4s
    timeout server 30m
    timeout check 5s

listen stats
    mode http
    bind *:7000
    stats enable
    stats uri /

listen postgres
    bind *:5432
    option httpchk
    http-check expect status 200
    default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
    server psql-node-1 <node-1-ip>:5432 maxconn 200 check port 8008
    server psql-node-2 <node-2-ip>:5432 maxconn 200 check port 8008
    server psql-node-3 <node-3-ip>:5432 maxconn 200 check port 8008

İki sunucuyu da aktif ediyoruz;

sudo systemctl enable --now haproxy
sudo systemctl restart haproxy

Bu iki sunucunun ip adreslerini connection string'e birlikte eklediğimizde yedekli olarak çalışacaklar. HAProxy servisleri primary sunucuya yönlendirme sağlayacaklar. Örneğin;

jdbc:postgresql://<ha-node-1>:5432,<ha-node-2>:5432/test