바로 이전의 글 인 Part7 : 인프라 구축기 를 개발할 때에는 순조롭게 구축하였다고 생각하였습니다.
하지만 곧 운영 환경으로 사용하기에는 치명적인 문제들이 있다는 것을 깨달았습니다.
문제는 다음과 같습니다.
1. HTTP 프로토콜의 보안 문제
문제: 모든 데이터가 평문으로 전송됨
위험: 사용자 비밀번호, 개인정보 등이 중간에서 가로채기 가능
사용자 인증 정보를 다루는 API인데, HTTP로 통신한다는 것은 심각한 보안 위험이었습니다. 현대 웹 표준에서도 Chrome 등의 브라우저는 HTTP 사이트에 "안전하지 않음" 경고를 표시합니다.
2. IP 주소 직접 노출
현재: http://54.180.125.227:8080
문제: 서버 IP가 그대로 노출되어 공격 대상이 되기 쉬움
도메인 없이 IP를 직접 사용하면 DDoS 공격 등의 위험에 더욱 취약합니다.
3. 확장성 부재
현재 구조:
Client → 단일 EC2 인스턴스 → RDS
문제점:
- EC2 장애 시 전체 서비스 중단
- 트래픽 증가 시 수동으로 서버 추가 필요
- 로드밸런서 없음
단일 장애점(Single Point of Failure)이 존재하고, 사용자가 늘어나면 수동으로 인프라를 확장해야 하는 상황이었습니다.
추가적인 문제점인 배포가 불편하여 CI/CD를 구축해야한다...? 정도 됩니다.
해결책 찾기: Elastic Beanstalk 발견
이런 문제들을 해결하기 위해 여러 방안을 고민하던 중, **AWS Elastic Beanstalk**이라는 PaaS를 알게 되었습니다.
Elastic Beanstalk이란?
개발자가 하는 일
- 코드 작성
- JAR 파일 업로드
- 끝!
Elastic Beanstalk이 자동으로 하는 일 :
- EC2 인스턴스 프로비저닝
- 로드밸런서 (ALB) 설정
- Auto Scaling 그룹 구성
- 보안 그룹 설정
- 헬스 체크 및 모니터링
- 로그 수집
- 플랫폼 업데이트
비교표
| 항목 | EC2 직접 배포 (ver1) | Elastic Beanstalk (ver2) |
| 프로토콜 | HTTP | HTTPS |
| 접근 방식 | IP:8080 | 도메인 (HTTPS) |
| 로드밸런서 | 없음 | ALB (자동 생성) |
| 스케일링 | 수동 | Auto Scaling |
| 배포 | SCP + SSH | 콘솔/CLI 업로드 |
| 인증서 | 없음 | ACM (무료, 자동 갱신) |
| 고가용성 | 없음 | 다중 AZ |
| 월 비용 | $0 | ~$16 (ALB) |
사실 여기서 Elastic Beanstalk 만 한다고 해서 비용이 증가하는건 아니고 ALB(Application Load Balence) 에서 비용이 발생합니다.
다른 써드파티를 사용하면 가격을 낮출 수 있지만 지금은 프로덕트를 개발하는 것이 목표이기 때문에 일단 눈물을 머금고 비용을 지출해보록 합시다.
ver2 구축기: Elastic Beanstalk으로의 전환
1단계: 환경 유형 선택의 딜레마
처음에는 비용을 절감하기 위해 "단일 인스턴스" 환경으로 생성했습니다.
하지만 곧 문제를 발견했습니다.
❌ ACM 인증서를 사용할 수 없음!
원인:
- AWS Certificate Manager (ACM) 인증서는 EC2에 직접 부착 불가능
- ACM 인증서를 사용하려면 ALB, CloudFront, API Gateway가 필요
구조 비교:
단일 인스턴스 환경:
인터넷 → EC2 (ACM 인증서 부착 불가능)
로드 밸런싱 환경:
인터넷 → ALB (ACM 인증서 부착) → EC2
결정:
HTTPS가 필수이므로 **로드 밸런싱 환경**으로 변경했습니다.
💡 참고: 단일 인스턴스에서도 Let's Encrypt를 사용하면 HTTPS를 구성할 수 있지만, 90일마다 수동 갱신이 필요하고 EB 재배포 시마다 재설정해야 하는 번거로움이 있습니다. 하지만 공짜임!!
2단계: 기존 RDS 연결
새로 RDS를 만들지 않고 기존 데이터베이스를 그대로 사용하기로 했습니다.
RDS 보안 그룹 수정:
인바운드 규칙 추가:
유형: PostgreSQL
포트: 5432
소스: Elastic Beanstalk 보안 그룹 (sg-xxxxx)
환경 변수 설정
DB_URL=jdbc:postgresql://modu-webtoon-db.c3cw2kggcfv4.ap-northeast-2.rds.amazonaws.com:5432/moduwebtoon
DB_USERNAME=postgres
DB_PASSWORD=[비밀번호]
JWT_SECRET=[시크릿]
SPRING_PROFILES_ACTIVE=prod
SERVER_PORT=5000
⚠️ 주의: `DB_URL`에 `jdbc:postgresql://` 프리픽스를 반드시 포함해야 합니다. 이것을 빼먹어서 502 에러가 발생했던 적이 있습니다.
3단계: 도메인 및 SSL 설정
AWS에서도 Amazon Route 53 이란 도메인 서비스가 있고 쉽게 붙일 수 있지만 가격이 년 12만원일 정도로 굉장히 비싸므로 외부 도메인 대행사에서 구매하였습니다.
도메인 구매:
- 등록 대행사: Gabia (가비아)
- 도메인: `moduwebtoon.co.kr`
- 서브도메인: `api.moduwebtoon.co.kr`
- 가격: 16,000원/년
DNS 설정 (Gabia):
호스트: api
타입: CNAME
값: modu-webtoon-env.eba-s7cymfrt.ap-northeast-2.elasticbeanstalk.com. (BeanStalk를 만들 시 하나가 생성됩니다.)
TTL: 3600
⚠️ 중요: CNAME 값 끝에 점(`.`)을 반드시 붙여야 합니다!
ACM SSL 인증서 발급:
- AWS Certificate Manager에서 인증서 요청
- 도메인: `api.moduwebtoon.co.kr`
- 검증 방법: DNS 검증
- ACM이 제공하는 CNAME 레코드를 Gabia DNS에 추가
- 약 5-10분 후 인증서 발급 완료 ✅
4단계: HTTPS 설정의 난관
첫 번째 시도: Elastic Beanstalk 콘솔에서 HTTPS 리스너 추가
- 구성 > 로드 밸런서 > 리스너 추가
- 포트 443, HTTPS, ACM 인증서 선택
- 결과: "연결할 수 없음" 에러
두 번째 시도: ALB 보안 그룹 확인
- 문제 발견: 443 포트 인바운드 규칙이 없음!
- 해결: HTTPS(443) 인바운드 규칙 추가
세 번째 시도: EC2 콘솔에서 직접 HTTPS 리스너 추가
EC2 > 로드 밸런서 > 리스너 추가
프로토콜: HTTPS
포트: 443
SSL 인증서: api.moduwebtoon.co.kr (ACM)
✅ 성공!
5단계: Health Check 문제 해결
배포 후 환경 상태가 Red로 변경되는 문제 발생.
로그 확인:
172.31.9.202 - - "GET / HTTP/1.1" 500 83 "-" "ELB-HealthChecker/2.0"
원인:
- ALB가 `/` 경로로 Health Check를 시도
- Spring Boot 앱에는 루트 엔드포인트가 없어서 500 에러
해결:
Health Check Path 변경: / → /api/v1/sections
환경 상태가 Green으로 변경 ✅
6단계: 최종 배포 및 테스트
JAR 빌드:
./gradlew clean build -x test
Elastic Beanstalk 콘솔에서 배포:
- 환경 > 업로드 및 배포
- JAR 파일 선택 및 업로드
🎉 배포 완료! 🎉
테스트:
curl https://api.moduwebtoon.co.kr/api/v1/sections
결과:
{
"success": true,
"data": [...],
"message": "섹션 목록 조회 성공"
}
최종 아키텍처
┌─────────────────────────────────────────────────────────────┐
│ AWS Cloud │
│ │
│ ┌─────────────────┐ │
│ │ Gabia DNS │ │
│ │ api.moduwebtoon │ │
│ │ .co.kr │ │
│ └────────┬────────┘ │
│ │ CNAME │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Elastic Beanstalk Environment │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ Application Load Balancer (ALB) │ │ │
│ │ │ - HTTPS:443 (ACM 인증서) │ │ │
│ │ │ - Health Check: /api/v1/sections │ │ │
│ │ └─────────────────┬───────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ Auto Scaling Group (Min:1, Max:4) │ │ │
│ │ │ │ │ │
│ │ │ ┌──────────────────────────────────┐ │ │ │
│ │ │ │ EC2 Instance (t2.micro) │ │ │ │
│ │ │ │ - Java 17 (Corretto) │ │ │ │
│ │ │ │ - Spring Boot (Port 5000) │ │ │ │
│ │ │ └──────────────────────────────────┘ │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ └─────────────────────┼───────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ RDS PostgreSQL (db.t3.micro) │ │
│ │ - 데이터베이스: moduwebtoon │ │
│ │ - 포트: 5432 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
트러블슈팅 회고
구축 과정에서 마주했던 주요 문제들:
1. 502 Bad Gateway
- 원인: 환경 변수 `DB_URL`에 `jdbc:postgresql://` 프리픽스 누락
- 해결: 전체 JDBC URL 입력
2. Health Check 실패 (Red 상태)
- 원인: ALB가 존재하지 않는 `/` 경로로 Health Check
- 해결: Health Check 경로를 `/api/v1/sections`로 변경
3. HTTPS 리스너 "연결할 수 없음"
- 원인: Elastic Beanstalk 콘솔의 버그 또는 설정 동기화 문제
- 해결: EC2 콘솔에서 직접 HTTPS 리스너 추가
4. HTTPS 연결 타임아웃
- 원인: ALB 보안 그룹에 443 포트 인바운드 규칙 미설정
- 해결: 보안 그룹에 HTTPS(443) 인바운드 규칙 추가
5. Gabia CNAME 검증 오류
- 원인: CNAME 레코드 값 끝에 점(`.`) 누락
- 해결: CNAME 값 끝에 `.` 추가 (예: `example.com.`)
개선 효과
Before (ver1)
❌ HTTP 평문 통신 (보안 취약)
❌ IP 주소 직접 노출
❌ 단일 장애점 존재
❌ 수동 스케일링
❌ 불편한 배포 프로세스
✅ 비용: $0
After (ver2)
✅ HTTPS 보안 통신
✅ 도메인 사용 (api.moduwebtoon.co.kr)
✅ ALB를 통한 고가용성
✅ Auto Scaling 지원
✅ 간편한 배포 (JAR 업로드만)
✅ ACM 자동 인증서 관리
💰 비용: ~$16/월 (ALB)
마치며
처음에는 비용 절감을 위해 가장 단순한 EC2 직접 배포 방식을 선택했지만, 운영 환경의 요구사항(보안, 안정성, 확장성)을 충족하기 위해 Elastic Beanstalk으로 전환하게 되었습니다.
월 $16의 추가 비용이 발생하지만...
저는 인프라에 대해 모르니 하나하나 뜯어가며 개선해나가야 할거같습니다!
'AI에서 살아남기(to. Android Developer) > 1인 프로젝트' 카테고리의 다른 글
| (모두의 웹툰) Part7 : 인프라 구축기 [잘못되었던 방식] (0) | 2025.12.17 |
|---|---|
| (모두의 웹툰) Part 6: API 설계 (0) | 2025.12.17 |
| (모두의 웹툰) Part 3: 핵심 기능 정의 (0) | 2025.12.17 |
| (모두의 웹툰) Part 1: 시작하며 - 왜 1인 개발을 시작하는가 (0) | 2025.12.17 |