Exécutez Keycloak en mode HA sur Kubernetes

Exécutez Keycloak en mode HA sur Kubernetes

TL; DR: il y aura une description de Keycloak, un système de contrôle d'accès open source, une analyse du dispositif interne, des détails de configuration.

Introduction et idées principales

Dans cet article, nous verrons les principales idées à garder à l'esprit lors du déploiement d'un cluster Keycloak au-dessus de Kubernetes.

Si vous voulez en savoir plus sur Keycloak, veuillez vous référer aux liens à la fin de l'article. Afin de vous immerger plus profondément dans la pratique, vous pouvez étudier notre référentiel avec un module qui implémente les idées principales de cet article (le guide de lancement est là, dans cet article il y aura un aperçu de l'appareil et des paramètres, environ. traducteur).

Keycloak est un système complexe écrit en Java et construit sur un serveur d'applications. Mouche sauvage. En bref, il s'agit d'un cadre d'autorisation qui donne aux utilisateurs de l'application la capacité de fédération et de SSO (single sign-on).

Nous vous invitons à lire le communiqué officiel site Web ou Wikipedia pour une compréhension détaillée.

Lancer Keycloak

Keycloak a besoin de deux sources de données persistantes pour fonctionner :

  • Une base de données utilisée pour stocker des données persistantes, telles que des informations sur les utilisateurs
  • Le cache Datagrid, qui est utilisé pour mettre en cache les données de la base de données, ainsi que pour stocker certaines métadonnées de courte durée et fréquemment modifiées, telles que les sessions utilisateur. Libéré Infinispan, qui est généralement beaucoup plus rapide que la base de données. Mais dans tous les cas, les données enregistrées dans Infinispan sont éphémères - et elles n'ont pas besoin d'être enregistrées quelque part lorsque le cluster est redémarré.

Keycloak fonctionne selon quatre modes différents :

  • Normal - un et un seul processus, configuré via un fichier autonome.xml
  • grappe régulière (option hautement disponible) - Tous les processus doivent utiliser la même configuration, qui doit être synchronisée manuellement. Les paramètres sont stockés dans un fichier autonome-ha.xml, de plus, vous devez créer un accès partagé à la base de données et à un équilibreur de charge.
  • Cluster de domaine - démarrer le cluster en mode normal devient rapidement une tâche routinière et ennuyeuse au fur et à mesure que le cluster grandit, car à chaque fois que vous modifiez la configuration, vous devez effectuer toutes les modifications sur chaque nœud du cluster. Le mode de fonctionnement de domaine résout ce problème en configurant un stockage partagé et en publiant la configuration. Ces paramètres sont stockés dans un fichier domaine.xml
  • Réplication entre centres de données - au cas où vous voudriez exécuter Keycloak dans un cluster de plusieurs centres de données, le plus souvent dans des lieux géographiques différents. Dans cette option, chaque centre de données aura son propre cluster de serveurs Keycloak.

Dans cet article, nous examinerons de plus près la deuxième option, c'est-à-dire grappe normale, ainsi qu'une petite touche sur le sujet de la réplication entre les centres de données, car il est logique d'exécuter ces deux options dans Kubernetes. Heureusement, Kubernetes n'a pas de problème avec la synchronisation des paramètres de plusieurs pods (nœuds Keycloak), donc cluster de domaine ce ne sera pas trop difficile à faire.

Veuillez également noter que le mot cluster jusqu'à la fin de l'article ne s'appliquera qu'à un groupe de nœuds Keycloak travaillant ensemble, il n'est pas nécessaire de se référer à un cluster Kubernetes.

Cluster Keycloak régulier

Pour exécuter Keycloak dans ce mode, vous avez besoin de :

  • mettre en place une base de données partagée externe
  • installer l'équilibreur de charge
  • avoir un réseau interne avec prise en charge de la multidiffusion IP

Nous n'analyserons pas la configuration de la base de données externe, puisque ce n'est pas l'objet de cet article. Supposons qu'il existe quelque part une base de données fonctionnelle - et que nous ayons un point de connexion vers celle-ci. Nous ajouterons simplement ces données aux variables d'environnement.

Pour mieux comprendre le fonctionnement de Keycloak dans un cluster de basculement (HA), il est important de savoir à quel point tout dépend des capacités de clustering de Wildfly.

Wildfly utilise plusieurs sous-systèmes, certains d'entre eux sont utilisés comme équilibreur de charge, certains sont utilisés pour le basculement. L'équilibreur de charge garantit la disponibilité de l'application lorsque le nœud du cluster est surchargé, et le basculement garantit la disponibilité de l'application même si certains des nœuds du cluster échouent. Certains de ces sous-systèmes sont :

  • mod_cluster: fonctionne en conjonction avec Apache en tant qu'équilibreur de charge HTTP, dépend de la multidiffusion TCP pour la découverte de l'hôte par défaut. Peut être remplacé par un équilibreur externe.

  • infinispan: cache distribué utilisant les canaux JGroups comme couche de transport. En option, il peut utiliser le protocole HotRod pour communiquer avec un cluster Infinispan externe afin de synchroniser le contenu du cache.

  • jgroups: Prend en charge l'association de groupes pour les services hautement disponibles basés sur les canaux JGroups. Les canaux nommés permettent aux instances d'application d'un cluster d'être connectées en groupes afin que la connexion ait des propriétés telles que la fiabilité, l'ordre et la sensibilité aux pannes.

équilibreur de charge

Lors de l'installation d'un équilibreur en tant que contrôleur d'entrée dans un cluster Kubernetes, il est important de garder à l'esprit les éléments suivants :

Le travail de Keycloak implique que l'adresse distante du client se connectant via HTTP au serveur d'authentification est la véritable adresse IP de l'ordinateur client. Les paramètres d'équilibreur et d'entrée doivent définir correctement les en-têtes HTTP X-Forwarded-For и X-Forwarded-Proto, et conserver le titre d'origine HOST. dernière version ingress-nginx (> 0.22.0) le désactive par défaut

Activation du drapeau proxy-address-forwarding en définissant une variable d'environnement PROXY_ADDRESS_FORWARDING в true donne à Keycloak la compréhension qu'il fonctionne derrière un proxy.

Vous devez également activer sessions collantes en entrée. Keycloak utilise le cache distribué d'Infinispan pour stocker les données associées à la session d'authentification et à la session utilisateur en cours. Les caches sont à propriétaire unique par défaut, en d'autres termes, cette session particulière est stockée sur un nœud de cluster et les autres nœuds doivent le demander à distance s'ils ont besoin d'accéder à cette session.

Concrètement, contrairement à la documentation, attacher une session avec le nom du cookie n'a pas fonctionné pour nous AUTH_SESSION_ID. Keycloak a bouclé la redirection, nous vous recommandons donc de choisir un nom de cookie différent pour la session persistante.

Keycloak attache également le nom de l'hôte qui a répondu en premier à AUTH_SESSION_ID, et puisque chaque nœud de la version hautement disponible utilise la même base de données, chacun d'eux avoir dû un ID de nœud distinct et unique pour la gestion des transactions. Il est recommandé de mettre en JAVA_OPTS Options d' jboss.node.name и jboss.tx.node.id unique pour chaque nœud - par exemple, vous pouvez définir le nom du pod. Si vous mettez le nom du pod - n'oubliez pas la limite de 23 caractères pour les variables jboss, il est donc préférable d'utiliser StatefulSet, pas Deployment.

Un râteau de plus - si un pod est supprimé ou redémarré, son cache est perdu. Dans cet esprit, il vaut la peine de définir le nombre de propriétaires de cache pour tous les caches à au moins deux, de sorte qu'il y aura une copie du cache. La solution est de courir scénario pour Wildfly au démarrage du pod, le placer dans le répertoire /opt/jboss/startup-scripts en conteneur :

Contenu du scénario

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

puis définissez la valeur de la variable d'environnement CACHE_OWNERS au requis.

Réseau privé avec prise en charge de la multidiffusion IP

Si vous utilisez Weavenet comme CNI, la multidiffusion fonctionnera immédiatement - et vos nœuds Keycloak se verront dès qu'ils seront opérationnels.

Si vous ne disposez pas de la prise en charge de la multidiffusion IP dans votre cluster Kubernetes, vous pouvez configurer JGroups pour qu'il fonctionne avec d'autres protocoles pour rechercher des nœuds.

La première option consiste à utiliser KUBE_DNSqui utilise headless service pour trouver des nœuds Keycloak, il suffit de passer à JGroups le nom du service qui sera utilisé pour trouver les nœuds.

Une autre option consiste à utiliser la méthode KUBE_PING, qui fonctionne avec l'API pour trouver des nœuds (vous devez configurer serviceAccount avec des droits list и get, puis configurez les pods pour qu'ils fonctionnent avec serviceAccount).

La façon dont les nœuds sont recherchés pour JGroups est configurée en définissant des variables d'environnement JGROUPS_DISCOVERY_PROTOCOL и JGROUPS_DISCOVERY_PROPERTIES. Pour KUBE_PING vous devez choisir les dosettes en demandant namespace и labels.

️ Si vous utilisez la multidiffusion et exécutez deux clusters Keycloak ou plus dans le même cluster Kubernetes (disons un dans l'espace de noms production, la deuxième - staging) - les nœuds d'un cluster Keycloak peuvent rejoindre un autre cluster. Assurez-vous d'utiliser une adresse de multidiffusion unique pour chaque cluster en définissant des variablesjboss.default.multicast.address и jboss.modcluster.multicast.address в JAVA_OPTS.

Réplication entre centres de données

Exécutez Keycloak en mode HA sur Kubernetes

Lien

Keycloak utilise plusieurs clusters de cache Infinispan distincts pour chaque centre de données hébergeant des clusters Keycloak composés de nœuds Keycloak. Mais en même temps, il n'y a aucune différence entre les nœuds Keycloak dans différents centres de données.

Les nœuds Keycloak utilisent une grille de données Java externe (serveurs Infinispan) pour communiquer entre les centres de données. La communication fonctionne selon le protocole Infinispan HotRod.

Les caches Infinispan doivent être configurés avec l'attribut remoteStore, afin que les données puissent être stockées à distance (dans un autre centre de données, environ. traducteur) caches. Il existe des clusters infinispan séparés parmi les serveurs JDG, donc les données sont stockées sur JDG1 sur site site1 sera reproduit sur JDG2 sur site site2.

Enfin, le serveur JDG récepteur informe les serveurs Keycloak de son cluster via des connexions client, ce qui est une fonctionnalité du protocole HotRod. Nœuds Keycloak activés site2 mettre à jour leurs caches Infinispan et la session utilisateur particulière devient disponible sur les nœuds Keycloak sur site2.

Il est également possible que certains caches ne soient pas sauvegardés et refusent totalement d'écrire des données via le serveur Infinispan. Pour ce faire, vous devez supprimer le paramètre remote-store cache spécifique Infinispan (dans le fichier autonome-ha.xml), après quoi quelques spécificités replicated-cache ne sera également plus nécessaire du côté du serveur Infinispan.

Mise en place de caches

Il existe deux types de caches dans Keycloak :

  • Locale. Il est situé à côté de la base, sert à réduire la charge sur la base de données, ainsi qu'à réduire la latence de réponse. Ce type de cache stocke le domaine, les clients, les rôles et les métadonnées utilisateur. Ce type de cache n'est pas répliqué même si ce cache fait partie d'un cluster Keycloak. Si une entrée du cache change, un message de modification est envoyé au reste des serveurs du cluster, après quoi l'entrée est exclue du cache. Voir description work ci-dessous pour une description plus détaillée de la procédure.

  • Réplicable. Traite les sessions utilisateur, les jetons hors ligne et surveille les échecs de connexion pour détecter les tentatives d'hameçonnage de mot de passe et d'autres attaques. Les données stockées dans ces caches sont temporaires, stockées uniquement dans la RAM, mais peuvent être répliquées dans le cluster.

Caches d'Infinispan

Sessions - un concept dans Keycloak, des caches séparés, qui s'appellent authenticationSessions, sont utilisés pour stocker les données d'utilisateurs spécifiques. Les requêtes provenant de ces caches sont généralement nécessaires au navigateur et aux serveurs Keycloak, et non aux applications. C'est là que la dépendance aux sessions persistantes se manifeste, et ces caches eux-mêmes n'ont pas besoin d'être répliqués, même dans le cas du mode actif-actif.

Jetons d'action. Un autre concept, généralement utilisé pour divers scénarios, lorsque, par exemple, l'utilisateur doit faire quelque chose de manière asynchrone par courrier. Par exemple, lors de la procédure forget password cache actionTokens utilisé pour suivre les métadonnées des jetons associés - par exemple, le jeton a déjà été utilisé et ne peut pas être réactivé. Ce type de cache doit généralement être répliqué entre les centres de données.

Mise en cache et expiration des données stockées fonctionne pour alléger la charge de la base de données. Cette mise en cache améliore les performances mais ajoute un problème évident. Si un serveur Keycloak met à jour les données, les autres serveurs doivent être avertis afin qu'ils puissent mettre à jour leurs caches. Keycloak utilise des caches locaux realms, users и authorization pour mettre en cache les données de la base de données.

Il y a aussi un cache séparé work, qui est répliqué dans tous les centres de données. Il ne stocke lui-même aucune donnée de la base de données, mais sert à envoyer des messages de vieillissement des données aux nœuds de cluster entre les centres de données. En d'autres termes, dès que les données sont mises à jour, le nœud Keycloak envoie un message aux autres nœuds de son centre de données, ainsi qu'aux nœuds des autres centres de données. A réception d'un tel message, chaque noeud purge les données correspondantes dans ses caches locaux.

Sessions utilisateur. Caches avec des noms sessions, clientSessions, offlineSessions и offlineClientSessions, sont généralement répliqués entre les centres de données et servent à stocker des données sur les sessions utilisateur actives pendant que l'utilisateur est actif dans le navigateur. Ces caches fonctionnent avec l'application qui gère les requêtes HTTP des utilisateurs finaux, ils sont donc associés à des sessions persistantes et doivent être répliqués entre les centres de données.

protection contre la force brute. Cache loginFailures utilisé pour suivre les données d'erreur de connexion, telles que le nombre de fois qu'un utilisateur a saisi un mot de passe incorrect. La réplication de ce cache appartient à l'administrateur. Mais pour un calcul précis, il convient d'activer la réplication entre les centres de données. Mais en revanche, si vous ne répliquez pas ces données, vous pourrez améliorer les performances, et si cette question se pose, la réplication risque de ne pas être activée.

Lors du déploiement d'un cluster Infinispan, vous devez ajouter des définitions de cache au fichier de paramètres :

<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" />

Vous devez configurer et démarrer le cluster Infinispan avant d'exécuter le cluster Keycloak

Ensuite, vous devez configurer remoteStore pour les caches Keycloak. Pour cela, un script suffit, ce qui se fait de manière similaire au précédent, qui sert à définir la variable CACHE_OWNERS, vous devez l'enregistrer dans un fichier et le placer dans un répertoire /opt/jboss/startup-scripts:

Contenu du scénario

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

N'oubliez pas d'installer JAVA_OPTS pour que les nœuds Keycloak fonctionnent HotRod : remote.cache.host, remote.cache.port et nom du service jboss.site.name.

Liens et documentation complémentaire

L'article a été traduit et préparé pour Habr par des employés Centre de formation Slurm — intensifs, cours vidéo et formation en entreprise dispensés par des praticiens (Kubernetes, DevOps, Docker, Ansible, Ceph, SRE)

Source: habr.com

Ajouter un commentaire