AWS Image Resizing 구현하기

이미지 리사이징 기능을 구현하기 위해 AWS 에서 S3Lambda 를 이용해서 이미지 리사이징을 거쳐 사용자에게 빠른 로딩 응답을 보장 할 수 있습니다.

AWS 에서 S3Lambda 를 이용해서 image-resizing 을 구현하기 위해 대표적으로 2가지 방법이 존재 합니다.

1. 최초 이미지 등록 시 원하는 크기로 image-resizing 을 해서 S3 에 저장 하는 방법

이 방법은 최초 이미지를 등록 할때 마다 image-resizing 해서 S3 에 저장 하는 방식 입니다.

자세하게 말하자면 특정 S3bucket 에 저장 하면 Lambda 에서 특정 트리거로 인해 image-resizing 작업을 거쳐 원본 이미지 데이터와 함께 resizing 된 이미지도 저장 하는 방식 입니다.

단점 으로는 개발 기간이 많이 필요 하고 기존 S3 에 원본 이미지 파일 저장 함께 resizing 된 이미지 파일도 저장 해야 합니다.

2. S3 + Lambda + CloudFront + API Gateway +Image Handler (서버리스 방식)

ImageResizingArchitecture

두번째 방식은 기존에 저장되어 있던 원본 이미지 파일을 유지 하되 프론트 환경에서 이미지를 불러 올때 마다 먼저 image resizing 전용 Cloud Front 서버로 호출하고 해당 캐시된 리사이징 이미지가 존재 하지 않으면 API gateway 로 호출을 해서 image-resizing 작업(Lambda 함수 호출) 이 발생 하는 Lambda 서버로 트리거 됩니다. 이때 이미지는 리사이징 되면서 리사이징 된 이미지는 S3 에 저장하고 응답 하는 방식 입니다.

  1. 원하는 해상도 포함해서 이미지를 요청 (Cloud Front 서버에 호출 한다.) ex) https://{아이디값}.cloudfront.net/test2.png?w=200&h=200
  2. CloudFront 서버에서 캐싱된 이미지가 있으면 바로 응답하지만 만약 존재하지 않으면 API gateway서버에 호출 합니다.
  3. API gateway 서버에서는 S3 에 원본 이미지 파일을 가져와서 resizing 작업을 거친 후 최종적으로 사용자에게 응답을 하게 됩니다.
  4. Lambda 서버에서는 요청 받는 이미지를 resizing 하게 되고 resizing 한 이미지는 S3 에 저장을 하게 됩니다. 동시에 사용자에게 응답을 하게 됩니다.

AWS 구축 방법

S3 버킷 생성

S3 버킷 생성
S3 버킷 생성
S3 콘솔 에서 bucket 을 생성 하도록 합니다.

생성한 bucket 에 이동하고 권한버킷 정책 으로 이동 해서 버킷 정책을 변경 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::${버킷 아이디값}/"
}
]
}

IAM 생성

IAM-Policy-Create1.png

AWS IAM 콘솔 접속 해서 왼쪽 메뉴에 정책 우측 상단에 정책 생성 을 클릭 합니다.

IAM-Policy-Create2.png

JSON 탭 선택 후 다음과 같이 입력 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"lambda:GetFunction",
"lambda:EnableReplication*",
"cloudfront:UpdateDistribution",
"s3:GetObject",
"s3:PutObject",
"logs:PutLogEvents"
],
"Resource": "*"
}
]
}

IAM-Role-Create1.png

이번에는 역할을 생성 해야 합니다. AWS IAM 콘솔 접속 해서 왼쪽 메뉴에 역할 우측 상단에 역할 생성 을 클릭 합니다.

IAM-Role-Create2.png

신뢰할 수 있는 엔티티 유형 에서 AWS 서비스 를 선택하고 사용 사례Lambda 로 선택 하도록 합니다.

IAM-Role-Create3.png

이전에 생성했던 정책 명을 검색해서 선택 후 다음을 눌러서 역할을 생성 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
const AWS = require('aws-sdk');
const sharp = require('sharp');

const s3 = new AWS.S3({ region: 'ap-northeast-2' });
const SOURCE_BUCKET = '${원본 이미지 저장 된 버킷명}';
const CACHE_BUCKET = '${저장 하고자 하는 리사이징 된 이미지 버킷명}';
const CACHE_PREFIX = 'resized/';

exports.handler = async (event) => {
try {
const path = event.pathParameters?.proxy;
const query = event.queryStringParameters || {};
const width = parseInt(query.w, 10);
const height = parseInt(query.h, 10);

if (!path || (!width && !height)) {
console.error("Invalid path or dimensions:", path, width, height);
return {
statusCode: 400,
body: 'Invalid image path or dimensions.'
};
}

const dotIndex = path.lastIndexOf('.');
if (dotIndex === -1) {
console.error("Invalid file path (no extension):", path);
return {
statusCode: 400,
body: 'Invalid file path.'
};
}

const filename = path.substring(0, dotIndex);
const ext = path.substring(dotIndex + 1).toLowerCase();
const format = ext === 'jpg' ? 'jpeg' : ext;
const supportedFormats = ['jpeg', 'png', 'webp', 'avif'];

if (!supportedFormats.includes(format)) {
return {
statusCode: 415,
body: `Unsupported format: ${format}`
};
}

const sourceKey = decodeURIComponent(path);
const resizedKey = `${CACHE_PREFIX}${filename}-${width || ''}x${height || ''}.${format}`;

console.log("event:", JSON.stringify(event, null, 2));
console.log("Query:", query);
console.log("Filename:", filename, "Ext:", ext, "Format:", format);
console.log("Resized S3 Key:", resizedKey);

// 1. 원본 이미지 가져오기
const sourceImage = await s3.getObject({
Bucket: SOURCE_BUCKET,
Key: sourceKey
}).promise();

// 2. 리사이징
const resizedImage = await sharp(sourceImage.Body)
.resize(width || null, height || null)
.toFormat(format)
.toBuffer();

// 3. 캐시 저장
await s3.putObject({
Bucket: CACHE_BUCKET,
Key: resizedKey,
Body: resizedImage,
ContentType: `image/${format}`,
CacheControl: 'max-age=31536000'
}).promise();

return {
statusCode: 200,
headers: {
'Content-Type': `image/${format}`,
'Cache-Control': 'max-age=31536000'
},
isBase64Encoded: true,
body: resizedImage.toString('base64')
};

} catch (err) {
console.error('Resize Error:', err);
return {
statusCode: 500,
body: `Image processing failed: ${err.message}`
};
}
};

index.js 파일에 해당 로직을 추가 하도록 합니다. 간단하게 말하자면 sharp 라이브러리를 이용해서 S3 에 저장 된 원본 이미지를 가져와서 Resizing 처리 하고 이후 S3 에 Resizing 된 이미지 저장과 동시에 응답 하는 로직 입니다.

Lambda 생성

LambdaCreate.png

Lambda 콘솔에 접속 해서 함수 생성 버튼을 클릭 합니다.

LambdaZipUpload.png

코드 소스를 업로드를 해야 합니다. 이미지 resizing 을 하기 위해서는 Node.js에서 제공하는 Sharp 라이브러리를 사용 할 예정입니다. 고성능 이미지 처리를 위해 널리 사용되는 라이브러리 이고 이미지 크기 조정, 자르기, 회전, 형식 변환 등 여러가지 기능을 제공 합니다.

우선 Amazon Linux OS 환경에서 Node 를 설치를 하고 npm install sharp aws-sdk 명령어로 라이브러리를 설치 하도록 합니다. 그럼

  1. node_modules
  2. package.json
  3. package-lock.json

파일이 생성 되는데 모두 zip 파일로 압축 해서 업로드를 하도록 합니다.

LambdaSetting1.png

아래 메뉴 탭에서 구성 버튼 클릭 후 왼쪽 메뉴 일반 구성 버튼을 클릭 합니다. 여기서 우측에 편집 버튼을 클릭 합니다.

LambdaSetting2.png

원활한 이미지 resizing 하기 위해서는 적어도 메모리 512 MB 필요 합니다. 그리고 제한 시간을 15초로 설정 합니다. 이전에 역할 에서 생성한 IAM 역할을 선택 후 저장 버튼을 클릭 합니다.

API Gateway 생성

GateWayCreate.png

AWS API gatway 콘솔에 이동해서 API 생성 하도록 합니다. 여기서 HTTP API 를 선택 및 구축 하도록 합니다.

GateWayAPICreate.png

API 이름을 지정하고 모두 default 로 최종 생성 합니다.

APIGateway-RoutesCreate.png

생성한 API 접근 후 Routers 버튼 클릭 경로 생성 버튼을 클릭 합니다.

APIGateway-RootCreate.png

모든 경로에 대해 이미지 요청을 받을 수 있도록 설정 할 수 있도록 /{proxy+} 입력 후 생성 버튼을 클릭 하도록 합니다.

APIGateway-IntegrationsCreate3.png

그 다음은 왼쪽 메뉴에서 Integrations 클릭 후 상단 탭 통합을 경로에 연결통합을 경로 및 관리 버튼을 클릭 합니다.

APIGateway-IntegrationsCreate4.png

통합 유형 을 Lambda 함수로 선택 후 통합 대상 을 선택 해야 하는데 이미 생성한 Lambda 로 지정 후 생성 버튼을 클릭 하도록 합니다.

APIGateway-Integrations.png

정상적으로 Lambda 로 통합 설정 완료 되었습니다.

APIGateway-StagesCreate.png

그 다음은 왼쪽 메뉴에서 Stages 클릭 후 상단 탭 생성버튼을 클릭 합니다.

APIGateway-CreateStage.png

이름을 prod 지정 후 생성 버튼을 클릭 합니다.

APIGateway-StagesCreate2.png

스테이지를 생성한 prod 로 선택 후 배포 버튼을 클릭 합니다.

APIGateway-StagesCreate3.png

배포 버튼을 클릭 합니다.

Cloud Front 생성

CloudFront-Create1.png

AWS Cloud Front 콘솔에 이동해서 배포 생성 버튼을 클릭 하도록 합니다.

CloudFront-Create2.png

Distribution name 지정 하고 Next 버튼을 클릭 합니다.

CloudFront-Origin-Create1.png

Origin 설정 해야 합니다. API Gateway 를 선택 하도록 합니다. 그런 다음 Browse APIs 버튼을 클릭 합니다.

CloudFront-Origin-Create2.png

적절한 Region 을 선택 하고 APIGATEWAY VersionHTTP API 로 선택 후 이전에 생성 했던 API Gateway 를 선택 하도록 합니다.

CloudFront-Origin-Create3.png

Origin path - optional 입력 박스에서 /prod 로 입력 후 Next 버튼을 클립 합니다.

CloudFront-behavior-Create1.png

생성한 Cloud Front 선택 후 상단 탭에 동작 버튼을 클릭 후 동작 생성 버튼을 클릭 하도록 합니다.

CloudFront-behavior-Create2.png

경로 패턴 을 * 로 입력하고 원본 및 원본 그룹 을 이전에 생성한 API Gateway 로 선택 합니다.

CloudFront-behavior-Create3.png

캐시 정책에서 Create cache policy 클릭 하도록 합니다.

CloudFront-behavior-Create4.png

적절한 cache policy의 이름 지정하고 Cache TTL 을 1년 으로 지정 합시다. 그리고 쿼리 문자열모두 로 선택 후 저장 합니다.

CloudFront-behavior-Create5.png

캐시 정책 을 방금 생성 한 캐시 정책으로 선택 하고 원본 요청 정책Create origin request policy 버튼을 클릭 합시다.

CloudFront-behavior-Create6.png

적절한 origin request policy 이름을 지정하고 쿼리 문자열모두 로 선택 후 생성 버튼을 클릭 하도록 합니다.

CloudFront-behavior-Create7.png

생선한 원본 요청 정책 을 선택 하고 마지막으로 Create behavior 버튼을 클릭 합니다.

실험 해보기

사용자 1 가정 이미지 파일 조회
사용자 1 가정 이미지 파일 조회
우선 크롬 브라우저 통해 https://${아이디값}.cloudfront.net/test9.jpg?w=200&h=200 접근 하면 CloudFront -> API Gateway (HTTP API) -> Lambda (리사이징 처리) 이렇게 처리가 됩니다.

x-cache 값이 Miss from cloudfront 라고 나오는데 CloudFront 에서 캐시 없다는 의미 입니다. 속도는 1.09s 발생 하였습니다.

사용자 2 가정 이미지 파일 조회
사용자 2 가정 이미지 파일 조회

다른 브라우저 통해 https://${아이디값}.cloudfront.net/test9.jpg?w=200&h=200 접근 하면 CloudFront(캐싱됨) 캐싱된 이미지가 있어서 빠르게 응답 할 수 있었습니다.

x-cache 값이 Hit from cloudfront 라고 나오는데 CloudFront 에서 캐시 있다는 의미 입니다. 속도는 70ms 로 이전 보다 빠르게 이미지 데이터를 가져 올 수 있었습니다.


Copyright 201- syh8088. 무단 전재 및 재배포 금지. 출처 표기 시 인용 가능.

버튼

×

喜欢就点赞,疼爱就打赏