Kjører Keycloak i HA-modus på Kubernetes

Kjører Keycloak i HA-modus på Kubernetes

TL; DR: det vil være en beskrivelse av Keycloak, et åpen kildekode-tilgangskontrollsystem, analyse av den interne strukturen, konfigurasjonsdetaljer.

Introduksjon og nøkkelideer

I denne artikkelen vil vi se de grunnleggende ideene du bør huske på når du distribuerer en Keycloak-klynge på toppen av Kubernetes.

Hvis du vil vite mer om Keycloak, se lenkene på slutten av artikkelen. For å bli mer fordypet i praksis kan du studere vårt depot med en modul som implementerer hovedideene i denne artikkelen (lanseringsveiledningen er der, denne artikkelen vil gi en oversikt over enheten og innstillingene, ca. oversetter).

Keycloak er et omfattende system skrevet i Java og bygget på toppen av en applikasjonsserver Villflue. Kort sagt er det et rammeverk for autorisasjon som gir applikasjonsbrukere federasjon og SSO (single sign-on) muligheter.

Vi inviterer deg til å lese den offisielle сайт eller Wikipedia for detaljert forståelse.

Lanserer Keycloak

Keycloak krever to vedvarende datakilder for å kjøre:

  • En database som brukes til å lagre etablerte data, for eksempel brukerinformasjon
  • Datagrid cache, som brukes til å bufre data fra databasen, samt til å lagre noen kortvarige og hyppig skiftende metadata, for eksempel brukersesjoner. Implementert Uendelig, som vanligvis er betydelig raskere enn databasen. Men uansett er dataene som er lagret i Infinispan flyktige – og de trenger ikke å lagres noe sted når klyngen startes på nytt.

Keycloak fungerer i fire forskjellige moduser:

  • Normal - én og bare én prosess, konfigurert via en fil frittstående.xml
  • Vanlig klynge (høy tilgjengelighetsalternativ) - alle prosesser må bruke samme konfigurasjon, som må synkroniseres manuelt. Innstillinger lagres i en fil frittstående-ha.xml, i tillegg må du lage delt tilgang til databasen og en lastbalanser.
  • Domeneklynge — Å starte en klynge i normal modus blir raskt en rutinemessig og kjedelig oppgave ettersom klyngen vokser, siden hver gang du endrer konfigurasjonen må du gjøre alle endringene på hver klyngennode. Domenedrift løser dette problemet ved å sette opp en delt lagringsplass og publisere konfigurasjonen. Disse innstillingene lagres i filen domene.xml
  • Replikering mellom datasentre — hvis du ønsker å kjøre Keycloak i en klynge av flere datasentre, oftest på forskjellige geografiske steder. I dette alternativet vil hvert datasenter ha sin egen klynge med Keycloak-servere.

I denne artikkelen vil vi vurdere i detalj det andre alternativet, det vil si vanlig klynge, og vi skal også berøre litt på temaet replikering mellom datasentre, siden det er fornuftig å kjøre disse to alternativene i Kubernetes. Heldigvis er det i Kubernetes ikke noe problem med å synkronisere innstillingene til flere pods (Keycloak-noder), så domeneklynge Det vil ikke være veldig vanskelig å gjøre.

Vær også oppmerksom på at ordet klynge for resten av artikkelen vil kun gjelde for en gruppe Keycloak-noder som jobber sammen, det er ikke nødvendig å referere til en Kubernetes-klynge.

Vanlig Keycloak klynge

For å kjøre Keycloak i denne modusen trenger du:

  • konfigurere ekstern delt database
  • installer lastbalanser
  • har et internt nettverk med IP multicast-støtte

Vi vil ikke diskutere å sette opp en ekstern database, siden det ikke er hensikten med denne artikkelen. La oss anta at det er en fungerende database et sted – og vi har et koblingspunkt til den. Vi vil ganske enkelt legge til disse dataene i miljøvariablene.

For bedre å forstå hvordan Keycloak fungerer i en failover (HA)-klynge, er det viktig å vite hvor mye det hele avhenger av Wildflys clustering-evner.

Wildfly bruker flere delsystemer, noen av dem brukes som lastbalanser, noen for feiltoleranse. Lastbalanseren sikrer applikasjonstilgjengelighet når en klyngennode er overbelastet, og feiltoleranse sikrer applikasjonstilgjengelighet selv om noen klyngenoder mislykkes. Noen av disse undersystemene:

  • mod_cluster: Fungerer sammen med Apache som en HTTP-lastbalanserer, avhenger av TCP multicast for å finne verter som standard. Kan byttes ut med en ekstern balanserer.

  • infinispan: En distribuert cache som bruker JGroups-kanaler som et transportlag. I tillegg kan den bruke HotRod-protokollen til å kommunisere med en ekstern Infinispan-klynge for å synkronisere cache-innhold.

  • jgroups: Gir gruppekommunikasjonsstøtte for høyt tilgjengelige tjenester basert på JGroups-kanaler. Navngitte rør gjør at applikasjonsforekomster i en klynge kan kobles sammen i grupper slik at kommunikasjonen har egenskaper som pålitelighet, orden og følsomhet for feil.

Load Balancer

Når du installerer en balanserer som en ingress-kontroller i en Kubernetes-klynge, er det viktig å huske på følgende:

Keycloak antar at den eksterne adressen til klienten som kobler til via HTTP til autentiseringsserveren er den virkelige IP-adressen til klientdatamaskinen. Balanser- og inngangsinnstillinger bør angi HTTP-hoder riktig X-Forwarded-For и X-Forwarded-Proto, og også lagre den originale tittelen HOST. Siste versjon ingress-nginx (> 0.22.0) deaktiverer dette som standard

Aktivering av flagget proxy-address-forwarding ved å sette en miljøvariabel PROXY_ADDRESS_FORWARDING в true gir Keycloak forståelsen av at den fungerer bak en proxy.

Du må også aktivere klissete økter på vei inn. Keycloak bruker en distribuert Infinispan-cache for å lagre data knyttet til gjeldende autentiseringsøkt og brukerøkt. Cacher opererer med en enkelt eier som standard, med andre ord, den bestemte økten er lagret på en node i klyngen, og andre noder må spørre den eksternt hvis de trenger tilgang til den økten.

Nærmere bestemt, i motsetning til dokumentasjonen, fungerte det ikke for oss å legge ved en økt med navneinformasjonskapselen AUTH_SESSION_ID. Keycloak har en omdirigeringsløkke, så vi anbefaler å velge et annet informasjonskapselnavn for den klebrige økten.

Keycloak legger også ved navnet på noden som svarte først på AUTH_SESSION_ID, og siden hver node i den svært tilgjengelige versjonen bruker den samme databasen, hver av dem burde en separat og unik nodeidentifikator for håndtering av transaksjoner. Det anbefales å sette inn JAVA_OPTS parametere jboss.node.name и jboss.tx.node.id unik for hver node - du kan for eksempel sette navnet på poden. Hvis du legger inn et podnavn, ikke glem grensen på 23 tegn for jboss-variabler, så det er bedre å bruke et StatefulSet i stedet for en distribusjon.

Enda en rake - hvis poden slettes eller startes på nytt, går cachen tapt. Tatt i betraktning er det verdt å sette antall cache-eiere for alle cache til minst to, slik at en kopi av cachen blir stående. Løsningen er å løpe manus til Wildfly når du starter poden, plasser den i katalogen /opt/jboss/startup-scripts i beholderen:

Skriptinnhold

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

deretter angi verdien av miljøvariabelen CACHE_OWNERS til det nødvendige.

Privat nettverk med støtte for IP multicast

Hvis du bruker Weavenet som en CNI, vil multicast fungere umiddelbart – og Keycloak-nodene dine vil se hverandre så snart de er lansert.

Hvis du ikke har støtte for ip multicast i Kubernetes-klyngen, kan du konfigurere JGroups til å jobbe med andre protokoller for å finne noder.

Det første alternativet er å bruke KUBE_DNSsom bruker headless service for å finne Keycloak-noder, sender du ganske enkelt JGroups navnet på tjenesten som skal brukes til å finne nodene.

Et annet alternativ er å bruke metoden KUBE_PING, som fungerer med API for å søke etter noder (du må konfigurere serviceAccount med rettigheter list и get, og konfigurer deretter podene til å fungere med dette serviceAccount).

Måten JGroups finner noder på, konfigureres ved å angi miljøvariabler JGROUPS_DISCOVERY_PROTOCOL и JGROUPS_DISCOVERY_PROPERTIES. Til KUBE_PING du må velge pods ved å spørre namespace и labels.

️ Hvis du bruker multicast og kjører to eller flere Keycloak-klynger i en Kubernetes-klynge (la oss si en i navneområdet production, sekund - staging) - noder til en Keycloak-klynge kan bli med i en annen klynge. Sørg for å bruke en unik multicast-adresse for hver klynge ved å angi variablerjboss.default.multicast.address и jboss.modcluster.multicast.address в JAVA_OPTS.

Replikering mellom datasentre

Kjører Keycloak i HA-modus på Kubernetes

Связь

Keycloak bruker flere separate Infinispan-cache-klynger for hvert datasenter der Keycloack-klynger består av Keycloak-noder er plassert. Men det er ingen forskjell mellom Keycloak-noder i forskjellige datasentre.

Keycloak-noder bruker et eksternt Java Data Grid (Infinispan-servere) for kommunikasjon mellom datasentre. Kommunikasjon fungerer i henhold til protokollen Infinispan HotRod.

Infinispan-cacher må konfigureres med attributtet remoteStore, slik at dataene kan lagres eksternt (i et annet datasenter, ca. oversetter) cacher. Det er separate infinispan-klynger blant JDG-serverne, slik at dataene lagret på JDG1 på stedet site1 vil bli replikert til JDG2 på stedet site2.

Og til slutt, den mottakende JDG-serveren varsler Keycloak-serverne om klyngen sin gjennom klientforbindelser, som er en funksjon i HotRod-protokollen. Keycloak-noder på site2 oppdater Infinispan-cachene deres og den spesifikke brukerøkten blir også tilgjengelig på Keycloak-nodene på site2.

For noen cacher er det også mulig å ikke ta sikkerhetskopier og unngå å skrive data gjennom Infinispan-serveren helt. For å gjøre dette må du fjerne innstillingen remote-store spesifikk Infinispan-cache (i filen frittstående-ha.xml), hvoretter noen spesifikke replicated-cache vil heller ikke lenger være nødvendig på Infinispan-serversiden.

Sette opp cacher

Det er to typer cacher i Keycloak:

  • Lokalt. Den er plassert ved siden av databasen og tjener til å redusere belastningen på databasen, samt å redusere responsforsinkelse. Denne typen cache lagrer rike, klienter, roller og brukermetadata. Denne typen cache er ikke replikert, selv om cachen er en del av en Keycloak-klynge. Hvis en oppføring i cachen endres, sendes en melding om endringen til de resterende serverne i klyngen, hvoretter oppføringen ekskluderes fra cachen. Se beskrivelse work Se nedenfor for en mer detaljert beskrivelse av prosedyren.

  • Replikert. Behandler brukerøkter, offline-tokens og overvåker også påloggingsfeil for å oppdage passord-phishing-forsøk og andre angrep. Dataene som er lagret i disse cachene er midlertidige, lagret bare i RAM, men kan replikeres på tvers av klyngen.

Infinispan-cacher

Økter - et konsept i Keycloak, separate cacher kalt authenticationSessions, brukes til å lagre data fra spesifikke brukere. Forespørsler fra disse cachene er vanligvis nødvendige av nettleseren og Keycloak-servere, ikke av applikasjoner. Det er her avhengigheten av klebrige økter spiller inn, og slike cacher i seg selv trenger ikke å replikeres, selv ikke i Active-Active-modus.

Action Tokens. Et annet konsept, vanligvis brukt for ulike scenarier når for eksempel brukeren må gjøre noe asynkront per post. For eksempel under prosedyren forget password cache actionTokens brukes til å spore metadata for tilknyttede tokens - for eksempel har et token allerede blitt brukt og kan ikke aktiveres igjen. Denne typen cache må vanligvis replikeres mellom datasentre.

Bufring og aldring av lagrede data fungerer for å avlaste databasen. Denne typen caching forbedrer ytelsen, men legger til et åpenbart problem. Hvis en Keycloak-server oppdaterer data, må de andre serverne varsles slik at de kan oppdatere dataene i cachene sine. Keycloak bruker lokale cacher realms, users и authorization for caching av data fra databasen.

Det er også en egen cache work, som er replikert på tvers av alle datasentre. Den lagrer i seg selv ingen data fra databasen, men tjener til å sende meldinger om dataaldring til klynge noder mellom datasentre. Med andre ord, så snart dataene er oppdatert, sender Keycloak-noden en melding til andre noder i datasenteret sitt, samt noder i andre datasentre. Etter å ha mottatt en slik melding, sletter hver node de tilsvarende dataene i sine lokale cacher.

Brukerøkter. Cacher med navn sessions, clientSessions, offlineSessions и offlineClientSessions, er vanligvis replikert mellom datasentre og tjener til å lagre data om brukerøkter som er aktive mens brukeren er aktiv i nettleseren. Disse cachene fungerer med applikasjonen som behandler HTTP-forespørsler fra sluttbrukere, så de er assosiert med klebrige økter og må replikeres mellom datasentre.

Brut force beskyttelse. Cache loginFailures Brukes til å spore innloggingsfeildata, for eksempel hvor mange ganger en bruker oppga feil passord. Replikering av denne hurtigbufferen er administratorens ansvar. Men for en nøyaktig beregning er det verdt å aktivere replikering mellom datasentre. Men på den annen side, hvis du ikke replikerer disse dataene, vil du forbedre ytelsen, og hvis dette problemet oppstår, kan replikering ikke aktiveres.

Når du ruller ut en Infinispan-klynge, må du legge til hurtigbufferdefinisjoner i innstillingsfilen:

<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å konfigurere og starte Infinispan-klyngen før du starter Keycloak-klyngen

Deretter må du konfigurere remoteStore for Keycloak-cacher. For å gjøre dette er et skript nok, som gjøres på samme måte som det forrige, som brukes til å sette variabelen CACHE_OWNERS, må du lagre den i en fil og legge den i en katalog /opt/jboss/startup-scripts:

Skriptinnhold

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

Ikke glem å installere JAVA_OPTS for Keycloak-noder å kjøre HotRod: remote.cache.host, remote.cache.port og tjenestenavn jboss.site.name.

Lenker og tilleggsdokumentasjon

Artikkelen er oversatt og utarbeidet for Habr av ansatte Slurm treningssenter — intensive kurs, videokurs og bedriftsopplæring fra praktiserende spesialister (Kubernetes, DevOps, Docker, Ansible, Ceph, SRE)

Kilde: www.habr.com

Legg til en kommentar