Ponz Dev Log

ゆるくてマイペースな開発日記

k8sやOpenShiftでKeycloakのクラスタを組むにはHeadless Serviceがいる

最近OpenShiftを触る機会があり、認証・認可サーバのOSSであるKeycloak on OpenShiftでクラスタを組むのにハマりました。 解決のために調べたことをメモ書きします。

TL; DR

  • 外部公開用のServiceを使ってDNS_PINGクラスタを作成しても、Podごとにクラスタができてしまう。
  • 外部公開用のServiceとは別に、クラスタ内のPod間で疎通するためのHeadless Serviceを作成しましょう。

動作確認

CodeReady Containerで動作確認を行いました。 これ以外の環境では別途確認をお勧めします。

  • crc: 1.16.0+bf72d3a
  • OpenShift: 4.5.9

サンプルを流用してクラスタを作成する...しかしできない

まずはKeycloakの公式ドキュメント "Keycloak on Openshift" で紹介されているTemplateを使用してクラスタを作成してみます。

Pod1つで動かす

サンプル通りまずはPodを1つだけ立てるパターンを確認します。 上記のTemplateを oc process コマンドで適用して各種リソースを作成します。

$ oc get po -w
NAME                     READY   STATUS      RESTARTS   AGE
keycloak-demo-1-deploy   0/1     Completed   0          5m26s
keycloak-demo-1-n8gpd    1/1     Running     0          5m23s

ここで作成されたPodのログを確認するとクラスタを作成していることがわかります。 最初に立てるPodなのでほかのクラスタのメンバーが見つからないというメッセージが出力されます。

$ oc logs keycloak-demo-1-n8gpd -f
(...中略...)
07:26:27,828 INFO  [org.jgroups.protocols.pbcast.GMS] (ServerService Thread Pool -- 60) keycloak-demo-1-n8gpd: no members discovered after 3321 ms: creating cluster as coordinator

Podを2つ以上で動かす

上記のTemplateで同じPodを追加すると、同じクラスタに2つのPodが含まれると予想できます。 実際にoc scale コマンドでDeploymentConfigを2つ目のPodを立ち上げます。

$ oc scale dc keycloak-demo --replicas=2

$ oc get po -w
NAME                     READY   STATUS      RESTARTS   AGE
keycloak-demo-1-deploy   0/1     Completed   0          5m26s
keycloak-demo-1-j6p5h    0/1     Running     0          8s
keycloak-demo-1-n8gpd    1/1     Running     0          5m23s
keycloak-demo-1-j6p5h    1/1     Running     0          44s

$ oc logs keycloak-demo-1-j6p5h -f
(...中略...)
07:30:35,764 INFO  [org.jgroups.protocols.pbcast.GMS] (ServerService Thread Pool -- 60) keycloak-demo-1-j6p5h: no members discovered after 3322 ms: creating cluster as coordinator

Podは正常に立ち上がりましたが、上記のログを見ると1つ目のPodと同じように新しくクラスタを作成しています。 2つのPodが1つのクラスタのメンバーになることを期待していますので、期待通りの結果とはなりませんでした。

解決方法

Headless Serviceを作成

結論としてはHeadless Serviceを作成し、以下のようにKeycloakのPodではHeadless Serviceに対してDNSクエリを投げるように設定すれば解決できます。

- apiVersion: v1
  kind: Service
  metadata:
    annotations:
      description: Cluster discovery http port.
    labels:
      application: "${APPLICATION_NAME}"
    name: "${APPLICATION_NAME}-discovery"
  spec:
    ports:
      - port: 8080
        targetPort: 8080
    selector:
      deploymentConfig: "${APPLICATION_NAME}"
    clusterIP: None  # Headless Service
  - apiVersion: v1
    kind: DeploymentConfig
    metadata: ...
    spec:
      replicas: 1
      selector:
        deploymentConfig: "${APPLICATION_NAME}"
      template:
        metadata: ...
        spec:
          containers:
            - env:
                - name: KEYCLOAK_USER
                  value: "${KEYCLOAK_USER}"
                - name: KEYCLOAK_PASSWORD
                  value: "${KEYCLOAK_PASSWORD}"
                - name: DB_VENDOR
                  value: "${DB_VENDOR}"
                - name: JGROUPS_DISCOVERY_PROTOCOL
                  value: dns.DNS_PING
                - name: JGROUPS_DISCOVERY_PROPERTIES  # xxx-discoveryにクラスタ疎通のPINGを投げる
                  value: "dns_query=${APPLICATION_NAME}-discovery.${NAMESPACE}.svc.cluster.local"
                # - name: JGROUPS_DISCOVERY_PROPERTIES
                #   value: "dns_query=${APPLICATION_NAME}.${NAMESPACE}.svc.cluster.local"
              image: quay.io/keycloak/keycloak:11.0.2

この設定はKeycloakのクラスタを組むしくみである JGroup のドキュメントに説明がありました。
JGroupのドキュメントには、KubernetesやOpenShiftでクラスタを構築するにはHeadless Serviceが必要との記載があります。

なお、上記の設定では環境変数 JGROUPS_DISCOVERY_PROTOCOL に何も変更を加えていません。 JGroupのDNS_PINGで使用されるプロトコルがUDPと記載されている記事もあるのですが、現在はTCPがデフォルトになっているためここであらためて設定する必要はありません。

JGROUPS_TRANSPORT_STACK - an optional name of the transport stack to use udp or tcp are possible values. Default: tcp

出典: Reliable group communication with JGroups : 6.4.15. DNS_PING

Podを2つ以上で動かしてクラスタを組む

Headless Serviceを作成してあらためてクラスタを組んでみます。 上記の通り追記・修正したTemplateを再度適用し、Podを2つ立てみます。

# 外部疎通用とクラスタ疎通用の2つのServiceを作成する
$ oc get svc
NAME                      TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
keycloak-demo             ClusterIP   172.25.250.178   <none>        8443/TCP   4s
keycloak-demo-discovery   ClusterIP   None             <none>        8080/TCP   4s

# 1つ目のPod
$ oc logs -f keycloak-demo-1-mvrfg
07:42:50,796 INFO  [org.jgroups.protocols.pbcast.GMS] (ServerService Thread Pool -- 60) keycloak-demo-1-mvrfg: no members discovered after 3037 ms: creating cluster as coordinator
07:42:51,889 INFO  [org.infinispan.CLUSTER] (MSC service thread 1-1) ISPN000078: Starting JGroups channel ejb
07:42:51,898 INFO  [org.infinispan.CLUSTER] (MSC service thread 1-1) ISPN000094: Received new cluster view for channel ejb: [keycloak-demo-1-mvrfg|0] (1) [keycloak-demo-1-mvrfg]

# 2つ目のPod
$ oc logs -f
07:45:02,173 INFO  [org.infinispan.CLUSTER] (MSC service thread 1-1) ISPN000078: Starting JGroups channel ejb
07:45:02,191 INFO  [org.infinispan.CLUSTER] (MSC service thread 1-1) ISPN000094: Received new cluster view for channel ejb: [keycloak-demo-1-mvrfg|1] (2) [keycloak-demo-1-mvrfg, keycloak-demo-1-vv5jn]

1つ目のPodはクラスタを新規で作成していますが、2つ目のPodは無事に既存クラスタに参加した旨のメッセージがログに出力されています。 これにて一件落着です。


以上。