Cloud Run에 LLM 앱 배포하기 — 요청 없으면 0원인 서버리스 백엔드
앱이나 웹에서 Claude 같은 LLM API를 쓰려면 API 키를 클라이언트에 심을 수 없으니 중간에 서버가 하나 필요하다. 문제는 이 프록시 서버 하나 때문에 상시 가동 VM을 돌리자니 비용이 아깝다는 것이다. Cloud Run은 이 상황에 거의 정확히 맞는 답이다. 컨테이너를 올려 두면 요청이 올 때만 인스턴스가 기동되고, 요청이 없으면 0개로 줄어 비용도 0원이 된다. LLM 백엔드를 Cloud Run에 배포하면서 밟은 순서 그대로 정리한다.
왜 Cloud Run인가 — 요청 없을 때 0원
Cloud Run의 기본 과금 방식은 요청을 처리하는 동안의 CPU·메모리 사용 시간과 요청 수 기준이다. 트래픽이 없으면 인스턴스가 0개까지 줄어들기 때문에(scale to zero) 개인 프로젝트나 초기 서비스처럼 트래픽이 들쭉날쭉한 워크로드에서 유리하다. 여기에 HTTPS 엔드포인트가 기본으로 제공되고, Dockerfile만 있으면 어떤 언어·프레임워크든 그대로 올릴 수 있다. LLM 프록시 서버는 대부분의 시간을 모델 응답 대기(I/O)에 쓰는 가벼운 워크로드라 서버리스와 궁합이 좋다.
컨테이너 준비 — FastAPI 프록시와 Dockerfile
예제는 Claude API를 대신 호출해 주는 최소한의 FastAPI 앱이다. main.py부터 만든다.
import anthropic
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
client = anthropic.Anthropic() # ANTHROPIC_API_KEY 환경변수를 자동 사용
class ChatRequest(BaseModel):
message: str
@app.post("/chat")
def chat(req: ChatRequest):
response = client.messages.create(
model="claude-haiku-4-5",
max_tokens=512,
messages=[{"role": "user", "content": req.message}],
)
return {"reply": response.content[0].text}
requirements.txt에는 fastapi, uvicorn, anthropic 세 줄을 넣는다. Dockerfile은 다음과 같다.
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"]
포트가 핵심이다. Cloud Run은 컨테이너가 PORT 환경변수(기본값 8080)로 들어오는 HTTP 요청을 받길 기대한다. 8080을 리슨하지 않으면 배포가 실패하거나 헬스체크에서 걸린다.
배포 명령과 환경변수·시크릿
배포는 명령 한 줄이다. --source .를 쓰면 로컬 소스를 올려 Cloud Build가 컨테이너 빌드까지 대신해 준다.
gcloud run deploy llm-backend \
--source . \
--region asia-northeast3 \
--allow-unauthenticated
리전은 사용자와 가까운 곳을 고른다. 국내 사용자 대상이라면 서울 리전(asia-northeast3)이 무난하다. 처음 배포하면 Cloud Build·Artifact Registry 등 필요한 API를 활성화하겠냐는 확인이 나오는데 그대로 진행하면 된다. 빌드가 끝나기까지 몇 분 정도 걸린다.
배포가 끝나면 https://...run.app 형태의 URL이 발급된다. 이제 API 키를 넣을 차례인데, 키를 --set-env-vars로 평문 저장하는 것은 피하고 Secret Manager를 쓴다.
printf '%s' 'your-api-key' | gcloud secrets create anthropic-key --data-file=-
gcloud run services update llm-backend \
--region asia-northeast3 \
--set-secrets=ANTHROPIC_API_KEY=anthropic-key:latest
서비스가 쓰는 서비스 계정에 Secret Manager 보안 비밀 접근자(roles/secretmanager.secretAccessor) 역할이 있어야 시크릿을 읽을 수 있다. 권한 오류가 나면 이것부터 확인한다. 그리고 --allow-unauthenticated는 말 그대로 전 세계에 열리는 옵션이므로, 실서비스라면 자체 토큰 검증이나 IAM 인증을 붙여 아무나 내 API 키로 LLM을 쓰지 못하게 막아야 한다.
콜드스타트와 동시성 설정
인스턴스가 0개인 상태에서 첫 요청이 오면 컨테이너가 새로 뜨는 시간, 즉 콜드스타트가 생긴다. 가벼운 Python 앱이면 수 초 안쪽이지만 첫 사용자 경험에는 영향을 준다. LLM 백엔드에서 실제로 만지게 되는 설정은 세 가지다.
- 동시성(concurrency): 인스턴스 하나가 동시에 받는 요청 수로 기본값은 80이다. LLM 프록시는 응답 대기가 대부분이라 동시성을 높게 유지해도 CPU가 놀기 때문에 기본값 언저리로 두는 것이 비용에 유리하다.
- 타임아웃: 긴 프롬프트는 응답에 수십 초가 걸릴 수 있다. 기본 타임아웃이 짧다고 느껴지면
--timeout=300처럼 늘린다. 스트리밍 응답을 구현하면 체감 대기도 줄어든다. - 최소 인스턴스:
--min-instances=1로 두면 콜드스타트가 사실상 사라지지만, 유휴 시간에도 인스턴스 유지 비용이 발생해 "요청 없으면 0원"이라는 장점과 맞바꾸게 된다.
gcloud run services update llm-backend \
--region asia-northeast3 \
--concurrency 80 --timeout 300 --memory 512Mi
앱에서 붙일 때는 발급된 URL의 /chat 엔드포인트로 JSON을 보내면 끝이다. 클라이언트 입장에서는 그냥 HTTP API 하나가 생긴 것이라 Flutter든 웹이든 연동 방식이 다르지 않다. 응답이 느리게 느껴진다면 서버 쪽에서 모델을 더 빠른 등급으로 바꾸거나 스트리밍으로 전환하는 식으로 백엔드에서만 손보면 되고, 앱을 다시 배포할 필요가 없다는 점도 프록시 구조의 장점이다.
비용 관리와 무료 한도
공식 문서 기준 Cloud Run 무료 한도는 매월 요청 200만 건, CPU 18만 vCPU초, 메모리 36만 GiB초다. 초과분 단가는 Tier 1 리전 기준 요청 100만 건당 0.40달러, vCPU초당 0.000024달러, GiB초당 0.0000025달러 수준이다. 개인 프로젝트의 프록시 서버 트래픽이라면 대부분 무료 한도 안에서 끝난다.
주의할 비용 포인트는 두 가지다. 첫째, 최소 인스턴스를 1 이상으로 두면 유휴 시간에도 과금이 이어지므로 정말 필요할 때만 켠다. 둘째, Cloud Run 요금이 0원이어도 앱이 호출하는 LLM API의 토큰 비용은 별개로 나간다. 전체 비용을 볼 때는 Cloud Run 요금과 모델 사용료를 함께 봐야 한다.
운영 중 상태 확인은 콘솔의 Cloud Run 서비스 화면에서 한다. 요청 수, 지연 시간, 인스턴스 수, 로그를 한 화면에서 볼 수 있어 콜드스타트가 실제로 얼마나 자주 생기는지도 여기서 확인된다. 트래픽이 늘어 인스턴스가 예상보다 많이 뜨면 최대 인스턴스 수(--max-instances)로 상한을 걸어 비용 폭주를 막는 것도 잊지 말자.
정리하면, Dockerfile 하나와 명령 몇 줄로 HTTPS 백엔드가 생기고 트래픽이 없을 땐 비용이 0원에 수렴한다. LLM 앱의 첫 배포처라 치면 Cloud Run보다 부담 없는 선택지를 찾기 어렵다.
함께 보면 좋은 글: 구글 클라우드 API 사용량 실시간 모니터링 방법
※ 이 글의 무료 한도·단가·명령 옵션은 2026년 기준이며 변경될 수 있습니다.
댓글
댓글 쓰기