DevOps
[DevOps] 백엔드 Prod 서버 ASG 기반 롤링 배포 자동화 - Modie
예찬예찬
2025. 4. 8. 00:10
728x90
반응형
이번 글에서는 백엔드 프로덕션 환경을 기록합니다.
데브서버의 단순히 빠른 개발 적용 서버와 다르게 비용, 보안, 확장성까지 모두 고려해서 설계한 풀스택 인프라 구축기입니다.
(ECR, EC2, RDS, ALB, ASG, CloudFront, GitHub Actions까지 다 활용했다)
구성 개요
핵심 컨셉
- 프라이빗 서브넷 기반 EC2 (Auto Scaling Group)
- ALB로 외부 트래픽 관리
- RDS는 프라이빗으로 운영
- NAT Gateway를 통해 외부 통신 허용
- GitHub Actions로 CI/CD 자동화
아키텍처를 이렇게 선택한 이유
(초기 시도) 퍼블릭 서브넷 + 보안 그룹
처음에는 비용 아끼려고 EC2와 RDS를 퍼블릭 서브넷에 넣고, 보안 그룹으로 외부 접근만 막는 전략을 썼다.
나쁘진 않았는데 문제는…
- 퍼블릭 IP가 있으면 아무리 보안 그룹을 잘 짜도 위험이 남는다.
- IAM 키 유출이나 설정 미스 났을 때, 바로 공격 타깃 될 수 있다.
그래서 퍼블릭 구조는 포기.
(결론) 프라이빗 서브넷 + NAT Gateway
지금은 EC2, RDS 모두 프라이빗 서브넷에 두고,
외부 통신은 NAT Gateway로만 시킨다.
덕분에:
- 퍼블릭 IP가 아예 없어서 외부 직접 침입 가능성 차단
- NAT Gateway 요금이 추가되긴 하지만 보안을 위해 투자할 가치 있음
EC2 Role + AMI 최적화
- EC2 IAM Role을 붙여서, ECR에서 인증 없이 Docker pull 가능
- 커스텀 AMI를 만들어 Docker 설치 & 기본 이미지 pull까지 완료된 상태로 시작
- 인스턴스 부팅 후 바로 docker run으로 서비스 기동!
CI/CD 파이프라인 (백엔드)
메인 브랜치 푸시하면 자동 배포
- GitHub Actions에서 Docker 빌드
- ECR로 이미지 push (prod / commit-hash 태그 둘 다)
- 기존 prod 이미지는 prod-stable로 백업
- ASG 롤링 배포(Instance Refresh) 로 무중단 배포
- 만약 실패하면 prod-stable 이미지로 롤백
name: CI/CD Pipeline for ECR and ASG
on:
push:
branches:
- main
jobs:
build-and-deploy:
name: Build, Push to ECR, Deploy on EC2
runs-on: ubuntu-22.04
env:
ECR_REGISTRY: 418272768555.dkr.ecr.ap-northeast-2.amazonaws.com
ECR_REPOSITORY: modie/modie-be-prod
COMMIT_HASH: "${{ github.sha }}"
ASG_NAME: modie-be-auto
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Set short COMMIT_HASH
run: echo "COMMIT_HASH=$(echo $GITHUB_SHA | cut -c1-7)" >> $GITHUB_ENV
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v3
with:
aws-access-key-id: ${{ secrets.ECR_USER_ACCESS_KEY }}
aws-secret-access-key: ${{ secrets.ECR_USER_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-2
- name: Login to Amazon ECR
run: |
aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin $ECR_REGISTRY
- name: Build, tag, and push image to Amazon ECR
run: |
set -e # 오류 발생 시 즉시 종료
echo "Building Docker image..."
docker build -q -t $ECR_REGISTRY/$ECR_REPOSITORY:$COMMIT_HASH \
-t $ECR_REGISTRY/$ECR_REPOSITORY:prod .
echo "Saving previous stable version..."
aws ecr batch-get-image --repository-name $ECR_REPOSITORY --image-ids imageTag=prod \
--query 'images[].imageManifest' --output text > stable_manifest.json
aws ecr put-image --repository-name $ECR_REPOSITORY --image-tag prod-stable \
--image-manifest file://stable_manifest.json || echo "✅ prod-stable 태그 업데이트 완료!"
echo "Pushing Docker image to ECR..."
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$COMMIT_HASH &
docker push $ECR_REGISTRY/$ECR_REPOSITORY:prod &
wait # 모든 Push 완료될 때까지 대기
echo "✅ Image pushed successfully!"
- name: 기존 EC2 인스턴스 점진적 교체 (롤링 업데이트)
run: |
echo "🚀 시작: Auto Scaling Group 롤링 배포"
aws autoscaling start-instance-refresh --auto-scaling-group-name $ASG_NAME
echo "⏳ 롤링 배포 진행 중..."
sleep 10 # 배포 시작 대기
# 배포 진행 상태 확인
STATUS=$(aws autoscaling describe-instance-refreshes --auto-scaling-group-name $ASG_NAME \
--query "InstanceRefreshes[0].Status" --output text)
echo "현재 배포 상태: $STATUS"
if [[ "$STATUS" != "InProgress" && "$STATUS" != "Successful" ]]; then
echo "❌ 배포 실패! 자동 롤백 실행..."
aws autoscaling cancel-instance-refresh --auto-scaling-group-name $ASG_NAME
echo "🔄 이전 `prod-stable` 이미지로 복구"
aws ecr batch-get-image --repository-name $ECR_REPOSITORY --image-ids imageTag=prod-stable \
--query 'images[].imageManifest' --output text > rollback_manifest.json
aws ecr put-image --repository-name $ECR_REPOSITORY --image-tag prod \
--image-manifest file://rollback_manifest.json
echo "🚀 롤백 완료! 배포 종료"
exit 1
fi
echo "✅ 롤링 배포 성공!"
Dockerfile (Spring Boot)
FROM bellsoft/liberica-openjdk-alpine:17 AS builder
WORKDIR /app
COPY . .
RUN ./gradlew clean build -x test
FROM bellsoft/liberica-openjdk-alpine:17
WORKDIR /app
COPY --from=builder /app/build/libs/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
인프라 구성 시 고민한 것들
- EC2 인스턴스: t3.medium (Spring Boot 애플리케이션 운영에 필요한 메모리(4GiB)와 vCPU(2개)를 적절히 제공)
- 가격은 시간당 약 $0.0416
- 월 $30 정도로 충분히 쓸 수 있음
- 성능 테스트: 초당 300~500 요청 버텨냄
- CPU 사용률 60% 수준 유지
- 부팅 속도:
- AMI 최적화 덕분에 40~50초면 새 인스턴스 올라옴
- 롤백 대비:
- CI/CD에 자동 롤백 프로세스 내장
마무리
이번 구축은 단순히 서버 한 대 띄우는 게 아니라,
비용, 보안, 자동화, 확장성, 장애 대응까지 종합적으로 고려한 인프라 구축을 목표로 했습니다.
S3 + CloudFront + EC2 + RDS + ECR + ASG + GitHub Actions
이 조합으로, 추가 관리 거의 없이도 안정적이고 신뢰할 수 있는 배포/운영 환경을 완성했습니다.
아쉬운 점
아무리 Auto Scaling Group(ASG)으로 서버를 구성했더라도,
트래픽이 급격하게 몰릴 때는 여전히 한계가 느껴진다.
- 새로운 인스턴스가 추가되더라도
- EC2가 부팅되고 애플리케이션이 완전히 기동될 때까지 걸리는 웜업 타임이 있기 때문
즉, 스케일 아웃 신호가 오더라도
즉각적으로 부하를 분산시키지 못하고 몇 분간 서비스가 버벅거릴 수 있는 문제가 있다.
쿠버네티스 도입 고민
이런 문제를 해결하는 방법 중 하나로,
K8s를 도입하는 걸 고려할 수 있습니다.
- 쿠버네티스에서는 애플리케이션을 컨테이너 단위로 관리하기 때문에
- 새 인스턴스를 띄우는 대신, 기존 노드에 빠르게 Pod를 스케일링할 수 있다.
- 부하가 몰리더라도, 훨씬 빠르게(수 초~수 십 초 단위로) 대응할 수 있게 된다.
특히 EKS 같은 매니지드 쿠버네티스 서비스를 사용하면,
- 오토스케일링도 훨씬 세밀하게 설정할 수 있고
- 롤링 업데이트나 롤백 같은 작업도 훨씬 안전하게 처리할 수 있다.
요약
- ASG는 편하고 관리가 간단하지만, 급격한 트래픽 변화 대응에는 한계가 있다.
- Kubernetes는 더 빠른 스케일 아웃과 유연한 리소스 관리가 가능하다.
- 추후 트래픽 패턴이 더 복잡해진다면, 쿠버네티스 전환을 고려할 예정!
728x90
반응형