인프라

CloudFront + Lambda(Edge) + S3 온디맨드 이미지 서버 만들기

JoJun's 2022. 11. 11. 14:59
728x90
반응형

이번 포스트는 CloudFront ( AWS CDN )과 Lambda 그리고 S3를 이용해서 온디맨드 이미지 서버를 만드는 과정을 기록하려고 한다.

 

온디맨드 이미지 서버란?

기존 이미지 서버에서는  이미지를 업로드하는 프로세스 중에 리사이징을 하고 각 사이즈의 이미지 파일도 저장을 하였다.

그 결과는 S3의 저장 증가와 새로운 사이즈의 타입이 추가되었을때 문제가 되었다.

 

그래서 이미지를 저장할때, 원본의 이미지를 바로 저장하고. GEt 요청할 때마다 원하는 타입의 사이즈로 리사이징 하여 이미지를 제공한다.

단, 매 요청마다 리사이징을 하게 되면 서버의 연산 과부화가 발생하기에 CloudFront와 같은 CND 캐싱 기능을 통해 최초 1회 리사이징 하여 응답한 이미지는 캐싱 처리한다.

 

순서는 아래와 같다.

 

1. IAM 정책 및 역할 생성

2. S3 생성

3. Lambda 함수 생성

4.Cloud9 AWS IDE로 Lambda함수 작성 및 업로드

5. Lambda함수 새 버전 게시 및 Edge배포(CloudFront)

 

1. IAM 정책 및 역할 생성

IAM이란?

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

1-1. 정책 생성

1. AWS IAM 콘솔에서 사이드 바 메뉴  "액세스 관리" - "정책" 메뉴로 이동.

2. 정책 생성

3. 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. "다음:태그" 버튼으로 다음 이동.

4. 태그는 생략.

5. 정책 이름 "ResizingImagePolicy"

6. 정책 생성.

1-2. 역할 생성

1. AWS IAM 콘솔에서 사이드 바 메뉴  "액세스 관리" - "역할" 메뉴로 이동.

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

3. 신뢰할 수 있는 엔터티 유형 "AWS 서비스" 선택, 사용 사례 Lambda 선택 후 "다음"

4. 권한 추가 화면에서 이전에 생성한 정책 선택 후 오른쪽 아래 "다음"

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

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": [
                    "edgelambda.amazonaws.com",
                    "lambda.amazonaws.com"
                ]
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

6. 오른쪽 아래 "역할 생성" 버튼 

 

2. S3 생성

1. AWS S3 콘솔에서 S3 버킷 만들기

2. 버켓 이름 입력

3. 리전은 서울!

4. 이 버킷의 퍼블릭 액세스 차단 설정 체크 다 풀기

5. 버킷 만들기.

6. 버킷에 "dev", "prod" 폴더 만들기

 

3. CloudFront 생성 및 동작 생성

3-1. CloudFront 배포

1. AWS CloudFront 콘솔로 이동

2. 배포 생성 아래와 같이 세팅

3. 배포 생성

4. 배포된 CloudFront 상세 화면에서 "원본" 탭 - 원본 선택 후 편집

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

6. S3 콘솔로 이동하여 버킷 상세 화면에서 "권한" 탭에 버킷 정책에 붙여 넣는다.

3-2. 동작 생성

1. CloudFront 배포한 상세 페이지에 동작 탭으로 이동하여 동작을 생성해준다.

2.  ClounFront 배포할 때와 정말 동일하다 단, 경로 패턴만 제외! 아래와 같이 작성하고 동작 생성한다.

3. dev/* 에 대한 동작을 생성하였으니 prod/*에 대한 동작도 생성해준다.!!

 

4. S3 버킷 "dev"에 이미지를 업로드하고  CloudFront 주소로 S3 이미지에 접근해본다. 

ex) https://cloudfront_domain/dev/test.jpg

 

4. Lambda 함수 생성

중요! 반드시 버지니아 북부(us-east-1)에서 생성할 것!! ( Lambda@Edge 가 버지니아 북부에서만 지원! )

1. AWS Lambda 콘솔로 이동 

2. 함수 생성

3. 새로 작성 박스 선택

4. 함수 이름 "resize-image"

5. 런타임 "Node.js 14.x" 선택

6. 기본 실행 역할 변경 - 기존 역할 사용 - 이전에 생성한 역할 규칙 선택

7. 함수 생성

8. 생성된 함수 상세 화면에서 "구성" 탭 - "일반 구성"  편집으로 제한 시간은 10초 변경

 

5.Cloud9 AWS IDE로 Lambda함수 작성 및 업로드

5-1. Cloud9 Ec2 인스턴스 생성

1. AWS Cloud9 콘솔로 이동

2. Create Environment 버튼으로 환경 생성

3. 이름 "resize-image"

4. 모두 기본으로 생성

5. 잠시 기다리면 Cloud9이 실행됩니다.

5-2. 생성된 Cloud9 인스턴스로 Lambda로 실행될 함수를 작성

1. 실행된 Cloud9 왼쪽 사이드 메뉴에 AWS 선택 - US East - Lambda - resize-image - 오른쪽 클릭 - 다운로드

2. 디렉터리 선택은 기본으로 resize-image

** Cloud9는 EC2이다 그래서 Cloud9 서버에 다운로드할 건데 어느 디렉터리인지를 물어보는 것

3. 사이드 메뉴 폴더 누르면 resize-image 폴더에 뭔가가 다운로드됨. ( 저는 node-modules, package.json 등 몇 가지 파일이 더 있습니다.  -> 4번 진행하면 됨. ) 

4. 아래 콘솔에 아래 명령어 입력  

cd resize-image
npm init -y

위 명령어 중 npm init -y 입력하시면 3번 절차와 같이 node_modules, package~ 등 파일이 생성됩니다.

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

npm i sharp

6. index.js 파일을 아래 소스로 수정 후 컨트롤 S로 저장 ( 주의!!!*** 10번 라인에는 자신이 생성한 S3 이름으로 수정할 것 )

'use strict';

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

const S3 = new AWS.S3({
  region: 'ap-northeast-2'
});
const BUCKET = 's3 name';

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 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);
};

7.  왼쪽 메뉴 AWS - US East - Lambda - resize-image 오른쪽 클릭 Upload Lambda 클릭

8. 이후에 나오는 팝업 전부 Yes 

 

6. Lambda함수 새 버전 게시 및 Edge배포(CloudFront)

Lambda 함수 작성 후 Upload Lambda를 하게 되면 알아서 최신 새 버전의 Lambda가 게시가 된다.

확인하는 방법은  Lambda 콘솔로 이동하여 "resize-image" 함수 상세 화면에 버전 탭이 있다.

1. Lambda@Edge 배포하기 resize-image 함수 상세화면에서 아래 이미지와 같이 작업- Lambda@Edge 배포를 한다.

주의할 점은 CloudFront event가 응답이라는 것이다.  추가로 Cache behavior는 입력하는 게 아닌 CloudFront에서 생성해준 동작을 선택하는 것이다 리스트가 안 뜬다면 CloudFront 동작 생성 부분이 이상한 것..

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

3. 이제 https://cloudfront_domain/dev/test.jpg? h=200&w=200을 호출해서 제대로 리사이징 되고 다시 호출했을 때 캐싱되었는지도 확인.!!

 

 

끝...

728x90
반응형