본문 바로가기

AWS

aws Lambda를 통해 썸네일(이미지 리사이징)을 생성해보자

썸네일(이미지 리사이징)을 사용하는 이유

원본 이미지는 종종 상세한 정보를 담고 있어 파일 크기가 크고, 불필요한 데이터를 주고받게 된다. 하지만 대부분의 경우에는 페이지에서 사용되는 이미지는 전체 원본 크기나 해상도를 필요로하지 않을 수 있다. 이에 따라 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' 카테고리의 다른 글