Blog

[NestJS] 21. AWS 클라우드 서비스와 배포

Category
Author
PinOnMain
1 more property
NestJS, TypeORM, Angular, AWS 배포 이해하기
Table of Content

1. AWS란?

📌
Amazon Web Service
•
아마존에서 제공하는 클라우드 컴퓨팅 서비스 플랫폼
◦
AWS는 다양한 IT 리소스를 인터넷을 통해 제공하며, 사용자는 필요에 따라 서버, 스토리지, 데이터베이스, 네트워킹, 머신 러닝, 분석 도구 등 다양한 서비스를 이용할 수 있음
◦
AWS는 스타트업부터 대기업까지 다양한 규모의 기업에서 사용되며, 클라우드 기반의 애플리케이션 개발, 데이터 저장, 웹 호스팅 등 여러 용도로 활용
📌
AWS의 강점
•
확장성
◦
스케일링 : 사용자는 필요에 따라 리소스를 쉽게 확장하거나 축소
▪
Scale Up/Down : 수직 확장, 간단히 컴퓨터 스펙자체를 올리는 행위라 이해
▪
Scale Out/In : 수평 확장, 간단히 컴퓨터를 병렬로 여러대를 배치하는 행위로 이해
•
비용 효율성
◦
사용한 만큼만 비용을 지불하는 Pay-as-you-go 모델을 채택하고 있어 초기 투자 비용이 낮음
•
다양한 서비스
◦
컴퓨팅, 스토리지, 데이터베이스, 네트워킹, 보안 등 웹 서비스를 위해 필요한 부분을 전반적으로 관리
•
통합 서비스
◦
서버와 각종 서비스간의 연결 설정을 간편화하여 기존 개발자가 직접 설정해야하는 여러 연결 설정 부분을 최소화 시켜 개발에 집중 할 수 있는 환경을 제공
•
글로벌 인프라
◦
전 세계 여러 지역에 데이터 센터를 운영하여 높은 가용성과 낮은 지연 시간을 제공
•
보안
◦
강력하고 검증된 보안 기능과 인증 절차를 통해 데이터 보호를 강화
📌
AWS를 선택하는 이유?
AWS는 아래와 같은 이유로 인해 클라우드 배포에서 가장 인기 있는 선택지로 클라우드 서비스의 성장과 함께 AWS의 사용률은 계속 증가하고 있음
•
클라우드 서비스 시장 점유율
◦
AWS는 클라우드 인프라 서비스(IaaS) 및 플랫폼 서비스(PaaS) 시장에서 약 30% 이상의 점유율을 보유
◦
주요 클라우드 제공업체 중 가장 높은 수치
•
광범위한 고객 기반
◦
AWS는 수백만 개의 활성 고객을 보유하고 있으며, 스타트업, 중소기업, 대기업, 공공 기관 등 다양한 산업에서 사용
◦
예를 들어, Netflix, Airbnb, NASA 등 유명 기업들이 AWS를 활용
•
다양한 서비스
◦
AWS는 200개 이상의 서비스와 기능을 제공하여 배포, 데이터 저장, 분석, 머신 러닝 등 다양한 요구를 충족
•
강력한 개발자 생태계
◦
AWS는 활발한 개발자 커뮤니티와 방대한 문서, 튜토리얼, 교육 자료를 제공하여 사용자가 쉽게 배포하고 관리할 수 있도록 돕고있음
◦
참고할 레퍼런스가 많다는 것은 그만큼 빠르게 배포 관련 설정 할 수 있고 비지니스 로직 개발에 더 집중 할 수 있는 장점

2. AWS 사용 준비

2.1 회원 등록(최초 1회)

📌
AWS 회원 가입
•
링크를 통해서 회원 가입 페이지로 이동
•
아래와 같이 root 계정(앞으로 사용할 관리 메인 계정) email 주소를 입력
•
계정 이름(account name = nick name 변경가능)을 입력
•
Verify email address 버튼으로 이메일 인증
•
이메일을 통해 인증 코드 획득
•
이메일을 인증 확인
•
비밀번호 설정
•
회원 기본 정보 입력
•
결재 정보 입력
•
카드 정보 인증
•
회원 SMS 인증
•
이용 플랜 선택(Free Tier)
•
회원 가입 완료

2.2 AWS 콘솔 사용 준비하기

📌
AWS 콘솔 들어가기
•
우측 상단의 Console로 들어가면 다음과 같은 링크로 이동하게 된다.
•
우측 상단의 네비게이션을 살펴보면 Ohio로 미국 동부가 기본적으로 선택되어 있다.
•
→ 이것을 Asia Pacific(Seoul) 로 변경해준다. 서비스의 기본 서버 위치이다.
📌
즐겨찾기 설정해두기
•
콘솔의 좌측 상단을 살펴보면 Services 탭으로 모든 AWS 서비스를 살펴 볼 수 있으며
•
Search 탭을 통해 원하는 Service를 검색 할 수 있다.
•
하지만, AWS는 200개 이상의 서비스가 있으므로 일일히 찾아서 사용하기엔 번거롭기 때문에 주요 서비스들을 미리 즐겨찾기로 등록해두고자 한다.
•
자주 사용되며 이 샘플에서 사용될 서비스들을 아래처럼 검색 필드에 각 이름을 입력하면서 동적으로 나타나는 결과를 즐겨찾기 해둔다.
◦
IAM
◦
RDS
◦
S3
◦
CloudFront
◦
Route 53
◦
EC2
◦
Certificate Manager
•
이제 아래와 같이 각 서비스를 즐겨찾기를 통해서 빠르게 접근 할 수 있다.

3. AWS 사용해보기

📌
현재 AWS를 통한 목표는 다음과 같다.
•
로컬 → 클라우드 환경으로 전환 부분
◦
AWS 회원 가입 및 IAM 권한 설정
◦
백엔드 API 서버
▪
로컬 MySQL 데이터베이스 → 데이터베이스 RDS로 변경
▪
로컬 파일 시스템 업로드 경로 → 저장소 S3로 변경
→ Cloudfront CDN 파일 제공 설정
▪
로컬 테스트 서버 실행 환경 → 실제 서비스 EC2 인스턴스로 변경
◦
프론트엔드 Client 서버
▪
로컬 테스트 서버 실행 환경 → 실제 서비스 프론트 정적배포 실행 환경 S3로 변경
▪
클라이언트 테스트 진입 경로 → 도메인 연결(Route 53, CloudFront)실제 클라이언트 진입 경로
◦
보안
▪
HTTP → HTTPS (ACM, SSL, Cloudfront) (SSR인경우는 ELB인데 CSR이라 Cloudfront가 책임짐)
◦
대용량 파일 스트리밍 → Cloudfront(CDN)

3.1 AWS IAM 접근 권한 설정

3.1.1 IAM(Amazon Identity and Access Management)

📌
Amazon IAM이란?
•
아마존 웹 서비스(AWS)에서 제공하는 사용자가 AWS 리소스에 대한 접근을 안전하게 관리할 수 있도록 돕는 기능
•
IAM을 사용하면 사용자, 그룹, 역할 및 정책을 설정하여 AWS 리소스에 대한 권한을 세밀하게 조정
◦
사용자 및 그룹 관리
▪
IAM을 통해 여러 사용자 계정을 생성하고, 이들을 그룹으로 묶어 권한을 관리
◦
정책 기반 접근 제어
▪
JSON 형식의 정책을 사용하여 특정 AWS 서비스에 대한 접근 권한을 정의하여 사용자 또는 그룹이 어떤 리소스에 접근할 수 있는지 세부적으로 설정
◦
역할(Role) 및 임시 자격 증명
▪
IAM 역할을 사용하여 특정 서비스나 애플리케이션이 다른 AWS 리소스에 접근할 수 있도록 임시 자격 증명을 부여
▪
예를 들어, EC2 인스턴스가 S3에 접근할 때 IAM 역할을 사용
◦
다단계 인증(MFA)
▪
IAM에서는 다단계 인증(MFA)을 설정하여 추가적인 인증 수단을 요구하여 계정을 보호
◦
로그 및 모니터링
▪
IAM과 AWS CloudTrail을 연동하여 사용자 활동을 로그하고 모니터링

3.1.2 MFA(Multi-Factor Authentication) 설정

📌
MFA란?
•
MFA(다단계 인증, Multi-Factor Authentication)는 사용자 계정의 보안을 강화하기 위한 인증 방식
•
MFA는 특히 중요한 정보나 자산을 다루는 서비스에서 필수적인 보안 수단으로 자리 잡고 있음
•
간단히 다단 인증 설정이라고 볼 수 있다.
◦
예로 우리는 평소에 어떤 서비스에서 로그인 시 이메일 인증 또는 SMS인증 등 추가 인증을 설정하던 것을 생각 하면 된다.
📌
IAM에서 루트 계정의 MFA 설정하기
•
AWS 콘솔 → ☑️ IAM (즐겨찾기 또는 검색)
•
IAM Dashboard 콘솔 → ☑️ Add MFA
•
Select MFA device
◦
Device name → ☑️ MFA 이름 구분
◦
MFA Device → ☑️ Authenticator app
•
본인의 스마트폰에서 Google Authenticator 다운로드
•
Google Authenticator → ☑️ 코드 추가(
•
Set up device → ☑️ 스마트폰으로 QR코드 인식
•
Google Authenticator → ☑️ OTP 임시번호가 자동 로딩됨
•
Set up Device → ☑️ OTP 인증 번호 연속 입력 2회(30초마다 갱신기다리고 2차 입력)
•
MFA - Google Authenticator 등록 완료
•
이후 로그인마다 해당 앱을 실행시켜 인증 번호 입력을 해야 함

3.1.3 Access Key 발급

📌
Access Key란?
•
AWS API에 접근할 때 사용되는 중요한 인증 정보
•
프로젝트 등 에서 AWS 각종 서비스를 사용 할 때(ex: NestJS에서 AWS SDK 모듈을 통해 AWS 서비스를 연결 할 때 등) 필요한 정보이다.
📌
AWS 계정 사용 권장사항
•
Root 계정은 모든 권한을 가지고 있기 때문에 보안에 취약한 상태라면 해커의 공격타겟이 될 가능성이 높다.
◦
실제로 수많은 사례들이 나타나고 있으며, 작성자 본인도 공격에 수십만원의 서비스 이용료가 청구된 적이 있었다.
◦
하지만 외부 공격이라는 것을 입증만 하면 해당 청구금액은 환불 시킬 수 있으나, 과정 자체가 Amazon과 직접 컨택해야 하는 과정, 요구 조치들을 이해해야 하는 과정 등 시간이 소요되므로 사전에 MFA, Group/User 설정을 하는 기본 조치들에 신경써나가야 한다.
•
따라서 Root 계정을 직접 사용하지 않고 않는 것은 AWS 공식 문서에서도 강력히 권장되고 있다.
•
어떠한 팀 프로젝트를 진행한다고 가정하고 아래와 같이 권한 그룹을 만들고 유저를 생성하는 것이 좋다.
•
싱글 프로젝트인 경우에도 프로젝트별로 나누는것이 좋음
📌
Group 생성
•
IAM 콘솔 → Access management → ☑️ User Group → ☑️ Create group
•
Create user group
◦
Name the group → User group name → ☑️ 그룹 이름 예제에선 “boardapp-aws-group”
•
Add users to the group - 그룹 생성 후 진행
•
Attach permissions policies
◦
해당 프로젝트에서 사용되는 모든 ☑️ AWS 서비스를 검색 → ☑️ 서비스명FullAccess 체크
▪
☑️ IAM
▪
☑️ RDS
▪
☑️ RDS Data
▪
☑️ S3
▪
☑️ CloudFront
▪
☑️ EC2
▪
☑️ Route 53
▪
☑️ Certificate Manager 각각 Full Access 정책 등록
◦
→ 이후 ☑️ Create user group
•
☑️ Group 생성 완료
📌
User 생성
•
IAM 콘솔 → Access management → ☑️ Users → ☑️ Create user
•
Specify user details
◦
User name → ☑️boardapp-fred처럼 해당 어플리케이션-이름 으로 구분해두면 프로젝트별 인원을 관리하기 편리해진다.
▪
다른 팀원들은 boardapp-alice, boardapp-tom 처럼 추가해나가면 된다.
▪
→ 이후 ☑️ Next
•
Set permissions
◦
Permission options → ☑️ Add user to group
◦
User groups → ☑️ 직전에 만든 프로젝트 그룹 예제에선 boardapp-aws-group선택
◦
→ 이후 ☑️ Next
•
Review and create → ☑️ 확인 후 Create user
•
☑️ User 생성 완료 → 해당 User name 을 눌러서 상세 정보 확인
•
생성된 User 의 상세 정보 → ☑️ Security credentials 탭 이동
•
Security credentials → Multi-factor authentication(MFA) → ☑️ Assign MFA device
•
Root 계정의 MFA 설정과 마찬가지로 해당 User의 MFA 설정
•
생성 User인 ☑️ boardapp-fred의 MFA 설정 완료
•
Security credentials → Access keys → ☑️ Create access key
•
Access key best practices & alternatives
◦
Use case → ☑️ Local code
◦
Confirmation → ☑️ I understood..
◦
→ 이후 ☑️ Next
•
Set description tag(생략) → ☑️ Create access key
•
Retrieve access keys → ☑️ Access key, Secret access key 개인 저장
◦
절대 공유 금지( github에 코드상으로도 공유되지 않도록 은닉화(.env) 신경 써야 함)
◦
개인 PC 메모장, 개인 카톡 등에 기록해 둘 것
◦
→ 이후 ☑️ Done
•
생성 User의 ☑️ MFA + Secret Key 까지 생성 완료
•
Security credentials → Console sign-in → ☑️ Enable console access
◦
이 부분은 현재 AWS 웹 콘솔에 로그인하는 설정임
•
Enable console access → Console password
◦
Root 계정 주인 본인인 경우 → ☑️ Custom password로 해당 User의 패스워드 지정해도 됨
◦
Root 계정의 팀원이 초대되는 경우
▪
→ ☑️ Autogenerated password로 자동 비밀번호로 생성해서 공유해주고
▪
→ 이후 해당 팀원이 첫 로그인하면 비밀번호를 본인이 변경 할 수 있도록 하단 ☑️ User must create new password at next sign-in 을 켜줌
◦
→ ☑️ Enable console access
•
팀원인 경우 이런 페이지를 공유해줘서 첫 로그인 할 수 있도록 함
◦
위 sign-in URL에 표기된 숫자가 ID
◦
Username 이 사용자 이름
◦
패스워드는 초기 랜덤 패스워드(이후 변경됨)
•
다음 처럼 Root 계정을 로그아웃하고
•
Root 계정 로그인이 아닌, ☑️ IAM User 로그인 을 가정하면 위 정보대로 입력
•
User 생성시 추가한 ☑️ User용 MFA 인증번호 입력 (Root 계정용 아님🙅🏻)
•
초기 비밀번호 확인 및 신규 비밀번호 설정 화면
•
로그인 이후 IAM 서비스로 들어가보면 다음과 같이 Root과 동일하게 페이지가 나타난다.
◦
이는 권한이 있기 때문
◦
해당 User group에 권한을 부여하지 않은 다른 서비스로 이동해보면 다음과 같이 권한 없음이 나타남

3.2 AWS RDS 사용하기

3.2.1 RDS(Amazon Relational Database Service)

📌
Amazon RDS란?
•
아마존 웹 서비스(AWS)에서 제공하는 관리형 관계형 데이터베이스 서비스
•
RDS는 데이터베이스의 설정, 운영, 유지보수를 자동화하여 사용자가 데이터베이스에만 집중할 수 있도록 돕는 서비스
◦
관리형 서비스
▪
RDS는 데이터베이스 인스턴스의 프로비저닝, 패치, 백업, 복구 등의 관리 작업을 자동화, 개발자는 이러한 작업에 시간을 할애할 필요가 없음
◦
다양한 데이터베이스 엔진 지원
▪
RDS는 여러 데이터베이스 엔진을 지원
▪
대표적으로 MySQL, PostgreSQL, MariaDB, Oracle, SQL Server 등 대중적인 DB선택 폭을 가지고 있음
◦
확장성
▪
RDS는 필요에 따라 쉽게 인스턴스를 확장하거나 축소할 수 있음(Auto Scaling)
▪
데이터베이스의 성능 요구에 따라 CPU, 메모리, 스토리지 용량을 조정
◦
고가용성
▪
Multi-AZ 배포를 통해 고가용성을 제공
▪
장애 발생 시 자동으로 대체 인스턴스로 전환하는 기술로 서비스의 연속성을 보장
◦
보안
▪
AWS Identity and Access Management(IAM)를 통해 데이터베이스에 대한 세밀한 접근 제어가 가능하며, 데이터 암호화 기능도 지원
◦
자동 백업
▪
RDS는 자동으로 데이터베이스 백업을 수행하여 데이터 손실을 방지하고, 필요 시 특정 시점으로 복원할 수 있는 기능을 제공

3.2.2 RDS로 MySQL Database 인스턴스 생성하기

📌
RDS로 클라우드 데이터 베이스 만들기 따라하기
•
AWS 콘솔 → ☑️ RDS (즐겨찾기 또는 검색)
•
Amazon RDS 콘솔 → Region 선택(지역 선택) → ☑️ Asia Pacific(Seoul)
•
Databases → ☑️ Create database
•
Choose a database creation method → ☑️ Standard create
•
개발환경에서의 MySQL 버전 확인 → 터미널 → mysql —version
•
Engine options
◦
Engine Type → ☑️ MySQL
◦
Edition → ☑️ MySQL Community
◦
Engine version → ☑️ MySQL 8.0.39 (*개발환경과 왠만하면 맞출것)
•
Templates → ☑️ Free tier
•
Availability and durability (Free tier 자동 비활성화)
•
Settings
◦
DB instance identifier → rds-mysql-boardapp 과 유사하게 본인 프로젝트명으로 변경
◦
Credentials Settings
▪
Master username → ☑️ admin 과 유사하게 팀에 공유될 아이디이므로 개인아이디 X
▪
Credentials management → ☑️ Self managed
▪
Master password → ☑️ 팀에 공유될 패스워드이므로 개인비밀번호 X
▪
Confirm master password → 비밀번호 재입력확인
•
Instance configuration → 클라우드 PC 사양 선택 프리티어로 가장 작은 ☑️ db.t3.micro
◦
이는 대여할 PC의 CPU, RAM을 선택하는것과 같음
•
Storage
◦
Storage type → ☑️ SSD(GP2)
▪
이는 대여할 PC의 SSD 하드디스크를 선택하는것과 같음
◦
Allocated storage → ☑️ 20 GiB(Giga bytes)
◦
Storage autoscaling → 🔲 Enable storage autoscaling 체크 해제
▪
자동 용량 확장 옵션 해제
•
Connectivity
◦
Compute resource → ☑️ Don’t connect to an EC2 compute resource
◦
VPC → ☑️ Default VPC
◦
DB subnet group → ☑️ default
◦
Public access → ☑️ YES
◦
New VPC security group name
◦
Availablity Zone → ☑️ No preference
◦
Certificate authority → ☑️ default
◦
Database port → ☑️ 3306
•
Tags
•
Database authentication
•
Mornitoring
•
Additional configuration
◦
Database options
▪
initial database name → ☑️ “boardapp”처럼 DB이름
•
☑️ Create Database
•
생성 후 몇분 정도 초기화 및 생성 과정 대기
•
☑️ RDS DB 생성 완료
•
해당 인스턴스 이름을 클릭하면 세부 정보를 확인 할 수 있음
◦
AWS에서는 이와 같이 특정 서비스들이 동작하는 렌탈 클라우드 PC를 인스턴스라 함

3.2.3 RDS 연결 설정

📌
보안그룹 설정
•
RDS 인스턴스 상세정보 → Security → ☑️ VPC security groups
•
Security Groups → ☑️ Security group ID 클릭
•
sg-073dc77…. - default 상세 정보 → ☑️ Edit Inbound rules
•
Add rule
◦
Type → ☑️ MySQL/Aurora
◦
Source → ☑️ My IP → ip주소 자동입력됨
→ ☑️ Save rules
•
Inbound 규칙 생성 확인
•
DBeaver 및 MySQL 터미널 등으로 접속 시도
◦
new Connection
▪
host name = RDS 인스턴스 상세정보에서 Endpoint 복사&붙여넣기
▪
port = 3306 그대로
▪
database = RDS 인스턴스 생성시 설정한 DB name (이샘플에선 boardapp)
▪
username = RDS 인스턴스 생성시 설정한 User name (이샘플에선 admin)
▪
password = RDS 인스턴스 생성시 설정한 Password

3.2.4 백엔드 API 서버와 RDS의 연결 🎯

📌
위 처럼 GUI 툴에서 접속 테스트가 완료된다면, 실제로 접속이 가능하다는 상태
•
NestJS 백엔드 프로젝트를 열어보자
•
.env
◦
우리는 DB 접속 정보를 .env 파일로 환경변수로 다루며 은닉화 시켜 왔다.
◦
민감 정보 은닉화를 필요한 이유가 이와 같은 AWS 서비스를 사용 할 때 외부인의 접속을 제한하기 위함이다.(해커의 공격 위험성 = 무자비한 데이터 공격가능성 = 비용$$)
▪
이는 실제 수도 없이 요금 폭탄 사고가 발생하는 매우 흔한일이다. (본인실제경험포함)
▪
다행히 AWS는 1회에 이상현상에 대해서 비용을 제외해주지만 처리를 모두 AWS 직원(영어)와 통화 또는 이메일로 수도없이 주고 받고 보안 조치를 확인하고 비용제외해주는 번거로움이 있음
◦
.env에 다음과 같이 DB 접속 정보를 본인의 인스턴스에 맞게 수정한다.
# DATABASE 설정 정보 DB_HOST=RDS인스턴스의Endpoint복사붙여넣기 DB_PORT=3306 DB_USERNAME=admin DB_PASSWORD=설정한비밀번호입력 DB_NAME=boardapp # JWT Secret Key ...
Shell
복사
◦
서버를 실행시키면 TypeORM의 테이블 자동 생성 기능에 따라 엔터티에 맞추어 RDS에 테이블을 생성하는 것을 볼 수 있다.
◦
DBeaver말고도 터미널로 들어가는것이 편한사람은 아래같은 명령어를 입력하는데 Endpoint 부분만 본인것으로 수정하면 된다.
mysql -h {본인의RDS Endpoint} -P 3306 -u {본인의RDS Username} -p
Shell
복사

3.3 AWS S3 사용하기(CDN)

3.3.1 S3(Amazon Simple Storage Service)

📌
Amazon S3란?
•
아마존 웹 서비스(AWS)에서 제공하는 객체 스토리지(저장소) 서비스
•
S3는 인터넷을 통해 대량의 데이터를 안전하게 저장하고 관리할 수 있는 기능을 제공, 사용자는 파일을 업로드하고 다운로드하며, 데이터를 효율적으로 관리
◦
무제한 스토리지
▪
S3는 사용자가 필요에 따라 용량을 조정할 수 있어, 데이터의 양에 제한이 없음
◦
고가용성 및 내구성
▪
S3는 99.999999999% (11 9s) 내구성을 제공하며, 데이터의 손실 없이 안전하게 저장. 또한, 여러 지역에 분산 저장되어 높은 가용성을 유지.
◦
다양한 데이터 관리 옵션
▪
객체에 대한 메타데이터를 설정할 수 있으며, 버전 관리, 라이프사이클 정책 등을 통해 데이터를 효율적으로 관리
◦
보안
▪
AWS IAM을 통해 접근 권한을 관리하고, 데이터 암호화 기능을 제공하여 데이터를 안전하게 보호
◦
비용 효율성
▪
사용한 만큼만 요금을 지불하는 요금 체계로, 데이터 저장 및 전송에 대한 비용을 효율적으로 관리

3.3.2 S3 인스턴스 생성하기

📌
S3로 클라우드 저장소(Bucket) 생성하기
•
AWS 콘솔 → ☑️ S3 (즐겨찾기 또는 검색)
•
Amazon RDS 콘솔 → Region 선택(지역 선택) → ☑️ Asia Pacific(Seoul)
•
Buckets → ☑️ Create bucket
•
General configuration → Bucket name → ☑️ s3-bucket-boardapp처럼 Unique한 이름
◦
먼저 이름을 유니크하게 설정하여 버킷을 생성한 후, 필요에 따라 모든 설정을 자유롭게 변경할 수 있음
•
Object Ownership
•
Block Public Access settings for this bucket
•
Bucket Versioning
•
Tags -optional
•
Availability and durability (Free tier 자동 비활성화)
•
Default encryption
•
Advanced settings
•
☑️ Create bucket
•
☑️ S3 Bucket 생성 완료
•
해당 인스턴스 이름을 클릭하면 첫 Object 탭을 볼 수 있으며 저장소 파일들을 볼 수 있음
•
샘플로 파일을 업로드 하기 위해 Upload 버튼으로 이동 → 이미지1개와 영상1개를 업로드 테스트
•
업로드 성공 상태

3.3.3 백엔드 API 서버와 S3의 연결 🎯

📌
위 처럼 웹 AWS 콘솔에서 파일 업로드 테스트가 완료된다면, 실제로 S3 저장소 인스턴스는 생성된 것이다.
•
NestJS 백엔드 프로젝트를 열어보자
•
.env
◦
우리는 DB 접속 정보를 .env 파일로 환경변수로 다루며 은닉화 시켜 왔다.
◦
특히 현재 IAM 유저 그룹과 유저 등록을 통해서 Access Key, Secret Key를 발급 받았다.
▪
받지 않은 사람은 다시 IAM 설정 부분으로 ⏫ @3.1.3 Access Key 발급
◦
이 정보는 은닉화에 신경써야 의도하지 않은 공격으로부터 보호 할 수 있다.
◦
아래와 같이 발급받은 키를 입력하여 환경변수로 받아서 사용 할 수있도록 준비 해둔다.
# DATABASE 설정 정보 ... # JWT Secret Key ... # File Upload PATH (Local Test용) # STORAGE_PATH=/Users/inyongkim/Documents/Projects/localStorage # AWS Keys AWS_ACCESS_KEY_ID=발급받은본인의ACCESSKEY AWS_SECRET_ACCESS_KEY=발급받은본인의SECRETKEY # 외부 API관련 Key 등 필요 시 추가
Shell
복사
📌
AWS-SDK 의존성 설치
•
NestJS 프로젝트에서 AWS 서비스와 쉽게 연결하기 위해서는 aws-sdk를 설치해야 한다.
•
아래 명령어를 통해 aws-sdk 모듈들을 설치해준다.
npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
Shell
복사
📌
s3.service.ts
•
기존 파일 업로드 서비스를 S3로 처리해 줄 서비스 계층을 하나 생성한다.
•
생성자를 통해 S3 객체를 생성하면서 이 때 AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY 를 통해 해당 백엔드 API 서버가 권한이 있는 관리자임을 인증하게 된다.
•
PutObjectCommand 클래스를 통해 S3에 파일을 전송 할 수 있는 객체로 만들어내고 S3의 send메서드를 통해 해당 객체를 S3 저장소로 전송하여 파일을 업로드하게 된다.
import { Injectable } from '@nestjs/common'; import { S3 } from '@aws-sdk/client-s3'; import { PutObjectCommand } from '@aws-sdk/client-s3'; @Injectable() export class S3Service { private s3: S3; constructor() { this.s3 = new S3({ region: 'ap-northeast-2', credentials: { accessKeyId: process.env.AWS_ACCESS_KEY_ID, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, }, }); } async uploadFile(file: Express.Multer.File, bucketName: string): Promise<string> { const encodedFileName = encodeURIComponent(`${Date.now()}-${file.originalname}`); const command = new PutObjectCommand({ Bucket: bucketName, Key: encodedFileName, Body: file.buffer, ContentType: file.mimetype, }); await this.s3.send(command); return `https://${bucketName}.s3.amazonaws.com/${encodedFileName}`; // 반환 URL } }
TypeScript
복사
📌
attachment-upload.service.ts
•
위 작성된 S3 Service를 호출하는 첨부파일 업로드 비지니스 로직 부분이다.
•
크게 변경된 점은 없지만 변경된 것은 파일을 S3에 저장하는 로직이 작성된 s3Service를 호출한다는 것
◦
추가로 S3 생성 시 설정한 저장소의 이름 ‘s3-bucket-boardapp’을 인수로 전달하는 점이 변경되었다.
@Injectable() export class AttachmentUploadService { constructor( @InjectRepository(Attachment) private readonly attachmentRepository: Repository<Attachment>, private readonly s3Service: S3Service, ) {} // 파일 업로드 async uploadFile(file: Express.Multer.File) { try { const fileUrl = await this.s3Service.uploadFile(file, 's3-bucket-boardapp'); return { message: 'File uploaded successfully', url: fileUrl, }; } catch (err) { console.error('S3 Upload Error:', err); throw new HttpException('Failed to upload file', HttpStatus.INTERNAL_SERVER_ERROR); } } // 파일 엔터티 데이터베이스에 저장 async save(file: Attachment) { try { return await this.attachmentRepository.save(file); } catch (err) { throw new HttpException('Failed to save file', HttpStatus.INTERNAL_SERVER_ERROR); } } }
TypeScript
복사
•
이와 동일하게 프로필 사진 업로드 로직이 작성된 profile-picture-upload.service.ts도 변경되었다.
◦
모듈화를 위해서 분리해 둔 것일 뿐 위 게시글의 첨부파일 코드와 거의 동일한 코드 구조이다.
◦
S3 Service를 호출하는 것으로 변경된 것도 똑같다.
@Injectable() export class ProfilePictureUploadService { constructor( @InjectRepository(ProfilePicture) private readonly profilePictureRepository: Repository<ProfilePicture>, private readonly s3Service: S3Service, ) {} // 프로필 사진 파일 업로드 async uploadProfilePicture(file: Express.Multer.File) { try { const fileUrl = await this.s3Service.uploadFile(file, 's3-bucket-boardapp'); return { message: 'File uploaded successfully', url: fileUrl, }; } catch (err) { console.error('S3 Upload Error:', err); throw new HttpException('Failed to upload file', HttpStatus.INTERNAL_SERVER_ERROR); } } // 프로필 사진 엔터티 데이터베이스에 저장 async save(file: ProfilePicture) { try { return await this.profilePictureRepository.save(file); } catch (err) { throw new HttpException('Failed to save file', HttpStatus.INTERNAL_SERVER_ERROR); } } }
TypeScript
복사

3.4 AWS CloudFront 사용하기(CDN 역할)

3.4.1 Amazon CloudFront Service

📌
Amazon CloudFront란?
아마존 웹 서비스의 .html, .css, .js 및 이미지 파일과 같은 정적 및 동적 웹 콘텐츠를 사용자에게 더 빨리 배포하도록 지원하는 웹 서비스
•
CDN(Content Delivery Network)의 기능
◦
CloudFront는 전 세계 여러 지역에 분산된 서버(엣지 로케이션)를 통해 사용자에게 콘텐츠를 빠르게 제공
◦
이를 통해 웹사이트와 애플리케이션의 성능을 향상시키고, 지리적 거리와 관계없이 빠른 응답 속도를 제공
◦
CloudFront는 정적 및 동적 콘텐츠를 모두 지원하며, 원본 서버를 보호하고 트래픽을 감소시키는 데 도움을 줌
•
Cache(캐싱 서버)의 기능
◦
CloudFront는 요청된 콘텐츠를 엣지 로케이션에 저장하여, 이후의 요청에 대해 빠르게 응답할 수 있는 캐싱 기능을 제공
◦
이를 통해 원본 서버에 대한 요청 수를 줄이고, 데이터 전송 비용을 절감하며, 사용자에게 빠른 로딩 속도를 제공
◦
캐시된 콘텐츠의 유효 기간(TTL)을 설정할 수 있어, 콘텐츠가 갱신될 때까지 기존 캐시를 활용할 수 있음
📌
CloudFront를 사용하는 이유의 이해 위한 현재까지의 상황 정리
•
AWS RDS에 데이터베이스가 연결되어 정보(문자열)들이 저장되고 있다.
•
AWS S3에 실제 파일이 업로드 되고 있다.
◦
→ S3에 저장된 파일의 경로(URL) 문자열은 RDS의 파일 관련 테이블 저장되며 S3의 주소값이다.
💡
public access를 막는 이유?
◦
백엔드 서버는 쓰기/읽기 권한이 있는 Access Key로 접근하기 떄문에 업로드가 가능한 상태이다.
•
그럼 S3 저장소의 파일을 public으로 접근 할 수 있는 중간 매개체가 필요하며 이것이 CDN(Content Delivery Network)의 역할이다.
•
AWS에서는 이런 CDN기능을 CloudFront 서비스로 구현하게 된다.
◦
이후 CloudFront를 통해 실제 외부 접근 가능한 파일의 경로를 URL 값으로 DB에 입력하는 것이 목표이다.

3.4.2 CloudFront 설정하기

📌
CloudFront로 CDN 구축 따라하기
•
CloudFront Dashboard → ☑️ Create distribution
•
Create distribution
◦
Origin
▪
Origin domain → ☑️ 생성했던 연결할 S3 버킷 선택
▪
Name → ☑️ 자동 입력됨
▪
Origin access → ☑️ Origin access control settings(recommanded)
•
S3 버킷은 public access를 비공개로 했던 설정이기 때문에 CF는 S3에 인증해서 접근 할 수 있도록 설정하는 부분
→ ☑️ Create new OAC → 인증 설정 생성 후 자동 입력
•
Default cache behavior
◦
View protocol policy → ☑️ HTTP and HTTPS
◦
Allowed HTTP methods → ☑️ GET, HEAD
◦
Restrict view access → ☑️ YES
▪
이를 통해 회원 전용 영상, 이미지와 같이 제한을 설정 할 수 있음
Add key groups → 아래 토글 내용 전체 진행 → ☑️ 생성한 키 그룹 선택
◦
☑️ 그 외 기본 설정 선택
•
Function associations - optional
•
Web Application Firewall (WAF) → ☑️ Do not enable security protections
•
Settings
◦
Price class → ☑️ Use NA,Euro, Asia, M/E, Africa 선택(과금 비용 최소화)
◦
Alternate domain name(CNAME) → ☑️ 기본값 → 이후 프론트엔드 배포 시 추가 설정
◦
Custom SSL certificate - optional → ☑️ 빈값 → 이후 프론트엔드 배포 시 추가 설정
◦
Supported HTTP versions → ☑️ HTTP/2 ☑️ HTTP/3
◦
Default root object - optional → ☑️ 빈값 → 이후 프론트엔드 배포 시 추가 설정
◦
Standard logging → ☑️ On + S3변경 팝업 활성화 → Log prefix → ☑️ CloudFrontLogs/ 입력(s3의 해당폴더에 쌓임)
◦
그 외 기본값 그대로 → ☑️ Create distribution
•
☑️ CloudFront 설정 생성 완료
◦
CloudFront에서 S3 폴더 경로에 따른 권한 변경이 필요 → ☑️ Behavior탭 → ☑️ Create behavior
▪
첫번째 모두 접근 가능한 경로
•
Create behavior
◦
Path pattern → ☑️ public/* 입력
◦
Origin and origin groups → ☑️ 생성했던 S3 선택
◦
Cache policy → ☑️ S3 선택시 자동 선택 됨(CachingOptimized for S3)
◦
그 외 기본 설정 → ☑️ Create behavior
▪
두번째 Access Key가 필요한 경로
•
Create behavior
◦
Path pattern → ☑️ private/* 입력(이부분은 정하기 나름)
◦
Origin and origin groups → ☑️ 생성했던 S3 선택
◦
Restrict view access → ☑️ YES → Add key groups → ☑️ 생성한 키 그룹 선택
◦
Cache policy → ☑️ S3 선택시 자동 선택 됨(CachingOptimized for S3)
◦
그 외 기본 설정 → ☑️ Create behavior
◦
경로에 따른 권한 설정 완료(프로젝트에 맞게 수정할 것)
•
S3에 public, private 폴더 만들기
◦
위 두가지 접근 권한(공개 및 AccessKey 필요비공개)경로를 설정했기 때문에 실제로 S3에 해당 폴더들이 위와 같은 공개 상태를 유지 할 수 있다.
◦
AWS S3 콘솔 → 나의 S3 버킷 클릭 → Object 탭 → ☑️ Create folder
◦
Create folder → Folder name → ☑️ private 입력 → ☑️ Create folder
▪
그 외 특별한 추가 설정은 필요 없다.
◦
Create folder → Folder name → ☑️ public 입력 → ☑️ Create folder
◦
이후 아래와 같이 폴더로 정리되면 된다. 외부에 파일이 있으면 각 경로로 이동 시켜줄 것
▪
예제에서는 프로필 사진은 private, 게시글 첨부 사진은 public으로 이동시켰다.
•
접근 권한의 테스트
◦
우선 권한을 정리하자면 다음과 같다.
▪
S3에서 설정된 부분
•
Block public access → Block All(Private)
•
버킷 정책
◦
S3는 연결된 CloudFront에 권한을 주고 있다.
▪
CloudFront에서 설정된 부분
•
OAC(Origin Access Control) - Sign Requests 설정 상태
•
CloudFront는 Behavior 설정으로 S3의 폴더마다 접근 권한을 설정하고 있다.
◦
CloudFront는 연결된 S3의 Default(/* 루트경로)경로에 대해서 비공개 상태이다. (AccessKey를 필요)
◦
CloudFront는 연결된 S3의 private/*경로에 대해서 비공개 상태이다. (AccessKey를 필요)
◦
CloudFront는 연결된 S3의 public/*경로에 대해서 전체 공개 상태이다.
•
이제 CloudFront에서 제공해주는 파일의 각 경로에 대한 권한 상태를 확인하고자 한다.
◦
CloudFront에서도 연결된 S3로 접근 할 수 있는 URL이 제공된다.

3.4.3 백엔드 API 서버와 CloudFront의 연결 🎯

📌
s3.service.ts
•
백엔드 서버 코드에서는 S3에 파일을 업로드하는 것은 동일하기 때문에 큰 변경이 없다.
•
반환하던 파일 접근 URL주소가 S3에서 CloudFront로 변경된 것 외에 차이점이 없다.
•
일부 하드코딩 되어 있던 AWS 관련 정보들은 모두 환경 변수로 넣어서 관리하도록 리팩토링했다.
•
기존 S3 URL만 반환하던 것을 CloudFront 접근 URL로 변경했으며, 파일 데이터베이스에 필요한 메타데이터들을 추가로 반환하도록 설정했다.
•
일부 한글에 대한 인코딩 문제가 있었다.
◦
한글 파일이 S3에 업로드될 때 UTF8인코딩을 하더라도 자모가분리되거나 특수문자에 대한 처리가 문제가 발생했다.
◦
여러 방법을 시도했지만 파일명에서 한글을 배제시키는 것만이 효과적이었다.
◦
따라서 nomalize 메서드를 추가했으며 영문숫자 등 허용된 파일명으로 변경되어 업로드 된다.
import { Injectable } from '@nestjs/common'; import { S3 } from '@aws-sdk/client-s3'; import { PutObjectCommand } from '@aws-sdk/client-s3'; @Injectable() export class S3Service { private s3: S3; constructor() { this.s3 = new S3({ region: process.env.AWS_S3_REGION, credentials: { accessKeyId: process.env.AWS_ACCESS_KEY_ID, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, }, }); } async uploadFile(file: Express.Multer.File, prefix: string): Promise<any> { const normalizeFileName = this.normalizeFileName(file.originalname); const filePath = `${prefix}/${normalizeFileName}`; const command = new PutObjectCommand({ Bucket: process.env.AWS_S3_BUCKET_NAME, Key: filePath, Body: file.buffer, ContentType: file.mimetype, }); await this.s3.send(command); // CloudFront URL 반환 및 메타데이터 return { url: `https://${process.env.AWS_CLOUDFRONT_DOMAIN}/${filePath}`, filename: normalizeFileName, mimetype: file.mimetype, size: file.size, }; } private normalizeFileName(fileName: string): string { return fileName .replace(/[^a-zA-Z0-9.-]/g, '_') // 알파벳, 숫자, 점, 하이픈만 허용 .replace(/_{2,}/g, '_') // 연속된 언더바를 하나로 .replace(/^_|_$/g, ''); // 언더바로 시작하거나 끝나는 경우 제거 } }
Shell
복사

3.5 ⭐ AWS 프론트엔드 기본 HTTP 배포(Static Deployment)

3.5.1 프론트엔드 프로젝트 빌드

📌
Frontend 프로젝트 빌드
•
프론트엔드 프로젝트를 열고 터미널을 통해 아래 명령어를 입력한다.
ng build
Shell
복사
•
빌드 후 www 라는 폴더가 생성되며 컴파일된 프로젝트의 내용이 스냅샷으로 저장된다.
◦
이후 본 프로젝트 원본 소스코드를 수정하면 다시 빌드해야 업데이트 내용이 반영된다.
◦
코드 수정과 업데이트, 배포 과정을 자동화 하는 것을 CI/CD 파이프라인 구축이라 한다.
◦
아직 CI/CD는 신경쓰지 않고 수동 배포부터 진행하고 추후 해당 내용을 다루려고 한다.
📌
www대신 dist라는 폴더가 생성되는데요?
•
빌드를 통해서 생성되는 스냅샷 산출물을 정의하는 것은 angular.json에서 정의되어 있다.
◦
www는 IONIC 프레임워크에서 주로 사용되는 빌드 산출물 폴더명이며 IONIC 명령어를 통해서 해당 프로젝트 구조가 초기 생성 되었으면 www로 설정되어있다.
◦
dist는 Angular 프레임워크에서 주로 사용되는 폴더명이며 Angular/CLI 명령어로 프로젝트가 생성된 경우 dist로 설정되어 있는 것이다.
◦
결론적으로 둘다 Angular 프레임워크를 혼용하는 상태이기도 하고 폴더명은 크게 문제되지 않기 때문에 기억하기만 하면 된다.
◦
또한 angular.json 에서 직접 원하는 폴더 명칭으로 변경도 가능하다는 점

3.5.2 배포용 S3 저장소 생성과 설정(첫번째 버킷)

📌
AWS S3에 프론트엔드 페이지 전용 버킷 생성
•
오리진 도메인 이름과 동일한 버킷, www가 포함된 버킷 두개가 필요하다.
•
첫번째 버킷(origin)
◦
AWS S3 콘솔 → Create Bucket → General configuration
◦
Bucket name → ☑️ boardapp.site 와 같이 본인이 구매한 도메인 그대로 입력
◦
Block all public access → 🔲 체크 해제
◦
그외 기본값 → ☑️ Create bucket → ☑️ 배포용 첫번째 버킷 생성 완료
▪
버킷 생성 후 버킷 설정
•
해당 버킷 상세정보 → Properties → ☑️ Edit static website hosting
◦
Static website hosting → ☑️ Enable
◦
Hosting type → ☑️ Host a static website
◦
Index document → ☑️ index.html 입력 → ☑️ Save changes
•
☑️ 첫번째 버킷 정적페이지 호스팅 설정 완료
◦
하단 웹페이지 링크로 접속되면 정상, 하지만 아직 도메인 적용, HTTPS도 안되어있다.
📌
프론트엔드 빌드 결과인 www 폴더 업로드
•
AWS S3 콘솔 → 내가 생성한 S3 Bucket → ☑️ Upload
•
Upload → ☑️ Add folder → www 폴더 찾아서 선택
•
그 외 설정 기본값 → ☑️ Upload
•
☑️ 프로젝트 업로드 완료
•
업로드 후 www폴더 내부 파일들을 ☑️ 버킷의 루트 경로로 이동시켜준다.
•
☑️ 버킷에 다음 처럼 index.html이 나타나면 된다.

3.5.3 www 리다이렉션 용 S3 저장소 생성과 설정(두번째 버킷)

📌
AWS S3에 프론트엔드 페이지 www 리다이렉션 전용 버킷 생성
•
오리진 도메인 이름과 동일한 버킷, www가 포함된 버킷 두개가 필요하다.
•
이 설명은 그 중 두번째 버킷(www. 프리픽스에 대한 리다이렉션 처리용)이다.
◦
AWS S3 콘솔 → Create Bucket → General configuration
◦
Bucket name → ☑️ www.boardapp.site 와 같이 www.를 포함한 도메인 주소 입력
◦
Block all public access → 🔲 체크 해제
◦
그외 기본값 → ☑️ Create bucket → ☑️ 배포용 두번째 버킷 생성 완료
▪
버킷 생성 후 버킷 설정
•
해당 버킷 상세정보 → Properties → ☑️ Edit static website hosting
◦
Static website hosting → ☑️ Enable
◦
Hosting type → ☑️ Redirect requests for an object
◦
Host name → ☑️ boardapp.site 와 같이 첫번째 버킷의 이름(도메인)
◦
Protocol - Optional → ☑️ http → ☑️ Save changes
•
☑️ 두번째 버킷 www. 리다이렉션 설정 완료
◦
첫번째 버킷과 다르게 www. 프리픽스가 붙은 URL이 확인될 것이다.

3.5.4 도메인 구매

📌
도메인 구매 과정
•
도메인 구매는 AWS의 Route 53에서도 할 수 있지만, 그 외 많은 업체에서도 구매 할 수 있다.
◦
AWS에서 구매하는 경우(비추천)
▪
AWS의 Route 53 콘솔 → 좌측 메뉴 Domains → ☑️ Registered domains
▪
검색하면 도메인이 나타나지만 가격대가 높은 편이다. 14~30 USD 수준
◦
도메인 및 호스팅 업체에서 구매하는 경우(Cafe24, 가비아 등)
▪
국내 도메인 관리 업체들도 많이 있으며 저렴하게 할인하는 경우도 있다.
▪
하지만 갱신 시 최초 투자한 가격보다 매우 높게 갱신되는 경우가 있기 때문에 실제 장기적인 이용을 위한 구매 시 잘 선택할 필요가 있다.
•
현재는 1,900원까지 할인된 도메인이지만 본래 가격이 60,000원이기 때문에 1년 후에도 계속 사용해야 한다면 비싼 금액으로 도메인을 유지해야 할 수도 있다.
•
보통 유명한 명사가 아닌 경우엔 10,000~20,000원/1년 정도로 가격대가 분포되어있다.
•
따라서 위 AWS에서 구매하는 가격이 일반적인 가격이라는 것.
▪
하지만 현재는 테스트 용도가 크기 때문에 최대한 저렴하게 사용 할 수 있는 도메인을 구매해서 사용하고자 한다.
•
가비아 업체를 통해 1,900원 할인가로 구매 할 수 있는 boardapp.site 를 구매하게 되었다. 본인들도 원하는 서비스 이름과 유사한 도메인을 구매하는 것을 추천한다.
▪
☑️ 도메인 구매 완료

3.5.5 구매한 도메인을 AWS에서 관리하도록 Route 53 설정

📌
도메인 구매가 완료되면 AWS Route 53에서 도메인을 등록해야 한다.
•
AWS Route 53 콘솔 → ☑️ Hosted zones → ☑️ Create hosted zone
•
Create hosted zone
◦
Hosted zone configuration → Domain name → ☑️ 본인의 도메인 주소 입력 boardapp.site
◦
그 외 기본 설정 → ☑️ Create hosted zone
◦
Hosted zone 생성 완료
▪
이 목록에서 Type 이 NS 인 레코드의 Value들을 기억해두자.
📌
AWS에서 Hosted zone을 생성했으면 도메인 구매 업체의 설정 화면으로 돌아가자
•
바로 직전에 NS(Name Server)의 값들로 변경해야 되는 부분이 있다.
•
구매 업체별로 메뉴의 구성은 다르지만 해당 도메인의 네임서버 설정할 수 있는 부분을 찾으면 된다.
•
가비아 → 서비스관리 → 구매한 도메인의 관리 메뉴 → ☑️ 네임서버 설정
•
이제 AWS Hosted zone에서 보았던 4개(일반적으론 4개) 값들을 입력해주고 적용시킨다.
◦
AWS에서 복사하는 경우 …org. 처럼 복사되는데 뒤에 .을 빼야 한다
•
☑️ AWS Hosted zone으로 NS 변경 완료

3.5.6 도메인과의 연결 HTTP 배포

📌
구매한 도메인으로 주소를 변경하자.
•
현재까지 정상적으로 진행되었다면 아래와 같은 2개의 복잡한 주소지만 본인의 웹 페이지가 브라우저에 나타날 것이다.
•
아직 boardap.site이외에 s3-website.ap…amazonaws.com 과 같은 복잡한 도메인 주소가 따라 붙는 것을 제거해야 한다.
◦
이 과정을 위해서는 우선 웹 호스팅과 도메인에 관련된 DNS 관리에 대해서 가볍게 알아야 한다. 하지만 네트워크 관련 이론이기 때문에 우리는 실무적으로 바로 사용할 수 있는 정도로 가이드를 따라 설정하는 방법을 익히는 것에 집중하고 해당 CS지식이 필요한 경우 추가로 스터디를 추천하는 편이다.
◦
하지만 이 복잡한 과정을 우리는 AWS 인프라 내에서 처리하고 있기 때문에 최대한 간편하게 설정하고 도메인을 사용 할 수 있게 된다.
📌
DNS관리의 A레코드와 CNAME
•
도메인을 구매하는 과정에서 NS(Name Server)를 설정하던 부분을 찾아가야 한다.
•
Route 53 → Hosted zones → ☑️ 본인의 도메인 boardapp.site 클릭
◦
Records → ☑️ Create Record
◦
첫번째 레코드 ( A )
▪
Record name → ☑️ 빈칸으로 그대로 둔다(boardapp.site가 됨)
▪
Record type → ☑️ A (레코드 선택)
▪
Alias → ☑️ On (스위치 선택)
▪
Route traffic to → ☑️ Alias to S3 web site endpoint
•
→ ☑️ Asia Pacific (Seoul)
•
→ ☑️ s3.website.ap-northeast… (본인의 S3 오리진 버킷 선택)
◦
☑️ Add another record
◦
두번째 레코드 ( CNAME)
▪
Record name → ☑️ www 입력 (www.boardapp.site가 됨)
▪
Record type → ☑️ CNAME (레코드 선택)
▪
Alias → 사용안한다.
▪
Value → ☑️ boardapp.site.s3-website.ap-northeast-2.amazonaws.com 처럼 본인의 S3 오리진 버킷의 Web hosting 설정 후 제공해준 도메인을 입력
▪
☑️ Create records
▪
☑️ 도메인 ↔ S3 버킷 연결 완료
•
주소에 할당 될 때 까지 잠시 기다린 후 접속해보면 http://boardapp.site 와 같은 도메인으로 정적 페이지가 나타나는 것을 볼 수 있다.
•
❗해당 A레코드, CNAME 주소 부분은 직후 CloudFront로 변경 시 CloudFront가 제공해주는 URL로 다시 변경 해야 HTTPS를 적용 할 수 있음을 기억해두자.❗

3.6 ⭐ HTTPS 배포(CloudFront, ACM→SSL)

3.6.1 Amazon Route 53 Service

📌
Amazon Route 53이란?
•
아마존 웹 서비스(AWS)에서 제공하는 클라우드 기반의 DNS(Domain Name System) 서비스
•
Route 53을 사용하여 도메인 등록, DNS 라우팅, 상태 확인 등 주요 기능을 제공
◦
도메인 등록
▪
Route 53을 사용하여 새로운 도메인을 등록할 수 있음
•
직접 도메인 구매 가능, 또는 타사 구매 도메인 등록 가능
◦
DNS 관리
▪
도메인에 대한 DNS 레코드를 쉽게 생성하고 관리
▪
A 레코드, CNAME 레코드, MX 레코드 등 다양한 유형의 레코드를 지원
◦
트래픽 라우팅
▪
사용자의 요청을 리소스(예: 웹 서버, S3 버킷 등)로 라우팅할 수 있는 기능을 제공
▪
Route 53은 다양한 라우팅 정책(예: 지리적 라우팅, 가용성 기반 라우팅 등)을 지원
◦
헬스 체크 및 관리
▪
리소스의 상태를 모니터링하고, 문제가 발생할 경우 트래픽을 다른 건강한 리소스로 자동으로 전환
▪
AWS의 다른 서비스와 쉽게 통합되어, AWS 리소스를 효율적으로 관리하고 배포

3.6.2 ACM(Amazon Certificate Manager Service)

📌
ACM이란?
•
아마존 웹 서비스(AWS)에서 제공하는 SSL/TLS 인증서를 쉽게 관리하고 배포할 수 있는 서비스
◦
인증서 생성
▪
ACM을 사용하여 무료로 SSL/TLS 인증서를 생성할 수 있음
▪
이를 통해 웹사이트와 애플리케이션의 HTTPS 보안을 강화
◦
인증서 관리
▪
인증서를 쉽게 관리하고, 만료 전에 자동으로 갱신할 수 있는 기능을 제공
◦
다양한 서비스와 통합
▪
ACM 인증서는 Amazon CloudFront, Elastic Load Balancing, Amazon API Gateway 등 여러 AWS 서비스와 통합되어 쉽게 연결하여 사용 가능
◦
도메인 검증
▪
도메인 소유권을 검증하는 DNS 검증 또는 이메일 검증 방법을 통해 소유권을 확인하는 기능을 제공

3.6.3 도메인 등록과 보안 설정 흐름 정리

📌
위 서비스들을 활용하기 위한 상황 정리
•
CloudFront가 기본적으로 제공해주는 도메인은 실제로 사용하기 어렵다.
◦
d1i0cqrftvetp.cloudfront.net 이름 다음과 같이 나타난다.
◦
도메인은 그 서비스, 기업의 이름이자 첫 인상이기 때문에 특정 이름의 도메인으로 변경할 필요가 있다.
◦
내가 원하는 문자의 도메인 구매가 필요하다.(HTTP에서 진행했다.)
◦
AWS Route 53을 통해 도메인을 관리, 해당 주소와 연결하게 될 것이다.
•
CloudFront가 기본적으로 제공해주는 도메인은 HTTPS 보안이 적용되어 있다.
◦
커스텀 도메인으로 변경하면 보안 인증서 SSL를 해당 도메인과 연결해야 한다.
◦
SSL은 유료로 발급 받기도 하며 발급 과정이 번거롭고 설정 또한 복잡하다.
◦
AWS Certificate Manager에서는 무료로 SSL을 제공해준다.
◦
또한 Route 53에 등록할 도메인, CloudFront로 배포되는 프로젝트는 동일한 AWS 인프라에 포함되어 있어 쉽게 HTTPS 환경을 적용 할 수있다.
•
아래 순서대로 진행하게 된다.
◦
도메인 구매 - 도메인구매 및 등록(이미 진행 됨)
◦
Route 53 설정 - DNS 레코드를 관리(S3→CloudFront 변경 필요)
◦
ACM에서 SSL 인증서 생성 - HTTPS 보안을 위한 인증서를 발급
◦
CloudFront 또는 ELB 설정 - SSL 인증서를 연결하여 HTTPS를 활성화
◦
웹사이트 배포 - 사이트를 사용자에게 제공

3.6.4 CloudFront에서 배포용 Distribution 생성 🎯

📌
CloudFront를 활용한 HTTPS 배포
•
CloudFront 콘솔 → Distributions → ☑️ Create distribution
◦
Origin domain → ☑️ boardapp.site.s3.ap-northeast-2.amazonaws.com 과 같이 S3에서 생성했던 오리진 버킷을 선택
◦
그 외 기본설정
◦
Default cache behavior
▪
Viewer → ☑️ Redirect HTTP to HTTPS
▪
Allowed HTTP methods → ☑️ GET, HEAD, OPTIONS
▪
그 외 기본 설정
◦
Web Application Firewall (WAF) → ☑️ Do not.. 선택
◦
Settings
▪
Alternate domain name (CNAME) → ☑️ boardapp.site , ☑️ www.boardapp.site 두개 모두 입력
▪
Custom SSL certificate → ☑️ ACM에서 구성한 인증서 선택 ( 없는경우 아래 Request certificate를 통해 발급 - Virginia region것만 됨)
💡
ACM에서 SSL(HTTPS 보안을 위한 인증서)받기
ACM(Amazon Certificate Manager)에서 SSL 발급 받기
•
AWS 콘솔 → 검색 및 즐겾차기로 이동 → ☑️ Certificate Manager → ☑️ Request a certificate
◦
Certificate type → ☑️ Request a public certificate → ☑️ Next
◦
Domain names → ☑️ boardapp.site / ☑️ www.boardapp.site 둘다 입력
◦
Validation method → ☑️ DNS validation 기본 설정
◦
Key algorithm → ☑️ RSA 2048 기본 설정
◦
☑️ Request
◦
☑️ SSL 인증서 신청 완료 → Status - Pending
▪
이는 해당 도메인 소유자 확인을 해야 된다. → ☑️ Create records in Route 53 버튼 클릭
▪
Create DNS records in Amazon Route 53 에서 자동으로 입력된다.
•
☑️ Create records
▪
Route 53의 Hosted zone Records에 자동으로 입력된 키들이 보인다.
▪
몇분 이후 다시 ACM 콘솔을 살펴보면 Issued, Success로 인증서가 발급된 것을 볼 수 있다.
▪
Supported HTTP versions → ☑️ HTTP/2, ☑️ HTTP/3 둘다 선택
▪
그 외 기본설정
▪
☑️ Create distribution
◦
☑️ Distribution 생성 완료

3.6.5 CloudFront 제공 주소를 구매한 커스텀 도메인으로 연결 🎯

📌
도메인 변경
•
각자 클라우드 프론트 대쉬보드를 보면 d1i0cqrftvetp.cloudfront.net 과 같은 주소로 S3에서 배포하는 것이 HTTPS로 우선 접속 될 것이다.
◦
이제 다시 구매한 도메인 주소로 깔끔하게 변경할 필요가 있다.
◦
이제 저 cloudfront.net으로 끝나는 주소를 복사하여 기존 A레코드, CNAME을 수정하여 변경해주면 된다.
▪
Route 53 → ☑️ 기존 S3와 연결된 boardapp.site, www.boardapp.site 각각 ☑️ cloudfront배포 주소를 입력 수정하여 아래와 같이 만들면 된다.
▪
일정 시간 이후에 접속하면 커스텀 도메인으로 커스텀 SSL이 적용된 웹 페이지를 볼 수 있다.
▪
10분 정도 이후 Region 별로 연결이 추가되는 모습
▪
HTTPS로 접속 되는 모습

3.7 ⭐ AWS EC2 사용하기

3.7.1 EC2(Amazon Elastic Compute Cloud Service)

📌
Amazon EC2란?
•
아마존 웹 서비스(AWS)에서 제공하는 클라우드에서 가상 서버(인스턴스) 컴퓨팅 서비스
•
EC2는 간단하게 AWS에서 컴퓨터 자체를 렌탈한다고 생각하면 된다.
•
실제로 각종 프로그램을 설치하고 실행 할 수 있다.
◦
RDS로 데이터베이스를 사용하고있지만 EC2 로컬 내부에 MySQL을 설치하고 데이터베이스 서버로 사용해도 된다.
▪
하지만 RDS가 데이터베이스 관련 기능만 경제적으로 사용 할 수 있는 서버기 때문에 해당 기술을 사용하는 것이다.
•
EC2의 강점은 다음과 같다.
◦
가상 서버
▪
EC2는 사용자가 필요에 따라 다양한 구성의 가상 서버를 생성
▪
CPU, 메모리, 스토리지 용량 등을 선택, 확장, 축소 가능
◦
유연성
▪
인스턴스를 필요에 따라 쉽게 시작하고 종료
▪
필요에 따라 스케일 업(성능 증가) 또는 스케일 다운(성능 감소)
◦
다양한 인스턴스 유형 제공
▪
특정 워크로드에 최적화된 인스턴스를 선택할 수 있음
▪
예를 들어, CPU 집약적인 작업에는 C 시리즈, 메모리 집약적인 작업에는 R 시리즈를 사용
◦
안전성
▪
EC2는 가용 영역(AZ)과 리전(region) 내에서 인스턴스를 배포할 수 있어, 고가용성을 유지
◦
스토리지 옵션
▪
EBS(Elastic Block Store)를 통해 영구 저장소를 제공하며, S3와 같은 다른 스토리지 옵션과도 통합하여 사용 가능
◦
보안
▪
AWS IAM(Identity and Access Management)을 통해 인스턴스에 대한 접근 권한을 관리할 수 있으며, 보안 그룹을 사용하여 네트워크 트래픽을 제어
◦
비용 효율성
▪
사용한 만큼만 비용을 지불하는 요금제(종량제)를 제공하여, 필요할 때만 리소스를 사용

3.7.2 EC2로 백엔드 서버 인스턴스 생성하기

📌
EC2로 클라우드 서버 환경 만들기 따라하기
•
AWS 콘솔 → ☑️ EC2 (즐겨찾기 또는 검색)
•
Amazon EC2 콘솔 → Region 선택(지역 선택) → ☑️ Asia Pacific(Seoul)
•
Instances → ☑️ Launch instances
•
Name and tags → ☑️ ec2-server-boardapp 처럼 알아 볼 수 있게 입력
•
Application and OS Images
◦
Quick Start → ☑️ Ubuntu
▪
Ubuntu는 타 클라우드 미래 호환성, 마이그레이션을 고려한 선호되는 관례적 선택임
◦
Amazon Machine Image (AMI) → ☑️ Ubuntu Server 24.04 LTS (Free tier eligible)
◦
Description → Architecture → ☑️ 64-bit
•
Instance type → ☑️ t2.micro
•
Key pair (login)
◦
EC2에 접근 할 때 필요한 인증 키 → ☑️ Create new key pair
▪
Key pair name → ☑️ ec2-boardapp-key 처럼 구분할 수 있는 이름
▪
Key pair type → ☑️ RSA
▪
Private key file format → ☑️ .pem
▪
☑️ Create key pair
▪
자동으로 다운로드 된 key를 알 수 있는 위치로 이동 → ☑️ PC 루트 경로(예로 c: 같은 위치로 옮겨둠)
•
Key pair name → ☑️ 위 생성한 key 선택
•
Network settings
◦
Firewall(security groups) → ☑️ Create security group (새로운 보안 그룹 생성)
◦
Allow SSH traffic from → Anywhere에서 ☑️ My IP로 변경
•
Configure storage → ☑️ 20GiB (*일반적인 서버 역할로는 기본 8GiB도 충분, 추후 고려)
•
☑️ Launch Instance
•
☑️ EC2 Instance 생성 완료
•
해당 인스턴스 이름을 클릭하면 세부 정보를 확인 할 수 있음
◦
AWS에서는 이와 같이 특정 서비스들이 동작하는 렌탈 클라우드 PC를 인스턴스라 함

3.7.3 EC2 서버 초기 설정

📌
Elastic IP address 설정
•
EC2 컴퓨터는 AWS 인터넷에 연결되어 있어서 외부 액세스가 가능하다.
•
제공되는 기본 IP들은 유동 IP로 서버 재부팅 및 일정 시간마다 주소가 변경된다.
•
하지만 프론트엔드 서버와의 호출 등 고정된 IP 주소가 필요하기 때문에 이를 탄력적 IP라고 불리우는 Elastic IP를 설정해야 한다.
•
EC2 콘솔 → ☑️ Elastic IPs
◦
Elastic IP adresses → ☑️ Allocate Elastic IP address
◦
기본 설정 → ☑️ Allocate
◦
☑️ Elastic IP 생성 완료
▪
이제 고정 IP 주소를 생성한 것이지 아직 EC2 인스턴스와 연결되지 않았다.
▪
☑️ 해당 IP 주소를 클릭하여 정보 페이지로 진입
▪
IP 정보 페이지 → ☑️ Associate Elastic IP address
▪
Instance → ☑️ 생성했던 EC2 선택
▪
Private IP address → ☑️ 드롭박스에 나타나는것 선택
▪
☑️ Associate
•
☑️ Elastic IP - EC2 연결 완료
•
EC2 Instance 세부 정보에서도 고정 IP를 확인 할 수 있다.
•
이 주소가 실제 요청, 터미널 접근 등에 사용되는 IP주소가 된다.

3.7.4 EC2 API 서버 실행 환경 구축

📌
이제 실제로 EC2에 접속하여 프로젝트를 실행 시키기 위한 단계이다.
•
많은 학습자들이 생소해서 어렵게 느끼기도 하지만 쉽게 아래처럼 생각하자.
◦
우리는 다른곳에 있는 컴퓨터를 빌렸을 뿐인데, 하필이면 DOS 환경의 옛날 컴퓨터를 빌린 상태이다.
◦
Zoom에서 흔하게 사용하던 원격 제어처럼 해당 컴퓨터를 내 컴퓨터에서 원격으로 제어하는 것 뿐이다.
◦
그런데, 아무것도 설치 안되어 있어서 하나하나 DOS(정확히는 Ubuntu, Linux) 명령어로 설치해줘야 한다.
◦
다행히 인터넷은 연결되어 있다.
📌
우선 EC2에 터미널로 접속해야 한다.
•
EC2 인스턴스 상세정보 페이지 → ☑️ Connect
•
Connect to instance → ☑️ SSH client 탭 → ☑️ Example Copy
•
본인 컴퓨터의 터미널을 연다 → Key Pair 생성 과정에서 .pem 으로 다운로드 받은 키의 저장 위치가 기억나는가? 해당 위치로 이동한다. cd.. 등 명령어 이용 → ☑️ 복사한 ssh 접속 명령어 붙여넣고 실행
◦
최초 접속에서 SSH 클라이언트가 연결하려는 호스트의 신원을 확인할 수 없기 때문에 다음 처럼 나타난다. → ☑️ yes
◦
bad permissions 같은 오류가 나타날 수도 있다.
◦
이 경우에는 key파일에 접근 권한의 문제이다.
◦
☑️ 아래 권한 명령어를 먼저 입력(본인의 키 파일 이름으로 변경)
chmod 400 "ec2-boardapp-key.pem"
Shell
복사
◦
☑️ EC2 SSH 접속 시도
ssh -i "ec2-boardapp-key.pem" ubuntu@ec2-43-200-247-144.ap-northeast-2.compute.amazonaws.com
Shell
복사
◦
☑️ 로컬(내 PC) 터미널에서 원격 EC2 접속 완료
📌
AWS EC2에 터미널을 통해 접속했다 무엇부터 해야 할까?
•
목표를 생각하고 점진적으로 접근해야 한다. 아래처럼 자연스러운 생각의 흐름을 따라가자.
💡
우리는 백엔드 API 서버를 이 EC2 컴퓨터에서 돌리고 싶은 목표가 있다.
•
프로젝트는 어디있지? → Github Repository
◦
Github에서 가져오려면? → git clone …
▪
git이 있나?
•
git 설치확인은? → 버전확인을 하자 → ☑️git -v
•
EC2는 아무것도 설치 안된 컴퓨터라 설명한 것 처럼..
◦
우리가 개발을 시작할 때 아무것도 설치되지 않았던 내 컴퓨터에서 개발을 위한 환경 구축 부분들을 리마인드해야 한다.
◦
일부 위 처럼 기본적인 일부 git과 같은 프로그램은 기본적으로 설치되어 있지만 확실하다고 보장 할 수 없다. 본인이 직접 설치한 기억이 없으면 불확실한 것이다. 없다고 생각하고 각종 프로그램 설치 상태 확인을 해줘야 한다.
◦
개발 PC와는 다르게 실행 환경(Runtime environment)만 구축되도 된다.
◦
하지만 예전 자바에서 JDK가 JRE를 포함하고 있는 것 처럼, 기본적으로 우리가 개발 환경을 구축하는 것은 실행환경을 포함하고 있어서 크게 다르지 않다.
📌
프로젝트 클론하기
•
☑️ 현재 폴더 내용 조회
ls -a
Shell
복사
•
☑️ git clone(본인 Remote Repository 주소로 변경)
git clone https://github.com/citeFred/nestjs-board-app
Shell
복사
•
☑️ 프로젝트 폴더 이동
cd nestjs-board-app
Shell
복사
•
☑️ npm run start
◦
NestJS는 Angular기반이며 Node.js 기반 웹 프레임워크이다.
◦
따라서 npm run start로 실행하기 위해서는 Node.js가 설치되어 있어야 한다.
◦
Node.js가 설치되기 위해서는 NVM이 설치되어야 하며 그에 따라 NPM이 설치된다.
▪
이에 대한 자세한 내용은 에서 [4], [5] 환경 구축 부분을 살펴 보길 바란다.
▪
☑️ NVM 설치
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
Shell
복사
•
☑️ NVM 설치완료
•
☑️ 터미널 환경 변수 확인(자동 등록, 혹시 없으면 작성)
vim /home/ubuntu/.bashrc
Shell
복사
◦
환경 변수 등록 코드
export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
Shell
복사
◦
VI에디터로 문서 편집 = I / 저장 후 종료 esc → :wq
•
☑️ 환경 변수 터미널 즉시 적용
source /home/ubuntu/.bashrc
Shell
복사
•
☑️ NVM 버전 확인
nvm -v
Shell
복사
◦
nvm 버전 확인이 안되는사람은 설치, 환경 변수 설정 재확인
▪
☑️ Node.js 설치
nvm install --lts
Shell
복사
•
☑️ Node.js 버전 확인
node -v
Shell
복사
•
☑️ NPM 버전 확인
npm -v
Shell
복사
◦
☑️ 현재 프로젝트의 모든 의존성 설치
▪
☑️ 경로 프로젝트 루트 경로여야 함(package.json이 보이는 위치)
npm install
Shell
복사
◦
☑️ npm run start
▪
서버는 실행되지만 Database(RDS)를 연결하지 못하는 상황
•
현재 프로젝트는 DB 접속 정보 등이 .env 파일로 은닉되어 있다.
◦
github에는 .gitignore로 추적이 제외되어 해당 파일이 존재하지 않기 때문에 git clone으로 받아온 현재 프로젝트에 존재하지 않는다.
◦
따라서 해당 파일을 직접 작성해주면 된다.
▪
☑️ .env 파일 생성 및 작성
vim .env
Shell
복사
•
VI에디터로 문서 편집 = I / 저장 후 종료 esc → :wq
▪
☑️ npm run start
•
.env까지 작성했는데 접속이 안된다.
•
그 이유는 접근 권한에 대한 것이다.
◦
EC2 인스턴스에서 RDS에 접근하려면 RDS는 EC2 IP주소에 대한 접근이 허용되어야 한다.
•
EC2의 보안 그룹 확인
◦
EC2 대시보드 → Security → ☑️ Security groups 이름 확인
▪
sg-08d2c9adfd58876c9 (launch-wizard-1)
•
RDS의 보안 그룹 확인
◦
RDS 대시보드 → Security group rules → ☑️ CIDR/IP - Inbound 인 부분 선택
▪
해당 보안그룹 정보로 들어가서 ☑️ Edit inboud rules
▪
→ ☑️ Add rule
•
Type → ☑️ MYSQL/Aurora
•
Source → 우측 드롭다운에서 위에서 기억한 ☑️ EC2의 보안 그룹 선택
•
☑️ Save rules
•
☑️ RDS ↔ EC2 권한 설정 완료
•
☑️ npm run start
◦
TypeORM이 정상적으로 작동한다면 DB 연결까지 완료된 상태
◦
이제 EC2의 3000번 포트에서 백엔드 API 서버가 실행되고 있다.

3.8 AWS Frontend와 Backend 연결

📌
현재 까지의 상황 정리
•
프론트엔드는 S3 버킷에 빌드되어 업로드되고 CloudFront가 S3의 페이지를 배포하고 있다.
•
백엔드는 EC2 서버에서 실행되고 있으며 RDS와 연결이 되었다.
•
이제 프론트엔드가 어떤 버튼을 눌렀을때(HTTP 요청시) 회원 가입이나 게시글 페이지가 나타나는 등 API 요청의 응답이 나타나야 한다.
◦
로컬에서는 이미 작동하던 것이지만 AWS로 옮겨지면서 요청 URL이 변경되어야 하고
◦
EC2와 CloudFront IP에서 보내는 요청을 허용해야 한다.

3.8.1 백엔드의 설정 변경 CORS

📌
EC2는 현재 로컬(나의 PC)의 접근만 허용되고 있다.
•
EC2 Dashboard → Security → ☑️ Security groups 선택
•
Inbound rules
◦
Type → ☑️ HTTP
◦
Source → ☑️ Anywhere 0.0.0.0
•
백엔드 코드 CORS 설정 변경
◦
main.ts
dotenv.config(); async function bootstrap() { const app = await NestFactory.create<NestExpressApplication>(AppModule); // cookie parser 미들웨어 추가 app.use(cookieParser()); app.enableCors({ origin: [ 'http://localhost:8100','http://localhost:4200', // 로컬 개발용 'https://d2r1i81lny2w8r.cloudfront.net', // CloudFront 도메인 'https://boardapp.site', // 커스텀 도메인 'https://www.boardapp.site', // www 커스텀 도메인 ], methods: 'GET,HEAD,PUT,PATCH,POST,DELETE', credentials: true, }); app.useStaticAssets(path.join(process.cwd(), 'public'), { prefix: '/files/' }); await app.listen(process.env.SERVER_PORT); Logger.log(`Application Running on Port : ${process.env.SERVER_PORT}`) } bootstrap();
TypeScript
복사
◦
EC2에는 해당 내용이 반영되어야 하기 떄문에 clone을 다시 받아와야 한다.
▪
임시로 직접 ubuntu 터미널로 파일에 접근해 vim으로 수정하긴 했다.

3.8.2 백엔드 API의 HTTP→HTTPS 요청이 가능하도록 변경 🎯

📌
현재 프론트 엔드 배포 이후 요청이 안되는 문제가 있을 것이다.
•
위 문서처럼 프론트엔드는 HTTPS로 보안 적용이 되어 있지만 백엔드 API는 여전히 HTTP를 사용하고 있기 때문이다.
•
따라서 백엔드 서버 또한 HTTPS 요청을 주고 받을 수 있도록 변경하고자 한다.
📌
SSL(Secure Socket Layer)이란?
•
서버와 사용자(브라우저) 간의 통신을 할 경우 정보를 암호화 하고 도중에 해킹을 통해 정보가 유출이 된다고 하더라도 정보의 내용을 보호할 수 있는 보안 인증 솔루션 기술
•
대표적으로 해킹 기법 중 '스니핑(sniffing)' 기법을 막기 위한 도구
💡
Sniffing?
•
해킹기법의 하나로 네트워크상에서 전송되는 패킷을 캡쳐하여 이의 내용을 엿보는 행위
•
패킷을 가로채는 것으로 도청과 비슷한 개념으로 이해
📌
Let’s encrypt 로 SSL 인증서 생성
•
Let's Encrypt는 HTTPS 의 확산이 늦어지는 것은 SSL 인증서에 있다고 보고 무료 인증서 보급을 통해 HTTPS 의 확산을 늘리겠다는 취지로 시작된 비영리 프로젝트
•
2016 년 4월 정식 버전을 출시 했으며, 2020년 12월 기준 2.34 억개의 웹서버 인증이 진행
•
인증 기간이 90일로 제한되어 재발행을 권장하고 있음
•
그 외 타기관 인증서는 유료로 관리되는 점에서 Let’s encrypt를 사용하여 무료로 개발 할 수 있다.

3.8.2.1 API서버 A레코드 생성

📌
EC2 IP주소를 가리키는 A레코드가 필요하다
•
EC2 대시보드 → API 서버 인스턴스 상세정보 → ☑️ Elastic IP address 복사
•
Route 53 대시보드 → 생성한 Hosted zones 선택 → ☑️ Create record
◦
Record name → ☑️ api 입력 ( api.boardapp.site 형태가 됨 )
◦
Record type → ☑️ A 선택
◦
Value → ☑️ 복사한 EC2의 IP 주소 붙여넣기
◦
☑️ EC2 를 가리키는 A 레코드 생성 완료

3.8.2.2 EC2에서의 작업 - Certbot 설치 및 인증서 발급

📌
EC2 콘솔에서 작업(SSH로 접근)
•
우선 NestJS 백엔드 API 서버가 배포되는 EC2 서버 터미널로 접근한다.
•
EC2 대시보드에서 인스턴스를 선택하여 Connect to instance에서 SSH 접근 명령어를 확인 할 수 있다.
◦
SSH 접근을 위해서 pem키를 생성했던 위치에서 아래 명령어를 입력한다.(잊은 사람은 이 링크에서 재확인 3.7.4 EC2 API 서버 실행 환경 구축 )
◦
나의 사례에서는 다음과 같은 형태이다.
ssh -i "ec2-boardapp-key.pem" ubuntu@ec2-43-200-247-144.ap-northeast-2.compute.amazonaws.com
Shell
복사
◦
접속하면 다음과 같이 ubuntu~ 로 현재 위치가 변경되어야 한다.
📌
Certbot 설치 및 인증서 설치
•
패키지 매니저 업데이트
sudo apt update
Shell
복사
•
Certbot 설치
sudo apt install certbot
Shell
복사
•
도메인에 대한 인증서 발급
sudo certbot certonly --standalone -d api.boardapp.site
Shell
복사
◦
email 주소 입력과 약관 등이 나타나며 각 항목을 동의한다.
◦
인증서 생성 완료
▪
.pem 확장자의 저장 경로를 안내해준다.
•
/etc/letsencrypt/live/api.boardapp.site/ 경로 내에 두개의 키가 저장된 것을 확인 할 수 있다.

3.8.2.3 EC2에서의 작업 - NestJS HTTPS 설정

📌
프로젝트의 main.ts 수정
•
EC2 → 프로젝트 폴더로 이동
cd nestjs-board-app/src
Shell
복사
•
VI에디터로 코드 수정
vim main.ts
Shell
복사
•
main.ts 아래 코드 추가
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import * as cookieParser from 'cookie-parser' import { Logger } from '@nestjs/common'; import * as dotenv from 'dotenv'; import { NestExpressApplication } from '@nestjs/platform-express'; import * as path from 'path'; import * as fs from 'fs'; import * as https from 'https'; // https 모듈 추가 dotenv.config(); async function bootstrap() { const app = await NestFactory.create<NestExpressApplication>(AppModule); // cookie parser 미들웨어 추가 app.use(cookieParser()); app.enableCors({ origin: [ 'http://localhost:8100','http://localhost:4200', // 로컬 개발용 'https://d2r1i81lny2w8r.cloudfront.net', // CloudFront 도메인 'https://boardapp.site', // 커스텀 도메인 'https://www.boardapp.site', // www 커스텀 도메인 'http://s3-bucket-boardapp.s3-website.ap-northeast-2.amazonaws.com' ], methods: 'GET,HEAD,PUT,PATCH,POST,DELETE', credentials: true, exposedHeaders: ['Authorization'], // 이 부분 추가 }); app.useStaticAssets(path.join(process.cwd(), 'public'), { prefix: '/files/' }); // HTTPS 옵션 설정 const httpsOptions = { key: fs.readFileSync('/etc/letsencrypt/live/api.boardapp.site/privkey.pem'), cert: fs.readFileSync('/etc/letsencrypt/live/api.boardapp.site/fullchain.pem'), }; // HTTPS 서버 생성 const server = https.createServer(httpsOptions, app.getHttpAdapter().getInstance()); // NestJS 애플리케이션을 HTTPS 서버에 연결 (HTTPS 적용) server.listen(process.env.HTTPS_SERVER_PORT, ()=> { Logger.log(`Application Running on [SECURED] https://api.boardapp.site:${process.env.HTTPS_SERVER_PORT}`); }); // NestJS 애플리케이션 시작 (HTTPS 기존) await app.listen(process.env.HTTP_SERVER_PORT, ()=> { Logger.log(`Application Running on [BASIC] http://localhost:${process.env.HTTP_SERVER_PORT}`); }); } bootstrap();
TypeScript
복사
•
ESC → :wq 로 저장하고 나가기
•
프로젝트를 실행해보면 npm run start 아래와 같이 권한 관련 문제가 나타난다.
◦
ubuntu 유저는 EC2의 root 권한이 없기 때문
◦
아래 명령어로 프로젝트를 실행하도록한다. 슈퍼유저 권한으로 실행하는 방법이다.
sudo npm run start
Shell
복사
▪
super user(root) 에 npm이 없다면 설치해주자
sudo apt update
Shell
복사
sudo apt install nodejs npm
Shell
복사
•
.env 포트 관련 환경변수 편집
◦
ls -a 로 .env 파일이 있는 곳을 확인(src 폴더 내 이동 cd src
◦
기존 SERVER_PORT인 부분에 앞에 HTTPS_ , HTTP_ 처럼 구분하여 포트번호를 다르게 설정
◦
3001번 포트가 HTTPS 키를 사용하는 서버로 설정했다. 클라이언트는 이곳으로 요청을 보내야 한다.
•
sudo npm run start 인증서를 사용하는 HTTPS API서버 URL 확인
•
EC2의 express 서버가 실행중인 현재 상태에서 HTTPS 요청이 정확히 되는지 테스트
◦
중요한점은 이전 개발 환경과 다르게 localhost가 아니라 현재 AWS EC2의 구매한 도메인의 API 서버인 서브 도메인 api.boardapp.site에 SSL이 적용된 요청이 되는지 확인하는 것이다.
◦
간단하게 터미널을 통해서도 테스트 할 수 있다.
curl -X GET https://api.boardapp.site:3001/api/articles
Shell
복사
◦
POSTMAN을 통해서 가장 간단한 API인 게시글 목록을 조회하는 요청을 시도
▪
◦
정상적으로 HTTPS 경로로 요청이 성공되는 모습

3.8.2.4 프론트엔드의 설정 변경

📌
API 요청 URL의 변경이 필요하다.
•
EC2의 Elastic IP 주소를 설정했었다. 해당 주소는 고정 IP주소이기 떄문에 요청 주소로 사용하기 위해 api.boardapp.site로 Route 53을 통해 도메인을 연결했다.
•
위 과정을 완료했기 때문에 프론트엔드에서 HttpClient를 통해(또는 fetch api)요청을 보내는 각 Service 계층에서 요청 주소를 HTTPS를 포함하는 주소로 변경해야 한다.
•
article.service.ts
@Injectable({ providedIn: 'root' }) export class ArticleService { // private apiUrl = 'http://localhost:3000/api/articles'; // 로컬 테스트용 // private apiUrl = 'http://43.200.247.144:3000/api/articles'; // EC2 연결용 private apiUrl = 'https://api.boardapp.site:3001/api/articles'; // HTTPS 주소로 변경 constructor(private http: HttpClient) { } // 생략... }
TypeScript
복사
•
auth.service.ts
@Injectable({ providedIn: 'root' }) export class AuthService { // private apiUrl = 'http://localhost:3000/api/auth'; // 로컬 테스트용 // private apiUrl = 'http://43.200.247.144:3000/api/auth'; // EC2 연결용 private apiUrl = 'https://api.boardapp.site:3001/api/auth'; // HTTPS 주소로 변경 constructor(private http: HttpClient) { } // 생략... }
TypeScript
복사
•
user.service.ts
@Injectable({ providedIn: 'root' }) export class UserService { // private apiUrl = 'http://localhost:3000/api/users'; // 로컬 테스트용 // private apiUrl = 'http://43.200.247.144:3000/api/users'; // EC2 연결용 private apiUrl = 'https://api.boardapp.site:3001/api/users'; // HTTPS 주소로 변경 constructor(private http: HttpClient) { } // 생략... }
TypeScript
복사
•
위 처럼 코드가 변경된 부분이 적용되어야 하기 떄문에 재빌드, S3에 재업로드 해야 한다.
◦
이런 불편함을 해결하는것이 CI/CD 파이프라인 구축인데 아직은 안내하지 않았음
◦
ng build 재빌드
◦
S3 → boardapp.site 버킷에 재업로드
◦
HTTPS 요청 성공 테스트
▪
구현 상태 정리
•
백엔드 API서버에서는 Let’s Encrypt를 통해 EC2내부에서 pem 키페어를 관리, HTTPS 요청에 대해 처리
◦
사용자가 구매한 커스텀 도메인의 서브 도메인(api.domain.com) 을 Route 53을 통해 EC2 IP주소와 연결
•
프론트 클라이언트 서버에서는 S3 버킷에 기본 HTTP 정적 페이지를 제공하면서 CloudFront를 연결하여 배포
◦
사용자가 구매한 커스텀 도메인을 Route 53을 통해 AWS Certificate Manager의 SSL 키페어를 등록, 연결하여 HTTPS 접근 URL을 제공

4. 기타 변경 사항 및 정리 과정에서의 트러블슈팅

4.1 트러블슈팅 - ”ERR_TOO_MANY_REDIRECTS”, “ChunkLoadError”

📌
예상과 다르게 백엔드 프론트엔드 서버 구축은 끝났지만 정상작동이 안되고 있다.
•
도메인 https://www.boardapp.site 에 접근해서 특정 페이지를 접근 할 때
◦
다음과 같이 이상한 경로로 나타나고 있다. 뭔가 이상한 prefix가 중복해서 붙고 있는 상황
◦
어디엔가 Redirection 관련 설정 또는 코드의 결함일 수 있다.
📌
EC2 로컬에서의 테스트
•
NestJS 서버 자체가 작동은 잘되는지부터 확인해보았다.
•
둘다 EC2의 터미널이며 왼쪽에는 서버를 실행시켜두고
•
오른쪽 아래 커맨드를 통해 요청을 보내봤다.
curl -X POST http://localhost:3000/api/auth/signin -H "Content-Type: application/json" -d '{"email": "test@test.com", "password": "Test1234!"}'
TypeScript
복사
•
결과 서버는 정상적으로 작동하며 RDS와의 연결도 정상이다.
•
이제 localhost라는 부분을 IP주소로 EC2의 대체 해본다.
curl -X POST http://172.31.41.28:3000/api/auth/signin -H "Content-Type: application/json" -d '{"email": "test@test.com", "password": "Test1234!"}'
TypeScript
복사
◦
마찬가지로 정상 작동하고 Elastic IP로 변경해본다.
curl -X POST http://43.200.247.144:3000/api/auth/signin -H "Content-Type: application/json" -d '{"email": "test@test.com", "password": "Test1234!"}'
TypeScript
복사
◦
역시나 정상적으로 쿼리까지 작동한다.
•
이말은 EC2의 서버는 정상적으로 작동하고 있으며 유동 IP, 고정 IP 모두 정상적으로 API 요청을 받고 있다.
•
그럼 문제는 외부로부터의 요청에 대한 문제일 가능성이 높아졌다.
◦
EC2 자체가 외부 권한을 어떻게 허용하고 있는지 체크할 필요가 있다.
▪
인바운드 규칙은 테스트를 위해서 로컬PC, FrontCloud 등 최대한 오픈했지만 작동하지 않았다.
▪
다시한번 깔끔하게 인바운드 규칙을 모두 정리하고 모든 권한을 열어보았다.
▪
이후 이번엔 나의 로컬 PC가 원격으로 EC2 서버에 요청해보려고한다.
curl -X POST http://43.200.247.144:3000/api/auth/signin -H "Content-Type: application/json" -d '{"email": "test@test.com", "password": "Test1234!"}'
TypeScript
복사
▪
결과 정상적으로 작동한다. 이말은 인바운드 규칙 방화벽의 문제가 확실해졌다.
▪
이번엔 내부 IP주소로 요청해보았다.
curl -X POST http://172.31.41.28:3000/api/auth/signin -H "Content-Type: application/json" -d '{"email": "test@test.com", "password": "Test1234!"}'
TypeScript
복사
▪
응답이 없다. Elastic IP로 요청을 보내야 정상적으로 처리된다는 것이다.
▪
로컬의 프론트엔드를 실행하여 다시 테스트했다.
•
정상적으로 로그인이 처리되었으며, EC2 서버에서도 로그가 나타나는 것을 볼 수 있었다.
•
하지만 쿠키가 로컬때 테스트와 다르게 노란색으로 표기된다.
•
EC2에서 src/auth/auth.controller.ts쿠키가 생성되는 로그인 컨트롤러로 접근하여 httponly 속성을 true로 변경
•
요청과 관련된 문제는 체크되었으나 CloudFront의 Redirection 문제는 해결되지 않았다.
◦
현재 정상 작동되는 배포 파일을 S3에 업로드해서 오류를 좁혀나가려고 한다.
📌
문제 해결 과정
•
하나의 버킷을 사용하면서 폴더별로 Behavior를 설정하는데서 가장 큰 문제가 있는것 같다.
◦
우선 과감하게 S3 버킷을 모두 삭제하고 다시 재생성해보았다. 이럴수록 설정을 부분적으로 수정하면서 더 문제가 커지는 경우가 많았다.
◦
S3에서의 작업
▪
CloudFront에서의 폴더별 리다이렉션을 설정하는 방법 대신, S3 버킷에서 boardapp.site 오리진 버킷을 만들고 호스팅한다.
▪
www.boardapp.site 와 같은 프리픽스 더미 버킷을 만들어서 오리진 버킷 자체로 리다이렉션 하도록 구성을 바꿨다.
◦
CloudFront에서의 작업
▪
Distribution을 생성 할 때 해당 배포와 관련된 URL을 CNAME으로 오리진인 boardapp.site, www.boardapp.site 두개 모두 추가한다.
▪
CloudFront가 직접 바라보는 오리진 버킷은 boardapp.site의 S3 버킷만을 지정한다.
▪
ACM을 통해 SSL(HTTPS인증서)를 발급받아야 하므로 (SSL 생성은 Virginia 지역에서만 처리된다는 점 기억) 안내에 따라 설정한다.
◦
Route 53에서의 작업
▪
그리고 Route 53에서 boardapp.site로 A레코드를 생성
▪
www.boardapp.site로는 CNAME을 생성
▪
이후 배포와 DNS 연결까지 잠시 대기 후 접속하여 해결
•
HTTPS 연결 테스트 모습
•
우선 Redirection 문제는 해결되었으며 이제 HTTPS 요청에 대한 문제로 변경되었다.

4.2 트러블 슈팅 - Mixed Content: The page at 'https://boardapp.site/auth/signin' was loaded over HTTPS, but requested an insecure

📌
해당 클라이언트 오류 로그를 통해서도 알 수 있듯이 HTTPS 로 배포되는 서비스에서 HTTP 요청이 혼재된 경우에 발생하는 오류이다.
•
해결 방법은 두가지 정도로 조사되었다.
◦
첫번쨰는 요청 자체를 HTTP → HTTPS로 바꾸는것
▪
프론트엔드의 HttpClient 요청 부분을 살펴보면 API 호출 URL이 모두 http로 시작한다.
@Injectable({ providedIn: 'root' }) export class AuthService { // private apiUrl = 'http://localhost:3000/api/auth'; private apiUrl = 'https://43.200.247.144:3000/api/auth'; constructor(private http: HttpClient) { } ... }
TypeScript
복사
▪
이 부분을 https로 변경한다. 해당 EC2 IP주소가 https 인증과 Security group에서 inbound 규칙이 설정이 되어 있어야 한다.
•
우선 inbound 규칙은 모든 요청을 허용하고 있다. 정상 실행되는 모습이 확인되면 HTTP, HTTPS를 별도로 그리고 정확한 IP 소스로부터 제한하도록 세부 설정하게 될 것이다.
•
역시 https로 요청을 보내도 EC2의 백엔드 서버 자체에 https 설정이 없기 때문에 해당 요청을 받아낼 수 없는 상태이다.
•
이 방법을 위해서 EC2에 Loadbalancer를 통해서 ACM의 인증서를 적용시키려 했으나 EC2는 AsiaPacific Region이며 SSL은 Virginia Region이기 때문에 AWS내에서 적용하기 어려움이 있다.
◦
두번째는 HTML의 head안에 meta태그를 추가로 설정하는 것이다.
▪
프론트엔드의 index.html의 head에 다음 코드를 작성한다.
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests"/>
HTML
복사
•
두번째 방법도 결국 EC2에서 https를 받을 수 없기 때문에 동일한 오류가 나타나고있다.
📌
근본적인 원인 해결 필요
•
현재 백엔드 API 서버에서 HTTPS를 위한 인증서 관련 작업을 진행한 적이 없다.
•
SSL을 발급 받고 HTTPS 요청을 받을 수 있도록 조치가 필요하다.

4.3 JWT 토큰 저장 방식 변경

📌
현 HTTPS 문제, Redirection 문제, 쿠키 저장 문제 등 보안 설정 및 연결에 다양한 이상이 있다.
•
FrontCloud에 CDN과 프론트엔드 배포를 하나의 버킷에서 처리하고 싶었지만 생각보다 보안과 DNS 연결 구성이 복잡하게 구성되어 있었다.
•
따라서 각 AWS 기술의 역할을 명확하게 정리하고 서비스를 다시 연결해보고자 한다.
📌
JWT 쿠키 방식을 JWT Header 로컬 스토리지 저장 방식으로 변경
•
우선 HTTPS 보안을 적용하기 전에 현재 인증과정을 좀더 깔끔하게 처리하고 싶어서 쿠키 생성보다 헤더로 전송하여 로컬스토리지에 저장하는 방식으로 변경하고자 한다.
•
백엔드의 auth.controller.ts
◦
기존 쿠키를 생성하여 Response에 추가하던 부분을 res.setHeader를 통해 헤더에 담아서 응답을 반환하도록 설정했다.
@Controller('api/auth') export class AuthController { private readonly logger = new Logger(AuthController.name); constructor(private authService: AuthService){} // 회원 가입 기능 @Post('signup') @UseInterceptors(FileInterceptor('profilePicture')) async signUp( ... } // 로그인 기능 @Post('signin') async signIn( @Body() signInRequestDto: SignInRequestDto, @Res() res: Response ): Promise<void> { this.logger.verbose(`Attempting to sign in user with email: ${signInRequestDto.email}`); const { jwtToken, user } = await this.authService.signIn(signInRequestDto); const userResponseDto = new UserResponseDto(user); this.logger.verbose(`User signed in successfully: ${JSON.stringify(userResponseDto)}`); // JWT를 응답 헤더에 설정 res.setHeader('Authorization', `Bearer ${jwtToken}`); res.status(200).json(new ApiResponse(true, 200, 'Sign in successful', { user: userResponseDto })); } ... }
TypeScript
복사
•
프론트엔드의 로그인 요청 부분 auth.service.ts
◦
기존 쿠키를 특별히 저장하거나 하는 로직은 없었고 이는 쿠키는 자동으로 클라이언트에서 저장되기 때문이었다.
◦
하지만 헤더로 JWT토큰을 전송하게 되면 로컬스토리지에 저장하는 로직이 프론트엔드에 구성되어야 한다.
signIn(signInRequestData: SignInRequestData): Observable<AuthResponse> { const headers = new HttpHeaders({ 'Content-Type': 'application/json' }); return this.http.post<AuthResponse>(`${this.apiUrl}/signin`, signInRequestData, { headers, withCredentials: true, observe: 'response' }).pipe( tap(response => { // JWT를 Authorization 헤더에서 추출하여 로컬 스토리지에 저장 const jwtToken = response.headers.get('Authorization')?.split(' ')[1]; console.log("get"+jwtToken) if (jwtToken) { localStorage.setItem('jwtToken', jwtToken); console.log("saved"+jwtToken) } }), map(response => { // response.body가 null이 아닐 때만 반환 if (response.body) { return response.body; // AuthResponse 반환 } throw new Error('Response body is null'); // null인 경우 예외 처리 }) ); }
TypeScript
복사
•
클라이언트 테스트
◦
이제 안정적으로 JWT토큰을 클라이언트에 저장하는 모습이다. 해당 클라이언트는 로컬 테스트 환경이 아니라 AWS 배포환경(EC2 + S3) 에서 실행된 모습이다.
◦
이는 쿠키 JWT를 보관했을때 배포환경에서 문제가 발생하였기 때문에 이와 같이 JWT 토큰 관리 방식 로직을 수정하게 되었다.

PS. Github

•
리팩토링 완료 된 결과 코드 묶음은 Github를 참고
•
Backend
◦
•
Frontend
◦
Search
⚙
 | Main Page | Category |  Tags | About Me | Contact | Portfolio