[AWS SAM]テンプレート内容を今一度チェック(自己レビュー)した結果...
はじめに
以下のような、サーバーレスアプリケーション構築を行なっているAWS SAMのテンプレートファイルの内容を今一度チェック(自己レビュー)した結果です。
どこかからコピペしたままの部分もあったので、パラメータ設定値の意味や、設定理由などをしっかりと考えて理解を深めました。
テンプレートファイル内容
テンプレートファイル内容です。長いので折りたたみました。
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
todo-app
SAM Template for todo-app
Globals:
Function:
Timeout: 3
MemorySize: 128
Tracing: Active
Api:
TracingEnabled: true
OpenApiVersion: 3.0.1
Resources:
ToDoApi:
Type: AWS::Serverless::Api
Properties:
StageName: v1
Cors: "'*'"
Auth:
DefaultAuthorizer: ToDoCognitoAuthorizer
Authorizers:
ToDoCognitoAuthorizer:
UserPoolArn: !GetAtt ToDoCognitoUserPool.Arn
ToDoFunction:
Type: AWS::Serverless::Function
Properties:
Environment:
Variables:
ENV_DB_TABLE: todo
ENV_DB_REGION: ap-northeast-1
ENV_DB_HOST: https://dynamodb.ap-northeast-1.amazonaws.com
ENV_TZ: Asia/Tokyo
CodeUri: todo/
Handler: app.lambda_handler
Runtime: python3.9
Architectures:
- x86_64
FunctionName: todo
Policies:
DynamoDBCrudPolicy:
TableName: !Ref TodoTable
Events:
GetToDoList:
Type: Api
Properties:
RestApiId: !Ref ToDoApi
Path: /todo/
Method: get
PostToDo:
Type: Api
Properties:
RestApiId: !Ref ToDoApi
Path: /todo/
Method: post
GetToDo:
Type: Api
Properties:
RestApiId: !Ref ToDoApi
Path: /todo/{id}
Method: get
PutToDo:
Type: Api
Properties:
RestApiId: !Ref ToDoApi
Path: /todo/{id}
Method: put
DeleteToDo:
Type: Api
Properties:
RestApiId: !Ref ToDoApi
Path: /todo/{id}
Method: delete
TodoTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: todo
AttributeDefinitions:
- AttributeName: userid
AttributeType: S
- AttributeName: todoid
AttributeType: S
KeySchema:
- AttributeName: userid
KeyType: HASH
- AttributeName: todoid
KeyType: RANGE
ProvisionedThroughput:
ReadCapacityUnits: "3"
WriteCapacityUnits: "3"
ApplicationResourceGroup:
Type: AWS::ResourceGroups::Group
Properties:
Name:
Fn::Sub: ApplicationInsights-SAM-${AWS::StackName}
ResourceQuery:
Type: CLOUDFORMATION_STACK_1_0
ApplicationInsightsMonitoring:
Type: AWS::ApplicationInsights::Application
Properties:
ResourceGroupName:
Ref: ApplicationResourceGroup
AutoConfigurationEnabled: 'true'
ToDoCognitoUserPool:
Type: AWS::Cognito::UserPool
Properties:
UserPoolName: !Sub ${AWS::StackName}-UserPool
Policies:
PasswordPolicy:
MinimumLength: 8
UsernameAttributes:
- email
Schema:
- AttributeDataType: String
Name: email
Required: false
ToDoCognitoUserPoolClient:
Type: AWS::Cognito::UserPoolClient
Properties:
UserPoolId: !Ref ToDoCognitoUserPool
ClientName: !Sub ${AWS::StackName}-Client
GenerateSecret: false
RefreshTokenValidity: 2
AccessTokenValidity: 1
IdTokenValidity: 1
PreventUserExistenceErrors: ENABLED
ExplicitAuthFlows:
- ALLOW_CUSTOM_AUTH
- ALLOW_USER_SRP_AUTH
- ALLOW_REFRESH_TOKEN_AUTH
- ALLOW_ADMIN_USER_PASSWORD_AUTH
Outputs:
# ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
# Find out more about other implicit resources you can reference within SAM
# https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
ToDoApi:
Description: API Gateway endpoint URL for Prod stage for ToDo function
Value: !Sub "https://${ToDoApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/todo/"
ToDoFunction:
Description: ToDo Lambda Function ARN
Value: !GetAtt ToDoFunction.Arn
ToDoIamRole:
Description: Implicit IAM Role created for ToDo function
Value: !GetAtt ToDoFunctionRole.Arn
1つ1つ見ていきましょう。
Globalsセクション
Globals:
Function:
Timeout: 3
MemorySize: 128
Tracing: Active
Api:
TracingEnabled: true
OpenApiVersion: 3.0.1
Globalsセクション自体は、SAMテンプレートで宣言するリソースの共通設定です。
- Function
実装途中で、複数のAWS::Serverless::Function
があったので共通設定に書いたのですが、現状では1つなのでGlobalsセクションでなくていいのですが、今後増える可能性を考えてこのままとします。 - Function-Timeout
Lambda関数のタイムアウト値です。
デフォルト値は3秒、最大値15分。1秒単位で設定可能。
タイムアウト値のベストプラクティスは、えーと...
AWS Lambda 関数を使用するためのベストプラクティス
Lambda 関数のロードテストにより、最適なタイムアウト値を決定します。関数の実行時間を分析し、依存関係サービスの問題に伴って関数の同時実行が必要以上に増えるような状況をより的確に判定します。
自分で決めなさいよと。
すでに動作確認ができているので、実際どのくらいの処理時間がかかっているか見てみます。
マネージメントコンソールにて、該当する関数のモニタリングタブ > トレースで見れます。
(X-Rayトレースがアクティブの場合)
これを見ると、GET、POST、PUT、DELETE共に正常時は0.3秒以下でした。
なので、今回は3秒のままいきます。
本番環境だと、タイムアウトした時のリカバリー処理をちゃんと入れないといけないんだろうなあ。
- Function-MemorySize【STAY】
関数に割り当てるメモリ量です。
128MB
から10,240MB
までの任意の量のメモリを1MB
単位で関数に割り当てることができます。
今回は大した処理を行わないこと、前述の関数処理時間により十分なのでこのままとします。
Lambda使用量に大きく影響するため余分な割り当てはしないとですね。 - Function-Tracing【STAY】
関数のX-Rayトレーシングモードを指定します。
有効な値は、Active
orPassThrough
上記で見た通り、かなり詳細なトレースが分かりやすく可視化できます。今回はActiveのままとします。 - Api-TracingEnabled【STAY】
APIステージのX-Rayアクティブトレースの有効・無効の設定です。
こちらも、かなり詳細なトレースが分かりやすく可視化できます。今回はtrueのままとします。 - Api-OpenApiVersion【STAY】
SAMを使ってAPI Gatewayをデプロイすると、Stage
という不要なステージが作成されてしまう解決策でいれています。
ふ〜。やっとGlobalsセクションが終わりました。
どんどんいっちゃいましょう。
Resouceセクション - AWS::Serverless::Api
Resources:
ToDoApi:
Type: AWS::Serverless::Api
Properties:
StageName: v1
Cors: "'*'"
Auth:
DefaultAuthorizer: ToDoCognitoAuthorizer
Authorizers:
ToDoCognitoAuthorizer:
UserPoolArn: !GetAtt ToDoCognitoUserPool.Arn
- StageName【STAY】
指定しないとprod
となります。
ここ以外でエンドポイントのURIにバージョン番号入れる方法がないのでここで入れる。
Web API The Good Parts
「5章 設計変更しやすいWeb APIを作る」5.2.2 バージョン番号をどう付けるか
を読む限り、URIのパスにバージョンを入れる方法が最もよく利用されているとのことなので。
ちなみに、GitHub APIでは、X-GitHub-Api-Version
ヘッダーにAPI指定バージョンを指定する必要がある。
ヘッダーでのバージョン指定が主流? - Cors【STAY】
CORS (Cross-Origin Resource Sharing)とは、ブラウザがオリジン(HTMLを読み込んだサーバのこと)以外のサーバからデータを取得することです。この設定がなされていないAPIは、ドメインが違うWebページ上のJavascriptから呼び出すことができません。
Amazon API Gatewayを利用して作成したAPIは、デフォルトのままではCORSが有効になっていないため、他のウェブページ(自分のサイトのWebページなど)から動的に呼び出すことができません。
引用元:Amazon API Gateway をクロスオリジンで呼び出す (CORS)
ということで、別ドメインのWebアプリケーションからアクセスできるように有効にしておきます。
- Auth
今回、APIへのアクセス制御をCognitoユーザープールで行うため必須設定です。
ちなみに、Fn::GetAtt
組み込み関数を使用して、後述のCognitoユーザープールのARNを指定しています。
Resouceセクション - AWS::Serverless::Function
テンプレートファイル内容です。長いので折りたたみました。
Resources:
Type: AWS::Serverless::Function
Properties:
Environment:
Variables:
ENV_DB_TABLE: todo
ENV_DB_REGION: ap-northeast-1
ENV_DB_HOST: https://dynamodb.ap-northeast-1.amazonaws.com
ENV_TZ: Asia/Tokyo
CodeUri: todo/
Handler: app.lambda_handler
Runtime: python3.9
Architectures:
- x86_64
FunctionName: todo
Policies:
DynamoDBCrudPolicy:
TableName: !Ref TodoTable
Events:
GetToDoList:
Type: Api
Properties:
RestApiId: !Ref ToDoApi
Path: /todo/
Method: get
PostToDo:
Type: Api
Properties:
RestApiId: !Ref ToDoApi
Path: /todo/
Method: post
GetToDo:
Type: Api
Properties:
RestApiId: !Ref ToDoApi
Path: /todo/{id}
Method: get
PutToDo:
Type: Api
Properties:
RestApiId: !Ref ToDoApi
Path: /todo/{id}
Method: put
DeleteToDo:
Type: Api
Properties:
RestApiId: !Ref ToDoApi
Path: /todo/{id}
Method: delete
- Environment【STAY】
Lambdaの環境変数の設定です。
DynamoDBの設定を環境変数にて持って、プログラムからアクセスするために必要です。 - Runtime【STAY】
python3.9
を選択してます。
最新は、python3.10
ですが、2023年4月に利用可能になったばかりで、関連問題などでつまずきたくなかったため、python3.9
としています。 - Events【STAY】
関数をトリガーするイベントを指定します。
1APIで、methodをanyにすることもできるようですが、設定してところAPIの全メソッドが受信対象となってしまうので、今回使用するメソッドが明確なので、メソッドごとに設定しました。
Resouceセクション - AWS::DynamoDB::Table
Resources:
TodoTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: todo
AttributeDefinitions:
- AttributeName: userid
AttributeType: S
- AttributeName: todoid
AttributeType: S
KeySchema:
- AttributeName: userid
KeyType: HASH
- AttributeName: todoid
KeyType: RANGE
ProvisionedThroughput:
ReadCapacityUnits: "3"
WriteCapacityUnits: "3"
- ProvisionedThroughput【STAY】
DynamoDBの読み取りおよび書き込みスループットのプロビジョニング情報です。
ProvisionedThroughputを指定している
→請求モード:PROVISIONED
(予測可能なワークロード)
ProvisionedThroughputを指定しない
→請求モード:PAY_PER_REQUEST
(予測不可能なワークロード)
読み込みキャパシティーユニット:3(Auto Scaling無効)
書き込みキャパシティーユニット:3(Auto Scaling無効)
→1秒間に3回の読み書きができる。
料金は利用している時間分だそう。
ひとまず、この設定のままとする。
Resouceセクション - AWS::ResourceGroups::Group、AWS::ApplicationInsights::Application
Resources:
ApplicationResourceGroup:
Type: AWS::ResourceGroups::Group
Properties:
Name:
Fn::Sub: ApplicationInsights-SAM-${AWS::StackName}
ResourceQuery:
Type: CLOUDFORMATION_STACK_1_0
Resources:
ApplicationInsightsMonitoring:
Type: AWS::ApplicationInsights::Application
Properties:
ResourceGroupName:
Ref: ApplicationResourceGroup
AutoConfigurationEnabled: 'true'
- ApplicationInsightsMonitoring【STAY】
Resource Groups を使用すると、単一のページでリソースを表示および管理できます。
Resource Groupsを使用しないと、複数のコンソールにアクセスしてサービスのステータスを確認したり、アプリケーションの 1 つのバージョンの設定を変更したりする必要があります。
→作っといてなんですが、各サービスにそれぞれアクセスして別タブで開いて操作してました。
これは、同一アカウントに大量のリソースがあるような開発環境だと、各サービスがまとまっているのですごく便利なのでしょうね。
ということで残します。
Resouceセクション - AWS::Cognito::UserPool
Resources:
ToDoCognitoUserPool:
Type: AWS::Cognito::UserPool
Properties:
UserPoolName: !Sub ${AWS::StackName}-UserPool
Policies:
PasswordPolicy:
MinimumLength: 8
UsernameAttributes:
- email
Schema:
- AttributeDataType: String
Name: email
Required: false
- AWS::Cognito::UserPool【STAY】
今回、APIへのアクセス制御をCognitoユーザープールで行うため必須設定です。
Resouceセクション - AWS::Cognito::UserPoolClient
Resources:
ToDoCognitoUserPoolClient:
Type: AWS::Cognito::UserPoolClient
Properties:
UserPoolId: !Ref ToDoCognitoUserPool
ClientName: !Sub ${AWS::StackName}-Client
GenerateSecret: false
RefreshTokenValidity: 2
AccessTokenValidity: 1
IdTokenValidity: 1
PreventUserExistenceErrors: ENABLED
ExplicitAuthFlows:
- ALLOW_CUSTOM_AUTH
- ALLOW_USER_SRP_AUTH
- ALLOW_REFRESH_TOKEN_AUTH
- ALLOW_ADMIN_USER_PASSWORD_AUTH
- AWS::Cognito::UserPoolClient【STAY】
今回、APIへのアクセス制御をCognitoユーザープールで行うため必須設定です。
以下3つの単位は、日です。
・RefreshTokenValidity: 2
・AccessTokenValidity: 1
・IdTokenValidity: 1 - ExplicitAuthFlows【STAY】
ExplicitAuthFlows の値を指定しない場合、以下の3つがデフォルトサポートになります。 - ALLOW_CUSTOM_AUTH
Lambdaトリガーベースのカスタム認証 - ALLOW_USER_SRP_AUTH
SRPプロトコルベースの認証 - ALLOW_REFRESH_TOKEN_AUTH
更新トークンベースの認証(必須)
他2つは以下のような認証 - ALLOW_ADMIN_USER_PASSWORD_AUTH
認証のための管理APIのユーザー名パスワード認証
→CognitoユーザーのアクセストークンおよびIDトークンを、AWS CLIから取得するために必要でした。設定されていないとエラーになります。 - ALLOW_USER_PASSWORD_AUTH
ユーザー名とパスワードの認証
Outputsセクション
Outputs:
ToDoApi:
Description: API Gateway endpoint URL for Prod stage for ToDo function
Value: !Sub "https://${ToDoApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/todo/"
ToDoFunction:
Description: ToDo Lambda Function ARN
Value: !GetAtt ToDoFunction.Arn
ToDoIamRole:
Description: Implicit IAM Role created for ToDo function
Value: !GetAtt ToDoFunctionRole.Arn
- Outputs【CHANGE】
Outputs セクションは、以下の出力で使用されるとのことです。- 他のスタックにインポートする (クロススタック参照を作成)、応答として返す (スタック呼び出しについて記述)
- AWS CloudFormation コンソールで表示する出力値
2つ目は、CLIでデプロイ時に、最後に出力する内容の定義ですね。
以下のような形で出力されます。
.
CloudFormation outputs from deployed stack
---------------------------------------------------------------------------------------------------------------------
Outputs
---------------------------------------------------------------------------------------------------------------------
Key ToDoApi
Description API Gateway endpoint URL for Prod stage for ToDo function
Value https://xxxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/todo/
Key ToDoIamRole
Description Implicit IAM Role created for ToDo function
Value arn:aws:iam::99999999999:role/todo-app-ToDoFunctionRole-XXXXXXXXXXX
Key ToDoFunction
Description ToDo Lambda Function ARN
Value arn:aws:lambda:ap-northeast-1:99999999999:function:todo
---------------------------------------------------------------------------------------------------------------------
Successfully created/updated stack - todo-app in ap-northeast-1
内容あまり気にしてなくて実環境と違っていました。ステージ名など直します。
また、以下の注意事項もあります。あまり情報出さない方が良いですね。
CloudFormation は、[Outputs] (出力) セクションに含める情報の編集または難読化を行いません。このセクションを使用して、パスワードやシークレットなどの機密情報を出力しないことを強くお勧めします。
テンプレート内容(見直し後)
テンプレートファイル内容です。長いので折りたたみました。
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
todo-app
SAM Template for todo-app
Globals:
Function:
Timeout: 3
MemorySize: 128
Tracing: Active
Api:
TracingEnabled: true
OpenApiVersion: 3.0.1
Resources:
ToDoApi:
Type: AWS::Serverless::Api
Properties:
StageName: v1
Cors: "'*'"
Auth:
DefaultAuthorizer: ToDoCognitoAuthorizer
Authorizers:
ToDoCognitoAuthorizer:
UserPoolArn: !GetAtt ToDoCognitoUserPool.Arn
ToDoFunction:
Type: AWS::Serverless::Function
Properties:
Environment:
Variables:
ENV_DB_TABLE: todo
ENV_DB_REGION: ap-northeast-1
ENV_DB_HOST: https://dynamodb.ap-northeast-1.amazonaws.com
ENV_TZ: Asia/Tokyo
CodeUri: todo/
Handler: app.lambda_handler
Runtime: python3.9
Architectures:
- x86_64
FunctionName: todo
Policies:
DynamoDBCrudPolicy:
TableName: !Ref TodoTable
Events:
GetToDoList:
Type: Api
Properties:
RestApiId: !Ref ToDoApi
Path: /todo/
Method: get
PostToDo:
Type: Api
Properties:
RestApiId: !Ref ToDoApi
Path: /todo/
Method: post
GetToDo:
Type: Api
Properties:
RestApiId: !Ref ToDoApi
Path: /todo/{id}
Method: get
PutToDo:
Type: Api
Properties:
RestApiId: !Ref ToDoApi
Path: /todo/{id}
Method: put
DeleteToDo:
Type: Api
Properties:
RestApiId: !Ref ToDoApi
Path: /todo/{id}
Method: delete
TodoTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: todo
AttributeDefinitions:
- AttributeName: userid
AttributeType: S
- AttributeName: todoid
AttributeType: S
KeySchema:
- AttributeName: userid
KeyType: HASH
- AttributeName: todoid
KeyType: RANGE
ProvisionedThroughput:
ReadCapacityUnits: "3"
WriteCapacityUnits: "3"
ApplicationResourceGroup:
Type: AWS::ResourceGroups::Group
Properties:
Name:
Fn::Sub: ApplicationInsights-SAM-${AWS::StackName}
ResourceQuery:
Type: CLOUDFORMATION_STACK_1_0
ApplicationInsightsMonitoring:
Type: AWS::ApplicationInsights::Application
Properties:
ResourceGroupName:
Ref: ApplicationResourceGroup
AutoConfigurationEnabled: 'true'
ToDoCognitoUserPool:
Type: AWS::Cognito::UserPool
Properties:
UserPoolName: !Sub ${AWS::StackName}-UserPool
Policies:
PasswordPolicy:
MinimumLength: 8
UsernameAttributes:
- email
Schema:
- AttributeDataType: String
Name: email
Required: false
ToDoCognitoUserPoolClient:
Type: AWS::Cognito::UserPoolClient
Properties:
UserPoolId: !Ref ToDoCognitoUserPool
ClientName: !Sub ${AWS::StackName}-Client
GenerateSecret: false
RefreshTokenValidity: 2
AccessTokenValidity: 1
IdTokenValidity: 1
PreventUserExistenceErrors: ENABLED
ExplicitAuthFlows:
- ALLOW_CUSTOM_AUTH
- ALLOW_USER_SRP_AUTH
- ALLOW_REFRESH_TOKEN_AUTH
- ALLOW_ADMIN_USER_PASSWORD_AUTH
Outputs:
ToDoApi:
Description: API Gateway endpoint URL for v1 stage for ToDo function
Value: !Sub "https://${ToDoApi}.execute-api.${AWS::Region}.amazonaws.com/v1/todo/"
まとめ
チェックしたところ、特に直すべきところは見つかりませんでした。
ただ、1つ1つ設定の意味を調べてしっかりと内容を理解することができました。
引き続きIaCしていきます。
Discussion