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

SmallRye Stork入門

分散システムの本質は、サービス間の相互作用にあります。現代のアーキテクチャでは、負荷を共有したり、冗長性によって回復力を高めたりするために、サービスの複数のインスタンスを持つことがよくあります。しかし、サービスの最適なインスタンスをどのように選択すればよいのでしょうか?そこで、 SmallRye Storkがお役に立ちます。Storkが最適なインスタンスを選択してくれます。次の機能を提供します:

  • 拡張可能なサービスディスカバリーメカニズム

  • ConsulとKubernetesの組込サポート

  • カスタマイズ可能なクライアントロードバランシング戦略

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

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

とりうるステータスの完全なリストについては、 FAQの項目 を参照してください。

前提条件

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

  • 約15分

  • IDE

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

  • Apache Maven 3.9.6

  • 動作するコンテナランタイム(Docker, Podman)

  • 使用したい場合は、 Quarkus CLI

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

アーキテクチャ

このガイドでは、次のような構成のアプリケーションを構築します。

  • 9000番ポートで公開されるシンプルなブルーサービス

  • 9001番ポートで公開されるシンプルなレッドサービス

  • ブルーまたはレッドのサービスを呼び出すRESTクライアント(選択はStorkに委ねられている)

  • RESTクライアントを使用し、サービスを呼び出すRESTエンドポイント

  • ブルーとレッドのサービスは Consulに登録されています。

Architecture of the application

簡略化のため、(Consulを除く)すべてのコンポーネントは、同じQuarkusアプリケーション内で実行されます。もちろん、実際には各コンポーネントはそれぞれのプロセスで実行されます。

ソリューション

次のセクションの指示に従って、段階的にアプリケーションを作成することをお勧めします。しかし、完成した例をすぐに見ることもできます。

Gitレポジトリをクローンするか git clone https://github.com/quarkusio/quarkus-quickstarts.gitアーカイブ をダウンロードします。

ソリューションは stork-quickstart ディレクトリ にあります。

ディスカバリーとセレクション

その前に、ディスカバリーとセレクションについて説明する必要があります。

  • サービスディスカバリーとは、サービスインスタンスを探すプロセスのことです。サービス・ディスカバリーでは、サービス・インスタンスのリストが作成されます。このリストは、(リクエストにマッチするサービスがない場合)空の可能性もあれば、複数のサービス・インスタンスを含む可能性もあります。

  • サービスセレクションはロードバランスとも呼ばれ、ディスカバリープロセスから返されたリストの中から最適なインスタンスを選択します。その結果、1つのサービスインスタンスになるか、適切なインスタンスが見つからない場合は例外となります。

Storkはディスカバリーとセレクションの両方を扱います。しかし、サービスとの通信は処理せず、サービスインスタンスを提供するだけです。Quarkusの様々な統合機能は、そのサービスインスタンスからサービスの場所を抽出します。

Discovery and Selection of services

プロジェクトのブートストラップ

quarkus-rest-client-reactiveおよびquarkus-resteasy-reactiveエクステンションをインポートして、好きなアプローチでQuarkusプロジェクトを作成します。

コマンドラインインタフェース
quarkus create app org.acme:stork-quickstart \
    --extension='quarkus-rest-client-reactive,quarkus-resteasy-reactive' \
    --no-code
cd stork-quickstart

Gradleプロジェクトを作成するには、 --gradle または --gradle-kotlin-dsl オプションを追加します。

Quarkus CLIのインストールと使用方法の詳細については、 Quarkus CLI ガイドを参照してください。

Maven
mvn io.quarkus.platform:quarkus-maven-plugin:3.8.3:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=stork-quickstart \
    -Dextensions='quarkus-rest-client-reactive,quarkus-resteasy-reactive' \
    -DnoCode
cd stork-quickstart

Gradleプロジェクトを作成するには、 -DbuildTool=gradle または -DbuildTool=gradle-kotlin-dsl オプションを追加します。

Windowsユーザーの場合:

  • cmdを使用する場合、(バックスラッシュ \ を使用せず、すべてを同じ行に書かないでください)。

  • Powershellを使用する場合は、 -D パラメータを二重引用符で囲んでください。例: "-DprojectArtifactId=stork-quickstart"

生成されたプロジェクトに、以下の依存関係を追加します:

pom.xml
<dependency>
  <groupId>io.smallrye.stork</groupId>
  <artifactId>stork-service-discovery-consul</artifactId>
</dependency>
<dependency>
  <groupId>io.smallrye.reactive</groupId>
  <artifactId>smallrye-mutiny-vertx-consul-client</artifactId>
</dependency>
build.gradle
implementation("io.smallrye.stork:stork-service-discovery-consul")
implementation("io.smallrye.reactive:smallrye-mutiny-vertx-consul-client")

stork-service-discovery-consul はConsulのサービスディスカバリーの実装を提供しています。 smallrye-mutiny-vertx-consul-client はConsulのクライアントで、Consulにサービスを登録するために使用します。

ブルーサービスとレッドサービス

一番最初から始めましょう:私たちが発見(ディスカバリー)し、選択(セレクション)し、呼び出すサービスです。

以下の内容で src/main/java/org/acme/services/BlueService.java を作成してください。

package org.acme.services;

import io.quarkus.runtime.StartupEvent;
import io.vertx.mutiny.core.Vertx;
import org.eclipse.microprofile.config.inject.ConfigProperty;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;

@ApplicationScoped
public class BlueService {

    @ConfigProperty(name = "blue-service-port", defaultValue = "9000") int port;

    /**
     * Start an HTTP server for the blue service.
     *
     * Note: this method is called on a worker thread, and so it is allowed to block.
     */
    public void init(@Observes StartupEvent ev, Vertx vertx) {
        vertx.createHttpServer()
                .requestHandler(req -> req.response().endAndForget("Hello from Blue!"))
                .listenAndAwait(port);
    }
}

アプリケーションの起動時に、新しいHTTPサーバー(Vert.xを使用)を作成し、シンプルなサービスを実装します。各HTTPリクエストに対して、"Hello from Blue!"をボディとするレスポンスを送信します。

同じ要領で、以下の内容の src/main/java/org/acme/services/RedService.java を作成します。

package org.acme.services;

import io.quarkus.runtime.StartupEvent;
import io.vertx.mutiny.core.Vertx;
import org.eclipse.microprofile.config.inject.ConfigProperty;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;

@ApplicationScoped
public class RedService {
    @ConfigProperty(name = "red-service-port", defaultValue = "9001") int port;

    /**
     * Start an HTTP server for the red service.
     *
     * Note: this method is called on a worker thread, and so it is allowed to block.
     */
    public void init(@Observes StartupEvent ev, Vertx vertx) {
        vertx.createHttpServer()
                .requestHandler(req -> req.response().endAndForget("Hello from Red!"))
                .listenAndAwait(port);
    }

}

今回は"Hello from Red!"と書かれています。

Consulでのサービス登録

サービスの実装が完了したので、Consulに登録する必要があります。

StorkはConsulに限らず、他のサービスディスカバリーメカニズムとも統合されています。

src/main/java/org/acme/services/Registration.java ファイルを以下の内容で作成します。

package org.acme.services;

import io.quarkus.runtime.StartupEvent;
import io.vertx.ext.consul.ServiceOptions;
import io.vertx.mutiny.ext.consul.ConsulClient;
import io.vertx.ext.consul.ConsulClientOptions;
import io.vertx.mutiny.core.Vertx;
import org.eclipse.microprofile.config.inject.ConfigProperty;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;

@ApplicationScoped
public class Registration {

    @ConfigProperty(name = "consul.host") String host;
    @ConfigProperty(name = "consul.port") int port;

    @ConfigProperty(name = "red-service-port", defaultValue = "9000") int red;
    @ConfigProperty(name = "blue-service-port", defaultValue = "9001") int blue;

    /**
     * Register our two services in Consul.
     *
     * Note: this method is called on a worker thread, and so it is allowed to block.
     */
    public void init(@Observes StartupEvent ev, Vertx vertx) {
        ConsulClient client = ConsulClient.create(vertx, new ConsulClientOptions().setHost(host).setPort(port));

        client.registerServiceAndAwait(
                new ServiceOptions().setPort(red).setAddress("localhost").setName("my-service").setId("red"));
        client.registerServiceAndAwait(
                new ServiceOptions().setPort(blue).setAddress("localhost").setName("my-service").setId("blue"));
    }
}

アプリケーションが起動すると、Vert.x Consul Clientを使ってConsulに接続し、2つのインスタンスを登録します。どちらの登録も同じ名前( my-service )を使用していますが、同じ サービス の2つのインスタンスであることを示すために異なるIDを使用しています。

RESTクライアントインターフェースとフロントエンドAPI

ここまでは、Storkを使わずに、これから発見するサービス、選択するサービス、呼び出すサービスの骨組を構築してきました。

Reactive REST Clientを使ってサービスを呼び出すことにします。以下の内容で src/main/java/org/acme/MyService.java ファイルを作成します。

package org.acme;

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

/**
 * The REST Client interface.
 *
 * Notice the `baseUri`. It uses `stork://` as URL scheme indicating that the called service uses Stork to locate and
 * select the service instance. The `my-service` part is the service name. This is used to configure Stork discovery
 * and selection in the `application.properties` file.
 */
@RegisterRestClient(baseUri = "stork://my-service")
public interface MyService {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    String get();
}

これは、1つのメソッドを含む単純なRESTクライアントインターフェイスです。ただし、 baseUri 属性に注意してください。 stork:// で始まっています。これは、RESTクライアントがサービスインスタンスのディスカバリーとセレクションをStorkに委ねることを指示しています。また、URLの my-service にもご注目ください。これは、アプリケーションの設定で使用するサービス名です。

RESTクライアントの使用方法を変更するものではありません。以下の内容で src/main/java/org/acme/FrontendApi.java ファイルを作成します。

package org.acme;

import org.eclipse.microprofile.rest.client.inject.RestClient;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

/**
 * A frontend API using our REST Client (which uses Stork to locate and select the service instance on each call).
 */
@Path("/api")
public class FrontendApi {

    @RestClient MyService service;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String invoke() {
        return service.get();
    }

}

通常通り、RESTクライアントを注入して使用します。

Stork Filter

RESTクライアントで設定された baseUri は、 StorkClientRequestFilter クラスで処理されます。これは Jakarta REST フィルタ です。メッセージに関連するメタデータを処理する必要がある場合:HTTPヘッダー、クエリパラメーター、メディアタイプ、その他のメタデータなど、メッセージに関連するメタデータを処理する必要がある場合は、別のフィルターを実装して必要なものを構成することができます。それでは、サービスにロギング機能を追加するために、カスタムフィルターを実装してみましょう。 CustomLoggingFilter を作成し、@Provider アノテーションを付けます:

package org.acme;

import io.vertx.core.http.HttpServerRequest;
import org.jboss.logging.Logger;
import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientRequestContext;
import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientRequestFilter;

import jakarta.ws.rs.ext.Provider;

@Provider
public class CustomLoggingFilter implements ResteasyReactiveClientRequestFilter {

    private static final Logger LOG = Logger.getLogger(CustomLoggingFilter.class);

    @Override
    public void filter(ResteasyReactiveClientRequestContext requestContext) {
        LOG.infof("Resolved address by Stork: %s",requestContext.getUri().toString());
    }
}

フィルタの実行順序は、 プロパティ で定義されます。 CustomLoggingFilter はデフォルト値を使用しているので、ユーザーレベルの優先順位、 StorkClientRequestFilter はセキュリティ認証フィルタの優先順位を使用していることに注意してください。これは、 StorkClientRequestFilterCustomLoggingFilter の前に実行されることを意味します。この挙動を変更するには、 @Priority アノテーションを使用します。

Stork設定

システムはほぼ完成です。あとはStorkと Registration Beanを設定するだけです。

src/main/resources/application.properties に以下を追加します:

consul.host=localhost
consul.port=8500

quarkus.stork.my-service.service-discovery.type=consul
quarkus.stork.my-service.service-discovery.consul-host=localhost
quarkus.stork.my-service.service-discovery.consul-port=8500
quarkus.stork.my-service.load-balancer.type=round-robin

最初の2行は、 Registration Beanが使用するConsulのロケーションを提供します。

stork.my-service.service-discovery は、 my-service サービスの検索に使用するサービスディスカバリーのタイプを示しています。 consul stork.my-service.service-discovery.consul-hoststork.my-service.service-discovery.consul-port は Consul へのアクセスを設定します。最後に、 stork.my-service.load-balancer は、サービスのセレクションを設定します。今回のケースでは、 round-robin を使用します。

アプリケーションの実行

できました!では、実際に動くかどうか見てみましょう。

まず、Consulを起動します:

docker run --rm --name consul -p 8500:8500 -p 8501:8501 consul:1.7 agent -dev -ui -client=0.0.0.0 -bind=0.0.0.0 --https-port=8501

Consulを別の方法で起動する場合は、アプリケーションの設定を編集することを忘れないでください。

次に、アプリケーションをパッケージ化します:

コマンドラインインタフェース
quarkus build
Maven
./mvnw install
Gradle
./gradlew build

そして実行します:

> java -jar target/quarkus-app/quarkus-run.jar

別の端末で、次を実行します。

> curl http://localhost:8080/api
...
> curl http://localhost:8080/api
...
> curl http://localhost:8080/api
...

応答は Hello from Red!Hello from Blue! の間で交互に行われます。

このアプリケーションをネイティブ実行可能ファイルにコンパイルして、起動することができます。

コマンドラインインタフェース
quarkus build --native
Maven
./mvnw install -Dnative
Gradle
./gradlew build -Dquarkus.package.type=native

次のように起動します:

> ./target/stork-getting-started-1.0.0-SNAPSHOT-runner

さらに詳しく

このガイドでは、SmallRye Storkを使ってサービスを発見し、選択する方法を紹介しました。Storkについては、以下のページで詳しく紹介しています:

関連コンテンツ