Lansăm Keycloak în modul HA pe Kubernetes

Lansăm Keycloak în modul HA pe Kubernetes

TL;DR: va fi o descriere a Keycloak, un sistem de control al accesului cu sursă deschisă, o analiză a structurii interne și detalii despre configurare.

Introducere și idei de bază

În acest articol, vom explora ideile principale pe care trebuie să le ținem minte atunci când desfășurăm un cluster Keycloak pe Kubernetes.

Dacă doriți să știți mai multe despre Keycloak – verificați linkurile de la finalul articolului. Pentru a vă aprofunda în practică – puteți studia repository-ul nostru cu modulul care implementează principalele idei ale acestui articol (un ghid de lansare se află și acolo, în acest articol va fi o prezentare a structurii și setărilor, nota traducătorului).

Keycloak este un sistem complex, scris în Java și construit pe serverul de aplicații Wildfly. Pe scurt, este un framework pentru autorizare, care oferă utilizatorilor aplicațiilor federație și posibilitatea SSO (autentificare unificată).

Vă invităm să citiți oficialul site-ul sau Wikipedia pentru o înțelegere detaliată.

Lansarea Keycloak

Pentru Keycloak sunt necesare două surse de date stocate permanent pentru a funcționa:

  • O bază de date utilizată pentru stocarea datelor persistente, cum ar fi informațiile despre utilizatori
  • Cache-ul DataGrid, care este folosit pentru a cache-a date din baza de date, precum și pentru a stoca metadate de scurtă durată și frecvent modificate, cum ar fi sesiunile utilizatorilor. Este realizat Infinispan, care de obicei este semnificativ mai rapid decât baza de date. Dar, în orice caz, datele stocate în Infinispan sunt efemere – nu este necesar să fie salvate undeva la repornirea cluster-ului.

Keycloak funcționează în patru moduri diferite:

  • Obișnuit – un singur proces, configurat prin fișierul standalone.xml
  • Cluster obișnuit (varianta de înaltă disponibilitate) – toate procesele trebuie să folosească aceeași configurație, care trebuie sincronizată manual. Setările sunt stocate în fișierul standalone-ha.xml, de asemenea, trebuie să se asigure accesul comun la baza de date și un balanceur de încărcare.
  • Cluster domeniu – lansarea unui cluster în modul obișnuit devine rapid o activitate rutină și plictisitoare pe măsură ce cluster-ul crește, deoarece de fiecare dată când configurația se schimbă, toate modificările trebuie aplicate pe fiecare nod al cluster-ului. Modul domeniu rezolvă această problemă prin configurarea unui spațiu comun de stocare și publicarea configurației. Aceste setări sunt stocate în fișierul domain.xml
  • Replicare între centrele de date — în cazul în care doriți să rulați Keycloak într-un cluster format din mai multe centre de date, adesea localizate în locuri diferite geografic. În această variantă de funcționare, fiecare centru de date va avea propriul său cluster de servere Keycloak.

În acest articol vom examina în detaliu a doua variantă, adică clusterul obișnuit, și vom aborda pe scurt și tema replicării între centrele de date, deoarece aceste două variante au sens să fie lansate în Kubernetes. Din fericire, în Kubernetes nu există problema sincronizării configurațiilor mai multor poduri (noduri Keycloak), așa că clusterul de domenii nu va fi foarte dificil de realizat.

De asemenea, vă rugăm să rețineți că termenul cluster va fi folosit pe tot parcursul articolului exclusiv pentru grupul de noduri Keycloak care lucrează împreună, fără a fi nevoie să ne referim la clusterul Kubernetes.

Clusterul obișnuit Keycloak

Pentru a rula Keycloak în acest mod, este necesar să:

  • configurați o bază de date externă comună
  • instalați un echilibrator de încărcare
  • să aveți o rețea internă care să suporte ip multicast

Configurarea bazei de date externe nu o vom detalia, deoarece nu reprezintă scopul acestui articol. Să presupunem că există o bază de date funcțională undeva — și avem un punct de conectare pentru aceasta. Vom adăuga aceste date în variabilele de mediu.

Pentru a înțelege mai bine cum funcționează Keycloak într-un cluster de tip failover (HA), este important să știm cât de mult depind toate acestea de capacitățile de clusterizare ale Wildfly.

Wildfly utilizează mai multe subsisteme, unele dintre ele fiind folosite ca echilibratoare de încărcare, iar altele pentru asigurarea disponibilității. Echipamentul de echilibrare a încărcării asigură disponibilitatea aplicației în cazul în care nodul clusterului este supraîncărcat, iar disponibilitatea garantează accesibilitatea aplicației chiar și în cazul eșecului uneia dintre nodurile clusterului. Unele dintre aceste subsisteme:

  • mod_cluster: funcționează împreună cu Apache ca echilibrator HTTP, depinde de TCP multicast pentru găsirea nodurilor în mod implicit. Poate fi înlocuit cu un echilibrator de încărcare extern.

  • infinispan: un cache distribuit, care folosește canale JGroups ca nivel de transport. De asemenea, poate utiliza protocolul HotRod pentru a comunica cu clusterul extern Infinispan pentru sincronizarea conținutului cache-ului.

  • jgroups: oferă suport pentru comunicarea grupurilor pentru servicii de înaltă disponibilitate bazate pe canale JGroups. Canalele denumite permit instanțelor aplicației din cluster să se conecteze în grupuri, astfel încât comunicarea să aibă proprietăți precum fiabilitate, ordonare și sensibilitate la defecțiuni.

Întreținătoare de sarcină

Atunci când instalați un echilibrator de sarcină ca și controlor ingress într-un cluster Kubernetes, este important să aveți în vedere următoarele aspecte:

Funcționarea Keycloak implică faptul că adresa IP remote a clientului, care se conectează prin HTTP la serverul de autentificare, este adresa IP reală a computerului client. Setările echilibratorului și ingress-ului trebuie să stabilească corect anteturile HTTP X-Forwarded-For și X-Forwarded-Proto, precum și să păstreze antetul original HOST. Cea mai recentă versiune ingress-nginx (> 0.22.0) dezactivează aceasta în mod implicit

Activarea flag-ului proxy-address-forwarding prin setarea variabilei de mediu PROXY_ADDRESS_FORWARDING în true oferă Keycloak înțelegerea faptului că funcționează în spatele unui proxy.

De asemenea, trebuie activat sticky sessions în ingress. Keycloak aplică un cache distribuit Infinispan pentru a păstra datele asociate cu sesiunea curentă de autentificare și sesiunea utilizatorului. Cache-urile funcționează cu un singur proprietar în mod implicit, cu alte cuvinte, această sesiune specifică este păstrată pe un anumit nod din cluster, iar celelalte noduri trebuie să o solicite de la distanță dacă au nevoie de acces la această sesiune.

În mod special, la noi, contrar documentației, nu a funcționat atașarea sesiunii cu numele cookie-ului AUTH_SESSION_ID. Keycloak a creat un ciclu de redirecționare, așa că recomandăm alegerea unui alt nume de cookie pentru sesiunea sticky.

De asemenea, Keycloak atașează numele nodului care a răspuns primul la AUTH_SESSION_ID, iar deoarece fiecare nod în varianta de înaltă disponibilitate utilizează aceeași bază de date, fiecare dintre ele trebuie să aibă un identificator de nod unic și separat pentru a gestiona tranzacțiile. Se recomandă setarea în JAVA_OPTS a parametrilor jboss.node.name și jboss.tx.node.id unice pentru fiecare nod — se poate, de exemplu, seta numele pod-ului. Dacă veți seta numele pod-ului — nu uitați de limita de 23 de caractere pentru variabilele jboss, astfel că este mai bine să folosiți StatefulSet, nu Deployment.

O altă capcană — dacă un pod este eliminat sau repornit, memoria cache se pierde. Având în vedere acest lucru, este recomandat să setați numărul de proprietari ai memoriei cache pentru toate cache-urile la minim doi, astfel încât să rămână o copie a memoriei cache. Soluția — rulați scriptul pentru Wildfly la pornirea podului, plasându-l în directorul /opt/jboss/startup-scripts din container:

Conținutul scriptului

embed-server --server-config=standalone-ha.xml --std-out=echo
batch

echo * Setarea CACHE_OWNERS la "${env.CACHE_OWNERS}" în toate containerele de cache

/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

după care setați valoarea variabilei de mediu CACHE_OWNERS la valoarea necesară.

Rețea privată cu suport pentru ip multicast

Dacă utilizați Weavenet ca CNI, multicast-ul va funcționa imediat — și nodurile dvs. Keycloak se vor vedea între ele imediat ce vor fi pornite.

Dacă nu aveți suport pentru ip multicast în clusterul Kubernetes, puteți configura JGroups să funcționeze cu alte protocoale pentru a găsi nodurile.

Prima opțiune — utilizarea KUBE_DNS, care utilizează serviciul headless pentru a găsi nodurile Keycloak, doar transmiteți JGroups numele serviciului, care va fi folosit pentru a găsi nodurile.

O altă opțiune — aplicarea metodei KUBE_PING, care funcționează cu API-ul pentru a găsi nodurile (trebuie configurat serviceAccount cu permisiuni list și get, după care configurați podurile pentru a lucra cu această serviceAccount).

Metodă de căutare a nodurilor pentru JGroups se configurează prin setarea variabilelor de mediu JGROUPS_DISCOVERY_PROTOCOL și JGROUPS_DISCOVERY_PROPERTIES. Pentru KUBE_PING trebuie să selectați podurile definind namespace și labels.

️ Dacă folosiți multicast și rulați două sau mai multe clustere Keycloak într-un singur cluster Kubernetes (de exemplu, unul în namespace producție, al doilea — staging) — nodurile unui cluster Keycloak se pot alătura altui cluster. Asigurați-vă că folosiți o adresă multicast unică pentru fiecare cluster prin setarea variabilelorjboss.default.multicast.address și jboss.modcluster.multicast.address în JAVA_OPTS.

Replicare între centrele de date

Lansăm Keycloak în modul HA pe Kubernetes

Conectivitate

Keycloak utilizează clustere de cache Infinispan separate pentru fiecare centru de date, unde sunt amplasate clusterele Keycloak, compuse din noduri Keycloak. Totuși, nu există nicio diferență între nodurile Keycloak din diferite centre de date.

Nodurile Keycloak utilizează un Java Data Grid extern (servere Infinispan) pentru a comunica între centrele de date. Comunicarea se desfășoară prin protocolul Infinispan HotRod.

Cacherele Infinispan trebuie să fie configurate cu atributul remoteStore, astfel încât datele să poată fi stocate în cache-uri externe (într-un alt centru de date, nota traducătorului) . Există clustere în care se utilizează infinispan printre serverele JDG, astfel încât datele stocate pe JDG1 în locația site1 vor fi replicate pe JDG2 în locația site2.

Și, în cele din urmă, serverul JDG receptor notifică serverele Keycloak din clusterul său prin conexiuni de client, ceea ce este o caracteristică a protocolului HotRod. Nodurile Keycloak de pe site2 își actualizează cache-urile Infinispan, iar sesiunea utilizatorului specifică devine, de asemenea, disponibilă pe nodurile Keycloak de pe site2.

Pentru unele cache-uri, nu este necesară copierea de rezervă și se poate renunța complet la scrierea datelor prin serverul Infinispan. Pentru aceasta, trebuie să se elimine configurația remote-store pentru cache-ul specific Infinispan (în fișierul standalone-ha.xml), după care un anumit replicated-cache nu va mai fi necesar pe partea serverului Infinispan.

Configurarea cache-urilor

Există două tipuri de cache-uri în Keycloak:

  • Local. Este situat aproape de baza de date, servește pentru a reduce încărcarea bazei de date și pentru a diminua latența răspunsului. În acest tip de cache sunt stocate realm, clienți, roluri și metadate de utilizator. Acest tip de cache nu este replicat, chiar dacă acest cache face parte din clusterul Keycloak. Dacă o înregistrare din cache se modifică, celelalte servere din cluster primesc un mesaj de modificare, după care înregistrarea este exclusă din cache. Consultați descrierea work mai jos pentru o descriere detaliată a procedurii.

  • Replicat. Gestionează sesiunile utilizatorilor, tokenurile offline și monitorizează erorile de autentificare pentru a detecta tentativele de phishing a parolelor și alte atacuri. Datele stocate în aceste cache-uri sunt temporare, sunt păstrate doar în memoria RAM, dar pot fi replicate în cluster.

Cache-urile Infinispan

Sesiunile — o conceptie în Keycloak, cache-uri separate, care se numesc authenticationSessions, sunt utilizate pentru stocarea datelor utilizatorilor specifici. Cererile de la aceste cache-uri sunt de obicei necesare browserului și serverelor Keycloak, nu aplicațiilor. Aici se manifestă dependența de sesiuni persistente, iar aceste cache-uri nu trebuie replicate, nici măcar în cazul modului Active-Active.

Tokenuri de acțiune. O altă concepție, aplicată de obicei în diverse scenarii, când, de exemplu, utilizatorul trebuie să facă ceva asincron prin email. De exemplu, în timpul procedurii uitare parolă cache actionTokens se aplică pentru a urmări metadatele legate de tokenuri — de exemplu, un token a fost deja folosit și nu poate fi activat din nou. Acest tip de cache trebuie, în general, să fie replicat între centrele de date.

Cache și expirarea datelor stocate funcționează pentru a reduce sarcina pe baza de date. Un astfel de caching îmbunătățește performanța, dar adaugă o problemă evidentă. Dacă un server Keycloak actualizează datele, celelalte servere trebuie să fie informate despre aceasta, pentru a putea actualiza datele în cache-urile lor. Keycloak utilizează cache-uri locale realm-uri, aveau adrese de email pe domeniile și autorizare pentru cache-ul datelor din bază.

Există de asemenea un cache separat work, care este replicat între toate centrele de date. Acesta nu stochează date din bază, ci servește pentru a trimite mesaje despre expirarea datelor nodurilor clusterului între centrele de date. Cu alte cuvinte, imediat ce datele sunt actualizate, un nod Keycloak trimite un mesaj altor noduri din centrul său de date, precum și nodurilor din alte centre de date. După primirea acestui mesaj, fiecare nod curăță datele corespunzătoare din cache-urile lor locale.

Sesiuni utilizator. Cache-urile cu denumirile sessions, clientSessions, offlineSessions și offlineClientSessions, sunt de obicei replicate între centrele de date și servesc pentru stocarea datelor despre sesiunile utilizator care sunt active în timpul activității utilizatorului în browser. Aceste cache-uri funcționează cu aplicația care procesează cererile HTTP de la utilizatorii finali, astfel încât sunt legate de sesiuni persistente și trebuie replicate între centrele de date.

Protecție împotriva atacurilor prin forță brută. Cache loginFailures serve pentru monitorizarea datelor erorilor de autentificare, de exemplu, câte ori un utilizator a introdus o parolă incorectă. Replicarea acestui cache este responsabilitatea administratorului. Dar pentru a avea un număr exact, este bine să activați replicarea între centrele de date. Pe de altă parte, dacă nu se replică aceste date, se poate îmbunătăți performanța, iar dacă se pune această problemă — replicarea poate să nu fie activată.

La implementarea cluster-ului Infinispan, trebuie adăugate definițiile de cache în fișierul de configurare:

Este necesar să configurați și să porniți cluster-ul Infinispan înainte de a lansa cluster-ul Keycloak

Apoi, trebuie configurat remoteStore pentru cache-urile Keycloak. Pentru aceasta, este suficient un script, care se face la fel ca cel precedent, folosit pentru a configura variabila CACHE_OWNERS, trebuie salvat într-un fișier și plasat în director /opt/jboss/startup-scripts:

Conținutul scriptului

embed-server --server-config=standalone-ha.xml --std-out=echo
batch

echo *** Actualizare subsistem inversat ***
/subsystem=infinispan/cache-container=keycloak:write-attribute(name=module, value=org.keycloak.keycloak-model-infinispan)

echo ** Adăugați binding socket la serverul inversat **
/socket-binding-group=standard-sockets/remote-destination-outbound-socket-binding=remote-cache:add(host=${remote.cache.host:localhost}, port=${remote.cache.port:11222})

echo ** Actualizare element de lucru cache replicat **
/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 ** Actualizare element sesiuni cache distribuit **
/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 ** Actualizare element offlineSessions cache distribuit **
/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 ** Actualizare element clientSessions cache distribuit **
/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 ** Actualizare element offlineClientSessions cache distribuit **
/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 ** Actualizare element loginFailures cache distribuit **
/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 ** Actualizare element actionTokens cache distribuit **
/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 ** Actualizare element authenticationSessions cache distribuit **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=authenticationSessions:write-attribute(name=statistics-enabled,value=true)

echo *** Actualizare subsistem undertow ***
/subsystem=undertow/server=default-server/http-listener=default:write-attribute(name=proxy-address-forwarding,value=true)

run-batch
stop-embedded-server

Nu uitați să instalați JAVA_OPTS pentru nodurile Keycloak pentru funcționarea HotRod: remote.cache.host, remote.cache.port și numele serviciului jboss.site.name.

Linkuri și documentație suplimentară

Articolul a fost tradus și pregătit pentru Habr de angajații centrului de formare Slurm — intensificări, cursuri video și formare corporativă de la specialiști practicanți (Kubernetes, DevOps, Docker, Ansible, Ceph, SRE)

Sursa: habr.com

Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS 🔥 Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS | ProHoster