Fly.io GPU 종료 후 AI 추론 서버 이전하기 — Modal·RunPod·Google Cloud Run 비용 비교·콜드스타트 벤치마크 포함
Fly.io가 GPU 서비스를 접겠다고 선언했을 때, 솔직히 저도 "설마?" 싶었습니다. 밀리초 단위 컨테이너 시작으로 유명한 Fly.io가 스스로 한계를 인정하는 공식 블로그 포스트("We Were Wrong About GPUs")를 냈고, 2026년 7월 31일을 기점으로 GPU 서비스가 완전히 종료됩니다. 지금 이 순간에도 Fly.io 위에서 LLM 추론 서버를 돌리고 있다면, 7월 31일 이후 어떤 요청이 갑자기 실패하기 시작할지 모르는 상황입니다. 더 이상 미룰 수 없는 이야기입니다.
이 글에서는 Modal, RunPod, Google Cloud Run 세 플랫폼을 실제로 써보면서 느낀 차이를 다음 내용과 함께 풀어드립니다:
- 플랫폼별 H100 기준 비용 및 초 단위 과금 비교
- 실측 콜드스타트 데이터(Beam Cloud 2025 벤치마크 기반)
- 바로 복붙 가능한 배포 코드(vLLM serverless migration 기준)
- 워크로드별 권장 플랫폼과 공통 마이그레이션 체크리스트
플랫폼마다 콜드스타트·비용·개발자 경험이 크게 달라, 본인 워크로드 특성에 맞는 선택이 핵심입니다. 이전 계획을 바로 세울 수 있도록 처음부터 끝까지 구체적으로 다뤄보겠습니다.
핵심 개념
AI 추론 마이그레이션 배경과 주요 지표
마이그레이션을 준비할 때 "GPU 스펙이 좋다" 정도만 보다가 나중에 요금 폭탄을 맞는 경우가 실무에서 꽤 있습니다. 처음부터 아래 세 가지 지표를 기준으로 비교해두면 훨씬 합리적인 판단이 가능합니다.
| 지표 | 설명 | 왜 중요한가 |
|---|---|---|
| 콜드스타트(Cold Start) | 유휴 상태에서 첫 요청까지 GPU 컨테이너가 준비되는 시간 | 응답 지연 직결, 사용자 경험에 영향 |
| GPU 시간당 비용 | 온디맨드 vs. 초 단위 사용량 기반 과금 | 가변 트래픽에서 총비용 결정 |
| 스케일-투-제로 | 트래픽 없을 때 비용이 0이 되는 능력 | 저트래픽 구간 비용 절감 |
스케일-투-제로(Scale-to-Zero): 요청이 없는 구간에는 컨테이너를 완전히 내려 과금을 멈추는 방식. 24시간 트래픽이 고르지 않은 서비스라면 비용을 크게 아낄 수 있습니다.
Fly.io의 핵심 가치 제안은 "밀리초 단위 머신 시작"이었는데, GPU 워크로드는 태생적으로 이 철학과 맞지 않습니다. CUDA 드라이버 초기화, 모델 가중치 로딩 등 GPU 준비 과정 자체가 수 초에서 수십 초를 요구하기 때문입니다. Fly.io도 이 점을 솔직하게 인정했고, 그 결과가 서비스 종료 결정입니다. 저는 오히려 이 결정을 긍정적으로 봤습니다. GPU 인프라를 범용 플랫폼에 욱여넣는 대신, GPU에 특화된 관리형 서버리스 플랫폼이 그 자리를 채우는 게 사용자 입장에서도 낫기 때문입니다.
세 플랫폼 모두 vLLM 기반 배포를 공식 지원합니다. vLLM은 PagedAttention이라는 기술로 GPU 메모리를 효율적으로 관리해 처리량을 크게 높인 덕분에, 현재 LLM 서빙 엔진 중 가장 널리 채택된 엔진 중 하나로 자리잡았습니다(SGLang, TGI 같은 경쟁 엔진도 활발히 사용됩니다).
vLLM: GPU 메모리를 페이지 단위로 동적 할당해 배치 처리 효율을 극대화하는 LLM 서빙 엔진. OpenAI 호환 엔드포인트를 기본으로 제공한다는 점이 실용적인데, 이는 기존 OpenAI SDK 코드를 거의 수정 없이 그대로 쓸 수 있다는 뜻입니다.
실전 적용
예시 1: Modal — Python 코드만으로 vLLM 서버 올리기
저도 처음엔 "YAML도 없이 Python만으로 GPU 인프라를 정의한다고?" 반신반의했는데, 실제로 써보니 설정 파일 없이 코드 한 파일로 전부 끝난다는 게 체감됩니다. ML 팀에서 인프라 엔지니어 없이 모델 서빙을 직접 해야 할 때 특히 강점을 발휘합니다.
@app.cls 데코레이터는 Python 클래스 자체를 GPU 컨테이너 하나로 추상화합니다. 클래스를 선언하는 것만으로 "이 클래스의 인스턴스 = GPU 컨테이너 하나"가 됩니다.
import modal
app = modal.App("vllm-inference")
# GPU 컨테이너 이미지 정의 — pip_install이 레이어 캐시됩니다
image = (
modal.Image.debian_slim()
.pip_install("vllm", "huggingface_hub")
)
# 모델 가중치를 저장할 Modal Volume — 콜드스타트 단축 핵심
volume = modal.Volume.from_name("model-weights", create_if_missing=True)
WEIGHTS_PATH = "/vol/models"
@app.cls(
gpu="H100",
image=image,
container_idle_timeout=300, # 5분 유휴 시 자동 종료 → 스케일-투-제로
volumes={WEIGHTS_PATH: volume},
)
class Model:
@modal.enter()
def load_model(self):
from vllm import LLM
# Volume에 캐시된 가중치를 읽으면 반복 다운로드가 생략됩니다
self.llm = LLM(
model="Qwen/Qwen3-8B-FP8",
download_dir=WEIGHTS_PATH,
)
@modal.method()
def generate(self, prompt: str):
# 프로덕션에서는 OOM, 타임아웃 처리를 추가하는 것을 권장합니다
return self.llm.generate(prompt)modal deploy inference.py| 코드 요소 | 역할 |
|---|---|
@app.cls(gpu="H100") |
H100 GPU 1장 할당 선언 |
container_idle_timeout=300 |
5분 무요청 시 컨테이너 자동 종료 |
volumes={WEIGHTS_PATH: volume} |
Modal Volume 마운트 — 가중치 캐싱 핵심 |
@modal.enter() |
컨테이너 시작 시 1회 실행 — 모델 로딩 |
@modal.method() |
HTTP 엔드포인트로 노출될 메서드 |
modal deploy 한 줄이면 OpenAI 호환 API 엔드포인트가 즉시 생성됩니다. 여기서 핵심은 modal.Volume입니다. 저도 초기에 모델 가중치를 이미지에 직접 포함시켰다가 배포마다 40분 가까이 기다린 적이 있었는데, Volume에 한 번 내려받은 가중치를 캐시해두면 이후 콜드스타트에서 반복 다운로드 시간을 완전히 건너뜁니다.
예시 2: RunPod — 가장 빠르고 저렴하게 시작하기
비용이 최우선 조건이라면 RunPod이 현재 시장에서 H100 기준 가장 저렴한 선택지입니다. RunPod Hub에서 vLLM Worker 템플릿을 고르면 Llama 3, Mistral, Qwen3, DeepSeek-R1, Phi-4 같은 주요 모델을 클릭 한 번으로 서버리스 엔드포인트로 올릴 수 있습니다. Docker 이미지를 직접 빌드하지 않아도 됩니다.
커스텀 이미지가 필요한 경우 RunPod 공식 vLLM 베이스 이미지(runpod/vllm:latest)를 출발점으로 삼는 것을 권장합니다. 다음은 CLI로 배포하는 예시입니다:
# RunPod CLI로 커스텀 Docker 이미지 배포
# 베이스 이미지: runpod/vllm:latest 를 시작점으로 활용하는 것을 권장합니다
runpodctl create endpoint \
--name my-llm \
--image my-org/my-vllm:latest \
--gpu-type H100 \
--workers-min 0 \
--workers-max 10| 옵션 | 설명 |
|---|---|
--workers-min 0 |
스케일-투-제로 활성화 |
--workers-max 10 |
트래픽 급증 시 최대 10 워커까지 확장 |
--gpu-type |
A4000, A100, H100, AMD 등 다양한 선택 가능 |
Beam Cloud의 2025 서버리스 GPU 벤치마크에 따르면, FlashBoot 기술 덕분에 소형 컨테이너의 48%가 200ms 이하에서 콜드스타트를 완료합니다. 전통적인 전용 Pod 대비 중간 트래픽 수준에서 77% 비용 절감이 보고된 사례도 있습니다(출처: RunPod 공식 블로그 2025).
예시 3: Google Cloud Run — GCP 생태계 안에서 그대로 이어가기
이미 GCP를 쓰고 있다면 IAM, Cloud Monitoring, Artifact Registry가 그대로 연결되는 Cloud Run이 가장 자연스러운 선택입니다. NVIDIA NIM 컨테이너를 직접 사용할 수 있어서 Llama, Gemma 같은 주요 모델은 별도 설정 없이 바로 올라옵니다.
Cloud Run은 구글의 Knative 기반으로 동작합니다. Knative는 쿠버네티스 위에 서버리스 추상화를 얹은 프레임워크인데, GPU 드라이버 초기화까지 포함하면 콜드스타트가 다른 플랫폼보다 긴 편입니다. 모델 로딩까지 합산하면 약 19초(gemma3:4b 기준)가 걸리므로, startupProbe를 설정해 트래픽이 너무 일찍 들어오는 상황을 막아두는 것이 중요합니다.
# cloud-run-gpu.yaml
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: llm-inference
spec:
template:
metadata:
annotations:
run.googleapis.com/execution-environment: gen2
spec:
containers:
- image: nvcr.io/nim/meta/llama-3.1-8b-instruct:latest
resources:
limits:
nvidia.com/gpu: "1"
memory: 32Gi
startupProbe:
httpGet:
path: /v1/models
port: 8000
initialDelaySeconds: 20 # 모델 로딩 시간을 감안한 초기 대기
periodSeconds: 5
failureThreshold: 10gcloud run services replace cloud-run-gpu.yaml --region us-central1L4 GPU($0.67/hr)는 가격 대비 성능이 좋아 중간 규모 모델 서빙에 충분합니다. 다만 세 플랫폼 중 총 콜드스타트가 가장 길다는 점은 감안이 필요합니다. 콜드스타트가 민감한 서비스라면 최소 인스턴스를 1개 유지하는 설정을 함께 쓰는 것을 권장합니다.
공통 마이그레이션 체크리스트
Fly.io에서 어느 플랫폼으로 옮기든 공통으로 챙겨야 할 다섯 가지입니다.
- 모델 가중치 저장소 이전 (시간이 가장 오래 걸립니다): Fly Volumes → Modal Volume / RunPod Network Volume / GCS. 가중치 크기에 따라 이전에만 수 시간이 걸릴 수 있으니 가장 먼저 시작하는 것이 좋습니다.
- Docker 이미지 재빌드:
fly.toml전용 설정 제거, 표준 Dockerfile로 전환 - 환경 변수 및 시크릿 이전: Fly secrets → 각 플랫폼 시크릿 관리자
- 엔드포인트 URL 교체: 클라이언트 코드의 base URL 업데이트
- 스케일링 정책 재설정: min/max worker 수, idle timeout 재정의
장단점 분석
플랫폼별 비용 한눈에 보기
| 플랫폼 | GPU | 시간당 비용 | 과금 단위 | 스케일-투-제로 |
|---|---|---|---|---|
| RunPod | H100 SXM | $1.99/hr | 초 단위 | 지원 |
| Modal | H100 | $3.95/hr | 초 단위 | 지원 |
| Google Cloud Run | L4 (24GB) | ~$0.67/hr | 초 단위 | 지원 |
| Google Cloud Run | A100 40GB | ~$3.67/hr | 초 단위 | 지원 |
콜드스타트 비교
| 플랫폼 | 콜드스타트 | 기술 배경 |
|---|---|---|
| RunPod | 소형 200ms 미만(48%), 대형 6~12초 | FlashBoot |
| Modal | 일관되게 2~4초 (Beam Cloud 2025 벤치마크, Qwen3-8B 기준) | gVisor 경량 VM + 컨테이너 풀링 |
| Google Cloud Run | GPU 드라이버 5초 미만, 모델 로딩 포함 ~19초 | Knative 기반, gVisor 샌드박스 |
컨테이너 풀링(Container Pooling): Modal이 사용하는 기법으로, 미리 워밍업된 컨테이너 풀을 유지해 요청이 들어오면 즉시 할당합니다. 콜드스타트가 "일관되게" 2~4초로 나오는 이유가 여기에 있습니다. gVisor는 경량 커널 격리 레이어로, 보안을 유지하면서 일반 VM보다 훨씬 빠르게 컨테이너를 띄울 수 있게 해줍니다.
장점
| 플랫폼 | 강점 |
|---|---|
| Modal | Python-native API, 안정적인 콜드스타트, 우수한 개발자 경험(DX), 자동 스케일링 |
| RunPod | 최저 GPU 가격($1.99/hr), 가장 빠른 콜드스타트(FlashBoot), 다양한 GPU 선택(AMD 포함) |
| Google Cloud Run | GCP 생태계 완전 통합(IAM·Monitoring·Artifact Registry), 기업급 SLA, L4 가격 경쟁력 |
단점 및 주의사항
| 플랫폼 | 단점 | 대응 방안 |
|---|---|---|
| Modal | H100 기준 RunPod 대비 약 2배 비용 | GPU 사용률 50% 미만 가변 워크로드에서는 초 단위 과금으로 총비용이 오히려 낮아질 수 있어서, 반드시 실제 패턴으로 계산해보는 것이 좋습니다 |
| RunPod | 대형 커스텀 이미지 콜드스타트 6~12초, 대형 클라우드 대비 안정성 불확실 | 저지연 필수 서비스는 항상 켜두는 Pod 모드로 운영하는 것을 권장합니다 |
| Google Cloud Run | 총 콜드스타트 가장 길고(~19초), 고성능 GPU 옵션 제한적 | 모델을 GCS에 미리 올려두고 시작 스크립트에서 빠르게 마운트, 최소 인스턴스 1개 유지 설정을 함께 활용하면 도움이 됩니다 |
워크로드별 권장 플랫폼
| 상황 | 권장 플랫폼 | 이유 |
|---|---|---|
| Python ML 팀, 빠른 프로토타이핑 | Modal | YAML 없는 코드 중심 배포, 안정적 DX |
| 비용 최우선, 가변 트래픽 | RunPod Serverless | 최저 GPU 가격 + FlashBoot |
| GCP 기존 인프라 활용 | Google Cloud Run | 생태계 통합, 재설정 최소화 |
| 저지연 필수 프로덕션 | RunPod Pod (항상 켜진 상태) | 콜드스타트 없는 전용 인스턴스 |
실무에서 가장 흔한 실수
-
모델 가중치를 Docker 이미지에 포함시키는 것: 이미지 크기가 수십 GB로 불어나 빌드·배포 시간이 폭발적으로 늘어납니다. 저도 초기에 이미지에 가중치를 넣었다가 배포에 40분이 걸린 적이 있었습니다. 가중치는 반드시 외부 스토리지(Modal Volume, RunPod Network Volume, GCS)에 두고 런타임에 마운트하는 방식을 권장합니다.
-
idle timeout을 너무 짧게 설정하는 것: 스케일-투-제로는 비용을 아끼지만, 콜드스타트 빈도가 높아지면 사용자 경험이 나빠집니다. 트래픽 패턴을 먼저 분석한 뒤 idle timeout을 설정하는 게 훨씬 안전해요.
-
단일 지표(시간당 비용)만 보고 결정하는 것: H100 시간당 비용만 보면 RunPod이 압도적이지만, 실제 GPU 사용률이 낮은 가변 트래픽에서는 Modal의 초 단위 과금이 총비용을 낮추는 경우가 있습니다. 반드시 실제 사용 패턴 기반으로 계산해보는 것을 권장합니다.
마치며
Fly.io GPU 종료는 GPU 전문 플랫폼을 선택해야 하는 이유가 다시 한번 확인된 사건입니다. Modal·RunPod·Cloud Run 모두 Fly.io보다 GPU 추론 워크로드에 훨씬 잘 맞는 플랫폼들이고, 마이그레이션을 계기로 더 나은 인프라로 갈아탈 기회이기도 합니다.
지금 바로 시작해볼 수 있는 3단계:
- 모델 가중치 이전부터 시작하는 것을 권장합니다. 가중치 크기에 따라 이 단계가 가장 오래 걸립니다. 일평균 GPU 사용 시간과 트래픽 편차를 확인하고, 위의 워크로드별 권장 플랫폼 표와 대조해 목적지를 먼저 정해두는 것이 좋습니다.
- 공통 마이그레이션 체크리스트 순서(모델 가중치 이전 → Dockerfile 정리 → 시크릿 이전 → URL 교체 → 스케일링 재설정)로 진행해볼 수 있습니다. Modal이라면 위의 Python 코드 예시를, RunPod이라면
runpodctl create endpoint명령어를, Cloud Run이라면 위의 YAML을 그대로 시작점으로 활용할 수 있습니다. - 2026년 7월 31일 전에 충분한 여유를 두고 스테이징 환경에서 먼저 검증해보는 것을 권장합니다. 콜드스타트 실측값과 요금 시뮬레이션을 직접 확인한 뒤 프로덕션으로 전환하는 순서가 훨씬 안전합니다.
본인 워크로드에 맞는 플랫폼을 고르는 게 고민된다면 댓글로 상황을 남겨주세요. 같이 생각해볼 수 있습니다.
다음 글: vLLM + Modal Volume으로 콜드스타트를 1초 이하로 줄이는 모델 가중치 캐싱 전략 심층 가이드
참고 자료
- We Were Wrong About GPUs | Fly.io 공식 블로그
- Fly.io GPU 마이그레이션 커뮤니티 공지 (종료일: 2026-07-31)
- Modal 공식 vLLM 배포 가이드
- Modal — vLLM 배포 방법 블로그
- RunPod Serverless LLM 2025 업데이트
- RunPod vLLM 시작 가이드
- Google Cloud Run GPU 공식 출시 블로그
- NVIDIA: Google Cloud Run L4 GPU 지원 발표
- 서버리스 GPU 플랫폼 콜드스타트 벤치마크 2025 | Beam Cloud
- Google Cloud Run GPU 가격 공식 문서
- Modal NVIDIA L4 가격 분석