見出し画像

AWSサービスを活用した包括的サーバーレスフレームワーク構築ガイド

最近、業務でサーバーレスフレームワークに触れる機会があったので、
勉強のために、記事にまとめました。



本ガイドでは、AWS(Amazon Web Services)の主要サービスを使って、シンプルながらパワフルなサーバーレスフレームワークを構築する方法を詳しく解説します。

はじめに


構築するアプリケーション

このガイドでは、API経由でDynamoDBからデータを取得する簡単なサーバーレスアプリケーションを構築します。具体的には、ユーザー情報を管理するRESTful APIを作成し、GET /users エンドポイントでユーザー一覧を取得できるようにします。

サーバーレスアーキテクチャの利点

サーバーレスアーキテクチャを選択する主な利点は以下の通りです:

  1. スケーラビリティ: トラフィックに応じて自動的にスケールするため、インフラの管理が不要です。

  2. コスト効率: 使用した分だけ課金されるため、特に変動の大きいワークロードに適しています。

  3. 開発者の生産性向上: インフラ管理からの解放により、ビジネスロジックの開発に集中できます。

API Gateway、Lambda、CloudFormation、DynamoDBを組み合わせることで、これらの利点を最大限に活かしたアプリケーションを作成できます。

目次

  1. サーバーレスアーキテクチャの概要

  2. 使用するAWSサービス

  3. アーキテクチャ設計

  4. 実装手順

  5. コード例と詳細説明

  6. セキュリティ設定

  7. デプロイとテスト

  8. エラーハンドリングと監視

  9. コスト最適化

  10. 今後のトレンドと高度なトピック

  11. まとめとワークショップ

<a name="概要"></a>

1. サーバーレスアーキテクチャの概要

サーバーレスアーキテクチャとは、サーバーの管理やスケーリングを気にすることなく、アプリケーションの開発に集中できる革新的なアプローチです。AWSのサービスを利用することで、インフラストラクチャの複雑さを抽象化し、ビジネスロジックの実装に注力できるのが大きな魅力です。

<a name="サービス"></a>

2. 使用するAWSサービス

今回のフレームワーク構築に使用する主なAWSサービスは以下の4つです:

  1. API Gateway: HTTPリクエストを受け取り、適切なLambda関数にルーティングします。

  2. Lambda: サーバーレスでコードを実行します。ビジネスロジックの中心となります。

  3. DynamoDB: サーバーレスのNoSQLデータベースとして機能し、アプリケーションのデータを保存します。

  4. CloudFormation: インフラストラクチャをコードとして定義し、上記のリソースをプロビジョニングおよび管理します。

<a name="アーキテクチャ"></a>

3. アーキテクチャ設計

全体アーキテクチャ

以下は、今回構築するサーバーレスフレームワークの簡単なアーキテクチャ図です:

graph LR
    A[クライアント] --> B[API Gateway]
    B --> C[Lambda]
    C --> D[DynamoDB]
    E[CloudFormation] --> B
    E --> C
    E --> D
    F[CloudWatch] --> C
    G[X-Ray] --> C

この設計では、以下のような流れでリクエストが処理されます:

  1. クライアントからのHTTPリクエストがAPI Gatewayに到達します。

  2. API GatewayはリクエストをLambda関数にルーティングします。

  3. Lambda関数がビジネスロジックを実行し、必要に応じてDynamoDBからデータを取得します。

  4. 処理結果がLambdaからAPI Gatewayを通してクライアントに返されます。

CloudFormationは、これら全てのリソースをコードとして定義し、一括でデプロイを管理します。また、CloudWatchとX-Rayを使用して、アプリケーションの監視とトレースを行います。

DynamoDBテーブル設計

ユーザー情報を格納するDynamoDBテーブルの設計は以下の通りです:

classDiagram
    class Users {
        +String id (Partition Key)
        String name
        String email
        Number age
        String createdAt
    }
  • `id`: ユーザーの一意識別子(UUID)

  • `name`: ユーザーの名前

  • `email`: ユーザーのメールアドレス

  • `age`: ユーザーの年齢

  • `createdAt`: ユーザー作成日時

このシンプルな設計により、効率的なデータ取得が可能になります。

<a name="手順"></a>

4. 実装手順

  1. CloudFormationテンプレートの作成:

    • AWS管理コンソールにログインし、CloudFormationサービスに移動します。

    • 「スタックの作成」をクリックし、「新しいリソースを使用(標準)」を選択します。

    • テンプレートファイルをアップロードし、以下のステップで提供するYAMLコードを使用します。

  2. Lambda関数の実装:

    • Lambda関数のコードを作成します(コード例は後述)。

  3. API Gatewayの設定:

    • CloudFormationテンプレートでAPI Gatewayリソースを定義します。

    • API GatewayコンソールでLambda関数との統合を確認します。

  4. DynamoDBテーブルの設計:

    • CloudFormationテンプレートでDynamoDBテーブルを定義します。

    • AWS管理コンソールでDynamoDBサービスに移動し、テーブルが正しく作成されたことを確認します。

  5. CloudFormationを使用したデプロイ:

<a name="コード"></a>

5. コード例と詳細説明

CloudFormationテンプレート

AWSTemplateFormatVersion: '2010-09-09'
Description: 'Serverless Framework with API Gateway, Lambda, and DynamoDB'

Resources:
  MyApi:
    Type: AWS::ApiGateway::RestApi
    Properties:
      Name: MyServerlessApi

  MyLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      Handler: index.handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        ZipFile: |
          const AWS = require('aws-sdk');
          const docClient = new AWS.DynamoDB.DocumentClient();

          exports.handler = async (event) => {
            // Lambda関数のコードはここに記述します(以下のセクションを参照)
          }
      Runtime: nodejs14.x

  MyDynamoDBTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: Users
      AttributeDefinitions:
        - AttributeName: id
          AttributeType: S
      KeySchema:
        - AttributeName: id
          KeyType: HASH
      ProvisionedThroughput:
        ReadCapacityUnits: 5
        WriteCapacityUnits: 5

  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: DynamoDBAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - dynamodb:Scan
                Resource: !GetAtt MyDynamoDBTable.Arn

  ApiGatewayDeployment:
    Type: AWS::ApiGateway::Deployment
    DependsOn: MyApiMethod
    Properties:
      RestApiId: !Ref MyApi
      StageName: prod

  MyApiResource:
    Type: AWS::ApiGateway::Resource
    Properties:
      ParentId: !GetAtt MyApi.RootResourceId
      PathPart: users
      RestApiId: !Ref MyApi

  MyApiMethod:
    Type: AWS::ApiGateway::Method
    Properties:
      HttpMethod: GET
      ResourceId: !Ref MyApiResource
      RestApiId: !Ref MyApi
      AuthorizationType: NONE
      Integration:
        Type: AWS_PROXY
        IntegrationHttpMethod: POST
        Uri: !Sub 
          - arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaArn}/invocations
          - LambdaArn: !GetAtt MyLambdaFunction.Arn

Outputs:
  ApiEndpoint:
    Description: "API Gateway endpoint URL for Prod stage"
    Value: !Sub "https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/prod/"

このCloudFormationテンプレートは、API Gateway、Lambda関数、DynamoDBテーブル、および必要なIAMロールを定義しています。

Lambda関数(Node.js)

const AWS = require('aws-sdk');
const docClient = new AWS.DynamoDB.DocumentClient();

/**
 * Lambda関数のハンドラー
 * API Gatewayからのリクエストを処理し、DynamoDBからユーザー情報を取得します
 * 
 * @param {Object} event - API Gatewayからのイベントオブジェクト
 * @returns {Object} HTTP応答オブジェクト
 */
exports.handler = async (event) => {
    console.log('Received event:', JSON.stringify(event, null, 2));

    const { httpMethod, path } = event;
    
    if (httpMethod === 'GET' && path === '/users') {
        const params = {
            TableName: 'Users'
        };
        
        try {
            // DynamoDBからすべてのユーザーを取得
            const data = await docClient.scan(params).promise();
            console.log('Successfully retrieved users:', JSON.stringify(data, null, 2));
            return {
                statusCode: 200,
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(data.Items)
            };
        } catch (err) {
            console.error('Error retrieving users:', err);
            return {
                statusCode: 500,
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ 
                    error: 'Could not retrieve users',
                    details: err.message
                })
            };
        }
    }
    
    // 未対応のHTTPメソッドやパスの場合
    return {
        statusCode: 400,
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({ error: 'Unsupported route' })
    };
};

このLambda関数は、API Gatewayからのリクエストを処理し、DynamoDBからユーザー情報を取得します。エラーハンドリングも実装されており、問題が発生した場合は適切なエラーメッセージを返します。

<a name="セキュリティ"></a>

6. セキュリティ設定

セキュリティは非常に重要な要素です。以下の点に注意してください:

  1. API Gatewayの認証設定:

    • IAM認証やCognitoユーザープールを使用して、APIへのアクセスを制限します。

    • CloudFormationテンプレートに以下のようなリソースを追加します:

  ApiGatewayAuthorizer:
    Type: AWS::ApiGateway::Authorizer
    Properties:
      Name: CognitoAuthorizer
      Type: COGNITO_USER_POOLS
      IdentitySource: method.request.header.Authorization
      RestApiId: !Ref MyApi
      ProviderARNs:
        - !Ref CognitoUserPool

  MyApiMethod:
    Type: AWS::ApiGateway::Method
    Properties:
      AuthorizationType: COGNITO_USER_POOLS
      AuthorizerId: !Ref ApiGatewayAuthorizer
      ResourceId: !Ref MyApiResource
      RestApiId: !Ref MyApi
      HttpMethod: GET
      Integration:
        Type: AWS_PROXY
        IntegrationHttpMethod: POST
        Uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyLambdaFunction.Arn}/invocations
      RequestParameters:
        method.request.header.Authorization: true
  1. CORS設定:

    • API Gatewayで適切なCORS設定を行い、許可されたドメインからのみアクセスを許可します。

    • AWS管理コンソールでの設定手順:

      1. API Gatewayコンソールを開き、該当するAPIを選択

      2. リソース → アクション → CORSの有効化を選択

      3. 許可するオリジン、ヘッダー、メソッドを指定

      4. 「CORS対応を有効化して既存のリソースを置き換える」をクリック

  2. Lambda関数のIAMロール:

    • 最小権限の原則に従い、必要最小限の権限のみを付与します。

    • 以下は、DynamoDBの特定のテーブルに対する読み取り権限のみを付与するIAMポリシーの例です:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:GetItem",
                "dynamodb:Scan",
                "dynamodb:Query"
            ],
            "Resource": "arn:aws:dynamodb:REGION:ACCOUNT_ID:table/Users"
        }
    ]
}
  1. DynamoDBのアクセス制御:

    • IAMポリシーを使用して、特定のテーブルや操作へのアクセスを制限します。

  2. 暗号化:

    • DynamoDBテーブルの暗号化を有効にします:

  MyDynamoDBTable:
    Type: AWS::DynamoDB::Table
    Properties:
      # ... 他のプロパティ ...
      SSESpecification:
        SSEEnabled: true
  1. WAF (Web Application Firewall) の導入:

    • SQL インジェクションやクロスサイトスクリプティング (XSS) などの一般的な攻撃からAPIを保護します。

    • CloudFormationテンプレートにWAFの設定を追加:

  MyWebACL:
    Type: AWS::WAFv2::WebACL
    Properties:
      Name: MyAPIProtection
      Scope: REGIONAL
      DefaultAction:
        Allow: {}
      VisibilityConfig:
        SampledRequestsEnabled: true
        CloudWatchMetricsEnabled: true
        MetricName: MyAPIProtectionMetrics
      Rules:
        - Name: AWSManagedRulesCommonRuleSet
          Priority: 1
          OverrideAction:
            None: {}
          VisibilityConfig:
            SampledRequestsEnabled: true
            CloudWatchMetricsEnabled: true
            MetricName: AWSManagedRulesCommonRuleSetMetric
          Statement:
            ManagedRuleGroupStatement:
              VendorName: AWS
              Name: AWSManagedRulesCommonRuleSet

  WebACLAssociation:
    Type: AWS::WAFv2::WebACLAssociation
    Properties:
      ResourceArn: !Sub arn:aws:apigateway:${AWS::Region}::/restapis/${MyApi}/stages/prod
      WebACLArn: !Ref MyWebACL

これらのセキュリティ設定により、アプリケーションの保護が強化されます。

<a name="デプロイ"></a>

7. デプロイとテスト

  1. AWS CLIを使用してCloudFormationスタックをデプロイします:

aws cloudformation create-stack --stack-name MyServerlessStack --template-body file://template.yaml --capabilities CAPABILITY_IAM
  1. デプロイが完了したら、API GatewayのエンドポイントURLを取得します:

aws apigateway get-rest-apis --query "items[?name=='MyServerlessApi'].id" --output text
  1. curlやPostmanを使用してAPIをテストします:

curl https://YOUR_API_ID.execute-api.YOUR_REGION.amazonaws.com/prod/users

注意: 認証を設定している場合は、適切な認証トークンをヘッダーに含める必要があります。

<a name="エラーハンドリング"></a>

8. エラーハンドリングと監視

適切なエラーハンドリングと監視は、アプリケーションの信頼性と運用性を向上させる重要な要素です。

CloudWatch Logsの活用

Lambda関数のログはCloudWatch Logsに自動的に送信されます。以下は、ログを効果的に活用する方法です:

  1. 構造化ログ:
    JSON形式でログを出力することで、後で分析しやすくなります。

console.log(JSON.stringify({
  level: 'INFO',
  message: 'Function executed successfully',
  timestamp: new Date().toISOString(),
  data: { /* 関連データ */ }
}));
  1. ログフィルタの設定:
    CloudWatch Logsのログフィルタを使用して、特定のパターンのログを検出し、アラートを設定できます。

X-Rayを使用したトレース

AWS X-Rayを使用すると、アプリケーションのパフォーマンスとエラーをより詳細に分析できます。

  1. Lambda関数でX-Rayを有効にします:

  MyLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      # ... 他のプロパティ ...
      TracingConfig:
        Mode: Active
  1. Lambda関数のコードでX-Rayのトレースを記録:

const AWSXRay = require('aws-xray-sdk-core');
const AWS = AWSXRay.captureAWS(require('aws-sdk'));

exports.handler = async (event) => {
  // カスタムサブセグメントの作成
  const segment = AWSXRay.getSegment();
  const subSegment = segment.addNewSubsegment('MyCustomLogic');
  
  try {
    // ビジネスロジック
    // ...
    
    subSegment.close();
  } catch (error) {
    subSegment.close(error);
    throw error;
  }
};

デッドレターキューの設定

Lambda関数が失敗した場合、再試行後もエラーが続く場合はデッドレターキュー(DLQ)にメッセージを送信できます。

  MyLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      # ... 他のプロパティ ...
      DeadLetterConfig:
        TargetArn: !Ref MyDeadLetterQueue

  MyDeadLetterQueue:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: MyDLQ

これにより、失敗したイベントを後で分析したり、再処理したりすることができます。

<a name="コスト最適化"></a>

9. コスト最適化

サーバーレスアーキテクチャは従量課金制ですが、適切な最適化を行うことでコストを抑えることができます。

  1. Lambdaのメモリ設定の最適化:

    • Lambda関数のメモリ設定を調整し、実行時間とのバランスを取ります。

    • AWS Lambda Power Tuningツールを使用して最適なメモリ設定を見つけることができます。

  2. DynamoDBのキャパシティモードの選択:

    • 予測可能なトラフィックパターンがある場合はプロビジョニングモード、変動が大きい場合はオンデマンドモードを選択します。

  MyDynamoDBTable:
    Type: AWS::DynamoDB::Table
    Properties:
      # ... 他のプロパティ ...
      BillingMode: PAY_PER_REQUEST  # オンデマンドモード
  1. API Gatewayのキャッシュ設定:

    • 頻繁にアクセスされるエンドポイントにキャッシュを設定し、Lambda関数の呼び出し回数を減らします。

  ApiGatewayStage:
    Type: AWS::ApiGateway::Stage
    Properties:
      # ... 他のプロパティ ...
      MethodSettings:
        - HttpMethod: "*"
          ResourcePath: "/*"
          CachingEnabled: true
          CacheTtlInSeconds: 300
  1. Lambda関数のコールドスタート対策:

    • Provisioned Concurrencyを使用して、重要な関数のコールドスタートを減らします。

  MyLambdaVersion:
    Type: AWS::Lambda::Version
    Properties:
      FunctionName: !Ref MyLambdaFunction
      ProvisionedConcurrencyConfig:
        ProvisionedConcurrentExecutions: 5

これらの最適化により、パフォーマンスを維持しながらコストを抑えることができます。

<a name="トレンド"></a>

10. 今後のトレンドと高度なトピック

サーバーレス技術は急速に進化しています。以下は、今後のトレンドと高度なトピックの一部です:

  1. Serverless Application Model (SAM):

    • AWSのSAMを使用すると、サーバーレスアプリケーションの開発とデプロイがさらに簡単になります。

    • AWS SAM開発者ガイド

  2. AWS Step Functions:

  3. コンテナベースのサーバーレス:

    • AWS FargateやAmazon ECS on AWS Fargateを使用して、コンテナベースのサーバーレスアプリケーションを構築できます。

  4. サーバーレスフレームワーク:

    • Serverless FrameworkやAWS CDKなどのツールを使用して、より効率的にサーバーレスアプリケーションを開発できます。

  5. イベントドリブンアーキテクチャ:

    • Amazon EventBridgeを使用して、さまざまなAWSサービスやサードパーティアプリケーション間でイベントを連携させることができます。

これらのトピックについて学ぶことで、より高度なサーバーレスアプリケーションを構築する準備が整います。

<a name="まとめ"></a>

11. まとめとワークショップ

おめでとうございます!これで基本的なサーバーレスフレームワークが構築できました。API Gateway、Lambda、CloudFormation、DynamoDBを組み合わせることで、スケーラブルで管理の容易なアプリケーションの基盤が整いました。

ワークショップ: ユーザー登録機能の追加

次のステップとして、ユーザー登録機能を追加してみましょう。以下の手順で実装してください:

  1. DynamoDBテーブルに新しい項目を追加するLambda関数を作成します。

  2. API Gatewayに新しいPOSTメソッドを追加し、この新しいLambda関数と統合します。

  3. CloudFormationテンプレートを更新して、新しいリソースを含めます。

  4. デプロイしてテストします。

ヒント:

  • ユーザーIDにはUUIDを使用します。

  • 入力バリデーションを忘れずに実装してください。

  • エラーハンドリングとログ記録を適切に行います。

このワークショップを完了することで、サーバーレスアプリケーションの開発スキルをさらに向上させることができます。

次のステップ

さらに学習を深めるために、以下のリソースをチェックしてみてください:


いいなと思ったら応援しよう!

この記事が参加している募集