♦️

FeralとAWS SAMを使ってScalaでLambda関数を作成する

2024/09/02に公開

はじめに

2023年10月26日に、Serverless Framework v4が発表され、新しくライセンスが導入されることが発表されました。v4自体は2024年5月21日からベータ版としてリリースされ、利用できるようになっていました。

https://www.serverless.com/blog/serverless-framework-v4-a-new-model

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のプロジェクトです。

https://typelevel.org/
https://github.com/typelevel/feral

Feralの特徴や使い方については、以下の記事に詳しく記載されておりますので、参考にしてください。

https://blog.3qe.us/entry/2024/08/05/000939

AWS SAMとは

AWS SAM(Serverless Application Model)は、AWSのサーバーレスアプリケーションを構築、テスト、デプロイするためのフレームワークです。AWS SAM CLIを使うことで、ローカルでLambda関数をテストすることができます。

AWS SAMに関しては、以下の記事に詳しく記載されておりますので、参考にしてください。

https://zenn.dev/k_tana/articles/2023-08_how-to-use-of-aws-sam

構築

以下の手順でFeralとAWS SAMを使ってScalaでLambda関数を作成します。

  1. AWS SAMを使用したプロジェクトの作成
  2. Feralを使用したLambda関数の作成
  3. AWS SAMを使用したデプロイ

AWS SAMを使用したプロジェクトの作成

まずはAWS SAM CLIをインストールします。

https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/install-sam-cli.html

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の構文チェックを行えます。

https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-validate.html

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に移行してみるのもいいかもしれません。

https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/reference-sam-connector.html

直近でサイボウズさんもAWS SAMを採用しているという記事を見かけたので、今後AWS SAMを使っていくことが増えるかもしれませんね。

https://zenn.dev/cybozu_ept/articles/migrate-from-serverless-framework

GitHubで編集を提案
nextbeat Tech Blog

Discussion