CloudFront Lambda@edge을 이용한CDN과 이미지 리사이징 제공

nawnoes·2021년 1월 12일
3

CloudFront Lambda@edge을 이용한CDN과 이미지 리사이징 제공

이전에 한번 구성하였던 기능이나, s3 버켓이 바뀌게 되면서 새로 구성할 필요성이 생기게 되었습니다. 다시 새로 구성하려고 하니 하나도 기억이 나지 않아 정리하여 남겨둡니다.

보다 자세한 절차는 참조 링크에 자세히 남겨져 있으니 참고하시면 됩니다.

이미지 리사이징

필요 AWS 서비스

  1. CloudFront
  2. Lambda
  3. S3
  4. IAM

1. S3 버킷 생성

CloudFront를 연결하기 위한 버킷 생성합니다. 버킷 내의 디렉토리 구조는 /dev,/pro로 만들어 개발과 운영 환경을 구분해 테스트를 자유롭게 하고자 합니다.

2. CloudFront 생성

① CloudFront 콘솔에 접속

② Create Distribution 클릭 > Web에서 Get Started

③ 사진 항목에 맞춰서 옵션 설정하기

Origin Settings
  • Origin Domain Name: 생성한 버킷 이름 입력
  • Restrict Bucket Access: 버킷의 접근 제한을 활성화 Yes
  • Origin Access Identity: S3의 접근 권한을 얻기 위한 새로운 Identity 생성
  • Grant Read Permissions on Bucket: 선택한 S3 의 버킷 정책에 읽기 권한을 자동으로 생성
Default Cache Behavior Settings
  • Cache and origin request settingsUse legacy cache settings 으로 설정
  • 리소스 자동 압축기능 활성화를 위해 Compress Objects Automatically Yes로 설정.
Edit Distribution

현재는 cloudfront에서 임시로 발급 받은 URL을 사용하지만 추후에 SSL인증서 등록 후 개인적으로 원하는 URL을 등록할수 있습니다.

생성

생성을 완료하면 CloudFront Distributions메뉴에서 새로 생성된 것을 확인할 수 있습니다.

④ CloudFront Cache Behavior 추가

prodev 디렉토리에 대해 앞에서 진행한 설정과 동일하게 맞춰서 behavior들을 추가합니다.

생성 후 패턴 우선순위도 변경

⑤ CloudFront 적용 확인

  • /pro, /dev, / 경로 각각에 sunglasses_smile.png를 올려둔다.
  • CloudFront Distributions에서 확인 되는 Domain Name 뒤에 sunglasses_smile.png을 호출하여 적용 되는지 확인

3. IAM 역할 생성

람다 함수가 여러 서비스에 접근 가능하도록 IAM 역할을 조합하여 정책을 생성합니다.

① 역할 만들기 내에 정책생성

  • [역할] -> [역할 만들기] 클릭
  • 사용사례에서 Lambda 선택 후 다음 클릭
  • 좌측상단에 [정책생성] 클릭 -> Json탭 클릭 -> 아래의 JSON 복붙
{
  "Version": "2012-10-17",
  "Statement": [
      {
          "Sid": "VisualEditor0",
          "Effect": "Allow",
          "Action": [
              "iam:CreateServiceLinkedRole",
              "lambda:GetFunction",
              "lambda:EnableReplication",
              "cloudfront:UpdateDistribution",
              "s3:GetObject",
              "logs:CreateLogGroup",
              "logs:CreateLogStream",
              "logs:PutLogEvents",
              "logs:DescribeLogStreams"
          ],
          "Resource": "*"
        }
    ]
}

  • 완료후 정책 검토 클릭
  • 필요한 정보 기입 및 요약에 허용되는 서비스들 목록 확인 후 정책 생성 완료.

② 역할 만들기

  • 다시 역할 만들기를 진행
  • 앞서 생성한 정책을 검색 및 선택후 다음
  • 3단계 건너뛰기
  • 검토 탭에서 역할 이름 지정 후 역할 만들기 완료

③ 신뢰관계 추가

  • 만들어진 역할을 클릭해서 신뢰관계 탭으로 이동 및 신뢰관계 편집
  • Service 부분에 "edgelambda.amazonaws.com" 추가 후 업데이트

4. Lambda 함수 생성

  • AWS 람다 콘솔에 접속
  • Lambda@edge의 배포를 위해서는 버지니아 리전에 생성합니다.
  • [함수] -> [함수 생성] 클릭
  • [함수이름] 기입
  • 런타임 Node.js 10.x
  • [권한] -> [기본 실행 역할 변경] -> [기존역할] 에서 앞서 만들었던 역할을 추가
  • 함수 생성후 생성한 함수에 들어와 [기본 설정 편집]. 메모리 128MB에 제한시간 5초로 설정

5. Cloud9 으로 Lambda 함수 작성하기

  • Cloud9는 클라우드에서 코드 작성을 위한 코드 편집기. 즉,IDE 입니다.
  • 서울리전을 지원하므로 서울 리전으로 진행합니다.
  • 아래와 같이 설정후 환경 생성
  • 몇분이 흐르면 아래와 같이 IDE 창을 확인 할 수 있습니다.
  • AWS:Explorer에서 불러오기를 통해 버지니아 리전에 있는 람다 함수를 불러오고
  • 람다 함수를 Import 하면 아래와 같이코드를 입력하는 화면이 나오게 됩니다.
  • 이미지 리사이징 패키지인 Sharp 패키지를 설치
ec2-user:~/environment $ cd ResizeImage
ec2-user:~/environment/ResizeImage $ npm init -y
...
ec2-user:~/environment/ResizeImage $ npm install sharp 
ec2-user:~/environment/ResizeImage $ npm install aws-sdk 
  • 아래와 이미지 리사이징 코드 복사
'use strict';

const querystring = require('querystring'); // Don't install.
const AWS = require('aws-sdk'); // Don't install.

// http://sharp.pixelplumbing.com/en/stable/api-resize/
const Sharp = require('sharp');

const S3 = new AWS.S3({
  signatureVersion: 'v4',
  region: 'ap-northeast-2'  // 버킷을 생성한 리전 입력
});

const BUCKET = '[mybucket]' // Input your bucket

// Image types that can be handled by Sharp
const supportImageTypes = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'tiff'];

exports.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 { uri } = request;
  const ObjectKey = decodeURIComponent(uri).substring(1);
  const params = querystring.parse(request.querystring);
  const { w, h, q, f } = params
  
  /**
   * ex) https://dilgv5hokpawv.cloudfront.net/dev/thumbnail.png?w=200&h=150&f=webp&q=90
   * - ObjectKey: 'dev/thumbnail.png'
   * - w: '200'
   * - h: '150'
   * - f: 'webp'
   * - q: '90'
   */
  
  // 크기 조절이 없는 경우 원본 반환.
  if (!(w || h)) {
    return callback(null, response);
  }

  
  const extension = uri.match(/\/?(.*)\.(.*)/)[2].toLowerCase();
  const width = parseInt(w, 10) || null;
  const height = parseInt(h, 10) || null;
  const quality = parseInt(q, 10) || 100; // Sharp는 이미지 포맷에 따라서 품질(quality)의 기본값이 다릅니다.
  let format = (f || extension).toLowerCase();
  let s3Object;
  let resizedImage;

  // 포맷 변환이 없는 GIF 포맷 요청은 원본 반환.
  if (extension === 'gif' && !f) {
    return callback(null, response);
  }

  // Init format.
  format = format === 'jpg' ? 'jpeg' : format;

  if (!supportImageTypes.some(type => type === extension )) {
    responseHandler(
      403,
      'Forbidden',
      'Unsupported image type', [{
        key: 'Content-Type',
        value: 'text/plain'
      }],
    );

    return callback(null, response);
  }


  // Verify For AWS CloudWatch.
  console.log(`parmas: ${JSON.stringify(params)}`); // Cannot convert object to primitive value.\
  console.log('S3 Object key:', ObjectKey)

  try {
    s3Object = await S3.getObject({
      Bucket: BUCKET,
      Key: ObjectKey
    }).promise();

    console.log('S3 Object:', s3Object);
  }
  catch (error) {
    responseHandler(
      404,
      'Not Found',
      'The image does not exist.', [{ key: 'Content-Type', value: 'text/plain' }],
    );
    return callback(null, response);
  }

  try {
    resizedImage = await Sharp(s3Object.Body)
      .resize(width, height)
      .withMetadata()
      .toFormat(format, {
        quality
      })
      .toBuffer();
  }
  catch (error) {
    responseHandler(
      500,
      'Internal Server Error',
      'Fail to resize image.', [{
        key: 'Content-Type',
        value: 'text/plain'
      }],
    );
    return callback(null, response);
  }
  
  // 응답 이미지 용량이 1MB 이상일 경우 원본 반환.
  if (Buffer.byteLength(resizedImage, 'base64') >= 1048576) {
    return callback(null, response);
  }

  responseHandler(
    200,
    'OK',
    resizedImage.toString('base64'), [{
      key: 'Content-Type',
      value: `image/${format}`
    }],
    'base64'
  );

  /**
   * @summary response 객체 수정을 위한 wrapping 함수
   */
  function responseHandler(status, statusDescription, body, contentHeader, bodyEncoding) {
    response.status = status;
    response.statusDescription = statusDescription;
    response.body = body;
    response.headers['content-type'] = contentHeader;
    if (bodyEncoding) {
      response.bodyEncoding = bodyEncoding;
    }
  }
  
  console.log('Success resizing image');

  return callback(null, response);
};
  • cloud9에서 람다 업로드 후 람다 콘솔로 재접속하여, 업로드한 람다 함수 정보로 이동합니다.

  • 우측상단 > 작업 > Lambda@edge 배포 클릭

  • 캐시 동작은 개발환경의 테스트를 위해 dev/* 선택

  • CloudFront 이벤트는 오리진 응답(Origin response)을 선택

    origin 에서 리소스 존재 여부를 판단 후 존재하면, 함수를 실행시켜야 하기 때문

  • 배포 클릭

  • 이미지 변환 확인

  • pro/*, * 경로들도 CloudFront 트리거에서 동일하게 추가

  • 각 경로에서 이미지 변환이 잘 되는지 확인

References

1개의 댓글

comment-user-thumbnail
2023년 7월 14일

react native 안에서는 resizeing 이미지 어떻게 호출하시나여? 커맨드로 사용하시나요? 보안적인 측면을 고려했을 때 RN에서 어떻게 사용하면 좋을지 궁금합니다

답글 달기