Keycloak 高可用集群与 IAM 灾难恢复实战 | IDaaS Book
场景
你在生产环境跑着单节点的 Keycloak,业务方已经依赖它完成 SSO 登录。某天凌晨数据库挂了、节点 OOM 重启了,或者 Kubernetes Node 被 drain 了——所有依赖这套 IAM 完成认证的应用全部掉线。
你需要在故障发生前就把集群搭好,并且有一套经过演练的备份恢复流程。
本文不重复 Keycloak Kubernetes 生产部署 的完整 Operator 部署步骤,而是聚焦三个核心问题:
- 多节点集群怎么配才能不丢 Session、不断登录?
- 数据库备份怎么做到可验证恢复而不仅仅是"dump 了"?
- 故障发生时的恢复步骤和回滚路径是什么?
适用与不适用
| 适用 | 不适用 |
|---|---|
| Keycloak 24+ 基于 Quarkus 发行版 | 旧版 WildFly 发行版(集群配置不同) |
| 生产环境需要 HA 和灾难恢复 | 开发环境单节点即可 |
| Kubernetes(Operator/Helm)或裸机部署 | 刚接触 Keycloak、还未完成单节点部署 |
| PostgreSQL/MySQL 等外部数据库 | 内嵌 H2 数据库(不可用于集群或生产) |
集群架构概览
Keycloak 集群的核心依赖只有两个:共享数据库 + 节点发现机制。没有共享存储、没有共享 Session 文件。
graph TB
LB[负载均衡器<br/>Nginx/ALB/HAProxy]
subgraph KC1[Keycloak 节点 1]
KC1_APP[Keycloak 26.x<br/>Quarkus]
KC1_CACHE[InfiniSpan Cache<br/>Sessions/ClientSessions]
end
subgraph KC2[Keycloak 节点 2]
KC2_APP[Keycloak 26.x<br/>Quarkus]
KC2_CACHE[InfiniSpan Cache<br/>Sessions/ClientSessions]
end
DB[(PostgreSQL<br/>共享数据库)]
LB --> KC1_APP
LB --> KC2_APP
KC1_APP --> DB
KC2_APP --> DB
KC1_CACHE <-->|JGroups 分布式缓存| KC2_CACHE
关键理解:Keycloak 26.x 的会话数据分两类存放。用户会话与 client 会话默认落库(persistent-user-sessions 特性默认启用),认证会话(authenticationSessions)、action token、登录失败计数只存在 Infinispan 分布式缓存里。因此节点故障时在线会话可以从其他缓存副本或数据库恢复;但如果所有节点在同一瞬间丢失,进行中的认证流程、action token 和暴力破解计数会一起丢失,进行中的登录需要重来。
关于缓存层的深度调优——
cache-ispn.xml参数详解、传输栈选型对比、owners参数调优、Istio 服务网格兼容方案、常见缓存故障排错,参见 Keycloak 集群缓存深度调优与排错指南。需要容忍「整个 Kubernetes 集群 / 机房掉线」而不只是节点级故障时,单集群不够用,见 Keycloak 跨机房多集群高可用与 IAM 双活部署。
节点发现:JGroups 配置
Keycloak 使用 JGroups 做节点间通信和发现。Kubernetes 环境下推荐 DNS_PING;裸机推荐 JDBC_PING 或 TCPPING。
Kubernetes 环境(DNS_PING)
使用 Operator 部署时,只需在 CR 中指定 hostname 使 Pod 有稳定的 DNS 名。JGroups 默认使用 dns.DNS_PING 通过 Headless Service 发现对端。CR 里 spec.hostname 的完整字段(admin / strict / backchannelDynamic)与校验约束见
Keycloak Hostname v2 配置与 v1 选项迁移。
# 使用 Operator CR 的多节点配置要点
apiVersion: k8s.keycloak.org/v2beta1
kind: Keycloak
metadata:
name: keycloak
spec:
instances: 2 # 节点数
hostname:
hostname: sso.example.com
additionalOptions:
- name: cache
value: ispn # 使用 InfiniSpan 分布式缓存(默认)
- name: cache-stack
value: kubernetes # JGroups 使用 kubernetes stack当前 Keycloak CR 的
apiVersion是k8s.keycloak.org/v2beta1;v2alpha1自 26.6 起标记为废弃,虽然仍然被服务,但新配置应使用v2beta1(两个版本的 schema 目前没有差异,只需改 apiVersion)。
验证节点发现:
# 查看集群节点
kubectl -n keycloak exec deploy/keycloak -- \
kcadm.sh get serverinfo --fields members -r master 2>/dev/null || \
kubectl -n keycloak logs deploy/keycloak | grep "ISPN000094\|joined the cluster"日志中看到 Received new cluster view 和 ISPN000094 表示节点已加入集群。
裸机/Docker Compose 环境(JDBC_PING)
如果不用 Kubernetes,推荐 JDBC_PING——节点通过数据库中的 JGROUPSPING 表相互发现,无需额外服务。
# 启动参数(每个节点用相同数据库但不同 --db 配置即可)
bin/kc.sh start \
--cache=ispn \
--cache-stack=jdbc-ping \
--db=postgres \
--db-url=jdbc:postgresql://postgres:5432/keycloak \
--db-username=keycloak \
--db-password=***JDBC_PING 会自动在数据库中维护节点注册表,节点退出时自动清理。
Session 亲和性与负载均衡
多节点集群中,推荐启用 Session 亲和性(Sticky Session)。Keycloak 官方说明:用户会话在数据库和缓存中都可访问,但生产部署仍应尽量把用户路由到最初创建会话的节点,避免不必要的状态传输,改善 CPU、内存和网络利用率。
例外:启用
stateless特性的跨机房方案(multi-cluster v2)不在内存中保存会话数据,官方明确此时配置会话亲和性不再有性能收益,可以改为轮询分发。见 Keycloak 跨机房多集群高可用与 IAM 双活部署。
Nginx 配置
upstream keycloak {
ip_hash; # 基于客户端 IP 的会话保持
server keycloak-0.keycloak-headless:8443;
server keycloak-1.keycloak-headless:8443;
}
server {
listen 443 ssl;
server_name sso.example.com;
location / {
proxy_pass https://keycloak;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}Kubernetes Ingress 配置
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: keycloak
annotations:
nginx.ingress.kubernetes.io/affinity: "cookie"
nginx.ingress.kubernetes.io/session-cookie-name: "KEYCLOAK_SESSION"
nginx.ingress.kubernetes.io/session-cookie-path: "/"
nginx.ingress.kubernetes.io/session-cookie-max-age: "3600"
spec:
rules:
- host: sso.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: keycloak-service
port:
number: 8443为什么不推荐 ip_hash → 替代方案:
ip_hash在企业 NAT/代理环境下会将大量用户分配到同一节点,负载不均。nginx.ingress.kubernetes.io/affinity: "cookie"更可靠,但 Keycloak 本身有 Cookie(KEYCLOAK_SESSION、AUTH_SESSION_ID等),Ingress Cookie 层只是加速路由,不影响 Keycloak 自身 Cookie。
备份策略
Keycloak 需要备份的三类数据:
| 数据类型 | 存储位置 | 备份方式 | 频率建议 |
|---|---|---|---|
| 数据库 | PostgreSQL/MySQL | pg_dump / mysqldump | 每日全量 + 持续 WAL 归档 |
| Realm 导出 | Keycloak 管理控制台 | kc.sh export 或 Operator RealmImport | 每次重大配置变更后 |
| 自定义主题/SPI | 挂载的 Volume/ConfigMap | 纳入配置管理(Git/Helm) | 变更时触发 |
| Realm 配置声明(config as code) | Git 仓库 + Keycloak realm attributes | keycloak-config-cli 通过 Admin API 收敛,运行状态写回 realm attributes | 每次配置提交后 |
配置即代码工具与备份是互补关系,不能互相替代:keycloak-config-cli 只保证「Git 里声明的配置被应用过」,它不保存用户、会话、密钥,也不检测 Keycloak 侧的手工漂移。灾难恢复时仍然需要上面那份 kc.sh export 的 Realm JSON。两者的分工与误删防护见
IAM 配置即代码:keycloak-config-cli 声明式 Realm 管理与误删防护。
数据库备份(含验证)
#!/bin/bash
# 备份并验证
BACKUP_FILE="/backup/keycloak-$(date +%Y%m%d-%H%M).sql.gz"
pg_dump -h $DB_HOST -U keycloak -d keycloak | gzip > "$BACKUP_FILE"
# 验证备份可恢复(在独立实例上)
if [ "${VERIFY:-0}" = "1" ]; then
zcat "$BACKUP_FILE" | psql -h verify-db -U keycloak -d keycloak_verify > /dev/null 2>&1
echo "Backup verified: $BACKUP_FILE"
fi关键提醒:只 dump 不验证 = 没有备份。建议每周至少一次自动恢复到独立验证实例。
Keycloak 配置导出
# 导出整个 Realm(含 client、role、group、identity provider 配置)
bin/kc.sh export --realm myrealm --dir /backup/realms/ --users realm_file
# 输出:/backup/realms/myrealm-realm.json导出的 JSON 文件可以直接用 Operator 的 KeycloakRealmImport 恢复,也可以手动导入。
恢复流程(重要)
按顺序执行,每步都验证:
# 步骤 1:停止所有 Keycloak 节点写入
kubectl -n keycloak scale deploy/keycloak --replicas=0 # K8s
# 或 systemctl stop keycloak # 裸机
# 步骤 2:恢复数据库
zcat /backup/keycloak-20260709-0100.sql.gz | psql -h $DB_HOST -U keycloak -d keycloak
# 验证恢复
psql -h $DB_HOST -U keycloak -d keycloak -c "SELECT COUNT(*) FROM REALM;"
# 步骤 3:如果需要,导入 Realm 配置
# 方式 A:通过 Operator(K8s)
kubectl -n keycloak apply -f realm-import.yaml
# 方式 B:导入数据库后重启节点自动使用已有 Realm 配置
# 步骤 4:启动集群
kubectl -n keycloak scale deploy/keycloak --replicas=2
# 步骤 5:验证集群就绪
kubectl -n keycloak rollout status deploy/keycloak
kubectl -n keycloak logs deploy/keycloak | grep "ISPN000094"
# 步骤 6:功能验证
curl -s https://sso.example.com/realms/master/.well-known/openid-configuration | jq .issuer
curl -s -X POST https://sso.example.com/realms/myrealm/protocol/openid-connect/token \
-d 'client_id=test&grant_type=password&username=test&password=test' | jq '.access_token'恢复后如果打不开管理控制台,通常不是数据库恢复失败,而是恢复点里没有可用的管理员凭据(例如备份取自密码轮换之前,或强化认证凭据在恢复后不匹配)。这种情形的恢复路径是停节点后执行 kc.sh bootstrap-admin,而不是重新恢复数据库,具体见
Keycloak 管理员账号进不去:bootstrap-admin 恢复与 26.x 变量变更。
同一套流程也是升级回滚的唯一路径:Keycloak 升级后数据库 schema 只能前滚,退回旧版本要恢复旧安装再按上面的步骤恢复数据库;升级前必须先判定这次能不能滚动、以及哪些变更会强制停机,见 Keycloak 升级与零停机滚动更新。
常见错误与排错
| 症状 | 根因 | 排错命令 |
|---|---|---|
| 节点启动后孤岛运行(只有自己) | JGroups 发现机制配置错误 | grep -i "ISPN000094|JGRP" 看日志:无 “Received new cluster view” 说明未发现对端 |
| 登录后跳转到另一个节点就掉登录 | Session 亲和性未配置 | 检查 Ingress/LB 是否配置 Sticky Session |
InfiniSpan org.infinispan.remoting.transport.jgroups.SuspectException | 网络分区或节点间端口不通 | 确认 JGroups 端口(默认 7800)在节点间互通:nc -zv <peer-ip> 7800 |
| 恢复数据库后 Realm 不显示 | 缓存未刷新 | 启动时加 --spi-connections-infinispan-quarkus-cache-remote-max-idle=1 清理缓存,或重启后等待缓存同步 |
备份恢复后用户登录报 invalid_grant | Session/Token 状态不一致 | 恢复的是数据库快照,快照时间点之后的会话与 token 状态丢失是正常的——用户重新登录即可;也可用 kc.sh export/import 配合 --users skip 保留数据一致性 |
| 集群节点全部重启后缓存的认证会话为空 | authenticationSessions / actionTokens / loginFailures 只在分布式缓存中,无持久化 | 正常行为:用户会话与 client 会话在数据库中,用户不必重新登录;但进行中的认证流程会中断,需要重新发起登录 |
Kubernetes DNS_PING 报 java.net.UnknownHostException | Headless Service 未创建或 DNS 未就绪 | 检查 Service: kubectl get svc keycloak-headless;确认 Pod 内 nslookup keycloak-headless 可解析 |
生产检查清单
- 数据库使用外部 PostgreSQL(非 H2),配置了主从或定期备份
- 备份脚本有验证步骤,定期做恢复演练(至少每月一次)
- 集群至少 2 个节点,分布于不同宿主机/可用区
- 负载均衡器配置了 Session 亲和性
- JGroups 节点发现已验证(启动日志中有 cluster view)
- PodDisruptionBudget 已配置(K8s)
- 监控已覆盖:节点数、JGroups 状态、数据库连接数、登录延迟(参考 Keycloak Prometheus 监控)
- 有明确的恢复 Runbook(步骤、负责人、预期时长)
- 节点间网络延迟 < 5ms(InfiniSpan 分布式缓存对网络延迟敏感)
- Keycloak 版本、Operator 版本、CRD 版本保持一致
回滚方式
灾难恢复有两种回滚场景:
场景 A:恢复后发现问题,回滚到上一个备份
# 1. 停服
kubectl -n keycloak scale deploy/keycloak --replicas=0
# 2. 恢复到上一个备份
zcat /backup/keycloak-20260708-0100.sql.gz | psql -h $DB_HOST -U keycloak -d keycloak
# 3. 重新启动集群(缓存为空,从数据库重新加载)
kubectl -n keycloak scale deploy/keycloak --replicas=2场景 B:数据库本身完好,只需重建 Keycloak 节点
# 用户会话与 client 会话在数据库中,认证会话在分布式缓存里;
# 逐个重建节点不会中断已登录用户,只会影响恰好在该节点上进行中的认证流程
kubectl -n keycloak rollout restart deploy/keycloak重要:不要在生产环境中直接 DELETE Keycloak CR 再重建——Operator 会删除关联的 Secrets/Ingress 等资源。需要重建时,使用 rollout restart 或逐个替换 Pod。
延伸阅读
- Keycloak 生产部署 — Operator、Helm 与高可用 — 完整 Operator 部署步骤
- Keycloak Prometheus 监控指标详解 — 集群节点监控和告警配置
- Keycloak 内部架构 — Realms、Clients、Users、Roles — 理解集群各组件关系
- Keycloak Redis 外部会话缓存 — 用 Redis 替代 Infinispan 实现无状态节点
- Keycloak 跨机房多集群高可用与 IAM 双活部署 — 容忍整个集群/机房故障的 multi-cluster v2 方案与 v1 迁移
- IDaaS 性能与扩展性 — 缓存策略与容量规划