← 메인으로 돌아가기

🛠️ 개발/검증/상용 환경별 배포 구조화

실제 파일 수정과 테스트 과정을 단계별로 따라하기 - 트러블슈팅 포함

📚 실습 목차

🔧 초기 환경 설정

🔹 1. 프로젝트 구조 생성

디렉토리 구조 생성

# 프로젝트 루트 디렉토리 생성
mkdir edustack
cd edustack

# 기본 구조 생성
mkdir -p backend/app/{api/v1,models,config}
mkdir -p k8s/{local,production}
mkdir -p scripts
mkdir -p docs

# 생성된 구조 확인
tree -L 3

🔹 2. 기본 FastAPI 애플리케이션 작성

backend/app/main.py

import os
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

# 환경 설정
ENVIRONMENT = os.getenv("ENVIRONMENT", "dev")
DEBUG = os.getenv("DEBUG", "true").lower() == "true"
API_PREFIX = os.getenv("API_PREFIX", "/api/edustack")

app = FastAPI(
    title="EduStack API",
    description="AI Education Platform Backend",
    version="1.0.0",
    debug=DEBUG,
    docs_url=f"{API_PREFIX}/docs",
    redoc_url=f"{API_PREFIX}/redoc"
)

@app.get("/")
async def root():
    return {
        "message": "EduStack API",
        "version": "1.0.0",
        "environment": ENVIRONMENT
    }
실습 포인트: 환경변수를 통한 동적 설정이 핵심입니다. 처음에는 복잡한 설정 클래스를 만들려 했지만, 간단한 환경변수 방식이 더 효과적이었습니다.

🐳 Development 환경 구축

🔹 1. Docker Compose 설정

docker-compose.dev.yml 작성

version: '3.8'
services:
  postgres:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: edustack
      POSTGRES_USER: edustack
      POSTGRES_PASSWORD: edustack123
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data

  backend:
    build: ./backend
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql://edustack:edustack123@postgres:5432/edustack
      - REDIS_URL=redis://redis:6379/0
      - ENVIRONMENT=dev
      - DEBUG=true
    volumes:
      - ./backend:/app
    depends_on:
      - postgres
      - redis

volumes:
  postgres_data:
  redis_data:

🔹 2. 첫 번째 실행 테스트

개발 환경 시작

# Docker Compose로 개발 환경 시작
docker-compose -f docker-compose.dev.yml up --build

# 다른 터미널에서 API 테스트
curl http://localhost:8000/
# 결과: {"message":"EduStack API","version":"1.0.0","environment":"dev"}

# Health Check 엔드포인트 테스트
curl http://localhost:8000/api/edustack/health
첫 번째 문제 발생: SQLAlchemy 2.0 호환성 문제로 "SELECT 1" 쿼리에서 오류 발생. text() 함수 추가로 해결했습니다.

⚙️ 환경별 설정 관리

🔹 1. 환경별 설정 파일 생성

config/dev.env

ENVIRONMENT=dev
DATABASE_URL=postgresql://edustack:edustack123@postgres:5432/edustack
REDIS_URL=redis://redis:6379/0
DEBUG=true
LOG_LEVEL=debug
API_PREFIX=/api/edustack

config/stg.env

ENVIRONMENT=stg
DATABASE_URL=postgresql://edustack:edustack123@host.docker.internal:5432/edustack
REDIS_URL=redis://host.docker.internal:6379/1
DEBUG=false
LOG_LEVEL=info
API_PREFIX=/api/edustack

🔹 2. 환경별 시작 스크립트 작성

backend/start.sh

#!/bin/bash

ENV=${1:-dev}
CONFIG_FILE="config/${ENV}.env"

if [ ! -f "$CONFIG_FILE" ]; then
    echo "❌ Config file not found: $CONFIG_FILE"
    exit 1
fi

echo "🚀 Starting EduStack API in $ENV environment..."
export $(cat $CONFIG_FILE | xargs)

if [ "$DEBUG" = "true" ]; then
    uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
else
    uvicorn app.main:app --host 0.0.0.0 --port 8000
fi

🔹 3. 설정 테스트

환경별 실행 테스트

# Development 환경으로 실행
./start.sh dev

# Staging 환경으로 실행  
./start.sh stg

# 환경 확인
curl http://localhost:8000/
# dev: {"environment":"dev","debug":true}
# stg: {"environment":"stg","debug":false}

🧪 Staging 환경 구축

🔹 1. Docker Desktop Kubernetes 활성화

Kubernetes 컨텍스트 확인

# 현재 컨텍스트 확인
kubectl config get-contexts

# Docker Desktop Kubernetes로 전환
kubectl config use-context docker-desktop

# 클러스터 정보 확인
kubectl cluster-info
# 결과: Kubernetes control plane is running at https://127.0.0.1:6443

🔹 2. Kubernetes 매니페스트 작성

k8s/local/namespace.yaml

apiVersion: v1
kind: Namespace
metadata:
  name: edustack-staging
  labels:
    environment: staging

k8s/local/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: edustack-backend
  namespace: edustack-staging
spec:
  replicas: 1
  selector:
    matchLabels:
      app: edustack-backend
  template:
    metadata:
      labels:
        app: edustack-backend
    spec:
      containers:
      - name: backend
        image: edustack-backend:staging
        imagePullPolicy: Never
        ports:
        - containerPort: 8000
        env:
        - name: APP_ENV
          value: "stg"

🔹 3. Staging 배포 테스트

이미지 빌드 및 배포

# 로컬용 이미지 빌드
docker build -t edustack-backend:staging ./backend

# Kubernetes 리소스 배포
kubectl apply -f k8s/local/

# 배포 상태 확인
kubectl get pods -n edustack-staging
# NAME                               READY   STATUS    RESTARTS   AGE
# edustack-backend-xxx-xxx           1/1     Running   0          30s

# 서비스 확인
kubectl get svc -n edustack-staging
# NAME               TYPE           EXTERNAL-IP   PORT(S)
# edustack-service   LoadBalancer   localhost     80:31520/TCP

🔹 4. 포트 포워딩 설정

로컬 접근 설정

# 포트 포워딩 설정
kubectl port-forward svc/edustack-service 8080:80 -n edustack-staging &

# API 테스트
curl http://localhost:8080/api/edustack/health
# 결과: {"status":"healthy","environment":"stg"}

🚀 Production 환경 배포

🔹 1. AWS EKS 컨텍스트 전환

EKS 클러스터 연결

# EKS 컨텍스트로 전환
kubectl config use-context arn:aws:eks:ap-northeast-2:463941703341:cluster/shr-prd-edustack-cluster-eks

# 클러스터 정보 확인
kubectl cluster-info
# 결과: Kubernetes control plane is running at https://EE9BEF57D98426681FCCB02377470C40.gr7.ap-northeast-2.eks.amazonaws.com

# 노드 아키텍처 확인
kubectl get nodes -o wide
# NAME                                               ARCH
# ip-192-168-8-22.ap-northeast-2.compute.internal   x86_64

🔹 2. 아키텍처 호환성 문제 발견

문제 발생: ImagePullBackOff 오류 지속 발생

원인 분석

# 로컬 시스템 아키텍처 확인
uname -m
# 결과: arm64 (Apple Silicon)

# EKS 노드 아키텍처 확인
kubectl get nodes -o wide
# 결과: x86_64

# 문제: ARM64로 빌드된 이미지를 x86_64 노드에서 실행 불가

🔹 3. ECR 리포지토리 생성 및 멀티 아키텍처 빌드

ECR 리포지토리 생성

# ECR 리포지토리 생성
aws ecr create-repository \
    --repository-name edustack-backend \
    --region ap-northeast-2 \
    --profile shared-service

# ECR 로그인
aws ecr get-login-password --region ap-northeast-2 --profile shared-service | \
    docker login --username AWS --password-stdin 463941703341.dkr.ecr.ap-northeast-2.amazonaws.com

x86_64 아키텍처용 이미지 빌드

# 멀티 아키텍처 빌드
docker buildx build --platform linux/amd64 \
    -t 463941703341.dkr.ecr.ap-northeast-2.amazonaws.com/edustack-backend:amd64 \
    ./backend --push

# 빌드 성공 확인
# The push refers to repository [463941703341.dkr.ecr.ap-northeast-2.amazonaws.com/edustack-backend]
# latest: digest: sha256:742ce43eea17214d8492bf0aa0e8cc80301bb1826b2d48b9d2b6b3cf7d838a30 size: 856

🔹 4. Production 배포 및 테스트

Production 매니페스트 배포

# Production 네임스페이스 및 서비스 생성
kubectl apply -f k8s/production/

# 배포 상태 확인
kubectl get pods -n edustack-production
# NAME                                READY   STATUS    RESTARTS   AGE
# edustack-backend-69757d795-f6ckl    1/1     Running   0          2m

# LoadBalancer 서비스 확인
kubectl get svc -n edustack-production
# NAME               TYPE           EXTERNAL-IP                     PORT(S)
# edustack-service   LoadBalancer   ae5ba96fbdbd645c0...elb.amazonaws.com   80:30234/TCP

🔍 트러블슈팅 과정

🔹 1. SQLAlchemy 2.0 호환성 문제

문제 상황

# 오류 메시지
sqlalchemy.exc.ArgumentError: Textual SQL expression 'SELECT 1' should be explicitly declared as text('SELECT 1')

해결 방법

# health.py 수정 전
db.execute("SELECT 1")

# health.py 수정 후
from sqlalchemy import text
db.execute(text("SELECT 1"))

🔹 2. 데이터베이스 마이그레이션 문제

문제 상황

# 오류 메시지
sqlalchemy.exc.ProgrammingError: relation "content" does not exist

해결 과정

# Alembic 초기화
docker-compose exec backend alembic init alembic

# 설정 파일 수정
sed -i 's|sqlalchemy.url = driver://user:pass@localhost/dbname|sqlalchemy.url = postgresql://edustack:edustack123@postgres:5432/edustack|' alembic.ini

# 마이그레이션 생성 및 적용
docker-compose exec backend alembic revision --autogenerate -m "Initial migration"
docker-compose exec backend alembic upgrade head

🔹 3. 테이블명 불일치 문제

문제 발견

# 데이터베이스 테이블 확인
docker-compose exec postgres psql -U edustack -d edustack -c "\dt"
#               List of relations
#  Schema |      Name       | Type  |  Owner   
# --------+-----------------+-------+----------
#  public | contents        | table | edustack  # 복수형으로 생성됨
#  public | page_views      | table | edustack

코드 수정

# analytics.py 수정
# 수정 전: UPDATE content SET ...
# 수정 후: UPDATE contents SET ...

🔹 4. 환경별 이미지 태깅 전략

최종 배포 스크립트

#!/bin/bash
ENV=${1:-dev}

case $ENV in
  "dev")
    docker-compose -f docker-compose.dev.yml up --build
    ;;
  "stg")
    docker build -t edustack-backend:staging ./backend
    kubectl apply -f k8s/local/
    kubectl port-forward svc/edustack-service 8080:80 -n edustack-staging &
    ;;
  "prd")
    GIT_HASH=$(git rev-parse --short HEAD)
    TIMESTAMP=$(date +%Y%m%d-%H%M%S)
    PROD_TAG="${TIMESTAMP}-${GIT_HASH}"
    
    docker buildx build --platform linux/amd64 \
      -t ${ECR_REGISTRY}/${IMAGE_NAME}:${PROD_TAG} \
      ./backend --push
    
    kubectl apply -f k8s/production/
    ;;
esac

🔹 5. 최종 테스트 결과

전체 환경 테스트

# Development 환경
./scripts/deploy.sh dev
curl http://localhost:8000/api/edustack/health
# ✅ {"status":"healthy","environment":"dev"}

# Staging 환경  
./scripts/deploy.sh stg
curl http://localhost:8080/api/edustack/health
# ✅ {"status":"healthy","environment":"stg"}

# Production 환경
./scripts/deploy.sh prd
curl http://ae5ba96fbdbd645c0...elb.amazonaws.com/api/edustack/health
# ✅ {"status":"healthy","environment":"prd"}

🎓 핵심 학습 포인트

  • 환경별 설정 분리: 복잡한 동적 설정보다 간단한 파일 분리가 효과적
  • 아키텍처 호환성: 로컬(ARM64)과 프로덕션(x86_64) 차이 반드시 고려
  • 단계별 검증: 각 환경에서 철저한 테스트 후 다음 단계 진행
  • 문제 해결 과정: 로그 확인 → 원인 분석 → 해결책 적용 → 재테스트
  • 자동화의 중요성: 반복 작업은 스크립트로 자동화하여 실수 방지
초기 설정
Development
설정 관리
Staging
Production
트러블슈팅