[ Internet ]
|
v
[ Route53 ]
|
v
[ ALB ] ← (여기에 WAF를 "나중에" 붙일 예정)
|
v
[ ECS Fargate ]
|
v
[ Spring Boot App ]
이번 글에서는 특정 기능 구현 이야기가 아니라,
Spring Boot 기반 API 서버를 SaaS 구조로 설계하고 AWS 상에 배포하면서 고민했던 아키텍처 포인트를 정리해보려 한다.
단순히 애플리케이션을 배포하는 것을 넘어, 멀티테넌시 구조, 무중단 배포 전략, 헬스체크 설계, 네트워크 경계 설정, 관찰성까지 고려한 “운영 가능한 구조”를 목표로 설계했다. (트래픽 분산처리는 하지 않음.)

EC2 vs ECS
초기에는 EC2에 직접 배포할지, ECS를 사용할지 고민했다.
Spring Boot 애플리케이션은 어차피 Docker 컨테이너로 패키징할 예정이었고,
운영 환경 또한 컨테이너 단위로 관리하고 싶었다.
EC2를 선택한다면 다음을 직접 관리해야 한다.
- OS 패치 및 보안 업데이트
- Docker 런타임 관리
- 인스턴스 스케일링 전략
- 로드밸런서 및 네트워크 구성
- 배포 시 인스턴스 단위 교체 전략
반면 ECS(Fargate)는 컨테이너 실행에 집중할 수 있는 환경을 제공한다.
Docker Image → ECR → ECS Service
구조로 배포하면, 인프라 레벨의 서버 관리 부담을 크게 줄일 수 있다.
ECS를 선택한 이유
컨테이너 중심 아키텍처와의 정합성
이미 Docker 기반으로 설계했기 때문에,
컨테이너를 1급 실행 단위로 취급하는 ECS가 자연스러운 선택이었다.
EC2 위에서 Docker를 직접 관리하는 것보다,
컨테이너 오케스트레이션 레이어를 활용하는 것이 구조적으로 명확하다고 판단했다.
비용 관점
이번 서비스는 트래픽이 일정하지 않고, 고정적으로 인스턴스를 상시 유지할 필요가 없었다.
Fargate는
- 인스턴스 예약 불필요
- 사용한 리소스만 과금
- 초기 운영 비용 부담 감소
라는 장점이 있었다.
EC2는 유연성이 높지만, 소규모 SaaS 서비스에서는 오히려 관리 오버헤드가 더 클 수 있다고 판단했다.
네트워크 및 라우팅 구성의 편의성
ECS + ALB 구조
Internet
↓
ALB
↓
Target Group
↓
ECS Task
이 구조는
- 헬스체크 기반 트래픽 제어
- 무중단 배포 (Rolling Update)
- 네트워크 경계 분리 (ALB SG → ECS SG)
- 향후 스케일아웃 확장
를 지원하기 때문에 EC2 단독 구성보다, 운영 관점에서 구조적 안정성을 확보하기 쉬웠다.
ALB를 선택한 이유
우선 서비스를 계속 운영 하되 , 비용적인 부분에서 최소화를 하고 싶었기 때문에
ip 접속 제한이 필수였다. 만약 ec2 일 경우에는 엔진엑스 설정을 통해 블랙 화이트 리스트를 설정하면 됐지만, ecs로 배포하기로 마음을 먹었기 때문에 ALB + WAF 의 경우를 생각해서 ALB를 선택했다.
(추후 ip 제한을 달기 위해서 현재는 적용하지 않았음.)
설정 순서
Step A. RDS(Postgres) 만들기 (DB 먼저)
Step B. ECR에 Spring 이미지 푸시
Step C. ECS Fargate + ALB(HTTPS)로 서비스 오픈
Step D. Route53에서 fileext.yeseulkim.cloud → ALB 연결
Step E. (나중에) WAF로 IP 제한 ON
우선 들어가기 전에 로컬에서 먼저 해둔 설정들 부터 정리해보자면
로컬에서의 기본 설정
들어가기 전, 기본적으로 도메인을 따로 구매해놔야 한다. (가비아에서 구매함! )
서버는 모놀로식으로 구성했기 때문에, 여러개를 할 필요 없이 ECR 에 올릴 하나의 도커 이미지만 연결하면 됐었다.
내 컴퓨터 로컬 환경에서 aws 로그인이 가능해야 한다. (왜냐면 이미지 빌드 한 뒤에 올리는 과정 때문... 여러개의 방법이 있었는데 처음은 키 발급이었음.)
ECS 배포에 들어가기 전에, 단순히 콘솔을 여는 것보다 먼저 준비해야 할 것들이 있다.
이 단계를 정리하지 않으면 이후 과정에서 권한 오류, 네트워크 오류, 이미지 푸시 실패 같은 문제가 반복된다.
AWS 계정 및 IAM
루트 계정으로 작업하지 않기
운영 환경에서는 루트 계정 대신 IAM User를 생성하여 작업해야 한다.
필수 권한 예시
- ECR Full Access
- ECS Full Access
- Elastic Load Balancing
- IAM Role 관리 권한
- CloudWatch Logs 권한
AWS CLI 설치 및 로그인
로컬에서 Docker 이미지를 ECR에 업로드해야 하므로
AWS CLI 설정은 필수다.
aws configure
입력 항목
- AWS Access Key
- AWS Secret Key
- Default Region (ex: ap-northeast-2)
- Output format (json 권장)
확인
aws sts get-caller-identity
정상적으로 계정 ID가 나오면 설정 완료.
ECS Service Linked Role 생성
ECS가 내부적으로 ALB, Target Group 등을 연결하기 위해 필요한 역할이다.
aws iam create-service-linked-role --aws-service-name ecs.amazonaws.com
이미 있다면 에러가 나지만 무시해도 된다.
Docker Desktop 실행 확인
ECR 로그인 및 이미지 푸시 전 반드시 확인해야 한다.
docker ps
여기서
Cannot connect to the Docker daemon
오류가 나오면 Docker Desktop이 실행되지 않은 상태다.
Mac(M1/M2)의 경우 반드시 실행 후 다시 시도해야 한다.
빌드 시 주의할 점!!!!!!!!
OS 에 따라 안돌아갈수도 있기 때문에,
나는 위의 방식으로 빌드를 진행했음!docker buildx build \ --platform linux/amd64 \ -t ${ECR_URI}:latest \ --push \ .
ECR 로그인
Docker 이미지를 AWS ECR에 업로드하기 위해 로그인한다.
aws ecr get-login-password --region ap-northeast-2 \
| docker login --username AWS --password-stdin <ACCOUNT_ID>.dkr.ecr.ap-northeast-2.amazonaws.com
로그인이 성공하면
Login Succeeded
가 출력된다.
도메인 준비
- Route53 사용할 거임
- ALB Alias 레코드 연결할 거
를 이용할 거기 때문에 미리 도메인을 구매해놔야 된다.
(도메인 등록은 aws 에서 프리티어 계정에는 지원을 안하는걸로 알고있기 때문에 참고하여 설정하시길!)
클라우드 세팅
앞서 로컬에서의 기본 세팅을 마쳤으면, 클라우드 세팅을 미리 해두는게 좋을 거 같다.
중심으로 본거는 네트워크와 보안 쪽이다. (잘 올려도 여기서 연결이 잘 안되면 호스팅 자체가 안됨.)
보안 & 네트워크
나는 우선 default VPC 를 선택했다. (단순 포폴용이기 때문에)
그래도 보안그룹은 따로 나눠놔야된다. (안그러면 ECS 용과 RDS 가 섞여버림.)
즉 설정해둔 VPC 에 총 3개의 보안그룹을 생성해둔다.
SG-ALB (예: sg-alb-(프로젝트명))
ALB public inbound
- Inbound: 80, 443 from 0.0.0.0/0
- Outbound: all
Inbound Rules
| HTTPS | 443 | 0.0.0.0/0 | 외부 사용자 접속 허용 |
※ HTTP(80)는 HTTPS 리다이렉트용이면 80 추가 가능
※ 그 외 모든 포트는 차단

Outbound Rules (ALB → ECS 통신 허용)
| All traffic | All | 0.0.0.0/0 |

SG-ECS (예: sg-ecs-(프로젝트명))
- Inbound: 8089 from SG-ALB
- Outbound: all (RDS 붙이면 5432는 아웃바운드에서 자동 OK)
Inbound Rules (ALB → ECS)
| TCP | 8089 | 위에서 만든 보안 그룹 | ALB에서만 접근 허용 |
0.0.0.0/0 금지
직접 외부 접근 차단
Outbound Rules (ECS → RDS 통신 허용)
| All traffic | All | 0.0.0.0/0 |

SG-RDS (예: sg-rds-(프로젝트명))
- Inbound: 5432 from SG-ECS
- Public access: No
Inbound Rules
| PostgreSQL | 5432 | ECS SG | ECS에서만 DB 접근 허용 |
VPC 전체 허용 금지
default SG 허용 금지
0.0.0.0/0 절대 금지
Outbound Rules
기본값 유지 (All traffic 허용)
Internet
↓ 443
ALB (Public Subnet)
↓ 8089 (SG로 제한)
ECS Fargate (Private)
↓ 5432 (SG로 제한)
RDS (Private)
ACM 인증서 발급 & Route53 설정하기
위의 보안그룹설정이 마쳤다면, 편하게 도메인 연결을 하기 위해서 Route 설정과 ACM 인증서를 먼저 발급해둔다.
여기서 만들어둔 인증서는 나중에 loadbalancer 에 붙음. 인증서 발급을 요청하면 도메인의 CNAME 이 나오는데 이걸 Route53에 붙여줘야 된다.
첫번째로 해야될것은 route 53의 host zone 생성이다.
생성 시에 ns 레코드 4개의 네임서버가 나오는데, 나는 DNS 관리를 가비아에 들어가서 매일 하기 귀찮을 듯 해 네임서버를 가비아에 등록해서, DNS 관리를 route 53에서 진행할 수 있게 했다.

이렇게 설정을 해두면 이제 route 53 에서 DNS 관리를 할 수 있음!
두번째로 해야될 설정은 HTTPS 인증서 발급이다.
ACM (Certificate Manager) 에 접속 (설정 시 나는 ALB나 전체 서비스의 리전을 단일로 했음. 서울로 선택!)
인증서 요청할 때의 주의사항은 *.~~.~~ 는 빼야된다.. (비용 폭탄됨)
인증서 요청 후에는 CName 레코드가 나오는데 이제 그걸로 Route 53 의 도메인에 레코드로 등록해주면 되고,
기다리다보면 인증서의 상태가 변경된다.
ECS + ALB+RDS
기본적인 세팅을 마쳤기 때문에 이제 전체 설정을 해줘야 된다.
(보안그룹 선택과 네트워크는 위에서 설정해뒀던걸 잘 기억하며 선택하면 됨.)
위에서 설정한 aws cli 를 통해, ecr에 이미지를 배포해야 된다.
해당 ECR 에 들어가서 실제로 존재하는지 확인을 한다. (배포과정에 대한 설명은 생략,, gpt가 잘 알려줌 ㅜ)
이미지가 무사히 올라가있다면 ecs 를 하기 전에 RDS 먼저 설정을 해보려 한다.
(ecs 먼저하게 되면, 배포 중 rds 연결 에러가 뜨기 때문에... )
RDS 연결
우선 일단 RDS 연결 먼저 해두려고 한다. (ECS 올릴 때 에러 피하기 위함임..미리 안해두면 재빌드가 되기 때문에 ㅜ)

선택은 자유지만, 나는 포폴용으로 올리는 예시기 때문에 비용이 최저로 나올 수 있게 올렸다.
마스터 사용자 이름이나 암호는 Spring의 (백엔드서버) 프로포티에 입력해둔 값들로 적으면 된다.
네트워크 같은 경우는 default VPC 보다는 따로 프로젝트용 설정을 권장하는데 굳이 여러개의 프로젝트를 올릴게 아니라면, 기본을 사용해도 된다. (하지만 보안 그룹은 분리해야함.)
퍼블릭 액세스 같은 경우는 인터넷에 직접 노출할지 말지를 결정하는 용도인데, 개발용에 외부 통신이 필요하다면 오픈해도 되지만, 나는 내부 통신으로만 열어뒀다. 만들고 나서 나오는 RDS 의 엔드포인트로 spring에서 접근하기 떄문에 저장해놔야 된다.
ECR에 Spring 이미지 푸시하기
RDS 를 마쳤다면, 이제 docker build 를 해야할 차례!
Spring boot 던 python이던 환경변수 파일 설정은 중요하다. 우선 Spring 기준으로 해둔 설정들만 공유해보면
.env.prod 모드에는 RDS 정보값 입력, cors 다시한번 확인하기!

ECS 로 클러스터 만들기
클러스터를 만들 때 주의할 점은 health check 부분,,
태스크를 만들게 되면 loadbalancer 설정을 하게 되는데 이때 리스너 규칙에 설정하는 헬스체크의 시간대를 잘 지정해둬야 된다.
Health Check Interval & Threshold
기본값으로 두면 배포 직후 바로 Unhealthy로 빠지는 경우가 있다.
Spring Boot는 기동 시간이 있기 때문에,
기본 Health Check 설정으로 두면 애플리케이션이 완전히 올라오기 전에 ALB가 Unhealthy로 판단해버린다.
이 경우 다음과 같은 문제가 발생할 수 있다.
- 배포 직후 태스크가 반복적으로 재시작됨
- Rolling Update가 제대로 동작하지 않음
- 서비스가 순간적으로 0개 태스크 상태가 됨
- 무중단 배포 실패
그래서 나는 다음과 같이 설정했다.
- Interval: 30초
- Timeout: 5초
- Healthy threshold: 2
- Unhealthy threshold: 5
이렇게 하면 애플리케이션이 완전히 기동될 시간을 확보할 수 있고,
배포 시에도 트래픽이 기존 태스크에서 신규 태스크로 자연스럽게 넘어간다.
헬스체크는 단순히 “살아있나?”를 확인하는 기능이 아니라,
- 무중단 배포를 가능하게 하는 핵심 장치
- 트래픽 흐름을 제어하는 스위치
- 장애 감지의 1차 필터
역할을 한다.
ECS + ALB 구조에서는
헬스체크 설계가 곧 운영 안정성 설계라고 봐도 무방하다.
최종 아키텍처 정리
Internet
↓ 443
ALB (Public Subnet)
↓ 8089 (SG 제한 + Health Check)
ECS Fargate (Private)
↓ 5432 (SG 제한)
RDS (Private)
- 외부 직접 접근 차단
- 내부 통신만 허용
- HTTPS 강제
- DB 비공개
- 헬스체크 기반 트래픽 제어
'DevOps > AWS' 카테고리의 다른 글
| [AWS] ECS는 Running인데 계속 Unhealthy? ALB Health Check 실패의 진짜 원인 (보안그룹 아웃바운드 문제) (Product : SpeakNote) (0) | 2026.02.16 |
|---|---|
| [cloud computing] 클라우드 컴퓨팅과 가상화 기술 (0) | 2025.12.11 |
| [cloud computing] 클라우드 컴퓨팅 (CC) 이란? (0) | 2025.12.10 |