この投稿は 「今年もやるよ!AWS Lambda縛り Advent Calendar 2015 - Qiita」 の 2日目の記事です。
はじめに
10月の re:Invent 2015 にて AWS Lambda の大幅アップデートが発表され、Node.js と Java のみが対応していた Lambda Function が Python に対応したことで、世界の Pythonista が「これで勝つる!」と色めき立ったのは記憶に新しいところです。
さて、それから約2ヶ月が経ち、そろそろ試してみようかと思ったところ、意外と Python for Lambda (Python Functions) の記事が少なかったので *1、今回、自分でやってみたという次第です。
やりたいこと
S3オブジェクトの追加や変更を AWS Lambda で検知して、Amazon SNS でメール通知する
はじめての Python for Lambda (Python Functions) ということで、シンプルなパターンをやってみます。
まず、AWS Lambda では、S3 の状態変更などをトリガーとして Lambda Function を発火させることができます。今回は、S3オブジェクトの追加や変更を Event source として Python Function を発火し、実行結果を Amazon SNS 経由でメール通知させてみようと思います。
CloudWatch ログでも実行結果を確認することができるのですが、ログを確認できるのが(デフォルトだと)5分間隔となっていて、デバッグには少し不便です。そこで Amazon SNS を使うと、AWS の非同期タスクが完了したタイミングやエラーが発生したタイミングで通知を出すことができ、容易にデバッグができるようになります。
Amazon SNS については、以下の過去記事を参照のこと。
<過去記事>akiyoko.hatenablog.jp
利用条件
- Python 2.7(現時点では 3系は利用できません)
- Boto3(Boto は AWS Lambda に対応していません)
利用手順
Python for Lambda (Python Functions) の利用方法は、概ね以下の通りです。
1. Event source を設定する
AWS Management Console から AWS Lambda を選択し、「Get Started Now」をクリックします。
Blueprint(Lambda のテンプレート)を選択します。
今回は、「s3-get-object-python」を選びます。
Event source の設定を行います。
項目 | 設定例 | 備考 |
---|---|---|
Event source type | S3 | |
Bucket | boto3-test | 作成済みの S3バケットを指定 |
Event type | Object Created (All) | 「Put / Post / Copy / Complete Multipart Upload」など細かな指定ができるが、オブジェクトの追加・変更を一括で定義したいときは「Object Created (All)」とすればよい |
Prefix | ||
Suffix | mp4 | この設定をしておくと、例えば、拡張子が「mp4」以外のファイルをアップロードしても Lambda Function が発火しなくなる |
2. Lamdba Function を登録する
Lamdba Function の登録画面が開くので、まずは Role の設定を行います。
Role で「S3 execution role」を選択すると、新しいウィンドウが開きます。*2
「S3 execution role」には、S3 にアクセスする権限しか記述されていないため、Amazon SNS にアクセスできるように、この Role のポリシーに追記をします。
「lambda_s3_exec_role」に Amazon SNS へのアクセス許可ポリシー(ただし、必要最低限の「sns:CreateTopic」と「sns:Publish」のみ)を加えます。
<編集前>(lambda_s3_exec_role)
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": "arn:aws:logs:*:*:*" }, { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject" ], "Resource": [ "arn:aws:s3:::*" ] } ] }
<編集後>
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": "arn:aws:logs:*:*:*" }, { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject" ], "Resource": [ "arn:aws:s3:::*" ] }, { "Effect": "Allow", "Action": [ "sns:CreateTopic", "sns:Publish" ], "Resource": "*" } ] }
Lambda Function名(ARN の一部になります)、および Pythonコードを以下のように修正します。
import json import urllib import boto3 print('Loading function') s3 = boto3.client('s3') sns = boto3.resource('sns', 'ap-northeast-1') def lambda_handler(event, context): print("Received event: " + json.dumps(event, indent=2)) # Get the object from the event and show its content type bucket = event['Records'][0]['s3']['bucket']['name'] key = urllib.unquote_plus(event['Records'][0]['s3']['object']['key']).decode('utf8') print("bucket={}, key={}".format(bucket, key)) try: response = s3.get_object(Bucket=bucket, Key=key) except Exception as e: print(e) print('Error getting object {} from bucket {}. Make sure they exist and your bucket is in the same region as this function.'.format(key, bucket)) # Publish a message sns.Topic(sns.create_topic(Name='test-complete').arn).publish( Subject="Error!", Message="Failed to get object. bucket={}, key={}, {}".format(bucket, key, e), ) raise e # Publish a message sns.Topic(sns.create_topic(Name='test-complete').arn).publish( Subject="Success!", Message="Completed to get object. bucket={}, key={}".format(bucket, key), ) print("CONTENT TYPE: " + response['ContentType']) return response['ContentType']
最後に、この Event の検知を ON にするかどうかを選択して、「Create function」をクリックします。今回は、「Enable later」を選択しておき、テストが完了した後に設定を変更することにします。
3. Lamdba Function のテストを行う
Lamdba Function のテストをします。
まずは、テストをする前に、テスト用のダミーファイルを置いておきます。
(「Sample event template」と呼ばれるテスト用のサンプルイベントに、CreateObject のテストが用意されておらず、PutObject しか用意されていないので、先にオブジェクトが存在しないとエラーになってしまうためです。)
テストしてみます。
Sample event template で「S3 Put」を選択します。
最低限、以下のようにバケット名だけを修正すれば、テストが実行できます。
テストが成功しました。
Execution result: succeeded
"image/jpeg"
Summary
Request ID c3e359d6-83c9-11e5-a817-8ff289613676 Duration 626.36 ms Billed duration 700 ms Resources configured 128 MB Max memory used 46 MB
Log output
START RequestId: c3e359d6-83c9-11e5-a817-8ff289613676 Version: $LATEST Received event: { "Records": [ { "eventVersion": "2.0", "eventTime": "1970-01-01T00:00:00.000Z", "requestParameters": { "sourceIPAddress": "127.0.0.1" }, "s3": { "configurationId": "testConfigRule", "object": { "eTag": "0123456789abcdef0123456789abcdef", "key": "HappyFace.jpg", "sequencer": "0A1B2C3D4E5F678901", "size": 1024 }, "bucket": { "ownerIdentity": { "principalId": "EXAMPLE" }, "name": "boto3-test", "arn": "arn:aws:s3:::mybucket" }, "s3SchemaVersion": "1.0" }, "responseElements": { "x-amz-id-2": "EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH", "x-amz-request-id": "EXAMPLE123456789" }, "awsRegion": "us-east-1", "eventName": "ObjectCreated:Put", "userIdentity": { "principalId": "EXAMPLE" }, "eventSource": "aws:s3" } ] } bucket=boto3-test, key=HappyFace.jpg CONTENT TYPE: image/jpeg END RequestId: c3e359d6-83c9-11e5-a817-8ff289613676 REPORT RequestId: c3e359d6-83c9-11e5-a817-8ff289613676 Duration: 626.36 ms Billed Duration: 700 ms Memory Size: 128 MB Max Memory Used: 46 MB
SNS へのメール通知も成功していました。
<メール件名>
Success!
<メール本文>
Completed to get object. bucket=boto3-test, key=HappyFace.jpg
CloudWatch にもログが吐き出されています。
ただし先述したように、CloudWatch のログはデフォルト(無料枠)では 5分ごとに更新されるので、何度もテストするには少し不便です。
Event Data Loading function START RequestId: c3e359d6-83c9-11e5-a817-8ff289613676 Version: $LATEST Received event: { "Records": [ { "eventVersion": "2.0", "eventTime": "1970-01-01T00:00:00.000Z", "requestParameters": { "sourceIPAddress": "127.0.0.1" }, "s3": { "configurationId": "testConfigRule", "object": { "eTag": "0123456789abcdef0123456789abcdef", "key": "HappyFace.jpg", "sequencer": "0A1B2C3D4E5F678901", "size": 1024 }, "bucket": { "ownerIdentity": { "principalId": "EXAMPLE" }, "name": "boto3-test", "arn": "arn:aws:s3:::mybucket" }, "s3SchemaVersion": "1.0" }, "responseElements": { "x-amz-id-2": "EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH", "x-amz-request-id": "EXAMPLE123456789" }, "awsRegion": "us-east-1", "eventName": "ObjectCreated:Put", "userIdentity": { "principalId": "EXAMPLE" }, "eventSource": "aws:s3" } ] } bucket=boto3-test, key=HappyFace.jpg CONTENT TYPE: image/jpeg END RequestId: c3e359d6-83c9-11e5-a817-8ff289613676 REPORT RequestId: c3e359d6-83c9-11e5-a817-8ff289613676 Duration: 626.36 ms Billed Duration: 700 ms Memory Size: 128 MB Max Memory Used: 46 MB
4. 実環境で試す
テストが成功したので、Event の検知を ON にしておきます。
最後に、実際に S3 に mp4ファイルをアップロードしてみて、Lambda Function が発火するかテストしてみます。
実行結果
<メール件名>
Success!
<メール本文>
Completed to get object. bucket=boto3-test, key=test/sample.mp4
メール通知されました。
まとめ
AWS Lambda をはじめて使った感想ですが、IAM の設定やら GUI画面のインタフェースに少し取っ付きづらいところはあるものの、Python のコードが実行でき、Boto3 から AWSの各種サービスを呼び出せるということで、「もう何でもできる!」という印象です。
サーバレスな 2-Tier アーキテクチャを考慮するなら、AWS Lambda は各サービスを連携させるためのコアとなるサービスになるので、ある程度のパターンを習得しておくのはアリかと思います。
今回は「S3オブジェクトの追加や変更を AWS Lambda で検知して、Amazon SNS でメール通知する」というシンプルなパターンでしたが、次回は応用パターンとして、「S3 に mp4ファイルが追加されたら、Amazon Elastic Transcoder を起動して HLS形式の動画ファイルにトランスコードする」というのをやってみたいと思います。
ということで明日は引き続き、私 akiyoko の 3日目の記事になります。
(連チャンですいません。。)