Skip to content

필사 모드: Keycloak Kubernetes HA 運用 — Infinispan クラスタリングと無停止デプロイ

日本語
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

はじめに

SSO サーバーは組織のすべてのサービスが依存する単一の入り口です。Keycloak が落ちればログインが止まり、ログインが止まれば事実上の全社障害です。だからこそ Keycloak 運用の中心課題は常に高可用性(HA)でした。

幸い、2026 年の Keycloak 26.x は HA 運用の難易度を大きく下げました。persistent user sessions がデフォルトになったことで「再起動すると全ユーザーがログアウト」という問題が消え、26.6 の zero-downtime rolling patch によりパッチアップグレード時の無停止デプロイが公式にサポートされます。本記事では Kubernetes 上で Keycloak HA クラスタを構築・運用する全工程を扱います。

- Operator vs Helm のデプロイ方式の選択

- Infinispan キャッシュ構造と JGroups DNS_PING ディスカバリ

- persistent user sessions の意味と動作

- DB 選択、コネクションプール、sticky session 論争の整理

- multi-site(cross-DC)Active-Active 構成

- リソースサイジング、JVM チューニング、ヘルスチェック

- 障害シナリオ別の対応マニュアル

デプロイ方式の選択 — Operator vs Helm

Kubernetes に Keycloak を載せる代表的な方法は、公式 Operator とコミュニティ Helm チャート(主に Bitnami または codecentric)です。

| 項目 | Keycloak Operator (公式) | Helm チャート (コミュニティ) |

| --- | --- | --- |

| メンテナンス主体 | Keycloak プロジェクト公式 | コミュニティ/ベンダー |

| 抽象化レベル | Keycloak CR で宣言 | values.yaml で詳細制御 |

| 26.6 rolling patch 自動化 | サポート (update strategy) | 手動構成が必要 |

| realm import | KeycloakRealmImport CR | 初期化スクリプト |

| カスタムイメージ | サポート (推奨パターン) | サポート |

| 細かい Pod 制御 | 限定的 (podTemplate で補完) | 自由 |

| 推奨対象 | 標準構成、運用自動化重視 | 非標準トポロジ、既存 Helm パイプライン |

新規構築なら公式 Operator を推奨します。バージョンアップグレードの自動化と 26.6 の無停止パッチ戦略が Operator に組み込まれているからです。Operator のインストールは次のとおりです。

kubectl create namespace keycloak

kubectl apply -n keycloak \

-f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.6.2/kubernetes/keycloaks.k8s.keycloak.org-v1.yml

kubectl apply -n keycloak \

-f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.6.2/kubernetes/keycloakrealmimports.k8s.keycloak.org-v1.yml

kubectl apply -n keycloak \

-f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.6.2/kubernetes/kubernetes.yml

Keycloak CR の実践例です。

apiVersion: k8s.keycloak.org/v2alpha1

kind: Keycloak

metadata:

name: keycloak

namespace: keycloak

spec:

instances: 3

image: registry.example.com/idp/keycloak-custom:26.6.2

startOptimized: true

db:

vendor: postgres

host: keycloak-db.database.svc.cluster.local

port: 5432

database: keycloak

usernameSecret:

name: keycloak-db-secret

key: username

passwordSecret:

name: keycloak-db-secret

key: password

poolMinSize: 10

poolInitialSize: 10

poolMaxSize: 30

hostname:

hostname: sso.example.com

strict: true

http:

httpEnabled: true

proxy:

headers: xforwarded

additionalOptions:

- name: log-console-output

value: json

- name: event-metrics-user-enabled

value: "true"

resources:

requests:

cpu: "1"

memory: 1500Mi

limits:

memory: 3Gi

update:

strategy: Auto

Infinispan キャッシュ構造 — セッションと認証状態の保管庫

Keycloak クラスタの心臓部は組み込みの [Infinispan](https://infinispan.org/documentation/) キャッシュです。ノード間の状態共有はすべてここで行われます。主要キャッシュを分類すると次のとおりです。

| キャッシュ名 | 種類 | 用途 | 26+ のデフォルト動作 |

| --- | --- | --- | --- |

| realms, users | local | DB エンティティの読み取りキャッシュ | ノードごとにローカル、invalidation メッセージで同期 |

| authorization | local | 認可ポリシーキャッシュ | ノードごとにローカル |

| sessions, clientSessions | distributed | ログインセッション | DB 永続化 + キャッシュ |

| offlineSessions | distributed | オフラインセッション | DB 永続化 + キャッシュ |

| authenticationSessions | distributed | 進行中の認証 (ログインフォーム段階) | クラスタ分散 |

| loginFailures | distributed | ブルートフォースカウンタ | クラスタ分散 |

| work | replicated | ノード間 invalidation の伝播 | 全ノード複製 |

| actionTokens | distributed | メールリンクなどワンタイムトークン | クラスタ分散 |

構造を図で見ると次のようになります。

+-----------------+ +-----------------+ +-----------------+

| Keycloak Pod 1 | | Keycloak Pod 2 | | Keycloak Pod 3 |

| | | | | |

| local: realms, | | local: realms, | | local: realms, |

| users | | users | | users |

| | | | | |

| distributed: | | distributed: | | distributed: |

| sessions(o2) <----> sessions(o2) <----> sessions(o2) |

| authSessions | | authSessions | | authSessions |

| | | | | |

| replicated: | | replicated: | | replicated: |

| work <----> work <----> work |

+--------+--------+ +--------+--------+ +--------+--------+

| | |

+----------+----------+----------+----------+

| JGroups (gossip) |

v v

+-------------+ +--------------+

| PostgreSQL | | DNS headless |

| (セッション | | service |

| 永続化) | | (DNS_PING) |

+-------------+ +--------------+

distributed キャッシュはデフォルトで owners 数が 2 で、一つのエントリが二つのノードに複製されます。つまりノードが一台落ちてもセッションデータは生きています。同時に二台失うとキャッシュ上のデータは失われる可能性がありますが、26 からはセッションが DB にも永続化されるため復旧が可能です。

JGroups DNS_PING — Kubernetes でのノードディスカバリ

Infinispan のクラスタメンバーシップは JGroups が担当します。Kubernetes ではマルチキャストが塞がれているため、DNS ベースのディスカバリ(DNS_PING)を使用します。動作原理はシンプルです。

1. headless Service がすべての Keycloak Pod の IP を DNS A レコードとして公開

2. 各ノードが起動時にその DNS 名を照会してピア一覧を取得

3. JGroups が 7800 ポートでクラスタを形成

Operator を使えば自動構成されますが、手動で構成する場合は次のとおりです。

apiVersion: v1

kind: Service

metadata:

name: keycloak-discovery

namespace: keycloak

spec:

clusterIP: None

publishNotReadyAddresses: true

selector:

app: keycloak

ports:

- name: jgroups

port: 7800

targetPort: 7800

Keycloak 起動オプション (StatefulSet/Deployment の環境変数として)

KC_CACHE=ispn

KC_CACHE_STACK=kubernetes

JAVA_OPTS_APPEND=-Djgroups.dns.query=keycloak-discovery.keycloak.svc.cluster.local

publishNotReadyAddresses を有効にする理由は、Pod が readiness 前の段階でもクラスタに参加しなければ、起動中のセッションリバランシングが正常に動作しないためです。クラスタ形成の確認はログで行います。

kubectl logs -n keycloak keycloak-0 | grep "ISPN000094"

ISPN000094: Received new cluster view ... (3) [keycloak-0-..., keycloak-1-..., keycloak-2-...]

26.x からは JGroups トラフィックの TLS 暗号化がデフォルトで有効になり(Operator デプロイ時)、ノード間のセッションデータが平文で流れることはありません。

Persistent User Sessions — 26 のゲームチェンジャー

Keycloak 24 までオンラインセッションは純粋なインメモリ(Infinispan)でした。全体再起動や複数ノードの同時障害ですべてのユーザーがログアウトされる構造でした。Keycloak 26 から persistent-user-sessions 機能がデフォルトで有効になり、次のことが変わりました。

- すべての user session / client session が作成時点で DB に記録されます。

- Infinispan はホットデータキャッシュの役割に降格し、真実の源泉は DB になります。

- クラスタ全体の再起動後もユーザーはログイン状態を維持します。

- メモリ使用量が大きく減ります(セッション全体をメモリに保持する必要がない)。

代償は DB 書き込み負荷の増加です。ログイン/ログアウト/refresh のたびに DB 書き込みが発生するため、ログイン集中シナリオ(朝 9 時の出勤時間)の DB IOPS を見積もりに反映する必要があります。無効化は可能ですが(features から除外)、26 の運用モデルは永続セッションを前提に設計されているため、特別な理由がなければデフォルトの維持を推奨します。

DB 選択とコネクションプール

| 項目 | 推奨 | 理由 |

| --- | --- | --- |

| DB エンジン | PostgreSQL 15+ | 公式パフォーマンステストの基準、Aurora PostgreSQL 検証済み |

| 分離レベル | READ COMMITTED | デフォルト、変更不要 |

| プールサイズ | ノードあたり max 30 前後 | 過大なプールは DB 負荷を増やすだけ |

| HA | Patroni / RDS Multi-AZ / Aurora | DB が SPOF にならないように |

| プール見積もり | ピーク同時リクエスト基準 | ログイン TPS x 平均クエリ数を考慮 |

コネクションプールの公式は単純ではありませんが、経験則として「ログイン 100 TPS あたりノードごとにプール 10-15」から始め、モニタリング(agroal メトリクス)で調整します。プールが枯渇するとログイン遅延ではなくログイン失敗に直結するため、db-pool 関連メトリクスにアラートを設定すべきです。

Keycloak CR 内のコネクションプール設定部分

db:

poolMinSize: 10

poolInitialSize: 10

poolMaxSize: 30

additionalOptions:

- name: transaction-xa-enabled

value: "false"

Sticky Session は必要か

結論から: 26 基準で必須ではありませんが、依然として有益です。

- authenticationSessions(ログイン進行状態)は distributed キャッシュなので、どのノードにリクエストが行っても処理可能です。

- ただし同じノードに継続的にルーティングされると owner ノード直行の確率が高まり、ノード間 RPC が減って遅延が改善します。

- Keycloak は AUTH_SESSION_ID クッキーにノード情報をエンコードしており、これを活用する LB(例: ingress-nginx の session affinity)は自然に sticky 動作をします。

apiVersion: networking.k8s.io/v1

kind: Ingress

metadata:

name: keycloak

namespace: keycloak

annotations:

nginx.ingress.kubernetes.io/affinity: "cookie"

nginx.ingress.kubernetes.io/session-cookie-name: "KC_ROUTE"

nginx.ingress.kubernetes.io/proxy-buffer-size: "128k"

spec:

ingressClassName: nginx

rules:

- host: sso.example.com

http:

paths:

- path: /

pathType: Prefix

backend:

service:

name: keycloak-service

port:

number: 8080

tls:

- hosts: [sso.example.com]

secretName: sso-tls

proxy-buffer-size の拡大は実務必須の Tips です。Keycloak のレスポンスヘッダー(特にトークンを含むリダイレクト)がデフォルトバッファを超えて 502 が発生するケースがよくあります。

Multi-Site (Cross-DC) Active-Active

26.x の公式 multi-site アーキテクチャは二サイトの Active-Active をサポートします。中核となる構成要素は次のとおりです。

Site A (eu-west-1) Site B (eu-central-1)

+------------------------+ +------------------------+

| Keycloak (3 pods) | | Keycloak (3 pods) |

| | | | | |

| Infinispan (external) | <-----> | Infinispan (external) |

| cross-site replication| RELAY2 | cross-site replication|

+-----------+------------+ +-----------+------------+

| |

+----------------+-----------------+

|

+----------v-----------+

| Aurora Global DB |

| (writer: Site A) |

+----------------------+

^

+----------------+----------------+

| Global LB (Route53 / |

| ヘルスチェックベース failover)|

+---------------------------------+

- セッション同期: 外部 Infinispan クラスタの cross-site replication(RELAY2)

- DB: Aurora Global Database のような単一 writer のグローバル DB

- ルーティング: グローバル LB がヘルスチェックに基づき二サイトにトラフィックを分配

- persistent user sessions のおかげで、サイト間キャッシュ同期が失敗しても DB 経由での復旧が可能

multi-site は運用の複雑さが非常に高いため、RTO/RPO 要件が本当に必要な場合にのみ導入し、その前に単一リージョン複数 AZ + 堅牢なバックアップ/リストア手順で十分かをまず検討するのがよいでしょう。詳細は[公式 HA ガイド](https://www.keycloak.org/high-availability/introduction)を参照してください。

26.6 Zero-Downtime Rolling Patch

26.6 以前は、どのバージョンアップグレードでもキャッシュプロトコル非互換の可能性のため、クラスタ全体を落として上げ直す recreate 戦略が基本でした。26.6 からはパッチリリース間(例: 26.6.0 から 26.6.2)の互換性が保証され、rolling update が公式サポートされます。

Keycloak CR

spec:

update:

strategy: Auto # 互換性を自動判定: 可能なら rolling、不可なら recreate

Auto 戦略の動作は次のとおりです。

1. Operator が新しいイメージで update-compatibility 検査ジョブを実行

2. キャッシュ/設定が互換なら Pod を一台ずつ入れ替え(無停止)

3. 非互換なら recreate(全体再起動、persistent sessions でログインは維持)

手動で互換性を検査することもできます。

既存バージョンでメタデータを生成

bin/kc.sh update-compatibility metadata --file=/tmp/metadata.json

新バージョンで検査

bin/kc.sh update-compatibility check --file=/tmp/metadata.json

echo $? # 0 なら rolling 可能

リソースサイジングと JVM チューニング

公式サイジングガイドに基づく出発点は次のとおりです。

| 負荷指標 | 1 vCPU あたりの処理量(概算) | 備考 |

| --- | --- | --- |

| パスワードログイン | 毎秒 15 回前後 | ハッシュコスト(argon2)に大きく左右 |

| client credentials グラント | 毎秒 120 回前後 | 最も軽い処理 |

| refresh token | 毎秒 120 回前後 | DB 書き込みを含む |

| メモリ(非ヒープ含む) | Pod あたり 1.25-3Gi | セッション数より realm/クライアント数の影響大 |

JVM メモリは 26 からコンテナメモリベースの比率算定がデフォルトです(デフォルトヒープ 70%)。明示的に制御するには:

additionalOptions: []

または環境変数で

JAVA_OPTS_KC_HEAP: "-XX:MaxRAMPercentage=70 -XX:InitialRAMPercentage=50"

resources:

requests:

cpu: "1"

memory: 1500Mi

limits:

memory: 3Gi

CPU limit は設定しないのが一般的な推奨です(スロットリングによる遅延スパイクの防止)。メモリ limit は OOMKill 防止のため、ヒープ+メタスペース+ネイティブの合算より余裕を持たせます。

ヘルスチェックと Startup Probe

Keycloak は管理ポート(デフォルト 9000)で health エンドポイントを提供します。

Deployment/StatefulSet を直接構成する場合

livenessProbe:

httpGet:

path: /health/live

port: 9000

periodSeconds: 10

failureThreshold: 3

readinessProbe:

httpGet:

path: /health/ready

port: 9000

periodSeconds: 10

failureThreshold: 3

startupProbe:

httpGet:

path: /health/started

port: 9000

periodSeconds: 5

failureThreshold: 60 # 最大 5 分の起動猶予

- started: 起動完了の判定。startup probe 専用で、マイグレーションが長いアップグレード直後を考慮し failureThreshold に余裕を持たせます。

- ready: DB 接続可否を含みます。DB の瞬断時に Pod が一斉に not-ready になり全面障害のように見える点を知っておくべきです。

- live: プロセス自体の生存。失敗時は再起動が発生するため保守的に設定します。

さらに PodDisruptionBudget と topologySpreadConstraints は HA の基本です。

apiVersion: policy/v1

kind: PodDisruptionBudget

metadata:

name: keycloak-pdb

namespace: keycloak

spec:

minAvailable: 2

selector:

matchLabels:

app: keycloak

障害シナリオ別の対応

シナリオ 1: ノード 1 台ダウン

- 症状: ほぼなし。distributed キャッシュ owners 2 でセッション保持、LB がトラフィックを再分配。

- 対応: Pod の自動再作成を確認。JGroups クラスタビューに再参加したかログで確認。

シナリオ 2: DB 瞬断 (failover 30 秒)

- 症状: 全ノードの readiness 失敗、ログイン/トークン発行が全面失敗。既発行トークンの検証への影響は少ない(署名検証はローカル)。

- 対応: DB failover の自動化確認が最優先。Keycloak は DB 復帰時に自動回復するため Pod の再起動は不要。むしろ liveness を敏感にしすぎて再起動の嵐にならないよう注意。

シナリオ 3: スプリットブレイン (ネットワークパーティション)

- 症状: クラスタが二グループに分かれ、それぞれビューを形成。ブルートフォースカウンタ/セッションの不整合の可能性。

- 対応: 26 のデフォルト設定はパーティション統合時に MERGE イベントで回復。persistent sessions のおかげでセッションデータは DB 基準に収束。パーティションが頻発するなら CNI/ノードネットワークの点検が根本対応。

シナリオ 4: 全体再起動 (災害復旧)

- 症状: 26 以前は全ユーザーログアウトでしたが、26+ では DB からセッションが復元されログインを維持。

- 対応: DB バックアップから復元する最悪シナリオに備え、realm export を別途保管(設定とデータの二重バックアップ)。

定期 realm export (CronJob での自動化を推奨)

bin/kc.sh export --dir /tmp/export --realm production --users different_files

シナリオ 5: ログイン集中 (出勤時間スパイク)

- 症状: CPU 飽和、パスワードハッシュ演算がボトルネック。

- 対応: HPA で水平スケールしますが、ハッシュコストが CPU を支配するためスケールアウトが直接的に効果あり。ただし DB コネクション総数が DB の上限を超えないようプール最大値を再計算。

apiVersion: autoscaling/v2

kind: HorizontalPodAutoscaler

metadata:

name: keycloak-hpa

namespace: keycloak

spec:

scaleTargetRef:

apiVersion: apps/v1

kind: StatefulSet

name: keycloak

minReplicas: 3

maxReplicas: 8

metrics:

- type: Resource

resource:

name: cpu

target:

type: Utilization

averageUtilization: 60

おわりに

Keycloak 26 時代の HA 運用は「Infinispan をなんとかなだめる作業」から「DB を中心に据えたごく普通のステートフルサービス運用」へとシンプルになりました。persistent user sessions と zero-downtime rolling patch がその転換点です。それでも JGroups ディスカバリ、コネクションプール見積もり、プローブチューニングといった基本は依然として運用者の仕事です。本記事の YAML 例を出発点として、必ず自分の環境の負荷テストで数値を検証してください。

次の記事では、Keycloak の機能そのものを拡張する SPI 開発(カスタム Authenticator、EventListener)を扱います。

参考資料

- [Keycloak High Availability ガイド](https://www.keycloak.org/high-availability/introduction)

- [Keycloak Operator インストールガイド](https://www.keycloak.org/operator/installation)

- [Keycloak サーバー設定ガイド](https://www.keycloak.org/server/configuration)

- [Keycloak キャッシュ設定ガイド](https://www.keycloak.org/server/caching)

- [Keycloak 26.6.0 リリース告知](https://www.keycloak.org/2026/04/keycloak-2660-released)

- [Keycloak リリースノート](https://www.keycloak.org/docs/latest/release_notes/index.html)

- [Infinispan 公式ドキュメント](https://infinispan.org/documentation/)

- [JGroups マニュアル](http://www.jgroups.org/manual5/index.html)

- [Kubernetes Probes 公式ドキュメント](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/)

- [OAuth 2.0 Security Best Current Practice (RFC 9700)](https://datatracker.ietf.org/doc/html/rfc9700)

- [Keycloak 公式ドキュメント](https://www.keycloak.org/documentation)

현재 단락 (1/311)

SSO サーバーは組織のすべてのサービスが依存する単一の入り口です。Keycloak が落ちればログインが止まり、ログインが止まれば事実上の全社障害です。だからこそ Keycloak 運用の中心課題は...

작성 글자: 0원문 글자: 11,920작성 단락: 0/311