- Published on
BentoML로 GPU 추론 API 배포·롤백 자동화
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서빙용 추론 API를 운영하다 보면, 모델 변경 한 번이 곧 장애로 이어질 수 있습니다. 특히 GPU 추론은 CUDA/드라이버/라이브러리 조합, 메모리 단편화, 워커 동시성 등 변수가 많아 배포 자동화와 안전한 롤백이 필수입니다.
이 글에서는 BentoML을 중심으로 다음을 한 번에 묶어봅니다.
- GPU 추론 API를 BentoML 서비스로 패키징
- 이미지 빌드 및 레지스트리 푸시 자동화
- Kubernetes(EKS 등)에서 Canary 또는 Rolling 업데이트
- 실패 시 자동 롤백(헬스체크/프로브/배포 전략)
- 운영 시 자주 터지는 이슈(499/502, CrashLoopBackOff, OOM) 예방 포인트
운영 환경이 EKS라면 권한/IRSA 설정도 함께 고려해야 합니다. 관련해서는 EKS IRSA AccessDenied 12분 해결 체크리스트도 같이 보면 배포 파이프라인에서 막히는 구간을 빠르게 정리할 수 있습니다.
BentoML로 GPU 추론 API 패키징하기
BentoML의 강점은 모델 아티팩트 + 런타임 의존성 + 서버 엔트리포인트를 한 덩어리로 묶어 “배포 가능한 단위”로 만드는 데 있습니다. GPU 추론에서는 특히 다음이 중요합니다.
- 런타임 의존성 고정:
torch,transformers,onnxruntime-gpu등 버전 고정 - 모델 로딩 비용 최소화: 프로세스 시작 시 1회 로드, 요청당 재로딩 금지
- 워커/배치/동시성 제어: GPU 과부하 방지
예시: PyTorch 기반 텍스트 분류(또는 임베딩) 서비스
아래 예시는 단순화를 위해 transformers 파이프라인 형태로 작성합니다.
service.py
import bentoml
from bentoml.io import JSON
import torch
from transformers import AutoTokenizer, AutoModelForSequenceClassification
MODEL_ID = "distilbert-base-uncased-finetuned-sst-2-english"
@bentoml.service(
name="gpu-infer-api",
traffic={
# 운영에서는 timeout을 충분히 주고, 프록시/Ingress 타임아웃과 맞추는 게 핵심
"timeout": 30,
"concurrency": 16,
},
)
class GPUInferService:
def __init__(self):
self.device = "cuda" if torch.cuda.is_available() else "cpu"
self.tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
self.model = AutoModelForSequenceClassification.from_pretrained(MODEL_ID)
self.model.to(self.device)
self.model.eval()
@bentoml.api(input=JSON(), output=JSON())
def predict(self, payload: dict) -> dict:
text = payload.get("text", "")
if not text:
return {"ok": False, "error": "text is required"}
inputs = self.tokenizer(text, return_tensors="pt", truncation=True)
inputs = {k: v.to(self.device) for k, v in inputs.items()}
with torch.inference_mode():
logits = self.model(**inputs).logits
probs = torch.softmax(logits, dim=-1)[0].detach().cpu().tolist()
return {"ok": True, "probs": probs, "device": self.device}
핵심 포인트는 __init__에서 모델을 로드하고 inference_mode()로 감싸는 것입니다. 운영에서 가장 흔한 실수는 요청마다 모델을 로드하거나, CPU 텐서를 GPU로 올리는 비용이 폭증하는 구조를 만드는 것입니다.
의존성 고정: bentofile.yaml
bentofile.yaml
service: "service:GPUInferService"
python:
packages:
- bentoml==1.2.19
- torch==2.2.2
- transformers==4.41.2
- accelerate==0.31.0
# 컨테이너 레벨에서 CUDA 런타임을 쓰는 경우, base image 전략을 명확히 해야 함
# (예: nvidia/cuda 기반 이미지 사용)
GPU 서빙은 “파이썬 패키지 버전”뿐 아니라 “CUDA 런타임”도 결합됩니다. 이미지 빌드 시 nvidia/cuda 런타임을 기반으로 잡고, 클러스터 노드 드라이버와 호환되는 조합을 선택하세요.
Bento 빌드 및 컨테이너 이미지 생성
BentoML의 일반적인 흐름은 다음과 같습니다.
- Bento 빌드: 모델/서비스를 Bento 아티팩트로 생성
- 컨테이너 이미지 빌드
- 레지스트리에 푸시
Bento 빌드
bentoml build
bentoml list
컨테이너 이미지 빌드
BentoML은 이미지 빌드 커맨드를 제공합니다. GPU 런타임을 위해서는 베이스 이미지/런타임 설정을 조정해야 합니다.
bentoml containerize gpu-infer-api:latest -t registry.example.com/gpu-infer-api:1.0.0
실무에서는 “이미지 태그 전략”이 롤백 품질을 좌우합니다.
- 배포용 불변 태그:
1.0.0,2026-02-26-abc1234 - 환경용 가변 태그는 지양:
latest는 롤백 시점 추적이 어려움
Kubernetes에서 GPU 추론 배포하기
GPU 파드는 보통 다음이 필요합니다.
nvidia.com/gpu리소스 요청/제한- 노드 셀렉터 또는 노드풀 분리
- 프로브 설정(Startup/Readiness/Liveness)
- 롤아웃 전략(rolling, canary)
Deployment 예시
아래 YAML에는 nvidia.com/gpu: 1 요청과 프로브를 포함했습니다. 본문에 부등호가 들어가지 않도록 모든 비교 표현은 코드블록에만 넣었습니다.
deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: gpu-infer-api
spec:
replicas: 2
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: gpu-infer-api
template:
metadata:
labels:
app: gpu-infer-api
spec:
terminationGracePeriodSeconds: 60
containers:
- name: api
image: registry.example.com/gpu-infer-api:1.0.0
ports:
- containerPort: 3000
resources:
limits:
nvidia.com/gpu: 1
cpu: "2"
memory: "8Gi"
requests:
nvidia.com/gpu: 1
cpu: "1"
memory: "6Gi"
readinessProbe:
httpGet:
path: /readyz
port: 3000
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 6
livenessProbe:
httpGet:
path: /healthz
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
startupProbe:
httpGet:
path: /healthz
port: 3000
failureThreshold: 60
periodSeconds: 2
프로브 설계 팁
startupProbe를 두지 않으면, 모델 로딩이 길 때livenessProbe가 먼저 실패해 재시작 루프에 빠질 수 있습니다.- GPU 모델 로딩은 수십 초가 흔합니다. 이때는
startupProbe로 “초기 기동 시간”을 별도로 확보하세요.
CrashLoopBackOff가 발생하면 원인을 빠르게 좁히는 체크리스트가 필요합니다. K8s CrashLoopBackOff 원인별 진단 체크리스트는 GPU 파드에서도 그대로 유효합니다.
롤백 자동화의 핵심: 배포 전략 + 관측 + 게이트
“자동 롤백”은 단순히 kubectl rollout undo를 스크립트로 실행하는 문제가 아닙니다. 다음 3요소가 결합돼야 안정적으로 동작합니다.
- 배포 전략: 새 버전이 트래픽을 받기 전에 검증할 구간 확보
- 관측 지표: 실패를 감지할 신호 정의(에러율, 지연, GPU OOM)
- 게이트: 실패 신호가 감지되면 자동으로 중단/롤백
1) Kubernetes 기본 롤백(가장 현실적인 1차 방어선)
Kubernetes Deployment는 ReplicaSet 히스토리를 기반으로 롤백이 가능합니다.
kubectl rollout status deployment/gpu-infer-api
kubectl rollout history deployment/gpu-infer-api
kubectl rollout undo deployment/gpu-infer-api --to-revision=3
운영 자동화에서는 “배포 후 상태 확인”을 파이프라인에 포함하고, 타임아웃 내 실패하면 즉시 undo를 수행하도록 만듭니다.
2) GitHub Actions로 배포·검증·실패 시 롤백
아래는 개념 예시입니다.
- 이미지 빌드/푸시
kubectl set image로 업데이트rollout status가 실패하면rollout undo
.github/workflows/deploy.yaml
name: deploy-gpu-infer
on:
push:
branches: ["main"]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build and push image
run: |
docker build -t registry.example.com/gpu-infer-api:${GITHUB_SHA} .
docker push registry.example.com/gpu-infer-api:${GITHUB_SHA}
- name: Set kubeconfig
run: |
mkdir -p ~/.kube
echo "${KUBECONFIG_B64}" | base64 -d > ~/.kube/config
env:
KUBECONFIG_B64: ${{ secrets.KUBECONFIG_B64 }}
- name: Deploy
run: |
kubectl set image deployment/gpu-infer-api api=registry.example.com/gpu-infer-api:${GITHUB_SHA}
- name: Wait rollout
run: |
set -e
if ! kubectl rollout status deployment/gpu-infer-api --timeout=300s; then
echo "rollout failed, rollback"
kubectl rollout undo deployment/gpu-infer-api
exit 1
fi
이 방식은 단순하지만 효과적입니다. 다만 “롤아웃 성공”이 “서비스 정상”을 의미하지는 않습니다. 다음 섹션의 게이트가 필요합니다.
3) 헬스체크만으로 부족한 이유: 데이터 플레인 오류(499/502)와 스트리밍
GPU 추론 API는 종종 다음과 같은 형태로 실패합니다.
- 파드는 살아있지만, 프록시 타임아웃/버퍼링으로 499/502 급증
- 응답이 길어지는 구간에서 커넥션이 끊김
- 워커 동시성 과다로 큐잉 지연이 폭증
이런 문제는 readinessProbe만으로는 감지되지 않습니다. Ingress/Nginx/Envoy 레벨 튜닝과 함께, 배포 게이트에 “실제 요청 기반 SLI”를 넣는 것이 안전합니다. 스트리밍/긴 응답에서 499/502가 늘어나는 패턴은 LLM SSE 스트리밍 499 502 급증과 응답 끊김을 잡는 프록시 튜닝 체크리스트에 정리된 항목을 그대로 적용할 수 있습니다.
Canary 배포로 롤백 비용 줄이기(Argo Rollouts 권장)
RollingUpdate는 간단하지만, 새 버전이 전체 트래픽을 빠르게 받는 구조가 될 수 있습니다. GPU 추론은 장애 시 비용이 크므로 Canary가 유리합니다.
Argo Rollouts를 쓰면 다음이 쉬워집니다.
- 5% 트래픽만 새 버전으로 라우팅
- 에러율/지연이 기준을 넘으면 자동 중단 및 롤백
- 일정 시간 안정적이면 점진적으로 25%/50%/100% 확대
개념 YAML 예시는 다음과 같습니다.
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: gpu-infer-api
spec:
replicas: 2
selector:
matchLabels:
app: gpu-infer-api
template:
metadata:
labels:
app: gpu-infer-api
spec:
containers:
- name: api
image: registry.example.com/gpu-infer-api:1.0.0
resources:
limits:
nvidia.com/gpu: 1
strategy:
canary:
steps:
- setWeight: 5
- pause: { duration: 60 }
- setWeight: 25
- pause: { duration: 120 }
- setWeight: 50
- pause: { duration: 180 }
실제로는 Analysis 템플릿을 붙여 Prometheus 지표(예: 5xx 비율, p95 latency)를 기준으로 자동 중단을 구성합니다.
GPU 추론 운영에서 자주 망가지는 지점과 예방책
1) OOM과 단편화: 배치/동시성/모델 로딩 패턴
- 워커 수를 늘리면 처리량이 늘 것 같지만, GPU 메모리는 공유되지 않습니다. 프로세스가 늘면 오히려 OOM이 빨라집니다.
- 요청 배치를 켜면 throughput은 늘지만 tail latency가 증가합니다.
BentoML 레벨에서 동시성을 과하게 잡지 말고, 실제 GPU 메모리 사용량 기준으로 튜닝하세요.
2) CrashLoopBackOff: 프로브/시작 시간/환경변수
- 모델 다운로드가 시작 시 발생하면 네트워크 이슈로 기동 실패가 반복됩니다.
- 해결책: 이미지 빌드 단계에서 모델을 포함하거나, initContainer로 사전 다운로드 후 메인 컨테이너는 로컬에서만 로딩.
3) 성능 회귀를 배포 게이트로 잡기
“정상 동작”과 “운영 가능”은 다릅니다.
- p95 지연이 기준을 넘으면 사실상 장애
- GPU utilization이 비정상적으로 낮으면 CPU 병목/토크나이징 병목 가능
배포 파이프라인에서 다음을 자동화하면 롤백이 훨씬 빨라집니다.
- 배포 직후 synthetic 트래픽 1~3분 주입
p95 latency,5xx rate,OOM kill이벤트를 기준으로 fail
롤백을 빠르게 만드는 아티팩트 전략
모델 서버 롤백이 느린 가장 큰 이유는 “이전 버전이 이미 사라졌거나 재현이 안 되는 것”입니다.
- 컨테이너 이미지: 불변 태그로 보관
- Bento 아티팩트: 빌드 결과를 저장(아티팩트 스토리지)
- 설정: ConfigMap/Secret 버전 관리
추가로, 모델 자체의 양자화나 런타임 변경(예: FP16, INT8)은 성능/정확도 변동이 크므로 배포 단위로 명확히 분리하세요. ONNX 기반 최적화까지 고려한다면 파이썬 ONNX Runtime로 INT8 양자화 실전 가이드를 참고해 “성능 개선 변경”을 “기능 변경”과 분리해 릴리스하는 전략이 안전합니다.
체크리스트: BentoML GPU 추론 배포·롤백 자동화
- 서비스
- 모델은 프로세스 시작 시 1회 로드
inference_mode()적용- 동시성/타임아웃은 프록시/Ingress와 일치
- 이미지
- CUDA 런타임/드라이버 호환성 확인
- 불변 태그 사용(커밋 SHA 기반)
- K8s
nvidia.com/gpu요청/제한startupProbe로 초기 로딩 시간 확보- RollingUpdate는
maxUnavailable: 0권장
- 자동 롤백
rollout status실패 시 즉시undo- 가능하면 Canary + 지표 기반 게이트(Argo Rollouts)
- 499/502, 타임아웃, p95 지연을 핵심 신호로 설정
마무리
BentoML은 “모델을 API로 만드는 과정”을 단순화하지만, GPU 추론 운영의 승부처는 결국 배포 전략과 롤백 자동화입니다.
- Kubernetes 기본 롤백만으로도 1차 방어선을 만들 수 있고
- Canary와 지표 기반 게이트를 붙이면 “장애를 내지 않는 배포”에 가까워집니다.
다음 단계로는 Prometheus 지표 설계(에러율/지연/GPU 메모리), 트래픽 리플레이 기반 회귀 테스트, 그리고 모델 변경과 런타임 변경을 분리한 릴리스 전략까지 확장해보면 운영 안정성이 체감될 정도로 올라갑니다.