FeralとAWS SAMを使ってScalaでLambda関数を作成する
はじめに
2023年10月26日に、Serverless Framework v4が発表され、新しくライセンスが導入されることが発表されました。v4自体は2024年5月21日からベータ版としてリリースされ、利用できるようになっていました。
Serverless Framework v4は、Serverless Framework v3とは異なり、オープンソースのライセンスがなくなり、有料のライセンスが導入されることになり、これによってServerless Framework v4を使用するためには、有料のライセンスが必要になりました。
前年度に年間収益が200万ドルを超えた組織がv4を利用する場合に対象となるみたいですが、200万ドル以下だったかどうかの審査はないため、自己申告のようです。
リリースブログとCLI上では個人の開発者や小規模企業、非営利団体は無料で利用できると記載されていましたが今回の変更でServerless Framework以外の選択肢も試してみようと思いこの記事を書きました。
いわゆる備忘録のようなものです。
この記事では、TypelevelのライブラリであるFeralを使用してScala 3でAWS Lambda関数を作成する方法を紹介します。また、AWS SAMを使って作成した関数のデプロイを行います。
本記事では以下の内容についての構築と説明を行います。
- FeralでのLambda関数の作成
- AWS SAMを使ったデプロイ
Feralとは
Feralは、Cats Effectを使ってScalaでサーバーレス関数を書き、FaaS(Function as a Service)基盤に適合させるライブラリであり、追加の便利なsbtコマンドを提供するプラグインで、JVMとJavaScriptの両方のランタイムをターゲットにしているTypelevelのプロジェクトです。
Feralの特徴や使い方については、以下の記事に詳しく記載されておりますので、参考にしてください。
AWS SAMとは
AWS SAM(Serverless Application Model)は、AWSのサーバーレスアプリケーションを構築、テスト、デプロイするためのフレームワークです。AWS SAM CLIを使うことで、ローカルでLambda関数をテストすることができます。
AWS SAMに関しては、以下の記事に詳しく記載されておりますので、参考にしてください。
構築
以下の手順でFeralとAWS SAMを使ってScalaでLambda関数を作成します。
- AWS SAMを使用したプロジェクトの作成
- Feralを使用したLambda関数の作成
- AWS SAMを使用したデプロイ
AWS SAMを使用したプロジェクトの作成
まずはAWS SAM CLIをインストールします。
brew install aws-sam-cli
インストールが完了したら、以下のコマンドでバージョンを確認します。
sam --version
次に、SAMを使用してプロジェクトを作成します。
sam init
まず、1 - AWS Quick Start Templates
を選択し、テンプレートの種類は1 - Hello World Example
を選択します。
Which template source would you like to use?
1 - AWS Quick Start Templates
2 - Custom Template Location
Choice: 1
Choose an AWS Quick Start application template
1 - Hello World Example
2 - Data processing
3 - Hello World Example with Powertools for AWS Lambda
4 - Multi-step workflow
5 - Scheduled task
6 - Standalone function
7 - Serverless API
8 - Infrastructure event management
9 - Lambda Response Streaming
10 - Serverless Connector Hello World Example
11 - Multi-step workflow with Connectors
12 - GraphQLApi Hello World Example
13 - Full Stack
14 - Lambda EFS example
15 - DynamoDB Example
16 - Machine Learning
Template: 1
次に、パッケージのランタイムを選択します。ここでは11 - nodejs20.x
を選択します。
Use the most popular runtime and package type? (Python and zip) [y/N]: N
Which runtime would you like to use?
1 - dotnet8
2 - dotnet6
3 - go (provided.al2)
4 - go (provided.al2023)
5 - graalvm.java11 (provided.al2)
6 - graalvm.java17 (provided.al2)
7 - java21
8 - java17
9 - java11
10 - java8.al2
11 - nodejs20.x
12 - nodejs18.x
13 - nodejs16.x
14 - python3.9
15 - python3.8
16 - python3.12
17 - python3.11
18 - python3.10
19 - ruby3.3
20 - ruby3.2
21 - rust (provided.al2)
22 - rust (provided.al2023)
Runtime: 11
次に、パッケージの種類を選択します。ここでは1 - Zip
を選択します。
What package type would you like to use?
1 - Zip
2 - Image
Package type: 1
ランライムにNode
を選択したため、npm
のテンプレートを選択する必要があります。ここでは1 - Hello World Example
を選択します。
※ 今回はScalaでLambda関数を作成するため、npm
のテンプレートを選択しても使用しません。
Based on your selections, the only dependency manager available is npm.
We will proceed copying the template using npm.
Select your starter template
1 - Hello World Example
2 - Hello World Example TypeScript
Template: 1
X-Ray
トレースを有効にするかどうかを尋ねられます。今回は使用しないためN
を選択します。
Would you like to enable X-Ray tracing on the function(s) in your application? [y/N]: N
次に、CloudWatch Application Insights
を有効にするかどうかを尋ねられます。今回は使用しないためN
を選択します。
Would you like to enable monitoring using CloudWatch Application Insights?
For more info, please view https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch-application-insights.html [y/N]: N
最後に、Lambda関数のログをJSON形式で出力するかどうかを尋ねられます。今回は使用しないためN
を選択します。
Would you like to set Structured Logging in JSON format on your Lambda functions? [y/N]: N
最後にプロジェクト名を入力します。
Project name [sam-app]: feral-example
Cloning from https://github.com/aws/aws-sam-cli-app-templates (process may take a moment)
-----------------------
Generating application:
-----------------------
Name: feral-example
Runtime: nodejs20.x
Architectures: x86_64
Dependency Manager: npm
Application Template: hello-world
Output Directory: .
Configuration file: feral-example/samconfig.toml
Next steps can be found in the README file at feral-example/README.md
Commands you can use next
=========================
[*] Create pipeline: cd feral-example && sam pipeline init --bootstrap
[*] Validate SAM template: cd feral-example && sam validate
[*] Test Function in the Cloud: cd feral-example && sam sync --stack-name {stack-name} --watch
プロジェクトが作成されたら、プロジェクト構成は以下のようになります。
.
├── README.md
├── events
│ └── event.json
├── hello-world
│ ├── app.mjs
│ ├── package.json
│ ├── .npmignore
│ └── tests
│ └── unit
│ └── test-handler.mjs
├── samconfig.toml
└── template.yaml
今回はScalaでLambda関数を作成するため、hello-world
ディレクトリは削除してしまいましょう。
rm -rf hello-world
Feralを使用したLambda関数の作成
次に、Feralを使用してScalaでLambda関数を作成します。
そのため、手動でテンプレートを作成します。
まずは、プロジェクトにFeralのプラグインを追加します。
addSbtPlugin("org.typelevel" %% "sbt-feral-lambda" % "0.3.0")
そして、build.sbt
を以下の内容で作成します。
ThisBuild / organization := "com.example"
ThisBuild / scalaVersion := "3.3.3"
lazy val helloWorld = (project in file("functions/hello-world"))
.enablePlugins(LambdaJSPlugin)
.settings(
name := "hello-world",
libraryDependencies ++= Seq(
"org.typelevel" %% "feral-lambda" % "0.3.0"
)
)
lazy val root = (project in file("."))
.settings(name := "feral-example")
.aggregate(helloWorld)
次に、functions/hello-world
ディレクトリを作成し、その中にsrc/main/scala
ディレクトリを作成します。
mkdir -p functions/hello-world/src/main/scala
functions/hello-world/src/main/scala
ディレクトリにHelloWorld.scala
を作成します。
import cats.effect.*
import feral.lambda.*
case class Param(message: String) derives io.circe.Decoder
object HelloWorld extends IOLambda.Simple[Param, Unit]:
def apply(event: Param, context: Context[IO], init: Init): IO[Option[Unit]] =
IO.println(s"got message: [${event.message}]").as(Some(()))
次に、このLambda関数をjsファイルとしてビルドします。
sbt npmPackage
するとtarget/scala-(Scalaバージョン)/npm-package
以下にファイルが生成されます。
.
├── functions
│ └── hello-world
│ ├── target
│ │ └── scala-3.3.3
│ │ ├── npm-package
│ │ │ ├── index.js
│ │ │ └── package.json
このファイルをAWS SAMを使ってデプロイします。
AWS SAMを使用したデプロイ
まずは、template.yaml
を以下の内容に修正します。
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
feral-example
Sample SAM Template for feral-example
Globals:
Function:
Timeout: 3
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: functions/hello-world/target/scala-3.3.3/npm-package/
Handler: index.HelloWorld
Runtime: nodejs20.x
Architectures:
- x86_64
Outputs:
HelloWorldFunction:
Description: "Hello World Lambda Function ARN"
Value: !GetAtt HelloWorldFunction.Arn
HelloWorldFunctionIamRole:
Description: "Implicit IAM Role created for Hello World function"
Value: !GetAtt HelloWorldFunctionRole.Arn
AWS SAMでは、CodeUri
にLambda関数のコードが格納されているディレクトリを指定します。今回はFeralによって生成されたfunctions/hello-world/target/scala-3.3.3/npm-package/
を指定しています。
そして、Handler
にはLambda関数のエントリーポイントを指定します。Feralによって生成されたLambda関数のハンドラーはindex.{オブジェクト名}
となるため、index.HelloWorld
を指定しています。
デプロイを行う前に変更したtemplate.yaml
が正しいかを確認します。
sam validate
コマンドを実行することでtemplate.yaml
の構文チェックを行えます。
sam validate
問題がなければ、次にビルドを行います。
sam build
コマンドを実行すると、Lambda関数のビルドが設定どおりに動作するか確認できます。今回ビルド自体はFeralを使用して行うのでここのコマンドは実行しても何も起こりません。
しかし、ビルドを行うことによって後続のデプロイなどに必要なファイルが.aws-sam
下に生成されますので、sam build
は実行しておきましょう。
sam build
ビルドが完了したので、デプロイを行う前にローカル環境でテストを行います。
まず、今回は単純なJSONをイベントとして使用します。events/event.json
を以下の内容に修正します。
{
"message": "Hello from Lambda!"
}
次に、ローカル環境でテストを行います。
sam local invoke HelloWorldFunction --event events/event.json
テストが成功すると、以下のようなログが出力されます。
Invoking index.HelloWorld (nodejs20.x)
Local image is up-to-date
Using local image: public.ecr.aws/lambda/nodejs:20-rapid-x86_64.
Mounting /Users/takapi327/Development/scala-app/feral-example/.aws-sam/build/HelloWorldFunction as /var/task:ro,delegated, inside runtime container
START RequestId: 4f6990f4-e447-4f74-95fe-4fe0b28dd47c Version: $LATEST
got message: [Hello from Lambda!]
END RequestId: bb986cc1-6dee-4cc6-9050-7073d386767d
REPORT RequestId: bb986cc1-6dee-4cc6-9050-7073d386767d Init Duration: 0.44 ms Duration: 966.21 ms Billed Duration: 967 ms Memory Size: 128 MB Max Memory Used: 128 MB
{}
テストが成功したら、デプロイを行います。
sam deploy
デプロイ準備が完了すると、CloudFormation スタックの変更確認を行い、問題なければスタックの作成/更新を行います。
Configuring SAM deploy
======================
Looking for config file [samconfig.toml] : Found
Reading default arguments : Success
Setting default arguments for 'sam deploy'
=========================================
Stack Name [feral-example]:
AWS Region [ap-northeast-1]:
#Shows you resources changes to be deployed and require a 'Y' to initiate deploy
Confirm changes before deploy [Y/n]: Y
#SAM needs permission to be able to create roles to connect to the resources in your template
Allow SAM CLI IAM role creation [Y/n]: Y
#Preserves the state of previously provisioned resources when an operation fails
Disable rollback [y/N]: N
Save arguments to configuration file [Y/n]: Y
SAM configuration file [samconfig.toml]:
SAM configuration environment [default]:
Looking for resources needed for deployment:
Creating the required resources...
Successfully created!
Managed S3 bucket: aws-sam-cli-managed-default-samclisourcebucket-cqiynjym6997
A different default S3 bucket can be set in samconfig.toml and auto resolution of buckets turned off by setting resolve_s3=False
Parameter "stack_name=feral-example" in [default.deploy.parameters] is defined as a global parameter [default.global.parameters].
This parameter will be only saved under [default.global.parameters] in /Users/takapi327/Development/scala-app/feral-example/samconfig.toml.
Saved arguments to config file
Running 'sam deploy' for future deployments will use the parameters saved above.
The above parameters can be changed by modifying samconfig.toml
Learn more about samconfig.toml syntax at
https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html
Uploading to feral-example/0d2dbd7484c39ac6c6903b307a7acde4 898080 / 898080 (100.00%)
Deploying with following values
===============================
Stack name : feral-example
Region : ap-northeast-1
Confirm changeset : True
Disable rollback : False
Deployment s3 bucket : aws-sam-cli-managed-default-samclisourcebucket-cqiynjym6997
Capabilities : ["CAPABILITY_IAM"]
Parameter overrides : {}
Signing Profiles : {}
Initiating deployment
=====================
Uploading to feral-example/4ead5ba28965c83603d8f72db1c5216d.template 865 / 865 (100.00%)
Waiting for changeset to be created..
CloudFormation stack changeset
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Operation LogicalResourceId ResourceType Replacement
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Add HelloWorldFunctionRole AWS::IAM::Role N/A
+ Add HelloWorldFunction AWS::Lambda::Function N/A
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Changeset created successfully. arn:aws:cloudformation:ap-northeast-1:573320908463:changeSet/samcli-deploy1723288838/e3b907b1-f311-47fa-b078-9505ebe18f54
Previewing CloudFormation changeset before deployment
======================================================
Deploy this changeset? [y/N]: y
デプロイが完了すると、Lambda関数が作成されます。
テスト実行のイベント JSONを以下のように変更して、テストを実行します。
{
"message": "Hello from Lambda!"
}
テストを実行すると、CloudWatch Logsにログが出力されます。
以上で、FeralとAWS SAMを使ってScalaでLambda関数を作成する方法を紹介しました。
まとめ
今回AWS SAMを初めて触ってみましたが、思ったよりも簡単にLambda関数のデプロイが簡単に行えることがわかりました。今回Scalaを使用して独自にビルドなどを行いましたがTypescriptなどの言語を使用する場合は、AWS SAMのテンプレートを使用するだけでLambda関数を作成することができるためより簡単にLambda関数を作成することができると思います。
また、ローカル環境でのLambda関数のテストも簡単に行うことができました。普段Serverless Frameworkを使用している時はLocalstackを使用しての検証も行なっていたのでここも問題なく動作するのか見てみたいですね。
まだ、単純なLambda関数の作成しか試していませんが、他にも色々なものに対応しているみたいなので本格的にServerless FrameworkからAWS SAMに移行してみるのもいいかもしれません。
直近でサイボウズさんもAWS SAMを採用しているという記事を見かけたので、今後AWS SAMを使っていくことが増えるかもしれませんね。
Discussion