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 chart(主要是 Bitnami 或 codecentric)。

项目Keycloak Operator(官方)Helm chart(社区)
维护主体Keycloak 项目官方社区/厂商
抽象层级用 Keycloak CR 声明用 values.yaml 做精细控制
26.6 rolling patch 自动化支持(update strategy)需手动配置
realm importKeycloakRealmImport 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 缓存。节点间的状态共享全都在这里发生。主要缓存分类如下。

缓存名称类型用途26+ 默认行为
realms, userslocalDB 实体的读缓存按节点本地,通过 invalidation 消息同步
authorizationlocal授权策略缓存按节点本地
sessions, clientSessionsdistributed登录会话DB 持久化 + 缓存
offlineSessionsdistributed离线会话DB 持久化 + 缓存
authenticationSessionsdistributed进行中的认证(登录表单阶段)集群分布
loginFailuresdistributed暴力破解计数器集群分布
workreplicated节点间 invalidation 的传播全节点复制
actionTokensdistributed邮件链接等一次性令牌集群分布

用图来看这套结构如下。

        +-----------------+   +-----------------+   +-----------------+
        |  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 |
                     | (sessions   |      | 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 负担
HAPatroni / 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 cookie,利用它的 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 是实务上的必备技巧。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 指南

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-3Girealm/客户端数量的影响大于会话数

从 26 开始,JVM 内存默认按容器内存的比例计算(默认堆 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:单节点宕机

  • 症状:几乎没有。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)。

参考资料

현재 단락 (1/311)

SSO 服务器是组织内所有服务共同依赖的单一入口。Keycloak 一旦宕机,登录就会停止;登录停止,实际上就等同于全公司级故障。因此 Keycloak 运维的核心课题始终是高可用性(HA)。

작성 글자: 0원문 글자: 10,601작성 단락: 0/311