Patroni ile PostgreSQL Yüksek Erişilebilirlik Kümesi Kurulumu

Veritabanı yönetiminde en büyük kabuslardan biri şudur: Gece 2’de PostgreSQL primary node’unuz çöker ve tüm uygulama katmanı ayağa kalkar. Manuel failover yapmaya çalışırken hem stres altındasınızdır hem de her saniye para kaybediyorsunuzdur. İşte tam bu noktada Patroni devreye girer. Patroni, PostgreSQL için geliştirilmiş, etcd, Consul veya ZooKeeper gibi dağıtık konfigürasyon depolarını kullanarak otomatik failover ve yüksek erişilebilirlik sağlayan açık kaynaklı bir araçtır. Bu yazıda gerçek bir production ortamı kurulumunu adım adım ele alacağız.

Mimari Genel Bakış

Bir Patroni kümesi temel olarak üç bileşenden oluşur. Patroni agent’ları her PostgreSQL node üzerinde çalışır ve node’un durumunu izler. DCS (Distributed Configuration Store) cluster state bilgisini tutar; biz bu yazıda etcd kullanacağız. HAProxy veya VIP ise istemci bağlantılarını doğru node’a yönlendirir.

Kuracağımız yapı şu şekilde olacak:

  • 3 PostgreSQL node (1 primary, 2 standby)
  • 3 etcd node (quorum için)
  • 1 HAProxy node (bağlantı yönlendirme)

Node IP adresleri:

  • pg-node1: 192.168.1.101
  • pg-node2: 192.168.1.102
  • pg-node3: 192.168.1.103
  • haproxy: 192.168.1.100

Ubuntu 22.04 LTS kullanıyoruz. Tüm PostgreSQL node’larında aynı işlemleri yapacağız.

etcd Kurulumu

Patroni’nin çalışması için önce etcd cluster’ını ayağa kaldırmamız gerekiyor. etcd, distributed key-value store olarak cluster state’ini tutar ve primary election işlemlerini koordine eder.

Tüm 3 node’da etcd’yi kuralım:

sudo apt update
sudo apt install -y etcd

sudo systemctl stop etcd

Her node için /etc/etcd/etcd.conf.yml dosyasını düzenleyin. pg-node1 için:

cat > /etc/etcd/etcd.conf.yml << 'EOF'
name: 'pg-node1'
data-dir: '/var/lib/etcd'
listen-client-urls: 'http://192.168.1.101:2379,http://127.0.0.1:2379'
advertise-client-urls: 'http://192.168.1.101:2379'
listen-peer-urls: 'http://192.168.1.101:2380'
initial-advertise-peer-urls: 'http://192.168.1.101:2380'
initial-cluster: 'pg-node1=http://192.168.1.101:2380,pg-node2=http://192.168.1.102:2380,pg-node3=http://192.168.1.103:2380'
initial-cluster-token: 'pg-etcd-cluster'
initial-cluster-state: 'new'
EOF

pg-node2 ve pg-node3 için name, listen-client-urls, advertise-client-urls, listen-peer-urls ve initial-advertise-peer-urls değerlerini ilgili IP adresleriyle güncelleyin. Ardından etcd’yi başlatın:

sudo systemctl start etcd
sudo systemctl enable etcd

# Cluster sağlığını kontrol et
etcdctl endpoint health 
  --endpoints=http://192.168.1.101:2379,http://192.168.1.102:2379,http://192.168.1.103:2379

Üç node da “is healthy” mesajı veriyorsa etcd hazır demektir.

PostgreSQL ve Patroni Kurulumu

Tüm PostgreSQL node’larında PostgreSQL ve Patroni’yi kuralım:

# PostgreSQL 15 repository ekle
sudo apt install -y curl ca-certificates
sudo install -d /usr/share/postgresql-common/pgdg
curl -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc 
  --fail https://www.postgresql.org/media/keys/ACCC4CF8.asc

sudo sh -c 'echo "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] 
  https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" 
  > /etc/apt/sources.list.d/pgdg.list'

sudo apt update
sudo apt install -y postgresql-15 postgresql-client-15

# Patroni ve bağımlılıklarını kur
sudo apt install -y python3-pip python3-dev libpq-dev
sudo pip3 install patroni[etcd] psycopg2-binary

# PostgreSQL servisini durdur, Patroni yönetecek
sudo systemctl stop postgresql
sudo systemctl disable postgresql

PostgreSQL’in varsayılan servisini devre dışı bırakmak kritik önem taşır. Patroni kendi başına PostgreSQL sürecini yönetecektir.

Patroni Konfigürasyonu

Her node için /etc/patroni/patroni.yml dosyasını oluşturalım. Bu dosya Patroni’nin beyni sayılır. pg-node1 için:

# /etc/patroni/patroni.yml - pg-node1
scope: pg-cluster
namespace: /db/
name: pg-node1

restapi:
  listen: 192.168.1.101:8008
  connect_address: 192.168.1.101:8008

etcd:
  hosts: 192.168.1.101:2379,192.168.1.102:2379,192.168.1.103:2379

bootstrap:
  dcs:
    ttl: 30
    loop_wait: 10
    retry_timeout: 10
    maximum_lag_on_failover: 1048576
    postgresql:
      use_pg_rewind: true
      use_slots: true
      parameters:
        wal_level: replica
        hot_standby: "on"
        wal_keep_size: 1024
        max_wal_senders: 10
        max_replication_slots: 10
        wal_log_hints: "on"
        archive_mode: "on"
        archive_command: "test ! -f /var/lib/postgresql/wal_archive/%f && cp %p /var/lib/postgresql/wal_archive/%f"

  initdb:
    - encoding: UTF8
    - data-checksums

  pg_hba:
    - host replication replicator 192.168.1.0/24 md5
    - host all all 0.0.0.0/0 md5

  users:
    admin:
      password: "StrongAdminPass123!"
      options:
        - createrole
        - createdb

postgresql:
  listen: 192.168.1.101:5432
  connect_address: 192.168.1.101:5432
  data_dir: /var/lib/postgresql/15/main
  bin_dir: /usr/lib/postgresql/15/bin
  pgpass: /tmp/pgpass0

  authentication:
    replication:
      username: replicator
      password: "ReplicatorPass456!"
    superuser:
      username: postgres
      password: "SuperuserPass789!"
    rewind:
      username: rewind_user
      password: "RewindPass321!"

  parameters:
    unix_socket_directories: '/var/run/postgresql'
    shared_buffers: 256MB
    effective_cache_size: 768MB
    maintenance_work_mem: 64MB
    checkpoint_completion_target: 0.9
    wal_buffers: 16MB
    default_statistics_target: 100

tags:
  nofailover: false
  noloadbalance: false
  clonefrom: false
  nosync: false

pg-node2 ve pg-node3 için name, restapi.listen, restapi.connect_address, postgresql.listen ve postgresql.connect_address alanlarını ilgili IP ile değiştirin.

Gerekli dizinleri oluşturalım:

sudo mkdir -p /etc/patroni
sudo mkdir -p /var/lib/postgresql/wal_archive
sudo chown -R postgres:postgres /var/lib/postgresql/wal_archive
sudo chown -R postgres:postgres /etc/patroni

Patroni Systemd Servisi

Patroni’yi systemd servis olarak tanımlayalım. Her node’da aynı dosyayı oluşturun:

cat > /etc/systemd/system/patroni.service << 'EOF'
[Unit]
Description=Runners to orchestrate a high-availability PostgreSQL
After=syslog.target network.target

[Service]
Type=simple
User=postgres
Group=postgres
ExecStart=/usr/local/bin/patroni /etc/patroni/patroni.yml
KillMode=process
TimeoutSec=30
Restart=no

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload

Önce pg-node1’de Patroni’yi başlatın, primary olarak seçilecek:

# pg-node1'de
sudo systemctl start patroni
sudo systemctl enable patroni

# Durumu kontrol et
sudo systemctl status patroni
journalctl -u patroni -f

Log çıktısında şunu görmelisiniz: promoted self to leader by acquiring session lock. Bu, pg-node1’in primary olduğunu gösterir. Ardından pg-node2 ve pg-node3’te de başlatın; bunlar otomatik olarak standby rolünü alacaktır.

Cluster Durumunu İzleme

Patroni, patronictl adında güçlü bir CLI aracı sunar. Bu araçla cluster’ı yönetmek oldukça kolaylaşır:

# Cluster genel durumu
patronictl -c /etc/patroni/patroni.yml list

# Beklenen çıktı:
# + Cluster: pg-cluster (123456789) ----+----+-----------+
# | Member   | Host            | Role    | State   | TL | Lag in MB |
# +-----------+----------------+---------+---------+----+-----------+
# | pg-node1 | 192.168.1.101  | Leader  | running |  1 |           |
# | pg-node2 | 192.168.1.102  | Replica | running |  1 |         0 |
# | pg-node3 | 192.168.1.103  | Replica | running |  1 |         0 |

# Cluster konfigürasyonunu görüntüle
patronictl -c /etc/patroni/patroni.yml show-config

# Anlık failover tetikle (planlı)
patronictl -c /etc/patroni/patroni.yml switchover pg-cluster

# Belirli node'u primary yapmak için
patronictl -c /etc/patroni/patroni.yml switchover pg-cluster --master pg-node1 --candidate pg-node2

# Bir node'u yeniden başlat
patronictl -c /etc/patroni/patroni.yml restart pg-cluster pg-node2

# Cluster genelinde rolling restart
patronictl -c /etc/patroni/patroni.yml restart pg-cluster

HAProxy ile Bağlantı Yönlendirme

Uygulama katmanının her zaman doğru node’a bağlanması için HAProxy kullanacağız. Patroni’nin REST API’si, HAProxy’nin health check’leri için mükemmel bir entegrasyon noktası sunar.

# HAProxy node'unda (192.168.1.100)
sudo apt install -y haproxy

/etc/haproxy/haproxy.cfg dosyasını düzenleyin:

cat > /etc/haproxy/haproxy.cfg << 'EOF'
global
    maxconn 100
    log /dev/log    local0
    log /dev/log    local1 notice
    chroot /var/lib/haproxy
    stats socket /run/haproxy/admin.sock mode 660 level admin
    user haproxy
    group haproxy
    daemon

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

# HAProxy istatistik sayfası
listen stats
    mode http
    bind *:7000
    stats enable
    stats uri /

# Primary (okuma-yazma) bağlantıları - port 5000
listen pg_primary
    bind *:5000
    option httpchk GET /primary
    http-check expect status 200
    default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
    server pg-node1 192.168.1.101:5432 maxconn 100 check port 8008
    server pg-node2 192.168.1.102:5432 maxconn 100 check port 8008
    server pg-node3 192.168.1.103:5432 maxconn 100 check port 8008

# Replica (salt okunur) bağlantıları - port 5001
listen pg_replicas
    bind *:5001
    option httpchk GET /replica
    http-check expect status 200
    default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
    server pg-node1 192.168.1.101:5432 maxconn 100 check port 8008
    server pg-node2 192.168.1.102:5432 maxconn 100 check port 8008
    server pg-node3 192.168.1.103:5432 maxconn 100 check port 8008
EOF

sudo systemctl restart haproxy
sudo systemctl enable haproxy

HAProxy’nin sihri şurada: Patroni REST API’si /primary endpoint’ine HTTP 200, /replica endpoint’ine de HTTP 200 döner. HAProxy bu sayede kimin primary kimin replica olduğunu dinamik olarak öğrenir. Failover gerçekleştiğinde, yeni primary node /primary için 200 döndürmeye başlar ve HAProxy trafiği otomatik olarak oraya yönlendirir.

Failover Test Senaryoları

Senaryo 1: Primary Node Çöküşü Simülasyonu

Gerçek bir production arızasını simüle edelim:

# pg-node1 üzerinde Patroni'yi zorla durdur
sudo systemctl stop patroni

# Başka bir node'dan cluster durumunu izle
watch -n1 'patronictl -c /etc/patroni/patroni.yml list'

# Birkaç saniye içinde pg-node2 veya pg-node3 leader olacak
# Log dosyalarını izle
journalctl -u patroni -f  # pg-node2 veya pg-node3'te

Patroni varsayılan ayarlarla 10-30 saniye içinde otomatik failover gerçekleştirir. ttl: 30 değeri, leader’ın bu süre içinde heartbeat göndermesi gerektiğini belirtir. Bu süre dolduğunda seçim başlar.

Senaryo 2: pg_rewind ile Eski Primary’yi Geri Ekleme

pg-node1’i tekrar başlattığınızda, eski primary olarak replica rolünde kümeye katılır:

# pg-node1'de Patroni'yi tekrar başlat
sudo systemctl start patroni

# Patroni otomatik olarak pg_rewind çalıştırır ve
# node'u yeni primary ile senkronize eder
patronictl -c /etc/patroni/patroni.yml list

use_pg_rewind: true ayarı sayesinde, eski primary’nin WAL geçmişini yeni primary ile uyumlu hale getirmek için pg_rewind kullanılır. Bu, tam bir resync (base backup) yerine çok daha hızlı bir yeniden entegrasyon sağlar.

Dinamik Konfigürasyon Yönetimi

Patroni’nin en güçlü özelliklerinden biri, cluster genelinde konfigürasyon değişikliklerini DCS üzerinden yönetebilmektir. PostgreSQL parametrelerini tek tek node’larda değil, merkezi olarak değiştirirsiniz:

# Mevcut DCS konfigürasyonunu görüntüle
patronictl -c /etc/patroni/patroni.yml show-config

# Parametre değişikliği - tüm cluster'a anında uygulanır
patronictl -c /etc/patroni/patroni.yml edit-config

# Veya doğrudan patch ile
patronictl -c /etc/patroni/patroni.yml edit-config --force 
  -p 'postgresql.parameters.max_connections=200'

# Reload gerektiren parametreler için
patronictl -c /etc/patroni/patroni.yml reload pg-cluster

# Restart gerektiren parametreler (shared_buffers gibi) için
patronictl -c /etc/patroni/patroni.yml restart pg-cluster --scheduled now

Önemli Not: maximum_lag_on_failover parametresi kritik bir güvenlik mekanizmasıdır. Bu değer (byte cinsinden) aşıldığında, o replica failover kandidatı olmaktan çıkar. 1MB gibi küçük bir değer, veri kaybını minimize eder ama sıkışık ağ ortamlarında failover yapılabilecek node sayısını azaltabilir.

Monitoring ve Alerting

Production ortamında Patroni cluster’ını izlemek için Prometheus ve patroni_exporter kullanabilirsiniz:

# patroni_exporter kur
sudo pip3 install patroni-exporter

# Her node'da çalıştır
patroni_exporter 
  --patroni-url http://192.168.1.101:8008 
  --listen-address :9630 &

# Önemli Patroni metrikleri:
# patroni_master - node primary ise 1, değilse 0
# patroni_replica - node replica ise 1, değilse 0
# patroni_postgres_running - PostgreSQL çalışıyor mu
# patroni_failsafe_mode_is_active - failsafe modu aktif mi
# patroni_lag_in_mb - replica lag MB cinsinden

Cluster sağlığını hızlıca kontrol eden basit bir shell script’i de işinize yarar:

#!/bin/bash
# /usr/local/bin/pg_cluster_health.sh

PATRONI_NODES=("192.168.1.101:8008" "192.168.1.102:8008" "192.168.1.103:8008")
ALERT_EMAIL="[email protected]"

check_node() {
    local node=$1
    local response=$(curl -s -o /dev/null -w "%{http_code}" http://$node/health)

    if [ "$response" != "200" ]; then
        echo "KRITIK: $node erişilemiyor! HTTP: $response"
        return 1
    fi

    local role=$(curl -s http://$node/patroni | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('role','unknown'))")
    local state=$(curl -s http://$node/patroni | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('state','unknown'))")

    echo "Node $node: Role=$role, State=$state"
    return 0
}

primary_count=0
for node in "${PATRONI_NODES[@]}"; do
    http_code=$(curl -s -o /dev/null -w "%{http_code}" http://$node/primary 2>/dev/null)
    if [ "$http_code" == "200" ]; then
        ((primary_count++))
    fi
    check_node $node
done

if [ "$primary_count" -ne 1 ]; then
    echo "KRITIK: Primary sayisi $primary_count! Beklenen: 1"
    # mail -s "Patroni Alert: Primary sorunu!" $ALERT_EMAIL <<< "Primary count: $primary_count"
fi

Yaygın Sorunlar ve Çözümleri

“No leader” durumu: Tüm node’lar replica olarak görünüyorsa, önce etcd sağlığını kontrol edin. etcd quorum’u kaybettiyse Patroni leader seçimi yapamaz.

etcdctl endpoint health 
  --endpoints=http://192.168.1.101:2379,http://192.168.1.102:2379,http://192.168.1.103:2379

Replica lag artıyorsa: pg_stat_replication ile wal sender durumunu inceleyin. Ağ bant genişliği, disk I/O veya long-running transaction’lar lag’a neden olabilir.

# Primary'de çalıştır
psql -U postgres -c "SELECT client_addr, state, sent_lsn, write_lsn, flush_lsn, replay_lsn, (sent_lsn - replay_lsn) AS replication_lag FROM pg_stat_replication;"

Patroni başlamıyorsa: Konfigürasyon dosyasında syntax hatası olabilir. Patroni’yi debug modda çalıştırın:

sudo -u postgres /usr/local/bin/patroni /etc/patroni/patroni.yml --verbose

Split-brain riski: etcd cluster’ı 3 node’dan az çalışıyorsa, Patroni’nin pause moduna geçtiğinden emin olun:

# Cluster'ı bakım moduna al (otomatik failover durdurulur)
patronictl -c /etc/patroni/patroni.yml pause pg-cluster

# Bakım bittikten sonra
patronictl -c /etc/patroni/patroni.yml resume pg-cluster

Backup Entegrasyonu

Yüksek erişilebilirlik tek başına yeterli değildir; backup stratejinizi de Patroni ile entegre etmelisiniz. pgBackRest ile birlikte kullanmak yaygın bir production tercihidir:

# pgBackRest kur
sudo apt install -y pgbackrest

# /etc/pgbackrest/pgbackrest.conf
cat > /etc/pgbackrest/pgbackrest.conf << 'EOF'
[pg-cluster]
pg1-path=/var/lib/postgresql/15/main
pg1-port=5432
pg1-user=postgres

[global]
repo1-path=/var/lib/pgbackrest
repo1-retention-full=2
log-level-console=info
log-level-file=detail
start-fast=y
EOF

# Patroni konfigürasyonunda archive_command'ı güncelle
# bootstrap.dcs.postgresql.parameters altında:
# archive_command: 'pgbackrest --stanza=pg-cluster archive-push %p'

Sonuç

Patroni, PostgreSQL yüksek erişilebilirlik ihtiyaçları için production-ready, savaşta test edilmiş bir çözümdür. Kurulumu ilk bakışta karmaşık görünebilir ancak bir kez doğru şekilde ayağa kaldırdıktan sonra son derece güvenilir çalışır. Ele aldığımız yapıda otomatik failover 10-30 saniye içinde gerçekleşir, pg_rewind sayesinde eski primary’leri hızla kümeye geri alabilirsiniz ve HAProxy entegrasyonu ile uygulama katmanı bu geçişlerden neredeyse hiç etkilenmez.

Kritik noktalara bir kez daha değinelim:

  • etcd cluster’ını her zaman odd sayıda (3, 5, 7) node ile çalıştırın
  • maximum_lag_on_failover değerini veri kritikliğinize göre ayarlayın
  • Patroni REST API’sini monitoring sistemlerinize mutlaka entegre edin
  • Düzenli olarak failover testleri yapın; production’da sürprizle karşılaşmayın
  • Backup stratejinizi pgBackRest ile tamamlayın

Bu yapıyı kurduktan sonra, gece 2’deki database arızaları artık sizi uyandırmaz; Patroni sessizce görevi devreder ve siz sabah geldiğinizde cluster’ın yeni primary ile mutlu bir şekilde çalışmaya devam ettiğini görürsünüz.

Yorum yapın