Amazon Simple Notification ServiceからAWS Lambdaにメッセージを受け渡す with Scala
はじめに
AWSでシステムを構築する際、各サービスの連携は重要な要素の1つです。
各サービス間の非同期な処理を実現するためには、メッセージングサービスを利用することが多いのではないでしょうか。
本記事では、メッセージングサービスである、Amazon Simple Notification Serviceをトリガーとして、AWS Lambda(以降、Lambdaと表記します)を実行する仕組みを実装します。
Amazon Simple Notification Serviceとは
Amazon Simple Notification Service(以降、SNSと表記します)は、サービス間でメッセージを送受信するための「ハブ」のような役割を果たします。
具体的には、あるアプリケーションがSNSにメッセージを送信すると、SNSは受け取ったメッセージをAmazon SQS、Lambda、モバイルテキストメッセージ (SMS) など、さまざまな配信先に送信できます。
本記事では、AWS CLIからSNSを呼び出し、Lambdaにメッセージを受け渡します。
引用: Amazon SNS とは
引用: 【初心者でも5分で分かる】Amazon SNS
実装
動作確認環境
- OS: macOS Ventura 13.6(Apple M1チップ搭載)
- AWS CLI: 2.8.5
- Scala: 2.13.12
- Java: 11
1. Lambda用のデプロイパッケージを作成
AWS Lambda function in Scala with Container Imageを参考に、Lambda関数をデプロイするためのパッケージを作成します。
事前準備
project/plugins.sbt
に以下の設定を追加します。
以下の記述を追加することで、sbt-assemblyプラグインをプロジェクトに追加します。
このプラグインは、複数のJARファイルを1つにまとめたFAT JARを作成するために使用します。
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "1.2.0")
build.sbt
ファイルに以下の設定を追記します。
これは、LambdaでJava(JVM言語)を実行する際に必要なライブラリと、sbt-assemblyプラグインの設定です。
libraryDependencies += "com.amazonaws" % "aws-lambda-java-core" % "1.1.0"
libraryDependencies += "com.amazonaws" % "aws-lambda-java-events" % "1.1.0"
assembly / assemblyOutputPath := file("target/function.jar")
assembly / assemblyMergeStrategy := {
case PathList("module-info.class") => MergeStrategy.last
case path if path.endsWith("/module-info.class") => MergeStrategy.last
case x =>
val oldStrategy = (assembly / assemblyMergeStrategy).value
oldStrategy(x)
}
-
com.amazonaws % aws-lambda-java-core
: Lambda関数を実行するために必要なライブラリです -
com.amazonaws % aws-lambda-java-events
: Lambda関数でイベントを処理するために必要なライブラリです。今回は、SNSイベントを扱うために使用します -
assembly / assemblyOutputPath := file("target/function.jar")
: sbt-assemblyプラグインで作成するFAT JARファイルの出力先をtarget/function.jarに設定します -
assembly / assemblyMergeStrategy := { ... }
: sbt-assemblyプラグインで複数のJARファイルを結合する際、module-info.classが重複する問題を解決するための設定です
この設定は、sbt-assembly で FAT JAR を生成しようとしたら module-info.class の重複で怒られた話を参考にしています。
1.1 実行するコードの作成
Lambda関数として実行する、Scalaのコードを作成します。
このコードは、SNSから受け取ったメッセージをログに出力する処理を行います。
基本的にAmazon SNS トリガーから Lambda 関数を呼び出すで紹介されていた、JavaのコードをScalaで置き換えたものとなります。
package com.example
import scala.util.{ Failure, Success, Try }
import scala.jdk.CollectionConverters._
import com.amazonaws.services.lambda.runtime.{ Context, LambdaLogger, RequestHandler }
import com.amazonaws.services.lambda.runtime.events.SNSEvent
import com.amazonaws.services.lambda.runtime.events.SNSEvent.SNSRecord
class LambdaHandler() extends RequestHandler[SNSEvent, Boolean] {
private def getSnsMessage(record: SNSRecord): Try[String] = Try(record.getSNS().getMessage())
override def handleRequest(event: SNSEvent, context: Context): Boolean = {
val logger: LambdaLogger = context.getLogger
logger.log("Lambda START\n")
val recordSeq: Seq[SNSRecord] = event.getRecords().asScala.toSeq
recordSeq.forall(record => {
getSnsMessage(record) match {
case Success(message) =>
logger.log(message)
true
case Failure(e) =>
logger.log(e.getMessage)
false
}
})
}
}
1.2 FAT JARファイル作成
sbt-assemblyでFAT JARを作成します。
コマンドを実行すると、target配下にfunction.jarというファイルが生成されます。
sbt assembly
1.3. Dockerイメージを定義
Dockerfileを作成し、Lambda関数用のDockerイメージを定義します。
FROM public.ecr.aws/lambda/java:11
COPY /target/function.jar ${LAMBDA_TASK_ROOT}/lib/
CMD ["com.example.LambdaHandler::handleRequest"]
-
FROM public.ecr.aws/lambda/java:11
: Lambda関数用のベースイメージを指定します -
COPY /target/function.jar ${LAMBDA_TASK_ROOT}/lib/
: 先ほど作成したFAT JARファイルを、Lambda実行環境のライブラリディレクトリにコピーします -
CMD ["com.example.LambdaHandler::handleRequest"]
: Lambda関数のエントリーポイントを指定します
1.4 Dockerイメージ作成
以下のコマンドを実行して、Dockerイメージを作成します。
docker build --platform linux/x86_64 --no-cache -t sns-lambda:v1 .
1.5 ECRにリポジトリ作成
以下のコマンドを実行して、Amazon Elastic Container Registry(以降、ECRと表記します)にリポジトリを作成します。
--repository-name
は任意のリポジトリ名に、--region
はご自身の環境に合わせて変更してください。
aws ecr create-repository --repository-name sns-lambda-repository --region ap-northeast-1
成功すると、repositoryUri
が出力されるので、メモしておきます。
1.6 Dockerイメージにタグ付け
作成したDockerイメージに、ECRリポジトリのURIを使ってタグ付けします。
[AWSアカウントID].dkr.ecr.ap-northeast-1.amazonaws.com/sns-lambda-repository:v1
は、先ほどメモしたrepositoryUri
に置き換えてください。
docker tag sns-lambda:v1 [AWSアカウントID].dkr.ecr.ap-northeast-1.amazonaws.com/sns-lambda-repository:v1
1.7 ECR にログイン
ECRへDockerイメージをプッシュするために、AWSにログインします。
aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin [AWSアカウントID].dkr.ecr.ap-northeast-1.amazonaws.com
1.8 イメージを ECR へプッシュ
DockerイメージをECRへプッシュします。
[AWSアカウントID].dkr.ecr.ap-northeast-1.amazonaws.com/sns-lambda-repository:v1
は、先ほどメモしたrepositoryUri
に置き換えてください。
docker push [AWSアカウントID].dkr.ecr.ap-northeast-1.amazonaws.com/sns-lambda-repository:v1
2. Lambda関数作成
2.1 IAMロール作成
Lambda関数が実行時に必要な権限を持つIAMロールを作成します。
aws iam create-role \
--role-name lambda-sns-role \
--assume-role-policy-document \
'{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}'
出力されるArn
をメモしておきます。
2.2 IAMロールにポリシーを付与
作成したロールに、Lambdaの基本的な実行権限を付与します。
aws iam attach-role-policy \
--role-name lambda-sns-role \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
2.3 Lambda関数作成
以下のコマンドを実行して、Lambda関数を作成します。
ImageUri
には、repositoryUri
を、--role
には、IAMロール作成時にメモしたArn
を指定してください。
aws lambda create-function \
--function-name Function-With-SNS \
--package-type Image \
--code ImageUri=[AWSアカウントID].dkr.ecr.ap-northeast-1.amazonaws.com/sns-lambda-repository:v1 \
--role arn:aws:iam::[AWSアカウントID]:role/lambda-sns-role \
--timeout 60
出力されるFunctionArn
をメモしておきます。
3. SNSの設定
3.1 SNSトピック作成
SNSトピックを作成します。SNSトピックは、メッセージの送信先を識別するための論理アクセスポイントです。
引用: https://docs.aws.amazon.com/ja_jp/sns/latest/dg/sns-create-topic.html
aws sns create-topic --name sns-topic-for-lambda
出力されるTopicArn
をメモしておきます。
3.2 SNSサブスクリプション作成(Lambda関数への紐付け)
作成したSNSトピックに、Lambda関数をサブスクライブ(紐付け)します。
これにより、SNSトピックにメッセージを送信すると、紐付けられたLambda関数が実行されます。
--topic-arn
はTopicArn
、--notification-endpoint
はFunctionArn
を指定します。
aws sns subscribe --protocol lambda \
--region ap-northeast-1 \
--topic-arn arn:aws:sns:ap-northeast-1:[AWSアカウントID]:sns-topic-for-lambda \
--notification-endpoint arn:aws:lambda:ap-northeast-1:[AWSアカウントID]:function:Function-With-SNS
3.3 Lambda関数にリソースベースのポリシーを付与
リソースベースのポリシーをリソースにアタッチします。
--source-arn
には、作成したTopicArn
を指定します。
リソースベースのポリシーとは、誰がリソースにアクセスでき、リソースでどのようなアクションを実行できるかの権限を定義します。
このケースでは、SNSがLambda関数を呼び出すための権限を付与します。
引用: https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/access_policies_identity-vs-resource.html
aws lambda add-permission \
--function-name Function-With-SNS \
--statement-id AllowSNSInvoke \
--action lambda:InvokeFunction \
--principal sns.amazonaws.com \
--source-arn arn:aws:sns:ap-northeast-1:[AWSアカウントID]:sns-topic-for-lambda
4. SNSにメッセージを送信する
作成したSNSトピックにメッセージを送信します。
aws sns publish --message 'Hello SNS' --subject Test \
--topic-arn arn:aws:sns:ap-northeast-1:[AWSアカウントID]:sns-topic-for-lambda
5. ログの確認
Amazon CloudWatchのメニューから「ロググループ」を選択し、作成したLambda関数のロググループ(例:/aws/lambda/Function-With-SNS)をクリックします。
SNSから送信されたメッセージがLambdaで受け取れていることが確認できます。
まとめ
今回は、AWS CLIからSNSを呼び出し、SNSからLambdaへメッセージを送信する仕組みを実装しました。
今後も色々なサービス間の連携に挑戦していきたいと思います。
最後までお付き合いいただき、ありがとうございました!
Discussion