0. 들어가기 전에


0-1. 사전 준비 체크 리스트

  • 리액트 프로젝트가 cra 나 vite 를 통해 리액트 프로젝트가 이미 만들어져있어야 합니다.
  • aws 계정을 미리 만들어 둡니다.
  • 처음이라면 branch 를 따거나 fork 한 후에 테스트를 먼저 해보는 것도 좋습니다.
  • 프로젝트는 npm run build 등으로 빌드를 시킨 후에 시작합니다.
  • (생략 가능) Route53 으로 도메인을 미리 생성합니다.

0-2. S3를 이용하는 이유

aws S3은 버킷이라는 공간에 정적 콘텐츠를 저장하여 인터넷을 통해 접근할 수 있도록 하는 서비스입니다. 간단히 말하면 웹상에 존재하는 하드 공간인 셈입니다. 다른 자료들을 보다보면 EC2에 배포를 하는 경우도 있는데 (EC2는 작은 컴퓨터라 생각하면 됩니다) 프론트엔드의 정적 콘텐츠를 배포할때에는 S3면 충분하기 때문입니다.

0-3. CloudFront 로 배포하는 이유

CloudFront 를 사용하지 않고 S3 자체만으로 배포하는 방법도 있습니다.

하지만 S3 로 배포했을 때와 다음과 같은 장점으로 CloudFront 를 이용하기로 하였습니다.

  • HTTP → HTTPS : 손쉬운 설정으로 http 로의 접속을 https로 리디렉션 시켜줄 수 있습니다.
  • CDN 을 통한 더 빠른 페이지 응답속도 : S3으로만 배포하는 경우, 선택한 리전 내에서만 생성이 되기 때문에 해당 리전에서 멀어질수록 접속 속도가 느려집니다. 하지만 CloudFront 를 이용하면 전 세계에 분포된 엣지 로케이션이라고 하는 데이터 센터의 엣지 서버를 사용해 콘텐츠를 캐싱하고, 사용자가 위치한 곳에서 가장 가까운 엣지 로케이션에서 콘텐츠를 제공받을 수 있도록 해주는 역할을 합니다.
  • 그 외의 장점들은 공식 문서를 참고합니다 : https://aws.amazon.com/ko/cloudfront/

0-4. 전반적인 과정 순서

  1. IAM 권한 설정
  2. S3 버킷 만들기
  3. 빌드된 프로젝트 S3 버킷에 업로드
  4. CloudFront 배포 생성
  5. 캐시 Invalidation(Purge) 하기

다음과 같은 순서로 진행되고, 마지막으로 직접 작업하면서 생겼던 오류나 추가적으로 필요한 설정들을 작성할 예정입니다.

1. IAM 권한 설정


AWS 에서 IAM을 검색하여 IAM 페이지로 이동합니다.

1-1. 사용자 생성

(이미 생성되어 있다면 생략합니다.)

사용자 이름을 입력 후, 액세스 유형은 액세스 키로 선택하여 다음으로 넘어갑니다.

1-2. 권한 설정

(이 부분도 역시 되어있다면 생략합니다.)

기존 정책 직접 연결 탭에서 AmazonS3FullAccess 를 체크합니다.
CloudFront는 이후에 진행할 예정이라 이때 같이 CloudFrontFullAccess 도 선택해주셔도 좋습니다.

1-3. 액세스 키 발급

다음으로 계속 넘어간 후, 사용자 만들기를 누르면 액세스키가 만들어집니다.

여기서 .csv 다운로드 버튼을 클릭하여 안전한 곳에 다운 받습니다.

2. S3 버킷 만들고 설정하기


2-1. 버킷 만들기

버킷 탭으로 들어가 버킷 만들기 버튼을 누릅니다.

그럼 다음과 같은 화면이 나옵니다

버킷 이름을 입력하고, AWS 리전을 선택합니다 (기본값으로 설정되어 나옵니다)

객체 소유권탭에서는 ACL 활성화를 선택합니다.

퍼블릭 액세스는 차단을 풀어주고 아래 체크박스에 체크해줍니다. 차단을 하면 웹으로 접근이 불가능합니다.

그외의 항목들은 기본값으로 그대로 두어도 되고, 상황에 맞게 설정해주면 됩니다.

각 항목의 구체적인 역할은 다음 사이트를 참고합니다 : https://real-dongsoo7.tistory.com/101

2-2. 속성 – 정적 웹사이트 호스팅

버킷 목록에서 생성한 버킷을 선택하면 다음과 같은 탭이 나오는데, 여기서 속성 탭을 선택합니다.

맨 아래로 내리면 정적 웹 사이트 호스팅 이라는 항목이 있는데, 여기서 편집을 선택합니다.

비활성화가 되있는것을 활성화로 바꿉니다. 활성화하면 아래에 여러 항목들이 생깁니다.

인덱스 문서, 오류 문서 둘 다 index.html 을 입력해주고, 변경 사항 저장 버튼을 눌러 저장합니다.
(저장 후에 정적 웹 사이트 호스팅 항목을 확인하면 엔드포인트 주소가 나오게 됩니다)

2-3. 권한 – 버킷 정책 편집

해당 버킷의 권한 탭을 선택합니다.

아래로 내려 버킷 정책 탭에서 편집을 선택합니다.

버킷 정책은 직접 편집으로 타이핑해도 되지만, 정책 생성기를 이용하면 편하게 정책을 생성할 수 있습니다.
이번에는 정책 생성기를 이용하는 방법을 설명하겠습니다.

정책 생성기를 클릭해주세요.

스크린샷과 같이

EffectAllow, (유저가 접속할 수 있도록 설정)
Select Type of Policy 에서는 S3 Bucket Policy 를 선택하고,
Principal 에는 * 을 입력하고 (모든 유저에 대해서 라는 뜻입니다)
Action 에는 GetObject 를 선택합니다 (유저들이 이 버킷에 접근할 수 있는 권한을 준다는 뜻입니다)
ARN 에는 아까 버킷 정책 편집 페이지에 적혀있는 ARN을 입력하면 되는데, 끝에 /* 를 추가로 붙여줍니다
(*은 모든 오브젝트에 대해서 라는 의미입니다.)
– 예제 입력 : arn:aws:s3:::버킷이름/*

Resource 항목에 ‘/*’가 빠져있습니다 참고해주세요

다 입력한 후에 Add Statement 를 클릭하면 스크린샷과 같이 리스트가 생성됩니다.

아래의 Generate Policy 를 클릭하면 JSON 형태의 문서가 나오는데 전체 복사를 한 후에,

다시 버킷 정책 편집 탭으로 들어와 복사한 값을 붙여넣기 합니다.

아래에 변경 사항 저장 버튼으로 꼭 저장해주세요 !

3. 생성한 S3 버킷에 코드 업로드 (with CLI)


S3 콘솔 내에서도 업로드버튼으로 직접 코드를 업로드시킬 수도 있지만

우리에겐 CLI가 조금 더 편하기때문도 있고 또한 이후 CI/CD 를 적용하기 위해서도 CLI 를 통한 업로드를 추천합니다.

3-1. AWS CLI 설치

공식 문서가 있습니다. 참고하여 설치합니다.

https://docs.aws.amazon.com/ko_kr/cli/latest/userguide/cli-chap-install.html

3-2. 유저 추가

설치를 완료하였다면 CLI 를 통해 유저를 추가합니다.

(IAM 에서 생성한 사용자 이름을 입력합니다)

aws configure --profile [유저명]

위와 같이 입력합니다.

입력하면 스크린샷과 같이 입력해야하는 부분들이 나옵니다.

이 항목은 이전에 IAM 에서 사용자를 생성하고 다운로드받은 .csv 를 확인하여 입력하면 됩니다.

  • AWS Access Key ID : (Access Key ID를 입력합니다)
  • AWS Secret Access Key : (절대 노출되면 안됩니다)
  • Default region name : ap-northeast-2
  • Default output format : json

3-3. 배포하기

배포할 프로젝트 디렉토리에서 명령어를 입력합니다.

aws s3 sync ./build s3://[S3 버킷 이름] --profile=[사용자 아이디]

앞으로 자주 사용할 명령어기 때문에 package.json 에 스크립트로 추가해두면 편리합니다.

이렇게 설정해두면 npm run deploy 혹은 yarn deploy 로 입력하면 됩니다.

3-4. 중간 체크

이 과정까지 진행하여도 배포가 잘 되어있습니다.

확인하려면 프로젝트에 .env 파일을 생성한 후에

PUBLIC_URL="http://[버킷이름].s3-website.[리전].amazonaws.com"

이렇게 입력 한 후에 저장하여 같이 배포하면 위 주소에서 확인 가능합니다.

( 이 주소는 형태가 조금 다를 수 있으니 S3 해당 버킷의 속성 탭 맨 아래에 정적 웹 사이트 호스팅 항목에서 주소를 확인하면 됩니다 )

4. CloudFront 배포 생성


이제 CloudFront 를 연결해봅시다.
AWS 에서 CloudFront 를 검색하여 페이지에 들어갑니다.

배포 생성 버튼을 클릭합니다.

원본 도메인은 클릭하여 우리가 생성한 S3 버킷을 선택합니다.
선택하면 이름이 기본값으로 설정되니 그대로 둡니다.
S3 버킷 액세스는 OAI 사용을 눌러줍니다.

OAI 사용을 선택하면 아래와 같은 항목들이 나오는데, 새 OAI 를 생성해줍니다.
(이미 가지고 있다면 기존 ID를 선택하면 됩니다)
자동으로 입력된 값을 사용하면 됩니다.

아래로 내려 뷰어 프로토콜 정책에서 Redirect HTTP to HTTPS 를 선택합니다.
이렇게 하면 http 접속을 https 로 리다이렉트 시킬 수 있습니다.

(Route53으로 커스텀 도메인을 설정한 경우)
아래로 내려 대체 도메인 이름에서 Route53으로 생성한 주소를 입력합니다.

(Route53으로 커스텀 도메인을 설정한 경우)
Route53을 이용하여 대체 도메인을 입력했다면,
https 를 이용할 것이기 때문에 SSL 인증서가 필요합니다.
인증서는 aws 에서 제공하는 AWS Certificate Manager 를 통하여 발급 받을 수 있습니다.

설정이 다되었다면 배포 생성을 합니다.
생성하고 나면 Status 가 In Progress 라고 뜨는데, 조금 기다리시면 생성이 완료 됩니다.

❗️Route53으로 커스텀 도메인을 설정한 경우

추가적으로 Route 53 에서 CloudFront 주소와 Route53으로 생성한 도메인을 연결해주기 위해 A 유형으로 레코드를 생성해주는 작업이 필요합니다.

Route53 페이지로 들어가서 호스팅한 도메인에 레코드 생성 버튼을 누릅니다.

라우팅 정책은 단순 라우팅을 선택하시면 됩니다.

단순 레코드 정의를 선택합니다

여기서 레코드 유형은 A 유형으로,
값/트래픽 라우팅 대상은 CloudFront 배포에 대한 별칭 으로,
배포 선택에서는 우리가 생성한 CloudFront 를 선택하여
단순 레코드 정의 버튼으로 생성하여 완료합니다.

5. 캐시 Invalidation하기


5-1. 캐시 Invalidation 이 필요한 이유

캐시 Invalidation은 프로젝트가 업데이트 되었을 때 CloudFront에 적용하려면 직접 파일을 업데이트 해주는 작업입니다. CloudFront 는 S3파일이 엣지 로케이션에 캐싱되어 저장되는데, S3에 파일을 새로 업로드 하여도 바로 적용되지 않는게 이때문입니다.

CloudFront 로 배포되는 파일의 캐시가 유지되는 기본 시간은 24시간이며 오리진 HTTP 헤더의 캐시 설정을 이용하여 캐시가 유지되는 시간을 자유롭게 설정할 수 있습니다.

여기서 캐시가 만료되기 전에 CloudFront 내의 파일 내용을 갱신하고 싶다면 무효화(Invalidation) 기능을 사용하면 됩니다. 쉽게 설명하자면 S3에 업데이트된 파일을 업로드하고 CloudFront가 가지고있는 캐시를 비워준다면 사용자가 해당 파일을 요청하면 CloudFront 의 엣지 로케이션은 캐시를 가지고 있지 않기 때문에 다시 오리진(S3)에서 새 파일을 가져오기 때문에 내용을 업데이트시킬 수 있는 것입니다.

5-2. IAM 권한 추가

1번 과정에서 IAM 사용자를 생성하였을 때 CloudFrontFullAccess 도 같이 체크하셨다면 건너뜁니다.

AWS 에서 IAM 페이지에 접속하여 사용자를 선택하여 권한 추가 버튼을 눌러준 뒤,

CloudFrontFullAccess 를 선택하여 추가하면 됩니다.

5-3. CLI 명령어 입력

이제 Invalidation 명령어를 입력합니다.

aws cloudfront create-invalidation --profil=[사용자 아이디] --distribution-id [CloudFront ID] --paths /*

사용자 아이디는 IAM 에서 설정한 아이디를 입력하면 되고,

distribution-id 는 CloudFront 페이지에서 ID 값을 입력해주시면 됩니다.

paths 는 invalidation 할 파일들을 지정하는 것인데, 전체를 변경할 것이기 때문에 /* 을 입력하였습니다. (업데이트 할 항목만 입력하여도 됩니다)

이 또한 package.json 의 script 에 추가하면 편리합니다.

이후 프로젝트 빌드 후 배포할 때

npm run build && npm run deploy && npm run invalidate

이렇게 입력하여도 괜찮지만,

npm-run-all 을 사용하면 순서대로 한번에 명령어를 처리 할 수 있습니다.(설치 필요)

이렇게 package.json 내에 all 이라는 명령어를 추가하면 npm run all 명령어로 간편하게 배포와 invalidation 작업을 한번에 진행할 수 있습니다. ( -s 플래그는 순서대로 실행한다는 의미입니다)

5-4. PUBLIC_URL 설정하기

%PUBLIC_URL% 을 사용하는 경우에 적용합니다.

프로젝트 루트 디렉토리에 .env 파일을 생성하여 다음과 같이 설정합니다.

PUBLIC_URL="https://[S3 버킷 이름].s3.[리전].amazonaws.com/"

❗️ Route53으로 커스텀 도메인을 생성한 경우에는 해당 도메인 주소를 PUBLIC_URL 에 입력하면 됩니다.

6. 부록


6-1. react-router-dom 사용 시 설정해야하는 부분

React 프로젝트에서는 보통 react-router-dom을 이용하여 라우팅 작업을 진행합니다. url이 변하면서 페이지가 같이 이동하는 것 같아보이지만 리액트는 SPA 이기 때문에 실제로 페이지는 index.html 하나에서 컴포넌트만 갈아치우는 것입니다.

이때, React 프로젝트를 배포하면 location 을 직접 조정하는 경우엔 페이지가 나오지만, react-router-dom 의 Link to 같은걸 사용하여 이동하면 403에러와 함께 access denied 가 나오며 페이지가 원할하게 나오지 않습니다.

그렇기 때문에 AWS 에서 추가적으로 설정해야 하는 부분이 있습니다.

S3 버킷 설정

이 부분은 위에서 정적 웹 사이트 호스팅 과정에서 입력한 인덱스 문서, 오류 문서를 둘 다 index.html 로 입력하는 것입니다. 이미 입력하셨다면 다음 단계로 갑니다.

CloudFront 설정

CloudFront 페이지의 오류 페이지로 들어갑니다.

사용자 정의 오류 응답 생성 버튼으로 오류코드 403, 404 를 생성하고 응답 페이지 경로를 둘 다 index.html 로 설정하고 응답코드도 같이 200으로 설정해주시면 됩니다.

이렇게 설정하고 나면 access denied 가 나오지 않고 정상적으로 페이지가 나오는 것을 확인할 수 있습니다.

6-2. 메인페이지는 접속되지만 파일을 못불러오는 경우

배포작업을 진행하면서 메인 페이지는 접속되어 파일 경로 요청은 가지만 실패하는 이슈가 있었습니다.

curl 명령어로 메인 페이지의 index.html 을 요청하면 응답이 오지만 js나 css 파일의 요청을 하면 계속해서 실패가 나왔습니다. S3, CloudFront, Route53 설정들을 하나씩 건드려봐도 해결이 되지 않아 굉장히 고생했었는데 의외로 간단한 문제였습니다.

위의 과정 중, .env 파일 내에 PUBLIC_URL 의 값으로 Route53으로 생성한 URL인 https://whatdidyoueat.net 을 그대로 입력했었는데 알고보니 CloudFront로 연결한 도메인은 www가 붙은 https://www.whatdidyoueat.net 이였습니다. 네트워크 탭에서 main 페이지는 상태코드 200으로 성공하지만 그외의 파일들이 전부 www 가 빠진 URL 로 요청을 보내고 있는것을 확인했고, curl 로 www 를 붙여 요청하니 응답이 왔습니다.

따라서 PUBLIC_URL 을 수정하여 해결할 수 있었습니다.

(위에도 썼듯이 %PUBLIC_URL% 을 사용하지 않는다면 굳이 설정해주지 않아도 잘 동작합니다)

너무 단순한 실수였기 때문에 구글에 검색해도 잘 안나왔던것 같습니다.

마무리

평소에 인터넷 창을 여러개 켜놓는 스타일이지만 이렇게 많은 창을 켜보는건 잘 없는 일이네요.

단순히 튜토리얼만 따라가서 해결하면 되지 않을까 라는 생각을 했었는데, 기존에 프론트가 배포된 환경에서 수정하려니 막히는 부분이 많아서 더 어려웠던거 같습니다. (결국 다 새로 만들었습니다ㅎㅎ; 같이 고생한 팀원 mki님 감사합니다…)

문서화를 하면서 튜토리얼을 따라가면서 ‘왜?’ 라고 의문을 품었지만 구체적으로 설명이 되지 않았던 글이 많았는데 직접 문서화를 진행하게되면 이런 부분들을 가능한 최대한 설명을 첨부하고 싶었습니다. 제대로 설명이 되었을지는 모르겠네요.

사실 배포하고서도 쏟아진 정보에 문서화할 엄두를 못내고 있었는데 배포완료 되자마자 바로 알아차리고 연락이 와서 문서화를 진행하도록 도와주신 허광남 멘토님 덕분에 좋은 기회가 되었습니다!

이 이후 GitHub Action을 이용한 CI/CD 작업을 진행하였는데 같이 쓰기엔 분량이 너무 많은거 같아 따로 작성하기로 하였습니다. 이 이후에 바로 문서화할 예정입니다.

여기까지 ‘뭐먹었니?’ 팀 dhyeon 이였습니다.

긴 글 읽어주셔서 감사합니다.

[참고 사이트]