Uruchamianie Keycloak w trybie HA na Kubernetesie

Uruchamianie Keycloak w trybie HA na Kubernetesie

TL; DR: będzie opis Keycloak, systemu kontroli dostępu typu open source, analiza struktury wewnętrznej, szczegóły konfiguracji.

Wprowadzenie i kluczowe pomysły

W tym artykule zobaczymy podstawowe pomysły, o których należy pamiętać podczas wdrażania klastra Keycloak na platformie Kubernetes.

Jeśli chcesz dowiedzieć się więcej o Keycloak, skorzystaj z linków na końcu artykułu. Aby bardziej zanurzyć się w praktyce, możesz się uczyć nasze repozytorium z modułem realizującym główne idee tego artykułu (jest tam przewodnik uruchamiania, w tym artykule znajdziesz przegląd urządzenia i ustawień, około. tłumacz).

Keycloak to kompleksowy system napisany w języku Java i zbudowany na serwerze aplikacji Dzika Mucha. Krótko mówiąc, jest to struktura autoryzacji, która zapewnia użytkownikom aplikacji możliwości federacji i SSO (pojedynczego logowania).

Zapraszamy do zapoznania się z oficjalnym broker lub Wikipedia dla szczegółowego zrozumienia.

Uruchamianie Keycloka

Keycloak wymaga do działania dwóch trwałych źródeł danych:

  • Baza danych używana do przechowywania ustalonych danych, takich jak informacje o użytkowniku
  • Pamięć podręczna Datagrid, która służy do buforowania danych z bazy danych, a także do przechowywania krótkotrwałych i często zmieniających się metadanych, takich jak sesje użytkowników. Wdrożone Nieskończoność, która jest zwykle znacznie szybsza niż baza danych. W każdym razie dane zapisane w Infinispan są tymczasowe i nie trzeba ich nigdzie zapisywać po ponownym uruchomieniu klastra.

Keycloak działa w czterech różnych trybach:

  • Normalny - jeden i tylko jeden proces, konfigurowany poprzez plik samodzielny.xml
  • Zwykły klaster (opcja wysokiej dostępności) - wszystkie procesy muszą korzystać z tej samej konfiguracji, która musi być synchronizowana ręcznie. Ustawienia są przechowywane w pliku samodzielny-ha.xml, dodatkowo musisz udostępnić dostęp do bazy danych i modułu równoważenia obciążenia.
  • Klaster domeny — uruchamianie klastra w trybie normalnym szybko staje się rutynowym i nudnym zadaniem w miarę powiększania się klastra, ponieważ za każdym razem, gdy zmienia się konfiguracja, wszystkie zmiany muszą być wprowadzane w każdym węźle klastra. Tryb domeny rozwiązuje ten problem, konfigurując wspólną lokalizację przechowywania i publikując konfigurację. Ustawienia te są przechowywane w pliku domena.xml
  • Replikacja pomiędzy centrami danych — jeśli chcesz uruchomić Keycloak w klastrze kilku centrów danych, najczęściej w różnych lokalizacjach geograficznych. W tej opcji każde centrum danych będzie miało własny klaster serwerów Keycloak.

W tym artykule szczegółowo rozważymy drugą opcję, czyli zwykły klaster, poruszymy też trochę temat replikacji pomiędzy centrami danych, bo sensowne jest uruchomienie tych dwóch opcji w Kubernetesie. Na szczęście w Kubernetesie nie ma problemu z synchronizacją ustawień kilku podów (węzłów Keycloak), więc klaster domeny Nie będzie to bardzo trudne.

Należy również pamiętać, że słowo grupa gdyż dalsza część artykułu będzie dotyczyć wyłącznie grupy współpracujących ze sobą węzłów Keycloak, nie ma potrzeby odwoływania się do klastra Kubernetes.

Zwykły klaster Keycloków

Aby uruchomić Keycloak w tym trybie, potrzebujesz:

  • skonfiguruj zewnętrzną udostępnioną bazę danych
  • zainstaluj moduł równoważenia obciążenia
  • mieć sieć wewnętrzną z obsługą multiemisji IP

Nie będziemy omawiać konfiguracji zewnętrznej bazy danych, gdyż nie jest to celem tego artykułu. Załóżmy, że gdzieś istnieje działająca baza danych - i mamy do niej punkt połączenia. Po prostu dodamy te dane do zmiennych środowiskowych.

Aby lepiej zrozumieć, jak Keycloak działa w klastrze pracy awaryjnej (HA), ważne jest, aby wiedzieć, jak bardzo wszystko zależy od możliwości klastrowania Wildfly.

Wildfly wykorzystuje kilka podsystemów, niektóre z nich służą do równoważenia obciążenia, inne do odporności na awarie. Moduł równoważenia obciążenia zapewnia dostępność aplikacji, gdy węzeł klastra jest przeciążony, a odporność na awarie zapewnia dostępność aplikacji nawet w przypadku awarii niektórych węzłów klastra. Niektóre z tych podsystemów:

  • mod_cluster: Działa w połączeniu z Apache jako moduł równoważenia obciążenia HTTP, domyślnie korzysta z multiemisji TCP w celu znalezienia hostów. Możliwość wymiany na zewnętrzny balanser.

  • infinispan: Rozproszona pamięć podręczna wykorzystująca kanały JGroups jako warstwę transportową. Dodatkowo może wykorzystywać protokół HotRod do komunikacji z zewnętrznym klastrem Infinispan w celu synchronizacji zawartości pamięci podręcznej.

  • jgroups: Zapewnia wsparcie komunikacji grupowej dla wysoce dostępnych usług w oparciu o kanały JGroups. Potoki nazwane umożliwiają łączenie instancji aplikacji w klastrze w grupy, dzięki czemu komunikacja ma takie właściwości, jak niezawodność, uporządkowanie i wrażliwość na awarie.

Moduł równoważenia obciążenia

Instalując moduł równoważący jako kontroler ruchu przychodzącego w klastrze Kubernetes, należy pamiętać o następujących kwestiach:

Keycloak zakłada, że ​​zdalny adres klienta łączącego się poprzez HTTP z serwerem uwierzytelniającym jest rzeczywistym adresem IP komputera klienckiego. Ustawienia modułu równoważenia i ruchu przychodzącego powinny poprawnie ustawiać nagłówki HTTP X-Forwarded-For и X-Forwarded-Proto, a także zapisz oryginalny tytuł HOST. Ostatnia wersja ingress-nginx (> 0.22.0) domyślnie to wyłącza

Aktywacja flagi proxy-address-forwarding ustawiając zmienną środowiskową PROXY_ADDRESS_FORWARDING в true daje Keycloakowi wiedzę, że działa za pośrednictwem serwera proxy.

Musisz także włączyć lepkie sesje w wejściu. Keycloak wykorzystuje rozproszoną pamięć podręczną Infinispan do przechowywania danych powiązanych z bieżącą sesją uwierzytelniania i sesją użytkownika. Pamięci podręczne domyślnie działają z jednym właścicielem, innymi słowy, konkretna sesja jest przechowywana w jakimś węźle w klastrze, a inne węzły muszą zdalnie wysyłać do niej zapytania, jeśli potrzebują dostępu do tej sesji.

Konkretnie, wbrew dokumentacji, u nas nie zadziałało dołączenie sesji o nazwie cookie AUTH_SESSION_ID. Keycloak ma pętlę przekierowań, dlatego zalecamy wybranie innej nazwy pliku cookie dla sesji trwałej.

Keycloak dołącza również nazwę węzła, na który odpowiedział jako pierwszy AUTH_SESSION_ID, a ponieważ każdy węzeł w wersji wysokodostępnej korzysta z tej samej bazy danych, każdy z nich muszę mieć odrębny i unikalny identyfikator węzła służący do zarządzania transakcjami. Zaleca się włożyć JAVA_OPTS Opcje jboss.node.name и jboss.tx.node.id unikalny dla każdego węzła - możesz np. umieścić nazwę poda. Jeśli umieścisz nazwę poda, nie zapomnij o limicie 23 znaków dla zmiennych jboss, więc lepiej jest użyć StatefulSet niż Deployment.

Kolejna prowizja – jeśli kapsuła zostanie usunięta lub uruchomiona ponownie, jej pamięć podręczna zostanie utracona. Biorąc to pod uwagę, warto ustawić liczbę właścicieli skrzynek dla wszystkich skrzynek na co najmniej dwóch, tak aby pozostała kopia kesza. Rozwiązaniem jest bieganie scenariusz dla Wildfly podczas uruchamiania kapsuły, umieszczając ją w katalogu /opt/jboss/startup-scripts w pojemniku:

Treść skryptu

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

następnie ustaw wartość zmiennej środowiskowej CACHE_OWNERS do wymaganego.

Sieć prywatna z obsługą multiemisji IP

Jeśli używasz Weavenet jako CNI, multicast będzie działać natychmiast, a Twoje węzły Keycloak będą się widzieć zaraz po uruchomieniu.

Jeśli w klastrze Kubernetes nie masz obsługi multiemisji IP, możesz skonfigurować JGroups do pracy z innymi protokołami w celu znalezienia węzłów.

Pierwszą opcją jest użycie KUBE_DNSktóry używa headless service aby znaleźć węzły Keycloak, wystarczy przekazać JGroups nazwę usługi, która będzie używana do wyszukiwania węzłów.

Inną opcją jest użycie tej metody KUBE_PING, który współpracuje z API w celu wyszukiwania węzłów (należy skonfigurować serviceAccount z prawami list и get, a następnie skonfiguruj zasobniki do pracy z tym serviceAccount).

Sposób, w jaki JGroups znajduje węzły, jest konfigurowany poprzez ustawienie zmiennych środowiskowych JGROUPS_DISCOVERY_PROTOCOL и JGROUPS_DISCOVERY_PROPERTIES. Dla KUBE_PING musisz wybrać kapsuły, pytając namespace и labels.

️ Jeśli korzystasz z multiemisji i uruchamiasz dwa lub więcej klastrów Keycloak w jednym klastrze Kubernetes (powiedzmy jeden w przestrzeni nazw production, druga - staging) - węzły jednego klastra Keycloak mogą dołączyć do innego klastra. Pamiętaj, aby użyć unikalnego adresu multiemisji dla każdego klastra, ustawiając zmiennejboss.default.multicast.address и jboss.modcluster.multicast.address в JAVA_OPTS.

Replikacja pomiędzy centrami danych

Uruchamianie Keycloak w trybie HA na Kubernetesie

Łącze

Keycloak wykorzystuje wiele oddzielnych klastrów pamięci podręcznej Infinispan dla każdego centrum danych, w którym znajdują się klastry Keycloack składające się z węzłów Keycloak. Nie ma jednak różnicy między węzłami Keycloak w różnych centrach danych.

Węzły Keycloak korzystają z zewnętrznej siatki danych Java (serwery Infinispan) do komunikacji między centrami danych. Komunikacja działa zgodnie z protokołem Infinispan HotRod.

Pamięci podręczne Infinispan muszą być skonfigurowane przy użyciu tego atrybutu remoteStore, aby dane mogły być przechowywane zdalnie (w innym data center, około. tłumacz) skrytki. Wśród serwerów JDG znajdują się oddzielne klastry Infinispan, dzięki czemu dane przechowywane na JDG1 na miejscu site1 zostaną zreplikowane do JDG2 na miejscu site2.

I wreszcie odbierający serwer JDG powiadamia serwery Keycloak o swoim klastrze poprzez połączenia klienckie, co jest cechą protokołu HotRod. Węzły Keyclook włączone site2 zaktualizuj swoje pamięci podręczne Infinispan, a konkretna sesja użytkownika stanie się również dostępna w węzłach Keycloak na site2.

W przypadku niektórych pamięci podręcznych można również nie tworzyć kopii zapasowych i całkowicie uniknąć zapisywania danych przez serwer Infinispan. Aby to zrobić, musisz usunąć to ustawienie remote-store konkretna pamięć podręczna Infinispan (w pliku samodzielny-ha.xml), po czym trochę konkretów replicated-cache nie będą już potrzebne po stronie serwera Infinispan.

Konfigurowanie pamięci podręcznych

W Keycloak istnieją dwa typy skrzynek:

  • Lokalny. Znajduje się obok bazy danych i służy do zmniejszenia obciążenia bazy danych, a także zmniejszenia opóźnienia odpowiedzi. Ten typ pamięci podręcznej przechowuje dziedzinę, klientów, role i metadane użytkowników. Ten typ pamięci podręcznej nie jest replikowany, nawet jeśli pamięć podręczna jest częścią klastra Keycloak. Jeżeli wpis w pamięci podręcznej ulegnie zmianie, do pozostałych serwerów w klastrze wysyłany jest komunikat o zmianie, po czym wpis jest wykluczany z pamięci podręcznej. Zobacz opis work Bardziej szczegółowy opis procedury znajdziesz poniżej.

  • Replikowane. Przetwarza sesje użytkowników, tokeny offline, a także monitoruje błędy logowania w celu wykrycia prób phishingu haseł i innych ataków. Dane przechowywane w tych pamięciach podręcznych są tymczasowe, przechowywane tylko w pamięci RAM, ale można je replikować w klastrze.

Skrytki Infinispan

Sesje - koncepcja w Keycloak, tzw. oddzielne pamięci podręczne authenticationSessions, służą do przechowywania danych konkretnych użytkowników. Żądania z tych pamięci podręcznych są zwykle potrzebne przeglądarce i serwerom Keycloak, a nie aplikacjom. Tutaj pojawia się zależność od sesji trwałych, a takie pamięci podręczne same w sobie nie muszą być replikowane, nawet w przypadku trybu Active-Active.

Żetony akcji. Inna koncepcja, zwykle używana w różnych scenariuszach, gdy na przykład użytkownik musi zrobić coś asynchronicznie za pośrednictwem poczty. Na przykład podczas zabiegu forget password pamięć podręczna actionTokens wykorzystywane do śledzenia metadanych powiązanych tokenów – np. token został już wykorzystany i nie można go ponownie aktywować. Ten typ pamięci podręcznej zazwyczaj wymaga replikacji między centrami danych.

Buforowanie i starzenie się przechowywanych danych działa w celu odciążenia bazy danych. Ten rodzaj buforowania poprawia wydajność, ale powoduje oczywisty problem. Jeśli jeden serwer Keycloak aktualizuje dane, pozostałe serwery muszą zostać powiadomione, aby mogły zaktualizować dane w swoich pamięciach podręcznych. Keycloak korzysta z lokalnych pamięci podręcznych realms, users и authorization do buforowania danych z bazy danych.

Istnieje również osobna pamięć podręczna work, który jest replikowany we wszystkich centrach danych. Sam nie przechowuje żadnych danych z bazy danych, lecz służy do wysyłania komunikatów o starzeniu się danych do węzłów klastra pomiędzy centrami danych. Innymi słowy, gdy tylko dane zostaną zaktualizowane, węzeł Keycloak wysyła wiadomość do innych węzłów w swoim centrum danych, a także węzłów w innych centrach danych. Po otrzymaniu takiego komunikatu każdy węzeł czyści odpowiednie dane w swoich lokalnych pamięciach podręcznych.

Sesje użytkowników. Skrytki z nazwami sessions, clientSessions, offlineSessions и offlineClientSessions, są zwykle replikowane między centrami danych i służą do przechowywania danych o sesjach użytkownika, które są aktywne, gdy użytkownik jest aktywny w przeglądarce. Te pamięci podręczne współpracują z aplikacją przetwarzającą żądania HTTP od użytkowników końcowych, dlatego są powiązane z sesjami trwałymi i muszą być replikowane między centrami danych.

Ochrona przed brutalną siłą. Pamięć podręczna loginFailures Służy do śledzenia danych o błędach logowania, np. ile razy użytkownik wprowadził nieprawidłowe hasło. Za replikację tej pamięci podręcznej odpowiada administrator. Ale dla dokładnych obliczeń warto aktywować replikację pomiędzy centrami danych. Ale z drugiej strony, jeśli nie zreplikujesz tych danych, poprawisz wydajność, a jeśli pojawi się ten problem, replikacja może nie zostać aktywowana.

Podczas wdrażania klastra Infinispan należy dodać definicje pamięci podręcznej do pliku ustawień:

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

Przed uruchomieniem klastra Keycloak należy skonfigurować i uruchomić klaster Infinispan

Następnie musisz skonfigurować remoteStore dla pamięci podręcznych Keycloak. Aby to zrobić wystarczy skrypt, który wykonuje się analogicznie do poprzedniego, który służy do ustawienia zmiennej CACHE_OWNERS, musisz zapisać go do pliku i umieścić w katalogu /opt/jboss/startup-scripts:

Treść skryptu

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

Nie zapomnij zainstalować JAVA_OPTS dla węzłów Keycloak do uruchomienia HotRod: remote.cache.host, remote.cache.port i nazwa usługi jboss.site.name.

Linki i dodatkowa dokumentacja

Artykuł został przetłumaczony i przygotowany dla Habr przez pracowników Centrum szkoleniowe slumsów — kursy intensywne, kursy wideo i szkolenia korporacyjne prowadzone przez praktykujących specjalistów (Kubernetes, DevOps, Docker, Ansible, Ceph, SRE)

Źródło: www.habr.com

Dodaj komentarz