Executar Keycloak en modo HA en Kubernetes

Executar Keycloak en modo HA en Kubernetes

TL, RD: haberá unha descrición de Keycloak, un sistema de control de acceso de código aberto, análise da estrutura interna, detalles de configuración.

Introdución e ideas clave

Neste artigo, veremos as ideas básicas a ter en conta ao implementar un clúster Keycloak encima de Kubernetes.

Se queres saber máis sobre Keycloak, consulta as ligazóns ao final do artigo. Para estar máis inmerso na práctica, podes estudar noso repositorio cun módulo que implementa as ideas principais deste artigo (a guía de lanzamento está alí, este artigo ofrecerá unha visión xeral do dispositivo e da configuración, aprox. tradutor).

Keycloak é un sistema completo escrito en Java e construído sobre un servidor de aplicacións Mosca salvaxe. En resumo, é un marco de autorización que ofrece aos usuarios de aplicacións capacidades de federación e SSO (inicio de sesión único).

Convidámosvos a ler o oficial sitio ou Wikipedia para unha comprensión detallada.

Lanzamento de Keycloak

Keycloak require dúas fontes de datos persistentes para executarse:

  • Unha base de datos utilizada para almacenar datos establecidos, como información do usuario
  • A caché de Datagrid, que se usa para almacenar na caché os datos da base de datos, así como para almacenar algúns metadatos de curta duración e que cambian con frecuencia, como sesións de usuarios. Implementado Infinispan, que adoita ser significativamente máis rápido que a base de datos. Pero en calquera caso, os datos gardados en Infinispan son efémeros e non é necesario gardarlos en ningún lugar cando se reinicia o clúster.

Keycloak funciona en catro modos diferentes:

  • Normal - un só proceso, configurado mediante un ficheiro autónomo.xml
  • Clúster regular (opción de alta dispoñibilidade): todos os procesos deben usar a mesma configuración, que debe sincronizarse manualmente. As configuracións gárdanse nun ficheiro standalone-ha.xml, ademais cómpre facer un acceso compartido á base de datos e un equilibrador de carga.
  • Clúster de dominios — iniciar un clúster en modo normal convértese rapidamente nunha tarefa rutinaria e aburrida a medida que o clúster crece, xa que cada vez que cambia a configuración, todos os cambios deben facerse en cada nodo do clúster. O modo de funcionamento do dominio resolve este problema configurando algunha localización de almacenamento compartido e publicando a configuración. Estes axustes almacénanse no ficheiro dominio.xml
  • Replicación entre centros de datos — se queres executar Keycloak nun clúster de varios centros de datos, a maioría das veces en diferentes localizacións xeográficas. Nesta opción, cada centro de datos terá o seu propio clúster de servidores Keycloak.

Neste artigo consideraremos en detalle a segunda opción, é dicir cluster regular, e tamén abordaremos un pouco o tema da replicación entre centros de datos, xa que ten sentido executar estas dúas opcións en Kubernetes. Afortunadamente, en Kubernetes non hai ningún problema para sincronizar a configuración de varios pods (nodos Keycloak), polo que clúster de dominios Non será moi difícil de facelo.

Tamén teña en conta que a palabra cúmulo para o resto do artigo aplicarase unicamente a un grupo de nodos Keycloak que traballen xuntos, non hai que facer referencia a un clúster de Kubernetes.

Clúster de Keycloak regular

Para executar Keycloak neste modo necesitas:

  • configurar base de datos externa compartida
  • instalar o equilibrador de carga
  • ten unha rede interna con soporte IP multicast

Non discutiremos a creación dunha base de datos externa, xa que non é o propósito deste artigo. Supoñamos que hai unha base de datos funcionando nalgún lugar e temos un punto de conexión con ela. Simplemente engadiremos estes datos ás variables de ambiente.

Para comprender mellor como funciona Keycloak nun clúster de conmutación por fallo (HA), é importante saber canto todo depende das capacidades de agrupación de Wildfly.

Wildfly usa varios subsistemas, algúns deles utilízanse como equilibrador de carga, outros para tolerancia a fallos. O equilibrador de carga garante a dispoñibilidade da aplicación cando se sobrecarga un nodo do clúster e a tolerancia a fallos garante a dispoñibilidade da aplicación aínda que fallen algúns nodos do clúster. Algúns destes subsistemas:

  • mod_cluster: Funciona en conxunto con Apache como equilibrador de carga HTTP, depende da multidifusión TCP para atopar hosts por defecto. Pódese substituír por un equilibrador externo.

  • infinispan: unha caché distribuída que usa as canles de JGroups como capa de transporte. Ademais, pode usar o protocolo HotRod para comunicarse cun clúster Infinispan externo para sincronizar o contido da caché.

  • jgroups: Ofrece soporte de comunicación grupal para servizos de alta dispoñibilidade baseados nas canles de JGroups. As canalizacións con nome permiten que as instancias de aplicación dun clúster se conecten en grupos para que a comunicación teña propiedades como a fiabilidade, a orde e a sensibilidade aos fallos.

Balanceador de carga

Ao instalar un equilibrador como controlador de entrada nun clúster de Kubernetes, é importante ter en conta as seguintes cousas:

Keycloak asume que o enderezo remoto do cliente que se conecta mediante HTTP ao servidor de autenticación é o enderezo IP real do ordenador cliente. A configuración do equilibrador e de entrada debe establecer correctamente as cabeceiras HTTP X-Forwarded-For и X-Forwarded-Proto, e tamén gardar o título orixinal HOST. Última versión ingress-nginx (>0.22.0) desactiva isto por defecto

Activando a bandeira proxy-address-forwarding establecendo unha variable de ambiente PROXY_ADDRESS_FORWARDING в true dá a Keycloak a comprensión de que está a traballar detrás dun proxy.

Tamén debes activar sesións pegajosas en ingreso. Keycloak usa unha caché Infinispan distribuída para almacenar os datos asociados á sesión de autenticación e sesión de usuario actual. Os cachés operan cun único propietario de forma predeterminada, é dicir, esa sesión en particular almacénase nalgún nodo do clúster e outros nodos deben consultalo de forma remota se necesitan acceso a esa sesión.

En concreto, ao contrario que a documentación, anexar unha sesión co nome cookie non funcionou para nós AUTH_SESSION_ID. Keycloak ten un bucle de redirección, polo que recomendamos escoller un nome de cookie diferente para a sesión permanente.

Keycloak tamén achega o nome do nodo ao que respondeu primeiro AUTH_SESSION_ID, e xa que cada nodo da versión altamente dispoñible usa a mesma base de datos, cada un deles debe ter un identificador de nodo separado e único para xestionar as transaccións. Recoméndase poñer JAVA_OPTS parámetros jboss.node.name и jboss.tx.node.id único para cada nodo - pode, por exemplo, poñer o nome do pod. Se pon un nome de pod, non se esqueza do límite de 23 caracteres para as variables jboss, polo que é mellor usar un StatefulSet en lugar de un Deployment.

Outro rake: se se elimina ou se reinicia o pod, pérdese a súa caché. Tendo isto en conta, paga a pena establecer como mínimo dous o número de propietarios da caché para todos os cachés, para que quede unha copia da caché. A solución é correr guión para Wildfly ao iniciar o pod, colocándoo no directorio /opt/jboss/startup-scripts no recipiente:

Contidos do guión

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

a continuación, estableza o valor da variable de ambiente CACHE_OWNERS ao requirido.

Rede privada con soporte IP multicast

Se usas Weavenet como CNI, a multidifusión funcionará inmediatamente e os teus nodos Keycloak veranse entre eles en canto se lancen.

Se non tes compatibilidade con IP multicast no teu clúster de Kubernetes, podes configurar JGroups para traballar con outros protocolos para atopar nós.

A primeira opción é usar KUBE_DNSque usa headless service para atopar nodos de Keycloak, simplemente pasa a JGroups o nome do servizo que se utilizará para atopar os nodos.

Outra opción é usar o método KUBE_PING, que funciona coa API para buscar nodos (cómpre configurar serviceAccount con dereitos list и get, e despois configure os pods para que funcionen con isto serviceAccount).

O xeito en que JGroups atopan os nós configúrase configurando variables de ambiente JGROUPS_DISCOVERY_PROTOCOL и JGROUPS_DISCOVERY_PROPERTIES. Para KUBE_PING cómpre seleccionar pods preguntando namespace и labels.

️ Se usas multicast e executas dous ou máis clústeres de Keycloak nun clúster de Kubernetes (digamos un no espazo de nomes production, o segundo - staging) - os nodos dun clúster Keycloak poden unirse a outro clúster. Asegúrate de usar un enderezo de multidifusión único para cada clúster configurando variablesjboss.default.multicast.address и jboss.modcluster.multicast.address в JAVA_OPTS.

Replicación entre centros de datos

Executar Keycloak en modo HA en Kubernetes

Связь

Keycloak usa varios clústeres de caché Infinispan separados para cada centro de datos onde se atopan os clústeres Keycloack compostos por nodos Keycloak. Pero non hai diferenzas entre os nodos Keycloak en diferentes centros de datos.

Os nodos Keycloak usan unha rede de datos Java externa (servidores Infinispan) para a comunicación entre centros de datos. A comunicación funciona segundo o protocolo Infinispan HotRod.

Os cachés de Infinispan deben configurarse co atributo remoteStore, para que os datos poidan almacenarse de forma remota (noutro centro de datos, aprox. tradutor) cachés. Hai clústeres Infinispan separados entre os servidores JDG, de xeito que os datos almacenados en JDG1 no sitio site1 replicarase a JDG2 in situ site2.

E, finalmente, o servidor JDG receptor notifica aos servidores Keycloak o seu clúster mediante conexións de clientes, que é unha característica do protocolo HotRod. Nodos Keycloak activados site2 actualizar os seus cachés de Infinispan e a sesión específica do usuario tamén estará dispoñible nos nodos Keycloak activados site2.

Para algúns cachés, tamén é posible non facer copias de seguridade e evitar por completo escribir datos a través do servidor Infinispan. Para iso, cómpre eliminar a configuración remote-store caché específica de Infinispan (no ficheiro standalone-ha.xml), despois de que algúns específicos replicated-cache tampouco será necesario no lado do servidor Infinispan.

Configurando cachés

Hai dous tipos de cachés en Keycloak:

  • Local. Está situado xunto á base de datos e serve para reducir a carga da base de datos, así como para reducir a latencia de resposta. Este tipo de caché almacena reino, clientes, roles e metadatos de usuarios. Este tipo de caché non se replica, aínda que a caché forme parte dun clúster Keycloak. Se cambia unha entrada da caché, envíase unha mensaxe sobre o cambio aos servidores restantes do clúster, despois de que a entrada é excluída da caché. Ver descrición work Vexa a continuación unha descrición máis detallada do procedemento.

  • Replicado. Procesa sesións de usuarios, tokens fóra de liña e tamén supervisa os erros de inicio de sesión para detectar intentos de phishing de contrasinais e outros ataques. Os datos almacenados nestas cachés son temporais, só se almacenan na memoria RAM, pero pódense replicar en todo o clúster.

Cachés Infinispan

Sesións - un concepto en Keycloak, chamado cachés separados authenticationSessions, úsanse para almacenar datos de usuarios específicos. As solicitudes destes cachés adoitan ser necesarias polo navegador e os servidores Keycloak, non polas aplicacións. Aquí é onde entra en xogo a dependencia das sesións pegajosas, e tales cachés non precisan ser replicados, mesmo no caso do modo Active-Active.

Fichas de acción. Outro concepto, usado habitualmente para varios escenarios nos que, por exemplo, o usuario debe facer algo de forma asíncrona por correo. Por exemplo, durante o procedemento forget password caché actionTokens úsase para rastrexar os metadatos dos tokens asociados; por exemplo, un token xa se utilizou e non se pode activar de novo. Este tipo de caché normalmente debe ser replicado entre centros de datos.

Almacenamento en caché e envellecemento dos datos almacenados funciona para aliviar a carga da base de datos. Este tipo de almacenamento na caché mellora o rendemento, pero engade un problema obvio. Se un servidor Keycloak actualiza os datos, os outros servidores deben ser notificados para que poidan actualizar os datos nos seus cachés. Keycloak usa cachés locais realms, users и authorization para almacenar en caché os datos da base de datos.

Tamén hai unha caché separada work, que se replica en todos os centros de datos. Ela en si non almacena ningún dato da base de datos, pero serve para enviar mensaxes sobre o envellecemento dos datos aos nodos de cluster entre centros de datos. Noutras palabras, tan pronto como se actualizan os datos, o nodo Keycloak envía unha mensaxe a outros nodos do seu centro de datos, así como aos nodos doutros centros de datos. Despois de recibir tal mensaxe, cada nodo borra os datos correspondentes nas súas cachés locais.

Sesións de usuarios. Cachés con nomes sessions, clientSessions, offlineSessions и offlineClientSessions, adoitan replicarse entre centros de datos e serven para almacenar datos sobre sesións de usuarios que están activas mentres o usuario está activo no navegador. Estes cachés funcionan coa aplicación que procesa solicitudes HTTP dos usuarios finais, polo que están asociados a sesións persistentes e deben ser replicados entre centros de datos.

Protección de forza bruta. Caché loginFailures Utilízase para rastrexar os datos de erro de inicio de sesión, como cantas veces un usuario introduciu un contrasinal incorrecto. A replicación desta caché é responsabilidade do administrador. Pero para un cálculo preciso, paga a pena activar a replicación entre centros de datos. Pero, por outra banda, se non replicas estes datos, mellorarás o rendemento e, se xorde este problema, é posible que a replicación non estea activada.

Ao lanzar un clúster de Infinispan, cómpre engadir definicións de caché ao ficheiro de configuración:

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

Debes configurar e iniciar o clúster Infinispan antes de iniciar o clúster Keycloak

Entón cómpre configurar remoteStore para cachés Keycloak. Para iso abonda cun script, que se fai de xeito similar ao anterior, que serve para establecer a variable CACHE_OWNERS, cómpre gardalo nun ficheiro e poñelo nun directorio /opt/jboss/startup-scripts:

Contidos do guión

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

Non esquezas instalar JAVA_OPTS para que os nodos Keycloak executen HotRod: remote.cache.host, remote.cache.port e nome do servizo jboss.site.name.

Ligazóns e documentación adicional

O artigo foi traducido e preparado para Habr polos empregados Centro de formación Slurm — cursos intensivos, cursos de vídeo e formación corporativa de especialistas en exercicio (Kubernetes, DevOps, Docker, Ansible, Ceph, SRE)

Fonte: www.habr.com

Engadir un comentario