
TL;DR: wird eine Beschreibung von Keycloak, einem Open-Source-Zugriffsmanagementsystem, sowie eine Analyse der internen Struktur und Details zur Konfiguration geben.
Einführung und grundlegende Ideen
In diesem Artikel werden wir die Hauptkonzepte betrachten, die beim Bereitstellen eines Keycloak-Clusters über Kubernetes zu beachten sind.
Wenn Sie mehr über Keycloak erfahren möchten, konsultieren Sie die Links am Ende des Artikels. Um tiefer in die Praxis einzutauchen, können Sie mit dem Modul, das die grundlegenden Konzepte dieses Artikels umsetzt (eine Anleitung zum Starten ist ebenfalls dort, in diesem Artikel wird eine Übersicht über die Struktur und Konfiguration bereitgestellt. Anm. der Übersetzer).
Keycloak ist ein umfassendes System, das in Java geschrieben und auf dem Anwendungsserver . Kurz gesagt, es ist ein Framework für die Authentifizierung, das Anwendungen den Nutzern Föderalität und die Möglichkeit von SSO (Single Sign-On) bietet.
Wir laden Sie ein, die offizielle oder für ein detailliertes Verständnis zu lesen.
Start von Keycloak
Für Keycloak sind zwei permanent gespeicherte Datenquellen erforderlich:
- Eine Datenbank, die zur Speicherung stabiler Daten verwendet wird, wie beispielsweise Benutzerdaten.
- Der Datagrid-Cache wird verwendet, um Daten aus der Datenbank zu cachen und einige kurzlebige, häufig wechselnde Metadaten, wie benutzerdefinierte Sitzungen, zu speichern. Er wird freigegeben. , das in der Regel deutlich schneller als die Datenbank ist. Aber die in Infinispan gespeicherten Daten sind vorübergehend und sollten beim Neustart des Clusters nicht gespeichert werden.
Keycloak arbeitet in vier verschiedenen Modi:
- Standard — ein und nur ein Prozess, konfiguriert über die Datei standalone.xml
- Standard-Cluster (Hochverfügbarkeitsvariante) — alle Prozesse müssen die gleiche Konfiguration verwenden, die manuell synchronisiert werden muss. Die Einstellungen werden in der Datei standalone-ha.xmlgespeichert, zusätzlich muss ein gemeinsamer Zugriff auf die Datenbank und ein Load Balancer eingerichtet werden.
- Domain-Cluster Der Start eines Clusters im Standardmodus wird schnell zu einer Routinebeschäftigung, die langweilig wird, wenn das Cluster wächst, da bei jeder Änderung der Konfiguration alle Anpassungen auf jedem Knoten des Clusters vorgenommen werden müssen. Der Betriebsmodus mit einer Domäne löst dieses Problem, indem er einen gemeinsamen Speicherort einrichtet und die Konfiguration veröffentlicht. Diese Einstellungen werden in der Datei domain.xml
- Replikation zwischen Rechenzentren — falls Sie Keycloak in einem Cluster aus mehreren Rechenzentren, häufig an verschiedenen geografischen Standorten, starten möchten. In diesem Betriebsmodus wird jedes Rechenzentrum einen eigenen Cluster an Keycloak-Servern besitzen.
In diesem Artikel werden wir den zweiten Ansatz im Detail untersuchen, also das Standardcluster, und wir werden auch kurz das Thema der Replikation zwischen den Rechenzentren ansprechen, da es sinnvoll ist, diese beiden Varianten in Kubernetes zu betreiben. Glücklicherweise gibt es in Kubernetes keine Probleme mit der Synchronisierung der Einstellungen mehrerer Pods (Knoten von Keycloak), sodass ein Domänencluster relativ einfach umzusetzen sein wird.
Bitte beachten Sie auch, dass das Wort ein Cluster Im Folgenden wird ausschließlich auf die Gruppe von Keycloak-Knoten eingegangen, die zusammenarbeiten; es ist nicht erforderlich, auf den Kubernetes-Cluster zu verweisen.
Standard Keycloak-Cluster
Um Keycloak in diesem Modus zu betreiben, sind folgende Schritte notwendig:
- eine externe gemeinsame Datenbank konfigurieren
- einen Lastenausgleicher installieren
- ein internes Netzwerk mit Unterstützung für IP-Multicast haben
Die Einrichtung der externen Datenbank werden wir hier nicht behandeln, da dies nicht Ziel dieses Artikels ist. Gehen wir davon aus, dass irgendwo eine funktionierende Datenbank vorhanden ist und wir einen Verbindungspunkt dazu haben. Wir fügen diese Informationen einfach in die Umgebungsvariablen ein.
Um besser zu verstehen, wie Keycloak in einem hochverfügbaren (HA) Cluster funktioniert, ist es wichtig zu wissen, wie sehr dies von den Clusterfähigkeiten von Wildfly abhängt.
Wildfly verwendet mehrere Subsysteme, von denen einige als Lastenausgleicher fungieren und andere der Fehlertoleranz dienen. Der Lastenausgleicher stellt die Verfügbarkeit der Anwendung bei Überlastung eines Clusterknotens sicher, während die Fehlertoleranz gewährleistet, dass die Anwendung auch bei einem Ausfall eines Teils der Clusterknoten verfügbar bleibt. Einige dieser Subsysteme sind:
mod_cluster: arbeitet zusammen mit Apache als HTTP-Load-Balancer und nutzt TCP-Multicast zur standardmäßigen Suche nach Knoten. Kann durch einen externen Load-Balancer ersetzt werden.infinispan: ein verteilter Cache, der JGroups-Kanäle als Transportebene verwendet. Zusätzlich kann das HotRod-Protokoll zur Kommunikation mit einem externen Infinispan-Cluster zur Synchronisierung des Cache-Inhalts verwendet werden.jgroups: bietet Unterstützung für Gruppenkommunikation für hochverfügbare Dienste basierend auf JGroups-Kanälen. Benannte Kanäle ermöglichen es Anwendungsinstanzen im Cluster, sich in Gruppen zu verbinden, sodass die Kommunikation Eigenschaften wie Zuverlässigkeit, Reihenfolge und Fehlertoleranz besitzt.
Lastenausgleich
Bei der Installation des Load-Balancers als Ingress-Controller in einem Kubernetes-Cluster sind folgende Punkte zu beachten:
Der Betrieb von Keycloak setzt voraus, dass die entfernte Adresse des Clients, der sich über HTTP mit dem Authentifizierungsserver verbindet, die tatsächliche IP-Adresse des Client-Computers ist. Die Einstellungen des Load-Balancers und des Ingress sollten die HTTP-Header korrekt setzen X-Forwarded-For und X-Forwarded-Proto, sowie den ursprünglichen Header HOST. Letzte Version ingress-nginx (> 0.22.0)
Aktivierung des Flags proxy-address-forwarding durch Setzen einer Umgebungsvariablen PROXY_ADDRESS_FORWARDING in true gibt Keycloak zu verstehen, dass es hinter einem Proxy arbeitet.
Außerdem muss aktiviert werden Sticky Sessions im ingress. Keycloak verwendet den verteilten Infinispan-Cache, um Daten zu speichern, die mit der aktuellen Authentifizierungssitzung und der Benutzersitzung verbunden sind. Caches arbeiten standardmäßig mit einem einzigen Besitzer, das bedeutet, dass diese spezifische Sitzung auf einem Knoten des Clusters gespeichert wird, während andere Knoten remote darauf zugreifen müssen, wenn sie Zugang zu dieser Sitzung benötigen.
Konkret hat bei uns entgegen der Dokumentation das Anheften der Sitzung mit dem Cookie-Namen
AUTH_SESSION_IDnicht funktioniert. Keycloak hat eine Umleitungsschleife verursacht, daher empfehlen wir, einen anderen Cookie-Namen für die sticky session zu wählen.
Außerdem fügt Keycloak den Namen des ersten antwortenden Knotens hinzu, AUTH_SESSION_IDund da jeder Knoten in der Hochverfügbarkeitsvariante dieselbe Datenbank verwendet, eine separate und eindeutige Knoten-ID haben, um Transaktionen zu verwalten. Es wird empfohlen, in JAVA_OPTS die Parameter jboss.node.name und jboss.tx.node.id einzigartig für jeden Knoten — zum Beispiel kann der Name des Pods vergeben werden. Wenn Sie den Namen des Pods festlegen, denken Sie bitte an die Beschränkung von 23 Zeichen für jboss-Variablen. Daher ist es besser, StatefulSet anstelle von Deployment zu verwenden.
Ein weiteres Problem — wenn der Pod gelöscht oder neu gestartet wird, geht sein Cache verloren. Daher sollte die Anzahl der Cache-Besitzer für alle Caches mindestens zwei betragen, um eine Kopie des Caches zu behalten. Lösung — führen Sie beim Start des Pods aus, indem Sie es in das Verzeichnis /opt/jboss/startup-scripts im Container legen:
Inhalt des Skripts
embed-server --server-config=standalone-ha.xml --std-out=echo
batch
echo * Setzen von CACHE_OWNERS auf "${env.CACHE_OWNERS}" in allen Cache-Containern
/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-serverdanach setzen Sie den Wert der Umgebung Variable CACHE_OWNERS auf den erforderlichen Wert.
Privates Netzwerk mit Unterstützung für IP-Multicast
Wenn Sie Weavenet als CNI verwenden, funktioniert Multicast sofort und Ihre Keycloak-Knoten sehen sich gegenseitig, sobald sie gestartet sind.
Wenn Ihr Kubernetes-Cluster keine Unterstützung für IP-Multicast hat, können Sie JGroups so konfigurieren, dass es mit anderen Protokollen zur Knotensuche funktioniert.
Die erste Option ist die Verwendung von KUBE_DNS, das headless service Um Keycloak-Knoten zu finden, übergeben Sie einfach den JGroups-Dienstnamen, der zur Lokalisierung der Knoten verwendet wird.
Eine weitere Möglichkeit ist die Verwendung der Methode KUBE_PING, die mit der API zum Auffinden von Knoten arbeitet (hierfür müssen Sie den serviceAccount mit Berechtigungen Liste und geteinrichten, bevor Sie die Pods für diese konfigurieren. serviceAccount).
Die Methode zur Auffindung von Knoten für JGroups wird durch das Setzen von Umgebungsvariablen konfiguriert JGROUPS_DISCOVERY_PROTOCOL und JGROUPS_DISCOVERY_PROPERTIES. Für KUBE_PING Man muss Pods festlegen, indem man namespace und labels.
️ Wenn Sie Multicast verwenden und zwei oder mehr Keycloak-Cluster in einem Kubernetes-Cluster ausführen (nehmen wir an, eines im Namespace
production, das zweite —staging) – können die Knoten eines Keycloak-Clusters einem anderen Cluster beitreten. Stellen Sie sicher, dass Sie für jedes Cluster eine eindeutige Multicast-Adresse festlegen, indem Sie die Variablenjboss.default.multicast.addressundjboss.modcluster.multicast.addressinJAVA_OPTS.
Replikation zwischen Rechenzentren

Kommunikation
verwenden. Keycloak verwendet mehrere separate Infinispan-Cache-Cluster für jedes Rechenzentrum, in dem sich die Keycloak-Cluster aus den Keycloak-Knoten befinden. Dabei spielt es jedoch keine Rolle, ob sich die Keycloak-Knoten in verschiedenen Rechenzentren befinden.
Die Keycloak-Knoten nutzen externe Java Data Grids (Infinispan-Server), um zwischen Rechenzentren zu kommunizieren. Die Kommunikation erfolgt über das Protokoll .
Infinispan-Caches müssen mit dem Attribut remoteStore, eingerichtet werden, damit Daten in entfernten (in einem anderen Rechenzentrum, Anm. der Übersetzer) Caches gespeichert werden können. Es gibt separate Infinispan-Cluster unter den JDG-Servern, sodass die Daten, die auf JDG1 am Standort site1 gespeichert werden, auf JDG2 am Standort site2.
repliziert werden. Schließlich informiert der empfangende JDG-Server die Keycloak-Server seines Clusters über Clientverbindungen, was ein Merkmal des HotRod-Protokolls ist. Die Keycloak-Knoten auf site2 aktualisieren ihre Infinispan-Caches, sodass die spezifische Benutzersitzung auch auf den Keycloak-Knoten auf site2.
verfügbar wird. Für einige Caches ist es auch möglich, keine Sicherungen zu erstellen und vollständig auf die Datenspeicherung über den Infinispan-Server zu verzichten. Dazu muss die Einstellung remote-store vom spezifischen Infinispan-Cache entfernt werden (in der Datei standalone-ha.xml), nach der ein spezifischer replicated-cache auch auf der Infinispan-Serverseite nicht mehr benötigt wird.
Cache-Konfiguration
Es gibt zwei Arten von Caches in Keycloak:
Lokal. Er befindet sich in der Nähe der Datenbank, dient zur Entlastung der Datenbank und zur Verringerung der Antwortzeiten. In diesem Cache werden Realm, Clients, Rollen und Benutzermetadaten gespeichert. Dieser Cache-Typ wird nicht repliziert, selbst wenn er Teil eines Keycloak-Clusters ist. Wenn ein Datensatz im Cache geändert wird, wird eine Meldung über die Änderung an die anderen Server im Cluster gesendet, nach der der Datensatz aus dem Cache entfernt wird. Siehe Beschreibung.
arbeitenweiter, für eine detailliertere Beschreibung des Verfahrens.Replizierbar. Verarbeitung von Benutzersitzungen, Offline-Token sowie Überwachung von Anmeldefehlern zur Identifizierung von Passwort-Phishing-Versuchen und anderen Angriffen. Die in diesen Caches gespeicherten Daten sind temporär, werden nur im Arbeitsspeicher gehalten, können jedoch im Cluster repliziert werden.
Infinispan-Caches
Sitzungen — ein Konzept in Keycloak, einzelne Caches, die als authenticationSessions, werden zur Speicherung spezifischer Benutzerdaten verwendet. Anfragen von diesen Caches sind in der Regel für den Browser und die Keycloak-Server erforderlich, jedoch nicht für die Anwendungen. Hier zeigt sich die Abhängigkeit von Sticky Sessions; derartige Caches müssen nicht repliziert werden, selbst im Active-Active-Modus.
Aktionstoken. Ein weiteres Konzept, das üblicherweise in verschiedenen Szenarien Anwendung findet, wenn beispielsweise ein Benutzer etwas asynchron per E-Mail tun muss. Zum Beispiel während des Passwort zurücksetzen Cache actionTokens wird verwendet, um Metadaten zu verfolgen, die mit Token verknüpft sind — zum Beispiel, ob ein Token bereits verwendet wurde und nicht erneut aktiviert werden kann. Dieser Cache-Typ sollte normalerweise zwischen Rechenzentren repliziert werden.
Caching und Veraltung von gespeicherten Daten dient dazu, die Last von der Datenbank zu nehmen. Solches Caching verbessert die Leistung, bringt jedoch ein offensichtliches Problem mit sich. Wenn ein Keycloak-Server Daten aktualisiert, müssen die anderen Server benachrichtigt werden, damit sie die Daten in ihren Caches aktualisieren können. Keycloak verwendet lokale Caches Realms, users und Autorisierung zum Caching von Daten aus der Datenbank.
Es gibt auch einen separaten Cache arbeiten, der in allen Rechenzentren repliziert wird. Er speichert keine Daten aus der Datenbank, sondern dient dazu, den Knoten im Cluster zwischen den Rechenzentren Nachrichten über veraltete Daten zu senden. Mit anderen Worten, sobald die Daten aktualisiert werden, sendet der Keycloak-Knoten eine Nachricht an andere Knoten in seinem Rechenzentrum sowie an Knoten in anderen Rechenzentren. Nach dem Erhalt einer solchen Nachricht bereinigt jeder Knoten die entsprechenden Daten in seinen lokalen Caches.
Benutzersitzungen. Caches mit den Namen sessions, clientSessions, offlineSessions und offlineClientSessions, werden normalerweise zwischen den Rechenzentren repliziert und dienen zur Speicherung der Daten zu Benutzersitzungen, die während der Aktivität des Benutzers im Browser aktiv sind. Diese Caches arbeiten mit der Anwendung, die HTTP-Anfragen von Endbenutzern bearbeitet, wodurch sie mit Sticky Sessions verbunden sind und zwischen den Rechenzentren repliziert werden müssen.
Schutz vor Brute-Force-Angriffen. Der Cache loginFailures dient zur Nachverfolgung von Fehlerdaten beim Login, zum Beispiel wie oft ein Benutzer ein falsches Passwort eingegeben hat. Die Replikation dieses Caches liegt in der Verantwortung des Administrators. Für eine genaue Zählung sollte jedoch die Replikation zwischen den Rechenzentren aktiviert werden. Auf der anderen Seite kann die Leistung verbessert werden, wenn diese Daten nicht repliiziert werden. Falls diese Frage aufkommt, kann die Replikation auch deaktiviert werden.
Beim Einrichten des Infinispan-Clusters müssen Cache-Definitionen in die Konfigurationsdatei eingefügt werden:
Das Infinispan-Cluster muss eingerichtet und gestartet werden, bevor das Keycloak-Cluster gestartet wird.
Anschließend müssen die Einstellungen vorgenommen werden. remoteStore für Keycloak-Caches. Dafür genügt ein Skript, das ähnlich dem vorherigen erstellt wird, das zur Konfiguration der Variablen verwendet wird. CACHE_OWNERS, muss es in eine Datei gespeichert und in das Verzeichnis gelegt werden. /opt/jboss/startup-scripts:
Inhalt des Skripts
embed-server --server-config=standalone-ha.xml --std-out=echo
batch
echo *** Aktualisierung des Infinispan-Subsystems ***
/subsystem=infinispan/cache-container=keycloak:write-attribute(name=module, value=org.keycloak.keycloak-model-infinispan)
echo ** Fügen Sie die Remote-Socket-Bindung zum Infinispan-Server hinzu **
/socket-binding-group=standard-sockets/remote-destination-outbound-socket-binding=remote-cache:add(host=${remote.cache.host:localhost}, port=${remote.cache.port:11222})
echo ** Aktualisierung des replizierten Cache-Arbeitselements **
/subsystem=infinispan/cache-container=keycloak/replizierter-cache=arbeit/store=remote:add(
passivation=false,
fetch-state=false,
purge=false,
preload=false,
shared=true,
remote-servers=["remote-cache"],
cache=arbeit,
properties={
rawValues=true,
marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory,
protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion}
}
)
/subsystem=infinispan/cache-container=keycloak/replizierter-cache=arbeit:write-attribute(name=statistics-enabled,value=true)
echo ** Aktualisierung des verteilten Cache-Element `sessions` **
/subsystem=infinispan/cache-container=keycloak/verteilte-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/verteilte-cache=sessions:write-attribute(name=statistics-enabled,value=true)
echo ** Aktualisierung des verteilten Cache-Elements `offlineSessions` **
/subsystem=infinispan/cache-container=keycloak/verteilte-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/verteilte-cache=offlineSessions:write-attribute(name=statistics-enabled,value=true)
echo ** Aktualisierung des verteilten Cache-Elements `clientSessions` **
/subsystem=infinispan/cache-container=keycloak/verteilte-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/verteilte-cache=clientSessions:write-attribute(name=statistics-enabled,value=true)
echo ** Aktualisierung des verteilten Cache-Elements `offlineClientSessions` **
/subsystem=infinispan/cache-container=keycloak/verteilte-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/verteilte-cache=offlineClientSessions:write-attribute(name=statistics-enabled,value=true)
echo ** Aktualisierung des verteilten Cache-Elements `loginFailures` **
/subsystem=infinispan/cache-container=keycloak/verteilte-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/verteilte-cache=loginFailures:write-attribute(name=statistics-enabled,value=true)
echo ** Aktualisierung des verteilten Cache-Elements `actionTokens` **
/subsystem=infinispan/cache-container=keycloak/verteilte-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/verteilte-cache=actionTokens:write-attribute(name=statistics-enabled,value=true)
echo ** Aktualisierung des verteilten Cache-Elements `authenticationSessions` **
/subsystem=infinispan/cache-container=keycloak/verteilte-cache=authenticationSessions:write-attribute(name=statistics-enabled,value=true)
echo *** Aktualisierung des Undertow-Subsystems ***
/subsystem=undertow/server=default-server/http-listener=default:write-attribute(name=proxy-address-forwarding,value=true)
run-batch
stop-embedded-serverVergessen Sie nicht, zu installieren JAVA_OPTS für die Keycloak-Knoten zur Nutzung von HotRod: remote.cache.host, remote.cache.port und den Servicenamen jboss.site.name.
Links und zusätzliche Dokumentation
Der Artikel wurde von den Mitarbeitern des — Intensivkurse, Videokurse und Unternehmensschulungen von erfahrenen Praktikern (Kubernetes, DevOps, Docker, Ansible, Ceph, SRE)
Quelle: habr.com
