Kör Keycloak i HA-läge på Kubernetes

Kör Keycloak i HA-läge på Kubernetes

TL; DR: det kommer att finnas en beskrivning av Keycloak, ett åtkomstkontrollsystem med öppen källkod, analys av den interna strukturen, konfigurationsdetaljer.

Introduktion och nyckelidéer

I den här artikeln kommer vi att se de grundläggande idéerna att tänka på när du distribuerar ett Keycloak-kluster ovanpå Kubernetes.

Om du vill veta mer om Keycloak, se länkarna i slutet av artikeln. För att bli mer fördjupad i praktiken kan du studera vårt förråd med en modul som implementerar huvudidéerna i den här artikeln (lanseringsguiden finns där, den här artikeln ger en översikt över enheten och inställningarna, cirka. översättare).

Keycloak är ett omfattande system skrivet i Java och byggt ovanpå en applikationsserver Vildfluga. Kort sagt, det är ett ramverk för auktorisering som ger applikationsanvändare federation och SSO-funktioner (single sign-on).

Vi inbjuder dig att läsa den officiella сайт eller Wikipedia för detaljerad förståelse.

Lanserar Keycloak

Keycloak kräver två beständiga datakällor för att köras:

  • En databas som används för att lagra etablerad data, såsom användarinformation
  • Datagrid cache, som används för att cache data från databasen, samt för att lagra en del kortlivade och ofta föränderliga metadata, såsom användarsessioner. Genomfört Infinispan, vilket vanligtvis är betydligt snabbare än databasen. Men i alla fall är data som sparats i Infinispan tillfällig – och den behöver inte sparas någonstans när klustret startas om.

Keycloak fungerar i fyra olika lägen:

  • vanlig - en och endast en process, konfigurerad via en fil fristående.xml
  • Vanligt kluster (hög tillgänglighet) - alla processer måste använda samma konfiguration, som måste synkroniseras manuellt. Inställningarna lagras i en fil fristående-ha.xml, dessutom måste du göra delad åtkomst till databasen och en lastbalanserare.
  • Domänkluster — att starta ett kluster i normalt läge blir snabbt en rutinmässig och tråkig uppgift när klustret växer, eftersom varje gång konfigurationen ändras måste alla ändringar göras på varje klusternod. Domändrift löser det här problemet genom att konfigurera en delad lagringsplats och publicera konfigurationen. Dessa inställningar lagras i filen domän.xml
  • Replikering mellan datacenter — om du vill köra Keycloak i ett kluster av flera datacenter, oftast på olika geografiska platser. I det här alternativet kommer varje datacenter att ha sitt eget kluster av Keycloak-servrar.

I den här artikeln kommer vi att överväga det andra alternativet i detalj, det vill säga vanligt kluster, och vi kommer också att röra lite på ämnet replikering mellan datacenter, eftersom det är vettigt att köra dessa två alternativ i Kubernetes. Lyckligtvis är det i Kubernetes inga problem med att synkronisera inställningarna för flera pods (Keycloak-noder), så domänkluster Det kommer inte att vara särskilt svårt att göra.

Observera också att ordet klunga för resten av artikeln kommer att gälla enbart för en grupp Keycloak-noder som arbetar tillsammans, det finns inget behov av att referera till ett Kubernetes-kluster.

Vanligt Keycloak-kluster

För att köra Keycloak i detta läge behöver du:

  • konfigurera extern delad databas
  • installera lastbalanserare
  • har ett internt nätverk med stöd för IP multicast

Vi kommer inte att diskutera att skapa en extern databas, eftersom det inte är syftet med denna artikel. Låt oss anta att det finns en fungerande databas någonstans – och vi har en kopplingspunkt till den. Vi kommer helt enkelt att lägga till dessa data till miljövariablerna.

För att bättre förstå hur Keycloak fungerar i ett failover-kluster (HA) är det viktigt att veta hur mycket det beror på Wildflys klustringsmöjligheter.

Wildfly använder flera delsystem, några av dem används som lastbalanserare, vissa för feltolerans. Lastbalanseraren säkerställer applikationstillgänglighet när en klusternod är överbelastad, och feltolerans säkerställer applikationstillgänglighet även om vissa klusternoder misslyckas. Några av dessa delsystem:

  • mod_cluster: Fungerar tillsammans med Apache som en HTTP-belastningsutjämnare, beroende på TCP multicast för att hitta värdar som standard. Kan bytas ut mot en extern balanserare.

  • infinispan: En distribuerad cache som använder JGroups-kanaler som ett transportlager. Dessutom kan den använda HotRod-protokollet för att kommunicera med ett externt Infinispan-kluster för att synkronisera cache-innehåll.

  • jgroups: Ger gruppkommunikationsstöd för högt tillgängliga tjänster baserade på JGroups-kanaler. Namngivna rör gör att applikationsinstanser i ett kluster kan kopplas till grupper så att kommunikationen har egenskaper som tillförlitlighet, ordning och reda och känslighet för fel.

Lastbalanserare

När du installerar en balancer som en ingångskontroller i ett Kubernetes-kluster är det viktigt att ha följande saker i åtanke:

Keycloak antar att fjärradressen för klienten som ansluter via HTTP till autentiseringsservern är den verkliga IP-adressen för klientdatorn. Inställningar för balansering och ingång bör ställa in HTTP-rubriker korrekt X-Forwarded-For и X-Forwarded-Proto, och även spara originaltiteln HOST. Senaste versionen ingress-nginx (> 0.22.0) inaktiverar detta som standard

Aktiverar flaggan proxy-address-forwarding genom att ställa in en miljövariabel PROXY_ADDRESS_FORWARDING в true ger Keycloak förståelsen att den fungerar bakom en proxy.

Du måste också aktivera klibbiga sessioner på inträde. Keycloak använder en distribuerad Infinispan-cache för att lagra data associerad med den aktuella autentiseringssessionen och användarsessionen. Cachar fungerar med en enda ägare som standard, med andra ord, den särskilda sessionen lagras på någon nod i klustret, och andra noder måste förfråga den på distans om de behöver åtkomst till den sessionen.

Specifikt, i motsats till dokumentationen, fungerade det inte för oss att bifoga en session med namnkakan AUTH_SESSION_ID. Keycloak har en omdirigeringsslinga, så vi rekommenderar att du väljer ett annat cookienamn för den klibbiga sessionen.

Keycloak bifogar även namnet på den nod som svarade först AUTH_SESSION_ID, och eftersom varje nod i den mycket tillgängliga versionen använder samma databas, var och en av dem måste ha en separat och unik nodidentifierare för att hantera transaktioner. Det rekommenderas att lägga in JAVA_OPTS parametrar jboss.node.name и jboss.tx.node.id unikt för varje nod - du kan till exempel sätta namnet på podden. Om du sätter ett podnamn, glöm inte gränsen på 23 tecken för jboss-variabler, så det är bättre att använda en StatefulSet istället för en Deployment.

Ytterligare en rake - om podden raderas eller startas om, förloras dess cache. Med hänsyn till detta är det värt att ställa in antalet cacheägare för alla cacher till minst två, så att en kopia av cachen finns kvar. Lösningen är att springa manus till Wildfly när du startar podden, placera den i katalogen /opt/jboss/startup-scripts i behållaren:

Skriptinnehåll

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

ställ sedan in värdet på miljövariabeln CACHE_OWNERS till det erforderliga.

Privat nätverk med stöd för IP multicast

Om du använder Weavenet som en CNI kommer multicast att fungera omedelbart – och dina Keycloak-noder kommer att se varandra så fort de lanseras.

Om du inte har stöd för ip multicast i ditt Kubernetes-kluster kan du konfigurera JGroups att arbeta med andra protokoll för att hitta noder.

Det första alternativet är att använda KUBE_DNSsom använder headless service för att hitta Keycloak-noder skickar du helt enkelt JGroups namnet på tjänsten som kommer att användas för att hitta noderna.

Ett annat alternativ är att använda metoden KUBE_PING, som fungerar med API:et för att söka efter noder (du måste konfigurera serviceAccount med rättigheter list и get, och konfigurera sedan poddarna för att fungera med detta serviceAccount).

Hur JGroups hittar noder konfigureras genom att ställa in miljövariabler JGROUPS_DISCOVERY_PROTOCOL и JGROUPS_DISCOVERY_PROPERTIES. För KUBE_PING du måste välja pods genom att fråga namespace и labels.

️ Om du använder multicast och kör två eller flera Keycloak-kluster i ett Kubernetes-kluster (låt oss säga ett i namnutrymmet production, andra - staging) - noder i ett Keycloak-kluster kan ansluta till ett annat kluster. Se till att använda en unik multicast-adress för varje kluster genom att ställa in variablerjboss.default.multicast.address и jboss.modcluster.multicast.address в JAVA_OPTS.

Replikering mellan datacenter

Kör Keycloak i HA-läge på Kubernetes

Связь

Keycloak använder flera separata Infinispan-cache-kluster för varje datacenter där Keycloack-kluster som består av Keycloak-noder finns. Men det är ingen skillnad mellan Keycloak-noder i olika datacenter.

Keycloak-noder använder ett externt Java Data Grid (Infinispan-servrar) för kommunikation mellan datacenter. Kommunikationen fungerar enligt protokollet Infinispan HotRod.

Infinispan-cacher måste konfigureras med attributet remoteStore, så att data kan lagras på distans (i ett annat datacenter, cirka. översättare) cacher. Det finns separata infinispan-kluster bland JDG-servrarna, så att data som lagras på JDG1 på plats site1 kommer att replikeras till JDG2 på plats site2.

Och slutligen meddelar den mottagande JDG-servern Keycloak-servrarna om sitt kluster genom klientanslutningar, vilket är en funktion i HotRod-protokollet. Keycloak noder på site2 uppdatera sina Infinispan-cacher och den specifika användarsessionen blir också tillgänglig på Keycloak-noderna på site2.

För vissa cachar är det också möjligt att inte göra säkerhetskopior och undvika att skriva data helt och hållet via Infinispan-servern. För att göra detta måste du ta bort inställningen remote-store specifik Infinispan-cache (i filen fristående-ha.xml), varefter några specifika replicated-cache kommer inte längre att behövas på Infinispan-serversidan.

Sätta upp cacher

Det finns två typer av cacher i Keycloak:

  • Lokal. Den är placerad bredvid databasen och tjänar till att minska belastningen på databasen, samt att minska svarslatens. Denna typ av cache lagrar rike, klienter, roller och användarmetadata. Denna typ av cache replikeras inte, även om cachen är en del av ett Keycloak-kluster. Om en post i cachen ändras skickas ett meddelande om ändringen till de återstående servrarna i klustret, varefter posten exkluderas från cachen. Se beskrivning work Se nedan för en mer detaljerad beskrivning av proceduren.

  • Replikeras. Bearbetar användarsessioner, offline-tokens och övervakar även inloggningsfel för att upptäcka lösenordsfiskeförsök och andra attacker. Data som lagras i dessa cachar är tillfälliga, lagras endast i RAM, men kan replikeras över klustret.

Infinispan-cacher

Sessioner - ett koncept i Keycloak, separata cacher kallas authenticationSessions, används för att lagra data från specifika användare. Förfrågningar från dessa cachar behövs vanligtvis av webbläsaren och Keycloak-servrarna, inte av applikationer. Det är här beroendet av klibbiga sessioner spelar in, och sådana cachar i sig behöver inte replikeras, inte ens i fallet med Active-Active-läget.

Action Tokens. Ett annat koncept, som vanligtvis används för olika scenarier då användaren till exempel måste göra något asynkront per post. Till exempel under proceduren forget password cache actionTokens används för att spåra metadata för associerade tokens - till exempel har en token redan använts och kan inte aktiveras igen. Denna typ av cache behöver vanligtvis replikeras mellan datacenter.

Cachning och åldrande av lagrad data fungerar för att avlasta databasen. Denna typ av cachning förbättrar prestandan, men lägger till ett uppenbart problem. Om en Keycloak-server uppdaterar data måste de andra servrarna meddelas så att de kan uppdatera data i sina cachar. Keycloak använder lokala cachar realms, users и authorization för cachning av data från databasen.

Det finns också en separat cache work, som replikeras i alla datacenter. Den lagrar i sig ingen data från databasen, utan tjänar till att skicka meddelanden om dataåldring till klusternoder mellan datacenter. Med andra ord, så snart data uppdateras skickar Keycloak-noden ett meddelande till andra noder i sitt datacenter, såväl som noder i andra datacenter. Efter att ha mottagit ett sådant meddelande rensar varje nod motsvarande data i sina lokala cacheminne.

Användarsessioner. Cacher med namn sessions, clientSessions, offlineSessions и offlineClientSessions, replikeras vanligtvis mellan datacenter och tjänar till att lagra data om användarsessioner som är aktiva medan användaren är aktiv i webbläsaren. Dessa cachar fungerar med applikationen som behandlar HTTP-förfrågningar från slutanvändare, så de är associerade med klibbiga sessioner och måste replikeras mellan datacenter.

Brute force skydd. Cache loginFailures Används för att spåra inloggningsfeldata, till exempel hur många gånger en användare angett ett felaktigt lösenord. Replikering av denna cache är administratörens ansvar. Men för en korrekt beräkning är det värt att aktivera replikering mellan datacenter. Men å andra sidan, om du inte replikerar dessa data kommer du att förbättra prestandan, och om det här problemet uppstår kanske replikering inte aktiveras.

När du rullar ut ett Infinispan-kluster måste du lägga till cachedefinitioner i inställningsfilen:

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

Du måste konfigurera och starta Infinispan-klustret innan du startar Keycloak-klustret

Sedan måste du konfigurera remoteStore för Keycloak-cacher. För att göra detta räcker det med ett skript, som görs på samma sätt som det föregående, som används för att ställa in variabeln CACHE_OWNERSmåste du spara den i en fil och lägga den i en katalog /opt/jboss/startup-scripts:

Skriptinnehåll

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

Glöm inte att installera JAVA_OPTS för Keycloak-noder att köra HotRod: remote.cache.host, remote.cache.port och tjänstens namn jboss.site.name.

Länkar och ytterligare dokumentation

Artikeln översattes och förbereddes för Habr av anställda Slurm träningscenter — intensiva kurser, videokurser och företagsutbildning från praktiserande specialister (Kubernetes, DevOps, Docker, Ansible, Ceph, SRE)

Källa: will.com

Lägg en kommentar