【新機能】AWS LambdaがDead Letter Queueをサポートしました #reinvent

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、虎塚です。

2016年12月1日のAWSアップデートで、AWS LambdaがDead Letter Queue (DLQ)をサポートするようになりました。キューとして使えるのは、SQSキューまたはSNSトピックです。

AWS Lambdaで、S3、SNS、IoTなどのAWSサービスに非同期メッセージを送信する機能を実装している方には嬉しいアップデートかと思いますので、ご紹介します。

※2016年12月3日現在、本機能はOhioリージョンでサポートされています。他のリージョンにも今後展開されるとのことです。

前提: Lambda関数が失敗するとどうなるか

Lambda関数は、Lambda関数のコードにエラーがある場合や、サービスまたはリソースの上限を超えた場合、実行に失敗します。失敗時の挙動は、次のとおりです。

関数とイベントの種類 挙動
同期呼び出しのLambda関数 例外が返される
非同期呼び出しのLambda関数、S3バケット通知 最低2回のリトライ(全3回の試行)、それでも失敗する場合はイベントが棄却される
Kinesisストリーム、DynamoDBストリームからのイベント データの有効期限切れまで再試行(データは24時間保持)

Lambda関数実行の進行状況は、CloudWatchメトリックスで監視するのが一般的です。

今回のアップデートではLambdaにDead Letter Queue (DLQ)を設定できるようになりました。これまでは実行に3回失敗したイベントは捨てられていましたが、SQSキューまたはSNSトピックに送信できるようになりました。

LambdaにDead Letter Queueを設定する方法

イベントを送信したいSNSトピックまたはSQSキューのARNを、Lambda関数のDeadLetterConfigパラメータとして指定することで、DLQを設定できます。

DeadLetterConfigは、Lambda関数の作成時または更新時に指定できます。

動作確認

Dead Letter QueueとしてSNSトピックを設定し、Lambda関数の実行失敗時にメールが送信されるように設定します。

1. テスト用Lambda関数の作成

Lambda Dashboardのブループリントから、Pythonランタイムで動くhello-world-pythonのLambda関数 (dlq-test) を作成します。テスト実行して、成功することを確認しておきます。

Lambda関数を作成するこのタイミングでDead Letter Queueを指定することもできます。今回は、後から設定します。

Dead Letter Queueの設定

2. Dead Letter用SNSの作成

SNSのトピック (failed-lambda) を作成します。

aws sns create-topic --name failed-lambda
{
    "TopicArn": "arn:aws:sns:us-east-2:123456789012:failed-lambda"
}

作成したトピックをsubscribeします。

aws sns subscribe --topic-arn arn:aws:sns:us-east-2:123456789012:failed-lambda \
  --protocol email \
  --notification-endpoint test@example.com 
{
    "SubscriptionArn": "pending confirmation"
}

上で指定したメールアドレスに認証メールが届くので、メール本文のリンクをクリックしてsubscribeします。もしくは、メール本文中のトークン (リンクURLのパラメータ) を使って、次のコマンドを実行します。

aws sns confirm-subscription \
  --topic-arn arn:aws:sns:us-east-2:123456789012:failed-lambda \
  --token 1234...abcd
{
    "SubscriptionArn": "arn:aws:sns:us-east-2:123456789012:failed-lambda:33333333-2222-1111-0000-444444444444"
}

3. Lambda実行ロールに権限追加

今回はDead Letter QueueとしてSNSを使うため、ステップ2で作成したトピックにAWS Lambdaがアクセスできるようにsns:Publishを許可します。なお、Dead Letter QueueとしてSQSを使う場合は、対象のキューに対するsqs:SendMeesageを許可します。

IAMポリシーを作成します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "sns:Publish"
      ],
      "Resource": "arn:aws:sns:us-east-2:123456789012:failed-lambda"
    }
  ]
}
aws iam create-policy --policy-name AllowDeadLetterQueueForLambdaPolicy \
  --policy-document file://AllowDeadLetterQueueForLambdaPolicy.json
{
    "Policy": {
        "PolicyName": "AllowDeadLetterQueueForLambdaPolicy",
        "CreateDate": "2016-12-02T23:57:26.582Z",
        "AttachmentCount": 0,
        "IsAttachable": true,
        "PolicyId": "AAAAAAAAAAAAAAAAAAAAA",
        "DefaultVersionId": "v1",
        "Path": "/",
        "Arn": "arn:aws:iam::123456789012:policy/AllowDeadLetterQueueForLambdaPolicy",
        "UpdateDate": "2016-12-02T23:57:26.582Z"
    }
}

作成したポリシーを、Lambda関数 (dlq-test) の実行ロールに追加で適用します。

aws iam attach-role-policy \
  --role-name lambda_basic_execution \
  --policy-arn arn:aws:iam::123456789012:policy/AllowDeadLetterQueueForLambdaPolicy

4. DeadLetterConfigの設定

Lambda関数 (dlq-test) の設定を更新して、ステップ2で作成したSNSトピック (failed-lambda) をDead Letter Queueとして指定します。

aws lambda update-function-configuration \
  --function-name dlq-test \
  --dead-letter-config TargetArn=arn:aws:sns:us-east-2:123456789012:failed-lambda
{
[...]
    "FunctionName": "dlq-test",
    "FunctionArn": "arn:aws:lambda:us-east-2:123456789012:function:dlq-test",
    "Version": "$LATEST",
    "Role": "arn:aws:iam::123456789012:role/lambda_basic_execution",
    "Handler": "lambda_function.lambda_handler",
    "DeadLetterConfig": {
        "TargetArn": "arn:aws:sns:us-east-2:123456789012:failed-lambda"
    },
    "Runtime": "python2.7",
    "Description": "A starter AWS Lambda function."
[...]
}

5. Lambda関数の実行を失敗させる

Lambda関数のコードを、エラーが出るように書き換えます。

from __future__ import print_function

import json

print('Loading function')

def lambda_handler(event, context):
    #print("Received event: " + json.dumps(event, indent=2))
    print("value1 = " + event['key1'])
    print("value2 = " + event['key2'])
    print("value3 = " + event['key3'])
    return event['key1']  # Echo back the first key value
    #raise Exception('Something went wrong')

上のコードの関数名 (lambda_handler) を、任意の名前 (foobar) に変更して保存します。あらかじめLambda関数に設定したhandler名と異なる名前にしたため、AWS LambdaがLambda関数を呼び出すことができず、実行時に失敗するようになります。

AWS Lambdaは、関数の実行に失敗して2回リトライした後でDead Letter Queueにイベントを送信するので、リトライが起きるようにトリガーを設定します。

その上で、Lambda関数を1分に1回実行するように設定します。

Lambdaを1分に1回実行するように設定

少し待つと、ステップ2でトピックをsubscribeしたメールアドレスに、「AWS Notification Message」というSubjectでメールが届きます。メールの本文は次のような内容です。

{"version":"0","id":"12345678-1234-1234-1234-210987654321","detail-type":"Scheduled Event","source":"aws.events","account":"1223456789012","time":"2016-12-03T00:11:45Z","region":"us-east-2","resources":["arn:aws:events:us-east-2:123456789012:rule/every1minute"],"detail":{}}

-- If you wish to stop receiving notifications from this topic, please click or visit the link below to unsubscribe: [...]

Lambda関数が失敗してリトライがおこなわれた後に、Dead Letter Queueとして設定したSNSにイベントが送られることを確認できました。

(放っておくと1分に1回メールが送信され続けるので、確認が完了したら、本ステップで作成したスケジュール設定は削除しておきましょう)

トラブルシュート

Dead Letter Queueを設定したLambdaを作成または更新するときに、次のようなエラーが出ることがあります。

An error occurred (AccessDeniedException) when calling the UpdateFunctionConfiguration operation: Your access has been denied by SNS, please make sure your function execution role have permission to Publish for arn:aws:sns:us-east-2:123456789012:lambda-function-name. SNS Error Code: AuthorizationError. SNS Error Message: User: arn:aws:sts::123456789012:assumed-role/lambda_basic_execution/awslambda_xxx_20161202xxxxxxxxx is not authorized to perform: SNS:Publish on resource: arn:aws:sns:us-east-2:123456789012:lambda-function-name

このエラーは、Dead Letter Queueに指定したAWSリソースへのアクセス権限がLambda関数に足りないときに発生します。ステップ3を参照して、Lambda関数の実行ロールに適切な権限を与えてください。

おわりに

失敗したLambdaのイベントがSQSやSNSに直接送信できるようになったことで、後続の例外処理を確実に実施しやすくなりました。たとえば、SQSキューに送信された未処理イベントをアプリケーション側で拾ったり、SNS経由で別のLambdaを実行したりといったことも可能なので、活用しましょう。

それでは、また。

参考