Ejecute Keycloak en modo HA en Kubernetes

Ejecute Keycloak en modo HA en Kubernetes

TL; DR: habrá una descripción de Keycloak, un sistema de control de acceso de código abierto, un análisis del dispositivo interno, detalles de configuración.

Introducción e ideas principales

En este artículo, veremos las principales ideas a tener en cuenta al implementar un clúster Keycloak sobre Kubernetes.

Si desea obtener más información sobre Keycloak, consulte los enlaces al final del artículo. Para sumergirte más profundamente en la práctica, puedes estudiar nuestro repositorio con un módulo que implementa las ideas principales de este artículo (la guía de lanzamiento está ahí, en este artículo habrá una descripción general del dispositivo y la configuración, aprox. traductor).

Keycloak es un sistema complejo escrito en Java y construido sobre un servidor de aplicaciones. Vuelo salvaje. En resumen, es un marco de autorización que brinda a los usuarios de aplicaciones la capacidad de federación y SSO (inicio de sesión único).

Te invitamos a leer el oficial sitio web o Wikipedia para una comprensión detallada.

Inicio Keycloak

Keycloak necesita dos fuentes de datos persistentes para ejecutarse:

  • Una base de datos utilizada para almacenar datos persistentes, como información sobre los usuarios.
  • Caché de cuadrícula de datos, que se utiliza para almacenar en caché datos de la base de datos, así como para almacenar algunos metadatos de corta duración y que se modifican con frecuencia, como las sesiones de usuario. Liberado Infinispan, que suele ser significativamente más rápido que la base de datos. Pero en cualquier caso, los datos guardados en Infinispan son efímeros y no es necesario guardarlos en algún lugar cuando se reinicia el clúster.

Keycloak funciona en cuatro modos diferentes:

  • Normal - un solo proceso, configurado a través de un archivo autónomo.xml
  • racimo regular (opción de alta disponibilidad) - Todos los procesos deben usar la misma configuración, que debe sincronizarse manualmente. Los ajustes se almacenan en un archivo. independiente-ha.xml, además, debe realizar un acceso compartido a la base de datos y un balanceador de carga.
  • Clúster de dominio - iniciar el clúster en modo normal se convierte rápidamente en una tarea rutinaria y aburrida a medida que crece el clúster, ya que cada vez que se cambia la configuración, todos los cambios deben realizarse en cada nodo del clúster. El modo de operación de dominio resuelve este problema configurando algo de almacenamiento compartido y publicando la configuración. Estos ajustes se almacenan en un archivo. dominio.xml
  • Replicación entre centros de datos - en caso de que desee ejecutar Keycloak en un grupo de varios centros de datos, con mayor frecuencia en diferentes ubicaciones geográficas. En esta opción, cada centro de datos tendrá su propio clúster de servidores Keycloak.

En este artículo, veremos más de cerca la segunda opción, es decir. grupo normal, así como un pequeño toque en el tema de la replicación entre centros de datos, ya que tiene sentido ejecutar estas dos opciones en Kubernetes. Afortunadamente, Kubernetes no tiene problemas para sincronizar la configuración de varios pods (nodos Keycloak), por lo que clúster de dominio no será muy difícil de hacer.

También tenga en cuenta que la palabra grupo hasta el final del artículo, solo se aplicará a un grupo de nodos Keycloak que trabajen juntos, no es necesario referirse a un clúster de Kubernetes.

Cúmulo de capa de llave regular

Para ejecutar Keycloak en este modo, necesita:

  • configurar una base de datos compartida externa
  • instalar balanceador de carga
  • tener una red interna con soporte ip multicast

No analizaremos la configuración de la base de datos externa, ya que no es el propósito de este artículo. Supongamos que en algún lugar hay una base de datos en funcionamiento, y tenemos un punto de conexión con ella. Simplemente agregaremos estos datos a las variables de entorno.

Para comprender mejor cómo funciona Keycloak en un clúster de conmutación por error (HA), es importante saber cuánto depende de las capacidades de agrupación en clústeres de Wildfly.

Wildfly usa varios subsistemas, algunos de ellos se usan como balanceador de carga, otros se usan para conmutación por error. El equilibrador de carga garantiza la disponibilidad de la aplicación cuando el nodo del clúster está sobrecargado y la conmutación por error garantiza la disponibilidad de la aplicación incluso si fallan algunos de los nodos del clúster. Algunos de estos subsistemas son:

  • mod_cluster: funciona junto con Apache como un equilibrador de carga HTTP, depende de la multidifusión TCP para el descubrimiento de host predeterminado. Puede ser reemplazado por un balanceador externo.

  • infinispan: caché distribuida utilizando canales JGroups como capa de transporte. Opcionalmente, puede usar el protocolo HotRod para comunicarse con un clúster Infinispan externo para sincronizar el contenido del caché.

  • jgroups: Brinda soporte para la asociación de grupos para servicios de alta disponibilidad basados ​​en canales JGroups. Las canalizaciones con nombre permiten que las instancias de aplicación en un clúster se conecten en grupos para que la conexión tenga propiedades como confiabilidad, orden y sensibilidad a fallas.

equilibrador de carga

Al instalar un equilibrador como controlador de entrada en un clúster de Kubernetes, es importante tener en cuenta lo siguiente:

El trabajo de Keycloak implica que la dirección remota del cliente que se conecta a través de HTTP al servidor de autenticación es la dirección IP real de la computadora del cliente. La configuración del balanceador y de ingreso debe establecer correctamente los encabezados HTTP X-Forwarded-For и X-Forwarded-Proto, y mantener el título original HOST. ultima versión ingress-nginx (> 0.22.0) lo deshabilita por defecto

Activación de bandera proxy-address-forwarding estableciendo una variable de entorno PROXY_ADDRESS_FORWARDING в true le da a Keycloak el entendimiento de que se está ejecutando detrás de un proxy.

También debe habilitar sesiones pegajosas en ingreso. Keycloak usa el caché distribuido de Infinispan para almacenar datos asociados con la sesión de autenticación actual y la sesión del usuario. Los cachés son de propietario único por defecto, en otras palabras, esa sesión en particular se almacena en algún nodo del clúster y otros nodos deben solicitarla de forma remota si necesitan acceder a esa sesión.

Específicamente, contrariamente a la documentación, adjuntar una sesión con el nombre de la cookie no funcionó para nosotros. AUTH_SESSION_ID. Keycloak ha repetido la redirección, por lo que recomendamos elegir un nombre de cookie diferente para la sesión persistente.

Keycloak también adjunta el nombre del host que respondió primero a AUTH_SESSION_ID, y dado que cada nodo en la versión de alta disponibilidad usa la misma base de datos, cada uno de ellos debe tener una ID de nodo separada y única para administrar transacciones. Se recomienda poner en JAVA_OPTS Opciones jboss.node.name и jboss.tx.node.id único para cada nodo; por ejemplo, puede establecer el nombre del pod. Si pone el nombre del pod, no se olvide del límite de 23 caracteres para las variables jboss, por lo que es mejor usar StatefulSet, no Deployment.

Otro rake: si se elimina o reinicia un pod, se pierde su caché. Con esto en mente, vale la pena establecer el número de propietarios de caché para todos los cachés en al menos dos, de modo que haya una copia del caché. La solución es ejecutar guión de Wildfly al iniciar el pod, colocándolo en el directorio /opt/jboss/startup-scripts en contenedor:

Contenido del 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

luego establezca el valor de la variable de entorno CACHE_OWNERS a lo requerido.

Red privada con soporte ip multicast

Si está utilizando Weavenet como su CNI, la multidifusión funcionará de inmediato, y sus nodos Keycloak se verán entre sí tan pronto como estén en funcionamiento.

Si no tiene soporte de multidifusión IP en su clúster de Kubernetes, puede configurar JGroups para trabajar con otros protocolos para encontrar nodos.

La primera opción es usar KUBE_DNSque usa headless service para encontrar nodos Keycloak, simplemente pasa a JGroups el nombre del servicio que se usará para encontrar los nodos.

Otra opción es usar el método KUBE_PING, que funciona con la API para encontrar nodos (es necesario configurar serviceAccount con derechos list и gety, a continuación, configure los pods para que funcionen con este serviceAccount).

La forma en que se buscan los nodos para JGroups se configura configurando variables de entorno JGROUPS_DISCOVERY_PROTOCOL и JGROUPS_DISCOVERY_PROPERTIES. Para KUBE_PING debes elegir pods preguntando namespace и labels.

️ Si usa multidifusión y ejecuta dos o más clústeres de Keycloak en el mismo clúster de Kubernetes (digamos uno en el espacio de nombres production, segundo - staging): los nodos de un clúster Keycloak pueden unirse a otro clúster. Asegúrese de usar una dirección de multidifusión única para cada clúster configurando variablesjboss.default.multicast.address и jboss.modcluster.multicast.address в JAVA_OPTS.

Replicación entre centros de datos

Ejecute Keycloak en modo HA en Kubernetes

Enlace

Keycloak utiliza varios clústeres de caché de Infinispan separados para cada centro de datos que aloja clústeres de Keycloack compuestos por nodos de Keycloak. Pero al mismo tiempo, no hay diferencia entre los nodos Keycloak en diferentes centros de datos.

Los nodos de Keycloak utilizan un Java Data Grid externo (servidores Infinispan) para comunicarse entre los centros de datos. La comunicación funciona según el protocolo. Infinispan Hot Rod.

Las cachés de Infinispan deben configurarse con el atributo remoteStore, para que los datos se puedan almacenar en remoto (en otro centro de datos, aprox. traductor) cachés. Hay clústeres infinispan separados entre los servidores JDG, por lo que los datos se almacenan en JDG1 en el sitio site1 se replicará en JDG2 en el sitio site2.

Finalmente, el servidor JDG receptor notifica a los servidores Keycloak de su clúster a través de conexiones de cliente, que es una característica del protocolo HotRod. Nodos Keycloak activados site2 actualice sus cachés de Infinispan y la sesión de usuario en particular estará disponible en los nodos Keycloak en site2.

También es posible que no se realice una copia de seguridad de algunos cachés y que se nieguen por completo a escribir datos a través del servidor Infinispan. Para hacer esto, debe eliminar la configuración remote-store caché de Infinispan específico (en el archivo independiente-ha.xml), después de lo cual algunos replicated-cache ya no será necesario en el lado del servidor Infinispan.

Configuración de cachés

Hay dos tipos de cachés en Keycloak:

  • Local. Se encuentra junto a la base, sirve para reducir la carga en la base de datos, así como para reducir la latencia de respuesta. Este tipo de caché almacena el reino, los clientes, los roles y los metadatos de los usuarios. Este tipo de caché no se replica incluso si este caché es parte de un clúster Keycloak. Si cambia alguna entrada en la memoria caché, se envía un mensaje de cambio al resto de los servidores del clúster, después de lo cual la entrada se excluye de la memoria caché. Ver la descripción work a continuación para obtener una descripción más detallada del procedimiento.

  • replicable. Procesa sesiones de usuario, tokens fuera de línea y monitorea fallas de inicio de sesión para detectar intentos de phishing de contraseñas y otros ataques. Los datos almacenados en estos cachés son temporales, se almacenan solo en la RAM, pero se pueden replicar en todo el clúster.

Cachés de Infinispan

Sesiones - un concepto en Keycloak, cachés separados, que se llaman authenticationSessions, se utilizan para almacenar los datos de usuarios específicos. Las solicitudes de estos cachés suelen ser necesarias para el navegador y los servidores Keycloak, no para las aplicaciones. Aquí es donde se manifiesta la dependencia de las sesiones pegajosas, y tales cachés no necesitan replicarse, incluso en el caso del modo Activo-Activo.

fichas de acción. Otro concepto, generalmente utilizado para varios escenarios, cuando, por ejemplo, el usuario necesita hacer algo de forma asíncrona por correo. Por ejemplo, durante el procedimiento forget password caché actionTokens se usa para rastrear los metadatos de tokens relacionados; por ejemplo, el token ya se usó y no se puede reactivar. Este tipo de caché normalmente se debe replicar entre centros de datos.

Almacenamiento en caché y caducidad de los datos almacenados trabaja para quitar la carga de la base de datos. Este almacenamiento en caché mejora el rendimiento pero agrega un problema obvio. Si un servidor Keycloak actualiza los datos, el resto de servidores deben ser notificados para que puedan actualizar sus cachés. Keycloak usa cachés locales realms, users и authorization para almacenar en caché los datos de la base de datos.

También hay un caché separado work, que se replica en todos los centros de datos. No almacena ningún dato de la base de datos, pero sirve para enviar mensajes de caducidad de datos a los nodos del clúster entre los centros de datos. En otras palabras, tan pronto como se actualizan los datos, el nodo Keycloak envía un mensaje a otros nodos en su centro de datos, así como a los nodos en otros centros de datos. Al recibir dicho mensaje, cada nodo purga los datos correspondientes en sus cachés locales.

Sesiones de usuario. Cachés con nombres sessions, clientSessions, offlineSessions и offlineClientSessions, generalmente se replican entre centros de datos y sirven para almacenar datos sobre sesiones de usuario que están activas mientras el usuario está activo en el navegador. Estos cachés funcionan con la aplicación que maneja las solicitudes HTTP de los usuarios finales, por lo que están asociados con sesiones persistentes y deben replicarse entre centros de datos.

protección de fuerza bruta. Cache loginFailures se utiliza para rastrear datos de error de inicio de sesión, como la cantidad de veces que un usuario ingresó una contraseña incorrecta. La replicación de este caché depende del administrador. Pero para un cálculo preciso, vale la pena activar la replicación entre centros de datos. Pero por otro lado, si no replicas estos datos, podrás mejorar el rendimiento, y si surge esta duda, es posible que no se active la replicación.

Al implementar un clúster de Infinispan, debe agregar definiciones de caché al archivo 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" />

Debe configurar e iniciar el clúster Infinispan antes de ejecutar el clúster Keycloak

Entonces necesitas configurar remoteStore para cachés Keycloak. Para ello basta con un script, que se hace de forma similar al anterior, que sirve para poner la variable CACHE_OWNERS, debe guardarlo en un archivo y colocarlo en un directorio /opt/jboss/startup-scripts:

Contenido del 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

No olvides instalar JAVA_OPTS para que los nodos Keycloak funcionen HotRod: remote.cache.host, remote.cache.port y nombre del servicio jboss.site.name.

Enlaces y documentación adicional

El artículo fue traducido y preparado para Habr por empleados Centro de entrenamiento Slurm — intensivos, cursos en video y capacitación corporativa de profesionales (Kubernetes, DevOps, Docker, Ansible, Ceph, SRE)

Fuente: habr.com

Añadir un comentario