メインコンテンツまでスキップ

「Java」タグの記事が6件件あります

全てのタグを見る

· 約3分
moritalous

re:Invent 2022の期間中にLambdaのコールドスタート問題を解消する大きなアップデートが発表されました。

New – Lambda SnapStart で Lambda 関数を高速化

過去に検証した内容のように、特にJavaの場合のコールドスタートに時間がかかる問題があり、この解消に有効とのことです。

追加費用もなく利用できるとのことなので、常に有効化でよいのではないかと考えていたのですが、いくつか制限がありそうです。

· 約4分
moritalous
お知らせ

過去にQiitaに投稿した内容のアーカイブです。

勉強がてら、話題のLog4Shellに脆弱な環境を作ってみます。 Log4j2のバージョン2.14.0を使っていると簡単に脆弱な環境を作れました。すぐにバージョンアップしないと危険です!

脆弱性に対するSpring公式サイトの解説

https://spring.io/blog/2021/12/10/log4j2-vulnerability-and-spring-boot

題材

公式のGetting Started Guidesの中にあるBuilding a RESTful Web Serviceをベースにします。

image.png

  • GitHubからソースコードの取得

https://github.com/spring-guides/gs-rest-service/archive/main.zip completeフォルダー内に完成したソースが含まれます。

│  build.gradle
│ gradlew
│ gradlew.bat
│ manifest.yml
│ mvnw
│ mvnw.cmd
│ pom.xml
│ settings.gradle

├─.mvn
│ └─wrapper
│ maven-wrapper.jar
│ maven-wrapper.properties

├─gradle
│ └─wrapper
│ gradle-wrapper.jar
│ gradle-wrapper.properties

└─src
├─main
│ └─java
│ └─com
│ └─example
│ └─restservice
│ Greeting.java
│ GreetingController.java
│ RestServiceApplication.java

└─test
└─java
└─com
└─example
└─restservice
GreetingControllerTests.java

ログ出力にLog4J2を使うように修正

Log4j2を使うように変更します。バージョンは脆弱性のある2.14.0です。

https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.logging.log4j

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.2</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>rest-service-complete</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>rest-service-complete</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
+ <log4j2.version>2.14.0</log4j2.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-logging</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

HTTPリクエストのHTTPヘッダーをログ出力するように変更

GreetingController.java
package com.example.restservice;

import java.util.concurrent.atomic.AtomicLong;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;


+ import java.util.Map;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+ import org.springframework.web.bind.annotation.RequestHeader;


@RestController
public class GreetingController {


+ static protected Logger logger = LoggerFactory.getLogger(GreetingController.class);

private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();

@GetMapping("/greeting")
- public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name) {
+ public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name,
+ @RequestHeader Map<String, String> headers) {
+
+ headers.forEach((key, value) -> {
+ logger.info(String.format("Header '%s' = %s", key, value));
+ });

return new Greeting(counter.incrementAndGet(), String.format(template, name));
}
}

なんとこれだけで、脆弱です。

起動します。

./mvnw spring-boot:run

確認

トレンドマイクロさんが、チェックサイトを用意しているのを見つけましたので、これを使って確認してみます。

https://log4j-tester.trendmicro.com/

こんな感じで、サイトから直接リクエストを投げてチェックできます。 今回はlocalhostで起動しているので、右側のcURLスクリプトを生成して、実行します。

log4j-tester.trendmicro.com_(iPad) (1).png

結果はこんな感じで表示されます。

log4j-tester.trendmicro.com_(iPad).png

Log4J2の2.14.0を使っていると脆弱であることがわかります。 pom.xmlのバージョンを2.15.0にあげて再実行すると、脆弱性は検出されませんでした。

怖いですね。すぐにバージョンアップしましょう。

· 約10分
moritalous
お知らせ

過去にQiitaに投稿した内容のアーカイブです。

Quarkus(https://quarkus.io/) とはSUPERSONIC SUBATOMIC JAVAで、

A Kubernetes Native Java stack tailored for OpenJDK HotSpot and GraalVM, crafted from the best of breed Java libraries and standards.

だそうです。 何言ってるかわかりませんがすごそうです。

GraalVMの機能でJavaのプログラムをネイティブに変換することが可能なので、AWS Lambdaのカスタムランタイムと組み合わせることで AWS Lambda上で動作するJavaアプリケーションの特性である コールドスタートが遅い問題 の解決に期待ができます。

公式サイトの手順に従い試してみましたので、手順を残します。

QUARKUS - BUILDING A NATIVE EXECUTABLE https://quarkus.io/guides/building-native-image

QUARKUS - AMAZON LAMBDA https://quarkus.io/guides/amazon-lambda

環境

  • Docker Desktop (Mac) 2.3.0.4
  • VSCode + Visual Studio Code Remote - Containers extension
  • Amazon Linux 2 (on Docker)

雛形アプリのデプロイ手順

手順1. Amazon Linux 2の環境設定

DockerイメージのAmazon Linux 2はtarunzipができないので色々入れておきます。 全部必要かわからないですが、これぐらい入れておきました。

yum install -y sudo shadow-utils procps tar.x86_64 gzip xz unzip witch git python3 tree

手順2. GraalVMのインストール

公式サイトからダウンロードして展開します。

curl -s -L -o /tmp/graalvm-ce-java11-linux-amd64-20.1.0.tar.gz https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-20.1.0/graalvm-ce-java11-linux-amd64-20.1.0.tar.gz
tar zxf /tmp/graalvm-ce-java11-linux-amd64-20.1.0.tar.gz -C /opt/
ln -s /opt/graalvm-ce-java11-20.1.0 /opt/graalvm

JAVA_HOMEをGraalVMにして、MavenでのビルドにGraalVMを使用します。

export GRAALVM_HOME=/opt/graalvm
export JAVA_HOME=$GRAALVM_HOME
export PATH=$GRAALVM_HOME/bin:$PATH

最後にNativeビルド時に必要なnative-imageをインストールします。コマンドはgu(GraalVM Updater)です。

gu install native-image

手順3. Mavenのインストール

Quarkusのビルドで使用するMavenは3.6.2以上のバージョンが必要です。yumでインストールできるバージョンが古かったので、公式サイトからダウンロードしてインストールしました。

curl -s -L -o /tmp/apache-maven-3.6.3-bin.tar.gz https://downloads.apache.org/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz
tar zxf /tmp/apache-maven-3.6.3-bin.tar.gz -C /opt/
ln -s /opt/apache-maven-3.6.3 /opt/apache-maven

mvnのバージョン確認

bash-4.2# mvn --version
Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
Maven home: /opt/apache-maven
Java version: 11.0.7, vendor: GraalVM Community, runtime: /opt/graalvm-ce-java11-20.1.0
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "4.19.76-linuxkit", arch: "amd64", family: "unix"
bash-4.2#

GraalVMのJVMで動作していることがわかります。

手順4. Mavenでプロジェクト作成

mvnコマンドでプロジェクトを生成します。

mvn archetype:generate \
-DarchetypeGroupId=io.quarkus \
-DarchetypeArtifactId=quarkus-amazon-lambda-archetype \
-DarchetypeVersion=1.6.0.Final

しばらくするとプロンプトで質問されますので答えます。 []で囲んだ部分がユーザー入力です。

Define value for property 'groupId': [myGroup]
Define value for property 'artifactId': [myArtifact]
Define value for property 'version' 1.0-SNAPSHOT: : []
Define value for property 'package' myGroup: : [example]
Confirm properties configuration:
groupId: myGroup
artifactId: myArtifact
version: 1.0-SNAPSHOT
package: example
Y: : [Y]

artifactId(myArtifact)でディレクトリが作成され、プロジェクトが生成されます。

作成直後のプロジェクト構成はこんな感じ。

bash-4.2# tree myArtifact/
myArtifact/
├── build.gradle
├── gradle.properties
├── payload.json
├── pom.xml
├── settings.gradle
└── src
├── main
│ ├── java
│ │ └── example
│ │ ├── InputObject.java
│ │ ├── OutputObject.java
│ │ ├── ProcessingService.java
│ │ ├── StreamLambda.java
│ │ ├── TestLambda.java
│ │ └── UnusedLambda.java
│ └── resources
│ └── application.properties
└── test
├── java
│ └── example
│ └── LambdaHandlerTest.java
└── resources
└── application.properties

9 directories, 14 files
bash-4.2#

手順5. Lambdaで起動するHandlerの設定

Lambdaで呼び出されるHandlerはresources/application.propertiesquarkus.lambda.handlerにて設定します。

resources/application.properties
quarkus.lambda.handler=test

上記設定の場合は、以下のtestと名前をつけたクラスが呼び出されます。

main/java/example.TestLambda.java
@Named("test")
public class TestLambda implements RequestHandler<InputObject, OutputObject> {
}

あとは通常のLambdaと同様にコーディングします。 雛形にはtestの他にstreamなども用意されています。

手順6. デプロイパッケージの作成

一旦雛形のままデプロイパッケージを作成してみます。

mvn clean package -Pnative

めちゃくちゃ時間がかかります。10分以上かかると思います。

手順7. デプロイパッケージの内容の確認

無事デプロイパッケージができると、target/function.zipが生成されいます。試しに展開してみると、中身はbootstrapのみでした。

bash-4.2# unzip function.zip 
Archive: function.zip
inflating: bootstrap
bash-4.2#

手順8. Lambdaのデプロイ

targetディレクトリ内には、manage.shというファイルも生成されており、ここからAWS上にデプロイしたりできるようです。 私はカスタムランタイムが初めてだったこともあり、マネジメントコンソールから試してみました。 デプロイパッケージ作成後に、target/sam.native.yamlというファイルも生成されるので、ハンドラー名や環境変数はこちらを参考にしました。

関数を新規作成します。 ランタイムをユーザー独自のブートストラップを提供するとします。

ap-northeast-1.console.aws.amazon.com_lambda_home_region=ap-northeast-1(Laptop with MDPI screen).png

関数を作成したら、プログラムをアップロードします。 関数コードのところのアクションから.zipファイルをアップロードを選び、function.zipを選択します。

ap-northeast-1.console.aws.amazon.com_lambda_home_region=ap-northeast-1(Laptop with MDPI screen) (1).png

ハンドラーはnot.used.in.provided.runtimeとなります。

ap-northeast-1.console.aws.amazon.com_lambda_home_region=ap-northeast-1(Laptop with MDPI screen) (2).png

環境変数にDISABLE_SIGNAL_HANDLERSを追加し値をtrueとします。

ap-northeast-1.console.aws.amazon.com_lambda_home_region=ap-northeast-1(Laptop with MDPI screen) (4).png

ここまでで設定は完了です。以下のJSONをインプットにテスト実行してみましょう。

ap-northeast-1.console.aws.amazon.com_lambda_home_region=ap-northeast-1(Laptop with MDPI screen) (3).png

{
"name": "Bill",
"greeting": "hello"
}

動作結果がこちら。無事動きました。

ap-northeast-1.console.aws.amazon.com_lambda_home_region=ap-northeast-1(Laptop with MDPI screen) (5).png

AWS SDK を追加

AWS SDKを使用するには単純にpom.xmlに追加するだけでなく、いくつか設定が必要です。

手順1. SSL通信の有効化

resources/application.propertiesに以下の内容を追加します。(公式ドキュメント的にはデフォルトで有効って書いてある気もしますが)

resources/application.properties
quarkus.ssl.native=true

手順2. 依存関係の追加

まずは、quarkus-jaxbが必要とのことで、これを追加します。

pom.xml
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jaxb</artifactId>
</dependency>

次にAWS SDKのライブラリーを追加するのですが、

For native image, however the URL Connection client must be preferred over the Apache HTTP Client when using synchronous mode, due to issues in the GraalVM compilation (at present).

翻訳すると ただし、ネイティブイメージの場合、GraalVMコンパイルの問題(現在)のため、同期モードを使用するときは、Apache HTTPクライアントよりもURL接続クライアントを優先する必要があります。 だそうです。そのため、以下のような記述となります。

pom.xml
<properties>
<aws.sdk2.version>2.10.69</aws.sdk2.version>
</properties>

<dependencyManagement>
<dependencies>

<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>bom</artifactId>
<version>${aws.sdk2.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>

</dependencies>
</dependencyManagement>
<dependencies>

<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>url-connection-client</artifactId>
</dependency>

<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>apache-client</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
<exclusions>
<!-- exclude the apache-client and netty client -->
<exclusion>
<groupId>software.amazon.awssdk</groupId>
<artifactId>apache-client</artifactId>
</exclusion>
<exclusion>
<groupId>software.amazon.awssdk</groupId>
<artifactId>netty-nio-client</artifactId>
</exclusion>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>commons-logging-jboss-logging</artifactId>
<version>1.0.0.Final</version>
</dependency>
</dependencies>

手順3. Javaコードの作成

S3にアクセスする例ですが、S3Clientを生成する際に、httpClientにて明示的にUrlConnectionHttpClientを指定します。

S3Client s3 = S3Client.builder()
.region(Region.AP_NORTHEAST_1)

.httpClient(software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient.builder().build())
.build();

手順4. SSL通信に必要な設定

SSL通信を行うためにデプロイパッケージに以下を含める必要があります。

  • カスタムBootstrap
  • libsunec.so
  • cacerts

まず、src/main/zip.native/ディレクトリを作成し、bootstrapを作成します。

zip.native/bootstrap
#!/usr/bin/env bash

./runner -Djava.library.path=./ -Djavax.net.ssl.trustStore=./cacerts

次にlibsunec.socacertsをコピーします。この2つのファイルはGraalVMに含まれています。

cp $GRAALVM_HOME/lib/libsunec.so $PROJECT_DIR/src/main/zip.native/
cp $GRAALVM_HOME/lib/security/cacerts $PROJECT_DIR/src/main/zip.native/

手順5. デプロイパッケージの作成

デプロイパッケージの作成手順は変わりません。

mvn clean package -Pnative

手順6. デプロイパッケージの内容の確認

SSL有効化した状態でデプロイパッケージを作成すると、target/function.zipの中身が変わります。

bash-4.2# unzip function.zip 
Archive: function.zip
inflating: bootstrap
inflating: cacerts
inflating: libsunec.so
inflating: runner
bash-4.2#

事前準備したbootstrap``cacerts``libsunec.soが含まれているのがわかります。

終わりに

通常のJavaランタイムよりも、コールドスタートが早くなる検証結果はこちらで確認ください。

QuarkusがJava Lambdaを救う!?

· 約2分
moritalous
お知らせ

過去にQiitaに投稿した内容のアーカイブです。

すごいですQuarkus!!!!!! JavaのLambdaのコールドスタートがチョッパヤです!!

以下の公式サイトを参考に試しました。

https://quarkus.io/guides/amazon-lambda#tracing-with-aws-xray-and-graalvm

テスト対象

前回のAWS LambdaのJavaは遅い?とほぼ同じですが、以下が異なります。いずれもQuarkusの制限?制約?です。

  • アノテーションが付いてる
  • httpClientがUrlConnectionHttpClient
ソース全体
package example;

import javax.inject.Named;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;

import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.model.PutObjectResponse;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.regions.Region;

@Named("test")
public class TestLambda implements RequestHandler<Object, Object> {

@Override
public Object handleRequest(Object input, Context context) {

String ENV_BUCKET = System.getenv("BUCKET");

S3Client s3 = S3Client.builder()
.region(Region.AP_NORTHEAST_1)
.httpClient(software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient.builder().build())
.build();

PutObjectResponse result = s3.putObject(
PutObjectRequest.builder().bucket(ENV_BUCKET).key("filename.txt").build(),
RequestBody.fromString("contents"));

System.out.println(result);

return "";
}
}

検証結果

回数レイテンシ(ms)処理内容
12700
2250
3305
4319
5187

前回はコールドスタートが6200msだったので、めちゃ早になりました!

検証結果(+Provisioned Concurrency)

こんなチョッパヤなQuarkusさんをProvisionedにしたらどうなるんでしょうね。ワクワクしますね。

回数レイテンシ(ms)処理内容
1417
2198
3206
4270
5147

期待を裏切らない速さ!すごいよ、Quarkus!

参考

https://quarkus.io/guides/amazon-lambda#tracing-with-aws-xray-and-graalvm https://aws.amazon.com/jp/blogs/architecture/field-notes-optimize-your-java-application-for-aws-lambda-with-quarkus/