The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.

RESTEasy Reactive、Undertow、または Reactive Routes を使用した Amazon Lambda

Quarkusでは、AWS Gateway HTTP API または AWS Gateway REST API のいずれかを使用して、お気に入りの Java HTTP フレームワークを Amazon Lambdaとしてデプロイできます。つまり、RESTEasy (JAX-RS)、Undertow (サーブレット)、Reactive Route、Funqy HTTP、その他の Quarkus の HTTP フレームワークで書かれたマイクロサービスを、AWS Lambda としてデプロイすることができます。

Lambda を純粋な Java jar としてデプロイすることもできますし、プロジェクトをネイティブイメージにコンパイルしてデプロイすることで、より少ないメモリーフットプリントと起動時間を実現することもできます。また、統合により、 AmazonのSAM framework で使用できるSAMデプロイメントファイルも生成されます。

Quarkusでは、ゲートウェイAPIごとに異なるエクステンションを用意しています。HTTP Gateway APIは、 quarkus-amazon-lambda-http のエクステンションで実装されています。REST Gateway APIは、 quarkus-amazon-lambda-rest のエクステンションで実装されています。どのGateway製品を使用すべきか迷っている場合は、Amazonが決断を助ける為に 素晴らしいガイドを提供しています。

Quarkus AWS Lambda HTTP/REST エクステンションは、大概の Quarkus エクステンションと同様、ライブコーディングをサポートしています。

この技術は、previewと考えられています。

preview では、下位互換性やエコシステムでの存在は保証されていません。具体的な改善には設定や API の変更が必要になるかもしれませんが、 stable になるための計画は現在進行中です。フィードバックは メーリングリストGitHub の課題管理 で受け付けています。

For a full list of possible statuses, check our FAQ entry.

前提条件

このガイドを完成させるには、以下が必要です:

  • ざっと 30 minutes

  • IDE

  • JDK 11+ がインストールされ、 JAVA_HOME が適切に設定されていること

  • Apache Maven 3.8.1+

  • 使用したい場合、 Quarkus CLI

  • ネイティブ実行可能ファイルをビルドしたい場合、MandrelまたはGraalVM(あるいはネイティブなコンテナビルドを使用する場合はDocker)をインストールし、 適切に設定していること

  • An Amazon AWS account

  • AWS SAM CLI

はじめに

This guide walks you through generating an example Java project via a Maven archetype. Later on, it walks through the structure of the project so you can adapt any existing projects you have to use Amazon Lambda.

AWS ビットのインストール

AWSのすべてのツールをインストールすることは、おそらくこのガイドでは最も難しいことです。AWS CLIをインストールするためのすべての手順に従っていることを確認してください。

デプロイ用のMavenプロジェクトを作成する

Maven Archetype を使用して Quarkus AWS Lambda Maven プロジェクトを作成します。

AWS Gateway HTTP APIを使用する場合は、このスクリプトでプロジェクトを生成します。

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

AWS Gateway REST APIを使用する場合は、このスクリプトでプロジェクトを生成します。

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

ビルドとデプロイ

プロジェクトをビルドします。

CLI
quarkus build
Maven
./mvnw clean package

これでコードがコンパイルされ、生成されたプロジェクト内に含まれるユニットテストが実行されます。ユニットテストは他のJavaプロジェクトと同じで、Amazonで実行する必要はありません。Quarkus dev モードもこのエクステンションで利用できます。

ネイティブ実行可能ファイルをビルドする場合は、GraalVM が正しくインストールされていることを確認し、ビルドに native プロパティーを追加してください。

CLI
quarkus build --native
Maven
./mvnw package -Dnative
Linux 以外のシステムでビルドしている場合は、Amazon Lambda が Linux バイナリーを必要とするため、Docker ビルドを使用するように Quarkus に指示するプロパティーも渡す必要があります。これを行うには、ビルドコマンドに -Dquarkus.native.container-build=true を渡します。ただし、これには Docker をローカルにインストールする必要があります。
CLI
quarkus build --native --no-tests -Dquarkus.native.container-build=true
# The --no-tests flag is required only on Windows and macOS.
Maven
./mvnw package -Dnative -Dquarkus.native.container-build=true

ビルド時に追加生成されるファイル

After you run the build, there are a few extra files generated by the Quarkus lambda extension you are using. These files are in the build directory: target/ for Maven, build/ for Gradle.

  • function.zip - Lambda デプロイファイル

  • sam.jvm.yaml - SAM CLI デプロイメントスクリプト

  • sam.native.yaml - ネイティブ用の SAM CLI デプロイメントスクリプト

AWS Lambda 環境のローカルでのライブコーディングとシミュレーション

開発モードおよびテストモードでは、Quarkus は模擬 AWS Lambda イベントサーバーを起動し、対応する API Gateway イベントタイプに HTTP リクエストを変換し、処理のために基盤となる Quarkus HTTP Lambda 環境に送信します。これにより、Docker や SAM CLI などのツールを必要とせずに、AWS Lambda 環境を可能な限りローカルでシミュレートします。

Quarkus Dev Mode を使用する場合は、REST エンドポイントをテストする場合と同じように、http://localhost:8080 で HTTP リクエストを呼び出すだけです。このリクエストは Mock Event Server にヒットし、Quarkus Lambda Poll ループによって消費される API Gateway json メッセージに変換されます。

テストのために、Quarkus はポート 8081 で別の Mock Event サーバーを起動します。Rest Assured のデフォルトポートは Quarkus によって自動的に 8081 に設定されるため、これを設定する必要はありません。

テストでより複雑な API Gateway イベントをシミュレートする場合は、生の API Gateway json イベントを使用して http://localhost:8080/_lambda_ (テストモードのポート 8081) に対して手動で HTTP POST を実行します。これらのイベントは、処理のために Quarkus Lambda ポーリングループに直接配置されます。その例を次に示します。

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.equalTo;

import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent;

import io.quarkus.test.junit.QuarkusTest;

@QuarkusTest
public class AmazonLambdaSimpleTestCase {
    @Test
    public void testJaxrsCognitoJWTSecurityContext() throws Exception {
        APIGatewayV2HTTPEvent request = request("/security/username");
        request.getRequestContext().setAuthorizer(new APIGatewayV2HTTPEvent.RequestContext.Authorizer());
        request.getRequestContext().getAuthorizer().setJwt(new APIGatewayV2HTTPEvent.RequestContext.Authorizer.JWT());
        request.getRequestContext().getAuthorizer().getJwt().setClaims(new HashMap<>());
        request.getRequestContext().getAuthorizer().getJwt().getClaims().put("cognito:username", "Bill");

        given()
                .contentType("application/json")
                .accept("application/json")
                .body(request)
                .when()
                .post("/_lambda_")
                .then()
                .statusCode(200)
                .body("body", equalTo("Bill"));
    }

上記の例は、HTTP リクエストを含む Cognito プリンシパルを HTTP Lambda に送信することをシミュレートしています。

AWS HTTP API の生のイベントをハンドコードする場合、AWS Lambda ライブラリーのリクエストイベントタイプは com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent で、レスポンスイベントタイプは com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPResponse です。これは、quarkus-amazon-lambda-http エクステンションおよび AWS HTTP API に対応します。

AWS REST API の生のイベントをハンドコードする場合、Quarkus には独自の実装 (io.quarkus.amazon.lambda.http.model.AwsProxyRequest および io.quarkus.amazon.lambda.http.model.AwsProxyResponse) があります。これは、quarkus-amazon-lambda-rest エクステンションおよび AWS REST API に対応します。

模擬イベントサーバーは、@NativeImageTest テストおよび @QuarkusIntegrationTest テストでも起動するため、ネイティブバイナリーでも機能します。これはすべて、Docker のオーバーヘッドなしで、SAM CLI ローカルテストと同様の機能を提供します。

最後に、ポート 8080 またはポート 8081 がコンピューターで使用できない場合は、application.properties を使用して開発モードとテストモードのポートを変更できます。

quarkus.lambda.mock-event-server.dev-port=8082
quarkus.lambda.mock-event-server.test-port=8083

ポート値がゼロの場合、ポートはランダムに割り当てられます。

SAM CLI を使用して Amazon Lambda のデプロイをシミュレートする

AWS SAM CLI を使用すると、ラップトップ上で Lambda をシミュレートした環境でローカルに実行することができます。このためには Docker をインストールする必要があります。Maven プロジェクトをビルドした後、以下のコマンドを実行してください

sam local start-api --template target/sam.jvm.yaml

これで Amazon の Lambda のデプロイ環境を模した Docker コンテナーが起動します。環境が起動したら、ブラウザで以下を開くとサンプルの Lambda を実行できます。

コンソールには、ラムダからの起動メッセージが表示されます。この特定のデプロイメントでは、JVM を起動し、純粋な Java として Lambda をロードします。

AWS にデプロイする

sam deploy -t target/sam.jvm.yaml -g

すべての質問に答えると、 Lambda がデプロイされ、API Gatewayへの必要なフックが設定されます。すべてが正常にデプロイされると、マイクロサービスのルート URL がコンソールに出力されます。このような感じです。

Key                 LambdaHttpApi
Description         URL for application
Value               https://234asdf234as.execute-api.us-east-1.amazonaws.com/

Value 属性は Lambda のルート URL です。これをブラウザにコピーして、最後に hello を追加します。

Responses for binary types will be automatically encoded with base64. This is different from the behavior using quarkus:dev which will return the raw bytes. Amazon’s API has additional restrictions requiring the base64 encoding. In general, client code will automatically handle this encoding but in certain custom situations, you should be aware you may need to manually manage that encoding.

ネイティブ実行可能ファイルのデプロイ

ネイティブ実行ファイルをデプロイするには、GraalVM を使用してビルドする必要があります。

CLI
quarkus build --native --no-tests -Dquarkus.native.container-build=true
# The --no-tests flag is required only on Windows and macOS.
Maven
./mvnw package -Dnative -Dquarkus.native.container-build=true

その後、ローカルで sam local を使って実行ファイルをテストすることができます。

sam local start-api --template target/sam.native.yaml

AWS Lambda へのデプロイ

sam deploy -t target/sam.native.yaml -g

POM を調べる

There is nothing special about the POM other than the inclusion of the quarkus-amazon-lambda-http extension (if you are deploying an AWS Gateway HTTP API) or the quarkus-amazon-lambda-rest extension (if you are deploying an AWS Gateway REST API). These extensions automatically generate everything you might need for your lambda deployment.

また、少なくとも生成された Maven アーキタイプ pom.xml では、quarkus-resteasy-reactivequarkus-reactive-routes、および quarkus-undertow の依存関係はすべて任意です。使用する HTTP フレームワーク (JAX-RS、Reactive Routes、Servlet) を選択し、他の依存関係を削除してデプロイメントを縮小します。

sam.yaml を調べる

sam.yaml の構文はこのドキュメントの範囲を超えています。しかし、カスタムの sam.yaml デプロイメントファイルを作成しようとしている場合、幾つかの強調しなければならないことがあります。

最初に注意することは、純粋なJavaラムダデプロイメントには特定のハンドラークラスが必要であるということです。 Lambdaハンドラー名は変更しないでください。

     Properties:
        Handler: io.quarkus.amazon.lambda.runtime.QuarkusStreamHandler::handleRequest
        Runtime: java11

このハンドラは、ラムダランタイムと、使用しているQuarkus HTTPフレームワーク(JAX-RS、Servletなど)との間のブリッジとなります。

ネイティブを利用する場合は、ネイティブのGraalVMデプロイメント用に設定する必要のある環境変数があります。 sam.native.yaml を見ると、次のことがわかります。

        Environment:
          Variables:
            DISABLE_SIGNAL_HANDLERS: true

この環境変数は、QuarkusとAmazon Lambdaカスタムランタイム環境との間のいくつかの非互換性を解決します。

最後に、AWS Gateway RESTAPIへのデプロイに固有のことが一つあります。そのAPIは、設定でどのメディアタイプがバイナリーであるかを明示的に指定しない限り、HTTPレスポンスボディはテキストであることを想定しています。より簡単にするために、QuarkusエクステンションはすべてのHTTP応答メッセージのバイナリー(ベース64)エンコーディングを強制します。 sam.yaml ファイルでは、すべてのメディアタイプがバイナリーであると仮定するようにAPI Gatewayを設定する必要があります。

  Globals:
    Api:
      EndpointConfiguration: REGIONAL
      BinaryMediaTypes:
        - "*/*"

注入可能なAWSコンテキスト変数

RESTEasy Reactive と JAX-RS を使用している場合は、JAX-RS @Context アノテーションを使用するか、CDI @Inject アノテーションを使用して、さまざまな AWS Context 変数を JAX-RS リソースクラスに挿入できます。

AWS HTTP APIでは、AWS変数 com.amazonaws.services.lambda.runtime.Context および com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent を注入することができます。以下はその例です。

import javax.ws.rs.core.Context;
import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent;


@Path("/myresource")
public class MyResource {
    @GET
    public String ctx(@Context com.amazonaws.services.lambda.runtime.Context ctx) { }

    @GET
    public String event(@Context APIGatewayV2HTTPEvent event) { }

    @GET
    public String requestContext(@Context APIGatewayV2HTTPEvent.RequestContext req) { }


}

AWS REST APIでは、AWS変数 com.amazonaws.services.lambda.runtime.Contextio.quarkus.amazon.lambda.http.model.AwsProxyRequestContext を注入することができます。以下はその例です。

import javax.ws.rs.core.Context;
import io.quarkus.amazon.lambda.http.model.AwsProxyRequestContext;
import io.quarkus.amazon.lambda.http.model.AwsProxyRequest;


@Path("/myresource")
public class MyResource {
    @GET
    public String ctx(@Context com.amazonaws.services.lambda.runtime.Context ctx) { }

    @GET
    public String reqContext(@Context AwsProxyRequestContext req) { }

    @GET
    public String req(@Context AwsProxyRequest req) { }

}

AWS XRay および GraalVM を使用したトレース

If you are building native images, and want to use AWS X-Ray Tracing with your lambda you will need to include quarkus-amazon-lambda-xray as a dependency in your pom. The AWS X-Ray library is not fully compatible with GraalVM, so we had to do some integration work to make this work.

セキュリティー統合

API Gateway で HTTP リクエストを呼び出すと、ゲートウェイはその HTTP リクエストを JSON イベントドキュメントに変換し、Quarkus Lambda に転送します。Quarkus Lambda はこの json を解析し、Quarkus がサポートする任意の HTTP フレームワーク (JAX-RS、サーブレット、Reactive Routes) で使用できる HTTP リクエストの内部表現に変換します。

API Gateway supports many ways to securely invoke on your HTTP endpoints that are backed by Lambda and Quarkus. If you enable it, Quarkus will automatically parse relevant parts of the event json document and look for security based metadata and register a java.security.Principal internally that can be looked up in JAX-RS by injecting a javax.ws.rs.core.SecurityContext, via HttpServletRequest.getUserPrincipal() in servlet, and RouteContext.user() in Reactive Routes. If you want more security information, the Principal object can be typecast to a class that will give you more information.

このセキュリティー機能を有効にするには、これを application.properties ファイルに追加します。

quarkus.lambda-http.enable-security=true

マッピング方法は次のとおりです。

Table 1. HTTP quarkus-amazon-lambda-http
認証タイプ プリンシパルクラス プリンシパル名の Json パス

Cognito JWT

io.quarkus.amazon.lambda.http.CognitoPrincipal

requestContext.authorizer.jwt.claims.cognito:username

IAM

io.quarkus.amazon.lambda.http.IAMPrincipal

requestContext.authorizer.iam.userId

カスタム Lambda

io.quarkus.amazon.lambda.http.CustomPrincipal

requestContext.authorizer.lambda.principalId

Table 2. REST quarkus-amazon-lambda-rest
認証タイプ プリンシパルクラス プリンシパル名の Json パス

Cognito

io.quarkus.amazon.lambda.http.CognitoPrincipal

requestContext.authorizer.claims.cognito:username

IAM

io.quarkus.amazon.lambda.http.IAMPrincipal

requestContext.identity.user

カスタム Lambda

io.quarkus.amazon.lambda.http.CustomPrincipal

requestContext.authorizer.principalId

カスタムセキュリティーの統合

The default support for AWS security only maps the principal name to Quarkus security APIs and does nothing to map claims or roles or permissions. You have full control on how security metadata in the lambda HTTP event is mapped to Quarkus security APIs using implementations of the io.quarkus.amazon.lambda.http.LambdaIdentityProvider interface. By implementing this interface, you can do things like define role mappings for your principal or publish additional attributes provided by IAM or Cognito or your Custom Lambda security integration.

HTTP quarkus-amazon-lambda-http
package io.quarkus.amazon.lambda.http;

/**
 * Helper interface that removes some boilerplate for creating
 * an IdentityProvider that processes APIGatewayV2HTTPEvent
 */
public interface LambdaIdentityProvider extends IdentityProvider<LambdaAuthenticationRequest> {
    @Override
    default public Class<LambdaAuthenticationRequest> getRequestType() {
        return LambdaAuthenticationRequest.class;
    }

    @Override
    default Uni<SecurityIdentity> authenticate(LambdaAuthenticationRequest request, AuthenticationRequestContext context) {
        APIGatewayV2HTTPEvent event = request.getEvent();
        SecurityIdentity identity = authenticate(event);
        if (identity == null) {
            return Uni.createFrom().optional(Optional.empty());
        }
        return Uni.createFrom().item(identity);
    }

    /**
     * You must override this method unless you directly override
     * IdentityProvider.authenticate
     *
     * @param event
     * @return
     */
    default SecurityIdentity authenticate(APIGatewayV2HTTPEvent event) {
        throw new IllegalStateException("You must override this method or IdentityProvider.authenticate");
    }
}

HTTP の場合、オーバーライドする重要なメソッドは LambdaIdentityProvider.authenticate(APIGatewayV2HTTPEvent event) です。ここで、APIGatewayV2HTTPEvent からセキュリティーデータをマッピングする方法に基づいて SecurityIdentity を割り当てます。

REST quarkus-amazon-lambda-rest
package io.quarkus.amazon.lambda.http;

import java.util.Optional;

import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent;

import io.quarkus.amazon.lambda.http.model.AwsProxyRequest;
import io.quarkus.security.identity.AuthenticationRequestContext;
import io.quarkus.security.identity.IdentityProvider;
import io.quarkus.security.identity.SecurityIdentity;
import io.smallrye.mutiny.Uni;

/**
 * Helper interface that removes some boilerplate for creating
 * an IdentityProvider that processes APIGatewayV2HTTPEvent
 */
public interface LambdaIdentityProvider extends IdentityProvider<LambdaAuthenticationRequest> {
...

    /**
     * You must override this method unless you directly override
     * IdentityProvider.authenticate
     *
     * @param event
     * @return
     */
    default SecurityIdentity authenticate(AwsProxyRequest event) {
        throw new IllegalStateException("You must override this method or IdentityProvider.authenticate");
    }
}

REST の場合、オーバーライドする重要なメソッドは LambdaIdentityProvider.authenticate(AwsProxyRequest event) です。ここで、AwsProxyRequest からセキュリティーデータをマップする方法に基づいて SecurityIdentity を割り当てます。

実装するプロバイダーは CDI Bean である必要があります。次に例を示します。

package org.acme;

import java.security.Principal;

import javax.enterprise.context.ApplicationScoped;

import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent;

import io.quarkus.amazon.lambda.http.LambdaIdentityProvider;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.runtime.QuarkusPrincipal;
import io.quarkus.security.runtime.QuarkusSecurityIdentity;

@ApplicationScoped
public class CustomSecurityProvider implements LambdaIdentityProvider {
    @Override
    public SecurityIdentity authenticate(APIGatewayV2HTTPEvent event) {
        if (event.getHeaders() == null || !event.getHeaders().containsKey("x-user"))
            return null;
        Principal principal = new QuarkusPrincipal(event.getHeaders().get("x-user"));
        QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder();
        builder.setPrincipal(principal);
        return builder.build();
    }
}

次も同じ例ですが、AWS Gateway REST API を使用しています。

package org.acme;

import java.security.Principal;

import javax.enterprise.context.ApplicationScoped;

import io.quarkus.amazon.lambda.http.model.AwsProxyRequest;

import io.quarkus.amazon.lambda.http.LambdaIdentityProvider;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.runtime.QuarkusPrincipal;
import io.quarkus.security.runtime.QuarkusSecurityIdentity;

@ApplicationScoped
public class CustomSecurityProvider implements LambdaIdentityProvider {
    @Override
    public SecurityIdentity authenticate(AwsProxyRequest event) {
        if (event.getMultiValueHeaders() == null || !event.getMultiValueHeaders().containsKey("x-user"))
            return null;
        Principal principal = new QuarkusPrincipal(event.getMultiValueHeaders().getFirst("x-user"));
        QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder();
        builder.setPrincipal(principal);
        return builder.build();
    }
}

Quarkus は、この実装を自動的に検出し、前述のデフォルトの実装の代わりに使用する必要があります。

単純な SAM ローカルプリンシパル

sam local を使用してアプリケーションをテストしている場合は、QUARKUS_AWS_LAMBDA_FORCE_USER_NAME 環境変数を設定することで、アプリケーションの実行時に使用するプリンシパル名をハードコーディングできます。