Keycloak HA cluster

https://habr.com/ru/companies/tuturu/articles/766284/

Разворачивая у нас в tutu Keycloak, мы столкнулись с необходимостью создания отказоустойчивого кластера. И если с БД всё более-менее понятно, то вот реализовать корректный обмен кешами между Keycloak оказалось довольно непростой для настройки задачей.

Мы упёрлись в то, что в документации Keycloak описано, как создать кластер, используя UDP-мультикаст. И это работает, если у вас все ноды будут находиться в пределах одного сегмента сети (например, ЦОДа). Если с этим сегментом что-то случится, то мы лишимся Keycloak. Нас это не устраивало.

Необходимо сделать так, чтобы ноды приложения были географически распределены между ЦОДами, находясь в разных сегментах сети.

В этом случае в документации Keycloak довольно неочевидно предлагается создать свой собственный кастомный JGroups транспортный стек, чтобы указать все необходимые вам параметры.

Бонусом приложу shell-скрипт, написанный для Consul, который предназначен для снятия анонсов путём выключения bird и попытки восстановления приложения.

Особенности

Нами была выбрана инсталляция без контейнеризации, приложение завёрнуто в systemd-сервис.

Keycloak может принять конфигурацию из четырёх разных источников:

  • CLI: kc.sh --key=value.

  • Переменная окружения: KC_KEY=value.

  • Файл конфигурации: key=value.

  • Файл Java KeyStore: kc.key=value.

Когда в туториале будет заходить речь про добавление переменной в конфигурацию, то подразумевается, что вы сами выбираете удобный для вас вариант.

В туториале я буду описывать передачу параметров через файл конфигурации.

Дано

  • Нода keycloak1.

  • Keycloak версии 20, завёрнутая в systemd-сервис.

  • Интерфейс eth0 с локальным IP-адресом виртуалки. Каждой ноде этот адрес должен быть доступен.

  • Интерфейс eth1, в котором через bgp анонсируется anycast IP-адрес.

  • Отказоустойчивая база данных за пределами Keycloak, к которой мы подключаем приложение.

Задача

Сделать Keycloak отказоустойчивым и геораспределённым.

Нам нужно создать кластер, в котором можно жёстко прибить адреса нод в конфигурации.

Для этого надо создать custom transport stack.

TCPPING

Остановим Keycloak.

Скопируем файл conf/cache-ispn.xml в новый файл conf/custom-cache-ispn.xml.

Добавим в секцию infinispan следующее:

stack name ― имя стека, который мы потом используем в секции transport. Можно указать что угодно. Имя стека будет писаться в логах.

initial_hosts ― перечисляем IP-адреса с портами всех наших Keycloak-нод.

port_range ― TCPPING будет пытаться связать с каждой из нод кластера, начиная с указанного порта + port_range. В нашем случае будет использоваться только порт 7800.

stack.combine ― способ изменения параметров протокола. REPLACE заменяет протокол.

stack.position ― протокол, который мы меняем.

Теперь надо в конфигурации задать с помощью переменной cache-config-file наш .xml-файл, а также переменной http-host указать anycast-адрес (cache=ispn ― это дефолтное значение):

Из-за того, что мы используем anycast-адрес, надо указать IP-адрес хоста, по которому infinispan будет слушать порт 7800. Для этого при запуске сервера нам надо явно задать основной IP-адрес ноды:

После этого мы должны увидеть в логах, что JGroups запускается со стеком add_tcpping:

При запуске остальных нод с такой конфигурацией мы увидим, что кластер обнаружил новый хост и добавил его:

Готово!

Объяснение

Понять, что мы сейчас настроили в .xml-файле, нам помог дефолтный конфиг стека TCP, находящегося по пути:

Там мы можем увидеть, что в качестве протокола обнаружения используется MPING. В conf/custom-cache-ispn.xml c помощью stack.position мы выбираем MPING, а с помощью stack.combine заменяем его на TCPPING.

HashiCorp Consul

Вы настроили anycast (у нас анонсируется адрес с помощью bird), кластеризацию, но вам надо как-то снимать анонсы, если с приложением что-то случится. Вариантов много, я рассмотрю используемый нами.

В этом туториале я не буду разбирать, как настраивать консул, рассмотрим лишь shell-скрипт, который запускается с его помощью раз в 15 секунд.

Keycloak имеет встроенный healthcheck, на его основе и построим проверку.

Чтобы включить его, надо в конфигурации задать переменную:

После этого у приложения становятся доступны следующие эндпоинты:

Будем отслеживать последний эндпоинт, так как там есть проверка подключения к базе данных. Её тоже будем отслеживать:

Также попытаемся один раз восстановить работу Keycloak ребилдом приложения:

Запуск ребилда в фоне позволяет нам запускать скрипт сколько угодно часто, чтобы как можно быстрее реагировать на упавшее приложение и выключать bird.service.

Ну и для управления всем этим безобразием создаём tmp-файл для отслеживания времени запуска восстановления:

Подробная настройка консула выходит за рамки данного туторила.

Собираем это всё вместе в скрипт:

Целиком скрипт

И настраиваем конфиг консула:

Заключение

Данная конфигурация позволила очень легко масштабировать приложение и сделать его геораспределённым. Конечно это далеко не всё, что можно сделать для отказоустойчивости, но, пожалуй, необходимый минимум.

Мы живём в такой конфигурации уже более полугода, и за это время она ни разу не давала сбой. За исключением не зависящих от кластера ситуаций.

Источники информации

https://dantheengineer.com/keycloak-on-distributed-sql-cockroach-part-2-2/

https://infinispan.org/docs/stable/titles/server/server.html

https://www.keycloak.org/server/caching

Last updated

Was this helpful?