0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

実践して学ぶAWSのIaC【SAM編】

0
Last updated at Posted at 2025-07-20

はじめに

AWSでリソースをコードとして管理する方法には、CloudFormation、SAM、CDK、Terraformなどいくつかの選択肢があります。前回はCloudFormationを使用してシステムを構築しましたが、今回は第2回目としてAWS SAMを使用してみました!
SAMはサーバーレスアプリケーションに特化したフレームワークで、CloudFormationを拡張してより簡潔にサーバーレスリソースを定義できます。同じシステムを構築することで、CloudFormationとの違いを実感していただけると思います!

  • 前回の記事:実践して学ぶAWSのIaC【CloudFormation編】

構築するシステム

image.png

ユーザーがアップロードしたファイルを、拡張子に基づいて振り分け、適切なS3バケットに保存する処理を行うものです。

処理フロー

  1. API Gatewayを通じてファイルをアップロード
  2. Lambda関数がファイルを受け取り、拡張子を基にファイル種別を判定
  3. 適切な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のテンプレート

今回作成したテンプレートはこちらです!

template.yaml
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権限設定の簡素化

CloudFormationのテンプレート
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}/*'
SAMのテンプレート
Policies:
  - S3WritePolicy:
      BucketName: !Ref ImagesBucket
  - S3WritePolicy:
      BucketName: !Ref DocumentsBucket
  - S3WritePolicy:
      BucketName: !Ref LogsBucket

SAMの組み込みポリシーテンプレートを使用することで、セキュリティのベストプラクティスが自動的に適用されます!記述量も少なくて済みますし、記述ミスによるセキュリティリスクも軽減されるかと思います。非常に助かりますね!

3. API Gatewayとの統合の簡素化

CloudFormationのテンプレート
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]
SAMのテンプレート
Events:
  UploadApi:
    Type: Api
    Properties:
      RestApiId: !Ref FileSorterAPI
      Path: /upload
      Method: post

SAMではEventsセクションに記述するだけで、API Gatewayのリソース、メソッド、権限、デプロイメントが自動的に作成され、依存関係も自動管理されます。CloudFormationのようにDependsOnで依存関係を記述する必要も無いので、依存関係の管理が不要になります!

4. CORS設定の簡易化

CloudFormationのテンプレート
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'"
SAMのテンプレート
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とは記述の仕方が変わってくると思うので楽しみです!

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?