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

「lambda」タグの記事が14件件あります

全てのタグを見る

· 約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/

· 約8分
moritalous
お知らせ

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

AWS Lambdaで動作するJavaは初回が遅いですが、速くする方法がないか調べました。 末尾にある参考サイトの内容にたどり着いて、実際に試してみたのでその記録です。

レイテンシ情報はX-Rayにて取得しました。

テスト対象

S3にファイルをPUTするだけのものです

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

検証1 普通に実行

まずは普通に試してみます。

ソース全体
package helloworld;

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

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

public class TestTarget0429 implements RequestHandler<Object, Object> {

public Object handleRequest(final Object input, final Context context) {
String ENV_BUCKET = System.getenv("BUCKET");

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

System.out.println(result);

return null;
}
}
回数レイテンシ(ms)処理内容
16200
2422
3217
4210
5315

1回目だけ遅い、いわゆるコールドスタートが遅い状態ですね。 S3に1ファイル作成するだけで6秒は遅いですよねぇ。

検証2 Provisioned Concurrencyを有効化

では昨年末に登場したProvisioned Concurrencyを使うとどうでしょう。 https://aws.amazon.com/jp/blogs/news/new-provisioned-concurrency-for-lambda-functions/

ソースコードは検証1と同じものです。

回数レイテンシ(ms)処理内容
15500
2266
3274
4402
5304

初回が遅いままじゃないか。。 同時実行1をプロビジョンドしただけでも月$14.42かかるのに、あんまりじゃないか。。。

なので、以降はProvisioned Concurrencyを無効にして検証を続けます

検証3 処理の分離(Provisioned Concurrencyなし)

初回に遅い原因を探るため、Lambda初回起動時と2回目起動時で処理を分けてみました。

staticなcount変数を作って、初回呼び出し時のみ速攻returnしてみます。

        if (count == 1) {
count++;
return null;
}
ソース全体
package helloworld;

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

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

public class TestTarget0429 implements RequestHandler<Object, Object> {

private static int count = 1;

public Object handleRequest(final Object input, final Context context) {
if (count == 1) {
count++;
return null;
}

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

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

System.out.println(result);

return null;
}
}

結果

回数レイテンシ処理内容
1625msInitialization処理のみ
25600msS3 PUT(1回目)
3393msS3 PUT(2回目)
4401msS3 PUT(3回目)
5311msS3 PUT(4回目)

Initialization処理が遅いわけじゃないことがわかりました。 S3 PUT(初回)に時間がかかっているようです。

検証4 初期化処理をstaticにする(Provisioned Concurrencyなし)

S3Clientを作る部分をstatic化してみます。

private static String ENV_BUCKET = System.getenv("BUCKET");
private static S3Client s3 = S3Client.builder().region(Region.AP_NORTHEAST_1).build();
ソース全体
package helloworld;

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

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

public class TestTarget0429 implements RequestHandler<Object, Object> {

private static int count = 1;

private static String ENV_BUCKET = System.getenv("BUCKET");
private static S3Client s3 = S3Client.builder().region(Region.AP_NORTHEAST_1).build();

public Object handleRequest(final Object input, final Context context) {
if (count == 1) {
count++;
return null;
}

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

System.out.println(result);

return null;
}
}

結果

回数レイテンシ処理内容
12400msInitialization処理 と S3Clientインスタンスの生成
22200msS3 PUT(1回目)
343msS3 PUT(2回目)
446msS3 PUT(3回目)
578msS3 PUT(4回目)

お!少し1回目の処理時間がかかるようになって、2回目が少し早くなりましたね。 3回目以降も早くなってますがこれもなにか影響があるのでしょうか?

検証5 staticイニシャライザで1回やっちゃう(Provisioned Concurrencyなし)

staticで処理をすれば早くなることがわかりました。 一旦staticイニシャライザでダミーファイルを作成してみます。

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

System.out.println(result);
}
ソース全体
package helloworld;

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

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

public class TestTarget0429 implements RequestHandler<Object, Object> {

private static int count = 1;

private static String ENV_BUCKET = System.getenv("BUCKET");
private static S3Client s3 = S3Client.builder().region(Region.AP_NORTHEAST_1).build();

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

System.out.println(result);
}

public Object handleRequest(final Object input, final Context context) {
if (count == 1) {
count++;
return null;
}

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

System.out.println(result);

return null;
}
}

結果

回数レイテンシ処理内容
14000msInitialization処理 と staticメソッドによるS3 PUT(1回目)ダミーファイル
242msS3 PUT(2回目)
3125msS3 PUT(3回目)
442msS3 PUT(4回目)
544msS3 PUT(5回目)

めでたく2回目以降が速くなりましたよ~!

検証6 検証5+Provisioned Concurrency

検証5で早くなったので、Provisioned Concurrencyも組み合わせたら、1回目から速くなるのか?!

ソースは検証5と同じものです。

回数レイテンシ処理内容
180msInitialization処理
2370msS3 PUT(2回目)※Provisionedの際にstaticイニシャライザで1回実行済みのため
343msS3 PUT(3回目)
472msS3 PUT(4回目)
584msS3 PUT(5回目)

やりましたよ! 期待してたのはこれです。

最終結果

最終形はこうなりました。

  • staticメソッドでダミーファイル作成を一回やっちゃう
  • Provisioned Concurrency有効
ソース全体
package helloworld;

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

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

public class TestTarget0429 implements RequestHandler<Object, Object> {

private static String ENV_BUCKET = System.getenv("BUCKET");
private static S3Client s3 = S3Client.builder().region(Region.AP_NORTHEAST_1).build();

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

System.out.println(result);
}

public Object handleRequest(final Object input, final Context context) {
PutObjectResponse result = s3.putObject(
PutObjectRequest.builder().bucket(ENV_BUCKET).key("filename.txt").build(),
RequestBody.fromString("contents"));

System.out.println(result);

return null;
}
}
回数レイテンシ処理内容
1552msS3 PUT(2回目)※Provisionedの際にstaticイニシャライザで1回実行済みのため
2118msS3 PUT(3回目)
344msS3 PUT(4回目)
486msS3 PUT(5回目)
5146msS3 PUT(6回目)

めでたし、めでたし。

考察

どうも、Javaのクラスローダーは初めてクラスが呼ばれたタイミングでクラスを読み込むようで、クラスの初期ロードに時間がかかるらしいです。 なので、一回読み込んじゃって、クラスをロード済みにしてしまえば次から速いということのようです。

呼ばれたタイミングじゃなくて、はじめに全部クラスをロードしてくれたらいいんですが、そんなことはできないのですかねぇ。

参考サイトはこちらです。

クラスメソッドさんのブログ https://dev.classmethod.jp/articles/report-best-practive-for-java-on-lambda/

re:Invent 2019でのセッション資料 https://d1.awsstatic.com/events/reinvent/2019/REPEAT_1_Best_practices_for_AWS_Lambda_and_Java_SVS403-R1.pdf https://youtu.be/ddg1u5HLwg8

他に見つけたブログ https://pattern-match.com/blog/2020/03/14/springboot2-and-aws-lambda-provisioned-concurrency/

· 約2分
moritalous
お知らせ

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

※追記 Greengrass Nodejs SDKのGitHubに書いてますね。。 https://github.com/aws/aws-greengrass-core-sdk-js

Rename the file to nodejs8.10 Make sure the file is not a symlink.

シンボリックリンクもだめらしいです。


GreengrassにNode.js(v8.10)のLambdaをデプロイすると、エラーになります。

Deployment xxxxx of type NewDeployment for group xxxxx failed error: worker with xxxxx failed to initialize

ログを確認すると、nodejs8.10というバイナリがないよと言っているようです。

/greengrass/ggc/var/log/system/runtime.log
[ERROR]-runtime execution error: unable to start lambda container.  {"errorString": "failed to run container sandbox: container_linux.go:344: starting container process caused \"exec: \\\"nodejs8.10\\\": executable file not found in $PATH\""}

無理やり作ってやると、うまくいくようになりました。

sudo ln -s /usr/bin/nodejs /usr/bin/nodejs8.10

Node.jsのセットアップの問題?

環境

OS: Ubuntu 18.04(VirtualBox on Mac) Greengrass: 1.9.4 Node.js:10.17.0

Node.jsのインストールは公式サイト( https://github.com/nodesource/distributions/blob/master/README.md )に従いました。(2019/11/24時点で最新のv10.17.0がインストールされました)

curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash -
sudo apt-get install -y nodejs

Raspberry Piでは続きがあります

Raspberry Piでも同様の問題が起きますが、上記手順でデプロイは成功するようになります。ただし、Lambdaの実行時にエラーとなって実行されません。 こちらは解決法がわかりません。。。ヘルプ。。。

Raspberry Pi Zero W OS:Raspbian Buster Greengrass: 1.9.3 Node.js:10.16.3

Lambdaが呼び出されると、以下のエラー

/greengrass/ggc/var/log/user/[リージョン]/[アカウントID]/[Lambda名].log
[ERROR]-standard_init_linux.go:207: exec user process caused "operation not permitted"

· 約8分
moritalous
お知らせ

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

CloudWatch Logsに出力されたLambdaのログをS3に保管する方法です。

CloudWatch Logsのサブスクリプションという機能でログをKinesis Data Firehoseに送信します。 そのままS3に出力すると複数のログが1行に並ぶ形になってしまいますが、Kinesis Data Firehoseのデータ変換機能で改行を加えることで解決します。

こんな感じです。

名称未設定ファイル.png

S3バケットの作成

ログを出力するバケットを作成します。今回はlog-backup-xxxxxとします。

データ変換用Lambdaの作成

データ変換用LambdaはAWSが設計図を用意してくれているので簡単に作成できます。 マネジメントコンソールでLambda関数を作成します。

項目選択説明
作成方法設計図の使用
設計図kinesis-firehose-cloudwatch-logs-processorNode.js版。Python2.7版のkinesis-firehose-cloudwatch-logs-processor-pythonもあります
関数名kinesis-firehose-cloudwatch-logs-processor任意
実行ロール基本的なLambdaアクセス権限で新しいロールを作成する

ソース中にコメントに以下の記載があり、どのような形式のログが渡ってくるかがわかります。

/*
For processing data sent to Firehose by Cloudwatch Logs subscription filters.

Cloudwatch Logs sends to Firehose records that look like this:

{
"messageType": "DATA_MESSAGE",
"owner": "123456789012",
"logGroup": "log_group_name",
"logStream": "log_stream_name",
"subscriptionFilters": [
"subscription_filter_name"
],
"logEvents": [
{
"id": "01234567890123456789012345678901234567890123456789012345",
"timestamp": 1510109208016,
"message": "log message 1"
},
{
"id": "01234567890123456789012345678901234567890123456789012345",
"timestamp": 1510109208017,
"message": "log message 2"
}
...
]
}

The data is additionally compressed with GZIP.

The code below will:

1) Gunzip the data
2) Parse the json
3) Set the result to ProcessingFailed for any record whose messageType is not DATA_MESSAGE, thus redirecting them to the
processing error output. Such records do not contain any log events. You can modify the code to set the result to
Dropped instead to get rid of these records completely.
4) For records whose messageType is DATA_MESSAGE, extract the individual log events from the logEvents field, and pass
each one to the transformLogEvent method. You can modify the transformLogEvent method to perform custom
transformations on the log events.
5) Concatenate the result from (4) together and set the result as the data of the record returned to Firehose. Note that
this step will not add any delimiters. Delimiters should be appended by the logic within the transformLogEvent
method.
6) Any additional records which exceed 6MB will be re-ingested back into Firehose.
*/

処理手順も色々ありそうですが、そのあたりはすでに実装済みなので、変換する形式を変更したい場合はtransformLogEvent関数を修正するだけです。 設計図での実装は、付加情報は全部除外して、ログのメッセージに改行を付与して出力しています。

/**
* logEvent has this format:
*
* {
* "id": "01234567890123456789012345678901234567890123456789012345",
* "timestamp": 1510109208016,
* "message": "log message 1"
* }
*
* The default implementation below just extracts the message and appends a newline to it.
*
* The result must be returned in a Promise.
*/
function transformLogEvent(logEvent) {
return Promise.resolve(`${logEvent.message}\n`);
}

例えば、logEventの内容をすべて出力し改行を付与する場合は、以下の様になると思います。

function transformLogEvent(logEvent) {
return Promise.resolve(`${JSON.stringify(logEvent)}\n`);
}

また、Amazon Kinesis Data Firehose CloudWatch Logs Processorというテストイベントも用意されているので、マネジメントコンソールで簡単にテストができます。

最後に、Lambdaのタイムアウトを1分以上にしておきましょう。

Kinesis Data Firehoseのストリームを作成

マネジメントコンソールで作成します。

項目選択説明
Delivery stream nameCloudWatchLogs-to-S3任意
Choose a source
sourceDirect PUT or other sources
---次のページ---
Transform source records with AWS Lambda
Record transformationEnabled
Lambda functionkinesis-firehose-cloudwatch-logs-processor作成したLambda
Lambda function version$LATEST
Convert record format
Record format conversionDisabled
---次のページ---
Select a destination
DestinationAmazon S3
S3 destination
S3 destinationlog-backup-xxxxx作成したバケット
S3 prefixlogs/
S3 error prefixerror/
S3 backup
Source record S3 backupDisabled
S3 buffer conditions
Buffer size5MBデフォルト値
Buffer interval300secondsデフォルト値
S3 compression and encryption
S3 compressionDisabled
S3 encryptionDisabled
Error logging
Error loggingEnabled
Permissions
IAM roleCreate new or choose新しくIAMロールを作成するといい感じにアクセス権限を付与してくれます

CloudWatch Logsに付与するIAMロールを作成

このあとの手順で作成するCloudWatch Logsのサブスクリプションフィルターに、Firehoseにアクセスする権限が必要なので、IAMロールを作成します。 ただ、マネジメントコンソール上からは、CloudWatch Logsに付与するIAMロールはそのままでは作れないので、以下の手順で作成します。

まずはマネジメントコンソールでIAMロールを作成します。

項目選択説明
信頼されたエンティティの種類を選択AWSサービス
このロールを使用するサービスを選択EC2
Attach アクセス権限ポリシーなし次の手順で付与します
ロール名CWLtoKinesisFirehoseRole
ロールの説明削除説明がEC2になってるので削除しておく

次にIAMポリシーを作成します。

項目選択説明
サービス1
サービスFirehose
アクションすべてのFirehoseアクション
リソースarn:aws:firehose:ap-northeast-1:[アカウントID]:deliverystream/CloudWatchLogs-to-S3作成したFirehoseの配信ストリームのARN
サービス2
サービスIAM
アクションPassRole
リソースarn:aws:iam::[アカウントID]:role/CWLtoKinesisFirehoseRole作成したIAMロールのARN
---次のページ---

| ポリシーの確認 | 名前 | Permissions-Policy-For-CWL |

再度IAMロールの編集画面に戻り、CWLtoKinesisFirehoseRoleロールにPermissions-Policy-For-CWLポリシーをアタッチします。

最後にCWLtoKinesisFirehoseRoleロールの信頼関係タブの信頼関係の編集をクリック。"Service": "ec2.amazonaws.com"の部分を"Service": "logs.ap-northeast-1.amazonaws.com"に変更し、保存します。

CloudWatch Logsサブスクリプションフィルターの作成

マネジメントコンソールからは作成できないようですので、CLIで作成します。

パラメータ(キー)パラメータ(値)説明
--log-group-name/aws/lambda/xxxxxxサブスクリプションフィルターを追加したいロググループ名
--filter-nameLogs-to-Firehose任意
--filter-pattern""フィルターせず、全ての場合
--destination-arnarn:aws:firehose:ap-northeast-1:[アカウントID]:deliverystream/CloudWatchLogs-to-S3作成したFirehoseの配信ストリームのARN
--role-arnarn:aws:iam::[アカウントID]:role/CWLtoKinesisFirehoseRole作成したIAMロールのARN
aws logs put-subscription-filter --log-group-name [ロググループ名] --filter-name Logs-to-Firehose --filter-pattern "" --destination-arn arn:aws:firehose:ap-northeast-1:[アカウントID]:deliverystream/CloudWatchLogs-to-S3 --role-arn arn:aws:iam::[アカウントID]:role/CWLtoKinesisFirehoseRole 

完成

これで無事にS3にCloudWatchLogsがS3に保存されます。 GlueやAthenaでもクエリーがかけられそうです。

参考

https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/logs/SubscriptionFilters.html#FirehoseExample https://docs.aws.amazon.com/ja_jp/firehose/latest/dev/data-transformation.html https://docs.aws.amazon.com/cli/latest/reference/logs/put-subscription-filter.html