#chiroito ’s blog

Java を中心とした趣味の技術について

Infinispan と Spring Session を使ったセッションレプリケーション

環境

  • SpringBoot 2.3.0
  • Infinispan 10.1.7

サンプルソースはこちら

GitHub - chiroito/spring-session-sample

手順

Java EE がインフラの設定だけで実現できることと比べ、SpringBoot はアプリ側を少し書き換えて設定しないと行けません。

手順は以下のとおりです。

  1. セッションを使ったアプリを書く
  2. プロジェクトに依存関係を追加
  3. アプリケーションにアノテーションを追加
  4. アプリに Infinispan へ接続する設定を追加

0. Infinispan を起動

今回は 2 ノードの Infinispan を起動します。 ダウンロードした Infinispan の zip を 2 回別の名前で解凍します。 普通に実行すると使用しているポートが被ってしまいます。それを回避するため引数に-Dinfinispan.socket.binding.port-offsetを付与します。それぞれのディレクトリで次の様に実行します。

1 つ目

bin\server.bat -Dinfinispan.node.name=nodeA

2 つ目

bin\server.bat -Dinfinispan.node.name=nodeB -Dinfinispan.socket.binding.port-offset=100

1. セッションを使ったアプリを書く

アプリはセッションを使ったものを普通に書くだけです。Infinispan にセッションをレプリケーションするかどうかで特に違いはありません。以下はセッション情報となるカウンターとそれを使ったエンドポイントのアプリです。

Counter.java

import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.SessionScope;

import java.io.Serializable;

@SessionScope
@Component
public class Counter implements Serializable {

    private static final long serialVersionUID = 1L;

    private int value;

    public int getValue() {
        return value;
    }

    public void countUp(){
        this.value++;
    }
}

CountEndpoint.java

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

@RestController
@RequestMapping("/session")
public class CountEndpoint {

    private final Counter counter;

    public CountEndpoint(Counter counter) {
        this.counter = counter;
    }

    @GetMapping("/count")
    public String count(){

        counter.countUp();
        return "counter=" + counter.getValue();
    }

    @GetMapping("/read")
    public String read(){

        return "counter=" + counter.getValue();
    }
}

2. プロジェクトに依存関係を追加

プロジェクトの依存関係に Spring Session の実装への依存関係を追加しないといけません。今回はセッションの格納先に Infinispan を使用するので Infinispan への依存関係を追加します。

<dependency>
    <groupId>org.infinispan</groupId>
    <artifactId>infinispan-bom</artifactId>
    <version>10.1.7.Final</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>
<dependency>
    <groupId>org.infinispan</groupId>
    <artifactId>infinispan-spring-boot-starter</artifactId>
    <version>2.2.4.Final</version>
</dependency>
<dependency>
    <groupId>org.infinispan</groupId>
    <artifactId>infinispan-spring-boot-starter-remote</artifactId>
    <version>2.2.4.Final</version>
</dependency>

3. アプリケーションにアノテーションを追加

キャッシュを使えるようにするのと Infinispan のクラスタにセッションを入れられるようにするため、SpringBootのアプリケーションには 2 つのアノテーションを追加します。追加するアノテーションは@EnableCaching@EnableInfinispanRemoteHttpSessionです。

@EnableCaching
@EnableInfinispanRemoteHttpSession
@SpringBootApplication
public class InfinispanSessionApplication {

    public static void main(String[] args) {
        SpringApplication.run(InfinispanSessionApplication.class, args);
    }
}

実行

最後に実行しましょう。SpringBoot を通常どおり実行するだけです。

HotRod ストアを使ったWildflyのセッションレプリケーション

これまでは、リモートストアを使用して HTTP セッションを Infinispan やその商用製品である Red Hat Data Grid へオフロードしていました。ですがその方法は Wildfly 14 (JBoss EAP 7.2)から古くなったようです。新しい方法は同バージョンから導入されたelytron サブシステムと統合する HotRod プロトコルをベースとするカスタムの最適化されたキャッシュストアを使います。

24.3.10. リモートストアを使用した HTTP セッションの Red Hat Data Grid への外部化

環境

  • Wildfly 19.1
  • Infinispan 9.4.18
  • Java EE 8

今回構築する構成は以下の様な構成です。1 つの Wildfly と 2 つの Infinispan を構築します。

f:id:chiroito:20200519172347p:plain

サンプルのソースコードはこちらにあります。

GitHub - chiroito/wildfly-session-sample

手順

Infinispan の設定は何も必要ありません。Wildfly の設定をしてアプリがそれを使えるように設定をすればセッションレプリケーションできます。

手順は以下のとおりです。

  1. 接続先となる Infinispan を定義する (下図の青)
  2. remote-cache-container と remote-cluster を定義する(下図の黄色)
  3. invalidation-cache を定義する (下図の赤)
  4. アプリでキャッシュを使うように設定する
  5. アプリをデプロイする

f:id:chiroito:20200521160717p:plain

0. Infinispan を起動する。

今回は 2 ノードの Infinispan を起動します。 ダウンロードした Infinispan の zip を 2 回別の名前で解凍します。 普通に実行すると使用しているポートが被ってしまいます。それを回避するため引数に-Djboss.socket.binding.port-offsetを付与します。それぞれのディレクトリで次の様に実行します。

1つ目

bin\standalone.bat -c clustered.xml -Djboss.node.name=nodeA

2つ目

bin\standalone.bat -c clustered.xml -Djboss.node.name=nodeB -Djboss.socket.binding.port-offset=100

1. 接続先となる Infinispan を定義する

これからの作業には jboss-cliを使用します。これはwlidflyのインストール先のbin\jboss-cli.batです。jboss-cli.bat -cで起動したら以下を実行します。

batch
/socket-binding-group=standard-sockets/remote-destination-outbound-socket-binding=infinispan-server1:add(host=127.0.0.1, port=11222)
/socket-binding-group=standard-sockets/remote-destination-outbound-socket-binding=infinispan-server2:add(host=127.0.0.1, port=11322)
run-batch

2. remote-cache-container と remote-cluster を定義する

こちらも同様に Jboss-cli で以下を実行します。

batch
/subsystem=infinispan/remote-cache-container=foo:add(default-remote-cluster=bar)
/subsystem=infinispan/remote-cache-container=foo/remote-cluster=bar:add(socket-bindings=[infinispan-server1, infinispan-server2])
run-batch

3. invalidation-cache を定義する

以前の方法とはここが変りました。以前はリモートストアを使っていましたが、今回はHotRodストアを使います。

これまでの方法では以下の様にstoreremoteでした。

/subsystem=infinispan/cache-container=web/invalidation-cache=infinispan/store=remote

これからの方法では以下の様にstorehotrodになります。

/subsystem=infinispan/cache-container=web/invalidation-cache=infinispan/store=hotrod

こちらも同様に Jboss-cli で以下を実行します。

batch
/subsystem=infinispan/cache-container=web/invalidation-cache=baz:add()
/subsystem=infinispan/cache-container=web/invalidation-cache=baz/component=transaction:add(mode=BATCH)
/subsystem=infinispan/cache-container=web/invalidation-cache=baz/component=locking:add(isolation=REPEATABLE_READ)
/subsystem=infinispan/cache-container=web/invalidation-cache=baz/store=hotrod:add(remote-cache-container=foo, fetch-state=false, purge=false, passivation=false, shared=true)
run-batch

4. アプリでキャッシュを使うように設定する

アプリケーションは以下の 2 つの設定ファイルを変更します。

  • WEB-INF/web.xml
  • WEB-INF/jboss-web.xml

WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <distributable/>
</web-app>

WEB-INF/jboss-web.xml

<?xml version="1.0" encoding="UTF-8"?>
<jboss-web xmlns="http://www.jboss.com/xml/ns/javaee"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.jboss.com/xml/ns/javaee http://www.jboss.org/j2ee/schema/jboss-web_13_0.xsd"
           version="13.0">

    <replication-config>
        <cache-name>web.baz</cache-name>
    </replication-config>

</jboss-web>

5. アプリをデプロイする

アプリケーションをビルドして Wildfly へデプロイします。ビルドには Maven などのビルドツールを使い、デプロイはこれまで同様にjboss-cliを使います。

cd wildfly-session-sample
mvn clean package
jboss-cli.bat -c
deploy target\wildfly-session-sample.war

確認

環境構築がうまくいってアプリがちゃんとできていると Wildfly が以下の様なメッセージを出力します。

[2020-05-19 03:19:43,748] Artifact wildfly-session-sample:war: Artifact is being deployed, please wait...
(略)
15:19:44,225 WARN  [org.wildfly.clustering.web.undertow] (MSC service thread 1-7) WFLYCLWEBUT0004: Legacy <replication-config/> overriding attached distributable session management provider for wildfly-session-sample.war
15:19:44,246 INFO  [org.jboss.as.clustering.infinispan] (ServerService Thread Pool -- 102) WFLYCLINF0032: Getting remote cache named 'wildfly-session-sample.war'. If it does not exist a new cache will be created from configuration template named 'transactional'; null value uses default cache configuration on the Infinispan Server.
(略)
15:19:44,282 INFO  [org.infinispan.CLUSTER] (ServerService Thread Pool -- 102) [Context=wildfly-session-sample.war] ISPN100008: Updating cache members list [laptop-tlfueb3p], topology id 2
(略)
[2020-05-19 03:19:44,510] Artifact wildfly-session-sample:war: Artifact is deployed successfully
[2020-05-19 03:19:44,510] Artifact wildfly-session-sample:war: Deploy took 762 milliseconds

以下の URL へアクセスしてみてください。

以下の場合は各設定ファイルがきちんと書かれているか、デプロイしている war ファイル内のディレクトリ構成が正しいかを確認しましょう。私は IntelliJ IDEA の Enterprise Java で war ファイルを作ったところ後者に嵌まりました・・・

デプロイした時のログがこんな場合jboss-web.xmlの設定ができていません。

[2020-05-19 03:20:19,013] Artifact wildfly-session-sample:war: Artifact is being deployed, please wait...
(略)
15:20:19,613 INFO  [org.infinispan.configuration.cache.MemoryConfigurationBuilder] (MSC service thread 1-4) ISPN000152: Passivation configured without an eviction policy being selected. Only manually evicted entities will be passivated.
15:20:19,628 INFO  [org.infinispan.configuration.cache.MemoryConfigurationBuilder] (MSC service thread 1-4) ISPN000152: Passivation configured without an eviction policy being selected. Only manually evicted entities will be passivated.
(略)
[2020-05-19 03:20:19,905] Artifact wildfly-session-sample:war: Artifact is deployed successfully
[2020-05-19 03:20:19,905] Artifact wildfly-session-sample:war: Deploy took 892 milliseconds

このログの(略)に挟まれているログすら出ない場合は web.xmlの設定ができていません。

参考

24.3.9. HTTP セッションの Red Hat Data Grid への外部化

OpenShift のクラスタモニタリングスタックを Infra ノードで動かす

OpenShift のクラスタモニタリングスタックの設定を変更するには、クラスタモニタリングの ConfigMap を変更する必要があります。この ConfigMap が存在しない場合は、参考の URL にあるマニュアルにある通り作成します。

設定をする前にまずはクラスタモニタリングスタックの設定を確認してみます。

$ oc -n openshift-monitoring get configmap cluster-monitoring-config -o yaml
apiVersion: v1
kind: ConfigMap
metadata:
  creationTimestamp: "2020-04-23T06:47:21Z"
  name: cluster-monitoring-config
  namespace: openshift-monitoring
  resourceVersion: "368857"
  selfLink: /api/v1/namespaces/openshift-monitoring/configmaps/cluster-monitoring-config
  uid: 619c4ac8-c623-4bbc-a47b-bc158b64e5d0

以下のコマンドを使用してクラウドモニタリングスタックの ConfigMap を編集します。

$ oc -n openshift-monitoring edit configmap cluster-monitoring-config

今回は、各スタックを Infra ノードで動かすため、以下を追加します。

data:
  config.yaml: |
    prometheusOperator:
      nodeSelector:
        node-role.kubernetes.io/infra: ""
    prometheusK8s:
      nodeSelector:
        node-role.kubernetes.io/infra: ""
    alertmanagerMain:
      nodeSelector:
        node-role.kubernetes.io/infra: ""
    kubeStateMetrics:
      nodeSelector:
        node-role.kubernetes.io/infra: ""
    grafana:
      nodeSelector:
        node-role.kubernetes.io/infra: ""
    auth:
      nodeSelector:
        node-role.kubernetes.io/infra: ""

以下の様になれば大丈夫です。

$ oc -n openshift-monitoring get configmap cluster-monitoring-config -o yaml
apiVersion: v1
kind: ConfigMap
metadata:
  creationTimestamp: "2020-04-23T06:47:21Z"
  name: cluster-monitoring-config
  namespace: openshift-monitoring
  resourceVersion: "368857"
  selfLink: /api/v1/namespaces/openshift-monitoring/configmaps/cluster-monitoring-config
  uid: 619c4ac8-c623-4bbc-a47b-bc158b64e5d0
data:
  config.yaml: |
    prometheusOperator:
      nodeSelector:
        node-role.kubernetes.io/infra: ""
    prometheusK8s:
      nodeSelector:
        node-role.kubernetes.io/infra: ""
    alertmanagerMain:
      nodeSelector:
        node-role.kubernetes.io/infra: ""
    kubeStateMetrics:
      nodeSelector:
        node-role.kubernetes.io/infra: ""
    grafana:
      nodeSelector:
        node-role.kubernetes.io/infra: ""
    auth:
      nodeSelector:
        node-role.kubernetes.io/infra: ""

参考