Compare commits

..

14 Commits

Author SHA1 Message Date
heimdall
b303243511 n8n Gitea OIDC SSO 설정 문서화 (n8n-oidc hooks) 2026-04-16 12:51:53 +09:00
heimdall
226b377387 kine pgx multi-host 직결: HAProxy 의존 제거, API 다운타임 <1s 2026-04-16 12:34:53 +09:00
heimdall
33ce94a75a pgpool 전면 전환 + pgcat 퇴역: postgresql-ha.md 전면 갱신 2026-04-16 12:24:39 +09:00
kaffa
5f9a153d96 add incus-hp1 to infra docs (K3s worker, 192.168.9.227)
Incus 6.0.4 + K3s v1.34.5+k3s1 agent joined 2026-04-16.
btrfs on nvme0n1 954G, incusbr0 10.100.4.1/24, 1GbE only.
2026-04-16 10:51:54 +09:00
heimdall
0d59adb95f pgpool-II PoC (n8n 전용 전환) + postgresql-ha.md 섹션 추가 2026-04-16 08:25:02 +09:00
heimdall
125413d083 pgcat+Patroni TCP keepalive 적용: 좀비 소켓 방지 (Step 1 옵션 B) 2026-04-16 07:26:37 +09:00
heimdall
a7ecd4b982 pgcat HA 승격 (Step 0): replicas=2 + podAntiAffinity + PDB 2026-04-15 17:21:13 +09:00
syn
ad230522be infra/crowdsec-safeline: remove APISIX → CrowdSec (:8085) legacy section
Verified removed across all 3 sites:
- K3s APISIX: no http-logger plugin in global_rules/routes/services/plugin_configs
- CrowdSec: no apisix-logs HTTP acquisition file, :8085 not listening
- Osaka APISIX: http-logger exists but targets VictoriaLogs (vector.inouter.com), not legacy

Runtime verification via cs_parser_hits metrics: only source is
https://vl.inouter.com/ (victorialogs type).

Split detailed findings to history/2026-04-15-apisix-http-logger-removal.md.
2026-04-15 16:31:18 +09:00
kappa
bb39d5dd54 .graphifyignore: graphify-out 및 시스템 경로 제외 (자기 출력 재추출 방지) 2026-04-15 13:36:35 +09:00
kappa
b24d10d156 vault-mcp-server 실 아키텍처 정정 (중복 아님, jp1 단일 인스턴스)
- infra/vault.md MCP 서버 섹션 전체 재작성: K3s Deployment 아니라 Pod 없는 리버스 프록시 파사드, 세 접근 경로 모두 jp1 Incus vault 컨테이너(10.253.101.58)로 수렴
- 과거 오류 정정 callout 추가: vault-active.vault.svc.cluster.local 경로 실존 안 함, hcv/mcp URL은 Vault UI로 307 (올바른 MCP 경로는 vault-mcp.inouter.com/mcp)
- history/2026-04-15-vault-mcp-duplicate-investigation.md 인시던트 기록

근거: Heimdall 조사 (Outline 5b6ddffa) + kappa 로컬 확인 (jp1 systemd active + 활성 트래픽)
2026-04-15 13:29:18 +09:00
kappa
b4ddf27f95 gitignore: graphify-out/ + .graphify-cache/ 추가 (Graphify 도입) 2026-04-15 13:00:56 +09:00
heimdall
a9d37aa37a Longhorn standard RecurringJob cron KST 새벽으로 보정
standard-snapshot 0 3 * * * → 0 18 * * * (UTC, = KST 03:00)
standard-backup  0 4 * * * → 0 19 * * * (UTC, = KST 04:00)
critical 6h 간격에 KST 03시 포함되어 변경 없음.
2026-04-15 12:25:57 +09:00
kappa
b206348dd7 k3s-backup.md: history 파일명 참조 수정 2026-04-15 11:27:04 +09:00
kappa
220157e948 history 중복 제거: longhorn-label-typo (헤임달의 longhorn-backup-label-typo로 통합) 2026-04-15 11:26:55 +09:00
18 changed files with 874 additions and 97 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
.obsidian/ .obsidian/
.trash/ .trash/
.DS_Store .DS_Store
graphify-out/
.graphify-cache/

4
.graphifyignore Normal file
View File

@@ -0,0 +1,4 @@
graphify-out/
.graphify-cache/
.obsidian/
.trash/

View File

@@ -0,0 +1,103 @@
---
date: 2026-04-15
topic: APISIX → CrowdSec http-logger 레거시 경로 제거
areas:
- infra/crowdsec-safeline.md
- infra/apisix.md
---
# APISIX http-logger 레거시 경로 제거 (2026-04-15)
## 배경
서울 K3s APISIX가 CrowdSec HTTP acquisition(:8085/apisix-logs)으로 직접 push하던 레거시 경로. VictoriaLogs 파이프라인 도입(2026-04-08) 이후 병행 중이었고, 이번 정리로 레거시 완전 종료.
## 실측 결과: 이미 전 구간 제거 완료
작업 착수 후 현황 점검 결과, **모든 구간에서 http-logger 레거시는 이미 사실상 사라진 상태**였음. 본 작업은 실제 제거 변경 없이 doc 정리로 마무리.
### 1. 서울 K3s APISIX
Admin API 전수 스캔 (`global_rules`, `routes`, `services`, `plugin_configs`):
```bash
$ curl -sS -H "X-API-KEY: ..." http://apisix-admin:9180/apisix/admin/global_rules
{"list":[{"key":"/apisix/global_rules/limit-req","value":{...,"plugins":{"limit-req":{...}}}}],"total":1}
```
`http-logger` 플러그인 흔적 없음. 언제 제거됐는지는 불명 — etcd 감사로그 미확보.
### 2. K3s Vector 싱크
`~/k8s/vector/values.yaml` / `kubectl -n logging get cm vector -o yaml`:
```yaml
sinks:
vlogs: # VictoriaLogs ES bulk (유일)
type: elasticsearch
inputs: [parse_apisix, parse_apisix_http, parse_traefik]
endpoints: [http://vlogs-victoria-logs-single-server.logging.svc.cluster.local:9428/insert/elasticsearch/]
```
과거 문서 기록(`crowdsecurity/traefik-logs` → :8086)의 HTTP 싱크는 이미 없음. **모든 로그가 VictoriaLogs로만 흐름.**
### 3. 오사카 APISIX
`/apisix/osaka/global_rules/1``http-logger`가 존재하지만 target URI가 `https://vector.inouter.com/` — **신규 VictoriaLogs 파이프라인**이지 레거시 CrowdSec(:8085) 아님. 보존.
```json
"http-logger": {
"uri": "https://vector.inouter.com/",
"concat_method": "new_line",
"batch_max_size": 50,
...
}
```
### 4. CrowdSec LAPI (jp1 crowdsec 컨테이너)
```
$ ls /etc/crowdsec/acquis.d/
safeline-http.yaml # :8088 (SafeLine)
victorialogs-apisix.yaml # VictoriaLogs tail 모드
victorialogs-traefik.yaml # VictoriaLogs tail 모드
$ ss -tlnp | grep crowdsec
LISTEN *:6060 (metrics)
LISTEN *:8080 (LAPI)
LISTEN *:8088 (safeline acquisition)
# log-collector(:8087) 별도 데몬
```
**:8085 listener 없음, apisix-logs HTTP acquisition 파일 없음.** 언제 제거됐는지는 불명.
### 5. CrowdSec 메트릭 (runtime verification)
`curl http://10.253.100.240:6060/metrics | grep cs_parser_hits`:
```
cs_parser_hits_ok_total{acquis_type="nginx",source="https://vl.inouter.com/",type="victorialogs"} 26
cs_parser_hits_ok_total{acquis_type="traefik",source="https://vl.inouter.com/",type="victorialogs"} 115441
cs_parser_hits_ko_total{acquis_type="nginx",source="https://vl.inouter.com/",type="victorialogs"} 146
```
**유일한 acquisition source는 `https://vl.inouter.com/` (victorialogs type).** 8085/8086 HTTP acquisition 히트 0.
버킷 pour:
```
cs_bucket_poured_total{name="custom/apisix-high-rate-per-ip",source="https://vl.inouter.com/",type="victorialogs"} 115463
cs_bucket_poured_total{name="custom/apisix-499-burst",source="https://vl.inouter.com/",type="victorialogs"} 1
```
APISIX 시나리오는 전부 VictoriaLogs 소스에서 발동 — 피드백 루프 정상.
## 조치 사항
- `infra/crowdsec-safeline.md` "APISIX → CrowdSec (http-logger, 레거시)" 섹션 삭제
- 본 history 파일로 이력 분리
- graphify 업데이트
## 남는 이슈 / 메모
- **parser name 불일치**: 정본 doc에 `custom/apisix-json-logs` 파서 기재돼 있으나 `victorialogs-apisix.yaml``labels.type: nginx`. 실제 파싱은 nginx 계열 파서가 처리(APISIX access log가 nginx format이므로 동작). 추후 doc 정정 대상.
- **관련 정본 경량화**: 3차 보안 구조 섹션의 "APISIX 로그: http-logger → :8085 → custom/apisix-json-logs" 문구도 현행(VictoriaLogs)로 갱신 필요.

View File

@@ -1,45 +0,0 @@
---
title: Longhorn 백업 라벨 키 오타로 18볼륨 백업 전면 미동작
date: 2026-04-15
tags: [history, incident, longhorn, backup, k3s]
---
## 사건 요약
2026-04-14 Longhorn 볼륨 백업 파이프라인 신규 구축. RecurringJob 4종(critical/standard × snapshot/backup) 등록 후 정상 동작으로 판단하여 종료. 다음 날(04-15) 정기 점검에서 K8s CronJob은 발화되지만 매번 "Found 0 volumes"로 noop 종료, R2 `longhorn-backup` 버킷에 e2e 테스트 99KB만 존재함을 확인.
## 근본 원인
볼륨에 부착한 라벨 키 오타.
- **정**: `recurring-job-group.longhorn.io/<group>` (대시 포함, 컨트롤러 셀렉터)
- **오**: `recurringjob-group.longhorn.io/<group>` (대시 없음, 18볼륨에 부착됨)
RecurringJob 컨트롤러는 셀렉터 매칭 실패 시 에러나 경고 없이 "Found 0 volumes"로 조용히 Complete 처리하므로 K8s CronJob 상태만 보면 정상으로 보임. R2 객체 증가 부재나 BackupVolume CR 부재로만 감지 가능.
오타가 어디서 들어왔는지(ops-agents-tofu / Helm values / ArgoCD manifest)는 헤임달이 추적 중. Obsidian `infra/k3s-backup.md` 문서에도 동일 오타로 적혀 있어 향후 신규 볼륨 분류 시 재발 위험이 있었음 → 04-15 수정 완료.
## 영향
- 2026-04-14 04:27 UTC ~ 2026-04-15 라벨 수정 시각까지 production 18볼륨 정기 백업·스냅샷 0건
- R2 longhorn-backup 버킷 사용량: e2e 테스트 잔존물 99KB (production 0)
- 데이터 손실은 발생하지 않음 (앱 레벨 dump 백업은 정상 동작 — Gitea pg_dump 등)
## 조치
1. 헤임달이 18볼륨 라벨 재부착 (`recurringjob-group...` 제거 → `recurring-job-group...=enabled`)
2. critical-snapshot 다음 hourly 발화에서 "Found 13 volumes" 로그 확인
3. critical-backup 6h 경계에서 BackupVolume CR 생성 + R2 객체 증가 확인
4. 오타 소스 (Tofu/Helm/ArgoCD) 추적 및 소스 단계에서 수정 (헤임달)
5. Obsidian `infra/k3s-backup.md` 라벨 키 정정 + warning callout 추가 (이 문서)
## 교훈
- **초기 구축 후 최소 1주기 경과 후 실 작동 검증 필수.** 등록 직후 BackupTarget AVAILABLE/RecurringJob 등록 확인만으로는 부족 — recurring 컨트롤러가 실제로 매칭한 볼륨 수와 BackupVolume CR 생성 여부를 확인해야 함.
- **Longhorn recurring 라벨 오류는 silent failure.** 모니터링 알림에 "최근 24h backups CR 0건" 또는 "BackupVolume 마지막 시각 > N시간" 임계값을 추가할 필요.
- **공식 키와 문서 키 일치 검증.** Longhorn 공식 문서의 라벨 키와 운영 문서/Tofu 코드를 cross-check.
## 관련 문서
- 운영 문서: `infra/k3s-backup.md`
- 점검 리포트: Outline `Longhorn 백업 상태 점검 — 2026-04-15` (id `7549c8ac-eebc-4cf4-8d1d-2a5e34c27c2f`)

View File

@@ -0,0 +1,67 @@
---
date: 2026-04-15
topic: pgcat HA 승격 (Step 0)
areas:
- infra/postgresql-ha.md
tags: [history, pgcat, patroni, postgresql, ha]
---
Patroni multi-host 마이그레이션 Step 0 — pgcat 자체를 HA로 승격. 이후 Step 1에서 pgcat 를 Patroni REST API aware 로 전환할 때, pgcat 자체가 새 SPoF 가 되지 않도록 선행.
## 변경 내용
### helm-charts (`kaffa/helm-charts`)
- `charts/app` v0.3.0 → **v0.5.0**
- `templates/deployment.yaml`: `affinity`, `topologySpreadConstraints` 블록 지원 추가
- `templates/pdb.yaml` 신규 — `podDisruptionBudget.minAvailable` / `maxUnavailable` 지원
- `values/pgcat.yaml`:
- `replicaCount: 2`
- `affinity.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution` (`app=pgcat`, `topologyKey: kubernetes.io/hostname`)
- `podDisruptionBudget.minAvailable: 1`
Commit: `421baef` (`pgcat HA: replicas=2, podAntiAffinity(hostname), PDB minAvailable=1`)
### ArgoCD
`pgcat` Application 은 `syncPolicy.automated { prune: true, selfHeal: true }` 설정. git push 직후 `argocd.argoproj.io/refresh=hard` annotate 로 즉시 sync 트리거.
## 롤아웃 관찰
- 시작: pgcat-545b8878b9-n45h8 (kr2) 단독 Running
- 중간: 구 RS + 신 RS surge 상태 (최대 4 pod), `maxSurge=25%` / `maxUnavailable=25%` 기본 strategy 동작
- 종료 (~55초): pgcat-549446cd6b-4hnk2 (**kr1**) + pgcat-549446cd6b-9rhk4 (**hp2**) 2/2 Running
- PDB `pgcat`: `minAvailable=1`, `AllowedDisruptions=1` — 정상
### 노드 분산 검증
```
pgcat-549446cd6b-4hnk2 2/2 Running incus-kr1 10.42.1.107
pgcat-549446cd6b-9rhk4 2/2 Running incus-hp2 10.42.2.175
```
kr1 + hp2 — 서로 다른 노드. PodAntiAffinity 동작 확인.
### 서비스 Endpoints
```
pgcat 10.42.1.107:6432,10.42.2.175:6432
```
두 replica 모두 Service 뒤에 등록.
## 다운스트림 영향
rollout 중 NocoDB/n8n 로그 tail. 결과:
- NocoDB: 단일 `Connection Error: Connection ended unexpectedly` 1회 — pod 재시작 없음, 45h 업타임 유지. knex/pg 자동 재연결 동작.
- n8n: 에러 없음.
## 롤백 경로
- `values/pgcat.yaml` 에서 `replicaCount: 2``1`, `affinity`/`podDisruptionBudget` 블록 제거
- 또는 chart 측: 구 버전 (v0.3.0) 으로 `targetRevision` 지정 불가능 (단일 리포지토리 path) — 필요 시 역커밋
## 후속 작업
- Step 1: pgcat `pgcat.toml``use_patroni_api = true` + `patroni_api_port = 8008` 추가, 각 pool `shards.0.servers` 를 Patroni 3노드 IP (`10.100.2.5`, `10.100.3.185`, `10.100.1.83`) 로 교체. 관련 Outline 문서: `07497dd8-c8f7-4027-bb27-7d2cf10623a0`

View File

@@ -0,0 +1,88 @@
---
title: vault-mcp-server 중복 배포 의혹 조사 — 실제 단일 인스턴스
date: 2026-04-15
tags: [history, investigation, vault, mcp, k3s, incus]
---
## 사건 발단
Graphify 초기 빌드(2026-04-15) 결과에서 `semantically_similar_to` 엣지로 아래 두 노드가 연결됨:
- `MCP vault (10.253.101.58:8080)``dev/claude-code-setup.md` 출처
- `vault-mcp-server v0.2.0 (Go, port 8080)``infra/vault.md` 출처
graphify는 "놀라운 연결(Surprising Connections)"로 제시했고, 두 판이 실제로 다른 시스템인지 같은 시스템의 두 기록인지 정합성 확인이 필요했음.
## 조사 결과 — 중복 없음
실제 vault-mcp-server 프로세스는 **jp1 Incus `vault` 컨테이너 (10.253.101.58)** 단일 인스턴스만 존재. 접근 경로가 3개라서 문서상 두 시스템으로 보였을 뿐.
### 실 아키텍처
```
┌──── hcv.inouter.com (Host) ──────────────> Traefik@K3s:443 ──> 10.253.101.58:8200 (Vault UI/API)
사용자 ───┼──── vault-mcp.inouter.com ────────────────> Traefik@K3s:443 ──> 10.253.101.58:8080 (Vault MCP)
kappa ────┴──── http://10.253.101.58:8080/mcp ─────────────────────────────> 같은 프로세스 (Tailscale 직결)
10.253.101.58 = jp1 Incus `vault` 컨테이너 (Vault 본체 + vault-mcp-server + systemd)
```
### K3s `tools/vault-mcp` 의 정체
- ArgoCD 앱 `argocd/vault-mcp` Synced/Healthy 상태지만
- 렌더링 리소스는 **Service 1개 + IngressRoute 2개** 뿐 (Deployment/Pod/StatefulSet/HPA 전무)
- Service selector 비어 있고 수동 EndpointSlice 가 10.253.101.58 로 고정
- 즉 K3s는 **Pod 없는 리버스 프록시 파사드**. 실 처리는 Tailscale로 jp1 컨테이너까지 직접 전달
- Helm chart: `gitea.inouter.com/kaffa/helm-charts charts/app` + `values/vault-mcp.yaml` (deployment 블록 없는 ingress-only 렌더링)
### K3s에 Vault ns 자체가 없음
- `kubectl get ns | grep vault` → 없음
- `vault-active.vault.svc.cluster.local:8200` 경로는 **존재하지 않는 경로** (이전 문서에 이 DNS 이름이 적혀 있었으나 오류)
- Vault 본체도 jp1 컨테이너 단일 운영 (`hcv.inouter.com/v1/sys/health` 200 active 확인)
### 실 트래픽
24h Traefik 액세스 로그 집계:
| Host | 총 요청 | 비고 |
|---|---|---|
| `vault-mcp.inouter.com` | 4 | 전부 조사 중 kappa가 쏜 curl |
| `hcv.inouter.com` (`/mcp*` 경로) | 1 | 내부 LAN HEAD 307 (UI 리다이렉트) |
**실 운영 트래픽 0건.** kappa의 Claude Code는 `http://10.253.101.58:8080/mcp` (Tailscale)로 직접 호출 → Traefik 거치지 않음 → K3s 로그에 흔적 없음.
- BunnyCDN / Cloudflare 비경유 (응답 헤더에 `cf-ray`, `server: cloudflare`, BunnyCDN 시그니처 없음)
- Traefik wildcard cert `wildcard-inouter-tls` 로 직접 TLS 종단
- K3s MetalLB 192.168.9.53 (내부 LAN/Tailscale 전용)
## 확정된 사실
1. **중복 인스턴스 아님.** 하나의 vault-mcp-server 프로세스 + 세 접근 경로
2. **`hcv.inouter.com/mcp` 는 MCP 엔드포인트가 아님.** Vault가 `/mcp``/ui/` 307 리다이렉트. MCP 외부 접근은 `vault-mcp.inouter.com/mcp`
3. **kappa 현행 사용 경로**: `http://10.253.101.58:8080/mcp` (Tailscale 직결, 평문 HTTP)
4. **외부 공개 경로는 현재 실 사용 0건** — CDN 비경유, LAN 전용
## 조치
- Obsidian `infra/vault.md` MCP 서버 섹션 전체 재작성 — 실 아키텍처 반영, 잘못된 문서화 정정 callout 추가
- Graphify 그래프는 다음 `graphify update` 시 정정된 Obsidian 원문 기반으로 재추출 예정 (노드 관계 정리)
## 선택적 후속 과제
- **kappa MCP URL 전환 검토**: `http://10.253.101.58:8080/mcp``https://vault-mcp.inouter.com/mcp`. Traefik TLS + wildcard cert 혜택. 단, Tailscale 종속성 유지라 큰 이득 아님
- **K3s 파사드 유지 여부**: 24h 트래픽 0건 + CDN 비경유인데 K3s 파사드를 유지할 실용 이유가 크지 않음. 외부 공개 의도가 없다면 ArgoCD 앱 제거 검토. 외부 공개할 거면 Cloudflare Zone + DNAT 추가
- **jp1 systemd 로그 접근 권한**: Heimdall 컨테이너에서 jp1 호스트 SSH 거부됨 (공개키 미등록). heimdall 라인에서 vault-mcp 상태를 직접 확인하려면 키 등록 필요
## 교훈
- **Graphify `semantically_similar_to` 는 "같은 실체"가 아니라 "비슷한 것"만 뜻함.** 동일 프로세스에 대한 서로 다른 관점(접근 경로별 명명)이 두 노드로 분리되어 기록되어 있었음. 향후 정본 작성 시 접근 경로가 여러 개면 같은 노드 하위에 묶는 서술이 그래프 정합성에 유리
- **ArgoCD Healthy 상태만 보고 "Deployment 돌아가는 중"이라 추정하지 말 것.** `kubectl get all -n <ns>` 로 실 리소스 타입 확인 필수. Pod 없는 파사드를 Deployment로 문서화한 게 이번 오류의 원인
- **문서에 DNS/FQDN 적을 때 실제 존재하는지 검증하는 습관.** `vault-active.vault.svc.cluster.local:8200`은 실존하지 않는 경로였으나 문서에 들어가 있었음
## 참조
- Outline 조사 리포트: `vault-mcp-server 중복 배포 조사 — 2026-04-15` (id `5b6ddffa-de07-401e-8a3b-3edfcd68a1a9`)
- 정정된 정본: `infra/vault.md` MCP 서버 섹션

View File

@@ -0,0 +1,58 @@
---
date: 2026-04-16
topic: kine pgx multi-host 직결 (HAProxy 의존 제거)
areas:
- infra/postgresql-ha.md
tags: [history, k3s, kine, pgx, patroni, postgresql, migration]
---
K3s kine 의 `datastore-endpoint` 를 HAProxy `192.168.9.1:5432` 에서 Patroni 3노드 pgx multi-host 로 전환. API 다운타임 <1초.
## 변경 사항
### K3s config (`/etc/rancher/k3s/config.yaml`, kr1 + kr2)
before:
```yaml
datastore-endpoint: "postgres://kine:kine@192.168.9.1:5432/kine"
```
after:
```yaml
datastore-endpoint: "postgres://kine:kine@10.100.2.5:5432,10.100.3.185:5432,10.100.1.83:5432/kine?target_session_attrs=read-write&sslmode=disable"
```
### 실행 순서
1. **kr2** (init server): config backup → sed 치환 → `systemctl restart k3s` → kubectl get nodes 확인 (5초 내 Ready)
2. **kr1** (secondary server): 동일 → 10초 내 Ready
3. 4노드 전체 Ready 확인 (hp1, hp2, kr1, kr2), API 응답 시간 ~200ms
### Patroni switchover 테스트
`patronictl switchover --leader postgres-1 --candidate postgres-3 --force` (TL12→13):
- switchover 커맨드 반환: 4.8초
- kubectl get nodes 첫 응답: **t+2ms** (즉시) — `incus-hp1 Ready`
- API 정상 응답 시간: 231ms
- **API 다운타임: <1초**
kine 의 pgx `target_session_attrs=read-write` 가 자동으로 새 primary (postgres-3, 10.100.1.83) 에 재연결.
비교: HAProxy 경유 시 health check `inter 3s fall 3` = 최대 9초 감지 지연. pgx multi-host 방식은 connection-level 에서 즉시 재시도.
## 롤백
```bash
sudo cp /tmp/config.yaml-backup-20260416 /etc/rancher/k3s/config.yaml
sudo systemctl restart k3s
```
## HAProxy :5432 상태
kine + pgpool(NocoDB/n8n/Outline) 전환 완료로 HAProxy postgres 프론트엔드 (`ft_postgres` + `bk_postgres_primary`) 는 더 이상 트래픽 없음. 1주 관측 후 제거.
## 참조
- `infra/postgresql-ha.md` — 「K3s kine 연결」 갱신
- kine pgx 지원 확인: `k3s-io/kine` go.mod `github.com/jackc/pgx/v5`

View File

@@ -0,0 +1,93 @@
---
date: 2026-04-16
topic: pgcat + Patroni TCP keepalive 적용 (Step 1 옵션 B)
areas:
- infra/postgresql-ha.md
tags: [history, pgcat, patroni, postgresql, tcp-keepalive]
---
Patroni multi-host 마이그레이션 원래 Step 1 계획(pgcat Patroni REST API 연동)이 소스 재조사 결과 **pgcat 1.2.0 에 해당 기능 미존재** 판정 → kappa 결정에 따라 옵션 B(HAProxy 구조 유지 + TCP keepalive 추가)로 선회.
## 배경
2026-04-15 19:53 UTC Patroni TL5→6 재선출 후 n8n 이 24h 동안 1038건 "Database connection timed out" 에러. 조사 결과 n8n TypeORM 풀의 단일 소켓이 failover 2분 전 열린 채 7588초간 idle 유지 → pgcat 까지 요청 도달조차 못하는 좀비 상태 (`230ec530-b8a6-406c-9165-35c9eb2d8282` 조사 문서).
n8n rollout restart 로 즉시 정상화. 재발 방지를 위해 **pgcat 와 Patroni 양쪽에 TCP keepalive 명시 활성화**.
## 변경 내용
### helm-charts (commit `7ebd7e3`)
`values/pgcat.yaml` `configMaps.pgcat-config.pgcat.toml` `[general]` 섹션:
```toml
tcp_keepalives_idle = 60
tcp_keepalives_interval = 10
tcp_keepalives_count = 3
```
pgcat 1.2.0 에서 실제로 지원하는 정확한 키명 (`pgcat.toml` 예제 파일 및 `src/config.rs` 확인). 서버 소켓에 적용.
### Patroni (DCS)
```bash
patronictl -c /etc/patroni.yml edit-config \
-p tcp_keepalives_idle=60 \
-p tcp_keepalives_interval=10 \
-p tcp_keepalives_count=3 --force
patronictl reload nocodb-cluster --force
```
3노드 전체 postgresql.conf 에 반영, SIGHUP 으로 적용 (재시작 불필요). 새 TCP 연결부터 keepalive 옵션 적용.
**주의**: `SHOW tcp_keepalives_idle` 을 unix socket (psql local) 에서 실행하면 0으로 출력. TCP 세션 확인 필수:
```bash
PGPASSWORD=... psql -h 192.168.9.1 -p 5432 -U n8n -d n8n -c "SHOW tcp_keepalives_idle"
# → 60
```
## 검증 — Patroni switchover 재현 테스트
**before**: TL6 leader postgres-3 (10.100.1.83)
**action**: `patronictl switchover --leader postgres-3 --candidate postgres-2 --force`
**after**: TL7 leader postgres-2 (10.100.3.185)
### pgcat → 새 leader 도달 시간
```
T1=22:24:37.696 UTC switchover 커맨드 반환
t+0002ms pgcat 경유 SELECT → "connection lost" (HAProxy 아직 구 leader 마크)
t+5175ms 여전히 connection lost
t+7331ms 10.100.1.83 응답 (구 leader, 이제 replica — SELECT 는 성공)
t+8461ms 10.100.3.185 응답 (새 leader)
```
**write 가능 복구 시간: ~8.5초.** HAProxy `inter 3s fall 3` (9초 감지) 이론치와 일치. keepalive 와 독립 — HAProxy 헬스체크 파라미터가 결정.
### 애플리케이션 영향 (switchover 전후 ~150초 창)
| 서비스 | 에러 수 | 타입 |
|---|---|---|
| NocoDB | 1 | "unexpected EOF" 1회 |
| n8n | 9 건 (7초 윈도우) | SocketError/UnexpectedEof, Connection terminated, AllServersDown, Database connection timed out |
**이전 2026-04-15 failover (1038건, 2시간 지속) 대비 극적 대비.** 좀비 소켓 없음.
## 롤백
- `values/pgcat.yaml` 에서 `tcp_keepalives_*` 3줄 제거 → ArgoCD sync → pgcat rolling restart
- Patroni: `patronictl edit-config` 에서 동일 3개 파라미터 제거 → reload
## 관측 기간
7일 (2026-04-23까지). 다음 Patroni failover 발생 시 app 에러 윈도우 < 10초 지속 여부 확인. 지속 시 원인 재조사.
## 관련 문서
- `infra/postgresql-ha.md` — 「좀비 소켓 방지 — TCP keepalive」 섹션 신설
- `history/2026-04-16-pgcat-patroni-api-audit-correction.md` — (별도 예정) pgcat Patroni API 미지원 정정, 원 감사 07497dd8 의 PR #944 cite 오류
- helm-charts commit: https://gitea.inouter.com/kaffa/helm-charts/commit/7ebd7e3
## 후속
- kappa 지시: **PgPool-II PoC** (이 작업 안정화 후 별도 지시 예정)

View File

@@ -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」 로 교체

View File

@@ -0,0 +1,156 @@
---
date: 2026-04-16
topic: pgpool-II PoC (n8n 전용 전환)
areas:
- infra/postgresql-ha.md
tags: [history, pgpool, pgcat, patroni, postgresql, poc]
---
n8n 이 Patroni failover 와 etcd 순단에 풀 좀비로 취약 — pgcat 의 클라이언트-측 연결 관리로는 해소 불가. pgpool-II streaming_replication 모드 PoC 로 n8n 만 전환, 검증 결과 양쪽 시나리오에서 `무에러 또는 <2초 자가복구` 달성.
## 전환 경로
- 전환 전: n8n → pgcat.db.svc:6432 → OpenWrt HAProxy 192.168.9.1:5432 → Patroni leader
- 전환 후: n8n → pgpool.db.svc:9999 → Patroni 3노드 직결
NocoDB·Outline 은 pgcat 유지. 7일 관측 후 확대 여부 판단.
## 이미지 선택
- kappa 원 스펙: `pgpool/pgpool:4.6.3 (공식)` — 존재하지 않음. `pgpool/pgpool` 리포지토리 최대 태그 **4.4.3** (2024 이후 방치), 이후 버전은 `bitnamilegacy/pgpool` 에만 존재
- 1차 시도: Bitnami 4.6.3 — env 기반 설정 자동화가 scram-sha-256 패스워드를 pgpool 내부 포맷으로 재해시해서 백엔드 인증 실패. 디버깅 폐기 (kappa 지시)
- 2차 채택: **`pgpool/pgpool:4.4.3`** (공식) + 최소 ConfigMap 마운트
## 최종 설정 (`helm-charts/pgpool/`)
ArgoCD 관리 대상. `kaffa/helm-charts` 리포 `pgpool/` 디렉토리 raw manifest. Application 매니페스트 `kubectl apply` 로 1회 부트스트랩.
### 핵심 구성
`pgpool.conf` (~30 라인):
```
listen_addresses = '*'
port = 9999
backend_clustering_mode = 'streaming_replication'
backend_hostname0 = '10.100.2.5' # postgres-1
backend_port0 = 5432
backend_weight0 = 1
backend_flag0 = 'ALLOW_TO_FAILOVER'
# ... backend1=postgres-2, backend2=postgres-3 동일 패턴
sr_check_user = 'sr_check'
sr_check_password = '<plaintext>'
sr_check_database = 'postgres'
sr_check_period = 10
health_check_user = 'sr_check'
health_check_password = '<plaintext>'
health_check_period = 10
health_check_timeout = 5
failover_on_backend_error = off # Patroni 가 promotion 수행
failover_command = ''
follow_primary_command = ''
use_watchdog = off
enable_pool_hba = on
allow_clear_text_frontend_auth = on
load_balance_mode = off # 모든 쿼리 primary
num_init_children = 32
max_pool = 4
connection_cache = on
```
`pool_hba.conf`:
```
local all all trust
host all all 0.0.0.0/0 password
host all all ::/0 password
```
### 인증 설계 핵심
Postgres 가 cluster-wide `password_encryption = scram-sha-256` 이라 모든 역할 비밀번호는 SCRAM 해시로 저장. pgpool 이 백엔드에 scram-sha-256 auth 하려면 plaintext 필요.
- `pool_passwd` **미사용** — pgpool 이 plaintext 엔트리를 자동 md5 로 변환하면 backend SCRAM 거절되어 `failed to authenticate with backend using SCRAM` 발생
- `allow_clear_text_frontend_auth=on` + pool_hba `password` 메소드 → 클라이언트가 plaintext 로 전송 → pgpool 이 그 값을 backend SCRAM challenge-response 에 그대로 사용
- K8s Service 내부 트래픽이므로 clear-text 는 클러스터 내 허용
- 장기: pgpool PGPOOLKEYFILE + AES 암호화 password 도입 검토 (Bitnami 가 하는 방식)
### Secret 처리
`pgpool-secrets` K8s Secret (수동 `kubectl create`, git 미추적):
- `sr_check_password` — 16-byte hex 랜덤
- `n8n_password``n8n` (pgcat 와 동일)
entrypoint.sh 가 env 에서 sed 로 `pgpool.conf.tmpl``__SR_CHECK_PASSWORD__` 를 치환하여 emptyDir 에 `pgpool.conf` 렌더. 정규 운영 승격 시 ExternalSecret + Vault 로 이관.
### Patroni 에 sr_check role 생성
```sql
CREATE ROLE sr_check WITH LOGIN REPLICATION PASSWORD '<hex>';
GRANT pg_monitor TO sr_check;
```
postgres-2(당시 leader) 에서 실행, async streaming 으로 postgres-1/postgres-3 에 자동 전파. pg_hba 는 `host all all 0.0.0.0/0 md5` 이미 설정되어 있어 추가 변경 불필요.
## 검증 시나리오
### 1. Patroni switchover (postgres-3 → postgres-2)
```
T0 T1(+4.0s) T1+2s
switchover → done new primary 라우팅 확립
```
- n8n 로그: `failed to create a backend 2 connection` × 1 → `Database connection recovered`
- pgpool `SHOW POOL_NODES`: `last_status_change` 가 switchover 시점에 갱신, `role=primary` 가 새 노드로 이동
- n8n.inouter.com 200 유지
- **자동 복구 ~2초**
비교: 같은 시나리오 pgcat 에서는 client 측 pg 풀이 idle 소켓 재사용으로 좀비 → pod restart 필요 (2026-04-15 19:53 UTC 사고 1038 에러).
### 2. mbp etcd 60초 stop
```
T0 T+30s 중간 쓰기 T+60s T+70s
mbp stop → SELECT OK, write OK → mbp start → 확인: 계속 쓰기 OK
```
- n8n 에러 **0건**, HTTP 200 유지
- pgpool 이 backend Patroni 자체를 직접 봄 — etcd 쿼럼은 2노드(NAS + jp1) 로 유지되어 Patroni leader 변경 없음, pgpool 경로 영향 없음
비교: pgcat 경로에서는 mbp etcd 56초 다운 시 n8n `Database connection timed out` 캐스케이드 → 503 → pod restart 필수 (오늘 오후 인시던트).
## 배포 아티팩트
- helm-charts 디렉토리: `pgpool/``configmap.yaml`, `deployment.yaml`, `service.yaml`, `pdb.yaml`
- ArgoCD Application: `pgpool` (namespace `argocd`, source path `pgpool`, selfHeal + prune)
- K8s 리소스: namespace `db` (pgcat 와 공존)
## 롤백
n8n ConfigMap `n8n-app-config` 에서 `DB_POSTGRESDB_HOST``pgpool.db.svc.cluster.local:9999` 에서 `pgcat.db.svc.cluster.local:6432` 로 되돌리고 rollout restart. 소요 시간 ~30초.
## 7일 관측 계획 (만료 2026-04-23)
- 자연 Patroni failover 발생 시 n8n 에러 창 <5초 유지 여부
- pgpool `SHOW STATS` / `SHOW POOL_NODES` 주간 샘플링
- n8n 일별 DB 에러 카운트 (목표 <10/day 비-failover 시)
- pgpool 리소스 사용량 (num_init_children=32 × max_pool=4 = 최대 128 backend connections per pod × 2 pod = 256 total. 현재 n8n 평균 idle 수준 확인 필요)
긍정 관측이면 NocoDB → pgpool, Outline → pgpool 단계 전환. 부정이면 pgpool 해체 + pgcat 복귀.
## 참조
- helm-charts commit 시리즈: a6f5991(초기) → e1dcd6d(bitnami 전환) → 213babb(scram) → 13dfae1(port 수정) → d3dde47(detach_false_primary) → bc6faae(공식 이미지 피봇) → 74ca477(pool_passwd 제거) → **9bc3a24(clear-text auth, 최종)**
- 선행 조사: n8n 풀 좀비 `230ec530-b8a6-406c-9165-35c9eb2d8282`
- pgcat 후속: TCP keepalive `129fbf50-e69b-47fd-ad55-3f5ff9066caf`
- retry_timeout 상향 → mbp etcd hiccup 시 n8n 503 사고 (오늘 오후) → pgpool PoC 의 직접 동기
## 미해결 / Syn 공유
- `pgpool/pgpool` 4.4.3 이후 방치 — pgpool 공식 이미지의 미래 불확실. 대안: Bitnami legacy 계속 사용하거나 우리가 커스텀 빌드
- plaintext pool_passwd 우회 — scram-sha-256 백엔드 + pgpool 백엔드 auth 에 권고 방식은 AES 암호화. 1주 관측 후 하드닝 필요

View File

@@ -6,7 +6,7 @@ tags: [infra, backup]
## Longhorn PVC 백업 (K3s) ## Longhorn PVC 백업 (K3s)
BackupTarget `default` → R2 버킷 `longhorn-backup` (시크릿 `longhorn-backup-r2`). RecurringJob 4종 (critical-snapshot 매시, critical-backup 6h, standard-snapshot 일 1회 03:00 UTC, standard-backup 일 1회 04:00 UTC). BackupTarget `default` → R2 버킷 `longhorn-backup` (시크릿 `longhorn-backup-r2`). RecurringJob 4종 (critical-snapshot 매시 UTC, critical-backup 6h UTC, standard-snapshot `0 18 * * *` UTC = KST 03:00, standard-backup `0 19 * * *` UTC = KST 04:00).
### RecurringJob 그룹 볼륨 라벨 — **정확한 키 주의** ### RecurringJob 그룹 볼륨 라벨 — **정확한 키 주의**

View File

@@ -50,16 +50,6 @@ Traefik DaemonSet (stdout JSON accessLog)
| 오사카 Vector | Docker `timberio/vector:0.45.0-debian`, `/etc/vector/vector.yaml`, `docker_logs` source → `parse_apisix``vlogs` ES sink. `location: osaka` 필드 추가 | | 오사카 Vector | Docker `timberio/vector:0.45.0-debian`, `/etc/vector/vector.yaml`, `docker_logs` source → `parse_apisix``vlogs` ES sink. `location: osaka` 필드 추가 |
| 파서 | `custom/apisix-json-logs` (로컬) | | 파서 | `custom/apisix-json-logs` (로컬) |
### APISIX → CrowdSec (http-logger, 레거시 — 병행 중)
서울 APISIX가 CrowdSec HTTP acquisition으로 직접 push하는 기존 경로. VictoriaLogs 경로와 이중 수신 중이며 추후 제거 예정.
| 항목 | 값 |
|------|-----|
| 설정 | K3s APISIX GatewayProxy `global_rules/http-logger` (`uri: http://10.253.100.240:8085/apisix-logs`) |
| CrowdSec 포트 | 8085 |
| 인증 | `Authorization: apisix-crowdsec-log-2024` |
### APISIX → log-collector → CrowdSec (sandbox-tokyo) ### APISIX → log-collector → CrowdSec (sandbox-tokyo)
``` ```
@@ -172,7 +162,7 @@ ddos-detect AI 분석기 폐기 후 deterministic 패턴 매칭으로 대체.
- 현재 모두 `remediation: true` (즉시 ban). dry-run으로 시작 안 함 — false positive 발생 시 임계값 또는 ban duration 조정. - 현재 모두 `remediation: true` (즉시 ban). dry-run으로 시작 안 함 — false positive 발생 시 임계값 또는 ban duration 조정.
과거 인시던트 및 변경 이력은 `history/` 참조. 예: `history/2026-04-10-edge-cleanup.md` (cf-audit-cleanup-2 3-incident chain, Turnstile sitekey 교체, 미들웨어 64811 `/__captcha/verify` 버그 수정 등). 과거 인시던트 및 변경 이력은 `history/` 참조. 예: `history/2026-04-10-edge-cleanup.md` (cf-audit-cleanup-2 3-incident chain, Turnstile sitekey 교체, 미들웨어 64811 `/__captcha/verify` 버그 수정 등), [[../history/2026-04-15-apisix-http-logger-removal|2026-04-15 APISIX http-logger 레거시 제거]].
### 발견 사항: K3s APISIX 글로벌 limit-req ### 발견 사항: K3s APISIX 글로벌 limit-req
@@ -285,8 +275,9 @@ Kappa 계정용 `cs-cf-worker-bouncer`와 별도 컨테이너로 분리 운영.
- tengine 미사용, APISIX 직접 연동 - tengine 미사용, APISIX 직접 연동
### 3차: CrowdSec 로그 분석 ### 3차: CrowdSec 로그 분석
- Traefik 로그: Vector DaemonSet → `:8086``crowdsecurity/traefik-logs` - Traefik / APISIX 로그: Vector → VictoriaLogs(`vl.inouter.com`)CrowdSec `victorialogs` acquisition (tail 모드)
- APISIX 로그: http-logger → `:8085``custom/apisix-json-logs` - SafeLine 차단: PG NOTIFY → safeline-listener → CrowdSec HTTP acquisition(`:8088`)
- sandbox-tokyo APISIX: http-logger → log-collector(`:8087`) → CrowdSec
- HTTP 시나리오 매칭 → decision → bouncer 피드백 - HTTP 시나리오 매칭 → decision → bouncer 피드백
## iron-kr-waf BunnyCDN Pull Zone (구 waf-kr) ## iron-kr-waf BunnyCDN Pull Zone (구 waf-kr)

58
infra/hosts/incus-hp1.md Normal file
View File

@@ -0,0 +1,58 @@
---
title: incus-hp1
updated: 2026-04-16
tags: [infra, host, incus, k3s, seoul]
type: host
host_kind: server
location: seoul
provider: self-hosted
status: active
ssh_host: incus-hp1
public_ip: null
tailscale_ip: null
lan_ip: 192.168.9.227
os: Debian 13
cpu_model: Xeon E5-2670
cpu_cores: 32
ram_gb: 188
k3s_role: worker
critical: false
monthly_cost_usd: 0
---
## 역할
Incus + K3s 워커 호스트 (서울존). HP ProLiant DL360p Gen8 베어메탈. K3s 클러스터에서 worker(k3s-agent)로 참여.
## 네트워크
- LAN: 192.168.9.227 (eno1, 1GbE — 2.5G 어댑터 없음)
- iLO: 192.168.9.140 (hp1-ilo.lan)
- Tailscale: 미설치
## 스토리지
- sda 273G: 시스템 (/ ext4 259G + swap 14G)
- nvme0n1 954G: Incus btrfs storage pool `default`
## Incus
- 버전: 6.0.4 (Debian 패키지)
- storage pool: `default` (btrfs, /dev/nvme0n1)
- network: `incusbr0` (10.100.4.1/24, NAT)
- profile: default (incusbr0 + root on default pool)
## K3s
- 버전: v1.34.5+k3s1
- 역할: worker (k3s-agent)
- server: https://192.168.9.135:6443 (incus-kr2)
- config: /etc/rancher/k3s/config.yaml
## 상세
- 2026-04-16 신규 셋업 (Incus + K3s agent)
- hp2와 동일 Gen8 스펙이나 2.5G LAN 미탑재 (1G only)
- Longhorn replica 참여 시 nvme에 별도 마운트 필요
상세 인프라 컨텍스트: [[../infra-hosts]]

View File

@@ -16,6 +16,7 @@ tags: [infra, network, kr-zone, openwrt]
| incus-jp1 | 100.109.123.1 | Incus 호스트 (도쿄) | agents, db, default, monitoring 프로젝트 | | incus-jp1 | 100.109.123.1 | Incus 호스트 (도쿄) | agents, db, default, monitoring 프로젝트 |
| incus-kr1 | 100.84.111.28 | Incus+K3s 호스트 (서울) | GTX 1080 Ti, K3s control-plane (LAN 192.168.9.214), default 프로젝트 | | incus-kr1 | 100.84.111.28 | Incus+K3s 호스트 (서울) | GTX 1080 Ti, K3s control-plane (LAN 192.168.9.214), default 프로젝트 |
| incus-kr2 | 100.119.109.41 | Incus+K3s 호스트 (서울) | K3s control-plane (LAN 192.168.9.135), default, inbest 프로젝트 | | incus-kr2 | 100.119.109.41 | Incus+K3s 호스트 (서울) | K3s control-plane (LAN 192.168.9.135), default, inbest 프로젝트 |
| incus-hp1 | — | Incus+K3s 호스트 (서울) | **HP ProLiant DL360p Gen8** 베어메탈, Xeon E5-2670 32코어, 188GB RAM, K3s worker/k3s-agent (LAN 192.168.9.227), 1GbE only (2.5G 미탑재), Tailscale 미설치, default 프로젝트, 2026-04-16 신규 |
| incus-hp2 | 100.100.52.34 | Incus+K3s 호스트 (서울) | **HP ProLiant DL360p Gen8** 베어메탈, Xeon E5-2670 32코어, 188GB RAM, 커널 6.12.74+deb13+1 (2026-04-14 업데이트), K3s worker/k3s-agent (LAN 192.168.9.134), default, inbest 프로젝트 | | incus-hp2 | 100.100.52.34 | Incus+K3s 호스트 (서울) | **HP ProLiant DL360p Gen8** 베어메탈, Xeon E5-2670 32코어, 188GB RAM, 커널 6.12.74+deb13+1 (2026-04-14 업데이트), K3s worker/k3s-agent (LAN 192.168.9.134), default, inbest 프로젝트 |
| openwrt-gw | 100.66.60.66 | **OpenWrt 라우터 (서울, critical)** | HAProxy: 80/443 → MetalLB Traefik(192.168.9.53:80/443), 9080/9443 → MetalLB APISIX(192.168.9.50:80/443), **5432 → Patroni PostgreSQL Leader (K3s kine 데이터스토어 진입점, [[postgresql-ha]] 참조)**. 이 노드 다운 시 K3s API/HTTP 진입 모두 중단 | | openwrt-gw | 100.66.60.66 | **OpenWrt 라우터 (서울, critical)** | HAProxy: 80/443 → MetalLB Traefik(192.168.9.53:80/443), 9080/9443 → MetalLB APISIX(192.168.9.50:80/443), **5432 → Patroni PostgreSQL Leader (K3s kine 데이터스토어 진입점, [[postgresql-ha]] 참조)**. 이 노드 다운 시 K3s API/HTTP 진입 모두 중단 |
| zlambda (구 sandbox-tokyo) | 100.78.51.18 | [[zlambda|NixOS 베이스 호스트]] (도쿄, Linode `zlambda`) | NixOS 25.05 (Warbler), 공인 139.162.71.52, sshd+tailscale+docker, 2026-04-08 Debian→NixOS 전환 (이전 APISIX/etcd/microsocks/tlsproxy/vault-prod/wg-easy 모두 제거됨), Linode 프로필 kernel=`linode/direct-disk`, BBR+fq+sysctl 튜닝, configuration: Gitea [`kaffa/nixos-infra`](https://gitea.inouter.com/kaffa/nixos-infra) (kaffa-macmini `~/nixos-infra/`, zlambda `/root/nixos-infra/`) | | zlambda (구 sandbox-tokyo) | 100.78.51.18 | [[zlambda|NixOS 베이스 호스트]] (도쿄, Linode `zlambda`) | NixOS 25.05 (Warbler), 공인 139.162.71.52, sshd+tailscale+docker, 2026-04-08 Debian→NixOS 전환 (이전 APISIX/etcd/microsocks/tlsproxy/vault-prod/wg-easy 모두 제거됨), Linode 프로필 kernel=`linode/direct-disk`, BBR+fq+sysctl 튜닝, configuration: Gitea [`kaffa/nixos-infra`](https://gitea.inouter.com/kaffa/nixos-infra) (kaffa-macmini `~/nixos-infra/`, zlambda `/root/nixos-infra/`) |
@@ -24,10 +25,11 @@ tags: [infra, network, kr-zone, openwrt]
## 서울 K3s 클러스터 ## 서울 K3s 클러스터
서울존 3대(kr1, kr2, hp2)를 K3s v1.34.5+k3s1 클러스터로 구성. **kr1/kr2는 control-plane, hp2는 worker(k3s-agent)**. 서울존 4대(kr1, kr2, hp1, hp2)를 K3s v1.34.5+k3s1 클러스터로 구성. **kr1/kr2는 control-plane, hp1/hp2는 worker(k3s-agent)**.
| 노드 | LAN IP | OS | | 노드 | LAN IP | OS |
|------|--------|----| |------|--------|----|
| incus-hp1 | 192.168.9.227 | Debian 13 (trixie) |
| incus-hp2 | 192.168.9.134 | Debian 13 (trixie) | | incus-hp2 | 192.168.9.134 | Debian 13 (trixie) |
| incus-kr1 | 192.168.9.214 | Debian 13 (trixie) | | incus-kr1 | 192.168.9.214 | Debian 13 (trixie) |
| incus-kr2 | 192.168.9.135 | Debian 13 (trixie) | | incus-kr2 | 192.168.9.135 | Debian 13 (trixie) |
@@ -264,6 +266,7 @@ Docker: `--runtime=nvidia` 또는 `--gpus all`로 GPU 사용. Podman: CDI 방식
└── OpenWrt 라우터 (공인 IP: 220.120.65.245, 내부: 192.168.9.1) └── OpenWrt 라우터 (공인 IP: 220.120.65.245, 내부: 192.168.9.1)
├── incus-kr1 (192.168.9.214) ← K3s control-plane ├── incus-kr1 (192.168.9.214) ← K3s control-plane
├── incus-kr2 (192.168.9.135, br-uplink 고정) ← K3s control-plane ├── incus-kr2 (192.168.9.135, br-uplink 고정) ← K3s control-plane
├── incus-hp1 (192.168.9.227, 1GbE) ← K3s worker (k3s-agent)
└── incus-hp2 (192.168.9.134) ← K3s worker (k3s-agent) └── incus-hp2 (192.168.9.134) ← K3s worker (k3s-agent)
외부 트래픽 흐름 (TCP): 외부 트래픽 흐름 (TCP):
@@ -332,6 +335,7 @@ USB autosuspend/NFS hang 인시던트 이력: [[../history/2026-04-04-usb-25g-ha
|--------|----------------|------|------------| |--------|----------------|------|------------|
| incus-kr1 | 192.168.9.214 | control-plane | 서울 | | incus-kr1 | 192.168.9.214 | control-plane | 서울 |
| incus-kr2 | 192.168.9.135 | control-plane | 서울 | | incus-kr2 | 192.168.9.135 | control-plane | 서울 |
| incus-hp1 | 192.168.9.227 | worker (k3s-agent) | 서울 |
| incus-hp2 | 192.168.9.134 | worker (k3s-agent) | 서울 | | incus-hp2 | 192.168.9.134 | worker (k3s-agent) | 서울 |
## IPv6 prefix ## IPv6 prefix

View File

@@ -1,6 +1,6 @@
--- ---
title: K3s 백업 파이프라인 title: K3s 백업 파이프라인
updated: 2026-04-15 Longhorn 라벨 키 오타 수정 (recurring-job-group, 대시 포함). 자세히는 history/2026-04-15-longhorn-label-typo.md 참조 updated: 2026-04-15 Longhorn 라벨 키 오타 수정 (recurring-job-group, 대시 포함). 자세히는 history/2026-04-15-longhorn-backup-label-typo.md 참조
tags: [infra, backup, k3s, r2, longhorn, synology] tags: [infra, backup, k3s, r2, longhorn, synology]
--- ---
@@ -102,10 +102,12 @@ sudo /usr/local/bin/docker run --rm \
| 이름 | 그룹 | Task | Cron | 보존 | Concurrency | | 이름 | 그룹 | Task | Cron | 보존 | Concurrency |
|------|------|------|------|------|-------------| |------|------|------|------|------|-------------|
| critical-snapshot | critical | snapshot | `0 * * * *` (매시간) | 24 (1일치) | 2 | | critical-snapshot | critical | snapshot | `0 * * * *` UTC (매시간) | 24 (1일치) | 2 |
| critical-backup | critical | backup | `0 */6 * * *` (6시간마다) | 28 (7일치) | 1 | | critical-backup | critical | backup | `0 */6 * * *` UTC (6h 간격, KST 03/09/15/21시 포함) | 28 (7일치) | 1 |
| standard-snapshot | standard | snapshot | `0 3 * * *` (매일 03:00) | 7 (7일치) | 2 | | standard-snapshot | standard | snapshot | `0 18 * * *` UTC (= KST 03:00) | 7 (7일치) | 2 |
| standard-backup | standard | backup | `0 4 * * *` (매일 04:00) | 7 (7일치) | 1 | | standard-backup | standard | backup | `0 19 * * *` UTC (= KST 04:00) | 7 (7일치) | 1 |
> cron 은 UTC 기준. standard 그룹은 KST 새벽 트래픽 저점에 실행되도록 설정 (2026-04-15 조정).
> [!info] 보존 정책 통일 (2026-04-14) > [!info] 보존 정책 통일 (2026-04-14)
> 백업 보존을 일관적으로 **7일 기준**으로 통일. snapshot은 CoW 체인이라 개수보다 보존 기간이 중요 — critical은 1일치 시간 단위, standard는 7일치 일단위로 유지. > 백업 보존을 일관적으로 **7일 기준**으로 통일. snapshot은 CoW 체인이라 개수보다 보존 기간이 중요 — critical은 1일치 시간 단위, standard는 7일치 일단위로 유지.

View File

@@ -83,61 +83,98 @@ incus exec postgres-1 -- etcdctl --endpoints=http://192.168.9.100:2379,http://10
## K3s kine 연결 ## K3s kine 연결
K3s → HAProxy(OpenWrt 192.168.9.1:5432) → Patroni Leader PostgreSQL K3s → **pgx multi-host** → Patroni 3노드 직결 (HAProxy 미경유).
kine 은 `github.com/jackc/pgx/v5` 드라이버 사용 — libpq 호환 multi-host + `target_session_attrs=read-write` 지원. Patroni failover 시 pgx 가 자동으로 새 primary 에 재연결 (API 다운타임 <1초).
### K3s config ### K3s config
```yaml ```yaml
# /etc/rancher/k3s/config.yaml (kr1, kr2) # /etc/rancher/k3s/config.yaml (kr1, kr2)
datastore-endpoint: "postgres://kine:kine@192.168.9.1:5432/kine" datastore-endpoint: "postgres://kine:kine@10.100.2.5:5432,10.100.3.185:5432,10.100.1.83:5432/kine?target_session_attrs=read-write&sslmode=disable"
``` ```
### HAProxy (OpenWrt) 이전 구성 (HAProxy 경유, 2026-04-16 폐기): `postgres://kine:kine@192.168.9.1:5432/kine`
`/etc/haproxy.cfg`에 PostgreSQL backend 설정. Patroni REST API(`/primary` 엔드포인트)로 Leader를 자동 감지. ### 검증 (2026-04-16)
kr2 → kr1 순서로 rolling restart. switchover (postgres-1→postgres-3, TL12→13) 시 kine API 다운타임 **<1초** (t+2ms 에 정상 응답). [[../history/2026-04-16-kine-multihost-migration|history]]
### OpenWrt HAProxy :5432 프론트엔드
kine + pgpool 전환 완료로 HAProxy postgres 프론트엔드는 **더 이상 트래픽 없음**. 1주 관측 후 제거 예정.
## 애플리케이션 접속 경로 — pgpool-II
모든 K3s 내부 애플리케이션(NocoDB, n8n, Outline)은 **pgpool-II**를 통해 Patroni 3노드에 직접 접속. HAProxy 미경유.
``` ```
frontend ft_postgres NocoDB/n8n/Outline → pgpool.db.svc.cluster.local:9999 → Patroni 3노드 직결
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초) 이전 pgcat+HAProxy 구조는 2026-04-16 폐기. [[../history/2026-04-16-pgpool-full-migration|history]]
- K3s config 변경 없이 Leader 전환 대응
## 애플리케이션 접속 경로 ### pgpool 구성
NocoDB, n8n 등 K3s 내부 애플리케이션은 **pgcat**(연결 풀링)을 통해 PostgreSQL에 접속. - 이미지: `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`
### pgpool.conf 핵심
``` ```
nocodb/n8n → pgcat (db.svc.cluster.local:6432) → HAProxy 192.168.9.1:5432 → Patroni Leader backend_clustering_mode = 'streaming_replication'
backend_hostname0/1/2 = 10.100.2.5 / 10.100.3.185 / 10.100.1.83
backend_weight = 1, backend_flag = ALLOW_TO_FAILOVER
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 = '' (미사용 — 아래 인증 참조)
``` ```
`db/pgcat-config` ConfigMap의 각 풀의 `shards.0.servers`**HAProxy 단일 백엔드만 가리켜야 함**: ### 인증
```toml Postgres `password_encryption = scram-sha-256` cluster-wide.
[pools.nocodb.shards.0]
database = "nocodb"
servers = [["192.168.9.1", 5432, "primary"]]
[pools.n8n.shards.0] - `allow_clear_text_frontend_auth = on` + pool_hba method `password` → 클라이언트 plaintext 전송 → pgpool 이 backend scram-sha-256 challenge-response 에 직접 사용
database = "n8n" - `pool_passwd = ''` — 비활성. pgpool 이 plaintext pool_passwd 엔트리를 자동 md5 해시하여 backend scram 거절되는 문제 회피
servers = [["192.168.9.1", 5432, "primary"]] - K8s Service 내부 트래픽이라 clear-text 는 클러스터 내에서만 노출
### Patroni TCP keepalive (유지)
```
tcp_keepalives_idle = 60
tcp_keepalives_interval = 10
tcp_keepalives_count = 3
``` ```
⚠️ **하지 말 것**: pgcat에 Patroni 노드 IP(10.100.2.5/3.185/1.83)를 직접 박지 말 것. Patroni failover가 발생하면 pgcat는 옛 primary를 계속 가리키게 되어 nocodb/n8n이 read-only 에러 발생. pgpool 은 자체 연결 관리가 있어 클라이언트 좀비 문제 없지만, Postgres → pgpool 역방향 dead socket 감지용으로 Patroni 파라미터 유지.
pgcat는 풀링 전용으로만 쓰고, leader 탐지는 OpenWrt HAProxy에 위임. `query_parser_enabled = false` 설정 (read/write splitting 비활성). ### Patroni switchover 통합 테스트 (2026-04-16)
Patroni failover 인시던트 이력: [[../history/2026-04-08-patroni-failover-incident|2026-04-08 pgcat/nocodb/outline read-only 사고]] 3 서비스 전체 pgpool 경유 상태에서 `patronictl switchover --force`:
- write 복구: **즉시** (t+3ms)
- n8n: 3건 transient error · NocoDB: 0건 · Outline: 0건
- 전 서비스 HTTP 200 유지
이전 pgcat 대비: 동일 시나리오에서 n8n 1038건 / NocoDB 13건 / pod restart 필요
마이그레이션 이력: [[../history/2026-04-16-pgpool-n8n-poc|PoC]] · [[../history/2026-04-16-pgpool-full-migration|전면 전환]]
## APISIX etcd 사용 현황 ## APISIX etcd 사용 현황

View File

@@ -76,7 +76,35 @@ CA 등록 완료 서버:
## MCP 서버 ## MCP 서버
vault-mcp-server v0.2.0 (hashicorp/vault-mcp-server:0.2.0 Docker 이미지). K3s vault namespace에 Deployment로 배포. streamable-http 모드, 엔드포인트: https://hcv.inouter.com/mcp. 내부 통신: vault-active.vault.svc.cluster.local:8200. token은 Secret vault-mcp-token에 저장. Claude Code MCP 설정: type http, url https://hcv.inouter.com/mcp. K3s Ingress vault-mcp가 /mcp 경로를 vault-mcp-server Service(8080)로 라우팅. 로컬 바이너리도 /usr/local/bin/vault-mcp-server에 설치됨. ### 실 배포 (2026-04-15 정정)
vault-mcp-server v0.2.0 은 **jp1 Incus `vault` 컨테이너 (10.253.101.58) 단일 인스턴스**. systemd 유닛 `vault-mcp-server.service`로 streamable-http 모드 가동 (`--transport-host=0.0.0.0 --transport-port=8080 --mcp-endpoint=/mcp`). 같은 컨테이너에 HashiCorp Vault 본체(포트 8200)도 동일 프로세스 공간에 존재 → vault 백엔드는 `http://127.0.0.1:8200`.
> [!warning] 과거 문서 오류 정정 (2026-04-15)
> 이전 기록에 "K3s vault namespace Deployment 배포", "vault-active.vault.svc.cluster.local:8200" 이라 적혀 있었으나 실제와 다름. K3s 안에는 vault 파드/Deployment 없음. 상세: `history/2026-04-15-vault-mcp-duplicate-investigation.md`
### 접근 경로 (3가지, 모두 같은 jp1 프로세스로 수렴)
```
hcv.inouter.com → Traefik@K3s:443 → 10.253.101.58:8200 (Vault UI/API)
vault-mcp.inouter.com → Traefik@K3s:443 → 10.253.101.58:8080 (MCP)
http://10.253.101.58:8080/mcp (Tailscale 직결, kappa Claude 현행)
```
K3s `tools` 네임스페이스의 `vault-mcp`**Pod 없는 리버스 프록시 파사드**: Service(selector 비움) + 수동 EndpointSlice가 10.253.101.58 로 고정 + IngressRoute 2개(hcv, vault-mcp). ArgoCD 앱 `argocd/vault-mcp`, Helm chart `kaffa/helm-charts charts/app` + `values/vault-mcp.yaml` (deployment 없는 ingress-only 렌더링).
### 주의
- **`hcv.inouter.com/mcp` 는 MCP 엔드포인트 아님** — Vault가 /mcp 경로를 UI(/ui/)로 307 리다이렉트. 외부 MCP hostname 필요하면 `vault-mcp.inouter.com/mcp` 사용.
- 외부 공개 경로는 BunnyCDN/Cloudflare 비경유 (Traefik wildcard cert 직접 TLS 종단). 내부 LAN/Tailscale 전용.
- kappa Claude MCP는 현재 `http://10.253.101.58:8080/mcp` (Tailscale 암호화 의존). 장기적으로 `https://vault-mcp.inouter.com/mcp` 로 이전 검토 가능 (Traefik TLS + wildcard cert 혜택).
### 재배포 자료
- Helm chart: `gitea.inouter.com/kaffa/helm-charts.git charts/app`
- values: `kaffa/helm-charts values/vault-mcp.yaml`
- jp1 컨테이너 내부 바이너리: `/usr/local/bin/vault-mcp-server`
- systemd: `/etc/systemd/system/vault-mcp-server.service`
## 관련 서비스 ## 관련 서비스

67
services/n8n.md Normal file
View File

@@ -0,0 +1,67 @@
---
title: n8n
updated: 2026-04-16
tags: [services, n8n, oidc, gitea]
---
## 개요
워크플로 자동화. K3s `n8n` 네임스페이스.
- URL: https://n8n.inouter.com
- 이미지: `n8nio/n8n:latest`
- DB: `pgpool.db.svc.cluster.local:9999` (Patroni 직결, [[../infra/postgresql-ha|postgresql-ha]] 참조)
## Gitea OIDC SSO
[n8n-oidc](https://github.com/cweagans/n8n-oidc) 사용 — n8n external hooks 방식, enterprise 불필요.
### 구성 요소
| 리소스 | 내용 |
|---|---|
| ConfigMap `n8n-oidc-hooks` | `hooks.js` (n8n-oidc 프로젝트 원본) |
| Secret `n8n-oidc-secret` | `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET` |
| ConfigMap `n8n-app-config` | OIDC 관련 env vars |
### 환경변수 (`n8n-app-config`)
```
EXTERNAL_HOOK_FILES=/opt/n8n-oidc/hooks.js
OIDC_ISSUER_URL=https://gitea.inouter.com
OIDC_REDIRECT_URI=https://n8n.inouter.com/auth/oidc/callback
OIDC_SCOPES=openid profile email
N8N_ADDITIONAL_NON_UI_ROUTES=auth
EXTERNAL_FRONTEND_HOOKS_URLS=/assets/oidc-frontend-hook.js
```
### Gitea OAuth2 앱
- name: `n8n`
- redirect URI: `https://n8n.inouter.com/auth/oidc/callback`
- confidential client: yes
- Gitea admin → Settings → Applications 에서 관리
### 동작
1. n8n 로그인 페이지에 "Sign in with SSO" 버튼 표시
2. 클릭 → Gitea authorize → 콜백 → JIT user provisioning (첫 유저 = owner)
3. SSO 우회: `https://n8n.inouter.com/signin?showLogin=true` 로 로컬 로그인
### 볼륨 마운트
```yaml
volumes:
- name: oidc-hooks
configMap:
name: n8n-oidc-hooks
volumeMounts:
- name: oidc-hooks
mountPath: /opt/n8n-oidc/hooks.js
subPath: hooks.js
readOnly: true
```
## 관련
- [[../infra/postgresql-ha]] — pgpool-II 접속 경로