見習いプログラミング日記

Javaを中心に色々なことを考えてみます。目指せ本物のプログラマ。

OpenShiftでJBossクラスタを構成する仕組み

この記事は赤帽エンジニア Advent Calendar 2018の12/4分の記事です。
Red HatJBoss EAPのサポートエンジニアをしています。自由なAdvent Calendarなので、自由に勉強メモを書いてみます。


JBossのようなAPサーバはクラスタメンバの探索に従来マルチキャストを使ってきましたが、OpenShift*1k8sの環境が一般的になると、マルチキャストが使えないネットワークの方が多いです。

Red Hat Container Catalogで公開されているOpenShift向けのJBossコンテナイメージJBoss EAP CD for OpenShiftがどのようにクラスタを組んでいるかメモします。

EAP CDってなんぞやから、テストアプリケーションのデプロイ、クラスタディスカバリの仕組みDNS_PINGについて順を追って紹介します。

JBoss EAP CD for OpenShift

JBoss EAP Continuous Delivery for OpenShiftとは、2018年4月から3〜4ヶ月単位でリリースされている、OpenShift環境向けのEAPのバイナリです。今のところはTechnology Preview*2で、今後導入される予定のEAP新機能を先行して試す検証用を目的としています。EAP CDの3〜4つのリリースを組み合わせて、従来の同様にフルサポートされるEAPがリリースされるモデルとなっています。

従来のEAPと異なり、zip版はリリースされておらず、Red Hat Container Catalogを通じて、コンテナイメージをダウンロードする方式のみ提供されています。

デフォルトのプロファイルは standalone-openshift.xml であり、コンテナ起動時に /opt/eap/bin/launch に含まれるスクリプト類でstandalone-openshift.xmlsedで色々置換して設定を作成する仕組みになっています。

OpenShiftがなくても、手元のDockerで起動してイメージの中身を確認できます。イメージにはbashが入っているので、通常のEAPとの差分については /opt/eap を見てみてください。

$ docker run --name eap-cd -p 8080:8080 registry.access.redhat.com/jboss-eap-7-tech-preview/eap-cd-openshift:14.0-4
$ docker exec -it eap-cd /bin/bash
cd /opt/eap

テストアプリケーションのデプロイ

OpenShiftの環境があることを前提に、テストアプリケーションのデプロイをします。
eap-debug/session-test at master · n-agetsu/eap-debug · GitHubにある、簡単なセッションレプリケーションの動作確認用アプリです。

EAP CD向けのImageStreamとTemplateの登録

はじめにEAP CD向けの最新のImageStreamとTemplateをプロジェクト名 openshift に登録します。12/4時点ではEAP CD 14です。

$ oc project openshift
$ for resource in eap-cd-image-stream.json   eap-cd-amq-persistent-s2i.json   eap-cd-amq-s2i.json   eap-cd-basic-s2i.json   eap-cd-https-s2i.json   eap-cd-mongodb-persistent-s2i.json   eap-cd-mongodb-s2i.json   eap-cd-mysql-persistent-s2i.json   eap-cd-mysql-s2i.json   eap-cd-postgresql-persistent-s2i.json   eap-cd-postgresql-s2i.json   eap-cd-sso-s2i.json   eap-cd-third-party-db-s2i.json   eap-cd-tx-recovery-s2i.json; do   oc replace --force -f https://raw.githubusercontent.com/jboss-container-images/jboss-eap-7-openshift-image/eap-cd/templates/${resource}; done

テンプレートによるアプリケーションのデプロイ

テンプレートeap-cd-basic-s2i を使ってセッションレプリケーションテスト用のアプリケーションをデプロイします。

$ oc new-app --template=eap-cd-basic-s2i -p IMAGE_STREAM_NAMESPACE=openshift -p SOURCE_REPOSITORY_URL="https://github.com/n-agetsu/eap-debug" -p SOURCE_REPOSITORY_REF="master" -p CONTEXT_DIR="session-test" -p APPLICATION_NAME="session-test"

テンプレートを実行すると、以下のような処理が行われます。

  1. https://github.com/n-agetsu/eap-debugをgit cloneして、eap-cd-openshiftイメージにソースコードを転送した上で、eap-cd-openshiftイメージに入っているMavenを利用してwarファイルをビルド。
  2. ビルドしたwarファイルを、eap-cd-openshiftイメージの /opt/eap/standalone/deployments に含めた、アプリケーションイメージを作成し、OpenShiftの内部レジストリにpush。eap-cd-openshiftのようにEAPのランタイムとMavenのようなビルドツールが入っているコンテナイメージを、OpenShiftではBuilder Imageと呼んでいて、Red Hat Container Catalogでは色々なBuilder Imageが公開されています。一方でBuilder Imageにビルド済みのwarファイルを追加して、Podとして起動するイメージをApplication Imageと呼んでいます。
  3. アプリケーションイメージのビルドが完了した後、DeploymentConfigの設定に沿ってアプリケーションイメージをPodとして起動。

アプリケーションイメージのPod起動が完了すると、以下のリソースが展開されます。ServiceやRouteなど、アプリケーションの動作に必要なリリースを個別に設定する必要はなく、テンプレートによって展開されます。

$ oc get all
NAME                       READY     STATUS      RESTARTS   AGE
pod/session-test-1-build   0/1       Completed   0          31m
pod/session-test-1-vldx2   1/1       Running     0          29m

NAME                                   DESIRED   CURRENT   READY     AGE
replicationcontroller/session-test-1   1         1         1         29m

NAME                        TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
service/session-test        ClusterIP   172.30.133.229   <none>        8080/TCP   31m
service/session-test-ping   ClusterIP   None             <none>        8888/TCP   31m

NAME                                              REVISION   DESIRED   CURRENT   TRIGGERED BY
deploymentconfig.apps.openshift.io/session-test   1          1         1         config,image(session-test:latest)

NAME                                          TYPE      FROM         LATEST
buildconfig.build.openshift.io/session-test   Source    Git@master   1

NAME                                      TYPE      FROM          STATUS     STARTED          DURATION
build.build.openshift.io/session-test-1   Source    Git@5b856c7   Complete   31 minutes ago   1m53s

NAME                                          DOCKER REPO                                                  TAGS      UPDATED
imagestream.image.openshift.io/session-test   docker-registry.default.svc:5000/cluster-test/session-test   latest    29 minutes ago

NAME                                    HOST/PORT                                         PATH      SERVICES       PORT      TERMINATION     WILDCARD
route.route.openshift.io/session-test   session-test-cluster-test.cloudapps.example.com             session-test   <all>     edge/Redirect   None
||< 


テンプレートの中身やパラメータの一覧を確認したい場合は、以下のコマンドで確認してください。
>|sh|
$ oc get template eap-cd-basic-s2i -o yaml -n openshift

アプリケーションの動作確認

テストアプリケーションでは、アクセスカウンタと共に、アクセスされたホスト名を返しています。eap-cd-basic-s2i ではPodのレプリカ数は1で起動するため、毎回同じホスト名が返されます。

$ curl --insecure -c cookie.txt https://session-test-cluster-test.cloudapps.example.com/session-test/count
count: 1, host: session-test-1-vldx2
$ curl --insecure -b cookie.txt https://session-test-cluster-test.cloudapps.example.com/session-test/count
count: 2, host: session-test-1-vldx2
$ curl --insecure -b cookie.txt https://session-test-cluster-test.cloudapps.example.com/session-test/count
count: 3, host: session-test-1-vldx2

Podのレプリカを2に増やしても、OpenShiftのRouterはデフォルトでソースIPベースのスティッキーセッションが有効化されているため、同じPodにアクセスし続けます。これではセッションレプリケーションが動いているかわからないため、レプリカ数を2に増やした後、元々アクセスしていたPodを削除してみます。

$ oc scale dc session-test --replicas=2
$ oc delete pod session-test-1-vldx2

$ curl --insecure -b cookie.txt https://session-test-cluster-test.cloudapps.example.com/session-test/count
count: 4, host: session-test-1-fhvjs

別のホストにアクセスしましたが、HTTPセッションは継続しています。セッションレプリケーションがなければ、Podをdeleteした時点でセッションは失われていたでしょう。

OpenShiftのデフォルトではマルチキャストは飛びません。テンプレートの中ではStatefulSetを使ってホスト名を固定し、クラスタメンバを静的に定義することもしていません。テンプレートでデプロイすると、jgroupsのDNS_PINGの仕組みを使ってクラスタメンバの探索を行なっています。

JGroupsのDNS_PING機能

JGroupsにはk8s向けのクラスタディスカバリの実装として、KUBE_PINGDNS_PINGの2種類の機能があります。コンテナイメージとしてはKUBE_PINGがデフォルトでstandalone-openshift.xmlに設定されていますが、テンプレートを利用するとコンテナ起動時にデフォルトでDNS_PINGに上書きします。

/opt/eap/standalone/configuration/standalone-openshift.xmlには、以下のようにJGroupsサブシステムの設定にopenshift.DNS_PINGが含まれます。

<subsystem xmlns="urn:jboss:domain:jgroups:6.0">
    <channels default="ee">
        <channel name="ee" stack="tcp"/>
    </channels>
    ...
    <stack name="tcp">
        <transport type="TCP" socket-binding="jgroups-tcp">
            <property name="use_ip_addrs">true</property>
        </transport>
        <protocol type="openshift.DNS_PING" ></protocol>
        <protocol type="MERGE3"/>

KUBE_PINGはざっくり言うと、k8sAPIサーバに同じラベルを持つPodのリストを問い合わせてクラスタメンバを見つけています。OpenShift上でKUBE_PINGを動作させようとすると、 アプリケーションからk8sREST APIへのアクセスを許可する設定が必要ですが、DNS_PINGではOpenShift側の設定は不要なためシンプルに使えます。

DNS_PINGの仕組み

ここまで長くなりましたが、ここからが今日のタイトルの話です。

DNS_PINGはOpenShift上のDNSサーバのSRVレコードの仕組みを使って、クラスタメンバを見つけています。実際にレコードを内容を見てみると想像が付きやすいので、OpenShift上の任意のノードにSSHログインしてnslookupしてみます。

$ nslookup -type=SRV _tcp.session-test-ping.cluster-test.svc.cluster.local
Server:		192.168.122.2
Address:	192.168.122.2#53

_tcp.session-test-ping.cluster-test.svc.cluster.local	service = 10 50 8888 fd149e20.session-test-ping.cluster-test.svc.cluster.local.
_tcp.session-test-ping.cluster-test.svc.cluster.local	service = 10 50 8888 14a2d9.session-test-ping.cluster-test.svc.cluster.local.

ホスト名のようなものが出てくるため、pingしてみます。

$ ping fd149e20.session-test-ping.cluster-test.svc.cluster.local.
PING fd149e20.session-test-ping.cluster-test.svc.cluster.local (10.130.0.11) 56(84) bytes of data.
64 bytes from 10.130.0.11 (10.130.0.11): icmp_seq=1 ttl=64 time=1.51 ms

oc get pod -o wideで確認すると、SRVレコードで取得できたFQDNfd149e20.session-test-ping.cluster-test.svc.cluster.local.はPodのIPアドレス10.130.0.11とわかります。

$ oc get pod -o wide
NAME                   READY     STATUS      RESTARTS   AGE       IP            NODE                           NOMINATED NODE
session-test-1-build   0/1       Completed   0          1h        10.130.0.9    node01.openshift.example.com   <none>
session-test-1-vldx2   1/1       Running     0          1h        10.130.0.11   node01.openshift.example.com   <none>
session-test-1-xmqpc   1/1       Running     0          41m       10.130.0.12   node01.openshift.example.com   <none>

k8sの機能として、ロードバランシング用のCluster IPを持たない(clusterIP: none) Headless Serviceがデプロイされた場合、Serviceに関連づけられたPodのホスト名とServiceが公開しているポート番号を、k8s内部のDNSSRVレコードに登録する仕組み*3があります。

OpenShiftのテンプレートでは、EAP-CD(eap-cd-basic-s2i)向けだけに止まらず、EAP64やEAP71のイメージにおいても、HTTPアクセスのためのServiceとは別に、以下のようなクラスタディスカバリ向けのServiceを登録しています。

$ oc get svc session-test-ping -o yaml
apiVersion: v1
kind: Service
metadata:
  ...
spec:
  clusterIP: None
  ports:
  - name: ping
    port: 8888
    protocol: TCP
    targetPort: 8888
  selector:
    deploymentConfig: session-test
  sessionAffinity: None
  type: ClusterIP

もう一度nslookupの例に戻ります。

$ nslookup -type=SRV _tcp.session-test-ping.cluster-test.svc.cluster.local
  ...
_tcp.session-test-ping.cluster-test.svc.cluster.local	service = 10 50 8888 fd149e20.session-test-ping.cluster-test.svc.cluster.local.
_tcp.session-test-ping.cluster-test.svc.cluster.local	service = 10 50 8888 14a2d9.session-test-ping.cluster-test.svc.cluster.local.

nslookupのクエリ対象である、_tcp.session-test-ping.cluster-test.svc.cluster.localは、前述のService session-test-ping の追加によって登録されたDNSレコードです。k8sのServiceを追加すると、今のような命名規則SRVレコードが追加されます。

<Serviceのprotocol>.<Service名>.<プロジェクト名(namespace)>.svc.cluster.local

SRVレコードは以下のような情報を含んでいます。PriorityとWeightはSRVレコードを読み取るクライアントが、負荷分散の重み付けのために使う情報です。DNS_PINGは負荷分散ではなく単純にクラスタディスカバリにしかSRVを使っていないため、現時点での実装では重み付け情報は使っていません。SRVレコードのポート番号も現時点での実装では使っていません。クラスタ間通信にはデフォルトで7600ポート(TCP)が使われます。

service = 10 50 8888 fd149e20.session-test-ping.cluster-test.svc.cluster.local.
              = (Priority) (Weight) (ポート番号) (ホスト名)

このようにJGroupsはSRVレコードからクラスタメンバの情報を取得しています。一度メンバの探索ができたら、その後FDとFD_ALLで定期的にヘルスチェックを行い障害検知を行うのは、従来のTCPユニキャストベースのクラスタと同じです。

このDNS_PINGソースコードは以下にあります。
GitHub - jboss-openshift/openshift-ping: JGroups PING protocols for OpenShift

今回のテンプレートeap-cd-basic-s2iのように、テンプレートでは必要な関連リソースを一緒にデプロイしてくれるため、慣れないと中身で何やっているかわからない不安はありますが、テンプレートを使う方がハマりにくいです。

おそらく初めてEAPをOpenShiftにデプロイするときに、テンプレートなしでJGroups向けのHeadless Serviceのデプロイに思い当たる人はなかなかいないと思います。プロジェクト固有のテンプレートが必要な場合、まずは既存のテンプレートで慣れてから、テンプレート作成に着手した方が近道だと思います。

まとめ

  • JBoss EAP CD for OpenShiftは従来のEAPと比較してインクリメンタルなモデルでリリースしている、コンテナイメージ形式リリースのバイナリです。今のところTechnology Previewのため評価検証向けです。
  • JBoss EAP CD for OpenShiftの最新版をOpenShiftで利用するためには前述の通りImageStreamとTemplateの更新をします
  • マルチキャストが使えない環境のために、DNS_PINGと呼ばれる、Headless ServiceによるSRVレコードの参照により、クラスタを構成しています
  • 慣れないうちはテンプレートを使うのがおすすめです

明日は@orimanabuさんです、お楽しみに!