실제 파일 수정과 테스트 과정을 단계별로 따라하기 - 트러블슈팅 포함
# 프로젝트 루트 디렉토리 생성
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
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
}
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:
# 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
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
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
#!/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
# Development 환경으로 실행
./start.sh dev
# Staging 환경으로 실행
./start.sh stg
# 환경 확인
curl http://localhost:8000/
# dev: {"environment":"dev","debug":true}
# stg: {"environment":"stg","debug":false}
# 현재 컨텍스트 확인
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
apiVersion: v1
kind: Namespace
metadata:
name: edustack-staging
labels:
environment: staging
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"
# 로컬용 이미지 빌드
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
# 포트 포워딩 설정
kubectl port-forward svc/edustack-service 8080:80 -n edustack-staging &
# API 테스트
curl http://localhost:8080/api/edustack/health
# 결과: {"status":"healthy","environment":"stg"}
# 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
# 로컬 시스템 아키텍처 확인
uname -m
# 결과: arm64 (Apple Silicon)
# EKS 노드 아키텍처 확인
kubectl get nodes -o wide
# 결과: x86_64
# 문제: ARM64로 빌드된 이미지를 x86_64 노드에서 실행 불가
# 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
# 멀티 아키텍처 빌드
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
# 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
# 오류 메시지
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"))
# 오류 메시지
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
# 데이터베이스 테이블 확인
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 ...
#!/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
# 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"}