Вклучување на Keycloak во режим HA на Kubernetes

Вклучување на Keycloak во режим HA на Kubernetes

TL; ДР: ќе има опис на Keycloak, систем за контрола на пристап со отворен код, анализа на внатрешната структура, детали за конфигурацијата.

Вовед и клучни идеи

Во оваа статија, ќе ги видиме основните идеи што треба да ги имате на ум при распоредување на кластерот Keycloak на врвот на Kubernetes.

Ако сакате да дознаете повеќе за Keycloak, погледнете ги врските на крајот од статијата. За да станете повеќе нурнати во пракса, можете да учите нашето складиште со модул што ги имплементира главните идеи на овој напис (водичот за лансирање е таму, овој напис ќе обезбеди преглед на уредот и поставките, прибл. преведувач).

Keycloak е сеопфатен систем напишан во Java и изграден на врвот на сервер за апликации Дивината. Накратко, тоа е рамка за овластување која на корисниците на апликациите им дава можности за федерација и ДЗС (едно најавување).

Ве покануваме да го прочитате официјалното веб-страница или Википедија за детално разбирање.

Стартување Keycloak

За извршување на Keycloak потребни се два постојани извори на податоци:

  • База на податоци што се користи за складирање на воспоставени податоци, како што се кориснички информации
  • Кеш на податочна мрежа, кој се користи за кеширање на податоци од базата на податоци, како и за складирање на некои краткотрајни и често променливи метаподатоци, како што се корисничките сесии. Имплементиран Infinispan, што обично е значително побрзо од базата на податоци. Но, во секој случај, податоците зачувани во Infinispan се ефемерни - и не треба никаде да се зачуваат кога кластерот се рестартира.

Keycloak работи во четири различни режими:

  • Нормално - еден и само еден процес, конфигуриран преку датотека самостоен.xml
  • Редовен кластер (опција за висока достапност) - сите процеси мора да ја користат истата конфигурација, која мора да се синхронизира рачно. Поставките се зачувани во датотека самостоен-ха.xml, дополнително треба да направите заеднички пристап до базата на податоци и load balancer.
  • Домен кластер — започнувањето на кластерот во нормален режим брзо станува рутинска и здодевна задача додека кластерот расте, бидејќи секогаш кога ќе се промени конфигурацијата, сите промени мора да се прават на секој јазол на кластерот. Режимот на работа на доменот го решава овој проблем со поставување на некоја споделена локација за складирање и објавување на конфигурацијата. Овие поставки се зачувани во датотеката домен.xml
  • Репликација помеѓу центрите за податоци — ако сакате да го извршите Keycloak во кластер од неколку центри за податоци, најчесто на различни географски локации. Во оваа опција, секој центар за податоци ќе има свој кластер на сервери на Keycloak.

Во оваа статија детално ќе ја разгледаме втората опција, т.е редовен кластер, а ние исто така ќе допреме малку на темата за репликација помеѓу центрите за податоци, бидејќи има смисла да се извршат овие две опции во Kubernetes. За среќа, во Kubernetes нема проблем со синхронизирање на поставките на неколку pods (Keycloak nodes), така што домен кластер Нема да биде многу тешко да се направи.

Исто така, имајте предвид дека зборот Грозд за остатокот од статијата ќе се однесува само на група Keycloak јазли кои работат заедно, нема потреба да се повикуваме на кластерот Kubernetes.

Редовна кластер на Keycloak

За да го извршите Keycloak во овој режим, ви треба:

  • конфигурирајте надворешна споделена база на податоци
  • инсталирајте балансер на оптоварување
  • имаат внатрешна мрежа со IP multicast поддршка

Нема да разговараме за поставување надворешна база на податоци, бидејќи тоа не е целта на овој напис. Да претпоставиме дека некаде има работна база на податоци - и имаме точка за поврзување со неа. Ние едноставно ќе ги додадеме овие податоци во нашите променливи на животната средина.

За подобро да разберете како работи Keycloak во кластерот со отстапување (HA), важно е да знаете колку сето тоа зависи од способностите за кластерирање на Wildfly.

Wildfly користи неколку потсистеми, некои од нив се користат како балансер на оптоварување, некои за толеранција на грешки. Балансерот на оптоварување обезбедува достапност на апликацијата кога јазолот на кластерот е преоптоварен, а толеранцијата на грешки обезбедува достапност на апликацијата дури и ако некои јазли на кластерот не успеат. Некои од овие потсистеми:

  • mod_cluster: Работи заедно со Apache како балансирач на оптоварување на HTTP, зависи од TCP multicast за да ги пронајде домаќините стандардно. Може да се замени со надворешен балансер.

  • infinispan: Дистрибуиран кеш кој користи JGroups канали како транспортен слој. Дополнително, може да го користи протоколот HotRod за да комуницира со надворешен кластер Infinispan за да ги синхронизира содржините на кешот.

  • jgroups: Обезбедува групна комуникациска поддршка за високо достапни услуги базирани на канали на JGroups. Именуваните цевки дозволуваат примероците на апликации во кластерот да се поврзат во групи, така што комуникацијата има својства како што се доверливост, уредност и чувствителност на дефекти.

Баланс на оптоварување

Кога инсталирате балансер како контролер за влез во кластерот Kubernetes, важно е да ги имате на ум следниве работи:

Keycloak претпоставува дека далечинската адреса на клиентот што се поврзува преку HTTP со серверот за автентикација е вистинската IP адреса на клиентскиот компјутер. Поставките за балансирање и влез треба правилно да ги постават заглавијата на HTTP X-Forwarded-For и X-Forwarded-Proto, а исто така зачувајте го оригиналниот наслов HOST. Најновата верзија ingress-nginx (>0.22.0) стандардно го оневозможува ова

Активирање на знамето proxy-address-forwarding со поставување на променлива на околината PROXY_ADDRESS_FORWARDING в true му дава на Keycloak разбирање дека работи зад прокси.

Исто така, треба да овозможите лепливи сесии во влез. Keycloak користи дистрибуирана Infinispan кеш за складирање на податоци поврзани со тековната сесија за автентикација и корисничка сесија. Кешовите стандардно работат со еден сопственик, со други зборови, таа конкретна сесија е зачувана на некој јазол во кластерот, а другите јазли мора да го побараат од далечина ако им треба пристап до таа сесија.

Поточно, спротивно на документацијата, прикачувањето на сесија со името колаче не ни успеа AUTH_SESSION_ID. Keycloak има јамка за пренасочување, затоа препорачуваме да изберете различно име на колаче за лепливата сесија.

Keycloak исто така го прикачува името на јазолот на кој прв одговорил AUTH_SESSION_ID, и бидејќи секој јазол во високо достапната верзија ја користи истата база на податоци, секој од нив би требало да има посебен и единствен идентификатор на јазол за управување со трансакции. Се препорачува да се стави во JAVA_OPTS параметри jboss.node.name и jboss.tx.node.id единствен за секој јазол - можете, на пример, да го ставите името на подлогата. Ако ставите име на pod, не заборавајте за ограничувањето од 23 знаци за jboss променливите, па затоа е подобро да користите StatefulSet наместо Deployment.

Друго гребло - ако подлогата е избришана или рестартирана, нејзиниот кеш се губи. Имајќи го предвид ова, вреди да се постави бројот на сопственици на кеш за сите кешови на најмалку два, така што ќе остане копија од кешот. Решението е да се кандидира скрипта за Wildfly при стартување на подлогата, ставајќи ја во директориумот /opt/jboss/startup-scripts во контејнерот:

Содржина на скрипта

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

потоа поставете ја вредноста на променливата на околината CACHE_OWNERS до бараното.

Приватна мрежа со IP мултикаст поддршка

Ако користите Weavenet како CNI, multicast ќе работи веднаш - и вашите Keycloak јазли ќе се видат веднаш штом ќе бидат лансирани.

Ако немате поддршка за ip multicast во вашиот кластер Kubernetes, можете да ги конфигурирате JGroups да работат со други протоколи за да ги најдете јазлите.

Првата опција е да се користи KUBE_DNSкоја користи headless service за да ги пронајдете јазлите на Keycloak, едноставно го пренесувате JGroups името на услугата што ќе се користи за пронаоѓање на јазлите.

Друга опција е да се користи методот KUBE_PING, кој работи со API за пребарување јазли (треба да го конфигурирате serviceAccount со права list и get, а потоа конфигурирајте ги мешунките да работат со ова serviceAccount).

Начинот на кој JGroups ги наоѓаат јазлите е конфигуриран со поставување на променливи на околината JGROUPS_DISCOVERY_PROTOCOL и JGROUPS_DISCOVERY_PROPERTIES. За KUBE_PING треба да изберете мешунки со барање namespace и labels.

️ Ако користите multicast и извршувате две или повеќе кластери Keycloak во еден кластер Kubernetes (да речеме еден во именскиот простор production, вториот - staging) - јазлите на еден кластер Keycloak можат да се приклучат на друг кластер. Погрижете се да користите единствена мултикаст адреса за секој кластер со поставување на променливиjboss.default.multicast.address и jboss.modcluster.multicast.address в JAVA_OPTS.

Репликација помеѓу центрите за податоци

Вклучување на Keycloak во режим HA на Kubernetes

Связь

Keycloak користи повеќе посебни Infinispan кеш кластери за секој центар за податоци каде што се наоѓаат Keycloack кластерите составени од Keycloak јазли. Но, нема разлика помеѓу Keycloak јазлите во различни центри за податоци.

Јазлите на Keycloak користат надворешна Java Data Grid (сервери Infinispan) за комуникација помеѓу центрите за податоци. Комуникацијата работи според протоколот Infinispan HotRod.

Infinispan кешовите мора да бидат конфигурирани со атрибутот remoteStore, за да може податоците да се складираат од далечина (во друг центар за податоци, прибл. преведувач) кешови. Меѓу серверите JDG има посебни infinispan кластери, така што податоците зачувани на JDG1 на локацијата site1 ќе се реплицира на JDG2 на локацијата site2.

И, конечно, JDG серверот што прима ги известува серверите Keycloak за својот кластер преку клиентските врски, што е карактеристика на протоколот HotRod. Вклучени јазли на тастатурата site2 ажурирајте ги нивните Infinispan кешови и конкретната корисничка сесија станува достапна и на јазлите на Keycloak на site2.

За некои кешови, исто така е можно да не се прават резервни копии и да се избегне целосно пишување податоци преку серверот Infinispan. За да го направите ова, треба да ја отстраните поставката remote-store специфичен Infinispan кеш (во датотеката самостоен-ха.xml), по што некои конкретни replicated-cache исто така повеќе нема да биде потребно на страната на серверот Infinispan.

Поставување кешови

Постојат два вида кешови во Keycloak:

  • Локално. Се наоѓа веднаш до базата на податоци и служи за намалување на оптоварувањето на базата на податоци, како и за намалување на латентноста на одговорот. Овој тип на кеш складира областа, клиентите, улогите и метаподатоците на корисниците. Овој тип на кеш не се реплицира, дури и ако кешот е дел од кластерот Keycloak. Ако некој запис во кешот се промени, пораката за промената се испраќа до преостанатите сервери во кластерот, по што записот се исклучува од кешот. Види опис work Погледнете подолу за подетален опис на постапката.

  • Реплицирано. Обработува сесии на корисници, офлајн токени, а исто така ги следи грешките при најавување за да открие обиди за кражба на лозинка и други напади. Податоците складирани во овие кешови се привремени, складирани само во RAM меморија, но може да се реплицираат низ кластерот.

Infinispan кешови

Сесии - концепт во Keycloak, наречени одделни кешови authenticationSessions, се користат за складирање на податоци на одредени корисници. Барањата од овие кешови обично се потребни од прелистувачот и серверите на Keycloak, а не од апликациите. Овде доаѓа во игра зависноста од лепливи сесии, а таквите кешови сами по себе не треба да се реплицираат, дури и во случај на Active-Active режим.

Акциони токени. Друг концепт, кој обично се користи за различни сценарија кога, на пример, корисникот мора да направи нешто асинхроно по пошта. На пример, во текот на постапката forget password кешот actionTokens се користи за следење на метаподатоците на поврзаните токени - на пример, токен веќе е користен и не може повторно да се активира. Овој тип на кеш обично треба да се реплицира помеѓу центрите за податоци.

Кеширање и стареење на складирани податоци работи на олеснување на оптоварувањето на базата на податоци. Овој вид на кеширање ги подобрува перформансите, но додава очигледен проблем. Ако еден Keycloak сервер ги ажурира податоците, другите сервери мора да бидат известени за да можат да ги ажурираат податоците во нивните кеш. Keycloak користи локални кешови realms, users и authorization за кеширање на податоци од базата на податоци.

Има и посебен кеш work, што се реплицира низ сите центри за податоци. Самиот не складира никакви податоци од базата на податоци, туку служи за испраќање пораки за стареење на податоците до кластерните јазли помеѓу центрите за податоци. Со други зборови, штом податоците се ажурираат, јазолот Keycloak испраќа порака до другите јазли во неговиот центар за податоци, како и до јазлите во другите центри за податоци. По добивањето на таква порака, секој јазол ги брише соодветните податоци во своите локални кешови.

Кориснички сесии. Кешови со имиња sessions, clientSessions, offlineSessions и offlineClientSessions, обично се реплицираат помеѓу центрите за податоци и служат за складирање податоци за корисничките сесии кои се активни додека корисникот е активен во прелистувачот. Овие кешови работат со апликацијата која ги обработува HTTP барањата од крајните корисници, така што тие се поврзани со лепливи сесии и мора да се реплицираат помеѓу центрите за податоци.

Заштита од брутална сила. Кеш loginFailures Се користи за следење на податоците за грешка при најава, како на пример колку пати корисникот внел погрешна лозинка. Репликацијата на овој кеш е одговорност на администраторот. Но, за точна пресметка, вреди да се активира репликацијата помеѓу центрите за податоци. Но, од друга страна, ако не ги реплицирате овие податоци, ќе ги подобрите перформансите, а ако се појави ова прашање, репликацијата може да не се активира.

Кога поставувате кластер Infinispan, треба да додадете дефиниции за кешот во датотеката со поставки:

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

Мора да го конфигурирате и стартувате кластерот Infinispan пред да го стартувате кластерот Keycloak

Потоа треба да конфигурирате remoteStore за кешот на Keycloak. За да го направите ова, доволно е скрипта, која се прави слично на претходната, која се користи за поставување на променливата CACHE_OWNERS, треба да го зачувате во датотека и да го ставите во директориум /opt/jboss/startup-scripts:

Содржина на скрипта

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

Не заборавајте да инсталирате JAVA_OPTS за Keycloak јазли да работат HotRod: remote.cache.host, remote.cache.port и име на услугата jboss.site.name.

Линкови и дополнителна документација

Статијата беше преведена и подготвена за Хабр од страна на вработените Центар за обука на Slurm — интензивни курсеви, видео курсеви и корпоративна обука од специјалисти кои практикуваат (Kubernetes, DevOps, Docker, Ansible, Ceph, SRE)

Извор: www.habr.com

Додадете коментар