SNSとLambdaを利用したS3のファイル同期を実装する
データアナリティクス事業本部@札幌の佐藤です。 私は今までAWSでEC2でVPNサーバーやバッチサーバーなどを構築することが多く、AWSが用意しているサーバーレスな環境構築の経験がありませんでした。 今回は入門として、Amazon Simple Notification Service (SNS) とAWS Lambdaを使用し、サーバーレスな環境構築を行ってみました。
今回の実装イメージ
以下のような形で、S3にファイルがPUTされたことをトリガーとし別リージョンのS3へファイルを格納します。
S3には標準でS3クロスリージョンレプリケーションがあります。 今回は同一リージョンでは利用できない制約を避けるため、AWS Lambdaを利用した実装を行いました。
初期準備としてINPUT側とOUTPUT側それぞれにS3を用意しておきます。 とりあえずバケットが存在している状態で大丈夫です。
各機能の連携の関係でAWS Lambdaから作成していきます。
実装
AWS Lambda
平野さんが丁寧に記載されている手順がありますので、こちらを参考にしつつ実装します。
関数を作成します。
画面ができました。 Amazon CloudWatch Logsの下の点線部にS3を表示させたいので、点線部に記載されている通りロールにS3の権限を付与させます。
画面を下にスクロールすると出てくる実行ロールのロールの表示リンクをクリックします。
今回はS3に対するフル権限を与えていますが、実業務で使用する場合は正しく権限を与える必要があります。
Amazon CloudWatch Logsの下の点線部にS3を表示されました。 Amazon SNSの処理で使用するのでARNをコピーしておきます。
Amazon SNS
ここではAmazon SNS側での「S3→Amazon SNS連携」と「Amazon SNS→AWS Lambda連携」の準備を行います。
トピックを作成します。
トピックを作成していきます。
S3から接続させるためにアクセスポリシーを変更していきます。 AWSのドキュメントに記載の通り、Conditionの中にS3バケット名を追加します。
{ "Version": "2008-10-17", "Id": "__default_policy_ID", "Statement": [ { "Sid": "__default_statement_ID", "Effect": "Allow", "Principal": { "AWS": "*" }, "Action": [ "SNS:Publish", "SNS:RemovePermission", "SNS:SetTopicAttributes", "SNS:DeleteTopic", "SNS:ListSubscriptionsByTopic", "SNS:GetTopicAttributes", "SNS:Receive", "SNS:AddPermission", "SNS:Subscribe" ], "Resource": "arn:aws:sns:ap-northeast-1:XXXXXXXX:cm-sato-tsuyoshi-sns", "Condition": { "ArnLike": { "AWS:SourceArn": "arn:aws:s3:::cm-sato-tsuyoshi-sns-lambda-input" } } } ] }
作成できました。 これでAmazon SNS側でのS3→Amazon SNSの連携の準備は完了です。 次にAmazon SNS→AWS Lambdaの連携の準備をしたいと思います。
サブスクリプションを作成します。
今回はLambdaへの連携になるためプロトコルはAWS Lambdaを選択。 ARNはAWS Lambdaの処理でコピーしていたARNを入力します。
Amazon SNS→AWS Lambdaの連携の準備が完了です。 Amazon SNSのARNは後続の処理で使用するのでARNをコピーしておきます。
S3(input側)
S3側での「S3→Amazon SNSの連携準備」を行います。
input側のディレクトリ構成はs3://cm-sato-tsuyoshi-sns-lambda-input/file/
とします。
プロパティタブを選択します。
いくつか設定できるところがありますが、今回はそのうちのEventsの設定を行います。
今回はファイルがPUTされたのをトリガーにファイル連携させていきたいので、イベントはPUTのみ選択します。
プレフィックスは上に書いた通り、file/
を入力、サフィックスは今回はテキストファイルをPUTするので、.txt
を入力します。
SNSはAmazon SNSの名前を入力します。
作成できました。
保存した際に以下のようなエラーが出力した場合、Amazon SNS側のポリシー設定にミスがあると思われますので見直してください。
AWS Lambda
最初に作成したAWS Lambdaに対して追加で設定を行います。 ここで「Amazon SNS→AWS Lambda連携」と「AWS Lambda→S3連携」の準備を行います。
トリガーを追加して、Amazon SNSからの連携を追加します。
トリガーの設定でSNS
を選択し、ARNはAmazon SNSのでコピーしたARNを入力します。
作成できると、AWS Lambdaへの連携にAmazon SNSが追加されます。
続いて、S3への連携を実装します。 上記画面と同じ画面の関数コードに実装していきます。
画面のlambda_function.pyに以下を実装します。 以下で実装されていた内容を一部修正します。 今度こそ理解する!俺式Lambda入門
import boto3 import json s3 = boto3.client('s3') def read_file(bucket_name, file_key): response = s3.get_object(Bucket=bucket_name, Key=file_key) return response[u'Body'].read().decode('utf-8') def upload_file(bucket_name, file_key, bytes): out_s3 = boto3.resource('s3') s3Obj = out_s3.Object(bucket_name, file_key) res = s3Obj.put(Body = bytes) return res def lambda_handler(event, context): print("Lambda Start") di = json.loads(event['Records'][0]['Sns']['Message']) print(di['Records'][0]['s3']['bucket']['name']) print(di['Records'][0]['s3']["object"]['key']) input_bucket=di['Records'][0]['s3']['bucket']['name'] input_key=di['Records'][0]['s3']["object"]['key'] # S3からファイルを読み込み text = read_file(input_bucket, input_key) # outputのキーを設定 output_key = "output/" + input_key # ファイルをS3に出力 upload_file('cm-sato-tsuyoshi-sns-lambda-output', output_key, bytes(text, 'UTF-8')) print("Lambda end")
s3://cm-sato-tsuyoshi-sns-lambda-output/output/
配下に格納されるように実装しています。
今回はS3バケットをハードコーディングしています
event['Records'][0]['Sns']['Message']
で返される型が文字列型で要素を取り出せなかったため、json.loadsを間で実施しています。
実装完了後、保存して完了です。
動かしてみる
実際に動かして正しく動いているかチェックしてみます。
input側S3のs3://cm-sato-tsuyoshi-sns-lambda-input/file/
配下にファイル(今回は「test001.txt」)をPUTします。
デバッグを入れているので、CloudWatch Logsでうまく動いているか確認します。 ログを見る限りうまく動いているようです。
output側S3のs3://cm-sato-tsuyoshi-sns-lambda-output/output/
配下にファイルが格納されました。
感想
サーバーレス初心者でも簡単にできたなというのが触ってみた感想です。 Amazon SNSのポリシーの設定がはまりどころかなとは思いますが、それ以外は画面の入力欄に従っていけばうまくできました。
ARNで機能間を連携しているのが実際に体験できたため、サーバーレス未経験の方はまずAmazon SNS→AWS Lambdaの連携を試してみていただければと思います。