Serverless Frameworkのプラグインを利用したDead Letter Queueの設定

2017.02.01

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

はじめに

こんにちは、中山です。

今回はServerless Frameworkのプラグインを利用して昨年末に導入されたLambda関数のDead Letter Queueの設定方法をご紹介したいと思います。Dead Letter Queueそのものについては以下のエントリを参照してください。

利用するプラグインはgmetzker/serverless-plugin-lambda-dead-letterです。執筆時点(2017/01/31)ではまだServerless Frameworkのコア機能としてDead Letter Queueがサポートされていません。ちょうどプラグインの作者の方がIssueを上げていますね。恐らく今後コア機能として導入されていくと思いますが、現状Serverless Frameworkを利用しつつDead Letter Queueも使いたい場合このプラグインを利用することになると思います。

なお、本エントリを執筆する上で検証に利用した主要な各種ツールのバージョンは以下の通りです。バージョンによって結果が変更される可能性があるので、その点ご了承ください。

  • Serverless Framework: 1.6.0
  • serverless-plugin-lambda-dead-letter: 1.2.0

使ってみる

早速使ってみましょう。

インストール

プラグインのインストールは簡単です。Serverless Frameworkで管理しているディレクトリに移動後以下のコマンドを実行するだけです。

$ npm install --save serverless-plugin-lambda-dead-letter

コマンドを実行すると node_modules というディレクトリにプラグインや依存するモジュールがインストールされます。

SQS

まずはじめにDead Letter Queueの送り先としてSQSを指定してみます。以下のファイルを用意してください。

  • serverless.yml
frameworkVersion: ">=1.6.0"

service: lambda-dlq-sqs-test

custom:
  sqs: LambdaDLQSQSTest

provider:
  name: aws
  runtime: python2.7
  stage: dev
  region: ap-northeast-1
  iamRoleStatements:
    - Effect: Allow
      Action:
        - sqs:SendMessage
      Resource:
        - Fn::Join: [ ":", [ "arn:aws:sqs", Ref: "AWS::Region", Ref: "AWS::AccountId", "${self:custom.sqs}" ] ]

plugins:
  - serverless-plugin-lambda-dead-letter

functions:
  fail:
    handler: handler.fail
    deadLetter:
      sqs: ${self:custom.sqs}
    events:
      - schedule: rate(1 minute)

ハイライトした点に注目してください。まず iamRoleStatements プロパティでSQSにメッセージを送信する権限( sqs:SendMessage )をLambda関数に付与しています。これを指定しないと以下のようなエラーが出力されてしまいます。

  Serverless Error ---------------------------------------

     The provided execution role does not have permissions
     to call SendMessage on SQS

続いて plugins プロパティで今回のプラグインを利用することを指定しています。 deadLetter プロパティでDead Letter Queueの宛先にSQSを指定しています。このプラグインではDead Letter Queueの設定方法を複数用意しているのですが(詳細はREADMEを参照)、今回は宛先の指定と同時にSQSの作成及びパーミッションの設定も行っています。最後に、Lambda関数を1分毎に実行させてキューにメッセージが蓄積されることを確認しやすくしています。

  • handler.py
def fail(event, context):
    raise Exception('Exception occured')

Lambda関数は実行を失敗させたいだけなので raise ですぐに例外を出力させています。

デプロイはいつものように実行するだけです。

$ sls deploy -v
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
<snip>

デプロイ後、暫く待つと以下のようにキューへメッセージが溜まっていくことが確認できます。

$ aws sqs get-queue-attributes \
  --queue-url "$(aws sqs get-queue-url \
    --queue-name LambdaDLQSQSTest \
    --output text)" \
  --attribute-names ApproximateNumberOfMessages
{
    "Attributes": {
        "ApproximateNumberOfMessages": "5"
    }
}

メッセージの中身を見ると以下のようになっています。

$ aws sqs receive-message \
  --queue-url "$(aws sqs get-queue-url \
    --queue-name LambdaDLQSQSTest \
    --output text)"
{
    "Messages": [
        {
            "Body": "{\"version\":\"0\",\"id\":\"4d652e87-c7d6-40bb-b49b-9481e4447ff7\",\"detail-type\":\"Scheduled Event\",\"source\":\"aws.events\",\"account\":\"************\",\"time\":\"2017-01-31T12:57:36Z\",\"region\":\"ap-northeast-1\",\"resources\":[\"arn:aws:events:ap-northeast-1:************:rule/lambda-dlq-sqs-test-dev-FailEventsRuleSchedule1-XO2V37TT4JZY\"],\"detail\":{}}",
            "ReceiptHandle": "AQEBWGjW4nofI0sR46Pt5L3xiAwEokcEHDOvT4JvYnwIwfUOApd9IDepygarwIwHM+jnXLs1e/FKHQOW/PCTtIwfpIBZgT2gknruuca55QdDsHzu+G8lA9KaR+g0Q8otolTIVWdvi6bcW5fD0fCU6uyPVZFNjBfo8DohhwRVwfonhKxoKBsVbsw+Fpqxh/cyNmB1qZ8r7HXbGv6p/nNAAofCBJwoLITveVvaGCStMTOB1/v7/j5lzF80rFdAb5AhL19xkao55KGL+DGMuGTPlcTF/tkYhUFl39gnk5xaybs+/TTtzAtsBFOc18OaAHQmq6VQWVNyDkGuHBLQfGr+fTwbU10ZJ0Lxl4598aiesb84HzRH7zqIPYpz6qUIKbRkOp2zXV5M1L7suln1aRcZnvhjwg==",
            "MD5OfBody": "cb56242c6a9c19ae6a0472397b2986c9",
            "MessageId": "66b735bb-df8e-4cef-ab17-b4c847d1b57a"
        }
    ]
}

SNS

続いてDead Letter Queueの宛先としてSNSを指定します。以下のファイルを用意してください。

  • serverless.yml
frameworkVersion: ">=1.6.0"

service: lambda-dlq-sns-test

custom:
  sns: LambdaDLQSNSTest

provider:
  name: aws
  runtime: python2.7
  stage: dev
  region: ap-northeast-1
  iamRoleStatements:
    - Effect: Allow
      Action:
        - sns:Publish
      Resource:
        - Ref: ${self:custom.sns}

plugins:
  - serverless-plugin-lambda-dead-letter

functions:
  fail:
    handler: handler.fail
    deadLetter:
      targetArn:
        GetResourceArn: ${self:custom.sns}
    events:
      - schedule: rate(1 minute)
  invoked:
    handler: handler.invoked
    events:
      - sns: arn:aws:sns:${self:provider.region}:${opt:account}:${self:custom.sns}

resources:
  Resources:
    LambdaDLQSNSTest:
      Type: AWS::SNS::Topic
      Properties:
        TopicName: ${self:custom.sns}

今回はLambda関数の実行に失敗した場合SNSのトピックに通知するので sns:Publish 権限を付与しています。この設定をしないと以下のようにエラーが出力されます。

  Serverless Error ---------------------------------------

     The provided execution role does not have permissions
     to call Publish on SNS

24から34行目でLambda関数を定義しています。今回はDead Letter Queueに指定したSNSから別のLambda関数を実行させているので計2つのLambda関数( fail 及び invoked )を指定して、 fail にDead Letter Queueの設定を、 invoked にSNSのトピックをトリガーに指定しています。今回、Dead Letter Queueuの設定は resources プロパティで作成したSNSトピックを GetResourceArn でARNを取得させています。本来であれば Ref 関数をこのコンテキストで使えばよいかと思いますが、恐らく現状使えないようなのでこういった少々回りくどい設定(独自プロパティの定義)になっているのかと推測します。

  • handler.py
from __future__ import print_function


def fail(event, context):
    raise Exception('Exception occured')


def invoked(event, context):
    print('Invoked with the event: {}'.format(event))

Lambda関数では fail 及び invoked を定義しています。

${opt:account} でコマンドライン引数からAWSアカウントIDを取得する処理にしているので、デプロイする際には以下のようにして引数を渡してください。

$ sls deploy -v \
  --account "$(aws sts get-caller-identity \
    --query 'Account' \
    --output text)"
Serverless: Packaging service...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading service .zip file to S3 (190.79 KB)...
<snip>

動作確認はSNSトピックからInvokeされるLambda関数のCloudWatch Logsを眺めて実行されるかどうかで確認してみます。以下のコマンドをしばらく実行させておくとログが標準出力に表示されると思います。

$ sls logs -f invoked -t \
  --account "$(aws sts get-caller-identity \
    --query 'Account' \
    --output text)"
START RequestId: 258fb904-e7bd-11e6-8365-a7da3782ec42 Version: $LATEST
Invoked with the event: {u'Records': [{u'EventVersion': u'1.0', u'EventSubscriptionArn': u'arn:aws:sns:ap-northeast-1:************:LambdaDLQSNSTest:23992f8f-5d52-4822-8085-516d209126da', u'EventSource': u'aws:sns', u'Sns': {u'SignatureVersion': u'1', u'Timestamp': u'2017-01-31T13:57:04.578Z', u'Signature': u'ezCxtZGdK+NHtk4dWGsVyWNjas6URrhZAjd9YMKei3uZypQteH9g8tOQum0gFCotMB132xwXjoVh9Q44xq/VX5PMb+bj/3UCw/dBHxT0zJvphe1WzpUCUmXh0m/LbLg3G+wgYjppoBtwLzkf9bE+aJiWu/1UsBaUuVdmSOy1Jg6QjlFbr7JiTERKGj6AjFcIslwzrZPVkxIyK5mTCi5eQd3KzDRt07djXOc7BWMXyE9gkEG4HM7dNVzSSpTXWVpdC4KHixylzY6sVp97+xffFkEtSbMUZq4/DsThfPq9gnCZaEu3ZGsmzhg4a0sWfRvjBhOaacOvaM1DI9Wips8gkg==', u'SigningCertUrl': u'https://sns.ap-northeast-1.amazonaws.com/SimpleNotificationService-b95095beb82e8f6a046b3aafc7f4149a.pem', u'MessageId': u'92d35e75-9cac-5b1c-b569-0f7f055c5c18', u'Message': u'{"version":"0","id":"44284c7d-b6b2-46dd-baa6-d8901c25619a","detail-type":"Scheduled Event","source":"aws.events","account":"************","time":"2017-01-31T13:53:30Z","region":"ap-northeast-1","resources":["arn:aws:events:ap-northeast-1:************:rule/lambda-dlq-sns-test-dev-FailEventsRuleSchedule1-1PH4WQ9GK4EUS"],"detail":{}}', u'MessageAttributes': {u'ErrorCode': {u'Type': u'String', u'Value': u'200'}, u'ErrorMessage': {u'Type': u'String', u'Value': u'Exception occured'}, u'RequestID': {u'Type': u'String', u'Value': u'b928907b-e7bc-11e6-9b02-8324f167199c'}}, u'Type': u'Notification', u'UnsubscribeUrl': u'https://sns.ap-northeast-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:ap-northeast-1:************:LambdaDLQSNSTest:23992f8f-5d52-4822-8085-516d209126da', u'TopicArn': u'arn:aws:sns:ap-northeast-1:************:LambdaDLQSNSTest', u'Subject': None}}]}
END RequestId: 258fb904-e7bd-11e6-8365-a7da3782ec42
REPORT RequestId: 258fb904-e7bd-11e6-8365-a7da3782ec42  Duration: 0.39 ms       Billed Duration: 100 ms         Memory Size: 1024 MB    Max Memory Used: 16 MB
<snip>

まとめ

いかがだったでしょうか。

Serverless Frameworkのプラグインを利用したDead Letter Queueの設定方法をご紹介しました。今回ご紹介した以外にもさまざまなプラグインが有志の方によって公開されています。品質はものによってだいぶ変わってくるのですが、今回ご紹介したプラグインはかなり安定して動作するなという印象です。一度利用されてみることをオススメします。

本エントリがみなさんの参考になれば幸いに思います。