
TL;DR: ще бъде описание на Keycloak, система за управление на достъпа с отворен код, анализ на вътрешната структура, детайли за настройка.
Въведение и основни идеи
В тази статия ще разгледаме основните идеи, които трябва да запомните при разгръщането на кластер Keycloak върху Kubernetes.
Ако искате да научите повече за Keycloak — моля, разгледайте линковете в края на статията. За да се потопите по-дълбоко в практиката — можете да проучите с модула, който реализира основните идеи на тази статия (наръчник за стартиране ще намерите там, в тази статия ще бъде преглед на структурата и настройките, прим. преводача).
Keycloak е комплексна система, написана на Java и изградена върху приложен сървър . В кратце, това е фреймуърк за автентикация, който дава на потребителите на приложения федеративност и възможност за SSO (еднократно вписване).
Каним ви да прочетете официалната или за подробно разбиране.
Стартиране на Keycloak
За Keycloak са необходими два постоянно запазени източника на данни за стартиране:
- База данни, използвана за съхранение на стабилни данни, например информация за потребителите
- Datagrid кеш, който се използва за кеширане на данни от базата, както и за съхраняване на някои краткотрайни и често променящи се метаданни, например потребителски сесии. Реализира се , който обикновено е значително по-бърз от базата данни. Но в никакъв случай данните, съхранявани в Infinispan, са ефемерни — и не трябва да се запазват никъде при рестартиране на кластера.
Keycloak работи в четири различни режима:
- Обикновен — един единствен процес, конфигурира се чрез файла standalone.xml
- Обикновен кластер (високодостъпен вариант) — всички процеси трябва да използват една и съща конфигурация, която трябва да се синхронизира ръчно. Настройките се съхраняват в файла standalone-ha.xml, допълнително трябва да направите общ достъп до базата данни и балансировчик на натоварването.
- Домашен кластер — стартирането на кластера в обикновен режим бързо става рутинно и скучно с растежа на кластера, тъй като всяко изменение в конфигурацията трябва да се внесе на всеки възел на кластера. Домашният работен режим решава този проблем, като настройва някакво общо хранилище и публикува конфигурацията. Тези настройки се съхраняват в файла domain.xml
- Репликация между датацентрове — ако искате да стартирате Keycloak в клъстер от няколко дата центъра, които често са географски различни. В този вариант всеки дата център ще има свой собствен клъстер от Keycloak сървъри.
В тази статия подробно ще разгледаме втория вариант, а именно обикновен клъстер, а също така ще засегнем и темата за репликацията между дата центровете, тъй като тези два варианта имат смисъл да бъдат стартирани в Kubernetes. За щастие, в Kubernetes няма проблем със синхронизацията на настройките на няколко пода (узла на Keycloak), така че домейнен клъстер няма да бъде особено сложно за изпълнение.
Моля, обърнете внимание, че терминът кластер до края на статията ще се прилага единствено за група от узли на Keycloak, работещи заедно, без да е необходимо да се споменава клъстер Kubernetes.
Обикновен клъстер Keycloak
За да стартирате Keycloak в този режим, е необходимо да:
- настройте външна обща база данни
- инсталирайте балансировчик на натоварването
- имате вътрешна мрежа с поддръжка на ip multicast
Няма да разглеждаме настройката на външната база, тъй като това не е целта на статията. Нека приемем, че има работеща база данни някъде, и имаме точка на свързване към нея. Просто ще добавим тези данни в променливите на средата.
За по-добро разбиране на начина, по който Keycloak работи в отказоустойчив (HA) клъстер, е важно да знаете колко силно това зависи от възможностите на Wildfly за клъстеризация.
Wildfly използва няколко подсистеми, някои от които се използват като балансировчик на натоварването, а други — за отказоустойчивост. Балансировчикът на натоварването осигурява наличността на приложението при претоварване на узел от клъстера, а отказоустойчивостта гарантира наличността на приложението дори в случай на отказ на част от узлите в клъстера. Някои от тези подсистеми:
mod_cluster: работи съвместно с Apache като балансировчик на HTTP, зависи от TCP multicast за откриване на узли по подразбиране. Може да бъде заменен с външен балансировчик.infinispan: разпределен кеш, използващ канали JGroups като транспортен слой. Освен това може да прилага протокола HotRod за връзка с външен клъстер Infinispan за синхронизация на съдържанието на кеша.jgroups: предоставя групова комуникационна поддръжка за високо достъпни услуги на базата на JGroups канали. Именуваните канали позволяват на приложенските инстанции в клъстера да се свързват в групи, така че комуникацията да притежава свойства като надеждност, подреденост и чувствителност към сривове.
Балансировчик на натоварването
При инсталиране на балансировчика като ingress контролер в клъстера Kubernetes е важно да се имат предвид следните неща:
Работата на Keycloak предполага, че отдалеченият адрес на клиента, свързан по HTTP с аутентикационния сървър, е реалният ip адрес на клиентския компютър. Настройките на балансировчика и ingress трябва коректно да задават HTTP заглавията. X-Forwarded-For и X-Forwarded-Proto, както и да запазват оригиналното заглавие ХОСТ. Последната версия ingress-nginx (> 0.22.0)
Активиране на флага proxy-address-forwarding чрез задаване на променлива среда PROXY_ADDRESS_FORWARDING в истинно дава на Keycloak информация, че работи зад прокси.
Също така трябва да се включи sticky sessions в ingress. Keycloak използва разпределена Infinispan кеш система за съхранение на данни, свързани с текущата аутентикационна сесия и потребителската сесия. Кешовете работят с един собственик по подразбиране, иначе казано, тази конкретна сесия се съхранява на някои възли в клъстера, а другите възли трябва да я запитват отдалечено, ако им е нужен достъп до тази сесия.
Конкретно при нас, противно на документацията, не сработи прикрепването на сесия с име на бисквитка
AUTH_SESSION_ID. Keycloak зацикли в пренасочване, затова препоръчваме да изберете друго име на бисквитка за sticky session.
Също така Keycloak прикрепя името на възела, който е отговорил първи, към AUTH_SESSION_ID, а тъй като всеки възел в хипервисока наличност използва една и съща база данни, всеки от тях отделен и уникален идентификатор на възел за управление на транзакциите. Препоръчително е да зададете в JAVA_OPTS параметрите jboss.node.name и jboss.tx.node.id уникални за всеки възел - например, можете да зададете името на пода. Ако зададете името на пода - не забравяйте за ограничението от 23 символа за jboss променливите, така че е по-добре да използвате StatefulSet, а не Deployment.
Още едно препятствие - ако пода бъде изтрит или рестартиран, кешът му се губи. С оглед на това е важно да зададете броя на собствениците на кеша на минимум два, за да остане копие на кеша. Решението е да стартирате при стартиране на пода, поставяйки го в директорията /opt/jboss/startup-scripts в контейнера:
Съдържание на скрипта
embed-server --server-config=standalone-ha.xml --std-out=echo
batch
echo * Задаване на CACHE_OWNERS на "${env.CACHE_OWNERS}" във всички кеш-контейнери
/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, multicast ще работи веднага — и вашите възли Keycloak ще се виждат един друг, веднага щом бъдат стартирани.
Ако нямате поддръжка на ip multicast в кластера Kubernetes, можете да настроите JGroups да работи с други протоколи за откриване на възли.
Първият вариант — използване на KUBE_DNS, който използва headless service за откриване на възли Keycloak, просто предавате на JGroups името на услугата, което ще се използва за откриване на възли.
Друг вариант — приложението на метода KUBE_PING, който работи с API за откриване на възли (трябва да настроите serviceAccount с права списък и get, след което да настроите подовете да работят с тази serviceAccount).
Методът за откриване на възли за JGroups се настройва чрез задаване на променливите на средата JGROUPS_DISCOVERY_PROTOCOL и JGROUPS_DISCOVERY_PROPERTIES. За KUBE_PING трябва да изберете подовете, задавайки namespace и labels.
️ Ако използвате multicast и стартирате две или повече кластери Keycloak в един кластер Kubernetes (да предположим, един в namespace
производството, вторият —staging) — възлите на един кластер Keycloak могат да се присъединят към друг клъстер. Задължително използвайте уникален multicast адрес за всеки клъстер, задавайки променливитеjboss.default.multicast.addressиjboss.modcluster.multicast.addressвJAVA_OPTS.
Репликация между датацентрове

Свързаност
Keycloak използва множество отделни клъстери от кешове Infinispan за всеки датацентър, в който са разположени клъстерите Keycloak, съставени от възли Keycloak. Но при това няма разлика между възлите Keycloak в различни датацентри.
Възлите на Keycloak използват външна Java Data Grid (сървъри Infinispan) за свързване между дата центрове. Връзката работи по протокол .
Кешовете на 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:
Локален. Той се намира близо до базата, служи за намаляване на натоварването на базата данни, както и за намаляване на латентността на отговора. В този тип кеш се съхраняват realm, клиенти, роли и потребителски метаданни. Този тип кеш не се репликира, дори и ако този кеш е част от клъстер на Keycloak. Ако се променя някаква запис в кеша — на останалите сървъри в клъстера се изпраща съобщение за промяна, след което записът се изключва от кеша. Вижте описанието
workпо-долу, за по-подробно описание на процедурата.Реплицирающ. Обработва потребителски сесии, offline токени, а също така следи за грешки при входа с цел определяне на опити за фишинг на пароли и други атаки. Съхраняваните данни в тези кешове са временни, съхраняват се само в оперативната памет, но могат да бъдат репликирани в клъстера.
Кешовете на Infinispan
Сесии — концепция в Keycloak, отделни кешове, наречени authenticationSessions, се използват за съхраняване на данни на конкретни потребители. Запитванията от тези кешове обикновено са нужни на браузъра и сървърите на Keycloak, а не на приложенията. Тук се проявява зависимостта от sticky sessions, а самите такива кешове не трябва да се репликират, дори и в случай на Active-Active режим.
Токени на действие. Следваща концепция, обикновено се прилага за различни сценарии, когато, например, потребителят трябва да направи нещо асинхронно по имейл. Например, по време на процедурата забравена парола кеш actionTokens се използва за проследяване на метаданни, свързани с токени — например токенът вече е използван и не може да бъде активиран отново. Този тип кеш обикновено трябва да се репликира между центрове за данни.
Кеширане и остаряване на съ хранявани данни работи, за да намали натоварването на базата данни. Такова кеширане подобрява производителността, но добавя очевиден проблем. Ако един сървър Keycloak актуализира данните, останалите сървъри трябва да бъдат уведомени, за да могат да актуализират данните в своите кешове. Keycloak използва локални кешове realms, users и разрешение за кеширане на данни от базата.
Съществува и отделен кеш work, който се репликира между всички центрове за данни. Той самият не съхранява никакви данни от базата, а служи за изпращане на съобщения за остаряване на данните към възлите на клъстера между центровете за данни. С други думи, веднага щом данните се актуализират, възелът Keycloak изпраща съобщение на другите възли в своя център за данни, а също така и на възлите на другите центрове за данни. След получаване на такова съобщение, всеки възел извършва почистване на съответните данни в своите локални кешове.
Потребителски сесии. Кешовете с имената sessions, clientSessions, offlineSessions и offlineClientSessions, обикновено се репликират между центровете за данни и служат за съхраняване на данни за потребителските сесии, които са активни по време на активността на потребителя в браузъра. Тези кешове работят с приложението, обработващо HTTP заявките от крайни потребители, така че те са свързани с sticky sessions и трябва да се репликират между центровете за данни.
Защита от атаки с груба сила. Кеш loginFailures служи за проследяване на данни за грешки при вход, например колко пъти потребителят е въвел неправилна парола. Репликацията на този кеш е задача на администратора. Но за точно броене е добре да се активира репликацията между центровете за данни. От друга страна, ако не се репликират тези данни, можем да подобрим производителността, и ако възникне такъв въпрос — репликацията може и да не се активира.
При разгръщане на клъстера Infinispan трябва да се добавят определения на кешовете във файла с настройки:
Трябва да конфигурирате и стартирате Infinispan клъстера, преди да стартирате Keycloak клъстера
След това трябва да конфигурирате remoteStore кешовете на Keycloak. За целта е достатъчен скрипт, който се прави по аналогия с предишния, използван за конфигуриране на променливата CACHE_OWNERS, трябва да го запазите в файл и да го поставите в директорията /opt/jboss/startup-scripts:
Съдържание на скрипта
embed-server --server-config=standalone-ha.xml --std-out=echo
batch
echo *** Актуализиране на подсистемата infinispan ***
\/subsystem=infinispan\/cache-container=keycloak:write-attribute(name=module, value=org.keycloak.keycloak-model-infinispan)
echo ** Добавяне на отдалечен сокет свързване към сървъра infinispan **
\/socket-binding-group=standard-sockets\/remote-destination-outbound-socket-binding=remote-cache:add(host=${remote.cache.host:localhost}, port=${remote.cache.port:11222})
echo ** Актуализиране на елемента replicated-cache work **
\/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 ** Актуализиране на елемента distributed-cache sessions **
\/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 ** Актуализиране на елемента distributed-cache offlineSessions **
\/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 ** Актуализиране на елемента distributed-cache clientSessions **
\/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 ** Актуализиране на елемента distributed-cache offlineClientSessions **
\/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 ** Актуализиране на елемента distributed-cache loginFailures **
\/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 ** Актуализиране на елемента distributed-cache actionTokens **
\/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 ** Актуализиране на елемента distributed-cache authenticationSessions **
\/subsystem=infinispan\/cache-container=keycloak\/distributed-cache=authenticationSessions:write-attribute(name=statistics-enabled,value=true)
echo *** Актуализиране на подсистемата undertow ***
\/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.
Линкове и допълнителна документация
Статията е преведена и подготвена за Хабра от служителите на — интензивни курсове, видеокурсове и корпоративно обучение от практикуващи специалисти (Kubernetes, DevOps, Docker, Ansible, Ceph, SRE)
Източник: habr.com
