この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちは、虎塚です。
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スクリプトを作成します。
shrink_image.py
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
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を利用しましょう。
それでは、また。