Стартирайте Keycloak в HA режим на Kubernetes

Стартирайте Keycloak в HA режим на Kubernetes

TL; DR: ще има описание на Keycloak, система за контрол на достъпа с отворен код, анализ на вътрешното устройство, подробности за конфигурацията.

Въведение и основни идеи

В тази статия ще видим основните идеи, които трябва да имате предвид, когато разгръщате Keycloak клъстер върху Kubernetes.

Ако искате да научите повече за Keycloak, моля, вижте връзките в края на статията. За да се потопите по-дълбоко в практиката, можете да изучавате нашето хранилище с модул, който реализира основните идеи на тази статия (ръководството за стартиране е там, в тази статия ще има общ преглед на устройството и настройките, прибл. преводач).

Keycloak е сложна система, написана на Java и изградена върху сървър за приложения. Дива муха. Накратко, това е рамка за оторизация, която дава възможност за обединяване на потребителите на приложението и SSO (единично влизане).

Каним ви да прочетете официалния уебсайт или Wikipedia за подробно разбиране.

Стартирайте Keycloak

Keycloak се нуждае от два постоянни източника на данни, за да работи:

  • База данни, използвана за съхраняване на постоянни данни, като информация за потребители
  • Datagrid кеш, който се използва за кеширане на данни от базата данни, както и за съхраняване на някои краткотрайни и често променяни метаданни, като потребителски сесии. Освободен Инфиниспан, което обикновено е значително по-бързо от базата данни. Но във всеки случай данните, записани в Infinispan, са ефимерни - и не е необходимо да се записват някъде, когато клъстерът се рестартира.

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

  • обикновен - един и само един процес, конфигуриран чрез файл standalone.xml
  • редовен клъстер (силно достъпна опция) - Всички процеси трябва да използват една и съща конфигурация, която трябва да се синхронизира ръчно. Настройките се съхраняват във файл standalone-ha.xml, освен това трябва да направите споделен достъп до базата данни и балансьор на натоварването.
  • Домейн клъстер - стартирането на клъстера в нормален режим бързо се превръща в рутинна и скучна задача, тъй като клъстерът расте, тъй като при всяка промяна на конфигурацията всички промени трябва да се правят на всеки възел на клъстера. Режимът на работа на домейна решава този проблем чрез настройване на споделено хранилище и публикуване на конфигурацията. Тези настройки се съхраняват във файл домейн.xml
  • Репликация между центровете за данни - в случай, че искате да стартирате Keycloak в клъстер от няколко центъра за данни, най-често на различни географски локации. При тази опция всеки център за данни ще има свой собствен клъстер от сървъри Keycloak.

В тази статия ще разгледаме по-отблизо втория вариант, т.е. редовен клъстер, както и малко докосване на темата за репликация между центрове за данни, тъй като има смисъл да стартирате тези две опции в Kubernetes. За щастие Kubernetes няма проблем със синхронизирането на настройките на множество подове (възли на Keycloak), така че домейн клъстер няма да е много трудно да се направи.

Също така имайте предвид, че думата клъстер до края на статията ще се отнася само за група възли на Keycloak, работещи заедно, няма нужда да се позовавате на клъстер на Kubernetes.

Обикновен клъстер Keycloak

За да стартирате Keycloak в този режим, трябва:

  • настройте външна споделена база данни
  • инсталирайте балансьор на натоварването
  • имат вътрешна мрежа с поддръжка на ip multicast

Няма да анализираме конфигурацията на външната база данни, тъй като това не е целта на тази статия. Да приемем, че някъде има работеща база данни - и имаме точка на връзка с нея. Просто ще добавим тези данни към променливите на средата.

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

Wildfly използва няколко подсистеми, някои от тях се използват като балансьор на натоварването, други се използват за отказ. Балансерът на натоварването гарантира наличността на приложението, когато възелът на клъстера е претоварен, а възстановяването при срив гарантира наличността на приложението, дори ако някои от възлите на клъстера се повредят. Някои от тези подсистеми са:

  • mod_cluster: работи във връзка с Apache като HTTP load balancer, зависи от 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 multicast

Ако използвате Weavenet като ваш CNI, мултикастът ще работи веднага – и вашите възли на 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.

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

Репликация между центровете за данни

Стартирайте Keycloak в HA режим на Kubernetes

Связь

Keycloak използва множество отделни Infinispan Cache Clusters за всеки център за данни, хостващ Keycloack клъстери, съставени от Keycloak възли. Но в същото време няма разлика между възлите на Keycloak в различните центрове за данни.

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

Кешовете на Infinispan трябва да бъдат конфигурирани с атрибута remoteStore, така че данните да могат да се съхраняват отдалечено (в друг център за данни, прибл. преводач) кешове. Има отделни infinispan клъстери сред JDG сървърите, така че данните се съхраняват на JDG1 на място site1 ще бъде репликиран към JDG2 на място site2.

И накрая, получаващият JDG сървър уведомява Keycloak сървърите на своя клъстер чрез клиентски връзки, което е характеристика на протокола HotRod. Включени възли на Keycloak site2 актуализират своите кешове на Infinispan и конкретната потребителска сесия става достъпна на възлите на Keycloak на site2.

Също така е възможно някои кешове да не бъдат архивирани и напълно да откажат да записват данни през сървъра на Infinispan. За да направите това, трябва да премахнете настройката remote-store специфичен кеш на Infinispan (във файл standalone-ha.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.

Връзки и допълнителна документация

Статията е преведена и подготвена за Habr от служители Център за обучение на Slurm — интензивни, видео курсове и корпоративно обучение от практици (Kubernetes, DevOps, Docker, Ansible, Ceph, SRE)

Източник: www.habr.com

Добавяне на нов коментар