OpenShiftでJBossクラスタを構成する仕組み
この記事は赤帽エンジニア Advent Calendar 2018の12/4分の記事です。
Red HatでJBoss EAPのサポートエンジニアをしています。自由なAdvent Calendarなので、自由に勉強メモを書いてみます。
JBossのようなAPサーバはクラスタメンバの探索に従来マルチキャストを使ってきましたが、OpenShift*1やk8sの環境が一般的になると、マルチキャストが使えないネットワークの方が多いです。
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.xmlをsedで色々置換して設定を作成する仕組みになっています。
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"
テンプレートを実行すると、以下のような処理が行われます。
- https://github.com/n-agetsu/eap-debugをgit cloneして、eap-cd-openshiftイメージにソースコードを転送した上で、eap-cd-openshiftイメージに入っているMavenを利用してwarファイルをビルド。
- ビルドした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と呼んでいます。
- アプリケーションイメージのビルドが完了した後、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_PINGとDNS_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はざっくり言うと、k8sのAPIサーバに同じラベルを持つPodのリストを問い合わせてクラスタメンバを見つけています。OpenShift上でKUBE_PINGを動作させようとすると、 アプリケーションからk8sのREST 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内部のDNSのSRVレコードに登録する仕組み*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さんです、お楽しみに!
*1:OpenShiftの場合デフォルトから設定変えるとマルチキャストは使える Managing Networking | Cluster Administration | OpenShift Container Platform 3.11
*2:Technology Previewの詳細は Technology Preview Features Support Scope - Red Hat Customer Portal を参照