はじめに
Lambda@Edgeで画像リサイズしたので、対応したことをメモしておきます。
下記の方のお役に立てば嬉しいです
SAM を使ってますが、Serverless Framework でも対応可能なはずです。
さて、AWS公式ブログのサンプルを見ても、Node ランタイムが v6 系と古く CommonJS の記述になっていました。
しかし、ランタイムにも EOL はあり、いつかはメンテナンスされなくなります。
どうせ今から作成するなら現時点で最新の Node.js 22 のランタイムで動かしたいと思いました。Node.js 22 はデフォルトで ESModule を採用していたり、sharp 自体の issue で躓いたポイントがあるので、一緒に見ていきましょう。
まずは、Lambda@Edge の仕組みと、制約について見ていきます。ご存知の方は読み飛ばしてください。
Lambda@Edge の仕組み
Lambda@Edge を使うと、よりクライアントに近い場所でLambdaを動かすことができ、CloudFront のキャッシュと組み合わせることで更にパフォーマンスが良いスケーラブルな処理できます。
Lambda@Edge はクライアント(End user)からCloudFront経由でサーバー(Origin server)へリクエストが往復する経路ごとに、それぞ4つのタイミングでを設定することができます。

CloudFront ビューワーリクエスト – CloudFront がビューワーからリクエストを受け取った後、リクエストされたオブジェクトがエッジキャッシュにあるかどうかを確認する前に関数が実行されます。
CloudFront ビューワーレスポンス – リクエストされたオブジェクトがビューワーに返される前に関数が実行されます。オブジェクトがすでにエッジキャッシュに存在するかどうかに関係なく関数が実行されます。
CloudFront オリジンリクエスト – CloudFront がリクエストをオリジンに転送する場合にのみ関数が実行されます。リクエストされたオブジェクトがエッジキャッシュにある場合は実行されません。
CloudFront オリジンレスポンス – CloudFront がオリジンからのレスポンスを受け取った後、レスポンス内のオブジェクトをキャッシュする前に関数が実行されます。
下図は、実際の CloudFront の関連付け項目です。
この記事ではオリジンサーバーに S3 を設定し、オリジンレスポンスでの webp 変換のみを行います。
Lambda@Edge の制約
Lambda@Edge は通常の Lambda と違って制約があります。私が主に感じた制約は下記の項目ですが、他にもあるので確認しておくと良いでしょう。
- 米国東部 (バージニア北部) リージョン
us-east-1
にデプロイする必要がある - Lambda 関数の番号付きバージョンを指定する必要がある($LATEST やエイリアスでのデプロイ切り替えできない)
- 環境変数を使えない(開発環境や本番環境ごとの設定の違いをどこかで対応する必要がある。私が対応した方法も後述します。)
設定方法
Lambda プロジェクトの作成
ここでは SAM を使っていますが、Serverless Framework でも大体の項目は同じなので読み替えていただければと思います。(ざっくり説明すると samconfig.toml と template.yaml が serverless.ts に相当します。)
.
├── .aws-sam(←ビルドすると生成される)
│ └─── build
│ └─── OriginResponseFunction(←設定したリソース名。これをデプロイする)
│ ├── node_modules
│ ├── package.json
│ └── index.js
├── src
│ ├── origin-response
│ │ ├── node_modules
│ │ ├── Dockerfile(←ビルドで使う)
│ │ ├── Makefile(←ビルドで使う)
│ │ ├── index.js
│ │ ├── package-lock.json
│ │ └── package.json
│ └── viewer-request(←今後、必要になれば)
├── samconfig.toml
└── template.yaml
ディレクトリ階層は任意ですが、今後オリジンレスポンス以外も追加しやすい構造になっていれば良いと思います。
ライブラリのインストール
下記のコマンドでインストールします。sharp については後述するバージョンに注意してください。
npm install @aws-sdk/client-s3 sharp@0.32.6
package.json
ライブラリのインストールが終わると、下記のように dependencies
が変更されています。
そして、Lambda が ESModule と分かるように "type": "module"
を設定し、エントリーポイントであるindex.js を main に指定しておきます。(自身のファイルが app.js なら、以後、読み替えてください。)
{
"name": "origin-response",
"version": "1.0.0",
"description": "Origin response に設定する画像リサイズ Lambda Edge",
"private": true,
"type": "module",
"main": "index.js",
"dependencies": {
"@aws-sdk/client-s3": "^3.779.0",
"sharp": "^0.32.6"
}
}
sharp のプラットフォームとアーキテクチャの依存
sharp は、Node.js のための高性能な画像処理ライブラリです。
sharp は C++ で実装された部分があるので、それぞれの動作環境に向けたビルドが必要になります。今回は Lambda の Node.js 22 ランタイムは Amazon Linux 2023 上で動くのと、x86_64 アーキテクチャで動かすので、下記のように別個の npm install コマンドが必要です。 (arm64 アーキテクチャでの動作確認はしていません。)
npm rebuild --arch=x64 --platform=linux sharp
Serverless Framework であれば、packagerOptions
オプションに記述できますが、これをビルド時に行う方法については後述します。
2025年4月5日時点の注意点
sharp の最新バージョン v0.33.5 では、指定したクロスプラットフォームの設定が反映されませんでした。現時点では v0.33 系ではなく、v0.32 系を指定すると良いでしょう。
v0.32.6 | v0.33.5 |
---|---|
![]() |
![]() |
samconfig.toml
Lambda@Edgeの制約で、米国東部 (バージニア北部) リージョン us-east-1
にデプロイする必要があるので、設定ファイルにリージョン指定をしておきます。
[prd.deploy.parameters]
region = "us-east-1"
template.yaml
Resources:
OriginResponseFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub "origin-response-${Env}"
CodeUri: src/origin-response
Role: !GetAtt OriginResponseFunctionIamRole.Arn
Handler: index.handler
Runtime: nodejs22.x
AutoPublishAlias: latest
Architectures:
- x86_64
Metadata:
BuildMethod: makefile
OriginResponseFunctionIamRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: 'Allow'
Principal:
Service:
- 'lambda.amazonaws.com'
- 'edgelambda.amazonaws.com'
Action: 'sts:AssumeRole'
Path: '/service-role/'
ManagedPolicyArns:
- 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
Policies:
- PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: 'Allow'
Action:
- s3:GetObject
Resource: '__対象のバケットを指定してください__'
- Effect: 'Allow'
Action:
- 'logs:CreateLogGroup'
- 'logs:CreateLogStream'
- 'logs:PutLogEvents'
Resource: '*'
Lambda にアタッチするロールの AssumeRolePlicyDocument の Principal に edgelambda.amazonaws.com
を追加することで、Lambda@Edge がこのロールを使えるようになります。
普段と違う点としては、ファンクションリソースの Metadata の BuildMethod に makefile
を指定したことです。(※小文字です)
これは、通常の sam build
コマンドだと、上記で説明した sharp 専用のビルドを挟めなかったからです。makefileを指定することで、CodeUri
で指定したディレクトリにある Makefile をビルド時に実行してくれます。
Makefile と Dockerfile
ビルドの仕組みは公式での説明を引用します。
画像リサイズ関数は ‘libvips’ ネイティブ拡張を必要とする ‘Sharp’ モジュールを使用します。 Lambda 関数のコードは Lambda 実行環境で動作するように依存関係を含んだ状態でビルドおよびパッケージ化されている必要があります。これらを実現する方法のひとつが、あなたの環境に ‘docker’ をインストールしてから、 Docker コンテナを使ってパッケージをローカルにビルドすることです。
Node.js 22 ランタイムは Amazon Linux 2023 上で動くので、ベースイメージに amazonlinux:2023
を使います。
FROM amazonlinux:2023
RUN yum install -y gcc-c++
RUN curl-minimal -sL https://rpm.nodesource.com/setup_22.x | bash - && \
yum install -y nodejs
WORKDIR /build
curl-minimal
は Amazon Linux 2023
にデフォルトで入ってる curl の軽量版みたいです。このイメージから作成したコンテナで npm install などをしてビルドするので nodejs をいれておきます。
さて、それでは実際にこの Dockerfile を使った Makefile 内の説明をしていきます。
build-OriginResponseFunction:
docker build --tag amazonlinux:nodejs --platform linux/amd64 .
docker run --platform linux/amd64 --rm --volume ${PWD}/src/origin-response:/build amazonlinux:nodejs /bin/bash -c "source ~/.bashrc; npm install --only=prod; npm rebuild --arch=x64 --platform=linux sharp"
cp -r ${PWD}/src/origin-response/node_modules $(ARTIFACTS_DIR)/node_modules
cp -r ${PWD}/src/origin-response/index.js $(ARTIFACTS_DIR)
cp -r ${PWD}/src/origin-response/package.json $(ARTIFACTS_DIR)
BuildMethod に makefile
を指定すると、build-{リソース名}
のコマンドを実行する仕様になっています。それでは中身を説明してきます。
- Makefileと同じ階層にあるDockerfileをまずビルドします(タグ名:
amazonlinux:nodejs
) - ビルドしたイメージを使ってパッケージをインストールしていきます。
--volume ${PWD}/src/origin-response:/build
でコードベースをマウントして npm install をすることで、package-lock.json と node_modules を作成してくれます
※この時に、 sharp で必要なnpm rebuild --arch=x64 --platform=linux sharp
を実行しています。 - インストールができたら、デプロイに必要なアーティファクトを
ARTIFACTS_DIR
配下にコピーします。ARTIFACTS_DIR
環境変数は組み込まれており、今回の例では.aws-sam/build/OriginResponseFunction
が指定されています
index.js
CommonJS の書き方であった require
: ライブラリの読み込み方やハンドラーの書き方 exports.handler
が変わっています。
また、AWS SDK for JavaScript も v2 から v3 に変わるので確認しておきましょう。
import sharp from 'sharp';
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
const BUCKET = 'dev-example-bucket';
export const handler = async (event) => {
const {Records: [{cf: { request, response }}]}} = event;
const client = new S3Client({ region: 'ap-northeast-1' });
const path = request.uri;
const key = path.slice(1);
const command = new GetObjectCommand({ Bucket: BUCKET, Key: key });
const s3Response = await client.send(command);
const body = await s3Response.Body.transformToByteArray();
const sharpBody = sharp(body);
const buffer = await sharpBody
.webp({ quality: 75 })
.resize({ width: 100, height: 100})
.toBuffer();
response.status = '200';
response.headers['content-type'] = [{ key: 'Content-Type', value: 'image/webp' }];
response.body = buffer.toString('base64');
response.bodyEncoding = 'base64';
return response;
}
上の例では、dev-example-bucket
バケットが東京リージョンにあるので new S3Client({ region: 'ap-northeast-1' })
にしています。バケットのあるリージョンごとに変更してください。
私が勘違いしたポイントとしては、S3 がオリジンサーバーなので response.body に画像データが含まれているので改めて S3 に取得する必要はないと思っていましたが、Lambda@Edge の仕様でレスポンスの中身を参照することはできませんでした。
(補足)
S3から再取得しているので、キャッシュがない場合のパフォーマンスが悪いですが、これを解決しているのが公式ブログのやり方で、sharp 変換した画像を S3 にアップロードしています。これにより、次のアクセス時にはリサイズ処理が不要になります。一方で、Request viewer でのリサイズサイズのバリデーションをつけないと、不要にリサイズされた画像が S3 にアップロードされてしまうので、セットで使ったほうがよいでしょう。
CloudFront
対象のディストリビューションの S3 をオリジンサーバーに指定したビヘイビアで、Lambda@Edge を関連付けてあげましょう。$LATEST やエイリアスの指定は不可で、バージョンの数字で指定する必要があります。
設定後、ブラウザから動作確認を行い、リサイズされ Response の Content-Type が image/webp
になっていれば成功です。
環境変数の切り替え対策
Lambda@Edge では環境変数を使えません。なので、先のindex.js の S3 バケットを process.env.BUCKET
のように外部から切り替えできません。
そこで、ビルドしたアーティファクトの index.js の中身を置換することで対策しました。具体的に説明します。
まず、index.js のバケット設定を書き換えます。
- const BUCKET = 'dev-example-bucket';
+ const BUCKET = '__BUCKET__';
こうすることで、デプロイ前に sed コマンドで置換することができます。
sam build
sed -i 's/__BUCKET__/dev-example-bucket/g' .aws-sam/build/OriginResponseFunction/index.js
sam deploy
バケットの設定箇所を集約できないデメリットがあるので、もっと良い方法がありましたら教えてください
おわりに
sharp のクロスプラットフォーム問題によるバージョン固定については、今後もウォッチしていく必要がありますが、Lambda@Edgeで画像リサイズすることができました。
AWS にも Cloudflare の Image Resizing のような機能があったら嬉しいので、今後の機能リリースを期待しています!
それでは、この記事が良かったよ〜って方は、チャンネル登録、高評価、お願いしま〜す!
参考
- AWS公式ブログ node v6 での実装例が書いてあります
- Lambda のランタイム一覧
- makefile の設定方法が記述してあります