Published on

KServe InferenceService 503? Istio·Knative 트러블슈팅

Authors

KServe로 모델 서빙을 올렸는데 InferenceService 엔드포인트가 503 Service Unavailable을 반환하면, 대부분 “모델이 죽었다”가 아니라 트래픽 라우팅 계층( Istio ) 또는 **서버리스 라우팅/스케일링 계층( Knative Serving )**에서 요청이 어딘가로 전달되지 못한 경우가 많습니다.

이 글은 503을 어느 레이어에서 발생하는지 먼저 식별하고, 그 레이어의 리소스와 로그를 따라가며 원인을 좁히는 방식으로 정리했습니다. 특히 KServe는 내부적으로 Knative와 Istio(또는 Kourier)를 조합해 쓰는 구성이 흔하므로, “KServe 리소스만 봐서는” 답이 안 나오는 케이스가 자주 나옵니다.

503의 의미를 먼저 분해하기

503은 크게 아래 패턴으로 나뉩니다.

  1. Istio Envoy가 업스트림을 못 찾음
    • no healthy upstream, cluster not found, upstream connect error
  2. Knative Route가 Revision으로 트래픽을 못 보냄
    • Revision 미준비, 스케일 0에서 콜드스타트 실패, Activator/queue-proxy 문제
  3. 파드까지 갔지만 컨테이너가 준비되지 않음
    • 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>

RevisionReady=False면 503은 거의 확정적으로 Knative/파드 레벨 문제로 이어집니다.

2) Istio 레이어: VirtualService·Gateway·DestinationRule 점검

(A) VirtualService가 올바른 서비스로 향하는지

KServe가 만든 VirtualService 또는 사용자가 만든 Ingress/VirtualService가 있다면, hostdestination이 실제 서비스와 맞는지 확인합니다.

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 정책을 명확히 정리
  • 필요 시 특정 서비스에 DestinationRuletls.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로 콜드 스타트 제거

트래픽이 간헐적이지만 첫 요청 성공률이 중요하면 minScale1 이상으로 두는 것이 가장 확실합니다. 비용과 안정성의 트레이드오프를 명확히 선택하세요.

모델 로딩을 readiness와 분리

가능하면 readiness는 “프로세스가 요청을 받을 준비가 됨”을 의미하게 하고, 모델 로딩 상태는 별도 엔드포인트/메트릭으로 관측하는 편이 장애 격리가 쉽습니다. 단, 실제 추론이 불가능한 상태에서 트래픽을 받지 않도록 주의가 필요합니다.

GitOps 적용 시 리소스 드리프트/싱크 실패도 함께 점검

Istio/Knative/KServe는 리소스가 많고 상호작용이 복잡해 GitOps에서 OutOfSyncHealth Degraded가 누적되면 “어느 순간 503”으로 튀어나오는 경우가 있습니다. 배포 파이프라인에서 동기화 상태를 함께 관리하세요.

마무리: 503은 결과, 원인은 레이어에 있다

KServe InferenceService503은 단일 원인이 아니라 트래픽 경로 상의 어떤 레이어가 준비되지 않았다는 신호입니다.

  • Istio 로그에서 upstream 오류가 보이면 라우팅/엔드포인트/mTLS를,
  • Knative Revision Ready=False면 이미지/프로브/리소스를,
  • 파드가 뜨지만 queue-proxy가 503을 내면 포트/프로브/콜드 스타트 타임아웃을,

이렇게 “첫 비정상 지점”을 찾는 방식으로 접근하면, 같은 503이라도 재발 방지까지 이어지는 해결책을 빠르게 도출할 수 있습니다.