これは、なにをしたくて書いたもの?
以前に、Spring Cloud Functionを単体で試してみました。
Spring Cloud Functionを試してみる - CLOVER🍀
今回は、Spring Cloud FunctionのAWS Adapterを使ってアプリケーションを作成し、LocalStack上にAWS Lambda関数
としてデプロイしてみたいと思います。
Spring Cloud Function AWS Adapter
Spring Cloud Function AWS Adapterは、Spring Cloud Functionで作成したアプリケーションをAWS Lambdaで
実行できる形式に変換するものです。
ドキュメントは、こちら。
Spring Cloud Function Reference Documentation / AWS Adapter
ソースコードは、Spring Cloud Functionのリポジトリの中に含まれています。
GitHub - spring-cloud/spring-cloud-function
サンプルの中に、AWS用のものも含まれています。
https://github.com/spring-cloud/spring-cloud-function/tree/v3.1.4/spring-cloud-function-samples
Spring Cloud Function自体のドキュメントは、こちら。
今回は、Spring Cloud Function AWS Adapterを使って、簡単なAWS Lambda関数を作成してLocalStackに
デプロイしてみます。
環境
今回の環境は、こちら。
LocalStack。
$ localstack --version 0.12.19.1
起動。
$ LAMBDA_EXECUTOR=docker-reuse localstack start
$ awslocal --version aws-cli/2.3.0 Python/3.8.8 Linux/5.4.0-89-generic exe/x86_64.ubuntu.20 prompt/off
アプリケーションを作成するJava環境。
$ java --version openjdk 11.0.11 2021-04-20 OpenJDK Runtime Environment (build 11.0.11+9-Ubuntu-0ubuntu2.20.04) OpenJDK 64-Bit Server VM (build 11.0.11+9-Ubuntu-0ubuntu2.20.04, mixed mode, sharing) $ mvn --version Apache Maven 3.8.3 (ff8e977a158738155dc465c6a97ffaf31982d739) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 11.0.11, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64 Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "5.4.0-89-generic", arch: "amd64", family: "unix"
プロジェクトを作成する
まずは、Spring Cloud Functionを使ったプロジェクトを作成します。
$ curl -s https://start.spring.io/starter.tgz \ -d dependencies=cloud-function,webflux \ -d groupId=org.littlewings \ -d artifactId=hello-spring-cloud-function-lambda \ -d packageName=org.littlewings.spring.cloudfuntion.aws \ -d bootVersion=2.5.6 \ -d javaVersion=11 \ -d type=maven-project \ -d baseDir=hello-spring-cloud-function-lambda | tar zxvf - $ cd hello-spring-cloud-function-lambda
dependencies
には、cloud-function
とwebflux
を加えています。
この時点では、Spring Cloud Function AWS Adapterは追加できません。後でdependency
を変更します。
生成されたpom.xml
。
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.6</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>org.littlewings</groupId> <artifactId>hello-spring-cloud-function-lambda</artifactId> <version>0.0.1-SNAPSHOT</version> <name>demo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>11</java.version> <spring-cloud.version>2020.0.4</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-function-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
Spring Cloud Functionとしては、3.1.4が使われます。
あらかじめ置いてあるソースコードは、使わないので削除します。
$ rm src/main/java/org/littlewings/spring/cloudfuntion/aws/DemoApplication.java src/test/java/org/littlewings/spring/cloudfuntion/aws/DemoApplicationTests.java
プロジェクトにSpring Cloud Function AWS Adapterを追加する
では、ドキュメントに沿ってSpring Cloud Function AWS Adapterを使えるようにしていきます。
Spring Cloud Function Reference Documentation / AWS Adapter / Getting Started
こちらのサンプルコードを合わせて確認しつつ。
AWS LambdaのSDKにはハンドラーのインターフェースがあり、Spring Cloud Function AWS Adapterはこの実装を
提供します。
Spring Cloud Function Reference Documentation / AWS Adapter / AWS Request Handlers
RequestStreamHandler
インターフェースの実装ですね。
Java の AWS Lambda 関数ハンドラー / ハンドラーのインターフェイス
こちらのクラスです。
ルーティングに関しては、今回はパス。
Spring Cloud Function Reference Documentation / AWS Adapter / AWS Function Routing
続いて、ビルドの設定へ。
Spring Cloud Function Reference Documentation / AWS Adapter / Build file setup
Spring Cloud Function AWS Adapterを使うには、spring-cloud-function-adapter-aws
を依存関係に追加する必要があります。
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-function-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-function-adapter-aws</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-test</artifactId> <scope>test</scope> </dependency> </dependencies>
Spring CloudのBOMがインポートされているので、特にバージョンを指定せずに追加可能です。
サンプルを見ていると、aws-lambda-java-core
やaws-lambda-java-events
も追加していますが、今回は特に使わないので
追加していません。
なお、spring-cloud-function-adapter-aws
を依存関係に追加しても、AWS SDKは推移的には引き込まれません。
Spring Cloud Function AWS Adapterに定義されている、AWS SDKに関する依存関係はprovided
だったり
optional
だったりするからですね。
もともとこうだったのを
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
ドキュメントに習ってspring-boot-thin-layout
を追加します。
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <dependencies> <dependency> <groupId>org.springframework.boot.experimental</groupId> <artifactId>spring-boot-thin-layout</artifactId> <version>1.0.27.RELEASE</version> </dependency> </dependencies> </plugin> </plugins> </build>
これで、JARを作成する時にSpring Cloud Function WebやStream Adapterを削除した、Thin JARが作成できるように
なります。このThin JARは、そのまま実行できます。
あと、追加でMaven Shade Pluginの設定も行います。
Spring Cloud Function Reference Documentation / AWS Adapter / Notes on JAR Layout
これは、Spring BootのAutoConfigureを使う場合に必要なようなのですが、どのみち使うことになりそうなので
設定しておくことにしました。
Maven Shade Pluginはもともと含まれていないので、定義自体を追加。
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.2.4</version> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.5.6</version> </dependency> </dependencies> <configuration> <createDependencyReducedPom>false</createDependencyReducedPom> <shadedArtifactAttached>true</shadedArtifactAttached> <shadedClassifierName>aws</shadedClassifierName> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.handlers</resource> </transformer> <transformer implementation="org.springframework.boot.maven.PropertiesMergingResourceTransformer"> <resource>META-INF/spring.factories</resource> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.schemas</resource> </transformer> </transformers> </configuration> </plugin>
これで、依存関係込みで、classfierがaws
のJARが作成されるようになります。
サンプルに習うと、このくらいでもいいのですが。
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.2.4</version> <configuration> <createDependencyReducedPom>false</createDependencyReducedPom> <shadedArtifactAttached>true</shadedArtifactAttached> <shadedClassifierName>aws</shadedClassifierName> </configuration> </plugin>
今回の例においては、こちらの定義でも動作します。
結果、Spring Boot Maven Pluginと合わせて、こんな感じに。
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <dependencies> <dependency> <groupId>org.springframework.boot.experimental</groupId> <artifactId>spring-boot-thin-layout</artifactId> <version>1.0.27.RELEASE</version> </dependency> </dependencies> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.2.4</version> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.5.6</version> </dependency> </dependencies> <configuration> <createDependencyReducedPom>false</createDependencyReducedPom> <shadedArtifactAttached>true</shadedArtifactAttached> <shadedClassifierName>aws</shadedClassifierName> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.handlers</resource> </transformer> <transformer implementation="org.springframework.boot.maven.PropertiesMergingResourceTransformer"> <resource>META-INF/spring.factories</resource> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.schemas</resource> </transformer> </transformers> </configuration> </plugin> </plugins> </build>
全体的な定義については、サンプルも参考に。
これで、ビルド設定は完了です。
アプリケーションを作成して、LocalStackにデプロイする
AWS Lambda関数として動作するアプリケーションを作成します。
今回は、こんな感じで簡単に作成。
src/main/java/org/littlewings/spring/cloudfuntion/aws/App.java
package org.littlewings.spring.cloudfuntion.aws; import java.util.Map; import java.util.function.Function; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import reactor.core.publisher.Mono; @SpringBootApplication public class App { public static void main(String... args) { SpringApplication.run(App.class, args); } @Bean public Function<Mono<Map<String, Object>>, Mono<Map<String, Object>>> upper() { return request -> request.map(r -> Map.of("result", r.get("message").toString().toUpperCase())); } }
message
というキーで送られてきたメッセージを、大文字に変換してresult
というキーにマッピングして返す
関数です。
これで、パッケージング。
$ mvn package
こんな結果になります。
$ ll -h target 合計 21M drwxrwxr-x 8 xxxxx xxxxx 4.0K 10月 24 20:13 ./ drwxr-xr-x 6 xxxxx xxxxx 4.0K 10月 24 20:13 ../ drwxrwxr-x 3 xxxxx xxxxx 4.0K 10月 24 20:13 classes/ drwxrwxr-x 3 xxxxx xxxxx 4.0K 10月 24 20:13 generated-sources/ drwxrwxr-x 3 xxxxx xxxxx 4.0K 10月 24 20:13 generated-test-sources/ -rw-rw-r-- 1 xxxxx xxxxx 21M 10月 24 20:13 hello-spring-cloud-function-lambda-0.0.1-SNAPSHOT-aws.jar -rw-rw-r-- 1 xxxxx xxxxx 12K 10月 24 20:13 hello-spring-cloud-function-lambda-0.0.1-SNAPSHOT.jar -rw-rw-r-- 1 xxxxx xxxxx 4.1K 10月 24 20:13 hello-spring-cloud-function-lambda-0.0.1-SNAPSHOT.jar.original drwxrwxr-x 2 xxxxx xxxxx 4.0K 10月 24 20:13 maven-archiver/ drwxrwxr-x 3 xxxxx xxxxx 4.0K 10月 24 20:13 maven-status/ drwxrwxr-x 2 xxxxx xxxxx 4.0K 10月 24 20:13 test-classes/
LocalStackに(AWSに)デプロイするのは、hello-spring-cloud-function-lambda-0.0.1-SNAPSHOT-aws.jar
です。
hello-spring-cloud-function-lambda-0.0.1-SNAPSHOT.jar
はThin JARですね。
では、こちらを参照しつつLocalStackにデプロイしてみます。
Spring Cloud Function Reference Documentation / AWS Adapter / Upload
ハンドラークラスは、org.springframework.cloud.function.adapter.aws.SpringBootStreamHandler
になります。
$ awslocal lambda create-function \ --function-name hello_spring_cloud_function_lambda \ --zip-file fileb://target/hello-spring-cloud-function-lambda-0.0.1-SNAPSHOT-aws.jar \ --handler org.springframework.cloud.function.adapter.aws.SpringBootStreamHandler \ --runtime java11 \ --role test-role\ --timeout 30 \ --memory-size 1024
動作確認。
$ awslocal lambda invoke \ --function-name hello_spring_cloud_function_lambda \ --payload '{"message": "Hello World"}' \ --cli-binary-format raw-in-base64-out \ result.json
OKですね。
$ cat result.json "HELLO WORLD"
ところで、サンプルを見るとmain
メソッドが空になっています。
実際、AWS Lambdaにデプロイされた時にmain
メソッドが呼び出されることはありません。
ただ、Spring Bootアプリケーションとしてパッケージングするのには必要なので、AWS Lambda上で動かすだけなら
このように空のmain
メソッドを定義しておけばOKです。
一方でmain
メソッドを定義しておくと、java -jar
でアプリケーションをローカルで起動できるようになります。
※ mvn spring-boot:run
でもOK
$ java -jar target/hello-spring-cloud-function-lambda-0.0.1-SNAPSHOT.jar ## または $ java -jar target/hello-spring-cloud-function-lambda-0.0.1-SNAPSHOT-aws.jar
ふつうにSpring Bootアプリケーションが起動します。
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.5.6) 2021-10-24 20:23:53.896 INFO 40360 --- [ main] o.l.spring.cloudfuntion.aws.App : Starting App v0.0.1-SNAPSHOT using Java 11.0.11 on ikaruga with PID 40360 (/path/hello-spring-cloud-function-lambda/target/hello-spring-cloud-function-lambda-0.0.1-SNAPSHOT.jar started by username in /path/to/hello-spring-cloud-function-lambda) 2021-10-24 20:23:53.900 INFO 40360 --- [ main] o.l.spring.cloudfuntion.aws.App : No active profile set, falling back to default profiles: default 2021-10-24 20:23:55.059 INFO 40360 --- [ main] o.s.c.f.web.flux.FunctionHandlerMapping : FunctionCatalog: org.springframework.cloud.function.context.catalog.BeanFactoryAwareFunctionRegistry@2c7c0e36 2021-10-24 20:23:55.379 INFO 40360 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port 8080 2021-10-24 20:23:55.393 INFO 40360 --- [ main] o.l.spring.cloudfuntion.aws.App : Started App in 1.946 seconds (JVM running for 4.19)
確認。
$ curl -i -XPOST -H 'Content-Type: application/json' localhost:8080/upper -d '{"message": "Hello World"}' HTTP/1.1 200 OK user-agent: curl/7.68.0 uri: http://localhost:8080/upper Content-Type: application/json Content-Length: 24 {"result":"HELLO WORLD"}
こんな感じですね。
まとめ
Spring Cloud Function AWS Adapterを使って、LocalStackにAWS Lambda関数をデプロイしてみました。
ドキュメントを見ただけではちょっと使い方がよくわからなかったのですが、サンプルを見つつ試していくと、
なんとか雰囲気がわかりました。
今回は、こんなところで。