Esecuzione di Keycloak in modalità HA su Kubernetes

Esecuzione di Keycloak in modalità HA su Kubernetes

TL; DR: verrà fornita una descrizione di Keycloak, un sistema di controllo accessi open source, analisi della struttura interna, dettagli di configurazione.

Introduzione e idee chiave

In questo articolo vedremo le idee di base da tenere a mente quando si distribuisce un cluster Keycloak su Kubernetes.

Se vuoi saperne di più su Keycloak, fai riferimento ai link alla fine dell'articolo. Per immergerti maggiormente nella pratica, puoi studiare il nostro deposito con un modulo che implementa le idee principali di questo articolo (la guida al lancio è lì, questo articolo fornirà una panoramica del dispositivo e delle impostazioni, ca. traduttore).

Keycloak è un sistema completo scritto in Java e costruito su un server applicativo Volo selvaggio. In breve, si tratta di un framework per l'autorizzazione che offre agli utenti dell'applicazione funzionalità di federazione e SSO (single sign-on).

Vi invitiamo a leggere l'ufficialità сайт o Wikipedia per una comprensione dettagliata.

Avvio di Keycloak

Keycloak richiede l'esecuzione di due origini dati persistenti:

  • Un database utilizzato per archiviare dati stabiliti, come le informazioni sull'utente
  • Cache Datagrid, utilizzata per memorizzare nella cache i dati dal database, nonché per archiviare alcuni metadati di breve durata e che cambiano frequentemente, come le sessioni utente. Implementato Infinispan, che di solito è significativamente più veloce del database. In ogni caso, i dati salvati in Infinispan sono effimeri e non è necessario salvarli da nessuna parte quando il cluster viene riavviato.

Keycloak funziona in quattro diverse modalità:

  • ordinario - uno ed un solo processo, configurato tramite un file autonomo.xml
  • Grappolo regolare (opzione alta disponibilità): tutti i processi devono utilizzare la stessa configurazione, che deve essere sincronizzata manualmente. Le impostazioni vengono memorizzate in un file autonomo-ha.xml, inoltre è necessario effettuare l'accesso condiviso al database e un bilanciatore del carico.
  • Cluster di domini — avviare un cluster in modalità normale diventa rapidamente un compito di routine e noioso man mano che il cluster cresce, poiché ogni volta che cambia la configurazione, tutte le modifiche devono essere apportate su ciascun nodo del cluster. La modalità operativa del dominio risolve questo problema impostando una posizione di archiviazione condivisa e pubblicando la configurazione. Queste impostazioni vengono memorizzate nel file dominio.xml
  • Replica tra data center — se desideri eseguire Keycloak in un cluster di diversi data center, molto spesso in posizioni geografiche diverse. In questa opzione, ogni data center avrà il proprio cluster di server Keycloak.

In questo articolo considereremo in dettaglio la seconda opzione, cioè grappolo regolaree toccheremo anche brevemente il tema della replica tra data center, poiché ha senso eseguire queste due opzioni in Kubernetes. Fortunatamente in Kubernetes non ci sono problemi con la sincronizzazione delle impostazioni di diversi pod (nodi Keycloak), quindi cluster di domini Non sarà molto difficile da fare.

Tieni inoltre presente che la parola grappolo poiché il resto dell'articolo si applicherà esclusivamente a un gruppo di nodi Keycloak che lavorano insieme, non è necessario fare riferimento a un cluster Kubernetes.

Cluster Keycloak normale

Per eseguire Keycloak in questa modalità è necessario:

  • configurare il database condiviso esterno
  • installare il bilanciatore del carico
  • disporre di una rete interna con supporto IP multicast

Non discuteremo della creazione di un database esterno, poiché non è lo scopo di questo articolo. Supponiamo che da qualche parte esista un database funzionante e che abbiamo un punto di connessione con esso. Aggiungeremo semplicemente questi dati alle variabili di ambiente.

Per comprendere meglio come funziona Keycloak in un cluster di failover (HA), è importante sapere quanto tutto dipende dalle capacità di clustering di Wildfly.

Wildfly utilizza diversi sottosistemi, alcuni di essi vengono utilizzati come bilanciatore del carico, altri per la tolleranza agli errori. Il sistema di bilanciamento del carico garantisce la disponibilità dell'applicazione quando un nodo del cluster è sovraccarico e la tolleranza agli errori garantisce la disponibilità dell'applicazione anche se alcuni nodi del cluster falliscono. Alcuni di questi sottosistemi:

  • mod_cluster: Funziona insieme ad Apache come bilanciatore del carico HTTP, dipende dal multicast TCP per trovare gli host per impostazione predefinita. Può essere sostituito con un bilanciatore esterno.

  • infinispan: Una cache distribuita che utilizza i canali JGroups come livello di trasporto. Inoltre, può utilizzare il protocollo HotRod per comunicare con un cluster Infinispan esterno per sincronizzare il contenuto della cache.

  • jgroups: fornisce supporto per la comunicazione di gruppo per servizi ad alta disponibilità basati sui canali JGroups. Le pipe denominate consentono alle istanze dell'applicazione in un cluster di essere connesse in gruppi in modo che la comunicazione abbia proprietà come affidabilità, ordine e sensibilità agli errori.

Bilanciatore del carico

Quando si installa un sistema di bilanciamento come controller di ingresso in un cluster Kubernetes, è importante tenere presente quanto segue:

Keycloak presuppone che l'indirizzo remoto del client che si connette tramite HTTP al server di autenticazione sia l'indirizzo IP reale del computer client. Le impostazioni del bilanciatore e dell'ingresso dovrebbero impostare correttamente le intestazioni HTTP X-Forwarded-For и X-Forwarded-Protoe salva anche il titolo originale HOST. Ultima versione ingress-nginx (> 0.22.0) lo disabilita per impostazione predefinita

Attivazione della bandiera proxy-address-forwarding impostando una variabile di ambiente PROXY_ADDRESS_FORWARDING в true dà a Keycloak la consapevolezza che sta lavorando dietro un proxy.

È inoltre necessario abilitare sessioni appiccicose in ingresso. Keycloak utilizza una cache Infinispan distribuita per archiviare i dati associati alla sessione di autenticazione e alla sessione utente corrente. Per impostazione predefinita, le cache funzionano con un singolo proprietario, in altre parole, quella particolare sessione viene archiviata su alcuni nodi del cluster e altri nodi devono interrogarla in remoto se necessitano di accedere a quella sessione.

Nello specifico, contrariamente alla documentazione, per noi allegare una sessione con il nome cookie non ha funzionato AUTH_SESSION_ID. Keycloak ha un ciclo di reindirizzamento, quindi ti consigliamo di scegliere un nome di cookie diverso per la sessione permanente.

Keycloak allega anche il nome del nodo a cui ha risposto per primo AUTH_SESSION_IDe poiché ciascun nodo nella versione ad alta disponibilità utilizza lo stesso database, ognuno di essi avrebbe dovuto un identificatore di nodo separato e univoco per la gestione delle transazioni. Si consiglia di inserire JAVA_OPTS parametri jboss.node.name и jboss.tx.node.id unico per ogni nodo: puoi, ad esempio, inserire il nome del pod. Se inserisci un nome pod, non dimenticare il limite di 23 caratteri per le variabili jboss, quindi è meglio utilizzare uno StatefulSet piuttosto che un Deployment.

Un altro rastrello: se il pod viene eliminato o riavviato, la sua cache viene persa. Tenendo conto di ciò, vale la pena impostare almeno due proprietari di cache per tutte le cache, in modo che rimanga una copia della cache. La soluzione è correre sceneggiatura per Wildfly all'avvio del pod, posizionandolo nella directory /opt/jboss/startup-scripts nel contenitore:

Contenuti della sceneggiatura

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

quindi impostare il valore della variabile d'ambiente CACHE_OWNERS al richiesto.

Rete privata con supporto multicast IP

Se usi Weavenet come CNI, il multicast funzionerà immediatamente e i tuoi nodi Keycloak si vedranno non appena verranno lanciati.

Se non disponi del supporto multicast IP nel tuo cluster Kubernetes, puoi configurare JGroups in modo che funzioni con altri protocolli per trovare i nodi.

La prima opzione è usare KUBE_DNSche usa headless service per trovare i nodi Keycloak è sufficiente passare a JGroups il nome del servizio che verrà utilizzato per trovare i nodi.

Un'altra opzione è utilizzare il metodo KUBE_PING, che funziona con l'API per cercare i nodi (è necessario configurare serviceAccount con diritti list и get, quindi configura i pod affinché funzionino con questo serviceAccount).

Il modo in cui JGroups trova i nodi è configurato impostando variabili di ambiente JGROUPS_DISCOVERY_PROTOCOL и JGROUPS_DISCOVERY_PROPERTIES. Per KUBE_PING devi selezionare i pod chiedendo namespace и labels.

️ Se utilizzi il multicast ed esegui due o più cluster Keycloak in un cluster Kubernetes (diciamo uno nello spazio dei nomi production, il secondo - staging) - i nodi di un cluster Keycloak possono unirsi a un altro cluster. Assicurati di utilizzare un indirizzo multicast univoco per ciascun cluster impostando le variabilijboss.default.multicast.address и jboss.modcluster.multicast.address в JAVA_OPTS.

Replica tra data center

Esecuzione di Keycloak in modalità HA su Kubernetes

Collegamento

Keycloak utilizza più cluster di cache Infinispan separati per ciascun data center in cui si trovano i cluster Keycloak costituiti da nodi Keycloak. Ma non c’è differenza tra i nodi Keycloak nei diversi data center.

I nodi Keycloak utilizzano una Java Data Grid esterna (server Infinispan) per la comunicazione tra data center. La comunicazione funziona secondo il protocollo Infinispan HotRod.

Le cache Infinispan devono essere configurate con l'attributo remoteStore, in modo che i dati possano essere archiviati in remoto (in un altro data center, ca. traduttore) cache. Tra i server JDG ci sono cluster Infinispan separati, in modo che i dati siano archiviati su JDG1 sul posto site1 sarà replicato su JDG2 sul posto site2.

Infine, il server JDG ricevente notifica ai server Keycloak il proprio cluster tramite connessioni client, che è una caratteristica del protocollo HotRod. Nodi Keycloak attivi site2 aggiornano le loro cache Infinispan e la sessione utente specifica diventa disponibile anche sui nodi Keycloak su site2.

Per alcune cache è anche possibile non effettuare backup ed evitare completamente la scrittura dei dati tramite il server Infinispan. Per fare ciò è necessario rimuovere l'impostazione remote-store cache Infinispan specifica (nel file autonomo-ha.xml), dopodiché alcuni specifici replicated-cache inoltre non sarà più necessario sul lato server Infinispan.

Impostazione delle cache

Esistono due tipi di cache in Keycloak:

  • Locale. Si trova accanto al database e serve a ridurre il carico sul database, nonché a ridurre la latenza di risposta. Questo tipo di cache memorizza realm, client, ruoli e metadati utente. Questo tipo di cache non viene replicata, anche se la cache fa parte di un cluster Keycloak. Se una voce nella cache cambia, un messaggio relativo alla modifica viene inviato ai restanti server nel cluster, dopodiché la voce viene esclusa dalla cache. Vedi la descrizione work Vedi sotto per una descrizione più dettagliata della procedura.

  • Replicato. Elabora sessioni utente, token offline e monitora anche gli errori di accesso per rilevare tentativi di phishing della password e altri attacchi. I dati archiviati in queste cache sono temporanei, archiviati solo nella RAM, ma possono essere replicati nel cluster.

Cache Infinispan

sessione - un concetto in Keycloak, chiamate cache separate authenticationSessions, vengono utilizzati per memorizzare dati di utenti specifici. Le richieste da queste cache sono generalmente necessarie al browser e ai server Keycloak, non alle applicazioni. È qui che entra in gioco la dipendenza dalle sessioni permanenti e tali cache stesse non necessitano di essere replicate, anche nel caso della modalità Active-Active.

Segnalini Azione. Un altro concetto, solitamente utilizzato per vari scenari quando, ad esempio, l'utente deve fare qualcosa in modo asincrono tramite posta. Ad esempio, durante la procedura forget password nascondiglio actionTokens utilizzato per tenere traccia dei metadati dei token associati, ad esempio un token è già stato utilizzato e non può essere attivato nuovamente. Questo tipo di cache in genere deve essere replicato tra data center.

Caching e invecchiamento dei dati archiviati lavora per alleviare il carico sul database. Questo tipo di memorizzazione nella cache migliora le prestazioni, ma aggiunge un ovvio problema. Se un server Keycloak aggiorna i dati, gli altri server devono essere avvisati in modo che possano aggiornare i dati nelle loro cache. Keycloak utilizza le cache locali realms, users и authorization per memorizzare nella cache i dati dal database.

C'è anche una cache separata work, che viene replicato in tutti i data center. Di per sé non memorizza alcun dato dal database, ma serve a inviare messaggi sull'invecchiamento dei dati ai nodi del cluster tra i data center. In altre parole, non appena i dati vengono aggiornati, il nodo Keycloak invia un messaggio agli altri nodi nel suo data center, nonché ai nodi in altri data center. Dopo aver ricevuto tale messaggio, ciascun nodo cancella i dati corrispondenti nelle sue cache locali.

Sessioni utente. Cache con nomi sessions, clientSessions, offlineSessions и offlineClientSessions, vengono solitamente replicati tra data center e servono per archiviare dati sulle sessioni utente attive mentre l'utente è attivo nel browser. Queste cache funzionano con l'applicazione che elabora le richieste HTTP degli utenti finali, quindi sono associate a sessioni permanenti e devono essere replicate tra i data center.

Protezione dalla forza bruta. Cache loginFailures Utilizzato per tenere traccia dei dati sugli errori di accesso, ad esempio quante volte un utente ha inserito una password errata. La replica di questa cache è responsabilità dell'amministratore. Ma per un calcolo accurato vale la pena attivare la replica tra data center. D'altro canto, se non si replicano questi dati, si miglioreranno le prestazioni e, se si verifica questo problema, la replica potrebbe non essere attivata.

Quando si distribuisce un cluster Infinispan, è necessario aggiungere le definizioni della cache al file delle impostazioni:

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

È necessario configurare e avviare il cluster Infinispan prima di avviare il cluster Keycloak

Quindi è necessario configurare remoteStore per le cache Keycloak. Per fare ciò è sufficiente uno script, che viene fatto in modo simile al precedente, che serve per impostare la variabile CACHE_OWNERS, è necessario salvarlo in un file e inserirlo in una directory /opt/jboss/startup-scripts:

Contenuti della sceneggiatura

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

Non dimenticare di installare JAVA_OPTS affinché i nodi Keycloak eseguano HotRod: remote.cache.host, remote.cache.port e nome del servizio jboss.site.name.

Collegamenti e documentazione aggiuntiva

L'articolo è stato tradotto e preparato per Habr dai dipendenti Centro di formazione Slurm — corsi intensivi, videocorsi e formazione aziendale da parte di specialisti (Kubernetes, DevOps, Docker, Ansible, Ceph, SRE)

Fonte: habr.com

Aggiungi un commento