Skip to content

[k-skill-proxy] 프로덕션 429 폭증(전체의 17.6%) — Cloudflare Tunnel 뒤 trustProxy 비활성으로 전체 트래픽이 단일 IP 버킷에 묶임 #568

Description

@vkehfdl1

증상

프로덕션 k-skill-proxy(gpu01, Cloudflare Tunnel 경유 k-skill-proxy.nomadamas.org)의 proxy.log 48일치(2026-06-25 ~ 2026-08-12, 517,675 라인)를 분석한 결과, **429 응답이 91,029건으로 전체 요청의 약 17.6%**를 차지합니다.

라우트 429 건수
/v1/korea-weather/forecast 24,060
/v1/real-estate/:assetType/:dealType 13,756
/v1/naver-news/search 13,365
/v1/building-register/title 9,206
/v1/korean-law/detail 4,682
/v1/korean-stock/trade-info 4,127
/v1/seoul-subway/arrival 3,028

일별 추이: 08-02 14,664건, 08-06 16,911건, 08-11 11,383건 등 버스트성 급증 반복.

원인 (코드 + 배포 설정)

레이트리미터는 클라이언트를 request.ip로 구분합니다 (packages/k-skill-proxy/src/server.js:373).

그런데:

  1. trustProxyHops 기본값이 0이라 (server.js:242) Fastify가 trustProxy: false로 기동됩니다 (server.js:2096).
  2. gpu01 프로덕션 .env에도 KSKILL_PROXY_TRUST_PROXY_HOPS가 설정되어 있지 않습니다 (KSKILL_PROXY_RATE_LIMIT_WINDOW_MS/KSKILL_PROXY_RATE_LIMIT_MAX만 설정됨).
  3. 프로덕션은 cloudflared가 localhost로 포워딩하는 구조라, trustProxy: false 상태에서는 모든 외부 요청의 request.ip가 cloudflared 커넥터 주소(127.0.0.1) 하나로 보입니다.

결과적으로 전체 사용자가 단일 레이트리밋 버킷을 공유합니다. 사용자가 누구든(에이전트 루프 하나든) 버스트를 치면 모두가 함께 429를 받는 구조이며, 관측된 429 폭증과 정확히 일치합니다.

요청하는 수정

  1. 즉시 조치(운영): gpu01 .envKSKILL_PROXY_TRUST_PROXY_HOPS=1 설정 후 서비스 재시작. cloudflared 1홉만 신뢰하면 X-Forwarded-For의 실제 클라이언트 IP로 버킷이 나뉩니다.
  2. 코드/배포 하드닝:
    • scripts/deploy-k-skill-proxy-gpu01.sh 또는 배포 문서(docs/deploy-k-skill-proxy.md)에 KSKILL_PROXY_TRUST_PROXY_HOPS=1을 기본값으로 박제하거나, 프로덕션 체크리스트에 포함.
    • 선택: Cloudflare Tunnel 전용 배포임을 고려해 CF-Connecting-IP 헤더 우선 사용 옵션 검토 (XFF 스푸핑 방지).
  3. 회귀 방지: trustProxy 활성 상태에서 서로 다른 X-Forwarded-For를 가진 두 클라이언트가 별도 버킷을 쓰는지 확인하는 테스트 추가 (packages/k-skill-proxy/test/server.test.js).

검증 근거

  • 로그 집계: gpu01 /data/home/jeffrey/apps/k-skill-proxy/proxy.log (routeUsage 라인 기준, statusCode별/route별 카운트)
  • 코드: server.js 239-242(config), 365-405(buildRateLimiter), 2093-2097(Fastify init)
  • 프로덕션 env: KSKILL_PROXY_TRUST_PROXY_HOPS 부재 확인 (값 자체는 미열람, 키 존재 여부만 grep)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions