Keycloak

Keycloak の クラスタリングで利用されるプロトコル

株式会社 日立製作所 茂木昂士

日立グループOSS Advent Calendar 2018 1日目は、近年注目されているSingle Sign OnのOSSであるKeycloakのクラスタリングで使われているプロトコルについてです。


Keycloak の クラスタリング

Keycloak では、JGroups, Infinispan というライブラリを利用してクラスタリングを実現しています。これらのライブラリは Keycloak のベースとなる Wildfly で利用されていますが、利用される Wildfly のバージョンが上がってきていることで様々な機能が追加されています。

筆者が、Keycloak 超入門 (4) : Keycloak のクラスタ環境を構成してみよう という記事で Keycloak のクラスタ構成について解説しましたが、記事執筆時点と比べてバージョンが上がり、機能が増えてきて様々な選択肢が取れるようになっています。

今回は Keycloak のクラスタ機能のなかでも、 JGroups の Discovery プロトコルに絞って解説します。


JGroups の Discovery プロトコル

JGroups はクラスタのメンバーとなるホストを見つけるために、Discovery プロトコルというものを利用しています。JGroups では、この Discovery プロトコルとして様々なプロトコルを提供しており、環境や要件に合わせて適切なプロトコルを選ぶことができるようになっています。

ここでは、いくつかのプロトコルについて簡単に説明していきます。なお、より詳しく知りたい場合はJGroups のマニュアル (6.4 Initial membership Discovery)を参照してください。


PING

PINGは IP マルチキャストを利用してクラスタのメンバーを見つけるプロトコルです。Keycloak のデフォルトではPINGが使われています。IP マルチキャストが利用可能な環境であれば、特別な設定もなく利用することができます。ですが、IaaS 等の環境ではマルチキャストが使えない環境も多いため注意が必要です。


TCPPING

TCPPINGは TCP 通信を使ってクラスタメンバーを見つけるプロトコルです。下記のようにinitial_hostsというパラメータで各ホストのアドレスを指定します。

        <protocol type="TCPPING">

<property name="initial_hosts">192.168.10.1[7600],192.168.10.2[7600]</property>
</protocol>

クラスタのメンバーが固定されているため、クラスタホスト数の増減がある状況や、IP アドレスが変わるような環境では利用しづらいですが、それ以外の環境では設定がシンプルであるため問題が起こりづらいプロトコルです。


JDBC_PING

JDBC_PINGはデータベースを利用してクラスタのメンバーを見つけるプロトコルです。クラスタの各メンバーが自身の情報をデータベースに書き込み、その情報をもとにクラスタを構築します。

ほとんどの環境では共有のデータベースを利用するので、このプロトコルが使われることが一般的のようです。


DNS_PING

DNS_PINGは DNS の A レコードや SVC レコードを利用してクラスタのメンバーを見つけるプロトコルです。もともとは Kubernetes や OpenShift などの環境向けに作られたのですが、DNS Discovery が利用できる環境であればどこでも利用できます。


KUBE_PING

KUBE_PINGは Kubernetes の API を利用してクラスタのメンバーを見つけるプロトコルです。KUBE_PINGは JGroups Extra というプロジェクトで提供されているものですが、Keycloak では 4.4.0.Final から同梱されるようになっています。

DNS_PINGとの大きな違いは、Kubernetes の API を利用するために追加の権限(View)が必要になる点です。そのため、Keycloak の公式でも特別な権限のいらないDNS_PINGの利用が推奨されています。

今回はこれらのプロトコルのうち、DNS_PING を試してみたいと思います。


DSN_PING の設定

今回は OKD 3.11 上で設定を行います。Keycloak Docker イメージの GitHub リポジトリ(https://github.com/jboss-dockerfiles/keycloak)のopenshift-examplesというディレクトリにテンプレート用の Json ファイル(keycloak-https.json)があるのでこれを利用します。


テンプレートのデプロイ

keycloak-https.jsonファイルを取得し、下記のコマンドを実行します。

# oc new-app -p NAMESPACE=`oc project -q` -f keycloak-https.json

これで Keycloak の DeploymentConfig が作成され、Pod が作成されます。クラスタの確認をするためoc scale --replicas=3 dc keycloakとして、スケールします。

一見うまくいっているように見えますが、実はクラスタが構築されておらずそれぞれの Pod が独自に動いています。これには下記二つの理由があります。


問題 1: Headless Service が構築されていない

JGroups ドキュメントのDNS_PINGの部分に Kubernetes/OpenShift では Headless Service が必要と書かれています。Headless Serviceとは clusterIP の値を持たない Service で、名前解決をすると Service のアドレスではなく、各 Pod のアドレスが得られるというものです。ですが今回のテンプレートには Headless Service を定義している部分がありません。


問題 2: Protocol Stack が udp で動いている

Keycloak の Docker イメージでは、JGroups で利用する Protocol Stack のデフォルトが udp になっています。ですが、udp のままだとクラスタ構築時にタイムアウトしていまい Keycloak が起動しません。

これらの問題を修正していきます。


Docker イメージの作成

まずは Docker のイメージを修正していきます。Keycloak の Docker イメージは、JGROUPS_DISCOVERY_PROTOCOLで指定した値と同名の jboss-cli ファイルがあればそれを実行し、なければdefault.cliというファイルを実行して Discovery プロトコルの設定を行います。なので、dns_ping.cliというファイルを作成し、下記の内容を記載します。


dns_ping.cli

embed-server --server-config=standalone-ha.xml --std-out=echo

batch

/subsystem=jgroups/channel=ee:write-attribute(name="stack", value="tcp")

/subsystem=jgroups/stack=tcp/protocol=MPING:remove()
/subsystem=jgroups/stack=tcp/protocol=dns.DNS_PING:add(add-index=0, properties=$keycloak_jgroups_discovery_protocol_properties)

run-batch
stop-embedded-server


ここでは、下記の処理を指定ます。


  • Protocol Stack を tcpに変更

  • Discovery Protocol に dns.DNS_PING を設定

そして、上記のファイルを追加した Keycloak の Docker イメージを作成します。


Dockerfile

FROM jboss/keycloak-openshift:latest

ADD dns_ping.cli /opt/jboss/tools/cli/jgroups/discovery/

作成した Docker イメージは OKD の Docker レジストリに Push しておきます。


Headless Service の作成

次に Headless Service を作成します。下記のようにclusterIP: Noneという設定を入れて Service を作成します。


keycloak-discovery.yaml

apiVersion: v1

kind: Service
metadata:
name: keycloak-discovery
spec:
clusterIP: None
ports:
- name: http
port: 8080
protocol: TCP
targetPort: 8080
selector:
deploymentconfig: keycloak

Service の作成が完了すると、下記のようにCLUSTER-IPの値がNoneという値になっているkeycloak-discveryというサービスが作成されます。

# oc get svc keycloak-discovery

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
keycloak-discovery ClusterIP None <none> 8080/TCP 2m


DeploymentConfig の修正

最後に DeploymentConfig の内容を修正していきます。修正する内容は下記の通りです。


  • Pod に利用するイメージを自身で作成したものに変更


  • JGROUPS_DISCOVERY_POROTOCOLの値をdns.DNS_PINGからdns_pingに変更


  • JGROUPS_DISCOVERY_PROPERTIESの値をkeycloak-discovery.keycloak.svc.cluster.localに変更

上記の変更をが反映されると、Keycloak のクラスターが正常に動作するようになります。正常に起動していると各 Pod のログに下記のようなログが出てクラスターが構築されていることが確認できます。

INFO  [org.infinispan.CLUSTER] (thread-11,ejb,keycloak-2-sgns7) ISPN000094: Received new cluster view for channel ejb: [keycloak-2-sgns7|2] (3) [keycloak-2-sgns7, keycloak-2-69ksp, keycloak-2-z7l9b]

...

これで DNS_PING の動作が確認できました。


まとめ

Keycloak や Wildfly は、Kbernetes/OpenShift 上で簡単に Cluster を組むことができるようになってきていますが、今回はまだ発展途上な感じがしました。これからどんどん使われていくことによって、よりよいものに発展していくでしょう。