infra: APISIX ingress controller 복구 및 PostgreSQL HA 구조 명확화
- apisix.md: ingress controller 2026-04-08 복구, GatewayProxy + ApisixRoute CRD 호환 검증, 옛 helm values 문제 정정, ApisixRoute 예시 추가 - apisix.md: 외부 통합 etcd 클러스터 사용 명시 (K3s 내부 StatefulSet 아님), global_rules는 chaitin-waf 미포함 (라우트별 적용) - postgresql-ha.md: pgcat가 HAProxy 단일 백엔드 경유로 변경 (2026-04-08 사고 기록), Patroni 노드 IP 직접 박지 말 것 경고 - gitea.md: 컨테이너 레지스트리 섹션 추가, gitea-registry secret 네임스페이스별 수동 복사 필요 명시
This commit is contained in:
@@ -52,13 +52,13 @@ BunnyCDN(inouter, ID 5316471) → apisix-osaka(172.233.93.180) → 백엔드
|
||||
- 배포: K3s apisix 네임스페이스, **Deployment replica 2** (2026-04-04 HA 업그레이드)
|
||||
- APISIX: 3.15.0-ubuntu
|
||||
- SafeLine WAF 연동: `plugin_attr.chaitin-waf` → `safeline-detector:8000` (10.43.253.244)
|
||||
- global_rules: `chaitin-waf` (mode: block) + `limit-req` (rate 20, burst 10)
|
||||
- global_rules (id=1): `http-logger` (CrowdSec 로그 전송) + `limit-req` (rate 20, burst 10). **`chaitin-waf`는 global_rules에 없음** — 라우트별 적용 (2026-03-15 git push 500 사건 이후)
|
||||
- 서비스: apisix-gateway LoadBalancer 192.168.9.50 (80→9080, 443→9443)
|
||||
- etcd: apisix-etcd StatefulSet **3 replicas** (K3s 내부, 2026-04-04 HA 업그레이드)
|
||||
- Admin API: `apisix-admin` ClusterIP :9180 (라우트/upstream/SSL 수동 관리)
|
||||
- etcd: **외부 통합 etcd 클러스터** (192.168.9.100, 10.100.2.214, 10.253.101.233), prefix `/apisix/seoul`. K3s 내부 apisix-etcd StatefulSet은 통합 이전 시 삭제됨 (2026-04-06). 자세한 내용은 [[postgresql-ha#etcd-클러스터-patroni-dcs|postgresql-ha]] 참고
|
||||
- Admin API: `apisix-admin` ClusterIP :9180 (`adminKey: edd1c9f034335f136f87ad84b625c8f1`)
|
||||
- HAProxy: OpenWrt에서 :9080→192.168.9.50:80, :9443→192.168.9.50:443 (MetalLB)
|
||||
- 2026-03-25 메인 라우팅 역할을 Traefik으로 이전, APISIX는 SafeLine WAF 전용으로 축소
|
||||
- Ingress Controller + Gateway API CRD 제거됨 (GatewayProxy 모드에서 ApisixRoute CRD 미지원, HTTPRoute에 플러그인 개별 적용 불가 문제)
|
||||
- 2026-04-08 ApisixRoute CRD 사용을 위해 ingress controller 복구
|
||||
|
||||
#### plugin_metadata (etcd 직접 등록)
|
||||
|
||||
@@ -79,11 +79,64 @@ etcdctl put /apisix/plugin_metadata/chaitin-waf '{"id":"chaitin-waf","nodes":[{"
|
||||
`real_ip_header: X-Forwarded-For`, `real_ip_from: 0.0.0.0/0` (BunnyCDN 경유이므로 전체 허용). Helm values로는 반영 안 되어 ConfigMap 직접 패치.
|
||||
|
||||
#### 이전 사유 (2026-03-25)
|
||||
- Ingress Controller 2.0 GatewayProxy 모드가 ApisixRoute CRD를 sync하지 않음
|
||||
- Gateway API HTTPRoute에 플러그인 개별 적용 방법이 없음
|
||||
- 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 복구 (2026-04-08)
|
||||
|
||||
`apisix-ingress-controller` Helm release는 살아있었으나 Deployment가 수동 삭제된 상태였음. helm values를 chart 1.1.2 (controller v2.0.1) 스키마에 맞게 재작성 후 `helm upgrade`로 복구.
|
||||
|
||||
값 수정 핵심:
|
||||
```yaml
|
||||
gatewayProxy:
|
||||
createDefault: true # GatewayProxy CR 자동 생성
|
||||
provider:
|
||||
type: ControlPlane
|
||||
controlPlane:
|
||||
service:
|
||||
name: apisix-admin
|
||||
port: 9180
|
||||
auth:
|
||||
type: AdminKey
|
||||
adminKey:
|
||||
value: "edd1c9f034335f136f87ad84b625c8f1"
|
||||
config:
|
||||
disableGatewayAPI: false # Gateway API + ApisixRoute 양쪽 다 지원
|
||||
kubernetes:
|
||||
ingressClass: apisix
|
||||
```
|
||||
|
||||
검증 결과: GatewayProxy 모드에서도 ApisixRoute CRD(v2)는 정상 동작함. 옛 메모의 "GatewayProxy 모드에서 ApisixRoute CRD 미지원"은 틀렸음 — 잘못된 helm values 때문이었음. ApisixRoute에 `ingressClassName: apisix`만 명시하면 controller가 자동으로 admin API에 push.
|
||||
|
||||
ApisixRoute 예시 (라우트별 chaitin-waf):
|
||||
```yaml
|
||||
apiVersion: apisix.apache.org/v2
|
||||
kind: ApisixRoute
|
||||
metadata:
|
||||
name: <name>
|
||||
namespace: <ns>
|
||||
spec:
|
||||
ingressClassName: apisix
|
||||
http:
|
||||
- name: rule1
|
||||
match:
|
||||
hosts: [<host>]
|
||||
paths: ["/*"]
|
||||
backends:
|
||||
- serviceName: <svc>
|
||||
servicePort: <port>
|
||||
plugins:
|
||||
- name: chaitin-waf
|
||||
enable: true
|
||||
config:
|
||||
mode: block
|
||||
append_waf_resp_header: true
|
||||
```
|
||||
|
||||
WAF가 문제 시 `plugins` 항목만 빼면 즉시 비활성화됨.
|
||||
|
||||
### BunnyCDN Pull Zone 매핑
|
||||
|
||||
| Zone | ID | Origin | 방향 | 주요 Hostnames |
|
||||
|
||||
@@ -112,6 +112,34 @@ backend bk_postgres_primary
|
||||
- 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
|
||||
```
|
||||
|
||||
`db/pgcat-config` ConfigMap의 각 풀의 `shards.0.servers`는 **HAProxy 단일 백엔드만 가리켜야 함** (2026-04-08 변경):
|
||||
|
||||
```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 비활성).
|
||||
|
||||
### 2026-04-08 사고 기록
|
||||
|
||||
Patroni failover 발생 → pgcat가 옛 primary IP(`10.100.2.5`)를 hardcoded 참조 → nocodb 마이그레이션 시 `cannot execute UPDATE in a read-only transaction` 에러로 4시간 가량 CrashLoopBackOff. n8n은 마이그레이션이 없어서 표면화되지는 않았으나 동일한 잠재 문제 존재. 위의 단일 백엔드 구조로 변경하여 항구 해결.
|
||||
|
||||
## APISIX etcd 통합 (2026-04-06)
|
||||
|
||||
기존 각 사이트별 독립 etcd를 통합 etcd 클러스터로 이전.
|
||||
|
||||
@@ -51,6 +51,23 @@ R2에 저장되는 데이터: packages, lfs, attachments, avatars, repo-avatars,
|
||||
|
||||
Gitea는 iron-kr에서 **iron-git** (ID 5584382)으로 분리. 이유: iron-kr의 `BlockNoneReferrer: true`가 git 클라이언트(Referrer 없음)를 차단하여 git push/pull 403 에러 발생. iron-git은 `BlockNoneReferrer: false`로 설정.
|
||||
|
||||
## 컨테이너 레지스트리
|
||||
|
||||
Gitea에 내장된 OCI 컨테이너 레지스트리. URL: `gitea.inouter.com`. K3s에서 이미지를 pull하려면 각 네임스페이스에 `gitea-registry` imagePullSecret이 필요.
|
||||
|
||||
```bash
|
||||
# 네임스페이스에 gitea-registry secret 복사 (mcp 네임스페이스에서)
|
||||
kubectl get secret gitea-registry -n mcp -o yaml | \
|
||||
sed 's/namespace: mcp/namespace: <대상-ns>/' | \
|
||||
kubectl apply -f -
|
||||
```
|
||||
|
||||
⚠️ **흔한 사고**: 새 네임스페이스에서 `gitea.inouter.com/...` 이미지로 Deployment를 만들면 `imagePullSecrets: [gitea-registry]`만 명시해도 secret이 그 네임스페이스에 없으면 ImagePullBackOff가 발생. K8s는 namespace 경계를 넘어 secret을 공유하지 않음. 매번 secret을 복사해야 함.
|
||||
|
||||
자동 배포 대안: cert-manager `reflector` annotation 또는 Kyverno `generate` 정책으로 자동 동기화 가능 (현재는 수동).
|
||||
|
||||
기존에 `gitea-registry` secret이 배포된 네임스페이스: `mcp`, `tools`, `api`, `default` (2026-04-08 추가).
|
||||
|
||||
## Act Runner
|
||||
|
||||
Gitea Act Runner는 [[infra-hosts|incus-jp1]]의 gitea-runner 컨테이너에서 실행 (act_runner daemon, systemd 서비스).
|
||||
|
||||
Reference in New Issue
Block a user