Spring 7기 프로젝트/모임 플렛폼 프로젝트

AWS EC2 vs ECS: 컨테이너 배포 전략과 구현

JuNo_12 2025. 7. 31. 16:39

들어가며

Spring Boot 기반의 모임 플랫폼 프로젝트를 진행하면서 배포 전략을 고민하게 되었습니다. 초기에는 EC2를 사용한 전통적인 배포 방식을 채택했지만, 프로젝트가 복잡해지고 라이브러리가 많아지면서 컨테이너 기반 배포의 필요성을 느끼게 되었습니다.

이 글에서는 AWS의 두 가지 주요 컨테이너 배포 옵션인 EC2와 ECS의 차이점을 분석하고, 프로젝트 특성에 맞는 최적의 선택을 위한 가이드를 제시하겠습니다. 또한 Docker 구성부터 ECS 배포까지의 전체 워크플로우를 예제와 함께 설명하겠습니다.


Docker Compose에서 Dockerfile이 필요한 이유

"어차피 컨테이너 하나만 띄우는데 왜 Dockerfile이 필요하지?"

 

핵심 차이점: 기성품 vs 커스텀 제품

이미 완성된 서비스들 (공식 이미지 존재)

services:
  mysql:
    image: mysql:8.0          # ✅ 이미 완성된 MySQL
  redis:
    image: redis:7-alpine     # ✅ 이미 완성된 Redis
  prometheus:
    image: prom/prometheus    # ✅ 이미 완성된 Prometheus

 

우리가 만든 Spring Boot 애플리케이션

services:
  momo-app:
    image: spring-boot:latest  # ❌ 이런 이미지는 존재하지 않습니다.

 

Dockerfile의 역할: 우리 코드를 컨테이너로 패키징

FROM openjdk:17-jdk-slim        # Java 실행 환경
WORKDIR /app                    # 작업 디렉토리 설정
COPY build/libs/*.jar app.jar   # 우리가 빌드한 JAR 파일 복사
ENTRYPOINT ["java", "-jar", "app.jar"]  # 실행 명령어

 

Dockerfile이 하는 일:

  1. 우리 애플리케이션 코드를 컨테이너에 넣기
  2. 실행 환경 설정하기
  3. 시작 명령어 정의하기

 

Docker Compose에서의 차이점

version: '3.8'
services:
  # 직접 빌드가 필요한 우리 앱
  momo-app:
    build: .              # ← Dockerfile 사용해서 빌드
    ports:
      - "8080:8080"
  
  # 기존 이미지를 그대로 사용하는 서비스들
  mysql:
    image: mysql:8.0      # ← 완성된 이미지 사용
  redis:
    image: redis:alpine   # ← 완성된 이미지 사용

결론

  • MySQL, Redis 등: 이미 완성된 소프트웨어 → image 사용
  • Spring Boot 앱: 우리가 만든 커스텀 애플리케이션 → build + Dockerfile 사용

Dockerfile은 우리의 고유한 애플리케이션 코드를 컨테이너 이미지로 만들어주는 레시피인 셈입니다.


EC2 vs ECS / ECR: 기본 개념과 특징

EC2 (Elastic Compute Cloud)

EC2는 가상 서버 서비스이며 사용자가 가상 머신을 직접 관리하며, 운영체제부터 애플리케이션까지 모든 것을 설정하고 운영해야 합니다.

 

주요 특징:

  • 가상 머신 인스턴스 제공
  • OS부터 애플리케이션까지 모든 것을 직접 관리
  • Docker를 설치하고 컨테이너를 직접 실행
  • 서버 설정, 보안, 업데이트 등 모든 관리 책임

장점:

  • 완전한 제어권과 자유로운 설정
  • 다양한 용도로 활용 가능
  • 상대적으로 학습하기 쉬움
  • 직접적인 비용 통제

단점:

  • 서버 관리 부담 증가
  • 복잡한 스케일링 설정
  • 보안 관리의 복잡성
  • 운영 오버헤드 발생

 

ECS (Elastic Container Service)

ECS는 컨테이너화된 애플리케이션의 손쉬운 베포, 관리 및 조정에 도움이 되는 완전관리형 컨테이너 오케스트레이션 서비스입니다.

 

주요 특징:

  • 완전 관리형 컨테이너 오케스트레이션
  • Docker 컨테이너 자동 배포 및 관리
  • 자동 스케일링과 로드밸런싱 내장
  • AWS 서비스들과의 완벽한 통합

장점:

  • 서버리스 옵션(Fargate) 제공
  • 자동 스케일링과 자가 치유 기능
  • 관리 부담 최소화
  • AWS 생태계와의 원활한 통합

단점:

  • AWS 플랫폼에 대한 종속성
  • 초기 학습 곡선 존재
  • 제한된 커스터마이징 옵션
  • 특정 상황에서 더 높은 비용

 

ECR (Elastic Container Registry)

ECR은 어디에서나 컨테이너 이미지와 아티팩트를 손쉽게 저장, 관리, 공유 및 베포할 수 있는 완전관리형 컨테이너 레지스트리입니다.


프로젝트 특성 분석과 ECS 선택 이유

저희 모임 플랫폼 프로젝트는 다음과 같은 특징을 가지고 있습니다:

  • Spring Boot 기반의 백엔드 애플리케이션
  • JWT, WebSocket, JPA 등 다수의 라이브러리 사용
  • MySQL, Elasticsearch 등 다양한 외부 의존성
  • OpenTelemetry, Jaeger, Prometheus, Loki, Grafana를 포함한 복합적인 모니터링 스택

이러한 복잡한 아키텍처를 고려했을 때 ECS가 더 적합한 이유는 다음과 같습니다:

 

1. 관리 복잡성 감소

EC2에서 필요한 작업들:

# 각 인스턴스마다 반복해야 하는 초기 설정
sudo yum update -y
sudo yum install -y docker
sudo systemctl start docker
sudo usermod -a -G docker ec2-user

# 컨테이너 수동 관리
docker run -d --name momo-app -p 8080:8080 momo:latest
docker run -d --name prometheus -p 9090:9090 prom/prometheus
docker run -d --name grafana -p 3000:3000 grafana/grafana

# 장애 시 수동 복구
docker ps | grep Exited
docker restart momo-app
docker logs momo-app --tail 100

 

ECS에서는:

  • Task Definition에 컨테이너 설정을 한 번 정의하면 AWS가 자동으로 실행 환경을 관리
  • 컨테이너 실패 시 자동 재시작 및 Health Check 기반 교체
  • 서버 패치, Docker 데몬 관리, 로그 로테이션 등을 AWS가 담당

 

2. 자동 스케일링과 가용성

모임 플랫폼의 트래픽 패턴:

  • 평일 저녁 19:00-21:00 시간대 트래픽 급증 (모임 생성/참가)
  • 주말 오후 모임 시작 전 대량 접속
  • 특정 이벤트나 홍보로 인한 예측 불가능한 트래픽 스파이크

EC2 방식의 한계:

# 트래픽 증가 감지 후 수동 대응 (5-10분 소요)
aws ec2 run-instances --image-id ami-12345 --instance-type t3.medium
# 인스턴스 부팅 대기 (2-3분)
# 로드밸런서에 수동 등록
# 애플리케이션 배포 및 설정 (5분)
# 총 12-18분 후에야 서비스 가능

 

ECS Auto Scaling 설정:

{
  "ScalingPolicy": {
    "TargetValue": 70.0,
    "ScaleOutCooldown": 300,
    "ScaleInCooldown": 300,
    "MetricType": "CPUUtilization"
  }
}
  • CPU 사용률 70% 초과 시 자동으로 태스크 수 증가
  • 평균 1-2분 내에 새로운 컨테이너 실행
  • 트래픽 감소 시 자동으로 불필요한 태스크 종료하여 비용 절약

 

3. 서비스 통합성

AWS 서비스 간 네이티브 연동:

# RDS와의 연동
Database:
  - RDS MySQL 8.0 (Multi-AZ)
  - VPC 내 private subnet에 배치
  - ECS 태스크에서 DB 엔드포인트로 직접 접근
  - RDS Proxy를 통한 연결 풀 관리

# ElastiCache Redis 연동  
Cache:
  - Session 저장 및 JWT 토큰 관리
  - WebSocket 연결 상태 정보 공유
  - ECS 서비스 디스커버리를 통한 자동 엔드포인트 해석

# Application Load Balancer 통합
LoadBalancer:
  - Target Group에 ECS 태스크 자동 등록/해제
  - Health Check 실패 시 자동으로 트래픽에서 제외
  - WebSocket Sticky Session 지원

 

EC2에서는 추가 설정이 필요:

  • 각 인스턴스마다 수동으로 로드밸런서 등록
  • 인스턴스 교체 시 수동으로 Target Group 관리
  • 서비스 디스커버리를 위한 별도 솔루션 필요 (Consul, etcd 등)

 

4. 운영 효율성

모니터링 스택 관리 비교:

EC2 방식:

# Prometheus 설정 파일 직접 관리
vim /opt/prometheus/prometheus.yml
systemctl restart prometheus

# Grafana 대시보드 수동 백업
grafana-cli admin reset-admin-password newpassword

# 로그 수집을 위한 별도 에이전트 설치
wget https://s3.amazonaws.com/amazoncloudwatch-agent/ubuntu/amd64/latest/amazon-cloudwatch-agent.deb
dpkg -i amazon-cloudwatch-agent.deb

 

ECS + CloudWatch 통합:

{
  "logConfiguration": {
    "logDriver": "awslogs",
    "options": {
      "awslogs-group": "/ecs/momo-app",
      "awslogs-region": "ap-northeast-2",
      "awslogs-stream-prefix": "ecs"
    }
  }
}

구체적인 시간 절약 효과:

  • 배포 시간: EC2 15-20분 → ECS 3-5분
  • 장애 대응: EC2 평균 10-15분 → ECS 1-3분 (자동 복구)
  • 모니터링 설정: EC2 2-3시간 → ECS 30분 (CloudWatch 기본 제공)
  • 보안 패치: EC2 월 4-6시간 → ECS 관리형 인프라로 불필요

Docker 구성 전략: 단일 책임 원칙

컨테이너 분리 전략

Docker의 핵심 철학인 "One Container, One Responsibility"에 따라 각 서비스를 독립적인 컨테이너로 구성해야합니다.:

직접 빌드가 필요한 컨테이너:

  • Spring Boot 애플리케이션 (momo-backend)

공식 이미지를 사용하는 컨테이너:

  • MySQL: mysql:8.0
  • Redis: redis:7-alpine
  • Elasticsearch: elasticsearch:8.8.0
  • Jaeger: jaegertracing/all-in-one:1.55
  • Prometheus: prom/prometheus:v2.47.0
  • Loki: grafana/loki:2.9.0
  • Grafana: grafana/grafana:10.1.0

 

Dockerfile 구성

Spring Boot 애플리케이션을 위한 Dockerfile은 다음과 같이 구성했습니다:

FROM openjdk:17-jdk-slim
WORKDIR /app
COPY build/libs/*.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]

이 Dockerfile은 최소한의 설정으로 효율적인 이미지를 생성하며, 멀티스테이지 빌드나 추가 최적화가 필요한 경우 확장 가능한 구조를 가지고 있습니다.


Docker Compose를 통한 로컬 개발 환경 구성

전체 시스템 구성

docker-compose.yml 파일을 통해 전체 시스템을 일관되게 관리할 수 있도록 구성해야합니다.:

version: '3.8'

services:
  # Spring Boot 애플리케이션
  momo-app:
    build: .
    ports:
      - "8080:8080"
    depends_on:
      - mysql
      - redis
      - otel-collector
    environment:
      - DB_URL=jdbc:mysql://mysql:3306/momo
      - DB_USERNAME=root
      - DB_PASSWORD=password
      - OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317
      - OTEL_SERVICE_NAME=momo-backend
    networks:
      - momo-network

  # MySQL 데이터베이스
  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: momo
      MYSQL_CHARACTER_SET_SERVER: utf8mb4
      MYSQL_COLLATION_SERVER: utf8mb4_unicode_ci
    ports:
      - "3306:3306"
    volumes:
      - mysql_data:/var/lib/mysql
    networks:
      - momo-network

  # 모니터링 스택
  prometheus:
    image: prom/prometheus:v2.47.0
    ports:
      - "9090:9090"
    volumes:
      - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
    networks:
      - momo-network

  grafana:
    image: grafana/grafana:10.1.0
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_USER=admin
      - GF_SECURITY_ADMIN_PASSWORD=admin
    networks:
      - momo-network

networks:
  momo-network:
    driver: bridge

volumes:
  mysql_data:

 

실행 방법

로컬 개발 환경에서는 다음 명령어로 전체 시스템을 실행할 수 있습니다:

# Gradle 빌드
./gradlew clean build -x test

# 전체 시스템 실행
docker-compose up --build

ECS 배포 아키텍처와 워크플로우

ECR + ECS + GitHub Actions 조합의 장점

  1. ECR (Elastic Container Registry): Docker 이미지의 안전한 저장소
  2. ECS (Elastic Container Service): 컨테이너 오케스트레이션과 실행 환경
  3. GitHub Actions: 자동화된 CI/CD 파이프라인

 

배포 워크플로우

1. GitHub Actions CI/CD 파이프라인

name: Deploy to ECS

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
    - name: Checkout code
      uses: actions/checkout@v3

    - name: Set up JDK 17
      uses: actions/setup-java@v4
      with:
        distribution: 'corretto'
        java-version: '17'

    - name: Build with Gradle
      run: ./gradlew clean build -x test

    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v2
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: ap-northeast-2

    - name: Login to ECR
      uses: aws-actions/amazon-ecr-login@v1

    - name: Build and push Docker image
      run: |
        docker build --platform linux/amd64 -t momo-app:latest .
        docker tag momo-app:latest ${{ secrets.AWS_ECR_REGISTRY }}/momo-app:latest
        docker push ${{ secrets.AWS_ECR_REGISTRY }}/momo-app:latest

    - name: Deploy to ECS
      run: |
        aws ecs update-service \
          --cluster momo-cluster \
          --service momo-service \
          --force-new-deployment

 

2. ECS 태스크 정의

{
  "family": "momo-task",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "256",
  "memory": "512",
  "executionRoleArn": "arn:aws:iam::account:role/ecsTaskExecutionRole",
  "containerDefinitions": [
    {
      "name": "momo-app",
      "image": "account.dkr.ecr.ap-northeast-2.amazonaws.com/momo-app:latest",
      "portMappings": [
        {
          "containerPort": 8080,
          "protocol": "tcp"
        }
      ],
      "environment": [
        {
          "name": "DB_URL",
          "value": "jdbc:mysql://rds-endpoint:3306/momo"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/momo-app",
          "awslogs-region": "ap-northeast-2",
          "awslogs-stream-prefix": "ecs"
        }
      }
    }
  ]
}

 

3. 배포 프로세스

  1. 코드 푸시: GitHub에 코드가 푸시되면 자동으로 CI/CD 파이프라인 시작
  2. 빌드: Gradle을 통해 JAR 파일 생성
  3. Docker 이미지 빌드: Dockerfile을 기반으로 이미지 생성
  4. ECR 푸시: 생성된 이미지를 ECR에 업로드
  5. ECS 배포: ECS 서비스를 업데이트하여 새 이미지로 배포
  6. 롤링 업데이트: 무중단 배포를 통해 서비스 연속성 보장

프로덕션 환경에서의 고려사항

1. 관리형 서비스 활용

  • MySQL: RDS를 사용하여 데이터베이스 관리 부담 감소
  • Elasticsearch: Amazon OpenSearch Service 활용

2. 모니터링과 로깅

  • CloudWatch: AWS 네이티브 모니터링 서비스
  • X-Ray: 분산 추적을 위한 AWS 서비스
  • CloudWatch Logs: 중앙화된 로그 관리

3. 보안 강화

  • VPC: 네트워크 격리를 통한 보안 강화
  • Security Groups: 세밀한 네트워크 접근 제어
  • IAM Roles: 최소 권한 원칙에 따른 권한 관리

성능과 비용 최적화 전략

Fargate vs EC2 선택 기준

Fargate 선택 시:

  • 서버 관리를 원하지 않는 경우
  • 트래픽이 불규칙한 경우
  • 개발 리소스를 애플리케이션에 집중하고 싶은 경우

EC2 선택 시:

  • 지속적으로 높은 CPU 사용률이 예상되는 경우
  • 비용 최적화가 중요한 경우
  • 특별한 인스턴스 타입이 필요한 경우

 

자동 스케일링 설정

# ECS 서비스 자동 스케일링 설정
Type: AWS::ApplicationAutoScaling::ScalableTarget
Properties:
  MaxCapacity: 10
  MinCapacity: 2
  ResourceId: service/momo-cluster/momo-service
  RoleARN: !Sub arn:aws:iam::${AWS::AccountId}:role/aws-service-role/ecs.application-autoscaling.amazonaws.com/AWSServiceRoleForApplicationAutoScaling
  ScalableDimension: ecs:service:DesiredCount
  ServiceNamespace: ecs

마무리

EC2에서 ECS로의 전환은 단순한 배포 방식의 변경이 아닌, 전체적인 개발 및 운영 패러다임의 변화를 의미합니다. 특히 복잡한 마이크로서비스 아키텍처를 가진 프로젝트에서는 ECS의 장점이 더욱 부각됩니다.

 

Docker와 Docker Compose를 통한 로컬 개발 환경 구성은 개발자 경험을 크게 향상시키며, ECR과 ECS를 활용한 배포 파이프라인은 안정적이고 확장 가능한 프로덕션 환경을 제공합니다.

 

앞으로도 클라우드 네이티브 기술의 발전과 함께 더욱 효율적인 배포 전략을 지속적으로 탐구하고 적용해 나갈 예정입니다. 이러한 경험이 비슷한 고민을 하고 있는 개발자들에게 도움이 되기를 바랍니다.