파이썬/Fast API

FastAPI 애플리케이션 Docker로 배포하기

코샵 2024. 11. 27. 10:15
반응형

소개

FastAPI 애플리케이션을 실제 운영 환경에 배포하는 것은 개발만큼 중요한 과정입니다. 이번 글에서는 Docker를 사용하여 FastAPI 애플리케이션을 안전하고 효율적으로 배포하는 방법을 알아보겠습니다.

Docker 기본 설정

Dockerfile 작성

# Dockerfile
# Python 3.9 이미지를 기본으로 사용
FROM python:3.9-slim

# 작업 디렉토리 설정
WORKDIR /app

# Poetry 설치 (의존성 관리 도구)
RUN pip install poetry

# 프로젝트 메타데이터 복사
COPY pyproject.toml poetry.lock ./

# Poetry 설정: 가상환경 생성하지 않음
RUN poetry config virtualenvs.create false

# 의존성 설치
RUN poetry install --no-dev

# 소스 코드 복사
COPY ./app ./app

# 환경변수 설정
ENV PORT=8000

# FastAPI 애플리케이션 실행
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

Docker Compose 설정

# docker-compose.yml
version: '3.8'

services:
  web:
    build: .
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql://user:password@db:5432/dbname
      - SECRET_KEY=your-secret-key
    depends_on:
      - db
    volumes:
      - ./app:/app/app
    networks:
      - app-network

  db:
    image: postgres:13
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=password
      - POSTGRES_DB=dbname
    networks:
      - app-network

volumes:
  postgres_data:

networks:
  app-network:
    driver: bridge

환경 설정 관리

환경변수 설정

# app/config.py
from pydantic import BaseSettings

class Settings(BaseSettings):
    """애플리케이션 설정"""
    # 데이터베이스
    DATABASE_URL: str

    # 보안
    SECRET_KEY: str
    ALGORITHM: str = "HS256"
    ACCESS_TOKEN_EXPIRE_MINUTES: int = 30

    # 서버
    HOST: str = "0.0.0.0"
    PORT: int = 8000

    # 환경
    ENVIRONMENT: str = "production"
    DEBUG: bool = False

    class Config:
        env_file = ".env"

settings = Settings()

환경별 설정 파일

# .env.example
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
SECRET_KEY=your-secret-key
ENVIRONMENT=development
DEBUG=True

# .env.production
DATABASE_URL=postgresql://user:password@db:5432/dbname
SECRET_KEY=your-production-secret-key
ENVIRONMENT=production
DEBUG=False

데이터베이스 마이그레이션

Alembic 설정

# alembic/env.py
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from alembic import context
from app.config import settings
from app.models import Base

config = context.config

# Alembic 설정 수정
config.set_main_option("sqlalchemy.url", settings.DATABASE_URL)

target_metadata = Base.metadata

def run_migrations_offline():
    """오프라인 마이그레이션 실행"""
    url = config.get_main_option("sqlalchemy.url")
    context.configure(
        url=url,
        target_metadata=target_metadata,
        literal_binds=True,
        dialect_opts={"paramstyle": "named"},
    )

    with context.begin_transaction():
        context.run_migrations()

def run_migrations_online():
    """온라인 마이그레이션 실행"""
    configuration = config.get_section(config.config_ini_section)
    configuration["sqlalchemy.url"] = settings.DATABASE_URL
    connectable = engine_from_config(
        configuration,
        prefix="sqlalchemy.",
        poolclass=pool.NullPool,
    )

    with connectable.connect() as connection:
        context.configure(
            connection=connection,
            target_metadata=target_metadata
        )

        with context.begin_transaction():
            context.run_migrations()

배포 스크립트

Docker 배포 스크립트

#!/bin/bash
# deploy.sh

# 환경 변수 로드
set -a
source .env.production
set +a

# 이전 컨테이너 정리
docker-compose down

# 이미지 빌드
docker-compose build

# 컨테이너 시작
docker-compose up -d

# 데이터베이스 마이그레이션
docker-compose exec web alembic upgrade head

echo "Deployment completed successfully!"

로깅 설정

# app/logger.py
import logging
from logging.handlers import RotatingFileHandler

def setup_logger():
    """로깅 설정"""
    logger = logging.getLogger("fastapi_app")
    logger.setLevel(logging.INFO)

    # 파일 핸들러
    file_handler = RotatingFileHandler(
        "logs/app.log",
        maxBytes=1024 * 1024,  # 1MB
        backupCount=5
    )
    file_handler.setFormatter(
        logging.Formatter(
            '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
        )
    )
    logger.addHandler(file_handler)

    # 콘솔 핸들러
    console_handler = logging.StreamHandler()
    console_handler.setFormatter(
        logging.Formatter(
            '%(levelname)s: %(message)s'
        )
    )
    logger.addHandler(console_handler)

    return logger

모니터링 설정

Prometheus 메트릭 설정

# app/monitoring.py
from prometheus_client import Counter, Histogram
from fastapi import FastAPI
from starlette_prometheus import PrometheusMiddleware, metrics

def setup_monitoring(app: FastAPI):
    """모니터링 설정"""
    # Prometheus 미들웨어 추가
    app.add_middleware(PrometheusMiddleware)
    app.add_route("/metrics", metrics)

    # 사용자 정의 메트릭
    REQUEST_COUNT = Counter(
        'http_requests_total',
        'Total HTTP requests',
        ['method', 'endpoint', 'status']
    )

    REQUEST_LATENCY = Histogram(
        'http_request_duration_seconds',
        'HTTP request latency',
        ['method', 'endpoint']
    )

    return REQUEST_COUNT, REQUEST_LATENCY

보안 설정

HTTPS 설정 (Nginx)

# nginx/conf.d/fastapi.conf
server {
    listen 80;
    server_name yourdomain.com;

    # HTTP to HTTPS redirect
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl;
    server_name yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;

    location / {
        proxy_pass http://web:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

CI/CD 파이프라인

GitHub Actions 설정

# .github/workflows/deploy.yml
name: Deploy FastAPI Application

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2

    - name: Login to Docker Hub
      uses: docker/login-action@v1
      with:
        username: ${{ secrets.DOCKER_HUB_USERNAME }}
        password: ${{ secrets.DOCKER_HUB_TOKEN }}

    - name: Build and push Docker images
      run: |
        docker-compose build
        docker-compose push

    - name: Deploy to server
      uses: appleboy/ssh-action@master
      with:
        host: ${{ secrets.SERVER_HOST }}
        username: ${{ secrets.SERVER_USERNAME }}
        key: ${{ secrets.SERVER_SSH_KEY }}
        script: |
          cd /path/to/your/app
          git pull
          docker-compose down
          docker-compose pull
          docker-compose up -d

성능 최적화

Gunicorn 설정

# gunicorn.conf.py
from multiprocessing import cpu_count

# Gunicorn 설정
bind = "0.0.0.0:8000"
workers = cpu_count() * 2 + 1
worker_class = "uvicorn.workers.UvicornWorker"
keepalive = 5
timeout = 120
graceful_timeout = 30

# 로깅 설정
accesslog = "/var/log/gunicorn/access.log"
errorlog = "/var/log/gunicorn/error.log"
loglevel = "info"

마치며

FastAPI 애플리케이션을 Docker를 사용하여 배포하는 것은 처음에는 복잡해 보일 수 있지만, 위의 설정들을 차근차근 따라하면 안정적이고 확장 가능한 배포가 가능합니다. 실제 운영 환경에서는 보안, 모니터링, 로깅 등 추가적인 고려사항들이 있으니 프로젝트의 요구사항에 맞게 적절히 조정하시기 바랍니다.