- #Etc
S3 + Route53 + CloudFront로 프로젝트 배포하기.
Prolip
2024-10-16
프리티어 끝나고 눈물의 계정 옮기기.. CloudFront OAC 설정하기.
시작..
오늘은 제 AWS 계정의 프리티어 기간이 종료되어 계정을 새로 생성해 배포 환경을 설정하는 과정을 기록해보려고 합니다..
기억을 더듬거리면서 다시 해보는 거라 이번엔 제대로 기록 좀 해보려고 합니다..
우선 Route53 먼저..
먼저 호스팅 영역을 생성해봅시다.
호스팅 영역 생성
사용할 도메인 이름을 입력하고 영역을 생성합니다.
네임서버 변경
구매한 도메인 호스팅 사에 네임서버 설정 권한을 넘겨줍시다. 저는 가비아에서 버거풋 도메인을 구매했습니다..
이제 1차부터 4차까지 생성한 호스팅 영역의 NS 레코드 값을 위에서부터 넣어주면 됩니다. 맨 뒤의 ‘.’은 포함하지 않습니다!!!
적용까지 시간이 걸리니 그 사이에 인증서를 발급해봅시다.
인증서 발급하기
AWS Certificate Manager
도메인 이름에 구매한 도메인 이름을 입력합니다. http://, www는 입력하지 않습니다! 이후 아래 요청 버튼을 클릭합니다.
검증
SSL 인증서를 발급했습니다만, 해당 도메인을 소유하고 있는지 검증해야 활성화되기 때문에 이전에 생성한 Route53 호스팅 영역에 CNAME 레코드를 추가해야됩니다.
발급 받은 인증서를 클릭해 Route53에서 레코드 생성을 클릭합니다.
이제 검증이 완료될 때까지 기다리면 됩니다. 이제 S3 버킷을 설정해봅시다.
S3 버킷 설정
- S3 버킷 생성
- 이름 및 리전 선택
문서에는 이름에 정적 웹 호스팅 기능을 사용하는게 아니라면 ‘.’을 포함하지 않는게 좋다고 합니다.. 몇몇 규칙이 존재하는데 궁금하신 분들은 후에 한 번 스윽 보기만 하셔도 좋을듯 합니다.
- 객체 소유권 설정
단순히 정적 웹 호스팅 기능을 사용한다면 적절한 버킷 정책, 객체 권한을 설정해야만 합니다.
만약 모든 사용자가 해당 버킷에 퍼블릭하게 접근이 가능하다면 정적 웹 호스팅 기능을 활성화해 나온 엔드포인트를 통해 직접적인 접근이 가능하기 때문에 보안에 유의해야합니다..
하지만 우리 프로젝트는 CloudFront를 사용하는데 그냥 쉽게 중계 서버로 생각하면 됩니다.
사용자가 연결된 도메인을 통해 접근하면 CloudFront가 요청을 받아 S3 원본 서버에 접근해 다시 사용자에게 의미있는 콘텐츠를 전달하게 됩니다.
이때 사용자는 버킷과 직접적으로 연결되는 것이 아닌 중계 서버를 거치는 형식으로 사용자는 S3 버킷의 URL로 직접 접근할 수 없게 됩니다.
이후 나머지 설정은 건드리지 않고 버킷을 생성합니다.
우선 확인은 해야되니 빌드된 프로젝트 파일을 버킷에 업로드해둡니다.. 다시 말하지만 정적 웹 호스팅 기능을 사용하지 않고 CloudFront를 통해 접근할 예정이니 해당 기능은 활성화하지 않습니다.
CloudFront
1. 배포 생성
2. 원본 액세스 제어 설정
위에서 설명했듯 CloudFront만을 사용해 버킷에 접근하려면 OAC 설정을 해줘야 됩니다.
OAC(Origin Access Control)란?
Origin Access Control이란 오리진 액세스 제어 기능으로, AWS의 CloudFront와 S3 버킷을 연결할 때 S3에 대한 접근을 더 세부적으로 제어하기 위해 도입된 기능입니다. 아래에 더 자세히 정리해보겠습니다.
1. IAM 기반 접근 제어
문서에 따르면 OAC는 IAM 서비스의 보안 주체를 사용해 CloudFront와 S3 간 요청이 신뢰할 수 있는 주체에 의해 발생했는지를 확인한다고 하는데, 여기서 보안 주체는 AWS 리소스에 대한 접근 권한을 가진 사용자, 역할, 서비스 등을 뜻합니다.
OAC는 CloudFront에서 S3에 요청을 보낼 때 CloudFront IAM Role을 ****사용해 SigV4로 요청에 서명합니다.
2. IAM 역할 및 정책을 통한 보안
OAC는 IAM Role을 사용해 CloudFront가 S3 오리진에 안전하게 접근할 수 있도록 제어한다고 합니다.
이 역할을 통해 CloudFront에서만 S3 버킷에 접근할 수 있도록 권한을 부여하고, 그 외의 외부 사용자나 다른 서비스는 S3에 접근하지 못하도록 할 수 있습니다.
또한 IAM 정책을 통해 CloudFront가 S3에 접근할 때 사용할 수 있는 권한을 구체적으로 정의하는데 S3 버킷 정책과 IAM 역할을 통해 어떤 요청이 허용되거나 거부될지를 세부적으로 설정할 수 있습니다.
3. SigV4(Signature Version 4) 서명 프로세스
이 서명 방식은 S3가 요청을 검증하고 해당 요청이 CloudFront IAM 역할을 통해 이루어졌는지 확인하는 방식으로 인증이 이루어집니다.
SigV4는 요청이 안전하게 인증되고, 무결성을 보장하는 암호화 기반 서명 프로세스로 OAC를 사용한다면 CloudFront가 S3에 요청을 보낼 때마다 SigV4 서명 프로토콜을 사용해 요청에 서명합니다.
이 서명에 위에서 설명한 IAM 보안 주체에 대한 정보가 포함됩니다.
S3는 CloudFront로부터 받은 요청에 대해 SigV4 서명을 검증해 요청이 유효한지와 해당 요청이 올바른 IAM 역할에 의해 발생했는지를 확인하고 만약 서명이 유효하고 IAM 역할이 허가된 리소스 접근 권한을 가지고 있다면 요청이 승인되고 처리됩니다.
만약 아니라면 거부되겠죠??
그리고.. OAI는 GET만 지원했는데 OAC는 GET, PUT, POST, PATCH 등등등 다양한 메서드를 지원한다던지, AWS KMS를 통한 서버 측 암호화인 SSE-KMS를 지원한다던지.. 모든 리전에서 S3 오리진 접근을 지원한다던지.. 많은 개선점이 존재한다고 합니다.
이제 다시 돌아와서 원본 액세스 제어 설정을 선택하면 OAC 설정 버튼이 나타납니다.
클릭하면 위에서 설명한 서명 방식을 선택할 수 있습니다. 각 옵션은 다음과 같습니다.
- 요청 서명 안 함
이 옵션을 선택하면 CloudFront는 S3로 보내는 요청에 서명을 하지 않습니다. 클라이언트가 직접 요청에 서명하거나 S3 버킷이 퍼블릭인 경우 사용할 수 있습니다.
근데 이거 요청에 서명을 하지 않으면 위에서 설명했던 IAM 기반 인증 방식이 동작하지 않는 거라 사실상 OAC를 적용하지 않은 것과 같은 거 아닙니까..?
그렇다면 애초에 퍼블릭인 버킷이거나 아니면 별도로 S3 접근에 대한 인증을 처리할 수 있는 환경에서만 사용해야 될 것 같습니다.
- 서명 요청
해당 옵션을 선택하면 CloudFront는 모든 요청에 **AWS Signature Version 4 (SigV4)**로 서명하게 됩니다. 이 옵션은 기본적으로 CloudFront가 클라이언트로부터 받은 Authorization 헤더를 무시하고 자체적으로 서명해 S3로 요청을 보냅니다.
S3는 CloudFront가 서명한 요청을 검증해 요청이 유효하면 처리하고 그렇지 않다면 거부합니다.
- 승인 헤더 재정의 안 함
이 옵션을 사용하지 않으면 클라이언트에서 보내는 승인 헤더는 CloudFront에서 무시됩니다.
만약 제가 Authorization: Bearer <JWT 토큰> 이렇게 헤더를 작성해 요청을 보낸다면 CloudFront는 이 헤더를 삭제하고 자신의 자격 증명으로 다시 서명한 헤더를 만들어 S3로 요청을 보냅니다.
일반적인 상황에선 CloudFront가 자체적으로 서명을 관리하는 것이 더 안전하고 클라이언트가 이 헤더를 사용할 필요가 없을 것 같습니다.
이 옵션은 제 기준에선 사용할 일이 없을 거 같은데 클라이언트가 직접 S3에 파일을 업로드하거나 S3 권한을 제어해야 하는 상황에서 사용된다고 합니다..
3. Origin Shield 설정
저는 설정 안 했습니다.
Origin Shield는 CloudFront 엣지 로케이션과 오리진(S3) 사이에 추가적인 캐시 계층을 제공하는데 이를 통해 캐시 적중률을 높이고 같은 콘텐츠에 대한 중복 요청을 오리진으로 보내는 것을 줄일 수 있는 기능입니다.
뜬금없지만 우선 CloudFront가 사용자에게 어떻게 콘텐츠를 전달하는지 알아보고 이 기능에 대해 더 설명하겠습니다.
CloudFront의 콘텐츠 전달 과정
How CloudFront delivers content - Amazon CloudFron 해당 문서를 확인하면 더 자세한 내용을 볼 수 있습니다.
- 사용자가 콘텐츠를 요청하면 DNS는 해당 요청을 사용자와 가장 가까운 CloudFront 엣지 로케이션으로 라우팅합니다.
- 여기서 엣지 로케이션은 사용자와 가까운 곳에 위치한 캐시 서버입니다.
- 엣지 로케이션에서 캐시된 콘텐츠가 있는지 확인합니다.
- 캐시가 있으면 해당 콘텐츠를 사용자에게 바로 전달합니다.
- 캐시가 없으면 엣지 로케이션은 리전 엣지 캐시 혹은 오리진(S3나 서버 등)으로 요청을 전달합니다. 이후 3번 서술.
- 리전 엣지 캐시는 엣지 로케이션에서 자주 사용되는 콘텐츠를 더 오래 유지하는 미드티어 캐시입니다. 콘텐츠가 리전 엣지 캐시에 있으면 그곳에서 콘텐츠를 가져와 엣지 로케이션에 전달하고 리전 엣지 캐시에도 없으면 오리진으로 요청을 보냅니다.
- 최종적으로 오리진에서 콘텐츠를 가져와 엣지 로케이션에 캐시하고 사용자에게 전달합니다.
다시 돌아와 Origin Shield
Origin Shield는 오리진 앞에 위치한 추가 캐시 계층입니다. 엣지 로케이션과 리전 엣지 캐시에서 콘텐츠를 찾지 못할 경우 요청이 오리진으로 직접 전달되지 않고 Origin Shield를 통해 처리됩니다.
Origin Shield의 캐시에 해당 콘텐츠가 있다면 오리진에 요청을 보내지 않고 Origin Shield에서 캐시된 콘텐츠를 제공합니다.
만약 Origin Shield에도 콘텐츠가 없다면 요청이 오리진으로 전달되는데 이런 방식을 통해 오리진으로 가는 중복된 요청을 피하게 됩니다.
또한 동일한 콘텐츠에 대해 여러 사용자 요청이 발생한다면 Origin Shield는 요청을 통합해 오리진으로 가는 중복된 요청을 최소화합니다. 이는 트래픽이 급증해 서버에 과부화가 발생할 상황을 방지할 수 있습니다.
Cache Hit Ratio
캐시 적중률은 캐시에 저장된 콘텐츠가 얼마나 자주 사용되는지를 나타내는 중요한 지표로 전체 요청 중에서 캐시된 콘텐츠로 요청을 처리한 비율을 의미합니다.
보통 캐시 적중률은 캐시에서 요청을 처리한 횟수 / 전체 요청 횟수 * 100으로 계산되는데 쉽게 사용자가 100번의 요청을 보냈는데 캐시에 저장된 콘텐츠로 처리한 횟수가 80번이다? 그럼 캐시 적중률은 **80%**입니다.
그래서 쓰면 무조건 좋냐?
무조건 쓰는 것 보다는 프로젝트의 특성과 필요에 따라 사용하면 되지 않을까요? 제가 생각했을 때 필요한 상황들은 다음과 같았습니다.
1. 많은 사용자들이 동일한 콘텐츠를 요청하는 경우
캐시 가능성이 높은 정적인 HTML 파일 혹은 이미지, 동영상 등을 제공하며 여러 사용자가 동일한 요청을 자주 보낸다면 Origin Shield를 사용해 요청을 최소화할 수 있겠습니다..
2. 오리진의 부하를 줄여야하는 경우
위와 비슷한 이유인데 오리진(S3, 서버 등)에 심각한 부하가 걸리거나 처리 용량에 한계가 있을 때 사용하면 좋을듯 합니다.
동시에 수많은 사용자가 접근할 가능성이 큰 이벤트 페이지에 Origin Shield를 사용한다면 부하를 줄일 수 있겠습니다..
3. 오리진이 AWS 외부에 있을 때
이건 위에서 설명하지 않았는데 Origin Shield는 오리진 서버가 AWS에 위치하지 않고 온프레미스나 다른 클라우드 호스팅 서비스에 위치해도 사용이 가능합니다.
Origin Shield는 해당 오리진과 가장 가까운 AWS 리전을 선택해 트래픽 처리가 가능합니다.
하지만.. 제가 현재 진행 중인 프로젝트는 위의 상황과는 거리가 멀어 굳이? 싶어서 사용 안 했습니다. 그냥 무조건 좋다고 쓰기 보다는 뭔지 알아보고 아니면 안 써도 되니까요..
CloudFront와 도메인 연결하기
이제 위에서 생성한 Route53 호스팅 영역과 인증서를 써먹어봅시다.
아 유의할 점으로 인증서는 US East(N. Virginia) Region(us-east-1)에 있어야 하니 혹시라도 ap-northeast-2로 생성하셨다면 다시 만드셔야 됩니다..
나머지 설정
Redirect HTTP to HTTPS
캐시 키 및 원본 요청
이쪽 설정은 필요한 경우 따로 설정하시면 됩니다.
WAF
버거풋에 지속적으로 20개 정도의 이상한 서버들이 크롤링하려고 들어오는건지 자꾸 로그가 남아서 이거 써서 방화벽 설정해보려고 했는데 돈 내놓으라고 해서 설정 안 했습니다.
루트 객체 설정
이제 이후에 배포 생성을 클릭해 완료합니다.
버킷 정책 편집
아까 위에서 S3 버킷 퍼블릭 액세스를 모두 차단했죠?? CloudFront가 접근하기 위해 버킷 정책을 변경해줘야합니다.
배포에 성공하면 아주 친절하게 정책 복사해서 따라오라고 해줍니다.
이렇게 붙여넣고 변경 사항을 저장하면 끝입니다!
마지막으로 Route53
이제 마지막으로 Route53에 들어와서 A 레코드를 생성해줍시다.
단순 라우팅 설정에서 단순 레코드 정의를 눌러 아래와 같이 배포된 CloudFront를 연결해주면 이제 정말 끝입니다!!!
이후 레코드 정의를 누른 후 설정을 완료하면 끝입니다.
만약 새로고침했는데 에러 나면
우리 리액트는 Single Page Application.. 처음에 텅 빈 index.html을 로드하고 자바스크립트로 처리됩니다.
페이지가 전환될 때 react-router-dom은 서버에서 해당 엔드포인트에서 리소스를 새로 받는 것이 아닌 처음에 로드된 자바스크립트를 통해 각 URL에 맞는 컴포넌트를 렌더링하죠..?
근데 새로고침하면 브라우저는 서버로 새로운 요청을 보내고 해당 URL에 맞는 리소스를 찾아서 보내주려고 시도할 겁니다.
그럼 당연히 다른 리소스에 접근하려다 실패하고 에러를 내뿜어주십니다.
403 에러 코드에 대해 200 코드와 함께 index.html을 반환하도록 설정해줍시다.
마치며..
프리티어 기간이 2년이면 좋겠습니다.
.
.
.
뿅..
아.. 계정 옮겨서 github secrets 값도 바꿔야 됩니다..