티스토리 뷰
반응형
핵심 질문
LLM 앱 배포 전략을 어떻게 설계해야 안전하게 변경을 적용할 수 있을까요?
이 글은 그 질문에 답하기 위해 LLM 앱 배포의 핵심 결정과 운영 함정을 살펴봅니다.
이 글에서 답할 질문
- FastAPI 기반 LLM 엔드포인트에서 health check는 어디까지 확인하면 충분할까요?
- 동기 Groq 클라이언트를 async 엔드포인트에서 안전하게 호출하려면 어떻게 감싸야 할까요?
- 로컬 self-test로 서버 기동 여부를 자동 확인하려면 어떤 흐름이 가장 단순할까요?
배포 가능한 예제의 기준은 코드가 예쁘게 보이는지가 아니라, 서버를 띄우고 헬스체크와 실제 채팅 요청을 같은 스크립트에서 검증할 수 있느냐입니다.
큰 그림

self-test가 헬스체크와 채팅 요청을 검증하는 흐름
왜 이 레이어가 필요한가

서버 기동 확인이 헬스체크로 이어지는 흐름
서버가 실제로 뜨고 응답하는지 스스로 증명하는 self-test가 있어야 배포 예제가 완성됩니다.
배포 글에서 가장 흔한 실수는 서버 코드만 보여 주고 실제로 떠 있는지 확인하지 않는 것입니다. 운영 준비가 된 예제라면 최소한 health check와 대표 요청 한 건은 자동으로 검증해야 합니다.
예제 파일: /root/Github/llm-apps-ops-101/ko/05-deployment/main.py
최소 실행 예제
import asyncio
import os
import threading
import time
from contextlib import asynccontextmanager
import httpx
import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel, Field
from groq import Groq
MODEL = "llama-3.1-8b-instant"
class ChatRequest(BaseModel):
message: str = Field(min_length=1, max_length=4000)
class ChatResponse(BaseModel):
response: str
model: str
def call_model(client: Groq, message: str) -> str:
response = client.chat.completions.create(
model=MODEL,
temperature=0,
messages=[
{"role": "system", "content": "You are a concise Python assistant."},
{"role": "user", "content": message},
],
)
return response.choices[0].message.content or ""
@asynccontextmanager
async def lifespan(app: FastAPI):
app.state.client = Groq(api_key=os.environ["GROQ_API_KEY"])
yield
app = FastAPI(title="llm-deployment-demo", lifespan=lifespan)
class ThreadSafeServer(uvicorn.Server):
def install_signal_handlers(self) -> None:
return None
@app.get("/health")
async def health() -> dict:
return {"status": "ok", "model": MODEL}
@app.post("/chat", response_model=ChatResponse)
async def chat(request: ChatRequest) -> ChatResponse:
answer = await asyncio.to_thread(call_model, app.state.client, request.message)
return ChatResponse(response=answer, model=MODEL)
def run_server(server: uvicorn.Server) -> None:
server.run()
def main() -> None:
config = uvicorn.Config(app, host="127.0.0.1", port=8015, log_level="warning")
server = ThreadSafeServer(config)
thread = threading.Thread(target=run_server, args=(server,), daemon=True)
thread.start()
for _ in range(40):
try:
health = httpx.get("http://127.0.0.1:8015/health", timeout=2.0)
if health.status_code == 200:
break
except Exception:
time.sleep(0.25)
else:
raise RuntimeError("server did not start")
print("HEALTH:", health.json())
response = httpx.post(
"http://127.0.0.1:8015/chat",
json={"message": "Explain Python async functions in two sentences."},
timeout=30.0,
)
print("CHAT:", response.json())
server.should_exit = True
thread.join(timeout=10)
if thread.is_alive():
raise RuntimeError("server did not stop cleanly")
if __name__ == "__main__":
main()
이 코드에서 봐야 할 것

비동기 엔드포인트가 동기 모델 호출을 우회하는 흐름
asyncio.to_thread로 동기 Groq 호출을 분리해 FastAPI 이벤트 루프를 막지 않습니다.uvicorn.Server를 코드에서 직접 띄우면 문서의 실행 예제와 검증 코드가 하나로 합쳐집니다.- self-test가
/health와/chat를 모두 치면 단순 기동 확인을 넘어 실제 의존성 경로까지 점검할 수 있습니다.
실무에서 헷갈리는 지점

self-test가 기동과 종료를 함께 검증하는 구조
- 헬스체크는 모델 품질을 보장하지 않습니다. 이 엔드포인트는 프로세스와 기본 의존성 상태만 확인합니다.
- 비동기 프레임워크를 쓴다고 외부 SDK 호출까지 자동으로 비동기가 되는 것은 아닙니다.
- 로컬 self-test가 통과해도 배포 환경에서는 네트워크 제한, 시크릿 주입, 타임아웃 값을 다시 확인해야 합니다.
시니어 엔지니어는 이렇게 생각합니다
- 모델·프롬프트도 코드처럼 버전 — 변경 추적이 회귀 진단의 기본입니다.
- 점진적 배포가 표준 — canary·shadow가 사고 영향을 최소화합니다.
- 롤백을 사전에 시연 — 배포만 자동화하면 사고가 됩니다.
- feature flag로 변경 제어 — 코드와 활성화를 분리합니다.
- 배포 마커를 메트릭에 둔다 — 회귀의 원인을 빠르게 좁힙니다.
체크리스트
- [ ] 서버 시작 후 /health를 자동 호출한다
- [ ] 실제 /chat 요청 한 건을 보내 응답을 확인한다
- [ ] 이벤트 루프를 막는 동기 호출을 to_thread로 분리한다
- [ ] 종료 시 서버 스레드가 정상 종료되는지 확인한다
정리
배포 예제는 서버 코드보다 self-test가 더 중요합니다. 스스로 기동과 요청을 증명하지 못하면 운영 문서로 쓰기 어렵습니다.
시리즈 목차
- LLM 앱 모니터링과 로깅
- LLM 비용 추적과 최적화
- LLM 출력 품질 평가
- LLM 앱 보안
- LLM 앱 배포 전략 (현재 글)
- LLM 앱 운영 완성 (예정)
참고 자료
반응형
'AI·LLM' 카테고리의 다른 글
| AI 이미지 생성 101 (1/10): 첫 이미지 생성하기 (0) | 2026.06.20 |
|---|---|
| LLM Apps Ops 101 (6/6): LLM 앱 운영 완성 (2) | 2026.06.05 |
| LLM Apps Ops 101 (4/6): LLM 앱 보안 (0) | 2026.06.05 |
| LLM Apps Ops 101 (3/6): LLM 출력 품질 평가 (0) | 2026.06.05 |
| LLM Apps Ops 101 (2/6): LLM 비용 추적과 최적화 (0) | 2026.06.05 |
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- backend
- reliability
- softwaredesign
- frontend
- ai agent
- APIDesign
- vector search
- openAI
- Ai
- AZURE
- LLM
- DesignPatterns
- Tool Use
- Architecture
- Python
- Computer Science
- docker
- containers
- testing
- embeddings
- http
- QUALITY
- langchain
- webdevelopment
- rag
- DevOps
- Agent
- Production
- Cloud
- Kubernetes
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 | 31 |
글 보관함
