[etc] PT.3 - React 프로젝트 이미지 성능 최적화 시키기 (AWS lambda@edge, cloudfront, S3 설정하기)

김채운·2023년 10월 29일
2

etc.

목록 보기
4/9

🖥️ IAM 정책 및 역할 생성

AWS Identity and Access Management(IAM)은 AWS 리소스에 대한 액세스를 안전하게 제어할 수 있는 웹 서비스이다. IAM을 사용하면 사용자가 액세스할 수 있는 AWS 리소스를 제어하는 권한을 중앙에서 관리할 수 있다. IAM을 사용하여 리소스를 사용하도록 인증(로그인) 및 권한 부여(권한 있음)된 대상을 제어한다.

✔️ IAM 정책 생성하기.

  1. AWS IAM 콘솔로 이동해 왼쪽 사이드바에서 액세스 관리 -> 정책으로 이동

  2. "정책 생성" 클릭

  3. JSON 탭에서 밑에 내용대로 입력

  1. "다음:태그" 클릭

  2. 태그는 생략한다.

이렇게 하면 IAM 정책 생성이 완료된다. 그 다음으로는 역할을 생성해 준다.

✔️ IAM 역할 생성하기.

  1. AWS IAM 콘솔로 이동해 왼쪽 사이드바에서 "액세스 관리" -> "역할"로 이동

  2. "역할 만들기" 버튼을 클릭

  3. 신뢰할 수 있는 엔터티 유형에서 "AWS 서비스" 옵션을 선택한다.
    사용 사례에서는 "Lambda"를 선택하고 "다음" 버튼을 클릭한다.

  1. 권한 추가 화면에서 IAM 정책 생성하기에서 생성해준 정책을 선택한 후에 "다음" 버튼을 클릭한다.

  2. 역할 이름 "ResizingImageRole"로 설정하고, 1단계 신뢰할 수 있는 엔터티 아래와 같이 편집해서 작성


6. 오른쪽 아래 "역할 생성" 버튼 클릭해서 생성 완료하기

🖥️ S3 버킷 생성하기

  1. AWS S3 콘솔로 이동하고 "버킷 만들기" 버튼 클릭

  2. 버킷 이름을 입력하고 AWS 리전은 서울로 설정을 한다.

  3. 퍼블릭 액세스 차단

  • 외부에 S3을 공개할 경우 모든 퍼블릭 액세스 차단을 체크 해제하고, 공개하지 않는다면 체크를 해주면 된다. 퍼블릭 액세스를 차단할경우, IAM에서 AWSAccessKeyId, AWSSecretKey를 발급받고 키를 이용해서 S3 객체에 접근할 수 있다.

새 ACL(액세스 제어 목록)을 통해 부여된 버킷 및 객체에 대한 퍼블릭 액세스 차단

  • 지정된 ACL이 퍼블릭이거나, 엑세스 요청에 퍼블릭 ACL이 포함되어 있으면 차단 한다.

임의의 ACL(액세스 제어 목록)을 통해 부여된 버킷 및 객체에 대한 퍼블릭 액세스 차단

  • 퍼블릭 ACL를 포함하는 PUT 요청은 허용 한다. (모든 퍼블릭 ACL과 포함된 모든 Object를 무시)

새 퍼블릭 버킷 또는 액세스 지점 정책을 통해 부여된 버킷 및 객체에 대한 퍼블릭 액세스 차단

  • 지정된 버킷 정책이 퍼블릭이면 액세스 차단한다.
  • 버킷 및 객체에 대한 퍼블릭 액세스를 차단하고 사용자가 버킷 정책을 관리할 수 있다.

임의의 퍼블릭 버킷 또는 액세스 지점 정책을 통해 부여된 버킷 및 객체에 대한 퍼블릭 액세스 차단

  • 퍼블릭 정책이 있는 버킷에 대한 액세스가 권한이 있는 사용자와 AWS 서비스로만 제한된다.
  1. 버저닝
  • 버저닝(Versioning)은 객체에 여러 버전을 관리할 수 있게 한다.
  • 객체에 버젼 아이디를 붙임으로서, 버전 관리를 사용하여 모든 객체의 버전을 보존, 검색 및 복원할 수 있다.
  • 버킷 버전 관리기능을 활성화하면 파일을 버전별로 관리 하기 때문에 비용이 더 들게 된다.
    대신 사용자가 실수로 파일을 삭제해도 복원할 수 있다. (지금은 비용의 문제로 비활성화 선택)
  • 버전을 사용하도록 설정한 후에는 비활성화 상태로 돌아갈 수는 없고, 일시 중지 설정은 가능하다.

  1. 태그는 따로 설정할 것이 없어 넘어가고, 기본 암호화는 default 옵션을 선택한다.

  1. 고급 설정은 넘어가고 "버킷 만들기"를 클릭하여 버킷을 생성한다.

  2. 생성한 버킷에 "dev", "prod" 폴더를 만들어준다.

🖥️ CloudFront 생성하고 배포하기.

  1. AWS CloudFront 콘솔로 이동한다.

  2. "배포 생성" 버튼을 클릭한다.

  3. CloudFront 생성 설정

  • 원본 도메인으로 아까 만든 버킷 선택
  • 이름은 자동 생성된다.
  • 원본 엑세스 제어 설정을 선택해서 CloudFront를 통해서만 내부 사진에 접근할 수 있도록 한다.

  1. Legacy ache settings로 설정해 주소창에서 쿼리를 설정해서 크기를 변경할 수 있도록 한다.
    (* w:넓이, h:높이, f:변경할 이미지 포맷, q:퀄리티)

  1. 로그를 관측하기 위해 표준 로깅을 켜주고 cloudfront가 로그 파일을 제공할 S3버킷을 설정한다.

  1. "배포 생성" 버튼을 클릭하고 cloudfront를 배포한다.

✔️ CloudFront 배포 후 S3 버킷 정책 수정

  1. 배포한 cloudfront를 클릭해서 상세페이지로 들어가 "원본"탭 클릭 -> 원본 선택한 후에 "편집" 클릭

  1. "정책 복사" 클릭 (CloudFront에서 S3로 액세스 할 수 있도록 정책을 복사하고 S3에 등록해야 한다. S3 액세스 권한을 아까 모두 해제했기 때문에.)

✔️ S3 버킷 정책 수정하기

  1. S3 콘솔로 이동해서 사이드 메뉴의 버킷에서 전에 만들어 뒀던 버킷 클릭한 다음 "권한"탭으로 이동, "버킷정책"에서 "편집" 클릭해서 정책을 붙여넣기 한다.

✔️ CloudFront 동작 생성하기

  1. CloudFront 콘솔의 사이드 메뉴에서 "배포"클릭 -> 내가 배포한 cloudfront 선택한 후 "동작 생성" 클릭

  2. ClounFront 배포할 때와 거의 동일하다 단, 아래와 같이 작성하고 "동작 생성"한다.
    ( dev/ 에 대한 동작을 생성하고 나면, prod/*에 대한 동작도 생성해준다.)



  1. S3 버킷 "dev"에 이미지를 업로드하고 CloudFront 주소로 S3 이미지에 접근해본다.
    ex) https://cloudfront_domain/dev/test.jpg

🖥️ Lambda 함수 생성

  1. AWS Lambda 콘솔에서 사이드바의 "함수"로 이동 -> "함수 생성" 클릭

  2. 아래와 같이 함수 이름은 "resize-image", 런타임 "Node.js 18x" 선택하고, "기본 실행 역할 변경"에서 "기존 역할"에서 이전에 생성한 역할 규칙 선택 사용


  1. "함수 생성" 클릭해서 생성 완료하기

  2. 생성된 함수 상세 화면에서 "구성" 탭 -> "일반 구성" 편집으로 제한 시간은 30초로 수정.
    (원하는 만큼 시간과 메모리를 수정하면 된다.)

🖥️ Cloud9 AWS IDE로 Lambda함수 작성 및 업로드

  1. AWS Cloud9 콘솔로 이동 -> "환경 생성" 클릭

  2. 이름을 설정해줌 (나머지는 모두 기본옵션)

  1. 마지막으로 "생성" 클릭해주면 Cloud9이 실행된다.
    밑에처럼 Cloud9 IDE에 "열림"을 클릭해도 Cloud9이 실행된다.

✔️ 생성된 Cloud9 인스턴스로 Lambda로 실행될 함수를 작성하기

  1. 왼쪽 사이드에 "aws"를 클릭 -> "Lambda"메뉴 클릭 -> "resize-image" 마우스 오른쪽으로 클릭 -> "Download"클릭

  1. 워크스페이스 폴더 선택은 기본으로 되어있는 resize-image를 선택한다. (* Cloud9은 EC2이다 그래서 Cloud9 서버에 다운로드할 건데 어느 디렉터리인지를 물어보는 것이다.)

  2. Cloud9 환경의 콘솔에 아래 명령어를 입력한다.

    cd resize-image
    npm init -y

  1. 이제 사이드 메뉴에 폴더모양을 클릭하면 resize-image 폴더에 node_modules,index.mjs,package.json등이 다운 받아진 걸 볼 수 있다.

  2. 아래 명령어로 sharp 라이브러리 설치 sharp 라이브러리가 이미지를 리사이징 해주는 아주 좋은 라이브러리입니다.

    npm i sharp

  3. index.mjs 파일에 아래 코드를 입력 후 저장 (* 12번 라인에는 자신이 생성한 S3버킷 이름으로 수정할 것)
'use strict';

import querystring from 'querystring'; // Don't install.
import AWS from 'aws-sdk'; // Don't install.
import Sharp from 'sharp';

const S3 = new AWS.S3({
  region: 'ap-northeast-2'
});
const BUCKET = 'openmarkte-resize-image';

export const handler = async (event, context, callback) => {
  const { request, response } = event.Records[0].cf;
  // Parameters are w, h, f, q and indicate width, height, format and quality.
  const params = querystring.parse(request.querystring);

  // Required width or height value.
  if (!params.w && !params.h) {
    return callback(null, response);
  }

  // Extract name and format.
  const { uri } = request;
  const [, imageName, extension] = uri.match(/\/?(.*)\.(.*)/);

  // Init variables
  let width;
  let height;
  let format;
  let quality; // Sharp는 이미지 포맷에 따라서 품질(quality)의 기본값이 다릅니다.
  let s3Object;
  let resizedImage;

  // Init sizes.
  width = parseInt(params.w, 10) ? parseInt(params.w, 10) : null;
  height = parseInt(params.h, 10) ? parseInt(params.h, 10) : null;

  
  // Init quality.
  if (parseInt(params.q, 10)) {
    quality = parseInt(params.q, 10);
  }

  // Init format.
  format = params.f ? params.f : extension;
  format = format === 'jpg' ? 'jpeg' : format;

  // For AWS CloudWatch.
  console.log(`parmas: ${JSON.stringify(params)}`); // Cannot convert object to primitive value.
  console.log(`name: ${imageName}.${extension}`); // Favicon error, if name is `favicon.ico`.

  try {
    s3Object = await S3.getObject({
      Bucket: BUCKET,
      Key: decodeURI(imageName + '.' + extension)
    }).promise();
  } catch (error) {
    console.log('S3.getObject: ', error);
    return callback(error);
  }

  try {
    resizedImage = await Sharp(s3Object.Body)
      .resize(width, height)
      .toFormat(format, {
        quality
      })
      .toBuffer();
  } catch (error) {
    console.log('Sharp: ', error);
    return callback(error);
  }

  const resizedImageByteLength = Buffer.byteLength(resizedImage, 'base64');
  console.log('byteLength: ', resizedImageByteLength);

  // `response.body`가 변경된 경우 1MB까지만 허용됩니다.
  if (resizedImageByteLength >= 1 * 1024 * 1024) {
    return callback(null, response);
  }

  response.status = 200;
  response.body = resizedImage.toString('base64');
  response.bodyEncoding = 'base64';
  response.headers['content-type'] = [
    {
      key: 'Content-Type',
      value: `image/${format}`
    }
  ];
  return callback(null, response);
};
  1. 왼쪽 사이드 메뉴 "aws"클릭 -> US East의 "Lambda"클릭 -> "resize-image" 오른쪽 클릭 "Upload Lambda" 클릭


8. Select Upload Type에서는 Directory를 선택.


9. Build directory에서는 "No"를 선택.


10. 그다음 "open" 클릭. 이렇게 하면 디렉토리가 있는 그대로 업로드 되고 배포 후 Lambda가 즉시 업데이트 된다.

Lambda@Edge 배포

Lambda 함수 작성 후 Upload Lambda를 하게 되면 알아서 최신 새 버전의 Lambda가 게시가 된다. (* Lambda 콘솔로 이동하여 "resize-image" 함수 상세 화면에 버전 탭을 통해서 최신 버전을 확인할 수 있음.)

  1. 위와 같은 resize-image 함수 상세 화면에서 "작업"버튼을 클릭해서 "Lambda@Edge배포"를 선택한다.

  2. 배포 설정을 해주는데, CloudFront event가 응답이기 때문에 Origin response로 설정을 해준다. (* Cache behavior는 입력하는 게 아닌 CloudFront에서 생성해준 동작을 선택하는 것이다 리스트가 안 뜬다면 CloudFront 동작 생성 부분이 이상한 것이다.)

  3. dev/, prod/ 둘 다 배포를 해주고 나면 resize-image 버전에 들어가서 해당 버전의 상세 화면에 아래와 같이 CloudFront 트리거가 추가된 것들 확인할 수 있다.

  4. 이제 "https://[clounfrontId].cloudfront.net/[이미지파일이름]?파라미터" 이미지 리사이징 할 주소를 호출해서 제대로 리사이징 되고 있는지 확인해 보면 된다.

    lambda@edge 함수 에러

    위의 과정대로 맞게 잘 설정을 했는데 503에러가 떠서 cloudwatch를 통해서 확인해 봤더니 index파일을 찾을 수 없다고 한다.

    이유는 resize-image2 함수를 빌드하고 나서 함수가 작동하는 handler의 경로 설정이 안 되어 있기 때문이다. handler가 작성되어 있는 파일은 index.mjs이고 이 파일은 resize-image2폴더 안에 있기 때문에 런타임 설정에서 핸들러 부분에 경로설정을 resize-image2/index.handler로 수정해야 된다. 수정 후에는 함수가 정상적으로 실행된다.

0개의 댓글