Amazon Web Services ブログ

新着 – Amazon CloudFront で Amazon S3 Object Lambda を使用してエンドユーザーのためにコンテンツをカスタマイズする

S3 Object Lambda を使用すると、Amazon S3 から取得したデータをアプリケーションに返すときに、独自のコードを使用してそのデータを処理できます。時間が経過するのに伴って、リリース時に利用可能だった S3 GET リクエストのサポートに加えて、S3 HEAD および LIST API リクエストに独自のコードを追加する機能など、新しい機能が S3 Object Lambda に追加されました。

3月14日は、S3 Object Lambda アクセスポイント用のエイリアスをリリースしました。S3 Object Lambda アクセスポイントの作成時にエイリアスが自動的に生成されるようになりました。これらのエイリアスは、バケット名を使用して Amazon S3 に保存されているデータにアクセスする場合は常にバケット名と相互に代替可能です。そのため、アプリケーションが S3 Object Lambda について知る必要はなく、エイリアスをバケット名とみなすことができます。

アーキテクチャ図。

S3 Object Lambda アクセスポイントのエイリアスを Amazon CloudFront ディストリビューションのオリジンとして使用して、エンドユーザーのためにデータを調整またはカスタマイズできるようになりました。これを使用して、画像の自動サイズ変更を実装したり、ダウンロード時にコンテンツにタグや注釈を付けたりできます。多くの画像はまだ JPEGPNG などの古い形式を使用しており、トランスコーディング機能を使用して、WebPBPGHEIC などのより効率的な形式で画像を配信できます。デジタル画像にはメタデータが含まれていますが、データプライバシーに関する要件を満たすのに役立つよう、メタデータを削除する機能を実装できます。

アーキテクチャ図。

ここからは、実際にどのように機能するのかを見ていきましょう。最初に、AWS マネジメントコンソールのみを使用して、簡単なテキストを使用したシンプルな例を示します。その後、画像処理のより高度なユースケースを実装します。

S3 Object Lambda アクセスポイントを CloudFront ディストリビューションのオリジンとして使用する
わかりやすくするために、リリースに関する記事でも同じアプリケーションを使用しています。これは、元のファイルのすべてのテキストを大文字に変更します。今回は、S3 Object Lambda アクセスポイントのエイリアスを使用して、CloudFront でパブリックディストリビューションを設定します。

リリースに関する記事と同じステップを実行して、S3 Object Lambda アクセスポイントと Lambda 関数を作成します。Python 3.8 以降の Lambda ランタイムには requests モジュールが含まれていないため、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']

  # S3 からオブジェクトを取得します
  response = urlopen(s3_url)
  original_object = response.read().decode('utf-8')

  # オブジェクトを変換します
  transformed_object = original_object.upper()

  # オブジェクトを 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 (Amazon S3) is an object storage service that offers...

ナビゲーションペインから [Object Lambda アクセスポイント] (Object Lambda アクセスポイント) を選択し、ドロップダウンから以前に使用した AWS リージョンを選択します。その後、先ほど作成した S3 Object Lambda アクセスポイントを検索します。以前と同じファイルを選択して、[Open] (開く) を選択します。

コンソールのスクリーンショット。

新しいタブでは、テキストは Lambda 関数によって処理され、すべて大文字になっています。

AMAZON SIMPLE STORAGE SERVICE (AMAZON S3) IS AN OBJECT STORAGE SERVICE THAT OFFERS...

S3 Object Lambda アクセスポイントが正しく設定されたので、CloudFront ディストリビューションを作成できます。これを実行する前に、S3 コンソールの S3 Object Lambda アクセスポイントのリストで、自動的に作成された Object Lambda アクセスポイントのエイリアスをコピーします。

コンソールのスクリーンショット。

CloudFront コンソールでは、ナビゲーションペインで [Distributions] (ディストリビューション) を選択してから、[Create distribution] (ディストリビューションを作成) を選択します。[Origin domain] (オリジンドメイン) では、S3 Object Lambda アクセスポイントのエイリアスとリージョンを使用します。ドメインの完全な構文は次のとおりです。

ALIAS.s3.REGION.amazonaws.com

コンソールのスクリーンショット。

S3 Object 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 Region] (オリジンシールドリージョン) を選択します。その後、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) を入力します。その後、[Principal] (プリンシパル) として cloudfront.amazonaws.com と入力し、[Action] (アクション) ドロップダウンから lambda:InvokeFunction を選択して、[Save] (保存) を選択します。将来的にこのステップを簡素化できるよう取り組んでいます。利用可能になり次第、この記事を更新します。

CloudFront ディストリビューションをテストする
ディストリビューションがデプロイされたら、以前に使用したのと同じサンプルファイルでその設定が機能することをテストします。CloudFront コンソールで、ディストリビューションを選択し、[Distribution domain name] (ディストリビューションドメイン名) をコピーします。ブラウザを使用してナビゲーションバーに https://DISTRIBUTION_DOMAIN_NAME/s3.txt と入力すると、CloudFront にリクエストを送信し、S3 Object 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 (AMAZON S3) IS AN OBJECT STORAGE SERVICE THAT OFFERS...

適切に機能しています! 想定どおり、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 (AMAZON S3) IS AN OBJECT STORAGE SERVICE THAT OFFERS...

今回は、コンテンツが CloudFront キャッシュから返され (x-cache: Hit from cloudfront)、S3 Object Lambda によってさらに処理されることはありませんでした。S3 Object Lambda をオリジンとして使用することで、CloudFront ディストリビューションは Lambda 関数によって処理されたコンテンツを配信し、キャッシュしてレイテンシーを低減し、コストを最適化できます。

S3 Object Lambda と CloudFront を使用して画像のサイズを変更する
この記事の冒頭で言及したように、S3 Object Lambda と CloudFront を使用して実装できるユースケースの 1 つとして、画像の変換を挙げることができます。目的の幅と高さをクエリパラメータ (それぞれ 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 CLIAWS 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']

    # 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

    # クエリパラメータから幅と高さを取得します
    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

    # オブジェクトを変換します
    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')

    # オブジェクトを S3 Object Lambda に書き込みます
    s3.write_get_object_response(
        Body=transformed_bytes.getvalue(),
        RequestRoute=request_route,
        RequestToken=request_token)

    return

トレヴィの泉で撮影した写真をソースバケットにアップロードします。まず、小さなサムネイル (200 x 150 ピクセル) を生成します。

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

サイズが 200 x 150 ピクセルのトレヴィの泉の写真。

少し大きいバージョン (400 x 300 ピクセル) をリクエストしてみます。

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

サイズが 400 x 300 ピクセルのトレヴィの泉の写真。

想定どおりに機能しています。特定のサイズの最初の呼び出しは、Lambda 関数によって処理されます。同じ幅と高さのさらなるリクエストは、CloudFront キャッシュから提供されます。

利用可能なリージョンと料金
S3 Object Lambda アクセスポイント用のエイリアスは、現在、すべての商用 AWS リージョンでご利用いただけます。エイリアスには追加料金はかかりません。S3 Object Lambda では、データを処理するために必要な Lambda コンピューティングおよびリクエストの料金、ならびに S3 Object Lambda がアプリケーションに返すデータについての料金をお支払いいただきます。また、Lambda 関数によって呼び出される S3 リクエストの料金もお支払いいただきます。詳細については、「Amazon S3 の料金」を参照してください。

S3 Object Lambda アクセスポイントが作成されると、エイリアスが自動的に生成されるようになりました。既存の S3 Object Lambda アクセスポイントには、エイリアスが自動的に割り当てられ、すぐに使用できます。

既存のアプリケーションでより簡単に S3 Object Lambda を使用できるようになりました。また、エイリアスは多くの新しい可能性を生み出します。例えば、CloudFront でエイリアスを使用して、Markdown のコンテンツを HTML に変換したり、画像のサイズを変更してウォーターマークを付けたり、テキスト、画像、ドキュメントから個人を特定できる情報 (PII) をマスキングしたりするウェブサイトを作成できます。

CloudFront で S3 Object Lambda を使用してエンドユーザーのためにコンテンツをカスタマイズしましょう。

Danilo

原文はこちらです。