썸네일(이미지 리사이징)을 사용하는 이유
원본 이미지는 종종 상세한 정보를 담고 있어 파일 크기가 크고, 불필요한 데이터를 주고받게 된다. 하지만 대부분의 경우에는 페이지에서 사용되는 이미지는 전체 원본 크기나 해상도를 필요로하지 않을 수 있다. 이에 따라 Lambda를 통해 썸네일 이미지를 생성하여 성능 개선, 데이터 효율성, 자원 절약 등 여러 이점을 챙겨보자.
build.gradle 작성
plugins {
id 'java'
id 'org.springframework.boot' version '3.1.9'
id 'io.spring.dependency-management' version '1.1.4'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '17'
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'com.amazonaws:aws-lambda-java-core:1.2.1'
implementation 'com.amazonaws:aws-lambda-java-events:3.11.0'
implementation 'com.amazonaws:aws-java-sdk-s3:1.12.183'
}
tasks.named('bootBuildImage') {
builder = 'paketobuildpacks/builder-jammy-base:latest'
}
tasks.named('test') {
useJUnitPlatform()
}
test {
exclude '**/*'
}
task buildZip(type: Zip) {
into('lib') {
from(jar)
from(configurations.runtimeClasspath)
}
}
build.dependsOn(buildZip)
핸들러 작성
package com.example.demo;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.LambdaLogger;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.S3Event;
import com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.GetObjectRequest;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.amazonaws.services.s3.model.S3Object;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.imageio.ImageIO;
public class Handler implements RequestHandler<S3Event, String> {
private static final float MAX_HEIGHT = 60.0F;
private final String JPG_TYPE = "jpg";
private final String JPG_MIME = "image/jpeg";
private final String JPEG_TYPE = "jpeg";
private final String JPEG_MIME = "image/jpeg";
private final String PNG_TYPE = "png";
private final String PNG_MIME = "image/png";
private final String GIF_TYPE = "gif";
private final String GIF_MIME = "image/gif";
public Handler() {
}
public String handleRequest(S3Event s3event, Context context) {
LambdaLogger logger = context.getLogger();
try {
Iterator var4 = s3event.getRecords().iterator();
if (var4.hasNext()) {
S3EventNotification.S3EventNotificationRecord record = (S3EventNotification.S3EventNotificationRecord)var4.next();
String srcBucket = record.getS3().getBucket().getName();
String key = record.getS3().getObject().getUrlDecodedKey();
String dstBucket = "버킷 이름";
logger.log("srcbucket" + srcBucket + " srcKey" + key);
Matcher matcher = Pattern.compile(".*\\.([^\\.]*)").matcher(key);
if (!matcher.matches()) {
logger.log("Unable to infer image type for key " + key);
return "";
} else {
String imageType = matcher.group(1);
if (!"jpg".equals(imageType) && !"jpeg".equals(imageType) && !"png".equals(imageType) && !"gif".equals(imageType)) {
logger.log("Skipping non-image " + key);
return "";
} else {
AmazonS3 s3Client = AmazonS3ClientBuilder.defaultClient();
S3Object s3Object = s3Client.getObject(new GetObjectRequest(srcBucket, key));
InputStream objectData = s3Object.getObjectContent();
BufferedImage srcImage = ImageIO.read(objectData);
int srcHeight = srcImage.getHeight();
int srcWidth = srcImage.getWidth();
int width = 400;
int height = 300;
BufferedImage resizedImage = new BufferedImage(width, height, 1);
Graphics2D g = resizedImage.createGraphics();
g.setPaint(Color.white);
g.fillRect(0, 0, width, height);
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.drawImage(srcImage, 0, 0, width, height, (ImageObserver)null);
g.dispose();
ByteArrayOutputStream os = new ByteArrayOutputStream();
ImageIO.write(resizedImage, imageType, os);
InputStream is = new ByteArrayInputStream(os.toByteArray());
ObjectMetadata meta = new ObjectMetadata();
meta.setContentLength((long)os.size());
if ("jpg".equals(imageType)) {
meta.setContentType("image/jpeg");
}
if ("jpeg".equals(imageType)) {
meta.setContentType("image/jpeg");
}
if ("png".equals(imageType)) {
meta.setContentType("image/png");
}
if ("gif".equals(imageType)) {
meta.setContentType("image/gif");
}
logger.log("Writing to: " + dstBucket + "/" + key);
try {
s3Client.putObject((new PutObjectRequest(dstBucket, key, is, meta)).withCannedAcl(CannedAccessControlList.PublicRead));
} catch (AmazonServiceException var25) {
logger.log(var25.getErrorMessage());
System.exit(1);
}
logger.log("Successfully resized " + srcBucket + "/" + key + " and uploaded to " + dstBucket + "/" + key);
return "Ok";
}
}
} else {
return "OK";
}
} catch (IOException var26) {
throw new RuntimeException(var26);
}
}
}
기본적으로 이 코드는 람다에서 트리거를 통해 실행 되므로 직접 우리가 콘솔창에서 확인 할 수 없기에
cloudWatch를 통해 로그를 확인해 문제점들을 찾기 위해 필요한 곳에 로그를 작성하는 것이 매우 중요하다.
각자의 상황에 따라, 이 소스코드는 바뀔 수 있다.
S3 생성
일단 본인의 경우 버킷은 3개를 만들었다.Bucket1 - Lambda 함수의 코드의 Zip파일을 저장할 버켓 (퍼블릭 액세스 X)Bucket2 - 원본의 이미지가 저장될 버켓 (퍼블릭 액세스 O)Bucket3 - 리사이징된 이미지가 저장될 버켓 (퍼블릭 액세스 O)
생성 시 주의할 점은


이미지가 저장될 두 버켓은 이 부분들을 열어줘야한다.물론 보안적인 측면에서 안좋을 수 있기 때문에, 실제 서비스에서 적용시엔 주의가 필요할 거같다.
Lambda 생성

원하는 함수의 이름을 작성하고사용할 언어를 골라준다.(본인의 경우 Java17)※ 노드,파이썬,루비의 경우 AWS 페이지의 콘솔에서 코드를 수정 할 수 있다.

생성 후 트리거 추가를 눌러

아까 만들어 둔 트리거를 발동 시킬 S3 버킷을 선택 후 본인의 상황에 맞는 Event types를 골라준다.

그리고 코드 소스를 업로드 시켜야 하는데, Java의 경우 코드 편집을 바로 할 수 없기 때문에 Zip파일로 올려주어야 한다
또한, 파일의 크기가 10mb이상일 경우 직접 업로드가 불가능해
S3에 업로드 후 해당 Zip파일의 Url을 넣어줘야한다.
권한의 경우

S3와 Lambda, 로깅을 위한 CloudWatch의 권한을 주었다.
그후 트리거를 발동 시키면 리사이징 된 이미지가 버킷에 저장되게 된다.
로그 확인 하는 법


이렇게 이벤트가 발생하여 Lambda가 작동하면 로그 스트림에 해당 이벤트 로그가 생겨 로그를 확인할 수 있다.
※ Lambda에 CloudWatch 권한을 주지 않으면 로그를 작성할 권한이 없어 로그가 생기지 않으므로 로그를 보기 위해선 권할을 주어야한다.
트러블 슈팅
1. Class not found: example.Hello: java.lang.ClassNotFoundException

1. 첫번 째 문제 : 경로 설정 문제

핸들러 부분 경로를 지정하지 않고 기본설정대로 실행해서 핸들러를 찾지 못함
-> 경로를 제대로 설정했지만 경로를 여전히 찾지 못함.
2. 두번 째 문제 : Zip파일이 아닌 Jar파일을 넣어줬음
아무 생각 없이 그냥 build 하고 build/libs 경로에 있는 jar파일을 소스코드에 넣어버렸다.
->
//build.gradle
task buildZip(type: Zip) {
into('lib') {
from(jar)
from(configurations.runtimeClasspath)
}
}
build.dependsOn(buildZip)
gradle에 해당 코드 추가 후 build -> build/distributions에 있는 Zip파일을 넣어주니 해결
2. The bucket does not allow ACLs


만약 생성할 때 이 부분을 건드리지 않았다면 사진 처럼 비활성화가 되어있을 텐데,

ACL을 활성화 시켜줘야함.
'AWS' 카테고리의 다른 글
CodeDeploy를 통해 CICD/무중단배포를 해보자 (0) | 2024.02.29 |
---|---|
EC2 솔루션스 아키텍트 어소시에이트 레벨 (0) | 2024.02.07 |
EC2 기초 (0) | 2024.02.07 |
AWS ECS, Fargate, ECR, EKS (0) | 2024.01.29 |
AWS IAM (0) | 2024.01.29 |