AWS上でKeycloakを冗長化するとき、詰まるのはALBの前段ではなく、その裏側のInfinispanクラスタ形成とセッションキャッシュの挙動です。本稿ではQuarkus版を基準に、ノード発見・sticky session・AZ分散・ヘルスチェックの現実を整理します。
Keycloakを1台で立てるのは簡単です。難しいのは「落ちても止まらない」構成にした瞬間、裏側のInfinispanクラスタとセッションキャッシュの挙動を正しく理解していないと、フェイルオーバー時にユーザーが再ログインを強いられたり、ノードが互いを見つけられずクラスタが形成されなかったりする点です。本稿はAWS上でKeycloak(現行のQuarkusベース、いわゆるKeycloak.X系)をHA構成にする際の実務的な勘所を、公式ドキュメントの一次情報を引きながら整理します。バージョン依存の挙動は新しめのバージョン(26系前後)を基準にしていますが、採用時は必ず自環境のバージョンの公式ドキュメントで確認してください。
01全体像 — ALB配下に複数ノードを並べる
基本形はシンプルです。ALB(Application Load Balancer)を前段に置き、その配下に複数のKeycloakノード(EC2、ECS、EKSいずれでも)を並べ、バックエンドにRDS/Auroraを共有DBとして置きます。Keycloakノード同士はInfinispanで分散キャッシュを組み、セッションやトークン失効情報を共有します。ここでのポイントは、Keycloakは「ステートレスなWebアプリ+ステートフルな分散キャッシュ」という二層構造を1プロセス内に抱えていることです。HTTPトラフィックはALBが捌きますが、ノード間のキャッシュ同期は別の経路(JGroups)で流れます。この二経路を両方とも設計対象にする、というのが最初の認識合わせです。
02ALBの前段 — TLS終端とプロキシヘッダ
ALBでTLSを終端し、バックエンドにはHTTP(あるいは内部TLS)で流す構成が一般的です。このとき必ず設定するのが、Keycloakにプロキシ経由であることを伝えるヘッダ処理です。Keycloakは自分が生成するリダイレクトURL(認可エンドポイントやトークンエンドポイントのissuer)を、受け取ったリクエストのホスト情報から組み立てます。プロキシ配下だと元のホスト・スキームが失われるため、ALBが付与するX-Forwarded-*ヘッダを信頼するよう明示する必要があります。
- Quarkus版では
KC_PROXY_HEADERSをxforwarded(ALBはこちら)またはforwardedに設定します。旧来の--proxy=edgeオプションは新しめのバージョンで非推奨/廃止方向のため、公式ドキュメントで現行の指定方法を確認してください。 - ホスト名は
KC_HOSTNAMEで外部公開URLを固定するのが安全です。曖昧なままだとフィッシング防止のホスト名チェックで弾かれたり、issuerがぶれてトークン検証が失敗したりします。
X-Forwarded-Proto: https が確実に伝わっているかを最初に疑ってください。Authorization: Negotiate ヘッダで提示)なのでALB(L7)でも成立します。接続に紐づき、接続の張り替え・多重化で壊れるのはNTLMの方です。なお、クライアントがKDC(88番)からチケットを取得するのは社内ネットワーク側の通信で、Keycloak前段のロードバランサ(HTTPのみを扱う)は通りません。Kerberosで本当に効くのはSPN(サービスプリンシパル名)とKeycloakの公開ホスト名(KC_HOSTNAME)の一致で、ここがずれると検証に失敗します。TLS終端やホスト名整合を単純化したい理由でNLBを選ぶことはありますが、「ALBだからKerberosが壊れる」わけではありません。03sticky sessionは必須か — 答えは「推奨だが必須ではない」
もっとも誤解が多い論点です。結論から言うと、Infinispanで分散キャッシュを組んでいればsticky sessionは必須ではありません。性能最適化のための推奨事項です。公式のリバースプロキシ設定ドキュメントもそう述べています。
仕組みを理解すると腹落ちします。Keycloakは認証セッションをランダムIDでInfinispanに保存しますが、分散キャッシュはエントリごとに「所有者(owner)」ノードを持ちます。あるノードが自分の持たないセッションを扱うと、所有者ノードへリモート参照が発生します。この往復を減らすため、Keycloakは AUTH_SESSION_ID クッキーを <session-id>.<owner-node-id> の形式(例: 123.node2)で発行し、ALB/プロキシが同じノードへ振れるようにしています。sticky sessionが効けばリモート参照が減り、レイテンシとクラスタ内トラフィックが下がる、という理屈です。
- ALBでは対象グループのstickiness(アプリケーションベースまたは期間ベース)を有効にすることでノード固定が可能です。
- プロキシ側がクッキーを触らずにセッションアフィニティを実現できる場合は、Keycloak側の
spi-sticky-session-encoder--infinispan--should-attach-routeをfalseにして、ノード名のクッキー付与を止め、プロキシの機能に任せる選択もあります。
04embedded Infinispanのクラスタ形成 — AWSではJDBC_PINGが本命
ここがAWSでのHA構成の心臓部です。Keycloakは各ノード内にInfinispanを埋め込み(embedded)、ノード同士はJGroupsでクラスタを形成します。問題は「ノードが互いをどう発見するか(discovery)」です。オンプレのデフォルトはUDPマルチキャストですが、AWSのVPCはマルチキャストを許可していないため、そのままでは絶対にクラスタが組めません。ここで手が止まる方が非常に多いです。
現行のKeycloakでは、分散キャッシュ設定ドキュメントにある通り、分散キャッシュ有効時のデフォルトスタックが jdbc-ping(JGroupsの JDBC_PING2 プロトコル)になっています。これはノードの参加情報を共有DBのテーブルに書き込んで互いを発見する方式で、AWSと相性が良いのが決定的です。
- 追加のAWSリソースが不要:すでに使っている共有DBをレジストリとして流用するため、S3バケットやDNSレコード、別途のディスカバリ基盤を用意しなくて済みます。
--cache-stackで明示指定もできますが、新しめのバージョンでは分散キャッシュを有効にすればjdbc-pingが既定になります。kubernetes(DNS_PING相当)やtcp/udpは用途特化・非推奨方向です。- 発見後の実データ通信はTCPで、デフォルトのバインドポートは7800、障害検知(FD)にオフセット+50000の57800を使います。セキュリティグループでノード間のこれらのポートの双方向通信を必ず許可してください。ここを閉じているとDB上ではノードが見えているのに実際のキャッシュ同期が成立しない、という厄介な半死状態になります。
05キャッシュのowner数とフェイルオーバーの現実
分散キャッシュは各エントリの複製数を「owner数」で制御します。ここにHAとメモリ・性能のトレードオフが凝縮されています。公式ドキュメントの記述では、ユーザ/クライアントセッションのキャッシュはメモリ節約のため所有者を絞る設計になっており、その分ノード障害時の挙動に影響します。
- owner=1:各セッションが1ノードにしか存在しません。そのノードが落ちると、そのノードが所有していたセッションは失われ、ユーザは再ログインになります。メモリは最小。
- owner=2以上:複製を持つため1ノード障害でセッションが生存します。HA的にはこちらが安心ですが、メモリと書き込み時のノード間トラフィックが増えます。
「単一ノード障害でもユーザを再ログインさせない」ことを要件にするなら、セッション系キャッシュのowner数を2以上にするのが定石です。ただしこれはキャッシュ設定XML(cache-ispn.xml相当)の調整領域で、公式には高度な用途と位置づけられるため、バージョンごとの推奨とサポート範囲を確認したうえで行ってください。owner数・キャッシュ調整の詳細は姉妹記事のInfinispanキャッシュチューニングで掘り下げています。
06AZ分散と、単一クラスタ/マルチサイトの線引き
AWSでの可用性はAZ分散から始めます。KeycloakのHA導入ドキュメントは、「透過的なネットワークを持つ単一クラスタ(1つのVPC/K8sクラスタ)を複数AZにまたがって配置する」構成が、AZ障害に耐える基本形だとしています。ノード間がL2的に素直に通信でき、JGroupsクラスタが1つにまとまるためです。実務ではこの「1リージョン・複数AZ・単一Infinispanクラスタ」で十分なケースが大半です。
一方、リージョン障害まで耐えたい、あるいは地理的に離れた2拠点に分けたい場合は、話が変わります。Keycloak 26.0.0のリリース告知は「両サイトで同時にユーザーリクエストを処理できるようになった」と明記しており、26系の公式HAガイドは2サイトのアクティブ-アクティブなマルチクラスタ構成を推奨構成としています(ロードバランサのblueprintもAWS Global Acceleratorベースに更新)。ただし、以下の制約は現行でも有効です。
- 各サイトに独立したInfinispanクラスタが必要で、サイト間はInfinispanのクロスサイト複製で繋ぎます(embeddedではなく外部Infinispanを立てる構成が前提になります)。
- 外部ロードバランサ(グローバルな振り分け)が必要です。
- 公式は「3つ以上のAZ/サイトはサポート対象外」と明記しており、構成は2サイトに限定されます。DBのレイテンシとレプリケーション設計もシビアになります。
07ヘルスチェック — ALBが見るべきは9000番のヘルスエンドポイント
ALBのターゲットグループのヘルスチェックを、Keycloakの実アプリ(8080/8443)のトップに向けてしまう設計をよく見かけますが、現行のKeycloakは専用のヘルスエンドポイントを持っています。デフォルトでは管理ポート9000上に公開されます。
/health/started:起動プローブ。初期起動が完了したか。/health/live:リブネス。プロセスが生きているか。/health/ready:レディネス。リクエストを受けられる状態か。ALBのヘルスチェックに使うならこれが基本です。
ヘルス/メトリクスの公開はデフォルトで無効なため、KC_HEALTH_ENABLED=true(および必要に応じ管理インターフェイス設定)で有効化します。ALBのヘルスチェックポートを9000、パスを/health/readyに設定するのが素直です。アプリのトラフィックは8080/8443、ヘルスは9000、と経路が分かれる点に注意してください。クラスタ全体の疎通確認にはロードバランサ経由の /lb-check の考え方も公式ガイドで示されています。
/health/readyはDB接続なども含めた準備状態を見ます。DBが一時的に不調だと全ノードが同時にunhealthy判定され、ALBが全ターゲットを切り離して完全停止する、という連鎖が起き得ます。DBのMulti-AZフェイルオーバー時間とヘルスチェックの閾値(間隔・回数)のバランスを、意図的に設計しておくと事故を防げます。08まとめ — どこまでやるかを要件から逆算する
AWS上のKeycloak HAは、「ALBの前段」よりも「Infinispanクラスタ形成とセッションキャッシュ」の理解で成否が決まります。実務的な優先順位はこうです。
- まずプロキシヘッダとホスト名を正しく設定し、リダイレクト/issuerを安定させる。
- JDBC_PING(jdbc-ping)でクラスタを組み、SGで7800/57800をノード間開放する。ここがAWS固有の最大の落とし穴。
- 複数AZの単一クラスタを基本形とし、DBはMulti-AZ。多くの要件はこれで満たせる。
- 単一ノード障害でも再ログインさせたいならセッションキャッシュのowner数を検討。sticky sessionは性能のために有効化推奨。
- ヘルスチェックは9000番の
/health/ready。DB連動の同時unhealthyに注意。 - リージョン障害まで見るなら、26系公式が推奨する2サイト・アクティブ-アクティブのマルチサイトへ。外部Infinispan・グローバルLB・2サイト上限といった制約を理解したうえで採用する。
EMWではKeycloakによるエンタープライズ認証基盤の実構築を通じ、これらの構成判断を要件から逆算して行ってきました。具体的な事例は導入事例もご覧ください。
—参考(一次情報)
- Keycloak — High Availability introduction(単一クラスタ/マルチサイトの前提)
- Keycloak 26.0.0 released(マルチサイトのアクティブ-アクティブ対応、LB blueprintのGlobal Accelerator化)
- Keycloak — Configuring distributed caches(jdbc-ping、ポート7800/57800、cache-stack)
- Keycloak — Configuring a reverse proxy(KC_PROXY_HEADERS、sticky session、AUTH_SESSION_ID)
- Keycloak — Tracking instance status with health checks(9000番/health/ready等)