serverless 프레임워크를 통해 lambda를 배포하자

 

serverless 설치

npm i serverless -g

 

명령어로 시작

serverless

 

docs 에는 starter 로 시작하라고 하는데 최근 업데이트를 해서 선택지가 없다. (docs는 업데이트가 안됐다..)

Simple Function을 선택하자

 

AWS 자격 증명 설정

여기 중요하다. 처음에 Easy & Recommended로 만들었는데 한번 만들면 serverless 에 등록이 되어버려 삭제하고 다시 Iam을 등록해줘야된다. 최근 업데이트 된거라 docs에도 삭제관련 내용이 없어서 한참 해맸다.. 우린 이미 만들어준 iam 을 연결해야되니 Easy & Recommended 로 만들어주는 iam은 필요가없다.

2번째 Save AWS Credentials을 선택해준다.

만약 1번을 선택했다면 다음 과정을 따라해보자.

serverless login 후에 나오는 serverless app 에서 등록되어버린 iam을 직접 삭제해준다.

저 provider 부분은 lambda 배포할때 필요한거긴 한데 저게있으면 serverless 가 AWS 자격 증명 설정 선택지를 안준다..

그래서 저거 삭제하고 2번으로 선택한 후 저 provider iam을 만들어서 다시 넣어줬다

(업데이트 된지 얼마 안되서인지 docs에 관련 내용이 없어서 이부분은 계속 오류나면서 이것저것 해보다가 된거라 맞는 방법인지 확신은 없다)

 

플러그인 설치

npm install --save-dev serverless-lambda-edge-pre-existing-cloudfront

이미 있는 cloudfront에 lambda를 배포하려면 추가 작업이 필요하다. (관련 docs)

docs에 나온대로 yml 파일도 작성해주고

// serverless.yml

functions:
  hello:
    handler: index.hello // index 파일의 hello 함수 배포
    events:
      - preExistingCloudFront:
          # ---- Mandatory Properties -----
          distributionId: ID # CloudFront distribution ID you want to associate
          eventType: origin-response # Choose event to trigger your Lambda function, which are `viewer-request`, `origin-request`, `origin-response` or `viewer-response`
          pathPattern: "*" # Specifying the CloudFront behavior
          includeBody: false # Whether including body or not within request
          # ---- Optional Property -----
          # stage: dev # Specify the stage at which you want this CloudFront distribution to be updated
plugins:
  - serverless-lambda-edge-pre-existing-cloudfront

 

추가로 1탄에서 만든 iam 을 입력해준다

// serverless.yml

provider:
  name: aws
  runtime: nodejs20.x
  iam:
    role:
      arn:aws:iam::[YOUR IAM ARN]

 

 

이제 적당한 예시 함수를 배포해보자

// index.js

export const hello = async (event) => {
  console.log(JSON.stringify(event, null, 2));
  return event;
};

 

배포 명령어 입력

serverless depoly

 

배포가 성공하면 버지니아 북부 > Lambda > 함수 에서 확인할 수 있다. 

 

최신 버전으로 들어가면

 

트리거 Cloudfront가 잘 연결되어있다 

 

테스트 코드도 잘 들어가있고

 

잘 되는지 테스트도 할수있다.

querystring 으로 선택

 

아래 로그는 cloudwatch 로 연결되는데 로그를 더 정확하게 보고싶을때 사용한다.

(무수한 에러의 요청에 많이 보게된다..)

 

test 함수가 잘 배포되었으니 실제 리사이징 코드를 작성하자.

"use strict";
import Sharp from "sharp";
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";

// S3 클라이언트 초기화
const S3 = new S3Client({
  region: "ap-northeast-2",
});

// 쿼리스트링에서 특정 키의 값을 가져오는 함수
const getQuerystring = (querystring, key) => {
  return new URLSearchParams("?" + querystring).get(key);
};

// 이미지 리사이즈 함수
export const imageResize2 = async (event, context) => {
  console.log("imageResize 실행!!");

  // 이벤트에서 요청 데이터 가져오기
  const { request, response } = event.Records[0].cf;

  // 쿼리스트링 가져오기
  const querystring = request.querystring;
  
  // 쿼리스트링이 없으면 원본 이미지 반환
  if (!querystring) {
    console.log("querystring is empty!! return origin");
    return response;
  }

  // URI 디코딩
  const uri = decodeURIComponent(request.uri);

  // 파일 확장자 추출
  const extension = uri.match(/(.*)\.(.*)/)[2].toLowerCase();
  console.log("extension", extension);

  // GIF 파일은 리사이징하지 않고 원본 반환
  if (extension === "gif") {
    console.log("extension is gif!! return origin");
    return response;
  }

  // 쿼리스트링 파싱
  const width = Number(getQuerystring(querystring, "w")) || null;
  const height = Number(getQuerystring(querystring, "h")) || null;
  const fit = getQuerystring(querystring, "f");
  const quality = Number(getQuerystring(querystring, "q")) || null;
  console.log({ width, height, fit, quality });

  // S3 버킷 이름 및 경로 추출
  const s3BucketDomainName = request.origin.s3.domainName;
  let s3BucketName = s3BucketDomainName.replace(
    ".s3.ap-northeast-2.amazonaws.com",
    ""
  ).replace(".s3.amazonaws.com", "");
  console.log("s3BucketName", s3BucketName);
  
  const s3Path = uri.substring(1);
  console.log("s3Path: ", s3Path);

  // S3에서 이미지 가져오기
  let s3Object = null;
  try {
    s3Object = await S3.send(
      new GetObjectCommand({
        Bucket: s3BucketName,
        Key: s3Path,
      })
    );
    console.log("S3 GetObject Success");
  } catch (err) {
    console.log("S3 GetObject Fail!!", err);
    return err;
  }

  // 이미지 리사이즈 수행
  const s3Uint8ArrayData = await s3Object.Body.transformToByteArray();

  let resizedImage = null;
  try {
    resizedImage = await Sharp(s3Uint8ArrayData)
      .resize({ width, height, fit })
      .toFormat(extension, { quality })
      .toBuffer();
    console.log("Sharp Resize Success");
  } catch (err) {
    console.log("Sharp Resize Fail!!", err);
    return err;
  }

  // 리사이징한 이미지 크기 확인
  const resizedImageByteLength = Buffer.byteLength(resizedImage, "base64");
  console.log("resizedImageByteLength:", resizedImageByteLength);

  // 리사이징한 이미지가 1MB 이상일 경우 원본 반환
  if (resizedImageByteLength >= 1048576) {
    console.log("resizedImageByteLength >= 1048576!! return origin");
    return response;
  }

  // 리사이징한 이미지 응답 설정
  response.status = 200;
  response.body = resizedImage.toString("base64");
  response.bodyEncoding = "base64";
  console.log("imageResize 종료!!");
  return response;
};

(코드 출처)

 

실제 쿼리스트링에 따라 lambda 함수가 실행되는지 확인해보자

Miss from cloudfront 인 경우( cloudfront 캐싱 전일 때)

 

위의 lambda에 배포한 리사이징 함수가 실행된다.

 

새로고침 후 Hit fron cloudfront ( cloudfront 캐싱됐을때 )

 

lambda 함수가 실행되지 않는다

 

s3, cloudfront, lambda 등등 aws 인프라 이용하면서 DevOps에 흥미가 생겼고 단순히 기능을 구현하는 건 정말 개발의 일부구나 생각했다. (그리고 aws 주식을 사고 싶어졌다.)

다음은 요새 뜨고있는 cloudflare 인프라도 이용해보고 싶은데 도메인 연결해서 cloudflare 이미지 리사이징을 해보는것도 재밌겠다.

 

출처 및 참고했던 블로그

aws docs

serverless docs

 

그대로 따라하기 정말 좋았던 연우리님 블로그

inpa 블로그

inpa 블로그-2 (개념)

올리브영 테크블로그

'aws' 카테고리의 다른 글

AWS cloudfront & lambda@edge 로 이미지 리사이징 -1  (0) 2024.08.22

+ Recent posts