【無限ループ防止】AWS LambdaとAmazon S3間の再帰ループ検出を試してみた

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

こんにちは。ディベロップメントサービス3課の千本松です。

少し前に、AWS Lambda(以下、Lambda)の再帰ループ検出がAmazon S3 (以下、S3)に対応しました。

AWS初心者にも使いやすいLambdaですが、意図しない無限ループで冷や汗をかいた経験がある方も多いのではないでしょうか。

今回のアップデートにより、S3を含む無限ループを自動的に検出して停止できるようになったので、実際に無限ループを発生させて動作を確認していきます。

aws.amazon.com

Lambdaの再帰ループ検出とは

docs.aws.amazon.com

Lambdaでは、関数を実行するためのトリガーとなるリソースを設定します。

Lambda関数の処理内で、このトリガーとなるリソースにデータを出力すると、その出力が再度トリガーとして働いてしまうことがあります。

このような設定をしてしまうと、Lambda関数が再帰的に実行され続けます。

よくある例として、「S3からデータを読み込み→Lambdaで処理→同じS3に出力」という処理を行う場合に、トリガーを適切に設定しなければ無限ループに陥ってしまいます。

この無限ループを自動的に検知してLambdaを停止してくれるのが、再帰ループ検出機能です。

これまでは、Lambda、Amazon SQS、Amazon SNSのみに対応していましたが、今回のアップデートでAmazon S3が追加されました。

  • AWS Lambda
  • Amazon SQS
  • Amazon SNS
  • Amazon S3 ← NEW!

なお、この機能はデフォルトで有効になっているので、意図的にループを利用する場合は機能を無効化する必要があります。

検出されるループの例

図のように、対応しているリソースのみで構成された無限ループを検出・停止してくれます。

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/invocation-recursion.html#invocation-recursion-supported

Amazon DynamoDB など、他のリソースがループに含まれる場合は検出ができないので注意が必要です。

本機能に加えて、Amazon CloudWatch Alarmを設定してLambdaの実行数(Invocation)などに対してアラートを設定することで、より安全に利用できるようになります。

実際にやってみた①:S3のみでループを構成

まずは次のシンプルな構成を試してみます。

Lambdaの準備

テスト用のS3バケットにオブジェクトが作成された時に、検証用のLambdaが起動するようにトリガーを設定しました。

Lambda中身は次のPythonコードで、トリガーと同じS3バケットに新しいオブジェクトを作成します。

import boto3
from datetime import datetime

s3 = boto3.client("s3")

def handler(event, context):
    file_body = "s3_loop_test"
    bucket_name = "test-s3-loop-bucket"
    file_name = f"made_by_lambda_{datetime.now().strftime('%Y-%m-%d_%H:%M:%S_%f')}.json"
    response = s3.put_object(Body=file_body, Bucket=bucket_name, Key=file_name)
    return response

いざ実行

本当に止まってくれるのかドキドキしながら実行してみると、、

ドキュメント通り、Lambdaが16回起動してオブジェクトが16個作成された時点で自動的に停止しました!

(first_trigger_objectはLambdaを起動するために手動追加したものです)

実際にやってみた②:S3以外のリソースも含めてループを構成

ドキュメントの例では、対応しているリソースを複数組み合わせた場合にも自動検出できると記載がありました。

そこで、次の少し複雑な構成でも試してみました。

Lambdaの準備

S3をトリガーにしたLambdaでは、次のコードでSNSにメッセージを送信します。

import boto3
import os
from datetime import datetime
import pprint

TOPIC_ARN = os.environ.get("TESTTOPIC_TOPIC_ARN")
client = boto3.client("sns")


def handler(event, context):
    msg = f"made_by_lambda_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S-%f')}.json"
    subject = "ファイル作成リクエスト"
    response = client.publish(TopicArn=TOPIC_ARN, Message=msg, Subject=subject)
    pprint.pprint(response)

SQSをトリガーにしたLambdaでは、前回同様S3にオブジェクトを作成します。

(オブジェクト名をSQS経由で受け取っています)

import boto3
import pprint
import json

s3 = boto3.client("s3")


def handler(event, context):
    print(json.dumps(event))
    file_body = "s3_loop_test"
    bucket_name = "test-s3-loop-complex-bucket"
    file_name = json.loads(event["Records"][0]["body"])["Message"]
    response = s3.put_object(Body=file_body, Bucket=bucket_name, Key=file_name)
    pprint.pprint(response)

実行結果

今回もドキュメントの通り、Lambdaが16回実行した時点で停止しました!

各種通知を確認

ドキュメントには自動停止した際の通知について、次のように記載があります。

それぞれどのような通知が来るのか確認していきます。

Lambda が再帰ループを停止すると、AWS Health Dashboard やメールで通知が届きます。CloudWatch メトリクスを使用して、Lambda が停止した再帰呼び出しの数をモニタリングすることもできます。

AWS Health Dashboard

ドキュメントでは次のように記載がありました。

ただし、私の場合は[その他の通知]タブに通知がありました。

Lambda が再帰呼び出しを停止すると、AWS Health Dashboard は、[アカウントヘルス] ページの [未解決の問題と最近の問題] に通知を表示します。

メール

以下の件名でメールが届きました。

  • [Action Required] Recursive loop detected and auto-remediated in your account [Account ID: xxxxxxxxxxxx]

メール送信のタイミングや回数については次のような注意点が記載されています。

実際に今回の検証でも、ループが停止した2時間ほど後にメールを受信しました。

Lambda が関数の再帰呼び出しを初めて停止すると、E メールアラートが送信されます。Lambda は、AWS アカウント の各関数につき 24 時間ごとに最大 1 通のメールを送信します。Lambda から E メール通知が送信された後の 24 時間は、Lambda によって関数の再帰呼び出しが再度停止された場合でも、その関数に関するメールが届きません。Lambda が再帰呼び出しを停止してからこの E メールアラートを受信するまでに、最大 3 時間かかる場合があることに注意してください。

Amazon CloudWatch メトリクス

しっかりとメトリクスが出力されていました。

ドキュメントによれば、このメトリクスはループの停止後すぐに出力されるとのことなので、すぐに通知を受け取りたい方はこのメトリクスに対するアラームを設定すると良さそうです。

CloudWatch メトリクスの RecursiveInvocationsDropped には、1 回のリクエストチェーンで関数が約 16 回を超えて呼び出されたために Lambda が停止した関数の呼び出し回数が記録されます。Lambda は再帰呼び出しを停止するとすぐにこのメトリクスを出力します。

まとめ

新たにS3に対応したLambdaの再帰ループ検出について、実際に無限ループを発生させて、自動停止することを確認しました。

今回のアップデートにより、Lambdaをより安心して利用できるようになりましたが、まだ対応外のケースもあるので注意が必要です。

本機能に加えて、Amazon CloudWatch Alarmを設定してLambdaの実行数(Invocation)などに対してアラートを設定することで、より安全に利用できるようになります。

また、今回のリソースはAWS SAMとAWS Infrastructure Composer(最近AWS Application Composerから名前が変わりましたね!)を使って構築しました。

機会があればこの手順についても今後のブログでまとめたいと思います。

最後までお読みいただきありがとうございました!

本ブログが誰かの参考になれば幸いです。

千本松 輝(記事一覧)

アプリケーションサービス部ディベロップメントサービス3課

CloudFormation・CDKなどIaCが好きです。AWS SAP資格勉強中。好物は生ガキと焼酎。