Executando Keycloak no modo HA no Kubernetes

Executando Keycloak no modo HA no Kubernetes

TL, DR: haverá descrição do Keycloak, sistema de controle de acesso de código aberto, análise da estrutura interna, detalhes de configuração.

Introdução e ideias principais

Neste artigo, veremos as ideias básicas que você deve ter em mente ao implantar um cluster Keycloak no Kubernetes.

Se quiser saber mais sobre o Keycloak, consulte os links no final do artigo. Para ficar mais imerso na prática, você pode estudar nosso repositório com um módulo que implementa as ideias principais deste artigo (o guia de lançamento está lá, este artigo fornecerá uma visão geral do dispositivo e das configurações, Aproximadamente. tradutor).

Keycloak é um sistema abrangente escrito em Java e construído sobre um servidor de aplicativos Vôo selvagem. Resumindo, é uma estrutura de autorização que fornece aos usuários do aplicativo recursos de federação e SSO (logon único).

Convidamos você a ler o documento oficial site ou Wikipedia para compreensão detalhada.

Iniciando o Keycloak

Keycloak requer duas fontes de dados persistentes para ser executado:

  • Um banco de dados usado para armazenar dados estabelecidos, como informações do usuário
  • Cache Datagrid, que é usado para armazenar dados em cache do banco de dados, bem como para armazenar alguns metadados de curta duração e que mudam frequentemente, como sessões de usuário. Implementado Infinispan, que geralmente é significativamente mais rápido que o banco de dados. Mas, em qualquer caso, os dados salvos no Infinispan são efêmeros – e não precisam ser salvos em nenhum lugar quando o cluster for reiniciado.

Keycloak funciona em quatro modos diferentes:

  • Normal - um e apenas um processo, configurado através de um arquivo autônomo.xml
  • Cluster regular (opção de alta disponibilidade) - todos os processos devem utilizar a mesma configuração, que deve ser sincronizada manualmente. As configurações são armazenadas em um arquivo autônomo-ha.xml, além disso você precisa fazer acesso compartilhado ao banco de dados e um balanceador de carga.
  • Cluster de domínio — iniciar um cluster no modo normal rapidamente se torna uma tarefa rotineira e enfadonha à medida que o cluster cresce, pois toda vez que a configuração muda, todas as alterações devem ser feitas em cada nó do cluster. O modo de operação de domínio resolve esse problema configurando algum local de armazenamento compartilhado e publicando a configuração. Essas configurações são armazenadas no arquivo domínio.xml
  • Replicação entre data centers — se você deseja executar o Keycloak em um cluster de vários data centers, geralmente em diferentes localizações geográficas. Nesta opção, cada data center terá seu próprio cluster de servidores Keycloak.

Neste artigo consideraremos em detalhes a segunda opção, ou seja cluster regular, e também abordaremos um pouco o tema da replicação entre data centers, pois faz sentido executar essas duas opções no Kubernetes. Felizmente, no Kubernetes não há problema em sincronizar as configurações de vários pods (nós Keycloak), então cluster de domínio Não será muito difícil de fazer.

Observe também que a palavra grupo pois o restante do artigo se aplicará apenas a um grupo de nós Keycloak trabalhando juntos, não há necessidade de se referir a um cluster Kubernetes.

Cluster Keycloak normal

Para executar o Keycloak neste modo, você precisa:

  • configurar banco de dados compartilhado externo
  • instalar balanceador de carga
  • ter uma rede interna com suporte multicast IP

Não discutiremos a configuração de um banco de dados externo, pois esse não é o objetivo deste artigo. Vamos supor que exista um banco de dados funcionando em algum lugar - e que tenhamos um ponto de conexão com ele. Simplesmente adicionaremos esses dados às variáveis ​​de ambiente.

Para entender melhor como o Keycloak funciona em um cluster de failover (HA), é importante saber o quanto tudo depende dos recursos de cluster do Wildfly.

Wildfly usa vários subsistemas, alguns deles são usados ​​como balanceador de carga, outros para tolerância a falhas. O balanceador de carga garante a disponibilidade do aplicativo quando um nó do cluster está sobrecarregado, e a tolerância a falhas garante a disponibilidade do aplicativo mesmo se alguns nós do cluster falharem. Alguns desses subsistemas:

  • mod_cluster: Funciona em conjunto com o Apache como um balanceador de carga HTTP, depende do multicast TCP para encontrar hosts por padrão. Pode ser substituído por um balanceador externo.

  • infinispan: Um cache distribuído usando canais JGroups como camada de transporte. Além disso, ele pode usar o protocolo HotRod para se comunicar com um cluster Infinispan externo para sincronizar o conteúdo do cache.

  • jgroups: Fornece suporte de comunicação de grupo para serviços altamente disponíveis baseados em canais JGroups. Pipes nomeados permitem que instâncias de aplicativos em um cluster sejam conectadas em grupos para que a comunicação tenha propriedades como confiabilidade, ordem e sensibilidade a falhas.

Balanceador de carga

Ao instalar um balanceador como controlador de entrada em um cluster Kubernetes, é importante ter em mente o seguinte:

Keycloak assume que o endereço remoto do cliente que se conecta via HTTP ao servidor de autenticação é o endereço IP real do computador cliente. As configurações do balanceador e de entrada devem definir os cabeçalhos HTTP corretamente X-Forwarded-For и X-Forwarded-Protoe também salve o título original HOST. Última versão ingress-nginx (> 0.22.0) desativa isso por padrão

Ativando a bandeira proxy-address-forwarding definindo uma variável de ambiente PROXY_ADDRESS_FORWARDING в true dá ao Keycloak a compreensão de que está trabalhando por trás de um proxy.

Você também precisa ativar sessões pegajosas no ingresso. Keycloak usa um cache Infinispan distribuído para armazenar dados associados à sessão de autenticação e à sessão do usuário atuais. Os caches operam com um único proprietário por padrão, ou seja, aquela sessão específica é armazenada em algum nó do cluster, e outros nós devem consultá-la remotamente se precisarem de acesso a essa sessão.

Especificamente, ao contrário da documentação, anexar uma sessão com o nome cookie não funcionou para nós AUTH_SESSION_ID. Keycloak tem um loop de redirecionamento, por isso recomendamos escolher um nome de cookie diferente para a sessão fixa.

Keycloak também anexa o nome do nó que respondeu primeiro ao AUTH_SESSION_ID, e como cada nó na versão altamente disponível usa o mesmo banco de dados, cada um deles deve ter um identificador de nó separado e exclusivo para gerenciar transações. Recomenda-se colocar JAVA_OPTS Opções jboss.node.name и jboss.tx.node.id exclusivo para cada nó - você pode, por exemplo, colocar o nome do pod. Se você colocar um nome de pod, não se esqueça do limite de 23 caracteres para variáveis ​​do jboss, então é melhor usar um StatefulSet ao invés de um Deployment.

Outro rake - se o pod for excluído ou reiniciado, seu cache será perdido. Levando isso em consideração, vale a pena definir o número de proprietários de cache para todos os caches como pelo menos dois, para que permaneça uma cópia do cache. A solução é correr roteiro para Wildfly ao iniciar o pod, colocando-o no diretório /opt/jboss/startup-scripts no contêiner:

Conteúdo do roteiro

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

em seguida, defina o valor da variável de ambiente CACHE_OWNERS ao necessário.

Rede privada com suporte multicast IP

Se você usar o Weavenet como CNI, o multicast funcionará imediatamente - e seus nós Keycloak se verão assim que forem iniciados.

Se você não tiver suporte multicast IP em seu cluster Kubernetes, poderá configurar JGroups para trabalhar com outros protocolos para localizar nós.

A primeira opção é usar KUBE_DNSque usa headless service para encontrar os nós Keycloak, basta passar ao JGroups o nome do serviço que será usado para encontrar os nós.

Outra opção é usar o método KUBE_PING, que funciona com a API para procurar nós (você precisa configurar serviceAccount com direitos list и gete, em seguida, configure os pods para funcionarem com este serviceAccount).

A forma como o JGroups encontra nós é configurada definindo variáveis ​​de ambiente JGROUPS_DISCOVERY_PROTOCOL и JGROUPS_DISCOVERY_PROPERTIES. Para KUBE_PING você precisa selecionar pods perguntando namespace и labels.

️ Se você usar multicast e executar dois ou mais clusters Keycloak em um cluster Kubernetes (digamos um no namespace production, o segundo - staging) - nós de um cluster Keycloak podem ingressar em outro cluster. Certifique-se de usar um endereço multicast exclusivo para cada cluster definindo variáveisjboss.default.multicast.address и jboss.modcluster.multicast.address в JAVA_OPTS.

Replicação entre data centers

Executando Keycloak no modo HA no Kubernetes

Link

Keycloak usa vários clusters de cache Infinispan separados para cada data center onde os clusters Keycloack compostos de nós Keycloak estão localizados. Mas não há diferença entre os nós Keycloak em diferentes data centers.

Os nós Keycloak usam um Java Data Grid externo (servidores Infinispan) para comunicação entre data centers. A comunicação funciona de acordo com o protocolo Infinispan HotRod.

Os caches Infinispan devem ser configurados com o atributo remoteStore, para que os dados possam ser armazenados remotamente (em outro data center, Aproximadamente. tradutor) caches. Existem clusters infinispan separados entre os servidores JDG, para que os dados armazenados no JDG1 no local site1 será replicado para JDG2 no local site2.

E, por fim, o servidor JDG receptor notifica os servidores Keycloak sobre seu cluster por meio de conexões de cliente, que é um recurso do protocolo HotRod. Nós Keycloak ativados site2 atualizam seus caches Infinispan e a sessão específica do usuário também fica disponível nos nós Keycloak em site2.

Para alguns caches, também é possível não fazer backups e evitar totalmente a gravação de dados através do servidor Infinispan. Para fazer isso, você precisa remover a configuração remote-store cache específico do Infinispan (no arquivo autônomo-ha.xml), após o que alguns específicos replicated-cache também não será mais necessário no lado do servidor Infinispan.

Configurando caches

Existem dois tipos de caches no Keycloak:

  • Local. Ele está localizado próximo ao banco de dados e serve para reduzir a carga no banco de dados, bem como para reduzir a latência de resposta. Esse tipo de cache armazena domínio, clientes, funções e metadados do usuário. Este tipo de cache não é replicado, mesmo que o cache faça parte de um cluster Keycloak. Se uma entrada no cache for alterada, uma mensagem sobre a alteração será enviada aos servidores restantes no cluster, após a qual a entrada será excluída do cache. Veja a descrição work Veja abaixo uma descrição mais detalhada do procedimento.

  • Replicado. Processa sessões de usuários, tokens offline e também monitora erros de login para detectar tentativas de phishing de senha e outros ataques. Os dados armazenados nesses caches são temporários, armazenados apenas na RAM, mas podem ser replicados no cluster.

Caches Infinispan

Sessões - um conceito no Keycloak, caches separados chamados authenticationSessions, são usados ​​para armazenar dados de usuários específicos. As solicitações desses caches geralmente são necessárias para o navegador e os servidores Keycloak, não para os aplicativos. É aqui que entra em jogo a dependência de sessões fixas, e esses caches em si não precisam ser replicados, mesmo no caso do modo Ativo-Ativo.

Tokens de Ação. Outro conceito, normalmente utilizado para diversos cenários quando, por exemplo, o usuário deve fazer algo de forma assíncrona por correio. Por exemplo, durante o procedimento forget password cache actionTokens usado para rastrear metadados de tokens associados - por exemplo, um token já foi usado e não pode ser ativado novamente. Esse tipo de cache normalmente precisa ser replicado entre data centers.

Cache e envelhecimento dos dados armazenados funciona para aliviar a carga no banco de dados. Esse tipo de cache melhora o desempenho, mas acrescenta um problema óbvio. Se um servidor Keycloak atualizar dados, os outros servidores deverão ser notificados para que possam atualizar os dados em seus caches. Keycloak usa caches locais realms, users и authorization para armazenar dados em cache do banco de dados.

Há também um cache separado work, que é replicado em todos os data centers. Ele próprio não armazena nenhum dado do banco de dados, mas serve para enviar mensagens sobre o envelhecimento dos dados para nós de cluster entre data centers. Em outras palavras, assim que os dados são atualizados, o nó Keycloak envia uma mensagem para outros nós em seu data center, bem como para nós em outros data centers. Após receber tal mensagem, cada nó limpa os dados correspondentes em seus caches locais.

Sessões de usuário. Caches com nomes sessions, clientSessions, offlineSessions и offlineClientSessions, geralmente são replicados entre data centers e servem para armazenar dados sobre sessões de usuários que estão ativas enquanto o usuário está ativo no navegador. Esses caches funcionam com o aplicativo processando solicitações HTTP de usuários finais, portanto, estão associados a sessões fixas e devem ser replicados entre data centers.

Proteção de força bruta. Cache loginFailures Usado para rastrear dados de erros de login, como quantas vezes um usuário digitou uma senha incorreta. A replicação deste cache é de responsabilidade do administrador. Mas para um cálculo preciso, vale a pena ativar a replicação entre data centers. Mas por outro lado, se você não replicar esses dados, você melhorará o desempenho e, se esse problema surgir, a replicação poderá não ser ativada.

Ao implementar um cluster Infinispan, você precisa adicionar definições de cache ao arquivo de configurações:

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

Você deve configurar e iniciar o cluster Infinispan antes de iniciar o cluster Keycloak

Então você precisa configurar remoteStore para caches Keycloak. Para isso basta um script, que é feito de forma semelhante ao anterior, que serve para definir a variável CACHE_OWNERS, você precisa salvá-lo em um arquivo e colocá-lo em um diretório /opt/jboss/startup-scripts:

Conteúdo do roteiro

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

Não se esqueça de instalar JAVA_OPTS para nós Keycloak executarem HotRod: remote.cache.host, remote.cache.port e nome do serviço jboss.site.name.

Links e documentação adicional

O artigo foi traduzido e preparado para Habr por funcionários Centro de treinamento Slurm — cursos intensivos, cursos em vídeo e treinamento corporativo com especialistas (Kubernetes, DevOps, Docker, Ansible, Ceph, SRE)

Fonte: habr.com

Adicionar um comentário