Amazon Web Services 한국 블로그

Amazon CloudFront와 Amazon S3 Object Lambda 기반 맞춤형 콘텐츠 제공하기

Amazon S3 Object Lambda를 사용하면 Amazon S3에서 검색된 데이터가 애플리케이션으로 반환될 때 자체 코드를 사용하여 처리할 수 있습니다. S3 Object Lambda에는 출시 시점에 사용할 수 있었던 S3 GET 요청에 대한 지원에 더해 S3 HEAD 및 LIST API 요청에 자체 코드를 추가하는 기능과 같은 새로운 기능이 지속적으로 추가되었습니다.

오늘 S3 Object Lambda 액세스 포인트에 대한 별칭(Alias)이 출시됩니다. 이제 S3 Object Lambda 액세스 포인트가 생성될 때 별칭이 자동으로 생성되며 Amazon S3에 저장된 데이터에 액세스하기 위해 버킷 이름을 사용하는 모든 위치에서 버킷 이름과 상호 교환하여 사용할 수 있습니다. 따라서 애플리케이션에서 S3 Object Lambda에 대해 알지 못해도 별칭을 버킷 이름으로 간주할 수 있습니다.

아키텍처 다이어그램.

이제 S3 Object Lambda 액세스 포인트 별칭을 Amazon CloudFront 배포의 오리진으로 사용하여 최종 사용자를 위한 데이터를 맞춤화하거나 사용자 지정할 수 있습니다. 이를 사용하여 자동 이미지 크기 조정을 구현하거나 콘텐츠를 다운로드할 때 콘텐츠에 태그를 지정하거나 주석을 달 수 있습니다

예를 들어, JPEG 또는 PNG와 같은 이전 형식을 사용하는 이미지가 여전히 많은데, 트랜스코딩 함수를 사용하여 WebP, BPG 또는 HEIC와 같은 보다 효율적인 형식으로 이미지를 제공할 수 있습니다. 디지털 이미지에는 메타데이터가 포함됩니다. 이 메타데이터를 제거하는 함수를 구현하면 데이터 프라이버시 요구 사항을 충족하는 데 도움이 됩니다.

아키텍처 다이어그램.

이제 실제로 어떻게 작동하는지 알아보겠습니다. 먼저 텍스트를 사용하는 간단한 예제를 보여드리겠습니다. AWS Management Console에서 따라할 수 있습니다. 그런 다음 이미지를 처리하는 고급 사용 사례를 구현합니다.

S3 Object Lambda 액세스 포인트를 CloudFront 배포의 오리진으로 사용
간단한 설명을 위해 원본 파일의 모든 텍스트를 대문자로 바꾸는, 시작 게시물의 동일한 애플리케이션을 사용합니다. 이번에는 S3 Object Lambda 액세스 포인트 별칭을 사용하여 CloudFront를 통한 퍼블릭 배포를 설정합니다.

시작 게시물과 동일한 단계에 따라 S3 Object Lambda 액세스 포인트와 Lambda 함수를 생성합니다. Python 3.8 이상의 Lambda 런타임에는 request 모듈이 포함되어 있지 않으므로 Python 표준 라이브러리urlopen을 사용하도록 함수 코드를 업데이트합니다.

import boto3
from urllib.request import urlopen

s3 = boto3.client('s3')

def lambda_handler(event, context):
  print(event)

  object_get_context = event['getObjectContext']
  request_route = object_get_context['outputRoute']
  request_token = object_get_context['outputToken']
  s3_url = object_get_context['inputS3Url']

  # Get object from S3
  response = urlopen(s3_url)
  original_object = response.read().decode('utf-8')

  # Transform object
  transformed_object = original_object.upper()

  # Write object back to S3 Object Lambda
  s3.write_get_object_response(
    Body=transformed_object,
    RequestRoute=request_route,
    RequestToken=request_token)

  return

작동을 테스트하기 위해 버킷과 S3 Object Lambda 액세스 포인트를 통해 동일한 파일을 엽니다. S3 콘솔에서 버킷을 선택하고 이전에 업로드한 샘플 파일(s3.txt)을 선택한 후 Open(열기)을 선택합니다.

콘솔 스크린샷.

새 브라우저 탭이 열리고(브라우저에서 팝업 차단을 사용 중지해야 할 수 있음) 대/소문자가 혼합된 텍스트가 있는 원본 파일이 표시됩니다.

Amazon Simple Storage Service(S3)는 객체 스토리지 서비스로...

탐색 창에서 Object Lambda Access Points(객체 Lambda 액세스 포인트)를 선택하고 드롭다운에서 이전에 사용한 AWS 리전을 선택합니다. 그런 다음 방금 생성한 S3 Object Lambda 액세스 포인트를 검색합니다. 이전과 동일한 파일을 선택하고 Open(열기)을 선택합니다.

콘솔 스크린샷.

새 탭에서 텍스트는 Lambda 함수에 의해 처리되어 이제 모두 대문자로 표시됩니다.

AMAZON SIMPLE STORAGE SERVICE(S3)는 객체 스토리지 서비스로...

이제 S3 객체 Lambda 액세스 포인트가 올바르게 구성되었으므로 CloudFront 배포를 생성할 수 있습니다. 그 전에 S3 콘솔의 S3 Object Lambda 액세스 포인트 목록에서 자동으로 생성된 객체 Lambda 액세스 포인트 별칭을 복사합니다.

콘솔 스크린샷.

CloudFront 콘솔의 탐색 창에서 Distributions(배포)를 선택한 다음Create distribution(배포 생성)을 선택합니다. Origin domain(오리진 도메인)에는 S3 Object Lambda 액세스 포인트 별칭과 리전을 사용합니다. 도메인의 전체 구문은 다음과 같습니다.

ALIAS.s3.REGION.amazonaws.com

콘솔 스크린샷.

S3 객체 Lambda 액세스 포인트는 퍼블릭일 수 없으므로 CloudFront 오리진 액세스 제어(OAC)를 사용하여 오리진에 대한 요청을 인증합니다. Origin access(오리진 액세스)에서는 Origin access control settings(오리진 액세스 제어 설정)를 선택하고 Create control setting(제어 설정 생성)을 선택합니다. 제어 설정의 이름을 쓰고 Origin type(오리진 유형) 드롭다운에서 Sign requests(요청 서명)S3를 선택합니다.

콘솔 스크린샷.

이제 Origin access control settings(오리진 제어 설정)에 방금 만든 구성이 사용됩니다.

콘솔 스크린샷.

S3 Object Lambda를 통해 전달되는 요청 수를 줄이기 위해 Origin Shield를 사용하도록 설정하고 사용 중인 리전과 가장 가까운 Origin Shield 리전을 선택합니다. 그런 다음 CachingOptimized(캐싱 최적화) 캐시 정책을 선택하고 배포를 생성합니다. 배포가 진행되는 동안 배포에 사용된 리소스에 대한 권한을 업데이트합니다.

S3 Object Lambda 액세스 포인트를 CloudFront 배포의 오리진으로 사용하기 위한 권한 설정
먼저 S3 Object Lambda 액세스 포인트에서 CloudFront 배포에 대한 액세스 권한을 부여해야 합니다. S3 콘솔에서 S3 Object Lambda 액세스 포인트를 선택하고 Permissions(권한) 탭에서 다음과 같이 정책을 업데이트합니다.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "cloudfront.amazonaws.com"
            },
            "Action": "s3-object-lambda:Get*",
            "Resource": "arn:aws:s3-object-lambda:REGION:ACCOUNT:accesspoint/NAME",
            "Condition": {
                "StringEquals": {
                    "aws:SourceArn": "arn:aws:cloudfront::ACCOUNT:distribution/DISTRIBUTION-ID"
                }
            }
        }
    ]
}

또한 지원하는 액세스 포인트는 S3 Object Lambda를 통해 호출될 때 CloudFront에 대한 액세스를 허용해야 합니다. 액세스 포인트를 선택하고 Permissions(권한) 탭에서 정책을 업데이트합니다.

{
    "Version": "2012-10-17",
    "Id": "default",
    "Statement": [
        {
            "Sid": "s3objlambda",
            "Effect": "Allow",
            "Principal": {
                "Service": "cloudfront.amazonaws.com"
            },
            "Action": "s3:*",
            "Resource": [
                "arn:aws:s3:REGION:ACCOUNT:accesspoint/NAME",
                "arn:aws:s3:REGION:ACCOUNT:accesspoint/NAME/object/*"
            ],
            "Condition": {
                "ForAnyValue:StringEquals": {
                    "aws:CalledVia": "s3-object-lambda.amazonaws.com"
                }
            }
        }
    ]
}

S3 버킷은 지원 액세스 포인트에 대한 액세스를 허용해야 합니다. 버킷을 선택하고 Permissions(권한) 탭에서 정책을 업데이트합니다.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "*"
            },
            "Action": "*",
            "Resource": [
                "arn:aws:s3:::BUCKET",
                "arn:aws:s3:::BUCKET/*"
            ],
            "Condition": {
                "StringEquals": {
                    "s3:DataAccessPointAccount": "ACCOUNT"
                }
            }
        }
    ]
}

마지막으로 CloudFront에서 Lambda 함수를 호출할 수 있어야 합니다. Lambda 콘솔에서 S3 Object Lambda에 사용되는 Lambda 함수를 선택한 다음 Configuration(구성) 탭에서 Permissions(권한)를 선택합니다. Resource-based policy statements(리소스 기반 정책 문) 섹션에서 Add permissions(권한 추가)를 선택하고 AWS Account(AWS 계정)를 선택합니다.

고유한 Statement ID(문 ID)를 입력합니다. 그런 다음 cloudfront.amazonaws.comPrincipal(보안 주체)로 선택하고 Action(작업) 드롭다운에서 lambda:InvokeFunction을 선택한 다음 Save(저장)를 선택합니다. 향후에는 이 단계가 간소화될 것입니다. 현재 간소화 작업을 진행하고 있습니다. 사용할 수 있게 되면 이 게시물을 업데이트할 것입니다.

CloudFront 배포 테스트
배포가 완료되면 이전에 사용한 것과 동일한 샘플 파일을 사용하여 설정이 작동하는지 테스트합니다. CloudFront 콘솔에서 배포를 선택하고 Distribution domain name(배포 도메인 이름)을 복사합니다. 브라우저에서 탐색 표시줄에 https://DISTRIBUTION_DOMAIN_NAME/s3.txt를 입력하여 CloudFront로 요청을 전송하고 S3 객체 Lambda로 파일을 처리할 수 있습니다. 모든 정보를 빠르게 가져오려면 curl-i 옵션을 사용하여 응답의 HTTP 상태와 헤더를 확인합니다.

curl -i https://DISTRIBUTION_DOMAIN_NAME/s3.txt

HTTP/2 200 
content-type: text/plain
content-length: 427
x-amzn-requestid: a85fe537-3502-4592-b2a9-a09261c8c00c
date: Mon, 06 Mar 2023 10:23:02 GMT
x-cache: Miss from cloudfront
via: 1.1 a2df4ad642d78d6dac65038e06ad10d2.cloudfront.net (CloudFront)
x-amz-cf-pop: DUB56-P1
x-amz-cf-id: KIiljCzYJBUVVxmNkl3EP2PMh96OBVoTyFSMYDupMd4muLGNm2AmgA==

AMAZON SIMPLE STORAGE SERVICE(S3)는 객체 스토리지 서비스로...

제대로 작동합니다! 예상한 대로 Lambda 함수로 처리되는 콘텐츠가 모두 대문자로 표시됩니다. 이 요청은 배포에 대한 첫 번째 호출이므로 캐시에서 반환되지 않았습니다(x-cache: Miss from cloudfront). 이 요청에서는 S3 Object Lambda를 통해 제가 제공한 Lambda 함수를 사용하여 파일이 처리되었습니다.

동일한 요청을 다시 시도해 보겠습니다.

curl -i https://DISTRIBUTION_DOMAIN_NAME/s3.txt

HTTP/2 200 
content-type: text/plain
content-length: 427
x-amzn-requestid: a85fe537-3502-4592-b2a9-a09261c8c00c
date: Mon, 06 Mar 2023 10:23:02 GMT
x-cache: Hit from cloudfront
via: 1.1 145b7e87a6273078e52d178985ceaa5e.cloudfront.net (CloudFront)
x-amz-cf-pop: DUB56-P1
x-amz-cf-id: HEx9Fodp184mnxLQZuW62U11Fr1bA-W1aIkWjeqpC9yHbd0Rg4eM3A==
age: 3

AMAZON SIMPLE STORAGE SERVICE(S3)는 객체 스토리지 서비스로...

이번에는 콘텐츠가 CloudFront 캐시(x-cache: Hit from cloudfront)에서 반환되며 더 이상 S3 객체 Lambda로 더 이상 처리되지 않았습니다. CloudFront 배포는 S3 Object Lambda를 오리진으로 사용하여 Lambda 함수로 처리된 콘텐츠를 제공합니다. 이 처리된 콘텐츠를 캐시하여 지연 시간을 줄이고 비용을 최적화할 수 있습니다.

S3 Object Lambda와 CloudFront를 사용하여 이미지 크기 변경
이 게시물의 시작 부분에서 언급했듯이 S3 Object Lambda와 CloudFront를 사용하여 구현할 수 있는 사용 사례 중 하나로 이미지 변환이 있습니다. 원하는 너비와 높이를 쿼리 파라미터(각각 wh)로 전달하여 이미지 크기를 동적으로 변경할 수 있는 CloudFront 배포를 만들어 보겠습니다. 예를 들면 다음과 같습니다.

https://DISTRIBUTION_DOMAIN_NAME/image.jpg?w=200&h=150

이 설정이 제대로 작동하려면 CloudFront 배포에서 2가지를 변경해야 합니다. 먼저 캐시 키에 쿼리 매개 변수를 포함하는 새 캐시 정책을 만듭니다. CloudFront 콘솔의 탐색 창에서 Policies(정책)를 선택합니다. Cache(캐시) 탭에서 Create cache policy(캐시 정책 생성)를 선택합니다. 그런 다음 캐시 정책의 이름을 입력합니다.

콘솔 스크린샷.

Cache key settings(캐시 키 설정)Query settings(쿼리 설정)에서 Include the following query parameters(다음 쿼리 파라미터 포함) 옵션을 선택하고 w(너비)와 h(높이)를 추가합니다.

콘솔 스크린샷.

그런 다음 배포의 Behaviors(동작) 탭에서 기본 동작을 선택하고 Edit(편집)를 선택합니다.

여기서 Cache key and origin requests(캐시 키 및 오리진 요청) 섹션을 업데이트합니다.

  • Cache policy(캐시 정책)에서 wh 쿼리 파라미터를 캐시 키에 포함하는 새 캐시 정책을 사용합니다.
  • Origin request policy(오리진 요청 정책)에서는 오리진으로 쿼리 파라미터를 전달하는 AllViewerExceptHostHeader 관리형 정책을 사용합니다.

콘솔 스크린샷.

이제 Lambda 함수 코드를 업데이트할 수 있습니다. 이미지 크기를 변경하기 위해 이 함수는 Lambda에 업로드할 때 함수와 함께 패키징되어야 하는 Pillow 모듈을 사용합니다. AWS SAM CLI 또는 AWS CDK와 같은 도구를 사용하여 함수를 배포할 수 있습니다. 이전 예와 비교하여 이 함수는 HTTP 오류(예: 버킷에서 콘텐츠를 찾을 수 없는 경우)도 처리하고 반환합니다.

import io
import boto3
from urllib.request import urlopen, HTTPError
from PIL import Image

from urllib.parse import urlparse, parse_qs

s3 = boto3.client('s3')

def lambda_handler(event, context):
    print(event)

    object_get_context = event['getObjectContext']
    request_route = object_get_context['outputRoute']
    request_token = object_get_context['outputToken']
    s3_url = object_get_context['inputS3Url']

    # Get object from S3
    try:
        original_image = Image.open(urlopen(s3_url))
    except HTTPError as err:
        s3.write_get_object_response(
            StatusCode=err.code,
            ErrorCode='HTTPError',
            ErrorMessage=err.reason,
            RequestRoute=request_route,
            RequestToken=request_token)
        return

    # Get width and height from query parameters
    user_request = event['userRequest']
    url = user_request['url']
    parsed_url = urlparse(url)
    query_parameters = parse_qs(parsed_url.query)

    try:
        width, height = int(query_parameters['w'][0]), int(query_parameters['h'][0])
    except (KeyError, ValueError):
        width, height = 0, 0

    # Transform object
    if width > 0 and height > 0:
        transformed_image = original_image.resize((width, height), Image.ANTIALIAS)
    else:
        transformed_image = original_image

    transformed_bytes = io.BytesIO()
    transformed_image.save(transformed_bytes, format='JPEG')

    # Write object back to S3 Object Lambda
    s3.write_get_object_response(
        Body=transformed_bytes.getvalue(),
        RequestRoute=request_route,
        RequestToken=request_token)

    return

트레비 분수에서 찍은 사진을 소스 버킷에 업로드합니다. 먼저 작은 썸네일(200×150픽셀)을 생성합니다.

https://DISTRIBUTION_DOMAIN_NAME/trevi-fountain.jpeg?w=200&h=150

크기 200x150픽셀의 트레비 분수 사진.

이제 약간 더 큰 버전(400 x 300 픽셀)을 요청합니다.

https://DISTRIBUTION_DOMAIN_NAME/trevi-fountain.jpeg?w=400&h=300

크기 400x300픽셀의 트레비 분수 사진.

예상대로 작동합니다. 특정 크기의 첫 번째 호출은 Lambda 함수에서 처리됩니다. 너비와 높이가 동일한 추가 요청은 CloudFront 캐시에서 제공됩니다.

가용성 및 요금
S3 Object Lambda 액세스 포인트의 별칭은 현재 모든 상용 AWS 리전에서 사용할 수 있습니다. 별칭에는 추가 비용이 없습니다. S3 Object Lambda를 사용하면 데이터를 처리하는 데 필요한 Lambda 컴퓨팅 및 요청 요금과 S3 Object Lambda가 애플리케이션으로 반환하는 데이터에 대한 요금을 지불하게 됩니다. 또한 Lambda 함수에서 호출되는 S3 요청에 대해서도 요금을 지불하게 됩니다. 자세한 내용은 Amazon S3 요금을 참조하세요.

이제 S3 객체 Lambda 액세스 포인트가 생성될 때 자동으로 별칭이 생성됩니다. 기존 S3 Object Lambda 액세스 포인트의 경우 별칭이 자동으로 할당되어 바로 사용할 수 있습니다.

이제 기존 애플리케이션에서 S3 Object Lambda를 더 쉽게 사용할 수 있습니다. 별칭은 여러 가지 새로운 가능성을 열어줍니다. 예를 들어 CloudFront의 별칭을 사용하여 Markdown의 콘텐츠를 HTML로 변환하거나, 이미지의 크기를 변경하고 워터마크를 표시하거나, 텍스트, 이미지 및 문서에서 개인 식별 정보(PII)를 마스킹하는 웹 사이트를 만들 수 있습니다.

Danilo