obsidian: 정본 문서에서 히스토리/인시던트 분리 완료

15개 정본 문서에서 날짜별 변경이력, 인시던트 기록, 폐기된 구현 상세를
history/ 디렉토리로 분리. 정본은 현재 상태만 기술하는 백서 형태로 정리.
각 정본에 history 위키링크 추가.

분리된 history 파일 12건:
- apisix git push 500, k3s postgresql migration, apisix→traefik 전환
- netbis DDoS 공격, gitea 이전/분리, usb 2.5g hang + NFS hard mount
- supabase→patroni, apisix etcd 통합/분리, anomaly-detect 재설계
- patroni failover incident, zlambda nixos migration, ops-agents setup

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
kappa
2026-04-10 12:09:21 +09:00
parent 72750cfc9d
commit 2356b86d36
27 changed files with 554 additions and 514 deletions

View File

@@ -9,7 +9,7 @@ updated: 2026-04-04
오사카 구성: 고객 도메인 → [[cloudflare]](DNS) → [[crowdsec-safeline|SafeLine WAF]] → APISIX(라우팅) → 고객 오리진
3개의 독립 APISIX 인스턴스. kr1의 Docker APISIX는 2026-03-15 제거 완료.
3개의 독립 APISIX 인스턴스.
### 서울 relay (relay4wd)
- 용도: 포트 포워딩 게이트웨이
@@ -22,8 +22,7 @@ updated: 2026-04-04
- 방화벽: 22/tcp + 2201-2299/tcp + 443/tcp 개방, SSH는 Tailscale 경유 포트 2222
- 22 → iptables REDIRECT → 9022 (SFTPGo용, privileged 포트 우회)
- 443 → iptables REDIRECT → 8443 (Teleport용, privileged 포트 우회)
- 2026-03-17 AWS EC2에서 Lightsail nano($5/월)로 이전
- **주의**: config.yaml의 stream_proxy.tcp에 privileged 포트(1-1023)를 넣으면 비특권 컨테이너에서 bind 실패로 크래시. 2026-03-27 포트 22 추가로 장애 발생, 제거하여 복구
- **주의**: config.yaml의 stream_proxy.tcp에 privileged 포트(1-1023)를 넣으면 비특권 컨테이너에서 bind 실패로 크래시
#### 포트 포워딩 (stream_routes)
@@ -49,16 +48,14 @@ BunnyCDN(iron-jp, ID 5555247) → apisix-osaka(172.233.93.180) → 백엔드
```
- 용도: **Traefik(VIP 192.168.9.53)과 동등한 병렬 독립 LoadBalancer gateway**. "SafeLine WAF 전용" 이 아니라, 2026-03-25 에 메인 HTTP 라우팅을 Traefik 으로 이전한 이후 현재 APISIX 에는 `juiceshop.keepanker.cv` 1건만 남아 있고 그것이 chaitin-waf 플러그인으로 SafeLine 통합 테스트 용도라 **결과적으로 현재만** SafeLine 테스트 전용처럼 보일 뿐 — 언제든 새 ApisixRoute 추가 가능한 범용 gateway.
- 클러스터: K3s 새 클러스터 (Supabase PostgreSQL 백엔드, kr2+kr1+hp2)
- 배포: K3s apisix 네임스페이스, **Deployment replica 2** (2026-04-04 HA 업그레이드)
- 배포: K3s apisix 네임스페이스, **Deployment replica 2**
- APISIX: 3.15.0-ubuntu
- SafeLine WAF 연동: `plugin_attr.chaitin-waf``safeline-detector:8000` (10.43.253.244)
- global_rules: `http-logger` (CrowdSec 로그 전송) + `limit-req` (rate 20, burst 10). **`chaitin-waf`는 global_rule에 없음** — 라우트별 적용 (2026-03-15 git push 500 사건 이후). 두 global_rule 모두 GatewayProxy CR(`spec.plugins`)로 선언되어 ingress controller가 관리.
- global_rules: `http-logger` (CrowdSec 로그 전송) + `limit-req` (rate 20, burst 10). **`chaitin-waf`는 global_rule에 없음** — 라우트별 적용. 두 global_rule 모두 GatewayProxy CR(`spec.plugins`)로 선언되어 ingress controller가 관리.
- 서비스: apisix-gateway LoadBalancer 192.168.9.50 (80→9080, 443→9443)
- etcd: **K3s 내부 apisix-etcd StatefulSet 3 replicas** (Bitnami etcd 차트, Longhorn PVC 5Gi × 3), prefix `/apisix`. ClusterIP `apisix-etcd.apisix.svc.cluster.local:2379`. 외부 통합 etcd로 잠시 이전했다가(2026-04-06) Patroni와의 장애 격리 + 네트워크 단순화를 위해 K3s 내부로 복귀(2026-04-08).
- etcd: **K3s 내부 apisix-etcd StatefulSet 3 replicas** (Bitnami etcd 차트, Longhorn PVC 5Gi × 3), prefix `/apisix`. ClusterIP `apisix-etcd.apisix.svc.cluster.local:2379`.
- Admin API: `apisix-admin` ClusterIP :9180 (`adminKey: edd1c9f034335f136f87ad84b625c8f1`). admin allow IPs: `127.0.0.1/24`, `10.42.0.0/16`(pod), `10.43.0.0/16`(svc), `192.168.9.0/24`(LAN), `100.64.0.0/10`(Tailscale)
- HAProxy: OpenWrt에서 :9080→192.168.9.50:80, :9443→192.168.9.50:443 (MetalLB)
- 2026-03-25 메인 HTTP 라우팅을 Traefik 으로 이전. APISIX route 축소되어 현재 juiceshop 1건만 (SafeLine chaitin-waf 통합 테스트). **APISIX 자체는 여전히 독립 외부 LoadBalancer gateway — MetalLB VIP 별도 유지**
- 2026-04-08 ApisixRoute CRD 사용을 위해 ingress controller 복구 + K3s 내부 etcd 복귀
#### plugin_metadata (GatewayProxy CR로 관리)
@@ -104,20 +101,14 @@ kubectl rollout restart deploy/apisix -n apisix
⚠️ **Vector parse_apisix 정규식과 짝**: APISIX log format을 변경할 때마다 `vector` helm values의 parse_apisix 정규식도 같이 업데이트해야 [[victorialogs|VictoriaLogs]]에 구조화 필드가 정상 추출됨. 현재 정규식은 `xff/xrip` 필드를 optional 그룹으로 처리.
#### 이전 사유 (2026-03-25)
- Ingress Controller 2.0 초기 시도에서 GatewayProxy 모드 + ApisixRoute CRD 연동 실패 (당시 helm values에 v1.x 형식의 `config.apisix.serviceName` 사용 → 차트 1.x 스키마와 불일치)
- Gateway API HTTPRoute에 플러그인 개별 적용 방법이 없음 → ApisixRoute CRD 필요
- global_rules를 Ingress Controller가 덮어쓰려는 충돌 발생
- → Traefik으로 메인 라우팅 교체, APISIX는 SafeLine WAF 연동 전용으로 유지
#### Ingress Controller 설정
#### Ingress Controller 복구 (2026-04-08)
`apisix-ingress-controller` Helm release (chart 1.1.2, controller v2.0.1). GatewayProxy 모드에서 ApisixRoute CRD(v2)도 정상 동작. ApisixRoute에 `ingressClassName: apisix`만 명시하면 controller가 자동으로 admin API에 push.
`apisix-ingress-controller` Helm release는 살아있었으나 Deployment가 수동 삭제된 상태였음. helm values를 chart 1.1.2 (controller v2.0.1) 스키마에 맞게 재작성 후 `helm upgrade`로 복구.
값 수정 핵심:
helm values 핵심:
```yaml
gatewayProxy:
createDefault: true # GatewayProxy CR 자동 생성
createDefault: true
provider:
type: ControlPlane
controlPlane:
@@ -129,12 +120,12 @@ gatewayProxy:
adminKey:
value: "edd1c9f034335f136f87ad84b625c8f1"
config:
disableGatewayAPI: false # Gateway API + ApisixRoute 양쪽 다 지원
disableGatewayAPI: false
kubernetes:
ingressClass: apisix
```
검증 결과: GatewayProxy 모드에서도 ApisixRoute CRD(v2)는 정상 동작함. 옛 메모의 "GatewayProxy 모드에서 ApisixRoute CRD 미지원"은 틀렸음 — 잘못된 helm values 때문이었음. ApisixRoute에 `ingressClassName: apisix`만 명시하면 controller가 자동으로 admin API에 push.
라우팅 전환/복구 이력: [[../history/2026-03-25-apisix-to-traefik-routing|history]]
ApisixRoute 예시 (라우트별 chaitin-waf):
```yaml
@@ -178,16 +169,9 @@ WAF가 문제 시 `plugins` 항목만 빼면 즉시 비활성화됨.
⚠️ **etcd 직접 PUT은 임시 디버깅 외에는 금지.** 1분 안에 삭제됨. 옛 운영 메모에 있는 `etcdctl put` 예제는 모두 deprecated.
#### K3s 내부 etcd 복귀 (2026-04-08)
#### etcd 설정
기존: 외부 통합 etcd 클러스터(192.168.9.100/10.100.2.214/10.253.101.233, prefix `/apisix/seoul`) — Patroni DCS와 etcd 공유.
복귀 후: K3s 내부 `apisix-etcd` StatefulSet 3 replicas (Bitnami etcd, Longhorn PVC 5Gi × 3, ClusterIP `apisix-etcd.apisix.svc:2379`, prefix `/apisix`).
이유:
- **장애 격리**: Patroni 이슈가 APISIX 라우팅에 영향 주지 않게
- **네트워크 단순화**: K3s 내부 통신만으로 충분
- **운영 일관성**: helm 한 곳에서 etcd + apisix + ingress controller 관리
K3s 내부 `apisix-etcd` StatefulSet 3 replicas (Bitnami etcd, Longhorn PVC 5Gi x 3, ClusterIP `apisix-etcd.apisix.svc:2379`, prefix `/apisix`).
helm values 핵심:
```yaml
@@ -200,10 +184,11 @@ etcd:
```
업그레이드 시 주의:
- Bitnami etcd 차트의 pre-upgrade hook은 기존 etcd 멤버 list를 시도함. 멤버가 없는 상황에서는 무한 retry 실패 `helm upgrade --no-hooks` 사용 또는 helm rollback 후 재시도
- helm upgrade 전 release values nesting 점검 필수: `apisix.admin.allow.ipList`(O) vs `admin.allow.ipList`(X), `apisix.nginx.http.realIpFrom`(O) vs `nginx.http.realIpFrom`(X), `service.http.containerPort`(O) vs `gateway.http.containerPort`(X). 잘못된 nesting은 차트가 조용히 무시함.
- 이전 후 ingress controller는 자동으로 모든 K8s CRD 객체를 etcd에 재push (rollout restart로 즉시 동기화 가능)
- 외부 통합 etcd의 stale `/apisix/seoul/*` 키는 수동 삭제 (postgresql-ha.md의 etcd cleanup 명령 참고)
- Bitnami etcd 차트의 pre-upgrade hook 실패 `helm upgrade --no-hooks` 사용
- helm upgrade 전 release values nesting 점검 필수: `apisix.admin.allow.ipList`(O) vs `admin.allow.ipList`(X). 잘못된 nesting은 차트가 조용히 무시함.
- ingress controller는 자동으로 모든 K8s CRD 객체를 etcd에 재push (rollout restart로 즉시 동기화)
etcd 통합/분리 이력: [[../history/2026-04-06-apisix-etcd-consolidation|history]]
### BunnyCDN Pull Zone 매핑 (2026-04-09 API 실측)
@@ -216,7 +201,7 @@ etcd:
| i-gate | 5557897 | 172.233.93.180 | (미사용 슬롯) | `i-gate.b-cdn.net` |
참고:
- iron-kr `IgnoreQueryStrings: true` / iron-jp `IgnoreQueryStrings: false` — 동일 미들웨어 풀존이지만 캐시 키 정책이 다름
- iron-kr / iron-jp 모두 `IgnoreQueryStrings: false` (통일)
- iron-kr / iron-jp `BlockNoneReferrer: true`, iron-git 는 `false` (git smart HTTP 호환)
- 모든 zone `EdgeRules: []` (Edge Rules 미사용, 모든 분기는 미들웨어 64811 안)
- Edge Script 64811 `crowdsec-bouncer-middleware` 는 **iron-jp + iron-kr 두 풀존**에만 attach. iron-git 은 의도적 우회 (git pack 바이너리 보호 불가).
@@ -259,7 +244,7 @@ APISIX 서울 라우트 hcv-inouter-com → K3s Traefik (192.168.9.134/214/135:4
트래픽 흐름: Cloudflare DNS (A 220.120.65.245, BunnyCDN 우회) → OpenWrt(:443) → hp2(:9443, incus proxy device) → APISIX(10.179.99.126, 라우트 nocodb/nocodb-nuxt) → K3s Traefik (192.168.9.134/214/135:443, roundrobin, scheme https) → nocodb svc:8080 (namespace tools).
BunnyCDN WAF가 NocoDB JS를 오탐 차단하여 CDN 우회 처리 (2026-03-15).
BunnyCDN WAF가 NocoDB JS를 오탐 차단하여 CDN 우회 처리.
## CrowdSec 로그 연동
@@ -271,36 +256,7 @@ BunnyCDN WAF가 NocoDB JS를 오탐 차단하여 CDN 우회 처리 (2026-03-15).
시나리오 매칭으로 반복 공격자 탐지.
## 트러블슈팅 기록
### git push 500 에러 (2026-03-15)
**증상**: `gitea.inouter.com`으로 git push 시 BunnyCDN에서 403 반환 (오리진 500)
**원인 1 — client_body_temp 퍼미션**:
- nginx가 큰 POST body(git pack ~20KB)를 `/usr/local/apisix/client_body_temp/`에 임시 파일로 쓸 때 퍼미션 거부
- 디렉토리 소유자가 `nobody`인데 APISIX는 `apisix` 사용자로 실행
- 작은 요청은 메모리 버퍼로 처리되어 정상, 큰 요청만 실패
- **수정**: `chown apisix:apisix /usr/local/apisix/client_body_temp`
**원인 2 — chaitin-waf 글로벌 적용**:
- `chaitin-waf`가 global_rules에 있어서 모든 요청에 WAF 검사 적용
- SafeLine detector가 git pack 바이너리를 파싱 실패 시 `mode: block`이면 500 반환 (fail-closed)
- 라우트 레벨에서 match 제외를 넣어도 global_rule이 먼저 적용되어 무시됨
- **수정**: global_rules에서 `chaitin-waf` 제거, 각 라우트에 개별 적용. gitea 라우트는 `match: [{"vars": [["uri", "!", "~*", "\\.git/"]]}]`로 git 경로 WAF 제외
**교훈**:
- SafeLine WAF는 바이너리 프로토콜(git pack 등)을 처리할 수 없음 → 해당 경로는 WAF에서 제외 필요
- global_rules의 WAF는 예외 처리가 어려움 → 라우트별 개별 적용이 유연함
- BunnyCDN `errorcode: 112`는 오리진 에러를 나타냄 → 실제 원인은 오리진 로그에서 확인
### http-logger 401 에러 (2026-03-15)
**증상**: 서울 APISIX http-logger가 CrowdSec으로 로그 전송 시 401 Unauthorized
**원인**: http-logger 플러그인은 Authorization 헤더를 `auth_header` 필드로 설정해야 하는데, `headers.Authorization`으로 설정되어 있었음 (무시됨)
**수정**: `"headers": {"Authorization": "..."}``"auth_header": "apisix-crowdsec-log-2024"`
과거 트러블슈팅 이력: [[../history/2026-03-15-apisix-git-push-500|2026-03-15 git push 500 에러 + http-logger 401]]
## jarvis.inouter.com 라우트