Published on

KServe + Istio에서 GPU 모델 503·타임아웃 해결

Authors

서빙 중인 GPU 모델이 KServe 위에서 잘 뜨는 것처럼 보이는데, 실제 트래픽을 붙이면 간헐적으로 503 이 터지거나 요청이 길게 걸리다 타임아웃으로 떨어지는 경우가 많습니다. 특히 Istio를 함께 쓰는 환경에서는 실패 지점이 client -> gateway/envoy -> knative queue-proxy -> predictor 로 길어지면서, 기본값 타임아웃/프로브/스케일링 설정이 GPU 워크로드의 특성과 충돌하기 쉽습니다.

이 글은 KServe + Istio 조합에서 GPU 모델 503·타임아웃을 재현 가능한 원인 단위로 쪼개고, 로그/지표로 어디서 끊기는지 판단한 뒤, 안전한 튜닝 값을 적용하는 순서로 정리합니다.

관련해서 쿠버네티스 장애를 빠르게 분류하는 관점은 Kubernetes CrashLoopBackOff 원인 7가지와 실전 디버깅 글도 같이 보면 도움이 됩니다.

1) 503이 의미하는 것: 어디서 503을 만들었나

503 은 “업스트림으로 라우팅하지 못했다”는 뜻이지만, KServe + Istio에서는 503의 출처가 다릅니다.

  • Istio IngressGateway/Envoy가 만든 503
    • 예: upstream connect error or disconnect/reset before headers
    • 보통 업스트림 연결 실패, 엔드포인트 없음, mTLS 문제, 타임아웃/재시도 정책 문제
  • Knative queue-proxy가 만든 503
    • Pod는 떴지만 컨테이너 readiness가 안 됨, 동시성 제한, 스케일 0에서 깨어나는 중
  • 애플리케이션이 만든 503
    • 모델 서버 내부에서 OOM, GPU 초기화 실패, 모델 로드 실패

따라서 가장 먼저 할 일은 “503을 누가 만들었는지”를 확인하는 것입니다.

빠른 판별 체크리스트

  1. IngressGateway access log에서 response_code=503 인지 확인
  2. KServe predictor Pod의 queue-proxy 로그에 503이 찍히는지 확인
  3. predictor 컨테이너(예: kserve-container) 로그에 에러가 있는지 확인

아래 명령은 기본적인 시작점입니다.

# IngressGateway 로그 (환경에 맞게 네임스페이스/파드명 수정)
kubectl -n istio-system logs deploy/istio-ingressgateway -c istio-proxy --tail=200

# InferenceService가 있는 네임스페이스에서 predictor 파드 확인
kubectl -n ml get pods -l serving.kserve.io/inferenceservice=my-model

# queue-proxy / 사용자 컨테이너 로그
kubectl -n ml logs pod/<predictor-pod> -c queue-proxy --tail=200
kubectl -n ml logs pod/<predictor-pod> -c kserve-container --tail=200

<predictor-pod> 같은 문자열에 부등호가 들어가면 MDX에서 JSX로 오인될 수 있으니, 문서나 주석에서는 반드시 백틱으로 감싸는 습관을 권장합니다.

2) GPU 모델에서 특히 흔한 원인 6가지

GPU 서빙은 CPU 기반 웹서비스와 달리 다음 특성이 있습니다.

  • 최초 요청 전에 모델 로드 + CUDA 컨텍스트 초기화가 길다
  • 큰 모델은 이미지 pull, PV mount, weight 로딩이 느리다
  • 첫 토큰까지 시간이 긴 LLM류는 초기 지연이 길고 응답 스트리밍도 고려해야 한다
  • GPU 메모리 부족 시 재시작/크래시가 아닌 “느려짐”으로 나타나기도 한다

이런 특성과 Istio/Knative 기본값이 충돌하면서 503·타임아웃이 생깁니다.

원인 A: 스케일 0 콜드스타트 + 기본 타임아웃 충돌

KServe는 Knative 기반일 때 스케일 0이 가능하고, 이때 첫 요청이 Pod 생성/모델 로드를 기다립니다. 기본 타임아웃(클라이언트, 게이트웨이, 라우팅)이 짧으면 첫 요청이 타임아웃으로 떨어지고, 재시도까지 겹치면 503 폭탄처럼 보입니다.

해결 방향:

  • 최소 레플리카를 1로 유지하거나
  • 타임아웃을 늘리고
  • 모델 로드가 끝나기 전에는 트래픽을 받지 않도록 readiness를 정확히 설정

원인 B: readiness probe가 “서버 포트 오픈”만 보고 통과

모델 서버 프로세스가 떠서 포트만 열려도 readiness가 통과하면, 실제로는 모델이 아직 로딩 중인데 트래픽이 들어와 503 또는 긴 지연이 발생합니다.

해결 방향:

  • readiness endpoint를 “모델 로딩 완료”를 반영하도록 구성
  • KServe 런타임별 health endpoint를 확인하고, 필요하면 커스텀 readiness 도입

원인 C: Istio/Envoy의 기본 요청 타임아웃

Istio VirtualService 또는 Gateway 레벨의 타임아웃이 짧으면, 모델 추론 시간이 길거나 첫 요청이 느릴 때 504 또는 503 계열로 보일 수 있습니다(환경에 따라). 또한 업스트림 연결이 늦어도 Envoy가 먼저 포기할 수 있습니다.

해결 방향:

  • VirtualService의 timeout 을 모델 SLA에 맞게 조정
  • 재시도 정책은 신중하게(추론 요청은 멱등이 아닐 수 있음)

원인 D: mTLS/AuthorizationPolicy로 인한 업스트림 연결 실패

Istio mTLS STRICT 환경에서, 특정 네임스페이스/서비스 계정 간 정책이 맞지 않으면 연결 자체가 실패합니다. 이 경우 Envoy 로그에 업스트림 TLS 핸드셰이크 실패가 남고 503이 발생합니다.

해결 방향:

  • PeerAuthentication / DestinationRule / AuthorizationPolicy를 점검
  • 우선 원인 분리를 위해 해당 서비스에 한해 PERMISSIVE로 내려 테스트

원인 E: GPU 리소스 부족으로 인한 재스케줄링/대기

GPU 노드가 부족하면 Pod가 Pending 상태로 오래 머물고, 스케일 0에서 깨우는 첫 요청이 계속 실패합니다.

해결 방향:

  • nvidia.com/gpu 요청량과 노드 가용량 확인
  • 노드 셀렉터/테인트/톨러레이션이 과하게 제한되어 있지 않은지 확인

원인 F: 커넥션 드레이닝/종료(graceful shutdown) 미흡

스케일 다운이나 롤링 업데이트 시, Envoy가 이미 라우팅 중인 연결을 끊어 503이 발생할 수 있습니다. GPU 모델은 종료 시간이 길 수 있어 더 취약합니다.

해결 방향:

  • terminationGracePeriodSeconds 를 충분히 주고
  • preStop 훅으로 드레이닝 시간을 확보

3) “어디서 끊겼는지”를 로그로 좁히는 실전 절차

3-1. 엔드포인트가 실제로 존재하는지 확인

Istio가 라우팅할 엔드포인트가 없으면 Envoy는 503을 반환합니다.

# 서비스 엔드포인트 확인
kubectl -n ml get svc
kubectl -n ml get endpoints my-model-predictor-default

# (EndpointSlice를 쓰는 클러스터라면)
kubectl -n ml get endpointslices | grep my-model

엔드포인트가 비어 있다면:

  • Pod readiness가 통과하지 못했거나
  • Pod 자체가 없거나(Pending/Crash)
  • selector가 잘못되었을 수 있습니다.

3-2. predictor Pod 상태와 이벤트 확인

kubectl -n ml describe pod <predictor-pod>
kubectl -n ml get events --sort-by=.lastTimestamp | tail -n 50

여기서 자주 보이는 힌트:

  • Readiness probe failed
  • FailedScheduling (GPU 부족)
  • Back-off pulling image (이미지 pull 지연)

3-3. Envoy가 왜 실패했는지 메시지 확인

IngressGateway 또는 사이드카에서 다음 문자열을 찾습니다.

  • upstream connect error
  • no healthy upstream
  • TLS error
  • upstream request timeout
# 특정 predictor 파드의 istio-proxy 로그
kubectl -n ml logs pod/<predictor-pod> -c istio-proxy --tail=200

4) 해결책 1: Istio VirtualService 타임아웃과 재시도 정리

GPU 추론은 요청 시간이 길 수 있고, 특히 첫 요청은 더 길 수 있습니다. 기본 재시도는 오히려 GPU를 더 바쁘게 만들어 지연을 악화시키거나, 멱등이 아닌 요청에 부작용을 만들 수 있습니다.

다음은 “타임아웃을 늘리고, 재시도는 최소화”하는 예시입니다.

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: my-model-vs
  namespace: ml
spec:
  hosts:
    - my-model.example.com
  gateways:
    - istio-system/my-gateway
  http:
    - timeout: 300s
      retries:
        attempts: 1
        perTryTimeout: 300s
        retryOn: gateway-error,connect-failure,refused-stream
      route:
        - destination:
            host: my-model-predictor-default.ml.svc.cluster.local
            port:
              number: 80

팁:

  • 스트리밍 응답을 쓰는 경우, 중간 프레임이 늦어도 연결이 유지되도록 게이트웨이/클라이언트 타임아웃도 함께 봐야 합니다.
  • 무조건 attempts 를 올리면 GPU가 “같은 요청을 여러 번” 처리할 수 있습니다.

5) 해결책 2: KServe 스케일링과 콜드스타트 완화

5-1. 최소 레플리카로 스케일 0 회피

GPU 모델은 스케일 0에서 깨우는 비용이 커서, 사용자 체감 503·타임아웃의 핵심 원인이 되곤 합니다. 비용과 SLA를 맞춰 minReplicas 를 1로 두는 것이 가장 단순하고 효과적인 해결책입니다.

apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
  name: my-model
  namespace: ml
spec:
  predictor:
    minReplicas: 1
    maxReplicas: 3
    # runtime에 맞게 설정
    containers:
      - name: kserve-container
        image: myrepo/my-gpu-model:latest
        resources:
          requests:
            nvidia.com/gpu: "1"
          limits:
            nvidia.com/gpu: "1"

5-2. 동시성 제한으로 “GPU 과부하로 인한 지연” 방지

GPU는 CPU처럼 무한 동시 요청을 잘 못 버팁니다. 동시성이 높아지면 지연이 늘고, 결국 Envoy/클라이언트 타임아웃으로 503처럼 보이는 실패가 늘어납니다.

Knative 기반이라면 컨테이너 동시성 제한을 고려할 수 있습니다(환경에 따라 annotation 키가 다를 수 있습니다).

metadata:
  annotations:
    autoscaling.knative.dev/target: "1"
    autoscaling.knative.dev/metric: "concurrency"

target 을 1로 두면 레플리카당 동시 요청을 1에 가깝게 유지해 지연 폭발을 막습니다. 처리량이 필요하면 maxReplicas 와 GPU 수를 늘리는 쪽이 예측 가능성이 좋습니다.

6) 해결책 3: readiness를 “모델 준비 완료” 기준으로 바꾸기

GPU 모델 서버는 프로세스가 뜬 시점과 “추론 가능한 시점”이 다릅니다. readiness가 너무 빨리 통과하면, 트래픽이 들어온 뒤 내부적으로 모델 로드 대기 때문에 타임아웃이 발생합니다.

6-1. HTTP readiness probe 예시

모델 로딩이 끝나야 200 을 반환하는 엔드포인트를 만들 수 있다면 가장 좋습니다.

spec:
  predictor:
    containers:
      - name: kserve-container
        image: myrepo/my-gpu-model:latest
        readinessProbe:
          httpGet:
            path: /health/ready
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5
          timeoutSeconds: 2
          failureThreshold: 60

핵심은 failureThreshold 를 충분히 크게 잡아 “모델 로딩 중”을 정상적인 준비 과정으로 허용하는 것입니다.

6-2. startup probe로 “긴 초기화”를 분리

Kubernetes에서 startup probe를 쓰면, 초기 구간에는 readiness/liveness 실패로 재시작되는 것을 막을 수 있습니다.

startupProbe:
  httpGet:
    path: /health/startup
    port: 8080
  periodSeconds: 5
  failureThreshold: 120
livenessProbe:
  httpGet:
    path: /health/live
    port: 8080
  periodSeconds: 10
readinessProbe:
  httpGet:
    path: /health/ready
    port: 8080
  periodSeconds: 5

이 조합은 “초기화는 오래 걸려도 된다, 대신 살아있고 준비되었을 때만 트래픽을 받는다”를 명확히 합니다.

7) 해결책 4: 종료 드레이닝과 graceful shutdown

스케일 다운/업데이트 시 503이 튀면 종료 과정이 짧은 경우가 많습니다. GPU 모델 서버는 메모리 해제나 세션 정리로 종료가 길어질 수 있습니다.

spec:
  predictor:
    containers:
      - name: kserve-container
        lifecycle:
          preStop:
            exec:
              command:
                - /bin/sh
                - -c
                - "sleep 20"
    terminationGracePeriodSeconds: 120

preStopsleep 은 단순하지만 효과적입니다. 더 나은 방식은 애플리케이션에 “드레이닝 모드”를 두고, preStop에서 해당 모드를 켠 뒤 일정 시간 후 종료하는 것입니다.

8) GPU 모델 503·타임아웃을 줄이는 운영 팁

8-1. 이미지 pull과 모델 파일 다운로드를 줄이기

  • 큰 이미지 레이어를 줄이고
  • 모델 weight는 가능하면 로컬 PV/캐시를 사용
  • 노드에 이미지 프리풀(daemonset)도 고려

콜드스타트가 줄면 타임아웃도 함께 줄어듭니다. 이 관점은 503을 콜드스타트로 접근하는 GCP Cloud Run 503·콜드스타트 줄이는 튜닝 글과도 결이 같습니다.

8-2. 실패를 “타임아웃”으로 숨기지 말고 SLO로 쪼개기

  • p50/p95/p99 latency
  • cold start latency
  • model load time 을 분리해 관측하면

단순히 타임아웃만 늘려서 장애를 늦추는 실수를 줄일 수 있습니다.

8-3. 401/403과 503을 혼동하지 않기

Istio 정책이 꼬이면 401/403이 먼저 나가야 할 것 같지만, 실제로는 업스트림 연결 실패로 503처럼 보일 때도 있습니다. 인증/인가 계층을 점검해야 한다면 Kubernetes 401 Unauthorized 원인별 해결 가이드도 참고하세요.

9) 최종 점검: 적용 순서 추천

현장에서 가장 빠르게 효과를 보는 순서는 보통 다음과 같습니다.

  1. 엔드포인트가 비는지 확인(ready 안 됨, Pending)
  2. readiness/startup probe 를 모델 로딩 완료 기준으로 수정
  3. minReplicas=1 로 스케일 0 콜드스타트 제거(비용과 SLA 절충)
  4. Istio VirtualService timeout 을 모델 SLA에 맞게 상향
  5. 재시도/동시성 정책을 줄여 GPU 과부하로 인한 지연 폭발 방지
  6. 종료 드레이닝으로 배포/스케일 다운 시 503 제거

GPU 모델 서빙에서 503·타임아웃은 단일 설정 실수라기보다, “콜드스타트 + 준비 상태 + 프록시 타임아웃 + 스케일링”이 맞물려 생기는 경우가 대부분입니다. 위 절차대로 실패 지점을 한 단계씩 분리해보면, 재현 가능한 형태로 원인을 고정하고 안정적으로 해결할 수 있습니다.