Notice
Recent Posts
Recent Comments
Link
«   2024/07   »
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
Archives
Today
Total
관리 메뉴

csct3434

Amazon S3 트리거와 Lambda를 활용한 썸네일 이미지 생성 (1) - 인프라 구축 본문

개발 일지

Amazon S3 트리거와 Lambda를 활용한 썸네일 이미지 생성 (1) - 인프라 구축

csct3434 2024. 4. 16. 19:10

인트로

여느 때처럼 시즈닝 사이트를 둘러보던 중, 친구의 프로필 사진이 끊기면서 로딩되는 것이 눈에 띄었습니다.

크롬 개발자 도구로 확인해 보니 6.5Mb 크기의 3024 x 4032 이미지를 다운받아 40 x 40 크기로 렌더링하고 있었습니다.

특히 모바일 환경에서는 이미지 로딩 지연이 크게 체감되어서, 이를 개선하기 위해 썸네일 이미지를 활용해 보기로 했습니다.

 

※ 이 글은 Amazon S3 트리거 및 Lambda를 활용하여 썸네일 이미지를 생성하는 솔루션을 다루고 있습니다.


썸네일 이미지 생성

https://docs.aws.amazon.com/ko_kr/lambda/latest/dg/with-s3-tutorial.html

 

자습서: Amazon S3 트리거를 사용하여 썸네일 이미지 생성 - AWS Lambda

Amazon S3 버킷 이름은 전역적으로 고유하지만 리소스 기반 정책을 사용할 때는 버킷이 반드시 계정에 속하도록 지정하는 것이 가장 좋습니다. 버킷을 삭제하면 다른 AWS 계정가 동일한 Amazon 리소

docs.aws.amazon.com

AWS에서 썸네일 이미지 생성에 대한 레퍼런스 문서를 제공하고 있으니 참고하시길 바랍니다.

별도의 언급이 없는 경우 기본값을 사용하시면 됩니다.

0. 이미지 버킷 생성

우선 이미지를 저장할 S3 버킷이 하나 필요합니다. 저는 CloudFront를 사용하고 있어서 S3 버킷에 대한 퍼블릭 액세스를 차단해 놨지만, S3 주소로 이미지를 응답할 경우 아래의 [Block all public access]를 해제하셔야 합니다.


1. IAM Role 생성

기본적으로 AWS 서비스 간에는 서로 접근이 차단되어 있습니다. 따라서 서로 다른 AWS 서비스 간에 액세스가 필요한 경우 별도로 권한을 설정해 주어야 합니다. 썸네일을 생성하는 과정에서 Lambda가 S3와 CloudWatch Logs 서비스에 접근하기 때문에, 관련된 권한을 보유한 IAM Role을 만들어서 추후 생성할 Lambda Function에 할당해주어야 합니다.

 

1. AWS IAM[Policies] 탭으로 이동 후, [Create Policy]를 선택합니다.

2. [JSON] 탭을 클릭하여 아래의 코드를 편집기에 붙여 넣습니다. S3 버킷에 대한 읽기 및 쓰기와 CloudWatch Logs에 대한 쓰기 작업을 허용하는 정책입니다.

{
    "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:::*/*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject"
            ],
            "Resource": "arn:aws:s3:::*/*"
        }
    ]
}

 

3. 다음 페이지로 이동하여, [Policy name]에 LambdaS3Policy를 입력하고 정책을 생성합니다.

4. AWS IAM[Roles] 탭으로 이동 후, [Create role]을 선택합니다.

5. [Trusted entity type]으로 AWS service를 선택하고 [Use case]Lambda를 선택한 후, 다음 페이지로 이동합니다.

 

6. 검색 상자에 앞서 생성한 LambdaS3Policy를 입력하여 박스를 체크한 후 다음으로 넘어갑니다.

 

7. [Role name]LambdaS3Role을 입력한 후 역할을 생성합니다.


2. Lambda 함수 생성

1. AWS Lambda[Functions] 탭으로 이동하여 [Create function]을 선택합니다.

2. [Function name]에 함수 이름을 입력하고 [Runtime]으로 Python 3.9를 선택합니다.

3. [Change default execution role]에서 [Use an existing role]을 선택합니다.

4. [Existing role]에서 앞서 생성한 LambdaS3Role를 선택한 후 함수를 생성합니다.


3. 배포 패키지 생성

Lambda 함수에 배포할 썸네일 생성 패키지를 만들겠습니다.

 

1. 아래의 코드를 lambda_function.py라는 파일로 저장합니다.

import boto3
import os
import sys
import uuid
from urllib.parse import unquote_plus
from PIL import Image, ImageOps
import mimetypes

s3_client = boto3.client('s3')

def resize_image(image_path, resized_path):
    with Image.open(image_path) as image:
        image_format = image.format

        image = ImageOps.exif_transpose(image)
        image.thumbnail((300, 300))

        if image_format == 'JPEG':
            image.save(resized_path, quality=100, subsampling=0, format=image_format)
        else:
            image.save(resized_path, format=image_format)

def lambda_handler(event, context):
    for record in event['Records']:
        bucket = record['s3']['bucket']['name']
        key = unquote_plus(record['s3']['object']['key'])

        tmpkey = key.replace('/', '')
        original_path = f'/tmp/{uuid.uuid4()}{tmpkey}'
        resized_path = f'/tmp/resize-{tmpkey}'

        s3_client.download_file(bucket, key, original_path)

        resize_image(original_path, resized_path)

        s3_client.upload_file(resized_path, bucket, key.replace('origin', 'resize', 1))

 

람다가 트리거되면 lambda_handler() 실행됩니다. 

  • s3_client.download_file() : S3 버킷으로부터 이미지를 다운받아 로컬 저장소의 original_path에 저장합니다.
  • resize_image() : 썸네일을 생성하여 로컬 저장소의 resized_path에 저장합니다.
  • s3_client.upload_file() : 생성된 썸네일을 S3 버킷에 업로드 합니다.

resize_image()

  • ImageOps.exif_transpose(image) : 썸네일 생성 과정에서 이미지가 회전되는 현상을 방지하기 위해, 썸네일 생성 전 이미지의 메타데이터에서 orientation 정보를 제거합니다.
  • image.thumbnail((300, 300))
    • 이미지의 비율을 유지한 채로 가로 최대 300px, 세로 최대 300px 크기로 사이즈를 변경합니다.
    • 예시 : 500x300 -> 300x180 / 400x800 -> 150x300
  • image.save(resized_path, format=image_format)
    • 썸네일 이미지를 resized_path에 저장합니다.
    • [주의] 썸네일 생성 과정에서 image의 format 속성이 제거되기 때문에, 원본 이미지의 format을 별도로 지정해 주지 않으면 파일 타입을 추정할 수 없어 에러가 발생합니다!
  • image.save(resized_path, quality=100, subsampling=0, format=image_format)
    • JPG(JPEG) 이미지의 경우 저장 과정에서 화질이 저하되기 때문에, 이러한 현상을 완화하기 위하여 'quality=100,  subsampling=0' 옵션을 추가로 지정했습니다.
    • JPG(JPEG)가 아닌 이미지에 해당 옵션을 적용한면 에러가 발생합니다.
    • 해당 옵션을 사용할 경우 부하가 증가합니다. 이를 참고하여 선택하시길 바랍니다.

s3_client.upload_file(resized_path, bucket, key.replace('origin', 'resize', 1))

  • resized_path에 위치한 썸네일을 S3 버킷으로 업로드 합니다.
  • 원본 이미지를 'origin/~'에 저장하고, 썸네일 이미지를 'resize/~'에 저장하고자 위와 같이 구성했습니다. (origin/profile/a.jpg -> resize/profile/a.jpg)

2. lambda_function.py 파일이 위치한 디렉터리에 package라는 이름으로 새 디렉터리를 생성합니다.

3. (2)와 동일한 위치에서 아래의 코드를 실행하여 package 디렉터리에 Pillow(PIL) 라이브러리와 AWS SDK for Python (Boto3)을 설치합니다.

pip install \
--platform manylinux2014_x86_64 \
--target=package \
--implementation cp \
--python-version 3.9 \
--only-binary=:all: --upgrade \
pillow boto3

4. 애플리케이션 코드와 Pillow 및 Boto3 라이브러리가 포함된 패키지 파일을 생성합니다. Linux 또는 MacOS의 경우 CLI에서 다음의 명령을 실행합니다. Windows의 경우 package 폴더에 lambda_function.py를 복사한 후, package 디렉터리를 압축해주시면 됩니다.

cd package
zip -r ../lambda_function.zip .
cd ..
zip lambda_function.zip lambda_function.py

4. 배포 패키지 업로드

1. AWS Lambda[Functions] 탭에서 앞서 생성한 람다 함수를 선택합니다.

2. [Code] 탭의 [Code source]의 오른쪽에 있는 [Upload from]에서 [.zip file]을 선택합니다.

 

3. [Upload]를 클릭하여 생성한 배포 패키지를 선택 후, [Save]를 눌러 Lambda Function에 패키지를 배포합니다.


5. S3 트리거 설정

1. AWS Lambda Function의 [Function overview]에서 다이어그램 왼쪽에 있는 [Add trigger]를 선택합니다.

 

2. [Select a source]에서 S3를 선택 후, 아래와 같이 구성하여 트리거를 구성해줍니다.

  • Bucket : S3 이미지 버킷 선택
  • Event types : (기본값) All object create events
  • Prefix : 'origin/' 입력 시, 'origin/'으로 시작하는 파일이 S3 버킷에 업로드되면 람다 함수가 실행됩니다.
  • 맨 아래 체크박스 : 체크
    • I acknowledge that using the same S3 bucket for both input and output is not recommended and that this configuration can cause recursive invocations, increased Lambda usage, and increased costs.
    • 입력과 출력 버킷이 동일한 경우 람다 함수가 무한 재귀호출 될 가능성이 있으므로, 별도의 버킷으로 분리하는 것이 안전한다는 내용

6. 테스트

이제 썸네일 생성을 위한 모든 설정이 완료되었습니다.

트리거를 설정한 'origin/' 경로 하위에 이미지를 업로드하여 'resize/' 경로에 썸네일이 생성되는지 테스트해 보시면 됩니다.

Lambda 함수의 실행 결과는 AWS CloudWatch[Log groups] 탭에서 /aws/lambda/{function_name}을 선택하여 확인할 수 있습니다.


7. Lambda 메모리 및 타임아웃 설정

람다 함수의 기본 메모리 용량과 타임아웃 값은 128MB3초입니다.

메모리가 부족한 경우 "Error: Runtime exited with error: signal: killed Runtime.ExitError" 메시지가 표시됩니다.

아래의 REPORT 부분을 보면 Max Memory UsedMemory Size 동일한 것을 확인할 수 있습니다.

작업 시간이 타임아웃 값을 초과하는 경우 "Task timed out after ~ seconds" 메시지가 표시됩니다.

람다 함수의 메모리 용량과 타임아웃 값은 [Configuration] 탭의 General configuration에서 설정할 수 있습니다.

테스트를 통해 CloudWatch Logs에 기록된 Duration과 Max Memory Used 값을 확인해가며, 작업을 정상적으로 처리할 수 있는 최적의 값을 찾아 설정해 주시면 됩니다.


마무리

썸네일 이미지와 원본 이미지의 크기를 비교해 본 결과, 약 100배 이상의 차이를 보였습니다.

 


이렇게 생성된 썸네일은 다음과 같이 활용할 수 있습니다.

  1. 백엔드는 클라이언트에게 썸네일 주소를 응답한다.
  2. 클라이언트는 해당 주소로 썸네일 이미지를 요청한다.
    1. 요청 성공 시, 썸네일 이미지를 렌더링한다.
    2. 요청 실패 시, 첫번째로 매칭되는 'resize/'를 'origin/'으로 변경하여 원본 이미지를 대신 요청한다.
      (ex: https://test.cloudfront.net/resize/profile/a.jpg -> https://test.cloudfront.net/origin/profile/a.jpg)

 위에 대한 코드 구현은 다음 글을 참고해 주시길 바랍니다.

 

Amazon S3 트리거와 Lambda를 활용한 썸네일 이미지 생성 (2) - 코드 구현

인트로 지난 글에서는 Amazon S3 트리거와 Lambda를 활용하여 썸네일 이미지를 생성하는 방법에 대해 알아봤습니다. AWS S3와 Lambda를 활용한 썸네일 이미지 생성 (1) 인트로 여느 때처럼 시즈닝 사이트

csct3434.tistory.com


참고한 글