のべラボ.blog

Tech Blog | AWS や サーバーレスやコンテナ などなど

AWS SAM で Open API の API 仕様を使って Amazon API Gateway の REST API を作成する

Amazon API Gateway では、 Open API 仕様(OpenAPI-Specification)をインポートすることで REST API を作成できます。

docs.aws.amazon.com

例えば、/order のパスで POST リクエストを受け付ける API を Open API 仕様に基づきファイルに記述しておくと、AWS CLI のコマンドにて API GatewayREST API としてインポートできます。

ただし、その API から AWS Lambda 関数を呼び出すための、いわゆる 統合 の設定については Amazon API Gateway 固有のものであり、Open API 仕様には含まれていません。

それを実現するためには、Open API の仕様に対する API Gateway 拡張という記述方法があるので、それを使用します。

docs.aws.amazon.com

一方、AWS Lambda 関数や Amazon API GatewayAPI をコードからデプロイするには、AWS SAM という強力なツールも存在します。

そこで今回は、Open API 仕様をベースに API をデザインして、それを AWS SAM を使用して Amazon API GatewayREST API としてデプロイしてみたいと思います。

この REST API から呼び出す AWS Lambda 関数も 同じ AWS SAM テンプレートで作成する前提です。

(この記事の内容は、2023年 1月に検証した内容を基に記載しています。)


使用する Open API 仕様

今回は、/hello のパスで GET リクエストを発行すると、Python で記述した AWS Lambda関数を呼び出す API を作ります。

使用する Open API 仕様は下記です。これを demo-openapi.yaml として保存します。

openapi: "3.0.1"
info:
  title: "demo-sam-openapi-api"
  version: "v1"
paths:
  /hello:
    get:
      responses:
        "200":
          description: "200 response"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Empty"
      x-amazon-apigateway-integration:
        httpMethod: "POST"
        uri: 
          Fn::Sub: "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${DemoSamOpenApiFunction.Arn}/invocations"
        responses:
          default:
            statusCode: "200"
        passthroughBehavior: "when_no_match"
        contentHandling: "CONVERT_TO_TEXT"
        type: "aws_proxy"
components:
  schemas:
    Empty:
      title: "Empty Schema"
      type: "object"

上記中で、x-amazon-apigateway-integration: で指定している箇所が Amazon API Gateway の拡張部分で、AWS Lambda 関数とのプロキシ統合を指定しています。

プロキシ統合の対象、つまり呼び出す AWS Lambda 関数自体も 同じ AWS SAM テンプレートで作成するため、uri: で指定する AWS Lambda 関数の ARN は組込み関数 Sub を用いて設定しています。


使用する AWS SAM テンプレート

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  sam-openapi

  Sample SAM Template for sam-openapi

Globals:
  Function:
    Timeout: 3

Resources:
  DemoSamOpenApi:
    Type: AWS::Serverless::Api
    Properties:
        Name: demo-sam-openapi-api
        StageName: Prod
        EndpointConfiguration:
          Type: REGIONAL
        DefinitionBody:
            Fn::Transform:
                Name: AWS::Include
                Parameters:
                    Location: demo-openapi.yaml
  DemoSamOpenApiFunction:
    Type: AWS::Serverless::Function 
    Properties:
      FunctionName: demo-sam-openapi-function
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.7
      Events:
        DemoSamOpenApiEvent:
          Type: Api 
          Properties:
            Path: /hello
            Method: get
            RestApiId: !Ref DemoSamOpenApi

Outputs:
 DemoSamOpenApi:
    Description: "API Gateway endpoint URL for Prod stage for DemoSamOpenApiFunction"
    Value: !Sub "https://${DemoSamOpenApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
 DemoSamOpenApiFunction:
    Description: "DemoSamOpenApiFunction ARN"
    Value: !GetAtt DemoSamOpenApiFunction.Arn
 DemoSamOpenApiFunctionIamRole:
    Description: "Implicit IAM Role created for DemoSamOpenApiFunction"
    Value: !GetAtt DemoSamOpenApiFunctionRole.Arn

上記テンプレートでは、Resources: にある DemoSamOpenApiAPI を定義しています。

その中で、Open API 仕様のファイルを指定します。

Open API 仕様のファイル ( demo-openapi.yaml ) は、この AWS SAM テンプレートのファイルと同じ場所にコピーしておきます。

なお今回は、AWS SAM テンプレート のファイルと Open API の仕様のファイルを別にしてローカルで保存していますが、Open API の仕様をそのまま AWS SAM テンプレートの中に記述することもできます。

その他の方法として、Open API の仕様ファイルを Amazon S3 バケットに保存して、AWS SAM テンプレートから その S3 の URI を指定することもできます。

AWS Lambda 関数は、今回の例では AWS SAM プロジェクトのフォルダに hello_world/ フォルダを作成し、そこに下記の PythonAWS Lambda 関数のコードを app.py として保存しています。

これは、hello world を返すだけのシンプルなコードです。

import json

def lambda_handler(event, context):

    return {
        "statusCode": 200,
        "body": json.dumps({
            "message": "hello world",
        }),
    }

AWS SAM テンプレートのデプロイと 結果確認

テンプレートの検証とビルドを行い、その後デプロイします。

sam validate
sam build
sam deploy --guided

上記では --guided オプションをつけて sam deploy を実行しているので、その後 対話式でデプロイが進みます。下記の時だけ y を入力します。それ以外はデフォルトのままで大丈夫です。

DemoSamOpenApiFunction may not have authorization defined, Is this okay? [y/N]:

デプロイが完了すると、出力された中で Key が DemoSamOpenApiValue で示されている URL を Web ブラウザでアクセスします。

下記は例です。

Key                 DemoSamOpenApi                                                              
Description         API Gateway endpoint URL for Prod stage for DemoSamOpenApiFunction          
Value               https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/  

その結果、下記のような hello world のメッセージが表示されれば成功です。

{"message": "hello world"}

やってみた所感

さて、今回やってみて成功したのですが、ひとつだけ気になる部分があります。

それは、AWS SAM テンプレートのリソースで AWS Lambda 関数の Events: の指定です。

この Events: で指定している内容は、Open API 仕様の中で指定している内容と同じなので、二重定義になってしまいます。

であれば、この Events: を記述しなければいい、という発想になるのですが、記述しない場合、Amazon API GatewayAPI からのアクセスを許可するという AWS Lambda 関数のリソースベースのポリシーが作成されません。

ではでは、そのポリシーを作成するように自分で明示的に記述すればいいではないかという発想になります。

確かに、下記のようなポリシー定義を AWS SAM テンプレートに追記することで Events: の記述は不要になります。

  DemoSamOpenApiFunctionPermission:
    Type: "AWS::Lambda::Permission"
    Properties: 
      Action: lambda:InvokeFunction
      FunctionName: !Ref DemoSamOpenApiFunction
      Principal: apigateway.amazonaws.com

ただし、上記のような記述でデプロイすると、AWS Lambda 関数のトリガーとして Amazon API GatewayAPI が認識されず、AWS マネジメントコンソールで AWS Lambda 関数のページを表示した場合、トリガーが表示されません。

それでも気にしない、運用上問題ない、ということであればよいのですが、どこか気持ち悪さを感じますよね。

よって、ポリシーも(自動)作成して、かつAWS Lambda 関数のトリガーとして Amazon API GatewayAPI を認識させたい場合は、 Events: を記載するのが良いかもしれません。

ただ、Open API 仕様で記述した内容と同じような内容を再び記述することになるので、混乱せず注意して記述するようにしましょう。

AWS Cloud9 の デバッグ機能をさわってみる

今回は AWS Cloud9 のデバッグ機能の基本的な使い方をまとめたいと思います。

AWS Cloud9は、ブラウザのみでコードを記述、実行、デバッグできる統合開発環境 (IDE) です。

aws.amazon.com

Cloud9 には、あらかじめ AWS CLIAWS SAM、AWS CDK などの 主要な AWSコマンドラインツールや、主要なプログラミング言語のランタイム、Git や Docker もインストールされており、短時間で環境をプロビジョニングできるので、私も重宝してます。

この Cloud9 でデバッグをサポートしているプログラミング言語は、次のドキュメントに記載されています。

docs.aws.amazon.com

Python や Node.js、PHPJava などがサポートされていますね。

今回は、Python のシンプルなプログラミングを使用してデバッグ機能を試していきます。

なお、この記事の内容は 2022年 12月に検証した内容を基にしています。


今回、デバッグ機能を適用する Python のコードです。

def add(num1, num2):
    return num1 + num2

a = 2
b = 8
result = add(a,b)
if result == 10:
    print('ten!')
print(result)

最初に addという足し算を行う関数を定義し、それを呼出し、結果が 10かどうかを確認します。10なら ten! というメッセージを表示しますが、10以外なら表示しません。そして最後に足し算の結果を表示します。非常にシンプルですね。

これを Cloud9 で calc.py というファイルに保存しておきます。


ブレークポイントの設定

では、この calc.py のコードにブレークポイントを設定してみます。

ブレークポイントを設定すると、デバッグ実行時にその設定したコードでいったん停止して、変数の状況などを確認できます。

Cloud9 では、行番号の左側をクリックすることで、ブレークポイントの設定や解除が行えます。

今回は、下図のように 4 行目 の a = 2 のコードに設定します。設定すると、ピンク色の丸印がつきます。

ブレークポイントの設定


デバッグ実行

次にデバッグ実行をしていきます。Cloud9 でデバッグ実行していく方法はいくつかありますが、今回は手っ取り早く実行する方法を紹介します。

まず、Cloud9 のメニューで Run をクリックして、デバッグではない通常の実行を行います。

すると Cloud9 の画面下部に結果が表示されますが、その中に 虫のアイコン が表示されているので、それをクリックして虫アイコンに色がついた状態にします。

このクリックにより、calc.py の実行をデバッグモードに切り替えることができます。

デバッグモードに設定

その後、再び Run メニューをクリックすると、デバッグモードで実行することができます。


Step Overと変数の内容の表示

デバッグ実行すると、ブレークポイントを設定した 4 行目の a = 2 で 実行が停止状態になります。

この状態で、Cloud9 の画面の右側に表示されている Debugger メニューをクリックします。

Debugger メニューをクリックすると、下図のようなペインが表示されます。

このペインの上にある、Step Over のメニュー(下図赤枠の部分)を1回クリックします。

するとブレークポイントで停止していた実行が1行進みます。これで ブレークポイントの 4 行目のコードの実行が完了した状態になります。

このように、ブレークポイントで実行をいったん停止し、1行づつコードを実行していくことを一般的に ステップ実行 などといいます。

また、下図の赤線部分をみると、変数 a には 2 という int 型の値が格納されていることがわかります。

このように、ブレークポイントでコードの実行をいったん停止したあとに、 Step Overでコードをステップ実行しながら変数の内容を確認していくことができます。

Step Over のメニューのクリックを続けてみましょう。すると 5 行目から 9行目まで 1行づつ実行され、デバッグ実行が完了しましたね。

ただ、1行目、2行目に定義されている add 関数のコードにステップ実行ができませんでした。

実は、Step Over では、呼び出している関数の内部のコードにステップ実行することができません。

それを行うには、Step IntoStep Into という操作を行います。


Step IntoとStep Out

では、Step IntoStep Out を試してみましょう。

もう一度 Run メニューをクリックして 4 行目のブレークポイントで停止します。

その後、6 行目まで、Step Over でステップ実行を続けます。

6 行目では add 関数を呼び出していますね。ステップ実行が 6 行目きたら、下図のように Step Intoのメニューをクリックします。

すると、ステップ実行が 2行目に進みましたね。

このように、呼び出し先の関数にステップ実行を進めるには、Step Into を行います。

また、呼び出し先の関数から、呼び出し元のコードにステップ実行を戻すには、Step Out を行います。

下図のように、ステップ実行が 2行目に進んだ状態で、Step Out のメニューをクリックします。

Step Outすると、下図のようにステップ実行が 7行目になりましたね。呼び出し元のコードに戻って、ステップ実行を続けることができます。


Resume と Stop

デバッグ実行中、ステップ実行ではなく、一気にコードを実行したい場合は、Resume メニューをクリックします。

これにより、次のブレークポイントが設定されていれば、そこまで一気にコードを実行できます。

また、デバッグ実行を途中で停止したい場合は画面下部にある Stop をクリックします。

これにより、ステップ実行の途中でもデバッグ実行を停止できます。


最後に

今回は、Cloud9 のデバッグ機能の基本的な使用方法についてまとめました。

Cloud9 のデバッグ機能は、他にも様々な使い方ができますので、下記のドキュメントを参考にしてみて下さい!

docs.aws.amazon.com

AWS Step Functions の Distributed Map ステートを試してみる

これまで、AWS Step Functions の Map ステートを試してみる記事を 2 つ(下記)書いてきました。

nobelabo.hatenablog.com

nobelabo.hatenablog.com

今回は、その続きとして 2022 年 12 月にアナウンスされた Distributed Map を試してみます。

aws.amazon.com

この記事の内容は、2022 年 12月時点で検証した内容に基づきます。リージョンは東京リージョンを使用しています。


これまでの Map ステートでは、並列度は 40 までしかサポートされていませんでした。

そのため、40 以上の並列度で処理をしたい場合は、Map ステートから別のステートマシンを呼出し、そのステートマシンでさらに Map ステートを使用するという Mapの入れ子構造を作成する必要がありました。

例えば、1024 の並列処理を行いたい場合は、親のステートマシンで 32 の並列度を指定した Map ステートを用意し、そこから呼び出した子となるステートマシンで 32 の並列度を指定した Map を構成することで、結果的に 32 × 32 = 1024 の並列度を実現していました。

しかし、今回 リリースされた Distributed Map では、並列度が 10,000 までサポートされましたので、「Map の 入れ子構造」を作成する必要はなくなりました。

では、実際に Distributed Map を使ったステートマシンを作成してみます。


今回も、Distributed Map に渡す配列は、下記の Lambda関数 ArrayGenerator で作成することにします。

この ArrayGenerator は Python 3.9 で作成されており、イベントオブジェクトから length というキーで指定された数値に基づき、動的に配列を生成してリターンします。

import json

def lambda_handler(event, context):
    print(event)
    length = event['length']
    items = []
    for id in range(length):
      items.append({'item': id})
    return items

次に、ステートマシンを作成します。下記の JSON を使用して、前回の記事と同様の手順で作成します。

なお、前回は Map から Lambda関数を invoke していましたが、今回はコスト面を考慮し、特に何もしないステートである Passステートを Distributed Map から実行する形にします。

ステートマシンの JSON は下記です。

(なお、Workflow Studio で GUI から作成することもできますが、その場合は Map ステートを選択して、「処理モード」として「分散」を指定します。)

{
  "Comment": "A description of my state machine",
  "StartAt": "Lambda Invoke",
  "States": {
    "Lambda Invoke": {
      "Type": "Task",
      "Resource": "arn:aws:states:::lambda:invoke",
      "Parameters": {
        "Payload.$": "$",
        "FunctionName": "arn:aws:lambda:ap-northeast-1:123412341234:function:ArrayGenerator"
      },
      "Retry": [
        {
          "ErrorEquals": [
            "Lambda.ServiceException",
            "Lambda.AWSLambdaException",
            "Lambda.SdkClientException"
          ],
          "IntervalSeconds": 2,
          "MaxAttempts": 6,
          "BackoffRate": 2
        }
      ],
      "ResultPath": "$.generatedArray",
      "Next": "Map"
    },
    "Map": {
      "Type": "Map",
      "ItemProcessor": {
        "ProcessorConfig": {
          "Mode": "DISTRIBUTED",
          "ExecutionType": "STANDARD"
        },
        "StartAt": "Pass",
        "States": {
          "Pass": {
            "Type": "Pass",
            "End": true
          }
        }
      },
      "End": true,
      "Label": "Map",
      "MaxConcurrency": 1000,
      "InputPath": "$.generatedArray.Payload"
    }
  }
}

作成したステートマシンは、下図のようになります。

内容は、Passステートを使っている以外は、こちらの記事の内容とほぼ同じですね。

では、AWS マネジメントコンソールから実行してみます。ひとまず、配列の要素数を 3 で指定してみます。

作成したステートマシンのページで [実行の開始] ボタンを選択して [入力] に下記のパラメータを指定します。[実行の開始] をクリックします。

{"length": 3}

結果、下図のようにステートマシンは成功します。

ページの下側の「イベント」セクションをみてみましょう。

Resource」列に「マップ実行」というリンクがありますね。これをクリックすると、次のように並列処理の結果を確認できます。

実行」セクションの中の「名前」のリンクをどれか1つクリックすると、Pass が実行されたことも確認できます。

問題なく動作できていたら、並列度を 40よりも大きい値でも試してみましょう。このステートマシンを実行する時のパラメータの値を変えるだけで試せます。

{"length": 50}

また、Distributed Map では全体の処理のうち、許容される失敗のしきい値を割合(%)、または失敗した数で指定することもできたり、処理対象を配列だけでなく Amazon S3バケットのオブジェクトにすることもできます。

非常に興味深いですね。このような Distributed Map のその他の機能も、試していきたいです!

AWS Lambda の Provisioned Concurrency を設定した後、利用可能になるまでの状況を確認してみる

本記事は「AWS LambdaとServerless Advent Calendar 2022」11日目の記事です。


AWS Lambda では、「プロビジョニングされた同時実行数」を設定することができます。本記事においては便宜上、この設定を Provisioned Concurrency と呼称します。

この Provisioned Concurrency の設定を行うと、AWS Lambda 関数の実行環境を初期化して、関数の呼び出しに即座に応答する準備を行うため、コールドスタートによるレイテンシーの影響を低減することが期待できます。ただし、この設定を行うと AWS アカウントに課金が発生することに注意してください。詳細は、次のドキュメントを確認しましょう。

docs.aws.amazon.com

この Provisioned Concurrency の設定は、AWS マネジメントコンソールからも行えます。

AWS マネジメントコンソールで 対象の AWS Lambda 関数のページを表示し、[設定] タブを選択、左側のメニューで、[同時実行] のメニューを選択すると [プロビジョニングされた同時実行設定] セクションが表示されるので、そのセクションから [追加] ボタンを選択します。

https://cdn-ak.f.st-hatena.com/images/fotolife/n/neob/20221203/20221203180449.png

その後、対象の AWS Lambda 関数のエイリアス または バージョン とプロビジョニングさせる実行環境の数を入力、保存することで設定は完了です。

https://cdn-ak.f.st-hatena.com/images/fotolife/n/neob/20221203/20221203180520.png

ただしここで設定したからといって、すぐに AWS Lambda 関数の実行環境が作成され、利用可能になるわけではありません。

上記の設定後、AWS マネジメントコンソールでは下図のように表示されます。

https://cdn-ak.f.st-hatena.com/images/fotolife/n/neob/20221203/20221203180547.png

指定した値にも依りますが、しばらく待ってから手作業でページをリフレッシュ表示しないと [準備完了] とは表示されません。

https://cdn-ak.f.st-hatena.com/images/fotolife/n/neob/20221203/20221203180611.png

ここで 私は 下記 2つの点が気になりました。

  • Provisioned Concurrency の設定後に利用可能になるまで、どれくらいの時間がかかったのか知りたいけど、現状のAWS マネジメントコンソールでは、その情報は表示されない。
  • Provisioned Concurrency の設定後、現時点でどれくらいの環境の数をプロビジョニングできたのか知りたいけど、現状のAWS マネジメントコンソールでは手動でページをリフレッシュしないと、その情報は表示されない。

しかし、次の AWS CLI のドキュメントを読んで、 aws lambda get-provisioned-concurrency-config コマンド を活用すれば、この気になった 2つの情報を知ることができるのでは、と考えました。

awscli.amazonaws.com

この AWS CLI のコマンドの出力には、下記が含まれます。

  • AllocatedProvisionedConcurrentExecutions: アロケート(作成)済の実行環境数
  • AvailableProvisionedConcurrentExecutions: 利用可能な実行環境数
  • Status: アロケートの状態

Provisioned Concurrency の設定後に、この AWS CLI のコマンドを継続的に実行すれば、上記 2つの情報を得られそうです。

そこで、下記のスクリプトを作成しました。(このスクリプトLinux での使用を前提にしています。)

#!/bin/bash
# Provisioned Concurrency を設定
aws lambda put-provisioned-concurrency-config --function-name $1 --qualifier $2 --provisioned-concurrent-executions $3
# 開始時刻を取得
started_time=$(date +'%s.%3N')
# Status が READY になるまで待機
while [ "$STATUS" != "READY" ]
do
  RESULT=$(aws lambda get-provisioned-concurrency-config --function-name $1 --qualifier $2)
  STATUS=$(echo ${RESULT} | jq -r '.Status')
  ALLOCATE=$(echo ${RESULT} | jq -r '.AllocatedProvisionedConcurrentExecutions')
  AVAILABLE=$(echo ${RESULT} | jq -r '.AvailableProvisionedConcurrentExecutions')
  echo `date`, ${STATUS}, ${ALLOCATE}, ${AVAILABLE} 
  sleep 1
done
# 完了時刻を取得
ended_time=$(date +'%s.%3N')
# 完了時刻 - 開始時刻で経過時間を算出
elapsed=$(echo "scale=3; $ended_time - $started_time" | bc)
# 経過時間を表示
echo "ELAPSED : ${elapsed} seconds"
# Provisioned Concurrencyの状況を表示
aws lambda get-provisioned-concurrency-config --function-name $1 --qualifier $2 

このスクリプトでは、第1引数に AWS Lambda 関数名を、第2引数に エイリアス名またはバージョンIDを、第3引数に Provisioned Concurrency の値を受け取る前提にしています。

また、Provisioned Concurrency の設定後、1秒毎に AWS CLI でステータスとアロケート済の環境数や利用可能な環境数を取得・表示させています。またステータスの値をチェックして 設定した Provisioned Concurrency が利用可能になったら、それまでかかった時間(秒)を算出して停止します。

実際の実行例を次に示します。 上記スクリプトを test.sh として実行しています。AWS Lambda 関数名は Demo-Burst、バージョンIDは3、Provisioned Concurrency の値は 100 で指定しています。

$ ./test.sh  Demo-Burst 3 100
{
    "RequestedProvisionedConcurrentExecutions": 100, 
    "AllocatedProvisionedConcurrentExecutions": 0, 
    "AvailableProvisionedConcurrentExecutions": 0, 
    "LastModified": "2022-12-03T06:30:51+0000", 
    "Status": "IN_PROGRESS"
}
Sat Dec 3 06:30:51 UTC 2022, IN_PROGRESS, 0, 0
Sat Dec 3 06:30:53 UTC 2022, IN_PROGRESS, 0, 0
Sat Dec 3 06:30:54 UTC 2022, IN_PROGRESS, 0, 0
(・・・中略・・・)
Sat Dec 3 06:32:41 UTC 2022, IN_PROGRESS, 0, 0
Sat Dec 3 06:32:43 UTC 2022, IN_PROGRESS, 0, 0
Sat Dec 3 06:32:45 UTC 2022, IN_PROGRESS, 0, 0
Sat Dec 3 06:32:46 UTC 2022, IN_PROGRESS, 87, 0
Sat Dec 3 06:32:48 UTC 2022, IN_PROGRESS, 87, 0
Sat Dec 3 06:32:50 UTC 2022, IN_PROGRESS, 87, 0
Sat Dec 3 06:32:52 UTC 2022, IN_PROGRESS, 87, 0
Sat Dec 3 06:32:54 UTC 2022, IN_PROGRESS, 87, 0
Sat Dec 3 06:32:55 UTC 2022, IN_PROGRESS, 87, 0
Sat Dec 3 06:32:57 UTC 2022, READY, 100, 100
ELAPSED : 127.205 seconds
{
    "RequestedProvisionedConcurrentExecutions": 100, 
    "AllocatedProvisionedConcurrentExecutions": 100, 
    "AvailableProvisionedConcurrentExecutions": 100, 
    "LastModified": "2022-12-03T06:30:51+0000", 
    "Status": "READY"
}

上記の出力はあくまで例の1つですが、Provisioned Concurrency の設定後、約 127秒で利用可能になりました。また、利用可能になるまでアロケートされた数として 87 という数字が確認できました。 もちろん、このスクリプトは1つの例ですが、少なくとも AWS マネジメントコンソールで手動でページをリフレッシュしなくともよくなりました。


このスクリプトを何度か実行していて、また気になる点が出てきました。

Provisioned Concurrency の設定後、現在のアロケートされた値はスクリプトで確認できるようになりましたが、最終的にステータスが READY、つまり利用可能になるのは、AvailableProvisionedConcurrentExecutions の値が設定した値になったタイミングです。

上記の出力例だと、途中にアロケートされた値が 87 と表示されてますが、いくら 87 の環境がアロケートされていても、その段階ではまだ Provisioned Concurrency によってプロビジョニングされた環境は利用不能ということになります。

https://cdn-ak.f.st-hatena.com/images/fotolife/n/neob/20221203/20221203181307.png

そこで、本当に利用できないのか確認してみました。

方法としてはシンプルに、上記のスクリプトを実行しアロケートされた値が 50 より大きい値になったタイミングで、その AWS Lambda関数に同時アクセスを行い、Amazon CloudWatch のメトリクスで確認することにしました。

この仕組みを解説すると長くなるので、概略だけ説明します。

  • 対象の AWS Lambda 関数は、実行環境が再利用されにくい状態で一気に同時アクセスを行いたかったので、コードの中に 3秒ほど sleep するコードを入れておきます。
    • Python だと、time.sleep(3) など
  • Amazon CloudWatch では、次のメトリクスを監視できるように Amazon CloudWatch のダッシュボードに設定しておきます。
    • ProvisionedConcurrencyInvocations
    • Invocations
    • ProvisionedConcurrentExecutions
    • ConcurrentExecutions
    • ProvisionedConcurrencyUtilization
    • ProvisionedConcurrencySpilloverInvocations
  • 一気に同時アクセスを行う方法として、下記の AWS Step Functions の Distributed Map を使ったステートマシンを実行することにしました。Distributed Mapを使用し、AWS Lambda 関数に対して 並列度 50 で同時アクセスしてみます。

aws.amazon.com

上記の仕組みで試してみた結果をみると、やはり アロケートの値が 50 以上になっても、ステータスが IN-PROGRESS の状態だと、Provisioned Concurrency として使用できない ということがわかりました。

Amazon CloudWatch の メトリクスを確認してみます。

  • まず Invocations が50に対して、ProvisionedConcurrencyInvocations は値なしという結果から、Provisioned Concurrency によって関数コードは実行されなかったことを示しています。

https://cdn-ak.f.st-hatena.com/images/fotolife/n/neob/20221203/20221203200929.png

  • 次に ConcurrentExecutions が 50に対して、ProvisionedConcurrentExecutions は値なしという結果から、Provisioned Concurrency の環境でイベントを処理しているものがないことがわかります。つまり通常 のコールドスタートのプロセスを経て実行環境がプロビジョニングされたことを示します。

https://cdn-ak.f.st-hatena.com/images/fotolife/n/neob/20221203/20221203201023.png

  • なお、ProvisionedConcurrencyUtilization と ProvisionedConcurrencySpilloverInvocations は値がないという結果になりました。これは、そもそも 利用可能な Provisioned Concurrency の環境がないため算出されなかったと推察できます。

https://cdn-ak.f.st-hatena.com/images/fotolife/n/neob/20221203/20221203180903.png


同じ環境で、Provisioned Concurrency のステータスが READY になった後に同様のアクセスを行うと、これらのメトリクスの値は下記のようになります。

  • Invocations が50に対して、ProvisionedConcurrencyInvocations も 50 という結果から、50のリクエストは全て Provisioned Concurrency 環境の AWS Lambda 関数を呼び出したことがわかります。

https://cdn-ak.f.st-hatena.com/images/fotolife/n/neob/20221203/20221203202626.png

  • ConcurrentExecutions が 50に対して、ProvisionedConcurrentExecutions も 50 という結果から、50 の Provisioned Concurrency の環境で AWS Lambda 関数のコードを処理したことがわかります。

https://cdn-ak.f.st-hatena.com/images/fotolife/n/neob/20221203/20221203202701.png

  • ProvisionedConcurrencyUtilization は 0.5、つまり Provisioned Concurrency の 100 の環境のうち 50 が使用されて AWS Lambda 関数が実行されたことがわかります。また ProvisionedConcurrencySpilloverInvocations は 0 であり、Provisioned Concurrency で賄いきれなかったリクエストがなかったことがわかります。

なお、AWS Lambda 関数の メトリクスについては下記のドキュメントにも説明がありますので、ご参照ください。

docs.aws.amazon.com


まとめ

  • AWS Lambda の Provisioned Concurrency を設定後の状況については、AWS CLI を活用することで取得、表示できます。
  • その AWS CLI でアロケートされた環境数は確認できるものの、ステータスが READY になるまでは、Provisioned Concurrency としては使用できません。

これからも、このように「ちょっとした疑問」から Dive Deep して調べてみる、ということをドンドンやっていきたいと思います!

なお繰り返しの説明になりますが、Provisioned Concurrency を使用すると料金がかかりますので、もし実際に試す場合は課金に十分に留意して下さい。 詳細は、下記をご参照ください。

aws.amazon.com

(本記事は 2022年12月11日時点で確認した内容を基に記載しています。)

AWS Lambda の同時実行数に関連する設定の整理

今回は、AWS Lambda の同時実行数に関連する各設定の整理をしていきます。


まず、AWS Lambda には サービスの制限値として、その AWS アカウントで同時に実行環境を作成できる数がリージョン毎に決められています。

これは、AWS Service Quotas で Concurrent executions の値で 確認することができます。

下図は東京リージョンで表示したもので 1,000となっています。ただし、[ 調整可能 ] 列の値が [ はい ] になっているので、緩和申請を行うことができます。

この 値は AWS Lambda 関数の同時実行の設定のページから、[予約されていないアカウントの同時実行] としても参照できます。

これはリージョン単位の値なので、例えば 東京リージョンに AWS Lambda 関数が A, B, C と 3つ作成した場合、その A, B, C の合計の同時実行数が 1,000 までということになります。


次に、特定の AWS Lambda 関数に対しての設定項目についてみていきます。

設定項目 デフォルト値 概要
同時実行の予約数 設定なし そのLambda関数の実行環境の作成を最大同時にいくつまで許可するか

もし AWS Lambda 関数 A に [同時実行の予約数] を 200 に設定すると、AWS Lambda関数 Aの実行環境が同時に作成される数は最大 200までとなります。

つまり、最大同時 200の実行が可能になります。これは逆にいうと、200までは予約・確保されているともいえます。

もし0に設定すると、Lambda関数 Aの実行環境が作成が全くできない、いわゆる ロットリング状態になり AWS Lambda関数 A は実行できない状態になります。


次に、特定の Lambda関数 の特定のエリアスまたは特定のバージョンに設定する項目についてみていきます。

設定項目 デフォルト値 概要
プロビジョニングされた同時実行数 設定なし そのLambda関数の実行環境を事前に作成しておく数

[プロビジョニングされた同時実行数] により、いわゆるコールドスタートと呼ばれる実行環境作成のオーバーヘッドを低減できるメリットが期待できますが、この設定を行うと別途課金が発生することを留意しておきましょう。


これまでみてきた設定項目に値を設定する上では、実はもう 1つ、知っておくべきルールがあります。

それは、[同時実行の予約数] や [プロビジョニングされた同時実行] を設定した結果、[予約されていないアカウントの同時実行]の残容量が 100 未満になってはいけない、というルールです。

[同時実行の予約数] や [プロビジョニングされた同時実行設定] は、1,000という容量に対して、特定の Lambda関数の容量を確保してしまう設定になります。ただし、その結果、残容量が 100未満になることは許されない、ということになります。

次の図は、[同時実行の予約数] での例を表したものですが、[プロビジョニングされた同時実行設定] でも同じです。

実際に AWS マネジメントコンソールで、残容量が 100未満になるような値を設定した時にも、設定不可としてエラーになります。

[同時実行の予約数] で で 910 を設定した場合の例

[プロビジョニングされた同時実行設定] で 910 を設定した場合の例


つまり、最大同時に 1,000の実行が可能であるからといって、特定の AWS Lambda 関数だけでその1,000という容量を全て予約・確保することはできません。 この [予約されていないアカウントの同時実行]の残容量が 100 未満になってはいけない、という制約はついつい見落としがちではないかと思うので、リマインドしておきたいと思います!

AWS X-Ray における Amazon SQS × AWS Lambda の新しいトレースリンクの機能を試してみる

今回は、最近発表されたAWS X-Ray の次の新機能を試してみます。

aws.amazon.com

従来までどうだったか、この新機能により何ができるようになったか、というのを簡単に図にまとめてみました。

https://cdn-ak.f.st-hatena.com/images/fotolife/n/neob/20221129/20221129162101.png

Amazon SQS にメッセージが送信され、その後 AWS Lambda 関数により取り出される処理についてもトレースを取得できるようになったわけですね。

この機能は自動的に適用されるので、使用している Amazon SQS の Queue や AWS Lambda 関数について追加の設定は不要です。

あくまで例ですが、次のような AWS SAM テンプレートから作成すれば、この機能を試すことができます。

github.com

上記の SAM テンプレートでは、Amazon SQS の Queue にメッセージを送信する AWS Lambda 関数と、受信する AWS Lambda 関数がリソースとして定義されています。

Globalsセクションで、それぞれの AWS Lambda 関数のプロパティに対して、Tracing: Active を設定し、AWS X-Ray のトレースを取得するようにしています。

Globals:
  Function:
    Timeout: 15
    Environment:
      Variables:
        SQS_QUEUE: !Ref SQSQUEUE
        DDB_TABLE: !Ref DDBTABLE
    Tracing: Active

同じく Globals セクションで、Amazon API Gateway にも AWS X-Rayのトレースを取得するようにしています。

 Api:
    TracingEnabled: True

またこのテンプレートでは、Amazon SQS の Queue にメッセージを送信する AWS Lambda 関数 は Python で作成しています。

github.com

この AWS Lambda 関数の中で下記のコードを適用して AWS Lambda 関数から他の AWS サービスにアクセスする処理のトレースも取得しています。

from aws_xray_sdk.core import patch
patch(['boto3'])

これらをみると、特に何も新しい設定やコードの追加などは行っていませんね。

では、この SAM テンプレートからスタックを作成します。テンプレートの中の Parameters セクションで、S3 バケット名は任意の値に変更して下さい。

SAMのリソース全体は、次から参照できます。samconfig.toml の内容も環境に応じて変更して下さい。

github.com

この SAM テンプレートで作成するアプリは、作成した S3 バケットにあらかじめ何かテキストファイルを入れてから実行する前提になっているので、適当なテキストファイルを S3 バケットに入れておきましょう。

その後、Amazon API Gateway に対して API リクエストを送ってみます。

curl -X POST \
    https://(API Gateway のURL)/Prod/xray \
    -d '{"bucket": "(バケット名)", "key": "(テキストファイル名)"}'

その後、AWS マネジメントコンソールで Amazon CloudWatch のコンソールを開き、左側のナビゲーションメニューから X-Ray トレース の下の サービスマップ を選択します。

次のようなマップが表示されます。赤枠の部分が新機能により表示されるようになったものです。

Amazon SQS の Queue の後にも、そのメッセージを受信している AWS Lambda 関数の情報が表示されていますね。

https://cdn-ak.f.st-hatena.com/images/fotolife/n/neob/20221128/20221128140048.png

次に左側のナビゲーションメニューから X-Ray トレース の下の トレース を選択し、ページ下側にある トレース セクションの中の ID のリンクを選択します。

次のようなトレースマップが表示されます。赤枠の部分が新機能により表示されるようになったものです。

https://cdn-ak.f.st-hatena.com/images/fotolife/n/neob/20221128/20221128140227.png

ページ下側に表示されるセグメントのタイムラインでは、次のような情報が参照できます。赤枠の部分が新機能により表示されるようになったものです。

https://cdn-ak.f.st-hatena.com/images/fotolife/n/neob/20221201/20221201145142.png

このように、Amazon SQS の Queue が介在する場合でも、一気通貫的なトレースを参照できるようになったので、Queue からメッセージを受信している AWS Lambda 関数をすぐに把握できてトレース分析ができるようになりました。個人的には、これはとてもありがたいことだと感動しています!

Amazon SNS の ペイロードベースのメッセージフィルタリングを試してみる

従来、Amazon SNSサブスクリプションでは、メッセージの属性値ベースのフィルタリングは可能だったんですが、最近の Update でメッセージ本文、つまり『ペイロード』ベースのフィルタリングも可能になりました。

aws.amazon.com

下記の AWS ブログでは、AWS SAM テンプレートを使用してこの機能を試せるように解説してくれています。

aws.amazon.com

個人的には、この Update は大きい と感じています。

従来、他のアプリケーションやサービスに送信したいメッセージの本文に対してフィルタリングを設定したい場合は、Amazon EventBridge を使うことが多かったのですが、Amazon SNS でも可能になったので選択肢の幅が広がったといえます。例えば、シンプルに AWS Lambda 関数にメッセージを送信したいだけであれば、Amazon SNS の場合は通知件数に対する課金は無料になるので、コスト節減も期待できます。(データ転送容量に対する課金は発生します。)


では、今回は AWS マネジメントコンソール を使ってシンプルにこの機能を試していきます。

なお、ここで紹介する内容は、 2022年 11月 27日時点に確認した内容に基づきます。

今回は、Amazon SNS のサブスクライバーAmazon SQS の標準キューを使用する前提とします。

任意の名前で 標準キューを作成します。また、そのキューのアクセスポリシーに次ようなステートメント追加しておきます。(デフォルトのステートメントの変更や削除はしません。)

{
      "Sid": "__sns_statement",
      "Effect": "Allow",
      "Principal": {
        "Service": "sns.amazonaws.com"
      },
      "Action": "SQS:SendMessage",
      "Resource": "arn:aws:sqs:ap-northeast-1:123412341234:MyQueue"
    }

Resource: で指定するのは 作成した Amazon SQS のキューのARN なので、環境に応じて変更して下さい。

次に、Amazon SNS の 標準トピックを任意の名前で作成します。

続いて、その標準トピックのサブスクリプションを作成します。

サブスクリプションは、事前に作成しておいた Amazon SQS のキューを指定します。

その後は、いよいよフィルターの設定です。

次の図のように、[サブスクリプションポリシー] を展開表示して、[サブスクリプションフィルターポリシー]のトグルを ON にします。そして、[メッセージ本文]を選択します。

https://cdn-ak.f.st-hatena.com/images/fotolife/n/neob/20221127/20221127155830.png

[ JSON エディタ]にフィルターとなる JSON を指定しますが、今回はシンプルに下記のように設定しました。

{
  "test": [
    0,
    1,
    2
  ],
  "check": [
    "A",
    "B",
    "C"
  ]
}

このフィルターでは、メッセージは "test" キーと "check" キーを持ち、"test" キーは、0,1,2 のいずれかの値、"check"キーは、"A","B","C"のいずれかの値が条件になります。

ここまで設定出来たら、サブスクリプションの作成を完了します。

AWS マネジメントコンソールを使って、作成した標準トピックにメッセージを発行しましょう。

[件名]は任意の値を入力しますが、[本文]では、フィルターの条件にマッチするように次の JSON を入力します。

{
  "test": 0,
  "check":"A"
}

メッセージを発行した後、AWS マネジメントコンソールを使ってサブスクリプションに指定したAmazon SQS のキューのメッセージを受信してみましょう。1件、メッセージを受信しているはずです。メッセージの ID のリンクを選択して、送信したメッセージを受信できていることを確認しましょう。

また、他のメッセージも送信してみましょう。

例えば、次のメッセージだとフィルターの条件にマッチするので、キューでは受信されません。

{
  "test": 9,
  "check":"A"
}

次のメッセージも、フィルターの条件にマッチしません。

{
"test": 0
}

サブスクリプションのフィルターの内容を変更して色々試してみてもよいでしょう。 複雑なフィルターを記述することもできるので、次のドキュメントを参考にしてみて下さい!

docs.aws.amazon.com

/* -----codeの行番号----- */