はじめに
AWSでリソースをコードとして管理する方法には、CloudFormation、SAM、CDK、Terraformなどいくつかの選択肢があります。前回はCloudFormationを使用してシステムを構築しましたが、今回は第2回目としてAWS SAMを使用してみました!
SAMはサーバーレスアプリケーションに特化したフレームワークで、CloudFormationを拡張してより簡潔にサーバーレスリソースを定義できます。同じシステムを構築することで、CloudFormationとの違いを実感していただけると思います!
- 前回の記事:実践して学ぶAWSのIaC【CloudFormation編】
構築するシステム
ユーザーがアップロードしたファイルを、拡張子に基づいて振り分け、適切なS3バケットに保存する処理を行うものです。
処理フロー:
- API Gatewayを通じてファイルをアップロード
- Lambda関数がファイルを受け取り、拡張子を基にファイル種別を判定
- 適切なS3バケットにファイルを保存
- 画像ファイル(jpg, png, gif)→ images-bucket
- ドキュメント(pdf, docx, txt)→ documents-bucket
- ログファイル(log, csv)→ logs-bucket
SAMとは
AWS SAMは、サーバーレスアプリケーションを簡単に構築できるフレームワークで、CloudFormationを拡張してサーバーレスリソース(Lambda、API Gateway、DynamoDBなど)をより簡潔に定義することができます!テンプレートの基本的な構造はCloudFormationと同じですが、テンプレートの冒頭に次の1行を追加するだけで、サーバーレス用の簡易的な記法が使えるようになります。
Transform: AWS::Serverless-2016-10-31
SAMのテンプレート
今回作成したテンプレートはこちらです!
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: 'File Sorter System - Upload files and sort them by type into appropriate S3 buckets'
Parameters:
Environment:
Type: String
Default: dev
AllowedValues: [dev, staging, prod]
Description: 'Deployment environment'
Conditions:
IsProdOrStaging: !Or [!Equals [!Ref Environment, prod], !Equals [!Ref Environment, staging]]
Resources:
# ===== S3 Buckets =====
# 画像ファイル用のバケット
ImagesBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub 'file-sorter-images-${Environment}-${AWS::AccountId}'
# ドキュメントファイル用のバケット
DocumentsBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub 'file-sorter-documents-${Environment}-${AWS::AccountId}'
# ログファイル用のバケット
LogsBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub 'file-sorter-logs-${Environment}-${AWS::AccountId}'
# ===== Lambda Function =====
FileSorterFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub 'file-sorter-${Environment}'
Runtime: python3.9
Handler: index.lambda_handler
Timeout: !If [IsProdOrStaging, 15, 10]
MemorySize: !If [IsProdOrStaging, 256, 128]
Environment:
Variables:
IMAGES_BUCKET: !Ref ImagesBucket
DOCUMENTS_BUCKET: !Ref DocumentsBucket
LOGS_BUCKET: !Ref LogsBucket
Policies:
- S3WritePolicy:
BucketName: !Ref ImagesBucket
- S3WritePolicy:
BucketName: !Ref DocumentsBucket
- S3WritePolicy:
BucketName: !Ref LogsBucket
InlineCode: |
import json
import boto3
import base64
import os
def lambda_handler(event, context):
try:
# ファイルデータの取得
if event.get('isBase64Encoded', False):
file_content = base64.b64decode(event['body'])
else:
file_content = event['body'].encode()
# ファイル名の取得
file_name = event['headers'].get('x-file-name', 'unknown.txt')
# 拡張子で振り分け
extension = file_name.lower().split('.')[-1]
if extension in ['jpg', 'png', 'gif']:
bucket = os.environ['IMAGES_BUCKET']
elif extension in ['pdf', 'docx', 'txt']:
bucket = os.environ['DOCUMENTS_BUCKET']
else:
bucket = os.environ['LOGS_BUCKET']
# S3に保存
s3 = boto3.client('s3')
s3.put_object(Bucket=bucket, Key=file_name, Body=file_content)
return {
'statusCode': 200,
'body': json.dumps(f'File saved to {bucket}')
}
except Exception as e:
return {
'statusCode': 500,
'body': json.dumps(f'Error: {str(e)}')
}
Events:
UploadApi:
Type: Api
Properties:
RestApiId: !Ref FileSorterAPI
Path: /upload
Method: post
# ===== API Gateway =====
FileSorterAPI:
Type: AWS::Serverless::Api
Properties:
Name: !Sub 'file-sorter-api-${Environment}'
StageName: !Ref Environment
BinaryMediaTypes:
- 'application/octet-stream'
- 'image/*'
- 'application/pdf'
Cors:
AllowMethods: "'POST,OPTIONS'"
AllowHeaders: "'Content-Type,x-file-name'"
AllowOrigin: "'*'"
Outputs:
ApiGatewayURL:
Description: 'URL of the API Gateway'
Value: !Sub 'https://${FileSorterAPI}.execute-api.${AWS::Region}.amazonaws.com/${Environment}'
SAMの特徴とメリット
実際にシステムを構築してみて感じた特徴やメリットについて紹介します!
1. 記述量がコンパクトになる
CloudFormationで205行必要だった設定が、SAMでは127行だったので、記述量が大幅に削減されました!今回のケースでは、以下の設定はSAMが自動化してくれるので記述しなくて済んだのが大きいと思います。
CloudFormationで必要だったがSAMでは不要な記述:
- IAMロールの詳細定義
- API Gatewayのリソース・メソッド定義
- Lambda呼び出し権限の設定
- API Gatewayのデプロイメントの設定
- 依存関係の管理
2. IAM権限設定の簡素化
FileSorterLambdaRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: S3AccessPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- s3:PutObject
Resource:
- !Sub 'arn:aws:s3:::${ImagesBucket}/*'
Policies:
- S3WritePolicy:
BucketName: !Ref ImagesBucket
- S3WritePolicy:
BucketName: !Ref DocumentsBucket
- S3WritePolicy:
BucketName: !Ref LogsBucket
SAMの組み込みポリシーテンプレートを使用することで、セキュリティのベストプラクティスが自動的に適用されます!記述量も少なくて済みますし、記述ミスによるセキュリティリスクも軽減されるかと思います。非常に助かりますね!
3. API Gatewayとの統合の簡素化
UploadResource:
Type: AWS::ApiGateway::Resource
UploadMethod:
Type: AWS::ApiGateway::Method
UploadOptionsMethod:
Type: AWS::ApiGateway::Method
ApiGatewayInvokePermission:
Type: AWS::Lambda::Permission
ApiDeployment:
Type: AWS::ApiGateway::Deployment
DependsOn: [UploadMethod, UploadOptionsMethod]
Events:
UploadApi:
Type: Api
Properties:
RestApiId: !Ref FileSorterAPI
Path: /upload
Method: post
SAMではEventsセクションに記述するだけで、API Gatewayのリソース、メソッド、権限、デプロイメントが自動的に作成され、依存関係も自動管理されます。CloudFormationのようにDependsOnで依存関係を記述する必要も無いので、依存関係の管理が不要になります!
4. CORS設定の簡易化
UploadOptionsMethod:
Type: AWS::ApiGateway::Method
Properties:
HttpMethod: OPTIONS
Integration:
Type: MOCK
IntegrationResponses:
- StatusCode: 200
ResponseParameters:
method.response.header.Access-Control-Allow-Origin: "'*'"
method.response.header.Access-Control-Allow-Methods: "'POST,OPTIONS'"
method.response.header.Access-Control-Allow-Headers: "'Content-Type,x-file-name'"
Cors:
AllowMethods: "'POST,OPTIONS'"
AllowHeaders: "'Content-Type,x-file-name'"
AllowOrigin: "'*'"
CloudFormationではCORS対応のためにOPTIONSメソッドやレスポンスヘッダーを手動で設定する必要があり、ミスが起きやすい部分でした。
SAMではCorsセクションを使うだけで、これらの設定が自動で行われ、CORSエラーを防ぎやすくなります!
デプロイ手順と実行時間
time sam deploy \
--template-file template.yaml \
--stack-name file-sorter-sam-dev \
--parameter-overrides Environment=dev \
--capabilities CAPABILITY_IAM \
--region ap-northeast-1
デプロイ完了までの実行時間を計測するために、timeコマンドを利用しています。
Successfully created/updated stack - file-sorter-sam-dev in ap-northeast-1
sam deploy --template-file SAM_template.yaml --stack-name file-sorter-sam-dev 0.87s user 0.28s system 1% cpu 1:13.83 total
計測した結果、1分13.8秒でデプロイが完了しました。
CloudFormationの1分7.4秒と比較して約6秒長くかかりました。これは恐らくSAMがCloudFormationに変換する処理に時間を要しているからだと思います。
エンドエンドの試験
簡単に試験手順にも触れておきます。
API_URL="https://<api-id>.execute-api.ap-northeast-1.amazonaws.com/dev"
curl -X POST $API_URL/upload \
-H "x-file-name: test.jpg" \
-H "Content-Type: application/octet-stream" \
--data-binary @test.jpg
aws s3 ls s3://file-sorter-images-dev-<account-id>/
試しにjpgファイルの振り分けが成功するか確認しました。
test.jpgが保存されているのが確認できたらOKです!
まとめ
今回はSAMでシステムを構築してみました!CloudFormationと比較して、記述量の大幅削減、IAM権限設定の簡素化、API Gateway連携の自動化など、サーバーレスアプリケーション開発における多くのメリットを実感できました!
次回はAWS CDKを使用して同じ構成を試してみたいと思います!CloudFormationやSAMとは記述の仕方が変わってくると思うので楽しみです!
