S3 Event Notificationを別アカウントのAWS Lambdaに送信する
こんにちは、虎塚です。
Amazon S3では、オブジェクトの生成や削除をトリガーに、SNS、SQSやAWS LambdaにEvent Notificationを送信できます。送信先のAWSリソースは、S3と別のアカウントでも問題ありません。
今回は、あるAWSアカウントのS3に画像がアップロードされた時に、別のアカウントのAWS Lambdaを起動して画像をリサイズする処理を例に、S3 Event Notificationをクロスアカウントで利用する設定を説明します。
処理の概要
AWS Lambdaの公式ドキュメントには、次のようなチュートリアルがあります。
(1) S3バケットに画像がアップロードされると、(2) S3がイベントを検知して (3) あらかじめ登録されたLambda関数を呼び出します。(4) Lambda関数は画像を取得し、(5) リサイズ処理をして、(6) 別のS3バケットにリサイズした画像を保存します。
上の処理に登場するAWSリソースが異なるAWSアカウントに所属していた場合、概念図は次のようになるでしょう。
アカウントAには、オリジナルの画像を保存するS3バケットがあります。アカウントBには、画像処理用のLambdaと、リサイズした画像を保存するS3バケットがあります。権限設定を適切におこなえば、上の図のような構成でも問題なく処理を実行できます。
これができると何が嬉しいでしょうか。たとえば、あなたがログ収集プロダクトのSaaSをAWS上で展開するサービサーだとします。顧客のAWSアカウントでS3にログファイルが出力されたことをトリガーに、サービサー自身のアカウントでAWS Lambdaを起動して、顧客のログにアクセスし、ログを取得、加工、再保存することができます。
以降の説明では、アカウントAを顧客アカウント、アカウントBをサービサーアカウントと呼び分けます。
手順
1. S3バケットの作成
まず、顧客アカウントに、オリジナル画像をアップロードするS3バケット (source2016) を作成します。次に、サービサーアカウントに、リサイズした画像をアップロードするS3バケット (source2016resized) を作成します。
オリジナル画像用のS3バケットの名前は任意でよいですが、リサイズ画像をアップロードするS3バケットは、オリジナル画像用のS3バケット名の直後に「resized」を続けた名前にします。ステップ2で作成するLambdaファンクションのコード内のロジックが、そのように定義しているためです。
2. Lambdaコードの作成
公式チュートリアルにしたがって、Lambdaコードを作成します。今回はPythonを使います。サービサーアカウントにEC2を起動して、その中でLambdaのコードをビルドします。
ビルド用インスタンスの準備
まず、Amazon Linux AMI 2015.09.1 (HVM) のインスタンスを1台起動して、ec2-userユーザでSSHログインします。
Lambdaコードの作成
次に、ec2-userのルートディレクトリ (/home/ec2-user/) で、次のPythonスクリプトを作成します。
import boto3 import os import sys import uuid from PIL import Image s3_client = boto3.client('s3') def resize_image(image_path, resized_path): with Image.open(image_path) as image: image.thumbnail(tuple(x / 2 for x in image.size)) image.save(resized_path) def handler(event, context): for record in event['Records']: bucket = record['s3']['bucket']['name'] key = record['s3']['object']['key'] download_path = '/tmp/{}{}'.format(uuid.uuid4(), key) upload_path = '/tmp/resized-{}'.format(key) s3_client.download_file(bucket, key, download_path) resize_image(download_path, upload_path) s3_client.upload_file(upload_path, '{}resized'.format(bucket), key)
パッケージのインストールと更新
それから、ビルドに必要なパッケージをインストールまたは更新します。
$ sudo yum install python27-devel python27-pip gcc $ sudo yum install libjpeg-devel zlib-devel
今回は、次のバージョンのパッケージがインストールされました。
- python27-devel.x86_64 0:2.7.10-4.120.amzn1
- python27-pip-6.1.1-1.21.amzn1.noarch
- gcc.noarch 0:4.8.3-3.20.amzn1
- libjpeg-turbo-devel.x86_64 0:1.2.90-5.14.amzn1
- zlib-devel.x86_64 0:1.2.8-7.18.amzn1
virtualenv環境を作成してアクティベートします。
$ virtualenv ~/shrink_venv $ source ~/shrink_venv/bin/activate
ライブラリをインストールします。
$ pip install Pillow $ pip install boto3
今回は、次のバージョンのライブラリがインストールされました。
- Pillow-3.1.0
- boto3-1.2.3
Lambdaコードのパッケージング
最後に、次のコマンドを実行して、Lambdaコードをライブラリと一緒にzip圧縮します。
$ zip -r ~/lambda_function.zip shrink_image.py $ cd $VIRTUAL_ENV/lib/python2.7/site-packages $ zip -r9 ~/lambda_function.zip * $ cd $VIRTUAL_ENV/lib64/python2.7/site-packages $ zip -r9 ~/lambda_function.zip *
Lambdaコードをパッケージングしたlambda_function.zipが作成されます。インスタンスからログアウトし、ローカル環境にlambda_function.zipをコピーしておきます。
$ scp -i ~/.ssh/key-pair.pem ec2-user@public-ip-address:lambda_function.zip .
EC2はもう使いませんので、stopしてかまいません。
3. AWS Lambdaの実行ロールの作成
サービサーアカウントでIAM Roleを作成します。このIAM Roleは、ステップ4で作成するLambda関数の実行ロールとして利用します。
AWS Lambdaタイプの実行ロールの作成
ここでの手順は、公式チュートリアルどおりです。
まず、AWS Management ConsoleのIAMダッシュボードを開きます。次に、左メニューの[Roles]→[Create New Role]ボタンを選択し、Role Name (lambda-cross-execution) を入力します。
それから、[Select Role Type]画面で、AWS Service RoleのAWS Lambdaを選択します。最後に、[Attach Policy]画面で、AWSLambdaExecuteポリシーを選択して、IAM Roleを作成します。
4. Lambda関数の作成
ステップ2で作成したLambdaコードをアップロードして、Lambda関数を作成します。その際に、ステップ3で作成した実行ロールを指定します。
次のコマンドを実行して、サービサーアカウント (AWSアカウントIDは000000000000とします) にLambda関数を作成します。
$ aws lambda create-function \ --region ap-northeast-1 \ --function-name CreateThumbnail \ --zip-file fileb://lambda_function.zip \ --role arn:aws:iam::00000000000:role/lambda-cross-execution \ --handler shrink_image.handler \ --runtime python2.7 \ --timeout 10 \ --memory-size 1024
作成したLambda関数のARNが戻り値に表示されるので、控えておきます。この値はステップ6で使用します。
5. Lambdaのアクセスポリシー設定
サービサーアカウントのLambda関数のアクセスポリシーに、顧客アカウント (999999999999) のS3バケット (source2016) から呼び出すことを許可するアクセス権限を追加します。
$ aws lambda add-permission \ --region ap-northeast-1 \ --function-name CreateThumbnail \ --statement-id sts20160127s3cross \ --principal s3.amazonaws.com \ --action lambda:InvokeFunction \ --source-arn arn:aws:s3:::source2016 \ --source-account 999999999999
公式チュートリアルのように同じAWSアカウントのS3からLambdaを呼び出す場合、source-accountオプションの値はサービサーアカウントのID (000000000000) になります。しかし、ここでは顧客アカウントのS3バケットからの呼び出しを許可するため、999999999999を設定します。
6. イベント通知の有効化
顧客アカウントにあるオリジナル画像用のS3バケット (source2016) で、イベント通知を有効化します。イベントの送信先はサービサーアカウント (000000000000) のLambdaです。ステップ4で控えておいたLambda関数のARNを指定します。
今回は、設定をJSONファイルで与えました。なお、ステップ5までに権限を適切に設定していれば、この操作をAWS Management Console上で実行することもできます。
aws s3api put-bucket-notification-configuration \ --cli-input-json file://notification_config
{ "Bucket": "source2016", "NotificationConfiguration": { "LambdaFunctionConfigurations": [ { "Id": "20160127", "LambdaFunctionArn": "arn:aws:lambda:ap-northeast-1:000000000000:function:CreateThumbnail", "Events": [ "s3:ObjectCreated:*" ] } ] } }
これで、オリジナル画像用のS3バケットにファイルがアップロードされると、サービサーアカウントのLambdaに通知が送信され、Lambdaファンクションが呼び出されるようになります。
7. S3バケットポリシーの付与
サービサーアカウント (000000000000) のLambdaから、顧客アカウントのS3バケット (source2016) にアクセスできるように、バケットポリシーを設定します。この権限は、Lambda関数がリサイズ対象のファイルを取得するために必要です。
AWS Management ConsoleでS3バケットを選択して、[Permissions]を開き、[Add bucket policy]ボタンをクリックします。[Bucket Policy Editor]で次のポリシーを設定します。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "GetObject", "Action": [ "s3:GetObject" ], "Effect": "Allow", "Resource": "arn:aws:s3:::source2016/*", "Principal": { "AWS": "000000000000" } }, { "Sid": "ListBucket", "Action": [ "s3:GetBucketLocation", "s3:ListBucket" ], "Effect": "Allow", "Resource": "arn:aws:s3:::source2016", "Principal": { "AWS": "000000000000" } } ] }
これは、S3バケットを他のアカウントに公開するときの一般的な設定です。
以上で完了です。
顧客アカウントのS3バケットに画像をアップロードすると、サービサーアカウントのLambdaが起動して画像をリサイズし、サービサーアカウントのS3バケットにアップロードします。
おわりに
今回紹介した手順のポイントは、次の3つです。
- Lambda関数の実行ロールで、AWS Lambdaサービスからの利用を許可します (ステップ3)
- Lambdaのアクセスポリシーで、別アカウントのS3からアクセスするための権限を付与します (ステップ5)
- 他のアカウントにS3バケットを公開するために、バケットポリシーを設定します (ステップ7)
公式ドキュメントに記述された例を組み合わせることで、S3 Event Notificationを別アカウントのLambdaに飛ばすことができました。
便利な一方で、AWSアカウントのセキュリティを維持するためには、次のことに気をつける必要がありそうですね。
- 意図しないAssumeRoleの設定がIAM Roleに存在しないこと
- 意図しないバケットポリシーがS3バケットに存在しないこと
IAM、S3、AWS Lambdaのアクセス制御の仕組みを理解して、安全かつ便利にAWSを利用しましょう。
それでは、また。