Amazon S3 객체 Lambda를 사용하여 이미지 검색 시 동적으로 워터마킹

자습서

개요

Amazon S3 객체 Lambda를 사용하면 자체 코드를 S3 GET, HEAD, LIST 요청에 추가하여 데이터가 애플리케이션에 반환될 때 데이터를 수정할 수 있습니다. 사용자 지정 코드를 사용하여 데이터 형식을 변환하고(예: XML에서 JSON으로) 이미지 크기를 동적으로 조정하고 기밀 데이터를 수정하는 등 S3 GET 요청에서 반환된 데이터를 수정할 수 있습니다. 또한 S3 객체 Lambda를 사용해 S3 LIST 요청 출력을 수정하여 버킷에 저장된 객체를 볼 수 있는 사용자 지정 뷰를 생성하거나, S3 HEAD 요청 출력을 수정하여 객체 이름이나 크기와 같은 객체 메타데이터를 변경할 수 있습니다.

이 자습서의 목적은 Amazon S3 객체 Lambda를 시작하는 방법을 보여주는 것입니다. 많은 조직이 각각 고유한 데이터 형식 요구 사항이 있는 다양한 애플리케이션에서 액세스하는 이미지를 Amazon S3에 저장합니다. 이미지에 액세스하는 사용자에 따라 워터마크를 포함하도록 이미지를 수정해야 하는 경우도 있습니다. 예를 들어, 유료 구독자는 워터마크가 없는 이미지를 볼 수 있지만 무료 사용자는 워터마크가 있는 이미지를 받습니다.

이 자습서에서는 S3 객체 Lambda를 사용하여 Amazon S3에서 검색 시 이미지에 워터마크를 추가합니다. S3 객체 Lambda를 사용하면 기존 객체를 변경하거나 데이터의 여러 파생 사본을 유지 관리할 필요 없이 Amazon S3에서 검색될 때 데이터를 수정할 수 있습니다. 동일한 데이터를 여러 번 볼 수 있고 파생 사본을 저장할 필요가 없으므로 스토리지 비용을 절약할 수 있습니다.

학습 목표

이 자습서 개요

  • Amazon S3 버킷 생성
  • S3 액세스 포인트 생성
  • 이미지 수정을 위한 AWS Lambda 함수 생성
  • S3 객체 Lambda 액세스 포인트 생성

사전 요구 사항

이 자습서를 완료하려면 AWS 계정이 필요합니다. 새 AWS 계정을 생성하고 활성화하는 방법에 대한 자세한 내용은 이 지원 페이지를 참조하세요.

자습서용 IAM 사용자를 생성하거나 기존 IAM 사용자에게 권한을 추가할 수 있습니다. 이 자습서를 완료하려면 IAM 사용자에게 관련 AWS 리소스에 액세스하고 특정 작업을 수행할 수 있는 다음 권한이 있어야 합니다.

  • s3:CreateBucket
  • s3:PutObject
  • s3:GetObject
  • s3: ListBucket
  • s3:CreateAccessPoint
  • s3:CreateAccessPointForObjectLambda
  • s3-object-lambda:WriteGetObjectResponse
  • lambda:CreateFunction
  • lambda:InvokeFunction
  • iam:AttachRolePolicy
  • iam:CreateRole
  • iam:PutRolePolicy

이 자습서에서 생성한 리소스를 정리하려면 다음과 같은 IAM 권한이 필요합니다.

  • s3:DeleteBucket
  • s3:DeleteAccessPoint
  • s3:DeleteAccessPointForObjectLambda
  • lambda:DeleteFunction
  • iam:DeleteRole

 

 AWS 경험

초보자

 소요 시간

20분

 완료 비용

 필요 사항

AWS 계정*

*생성된 지 24시간이 지나지 않은 계정은 이 자습서를 완료하는 데 필요한 리소스에 액세스할 권한이 아직 없을 수 있습니다.

 사용 서비스

 최종 업데이트 날짜

2023년 2월 1일

사전 요구 사항

이 자습서를 완료하려면 AWS 계정이 필요합니다. 새 AWS 계정을 생성하고 활성화하는 방법에 대한 자세한 내용은 이 지원 페이지를 참조하세요.

자습서용 IAM 사용자를 생성하거나 기존 IAM 사용자에게 권한을 추가할 수 있습니다. 이 자습서를 완료하려면 IAM 사용자에게 관련 AWS 리소스에 액세스하고 특정 작업을 수행할 수 있는 다음 권한이 있어야 합니다. 

구현

1단계: Amazon S3 버킷 생성

1.1 - Amazon S3 콘솔에 로그인

1.2 - S3 버킷 생성

  • 왼쪽 탐색 창의 Amazon S3 메뉴에서 버킷을 선택하고 버킷 생성 버튼을 선택합니다.

1.3

  • 버킷 이름 필드에 버킷에 사용할 알기 쉽고 전역적으로 고유한 이름을 입력합니다. 버킷을 생성할 AWS 리전을 선택합니다. 이 자습서의 뒷부분에서 동일한 AWS 리전에 있어야 하는 또 다른 리소스를 생성합니다.
  • 나머지 옵션은 기본 선택으로 둘 수 있습니다. 페이지 하단으로 이동하고 버킷 생성을 클릭합니다.

2단계 - 객체 업로드

버킷이 생성되고 구성되었으므로 이미지를 업로드할 준비가 되었습니다.

2.1 - 객체 업로드

  • 사용 가능한 버킷의 목록에서 방금 생성한 버킷의 이름을 선택합니다.

2.2

  • 다음으로 객체 탭이 선택되었는지 확인합니다. 그런 다음 객체 섹션에서 업로드 버튼을 선택합니다.

2.3 - 파일 추가

  • 파일 추가 버튼을 선택한 다음 파일 브라우저에서 업로드할 이미지를 선택합니다.
  • 원하는 경우 이 샘플 이미지를 업로드할 수 있습니다.

2.4 - 업로드

  • 페이지에서 아래로 이동하여 업로드 버튼을 선택합니다.

2.5

  • 업로드가 성공적으로 완료되면 닫기 버튼을 선택합니다.

3단계: S3 액세스 포인트 생성

S3 객체 Lambda 액세스 포인트를 지원하는 데 사용할 Amazon S3 액세스 포인트를 생성합니다. 이 액세스 포인트는 자습서 뒷부분에서 생성합니다.

3.1 - S3 액세스 포인트 생성

  • S3 콘솔로 이동하고 왼쪽 탐색 창에서 액세스 포인트 메뉴 옵션을 선택합니다. 그런 다음 액세스 포인트 생성 버튼을 선택합니다.

3.2

  • 속성 섹션에서 원하는 액세스 포인트 이름을 입력하고 S3 찾아보기 버튼을 선택하여 1단계에서 입력한 버킷 이름을 선택합니다. 그런 다음 네트워크 오리진인터넷으로 설정합니다.

3.3

  • 다른 모든 기본값은 그대로 둡니다. 페이지 하단으로 이동하고 액세스 포인트 생성 버튼을 선택합니다.

3.4

  • 이제 왼쪽 탐색 창에서 액세스 포인트로 이동하면 S3 액세스 포인트가 목록에 나타납니다.

4단계: Lambda 함수 생성

  • 다음으로, S3 객체 Lambda 액세스 포인트를 통해 S3 GET 요청이 생성될 때 간접적으로 호출될 Lambda 함수를 생성합니다.
  • AWS Management Console에서 AWS CloudShell을 사용하여 S3 객체 Lambda를 빌드하고 테스트합니다. 다음 요구 사항을 충족하는 경우 자체 컴퓨터 또는 AWS Cloud9 인스턴스를 사용하여 솔루션을 구축할 수 있습니다.
    - 최신 버전의 AWS 명령줄 인터페이스(CLI)
    - AWS Lambda 함수/계층 및 IAM 역할을 생성하기 위한 보안 인증
    - Python 3.9
    - zip 유틸리티
    - jq 유틸리티

4.1 - CloudShell 터미널 시작

AWS Management Console의 오른쪽 상단 메뉴에서 CloudShell 아이콘을 선택합니다.

CloudShell 소개 창이 나타나면 내용을 읽고 닫기를 선택합니다.

CloudShell 터미널과 함께 새 브라우저 탭이 열립니다(다음 스크린샷과 유사).

4.2 - Lambda 함수를 배포하도록 CloudShell 준비

  • CloudShell에서 다음 코드를 실행하여 환경을 준비하고 Pillow 모듈로 Lambda 계층을 배포합니다. 다음 코드를 복사하여 CloudShell에 붙여 넣어 필요한 종속 항목을 설치하고 Lambda 함수를 배포합니다.
# Install the required libraries to build new python
sudo yum install gcc openssl-devel bzip2-devel libffi-devel -y
# Install Pyenv
curl https://pyenv.run | bash
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bash_profile
echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bash_profile
echo 'eval "$(pyenv init -)"' >> ~/.bash_profile
source ~/.bash_profile

# Install Python version 3.9
pyenv install 3.9.13
pyenv global 3.9.13

# Build the pillow Lambda layer
mkdir python
cd python
pip install pillow -t .
cd ..
zip -r9 pillow.zip python/
aws lambda publish-layer-version \
    --layer-name Pillow \
    --description "Python Image Library" \
    --license-info "HPND" \
    --zip-file fileb://pillow.zip \
    --compatible-runtimes python3.9

참고: 코드를 복사하여 붙여 넣을 때 CloudShell에서 여러 줄 코드를 붙여 넣을 것인지 확인하는 경고 창이 열립니다. 붙여넣기를 선택합니다.

이 단계를 완료하는 데 10~15분 정도 소요될 수 있습니다.

4.3 - Lambda 함수 빌드

  • Lambda 함수에서 이미지에 워터마크를 추가하는 데 사용할 TrueType 글꼴을 다운로드합니다. 다음 명령을 복사하여 CloudShell에 붙여 넣습니다.
wget https://m.media-amazon.com/images/G/01/mobile-apps/dex/alexa/branding/Amazon_Typefaces_Complete_Font_Set_Mar2020.zip
  • 이미지에 워터마킹된 텍스트를 쓰는 데 사용할 TrueType 글꼴을 추출합니다.
unzip -oj Amazon_Typefaces_Complete_Font_Set_Mar2020.zip "Amazon_Typefaces_Complete_Font_Set_Mar2020/Ember/AmazonEmber_Rg.ttf"
  • S3 객체 Lambda 요청을 처리하는 데 사용할 Lambda 코드를 생성합니다.
cat << EOF > lambda.py
import boto3
import json
import os
import logging
from io import BytesIO
from PIL import Image, ImageDraw, ImageFont
from urllib import request
from urllib.parse import urlparse, parse_qs, unquote
from urllib.error import HTTPError
from typing import Optional

logger = logging.getLogger('S3-img-processing')
logger.addHandler(logging.StreamHandler())
logger.setLevel(getattr(logging, os.getenv('LOG_LEVEL', 'INFO')))
FILE_EXT = {
    'JPEG': ['.jpg', '.jpeg'],
    'PNG': ['.png'],
    'TIFF': ['.tif']
}
OPACITY = 64  # 0 = transparent and 255 = full solid


def get_img_encoding(file_ext: str) -> Optional[str]:
    result = None
    for key, value in FILE_EXT.items():
        if file_ext in value:
            result = key
            break
    return result


def add_watermark(img: Image, text: str) -> Image:
    font = ImageFont.truetype("AmazonEmber_Rg.ttf", 82)
    txt = Image.new('RGBA', img.size, (255, 255, 255, 0))
    if img.mode != 'RGBA':
        image = img.convert('RGBA')
    else:
        image = img

    d = ImageDraw.Draw(txt)
    # Positioning Text
    width, height = image.size
    text_width, text_height = d.textsize(text, font)
    x = width / 2 - text_width / 2
    y = height / 2 - text_height / 2
    # Applying Text
    d.text((x, y), text, fill=(255, 255, 255, OPACITY), font=font)
    # Combining Original Image with Text and Saving
    watermarked = Image.alpha_composite(image, txt)
    return watermarked


def handler(event, context) -> dict:
    logger.debug(json.dumps(event))
    object_context = event["getObjectContext"]
    # Get the presigned URL to fetch the requested original object
    # from S3
    s3_url = object_context["inputS3Url"]
    # Extract the route and request token from the input context
    request_route = object_context["outputRoute"]
    request_token = object_context["outputToken"]
    parsed_url = urlparse(event['userRequest']['url'])
    object_key = parsed_url.path
    logger.info(f'Object to retrieve: {object_key}')
    parsed_qs = parse_qs(parsed_url.query)
    for k, v in parsed_qs.items():
        parsed_qs[k][0] = unquote(v[0])

    filename = os.path.splitext(os.path.basename(object_key))
    # Get the original S3 object using the presigned URL
    req = request.Request(s3_url)
    try:
        response = request.urlopen(req)
    except HTTPError as e:
        logger.info(f'Error downloading the object. Error code: {e.code}')
        logger.exception(e.read())
        return {'status_code': e.code}

    if encoding := get_img_encoding(filename[1].lower()):
        logger.info(f'Compatible Image format found! Processing image: {"".join(filename)}')
        img = Image.open(response)
        logger.debug(f'Image format: {img.format}')
        logger.debug(f'Image mode: {img.mode}')
        logger.debug(f'Image Width: {img.width}')
        logger.debug(f'Image Height: {img.height}')

        img_result = add_watermark(img, parsed_qs.get('X-Amz-watermark', ['Watermark'])[0])
        img_bytes = BytesIO()

        if img.mode != 'RGBA':
            # Watermark added an Alpha channel that is not compatible with JPEG. We need to convert to RGB to save
            img_result = img_result.convert('RGB')
            img_result.save(img_bytes, format='JPEG')
        else:
            # Will use the original image format (PNG, GIF, TIFF, etc.)
            img_result.save(img_bytes, encoding)
        img_bytes.seek(0)
        transformed_object = img_bytes.read()

    else:
        logger.info(f'File format not compatible. Bypass file: {"".join(filename)}')
        transformed_object = response.read()

    # Write object back to S3 Object Lambda
    s3 = boto3.client('s3')
    # The WriteGetObjectResponse API sends the transformed data
    if os.getenv('AWS_EXECUTION_ENV'):
        s3.write_get_object_response(
            Body=transformed_object,
            RequestRoute=request_route,
            RequestToken=request_token)
    else:
        # Running in a local environment. Saving the file locally
        with open(f'myImage{filename[1]}', 'wb') as f:
            logger.debug(f'Writing file: myImage{filename[1]} to the local filesystem')
            f.write(transformed_object)

    # Exit the Lambda function: return the status code
    return {'status_code': 200}
EOF
  • Python 코드와 TrueType 글꼴 파일을 포함하는 Lambda zip 파일을 생성합니다.
zip -r9 lambda.zip lambda.py AmazonEmber_Rg.ttf
  • Lambda 함수에 연결되는 IAM 역할을 생성합니다.
aws iam create-role --role-name ol-lambda-images --assume-role-policy-document '{"Version": "2012-10-17","Statement": [{"Effect": "Allow", "Principal": {"Service": "lambda.amazonaws.com"}, "Action": "sts:AssumeRole"}]}'
  • 이전에 생성한 IAM 역할에 미리 정의된 IAM 정책을 연결합니다. 이 정책에는 Lambda 함수를 실행하는 데 필요한 최소 권한이 포함되어 있습니다.
aws iam attach-role-policy --role-name ol-lambda-images --policy-arn arn:aws:iam::aws:policy/service-role/AmazonS3ObjectLambdaExecutionRolePolicy

export OL_LAMBDA_ROLE=$(aws iam get-role --role-name ol-lambda-images | jq -r .Role.Arn)

export LAMBDA_LAYER=$(aws lambda list-layers --query 'Layers[?contains(LayerName, `Pillow`) == `true`].LatestMatchingVersion.LayerVersionArn' | jq -r .[])
  • Lambda 함수를 생성하고 업로드합니다.
aws lambda create-function --function-name ol_image_processing \
 --zip-file fileb://lambda.zip --handler lambda.handler --runtime python3.9 \
 --role $OL_LAMBDA_ROLE \
 --layers $LAMBDA_LAYER \
 --memory-size 1024

5단계: S3 객체 Lambda 액세스 포인트 생성

S3 버킷에 저장된 이미지에 액세스하는 데 사용할 S3 객체 Lambda 액세스 포인트를 생성합니다.

5.1 - S3 객체 Lambda 액세스 포인트 생성

일반 섹션에서 객체 Lambda 액세스 포인트 이름ol-amazon-s3-images-guide를 입력합니다.

S3 객체 Lambda 액세스 포인트의 AWS 리전이 1.3단계에서 S3 버킷을 생성할 때 지정한 AWS 리전과 일치하는지 확인합니다.

S3 찾아보기 버튼을 사용하여 3.2단계에서 생성한 S3 액세스 포인트의 Amazon 리소스 이름(ARN)을 지원 액세스 포인트로 지정합니다.

아래로 이동하여 변환 구성을 봅니다. S3 API 목록에서 GetObject 옵션을 선택합니다.

Lambda 함수에서 ol_image_processing을 지정합니다.

그런 다음 페이지 하단으로 이동하여 객체 Lambda 액세스 포인트 생성을 선택합니다.

6단계: S3 객체 Lambda 액세스 포인트에서 이미지 다운로드

S3 객체 Lambda 액세스 포인트를 생성한 후 이미지를 열어 요청 중 워터마크가 제대로 추가되었는지 확인합니다.

6.1 - S3 객체 Lambda 액세스 포인트 열기

  • S3 콘솔 왼쪽 탐색 창에서 객체 Lambda 액세스 포인트를 선택하여 S3 객체 Lambda 액세스 포인트 목록으로 돌아간 후 5.1단계에서 생성한 S3 객체 Lambda 액세스 포인트를 선택합니다. 이 예제에서는 S3 객체 Lambda 액세스 포인트를 ol-amazon-s3-images-guide로 선택했습니다.

2.4단계에서 업로드한 이미지를 선택한 다음 열기 버튼을 선택합니다.

이미지와 워터마크가 있는 새 브라우저 탭이 열립니다.
 
이제 S3 객체 Lambda 액세스 포인트에서 다운로드한 모든 호환 가능한 이미지에 워터마크 텍스트가 포함됩니다.


6.2 - AWS CLI에서 변환된 이미지 다운로드

  • AWS CLI를 사용하여 이미지를 다운로드할 수도 있습니다. 이렇게 하려면 S3 객체 Lambda 액세스 포인트의 Amazon 리소스 이름(ARN)이 필요합니다. S3 콘솔에서 객체 Lambda 액세스 포인트 페이지로 이동하여 S3 객체 Lambda 액세스 포인트의 이름을 선택하고 속성 탭을 선택한 다음 Amazon 리소스 이름(ARN) 아래의 복사 아이콘을 선택합니다.

6.3 - CloudShell에서 AWS CLI 명령 실행

CloudShell 브라우저 탭에서 다음을 입력합니다.

aws s3api get-object --bucket <paste the ARN copied above here> --key <image filename here> <filename to write here>

6.4 - 로컬 컴퓨터에 이미지 다운로드

CloudShell에서 오른쪽 상단의 작업을 선택하고 파일 다운로드를 선택합니다.

S3 객체 Lambda 액세스 포인트에서 이미지를 다운로드할 때 6.3단계에서 정의한 파일 이름을 입력하고 다운로드를 선택합니다.

이제 로컬 컴퓨터에서 이미지를 열 수 있습니다.

참고: 이미지 뷰어는 컴퓨터와 운영 체제에 따라 다를 수 있습니다. 이미지를 여는 데 어떤 애플리케이션을 사용해야 할지 잘 모르겠으면 관리자에게 문의하세요.

7단계: 리소스 정리

다음으로 이 자습서에서 생성한 리소스를 정리합니다. 사용하지 않는 리소스를 삭제하여 의도하지 않은 비용이 부과되지 않도록 하는 것이 모범 사례입니다.

7.1 - S3 객체 Lambda 액세스 포인트 삭제

  • S3 콘솔로 이동하고 왼쪽 탐색 창에서 객체 Lambda 액세스 포인트를 선택합니다.
  • 객체 Lambda 액세스 포인트 페이지에서 5.1단계에서 생성한 S3 객체 Lambda 액세스 포인트 왼쪽에 있는 라디오 버튼을 선택합니다.

삭제를 선택합니다.

표시되는 텍스트 필드에 이름을 입력하여 S3 객체 Lambda 액세스 포인트 삭제를 확인한 다음 삭제를 선택합니다.

7.2 - S3 액세스 포인트 삭제

  • S3 콘솔의 왼쪽 탐색 창에서 액세스 포인트를 선택합니다.
  • 3.1단계에서 생성한 S3 액세스 포인트로 이동하고 S3 액세스 포인트 이름 옆에 있는 라디오 버튼을 선택합니다.
  • 삭제를 선택합니다.

표시되는 텍스트 필드에 이름을 입력하여 액세스 포인트 삭제를 확인한 다음 삭제를 선택합니다.

7.3 - 테스트 객체 삭제

  • S3 콘솔로 이동하고 왼쪽 탐색 창에서 버킷 메뉴 옵션을 선택합니다. 먼저 테스트 버킷에서 테스트 객체를 삭제해야 합니다. 이 자습서에서 작업한 버킷의 이름을 선택합니다.
  • 테스트 객체 이름 왼쪽의 확인란을 선택한 후 삭제 버튼을 선택합니다.
  • 객체 삭제 페이지에서 삭제할 객체를 올바르게 선택했는지 확인하고 객체 영구 삭제 확인 상자에 delete를 입력합니다. 그런 다음 객체 삭제 버튼을 선택하여 계속 진행합니다.
그러면 삭제 성공 여부를 나타내는 배너가 표시됩니다.

7.4 - S3 버킷 삭제

  • 그런 다음 왼쪽 탐색 창의 S3 콘솔 메뉴에서 버킷을 선택합니다. 이 자습서용으로 생성한 소스 버킷의 왼쪽에 있는 라디오 버튼을 선택한 후 삭제 버튼을 선택합니다.

경고 메시지를 살펴봅니다. 이 버킷을 삭제하려면 버킷 삭제 확인 상자에 버킷 이름을 입력하고 버킷 삭제를 선택합니다.

7.5 - Lambda 함수 삭제

  • AWS Lambda 콘솔의 왼쪽 탐색 창에서 함수를 선택합니다.
  • 4.3단계에서 생성한 함수 이름 왼쪽에 있는 확인란을 선택합니다.
  • 작업을 선택하고 삭제를 선택합니다. 함수 삭제 대화 상자에서 삭제를 선택합니다.

결론

축하합니다! Amazon S3 객체 Lambda를 사용하여 검색 시 이미지에 워터마크를 동적으로 추가하고 처리된 이미지를 요청 클라이언트에 다시 전달하는 방법을 알아보았습니다. S3 GET, HEAD 및 LIST 요청에서 반환된 데이터를 수정하도록 사용 사례에 맞게 Lambda 함수를 사용자 지정할 수 있습니다. 일반적인 사용 사례로 호출자별 세부 정보를 사용한 워터마크 사용자 지정, 민감한 데이터 마스킹, 특정 데이터 행 필터링, 다른 데이터베이스의 정보로 데이터 보강, 데이터 형식 변환 등이 있습니다.

이 페이지의 내용이 도움이 되었나요?

다음 단계