--- title: PostgreSQL HA (Patroni + etcd) updated: 2026-04-06 tags: [infra, postgresql, patroni, etcd, ha] --- ## 개요 PostgreSQL 3노드 HA 클러스터. Patroni가 자동 failover를 관리하고, etcd를 DCS(Distributed Consensus Store)로 사용. K3s의 kine 데이터스토어로 사용 중. ## PostgreSQL 클러스터 | 노드 | 호스트 | IP | 역할 | |------|--------|-----|------| | postgres-1 | incus-hp2 | 10.100.2.5 | Replica 또는 Leader | | postgres-2 | incus-kr1 | 10.100.3.185 | Replica 또는 Leader | | postgres-3 | incus-kr2 | 10.100.1.83 | Replica 또는 Leader | - PostgreSQL 17.9, Patroni 4.1.0 - Patroni 설정: `/etc/patroni.yml` - Patroni 서비스: `/etc/systemd/system/patroni.service` (ExecStart: `/opt/patroni/bin/patroni /etc/patroni.yml`) - 클러스터 이름: `nocodb-cluster` - 레플리케이션: async streaming - Patroni REST API: 각 노드 8008 포트 - 컨테이너 메모리 제한: 2GiB (limits.memory) - shared_buffers: 512MB, effective_cache_size: 1536MB, work_mem: 8MB, maintenance_work_mem: 128MB, wal_buffers: 16MB ### DB 목록 | DB | 용도 | |----|------| | kine | K3s 데이터스토어 (kine) | | nocodb | NocoDB | | n8n | n8n 워크플로 | | outline | Outline 위키 | ### Patroni 명령어 ```bash # 클러스터 상태 확인 incus exec postgres-1 -- /opt/patroni/bin/patronictl -c /etc/patroni.yml list # 수동 switchover incus exec postgres-1 -- /opt/patroni/bin/patronictl -c /etc/patroni.yml switchover # Replica reinitialize incus exec postgres-1 -- /opt/patroni/bin/patronictl -c /etc/patroni.yml reinit nocodb-cluster postgres-3 --force ``` ## etcd 클러스터 (Patroni DCS) | 노드 | 위치 | IP | 방식 | |------|------|-----|------| | etcd-nas | Synology NAS (서울) | 192.168.9.100 | Docker (`quay.io/coreos/etcd:v3.5.21`) | | etcd-mbp | kaffa-macbookpro (서울, Tailscale) | 100.115.154.78 | Docker (`quay.io/coreos/etcd:v3.5.17`) via colima, peer/client는 socat으로 Tailscale 노출 | | etcd-jp1 | Incus 컨테이너 jp1 (도쿄) | 10.253.101.233 | Alpine + `apk add etcd` (v3.5.16) | - NAS: 데이터 `/volume1/docker/etcd/data`, `--restart=always` - jp1: openrc 서비스 (`/etc/init.d/etcd`), `command_background=true` - mbp: docker container `etcd` (named volume `etcd-data`), client/peer 모두 호스트 `127.0.0.1`만 노출 → `socat`이 Tailscale IP `100.115.154.78`의 `2379`/`2380`으로 forward (`~/Library/LaunchAgents/com.kaffa.etcd-socat{,-peer}.plist`) - Patroni etcd namespace: `/patroni` ### etcd 확인 명령어 ```bash # 클러스터 멤버 확인 incus exec postgres-1 -- etcdctl --endpoints=http://192.168.9.100:2379 member list -w table # 엔드포인트 상태 incus exec postgres-1 -- etcdctl --endpoints=http://192.168.9.100:2379,http://100.115.154.78:2379,http://10.253.101.233:2379 endpoint status -w table ``` ### etcd에 저장된 데이터 | prefix | 용도 | |--------|------| | `/patroni` | Patroni DCS (Leader election, 설정) | | `/apisix/osaka` | APISIX 오사카 라우팅 설정 | | `/apisix/tokyo` | APISIX sandbox-tokyo 라우팅 설정 (미사용, 데이터 보존) | | `/apisix/seoul` | APISIX 서울 K3s 라우팅 설정 | ## K3s kine 연결 K3s → HAProxy(OpenWrt 192.168.9.1:5432) → Patroni Leader PostgreSQL ### K3s config ```yaml # /etc/rancher/k3s/config.yaml (kr1, kr2) datastore-endpoint: "postgres://kine:kine@192.168.9.1:5432/kine" ``` ### HAProxy (OpenWrt) `/etc/haproxy.cfg`에 PostgreSQL backend 설정. Patroni REST API(`/primary` 엔드포인트)로 Leader를 자동 감지. ``` frontend ft_postgres bind :5432 default_backend bk_postgres_primary backend bk_postgres_primary option httpchk GET /primary http-check expect status 200 default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions server postgres-1 10.100.2.5:5432 check port 8008 server postgres-2 10.100.3.185:5432 check port 8008 server postgres-3 10.100.1.83:5432 check port 8008 ``` - Patroni failover 시 HAProxy가 자동으로 새 Leader를 감지 (~3초) - K3s config 변경 없이 Leader 전환 대응 ## 애플리케이션 접속 경로 NocoDB, n8n 등 K3s 내부 애플리케이션은 **pgcat**(연결 풀링)을 통해 PostgreSQL에 접속. ``` nocodb/n8n → pgcat (db.svc.cluster.local:6432) → HAProxy 192.168.9.1:5432 → Patroni Leader ``` ### pgcat HA 구성 `db/pgcat` Deployment는 **2 replica**, 서로 다른 K3s 노드에 강제 분산. - `replicaCount: 2` (Helm values `values/pgcat.yaml`) - `podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution` (topologyKey `kubernetes.io/hostname`) - `PodDisruptionBudget` `minAvailable: 1` — 최소 1개 pod 유지 보장 - `Service` ClusterIP 로 두 endpoint 모두 등록 — 클라이언트 요청은 kube-proxy 라운드로빈 - 노드 장애 또는 롤아웃 시 나머지 replica가 트래픽 흡수. NocoDB는 일시적 `Connection ended unexpectedly` 후 자동 재연결 (drop 1회, 재시작 없음) `ghcr.io/postgresml/pgcat:latest`. app chart 템플릿은 `affinity` / `topologySpreadConstraints` / `podDisruptionBudget` 옵션 지원 (v0.5.0+). `db/pgcat-config` ConfigMap의 각 풀의 `shards.0.servers`는 **HAProxy 단일 백엔드만 가리켜야 함**: ```toml [pools.nocodb.shards.0] database = "nocodb" servers = [["192.168.9.1", 5432, "primary"]] [pools.n8n.shards.0] database = "n8n" servers = [["192.168.9.1", 5432, "primary"]] ``` ⚠️ **하지 말 것**: pgcat에 Patroni 노드 IP(10.100.2.5/3.185/1.83)를 직접 박지 말 것. Patroni failover가 발생하면 pgcat는 옛 primary를 계속 가리키게 되어 nocodb/n8n이 read-only 에러 발생. pgcat는 풀링 전용으로만 쓰고, leader 탐지는 OpenWrt HAProxy에 위임. `query_parser_enabled = false` 설정 (read/write splitting 비활성). Patroni failover 인시던트 이력: [[../history/2026-04-08-patroni-failover-incident|2026-04-08 pgcat/nocodb/outline read-only 사고]] ## APISIX etcd 사용 현황 | 사이트 | etcd | prefix | 비고 | |--------|------|--------|------| | osaka | 통합 클러스터 (192.168.9.100, ...) | `/apisix/osaka` | Docker APISIX (waf-apisix), [[apisix#오사카-apisix-osaka]] | | sandbox-tokyo | (미가동) | `/apisix/tokyo` | 2026-04-08 NixOS 전환으로 APISIX 자체 폐기, etcd 데이터만 보존 | | 서울 K3s | **K3s 내부 apisix-etcd StatefulSet** (apisix.apisix.svc:2379) | `/apisix` | 2026-04-08 외부 통합에서 K3s 내부로 복귀 | APISIX etcd 통합/분리 이력: [[../history/2026-04-06-apisix-etcd-consolidation|history]]. 현재 외부 통합 etcd는 **Patroni DCS + osaka APISIX 전용**. ## 관련 문서 - [[nas-storage]] — NAS StorageClass, Jumbo Frame 설정 - [[storage-plan]] — NVMe NAS + iSCSI 기획 - [[backup]] — 백업 파이프라인 - [[infra-hosts]] — 서버 목록 - [[k3s-migration]] — K3s 마이그레이션 기록