# Part 9: 프로덕션 레벨 API와 Docker **⬅️ 이전 시간: [Part 8: FastAPI를 이용한 모델 서빙](./part_8_model_serving_with_fastapi.md)** **➡️ 다음 시간: [Part 10: 전문가로 가는 길](./part_10_expert_path.md)** --- ## 1. 학습 목표 (Learning Objectives) 이번 파트가 끝나면, 여러분은 다음을 할 수 있게 됩니다. - 유지보수와 협업에 용이하도록 FastAPI 프로젝트를 기능별로 구조화할 수 있습니다. - `APIRouter`를 사용하여 API 엔드포인트를 모듈화하고 관리할 수 있습니다. - "제 컴퓨터에선 되는데요..." 문제를 해결하는 Docker의 원리를 이해하고, 이미지/컨테이너 개념을 설명할 수 있습니다. - `Dockerfile`을 작성하여 파이썬 애플리케이션을 컨테이너 이미지로 만들 수 있습니다. - `docker-compose.yml`을 사용하여 여러 서비스(API 서버, DB 등)를 한 번에 실행하고 관리할 수 있습니다. ## 2. 핵심 키워드 (Keywords) `프로젝트 구조화`, `관심사 분리(SoC)`, `APIRouter`, `의존성 주입(DI)`, `Docker`, `컨테이너(Container)`, `이미지(Image)`, `Dockerfile`, `Docker Compose` ## 3. 도입: '1인 식당'에서 '프랜차이즈 레스토랑'으로 지난 시간에는 FastAPI로 '1인 식당'처럼 빠르게 AI 모델 API를 만들었습니다. 프로토타입으로는 훌륭하지만, 메뉴가 다양해지고(기능 추가) 여러 셰프가 협업해야 하는(팀 개발) 실제 '프랜차이즈 레스토랑' 환경에서는 한계가 명확합니다. 이번 주차에는 `main.py` 하나에 모든 것을 담던 방식에서 벗어나, **유지보수와 확장이 용이한 프로젝트 구조**로 우리 레스토랑을 리팩터링합니다. 또한, 어떤 백화점에 입점하든 동일한 맛과 서비스를 보장하는 **'밀키트(Docker 컨테이너)'** 기술을 배워, 우리 AI 서비스를 세상 어디에든 쉽고 안정적으로 배포하는 능력을 갖추게 될 것입니다. > [!TIP] > 본 파트의 모든 예제 코드는 `source_code/part_9_production_ready_api` 폴더에서 직접 실행하고 수정해볼 수 있습니다. --- ## 4. 1단계: 프로덕션 레벨 프로젝트 구조 설계 > **🎯 1-2일차 목표:** '관심사 분리' 원칙에 따라 FastAPI 프로젝트를 기능별 파일과 폴더로 재구성합니다. `main.py` 하나에 모든 코드가 있는 것은, 주방장이 혼자 주문받고, 요리하고, 서빙까지 하는 것과 같습니다. 우리는 이제 역할을 분담하여 '프랜차이즈 본사'처럼 체계적인 시스템을 만들 것입니다. - **`settings.py`**: 본사의 운영 방침 (환경 설정) - **`schemas.py`**: 모든 지점에서 통일된 '주문서 양식' (데이터 형식 정의) - **`routers/`**: '한식 코너', '중식 코너' 등 메뉴별로 분리된 주방 (기능별 API) - **`main.py`**: 모든 코너를 총괄하고 손님을 맞는 '총지배인' ### 4-1. 프로젝트 폴더 및 라우터 분리 먼저 아래와 같이 프로젝트 구조를 만듭니다. `main.py`에 있던 예측 관련 로직을 `routers/predictions.py`로 옮깁니다. ``` /fastapi_project ├── app/ │ ├── __init__.py │ ├── main.py │ └── routers/ │ ├── __init__.py │ └── predictions.py ├── requirements.txt └── iris_model.pkl ``` **`app/routers/predictions.py` (예측 코너 주방)**: `APIRouter`를 사용하여 예측 기능만을 위한 독립적인 API 그룹을 만듭니다. ```python from fastapi import APIRouter, HTTPException from pydantic import BaseModel from typing import List import joblib import numpy as np # 1. 이 라우터만의 '주문서 양식' 정의 class PredictionRequest(BaseModel): features: List[float] # 2. APIRouter 인스턴스 생성 router = APIRouter(prefix="/predict", tags=["predictions"]) # 3. 모델 로드 (임시) model = joblib.load("iris_model.pkl") class_names = ['setosa', 'versicolor', 'virginica'] # 4. 예측 엔드포인트 정의 @router.post("/", summary="붓꽃 품종 예측") def predict_iris(request: PredictionRequest): model_input = np.array(request.features).reshape(1, -1) prediction_idx = model.predict(model_input)[0] confidence = model.predict_proba(model_input)[0][prediction_idx] return { "predicted_class": class_names[prediction_idx], "confidence": float(confidence) } ``` ### 4-2. 최종 통합 (`main.py`) '총지배인' `main.py`에서 분리된 '예측 코너'를 전체 레스토랑에 포함시킵니다. ```python from fastapi import FastAPI from .routers import predictions app = FastAPI(title="AI 레스토랑 (프랜차이즈 ver)") # "예측 코너" 라우터 포함 app.include_router(predictions.router) @app.get("/") def read_root(): return {"message": "AI 레스토랑에 오신 것을 환영합니다."} ``` 이제 `uvicorn app.main:app --reload`로 서버를 실행하고 `http://127.0.0.1:8000/docs`에 접속하면, `predictions`라는 태그로 그룹화된 API를 확인할 수 있습니다. --- ## 5. 2단계: Docker로 우리 앱 포장하기 > **🎯 3-4일차 목표:** Dockerfile을 작성하여 우리 앱을 이미지로 만들고, docker-compose로 실행합니다. ### 5-1. 왜 Docker를 써야 할까? > **💡 비유: '어디서든 똑같은 맛을 내는 밀키트'** > > 우리 레스토랑의 레시피와 재료를 완벽하게 준비해도, 지점마다 주방 환경(OS, 라이브러리 버전 등)이 다르면 음식 맛이 달라질 수 있습니다. "제 주방(컴퓨터)에서는 되는데요?" 라는 문제는 개발자들의 흔한 악몽입니다. > > **Docker**는 우리 레스토랑의 모든 것(코드, 라이브러리, 설정)을 하나의 **'밀키트(이미지)'**로 완벽하게 포장하는 기술입니다. 이 밀키트는 어떤 주방(서버)에서 데우든 항상 똑같은 환경에서, 똑같은 맛의 요리를 만들어냅니다. ### 5-2. Dockerfile: 밀키트 레시피 작성 `Dockerfile`은 이 밀키트를 만드는 '레시피'입니다. 프로젝트 최상위 폴더에 `Dockerfile`이라는 이름으로 파일을 만듭니다. ```dockerfile # 1. 베이스 이미지 선택 (기본 주방 키트 가져오기) FROM python:3.9-slim # 2. 작업 디렉토리 설정 (조리대 위치 지정) WORKDIR /code # 3. 의존성 파일 먼저 복사 (소스가 바뀌어도 의존성이 같으면 캐시 재사용) COPY ./requirements.txt /code/requirements.txt # 4. 의존성 설치 (필요한 조미료 채우기) RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt # 5. 소스 코드와 모델 파일 복사 (레시피와 식재료 넣기) COPY ./app /code/app COPY ./iris_model.pkl /code/iris_model.pkl # 6. 서버 실행 (레스토랑 개점!) # 0.0.0.0 주소로 실행해야 Docker 외부에서 접속 가능 CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] ``` ### 5-3. Docker Compose: 프랜차이즈 매장 동시 오픈 `docker-compose.yml`은 여러 '밀키트'(서비스)들을 정의하고 한 번에 관리하는 '프랜차이즈 오픈 계획서'입니다. 지금은 API 서버 하나지만, 나중에 DB 등이 추가되면 그 위력을 실감하게 됩니다. ```yaml # docker-compose.yml version: '3.8' services: # 서비스 이름 정의 (우리 레스토랑 지점) api: # 어떤 Dockerfile을 사용할지 지정 build: . # 포트 매핑 (외부 8000번 포트 -> 컨테이너 내부 8000번 포트) ports: - "8000:8000" # 컨테이너에 이름 부여 container_name: fastapi_iris_app ``` --- ## 6. 직접 해보기 (Hands-on Lab): Docker로 레스토랑 오픈하기 > **🎯 5일차 목표:** Dockerfile과 docker-compose.yml을 사용하여, 단 한 줄의 명령어로 FastAPI 서버를 실행합니다. ### 문제: 1. **`requirements.txt` 파일 생성**: 프로젝트에 필요한 라이브러리(`fastapi`, `uvicorn`, `scikit-learn`, `joblib`, `numpy`) 목록을 `requirements.txt` 파일에 작성하세요. 2. **`Dockerfile` 작성**: 위 템플릿을 참고하여 프로젝트 최상위 경로에 `Dockerfile`을 완성하세요. 3. **`docker-compose.yml` 작성**: 위 템플릿을 참고하여 프로젝트 최상위 경로에 `docker-compose.yml`을 완성하세요. 4. **Docker로 서버 실행**: 터미널에서 `docker-compose up --build` 명령어를 실행하여 Docker 이미지를 빌드하고 컨테이너를 실행하세요. 5. **결과 확인**: 빌드가 성공적으로 완료되고 서버가 실행되면, 웹 브라우저에서 `http://localhost:8000/docs`에 접속하여 API 문서가 이전과 동일하게 잘 보이는지 확인하세요. --- ## 7. 되짚어보기 (Summary) 이번 주차에는 '1인 식당'을 '프랜차이즈 레스토랑'으로 확장하는 여정을 통해, 프로덕션 레벨의 AI 서비스를 구축하는 핵심 기술을 배웠습니다. - **프로젝트 구조화**: `APIRouter`를 사용해 기능별로 코드를 분리하여, 유지보수와 협업이 용이한 프로젝트 구조를 설계했습니다. - **Docker 컨테이너화**: '밀키트' 비유를 통해 Docker의 필요성을 이해하고, `Dockerfile`을 작성하여 우리 앱을 재현 가능한 이미지로 만들었습니다. - **Docker Compose**: `docker-compose.yml`을 통해 여러 서비스를 명령어 하나로 관리하는 방법을 익혀, 복잡한 애플리케이션 배포의 기초를 다졌습니다. ## 8. 더 깊이 알아보기 (Further Reading) - [TestDriven.io: FastAPI and Docker](https://testdriven.io/blog/dockerizing-fastapi/): Docker를 사용한 FastAPI 배포에 대한 상세한 튜토리얼 - [Docker 공식 문서: Get Started](https://docs.docker.com/get-started/): Docker의 기본 개념부터 차근차근 배울 수 있는 최고의 자료 - [『코어 자바스크립트』 저자의 Docker/k8s 강의](https://www.inflearn.com/course/%EB%8F%84%EC%BB%A4-k8s-%ED%95%B5%EC%8B%AC%EA%B0%9C%EB%념-%EC%9D%B5%ED%9E%88%EA%B8%B0): Docker와 쿠버네티스의 핵심 원리를 비유를 통해 쉽게 설명하는 강의 --- **➡️ 다음 시간: [Part 10: 전문가로 가는 길](./part_10_expert_path.md)**