EC2를 넘어 ECS Fargate로 서버리스 컨테이너와 무중단 배포 아키텍처 구축
저번 글에선 AWS의 가장 기본적인 컴퓨팅 리소스인 EC2 인스턴스 하나에 Docker를 띄워 서비스를 배포하는 과정을 다뤘다.
하지만 서비스를 운영하다 보니 몇 가지 한계에 부딪혔다.
- 배포 시 다운타임 발생: 새로운 버전을 배포할 때마다 잠시 서비스를 멈춤
- 서버 관리의 부담: EC2 인스턴스의 OS 보안 패치, 스케일링 등을 직접 신경 써야함.
- 수동 배포의 번거로움: 코드를 짜는 시간보다 배포 스크립트를 돌리고 확인하는 데 쓰는 에너지가 큼.
이번 글에서는 이러한 문제를 해결하기 위해 ECS Fargate를 도입하여 서버리스(Serverless) 아키텍처로 전환하고,
GitHub Actions를 통해 무중단 자동 배포 파이프라인을 구축한 과정을 공유한다.
1. 전체 아키텍처 개요 (As-Is vs To-Be)
가장 큰 변화는 '관리형 서비스'의 적극적인 도입이다.
기존에는 Public Subnet의 EC2 하나가 모든 짐을 짊어졌다면, 이번에는 역할별로 리소스를 분리하고 가용성을 높였다.
🛠 전체 아키텍처 다이어그램

주요 변경 포인트:
- Compute: EC2 → ECS Fargate (서버 관리 불필요)
- Network: Public Subnet 단일 배포 → Private Subnet 배포 (보안 강화)
- Load Balancer: 직접 접속 → ALB (Application Load Balancer)
- Deploy: 수동 스크립트 → GitHub Actions + ECR
2. 왜 ECS Fargate인가? (Server vs Serverless)
컨테이너 오케스트레이션 도구를 선택할 때, 사실상 표준인 Kubernetes(EKS)와 AWS Native인 ECS 사이에서 고민했다.
결론적으로 ECS Fargate를 선택했는데, 이유는 다음과 같다.
- 낮은 러닝 커브와 운영 비용: 쿠버네티스는 강력하지만 클러스터 자체를 관리하고 유지보수하는 데 상당한 학습 비용과 리소스가 든다. 반면 ECS는 AWS 서비스들과 가장 직관적으로 통합되어 있어 설정이 간편하다.
- 비즈니스 로직 집중: 현재 단계에서는 인프라 자체를 복잡하게 관리하는 것보다, 안정적인 환경 위에서 애플리케이션 기능을 빠르게 배포하는 것이 우선이라 판단했다.
Fargate Launch Type
ECS에는 두 가지 모드가 있다.
- EC2 모드: 내가 띄운 EC2 위에서 컨테이너를 관리 (인스턴스 관리 필요)
- Fargate 모드: 인스턴스 없이 컨테이너 실행에 필요한 CPU/Memory만 할당받아 사용 (Serverless)
저는 인프라 관리보다 애플리케이션 로직에 집중하고 싶었기에, OS 관리가 전혀 필요 없는 Fargate를 선택했다.
3. 배포의 시작: Dockerfile과 ECR
Fargate는 '이미지' 기반으로 동작하기에 애플리케이션을 불변(Immutable)의 이미지로 만드는 것이 첫걸음이다.
3.1 Dockerfile 최적화 (Multi-stage Build)
빌드 환경과 실행 환경을 분리하여 이미지 크기를 줄였다.
# Build stage
FROM gradle:8.5-jdk21 AS build
WORKDIR /app
COPY build.gradle settings.gradle gradlew ./
COPY gradle gradle
COPY src src
RUN gradle bootJar --no-daemon -x test
# Runtime stage
FROM eclipse-temurin:21-jre
WORKDIR /app
# 보안을 위해 root가 아닌 별도 유저 생성
RUN groupadd -r spring && useradd -r -g spring spring
USER spring:spring
COPY --from=build /app/build/libs/*.jar app.jar
EXPOSE 8080
ENV JAVA_OPTS="-Xms256m -Xmx512m -XX:+UseContainerSupport"
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -Dspring.profiles.active=${SPRING_PROFILE:-default} -jar app.jar"]
3.2 ECR (Elastic Container Registry)
빌드된 이미지를 저장할 저장소이다. Docker Hub의 Private Repository와 같지만, AWS IAM과 연동되어 ECS가 이미지를 가져올 때 별도의 인증 번거로움이 줄어든다.
4. 핵심 구성 요소: Task Definition과 Service
ECS를 이해하려면 Task Definition(작업 정의)과 Service의 개념을 잡아야 한다.
- Task Definition: "어떤 컨테이너를 띄울 것인가?" (청사진)
- 사용할 이미지 URI
- CPU/Memory 할당량 (Fargate는 이 설정에 따라 과금이 결정)
- 환경 변수
- Service: "어떻게 실행하고 유지할 것인가?"
- Task의 개수 (Desired Count)
- 로드 밸런서 연결
- 배포 전략 (Rolling Update 등)
이번 프로젝트에서는 oddventure-task라는 패밀리 이름으로 정의하고, DB 접속 정보나 API 키 같은 민감 정보는 코드에 하드코딩하지 않고 실행 시점에 SSM에서 안전하게 주입받도록 구성했다.
5. 네트워크 구성과 보안 (Private Subnet & NAT Gateway)
아키텍처 그림을 보면 ECS Task들이 Private Subnet에 위치해 있다.
- 보안: 외부에서 직접 접근이 불가능하여 데이터베이스나 애플리케이션이 공격에 노출될 위험이 줄어든다.
- 트래픽 흐름:
- Inbound: 사용자는 인터넷 → ALB (Public Subnet) → ECS Task (Private Subnet) 경로로 접속한다.
- Outbound: ECS Task가 외부 API(Steam, Grid, Groq 등)를 호출하거나 ECR에서 이미지를 당겨올 때는 NAT Gateway를 통해 나간다.
Private Subnet에 있는 DB나 서버에 접근해서 디버깅해야 할 때는 다이어그램 좌측 하단에 있는 Bastion Host를 통해 터널링하여 접근하도록 구성했다.
6. 무중단 배포의 핵심: ALB와 Rolling Update
기존 EC2 배포의 가장 큰 불만이었던 '배포 중 서비스 중단'은 ECS의 Rolling Update 전략으로 해결되었다.
- 새로운 버전의 Task가 실행
- ALB가 새 Task의 Health Check( /actuator/health )를 수행
- 상태가 'Healthy'가 되면 트래픽을 새 Task로 흘려보냄
- 기존 버전의 Task를 종료(Draining)함
이 과정 덕분에 사용자는 배포가 일어나는지조차 모르게 서비스를 계속 이용할 수 있다.
7. 자동화의 완성: GitHub Actions
이제 코드를 main 브랜치에 푸시하기만 하면 이 모든 과정이 자동으로 일어난다.
워크플로우 요약:
- Checkout & Login: 코드 체크아웃 및 AWS ECR 로그인
- Build & Push: Docker 이미지를 빌드하고 ECR에 푸시 (태그는 Git SHA 활용)
- Deploy: 최신 Task Definition을 다운받아 새 이미지로 교체 후 ECS 서비스 업데이트
# .github/workflows/ecs-deploy.yml
- name: Fill in the new image ID in the Amazon ECS task definition
id: task-def
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: oddventure-container
image: ${{ steps.login-ecr.outputs.registry }}/oddventure-repo:${{ github.sha }}
- name: Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def.outputs.task-definition }}
service: oddventure-service
cluster: oddventure-cluster
wait-for-service-stability: true
8. 마치며: EC2에서 Fargate로 넘어오며 느낀 점
EC2에서 직접 Docker를 띄우던 방식에서 ECS Fargate로 넘어오면서 느낀 장단점은 명확했다.
- 장점:
- 운영 스트레스 해소: 서버가 죽었나? 디스크가 찼나? 걱정할 필요가 없어짐
- 안정성: Multi-AZ와 자동화된 로드 밸런싱으로 서비스가 훨씬 견고해짐
- 배포 속도: 클릭 한 번(Merge)으로 배포가 완료되니 개발 생산성이 비약적으로 상승
- 단점(고려사항):
- 초기 설정 난이도: VPC, Subnet, IAM Role, Security Group 등 네트워크와 권한에 대한 이해가 필수적이라 초기 러닝 커브가 있었다.
- 비용: 소규모 트래픽에서는 t2.micro 같은 EC2 인스턴스보다 Fargate 비용이 조금 더 나올 수 있다.
이번 마이그레이션을 통해 인프라에 대한 걱정을 덜고, Oddventure의 핵심 비즈니스 로직 개발에 더 집중할 수 있게 되었다.
'TIL' 카테고리의 다른 글
| AWS 인프라 기초 설계 (1) | 2025.12.08 |
|---|---|
| Spring Boot 성능 최적화 10 (0) | 2025.11.12 |
| Spring Boot 성능 최적화 9 (0) | 2025.11.11 |
| Spring Boot 성능 최적화 8 (0) | 2025.11.10 |
| 11/7 (0) | 2025.11.07 |