Published on

EKS NGINX Ingress 400·413 해결 - body·버퍼 튜닝

Authors

서버는 멀쩡한데 특정 요청만 NGINX Ingress에서 400(Bad Request) 혹은 413(Request Entity Too Large) 로 떨어지면, 애플리케이션 로직을 의심하기 전에 “Ingress(=NGINX)에서 요청을 받는 단계”의 제한값을 먼저 점검해야 합니다. 특히 EKS에서는 ALB/NLB, WAF, Ingress Controller, 백엔드(서비스/파드)까지 홉이 여러 개라서, 어느 계층에서 컷(cut) 나는지를 분리하지 않으면 튜닝이 헛바퀴가 되기 쉽습니다.

이 글은 EKS의 ingress-nginx(커뮤니티 NGINX Ingress Controller 기준)에서 자주 만나는 400/413을 proxy-body-size(요청 바디 크기)버퍼(헤더/요청라인/업스트림 버퍼링) 관점에서 재현·진단·해결하는 실전 체크리스트입니다.

> 413이 “업로드/푸시”에서 자주 보인다면, 같은 증상을 다른 맥락에서 다룬 글도 참고할 만합니다: Git LFS 푸시 실패 413·403 원인과 해결법

1) 400 vs 413: 어떤 종류의 실패인가

413 (Request Entity Too Large)

  • 요청 바디가 너무 큼: 파일 업로드, 큰 JSON, base64 이미지 등
  • NGINX가 백엔드로 프록시하기 전에 바디 크기를 검사하고 차단
  • ingress-nginx에서는 주로 proxy-body-size(annotation) 또는 ConfigMap의 proxy-body-size가 관여

400 (Bad Request)

400은 원인이 더 다양합니다. ingress-nginx에서 흔한 케이스는:

  • 요청 헤더가 너무 큼(특히 쿠키/Authorization/JWT가 커짐)
  • 요청 라인(URI+쿼리)이 너무 김(긴 쿼리스트링)
  • 잘못된 Host 헤더, 깨진 HTTP, TLS/HTTP 혼선 등
  • gRPC/HTTP2에서 특정 프록시 설정과 충돌

즉, 413은 비교적 명확하게 “바디 크기”, 400은 “헤더/요청라인/프로토콜” 계열을 먼저 의심하는 게 빠릅니다.

2) 먼저 해야 할 일: 어디서 끊겼는지 로그로 확정

(1) ingress-nginx 컨트롤러 로그 확인

# 네임스페이스는 환경에 맞게 변경
kubectl -n ingress-nginx logs deploy/ingress-nginx-controller \
  --since=30m | egrep " 400 | 413 | client sent | request header | too large" -n

로그에서 자주 보이는 힌트:

  • client intended to send too large body → 413, 바디 제한
  • client sent too large request header → 400, 헤더 버퍼/크기
  • upstream sent too big header → (대개 502/500 계열이지만) 업스트림 응답 헤더 버퍼 문제

(2) 실제로 NGINX가 반환했는지 확인

응답 헤더에 server: nginx 또는 ingress-nginx 특유의 헤더가 있으면 Ingress에서 컷일 확률이 큽니다.

curl -i https://api.example.com/upload \
  -H 'Expect:' \
  -F 'file=@big.bin'

(3) WAF/ALB 계층도 함께 의심

EKS 앞단에 AWS WAF를 붙였다면 400/403이 섞여 보일 수 있습니다. WAF에서 차단인데 NGINX로 오해하면 튜닝이 안 먹습니다. WAF 쪽 이슈가 의심될 때는 아래 글 흐름도 도움이 됩니다.

3) 413 해결: proxy-body-size 제대로 늘리기

(1) Ingress annotation으로 서비스별 적용

가장 안전한 방법은 “전체 클러스터”가 아니라 해당 Ingress에만 상향 적용하는 것입니다.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api
  namespace: default
  annotations:
    nginx.ingress.kubernetes.io/proxy-body-size: "50m"
    # 업로드가 느릴 경우 타임아웃도 함께(필요 시)
    nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
spec:
  ingressClassName: nginx
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /upload
            pathType: Prefix
            backend:
              service:
                name: api-svc
                port:
                  number: 80
  • 단위는 m, k 등을 사용합니다.
  • 0은 무제한이지만, 운영에서는 권장하지 않습니다(악성/실수로 메모리/디스크 압박).

(2) ConfigMap으로 전역 기본값 변경(신중)

여러 서비스가 동일하게 큰 바디를 받아야 한다면 컨트롤러 ConfigMap을 조정합니다.

kubectl -n ingress-nginx get configmap ingress-nginx-controller -o yaml

예시:

apiVersion: v1
kind: ConfigMap
metadata:
  name: ingress-nginx-controller
  namespace: ingress-nginx
data:
  proxy-body-size: "50m"

전역 설정은 영향 범위가 크므로, 가능하면 annotation(서비스별) → 필요 시 전역 순으로 접근하세요.

(3) 업로드 경로는 버퍼링/디스크 스풀도 고려

큰 업로드에서 413이 아니라도 지연/끊김이 있다면 요청 바디를 NGINX가 어떻게 처리하는지(버퍼링/임시파일)도 함께 봐야 합니다. 특히 SSE/스트리밍이나 긴 연결에서는 프록시 버퍼링이 부작용을 만들 수 있습니다.

4) 400 해결: 헤더/요청라인/버퍼 제한 튜닝

413은 proxy-body-size로 끝나는 경우가 많지만, 400은 헤더 크기가 원인인 경우가 매우 흔합니다. 예를 들어:

  • 쿠키에 추적/AB테스트 값이 잔뜩 붙음
  • OAuth/세션 토큰이 비대해짐(JWT가 커짐)
  • 프론트가 쿼리스트링에 상태를 과하게 담음

(1) “request header too large”면 large-client-header-buffers

ingress-nginx에서는 ConfigMap에서 large-client-header-buffers를 조정합니다.

apiVersion: v1
kind: ConfigMap
metadata:
  name: ingress-nginx-controller
  namespace: ingress-nginx
data:
  large-client-header-buffers: "4 32k"

의미:

  • 헤더 버퍼를 4개, 각 32KB로 확보
  • 기본값은 배포판/버전에 따라 다를 수 있으나, 큰 쿠키/Authorization을 쓰는 서비스에서 400을 줄이는 데 효과적입니다.

주의:

  • 무작정 크게 하면 워커당 메모리 사용량이 증가합니다.
  • 근본적으로는 “헤더를 줄이는 것(쿠키 정리, JWT 축소)”이 최선입니다.

(2) “too long URI”면 요청라인/버퍼 상향 또는 설계 수정

긴 쿼리스트링 때문에 400이 나면, NGINX 레벨에서 버퍼를 늘릴 수도 있지만 API 설계를 바꾸는 게 좋습니다.

  • 긴 검색 조건/필터를 GET 쿼리에 넣지 말고 POST body로 이동
  • 클라이언트 상태를 URL에 과하게 담지 않기

NGINX에서 요청라인과 관련된 버퍼는 client-header-buffer-size(초기 버퍼)나 위의 large-client-header-buffers와 함께 영향을 받습니다. ingress-nginx에서는 ConfigMap으로 설정합니다.

data:
  client-header-buffer-size: "16k"
  large-client-header-buffers: "4 32k"

(3) 업스트림 응답 헤더가 커서 생기는 문제도 구분

간혹 400이 아니라 502/503으로 보이지만, 원인은 백엔드가 너무 큰 응답 헤더(예: Set-Cookie 다수)를 내려서 프록시가 처리 못 하는 경우가 있습니다. 이때는 다음 계열을 봅니다.

  • proxy-buffer-size
  • proxy-buffers
  • proxy-busy-buffers-size

ingress-nginx에서는 annotation으로도 일부 조정 가능합니다.

metadata:
  annotations:
    nginx.ingress.kubernetes.io/proxy-buffer-size: "16k"
    nginx.ingress.kubernetes.io/proxy-buffers-number: "8"
    nginx.ingress.kubernetes.io/proxy-busy-buffers-size: "32k"

다만 이 영역은 “무조건 키우기”보다,

  • 왜 응답 헤더가 커졌는지(쿠키 남발, 헤더에 데이터 넣기)
  • 캐시/세션 전략이 적절한지 를 먼저 점검하는 게 운영 비용을 줄입니다.

5) 재현 테스트: 크기 기준을 수치로 확인하기

(1) 413 재현: 큰 바디 보내기

# 60MB 파일 생성
dd if=/dev/zero of=/tmp/60m.bin bs=1m count=60

# 업로드 시도
curl -i https://api.example.com/upload \
  -H 'Expect:' \
  -F 'file=@/tmp/60m.bin'
  • Expect:를 비워 100-continue 협상 때문에 헷갈리는 상황을 줄입니다.

(2) 400(헤더) 재현: 큰 쿠키 넣기

python - <<'PY'
print('a'*40000)
PY

출력값을 쿠키로 넣어 테스트(실서비스에는 절대 금지):

BIG=$(python - <<'PY'
print('a'*40000)
PY
)

curl -i https://api.example.com/ \
  -H "Cookie: big=$BIG"

이때 400이 나고 ingress 로그에 client sent too large request header가 찍히면, 헤더 버퍼 튜닝의 적중률이 높습니다.

6) 운영에서의 권장 접근 순서(실전 체크리스트)

  1. Ingress 로그로 원인 문자열 확정: body인지 header인지, upstream인지
  2. 413이면 Ingress annotation의 proxy-body-size 를 경로/서비스 단위로 우선 상향
  3. 400이면 로그에 따라
    • too large request headerlarge-client-header-buffers 조정 + 쿠키/JWT 축소
    • too long URI → API 설계 수정(POST로 이동) + 필요 시 버퍼 상향
  4. “전역 ConfigMap 변경”은 마지막에, 변경 후에는 롤아웃/재기동이 필요한지(컨트롤러 버전별 동작) 확인
  5. 프록시 버퍼를 키웠다면 메모리 사용량/워커 수/Pod 리소스를 함께 모니터링

7) 자주 하는 실수와 함정

(1) 모든 걸 무제한(0)으로 풀기

급한 불은 끄지만, 악성 트래픽/실수로 대용량 업로드가 들어오면 Ingress가 먼저 흔들립니다. 서비스별 상향 + 애플리케이션 레벨 제한(예: 업로드 크기 제한 응답)을 병행하세요.

(2) 400을 “백엔드 버그”로만 보는 것

Ingress에서 400이 떨어지면 백엔드 로그에는 아무것도 남지 않습니다. 이때 백엔드만 디버깅하면 시간만 날아갑니다.

(3) gRPC/스트리밍을 HTTP 업로드처럼 튜닝

gRPC는 메시지 크기 제한이 NGINX/Envoy/애플리케이션에 각각 존재합니다. 413이 아니라 502로 보이는 경우도 있어요. gRPC 최대 메시지 쪽 이슈라면 아래 글이 더 직접적입니다.

8) 마무리: 결론은 “크기 제한은 계층별로, 최소 범위로”

EKS에서 NGINX Ingress의 400/413은 대부분 요청 바디 크기(proxy-body-size) 또는 요청 헤더/요청라인 버퍼(large-client-header-buffers 등) 로 정리됩니다. 핵심은 다음 두 가지입니다.

  • 413은 proxy-body-size로, 가능하면 Ingress annotation으로 서비스 단위 조정
  • 400은 로그 힌트에 따라 헤더/URI 설계 개선을 우선하고, 필요 시 버퍼를 제한적으로 상향

이 원칙대로 접근하면 “전역 설정을 크게 풀고도 계속 불안한 상태”를 피하면서, 재현 가능한 방식으로 400/413을 안정적으로 줄일 수 있습니다.