092 Docker로 배포 환경 구성
키워드: Docker, 컨테이너, 배포, DevOps
개요
Docker를 사용하면 FLAML 모델을 일관된 환경에서 배포할 수 있습니다. 이 글에서는 모델 서빙을 위한 Docker 컨테이너 구성과 배포 방법을 알아봅니다.
실습 환경
- Docker: 20.10 이상
- Docker Compose: 2.0 이상
# 092 Docker 설치 확인
docker --version
docker-compose --version
프로젝트 구조
project_structure = """
flaml-api/
├── app/
│ ├── __init__.py
│ ├── main.py # FastAPI 앱
│ ├── model.py # 모델 로딩
│ └── schemas.py # Pydantic 스키마
├── models/
│ └── model.pkl # 학습된 모델
├── requirements.txt # 의존성
├── Dockerfile # Docker 이미지 정의
├── docker-compose.yml # 서비스 구성
└── .dockerignore # Docker 제외 파일
"""
print("프로젝트 구조:")
print(project_structure)
requirements.txt
requirements = """
flaml[automl]==2.1.0
fastapi==0.104.0
uvicorn==0.24.0
pydantic==2.5.0
numpy==1.24.0
pandas==2.0.0
scikit-learn==1.3.0
joblib==1.3.0
lightgbm==4.1.0
xgboost==2.0.0
"""
print("requirements.txt:")
print(requirements)
Dockerfile
dockerfile = '''
# 092 Dockerfile
FROM python:3.11-slim
# 092 작업 디렉토리 설정
WORKDIR /app
# 092 시스템 의존성 설치
RUN apt-get update && apt-get install -y \\
build-essential \\
&& rm -rf /var/lib/apt/lists/*
# 092 Python 의존성 설치
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 092 앱 코드 복사
COPY app/ ./app/
COPY models/ ./models/
# 092 환경 변수
ENV PYTHONPATH=/app
ENV MODEL_PATH=/app/models/model.pkl
# 092 포트 노출
EXPOSE 8000
# 092 헬스체크
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \\
CMD curl -f http://localhost:8000/health || exit 1
# 092 실행 명령
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
'''
print("Dockerfile:")
print(dockerfile)
FastAPI 앱 코드
main_py = '''
# 092 app/main.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import joblib
import numpy as np
import os
from typing import List
app = FastAPI(title="FLAML Model API", version="1.0.0")
# 092 모델 로드
MODEL_PATH = os.getenv("MODEL_PATH", "models/model.pkl")
model = None
@app.on_event("startup")
async def load_model():
global model
model = joblib.load(MODEL_PATH)
print(f"Model loaded: {model.best_estimator}")
class PredictRequest(BaseModel):
features: List[float]
class PredictResponse(BaseModel):
prediction: int
probability: List[float]
@app.get("/health")
def health():
return {"status": "healthy", "model_loaded": model is not None}
@app.post("/predict", response_model=PredictResponse)
def predict(request: PredictRequest):
if model is None:
raise HTTPException(503, "Model not loaded")
X = np.array(request.features).reshape(1, -1)
pred = int(model.predict(X)[0])
proba = model.predict_proba(X)[0].tolist()
return PredictResponse(prediction=pred, probability=proba)
'''
print("app/main.py:")
print(main_py)
Docker 이미지 빌드
build_commands = """
=== Docker 이미지 빌드 ===
# 1. 이미지 빌드
docker build -t flaml-api:latest .
# 2. 이미지 확인
docker images | grep flaml-api
# 3. 이미지 크기 최적화 확인
docker images --format "{{.Repository}}:{{.Tag}} - {{.Size}}"
"""
print(build_commands)
Docker 컨테이너 실행
run_commands = """
=== Docker 컨테이너 실행 ===
# 1. 기본 실행
docker run -d -p 8000:8000 --name flaml-api flaml-api:latest
# 2. 환경 변수와 함께 실행
docker run -d -p 8000:8000 \\
-e MODEL_PATH=/app/models/model.pkl \\
--name flaml-api flaml-api:latest
# 3. 볼륨 마운트 (모델 업데이트용)
docker run -d -p 8000:8000 \\
-v $(pwd)/models:/app/models \\
--name flaml-api flaml-api:latest
# 4. 컨테이너 상태 확인
docker ps
docker logs flaml-api
# 5. API 테스트
curl http://localhost:8000/health
"""
print(run_commands)
Docker Compose
docker_compose = '''
# 092 docker-compose.yml
version: "3.8"
services:
api:
build: .
image: flaml-api:latest
container_name: flaml-api
ports:
- "8000:8000"
environment:
- MODEL_PATH=/app/models/model.pkl
- LOG_LEVEL=INFO
volumes:
- ./models:/app/models:ro
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
restart: unless-stopped
deploy:
resources:
limits:
cpus: "2.0"
memory: 4G
prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
'''
print("docker-compose.yml:")
print(docker_compose)
Docker Compose 명령어
compose_commands = """
=== Docker Compose 명령어 ===
# 1. 서비스 시작
docker-compose up -d
# 2. 로그 확인
docker-compose logs -f api
# 3. 서비스 상태
docker-compose ps
# 4. 서비스 재시작
docker-compose restart api
# 5. 서비스 중지
docker-compose down
# 6. 이미지 재빌드 후 시작
docker-compose up -d --build
"""
print(compose_commands)
멀티 스테이지 빌드 (최적화)
multi_stage = '''
# 092 Dockerfile (Multi-stage)
# 092 빌드 스테이지
FROM python:3.11-slim as builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
# 092 실행 스테이지
FROM python:3.11-slim
WORKDIR /app
# 092 빌드된 패키지만 복사
COPY --from=builder /root/.local /root/.local
ENV PATH=/root/.local/bin:$PATH
# 092 앱 코드만 복사
COPY app/ ./app/
COPY models/ ./models/
ENV PYTHONPATH=/app
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
'''
print("멀티 스테이지 Dockerfile (최적화):")
print(multi_stage)
.dockerignore
dockerignore = """
# 092 .dockerignore
__pycache__
*.pyc
*.pyo
.git
.gitignore
.env
*.md
tests/
notebooks/
.pytest_cache
.coverage
*.egg-info
dist/
build/
"""
print(".dockerignore:")
print(dockerignore)
컨테이너 모니터링
monitoring = """
=== 컨테이너 모니터링 ===
# 092 리소스 사용량
docker stats flaml-api
# 092 로그 스트리밍
docker logs -f --tail 100 flaml-api
# 092 컨테이너 내부 접속
docker exec -it flaml-api /bin/bash
# 092 헬스 체크 상태
docker inspect --format='{{.State.Health.Status}}' flaml-api
"""
print(monitoring)
프로덕션 배포 체크리스트
import pandas as pd
checklist = {
'항목': ['이미지 태깅', '환경 변수', '볼륨', '헬스체크', '리소스 제한', '로깅'],
'내용': [
'flaml-api:v1.0.0 형식 태그',
'MODEL_PATH, LOG_LEVEL 등',
'모델 파일 마운트',
'/health 엔드포인트',
'CPU, 메모리 제한',
'stdout/stderr → 로그 시스템'
],
'상태': ['체크', '체크', '체크', '체크', '체크', '체크']
}
print("\n=== 프로덕션 배포 체크리스트 ===")
print(pd.DataFrame(checklist).to_string(index=False))
정리
- Dockerfile: 재현 가능한 환경 정의
- Docker Compose: 멀티 컨테이너 구성
- 멀티 스테이지: 이미지 크기 최적화
- 헬스체크: 컨테이너 상태 모니터링
- 볼륨 마운트: 모델 업데이트 지원
다음 글 예고
다음 글에서는 배치 예측 파이프라인 구축을 알아봅니다. 대용량 데이터의 배치 처리 방법을 다룹니다.
FLAML AutoML 마스터 시리즈 #092