serverless 프레임워크를 통해 lambda를 배포하자
serverless 설치
npm i serverless -g
명령어로 시작
serverless
docs 에는 starter 로 시작하라고 하는데 최근 업데이트를 해서 선택지가 없다. (docs는 업데이트가 안됐다..)

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가 잘 연결되어있다

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

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

아래 로그는 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' 카테고리의 다른 글
AWS cloudfront & lambda@edge 로 이미지 리사이징 -1 (0) | 2024.08.22 |
---|