← 메인으로 돌아가기

🐳 Docker부터 EKS까지

개인→팀→엔터프라이즈 3단계 진화 과정 - 컨테이너 기술 실습

📚 목차

🛠️ 실습 전 준비사항

🖥️ 터미널 환경 설정 (권장)

# 터미널 2개 사용 권장
터미널 1: Docker 명령어 실행용
터미널 2: 가이드 확인, 메모 작성용

# macOS에서 터미널 분할
Command + D (세로 분할)
Command + Shift + D (가로 분할)
🖥️ 터미널 분리의 장점:
  • 실습 중 문제 발생 시 가이드 참조 가능
  • Docker 명령어 실행 중에도 다른 작업 병행
  • 세션 안전성 확보 (한 터미널 문제가 다른 터미널에 영향 없음)
⚠️ 문제 발생 시 대처법:
  • 터미널 1에서 Ctrl+C로 명령어 중단
  • 새 터미널에서 docker ps -a로 상태 확인
  • 필요시 docker stop <container_name>으로 정리

💻 개인 개발 환경 구축 로그

🐳 Docker 기본 개념

핵심 용어 이해

  • 이미지 (Image): 실행 파일 같은 개념, 앱 + 환경을 패키징한 템플릿
  • 컨테이너 (Container): 이미지를 실행한 인스턴스, 실제로 돌아가는 앱
  • 포트 매핑: 컨테이너 내부 포트를 호스트 포트로 연결 (-p 8000:8000)
  • 볼륨: 컨테이너와 호스트 간 데이터 공유 공간

개인 개발에서 Docker를 쓰는 이유

❌ 기존 방식
  • PostgreSQL 로컬 설치 필요
  • 버전 충돌 문제
  • 환경 설정 복잡
  • 프로젝트별 격리 어려움
✅ Docker 방식
  • 필요한 서비스만 컨테이너로 실행
  • 버전 관리 쉬움
  • 한 줄 명령어로 실행
  • 프로젝트별 격리

기본 Docker 명령어

# 이미지 관련
docker pull postgres:15        # 이미지 다운로드
docker images                  # 로컬 이미지 목록
docker rmi <image_name>        # 이미지 삭제

# 컨테이너 관련  
docker run                     # 컨테이너 실행
docker ps                      # 실행 중인 컨테이너 목록
docker ps -a                   # 모든 컨테이너 목록
docker stop <container_name>   # 컨테이너 중지
docker rm <container_name>     # 컨테이너 삭제

# 유용한 옵션들
-d                            # 백그라운드 실행
-p 8000:8000                  # 포트 매핑 (호스트:컨테이너)
--name my-app                 # 컨테이너 이름 지정
-e POSTGRES_PASSWORD=pass     # 환경변수 설정

🎯 목표: 내 MacBook에서 PostgreSQL + FastAPI 실행

Step 1: Docker Desktop 설치 (10분)

💡 Docker Desktop이란?
  • Docker Engine: 실제 컨테이너를 실행하는 핵심 엔진
  • GUI 관리 도구: 컨테이너, 이미지를 시각적으로 관리
  • 필수 구성요소: Docker CLI, Docker Compose 포함
  • 중요: 앱이 실행되어야 Docker 명령어 사용 가능
# 2025-10-02 11:00 시작
$ brew install --cask docker
# 또는 https://docker.com/products/docker-desktop 에서 다운로드

# 설치 확인
$ docker --version
Docker version 20.10.12, build e91ed57

# ⚠️ 중요: Docker Desktop 앱 실행 필요
$ open -a Docker
# 또는 Applications에서 Docker Desktop 실행
# 상단 메뉴바에 Docker 아이콘이 나타날 때까지 대기 (약 30초)

# Docker daemon 실행 확인
$ docker run hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.

# 만약 "Cannot connect to the Docker daemon" 에러가 나면:
# 1. Docker Desktop이 실행되었는지 확인
# 2. 상단 메뉴바에 Docker 아이콘 확인
# 3. 30초 정도 대기 후 재시도
결과: Docker 정상 설치 완료

Step 2: PostgreSQL 컨테이너 실행 및 데이터베이스 테스트 (10분)

💡 PostgreSQL 컨테이너 실행 이해하기
  • -d: 백그라운드에서 실행 (detached mode)
  • --name: 컨테이너에 이름 부여 (my-postgres)
  • -e: 환경변수 설정 (비밀번호, 데이터베이스명)
  • -p 5432:5432: 포트 매핑 (로컬5432 → 컨테이너5432)
  • postgres:15: 사용할 이미지와 버전
# PostgreSQL 컨테이너 실행
$ docker run -d \
  --name my-postgres \
  -e POSTGRES_PASSWORD=mypassword \
  -e POSTGRES_DB=testdb \
  -p 5432:5432 \
  postgres:15

# 실행 확인
$ docker ps
CONTAINER ID   IMAGE         COMMAND                  STATUS
abc123def456   postgres:15   "docker-entrypoint.s…"   Up 2 minutes

# ⚠️ 중요: 페이징 모드 문제 해결
# 테이블 목록 조회 시 페이징 모드로 인해 멈출 수 있음
# 해결책: \pset pager off 옵션 사용

# 테스트 테이블 생성
$ docker exec -it my-postgres psql -U postgres -d testdb -c "CREATE TABLE users (id SERIAL PRIMARY KEY, name VARCHAR(50), email VARCHAR(100));"
CREATE TABLE

# 테스트 데이터 삽입
$ docker exec -it my-postgres psql -U postgres -d testdb -c "INSERT INTO users (name, email) VALUES ('테스트유저', 'test@example.com'), ('관리자', 'admin@example.com');"
INSERT 0 2

# 데이터 조회 (페이징 끄고)
$ docker exec -it my-postgres psql -U postgres -d testdb -c "\pset pager off" -c "SELECT * FROM users;"
Pager usage is off.
 id |    name    |       email
----+------------+-------------------
  1 | 테스트유저 | test@example.com
  2 | 관리자     | admin@example.com
(2 rows)

# 테이블 목록 확인 (페이징 끄고)
$ docker exec -it my-postgres psql -U postgres -d testdb -c "\pset pager off" -c "\dt"
Pager usage is off.
         List of relations
 Schema | Name  | Type  |  Owner
--------+-------+-------+----------
 public | users | table | postgres
(1 row)
💡 PostgreSQL 접속 시 주의사항:
  • 페이징 모드 문제: 테이블이 많거나 결과가 길면 페이징 모드로 전환되어 testdb# 프롬프트에서 멈춤
  • 해결 방법: -c "\pset pager off" 옵션으로 페이징 비활성화
  • 안전한 종료: \q + Enter 또는 Ctrl+D
  • 페이징 모드에서 탈출: q 누른 후 \q
결과: PostgreSQL 컨테이너 정상 실행, 테스트 테이블 생성 및 데이터 조회 성공

Step 3: 간단한 FastAPI 앱 컨테이너화 (15분)

🎯 이 단계에서 하는 일

PostgreSQL과 연결되는 간단한 웹 API를 만들어서 Docker 컨테이너로 실행해보겠습니다. 실제 개발에서 자주 사용하는 패턴입니다.

💡 애플리케이션 컨테이너화 과정
  • 1단계: 앱 코드 작성 (main.py) - 실제 웹 API 코드
  • 2단계: 의존성 정의 (requirements.txt) - 필요한 라이브러리 목록
  • 3단계: 실행 환경 정의 (Dockerfile) - 컨테이너 설정
  • 4단계: 이미지 빌드 → 컨테이너 실행
🔗 컨테이너 간 통신
  • host.docker.internal: 컨테이너에서 호스트 접근 시 사용
  • localhost: 컨테이너 내부에서만 유효
  • 포트 매핑: 외부에서 컨테이너 접근을 위해 필요

🤔 왜 FastAPI를 사용하나요?

  • 빠른 개발: 몇 줄로 웹 API 완성
  • 자동 문서화: /docs 페이지 자동 생성
  • 타입 안전성: Python 타입 힌트 지원
  • 비동기 처리: 높은 성능
# 프로젝트 디렉토리 생성
$ mkdir docker-test && cd docker-test

# 간단한 FastAPI 앱 작성
$ cat > main.py << 'EOF'
from fastapi import FastAPI
import psycopg2

app = FastAPI()

@app.get("/")
def read_root():
    return {"message": "Hello Docker!"}

@app.get("/db-test")
def test_db():
    try:
        conn = psycopg2.connect(
            host="host.docker.internal",
            database="testdb",
            user="postgres",
            password="mypassword"
        )
        return {"db_status": "connected"}
    except Exception as e:
        return {"db_status": "error", "detail": str(e)}
EOF

# requirements.txt 작성
$ cat > requirements.txt << 'EOF'
fastapi==0.104.1
uvicorn==0.24.0
psycopg2-binary==2.9.9
EOF

# Dockerfile 작성
$ cat > Dockerfile << 'EOF'
FROM python:3.11-slim

WORKDIR /app

# 시스템 의존성 설치
RUN apt-get update && apt-get install -y \
    gcc \
    && rm -rf /var/lib/apt/lists/*

# Python 의존성 설치
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 앱 코드 복사
COPY . .

# 포트 노출
EXPOSE 8000

# 앱 실행
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
EOF

📝 각 파일의 역할 이해하기

🐍 main.py (애플리케이션 코드)
  • FastAPI 앱: 웹 API 서버 역할
  • 두 개의 엔드포인트:
    • / → "Hello Docker!" 메시지 반환
    • /db-test → PostgreSQL 연결 테스트
  • host.docker.internal: 컨테이너에서 호스트 PostgreSQL 접근
📦 requirements.txt (의존성 목록)
  • fastapi==0.104.1: 웹 프레임워크
  • uvicorn==0.24.0: ASGI 서버 (FastAPI 실행용)
  • psycopg2-binary==2.9.9: PostgreSQL 연결 드라이버

왜 버전을 고정하나? 팀원들이 모두 같은 버전을 사용하도록

🐳 Dockerfile (컨테이너 설정)
  • FROM python:3.11-slim: 기본 Python 이미지
  • WORKDIR /app: 컨테이너 내 작업 디렉토리
  • RUN apt-get install gcc: psycopg2 컴파일용
  • COPY requirements.txt: 의존성 파일 복사
  • RUN pip install: 라이브러리 설치
  • COPY . .: 앱 코드 복사
  • CMD ["uvicorn"...]: 컨테이너 시작 시 실행할 명령어

Step 4: 이미지 빌드 및 실행 (10분)

💡 Docker 빌드 과정 이해
  • docker build: Dockerfile을 읽어서 이미지 생성
  • -t my-fastapi: 생성할 이미지에 태그(이름) 부여
  • 레이어 시스템: 각 명령어가 레이어로 쌓임 (캐싱 효과)
  • 컨텍스트: 현재 디렉토리(.)의 모든 파일이 빌드에 포함
# 이미지 빌드
$ docker build -t my-fastapi .
[+] Building 45.2s (9/9) FINISHED
 => [1/5] FROM docker.io/library/python:3.11-slim
 => [2/5] WORKDIR /app
 => [3/5] COPY requirements.txt .
 => [4/5] RUN pip install -r requirements.txt
 => [5/5] COPY . .
 => exporting to image

# 컨테이너 실행
$ docker run -d -p 8000:8000 --name my-app my-fastapi

# 테스트
$ curl http://localhost:8000/
{"message":"Hello Docker!"}

$ curl http://localhost:8000/db-test
{"db_status":"connected"}
결과: FastAPI 앱이 컨테이너에서 정상 실행, DB 연결 성공

💡 개인 개발 단계 인사이트

  • 장점: 로컬에 PostgreSQL 설치 없이 바로 개발 가능
  • 문제점: 컨테이너 재시작 시 데이터 손실
  • 해결책: 볼륨 마운트 필요 → 팀 프로젝트에서 해결

👥 팀 프로젝트로 전환

🔄 개인 → 팀: 무엇이 바뀌어야 하나?

❌ 개인 개발의 한계

  • 각자 다른 Docker 명령어 실행
  • 데이터베이스 설정 불일치
  • 환경변수 관리 어려움
  • 신입 개발자 온보딩 복잡

✅ 팀 프로젝트 해결책

  • docker-compose.yml로 통일
  • 환경변수 파일 공유
  • 볼륨으로 데이터 영속성
  • 한 번의 명령어로 전체 스택 실행

💡 Docker Compose 기본 개념

핵심 파일들과 역할

# 예약된 기본 파일명들 (Docker가 자동으로 찾음)
docker-compose.yml    # 서비스 정의 파일 (필수)
docker-compose.yaml   # yml과 동일 (선택)
.env                  # 환경변수 파일 (자동 로드)
Dockerfile           # 이미지 빌드 파일

# 서비스명은 자유롭게 설정 가능
services:
  postgres:     # 이름 자유 (db, database, pg 등 가능)
  redis:        # 이름 자유 (cache, memory 등 가능)  
  backend:      # 이름 자유 (api, app, web 등 가능)

팀 환경 통일의 핵심

✅ 올바른 팀 워크플로우
git clone <프로젝트>     # 1. 프로젝트 복제
cd <프로젝트>            # 2. 디렉토리 이동  
docker-compose up -d    # 3. 전체 환경 실행 (끝!)
❌ 잘못된 방법
  • 각자 다른 Docker 명령어 실행
  • 수동으로 환경변수 설정
  • 데이터베이스 개별 설치

Git에 포함되어야 할 파일들

  • docker-compose.yml - 서비스 정의 ✅
  • Dockerfile - 이미지 빌드 설정 ✅
  • requirements.txt - 의존성 목록 ✅
  • init.sql - 초기 데이터베이스 스키마 ✅
  • .env - 환경변수 (또는 .env.example) ✅

🎯 목표: 팀원 누구나 `docker-compose up`으로 동일한 환경

Step 1: docker-compose.yml 작성 (20분)

# 기존 컨테이너 정리
$ docker stop my-postgres my-app
$ docker rm my-postgres my-app

# docker-compose.yml 작성
$ cat > docker-compose.yml << 'EOF'
version: '3.8'

services:
  postgres:
    image: postgres:15
    environment:
      POSTGRES_DB: edustack
      POSTGRES_USER: team
      POSTGRES_PASSWORD: teampass123
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U team"]
      interval: 10s
      timeout: 5s
      retries: 5

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

  backend:
    build: .
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql://team:teampass123@postgres:5432/edustack
      - REDIS_URL=redis://redis:6379
    volumes:
      - .:/app
    depends_on:
      postgres:
        condition: service_healthy
    command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload

volumes:
  postgres_data:
  redis_data:
EOF

📝 docker-compose.yml 파일 이해하기

🐘 postgres 서비스
  • image: postgres:15: 공식 PostgreSQL 15 이미지 사용
  • environment: 데이터베이스 초기 설정 (DB명, 사용자, 비밀번호)
  • volumes:
    • postgres_data:/var/lib/postgresql/data → 데이터 영속성
    • ./init.sql:/docker-entrypoint-initdb.d/init.sql → 초기 데이터 자동 생성
  • healthcheck: 서비스 준비 상태 확인
🔴 redis 서비스
  • image: redis:7-alpine: 경량화된 Redis 이미지
  • volumes: Redis 데이터 영속성 보장
  • ports: 6379 포트로 외부 접근 가능
🚀 backend 서비스
  • build: .: 현재 디렉토리의 Dockerfile로 이미지 빌드
  • environment: 환경변수로 DB/Redis 연결 정보 전달
  • volumes: .:/app: 코드 변경 시 자동 반영 (개발 편의성)
  • depends_on: PostgreSQL이 준비된 후 시작
  • command: --reload 옵션으로 코드 변경 시 자동 재시작
💾 volumes 섹션
  • postgres_data: PostgreSQL 데이터 저장
  • redis_data: Redis 데이터 저장
  • 왜 필요한가? 컨테이너 재시작해도 데이터 보존
💡 docker-compose.yml 핵심 포인트:
  • services: 실행할 컨테이너들 정의
  • volumes: 데이터 영속성 보장 (컨테이너 재시작해도 데이터 유지)
  • depends_on: 서비스 시작 순서 제어
  • healthcheck: 서비스 준비 상태 확인

Step 2: 환경 설정 파일 추가 (10분)

# .env 파일 (팀 공유용)
$ cat > .env << 'EOF'
# Database
DB_HOST=postgres
DB_NAME=edustack
DB_USER=team
DB_PASSWORD=teampass123

# Redis
REDIS_HOST=redis
REDIS_PORT=6379

# App
DEBUG=true
LOG_LEVEL=info
EOF

# 초기 데이터베이스 스키마
$ cat > init.sql << 'EOF'
CREATE TABLE IF NOT EXISTS users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    email VARCHAR(100),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

INSERT INTO users (name, email) VALUES 
('팀장', 'leader@team.com'),
('개발자1', 'dev1@team.com'),
('개발자2', 'dev2@team.com');
EOF

📝 환경 설정 파일들의 역할

🔧 .env 파일 (환경변수 중앙 관리)
  • 목적: 팀원들이 공유하는 개발 환경 설정
  • 자동 로드: docker-compose가 자동으로 읽어서 적용
  • 보안: 실제 운영에서는 .env를 .gitignore에 추가
  • 일관성: 모든 팀원이 동일한 설정 사용
💡 실제 팀 프로젝트에서는
  • .env.example 파일을 Git에 커밋
  • 각자 .env 파일을 복사해서 사용
  • 민감한 정보는 각자 다르게 설정
🗄️ init.sql 파일 (데이터베이스 초기화)
  • 자동 실행: PostgreSQL 컨테이너 첫 시작 시 자동 실행
  • 팀 데이터: 모든 팀원이 동일한 테스트 데이터 사용
  • 스키마 통일: 테이블 구조를 팀원들과 동기화
  • 개발 편의성: 매번 수동으로 데이터 입력할 필요 없음
✅ 팀 프로젝트의 장점

신입 개발자가 git clonedocker-compose up 두 명령어만으로 완전한 개발 환경을 구축할 수 있습니다!

💡 환경 설정 파일의 역할:
  • .env: 환경변수 중앙 관리 (docker-compose가 자동 로드)
  • init.sql: PostgreSQL 컨테이너 시작 시 자동 실행되는 초기화 스크립트

Step 3: 팀 환경 실행 및 테스트 (5분)

# 전체 스택 실행
$ docker-compose up -d
Creating network "docker-test_default" with the default driver
Creating volume "docker-test_postgres_data" with local driver
Creating volume "docker-test_redis_data" with local driver
Creating docker-test_postgres_1 ... done
Creating docker-test_redis_1    ... done
Creating docker-test_backend_1  ... done

# 서비스 상태 확인
$ docker-compose ps
       Name                     Command               State           Ports         
---------------------------------------------------------------------------------
docker-test_backend_1    uvicorn main:app --host 0 ...   Up      0.0.0.0:8000->8000/tcp
docker-test_postgres_1   docker-entrypoint.sh postgres   Up      0.0.0.0:5432->5432/tcp
docker-test_redis_1      docker-entrypoint.sh redis ...   Up      0.0.0.0:6379->6379/tcp

# 데이터베이스 테스트
$ docker-compose exec postgres psql -U team -d edustack -c "\pset pager off" -c "SELECT * FROM users;"
Pager usage is off.
 id |   name   |     email      |         created_at         
----+----------+----------------+----------------------------
  1 | 팀장     | leader@team.com | 2025-10-03 06:07:47.747639
  2 | 개발자1  | dev1@team.com   | 2025-10-03 06:07:47.747639
  3 | 개발자2  | dev2@team.com   | 2025-10-03 06:07:47.747639

# API 테스트 - 첫 번째 시도
$ curl http://localhost:8000/db-test
{"db_status":"error","detail":"password authentication failed for user \"postgres\""}

# 🚨 문제 발견! FastAPI 앱이 아직 개인 개발 설정을 사용하고 있음
⚠️ 실제 경험: 개인→팀 전환 시 놓치기 쉬운 부분

docker-compose는 정상 실행되지만 API가 데이터베이스에 연결되지 않는 경우가 있습니다!

  • 원인: FastAPI 코드가 여전히 개인 개발 설정 사용
  • 해결: 데이터베이스 연결 정보를 팀 환경에 맞게 수정

🔧 FastAPI 코드 수정 필요

❌ 개인 개발 설정
conn = psycopg2.connect(
    host="host.docker.internal",
    database="testdb", 
    user="postgres",
    password="mypassword"
)
✅ 팀 프로젝트 설정
conn = psycopg2.connect(
    host="postgres",  # 서비스명 사용
    database="edustack",
    user="team", 
    password="teampass123"
)
💡 핵심 차이점
  • host: "host.docker.internal" → "postgres" (서비스명)
  • database: "testdb" → "edustack"
  • user/password: 팀 공통 계정 사용
# FastAPI 코드 수정 후 컨테이너 재시작
$ docker-compose restart backend

# 다시 API 테스트
$ curl http://localhost:8000/
{"message":"Hello Docker!"}

$ curl http://localhost:8000/db-test
{"db_status":"connected"}
결과: 팀 환경 구축 완료, 데이터 영속성 확보

💡 팀 프로젝트 단계 인사이트

  • 신입 온보딩: `git clone` → `docker-compose up` 끝
  • 환경 통일: 모든 팀원이 동일한 DB, Redis 버전 사용
  • 데이터 보존: 컨테이너 재시작해도 데이터 유지
  • 개발 효율: 코드 변경 시 자동 리로드

🔄 실제 팀 시나리오

신입 개발자 온보딩
$ git clone https://github.com/team/project
$ cd project
$ docker-compose up -d
# → 모든 팀원과 동일한 환경 완성!
기존 개발자 업데이트
$ git pull
$ docker-compose down
$ docker-compose up -d --build
# → 최신 변경사항 반영

🏢 엔터프라이즈로 확장

🔄 팀 → 엔터프라이즈: 무엇이 추가되어야 하나?

🎯 실제 경험: 왜 EKS가 필요한가?

팀 프로젝트에서 docker-compose로 잘 작동하던 것이 왜 EKS로 가야 할까요? 실제 운영 환경에서 발생하는 문제들을 해결하기 위해서입니다.

❌ 팀 프로젝트의 한계

  • 로컬 개발 환경에만 의존
  • 보안 정책 미적용
  • 프로덕션 배포 수동
  • 모니터링 부족
🚨 실제 문제 상황들
  • 서버 장애: 한 대 서버가 죽으면 서비스 중단
  • 트래픽 급증: 갑작스런 사용자 증가 시 대응 불가
  • 보안 감사: 기업 보안 정책 준수 어려움
  • 배포 실수: 수동 배포로 인한 휴먼 에러

✅ 엔터프라이즈 요구사항

  • 기업 거버넌스 준수
  • ECR 이미지 레지스트리
  • EKS 클러스터 배포
  • 보안 스캔 및 모니터링
✅ EKS가 해결하는 문제들
  • 고가용성: 여러 서버에 분산, 자동 복구
  • 자동 스케일링: 트래픽에 따라 자동 확장/축소
  • 보안 강화: AWS IAM, VPC, 보안 그룹
  • 운영 자동화: 배포, 모니터링, 로깅 자동화

💡 EKS 기본 개념

EKS란?

  • Elastic Kubernetes Service: AWS가 관리해주는 Kubernetes
  • Control Plane: AWS가 관리 (마스터 노드, 복잡한 설정 불필요)
  • Worker Nodes: 우리가 관리 (실제 앱이 돌아가는 EC2 인스턴스)
  • 관리형 서비스: 업그레이드, 패치, 백업을 AWS가 자동 처리
💡 쉽게 이해하기

EKS는 "여러 대의 서버를 하나처럼 관리해주는 시스템"입니다. docker-compose가 한 서버에서 여러 컨테이너를 관리한다면, EKS는 여러 서버에서 수백 개의 컨테이너를 관리합니다.

왜 EKS를 사용하나?

Docker Compose (팀 프로젝트)
  • 한 서버에서만 실행
  • 서버 죽으면 서비스 중단
  • 수동 스케일링
  • 단일 장애점 존재
EKS (엔터프라이즈)
  • 여러 서버에 분산 실행
  • 서버 죽어도 자동 복구
  • 자동 스케일링
  • 고가용성 보장

EKS 구성 요소

🎛️ Control Plane

AWS 완전 관리, API 서버, etcd, 스케줄러

🖥️ Worker Nodes

EC2 인스턴스, 실제 컨테이너 실행

🌐 네트워킹

VPC, 서브넷, 로드밸런서

🔐 보안

IAM 역할, 보안 그룹

✅ EKS 클러스터 생성 전체 로드맵

0

사전 준비 (10분)

  • eksctl 설치 및 확인
  • kubectl 설치 및 확인
  • AWS CLI 설정 확인
  • VPC/서브넷 확인
  • IAM 역할 확인
1

ECR 리포지토리 설정 (15분)

  • ECR 리포지토리 생성
  • Docker 이미지 빌드
  • ECR 푸시
2

EKS 클러스터 생성 (20-30분)

  • Control Plane 생성 (15분)
  • Worker Node 그룹 생성 (10분)
  • kubectl 설정 (2분)
3

PostgreSQL 배포 (10분)

  • 데이터베이스 배포
  • 서비스 설정
4

백엔드 애플리케이션 배포 (15분)

  • Kubernetes 매니페스트 작성
  • ECR 이미지로 배포
  • 로드밸런서 설정
5

테스트 및 확인 (10분)

  • 애플리케이션 접근 테스트
  • 스케일링 테스트
  • 비용 모니터링 설정

💡 예상 비용 분석 (1일 기준)

⚠️ 실제 운영 비용 고려사항

EKS는 편리하지만 비용이 발생합니다. 실제 프로젝트에서는 비용 대비 효과를 신중히 고려해야 합니다.

  • 소규모 프로젝트: docker-compose로도 충분할 수 있음
  • 대규모 서비스: EKS의 자동화 기능이 비용 절감 효과
  • 학습 목적: 실습 후 반드시 리소스 정리 필요
EKS Control Plane $0.10/시간 × 24시간 = $2.40
Worker Nodes (t3.medium × 2) $0.0416/시간 × 2 × 24시간 = $2.00
로드밸런서 $0.0225/시간 × 24시간 = $0.54
EBS 스토리지 $0.10/GB/월 × 20GB = $0.07
총 예상 비용 약 $5/일
💡 비용 주의사항
  • 생성 즉시 시간당 과금 시작
  • 실습 후 반드시 리소스 삭제 필요
  • 비용 모니터링 대시보드로 실시간 확인

🎯 목표: 기업 거버넌스 준수하며 EKS 배포

Step 0: 필수 도구 설치 가이드

💡 설치가 필요한 경우 아래 가이드 참고
# 1. eksctl 설치 (macOS)
$ brew tap weaveworks/tap
$ brew install weaveworks/tap/eksctl

# eksctl 직접 설치 (Homebrew 없는 경우)
$ curl --silent --location "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp
$ sudo mv /tmp/eksctl /usr/local/bin

# 2. kubectl 설치 (macOS)  
$ brew install kubectl

# kubectl 직접 설치
$ curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/darwin/amd64/kubectl"
$ chmod +x kubectl
$ sudo mv kubectl /usr/local/bin/

# 3. AWS CLI 설치
$ brew install awscli
# 또는
$ pip3 install awscli

# AWS CLI 설정
$ aws configure
AWS Access Key ID [None]: YOUR_ACCESS_KEY
AWS Secret Access Key [None]: YOUR_SECRET_KEY  
Default region name [None]: ap-northeast-2
Default output format [None]: json

Step 1: 사전 준비 확인 (10분)

💡 사전 준비 항목
  • eksctl: EKS 클러스터 생성 도구
  • kubectl: Kubernetes 명령어 도구
  • VPC/서브넷: 네트워크 환경 확인
  • IAM 권한: EKS 생성 권한 확인
# eksctl 설치 확인
$ eksctl version
0.147.0

# kubectl 설치 확인  
$ kubectl version --client
Client Version: v1.22.5

# AWS CLI 및 권한 확인
$ aws sts get-caller-identity
{
    "UserId": "AROAVP3ECGNLQBTNMJEH4:████████████████████",
    "Account": "████████████",
    "Arn": "arn:aws:sts::████████████:assumed-role/AWSReservedSSO_emart-architect-admin-sso-ps_████████████████/████████████████████"
}

# IAM 권한 확인
$ aws iam list-attached-role-policies --role-name AWSReservedSSO_emart-architect-admin-sso-ps_████████████████
{
    "AttachedPolicies": [
        {
            "PolicyName": "AdministratorAccess",
            "PolicyArn": "arn:aws:iam::aws:policy/AdministratorAccess"
        }
    ]
}

# 기존 VPC 확인
$ aws ec2 describe-vpcs --region ap-northeast-2
{
    "Vpcs": [
        {
            "VpcId": "vpc-█████████████████",
            "State": "available",
            "CidrBlock": "10.224.48.0/20",
            "Tags": [
                {
                    "Key": "Name",
                    "Value": "prd-emart-vpc"
                }
            ]
        }
    ]
}
✅ 사전 준비 완료 확인
  • [v] eksctl 및 kubectl 설치 완료
  • [v] AWS 자격증명 확인 (AdministratorAccess)
  • [v] VPC 환경 확인 (prd-emart-vpc)

Step 2: ECR 리포지토리 설정 (15분)

💡 ECR 리포지토리 이해
  • Private Registry: 기업 내부 전용 이미지 저장소
  • 보안 스캔: 취약점 자동 검사 (scanOnPush=true)
  • 수명 주기 정책: 오래된 이미지 자동 정리
  • IAM 통합: 세밀한 권한 제어
✅ 실제 회사 환경에서는
  • 공통 ECR 사용: 인프라팀이 관리하는 중앙 집중식 ECR
  • 권한 요청: 새 리포지토리 생성 시 승인 프로세스
  • 네이밍 정책: 회사 표준 네이밍 규칙 준수
  • 비용 최적화: 공유 리소스로 비용 절감
💡 ECR 로그인 이해하기
  • Username: 항상 "AWS" (고정값)
  • Password: AWS CLI가 생성하는 임시 토큰 (12시간 유효)
  • 매일 재로그인 필요: 보안상 장기간 유효한 패스워드 방지
  • 자동화: GitHub Actions에서 CI/CD 파이프라인으로 처리
📋 참고사항: 실제 계정 정보는 보안상 마스킹 처리되었습니다. 학습 목적으로 명령어와 결과는 실제 데이터를 기반으로 합니다.
# 🧪 실습용 ECR 생성 (테스트 목적)
$ aws ecr create-repository \
  --repository-name shr-dev-edustack-backend-ecr \
  --image-scanning-configuration scanOnPush=true \
  --tags '[
    {"Key":"ServiceName","Value":"edustack"},
    {"Key":"Environment","Value":"dev"},
    {"Key":"Owner","Value":"████"},
    {"Key":"Email","Value":"██████████████@emart.com"},
    {"Key":"Purpose","Value":"ai-education-platform"}
  ]'

# ✅ 실제 회사에서는 기존 ECR 사용
# 예시: ████████████.dkr.ecr.ap-northeast-2.amazonaws.com/company-shared-ecr

# ECR 로그인 (임시 토큰 방식)
$ aws ecr get-login-password --region ap-northeast-2 --profile shared-service | \
  docker login --username AWS --password-stdin \
  ████████████.dkr.ecr.ap-northeast-2.amazonaws.com
Login Succeeded

# 이미지 태그 및 푸시
$ docker tag my-fastapi:latest \
  ████████████.dkr.ecr.ap-northeast-2.amazonaws.com/shr-dev-edustack-backend-ecr:latest

$ docker push \
  ████████████.dkr.ecr.ap-northeast-2.amazonaws.com/shr-dev-edustack-backend-ecr:latest
The push refers to repository [████████████.dkr.ecr.ap-northeast-2.amazonaws.com/shr-dev-edustack-backend-ecr]
6044865a98da: Pushed
6ea2fa38e70e: Pushed
3b63a3e4f2b3: Pushed
1feeb1efc936: Pushed
41cfedeb33b0: Pushed
5c1c2aa63cc6: Pushed
5fc964a1adf1: Pushed
1e09695463df: Pushed
090e9c58f474: Pushed
latest: digest: sha256:548cbb13c49f786733492b80f0928615efb1009c9fd4807a042e3bbcd9667bf3 size: 2204

# 업로드된 이미지 확인
$ aws ecr list-images --repository-name shr-dev-edustack-backend-ecr
{
    "imageIds": [
        {
            "imageDigest": "sha256:548cbb13c49f786733492b80f0928615efb1009c9fd4807a042e3bbcd9667bf3",
            "imageTag": "latest"
        }
    ]
}
✅ ECR 푸시 성공 확인
  • AWS 콘솔에서 이미지 확인
  • 보안 스캔 결과 확인
  • 태그 정보 검증

Step 3: EKS 클러스터 생성 (30분)

🎯 이 단계에서 하는 일

지금까지 한 대의 서버에서 docker-compose로 실행하던 것을 여러 대의 서버에서 자동으로 관리되도록 확장합니다.

🤔 EKS 클러스터 생성 시 실제로 일어나는 일

  1. Control Plane 생성: AWS가 Kubernetes 마스터 노드를 관리형으로 생성
  2. VPC 네트워크 설정: 보안을 위한 격리된 네트워크 환경 구축
  3. Worker Node 생성: 실제 컨테이너가 실행될 EC2 인스턴스들 생성
  4. 보안 그룹 설정: 네트워크 접근 권한 자동 구성
  5. IAM 역할 생성: AWS 리소스 접근 권한 자동 설정
💡 왜 30분이나 걸리나요?

EKS는 단순히 서버 하나를 만드는 게 아니라 전체 인프라를 구축하기 때문입니다. 네트워크, 보안, 로드밸런서, 모니터링 등 운영에 필요한 모든 것을 자동으로 설정합니다.

🏗️ 구축할 아키텍처 이해

먼저 우리가 구축할 EduStack EKS 아키텍처를 살펴보겠습니다.

📐 EduStack EKS 아키텍처
EduStack EKS Architecture Diagram
🌐 네트워크 계층
  • VPC: 격리된 네트워크 환경
  • Public Subnet: ALB, NAT Gateway
  • Private Subnet: Worker Nodes
  • Multi-AZ: 고가용성 보장
⚙️ EKS 구성요소
  • Control Plane: AWS 완전 관리
  • Worker Nodes: t3.medium × 2
  • ECR: 컨테이너 이미지 저장
  • ALB: 트래픽 로드밸런싱
🐳 애플리케이션
  • FastAPI Backend: 2 replicas
  • PostgreSQL: 데이터베이스
  • Redis: 캐시 & 세션
  • Namespace: edustack
💡 아키텍처 핵심 포인트
  • 고가용성: Multi-AZ 구성으로 장애 대응
  • 보안: Private Subnet에 워커 노드 배치
  • 확장성: Auto Scaling으로 트래픽 대응
  • 관리 편의성: AWS 관리형 서비스 활용
💡 EKS 클러스터 이해
  • EKS (Elastic Kubernetes Service): AWS가 관리해주는 Kubernetes 서비스
  • Control Plane: AWS가 완전 관리 (마스터 노드, API 서버, etcd, 스케줄러)
  • Worker Nodes: 우리가 관리하는 EC2 인스턴스 (실제 컨테이너 실행)
  • 관리형 서비스: 업그레이드, 패치, 백업을 AWS가 자동 처리
✅ 실제 회사 환경에서는
  • 공통 EKS 클러스터: 인프라팀이 관리하는 중앙 집중식 클러스터
  • 네임스페이스 분리: 팀별/프로젝트별로 네임스페이스 할당
  • RBAC 권한: 팀별로 접근 권한 제한
  • 비용 최적화: 노드 그룹 공유로 리소스 효율성 확보
💡 실습 환경 vs 운영 환경
  • 실습: 개인 클러스터 생성 (학습 목적)
  • 운영: 기존 클러스터에 네임스페이스 할당받아 사용
💡 EKS 클러스터 구성 요소
  • Control Plane: AWS가 관리하는 Kubernetes 마스터
  • Worker Nodes: 실제 컨테이너가 실행되는 EC2 인스턴스
  • VPC: 네트워크 격리 및 보안
  • IAM Roles: 클러스터 및 노드 권한
# eksctl로 거버넌스 준수 클러스터 생성
$ eksctl create cluster \
  --name shr-prd-edustack-cluster-eks \
  --region ap-northeast-2 \
  --vpc-private-subnets subnet-xxx,subnet-yyy \
  --tags ServiceName=edustack,Environment=prd,Owner=emart

# 노드 그룹 추가
$ eksctl create nodegroup \
  --cluster shr-prd-edustack-cluster-eks \
  --name workers \
  --node-type t3.medium \
  --nodes 2 \
  --nodes-min 1 \
  --nodes-max 4
✅ 클러스터 생성 완료 확인
# 노드 상태 확인
$ kubectl get nodes
NAME                                                STATUS   ROLES    AGE    VERSION
ip-███████████.ap-northeast-2.compute.internal   Ready    <none>   4m5s   v1.32.9-eks-113cf36
ip-██████████.ap-northeast-2.compute.internal    Ready    <none>   4m4s   v1.32.9-eks-113cf36

# 클러스터 정보 확인
$ kubectl cluster-info
Kubernetes control plane is running at https://████████████████████████████████.gr7.ap-northeast-2.eks.amazonaws.com
CoreDNS is running at https://████████████████████████████████.gr7.ap-northeast-2.eks.amazonaws.com/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

# 네임스페이스 확인
$ kubectl get namespaces
NAME              STATUS   AGE
default           Active   10m
kube-node-lease   Active   10m
kube-public       Active   10m
kube-system       Active   10m
✅ EKS 클러스터 배포 완료 확인
# EduStack 애플리케이션 배포 상태
$ kubectl get pods -n edustack
NAME                              READY   STATUS    RESTARTS   AGE
edustack-backend-██████████   1/1     Running   0          7s
edustack-backend-██████████   1/1     Running   0          6s
postgres-█████████████         1/1     Running   0          59s

# 서비스 및 LoadBalancer 확인
$ kubectl get svc -n edustack
NAME                       TYPE           CLUSTER-IP       EXTERNAL-IP                                                                   PORT(S)        AGE
edustack-backend-service   LoadBalancer   10.100.239.15    ████████████████████████████████████████████████████████████████████████████████████   80:32695/TCP   10m
postgres                   ClusterIP      10.100.177.197   <none>                                                                        5432/TCP       73s

# 애플리케이션 접근 테스트
$ curl http://████████████████████████████████████████████████████████████████████████████████████/
{"message":"EduStack API","version":"1.0.0","docs":"/docs"}
✅ S3 정적 → EKS 동적 마이그레이션 완료
  • 기존: https://sac-serviceinfra.emart.com/aiseminar/ (S3 정적 사이트)
  • 신규: EKS 동적 플랫폼 (LoadBalancer 접근)
💡 아키텍처 호환성 이슈 해결
  • 문제: Mac (ARM64)에서 빌드한 이미지가 EKS (x86_64) 노드에서 실행 불가
  • 해결: docker buildx build --platform linux/amd64 사용
  • ECR 태그: shr-dev-edustack-backend-ecr:amd64
클러스터 생성 시간
  • Control Plane: 약 10-15분
  • Worker Nodes: 약 5-10분
  • 총 소요시간: 20-30분
  • 커피 한 잔 하고 오세요! ☕

Step 3.5: 아키텍처 호환성 이슈 해결 (중요!)

🚨 실제 경험한 문제: exec format error

Mac (ARM64)에서 빌드한 Docker 이미지를 EKS (x86_64) 노드에서 실행할 때 발생하는 아키텍처 불일치 문제

🤔 왜 이런 문제가 발생하나요?

🖥️ 로컬 개발 환경
  • Mac M1/M2: ARM64 아키텍처
  • Docker 빌드: ARM64용 바이너리 생성
  • 로컬 테스트: 정상 작동
☁️ EKS 운영 환경
  • EC2 인스턴스: x86_64 아키텍처
  • 컨테이너 실행: ARM64 바이너리 실행 불가
  • 결과: exec format error
💡 실제 운영 환경에서는 이런 문제가 없는 이유
  • GitHub Actions: Linux x86_64 러너에서 빌드
  • Azure DevOps: Linux x86_64 에이전트에서 빌드
  • 결과: 빌드 환경과 배포 환경이 동일한 아키텍처
# ❌ 문제 상황: 기본 빌드 (ARM64)
$ docker build -t edustack/backend:v1 .
$ docker push $ECR_REPO:v1

# EKS 배포 후 Pod 상태 확인
$ kubectl get pods -n edustack
NAME                              READY   STATUS             RESTARTS   AGE
edustack-backend-xxx-yyy          0/1     CrashLoopBackOff   3          2m

# Pod 로그 확인
$ kubectl logs edustack-backend-xxx-yyy -n edustack
exec /usr/local/bin/uvicorn: exec format error

# ✅ 해결책: 플랫폼 지정 빌드 (x86_64)
$ docker buildx create --use
$ docker buildx build --platform linux/amd64 \
  -t edustack/backend:v2-amd64 \
  --push \
  -t $ECR_REPO:v2-amd64 .

# 배포 매니페스트 업데이트
$ kubectl set image deployment/edustack-backend \
  backend=$ECR_REPO:v2-amd64 -n edustack

# 정상 배포 확인
$ kubectl get pods -n edustack
NAME                              READY   STATUS    RESTARTS   AGE
edustack-backend-abc-def          1/1     Running   0          30s
edustack-backend-ghi-jkl          1/1     Running   0          25s

🛠️ 해결 방법 상세

1️⃣ Docker Buildx 활성화
  • docker buildx: 멀티 플랫폼 빌드 지원 도구
  • --platform linux/amd64: 타겟 아키텍처 명시적 지정
  • 크로스 컴파일: ARM64에서 x86_64용 바이너리 생성
2️⃣ 이미지 태그 전략
  • v2-amd64: 아키텍처 정보를 태그에 포함
  • 명확한 구분: ARM64/AMD64 이미지 혼동 방지
  • 버전 관리: 아키텍처별 이미지 추적 가능
3️⃣ 배포 매니페스트 수정
  • image 필드 업데이트: 새로운 AMD64 태그 사용
  • kubectl set image: 무중단 롤링 업데이트
  • 검증: Pod 상태 및 로그 확인
✅ 예방 방법
  • CI/CD 파이프라인: GitHub Actions에서 자동 빌드
  • 멀티 아키텍처 이미지: ARM64/AMD64 동시 지원
  • 로컬 테스트: --platform linux/amd64 옵션으로 테스트
결과: 아키텍처 호환성 문제 해결, EKS에서 정상 실행

Step 4: EduStack 프로젝트 배포 및 테스트 (30분)

✅ 실제 프로젝트 연결

이제 앞서 개발한 EduStack 프로젝트를 EKS에 배포해보겠습니다!

  • FastAPI + PostgreSQL + Redis 스택
  • AI 교육 플랫폼 백엔드
  • 실제 운영 환경 시뮬레이션
💡 Kubernetes 리소스 이해
  • Namespace: 리소스 격리 및 조직화
  • Deployment: 애플리케이션 배포 및 관리
  • Service: 내부 로드밸런싱
  • Ingress: 외부 트래픽 라우팅
# EduStack 전체 스택 배포
$ kubectl apply -f infrastructure/k8s/

# 배포 상태 확인
$ kubectl get pods -n edustack
NAME                                 READY   STATUS    RESTARTS   AGE
edustack-backend-7d4b8c8f9d-abc12    1/1     Running   0          2m
edustack-backend-7d4b8c8f9d-def34    1/1     Running   0          2m
edustack-postgres-6b8d9c7f8e-xyz89   1/1     Running   0          3m
edustack-redis-5c7d8e9f0a-qrs12      1/1     Running   0          3m

# 서비스 확인
$ kubectl get svc -n edustack
NAME                       TYPE           CLUSTER-IP     EXTERNAL-IP                                                               PORT(S)        AGE
edustack-backend-service   LoadBalancer   10.100.123.45  a1b2c3d4e5f6-123456789.ap-northeast-2.elb.amazonaws.com   80:32000/TCP   3m
postgres                   ClusterIP      10.100.234.56                                                          5432/TCP       3m
redis                      ClusterIP      10.100.345.67                                                          6379/TCP       3m

# EduStack API 테스트
$ curl http://a1b2c3d4e5f6-123456789.ap-northeast-2.elb.amazonaws.com/
{"message":"Welcome to EduStack AI Education Platform","version":"1.0.0","environment":"dev"}

$ curl http://a1b2c3d4e5f6-123456789.ap-northeast-2.elb.amazonaws.com/health
{"status":"healthy","database":"connected","redis":"connected","timestamp":"2025-10-02T13:40:00Z"}

# 로그 확인
$ kubectl logs -n edustack deployment/edustack-backend
INFO:     Started server process [1]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO:     EduStack AI Education Platform initialized
INFO:     Database connection established
INFO:     Redis connection established

# 스케일링 테스트
$ kubectl scale deployment edustack-backend --replicas=3 -n edustack
deployment.apps/edustack-backend scaled

$ kubectl get pods -n edustack
NAME                                 READY   STATUS    RESTARTS   AGE
edustack-backend-7d4b8c8f9d-abc12    1/1     Running   0          5m
edustack-backend-7d4b8c8f9d-def34    1/1     Running   0          5m
edustack-backend-7d4b8c8f9d-ghi56    1/1     Running   0          30s
결과: EduStack AI 교육 플랫폼이 EKS에서 정상 실행, 마이크로서비스 아키텍처 구현

Kubernetes 매니페스트 작성

📋 Kubernetes 매니페스트 파일 종류
  • Namespace: 리소스 격리 및 조직화 (namespace.yaml)
  • Deployment: 애플리케이션 배포 및 관리 (backend-deployment.yaml)
  • Service: 내부/외부 네트워크 연결 (backend-service.yaml)
  • ConfigMap: 설정 데이터 관리 (config.yaml)
  • Secret: 민감한 데이터 관리 (secrets.yaml)
  • PersistentVolume: 데이터 영속성 (postgres-pv.yaml)
# EduStack 프로젝트 디렉토리로 이동
$ cd ~/Develop/edustack

# Kubernetes 매니페스트 디렉토리 생성 (프로젝트 구조 준수)
$ mkdir -p infrastructure/k8s

1️⃣ Namespace 생성

📋 Namespace: 리소스 격리 및 조직화 - EduStack 전용 네임스페이스
$ cat > infrastructure/k8s/namespace.yaml << 'EOF'
apiVersion: v1
kind: Namespace
metadata:
  name: edustack
  labels:
    name: edustack
    environment: prd
    project: edustack
EOF

2️⃣ Backend Deployment 생성

📋 Deployment: 애플리케이션 배포 및 관리 - FastAPI 백엔드 서비스
$ cat > infrastructure/k8s/backend-deployment.yaml << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: edustack-backend
  namespace: edustack
  labels:
    app: edustack-backend
    component: backend
spec:
  replicas: 2
  selector:
    matchLabels:
      app: edustack-backend
  template:
    metadata:
      labels:
        app: edustack-backend
        component: backend
    spec:
      containers:
      - name: backend
        image: ████████████.dkr.ecr.ap-northeast-2.amazonaws.com/shr-dev-edustack-backend-ecr:latest
        ports:
        - containerPort: 8000
        env:
        - name: DATABASE_URL
          value: "postgresql://edustack:edustack123@postgres:5432/edustack"
        - name: REDIS_URL
          value: "redis://redis:6379"
EOF

3️⃣ Backend Service 생성

📋 Service: 외부 트래픽 로드밸런싱 - LoadBalancer로 외부 접근 허용
$ cat > infrastructure/k8s/backend-service.yaml << 'EOF'
apiVersion: v1
kind: Service
metadata:
  name: edustack-backend-service
  namespace: edustack
spec:
  selector:
    app: edustack-backend
  ports:
  - port: 80
    targetPort: 8000
  type: LoadBalancer
EOF
# PostgreSQL 배포 생성
$ cat > infrastructure/k8s/postgres-deployment.yaml << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
  namespace: edustack
spec:
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:15
        ports:
        - containerPort: 5432
        env:
        - name: POSTGRES_DB
          value: "edustack"
        - name: POSTGRES_USER
          value: "edustack"
        - name: POSTGRES_PASSWORD
          value: "edustack123"
---
apiVersion: v1
kind: Service
metadata:
  name: postgres
  namespace: edustack
spec:
  selector:
    app: postgres
  ports:
  - port: 5432
    targetPort: 5432
  type: ClusterIP
EOF
# EduStack Backend Deployment
$ cat > infrastructure/k8s/backend-deployment.yaml << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: edustack-backend
  namespace: edustack
  labels:
    app: edustack-backend
    component: api
    environment: dev
spec:
  replicas: 2
  selector:
    matchLabels:
      app: edustack-backend
  template:
    metadata:
      labels:
        app: edustack-backend
        component: api
    spec:
      containers:
      - name: backend
        image: 123456789.dkr.ecr.ap-northeast-2.amazonaws.com/shr-dev-edustack-backend-ecr:latest
        ports:
        - containerPort: 8000
        env:
        - name: DATABASE_URL
          value: "postgresql://edustack:edustack123@postgres:5432/edustack"
        - name: REDIS_URL
          value: "redis://redis:6379"
        - name: ENVIRONMENT
          value: "dev"
        - name: DEBUG
          value: "true"
        - name: PROJECT_NAME
          value: "EduStack AI Education Platform"
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /health
            port: 8000
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /health
            port: 8000
          initialDelaySeconds: 5
          periodSeconds: 5
EOF

# EduStack Backend Service
$ cat > infrastructure/k8s/backend-service.yaml << 'EOF'
apiVersion: v1
kind: Service
metadata:
  name: edustack-backend-service
  namespace: edustack
  labels:
    app: edustack-backend
spec:
  selector:
    app: edustack-backend
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 8000
  type: LoadBalancer
EOF

4️⃣ PostgreSQL 생성

📋 PostgreSQL: 데이터베이스 서비스 - 메인 데이터 저장소
$ cat > infrastructure/k8s/postgres-deployment.yaml << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: edustack-postgres
  namespace: edustack
spec:
  replicas: 1
  selector:
    matchLabels:
      app: edustack-postgres
  template:
    metadata:
      labels:
        app: edustack-postgres
    spec:
      containers:
      - name: postgres
        image: postgres:15-alpine
        env:
        - name: POSTGRES_DB
          value: "edustack"
        - name: POSTGRES_USER
          value: "edustack"
        - name: POSTGRES_PASSWORD
          value: "edustack123"
        ports:
        - containerPort: 5432
        volumeMounts:
        - name: postgres-storage
          mountPath: /var/lib/postgresql/data
      volumes:
      - name: postgres-storage
        emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
  name: postgres
  namespace: edustack
spec:
  selector:
    app: edustack-postgres
  ports:
    - port: 5432
      targetPort: 5432
EOF

5️⃣ Redis 생성

📋 Redis: 캐시 및 세션 스토어 - 고성능 인메모리 데이터베이스
$ cat > infrastructure/k8s/redis-deployment.yaml << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: edustack-redis
  namespace: edustack
spec:
  replicas: 1
  selector:
    matchLabels:
      app: edustack-redis
  template:
    metadata:
      labels:
        app: edustack-redis
    spec:
      containers:
      - name: redis
        image: redis:7-alpine
        ports:
        - containerPort: 6379
        command: ["redis-server", "--appendonly", "yes"]
        volumeMounts:
        - name: redis-storage
          mountPath: /data
      volumes:
      - name: redis-storage
        emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
  name: redis
  namespace: edustack
spec:
  selector:
    app: edustack-redis
  ports:
    - port: 6379
      targetPort: 6379
EOF

💡 엔터프라이즈 단계 인사이트

  • 거버넌스: 네이밍 룰과 태그 정책 준수로 관리 용이
  • 확장성: 트래픽 증가 시 자동 스케일링 가능
  • 보안: 프라이빗 서브넷, 이미지 스캔으로 보안 강화
  • 운영: 모니터링과 로깅으로 안정적 운영

🔄 실제 엔터프라이즈 시나리오

개발자 워크플로우
# 1. 로컬 개발
docker build -t my-app .

# 2. ECR 푸시
docker tag my-app:latest $ECR_REPO:v1.0.0
docker push $ECR_REPO:v1.0.0

# 3. EKS 배포
kubectl set image deployment/backend-deployment \
  backend=$ECR_REPO:v1.0.0 -n edustack
운영팀 모니터링
# 클러스터 상태 확인
kubectl get nodes
kubectl top pods -n edustack

# 로그 수집
kubectl logs -f deployment/backend-deployment \
  -n edustack

# 스케일링
kubectl scale deployment backend-deployment \
  --replicas=5 -n edustack

🌐 실제 운영: 도메인 개선 의사결정 과정

🤔 EKS 배포 후 발견한 실제 문제

EKS 배포는 성공했지만 LoadBalancer 주소가 문제였습니다:

https://████████████████████████████████.gr7.ap-northeast-2.eks.amazonaws.com
  • 문제점: 못생긴 AWS 기본 도메인, 기억하기 어려움, 브랜딩 없음
  • 목표: 이 못생긴 도메인을 깔끔한 도메인으로 완전 대체
  • 요구사항: 기존 인프라 활용, 사용자 친화적 주소

🎯 1차 의사결정: CloudFront Origin 확장

처음에는 기존 CloudFront에 EKS ALB를 새 Origin으로 추가하는 방식을 계획했습니다.

📐 1차 계획된 아키텍처

# 1차 계획
기존: https://sac-serviceinfra.emart.com/aiseminar/     → S3 (정적)
신규: https://sac-serviceinfra.emart.com/aiseminar-api/ → EKS ALB (동적)

CloudFront 배포:
├── Origin 1: S3 (기존)
├── Origin 2: EKS ALB (신규 추가)
├── Behavior 1: /aiseminar/* → S3
├── Behavior 2: /aiseminar-api/* → ALB
├── WAF: 기존 규칙 적용
└── SSL: 기존 인증서 활용
✅ 1차 계획의 장점
  • 비용 $0: 기존 인프라 활용
  • 보안 일관성: 기존 WAF 규칙 자동 적용
  • 일관된 도메인: 정적/동적 컨텐츠 통합

🔍 2차 발견: 기존 API Gateway 인프라

💡 추가 분석 중 발견

CloudFront Origin 분석 중 기존 API Gateway가 이미 연결되어 있음을 발견!

# 기존 CloudFront Origins 확인
$ aws cloudfront get-distribution-config --id ██████████████
{
    "Origins": [
        {"DomainName": "sac-prd-comm-contents-s3.s3.amazonaws.com"},
        {"DomainName": "shr-prd-ai-seminar-materials-s3.s3.amazonaws.com"},
        {"DomainName": "██████████.execute-api.ap-northeast-2.amazonaws.com"}  ← 기존 API Gateway!
    ]
}

# API Gateway 상세 정보
$ aws apigateway get-rest-api --rest-api-id ██████████
{
    "name": "sac-prd-aws-account-alert-cfn",
    "createdDate": "2022-05-16T23:30:57+09:00"
}
🔍 발견된 기존 인프라
  • API Gateway ID: ██████████
  • 용도: AWS 계정 알림 시스템
  • 경로: /api/aws/accounts/{id}
  • CloudFront 연동: 이미 Origin으로 등록됨

🔄 3차 재검토: 브라우저 보안 정책 고려

⚠️ 브라우저 보안 정책의 현실

정적 사이트에서 API를 호출할 때 브라우저 제약사항 발견:

  • Mixed Content: HTTPS 사이트에서 HTTP API 직접 호출 불가
  • CORS 정책: 다른 도메인 간 API 호출 제한
  • 내부 ALB: 브라우저에서 VPC 내부 리소스 접근 불가
❌ 불가능한 시나리오
  • 브라우저 → EKS ALB 직접 호출
  • HTTPS → HTTP Mixed Content
  • 내부 ALB 퍼블릭 노출 필요
✅ 가능한 해결책들
  • CloudFront 프록시 (Origin 추가)
  • API Gateway 확장
  • 서버사이드 프록시

🏢 4차 고려: 실무 환경 제약사항

💼 기업 환경의 현실적 제약
  • 도메인 신청: 새 서브도메인 신청 절차의 복잡성
  • 기존 사용자: 현재 서비스 이용 중인 사용자 URL 호환성
  • 관리 권한: 개인 선에서 실행 가능한 범위
  • 승인 프로세스: 인프라 변경 시 필요한 검토 과정
🚫 제약사항
  • 새 서브도메인 신청 복잡
  • 기존 사용자 URL 변경 부담
  • 승인 절차 시간 소요
  • 관리 권한 범위 제한
✅ 실행 가능한 범위
  • 기존 도메인 경로 확장
  • 기존 CloudFront 설정 수정
  • 즉시 실행 가능한 변경
  • 기존 서비스 무중단

🎯 최종 결정: 경로 기반 분리

✅ 모든 제약사항을 고려한 현실적 선택

경로 기반 분리 + CloudFront Origin 확장 방식으로 최종 결정

📋 최종 확정된 URL 구조

# 기존 서비스 (변경 없음)
정적 학습 자료: https://sac-serviceinfra.emart.com/aiseminar/

# 신규 API 서비스  
동적 API: https://sac-serviceinfra.emart.com/api/edustack/

CloudFront Behavior 설정:
우선순위 0: /api/edustack/*  → EKS ALB Origin (no-cache)
우선순위 1: /aiseminar/*     → S3 Origin (기존, medium-cache)

주요 엔드포인트:
├── /api/edustack/          → API 정보
├── /api/edustack/docs      → API 문서  
├── /api/edustack/health    → 상태 확인
├── /api/edustack/contents  → 컨텐츠 API
└── /api/edustack/analytics → 분석 API
# CloudFront 설정 변경 (AWS 콘솔에서)
1. CloudFront 배포 ██████████████ 선택
2. Origins 탭에서 EKS ALB Origin 추가
   - Origin Domain: ████████████████████████████████.gr7.ap-northeast-2.eks.amazonaws.com
   - Protocol: HTTP Only
   - Port: 80
3. Behaviors 탭에서 /api/edustack/* 규칙 추가
   - Origin: EKS ALB
   - Cache Policy: CachingDisabled
4. 배포 업데이트 (5-10분 소요)

# 테스트
$ curl https://sac-serviceinfra.emart.com/api/edustack/health
{"status":"healthy","timestamp":"2025-10-03T16:42:00Z"}

# JavaScript에서 API 호출 (CORS 문제 없음)
fetch('https://sac-serviceinfra.emart.com/api/edustack/contents')
  .then(response => response.json())
  .then(data => console.log(data));
도메인 대체 완료
❌ Before (못생긴 EKS 도메인)
https://████████████████████████████████.gr7.ap-northeast-2.eks.amazonaws.com
  • 기억하기 어려움
  • 브랜딩 없음
  • 사용자 비친화적
✅ After (깔끔한 통합 도메인)
https://sac-serviceinfra.emart.com/api/edustack/
  • 기존 도메인과 통합
  • 기억하기 쉬움
  • 브랜딩 일관성
  • CORS 문제 없음
🔄 CloudFront 프록시 동작

사용자 → https://sac-serviceinfra.emart.com/api/edustack/ → CloudFront → EKS ALB (내부)

결과: 못생긴 EKS 도메인은 완전히 숨겨지고, 깔끔한 통합 도메인으로 완전 대체!

💡 실무 인사이트: 의사결정 과정의 진화

  1. 문제 인식: EKS ALB 도메인의 사용성 문제
  2. 1차 계획: CloudFront Origin 확장 (이론적 최적해)
  3. 2차 발견: 기존 API Gateway 인프라 활용 가능성
  4. 3차 재검토: 브라우저 보안 정책의 현실적 제약
  5. 4차 고려: 기업 환경의 실무적 제약사항
  6. 최종 결정: 모든 제약을 고려한 실용적 해결책
🤖 AI Assistant 협업의 핵심 가치

이론적 최적해 → 실무적 최적해로의 진화 과정을 함께 거쳐가며, 단순한 기술 구현이 아닌 비즈니스 제약사항을 고려한 현실적 의사결정을 지원하는 것이 AI Assistant와의 협업에서 얻는 진정한 가치입니다.

💡 전체 여정에서 배운 점

🎯 핵심 인사이트

1. 단계별 진화의 필요성

  • 개인: 빠른 프로토타이핑과 학습
  • : 협업과 일관성 확보
  • 엔터프라이즈: 거버넌스와 안정성

2. 각 단계별 핵심 도구

  • 개인: `docker run`, `docker build`
  • : `docker-compose`
  • 엔터프라이즈: `kubectl`, `eksctl`

💡 실제 적용 팁

  • 처음부터 엔터프라이즈 환경 구축하지 말 것
  • 개인 → 팀 → 엔터프라이즈 순서로 점진적 발전
  • 각 단계에서 충분히 학습 후 다음 단계로
  • 실제 문제가 발생했을 때 다음 단계 고려

🧹 실습 후 리소스 정리

⚠️ 중요: 비용 절감을 위한 필수 작업

실습 완료 후 반드시 리소스를 정리해야 불필요한 비용이 발생하지 않습니다.

💡 옵션 1: 일시 중지 (비용 절감)

💡 언제 사용? 나중에 다시 실습할 예정이거나 설정을 보존하고 싶을 때
# Worker Node 스케일 다운 (EC2 비용 절약)
$ eksctl scale nodegroup --cluster=edustack-cluster --name=edustack-nodes --nodes=0

# 애플리케이션 스케일 다운 (LoadBalancer는 유지)
$ kubectl scale deployment edustack-backend --replicas=0 -n edustack
$ kubectl scale deployment edustack-postgres --replicas=0 -n edustack
$ kubectl scale deployment edustack-redis --replicas=0 -n edustack

# 비용 절감 효과: 약 80% (Control Plane + LoadBalancer만 유지)
# 예상 비용: $5/일 → $1/일
절약 효과: EC2 Worker Node 비용 제거, 설정은 보존

🗑️ 옵션 2: 완전 삭제 (비용 0원)

💡 주의: 모든 설정과 데이터가 영구 삭제됩니다
# 1. EduStack 애플리케이션 삭제
$ kubectl delete namespace edustack

# 2. EKS 클러스터 완전 삭제 (모든 리소스 포함)
$ eksctl delete cluster --name=edustack-cluster

# 3. ECR 리포지토리 삭제 (선택사항)
$ aws ecr delete-repository \
  --repository-name shr-dev-edustack-backend-ecr \
  --force \
  --region ap-northeast-2

# 4. 로컬 Docker 이미지 정리
$ docker system prune -a
완전 정리: 모든 AWS 리소스 삭제, 비용 0원

💸 비용 비교표

상태 일일 비용 주요 구성요소 복구 시간
🟢 실행 중 ~$5/일 Control Plane + Worker Nodes + LoadBalancer 즉시
⏸️ 일시 중지 ~$1/일 Control Plane + LoadBalancer 5-10분
🗑️ 완전 삭제 $0/일 없음 20-30분
💡 권장사항
  • 학습 중: 일시 중지 사용 (설정 보존 + 비용 절감)
  • 실습 완료: 완전 삭제 (비용 0원)
  • 운영 환경: 자동 스케일링 설정으로 비용 최적화

✅ 다음 단계 추천

개인 개발자라면

  1. Docker Desktop으로 시작
  2. 간단한 앱 컨테이너화 연습
  3. 다양한 이미지 활용 경험

팀 프로젝트라면

  1. docker-compose 마스터
  2. 환경변수 관리 체계화
  3. CI/CD 파이프라인 구축

엔터프라이즈라면

  1. 거버넌스 정책 수립
  2. 보안 스캔 자동화
  3. 모니터링 시스템 구축
실습 준비
개인 개발
팀 프로젝트
엔터프라이즈
도메인 개선
핵심 인사이트