From 33ce94a75afc46bd8e7cf2bf84434a2897750844 Mon Sep 17 00:00:00 2001 From: heimdall Date: Thu, 16 Apr 2026 12:24:39 +0900 Subject: [PATCH] =?UTF-8?q?pgpool=20=EC=A0=84=EB=A9=B4=20=EC=A0=84?= =?UTF-8?q?=ED=99=98=20+=20pgcat=20=ED=87=B4=EC=97=AD:=20postgresql-ha.md?= =?UTF-8?q?=20=EC=A0=84=EB=A9=B4=20=EA=B0=B1=EC=8B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- history/2026-04-16-pgpool-full-migration.md | 64 ++++++++++++++ infra/postgresql-ha.md | 98 +++++++++------------ 2 files changed, 107 insertions(+), 55 deletions(-) create mode 100644 history/2026-04-16-pgpool-full-migration.md diff --git a/history/2026-04-16-pgpool-full-migration.md b/history/2026-04-16-pgpool-full-migration.md new file mode 100644 index 0000000..950119a --- /dev/null +++ b/history/2026-04-16-pgpool-full-migration.md @@ -0,0 +1,64 @@ +--- +date: 2026-04-16 +topic: pgpool-II 전면 전환 + pgcat 퇴역 +areas: + - infra/postgresql-ha.md +tags: [history, pgpool, pgcat, patroni, postgresql, migration] +--- + +n8n PoC 성공 확인 후 NocoDB·Outline 도 pgpool 경유로 전환. pgcat ArgoCD App + 리소스 삭제. + +## 전환 순서 + 결과 + +### NocoDB (tools/nocodb) + +`kubectl -n tools set env deploy/nocodb NC_DB="pg://pgpool.db.svc.cluster.local:9999?u=nocodb&p=nocodb&d=nocodb"` → rollout restart → 200 OK. Baseline 2분 에러 0건. + +### Outline (outline/outline) + +`outline-secrets` DATABASE_URL 호스트를 `pgpool.db.svc.cluster.local:9999` 로 변경 → rollout restart → 200 OK. PVC multi-attach 이슈로 old pod 수동 삭제 필요 (RWO volume Longhorn). Sequelize 호환 문제 없음. + +**주의**: outline-secrets 는 ExternalSecret (Vault `secret/apps/outline`) 관리. K8s Secret 직접 패치는 refreshInterval(1h) 후 Vault 값으로 덮어씌워짐 → **kappa 가 Vault 의 DATABASE_URL 을 갱신해야 영속**. + +### pgpool 설정 변경 + +`pool_passwd = ''` 추가 — 이전 entrypoint 가 생성한 pool_passwd 파일이 남아 nocodb/outline 유저를 찾지 못하는 에러 해결. `allow_clear_text_frontend_auth=on` + pool_hba `password` 방식으로 pool_passwd 불필요. + +### 통합 switchover 테스트 + +`patronictl switchover --leader postgres-2 --candidate postgres-1 --force` (TL10→11): + +| 서비스 | 에러 수 | HTTP | +|---|---|---| +| n8n | 3 (transient) | 200 | +| NocoDB | 0 | 200 | +| Outline | 0 | 200 | + +write 복구 **t+3ms** (즉시). pgpool `SHOW POOL_NODES` 에서 primary 자동 갱신. + +### pgcat 퇴역 + +1. `kubectl -n argocd delete application pgcat` (cascade 삭제) +2. 잔존 리소스 수동 정리: Deployment, Service, ConfigMap (pgcat-config, pgcat-monitor), PDB +3. `helm-charts/values/pgcat.yaml` git rm + push (commit `534118e`) + +## 최종 상태 + +``` +n8n → pgpool.db.svc.cluster.local:9999 ✓ 200 +NocoDB → pgpool.db.svc.cluster.local:9999 ✓ 200 +Outline → pgpool.db.svc.cluster.local:9999 ✓ 200 +pgcat → 삭제 (ArgoCD App + K8s resources + helm values) +``` + +## 미해결 + +- Outline ExternalSecret → Vault 에서 DATABASE_URL 갱신 필요 (1h 이내 kappa 처리) +- pgpool `pool_passwd` 비활성 (clear-text frontend) → 장기 AES 암호화 하드닝 +- retry_timeout=70/ttl=30 상향 작업 중단 상태 — 별도 재개 + +## 참조 + +- helm-charts: a6bb681(pool_passwd 비활성) → **534118e**(pgcat 퇴역) +- PoC history: `history/2026-04-16-pgpool-n8n-poc.md` +- `infra/postgresql-ha.md` — 「pgcat」 섹션 삭제, 「pgpool-II」 로 교체 diff --git a/infra/postgresql-ha.md b/infra/postgresql-ha.md index 222817f..058999a 100644 --- a/infra/postgresql-ha.md +++ b/infra/postgresql-ha.md @@ -113,89 +113,77 @@ backend bk_postgres_primary - Patroni failover 시 HAProxy가 자동으로 새 Leader를 감지 (~3초) - K3s config 변경 없이 Leader 전환 대응 -## 애플리케이션 접속 경로 +## 애플리케이션 접속 경로 — pgpool-II -NocoDB, n8n 등 K3s 내부 애플리케이션은 **pgcat**(연결 풀링)을 통해 PostgreSQL에 접속. +모든 K3s 내부 애플리케이션(NocoDB, n8n, Outline)은 **pgpool-II**를 통해 Patroni 3노드에 직접 접속. HAProxy 미경유. ``` -nocodb/n8n → pgcat (db.svc.cluster.local:6432) → HAProxy 192.168.9.1:5432 → Patroni Leader +NocoDB/n8n/Outline → pgpool.db.svc.cluster.local:9999 → Patroni 3노드 직결 ``` -### pgcat HA 구성 +이전 pgcat+HAProxy 구조는 2026-04-16 폐기. [[../history/2026-04-16-pgpool-full-migration|history]] -`db/pgcat` Deployment는 **2 replica**, 서로 다른 K3s 노드에 강제 분산. +### pgpool 구성 -- `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회, 재시작 없음) +- 이미지: `pgpool/pgpool:4.4.3` (공식, DockerHub 최신 태그) +- K8s 매니페스트: `kaffa/helm-charts` repo `pgpool/` 디렉토리 (ArgoCD `pgpool` Application, selfHeal+prune) +- replicas=2, `podAntiAffinity` (topologyKey `kubernetes.io/hostname`) +- `PodDisruptionBudget` `minAvailable: 1` +- Service: ClusterIP `pgpool.db.svc.cluster.local:9999` -`ghcr.io/postgresml/pgcat:latest`. app chart 템플릿은 `affinity` / `topologySpreadConstraints` / `podDisruptionBudget` 옵션 지원 (v0.5.0+). +### pgpool.conf 핵심 -`db/pgcat-config` ConfigMap의 각 풀의 `shards.0.servers`는 **HAProxy 단일 백엔드만 가리켜야 함**: +``` +backend_clustering_mode = 'streaming_replication' -```toml -[pools.nocodb.shards.0] -database = "nocodb" -servers = [["192.168.9.1", 5432, "primary"]] +backend_hostname0/1/2 = 10.100.2.5 / 10.100.3.185 / 10.100.1.83 +backend_weight = 1, backend_flag = ALLOW_TO_FAILOVER -[pools.n8n.shards.0] -database = "n8n" -servers = [["192.168.9.1", 5432, "primary"]] +sr_check_user = 'sr_check' (REPLICATION + pg_monitor 역할, pg에 생성 필요) +sr_check_period = 10 +health_check_period = 10 + +failover_on_backend_error = off (Patroni가 promotion 수행) +failover_command = '' +follow_primary_command = '' +use_watchdog = off + +load_balance_mode = off (모든 쿼리 primary — read-your-write 일관성) +num_init_children = 32 +max_pool = 4 +connection_cache = on +pool_passwd = '' (미사용 — 아래 인증 참조) ``` -⚠️ **하지 말 것**: 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 비활성). +Postgres `password_encryption = scram-sha-256` cluster-wide. -### 좀비 소켓 방지 — TCP keepalive +- `allow_clear_text_frontend_auth = on` + pool_hba method `password` → 클라이언트 plaintext 전송 → pgpool 이 backend scram-sha-256 challenge-response 에 직접 사용 +- `pool_passwd = ''` — 비활성. pgpool 이 plaintext pool_passwd 엔트리를 자동 md5 해시하여 backend scram 거절되는 문제 회피 +- K8s Service 내부 트래픽이라 clear-text 는 클러스터 내에서만 노출 -Patroni failover 시 pgcat↔backend 및 client↔pgcat TCP 소켓이 반쪽만 끊어진 채 애플리케이션 풀이 계속 붙잡는 현상을 방지하기 위해 양쪽 모두 TCP keepalive 활성화. +### Patroni 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 소켓) 측에 각각 설정 — 어느 쪽에서 먼저 죽음을 감지해도 소켓이 정리됨. +pgpool 은 자체 연결 관리가 있어 클라이언트 좀비 문제 없지만, Postgres → pgpool 역방향 dead socket 감지용으로 Patroni 파라미터 유지. -검증: 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 switchover 통합 테스트 (2026-04-16) -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]] +3 서비스 전체 pgpool 경유 상태에서 `patronictl switchover --force`: -## pgpool-II PoC (n8n 전용) +- write 복구: **즉시** (t+3ms) +- n8n: 3건 transient error · NocoDB: 0건 · Outline: 0건 +- 전 서비스 HTTP 200 유지 -2026-04-16 기준 **n8n 만** pgpool 경유 (NocoDB·Outline 은 pgcat 유지). 1주 관측 후 확대 여부 판단. +이전 pgcat 대비: 동일 시나리오에서 n8n 1038건 / NocoDB 13건 / pod restart 필요 -### 구조 - -``` -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]] +마이그레이션 이력: [[../history/2026-04-16-pgpool-n8n-poc|PoC]] · [[../history/2026-04-16-pgpool-full-migration|전면 전환]] ## APISIX etcd 사용 현황