[AWS] Lambda를 이용한 Image Resizing

상트리버·2023년 1월 25일
0

AWS

목록 보기
4/4

현재 진행하고 있는 프로젝트는 브랜드 정보와 상품 정보를 등록할때 해당하는 이미지 파일도 함께 업로드를 진행해야 한다. 이번 포스팅에서는 AWS S3에 이미지 파일을 저장하는 방법과 AWS Lambda를 이용한 이미지 파일 용량 리사이징 과정을 소개하려고 한다.

1. AWS S3에 이미지 파일 업로드하기

1. S3 버킷 생성

먼저 AWS에 로그인 한 후 S3 버킷을 생성해보자.

pulbattelambda,
pulbattelambda-resized
pulbattelambda는 원본 이미지를 올리는 버킷,
pulbattelambda-resized는 이미지를 리사이징한 버킷으로 사용하기 위해 만들었다.


모든 퍼블릭 액세스 차단을 해제하고, 아래 경고 메시지를 체크해주자. 모든퍼블릭 액세스 차단을 해제하지 않으면 S3에 이미지를 등록할때

Access Denied (Service: Amazon S3; Status Code: 403; Error Code: AccessDenied; Request ID :

와 같은 오류가 발생한다. 따라서 생성시에는 액세스 차단을 해제하고 추후 버캣 정책을 설정해야 한다.

생성된 버킷으로 이동한 후 권한 탭으로 이동하자.

{
    "Version": "2012-10-17",
    "Id": "Policy1674552169617",
    "Statement": [
        {
            "Sid": "Stmt1674552168225",
            "Effect": "Allow",
            "Principal": "*",
            "Action": [
                "s3:DeleteObject",
                "s3:GetObject",
                "s3:PutObject"
            ],
            "Resource": "arn:aws:s3:::pulbattelambda/*"
        }
    ]
}
// pulbattelambda 부분은 본인 버킷 이름으로 설정하면 된다.

위 내용을 그대로 입력하고 버킷명만 자신의 버킷 이름으로 변경해주자.
다음으로 생성한 S3에 접근하기 위해 IAM 사용자를 등록해보자.

사용자를 클릭하고 왼쪽 상단에 사용자 추가 버튼을 클릭

S3 사용 권한을 부여하기 위해 AmazonS3FullAccess를 검색해서 체크한 후 다음 버튼 클릭

생성된 accessKey와 secretKey를 잊어버리지 않게 적어두자. 참고로 secretKey는 절대로 외부에 노출해서는 안된다.
여기까지 완료하면 이미지 파일을 S3에 등록하기 위한 작업이 모두 완료되었다.

이미지를 s3에 등록하는 부분은 생략하고, 바로 AWS Lambda로 넘어가도록 하겠다

2. AWS Lambda를 이용한 이미지 리사이징

현재 진행하고 있는 Pulbatte 사이트에서,
300여가지 되는 식물의 정보를 검색해 볼 수 있는 페이지와
커뮤니티쪽 게시판의 이미지를 원본 크기의 이미지로 불러올 경우에는 이미지 크기가 크기 때문에 로딩 속도가 현저하게 느려진다 따라서 상황에 맞는 이미지를 따로 리사이징을 한 후에 저장을 해서 작은 이미지가 필요한 페이지에서는 리사이징된 이미지를 불러와서 로딩속도를 향상시키기 위해서
원본이미지를 저장할 때에 따로 aws Lambda를 이용하여 이미지 리사이징을 진행하였다

🔎AWS Lambda ?

2-1 적용 과정


위에서 만든 것 처럼 두개의 버킷을 생성해 준다

이후 원본 이미지를 저장할 버킷과 리사이징된 이미지를 저장할 버킷의 접근 권한을 포함하는 정책을 생성해야 한다. IAM 정책 페이지로 접속해서 정책 생성 버튼을 클린한다.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:PutLogEvents",
                "logs:CreateLogGroup",
                "logs:CreateLogStream"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject"
            ],
            "Resource": "arn:aws:s3:::pulbattelambda/*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject"
            ],
            "Resource": "arn:aws:s3:::pulbattelambda-resized/*"
        }
    ]
}  

다음으로 AWS 리소스에 액세스 할 수 있는 권한을 제공하는 실행 역할을 생성해야 한다.
IAM에 접속해서 역할 만들기 버튼을 클릭한다.

신뢰할 수 있는 객체로 Lambda를 선택하고 다음 버튼을 클릭한다.
이전에 만든 정책을 연결해야 하기 때문에 AWSLambdaS3Policy 를 검색 후 선택한다.
역할 이름은 임의로 lambda-s3-role로 입력하자.

함수 생성
이제 우리의 S3 버킷에 이미지가 업로드 되었을때 자동으로 resized 버킷에 리사이징된 이미지가 저장되도록 함수를 만들어야 한다. 여러가지 언어를 지원하지만 본 프로젝트에서는 Java11로 진행하였다.

Node.js로 진행할 경우 콘솔의 코드 편집기로 바로 코드 작성이 가능하지만 java11은 해당 기능을 지원하지 않기 때문에 인텔리제이 또는 이클립스를 이용해 함수(메소드)를 작성 후 zip파일을 빌드하는 과정을 거쳐야 한다.

여기서는 인텔리제이로 실행하도록 했다
여기저기 사이트를 참고하다가 중복되는 디펜던시등 쓸모없는 코드들도 섞여있습니다 참고바랍니다!!

build.gradle

plugins {
    id 'java'
    id 'org.springframework.boot' version '2.7.8'
    id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
repositories {
    mavenCentral()
}
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    implementation platform('software.amazon.awssdk:bom:2.15.0')
    implementation platform('com.amazonaws:aws-xray-recorder-sdk-bom:2.11.0')
    implementation 'software.amazon.awssdk:s3'
    implementation 'com.amazonaws:aws-lambda-java-core:1.2.1'
    implementation 'com.amazonaws:aws-lambda-java-events:3.11.0'
    implementation 'org.apache.logging.log4j:log4j-api:[2.17.1,)'
    implementation 'org.apache.logging.log4j:log4j-core:[2.17.1,)'
    implementation 'org.apache.logging.log4j:log4j-slf4j18-impl:[2.17.1,)'
    runtimeOnly 'com.amazonaws:aws-lambda-java-log4j2:1.5.1'
    implementation 'com.amazonaws:aws-xray-recorder-sdk-core'
    implementation 'com.amazonaws:aws-xray-recorder-sdk-aws-sdk'
    implementation 'com.amazonaws:aws-xray-recorder-sdk-aws-sdk-instrumentor'
    implementation 'com.google.code.gson:gson:2.8.9'
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.6.0'
    implementation group: 'com.amazonaws', name: 'aws-lambda-java-core', version: '1.2.1'
    implementation group: 'com.amazonaws', name: 'aws-lambda-java-events', version: '3.7.0'
    implementation group: 'com.amazonaws', name: 'aws-java-sdk', version: '1.11.969'
    implementation 'com.amazonaws:aws-java-sdk-s3:1.11.578'
}
test {
    useJUnitPlatform()
}
task packageBig(type: Zip) {
    from compileJava
    from processResources
    into('lib') {
        from configurations.runtimeClasspath
    }
}
task packageLibs(type: Zip) {
    into('java/lib') {
        from configurations.runtimeClasspath
    }
}
task packageSmall(type: Zip) {
    from compileJava
    from processResources
}
java {
    sourceCompatibility = JavaVersion.VERSION_1_8
    targetCompatibility = JavaVersion.VERSION_1_8
}
task buildZip(type: Zip) {
    from compileJava
    from processResources
    into('lib') {
        from configurations.runtimeClasspath
    }
}
build.dependsOn buildZip

Handler2.java

package lambda;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.amazonaws.services.s3.model.*;
import javax.imageio.ImageIO;
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.models.s3.S3EventNotification.S3EventNotificationRecord;
import com.amazonaws.services.lambda.runtime.events.S3Event;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
public class Handler2 implements RequestHandler<S3Event, String> {
    private static final float MAX_WIDTH = 500;
    private static final float MAX_HEIGHT = 500;
    private final String JPG_TYPE = (String) "jpg";
    private final String JPG_MIME = (String) "image/jpeg";
    private final String JPEG_TYPE = (String) "jpeg";
    private final String JPEG_MIME = (String) "image/jpeg";
    private final String PNG_TYPE = (String) "png";
    private final String PNG_MIME = (String) "image/png";
    private final String GIF_TYPE = (String) "gif";
    private final String GIF_MIME = (String) "image/gif";
    public String handleRequest(S3Event s3event, Context context) {
        LambdaLogger logger = context.getLogger();
        try {
            S3EventNotificationRecord record = s3event.getRecords().get(0);
            String srcBucket = record.getS3().getBucket().getName();
            // Object key may have spaces or unicode non-ASCII characters.
            String key = record.getS3().getObject().getUrlDecodedKey();
            String dstBucket = srcBucket.replace("temp", "resized");
            // Infer the image type.
            Matcher matcher = Pattern.compile(".*\\.([^\\.]*)").matcher(key);
            if (!matcher.matches()) {
                logger.log("Unable to infer image type for key " + key);
                return "";
            }
            String imageType = matcher.group(1);
            if (!(JPG_TYPE.equals(imageType)) && !(JPEG_TYPE.equals(imageType))
                    && !(PNG_TYPE.equals(imageType)) && !(GIF_TYPE.equals(imageType))) {
                logger.log("Skipping non-image " + key);
                return "";
            }
            // Download the image from S3 into a stream
            AmazonS3 s3Client = AmazonS3ClientBuilder.defaultClient();
            S3Object s3Object = s3Client.getObject(new GetObjectRequest(
                    srcBucket, key));
            InputStream objectData = s3Object.getObjectContent();
            // Read the source image
            BufferedImage srcImage = ImageIO.read(objectData);
            int srcHeight = srcImage.getHeight();
            int srcWidth = srcImage.getWidth();
            // Infer the scaling factor to avoid stretching the image
            // unnaturally
            float scalingFactor = Math.min(MAX_WIDTH / srcWidth,MAX_HEIGHT/srcHeight);
            int width = (int) (scalingFactor * srcWidth);
            int height = (int) (scalingFactor * srcHeight);
            BufferedImage resizedImage = new BufferedImage(width, height,
                    BufferedImage.TYPE_INT_RGB);
            Graphics2D g = resizedImage.createGraphics();
            // Fill with white before applying semi-transparent (alpha) images
            g.setPaint(Color.white);
            g.fillRect(0, 0, width, height);
            // Simple bilinear resize
            g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                    RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            g.drawImage(srcImage, 0, 0, width, height, null);
            g.dispose();
            // Re-encode image to target format
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            ImageIO.write(resizedImage, imageType, os);
            InputStream is = new ByteArrayInputStream(os.toByteArray());
            // Set Content-Length and Content-Type
            ObjectMetadata meta = new ObjectMetadata();
            meta.setContentLength(os.size());
            if (JPG_TYPE.equals(imageType)) {
                meta.setContentType(JPG_MIME);
            }
            if (JPEG_TYPE.equals(imageType)) {
                meta.setContentType(JPEG_MIME);
            }
            if (PNG_TYPE.equals(imageType)) {
                meta.setContentType(PNG_MIME);
            }
            if (GIF_TYPE.equals(imageType)) {
                meta.setContentType(GIF_MIME);
            }
            // Uploading to S3 destination bucket
            logger.log("Writing to: " + dstBucket + "/" + key);
            try {
                dstBucket += "-resized";
                s3Client
                        .putObject(new PutObjectRequest(dstBucket, key, is, meta).withCannedAcl(
                                CannedAccessControlList.PublicRead));
            } catch (AmazonServiceException e) {
                logger.log(e.getErrorMessage());
                System.exit(1);
            }
            logger.log(
                    "Successfully resized " + srcBucket + "/" + key + " and uploaded to " + dstBucket
                            + "/" + key);
            return "Ok";
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

해당 메서드는 현재 진행중인 프로젝트에 맞게 작성된 코드로, AWS에서 제공하는 예제코드를 조금만 수정한 코드다.

그 후에 gradle build를 진행한다

빌드가 완료되면 다음과 같이 zip파일이 생성된다

람다 생성
Lambda 함수 생성 버튼을 클릭하고 기존 역할 사용에 체크하고 위에서 생성한 역할을 선택한다.

Lambda 함수를 생성 후

생성한 Lambda에 build한 코드 소스를 추가해야하는데 10MB가 초과하는 경우 바로 업로드할 수 없다. 따라서 임의의 S3버킷을 하나 생성한 후 좀 전에 build한 .zip파일을 업로드 후 업로드한 .zip 파일의 객체 URL을 복사 하자.

다시 생성한 Lambda로 돌아가서 소스코드 업로드를 URL을 입력 후 , 런타임 설정의 핸들러 값을 [패키지명.클래스명::메서드명] 으로 설정해야 한다.

마지막으로, 함수 개요에서 트리거 추가를 클릭 후 S3를 선택한다. 버킷은 원본 이미지가 저장되는 버킷을 선택하고, 이벤트 유형을 모든 객체 생성 이벤트로 선택한다.

이렇게 설정을 한 후에는
원본 이미지는
pulbattelambda에 저장이 되고,
pulbattelambda-resized에는 리사이징된 이미지가 저장이 된다.

0개의 댓글