AWS LambdaのデッドレターキューとSNSでリトライ構成を構築する(実践編)

前回の記事で準備したServerless Frameworkを使って、デッドレターキューを利用したAWS Lambda関数のリトライ構成を構築してみます。

ulab.hatenablog.com

なお、本記事に記載している関数のコードは下記リポジトリです。Serverless Frameworkでデプロイも可能です。

github.com



もくじ



そもそもなぜデッドレターキューか

デッドレターキューを利用したリトライ構成のメリットは以下だと思います。

  • デッドレターキューにより非同期イベントがトリガーでもエラーハンドリングが用意になる
  • エラー時のイベント情報を引き継いで次の関数に処理させることができる

AWS Lambda関数のデッドレターキューについてはAWS公式のドキュメントも参考になります。

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/invocation-async.html



2つの関数を作成

失敗する関数

以下のような、必ずエラーになるhandler.jsを作成します。

'use strict';

module.exports.failFunc = async event => {
  console.log(event);
  throw new Error('Erorr occured!!!!!!!!!');
};


serverless.ymlに諸々の設定を記述します。

  • S3のPUTイベントをトリガーにして関数を実行する想定です。すでに存在するバケットを指定するためexisting: trueとしています。
  • S3のバケット名やSNSのARNは${file(./config.json):KEY}という構文で外部ファイルから読み込んでいます。ARNを直書きしたくなかったためです。
service: failfunc

provider:
  name: aws
  runtime: nodejs12.x
  region: ap-northeast-1
  memorySize: 512
  stage: dev
  timeout: 10

# 外部ファイルは関数に含めない
package:
  exclude:
    - config.json

functions:
  failFunc:
    handler: handler.failFunc
    name: ${self:service}
    onError: ${file(./config.json):SNS_ARN}
    events:
      - s3:
          bucket: ${file(./config.json):S3_BUCKET_NAME}
          event: s3:ObjectCreated:*
          existing: true

外部ファイルのconfig.jsonは以下の様な感じです。

{
  "SNS_ARN": "ARNを記載",
  "S3_BUCKET_NAME": "バケット名を記載"
}

serverless.ymlonErrorの部分がデッドレターキューの設定です。関数がエラーで失敗したらここに記載したSNSにpublishします。 2020年4月時点ではonErrorの設定はSQSは未対応とのことです。

本記事ではこれが理由でSNSでデッドレターキューを試しています。

sls deployでデプロイすると、下図のようにトリガーにS3、デッドレターキューにSNSトピックが設定された関数が作成されます。

f:id:urawa72h:20200410225222p:plain

f:id:urawa72h:20200410225243p:plain


成功する関数

デッドレターキューを利用して呼ばれる関数です。引数のeventを出力しています。

event.Record[0].SnsSNSを介して渡されるfailFunceventの情報(S3のバケット名やファイル名)を参照できます。

'use strict';

module.exports.dlqFunc = async event => {
  console.log(event.Records[0].Sns);
  return {
    statusCode: 200,
    body: JSON.stringify(
      {
        message: 'This is dlq function!',
        input: event,
      },
      null,
      2
    ),
  };
};


serverless.ymlは以下の通りです。

service: dlqfunc

provider:
  name: aws
  runtime: nodejs12.x
  region: ap-northeast-1
  memorySize: 512
  stage: dev
  timeout: 10

# 外部ファイルは関数に含めない
package:
  exclude:
    - config.json

functions:
  dlqFunc:
    handler: handler.dlqFunc
    events:
      - sns: ${file(./config.json):SNS_TOPIC_ARN}

eventで外部ファイルを通してsns: SNSトピックのARNを指定します。ARNではなくSNSトピック名を指定した場合はsls deploySNSトピックが新規作成されます。



関数を実行するIAMロール

上記の2つの関数のsls deployが成功すると、必要なインラインポリシーがアタッチされたIAMロールが自動で作成されます。

このIAMロールはAWS Lambda関数を実行するためのロールで、デプロイのためのIAMロールとは異なります。

今回のようなお試しでは自動で作成されたロールでも構いませんが、実業務では予め専用のロールを作ってserverless.ymlに設定したり、serverless.ymlにIAMロールの詳細を記述したりします。

これら設定の詳細はServerless Frameworkの公式ドキュメントが参考になります。

例えば、予め作成したIAMロールを設定する場合は、serverless.ymlproviderroleでIAMロール名を設定します。

provider:
  role: myIAMRole

インフラ構築の自動化の観点からするとIAMロールの設定も全てserverless.ymlに記載するのが望ましいのかもしれません。



関数をテスト実行する

2つの関数の準備ができたら、一通りテストしてみます。

今回はS3のPUTイベントをトリガーにしてfailFuncが実行されるので、試しにS3の対象バケットに適当なファイルをアップロードしてみます。

2つの関数のモニタリングタブからCloudWatch Logsを参照すると、failFuncでは2回リトライ後に処理が終了します。一方、dlqFuncfailFuncの失敗によりSNSを介して1回実行されます。

dlwFuncのログには、下記のようにfailFuncのS3トリガーイベントに関係するバケット名やファイル名が出力されています("object":{"key":"AWS-Marketplace_dark-bg%404x.png","size":6024,"eTag":"dummy","sequencer":"dummy"}部分にS3バケットに保存したファイル名が記載されている)。

ちなみに、MessageAttribuesにエラーメッセージも設定されています。

{
Type: 'Notification',
  MessageId: 'dummy',
  TopicArn: 'adummy',
  Subject: null,
  Message: '{"Records":[{"eventVersion":"2.1","eventSource":"aws:s3","awsRegion":"ap-northeast-1","eventTime":"2020-04-10T13:47:52.958Z","eventName":"ObjectCreated:Put","userIdentity":{"principalId":"dummy"},"requestParameters":{"sourceIPAddress":"dummy"},"responseElements":{"x-amz-request-id":"dummy","x-amz-id-2":"dummy"},"s3":{"s3SchemaVersion":"1.0","configurationId":"failfunc-68a737133cefb25bff959852b8f04754","bucket":{"name":"lambda-test-dummy-name","ownerIdentity":{"principalId":"dummy"},"arn":"dummy"},"object":{"key":"AWS-Marketplace_dark-bg%404x.png","size":6024,"eTag":"dummy","sequencer":"dummy"}}}]}',
  Timestamp: '2020-04-10T13:51:00.559Z',
  SignatureVersion: '1',
  Signature: 'dummy',
  SigningCertUrl: 'dummy',
  UnsubscribeUrl: 'dummy',
  MessageAttributes: {
    RequestID: { Type: 'String', Value: '218a0795-840c-4be9-a441-396e32db5b3b' },
    ErrorCode: { Type: 'String', Value: '200' },
    ErrorMessage: { Type: 'String', Value: 'Erorr occured!!!!!!!!!' }
  }
}

上記のように、failFunc関数がエラーで失敗した時のeventの情報が、デッドレターキューを利用することでdlqFunc関数で参照することができます。

これにより、エラーで失敗した時の情報を引き継いで何らかのリトライ処理(別にAWS Lambdak関数や、その他のAWSの各種サービスを利用した処理)に振り分けることができます。



Serverless Framework便利すぎる件

以上、AWS LambdaのデッドレターキューとSNSでリトライ構成を構築してみました。

まあ、厳密にはリトライ構成ではなく非同期イベントをトリガーにした場合のAWS Lambda関数のエラー時にどう対応するか、くらいしか解説していませんが・・・。

Serverless Frameworkはyamlを書くだけであとはよろしく構成管理してくれるので、非常に助かります。もっと色々できると思うので、また知識が増えたらまとめたいと思います。