【AWS SAM】API Gateway+SQS+Lambdaの構築および動作確認

記事タイトルとURLをコピーする

こんにちは。AWS CLIが好きな福島です。

はじめに

今回は、AWS SAMを利用して、API Gateway+SQS+Lambdaを構築する機会があったため、備忘としてブログにまとめます。

また、API Gateway+SQS+Lambdaの動きを見るために3つの動作確認を行います。

参考

https://docs.aws.amazon.com/ja_jp/prescriptive-guidance/latest/patterns/integrate-amazon-api-gateway-with-amazon-sqs-to-handle-asynchronous-rest-apis.html

構成

今回構築する構成は以下の通りです。

作成するリソース

  • ①API Gateway
  • ②IAM Role(API Gateway用)
  • ③SQS * 2
    • API Gatewayからのメッセージを保持する用
    • デッドレターキュー用
  • ④Lambda

ポイント

個人的に構築する上でポイントに感じた点は、以下の通りです。

  • API Gatewayの統合リクエストのパスオーバーライドによって、メッセージを送信したいSQSのリソース名を指定すること
  • API GatewayがSQSにメッセージを送信できるよう、API Gateway用のIAMロールが必要になること

  • API Gatewayの統合リクエストのリクエストヘッダーに「Content-Type: application/x-www-form-urlencoded」を設定すること
  • API Gatewayの統合リクエストのマッピングテンプレートに「Action=SendMessage&MessageBody=$input.body」を設定すること
  • もし、リクエストに特殊文字を含めた場合は、「Action=SendMessage&MessageBody=$util.urlEncode($input.body)」を設定すること

手順

1. リポジトリをクローン

git clone git@github.com:kazuya9831/blog-sample.git

2. リソースのデプロイ

ディレクトリを移動します。

cd blog-sample/api-gateway-sqs-lambda

リソースをデプロイします。

sam deploy

3. 動作確認①(SQSのメッセージをLambdaが処理することを確認)

3.1 API GatewayのURLを取得

CloudFormationのOutputsを基にAPI GatewayのURLを取得します。

api_gateway_url=$(aws cloudformation describe-stacks \
    --stack-name "sample-api-sqs-lambda" \
    --query "Stacks[].Outputs[?OutputKey=='ApiGatewayUrl'].[OutputValue]" \
    --output text
)

3.2 API GatewayにPOSTリクエストを送信

API GatewayのURLに対し、POSTリクエストを送信します。
リクエストのボディには、{"message": "sample-api-gatewa-sqs-lambda-test"}を含めます。
※リクエストボディがメッセージとして、API GatewayからSQSに送信されます。

curl -i -X POST \
-H "Content-Type: application/json" \
-d '{"message": "sample-api-gatewa-sqs-lambda-test"}' \
${api_gateway_url}

3.3 Lambdaのログを確認

SQSでメッセージを受信後、Lambdaが自動でSQSのメッセージを取得し処理を行うため、 LambdaのCloudWatch Logsのログを確認します。

まずは、最新のログストリーム名を取得します。

log_stream_name=$(aws logs describe-log-streams \
    --log-group-name "/aws/lambda/sample-dev-lambda" \
    --query "logStreams | sort_by(@, &lastEventTimestamp) | [-1].logStreamName" \
    --output text
)

その後、最新のログストリームの詳細を確認します。

aws logs get-log-events \
--log-group-name "/aws/lambda/sample-dev-lambda" \
--log-stream-name ${log_stream_name} \
--query "events[].[timestamp,message]" --output text

実行結果例は以下の通りで8行目のようにAPI Gatewayへのリクエストボディに含めた値が入っていることを確認できると思います。

$ aws logs get-log-events \
--log-group-name "/aws/lambda/sample-dev-lambda" \
--log-stream-name ${log_stream_name} \
--query "events[].[timestamp,message]" --output text

1719895143138   INIT_START Runtime Version: python:3.12.v28     Runtime Version ARN: arn:aws:lambda:ap-northeast-1::runtime:7776dee4c90fff4d4bf833b78d94e5b4148d14d044b867a14756f301ecfe1e28

1719895143223   START RequestId: 2f3dbb4e-ef51-5748-b48b-e5b369f402e7 Version: $LATEST

1719895143224   LambdaがSQSから読み取ったデータ: {"Records": [{"messageId": "92333e9c-4642-4143-9768-2a25514a50c9", "receiptHandle": "AQEBTV+SXcgYRBRG67Kp2hJ/QWw3IlDFTq3QOd1DImZIRGmjlfPXcmgihnyLUBAKbvC17HNe++R0SMlDNCPT4EN2guAyYIm8KIx80SXkl59xdbrdpfWykdWcCB/x1rJb3K3nOe+SFUg3ck02MvPBoRU2I7ytRiccR5+Ys4NsbGn1XnjiyU3ljc82IIXg5qRBSEdOACAMosTSMikkaP9DYO66zJJalY+yMEjhMu0SlhhqATGYOvUMF+Cc6iduSqYqqII601Q4PmKazZa2UHd8q5k3r6D/DP1LVY73fi+tzSOUp/VapaYHU0eNC+2pPrSvKymB5/0B7L97VWdOKwmXc3FAqqv3GKDRaWSUgmFUMXKXiXDAmX9Iyq0NQWR1+K0pf9LfJ3godS+hzBxpjz+CjgVXSw==", "body": "{\"message\": \"sample-api-gatewa-sqs-lambda-test\"}", "attributes": {"ApproximateReceiveCount": "1", "AWSTraceHeader": "Root=1-66838466-2c8145fe6dba4bcf5eb3e47e", "SentTimestamp": "1719895143003", "SenderId": "AROAT6MJ7L4P6TUZ7ULKA:BackplaneAssumeRoleSession", "ApproximateFirstReceiveTimestamp": "1719895143009"}, "messageAttributes": {}, "md5OfBody": "a250fa8274b41aa62134752eaa5af660", "eventSource": "aws:sqs", "eventSourceARN": "arn:aws:sqs:ap-northeast-1:123456789012:sample-dev-sqs", "awsRegion": "ap-northeast-1"}]}1719895143224   SQSに送信されたメッセージ: {"message": "sample-api-gatewa-sqs-lambda-test"}

1719895143258   END RequestId: 2f3dbb4e-ef51-5748-b48b-e5b369f402e7

1719895143258   REPORT RequestId: 2f3dbb4e-ef51-5748-b48b-e5b369f402e7  Duration: 34.62 ms      Billed Duration: 35 ms  Memory Size: 128 MB     Max Memory Used: 35 MB  Init Duration: 83.90 ms 

マネジメントコンソールからは以下のように確認することができます。

  • 補足

今回、Lambdaのコードは以下の通りでSQSから読み取ったデータを出力するだけの処理が行われます。

import json


def lambda_handler(event, context):

    print(f'LambdaがSQSから読み取ったデータ: {json.dumps(event)}')

    for i in event["Records"]:
        print(f'SQSに送信されたメッセージ: {i["body"]}')

    return {"statusCode": 200, "body": json.dumps("Hello from Lambda!")}

4. 動作確認②(SQSでメッセージを受信できているのかを確認)

API Gatewayにリクエストを送るとSQSにメッセージが送られますが、 Lambdaの処理が早く、SQSでメッセージを受信している状態を確認できないため、 LambdaとSQSの連携を一旦、無効化した上でSQSでメッセージを受信できているか確認してみます。

4.1 Lambdaのイベントソースマッピングの設定無効化

LambdaとSQSのイベントソースマッピングのUUIDを確認します。

uuid=$(aws lambda list-event-source-mappings \
--function-name sample-dev-lambda \
--query "EventSourceMappings[].UUID" \
--output text
)

LambdaとSQSのイベントソースマッピング設定を無効化します。

aws lambda update-event-source-mapping \
--uuid "${uuid}" \
--no-enabled

ステータスがDisabledになっていることを確認します。

aws lambda get-event-source-mapping \
--uuid "${uuid}" \
--query State \
--output text

4.2 API GatewayにPOSTリクエストを送信

API GatewayのURLに対し、POSTリクエストを送信します。
リクエストのボディには、{"message": "sample-api-gatewa-sqs-lambda-test"}を含めます。
※リクエストボディがメッセージとして、API GatewayからSQSに送信されます。

curl -i -X POST \
-H "Content-Type: application/json" \
-d '{"message": "sample-api-gatewa-sqs-lambda-test"}' \
${api_gateway_url}

4.3 SQSでメッセージを受信しているか確認

SQSに保存されているメッセージを1件取得する。
※少しラグがあり、Lambdaがメッセージを処理をする場合があるため、メッセージを取得できない場合は、少し時間置いてから再度API Gatewayにリクエストを送信します。

aws sqs receive-message \
--queue-url $(aws sqs get-queue-url --queue-name sample-dev-sqs --query QueueUrl --output text) \
--max-number-of-messages 1

以下の実行結果例の通り、Bodyに送信したメッセージが含まれていることを確認できるかと思います。

$ aws sqs receive-message --queue-url $(aws sqs get-queue-url --queue-name sample-dev-sqs --query QueueUrl --output text) --max-number-of-messages 1
{
    "Messages": [
        {
            "MessageId": "cf597070-a7fa-493f-9f49-e593f58199e6",
            "ReceiptHandle": "AQEBss6yoZzeh/Psuqss7hOneUHLQKT7FdbX6qJ1HfqCrC/6nnloiSnqy4GI6kKz+Xj1ad+yFzMe3leigXv4xorFAdivAJNYAixSl7/kumk/zEKoUnwcbzPPYz66zBIl4FgqllhKZ+++G/3cHGzkGm46smkEz1QzvwcvdpTQpPeLQHGsuLQEJ5BjUOjsfF2UJRp+RLccOP8NBdDCG0grfADQcvcDrJOj+eEWMZztyhQ6rn5XpZ7alRwvJ15PYNMCXODdFNpccLOb2l5ugR1y+31BDaQJWmsiljrm4GWuwzTV+nNEICfIBAGZHcoXenInEPhV/aiabTBthDRomK6ct7GAiMReOJOgIdHuQJcqG7LSF9OHc0C0JuBfIbxXFY9PiUdaQrpxEaz7WWk4l7C1RE6pWQ==",
            "MD5OfBody": "330ddb359d72714775069d3e05f4714e",
★            "Body": "{\"message\": \"sample-api-gatewa-sqs-lambda-test\"}"
        }
    ]
}

マネジメントコンソールからは以下のように確認することができます。

詳細は、「sample-dev-sqs」 >「メッセージを送受信」> 「メッセージをポーリング」の順に押下することで確認できます。

IDを押下することで詳細を確認できます。

4.4 Lambdaのイベントソースマッピングを有効化

LambdaとSQSのイベントソースマッピング設定を有効化します。

aws lambda update-event-source-mapping \
--uuid "${uuid}" \
--enabled

ステータスがEnabledになったら、動作確認①と同様にSQSのメッセージをLambdaが自動で取得し処理を行ってくれます。

5. 動作確認③(デッドレターキュー用のSQSにメッセージが配信されるのか)

今回、Lambdaの処理が3回失敗した場合、デッドレターキュー用のSQSにメッセージが配信されるように設定しているため、 想定通りに動作するか確認します。

5.1 Lambdaの予約済み同時実行数を0に変更

Lambdaを動かせないよう、予約済み同時実行数を0に変更します。

aws lambda put-function-concurrency \
    --function-name sample-dev-lambda \
    --reserved-concurrent-executions 0 

5.2 API GatewayにPOSTリクエストを送信

API GatewayのURLに対し、POSTリクエストを送信します。
リクエストのボディには、{"message": "sample-api-gatewa-sqs-lambda-test"}を含めます。
※リクエストボディがメッセージとして、API GatewayからSQSに送信されます。

curl -i -X POST \
-H "Content-Type: application/json" \
-d '{"message": "sample-api-gatewa-sqs-lambda-test"}' \
${api_gateway_url}

5.3 デッドレターキュー用のSQSにメッセージが配信されているか確認

少し時間をおいて、デッドレターキュー用のSQSのメッセージを確認します。

aws sqs receive-message \
--queue-url $(aws sqs get-queue-url --queue-name sample-dev-dead-letter-sqs --query QueueUrl --output text) \
--max-number-of-messages 1

以下のようにメッセージを取得できれば、API Gatewayからメッセージを受信する用のSQSからデッドレターキュー用のSQSにメッセージが配信されたことが分かります。

$ aws sqs receive-message \
--queue-url $(aws sqs get-queue-url --queue-name sample-dev-dead-letter-sqs --query QueueUrl --output text) \
--max-number-of-messages 1
{
    "Messages": [
        {
            "MessageId": "91e538cb-9d92-464f-b948-16cf9ac820ec",
            "ReceiptHandle": "AQEBaewZ4lmLF1RY77zmp252zZQBYXCJDixXUTl1PGmFdTLYKhrRFfdcjXKUvskiAmLEA19e2PVkME0Ty3XBZxuBY9VZNmU8Ssxy8UQxwVH6Q3jD5vLyeWJqBkpu4S0q+3uqPub5m6BEiTz8SV58lfYHoD6104XncM/wLGIDwe6jOnbiM/U2TknDCafPfQb+vjLI1EVvhMoowBBxB4XyHojs57h8vM3KGpBT/3RPigjy56ZQ2cfeF3U03e8HG0Qln9dYpLofpuhRplKQ7RZY85HqvfMJ5unHBHgYTw4g1dKGsISl+q+0ymx/niWL7LYcXI08v7dWxuHSd0JmddgIqqluvNwHk77o70MRoRxi07+BKmxHtOWTRafZNFBcMRvnw06hsVx0ijR2b2PQhyI74O3IEoOPPMffW49AfrEU9t4USkM=",
            "MD5OfBody": "330ddb359d72714775069d3e05f4714e",
            "Body": "{\"message\": \"sample-api-gatewa-sqs-lambda-test\"}"
        }
    ]
}
$ 

マネジメントコンソールから確認すると、「sample-dev-sqs」のメッセージが処理中になります。

しばらくすると、「sample-dev-dead-letter-sqs」にメッセージが移動されます。

5.4 Lambdaの予約済み同時実行数を元に戻す

Lambdaが動くように、予約済み同時実行数の設定を削除します。

aws lambda delete-function-concurrency \
--function-name sample-dev-lambda

5.5 デッドレター用のSQSからAPI Gatewayからメッセージを受信する用のSQSにメッセージを戻す

以下のコマンドを実行し、デッドレター用のSQSからAPI Gatewayからメッセージを受信する用のSQSにメッセージを戻します。

DLQ_QUE_NAME='sample-dev-dead-letter-sqs'
DLQ_QUE_URL=$(aws sqs get-queue-url --queue-name $DLQ_QUE_NAME --output text)
DLQ_QUE_ARN=$(aws sqs get-queue-attributes --queue-url $DLQ_QUE_URL --attribute-names QueueArn --query 'Attributes.QueueArn' --output text)
aws sqs start-message-move-task --source-arn $DLQ_QUE_ARN 

処理が正常に完了すれば、動作確認①と同様にSQSのメッセージをLambdaが自動で取得し処理を行ってくれます。

また、処理が一瞬で完了するため、sample-dev-sqsにメッセージが戻ったことを確認することはできませんが、 マネジメントコンソールから確認すると、sample-dev-sqs, sample-dev-dead-letter-sqsどちらにもメッセージが存在しないことが分かります。 (Lambdaによってメッセージが処理されていることが分かります。)

終わりに

今回は、SAMを利用し、API Gateway+SQS+Lambdaの構築後、動作確認をしてみました。 どなたかのお役に立てれば幸いです。

福島 和弥 (記事一覧)

2019/10 入社

AWS CLIが好きです。