Lambda-Backed Custom Resourceを利用してServerless Application Modelで定義したLambda関数にDead Letter Queueを設定する
2017年3月29日追記
米国時間3月28日のアップデートでAWS::Lambda::FunctionリソースがDeadLetterConfigプロパティをサポートしました。
はじめに
こんにちは、中山です。
引き続きServerless Application Model(以下AWS SAM)ネタです。今回はAWS SAMで定義したLambda関数にDead Letter Queueを設定する方法をご紹介します。Dead Letter Queueそのものについては以下のエントリを参照してください。
残念ながら執筆時点(2017/02/14)では、AWS SAMでLambda関数を定義するためのAWS::Serverless::Function及びAWS::Lambda::FunctionがDead Letter Queue用のプロパティをサポートしていません。その内サポートされると思いますが、すぐに使いたい場合は自分で作り込む必要があります。AWS SAMの対抗フレームワークであるServerless Frameworkでは、プラグインという形で設定可能です。以下のエントリに内容をまとめています。
では、AWS SAMの場合どうやるのでしょうか。やはりそこはみんな大好きLambda-Backed Custom Resourceですね。AWS SAMで定義したLambda関数を、Lambda-Backed Custom ResourceでアップデートしてDead Letter Queueを設定可能です。今回簡単なサンプルを交えて具体的にご紹介したいと思います。
なお、本エントリを執筆する上で検証に利用した主要な各種ツールのバージョンは以下の通りです。バージョンによって結果が変更される可能性があるので、その点ご了承ください。
- AWS SAM: 2016-10-31
- AWS CLI: aws-cli/1.11.47 Python/2.7.12 Darwin/16.4.0 botocore/1.5.10
やってみる
早速やってみましょう。
ディレクトリ構成
今回は以下のようなディレクトリ構成にします。
$ tree . . ├── sam.yml └── src ├── handlers │ ├── failure │ │ └── index.py │ └── invoked │ └── index.py └── templates ├── custom.yml ├── sns.yml └── sqs.yml 5 directories, 6 files
トップディレクトリにAWS SAM用テンプレート、 src/handlers
以下にLambda関数、 src/templates
以下にCloudFormationテンプレートを設置しています。このディレクトリ配置にした主な理由は以下のエントリにまとめているので、よろしければ参照してください。
トップディレクトリの sam.yml
はもちろん単なるCloudFormationテンプレートなので、 src/templates
以下のテンプレートと分けなくても問題ありません。しかし、CloudFormationのベストプラクティス的には各種コンポーネント毎にスタックを分けることが推奨されている点、また詳細は後述しますが aws cloudformation package
コマンドでスタックのネストがとても使いやすくなった、という点からこの構成にしています。
テンプレートの内容
sam.yml
--- AWSTemplateFormatVersion: 2010-09-09 Transform: AWS::Serverless-2016-10-31 Description: AWS SAM Resources: SQS: Type: AWS::CloudFormation::Stack Properties: TemplateURL: src/templates/sqs.yml SNS: Type: AWS::CloudFormation::Stack Properties: TemplateURL: src/templates/sns.yml Failure: Type: AWS::Serverless::Function Properties: CodeUri: src/handlers/failure Handler: index.handler Runtime: python2.7 Policies: - Version: 2012-10-17 Statement: - Sid: SQSSendMessagePolicy Effect: Allow Action: sqs:SendMessage Resource: !GetAtt SQS.Outputs.QueueArn - Sid: SNSPublishPolicy Effect: Allow Action: sns:Publish Resource: !GetAtt SNS.Outputs.TopicArn Events: Timer: Type: Schedule Properties: Schedule: rate(1 minute) DeadLetterQueue: Type: AWS::CloudFormation::Stack Properties: TemplateURL: src/templates/custom.yml Parameters: FunctionName: !Ref Failure QueueArn: !GetAtt SQS.Outputs.QueueArn TopicArn: !GetAtt SNS.Outputs.TopicArn Invoked: Type: AWS::Serverless::Function Properties: CodeUri: src/handlers/invoked Handler: index.handler Runtime: python2.7 Events: Invoker: Type: SNS Properties: Topic: !GetAtt SNS.Outputs.TopicArn
AWS::CloudFormation::Stackリソースでこのテンプレートから別のスタックを作成しています。 TemplateURL
プロパティに注目してください。このプロパティの値はS3に保存されたテンプレートへのパスを指定しなければならないのですが、ローカルファイルへのパスを設定しています。 aws cloudformation package
を実行すると、パスで指定されたテンプレートをS3にアップロードし、プロパティの値をS3へのパスへ自動で変換することが可能です。例えば以下のような形になります。
SNS: Properties: TemplateURL: https://s3.amazonaws.com/<_YOUR_S3_BUCKET_>/e9711c8c48b6ea7ddb2fffc39f9fe2ed.template Type: AWS::CloudFormation::Stack
このコマンドが登場するまではスタックのネストが非常に使いづらい状況でした。テンプレートの修正後S3にアップロードするといった作業を手動で繰り返さなければならないからです。ローカルファイルへのパスを指定できるようになったことでかなり使いやすくなったなと感じています。
Failure
という論理リソースIDでDead Letter Queueを設定するLambda関数を定義しています。SQSキューに対して sqs:SendMessage
を、SNSトピックに対して sns:Publish
権限を付与しています。また、動作確認をしやすくするために Events
プロパティでCloudWatch Eventを指定しました。
DeadLetterQueue
という論理リソースIDでLambda-Backed Custom Resourceを定義したテンプレートからスタックを作成しています。内容は後述しますが、Dead Letter Queueを設定するLambda関数名と、その宛先となるSQSキューARN/SNSトピックARNをパラメータで渡しています。
Invoked
という論理リソースIDでSNSトピックから呼び出されるLambda関数を定義しています。EventsプロパティでSNSタイプを設定しているため、Dead Letter Queueの宛先にSNSトピックを指定した場合、このLambda関数が起動されることになります。
src/templates/sns.yml
--- AWSTemplateFormatVersion: 2010-09-09 Description: SNS Resources: Topic: Type: AWS::SNS::Topic Outputs: TopicArn: Value: !Ref Topic
Dead Letter Queueの宛先となるSNSトピックを作成し、そのARNをアウトプットさせているだけです。
src/templates/sqs.yml
--- AWSTemplateFormatVersion: 2010-09-09 Description: SQS Resources: Queue: Type: AWS::SQS::Queue Outputs: QueueArn: Value: !GetAtt Queue.Arn
こちらも同様にキューの作成とARNのアウトプットをしています。
src/templates/custom.yml
--- AWSTemplateFormatVersion: 2010-09-09 Description: Dead Letter Queue Parameters: FunctionName: Type: String QueueArn: Type: String TopicArn: Type: String Resources: LambdaBasicExecRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Sid: LambdaAssumeRolePolicy Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole Path: / ManagedPolicyArns: - arn:aws:iam::aws:policy/AWSLambdaFullAccess LambdaDeadLetterQueue: Type: AWS::Lambda::Function Properties: Code: ZipFile: | import cfnresponse import boto3 import json def handler(event, context): if event['RequestType'] == 'Delete': cfnresponse.send(event, context, cfnresponse.SUCCESS, {}) function_name = event['ResourceProperties']['FunctionName'] target_arn = event['ResourceProperties']['TargetArn'] try: resp = boto3.client('lambda').update_function_configuration( FunctionName=function_name, DeadLetterConfig={'TargetArn': target_arn}) except: cfnresponse.send(event, context, cfnresponse.FAILED, {}) else: response_data = {'Response': json.dumps(resp)} cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data) Handler: index.handler Role: !GetAtt LambdaBasicExecRole.Arn Runtime: python2.7 CustomDeadLetterQueue: Type: Custom::DeadLetterQueue Version: 1.0 Properties: ServiceToken: !GetAtt LambdaDeadLetterQueue.Arn FunctionName: !Ref FunctionName TargetArn: !Ref QueueArn #TargetArn: !Ref TopicArn
このテンプレートが本エントリの本題です(ここまでの説明が長くなりましたが)。 LambdaDeadLetterQueue
でLambda関数を、 CustomDeadLetterQueue
でそれを呼び出すカスタムリソースを定義しています。インラインで記述したLambda関数の内容は単純です。Boto3のupdate_function_configurationメソッドを利用して関数の設定をアップデートさせているだけです。こうすることで AWS::Serverless::Function
で定義したLambda関数の設定をアップデートできるという訳です。
Lambda関数自体はシンプルに以下のようにしました。1つ目は常に関数の実行を失敗させています。2つ目は event
の内容を出力させているだけです。
src/handlers/failure/index.py
def handler(event, context): raise Exception('Exception occured')
src/handlers/invoked/index.py
from __future__ import print_function def handler(event, context): print('Invoked with this event: {}'.format(event))
動作確認
Dead Letter Queueは現状1つしか指定できないので、SQSから確認してみます。まずはテンプレートの変換とアーティファクトのアップロードを実施します。トップディレクトリ上で以下のコマンドを実行してください。変換されたテンプレートはデプロイ以外では必要ないので、 .sam
などのディレクトリに置いてVCSの管理外にしておくとよいと思います。
$ aws cloudformation package \ --template-file sam.yml \ --s3-bucket <_YOUR_S3_BUCKET_> \ --output-template-file .sam/packaged.yml
スタックを作成します。
$ aws cloudformation deploy \ --template-file .sam/packaged.yml \ --stack-name dead-letter-queue \ --capabilities CAPABILITY_IAM
計4つのスタックが作成されます。
$ aws cloudformation list-stacks \ --stack-status-filter CREATE_COMPLETE \ --query 'StackSummaries[?contains(StackName,`dead-letter-queue`)].StackName' [ "dead-letter-queue-DeadLetterQueue-1G2G5WY2PFN3E", "dead-letter-queue-SNS-1Q8QPI3W6NOZQ", "dead-letter-queue-SQS-MJSS8933C46S", "dead-letter-queue" ]
Lambda関数の設定を確認してみます。 TargetArn
にSQSキューのARNが指定されていることを確認できます。
$ aws lambda get-function-configuration \ --function-name "$(aws lambda list-functions \ --query 'Functions[?contains(FunctionName,`dead-letter-queue-Failure`)].FunctionName' \ --output text)" { "CodeSha256": "UsH0yYCDIX+/bqK44BEorhPz0AKZJuKj8lO1ZYZBKQA=", "FunctionName": "dead-letter-queue-Failure-1CUIKP61Z8K66", "CodeSize": 177, "MemorySize": 128, "FunctionArn": "arn:aws:lambda:ap-northeast-1:************:function:dead-letter-queue-Failure-1CUIKP61Z8K66", "Version": "$LATEST", "Role": "arn:aws:iam::************:role/dead-letter-queue-FailureRole-14A11UV6SD531", "Timeout": 3, "LastModified": "2017-02-14T11:17:19.781+0000", "Handler": "index.handler", "DeadLetterConfig": { "TargetArn": "arn:aws:sqs:ap-northeast-1:************:dead-letter-queue-SQS-MJSS8933C46S-Queue-1H4YEWO8ECVJP" }, "Runtime": "python2.7", "Description": "" }
少し待つとキューにメッセージが溜まっていくことが確認できます。
$ aws sqs get-queue-attributes \ --queue-url "$(aws sqs list-queues \ --query 'QueueUrls' \ --output text)" \ --attribute-names ApproximateNumberOfMessages { "Attributes": { "ApproximateNumberOfMessages": "5" } }
続いてDead Letter Queueの宛先にSNSを設定してみましょう。 src/templates/custom.yml
で TargetArn: !Ref TopicArn
のように修正して再度テンプレートの変換とアップロード/デプロイを実行してください。スタックの更新が完了すると、以下のように TargetArn
にSNSトピックのARNが設定されると思います。
$ aws lambda get-function-configuration \ --function-name "$(aws lambda list-functions \ --query 'Functions[?contains(FunctionName,`dead-letter-queue-Failure`)].FunctionName' \ --output text)" { "CodeSha256": "UsH0yYCDIX+/bqK44BEorhPz0AKZJuKj8lO1ZYZBKQA=", "FunctionName": "dead-letter-queue-Failure-1CUIKP61Z8K66", "CodeSize": 177, "MemorySize": 128, "FunctionArn": "arn:aws:lambda:ap-northeast-1:************:function:dead-letter-queue-Failure-1CUIKP61Z8K66", "Version": "$LATEST", "Role": "arn:aws:iam::************:role/dead-letter-queue-FailureRole-14A11UV6SD531", "Timeout": 3, "LastModified": "2017-02-14T11:04:55.606+0000", "Handler": "index.handler", "DeadLetterConfig": { "TargetArn": "arn:aws:sns:ap-northeast-1:************:dead-letter-queue-SNS-1Q8QPI3W6NOZQ-Topic-RXM2F1OYAE4D" }, "Runtime": "python2.7", "Description": "" }
しばらく待った後に、SNSトピックから起動されるLambda関数のCloudWatch Logsを確認すると、正常に起動されていることが確認できます。
$ aws logs get-log-events \ --log-group-name /aws/lambda/dead-letter-queue-Invoked-1M4Q6QTZC9POI \ --log-stream-name '2017/02/14/[$LATEST]********************************' <snip> { "ingestionTime": 1487070326578, "timestamp": 1487070311392, "message": "Invoked with this event: {u'Records': [{u'EventVersion': u'1.0', u'EventSubscriptionArn': u'arn:aws:sns:ap-northeast-1:************:dead-letter-queue-SNS-1Q8QPI3W6NOZQ-Topic-RXM2F1OYAE4D:f52640d5-8d84-4051-9419-91b59b8f7d8b', u'EventSource': u'aws:sns', u'Sns': {u'SignatureVersion': u'1', u'Timestamp': u'2017-02-14T11:05:10.953Z', u'Signature': u'FentwiyVJt6N/z6sAa5mXU+HMbmcvhGvJKbvcPrqAIhjyahswUHjQ4MrG6pwc4T0kYzRrithfXsedJlJ2kgDRMeDWLYhGg2XczijPV4WzguIW7IXIrV5z5w3p/D+SB7kjdOxlAuBHsZ6Q7WJbSpVVAjLYtaT17RnGMXGzEC2hBwzNbGX9Kvlac5vfe5ULMBgljIRik+H7k/qQFyKZ3edoiuq4IRhH+QSOXb4AUre3PJlzGRoWNkOdiiwyMQ54J9/++rZKR1JWC2JSMKXNxryl28/Juwebd9tWyUHnl2hOyPdyx2ckVUByvK46NmKbLw5NZYzQ+H5w05kpo+lDsPS0w==', u'SigningCertUrl': u'https://sns.ap-northeast-1.amazonaws.com/SimpleNotificationService-b95095beb82e8f6a046b3aafc7f4149a.pem', u'MessageId': u'ea445674-eb6c-5b01-9d08-07680dca07b4', u'Message': u'{\"version\":\"0\",\"id\":\"fa248b23-6277-4ae0-9701-3121a6a8ca03\",\"detail-type\":\"Scheduled Event\",\"source\":\"aws.events\",\"account\":\"************\",\"time\":\"2017-02-14T11:01:19Z\",\"region\":\"ap-northeast-1\",\"resources\":[\"arn:aws:events:ap-northeast-1:************:rule/dead-letter-queue-FailureTimer-G2B3KIPHXTIE\"],\"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'078a04f6-f2a5-11e6-bf49-9fb4922eecf8'}}, u'Type': u'Notification', u'UnsubscribeUrl': u'https://sns.ap-northeast-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:ap-northeast-1:************:dead-letter-queue-SNS-1Q8QPI3W6NOZQ-Topic-RXM2F1OYAE4D:f52640d5-8d84-4051-9419-91b59b8f7d8b', u'TopicArn': u'arn:aws:sns:ap-northeast-1:************:dead-letter-queue-SNS-1Q8QPI3W6NOZQ-Topic-RXM2F1OYAE4D', u'Subject': None}}]}\n" }, <snip>
まとめ
いかがだったでしょうか。
AWS SAMとLambda-Backed Custom Resourceを利用したDead Letter Queueの設定方法をご紹介しました。やはりLambda-Backed Custom Resourceは最高ですね。Dead Letter Queueサポート早く来てくれ!!!!1111
本エントリがみなさんの参考になれば幸いに思います。