メインコンテンツまでスキップ
最新1mo ago

Image Delivery S3 + CloudFront + Lambda Proxy

Tip: このセットアップの役割: S3に画像をプライベートに保存し、Lambdaをスマートなプロキシレイヤーとして使用し、CloudFront CDNを通じてグローバルに配信します — 高速、低コスト、セキュアです。


📐 アーキテクチャ

User → CloudFront CDN → Lambda (proxy) → S3 Bucket (private)

LayerRole
S3すべての画像のプライベートオリジンストレージ
Lambdaプロキシ — 認証、リサイズ、ルーティング、フォーマット変換を処理
CloudFrontグローバルCDN — エッジロケーションからキャッシュして配信

Warning: S3をCloudFrontに直接公開しない理由は?
Lambdaをプロキシとして使用することで、認証、オンザフライの画像変換、署名付きURL生成、ルーティングロジック など、S3単独では実現できない制御が可能になります。


🔧 ステップバイステップのセットアップ

1 — プライベートS3バケットの作成

aws s3api create-bucket \
--bucket my-images-bucket \
--region us-east-1

aws s3api put-public-access-block \
--bucket my-images-bucket \
--public-access-block-configuration \
"BlockPublicAcls=true,IgnorePublicAcls=true,\
BlockPublicPolicy=true,RestrictPublicBuckets=true"

🔒 バケットを完全にプライベートに保ちます。CloudFront + Lambdaが画像にアクセスする唯一の方法になります。


2 — Lambda プロキシ関数の作成

この関数はCloudFrontからリクエストを受け取り、S3から画像をフェッチして返します。

// index.mjs (Node.js 20.x, ES Module)
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";

const s3 = new S3Client({ region: "us-east-1" });
const BUCKET = "my-images-bucket";

export const handler = async (event) => {
// パスから画像キーを抽出 例: /images/photo.jpg → images/photo.jpg
const key = decodeURIComponent(event.rawPath.slice(1));

try {
const command = new GetObjectCommand({ Bucket: BUCKET, Key: key });
const s3Response = await s3.send(command);

// ボディをバッファにストリーム
const chunks = [];
for await (const chunk of s3Response.Body) chunks.push(chunk);
const imageBuffer = Buffer.concat(chunks);

return {
statusCode: 200,
headers: {
"Content-Type": s3Response.ContentType || "image/jpeg",
"Cache-Control": "public, max-age=31536000", // 1年キャッシュ
},
body: imageBuffer.toString("base64"),
isBase64Encoded: true,
};
} catch (err) {
return { statusCode: 404, body: "Image not found" };
}
};

関数をデプロイします:

zip -r function.zip index.mjs node_modules/

aws lambda create-function \
--function-name image-proxy \
--runtime nodejs20.x \
--handler index.handler \
--role arn:aws:iam::YOUR_ACCOUNT:role/lambda-s3-role \
--zip-file fileb://function.zip

# Function URLを追加 (CloudFrontがオリジンとして使用できるように)
aws lambda create-function-url-config \
--function-name image-proxy \
--auth-type NONE


3 — LambdaにS3アクセス権限を付与

Lambda実行ロールにこのIAMポリシーをアタッチします:

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": "arn:aws:s3:::my-images-bucket/*"
}
]
}


4 — CloudFront ディストリビューションの作成

aws cloudfront create-distribution \
--origin-domain-name YOUR_LAMBDA_FUNCTION_URL \
--default-cache-behavior '{
"ViewerProtocolPolicy": "redirect-to-https",
"CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6",
"AllowedMethods": {
"Quantity": 2,
"Items": ["GET", "HEAD"]
},
"Compress": true
}'

Note: CloudFrontオリジンとしてLambda Function URL(生のS3 URLではなく)を使用します。これによってすべてのリクエストがLambdaプロキシを通じてルーティングされます。

CloudFrontの主要な設定:

設定推奨値
Viewer ProtocolHTTP を HTTPS にリダイレクト
Cache PolicyCachingOptimized (ビルトイン)
Compress ObjectsYes (gzip/brotli)
Price Classすべてのエッジロケーションを使用 (またはコスト削減のため制限)
Custom Domainオプション — ACM証明書で独自ドメインを追加

5 — S3に画像をアップロード


# 単一の画像
aws s3 cp photo.jpg s3://my-images-bucket/images/photo.jpg

# フォルダを一括アップロード
aws s3 sync ./photos/ s3://my-images-bucket/images/

# CloudFrontを経由でアクセス

# https://d1234abcd.cloudfront.net/images/photo.jpg


🧩 オプションの強化機能

オンザフライ画像リサイズ

sharpをLambdaに追加してクエリパラメータに基づいてリサイズします:

import sharp from "sharp";

// ハンドラー内で、S3からフェッチした後:
const width = parseInt(event.queryStringParameters?.w || "800");
const resized = await sharp(imageBuffer).resize(width).webp().toBuffer();

return {
statusCode: 200,
headers: { "Content-Type": "image/webp", "Cache-Control": "public, max-age=31536000" },
body: resized.toString("base64"),
isBase64Encoded: true,
};

// 使用例: https://cdn.example.com/images/photo.jpg?w=400

署名付きURL認証 (プライベート画像)

import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

// まずリクエストヘッダーからトークンを検証
const token = event.headers?.authorization;
if (!isValidToken(token)) {
return { statusCode: 403, body: "Forbidden" };
}

// その後、通常通り画像を提供


💰 コスト内訳 — 月10,000枚の画像

仮定: 平均画像サイズ 500 KB月50,000リクエスト(画像あたり5リクエスト)、ユーザーが世界中に分散、エッジでの画像キャッシュが良好。

S3 ストレージ

Item計算月額費用
Storage (5 GB)5 GB × $0.023/GB$0.12
PUT requests (アップロード)10,000 × $0.005/1,000$0.05
GET requests (Lambdaが取得)~5,000 キャッシュミス × $0.0004/1,000$0.00
S3 合計~$0.17

Lambda

Item計算月額費用
Invocations5,000 キャッシュミス (残りはCFで提供)無料枠
Duration (128 MB, 平均200ms)5,000 × 0.2s × $0.0000000021/ms~$0.00
Lambda 合計~$0.00

完了: Lambdaは月100万リクエストの無料枠があります — このスケールでは簡単にカバーできます。

CloudFront CDN

Item計算月額費用
Data transfer out (25 GB)25 GB × $0.0085/GB (US/EU)$0.21
HTTP requests (50,000)50,000 × $0.0000010/request$0.05
CloudFront 合計~$0.26

完了: CloudFront無料枠には1 TB データ転送 + 1000万リクエスト/月が最初の12ヶ月間含まれています。


📊 月額総コスト概要

サービス月額費用
S3 Storage + Requests$0.17
Lambda~$0.00
CloudFront$0.26
合計~$0.43 / 月

🎉 10,000枚の画像と月50,000回の配信の場合、月額1ドル未満 — そして初年度は無料枠内に十分に収まります。

スケーリング参考値

スケールImagesRequests/月推定 Cost/月
Small10,00050,000~$0.43
Medium100,000500,000~$4.00
Large1,000,0005,000,000~$38.00
Enterprise10,000,00050,000,000~$350.00

✅ クイックチェックリスト

  • S3バケットを作成してプライベートに設定

  • Lambda関数をS3読み取りIAMポリシーでデプロイ

  • Lambda Function URLを有効化

  • CloudFrontディストリビューションをLambda URLをオリジンとして設定

  • Lambdaレスポンスに Cache-Control ヘッダーを設定

  • カスタムドメイン + SSL証明書を設定 (オプション)

  • 画像アップロードパイプラインを配置 (CLI / SDK / CI)

  • モニタリングを有効化 (Lambda用CloudWatch、CloudFrontメトリクス)


📚 有用なリンク

Related Articles