🤔

[AWS SAM]テンプレート内容を今一度チェック(自己レビュー)した結果...

2023/07/01に公開

はじめに


以下のような、サーバーレスアプリケーション構築を行なっているAWS SAMのテンプレートファイルの内容を今一度チェック(自己レビュー)した結果です。
どこかからコピペしたままの部分もあったので、パラメータ設定値の意味や、設定理由などをしっかりと考えて理解を深めました。

テンプレートファイル内容

テンプレートファイル内容です。長いので折りたたみました。
template.yaml
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セクション

template.yaml
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 or PassThrough
    上記で見た通り、かなり詳細なトレースが分かりやすく可視化できます。今回はActiveのままとします。
  • Api-TracingEnabled【STAY】
    APIステージのX-Rayアクティブトレースの有効・無効の設定です。
    こちらも、かなり詳細なトレースが分かりやすく可視化できます。今回はtrueのままとします。
  • Api-OpenApiVersion【STAY】
    SAMを使ってAPI Gatewayをデプロイすると、Stageという不要なステージが作成されてしまう解決策でいれています。


    ふ〜。やっとGlobalsセクションが終わりました。
    どんどんいっちゃいましょう。

Resouceセクション - AWS::Serverless::Api

template.yaml
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

テンプレートファイル内容です。長いので折りたたみました。
template.yaml
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

template.yaml
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

template.yaml
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

template.yaml
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

template.yaml
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セクション

template.yaml
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] (出力) セクションに含める情報の編集または難読化を行いません。このセクションを使用して、パスワードやシークレットなどの機密情報を出力しないことを強くお勧めします。

テンプレート内容(見直し後)

テンプレートファイル内容です。長いので折りたたみました。
template.yaml
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