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회)
2.2 AWS 콘솔 사용 준비하기
즐겨찾기 설정해두기
•
콘솔의 좌측 상단을 살펴보면 Services 탭으로 모든 AWS 서비스를 살펴 볼 수 있으며
•
Search 탭을 통해 원하는 Service를 검색 할 수 있다.
•
하지만, AWS는 200개 이상의 서비스가 있으므로 일일히 찾아서 사용하기엔 번거롭기 때문에 주요 서비스들을 미리 즐겨찾기로 등록해두고자 한다.
•
자주 사용되며 이 샘플에서 사용될 서비스들을 아래처럼 검색 필드에 각 이름을 입력하면서 동적으로 나타나는 결과를 즐겨찾기 해둔다.
◦
IAM
◦
RDS
◦
S3
◦
CloudFront
◦
Route 53
◦
EC2
◦
Certificate Manager
•
이제 아래와 같이 각 서비스를 즐겨찾기를 통해서 빠르게 접근 할 수 있다.
3. AWS 사용해보기
현재 AWS를 통한 목표는 다음과 같다.
•
로컬 → 클라우드 환경으로 전환 부분
◦
AWS 회원 가입 및 IAM 권한 설정
◦

▪
로컬 MySQL 데이터베이스 → 데이터베이스 RDS로 변경
▪
로컬 파일 시스템 업로드 경로 → 저장소 S3로 변경
→ Cloudfront CDN 파일 제공 설정
▪
로컬 테스트 서버 실행 환경 → 실제 서비스 EC2 인스턴스로 변경
◦

▪
로컬 테스트 서버 실행 환경 → 실제 서비스 프론트 정적배포 실행 환경 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 계정을 직접 사용하지 않고 않는 것은 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 체크
▪
▪
▪
▪
▪
▪
▪
▪
◦
→ 이후
Create user 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 의 상세 정보 →
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이름
•
•
생성 후 몇분 정도 초기화 및 생성 과정 대기
•
•
해당 인스턴스 이름을 클릭하면 세부 정보를 확인 할 수 있음
◦
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 그대로
▪
▪
▪
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
•
•
•
해당 인스턴스 이름을 클릭하면 첫 Object 탭을 볼 수 있으며 저장소 파일들을 볼 수 있음
•
샘플로 파일을 업로드 하기 위해 Upload 버튼으로 이동 → 이미지1개와 영상1개를 업로드 테스트
•
업로드 성공 상태
3.3.3 백엔드 API 서버와 S3의 연결 
위 처럼 웹 AWS 콘솔에서 파일 업로드 테스트가 완료된다면, 실제로 S3 저장소 인스턴스는 생성된 것이다.
•
NestJS 백엔드 프로젝트를 열어보자
•
.env
◦
우리는 DB 접속 정보를 .env 파일로 환경변수로 다루며 은닉화 시켜 왔다.
◦
특히 현재 IAM 유저 그룹과 유저 등록을 통해서 Access Key, Secret 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에서 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
◦
◦
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폴더 내부 파일들을
버킷의 루트 경로로 이동시켜준다.
•
3.5.3 www 리다이렉션 용 S3 저장소 생성과 설정(두번째 버킷)
AWS S3에 프론트엔드 페이지 www 리다이렉션 전용 버킷 생성
•
오리진 도메인 이름과 동일한 버킷, www가 포함된 버킷 두개가 필요하다.
•
이 설명은 그 중 두번째 버킷(www. 프리픽스에 대한 리다이렉션 처리용)이다.
◦
AWS S3 콘솔 → Create Bucket → General configuration
◦
◦
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. 프리픽스가 붙은 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에서 구매하는 가격이 일반적인 가격이라는 것.
▪
하지만 현재는 테스트 용도가 크기 때문에 최대한 저렴하게 사용 할 수 있는 도메인을 구매해서 사용하고자 한다.
•
▪
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. 처럼 복사되는데 뒤에 .을 빼야 한다
•
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 오리진 버킷 선택)
◦
◦
두번째 레코드 ( CNAME)
▪
Record name →
www 입력 (www.boardapp.site가 됨)
▪
Record type →
CNAME (레코드 선택)
▪
Alias → 사용안한다.
▪
Value →
boardapp.site.s3-website.ap-northeast-2.amazonaws.com 처럼 본인의 S3 오리진 버킷의 Web hosting 설정 후 제공해준 도메인을 입력
▪
▪
•
•
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
▪
▪
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 기본 설정
◦
◦
▪
이는 해당 도메인 소유자 확인을 해야 된다. →
Create records in Route 53 버튼 클릭
▪
Create DNS records in Amazon Route 53 에서 자동으로 입력된다.
•
▪
Route 53의 Hosted zone Records에 자동으로 입력된 키들이 보인다.
▪
몇분 이후 다시 ACM 콘솔을 살펴보면 Issued, Success로 인증서가 발급된 것을 볼 수 있다.
▪
Supported HTTP versions →
HTTP/2,
HTTP/3 둘다 선택
▪
그 외 기본설정
▪
◦
3.6.5 CloudFront 제공 주소를 구매한 커스텀 도메인으로 연결 
도메인 변경
•
◦
이제 다시 구매한 도메인 주소로 깔끔하게 변경할 필요가 있다.
◦
HTTP 배포 시 Route 53에서 추가했던 A레코드와 CNAME 기억 날 것이다.
◦
이제 저 cloudfront.net으로 끝나는 주소를 복사하여 기존 A레코드, CNAME을 수정하여 변경해주면 된다.
▪
▪
일정 시간 이후에 접속하면 커스텀 도메인으로 커스텀 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
▪
▪
자동으로 다운로드 된 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도 충분, 추후 고려)
•
•
•
해당 인스턴스 이름을 클릭하면 세부 정보를 확인 할 수 있음
◦
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
◦
▪
이제 고정 IP 주소를 생성한 것이지 아직 EC2 인스턴스와 연결되지 않았다.
▪
▪
IP 정보 페이지 →
Associate Elastic IP address
▪
Instance →
생성했던 EC2 선택
▪
Private IP address →
드롭박스에 나타나는것 선택
▪
•
•
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
복사
◦
ssh -i "ec2-boardapp-key.pem" ubuntu@ec2-43-200-247-144.ap-northeast-2.compute.amazonaws.com
Shell
복사
◦
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 https://github.com/citeFred/nestjs-board-app
Shell
복사
•
cd nestjs-board-app
Shell
복사
•
◦
NestJS는 Angular기반이며 Node.js 기반 웹 프레임워크이다.
◦
따라서 npm run start로 실행하기 위해서는 Node.js가 설치되어 있어야 한다.
◦
Node.js가 설치되기 위해서는 NVM이 설치되어야 하며 그에 따라 NPM이 설치된다.
▪
▪
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
Shell
복사
•
•
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 -v
Shell
복사
◦
nvm 버전 확인이 안되는사람은 설치, 환경 변수 설정 재확인
▪
nvm install --lts
Shell
복사
•
node -v
Shell
복사
•
npm -v
Shell
복사
◦
▪
npm install
Shell
복사
◦
▪
서버는 실행되지만 Database(RDS)를 연결하지 못하는 상황
•
현재 프로젝트는 DB 접속 정보 등이 .env 파일로 은닉되어 있다.
◦
github에는 .gitignore로 추적이 제외되어 해당 파일이 존재하지 않기 때문에 git clone으로 받아온 현재 프로젝트에 존재하지 않는다.
◦
따라서 해당 파일을 직접 작성해주면 된다.
▪
vim .env
Shell
복사
•
VI에디터로 문서 편집 = I / 저장 후 종료 esc → :wq
▪
•
.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의 보안 그룹 선택
•
•
•
◦
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 type →
A 선택
◦
Value →
복사한 EC2의 IP 주소 붙여넣기
◦
3.8.2.2 EC2에서의 작업 - Certbot 설치 및 인증서 발급
EC2 콘솔에서 작업(SSH로 접근)
•
우선 NestJS 백엔드 API 서버가 배포되는 EC2 서버 터미널로 접근한다.
•
EC2 대시보드에서 인스턴스를 선택하여 Connect to instance에서 SSH 접근 명령어를 확인 할 수 있다.
◦
◦
나의 사례에서는 다음과 같은 형태이다.
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 재빌드
◦
◦
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”
예상과 다르게 백엔드 프론트엔드 서버 구축은 끝났지만 정상작동이 안되고 있다.
•
◦
다음과 같이 이상한 경로로 나타나고 있다. 뭔가 이상한 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 버킷을 모두 삭제하고 다시 재생성해보았다. 이럴수록 설정을 부분적으로 수정하면서 더 문제가 커지는 경우가 많았다.
◦
◦
CloudFront에서의 작업
▪
▪
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
Related Posts
Search