48~52일차) 2025-03-10 ~ 2025-03-14 [1. AWS 팀 프로젝트]
🚀주제: AWS 서비스 + 테라폼 + CI / CD에 초점을 맞춘 3-tier 토이프로젝트
아키텍처 구성도
CI/CD 파이프라인을 통해 프론트엔드와 백엔드를 배포 자동 배포 및 스케일링 기능을 포함한 웹 애플리케이션 인프라를 구축
1️⃣ 배포 프로세스 (CI/CD 자동화)
- GitHub Actions: 개발자가 GitHub에 코드를 푸시하면 GitHub Actions가 이를 감지하고 빌드 파이프라인을 실행
- S3 업로드: 빌드된 애플리케이션이 S3 버킷으로 업로드됨
- AWS CodeDeploy: S3에서 빌드된 파일을 가져와 배포를 진행
- EC2 Auto Scaling Group: EC2 인스턴스에서 애플리케이션이 실행되며, 필요에 따라 Auto Scaling을 통해 서버 수를 조절
2️⃣ 네트워크 및 인프라 구성
✅ VPC (가상 네트워크)
- Public Subnet:
- GitHub Actions, Bastion, NAT Gateway가 위치
- WEB ALB (Application Load Balancer)를 통해 외부 트래픽을 관리
- Private Subnet:
- Auto Scaling Group을 사용해 백엔드(서버)와 프론트엔드를 관리
- WAS ALB (Web Application Server Load Balancer)를 통해 내부 서버 간 로드 밸런싱
✅ 로드 밸런싱
- WEB ALB: 클라이언트 요청을 받아 백엔드 서비스로 전달
- WAS ALB: 내부에서 백엔드 애플리케이션 간 트래픽을 분산
3️⃣ AWS 서비스 사용
- Route 53: 도메인 네임 서비스(DNS) 설정 (https://www.sujoung.store/)
- ACM (AWS Certificate Manager): SSL/TLS 인증서 관리
- Auto Scaling Group: EC2 인스턴스를 자동으로 확장/축소
- NAT Gateway: Private Subnet의 EC2 인스턴스가 인터넷에 접근 가능하도록 지원
✅ 결과
- VPC 내부 3-tier(web, was, db) 구축
- client의 도메인 접근
- React / SpringBoot
- 연동 및 배포(Docker)
- CI/CD - Blue/Green 배포
- Github Action
📌 전체 인프라
- VPC
- tgt-vpc(10.10.0.0/16)
- 서브넷
- ap-northeast-2a
- tgt-vpc-2a-pub-sub(10.10.10.0/24)
- tgt-vpc-2a-pri-sub(10.10.11.0/24)
- tgt-vpc-2a-pri-sub2(10.10.12.0/24)
- ap-northeast-2c
- tgt-vpc-2c-pub-sub(10.10.20.0/24)
- tgt-vpc-2c-pri-sub(10.10.21.0/24)
- tgt-vpc-2c-pri-sub2(10.10.22.0/24)
- ap-northeast-2a
- 라우팅 테이블
- tgt-vpc-2a-pub-rt → tgt-vpc-2a-pub-sub
- tgt-vpc-2a-pri-rt → tgt-vpc-2a-pri-sub
- tgt-vpc-2c-pub-rt → tgt-vpc-2c-pub-sub
- tgt-vpc-2c-pri-rt → tgt-vpc-2c-pri-sub
- 네트워크 연결(IGW, NATGW)
- tgt-vpc-igw → tgt-vpc-2a-pub-rt, tgt-vpc-2c-pub-rt
- tgt-vpc-nat-gw → tgt-vpc-2a-pri-rt, tgt-vpc-2c-pri-rt
- 통신 확인
- EC2
- instance 생성
- tgt-web-1
- 보안 그룹: tgt-web-sg
- tgt-was-1
- 보안 그룹: tgt-was-sg
- tgt-web-1
- AMI 생성: instance 중지 후 이미지 생성
- tgt-web-img → tgt-web-1
- tgt-was-img → tgt-was-1
- 시작 템플릿 생성
- tgt-web-tem → tgt-web-img
- tgt-was-tem → tgt-was-img
- load balancer(ALB) 생성
- tgt-web-alb(Internet-facing) → tgt-vpc-2a-pri-sub, tgt-vpc-2c-pri-sub
- 보안 그룹: tgt-web-alb-sg
- 리스너: 443, 80 → 443(리다이렉션)
- tgt-was-alb(Internal) → tgt-vpc-2a-pri-sub2, tgt-vpc-2c-pri-sub2
- 보안 그룹: tgt-was-alb-sg
- 리스너: 8080
- 대상 그룹 생성
- tgt-web-tg → tgt-web-alb
- tgt-was-tg → tgt-was-alb
- Auto Scaling 그룹 생성
- tgt-web-asg
- 태그) Name : tgt-web-asg
- tgt-was-asg
- 태그) Name : tgt-was-asg
- 인스턴스 생성 확인
- tgt-web-asg
- tgt-web-alb(Internet-facing) → tgt-vpc-2a-pri-sub, tgt-vpc-2c-pri-sub
- instance 생성
- IAM
- 역할 생성
- SSM-EC2-Role
- tgt-web-asg 인스턴스(tgt-vpc-2a-pri-sub)에 IAM 역할 수정
- session manger(SSM) 연결
- private subnet의 web instance에 접속 확인
- 역할 생성
- Aurora and RDS
- 데이터베이스 생성
- tgt-aurora-db
- 보안그룹: tgt-aurora-db-sg
- tgt-aurora-db-instance-1 (Primary DB)
- tgt-aurora-db-instance-1-ap-northeast-2a (Standby DB)
- 보안그룹: tgt-aurora-db-sg
- tgt-aurora-db
- 서브넷 그룹 생성
- tgt-aurora-subnet-group → tgt-aurora-db
- private subnet의 was instance에서 AuroraDB 접속 확인
- tgt-aurora-subnet-group → tgt-aurora-db
- 데이터베이스 생성
- Route 53
- 퍼블릭 호스팅 영역 생성
- 도메인 이름: eitherwho.store
- 가비아
- 네임서버 등록
- cmd에서 nslookup eitherwho.store 확인
- 레코드 생성
- www
- 별칭
- 트래픽 라우팅 대상: Classic Load Balancer
- 로드 밸런서 선택: tgt-web-alb-979370717.ap-northeast-2.elb.amazonaws.com
- 브라우저에서 www.eitherwho.store 확인
- www
- 네임서버 등록
- AWS Certificate Manager(ACM)
- 퍼블릭 인증서 요청
- 도메인 이름: *.eitherwho.store
- Route 53에서 레코드 생성
- CNAME ACM 인증 레코드 확인
- 퍼블릭 인증서 요청
- 3-tier에 서비스 연결
- private subnet의 was instance IP를 백엔드에서 받아와 프론트엔드에 연동해 표시
- 프론트엔드: React
- 백엔드: SpringBoot
- private subnet의 was instance IP를 백엔드에서 받아와 프론트엔드에 연동해 표시
📌 Terraform
[테라폼] VPC → ELB → ASG
1. Terraform을 활용한 구축 방법
Terraform을 사용하면 인프라를 코드로 관리하면서 자동화 가능
/together-project
│── variables.tf # 변수 정의
│── provider.tf # AWS Provider 설정
│── vpc.tf # VPC 및 서브넷 생성
│── elb.tf # Load Balancer 구성
**│── asg.tf # Auto Scaling Group 설정 → 잘안되네
│── ecs.tf # ECS Cluster 및 Task Definition → 아직안함
│── rds.tf # RDS (DB) 설정
│── s3.tf # S3 버킷 및 이벤트 설정**
│── iam.tf # IAM 역할 및 정책 설정
│── security.tf # 보안 그룹 및 네트워크 ACL 설정
1) VPC 생성
- VPC
- Public Subnet (Bastion, NAT Gateway 배치)
- Private Subnet (EC2, DB 배치)
- Internet Gateway, Route Table, NAT Gateway 설정
# VPC 생성
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "MainVPC"
}
}
# 퍼블릭 서브넷 1
resource "aws_subnet" "public_subnet_1" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
availability_zone = "ap-northeast-2a"
map_public_ip_on_launch = true
tags = {
Name = "PublicSubnet-1"
}
}
# 퍼블릭 서브넷 2
resource "aws_subnet" "public_subnet_2" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.2.0/24"
availability_zone = "ap-northeast-2c"
map_public_ip_on_launch = true
tags = {
Name = "PublicSubnet-2"
}
}
# 프라이빗 서브넷 - WAS
resource "aws_subnet" "private_subnet_was" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.3.0/24"
availability_zone = "ap-northeast-2a"
tags = {
Name = "PrivateSubnet-WAS"
}
}
# 프라이빗 서브넷 - DB
resource "aws_subnet" "private_subnet_db" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.4.0/24"
availability_zone = "ap-northeast-2c"
tags = {
Name = "PrivateSubnet-DB"
}
}
# 인터넷 게이트웨이 생성
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "MainIGW"
}
}
# 라우팅 테이블 생성 (퍼블릭 서브넷용)
resource "aws_route_table" "public_route_table" {
vpc_id = aws_vpc.main.id
tags = {
Name = "PublicRouteTable"
}
}
# 라우팅 테이블에 IGW 연결
resource "aws_route" "internet_access" {
route_table_id = aws_route_table.public_route_table.id
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
# 퍼블릭 서브넷 1에 라우팅 테이블 연결
resource "aws_route_table_association" "public_subnet_1_association" {
subnet_id = aws_subnet.public_subnet_1.id
route_table_id = aws_route_table.public_route_table.id
}
# 프라이빗 서브넷용 라우팅 테이블 생성
resource "aws_route_table" "private_route_table" {
vpc_id = aws_vpc.main.id
tags = {
Name = "PrivateRouteTable"
}
}
2) ALB (Application Load Balancer) 생성
- WEB ALB (Public-facing, 클라이언트 트래픽 처리)
- WAS ALB (Internal, EC2로 트래픽 전달)
resource "aws_lb" "web_alb" {
name = "web-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb_sg.id]
subnets = [aws_subnet.public_subnet_1.id, aws_subnet.public_subnet_2.id]
}
resource "aws_lb_target_group" "web_tg" {
name = "web-tg"
port = 80
protocol = "HTTP"
vpc_id = aws_vpc.main.id
}
resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.web_alb.arn
port = 80
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.web_tg.arn
}
}
3) Auto Scaling Group (ASG)
- Auto Scaling Group을 사용하여 EC2 기반 배포
4) DB 생성
- RDS (Aurora) Primary/Standby 구성
- Private Subnet 내부에 배치
- Subnet Group 생성 후 연결
5) CI/CD 파이프라인
- GitHub Actions → ECR → EC2 ASG 배포 자동화
- Terraform으로 IAM Role, CodePipeline 구성 가능
6) S3 + Lambda를 통한 이미지 처리
- S3 이벤트 트리거 → SQS → Lambda
- Terraform을 이용해 IAM Role, Lambda 함수 배포
Terraform 적용
1️⃣ Terraform 초기화
terraform init
2️⃣ 실행 계획 확인
terraform plan
3️⃣ 적용
terraform apply -auto-approve
1. 네트워크 계층 (VPC, 서브넷, 보안 그룹)
✅ VPC 생성
AWS 콘솔에서 VPC 대시보드 → VPC 생성
- IPv4 CIDR: 10.0.0.0/16
- DNS 호스트네임 활성화
✅ 서브넷 구성
서브넷 유형 서브넷 이름 CIDR 블록 배포
Public Subnet | Public Subnet 1 | 10.0.1.0/24 | Web ALB, Bastion |
Public Subnet | Public Subnet 2 | 10.0.2.0/24 | NAT Gateway |
Private Subnet | Private Subnet 1 | 10.0.3.0/24 | EC2 (WEB, WAS) |
Private Subnet | Private Subnet 2 | 10.0.4.0/24 | EC2 (WEB, WAS) |
Private Subnet | Private DB Subnet 1 | 10.0.5.0/24 | RDS (Primary) |
Private Subnet | Private DB Subnet 2 | 10.0.6.0/24 | RDS (Standby) |
✅ 인터넷 게이트웨이 & 라우팅
- 인터넷 게이트웨이 생성 → Public Subnet과 연결
- NAT Gateway 생성 → Private Subnet이 인터넷 접근할 수 있도록 설정
- Route Table 설정
- Public Subnet → 0.0.0.0/0 → Internet Gateway 연결
- Private Subnet → 0.0.0.0/0 → NAT Gateway 연결
✅ 보안 그룹 설정
보안 그룹 설명 허용 포트
ALB Security Group | 웹 요청 수락 | 80, 443 |
WAS Security Group | ALB에서 WAS로 트래픽 전달 허용 | 8080 |
DB Security Group | WAS에서 DB로 접근 허용 | 3306 (MySQL) |
2. 웹 계층 (ELB)
✅ Application Load Balancer (WEB ALB)
AWS 콘솔 → EC2 → Load Balancer 생성
- 인터넷-facing ALB
- Public Subnet 1, Public Subnet 2 배치
- Target Group 생성 (포트 8080으로 EC2 연결)
- Listener 추가: HTTP (80) → Target Group으로 라우팅
3. WAS 계층 (EC2 + ASG)
✅ Application Load Balancer (WAS ALB)
✅ ECR에 Docker 이미지 Push
- Docker 컨테이너를 AWS ECR에 Push
docker build -t my-app .
docker tag my-app:latest <AWS_ACCOUNT_ID>.dkr.ecr.<REGION>.amazonaws.com/my-app:latest
docker push <AWS_ACCOUNT_ID>.dkr.ecr.<REGION>.amazonaws.com/my-app:latest
✅ EC2 서비스 & Auto Scaling Group 설정
- EC2 Task Definition 생성 (ECR 이미지 사용)
- EC2 서비스 생성
- Load Balancer에 연결
- Desired count: 2개
- Auto Scaling Policy 적용
- CPU 사용량 60% 이상 시 인스턴스 증가
4. DB 계층 (Aurora/RDS)
✅ RDS (Aurora MySQL) 생성
AWS 콘솔 → RDS → 데이터베이스 생성
- Aurora MySQL 선택
- Primary & Standby DB 구성 (Multi-AZ)
- Private Subnet Group 사용
- 보안 그룹 설정 (EC2 WAS에서 접근 가능하도록 설정)
5. CI/CD 파이프라인
✅ GitHub Actions → ECR → EC2 배포 자동화
- GitHub Actions에서 aws-cli 사용하여 ECR에 이미지 Push
- AWS CodePipeline + CodeDeploy 사용하여 EC2 자동 배포 설정
📌 IAM
1. AWS IAM(User) 계정 생성 방법
✅ 1단계: AWS Management Console 로그인
- 계정 소유자가 AWS Management Console에 로그인합니다.
- IAM 서비스로 이동합니다.
- (검색창에서 "IAM" 검색 후 이동)
✅ 2단계: 새로운 사용자(User) 추가
- 왼쪽 메뉴에서 Users 클릭
- Add users 버튼 클릭
- 사용자 이름 입력
- 예: second-user (다른 사람이 사용할 사용자 계정 이름)
- Access type 선택
- AWS Management Console access → ✅ 체크→ Require password reset 옵션 해제 (비밀번호를 강제로 변경하도록 하지 않으려면)
- → Custom password 선택 후 비밀번호 설정
✅ 3단계: 권한 부여 (Permissions 설정)
- 권한 부여 방식 선택:
- Attach policies directly (정책 직접 연결) 선택
- 권한 설정 예시:
- AdministratorAccess (모든 권한 부여)
- ReadOnlyAccess (읽기 전용 권한)
- 특정 서비스 권한만 주려면 검색창에서 원하는 서비스 선택 (예: AmazonS3FullAccess, AmazonEC2ReadOnlyAccess 등)
- 🔹 최소 권한 원칙(Least Privilege Principle)에 따라 필요한 권한만 부여하는 것이 좋음.
✅ 4단계: 태그(Tag) 추가 (선택 사항)
- 태그를 추가하고 싶다면 사용자에 대한 정보를 입력할 수 있음.
- (예: Key: Role, Value: Developer)
✅ 5단계: 사용자 생성 완료 및 로그인 정보 공유
- 마지막 Create user 클릭
- 생성된 사용자 목록에서 방금 만든 사용자 선택 후 Security credentials 탭에서 Sign-in URL 확인
- 보통 https://<AWS계정번호>.signin.aws.amazon.com/console 형태임
- 해당 URL과 함께 생성된 IAM 사용자 이름 및 비밀번호를 공유
2. IAM 사용자 로그인 및 사용 방법
- 두 번째 사용자가 위의 Sign-in URL을 이용하여 로그인
- 공유받은 IAM 사용자 이름과 비밀번호 입력 후 로그인
- 필요에 따라 MFA(다중 인증) 설정을 추가할 수도 있음 (보안 강화 목적)
관리자 권한 사용자 및 그룹
🔹 고려할 점: ALB의 대상 그룹으로 Lambda를 직접 포함할 수 없음
- ALB(Application Load Balancer)는 EC2, Lambda를 대상으로 할 수 있음. 하지만 ALB 대상 그룹(Target Group)에 Lambda와 EC2를 동시에 추가할 수는 없음
- 즉, /image 요청을 Lambda로 보내고 나머지를 EC2로 보내려면 별도의 라우팅 규칙이 필요함
🔹 해결책 1: ALB Listener에서 URL 기반 라우팅 사용
- ALB Listener Rule을 사용하여 /image 요청은 Lambda로, 나머지는 EC2로 보낼 수 있음
- ALB는 Lambda를 대상으로 하는 Target Group을 가질 수 있음. → /image 요청이 들어오면 Lambda Target Group으로 전달 → 나머지는 기존 EC2 Target Group으로 전달
📌 설정 방법
- ALB 리스너 추가
- /image 요청이 오면 Lambda Target Group으로 전달
- 그 외 요청은 EC2 Target Group으로 전달
- Lambda Target Group 생성
- AWS 콘솔 → EC2 → Target Group → Lambda 선택
- /image 요청을 처리할 Lambda 연결
🔹 해결책 2: API Gateway + Lambda 사용 (추천)
- ALB 대신 API Gateway를 활용하여 특정 URL 요청을 Lambda로 보냄
- API Gateway가 /image 요청을 받으면 Lambda를 실행하고, 다른 요청은 그대로 ALB로 전달
📌 구성 방법
- API Gateway → /image 경로 추가
- Integration Target을 Lambda로 설정
- 그 외의 요청은 기존 ALB → EC2 흐름 유지
📌 이 방식의 장점
✅ ALB와 API Gateway의 역할을 명확하게 구분 가능
✅ Lambda는 ALB보다 API Gateway와 더 자연스럽게 연동됨
✅ 비용 절감 가능 (ALB는 주로 EC2 대상, API Gateway는 Lambda에 최적화됨)
🔹 결론
- Lambda와 EC2를 ALB Target Group에 같이 넣는 건 불가능함
- ALB Listener Rule을 이용한 라우팅 또는 API Gateway + Lambda 방식이 필요함
- API Gateway를 활용하는 것이 더 깔끔하고 AWS 아키텍처에 적합한 해결책
📌 RDS
Primary DB와 Standby DB 사용
고가용성(HA, High Availability) 및 장애 복구(Failover) 를 위한 핵심적인 설계 방식
Primary DB(주 데이터베이스)와 Standby DB(대기 데이터베이스)를 활용하면 데이터 가용성을 높이고 장애 발생 시 빠르게 복구할 수 있습니다. 주로 고가용성(HA, High Availability) 및 재해 복구(DR, Disaster Recovery) 목적으로 사용됩니다.
🔹 1. Primary DB - Standby DB 구조란?
- Primary DB (Master): 모든 쓰기(Write) 및 읽기(Read) 연산을 처리하는 메인 데이터베이스
- Standby DB (Replica / Secondary): Primary DB의 데이터를 동기(또는 비동기) 복제하여 장애 발생 시 대체(Failover)할 준비가 된 백업 DB
📌 구성 방식
- 동기 복제(Synchronous Replication) → RTO(복구 시간) 단축, 데이터 유실 없음 (AWS RDS Multi-AZ 방식)
- 비동기 복제(Asynchronous Replication) → 복제 지연 가능, 하지만 성능 향상 (Read Replica 방식)
🔹 2. Primary DB - Standby DB 구조의 장점
장점 설명
✅ 고가용성(High Availability) | Primary 장애 발생 시 Standby가 자동으로 역할 승격(Failover)하여 서비스 지속 가능 |
✅ 데이터 보호 및 무중단 운영 | Multi-AZ 배포 시, 데이터 손실 없이 지속적인 운영 가능 |
✅ 백업 대비 신속한 복구 | 백업에서 복구하는 것보다 Standby를 활용한 Failover가 훨씬 빠름 |
✅ 읽기 부하 분산 가능(Read Replica 포함 시) | Standby를 Read Replica로 활용하면 읽기 트래픽 분산 가능, 성능 향상 |
✅ 운영 효율성 증가 | 데이터베이스 유지보수(업데이트, 마이그레이션 등) 시 가용성을 보장 |
🔹 3. Primary - Standby DB 구축 방법
🔸 AWS RDS Multi-AZ 방식
- AWS에서 자동으로 Primary - Standby DB를 관리
- 장애 발생 시 Standby를 Primary로 자동 승격(Failover)
- 설정 시 Multi-AZ 배포를 활성화하면 AWS가 자동으로 Standby 생성 및 관리
📌 AWS RDS Multi-AZ 설정 방법
- AWS 콘솔 접속 → RDS → "Create database"
- "Multi-AZ deployment" 옵션에서 Enable 선택
- Primary DB 생성 시 AWS가 자동으로 Standby를 배포
- 장애 발생 시 Standby가 Primary로 자동 승격
📌 CLI 설정 예시
bash
복사편집
aws rds create-db-instance \\
--db-instance-identifier mydb \\
--db-instance-class db.t3.medium \\
--engine mysql \\
--multi-az \\
--master-username admin \\
--master-user-password mypassword \\
--allocated-storage 20
DB 자동 복구 확인
📌 Route53
📌 cloudWatch
web
- 경보 생성
- scale out
- 부하
sudo su
yum install stress -y
stress -c 1 -t 600
top
- 경보
- 인스턴스 생성
- scale in
- 부하
sudo su
yum install stress -y
stress -c 1 -t 600
top
- 경보
- 인스턴스 종료
was
- scale in
- scale out
📌 CI/CD - Blue/Green 배포
Github Action
/workflow 파일 작성
docker에서 이미지 생성 후 push
ECR에서 이미지 pull
S3에 배포파일 업로드
📌 트러블슈팅
3-tier
- Private Subnet에 위치한 프라이빗 인스턴스에 접근 방법
- bastion 실패 → IAM roles (SSM)을 통해 해결
- tgt-vpc-2a-pub-sub에서 bastion server를 사용해 tgt-vpc-2a-pri-sub에 접근하려고 했으나 scp로 .pem키 전송이 어려움 → **SSM(System Manager)**에서 IAM 역할에 SSM 권한을 부여하고, 해당 IAM 역할을 EC2에 연결하여, private 네트워크에서도 안전하게 접속 가능해짐 https://5equal0.tistory.com/entry/AWS-EC2-인스턴스에-ssh-접속-하기
- WEB ELB와 연결된 ASG 타켓 그룹의 인스턴스에서 WAS ELB와 연결 안됨
- 보안 그룹 수정해서 해결
- tgt-web-alb-sg ⇒ tgt-web-sg
- WEB ELB에 80으로 들어오도록 되어있는데, route 53에서 443으로 들어가야 하므로 리다이렉션함
- 리다이렉션은 보안규칙 평가 전에 진행됨을 확인 → 80 대신 80으로 들어오면 url 리다이렉션해서 443으로 바꿔줌
- url 매핑이 안됨 → clean build
- 네트워크 못 찾음 → docker-compose 사용
- 자꾸 서버가 죽음 → health check (/)가 현재 API가 없어 404로 반환되기 떄문