FastAPI API, Docker와 Kubernetes로 프로덕션 환경에 배포하는 완벽 실습 가이드
"로컬 환경에서는 완벽하게 돌아갔는데, 스테이징 서버에 올리니 500 에러가 나요."
이 문장, 백엔드 개발자라면 누구나 한 번쯤 겪어봤을 법한 고통의 순간일 겁니다. 우리는 수많은 시간을 비즈니스 로직(Business Logic)을 짜는 데 쏟지만, 막상 이 코드를 '실제 운영 환경'이라는 거대한 생태계에 올리는 '배포(Deployment)' 과정은 마치 블랙박스처럼 느껴지곤 합니다.
FastAPI 같은 최신 프레임워크로 멋진 API를 완성했다고 해도, 이 코드가 트래픽을 감당하며 24시간 안정적으로 동작하려면 컨테이너화(Containerization)와 오케스트레이션(Orchestration)이라는 거대한 산을 넘어야 합니다.
이 가이드는 이론으로만 접했던 Docker와 Kubernetes를, 여러분이 직접 개발한 FastAPI 코드를 가지고 '실제로 배포'하는 과정을 따라 하며 완벽하게 이해할 수 있도록 설계되었습니다. 주니어 개발자부터 DevOps 엔지니어까지, 이 포스트 하나로 배포의 전 과정을 마스터할 수 있을 겁니다.
🚀 1단계: FastAPI 애플리케이션 컨테이너화하기 (Dockerizing)
애플리케이션을 독립적인 '패키지'로 만드는 과정이 바로 컨테이너화입니다. Docker는 이 패키징을 표준화하는 가장 강력한 도구입니다.
1. FastAPI 기본 코드 준비
먼저, 배포할 간단한 FastAPI 코드를 준비합니다. main.py 파일에 아래 코드를 작성해 주세요.
# main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/{item_id}")
def read_item(item_id: int, q: str = None):
"""
특정 ID의 아이템 정보를 조회하는 엔드포인트입니다.
"""
return {"item_id": item_id, "q": q, "message": "Successfully deployed via Docker!"}
# 참고: 실제 운영 시에는 uvicorn을 사용하여 서버를 구동합니다.그리고 의존성 파일 requirements.txt를 작성합니다.
# requirements.txt
fastapi
uvicorn[standard]2. 최적화된 Dockerfile 작성 원칙
단순히 FROM python:3.10만 사용하는 것은 최적화가 아닙니다. 운영 환경에서는 이미지 크기 최소화와 보안 강화가 핵심입니다. 이를 위해 '멀티 스테이지 빌드(Multi-stage Build)'를 사용합니다.
Dockerfile 예시:
# --------------------------------------
# STAGE 1: Builder Stage (의존성 설치 및 빌드)
# --------------------------------------
FROM python:3.11-slim AS builder
WORKDIR /app
# 캐시 효율성을 위해 requirements.txt를 먼저 복사하여 레이어를 분리
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 나머지 소스 코드를 복사
COPY . .
# --------------------------------------
# STAGE 2: Final Stage (최종 실행 이미지)
# --------------------------------------
# 가장 가볍고 보안에 강한 기본 이미지 사용
FROM python:3.11-slim
WORKDIR /app
# 빌더 스테이지에서 설치된 라이브러리만 복사 (불필요한 빌드 도구 제거)
COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
COPY --from=builder /app /app
# 컨테이너가 노출할 포트 정의
EXPOSE 8000
# 컨테이너 시작 시 실행할 명령어 정의
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]3. 로컬 빌드 및 테스트
이제 터미널에서 다음 명령어를 실행하여 이미지를 빌드하고, 로컬에서 동작을 검증합니다.
# 1. 이미지 빌드 (my-fastapi-app:latest 태그 사용)
docker build -t my-fastapi-app:latest .
# 2. 로컬 포트 8080으로 컨테이너 실행 및 테스트
docker run -d -p 8080:8000 --name fastapi-test my-fastapi-app:latest✅ 테스트 확인: 브라우저나 curl로 http://localhost:8080/items/1?q=test에 접속해 보세요. FastAPI가 정상적으로 응답하는 것을 확인할 수 있습니다.
🌐 2단계: Kubernetes 환경 이해 및 Manifest 작성하기 (Orchestration)
Docker가 '단일 컨테이너'를 패키징하는 것이라면, Kubernetes(K8s)는 이 컨테이너들을 '대규모로 관리하고 자동 복구'하는 운영체제 같은 역할을 합니다.
K8s에서 애플리케이션을 배포하려면 최소한 다음 세 가지 개념을 이해해야 합니다.
- Deployment: 애플리케이션의 '상태'를 정의합니다. "항상 3개의 Pod가 이 이미지를 사용해서 실행되어야 한다"와 같은 선언적 상태를 유지합니다.
- Service: Pod들은 IP 주소가 계속 바뀝니다. Service는 이 변동하는 Pod들에게 **안정적인 네트워크 접근 주소(Cluster IP)**를 제공하는 역할을 합니다.
- Ingress: 외부 트래픽(인터넷)이 클러스터 내부로 들어오는 '관문' 역할을 합니다. (예:
api.mycompany.com/items요청을 특정 Service로 라우팅)
💡 K8s Manifest YAML 작성 예시
실제 배포를 위해 deployment.yaml과 service.yaml을 작성합니다. (여기서 my-fastapi-app:latest는 1단계에서 만든 이미지 이름입니다.)
deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: fastapi-deployment
labels:
app: fastapi
spec:
replicas: 3 # 3개의 Pod를 항상 유지하도록 설정
selector:
matchLabels:
app: fastapi
template:
metadata:
labels:
app: fastapi
spec:
containers:
- name: fastapi-container
image: your-docker-registry/my-fastapi-app:latest # 실제 레지스트리 경로로 변경 필수
ports:
- containerPort: 8000
# 환경 변수 설정 예시
env:
- name: ENVIRONMENT
value: productionservice.yaml:
apiVersion: v1
kind: Service
metadata:
name: fastapi-service
spec:
selector:
app: fastapi # Deployment에서 정의한 레이블과 일치해야 함
ports:
- protocol: TCP
port: 80 # 클러스터 내부에서 접근할 포트
targetPort: 8000 # 컨테이너가 실제로 리스닝하는 포트
type: ClusterIP # 내부 서비스용 기본 타입🛠️ 3단계: 실제 클러스터에 배포하고 검증하기 (실습 중심)
이제 준비된 Manifest 파일을 클러스터에 적용할 차례입니다.
1. 배포 실행 명령어
클러스터에 접속한 후, 다음 명령어를 순차적으로 실행합니다.
# 1. Deployment와 Service를 한 번에 적용
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
# 2. 배포 상태 확인 (Pod가 Running 상태가 될 때까지 대기)
kubectl get pods -l app=fastapi💡 문제 해결: Pod가 Pending 상태일 때?
만약 Pod가 Pending 상태로 멈춰있다면, 다음 명령어로 상세 로그를 확인하세요.
kubectl describe pod <pod-name>
🚀 실제 서비스 노출 (Ingress/Service Type 변경)
외부에서 접근 가능하게 하려면, Service 정의 시 type: LoadBalancer를 사용하거나, 실제 운영 환경에서는 Ingress Controller를 사용하는 것이 표준입니다.
📚 요약 및 학습 포인트
| 단계 | 도구/개념 | 목적 | 핵심 명령어 |
|---|---|---|---|
| 컨테이너화 | Dockerfile | 애플리케이션을 격리된 환경으로 패키징 | docker build, docker push |
| 오케스트레이션 | Kubernetes (K8s) | 컨테이너의 배포, 확장, 관리를 자동화 | kubectl apply -f, kubectl get pods |
| 서비스 노출 | Service / Ingress | 외부 트래픽을 내부 컨테이너로 라우팅 | kubectl expose, Ingress Controller |
이 과정을 통해 여러분은 단순히 코드를 작성하는 것을 넘어, 실제 운영 환경에서 서비스를 배포하고 관리하는 엔지니어링 사이클을 완벽하게 경험하게 됩니다.
이 글은 AI 에이전트가 1차 초안을 작성한 뒤, 사람 편집자가 사실관계·출처·톤과 맥락을 검토하여 발행했습니다. 오류나 부정확한 내용이 확인되면 24시간 이내에 정정합니다.
댓글
불러오는 중...