Docker to K8s 네트워크 아키텍처 구성
실제 Docker에서 EKS로 마이그레이션 프로젝트를 진행하면서 아키텍처 다이어그램을 그리는 과정에서 여러 시행착오를 겪었습니다. 특히 ALB의 위치, VPC 서비스 구분, HA 구성 등에서 혼란이 있었고, 이런 과정을 통해 얻은 인사이트를 공유하고자 합니다.
# docker-compose.yml
version: '3.8'
services:
postgres:
image: postgres:15
# ❌ 단일 컨테이너, 재시작 시 데이터 손실
redis:
image: redis:7-alpine
# ❌ 단일 컨테이너, 메모리 데이터만
backend:
build: ./backend
# ❌ 단일 컨테이너, 로드밸런싱 없음
infrastructure/k8s/
├── namespace.yaml # 네임스페이스 분리
├── postgres-deployment.yaml # StatefulSet + Anti-Affinity
├── redis-statefulset.yaml # StatefulSet + Anti-Affinity
├── backend-deployment.yaml # Deployment + Topology Spread
├── backend-service.yaml # LoadBalancer Service
└── ingress.yaml # ALB Ingress
# ❌ 기존: docker-compose.yml (1개 파일)
services:
postgres: ...
redis: ...
backend: ...
# ✅ 변환: K8s manifests (6개 파일)
postgres-deployment.yaml # Deployment
backend-deployment.yaml # Deployment
backend-service.yaml # Service
# ❌ 문제: 단일 인스턴스
spec:
replicas: 1
# ✅ 해결: Multi-AZ 분산
spec:
replicas: 2
affinity:
podAntiAffinity: ...
# ❌ 문제: Deployment (Stateless)
kind: Deployment
# ✅ 해결: StatefulSet (Stateful)
kind: StatefulSet
volumeClaimTemplates: ...
# ❌ 문제: 외부 접근 불가
type: ClusterIP
# ✅ 해결: ALB Ingress 추가
kind: Ingress
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
| 단계 | 파일 수 | 설정 복잡도 | 가용성 | 확장성 |
|---|---|---|---|---|
| Docker Compose | 1개 | 낮음 ⭐ | 낮음 ❌ | 낮음 ❌ |
| 기본 K8s | 3개 | 중간 ⭐⭐ | 중간 ⚠️ | 중간 ⚠️ |
| HA K8s | 6개 | 높음 ⭐⭐⭐ | 높음 ✅ | 높음 ✅ |
Deployment → StatefulSetreplicas: 1 → replicas: 2(없음) → podAntiAffinity(없음) → volumeClaimTemplates
(없음) → redis-statefulset.yaml(없음) → replicas: 2(없음) → podAntiAffinity(없음) → persistence 설정
Deployment → Deployment (유지)replicas: 2 → replicas: 2 (유지)(없음) → topologySpreadConstraints(없음) → REDIS_URL 환경변수
실제 EduStack 프로젝트에서 아키텍처 다이어그램을 그리면서 v02부터 v10까지 9번의 수정을 거쳤습니다. 각 버전마다 발견한 문제점과 개선 과정을 단계별로 살펴보겠습니다.
💡 v01은 너무 엉망이라 보이지도 않고, v05는 버전 관리 실수로 빈 파일입니다 😅
✅ 요구사항
- 기존 S3 정적 사이트 유지 (/aiseminar/*)
- 새로운 동적 API 추가 (/api/edustack/*)
- CloudFront로 통합 라우팅
- EKS에서 FastAPI + PostgreSQL + Redis 운영
- 고가용성(HA) 구성
첫 시도: 기본적인 구조는 있지만 뭔가 부족해...
조금 더 구체화했지만 여전히 혼란스러운 상태
조금씩 나아지고 있지만 아직 완성도가...
드디어 체계적인 구조가 잡히기 시작!
사용자 → CloudFront → ALB → EKS Pods
→ S3 (정적 콘텐츠)
VPC 서비스 구분의 중요성을 깨달음:
VPC 외부 서비스 (글로벌/리전):
VPC 내부 서비스 (VPC 종속):
트래픽 라우팅 경로 명확화:
정적 콘텐츠: 사용자 → Internet → WAF → CloudFront → S3
동적 API: 사용자 → Internet → WAF → CloudFront → VPC → ALB → EKS Pods
네트워크 보안 계층:
기존 문제점:
Private Subnet 1: FastAPI + PostgreSQL
Private Subnet 2: FastAPI + Redis
HA 구성 해결책:
Private Subnet 1 (AZ-2a): FastAPI + PostgreSQL + Redis
Private Subnet 2 (AZ-2c): FastAPI + PostgreSQL + Redis
HA 설계 원칙:
ALB와 NAT Gateway를 퍼블릭 서브넷에 올바르게 배치
ALB를 단일 논리적 엔티티로 표현하면서 Multi-AZ 노드 표시
각 AZ에 동일한 Pod 구성으로 진정한 HA 달성
🎉 완성! VPC 내부/외부 서비스가 명확히 구분된 최종 아키텍처
🌐 VPC 외부: S3, CloudFront, WAF
🏠 VPC 내부: ALB, NAT Gateway, EKS
사용자 → Internet → WAF → CloudFront → S3
사용자 → Internet → WAF → CloudFront → VPC → ALB → EKS Pods
Private Subnet 1: FastAPI + PostgreSQL
Private Subnet 2: FastAPI + Redis
Private Subnet 1 (AZ-2a): FastAPI + PostgreSQL + Redis
Private Subnet 2 (AZ-2c): FastAPI + PostgreSQL + Redis
# 현재 상태 확인
kubectl get pods -o wide
# 결과: 모든 Pod가 같은 노드에 몰려있음
📱 FastAPI (웹 서버) → Deployment (Stateless)
"요청 처리만 하고 끝, 메모리에 저장 안 함"
💾 PostgreSQL (데이터베이스) → StatefulSet (Stateful)
"데이터 파일 저장, 순서대로 시작/종료 필요"
🗄️ Redis (캐시) → StatefulSet (Stateful)
"메모리 데이터 보존 필요"
# edustack 프로젝트 구조
infrastructure/k8s/
├── namespace.yaml
├── postgres-deployment.yaml # ← 이 파일을 수정
├── backend-deployment.yaml # ← 이 파일을 수정
└── backend-service.yaml
postgres-deployment.yaml → StatefulSet으로 변경 ✅redis-statefulset.yaml 새로 생성 ← 현재 단계backend-deployment.yaml에 Topology Spread 추가infrastructure/k8s/postgres-deployment.yaml
apiVersion: apps/v1
kind: Deployment # ❌ Stateless
metadata:
name: postgres
spec:
replicas: 1 # ❌ 단일 인스턴스
selector:
matchLabels:
app: postgres
template:
spec:
# ❌ Anti-Affinity 없음
containers:
- name: postgres
image: postgres:15
# ❌ 영구 스토리지 없음
apiVersion: apps/v1
kind: StatefulSet # ✅ Stateful
metadata:
name: postgres
spec:
serviceName: postgres # ✅ StatefulSet 필수
replicas: 2 # ✅ HA 구성
selector:
matchLabels:
app: postgres
template:
spec:
affinity: # ✅ Anti-Affinity 추가
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- postgres
topologyKey: topology.kubernetes.io/zone
containers:
- name: postgres
image: postgres:15
volumeMounts: # ✅ 영구 스토리지
- name: postgres-storage
mountPath: /var/lib/postgresql/data
volumeClaimTemplates: # ✅ 각 Pod마다 독립 스토리지
- metadata:
name: postgres-storage
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
infrastructure/k8s/redis-statefulset.yaml (새 파일)
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis
namespace: edustack
spec:
serviceName: redis
replicas: 2 # ✅ HA 구성
template:
spec:
affinity: # ✅ Anti-Affinity
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- redis
topologyKey: topology.kubernetes.io/zone
containers:
- name: redis
image: redis:7-alpine
command: ["redis-server"]
args: ["--appendonly", "yes"] # ✅ 데이터 영속성
volumeMounts:
- name: redis-storage
mountPath: /data
resources: # ✅ 리소스 제한
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "200m"
volumeClaimTemplates: # ✅ 영구 스토리지
- metadata:
name: redis-storage
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 5Gi
사용자 로그인 → 세션 정보를 Redis에 저장
API 응답 → 자주 조회되는 데이터를 Redis에 캐시
Anti-Affinity: "절대 같은 곳에 있으면 안 돼!" (엄격함)
Topology Spread: "가능하면 고르게 분산해줘" (유연함)
Service (ClusterIP): 클러스터 내부에서만 접근 가능
postgres:5432 ← FastAPI에서만 접근
Ingress: 외부 인터넷에서 접근 가능
https://your-domain.com/api → FastAPI로 라우팅
/api/* → FastAPI, /static/* → S3infrastructure/k8s/ingress.yaml (새 파일)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: edustack-ingress
namespace: edustack
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internet-facing # ✅ 외부 접근
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/ssl-redirect: '443' # ✅ HTTPS 강제
alb.ingress.kubernetes.io/healthcheck-path: /health # ✅ 헬스체크
spec:
rules:
- http:
paths:
- path: /api/edustack # ✅ API 경로 라우팅
pathType: Prefix
backend:
service:
name: edustack-backend-service
port:
number: 80
- path: /health # ✅ 헬스체크 엔드포인트
pathType: Exact
backend:
service:
name: edustack-backend-service
port:
number: 80
인터넷 → ALB → FastAPI Service → FastAPI Pod (2개)https://your-domain.com/api/edustack → 자동 로드밸런싱
infrastructure/k8s/backend-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: edustack-backend
spec:
replicas: 2 # ❌ 적은 인스턴스
template:
spec:
# ❌ 분산 정책 없음 → 한 곳에 몰릴 수 있음
containers:
- name: backend
env:
- name: DATABASE_URL
value: "postgresql://..."
# ❌ Redis 연결 없음
apiVersion: apps/v1
kind: Deployment
metadata:
name: edustack-backend
spec:
replicas: 2 # ✅ 적절한 인스턴스 수
template:
spec:
topologySpreadConstraints: # ✅ 균등 분산 정책
- maxSkew: 1 # ✅ AZ간 최대 1개 차이
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: edustack-backend
- maxSkew: 1 # ✅ 노드간 분산
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: edustack-backend
containers:
- name: backend
env:
- name: DATABASE_URL
value: "postgresql://..."
- name: REDIS_URL # ✅ Redis 연결 추가
value: "redis://redis:6379"
2개 Pod → 2개 AZ → 각 AZ마다 1개씩 균등 배치AZ-2a: [Pod1] | AZ-2c: [Pod2]
topologyKey: topology.kubernetes.io/zone # AZ별로 분산
maxSkew: 1 # AZ 간 Pod 개수 차이를 1개 이하로 제한
alb.ingress.kubernetes.io/subnets: subnet-xxx,subnet-yyy
# 1. Pod가 다른 AZ에 분산되었는지 확인
kubectl get pods -o wide
# 2. ALB가 정상 동작하는지 확인
kubectl get ingress
kubectl describe ingress edustack-ingress
# 3. 서비스 간 통신 테스트
kubectl exec -it fastapi-pod -- curl postgresql-service:5432
# 4. HA 테스트 (한 AZ 장애 시뮬레이션)
kubectl cordon node-in-az-2a
kubectl get pods -o wide # 다른 AZ로 이동했는지 확인
이 가이드에서는 학습 목적으로 PostgreSQL과 Redis를 Pod로 운영했지만, 실제 상용 서비스에서는 AWS 관리형 서비스 사용을 강력히 권장합니다!
EKS 클러스터 (애플리케이션만)
├── FastAPI Pods (Stateless)
└── 관리형 서비스 연결
├── RDS PostgreSQL (Multi-AZ)
└── ElastiCache Redis (Cluster Mode)
# RDS PostgreSQL 연결
DATABASE_URL=postgresql://username:password@rds-endpoint.region.rds.amazonaws.com:5432/dbname
# ElastiCache Redis 연결
REDIS_URL=redis://elasticache-endpoint.cache.amazonaws.com:6379
# FastAPI 환경변수 설정
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
DATABASE_URL: "postgresql://username:password@prod-db.region.rds.amazonaws.com:5432/edustack"
REDIS_URL: "redis://prod-cache.cache.amazonaws.com:6379"
| 구분 | Pod 방식 | 관리형 서비스 |
|---|---|---|
| 비용 | 💰 저렴 (컴퓨팅만) | 💰💰 비쌈 (관리 비용 포함) |
| 안정성 | ⚠️ 직접 관리 필요 | ✅ AWS가 보장 |
| 운영 부담 | 😰 높음 (백업, 패치, 모니터링) | 😌 낮음 (AWS가 처리) |
| 추천 환경 | 🧪 개발/테스트 | 🚀 프로덕션 |
이제 여러분은 실제 운영 환경에서 사용할 수 있는 HA Kubernetes 아키텍처를 설계할 수 있습니다. 이 과정에서 겪은 시행착오들이 실무에서 큰 도움이 될 것입니다!