Published on

KServe GPU 추론 503·콜드스타트 줄이는 법

Authors

KServe로 GPU 추론 서비스를 올리면, 배포 직후나 트래픽 스파이크 때 503 Service Unavailable 이 튀고 첫 요청 지연이 수십 초에서 수 분까지 늘어나는 경우가 흔합니다. 특히 Knative 기반의 오토스케일링, 대형 모델 로딩, GPU 노드 스케줄링이 동시에 얽히면서 “정상 배포인데 간헐적으로 죽는” 형태로 나타나서 진단이 어렵습니다.

이 글은 KServe InferenceService 기준으로 GPU 추론 배포에서 503과 콜드스타트를 줄이는 방법을 원인별로 분해하고, 바로 적용 가능한 설정 조합관측 포인트를 제공합니다.

참고로, 503이 사실상 “뒤에서 Pod가 준비가 안 됨” 문제인 경우가 많아서, EKS 환경이라면 먼저 EKS Pod Pending? 노드 셀렉터·테인트 5분 진단 도 같이 보면 원인 압축에 도움이 됩니다.

1) 503이 나는 지점을 먼저 분리하기

503 이라고 해도 어디서 503이 났는지에 따라 처방이 완전히 다릅니다.

1-1. 503 발생 레이어 3가지

  1. Ingress / Gateway 레벨
    • ALB, Nginx, Istio Gateway 등이 업스트림을 못 찾거나 타임아웃
  2. Knative Queue-Proxy 레벨
    • Revision은 있으나 Ready가 아니거나, 스케일링 중에 라우팅이 비는 구간
  3. 컨테이너 애플리케이션 레벨
    • 서버는 떠 있지만 모델 로딩 중이라 실제 요청 처리 불가

1-2. 빠른 확인 명령

아래는 “지금 라우팅 대상이 Ready인지”를 빠르게 보는 기본 루틴입니다.

# InferenceService 상태
kubectl get inferenceservice -n ml
kubectl describe inferenceservice <name> -n ml

# Knative Revision / Pod 상태
kubectl get revision -n ml
kubectl get pod -n ml -l serving.kserve.io/inferenceservice=<name>

# 최근 이벤트에서 스케줄링/프로브 실패 확인
kubectl get events -n ml --sort-by=.lastTimestamp | tail -n 50
  • Pod가 Pending 이면 503의 원인은 거의 항상 스케줄링 실패입니다.
  • Pod가 Running 인데도 503이면 프로브/트래픽 전환/모델 로딩 타이밍 쪽을 봐야 합니다.

2) GPU 추론에서 콜드스타트가 커지는 구조

GPU 추론의 콜드스타트는 단순히 컨테이너 시작 시간이 아닙니다. 보통 아래 4개가 합산됩니다.

  1. 이미지 Pull 시간
  2. 컨테이너 시작 및 런타임 초기화
  3. 모델 다운로드 및 로딩
  4. GPU 메모리 할당 및 커널 워밍업

KServe는 Knative 기반이라 scale-to-zero 를 쓰면 1~4가 매번 반복될 수 있습니다. “첫 요청이 느리다”가 아니라 “첫 요청이 실패(503)한다”로 이어지는 이유는, 라우팅이 열렸는데 애플리케이션이 아직 준비가 안 된 상태가 생기기 때문입니다.

3) 해결 전략 A: 스케일 투 제로를 끄거나 최소 파드를 유지

가장 확실한 방법은 “0에서 1로 올라가는 구간”을 줄이는 것입니다.

3-1. minReplicas 로 상시 1개 유지

GPU 비용이 허용된다면 minReplicas: 1 이 503과 콜드스타트를 가장 크게 줄입니다.

apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
  name: llm-gpu
  namespace: ml
spec:
  predictor:
    minReplicas: 1
    maxReplicas: 3
    containers:
      - name: kserve-container
        image: <your-image>
        resources:
          limits:
            nvidia.com/gpu: "1"
            cpu: "4"
            memory: 16Gi
  • minReplicas 가 0이면, 트래픽이 들어오는 순간 스케일링과 모델 로딩이 겹치며 503이 나기 쉽습니다.
  • maxReplicas 는 GPU 노드 수와 맞춰야 합니다.

3-2. “완전 상시”가 부담이면, 시간대별 유지

운영에서 흔한 패턴은 “주간에는 minReplicas: 1 유지, 야간에는 0”입니다. 이는 GitOps 파이프라인이나 크론 기반으로 패치할 수 있습니다.

kubectl patch inferenceservice llm-gpu -n ml --type merge -p '{"spec":{"predictor":{"minReplicas":1}}}'

4) 해결 전략 B: 프로브를 모델 로딩 현실에 맞추기

503이 “준비 안 된 Pod로 트래픽이 간다”로 보이면, 프로브 설계가 핵심입니다.

4-1. startupProbe 를 강하게 주고, readinessProbe 는 엄격히

모델 로딩이 긴 서비스는 startupProbe부팅 시간을 충분히 주고, readinessProbe진짜 추론 가능 시점에만 성공하도록 해야 합니다.

containers:
  - name: kserve-container
    image: <your-image>
    ports:
      - containerPort: 8080
    startupProbe:
      httpGet:
        path: /healthz
        port: 8080
      failureThreshold: 180
      periodSeconds: 2
    readinessProbe:
      httpGet:
        path: /readyz
        port: 8080
      periodSeconds: 2
      failureThreshold: 3
    livenessProbe:
      httpGet:
        path: /livez
        port: 8080
      periodSeconds: 10
      failureThreshold: 3
  • startupProbe 는 “모델 로딩이 끝날 때까지 재시작하지 말라”는 의미입니다.
  • readinessProbe 는 “트래픽을 받아도 된다”의 의미입니다.
  • /readyz 는 반드시 모델이 GPU에 올라가고 첫 더미 추론까지 가능한 상태를 기준으로 구현하는 게 좋습니다.

4-2. 애플리케이션 헬스 엔드포인트 구현 예시

FastAPI 기준으로, 모델 로딩 완료 플래그를 readiness에 반영합니다.

from fastapi import FastAPI

app = FastAPI()
model_ready = False

@app.on_event("startup")
def load_model():
    global model_ready
    # 1) 모델 파일 준비
    # 2) GPU 로딩
    # 3) 워밍업
    model_ready = True

@app.get("/healthz")
def healthz():
    return {"ok": True}

@app.get("/readyz")
def readyz():
    if not model_ready:
        return {"ready": False}
    return {"ready": True}

@app.get("/livez")
def livez():
    return {"alive": True}

이렇게 하면, 컨테이너는 떠 있어도 모델이 준비되기 전까지는 Ready가 되지 않아서 503이 줄어듭니다.

5) 해결 전략 C: Knative 스케일링 파라미터 튜닝

KServe는 내부적으로 Knative Serving을 사용합니다. GPU 추론은 “동시성 당 처리량”이 낮고, 워밍업 비용이 커서 기본값이 맞지 않는 경우가 많습니다.

5-1. 동시성(containerConcurrency)을 보수적으로

동시성이 과도하면 큐잉이 늘고 타임아웃이 503처럼 보일 수 있습니다.

spec:
  predictor:
    containerConcurrency: 1
  • LLM 계열은 1 또는 2부터 시작해 관측으로 올리는 편이 안전합니다.

5-2. 스케일링 윈도우와 안정화

급격한 스케일 다운이 다시 콜드스타트를 만들기도 합니다. Revision이 자주 내려갔다 올라가면 “503 + 지연”이 반복됩니다.

Knative 어노테이션은 환경에 따라 다르지만, 대표적으로 아래처럼 “스케일 다운을 느리게” 하거나 “유휴 시간”을 늘려서 플랩핑을 줄입니다.

metadata:
  annotations:
    autoscaling.knative.dev/scale-down-delay: 10m
    autoscaling.knative.dev/window: 60s

운영 클러스터에서 어노테이션 허용 범위가 제한될 수 있으니, 적용 후 kubectl describe revision 로 반영 여부를 확인하세요.

6) 해결 전략 D: 이미지 Pull과 모델 다운로드를 줄이기

GPU 노드는 보통 비싸고 수가 적어서, 한 번 Pending 이나 ImagePullBackOff 가 나면 사용자 입장에서는 503으로 체감됩니다.

이미지 Pull 실패/지연이 의심되면 EKS라면 EKS Pod ImagePullBackOff - ECR 인증·IRSA 점검법 처럼 인증과 레지스트리 경로부터 확인하는 것이 빠릅니다.

6-1. 큰 이미지를 줄이는 실전 팁

  • 베이스 이미지를 cuda-runtime 계열로 최소화
  • 빌드 스테이지 분리로 불필요한 빌드 도구 제거
  • 모델 파일을 이미지에 포함할지, PV나 오브젝트 스토리지에서 받을지 결정

일반적으로 “모델이 자주 바뀌면 외부 스토리지”, “모델이 고정이면 이미지 포함”이 운영이 편합니다. 다만 이미지 포함은 레이어가 커져 Pull 시간이 길어질 수 있습니다.

6-2. 모델 다운로드 병목 줄이기

  • 노드 로컬 캐시: DaemonSet으로 미리 모델을 내려받아 /var/lib/models 같은 경로에 캐시
  • PV 캐시: EBS/EFS에 모델을 두고 여러 Pod가 공유
  • 오브젝트 스토리지: 시작 시 다운로드하되, readiness는 다운로드 완료 후에만 성공

핵심은 “모델 다운로드 중에 Ready가 되지 않게”와 “다운로드가 매번 발생하지 않게”입니다.

7) 해결 전략 E: GPU 스케줄링 실패로 인한 503 제거

트래픽은 들어오는데 Pod가 안 뜨면, Knative는 라우팅을 시도하다가 503을 반환할 수 있습니다. GPU에서 흔한 원인은 다음입니다.

  • nvidia.com/gpu 리소스 부족
  • GPU 노드에 taint 가 있는데 toleration이 없음
  • nodeSelector 또는 affinity가 너무 빡빡함
  • 디바이스 플러그인 미설치 또는 드라이버 불일치

7-1. GPU 노드에만 스케줄되게 하되, 과도하게 제한하지 않기

spec:
  predictor:
    podSpec:
      nodeSelector:
        nvidia.com/gpu.present: "true"
      tolerations:
        - key: "nvidia.com/gpu"
          operator: "Exists"
          effect: "NoSchedule"

환경마다 라벨과 테인트 키는 다릅니다. 중요한 건 “GPU 노드로 가야 한다”와 “GPU 노드에 들어갈 수 있다”를 동시에 만족시키는 것입니다.

7-2. Pending 원인 한 방에 보기

kubectl describe pod <pod-name> -n ml | sed -n '/Events:/,$p'

Events에 0/.. nodes are available 라고 나오면, 그 문장에 답이 들어 있습니다.

8) 해결 전략 F: 타임아웃을 콜드스타트에 맞추기

콜드스타트가 60초인데, 상위 프록시 타임아웃이 30초면 사용자는 503 또는 504를 받습니다. 아래 타임아웃을 함께 봐야 합니다.

  • Ingress 또는 ALB idle timeout
  • Istio/Nginx proxy timeout
  • Knative request timeout
  • 클라이언트 타임아웃

특히 “첫 요청만 길다”면, 클라이언트 재시도 정책이 체감 안정성을 크게 올립니다. 백오프/재시도 설계는 OpenAI 429/Rate Limit 재시도·백오프 실전 가이드 의 패턴을 그대로 응용할 수 있습니다.

예를 들어, 첫 요청 실패 시 지수 백오프로 2~3회 재시도하면, 스케일업 완료 후 정상 응답으로 전환되는 경우가 많습니다.

9) 운영에서 자주 쓰는 “안정화 조합” 예시

아래는 GPU 추론에서 현실적으로 효과가 좋았던 조합입니다.

  • minReplicas: 1 로 0에서 올라오는 구간 제거
  • containerConcurrency: 1 로 과도한 동시성 방지
  • startupProbe 를 충분히 크게
  • readiness는 “모델 로딩 + 워밍업 완료” 기준
  • 스케일 다운 지연으로 플랩핑 방지
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
  name: vision-gpu
  namespace: ml
  annotations:
    autoscaling.knative.dev/scale-down-delay: 10m
spec:
  predictor:
    minReplicas: 1
    maxReplicas: 2
    containerConcurrency: 1
    containers:
      - name: kserve-container
        image: <your-image>
        resources:
          limits:
            nvidia.com/gpu: "1"
            cpu: "4"
            memory: 16Gi
        startupProbe:
          httpGet:
            path: /healthz
            port: 8080
          failureThreshold: 180
          periodSeconds: 2
        readinessProbe:
          httpGet:
            path: /readyz
            port: 8080
          periodSeconds: 2
          failureThreshold: 3

10) 체크리스트: 503·콜드스타트 원인별 처방

10-1. 배포 직후 503이 난다

  • readiness가 너무 빨리 성공하는지 확인
  • startupProbe 추가 또는 확대
  • 모델 다운로드/로딩 중에는 readiness 실패하도록 구현

10-2. 트래픽 스파이크 때만 503이 난다

  • maxReplicas 가 GPU 노드 수보다 큰지 확인
  • containerConcurrency 가 과도한지 확인
  • 스케일업이 느리면 minReplicas 를 올려 완충

10-3. 간헐적 503과 함께 Pod가 Pending 이다

  • GPU 리소스 부족 또는 테인트/셀렉터 불일치
  • 노드 오토스케일러가 GPU 노드를 제때 늘리는지 확인
  • 이벤트 메시지로 스케줄링 실패 사유 확정

10-4. 첫 요청이 매우 느리지만 실패는 안 한다

  • 모델 워밍업을 startup 단계에서 수행
  • 이미지/모델 캐시 전략 도입
  • 상위 타임아웃을 콜드스타트에 맞게 조정

마무리

KServe GPU 추론에서 503과 콜드스타트는 “한 가지 설정”으로 해결되지 않습니다. 라우팅 레이어, Knative 오토스케일링, 프로브, 이미지와 모델 캐시, GPU 스케줄링이 서로 영향을 주기 때문입니다.

우선순위를 정리하면 다음이 가장 효율적입니다.

  1. minReplicas 로 0에서 1 구간을 제거하거나 최소화
  2. readiness를 “진짜 추론 가능” 기준으로 엄격하게
  3. GPU 스케줄링 실패를 Events로 확정하고 라벨/테인트를 정리
  4. 이미지 Pull 및 모델 다운로드를 캐시로 줄이기

이 4가지만 제대로 잡아도, 운영에서 체감하는 503과 콜드스타트는 대부분 의미 있게 감소합니다.