AWSサービスを活用した包括的サーバーレスフレームワーク構築ガイド
最近、業務でサーバーレスフレームワークに触れる機会があったので、
勉強のために、記事にまとめました。
本ガイドでは、AWS(Amazon Web Services)の主要サービスを使って、シンプルながらパワフルなサーバーレスフレームワークを構築する方法を詳しく解説します。
はじめに
構築するアプリケーション
このガイドでは、API経由でDynamoDBからデータを取得する簡単なサーバーレスアプリケーションを構築します。具体的には、ユーザー情報を管理するRESTful APIを作成し、GET /users エンドポイントでユーザー一覧を取得できるようにします。
サーバーレスアーキテクチャの利点
サーバーレスアーキテクチャを選択する主な利点は以下の通りです:
スケーラビリティ: トラフィックに応じて自動的にスケールするため、インフラの管理が不要です。
コスト効率: 使用した分だけ課金されるため、特に変動の大きいワークロードに適しています。
開発者の生産性向上: インフラ管理からの解放により、ビジネスロジックの開発に集中できます。
API Gateway、Lambda、CloudFormation、DynamoDBを組み合わせることで、これらの利点を最大限に活かしたアプリケーションを作成できます。
目次
<a name="概要"></a>
1. サーバーレスアーキテクチャの概要
サーバーレスアーキテクチャとは、サーバーの管理やスケーリングを気にすることなく、アプリケーションの開発に集中できる革新的なアプローチです。AWSのサービスを利用することで、インフラストラクチャの複雑さを抽象化し、ビジネスロジックの実装に注力できるのが大きな魅力です。
<a name="サービス"></a>
2. 使用するAWSサービス
今回のフレームワーク構築に使用する主なAWSサービスは以下の4つです:
API Gateway: HTTPリクエストを受け取り、適切なLambda関数にルーティングします。
Lambda: サーバーレスでコードを実行します。ビジネスロジックの中心となります。
DynamoDB: サーバーレスのNoSQLデータベースとして機能し、アプリケーションのデータを保存します。
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
この設計では、以下のような流れでリクエストが処理されます:
クライアントからのHTTPリクエストがAPI Gatewayに到達します。
API GatewayはリクエストをLambda関数にルーティングします。
Lambda関数がビジネスロジックを実行し、必要に応じてDynamoDBからデータを取得します。
処理結果が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. 実装手順
CloudFormationテンプレートの作成:
AWS管理コンソールにログインし、CloudFormationサービスに移動します。
「スタックの作成」をクリックし、「新しいリソースを使用(標準)」を選択します。
テンプレートファイルをアップロードし、以下のステップで提供するYAMLコードを使用します。
Lambda関数の実装:
Lambda関数のコードを作成します(コード例は後述)。
API Gatewayの設定:
CloudFormationテンプレートでAPI Gatewayリソースを定義します。
API GatewayコンソールでLambda関数との統合を確認します。
DynamoDBテーブルの設計:
CloudFormationテンプレートでDynamoDBテーブルを定義します。
AWS管理コンソールでDynamoDBサービスに移動し、テーブルが正しく作成されたことを確認します。
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. セキュリティ設定
セキュリティは非常に重要な要素です。以下の点に注意してください:
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
CORS設定:
API Gatewayで適切なCORS設定を行い、許可されたドメインからのみアクセスを許可します。
AWS管理コンソールでの設定手順:
API Gatewayコンソールを開き、該当するAPIを選択
リソース → アクション → CORSの有効化を選択
許可するオリジン、ヘッダー、メソッドを指定
「CORS対応を有効化して既存のリソースを置き換える」をクリック
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"
}
]
}
DynamoDBのアクセス制御:
IAMポリシーを使用して、特定のテーブルや操作へのアクセスを制限します。
暗号化:
DynamoDBテーブルの暗号化を有効にします:
MyDynamoDBTable:
Type: AWS::DynamoDB::Table
Properties:
# ... 他のプロパティ ...
SSESpecification:
SSEEnabled: true
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. デプロイとテスト
AWS CLIを使用してCloudFormationスタックをデプロイします:
aws cloudformation create-stack --stack-name MyServerlessStack --template-body file://template.yaml --capabilities CAPABILITY_IAM
デプロイが完了したら、API GatewayのエンドポイントURLを取得します:
aws apigateway get-rest-apis --query "items[?name=='MyServerlessApi'].id" --output text
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に自動的に送信されます。以下は、ログを効果的に活用する方法です:
構造化ログ:
JSON形式でログを出力することで、後で分析しやすくなります。
console.log(JSON.stringify({
level: 'INFO',
message: 'Function executed successfully',
timestamp: new Date().toISOString(),
data: { /* 関連データ */ }
}));
ログフィルタの設定:
CloudWatch Logsのログフィルタを使用して、特定のパターンのログを検出し、アラートを設定できます。
X-Rayを使用したトレース
AWS X-Rayを使用すると、アプリケーションのパフォーマンスとエラーをより詳細に分析できます。
Lambda関数でX-Rayを有効にします:
MyLambdaFunction:
Type: AWS::Lambda::Function
Properties:
# ... 他のプロパティ ...
TracingConfig:
Mode: Active
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. コスト最適化
サーバーレスアーキテクチャは従量課金制ですが、適切な最適化を行うことでコストを抑えることができます。
Lambdaのメモリ設定の最適化:
Lambda関数のメモリ設定を調整し、実行時間とのバランスを取ります。
AWS Lambda Power Tuningツールを使用して最適なメモリ設定を見つけることができます。
DynamoDBのキャパシティモードの選択:
予測可能なトラフィックパターンがある場合はプロビジョニングモード、変動が大きい場合はオンデマンドモードを選択します。
MyDynamoDBTable:
Type: AWS::DynamoDB::Table
Properties:
# ... 他のプロパティ ...
BillingMode: PAY_PER_REQUEST # オンデマンドモード
API Gatewayのキャッシュ設定:
頻繁にアクセスされるエンドポイントにキャッシュを設定し、Lambda関数の呼び出し回数を減らします。
ApiGatewayStage:
Type: AWS::ApiGateway::Stage
Properties:
# ... 他のプロパティ ...
MethodSettings:
- HttpMethod: "*"
ResourcePath: "/*"
CachingEnabled: true
CacheTtlInSeconds: 300
Lambda関数のコールドスタート対策:
Provisioned Concurrencyを使用して、重要な関数のコールドスタートを減らします。
MyLambdaVersion:
Type: AWS::Lambda::Version
Properties:
FunctionName: !Ref MyLambdaFunction
ProvisionedConcurrencyConfig:
ProvisionedConcurrentExecutions: 5
これらの最適化により、パフォーマンスを維持しながらコストを抑えることができます。
<a name="トレンド"></a>
10. 今後のトレンドと高度なトピック
サーバーレス技術は急速に進化しています。以下は、今後のトレンドと高度なトピックの一部です:
Serverless Application Model (SAM):
AWSのSAMを使用すると、サーバーレスアプリケーションの開発とデプロイがさらに簡単になります。
AWS Step Functions:
複雑なワークフローを視覚的に設計し、複数のLambda関数を連携させることができます。
コンテナベースのサーバーレス:
AWS FargateやAmazon ECS on AWS Fargateを使用して、コンテナベースのサーバーレスアプリケーションを構築できます。
サーバーレスフレームワーク:
Serverless FrameworkやAWS CDKなどのツールを使用して、より効率的にサーバーレスアプリケーションを開発できます。
イベントドリブンアーキテクチャ:
Amazon EventBridgeを使用して、さまざまなAWSサービスやサードパーティアプリケーション間でイベントを連携させることができます。
これらのトピックについて学ぶことで、より高度なサーバーレスアプリケーションを構築する準備が整います。
<a name="まとめ"></a>
11. まとめとワークショップ
おめでとうございます!これで基本的なサーバーレスフレームワークが構築できました。API Gateway、Lambda、CloudFormation、DynamoDBを組み合わせることで、スケーラブルで管理の容易なアプリケーションの基盤が整いました。
ワークショップ: ユーザー登録機能の追加
次のステップとして、ユーザー登録機能を追加してみましょう。以下の手順で実装してください:
DynamoDBテーブルに新しい項目を追加するLambda関数を作成します。
API Gatewayに新しいPOSTメソッドを追加し、この新しいLambda関数と統合します。
CloudFormationテンプレートを更新して、新しいリソースを含めます。
デプロイしてテストします。
ヒント:
ユーザーIDにはUUIDを使用します。
入力バリデーションを忘れずに実装してください。
エラーハンドリングとログ記録を適切に行います。
このワークショップを完了することで、サーバーレスアプリケーションの開発スキルをさらに向上させることができます。
次のステップ
さらに学習を深めるために、以下のリソースをチェックしてみてください:
[AWS CloudFormationユーザーガイド](https://docs.aws.amazon.com/AWSCloudFormation/latest/