はじめに
「S3にファイルが上がったら自動で処理したい」というのは、AWSを使う現場でよく出てくる要件です。画像のリサイズ、CSVの集計、ログファイルの解析など、ファイルアップロードを起点にした自動処理はサーバーレス構成の代表的なユースケースです。
この記事では、S3 + Lambda によるファイルアップロードトリガー処理を、AWS SAM を使ってゼロから構築するハンズオンを紹介します。
ファイルアップロード(aws s3 cp)
↓ S3 ObjectCreated イベント発火
S3 バケット(UploadBucket)
↓ Lambda を呼び出し
Lambda(Python 3.12 / S3EventFunction)
↓ ファイル情報(バケット名・ファイル名・サイズ)をログ出力
CloudWatch Logsこのハンズオンで体験できること:
- S3 ObjectCreated イベントによる Lambda の自動起動
- URLデコード処理(日本語ファイル名・特殊文字を含むキー名の正しい取得)
- SAMテンプレートで S3 バケット名をグローバルユニークにする
!Subの使い方 DeletionPolicyと「バケットを先に空にしてから削除」というSAM特有の注意点
この記事の特徴:
- Windows(VSCode + コマンドプロンプト)での実際の手順を詳細に解説
- フォルダ付きキー・日本語ファイル名など実務でよく出る応用テストも含む
sam deleteで失敗しやすいポイントと対処法を詳細に解説
S3イベントの仕組みを理解する
キーワード解説
| 用語 | 意味 |
|---|---|
| S3イベント通知 | S3バケットで特定の操作(アップロードなど)が行われたときに他サービスを呼び出す仕組み |
| ObjectCreated | ファイルのアップロード・コピー・マルチパート完了などの「作成」系イベントの総称 |
| リソースベースポリシー | Lambda側に「S3からの呼び出しを許可する」ポリシーを設定する仕組み |
| URLデコード | S3のキー名は特殊文字がURLエンコードされるため、Lambda側でデコードして読み取る処理 |
S3イベントの種類
SAMテンプレートで指定できる代表的なイベント:
| イベント | 発火タイミング |
|---|---|
s3:ObjectCreated:Put | PUTアップロード(aws s3 cp など) |
s3:ObjectCreated:Post | HTMLフォームからのアップロード |
s3:ObjectCreated:Copy | オブジェクトのコピー |
s3:ObjectCreated:* | 上記すべての作成系イベント(今回はこれを使用) |
s3:ObjectRemoved:* | オブジェクトの削除イベント |
今回は s3:ObjectCreated:* を使い、アップロード・コピーを問わず全ての作成操作でLambdaを起動します。
前提条件
| ツール | 確認コマンド | 最低バージョン目安 |
|---|---|---|
| AWS CLI v2 | aws --version | 2.x |
| AWS SAM CLI | sam --version | 1.x |
| Python | python --version | 3.12推奨 |
| Git | git --version | - |
| VSCode | - | 最新版推奨 |
aws sts get-caller-identityアカウントIDが表示されれば認証設定済みです。
使用するAWSサービス
| サービス | 役割 | 料金 |
|---|---|---|
| S3(標準ストレージ) | ファイルの保存・イベント発火 | 5GBまで無料(12ヶ月) |
| Lambda | ファイル情報のログ記録 | 100万リクエスト無料枠あり |
| CloudWatch Logs | Lambda実行ログの保存 | 5GBまで無料 |
| S3(デプロイ用) | SAMのデプロイパッケージ置き場 | 自動作成・少量なのでほぼ無料 |
学習目的の短時間ハンズオンであれば、ほぼ無料枠内で収まります。
Step 1: プロジェクトフォルダを開く
cd C:\my-aws\aws-learning-projects\s3-event-lambdaフォルダ構造
s3-event-lambda/
├── template.yaml # SAMテンプレート(S3バケット + Lambda定義)
├── samconfig.toml # デプロイ設定(gitignore対象・毎回手動作成が必要)
├── docs/
│ ├── 1_console.md # AWSコンソール版手順
│ └── 2_sam.md # SAM版手順
└── src/
└── app.py # Lambda関数(ファイル情報をCloudWatch Logsに出力)Step 2: SAMテンプレートの確認(template.yaml)
template.yaml がAWSリソース全体の設計図です。S3バケットとLambda関数をこの1ファイルで定義します。
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: S3 file upload trigger with Lambda
Globals:
Function:
Runtime: python3.12
Timeout: 30
MemorySize: 128
Resources:
UploadBucket:
Type: AWS::S3::Bucket
Properties:
# アカウントIDを含めることでグローバルユニークを保証
BucketName: !Sub "${AWS::StackName}-upload-${AWS::AccountId}"
DeletionPolicy: Delete # sam delete 時にバケットも削除(空の場合のみ)
# Lambda 関数
S3EventFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: src/
Handler: app.lambda_handler
Description: S3アップロードイベントを受信してファイル情報をログ出力する
Events:
S3Event:
Type: S3 # S3イベントトリガーを設定
Properties:
Bucket: !Ref UploadBucket # 上で作ったバケットを参照
Events: s3:ObjectCreated:* # 全ての「作成」イベントをトリガー
Outputs:
BucketName:
Description: "アップロード先バケット名"
Value: !Ref UploadBucket
S3EventFunctionArn:
Description: "Lambda 関数 ARN"
Value: !GetAtt S3EventFunction.Arn
CloudWatchLogsGroup:
Description: "CloudWatch Logs グループ名"
Value: !Sub "/aws/lambda/${S3EventFunction}"template.yaml のポイント解説
BucketName: !Sub "${AWS::StackName}-upload-${AWS::AccountId}"
S3バケット名はAWS全体(全リージョン・全アカウント)でグローバルユニークである必要があります。スタック名+アカウントIDを組み合わせることで、他のアカウントと名前が被る可能性を排除しています。コンソール版では手動で一意な名前を考える必要があります。
Type: S3 イベント
SAMがS3バケットのイベント通知設定とLambdaのリソースベースポリシー(「S3からの呼び出しを許可する」設定)を自動作成します。コンソール版では「Lambda → トリガーを追加」の画面操作が必要です。
DeletionPolicy: Deletesam delete 実行時にS3バケットを削除しようとします。ただし、オブジェクトが残っているバケットは削除できないため、sam delete 前に必ずバケットを空にする必要があります(Step 9で詳しく解説)。
S3バケット名についての補足:
一度使われたバケット名は削除後しばらく再利用できないことがあります。
アカウントIDを含める方法が最も確実です。
Step 3: Lambdaコードの確認(src/app.py)
src/app.py がファイル情報取得の本体です。S3イベントからバケット名・ファイル名・サイズ・イベント種別を取得してCloudWatch Logsに記録します。
import json
import urllib.parse
def lambda_handler(event, context):
results = []
for record in event["Records"]: # 複数ファイル同時アップロードに対応
# S3 イベントからファイル情報を取得
bucket = record["s3"]["bucket"]["name"]
# キー名は URL エンコードされているためデコードが必要(日本語・スペース対応)
key = urllib.parse.unquote_plus(record["s3"]["object"]["key"])
size = record["s3"]["object"]["size"]
event_name = record["eventName"] # 例: "ObjectCreated:Put"
event_time = record["eventTime"] # 例: "2026-02-22T10:00:00.000Z"
info = {
"eventName": event_name,
"bucket": bucket,
"key": key,
"sizeBytes": size,
"eventTime": event_time,
}
print(json.dumps(info, ensure_ascii=False))
results.append(info)
print(json.dumps({"processedCount": len(results)}, ensure_ascii=False))
return {"processedCount": len(results)}app.py のポイント解説
event["Records"] のループ処理
S3イベントはリスト形式で渡されます。通常は1件ですが、複数ファイルの同時アップロード時に複数件になる場合があるため、for ループで処理します。
urllib.parse.unquote_plus(key) が必要な理由
S3のキー名(ファイルパス)に日本語や特殊文字・スペースが含まれると、URLエンコードされてLambdaに渡されます。
| アップロードしたファイル名 | デコード前(Lambdaに渡される値) | デコード後 |
|---|---|---|
テスト.txt | %E3%83%86%E3%82%B9%E3%83%88.txt | テスト.txt |
my file.txt | my+file.txt | my file.txt |
report-2026.csv | report-2026.csv(変化なし) | report-2026.csv |
urllib.parse.unquote_plus はこのデコードを正しく処理します。
外部ライブラリ不使用
標準ライブラリ(json, urllib.parse)のみ使用するため、requirements.txt が不要です。
Step 4: samconfig.toml を作成する
samconfig.toml は .gitignore で管理外のため、毎回手動で作成する必要があります。
s3-event-lambda/samconfig.toml を新規作成して以下を貼り付けてください。
version = 0.1
[default.deploy.parameters]
stack_name = "s3-event-lambda-stack"
resolve_s3 = true
s3_prefix = "s3-event-lambda-stack"
region = "ap-northeast-1"
confirm_changeset = true
capabilities = "CAPABILITY_IAM"
disable_rollback = true
image_repositories = []
[default.global.parameters]
region = "ap-northeast-1"【注意】
samconfig.tomlの置き場所
s3-event-lambda/フォルダの直下に置く必要があります。
Step 5: sam build(ビルド)
cd C:\my-aws\aws-learning-projects\s3-event-lambda
sam buildBuild Succeeded
Built Artifacts : .aws-sam/build
Built Template : .aws-sam/build/template.yamlStep 6: sam deploy(デプロイ)
sam deployDeploy this changeset? [y/N]: yy を入力して進めます。数分でデプロイが完了します。
デプロイ完了の確認
CloudFormation outputs from deployed stack
----------------------------------------------------------------------
Outputs
----------------------------------------------------------------------
Key BucketName
Value s3-event-lambda-stack-upload-123456789012
Key S3EventFunctionArn
Value arn:aws:lambda:ap-northeast-1:123456789012:function:s3-event-lambda-stack-S3EventFunction-XXXX
Key CloudWatchLogsGroup
Value /aws/lambda/s3-event-lambda-stack-S3EventFunction-XXXX
----------------------------------------------------------------------BucketName を控えておきます。(テスト時に使います)
BucketAlreadyExistsエラーが出た場合:
既に同名バケットが他アカウントに存在しています。samconfig.tomlのstack_nameを別の名前に変更して再デプロイしてください。
Step 7: 動作テスト
7-1. テスト用ファイルの準備
echo テストファイルです > C:\test.txt7-2. バケット名を変数にセットする
set BUCKET=s3-event-lambda-stack-upload-123456789012(BUCKET の値は Outputs の BucketName に表示された実際の値に変更してください)
7-3. ファイルのアップロード
aws s3 cp C:\test.txt s3://%BUCKET%/test.txt --region ap-northeast-17-4. CloudWatch Logs でログを確認
aws logs tail /aws/lambda/s3-event-lambda-stack-S3EventFunction-XXXX --follow期待するログ出力:
{"eventName": "ObjectCreated:Put", "bucket": "s3-event-lambda-stack-upload-123456789012", "key": "test.txt", "sizeBytes": 28, "eventTime": "2026-02-22T10:00:00.000Z"}
{"processedCount": 1}確認ポイント:
"eventName": "ObjectCreated:Put"— S3 の Put(アップロード)が検知されている"key": "test.txt"— アップロードしたファイル名が正しく取得されている"sizeBytes"— ファイルサイズ(バイト)が記録されている
7-5. フォルダ付きキーのテスト(任意)
S3の「フォルダ」はキー名のプレフィックスとして表現されます。
aws s3 cp C:\test.txt s3://%BUCKET%/images/photo.jpg --region ap-northeast-1ログで "key": "images/photo.jpg" となることを確認します。S3にフォルダ構造でファイルを整理している場合でも、キー名全体(フォルダ名/ファイル名)が取得できます。
7-6. 日本語ファイル名のテスト(任意)
echo 日本語テスト > "C:\テスト.txt"
aws s3 cp "C:\テスト.txt" "s3://%BUCKET%/テスト.txt" --region ap-northeast-1ログで "key": "テスト.txt" と正しくデコードされていることを確認します。urllib.parse.unquote_plus がなければ %E3%83%86%E3%82%B9%E3%83%88.txt のままになります。
Step 8: AWSコンソールで確認(任意)
- S3: S3 →
s3-event-lambda-stack-upload-XXXX→ アップロードしたファイルが存在することを確認 - Lambda: Lambda → 関数 →
s3-event-lambda-stack-S3EventFunction-XXXX→ 「設定」→「トリガー」でS3トリガーを確認 - CloudWatch Logs: Lambda → 対象関数 → 「モニタリング」タブ → 「CloudWatch Logs を表示」
Step 9: リソースの削除
課金を止めるために、ハンズオン完了後は必ずリソースを削除してください。
重要:
sam deleteの前に必ずバケットを空にしてください。
S3バケットにオブジェクトが残った状態でsam deleteを実行すると、CloudFormation のスタック削除が失敗します。
① バケット内のオブジェクトをすべて削除する
aws s3 rm s3://%BUCKET% --recursive --region ap-northeast-1② スタックを削除する
sam delete --stack-name s3-event-lambda-stack --region ap-northeast-1Are you sure you want to delete the stack s3-event-lambda-stack? [y/N]: y
Are you sure you want to delete the folder s3-event-lambda-stack in S3? [y/N]: y削除完了の確認
aws cloudformation describe-stacks --stack-name s3-event-lambda-stack --region ap-northeast-1An error occurred (ValidationError): Stack with id s3-event-lambda-stack does not existバケット削除を忘れて sam delete が失敗した場合
Error: Failed to delete the stack: s3-event-lambda-stack, ...
The following resource(s) failed to delete: [UploadBucket]このエラーが出た場合は、バケットを空にしてから再度 sam delete を実行します。
aws s3 rm s3://%BUCKET% --recursive --region ap-northeast-1
sam delete --stack-name s3-event-lambda-stack --region ap-northeast-1トラブルシューティング
| 症状 | 原因 | 対処 |
|---|---|---|
sam deploy が BucketAlreadyExists エラー | 同名バケットが他アカウントに存在する | samconfig.toml の stack_name を変更する |
sam deploy が Missing --stack-name エラー | samconfig.toml がない | s3-event-lambda/ 直下に samconfig.toml を作成する |
| ファイルアップロード後もログが出ない | Lambda が呼ばれていない | Lambda → 「設定」→「トリガー」でS3トリガーが有効か確認 |
ログの "key" が %XX%XX... のまま | URLデコードされていない | app.py の unquote_plus 処理を確認 |
sam delete が失敗する | バケットにオブジェクトが残っている | aws s3 rm s3://バケット名 --recursive で先に空にする |
まとめ
今回のハンズオンで実現したこと:
| 確認項目 | 内容 |
|---|---|
| S3イベントトリガー | アップロード直後にLambdaが自動起動することを確認 |
| URLデコード | 日本語ファイル名が正しく取得できることを確認 |
| フォルダ付きキー | images/photo.jpg のようなプレフィックス付きキーも取得できることを確認 |
| 削除 | バケットを先に空にしてから sam delete でクリーンアップ |
SAMのメリットを実感できたポイント
!Sub "${AWS::StackName}-upload-${AWS::AccountId}"でバケット名のグローバルユニークを自動保証(コンソール版では手動で一意な名前を考える必要がある)Type: S3の1行でS3イベント通知設定とLambdaリソースベースポリシーを自動作成(コンソール版では「トリガーを追加」の手動操作が必要)
このハンズオンの発展形
- S3からファイルの中身を読み取る — Lambda の実行ロールに
s3:GetObject権限を追加して、CSVやJSONを読み込む処理に拡張 - DynamoDBへの記録 — ファイル情報をDynamoDBに書き込んで一覧管理
- 特定のフォルダ・拡張子のみトリガー —
Prefix: images/やSuffix: .csvでフィルタリング - 画像リサイズ —
Pillowライブラリと組み合わせてアップロード時に自動リサイズ
コンソール操作と比較してみる
SAMが裏で何をやっているか、全く同じ構成をAWSコンソールのみで構築する手順をまとめました。「バケット名のグローバルユニーク問題」や「再帰呼び出しの警告」など、コンソールならではのつまずきポイントが体験できます。
- AWSコンソールだけでS3 + Lambdaファイルアップロードトリガーを構築する手順【SAM版との比較付き】
コメント