在 Kubernetes 上以 HA 模式執行 Keycloak

在 Kubernetes 上以 HA 模式執行 Keycloak

TL博士:會有Keycloak這個開源門禁系統的介紹,分析內部結構,配置細節。

簡介和主要思想

在本文中,我們將了解在 Kubernetes 上部署 Keycloak 叢集時要記住的基本想法。

如果您想了解更多關於Keycloak的信息,請參考文末的連結。 為了更沉浸於實踐,你可以學習 我們的儲存庫 具有實現本文主要思想的模組(啟動指南在那裡,本文將提供設備和設定的概述, 約譯員).

Keycloak 是一個用 Java 寫的綜合系統,建構在應用程式伺服器之上 野蠅。 簡而言之,它是一個授權框架,為應用程式使用者提供聯合和 SSO(單一登入)功能。

我們邀請您閱讀官方 網站維基百科 以便詳細了解。

啟動Keycloak

Keycloak 需要兩個持久性資料來源才能運作:

  • 用於儲存已建立資料的資料庫,例如使用者信息
  • Datagrid緩存,用於緩存資料庫中的數據,以及儲存一些短暫且頻繁變化的元數據,例如用戶會話。 實施的 無限跨度,通常比資料庫快得多。 但無論如何,Infinispan 中保存的資料都是短暫的——並且在叢集重新啟動時不需要將其保存在任何地方。

Keycloak 有四種不同的工作模式:

  • 正常 - 唯一一個進程,透過檔案配置 獨立.xml
  • 規則簇 (高可用性選項)-所有進程必須使用相同的配置,並且必須手動同步。 設定儲存在檔案中 獨立-ha.xml,此外您還需要對資料庫和負載平衡器進行共享存取。
  • 域集群 - 隨著叢集的成長,以正常模式啟動叢集很快就會成為例行公事且無聊的任務,因為每次配置變更時,都必須在每個叢集節點上進行所有變更。 網域操作模式透過設定一些共用儲存位置並發布配置來解決此問題。 這些設定儲存在檔案中 域.xml
  • 資料中心之間的複製 — 如果您想在多個資料中心的叢集中執行 Keycloak,通常是在不同的地理位置。 在此選項中,每個資料中心將擁有自己的 Keycloak 伺服器叢集。

在本文中,我們將詳細考慮第二個選擇,即 規則簇,我們還將稍微討論一下資料中心之間的複製主題,因為在 Kubernetes 中運行這兩個選項是有意義的。 幸運的是,在 Kubernetes 中同步幾個 pod(Keycloak 節點)的設定是沒有問題的,所以 域簇 做起來不會很難。

另請注意這個詞 本文的其餘部分僅適用於一組協同工作的 Keycloak 節點,無需提及 Kubernetes 叢集。

常規Keycloak簇

要在此模式下執行 Keycloak,您需要:

  • 配置外部共享資料庫
  • 安裝負載平衡器
  • 擁有支援 IP 多播的內部網絡

我們不會討論設定外部資料庫,因為這不是本文的目的。 假設某個地方有一個正在工作的資料庫 - 並且我們有一個到它的連接點。 我們只需將此數據添加到環境變數中即可。

為了更了解 Keycloak 在故障轉移 (HA) 叢集中的工作原理,了解它在多大程度上依賴 Wildfly 的叢集功能非常重要。

Wildfly 使用多個子系統,其中一些用作負載平衡器,一些用於容錯。 負載平衡器在叢集節點過載時確保應用程式可用性,容錯器即使在某些叢集節點發生故障時也確保應用程式可用性。 其中一些子系統:

  • mod_cluster:與 Apache 一起作為 HTTP 負載平衡器工作,預設依賴 TCP 多播來查找主機。 可用外部平衡器代替。

  • infinispan:使用 JGroups 通道作為傳輸層的分散式快取。 此外,它還可以使用 HotRod 協定與外部 Infinispan 叢集通訊以同步快取內容。

  • jgroups:基於JGroups通道為高可用服務提供群組通訊支援。 命名管道允許叢集中的應用程式實例連接成群組,以便通訊具有可靠性、有序性和對故障的敏感度等屬性。

負載平衡器

在 Kubernetes 叢集中安裝平衡器作為入口控制器時,請務必記住以下事項:

Keycloak假設透過HTTP連接到認證伺服器的客戶端的遠端位址是客戶端電腦的真實IP位址。 平衡器和入口設定應正確設定 HTTP 標頭 X-Forwarded-For и X-Forwarded-Proto,並保存原來的標題 HOST。 最新版本 ingress-nginx (>0.22.0) 預設情況下禁用此功能

啟動標誌 proxy-address-forwarding 透過設定環境變數 PROXY_ADDRESS_FORWARDING в true 讓 Keycloak 知道它在代理後面工作。

您還需要啟用 粘性會話 在入口處。 Keycloak 使用分散式 Infinispan 快取來儲存與目前驗證會話和使用者會話相關的資料。 預設情況下,快取由單一擁有者運行,換句話說,該特定會話儲存在叢集中的某個節點上,其他節點如果需要存取該會話,則必須遠端查詢它。

具體來說,與文件相反,附加名為 cookie 的會話對我們來說不起作用 AUTH_SESSION_ID。 Keycloak 有一個重定向循環,因此我們建議為黏性會話選擇不同的 cookie 名稱。

Keycloak 還附加了首先回應的節點的名稱 AUTH_SESSION_ID,並且由於高可用版本中的每個節點都使用相同的資料庫,因此每個節點 必須有 用於管理事務的單獨且唯一的節點識別碼。 建議放入 JAVA_OPTS 參數 jboss.node.name и jboss.tx.node.id 每個節點都是唯一的 - 例如,您可以輸入 pod 的名稱。 如果您輸入 pod 名稱,請不要忘記 jboss 變數的 23 個字元限制,因此最好使用 StatefulSet 而不是 Deployment。

另一個 rake - 如果 pod 被刪除或重新啟動,其快取就會遺失。 考慮到這一點,值得將所有快取的快取所有者數量設定為至少兩個,以便保留快取的副本。 解決方案是運行 野蠅腳本 啟動pod時,將其放入目錄中 /opt/jboss/startup-scripts 在容器中:

腳本內容

embed-server --server-config=standalone-ha.xml --std-out=echo
batch

echo * Setting CACHE_OWNERS to "${env.CACHE_OWNERS}" in all cache-containers

/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions:write-attribute(name=owners, value=${env.CACHE_OWNERS:1})
/subsystem=infinispan/cache-container=keycloak/distributed-cache=authenticationSessions:write-attribute(name=owners, value=${env.CACHE_OWNERS:1})
/subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens:write-attribute(name=owners, value=${env.CACHE_OWNERS:1})
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions:write-attribute(name=owners, value=${env.CACHE_OWNERS:1})
/subsystem=infinispan/cache-container=keycloak/distributed-cache=clientSessions:write-attribute(name=owners, value=${env.CACHE_OWNERS:1})
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineClientSessions:write-attribute(name=owners, value=${env.CACHE_OWNERS:1})
/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures:write-attribute(name=owners, value=${env.CACHE_OWNERS:1})

run-batch
stop-embedded-server

然後設定環境變數的值 CACHE_OWNERS 到所需要的。

支援 IP 多播的專用網絡

如果您使用 Wea​​venet 作為 CNI,多播將立即起作用 - 並且您的 Keycloak 節點一啟動就會看到彼此。

如果您的 Kubernetes 叢集中沒有 ip 多播支持,您可以將 JGroups 設定為與其他協定一起尋找節點。

第一個選項是使用 KUBE_DNS,它使用 headless service 若要尋找 Keycloak 節點,您只需向 JGroups 傳遞將用於尋找節點的服務的名稱即可。

另一種選擇是使用該方法 KUBE_PING,配合API搜尋節點(需配置 serviceAccount 有權利 list и get,然後配置 pod 以使用它 serviceAccount).

JGroups查找節點的方式是透過設定環境變數來配置的 JGROUPS_DISCOVERY_PROTOCOL и JGROUPS_DISCOVERY_PROPERTIES。 為 KUBE_PING 您需要透過詢問來選擇 Pod namespace и labels.

️ 如果您使用多播並在一個 Kubernetes 叢集中執行兩個或多個 Keycloak 叢集(假設一個在命名空間中) production, 第二 - staging) - 一個 Keycloak 叢集的節點可以加入另一個叢集。 確保透過設定變數為每個叢集使用唯一的多重播送位址jboss.default.multicast.address и jboss.modcluster.multicast.address в JAVA_OPTS.

資料中心之間的複製

在 Kubernetes 上以 HA 模式執行 Keycloak

Связь

Keycloak為Keycloak節點組成的Keycloack叢集所在的每個資料中心使用多個獨立的Infinispan快取叢集。 但不同資料中心的Keycloak節點之間沒有差別。

Keycloak 節點使用外部 Java 資料網格(Infinispan 伺服器)進行資料中心之間的通訊。 通訊按照協定進行 Infinispan 熱棒.

Infinispan 快取必須配置以下屬性 remoteStore,以便資料可以遠端儲存(在另一個資料中心, 約譯員)快取. JDG伺服器之間有獨立的infinispan集群,以便現場資料儲存在JDG1上 site1 將在現場複製到JDG2 site2.

最後,接收 JDG 伺服器透過客戶端連線通知其叢集的 Keycloak 伺服器,這是 HotRod 協定的功能。 Keycloak 節點開啟 site2 更新其 Infinispan 緩存,並且特定用戶會話也可以在 Keycloak 節點上使用 site2.

對於某些緩存,也可以不進行備份並完全避免透過Infinispan伺服器寫入資料。 為此,您需要刪除該設置 remote-store 特定的 Infinispan 快取(在檔案中 獨立-ha.xml),之後是一些具體的 replicated-cache Infinispan 伺服器端也不再需要。

設定快取

Keycloak中有兩種類型的快取:

  • 當地的。 它位於資料庫旁邊,用於減少資料庫的負載,並減少回應延遲。 這種類型的快取儲存領域、客戶端、角色和用戶元資料。 即使快取是 Keycloak 叢集的一部分,這種類型的快取也不會被複製。 如果快取中的條目發生更改,則有關更改的訊息將發送到叢集中的其餘伺服器,之後該條目將從快取中排除。 請參閱說明 work 有關該過程的更詳細說明,請參閱下文。

  • 複製。 處理使用者會話、離線令牌,也監視登入錯誤以偵測密碼網路釣魚嘗試和其他攻擊。 這些快取中儲存的資料是暫時的,僅儲存在 RAM 中,但可以跨叢集複製。

Infinispan 緩存

會話 - Keycloak 中的一個概念,單獨的快取稱為 authenticationSessions,用於儲存特定使用者的資料。 來自這些快取的請求通常是瀏覽器和 Keycloak 伺服器需要的,而不是應用程式需要的。 這就是對黏性會話的依賴發揮作用的地方,即使在主動-主動模式下,此類快取本身也不需要複製。

行動代幣。 另一個概念,通常用於各種場景,例如,使用者必須透過郵件非同步執行某些操作。 例如,在手術過程中 forget password 高速緩存 actionTokens 用於追蹤關聯令牌的元資料 - 例如,令牌已被使用且無法再次啟動。 這種類型的快取通常需要在資料中心之間複製。

儲存資料的快取和老化 旨在減輕資料庫的負載。 這種快取提高了效能,但也帶來了一個明顯的問題。 如果一台 Keycloak 伺服器更新數據,則必須通知其他伺服器,以便它們更新快取中的數據。 Keycloak使用本地緩存 realms, users и authorization 用於快取資料庫中的資料。

還有一個單獨的緩存 work,在所有資料中心複製。 它本身不儲存資料庫中的任何數據,而是用於向資料中心之間的叢集節點發送有關資料老化的訊息。 換句話說,一旦資料更新,Keycloak節點就會向其資料中心內的其他節點以及其他資料中心內的節點發送訊息。 各節點收到該訊息後,清除本地快取中對應的資料。

使用者會話。 帶有名稱的緩存 sessions, clientSessions, offlineSessions и offlineClientSessions,通常在資料中心之間複製,並用於儲存有關使用者在瀏覽器中處於活動狀態時活動的使用者會話的資料。 這些快取與處理來自最終用戶的 HTTP 請求的應用程式一起工作,因此它們與黏性會話相關聯,並且必須在資料中心之間進行複製。

暴力保護。 快取 loginFailures 用於追蹤登入錯誤數據,例如使用者輸入錯誤密碼的次數。 複製此快取是管理員的責任。 但為了準確計算,值得啟動資料中心之間的複製。 但另一方面,如果不複製此數據,則會提高效能,如果出現此問題,複製可能不會被啟動。

部署 Infinispan 叢集時,您需要將快取定義新增至設定檔:

<replicated-cache-configuration name="keycloak-sessions" mode="ASYNC" start="EAGER" batching="false">
</replicated-cache-configuration>

<replicated-cache name="work" configuration="keycloak-sessions" />
<replicated-cache name="sessions" configuration="keycloak-sessions" />
<replicated-cache name="offlineSessions" configuration="keycloak-sessions" />
<replicated-cache name="actionTokens" configuration="keycloak-sessions" />
<replicated-cache name="loginFailures" configuration="keycloak-sessions" />
<replicated-cache name="clientSessions" configuration="keycloak-sessions" />
<replicated-cache name="offlineClientSessions" configuration="keycloak-sessions" />

您必須在啟動 Keycloak 叢集之前配置並啟動 Infinispan 集群

然後你需要配置 remoteStore 用於Keycloak快取。 為此,一個腳本就足夠了,其操作與上一個類似,用於設定變量 CACHE_OWNERS,需要將其保存到文件並放入目錄中 /opt/jboss/startup-scripts:

腳本內容

embed-server --server-config=standalone-ha.xml --std-out=echo
batch

echo *** Update infinispan subsystem ***
/subsystem=infinispan/cache-container=keycloak:write-attribute(name=module, value=org.keycloak.keycloak-model-infinispan)

echo ** Add remote socket binding to infinispan server **
/socket-binding-group=standard-sockets/remote-destination-outbound-socket-binding=remote-cache:add(host=${remote.cache.host:localhost}, port=${remote.cache.port:11222})

echo ** Update replicated-cache work element **
/subsystem=infinispan/cache-container=keycloak/replicated-cache=work/store=remote:add( 
    passivation=false, 
    fetch-state=false, 
    purge=false, 
    preload=false, 
    shared=true, 
    remote-servers=["remote-cache"], 
    cache=work, 
    properties={ 
        rawValues=true, 
        marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory, 
        protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion} 
    } 
)

/subsystem=infinispan/cache-container=keycloak/replicated-cache=work:write-attribute(name=statistics-enabled,value=true)

echo ** Update distributed-cache sessions element **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions/store=remote:add( 
    passivation=false, 
    fetch-state=false, 
    purge=false, 
    preload=false, 
    shared=true, 
    remote-servers=["remote-cache"], 
    cache=sessions, 
    properties={ 
        rawValues=true, 
        marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory, 
        protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion} 
    } 
)
/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions:write-attribute(name=statistics-enabled,value=true)

echo ** Update distributed-cache offlineSessions element **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions/store=remote:add( 
    passivation=false, 
    fetch-state=false, 
    purge=false, 
    preload=false, 
    shared=true, 
    remote-servers=["remote-cache"], 
    cache=offlineSessions, 
    properties={ 
        rawValues=true, 
        marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory, 
        protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion} 
    } 
)
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions:write-attribute(name=statistics-enabled,value=true)

echo ** Update distributed-cache clientSessions element **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=clientSessions/store=remote:add( 
    passivation=false, 
    fetch-state=false, 
    purge=false, 
    preload=false, 
    shared=true, 
    remote-servers=["remote-cache"], 
    cache=clientSessions, 
    properties={ 
        rawValues=true, 
        marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory, 
        protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion} 
    } 
)
/subsystem=infinispan/cache-container=keycloak/distributed-cache=clientSessions:write-attribute(name=statistics-enabled,value=true)

echo ** Update distributed-cache offlineClientSessions element **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineClientSessions/store=remote:add( 
    passivation=false, 
    fetch-state=false, 
    purge=false, 
    preload=false, 
    shared=true, 
    remote-servers=["remote-cache"], 
    cache=offlineClientSessions, 
    properties={ 
        rawValues=true, 
        marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory, 
        protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion} 
    } 
)
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineClientSessions:write-attribute(name=statistics-enabled,value=true)

echo ** Update distributed-cache loginFailures element **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures/store=remote:add( 
    passivation=false, 
    fetch-state=false, 
    purge=false, 
    preload=false, 
    shared=true, 
    remote-servers=["remote-cache"], 
    cache=loginFailures, 
    properties={ 
        rawValues=true, 
        marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory, 
        protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion} 
    } 
)
/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures:write-attribute(name=statistics-enabled,value=true)

echo ** Update distributed-cache actionTokens element **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens/store=remote:add( 
    passivation=false, 
    fetch-state=false, 
    purge=false, 
    preload=false, 
    shared=true, 
    cache=actionTokens, 
    remote-servers=["remote-cache"], 
    properties={ 
        rawValues=true, 
        marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory, 
        protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion} 
    } 
)
/subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens:write-attribute(name=statistics-enabled,value=true)

echo ** Update distributed-cache authenticationSessions element **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=authenticationSessions:write-attribute(name=statistics-enabled,value=true)

echo *** Update undertow subsystem ***
/subsystem=undertow/server=default-server/http-listener=default:write-attribute(name=proxy-address-forwarding,value=true)

run-batch
stop-embedded-server

不要忘記安裝 JAVA_OPTS 對於執行 HotRod 的 Keycloak 節點: remote.cache.host, remote.cache.port 和服務名稱 jboss.site.name.

連結和附加文檔

文章由員工翻譯並為 Habr 準備 斯萊姆培訓中心 — 來自實務專家(Kubernetes、DevOps、Docker、Ansible、Ceph、SRE)的密集課程、視訊課程和企業培訓

來源: www.habr.com

添加評論