S3와 CloudFront

zunzero·2024년 3월 22일
0

AWS

목록 보기
6/7

작성 예정

발단

서버 비용 중, 데이터 처리량 관련 비용이 올해 초부터 급격히 늘었다고 공유를 받아, 이에 대한 처리가 필요했다.

원인

우리 서비스는 이미지를 S3에 보관하고, 클라이언트에서 이미지를 조회하고자 할 때, API에서 s3 signed url 을 직접 전달해, s3에 직접 전달하게 했다.

클라이언트 -> API 서버 이미지 조회 요청 (https://api.company.com/contents/{contentId}
API 서버 -> S3 (contentId: S3 object key) signed url 조회
클라이언트 <-> API 서버 <-> S3

위와 같은 방식으로 작업이 되어있었으니, S3 로의 접근이 빈번해 데이터 처리량 비용이 높게 나온 것이었다.

CloudFront 도입

CloudFront 는 CDN 의 일종으로, 정적 컨텐츠의 전송과 캐싱을 돕는다.
CloudFront 를 도입한 이유는 이미지 파일을 캐싱함으로써 데이터 처리 비용을 낮추기 위함이다.

클라이언트 <-> API 서버 <-> CloudFront <-> S3

위와 같이, API 서버와 S3 사이에 CloudFront 를 넣는 것이다.
CloudFront 를 통해 캐싱된 데이터를 가져오니, S3 로 직접 접근하는 횟수를 낮추게 되어, 데이처 처리 비용이 낮아진다.

CloudFront 설정

  • AWS CloudFront 페이지에서 배포 생성을 클릭해 초기 설정을 하자.
    origin domain 설정 부분은 우리가 컨텐츠를 가져올 곳을 설정하면 된다.
    나의 경우는 S3 버킷을 지정했다.
  • 원본 엑세스 OAC 지정
    만들어지지 않았다면 생성하자.
    대게 S3는 권한이 부여된 요청에만 응답하도록 설정되어 있을 것이다. (S3 관련한 자세한 사항은 이 글에서 생략.)
    때문에 S3와 CloudFront의 엑세스를 허용하기 위함이다.
  • 나머지는 기본 혹은 권장 설정으로 세팅해서 생성해보자.

위와 같이 CloudFront 배포가 하나 생성되었을 것이다.

S3 접근 허용 (from CloudFront)

S3는 대게 권한이 부여된 요청에만 응답하도록 설정되어 있으므로, CloudFront로부터의 접근을 S3에서 허용해주어야 한다.
이를 위해선 S3 버킷의 권한에 CloudFront 를 추가해야한다.

CloudFront 배포 > 원본 > 편집 란에 들어가자.

위와 같이 기존에 설정한 OAC를 확인할 수 있고, 설정하지 않았다면 위와 같이 설정하자!

정책 복사 버튼을 클릭해 정책을 복사하여 S3 버킷 권한으로 이동하자.
그리고 버킷 정책에 해당 JSON을 붙여넣고 저장하면 된다.

그럼 우리는 CloudFront 도메인을 통해 S3 객체를 확인할 수 있다!

이미지 확인

CloudFront 도메인을 활용해 이미지를 확인하는 방법은 아래와 같다.

https://{CloudFront_도메인주소}/{S3-object-key}

위 링크를 브라우저에 입력하면 이미지가 노출되는 것을 확인할 수 있다.

또한 잘 캐싱되고 있는지 확인하려면, 이미지 렌더링 화면의 네트워크를 확인했을 때, response headers 에 아래와 같이 Hit from cloudfront 라는 문구가 있는지 확인하면 된다.

문제점

기존에 S3로 이미지를 조회했을 때는, S3로부터 signed url을 받아왔기 때문에, 해당 url이 유효한 기간 범위가 있었다.
예를 들어, signed url 의 유효시간을 1시간으로 설정했다면, signed url이 외부에 노출되었을 때에도, 1시간 이후부터는 해당 이미지를 사용할 수 없게 된다.
만약 signed url 없이 S3 객체의 원본 url 을 사용해왔다면, 해당 url이 노출되어 외부의 누군가가 사용한다면, 그만큼의 비용을 우리가 지불하는 것이다!

지금 CloudFront 는 따로 signed url 을 반환하도록 설정이 되어있지 않다.
그러므로 https://{CloudFront_도메인주소}/{S3-object-key} 요 url이 외부로 노출된다면, 누군가 사용하는만큼의 비용을 우리가 지속적으로 지불해야 한다.

따라서 CloudFront가 signed url을 반환하도록 CloudFront 설정을 수정해야 한다.

CloudFront signed url 설정

CloudFront의 동작편집 화면에 들어가면 뷰어 엑세스 제한 탭에서 이를 설정할 수 있다.
기본으로는 No 로 설정이 되어 있을텐데, 우리는 이제 Yes 로 설정을 바꾸어 뷰어 접근을 제한해야 한다.

(키그룹에 관한 이야기는 바로 뒤에 이어서 진행한다.)

신뢰할 수 있는 특정 키 그룹을 통해서 CloudFront 의 요청하는 경우에만 콘텐츠를 조회할 수 있게 설정하는 것이다.

키페어 & 키그룹 만들기

참고: https://docs.aws.amazon.com/ko_kr/AmazonCloudFront/latest/DeveloperGuide/private-content-trusted-signers.html

우선 키페어를 만들어야 한다.
터미널에 아래 명령어 2개를 입력해, private key pem 과 public key pem 파일을 생성하자.

openssl genrsa -out private_key.pem 2048
openssl rsa -pubout -in private_key.pem -out public_key.pem

이후 CloudFront > 퍼블릭 키 탭으로 이동해 퍼블릭 키를 생성하자. 이름을 지정하고, public_key.pem 파일의 내용을 복사붙여넣기 하여 퍼블릭 키를 생성할 수 있다.

이후 CloudFront > 키 그룹 탭으로 이동해, 방금 만든 퍼블릭 키를 지정해 키 그룹을 만들자.

이를 통해 만든 키그룹을, 위에서 살펴본 뷰어 엑세스 제한 의 신뢰할 수 있는 키 그룹으로 설정하면 된다.

다시 이미지 조회 - 실패

방금과 같은 방법으로 이미지를 다시 조회해보자.

https://{CloudFront_도메인주소}/{S3-object-key}

이미지가 조회되지 않고, 위와 같은 에러 문구가 발생한다.
우리가 키 페어, private key 를 설정하지 않고, CloudFront 로 바로 접속했기 때문에 발생하는 문제이다.

이미지 조회 설정

다음은 Nest.js 코드이다.

import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as cloudfrontSigner from '@aws-sdk/cloudfront-signer';

@Injectable()
export class CloudfrontService {
  constructor(private readonly configService: ConfigService) {}

  async getPreSignedUrl(s3ObjectKey: string): Promise<string> {
    const distributionDomainName = this.configService.get(
      'cloudfront.distributionDomainName',
    );

    const url = `${distributionDomainName}/${s3ObjectKey}`;

    const privateKey = this.configService
      .get('cloudfront.privateKey')
      .replace(/\\n/g, '\n');

    const keyPairId = this.configService.get('cloudfront.publicKeyId');
    const dateLessThan = new Date(Date.now() + 1000 * 60 * 60).toUTCString(); // 1 hour

    return cloudfrontSigner.getSignedUrl({
      dateLessThan,
      url,
      keyPairId,
      privateKey,
    });
  }
}

각 설정들을 환경변수로 지정해두고, CloudFront 로 요청을 보낼 때, keyPairId(public key id, aws cloudfront 퍼블릭 키 탭에서 확인할 수 있다.) 와 private key를 함께 요청해야 한다.

우리는 이렇게 반환된 signed url 을 통해서만 이미지를 조회할 수 있다.

https://d3qo25611bcvyd.cloudfront.net/6442?Expires=1711337210&Key-Pair-Id=K1K7M7EPONZYO1&Signature=M5IPj7zx9A80RC4e1NscEozsQ-9K~giY0RA6et9zWBDUEOss6ttOOYs5bkT-VK~7LQJDYlTfUWirS9mkvckQf~SDh7mdtFOGx8CgBFl4DcIgNxPaJE264lA1D-WnXZNaapOS7lBSKgV2YImbNFDotI3RvRRaSOi4U6PBVVJQxliiVcpYK9~VBIK-8bcqloJMtj9~gSf~Jyavjb7GL-JtOj3Wk7c2sovtaooOEXCaE3Rh3yepK40~QGynequ2ujiizSB3BzdGKHZubHMFAcEZqdc~JIS92j7KgEfbao4KfLj04ckYD8WL6-kr3rjgENO3HAhcNVv2ab8foj~07Ne7Xg__

이런식의 signed url이 반환될 것이다.

profile
나만 읽을 수 있는 블로그

0개의 댓글