217 lines
9.7 KiB
Markdown
217 lines
9.7 KiB
Markdown
---
|
|
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 비활성).
|
|
|
|
### 좀비 소켓 방지 — TCP keepalive
|
|
|
|
Patroni failover 시 pgcat↔backend 및 client↔pgcat TCP 소켓이 반쪽만 끊어진 채 애플리케이션 풀이 계속 붙잡는 현상을 방지하기 위해 양쪽 모두 TCP keepalive 활성화.
|
|
|
|
**pgcat (`db/pgcat-config` `[general]`)**:
|
|
```toml
|
|
tcp_keepalives_idle = 60
|
|
tcp_keepalives_interval = 10
|
|
tcp_keepalives_count = 3
|
|
```
|
|
|
|
**Patroni (`postgresql.parameters` via `patronictl edit-config -p`)**:
|
|
```
|
|
tcp_keepalives_idle = 60
|
|
tcp_keepalives_interval = 10
|
|
tcp_keepalives_count = 3
|
|
```
|
|
|
|
동작: 60s idle 후 keepalive probe 전송, 10s 간격으로 3회 재전송 실패 시 커넥션 종료. 최대 감지 시간 **90초**. pgcat 는 서버 측에, Patroni 는 클라이언트(= pgcat 소켓) 측에 각각 설정 — 어느 쪽에서 먼저 죽음을 감지해도 소켓이 정리됨.
|
|
|
|
검증: 2026-04-16 switchover 테스트(postgres-3→postgres-2, TL6→7) 기준 n8n 에러 윈도우 7초·9건으로 한정 ([[../history/2026-04-16-pgcat-patroni-tcp-keepalive|history]]). 이전 2026-04-15 19:53 UTC failover 시 1038건과 극적 대비.
|
|
|
|
Patroni failover 인시던트 이력: [[../history/2026-04-08-patroni-failover-incident|2026-04-08 pgcat/nocodb/outline read-only 사고]] · [[../history/2026-04-15-pgcat-ha-promotion|2026-04-15 pgcat HA 승격 Step 0]]
|
|
|
|
## pgpool-II PoC (n8n 전용)
|
|
|
|
2026-04-16 기준 **n8n 만** pgpool 경유 (NocoDB·Outline 은 pgcat 유지). 1주 관측 후 확대 여부 판단.
|
|
|
|
### 구조
|
|
|
|
```
|
|
n8n → pgpool.db.svc.cluster.local:9999 → Patroni 3노드 직결 (HAProxy 미경유)
|
|
```
|
|
|
|
- 이미지: `pgpool/pgpool:4.4.3` (공식). `pgpool/pgpool` 태그 최신. 4.5/4.6 은 Bitnami 쪽 `bitnamilegacy/pgpool` 에만 있으나 env 래퍼 복잡도로 포기
|
|
- replicas=2 on kr1 + kr2 (`podAntiAffinity` topologyKey hostname), PDB `minAvailable: 1`
|
|
- 인증: `allow_clear_text_frontend_auth=on` + `pool_hba.conf` 메소드 `password`. 클라이언트가 plaintext 전송 → pgpool 이 그대로 backend scram-sha-256 challenge-response 에 사용. `pool_passwd` 미사용 (pgpool 이 plaintext 엔트리를 자동으로 md5 로 해시하는데 postgres 가 md5 거절 → 인증 실패)
|
|
- sr_check/health_check: `sr_check_user=sr_check` (REPLICATION + pg_monitor), plaintext password 를 entrypoint sed 로 주입
|
|
- `backend_clustering_mode = streaming_replication` (Patroni 연동). `failover_on_backend_error = off` — Patroni 가 promotion 수행, pgpool 은 role 변경 탐지 후 라우팅만 갱신
|
|
- `load_balance_mode = off` — 모든 쿼리 primary 로 (n8n read-your-write 일관성)
|
|
|
|
### 검증 결과 (2026-04-16)
|
|
|
|
- **Patroni switchover** (postgres-3→postgres-2, TL9→10): n8n 에러 창 ~2초 / 1건 `failed to create a backend 2 connection` → 즉시 `Database connection recovered`. pgpool `SHOW POOL_NODES` 에서 `last_status_change` 가 switchover 시점에 갱신되어 primary 자동 재탐지 확인
|
|
- **mbp etcd 60s down**: n8n 에러 **0건**, n8n.inouter.com 200 유지. pgcat 에서는 동일 시나리오에서 n8n 풀 좀비 → 503 / pod restart 필요했음
|
|
|
|
자세한 구성·검증 로그: [[../history/2026-04-16-pgpool-n8n-poc|history]]
|
|
|
|
## 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 마이그레이션 기록
|