095 Docker 컨테이너화
키워드: Docker, 컨테이너
개요
Docker는 애플리케이션을 컨테이너로 패키징하여 어디서나 동일하게 실행할 수 있게 합니다. ML 모델과 API 서버를 Docker 이미지로 만들어 일관된 배포 환경을 구축합니다.
실습 환경
- Python 버전: 3.11 권장
- 필요 도구: Docker Desktop,
pycaret[full]>=3.0
프로젝트 구조
ml-api/
├── app/
│ ├── __init__.py
│ ├── main.py # FastAPI 앱
│ └── model/
│ └── diabetes_model.pkl
├── requirements.txt
├── Dockerfile
└── docker-compose.yml
requirements.txt
pycaret>=3.0.0
fastapi>=0.100.0
uvicorn>=0.23.0
pandas>=1.5.0
numpy>=1.23.0
scikit-learn>=1.2.0
기본 Dockerfile
# 095 Dockerfile
FROM python:3.11-slim
# 095 작업 디렉토리 설정
WORKDIR /app
# 095 시스템 의존성 설치
RUN apt-get update && apt-get install -y \
gcc \
g++ \
&& rm -rf /var/lib/apt/lists/*
# 095 파이썬 의존성 설치
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 095 애플리케이션 코드 복사
COPY app/ ./app/
# 095 포트 노출
EXPOSE 8000
# 095 실행 명령
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
최적화된 Dockerfile
# 095 Dockerfile.optimized
# 095 멀티스테이지 빌드
FROM python:3.11-slim as builder
WORKDIR /app
# 095 빌드 의존성 설치
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
g++ \
&& rm -rf /var/lib/apt/lists/*
# 095 가상환경 생성 및 의존성 설치
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 095 런타임 이미지
FROM python:3.11-slim
WORKDIR /app
# 095 가상환경 복사
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# 095 비루트 사용자 생성
RUN useradd -m appuser && chown -R appuser:appuser /app
USER appuser
# 095 애플리케이션 복사
COPY --chown=appuser:appuser app/ ./app/
EXPOSE 8000
# 095 헬스체크
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
app/main.py
# 095 app/main.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
import pandas as pd
import os
# 095 모델 로드
from pycaret.classification import load_model, predict_model
app = FastAPI(title="ML API", version="1.0.0")
# 095 모델 경로
MODEL_PATH = os.getenv('MODEL_PATH', 'app/model/diabetes_model')
model = load_model(MODEL_PATH)
class PatientData(BaseModel):
Pregnancies: float = Field(..., ge=0)
Glucose: float = Field(..., ge=0)
BloodPressure: float = Field(..., ge=0)
SkinThickness: float = Field(..., ge=0)
Insulin: float = Field(..., ge=0)
BMI: float = Field(..., ge=0)
DiabetesPedigreeFunction: float = Field(..., ge=0)
Age: float = Field(..., ge=0)
class PredictionResponse(BaseModel):
prediction: int
probability: float
@app.get("/health")
def health():
return {"status": "healthy"}
@app.post("/predict", response_model=PredictionResponse)
def predict(data: PatientData):
try:
input_df = pd.DataFrame([data.dict()])
result = predict_model(model, data=input_df)
return PredictionResponse(
prediction=int(result['prediction_label'].values[0]),
probability=float(result['prediction_score'].values[0])
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
Docker 빌드 및 실행
# 095 이미지 빌드
docker build -t ml-api:latest .
# 095 컨테이너 실행
docker run -d -p 8000:8000 --name ml-api ml-api:latest
# 095 로그 확인
docker logs ml-api
# 095 테스트
curl http://localhost:8000/health
# 095 컨테이너 중지 및 삭제
docker stop ml-api
docker rm ml-api
docker-compose.yml
# 095 docker-compose.yml
version: '3.8'
services:
ml-api:
build:
context: .
dockerfile: Dockerfile
ports:
- "8000:8000"
environment:
- MODEL_PATH=app/model/diabetes_model
- LOG_LEVEL=INFO
volumes:
- ./logs:/app/logs
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
# 선택: 모니터링 추가
prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
depends_on:
- prometheus
Docker Compose 명령
# 095 서비스 시작
docker-compose up -d
# 095 서비스 상태 확인
docker-compose ps
# 095 로그 확인
docker-compose logs -f ml-api
# 095 서비스 중지
docker-compose down
# 095 이미지 재빌드
docker-compose build --no-cache
docker-compose up -d
환경 변수 관리
# 095 Dockerfile - 환경 변수 설정
ENV MODEL_PATH=/app/model/diabetes_model
ENV LOG_LEVEL=INFO
ENV WORKERS=4
# 095 docker-compose.yml - 환경 변수 파일 사용
services:
ml-api:
build: .
env_file:
- .env
# 095 .env
MODEL_PATH=app/model/diabetes_model
LOG_LEVEL=INFO
WORKERS=4
볼륨 마운트
# 095 docker-compose.yml
services:
ml-api:
build: .
volumes:
# 모델 파일 마운트 (재배포 없이 모델 업데이트 가능)
- ./models:/app/model:ro
# 로그 영구 저장
- ./logs:/app/logs
멀티 컨테이너 구성
# 095 docker-compose.yml - 프로덕션 구성
version: '3.8'
services:
# 로드 밸런서
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- ml-api-1
- ml-api-2
# API 서버 인스턴스 1
ml-api-1:
build: .
environment:
- INSTANCE_ID=1
expose:
- "8000"
# API 서버 인스턴스 2
ml-api-2:
build: .
environment:
- INSTANCE_ID=2
expose:
- "8000"
# Redis 캐시
redis:
image: redis:alpine
ports:
- "6379:6379"
nginx.conf
# 095 nginx.conf
events {
worker_connections 1024;
}
http {
upstream ml_api {
server ml-api-1:8000;
server ml-api-2:8000;
}
server {
listen 80;
location / {
proxy_pass http://ml_api;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /health {
proxy_pass http://ml_api;
}
}
}
GPU 지원 Docker
# 095 Dockerfile.gpu
FROM nvidia/cuda:11.8-runtime-ubuntu22.04
# 095 Python 설치
RUN apt-get update && apt-get install -y \
python3.11 \
python3-pip \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app/ ./app/
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
# 095 docker-compose.gpu.yml
services:
ml-api-gpu:
build:
dockerfile: Dockerfile.gpu
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
ports:
- "8000:8000"
이미지 크기 최적화
# 095 .dockerignore
__pycache__
*.pyc
*.pyo
.git
.gitignore
*.md
.env
.venv
tests/
docs/
*.log
# 095 이미지 크기 확인
docker images ml-api
# 095 레이어 분석
docker history ml-api:latest
# 095 슬림 이미지 사용
# 095 python:3.11-slim vs python:3.11 (약 800MB 차이)
프로덕션 체크리스트
# 095 docker-compose.prod.yml
version: '3.8'
services:
ml-api:
build:
context: .
dockerfile: Dockerfile.optimized
ports:
- "8000:8000"
environment:
- LOG_LEVEL=WARNING
- WORKERS=4
deploy:
resources:
limits:
cpus: '2'
memory: 4G
reservations:
cpus: '1'
memory: 2G
restart: always
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
테스트 스크립트
# 095 test_docker.py
import requests
import time
def test_api():
base_url = "http://localhost:8000"
# 헬스 체크
response = requests.get(f"{base_url}/health")
assert response.status_code == 200
print("Health check: OK")
# 예측 테스트
patient = {
"Pregnancies": 6, "Glucose": 148, "BloodPressure": 72,
"SkinThickness": 35, "Insulin": 0, "BMI": 33.6,
"DiabetesPedigreeFunction": 0.627, "Age": 50
}
response = requests.post(f"{base_url}/predict", json=patient)
assert response.status_code == 200
result = response.json()
print(f"Prediction: {result}")
print("All tests passed!")
if __name__ == "__main__":
# 컨테이너 시작 대기
time.sleep(5)
test_api()
정리
- Docker: 일관된 실행 환경 제공
- Dockerfile: 이미지 빌드 정의
- docker-compose: 멀티 컨테이너 관리
- 최적화: 멀티스테이지 빌드, slim 이미지
- 프로덕션: 리소스 제한, 로깅, 헬스체크
다음 글 예고
다음 글에서는 모니터링과 재학습을 다룹니다.
PyCaret 머신러닝 마스터 시리즈 #095