- Published on
Kubernetes API 413 Request Entity Too Large 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버에서 413 Request Entity Too Large가 떨어질 때 대부분의 사람은 “Kubernetes API 서버가 뭔가 제한을 걸었나?”라고 생각합니다. 맞는 경우도 있지만, 실제 현장에서는 API 서버까지 도달하기 전에(Ingress/Nginx/ALB/프록시) 잘리거나, 혹은 요청 바디가 과도하게 커지는 리소스 패턴(대형 Secret/ConfigMap, 거대한 last-applied-configuration annotation, 과도한 CRD 스키마/매니페스트)이 원인인 경우가 훨씬 많습니다.
이 글에서는 413을 어디에서 발생했는지를 빠르게 식별하고, 요청 크기를 줄이는 구조적 해결과 구성값으로 상한을 올리는 임시 해결을 구분해 정리합니다. (EKS 포함)
1) 413은 “어느 계층”에서 터졌는지부터 확인
동일한 413이라도 주체가 다르면 접근이 완전히 달라집니다.
1-1. kubectl 출력만으로 힌트 얻기
대체로 아래 형태로 보입니다.
Error from server (RequestEntityTooLarge): ...the server responded with the status code 413 but did not return more information
여기서 “from server”는 API 서버일 수도, 앞단 프록시(예: Nginx, API Gateway, ALB)일 수도 있습니다. 따라서 다음을 곧바로 확인합니다.
1-2. curl로 kube-apiserver에 직접 쏴보기(가능한 경우)
클러스터 내부(또는 bastion)에서 API 서버 엔드포인트로 직접 호출해 중간 프록시를 우회해봅니다.
# kubeconfig가 있는 환경에서
APISERVER=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}')
TOKEN=$(kubectl -n kube-system create token default 2>/dev/null || true)
# 토큰이 없으면(구버전) 다른 서비스어카운트 토큰 사용 필요
curl -k -H "Authorization: Bearer $TOKEN" "$APISERVER/version"
- 직접 호출은 성공하지만, 특정 경로(예: 회사 프록시, Ingress 경유)에서만 413이면 프록시 제한일 확률이 큽니다.
- 직접 호출에서도 413이면 API 서버(또는 그 앞의 managed control plane 프록시) 제한이거나, 요청 자체가 매우 큽니다.
1-3. EKS/관리형 환경에서의 현실
EKS 같은 관리형 컨트롤 플레인은 kube-apiserver 플래그를 마음대로 바꾸기 어렵습니다. 그래서 실전에서는:
- 요청 바디를 줄이는 방향이 정답인 경우가 많고
- 413이 로드밸런서/프록시/웹훅에서 발생하는지 로그로 증명하는 것이 중요합니다.
관련해서 EKS에서 네트워크 계층을 점검하는 글도 함께 참고하면 진단 속도가 빨라집니다: EKS에서 NodePort만 안 열릴 때 CNI·SG 점검
2) 가장 흔한 원인 Top 5
원인 A) Secret/ConfigMap에 “너무 큰 데이터”를 넣음
- 인증서 체인, 거대한 JSON 설정, 번들 파일, 모델 파일 등을 통째로 넣는 경우
- 특히
stringData:로 큰 값을 넣고 base64 인코딩되면서 더 커짐
증상: kubectl apply -f secret.yaml 시 413
원인 B) kubectl apply의 last-applied-configuration annotation이 비대해짐
kubectl apply는 기본적으로 metadata.annotations["kubectl.kubernetes.io/last-applied-configuration"] 에 “전체 매니페스트”를 저장합니다.
- 매니페스트가 큰 리소스(대형 CRD, 대형 ConfigMap, Helm이 만든 방대한 스펙)일수록 annotation이 커져 요청이 더 커집니다.
- 한 번 커지면 이후 patch/apply 때도 계속 큰 payload가 오갑니다.
원인 C) CRD가 너무 큼(스키마/설명/검증 규칙 과다)
OpenAPI v3 schema가 방대하거나, 긴 description, 과도한 enum/validation이 들어가면 CRD 자체가 커지고, 업데이트/적용 시 413이 터질 수 있습니다.
원인 D) Admission Webhook(Validating/Mutating) 앞단 프록시 제한
웹훅이 Nginx/Envoy/Ingress 뒤에 있고 client_max_body_size 같은 제한이 낮으면, API 서버가 웹훅 호출을 시도하다가 413을 받아 실패합니다.
EKS에서 웹훅 타임아웃처럼 “컨트롤 플레인 ↔ 웹훅” 경로가 문제되는 사례와 결이 비슷합니다: EKS AWS Load Balancer Controller 500 Webhook 타임아웃 해결
원인 E) 클라이언트/프록시가 압축을 안 하거나, 리버스 프록시가 제한
- kubectl/클라이언트가 보내는 요청이 gzip 압축 없이 원문으로 전달되거나
- 회사 보안 프록시가 request body 제한을 걸어두는 경우
3) “정석 해결”: 요청 바디를 줄이는 방법
관리형 Kubernetes에서는 상한을 올리기 어렵거나, 올려도 다른 병목(ETCD, apiserver 메모리/CPU, admission chain) 때문에 장애를 부릅니다. 따라서 먼저 요청을 작게 만드는 구조를 권장합니다.
3-1. 큰 파일은 Secret/ConfigMap에 넣지 말고 “외부 스토리지 + 참조”로
대형 설정/바이너리는 아래 패턴으로 바꾸는 게 안전합니다.
- S3/GCS/Blob Storage에 두고 initContainer로 다운로드
- 또는 CSI Driver(예: Secrets Store CSI Driver)로 외부 Secret Manager 연동
- 앱은 환경변수/짧은 키만 K8s Secret에 저장
예시: initContainer로 S3에서 설정을 가져와 볼륨에 저장
apiVersion: v1
kind: Pod
metadata:
name: app
spec:
volumes:
- name: config
emptyDir: {}
initContainers:
- name: fetch-config
image: amazon/aws-cli:2.15.0
command: ["sh", "-c"]
args:
- |
aws s3 cp s3://my-bucket/app/config.json /config/config.json
volumeMounts:
- name: config
mountPath: /config
containers:
- name: app
image: myapp:latest
volumeMounts:
- name: config
mountPath: /app/config
readOnly: true
이 방식은 API 요청 크기를 줄일 뿐 아니라, etcd에 거대한 값을 저장하지 않게 되어 클러스터 안정성에도 좋습니다.
3-2. kubectl apply 대신 Server-Side Apply(SSA)로 전환
클라이언트 사이드 apply는 last-applied annotation 때문에 커지기 쉽습니다. SSA는 그 부담을 줄이고 필드 매니지먼트를 서버에서 처리합니다.
kubectl apply --server-side -f big-resource.yaml
이미 last-applied annotation이 비대해진 리소스는 annotation을 제거하면 즉시 효과가 나는 경우가 많습니다.
# 특정 리소스에서 last-applied annotation 제거
kubectl annotate deploy myapp kubectl.kubernetes.io/last-applied-configuration- --overwrite
# 여러 리소스에 일괄 적용(예: 네임스페이스 내 모든 디플로이)
kubectl -n myns get deploy -o name \
| xargs -I{} kubectl -n myns annotate {} kubectl.kubernetes.io/last-applied-configuration- --overwrite
주의:
- annotation 제거 후에는 이후 apply 동작이 달라질 수 있으니(3-way merge 히스토리) 배포 파이프라인에서 SSA로 고정하는 것을 권장합니다.
3-3. CRD 크기를 줄이는 실전 팁
CRD가 원인이라면 아래를 점검합니다.
- 불필요하게 긴
description제거(문서화는 별도 문서로) - 지나치게 큰
enum/oneOf/anyOf축소 - 스키마를 여러 CRD로 쪼개기(도메인 단위)
status서브리소스 분리(필수는 아니지만 스키마 정리가 쉬움)
CRD는 한 번 커지면 업데이트/롤백/동기화가 모두 무거워지므로, 413은 “지금만” 문제가 아니라는 신호로 보는 게 좋습니다.
3-4. 큰 리스트/대량 리소스는 한 번에 apply하지 말고 쪼개기
예를 들어 수백 개 리소스를 하나의 YAML에 묶어 apply하면, kubectl이 한 번에 보내는 요청/패치가 커질 수 있습니다(특히 kustomize/helm 출력물을 그대로 apply할 때).
- 파일을 리소스 단위로 분리
kubectl apply -f dir/로 여러 요청으로 나눔- GitOps(Argo CD/Flux) 사용 시 sync wave/분할 적용
4) “임시 해결”: 제한을 올려야 할 때(프록시/웹훅/Nginx)
요청을 줄이는 게 최선이지만, 현실적으로 웹훅 앞단 프록시에서 413이 나는 경우는 제한을 올려야 합니다.
4-1. Nginx Ingress의 client_max_body_size 조정
Nginx Ingress Controller를 사용한다면 annotation 또는 ConfigMap으로 조정합니다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: webhook-ingress
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "10m"
spec:
rules:
- host: webhook.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-webhook
port:
number: 443
웹훅은 API 서버가 호출하는 트래픽이므로, 실제로는 Ingress를 거치지 않게(ClusterIP 직접 호출) 구성하는 게 더 일반적입니다. 하지만 사내 네트워크 정책 때문에 Ingress 경유가 강제되는 환경이라면 위 설정이 필요할 수 있습니다.
4-2. Envoy/프록시의 max_request_bytes 계열 설정
Envoy를 쓴다면 max_request_headers_kb가 아니라 request body 제한(필터/route 레벨)을 확인해야 합니다. 조직마다 배치가 달라 일반화는 어렵지만, 413이 Envoy에서 나면 access log에 response_code_details로 원인이 찍히는 경우가 많습니다.
4-3. (자체 운영 kube-apiserver) apiserver 요청 크기 제한
온프레미스에서 kube-apiserver를 직접 운영한다면 다음 계열 플래그/구성을 점검합니다.
- API 서버/aggregator의 제한
- ETCD의 request size 제한(간접 원인)
다만 이 영역은 버전/배포판(kubeadm, kOps, RKE2 등)마다 옵션이 달라서, 무턱대고 상한만 올리면 etcd/컨트롤 플레인 리소스가 급격히 증가할 수 있습니다. 가능하면 3장에서 다룬 “요청 축소”를 우선하세요.
5) 빠른 트러블슈팅 체크리스트(10분 컷)
5-1. 어떤 리소스가 문제인지 최소 재현
# 어떤 파일에서 터지는지 확인
kubectl apply -f a.yaml
kubectl apply -f b.yaml
# dry-run으로도 재현되는지(서버까지 가므로 413이면 여기서도 터짐)
kubectl apply --server-side --dry-run=server -f big.yaml
5-2. 리소스의 크기/annotation 확인
# 리소스 전체 YAML 크기 대략 확인
kubectl get cm big-config -o yaml | wc -c
# last-applied annotation 존재/크기 확인
kubectl get deploy myapp -o json \
| jq -r '.metadata.annotations["kubectl.kubernetes.io/last-applied-configuration"]' \
| wc -c
annotation이 수백 KB~MB 단위면 거의 확정입니다.
5-3. 웹훅이 개입하는지 확인
kubectl get validatingwebhookconfigurations
kubectl get mutatingwebhookconfigurations
특정 리소스 apply 시에만 413이면, 해당 리소스의 admission chain(웹훅)을 의심하세요.
5-4. 에러가 네트워크 계층인지 확인(로그)
- Ingress Controller/Nginx/Envoy access log에 413이 찍히는지
- API 서버 audit log(가능한 환경에서)에서 요청이 들어왔는지
네트워크 계층 장애 진단은 다른 에러(401/403/timeout)와 함께 엮여 나타나기도 합니다. 인증/권한 이슈가 동반된다면 Kubernetes 401 Unauthorized 원인별 해결 가이드도 함께 보면 분리 진단에 도움이 됩니다.
6) 예방: “큰 데이터는 K8s API에 싣지 않는다”를 팀 규칙으로
413은 단발성 에러처럼 보이지만, 사실은 클러스터가 다루기 부적절한 데이터가 API를 통해 유통되고 있다는 신호인 경우가 많습니다.
권장 운영 규칙:
- Secret/ConfigMap에는 키(레퍼런스)와 작은 설정만 저장
- 대형 설정/바이너리는 오브젝트 스토리지/아티팩트 저장소/Secret Manager로
- 배포는 가능하면 Server-Side Apply로 통일
- CRD는 스키마를 “문서”가 아니라 “검증” 관점으로 최소화
- GitOps를 쓴다면 큰 매니페스트를 한 번에 밀어 넣지 않게 분할
7) 결론: 413은 ‘상한 올리기’보다 ‘구조 개선’이 먼저
413 Request Entity Too Large는 해결 자체는 단순해 보이지만, 무작정 제한을 올리면 컨트롤 플레인/etcd 부담이 커져 더 큰 장애로 이어질 수 있습니다. 가장 효과적인 순서는 보통 다음과 같습니다.
- 문제 리소스를 특정하고(Secret/ConfigMap/CRD/웹훅)
last-applied-configuration같은 불필요한 비대 요소 제거- 큰 데이터의 저장 위치를 K8s 밖으로 이동
- 정말 필요한 경우에만 프록시/웹훅의 body size 제한을 상향
위 순서대로 접근하면, EKS 같은 관리형 환경에서도 413을 안정적으로 제거할 수 있습니다.