- Published on
KServe InferenceService 503? Istio·Knative 트러블슈팅
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
KServe로 모델 서빙을 올렸는데 InferenceService 엔드포인트가 503 Service Unavailable을 반환하면, 대부분 “모델이 죽었다”가 아니라 트래픽 라우팅 계층( Istio ) 또는 **서버리스 라우팅/스케일링 계층( Knative Serving )**에서 요청이 어딘가로 전달되지 못한 경우가 많습니다.
이 글은 503을 어느 레이어에서 발생하는지 먼저 식별하고, 그 레이어의 리소스와 로그를 따라가며 원인을 좁히는 방식으로 정리했습니다. 특히 KServe는 내부적으로 Knative와 Istio(또는 Kourier)를 조합해 쓰는 구성이 흔하므로, “KServe 리소스만 봐서는” 답이 안 나오는 케이스가 자주 나옵니다.
503의 의미를 먼저 분해하기
503은 크게 아래 패턴으로 나뉩니다.
- Istio Envoy가 업스트림을 못 찾음
no healthy upstream,cluster not found,upstream connect error류
- Knative Route가 Revision으로 트래픽을 못 보냄
- Revision 미준비, 스케일 0에서 콜드스타트 실패, Activator/queue-proxy 문제
- 파드까지 갔지만 컨테이너가 준비되지 않음
- readiness probe 실패, 포트 불일치, 모델 로딩 지연/메모리 부족
핵심은 “내 요청이 어디까지 도달했는지”를 확인하는 것입니다.
1) 가장 빠른 1차 진단: 어디서 503이 나오는가
Ingress(게이트웨이)에서 503인지 확인
Istio IngressGateway를 쓰는 경우, 우선 게이트웨이 로그에서 503이 찍히는지 봅니다.
# 네임스페이스는 환경에 맞게 조정
kubectl -n istio-system logs deploy/istio-ingressgateway -c istio-proxy --tail=200
여기서 no healthy upstream 같은 문구가 보이면, 게이트웨이는 요청을 받았지만 보낼 대상(엔드포인트)이 건강하지 않다는 뜻입니다. 즉, Istio 라우팅 또는 서비스 엔드포인트/프로빙을 의심합니다.
Knative 쪽 이벤트/상태 확인
KServe InferenceService는 보통 Knative Service, Configuration, Route, Revision을 생성합니다. KServe 상태만 보지 말고 Knative 리소스 상태를 함께 확인합니다.
# KServe 상태
kubectl -n <your-ns> get inferenceservice
kubectl -n <your-ns> describe inferenceservice <isvc-name>
# Knative 상태
kubectl -n <your-ns> get ksvc
kubectl -n <your-ns> get route,configuration,revision
kubectl -n <your-ns> describe ksvc <ksvc-name>
kubectl -n <your-ns> describe revision <revision-name>
Revision이 Ready=False면 503은 거의 확정적으로 Knative/파드 레벨 문제로 이어집니다.
2) Istio 레이어: VirtualService·Gateway·DestinationRule 점검
(A) VirtualService가 올바른 서비스로 향하는지
KServe가 만든 VirtualService 또는 사용자가 만든 Ingress/VirtualService가 있다면, host와 destination이 실제 서비스와 맞는지 확인합니다.
kubectl -n <your-ns> get virtualservice
kubectl -n <your-ns> describe virtualservice
특히 다음 실수가 흔합니다.
host에 내부 서비스 DNS가 아닌 외부 도메인을 넣었는데 Gateway가 그 호스트를 수용하지 않음destination.host가 존재하지 않는 서비스 이름- 포트가 틀림(예: 서비스는
80인데8080으로 라우팅)
(B) 엔드포인트가 실제로 존재하는지
Istio가 “healthy upstream”을 찾으려면 결국 Kubernetes Endpoints 또는 EndpointSlice가 있어야 합니다.
kubectl -n <your-ns> get svc
kubectl -n <your-ns> get endpoints
kubectl -n <your-ns> get endpointslice
endpoints가 비어 있으면, 서비스 셀렉터가 파드를 못 잡고 있거나 파드가 준비 상태가 아니라서 엔드포인트로 등록되지 않은 것입니다.
(C) DestinationRule의 TLS/mTLS 설정 충돌
네임스페이스에 PeerAuthentication으로 STRICT mTLS가 걸려 있는데, 대상 서비스가 그에 맞게 사이드카/정책이 정렬되지 않으면 503이 날 수 있습니다.
kubectl -n <your-ns> get peerauthentication
kubectl -n <your-ns> get destinationrule
증상 예:
- 게이트웨이 로그에
upstream connect error or disconnect/reset before headers류 - 파드 간 통신이 TLS 핸드셰이크에서 끊김
해결 방향:
- Knative/KServe 네임스페이스의 mTLS 정책을 명확히 정리
- 필요 시 특정 서비스에
DestinationRule로tls.mode: ISTIO_MUTUAL또는DISABLE을 의도대로 설정
3) Knative 레이어: Route·Revision·Activator·queue-proxy
KServe 503의 “진짜 범인”이 Knative인 경우가 많습니다. 특히 스케일 0, 콜드 스타트, 프로브 실패가 얽히면 503이 간헐적으로 발생합니다.
(A) Revision이 Ready인지, 이유(Reason)는 무엇인지
kubectl -n <your-ns> get revision
kubectl -n <your-ns> describe revision <revision-name>
자주 보는 원인:
- 이미지 풀 실패(
ImagePullBackOff) - 컨테이너 포트 불일치
- readiness probe 실패
- 리소스 부족(OOMKilled, 스케줄 실패)
이미지 풀 문제는 모델 서버 이미지/프라이빗 레지스트리에서 자주 터집니다. 이 경우 아래 글의 체크리스트가 진단 방식이 유사합니다.
(B) queue-proxy 로그 확인
Knative는 요청을 queue-proxy가 먼저 받고 사용자 컨테이너로 전달합니다. 503이면 queue-proxy가 “뒤 컨테이너가 준비 안 됨”을 판단했을 수 있습니다.
# 파드에서 queue-proxy 컨테이너 로그 확인
kubectl -n <your-ns> logs <pod-name> -c queue-proxy --tail=200
# 사용자 컨테이너 로그
kubectl -n <your-ns> logs <pod-name> -c <user-container-name> --tail=200
queue-proxy 로그에서 probe 실패, no endpoints 같은 메시지가 보이면 readiness/포트/응답코드 문제를 의심합니다.
(C) 스케일 0에서 콜드 스타트 타임아웃
모델 로딩이 오래 걸리면, 스케일 0에서 첫 요청이 들어왔을 때 Activator가 대기하다가 타임아웃으로 503을 반환할 수 있습니다.
점검 포인트:
- 모델 로딩 시간 vs Knative 요청 타임아웃
containerConcurrency가 너무 낮아 큐가 쌓이는지- 리소스(CPU/메모리) 부족으로 로딩이 지연되는지
Knative의 타임아웃/스케일링 관련 어노테이션을 조정해 완화할 수 있습니다(환경/버전에 따라 키가 다를 수 있음).
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: my-model
namespace: <your-ns>
spec:
template:
metadata:
annotations:
autoscaling.knative.dev/minScale: "1"
autoscaling.knative.dev/target: "10"
# 요청을 오래 잡아야 하는 모델이면 타임아웃도 고려
spec:
containerConcurrency: 1
containers:
- image: <your-image>
ports:
- containerPort: 8080
KServe를 쓰는 경우 위 설정을 KServe가 생성하는 Knative 리소스에 직접 적용하기보다는, KServe의 InferenceService 스펙/어노테이션으로 전달하는 방식이 일반적입니다(배포 방식에 따라 다름).
4) 파드/컨테이너 레이어: 포트·프로브·메모리·모델 다운로드
(A) 서비스 포트와 컨테이너 포트 불일치
KServe/Knative는 기본적으로 HTTP 트래픽을 특정 포트(예: 8080)로 기대하는데, 모델 서버가 다른 포트에서 뜨면 readiness가 실패하고 엔드포인트가 비어 503으로 이어집니다.
kubectl -n <your-ns> get pod <pod-name> -o jsonpath='{.spec.containers[*].ports}'
kubectl -n <your-ns> get svc <service-name> -o yaml
(B) readiness/liveness probe가 너무 공격적
모델 로딩이 60초 걸리는데 readiness probe가 10초 안에 성공해야 한다면, 파드는 계속 NotReady로 남고 503이 납니다.
readinessProbe:
httpGet:
path: /v1/models
port: 8080
initialDelaySeconds: 60
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 12
프로브 경로도 중요합니다. 모델 서버가 실제로 제공하는 헬스 엔드포인트와 일치해야 합니다.
(C) OOMKilled로 인한 간헐적 503
모델이 크거나 첫 로딩 시 메모리 피크가 높으면 OOMKilled로 재시작하며 503이 간헐적으로 발생합니다.
kubectl -n <your-ns> describe pod <pod-name>
kubectl -n <your-ns> get pod <pod-name> -o jsonpath='{.status.containerStatuses[*].lastState.terminated.reason}'
해결은 단순히 메모리를 올리는 것뿐 아니라,
- 모델 로딩 방식 최적화(지연 로딩, 가중치 메모리 매핑)
- 노드 타입 상향
requests/limits를 현실적으로 설정
같은 접근이 필요합니다.
(D) 모델 아티팩트 다운로드 실패(권한/네트워크)
S3, GCS, 사내 오브젝트 스토리지에서 모델을 당겨오는 구성이라면, 컨테이너 로그에 인증/권한 오류가 숨어 있을 수 있습니다. 이 경우 HTTP 503은 “서빙 준비 실패”의 결과일 뿐 원인은 외부 스토리지입니다.
S3 계열이라면 아래 체크리스트 방식으로 접근하면 빠릅니다.
5) 재현 가능한 디버깅 루틴(명령어 세트)
아래 순서대로 실행하면 “감”이 아니라 증거 기반으로 503의 위치를 잡을 수 있습니다.
# 1) InferenceService 상태
kubectl -n <ns> get isvc
kubectl -n <ns> describe isvc <name>
# 2) Knative Service/Revision 상태
kubectl -n <ns> get ksvc
kubectl -n <ns> get revision
kubectl -n <ns> describe revision <rev>
# 3) 파드/이벤트(스케줄, 이미지 풀, OOM)
kubectl -n <ns> get pod -owide
kubectl -n <ns> describe pod <pod>
kubectl -n <ns> get events --sort-by=.lastTimestamp | tail -n 50
# 4) 엔드포인트 유무
kubectl -n <ns> get svc
kubectl -n <ns> get endpoints
# 5) 로그(사용자 컨테이너 + queue-proxy)
kubectl -n <ns> logs <pod> -c queue-proxy --tail=200
kubectl -n <ns> logs <pod> -c <user-container> --tail=200
# 6) Istio 게이트웨이 로그
kubectl -n istio-system logs deploy/istio-ingressgateway -c istio-proxy --tail=200
이 루틴에서 “어느 단계에서 비정상(Ready False, endpoints 비어 있음, probe 실패, gateway upstream 에러)”이 처음 나타나는지 찾으면, 그 지점이 원인에 가장 가깝습니다.
6) 운영 관점: 503을 줄이는 설계 팁
minScale로 콜드 스타트 제거
트래픽이 간헐적이지만 첫 요청 성공률이 중요하면 minScale을 1 이상으로 두는 것이 가장 확실합니다. 비용과 안정성의 트레이드오프를 명확히 선택하세요.
모델 로딩을 readiness와 분리
가능하면 readiness는 “프로세스가 요청을 받을 준비가 됨”을 의미하게 하고, 모델 로딩 상태는 별도 엔드포인트/메트릭으로 관측하는 편이 장애 격리가 쉽습니다. 단, 실제 추론이 불가능한 상태에서 트래픽을 받지 않도록 주의가 필요합니다.
GitOps 적용 시 리소스 드리프트/싱크 실패도 함께 점검
Istio/Knative/KServe는 리소스가 많고 상호작용이 복잡해 GitOps에서 OutOfSync나 Health Degraded가 누적되면 “어느 순간 503”으로 튀어나오는 경우가 있습니다. 배포 파이프라인에서 동기화 상태를 함께 관리하세요.
마무리: 503은 결과, 원인은 레이어에 있다
KServe InferenceService의 503은 단일 원인이 아니라 트래픽 경로 상의 어떤 레이어가 준비되지 않았다는 신호입니다.
- Istio 로그에서
upstream오류가 보이면 라우팅/엔드포인트/mTLS를, - Knative
Revision Ready=False면 이미지/프로브/리소스를, - 파드가 뜨지만
queue-proxy가 503을 내면 포트/프로브/콜드 스타트 타임아웃을,
이렇게 “첫 비정상 지점”을 찾는 방식으로 접근하면, 같은 503이라도 재발 방지까지 이어지는 해결책을 빠르게 도출할 수 있습니다.