Недавно мы с коллегами из X5 Tech проводили митап, на котором разбирали, что такое Keycloak X и чего от него ждать. Для тех, кто пропустил или предпочитает читать, а не смотреть, подготовили текстовый вариант. 

Меня зовут Виктор Попов, я техлид DevOps-команды в X5 Tech. И я расскажу, как сэкономить время на чтении плохой документации, с какими сложностями можно столкнуться при обновлении на Keycloak X и как их преодолеть.

Что такое Keycloak X

Keycloak — это Java-приложение, которое запускается на application-сервере. Исторически это был Wildfly для open source версии или JBoss EAP для Red Hat SSO.

Keycloak X — это тот же Keycloak, только на Quarkus. Само приложение остаётся прежним, но есть нюансы.

Quarkus — это фреймворк Java, заточенный под Kubernetes и container first. С Quarkus можно использовать GraalVM, но Keycloak пока этого не делает. Надеюсь, что пока.

Здесь подробнее про первый релиз Keycloak X.

Здесь про Quarkus.

Зачем всё это надо

Зачем нужно было менять Wildfly на Quarkus? Например, вот зачем:

Согласно официальной документации, у Quarkus быстрее запуск приложения, причём есть разница между первым и повторным запуском. А еще нужно меньше памяти.

Вот тут RedHat описывает основные преимущества Quarkus.

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

Смена модели хранения данных

Наконец-то Keycloak переходит на полностью внешний Infinispan. В WildFly версии Keycloak выпилить Infinispan полностью невозможно. Можно добавить внешние ноды или кросс-DCрепликацию в другой кластер IS, но полностью отделить Infinispan от Keycloak не получится, только расширить его.

Теперь же Infinispan используется как внешний сервис. Да, он может запускаться в тех же контейнерах, что и Keycloak, но возможность запуска внешнего Infinispan рассматривается как продовая и целевая.

Подробнее про смену модели хранения данных тут.

И тут.

А вот тут пример настройки внешнего Infinispan.

Упрощение конфигурации

Раньше Wildfly конфижился здоровенными XML-никами. Пример дефолтных конфигов, которые идут в контейнере вместе с Keycloak:

Было
Было
Стало
Стало

Первое — из Keycloak 16, а второе — из Keycloak 17. Как видите, XML-ник на первом скрине — это 652 строчки, на втором — 45 строчек, причём большая часть — это комментарии. Новая версия подарила нам, нормально читаемый конфигурационный файл.

Но немного XML всё же осталось:

Infinispan конфижится в XML, так что у нас сохраняется файл, в котором описывается состояние кэшей. Но для небольших инсталляций можно его даже ни разу не увидеть и жить счастливо. По умолчанию, в стандартном файле всего 85 строк.

Плюс, Keycloak умеет самостоятельно создавать нужную конфигурацию кэшей во внешнем Infinispan. Если Keycloak видит пустой Infinispan, он создаёт из конфига кэши в той конфигурации, которая описана. Больше не нужно подготавливать внешние реплики Infinispan, как это было раньше при расширении кластера кешей.

В каком всё это состоянии

11 февраля 2022 года вышел Keycloak 17.0.0 – это первая стабильная Quarkus-версия. В целом Quarkus-версии с нами уже почти год, но это были превью разной степени сырости. В них часто и много чего менялось. Особой пользы ковыряться в ранних версий не было, потому что стоило тебе разобраться с одной, как в следующей всё уже было по-другому.

Сейчас Widfly версия объявлена Legacy. То есть дефолтная версия Keycloak на Quarkus. И ориентировочно в июле Widfly версии перестанут выходить вообще.

Для пользователей RedHat SSO обещают ещё два года поддержки JBoss версии — они могут мигрировать сильно медленнее. Но Enterprise платит деньги, в конце концов. Всем остальным стоит заранее позаботиться о миграции на Keycloak X — все там будем, ничего с этим не поделать. Но процесс перехода можно сделать чуть менее болезненным.

В каком состоянии документация

Этапы, которые я прошёл при подготовке этого доклада:

Когда я начинал готовиться, была только шестнадцатая версия, и документация отсутствовала вообще. Keycloak 17.0.0 наконец-то принёс нам документацию, и сначала она показалась вполне приличной.

Вот несколько полезных ссылок:

По мере того как я закапывался во всё это, я понял, что прямо сейчас с документацией всё по-прежнему плохо. Много вещей не документированы. Есть прикольные штуки, которые встречаются в примерах, но не встречаются в документации.

Предположим, мы тюним connection pool к базе данных. В документации есть такие настройки:

Настройки, которые есть в документации
Настройки, которые есть в документации

Они хорошо описаны, их можно использовать. Но, представим, что мы также тюним time out подключения к базе данных или проверку подключения. Раньше это настраивалось в XML-нике. Как настраивается сейчас — непонятно, потому что такой информации нет.

Настройки, которых нет в документации
Настройки, которых нет в документации

А вот настройка, которая есть в примере, но нет в документации:

Понять, что делает параметр, вроде можно, но в документации его нет. Остаётся только гадать и ждать пока допишут. Ну или лезть в исходники, ведь это лучшая документация :)

У нас осталось с десяток  параметров, которые не перенесли с Wildfly на Quarkus-версию. Ждём, когда станет понятно, как это сделать.

В основном это всякие настройки подключения Keycloak к базе данных, в нашему случае к Postgres.

Но даже без этих параметров мы планируем миграцию на Quarkus в ближайшие пару месяцев. 

Важные изменения: настройки по умолчанию

Изменились настройки по умолчанию, которые важно учитывать при миграции:

Hostname-strict = true

Keycloak в его текущей Wildfly-реализации реагирует на хедер Host либо X-Forward-Host и отдаёт все эндпоинты и токены согласно тому, что получил в этих хедерах. Например, вы кидаете curl запрос на получение токена с произвольным хедером Host и у токена в поле Issuer будет указано имя хоста из хедера.

Мы в X5 это используем. У нас некоторые приложения внутри кластера Kubernetes входят в Keycloak и получают токены от одного издателя. А снаружи через Ingres получают токены от другого издателя. В некоторых ситуациях это полезно и удобно.

Теперь же по умолчанию Keycloak выдаёт все токены и эндпоинты строго по hostname, указанному в настройках. И указывать hostname обязательно.

Но если выставить hostname-strict=false, то поведение будет такое же, как и в Wildfly версиях.

Следующее нововведение — сменился путь по-умолчанию. Раньше все пути начинались с /auth. Сейчас же /.

То есть, например, раньше well_known эндпоинт выглядел вот так.

А теперь вот так.

Это ломает существующие liveness и readiness проверки, а также прочие запросы к API, которые внешние пользователи могут использовать.

Добавление в настройкиhttp-relative-path=/authсохраняет старые пути.
Правда редиректа с mykeycloak.ru на mykeycloak.ru/auth всё же не будет, и ссылка по которой вы ходили в админку сломается, но можно добавить редирект на ingress перед keycloak.

Также по умолчанию http теперь отключён. Если вы крутите Keycloak в Kubernetes и терминируете SSL на Ingress, то работать не будет.

Вернуть старое поведение можно добавив в конфиг вот такой параметр:
http-enabled=true

А лучше честно пробрасывайте TLS внутрь кластера. 

Важные изменения: темы и SPI

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

Из нового — изменился путь, куда нужно копировать темы, потому что директория JBoss пропала из opt:

  • было — /opt/jboss/keycloak/themes

  • стало — /opt/keycloak/themes

С SPI аналогичная история:

  • если использовался только функционал keycloak, то, скорее всего, SPI будут работать;

  • если использовался функционал WildFly, то SPI работать не будут, потому что Wildfly больше нет.

Но, думаю, если вы пишите кастомные плагины, вы уже знаете, что каждая новая версия Keycloak это лотерея.

А если не пишите но хотите — вот тут коллеги рассказывают о том, как это делать и какие сложности это влечёт.

А ещё поменялся путь для плагинов — /opt/keycloak/providers

Ах да, мой любимый плагин с метриками на 17.0 работает.

Важные изменения: Build и Start

Специфика Quarkus — он предсобирает Java-приложения. При первом запуске приложение с его настройками запекается в некую исполняемую сущность, за счёт чего обеспечивается более быстрый повторный запуск.

Ключ --build «готовит» приложение к запуску с заданными настройками.

Можно сделать вот такой докерфайл:

FROM quay.io/keycloak/keycloak-x:17.0.0
 RUN /opt/keycloak/bin/kc.sh --build  --metrics-enabled=true

Собрать его, и получить контейнер, который будет запускать очень быстро.

Если вам критичен быстрый старт, это правильный способ. Вы сначала делаете --build, собираете контейнер, а потом используете ключ --start, чтобы запустить уже преднастроенное приложение.

Второй вариант — использовать ключ --auto-build, который при запуске всегда сначала прогоняет build, а потом запускает start. Получается дольше, но вам не нужен подготовительный этап, вы можете менять любые настройки, и ничего при этом не ломается.

Как обновлялся я: dockerfile

У нас Standalone-ha (в терминологии Wildfly релизов) инсталляция, внешняя база данных, Keycloak в 3-х репликах в Kubernetes, развёрнутый как statefull set. Мы пересобираем контейнер Keycloak, потому что подкладываем туда темы, внутренние сертификаты и прочие вещи.

Было:

Мы брали контейнер, копировали темы, добавляли сертификаты, удаляли старый XML, копировали новый XML, и возвращали на user 1000, который используется в контейнере keycloak изначально. 

Стало:

Поменялись пути, о которых я говорил выше. Теперь мы меняем два конфигурационных файла — у нас нет standaloneha.xml, зато есть keycloak.conf и cache.ispn

Мы меняем их на наши из репы и собираем контейнер.

Как я обновлялся: конфиги

Было: Огромная xml портянка

Стало: keycloak.conf. Ниже его содержимое

cache=ispn
cache-stack=kubernetes
hostname-strict=false
http-enabled=true
db-pool-min-size=10
db-pool-max-size=100
http-relative-path=/auth
features=token-exchange, admin-fine-grained-authz

Настроек стало сильно меньше. Теперь мы включаем фичи, меняем relative-path на /auth, чтобы все пути остались такими же как и были. Ещё тюним пул коннектов к базе данных, включаем http, отключаем strict hostname и выбираем cache-stack — некоторый набор настроек, которые идут из коробки и которые раньше отвечали за то, как собирается standalone-ha кластер.

Как я обновлялся: подключение к базе

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

DB_ADDR -> KC_DB_URL_HOST
DB_PORT -> KC_DB_PORT
DB_DATABASE -> KC_DB_URL_DATABASE
DB_USER -> KC_DB_USERNAME
DB_PASSWORD -> KC_DB_PASSWORD
DB_VENDOR -> KC_DB

Как видите, имена всех переменных для подключения к базе данных поменялись. Переменную KC_DB я вынес в конфигурационный файл keycloak.conf, потому что это один из тех параметров, которые настраиваются на этапе --build. Тем самым я оставил себе возможность сделать билд на этапе сборки контейнера с настройками, если вдруг этого захочу, чтобы быстрее запускать приложение. 

Зачем нужно было менять имена переменных?
У меня только одна версия:

Как я обновлялся: HA

Как HA конфижилось раньше:

У нас были переменные среды, и в Kubernetes мы использовали метод DNS_PING, чтобы ноды Infinispan находили друг друга с помощью JGroups. В переменных среды мы указывали discovery protocol, discovery properties и имя headless сервиса в k8s по которому и работал DNS_PING.

Как это конфижится сейчас:

cache=ispn
cache-stack=kubernetes

Кажется, что стало намного проще. Есть настройка cache-stack, которая имеет готовый параметр для работы в kubernetes. Но проблема в том, что стэк Kubernetes берёт дефолтные настройки, где не указан адрес headless service, с помощью которого ноды находят друг друга — он смотрит непонятно куда, и кластер не собирается.  Как задать адрес для dns.query в документации Keycloak не описано.

Об эту проблему я бился довольно долго, но в итоге нашёл в документации Infinispan, что можно задать адрес dns_query через Java Ops. Туда мы передали вот такой параметр:

-Djgroups.dns.query=keycloak-headless.keycloakx-tst.svc.cluster.local

И всё заработало.

Как я обновлялся: параметры запуска

Как было раньше:

В контейнер с Keycloak монтировали шельник, в котором запускался docker-entrypoint, идущий в комплекте с контейнером. Мы «скармливали» ему standalone-ha.xml и передавали какое-то количество параметров. Дальше он прогонял ещё один шельник, который обогащал XML-ник в Wildfly, добавлял в него параметры и запускался.

Теперь всё немного проще:

command:
  - /opt/keycloak/bin/kc.sh

args:
  - --verbose
  - -cf /opt/keycloak/conf/keycloak.conf
  - start
  - --auto-build
  - --cache-stack kubernetes

Мы указываем путь конфигурационному файлу, с которым мы стартуем Keycloak. Говорим, что его надо стартануть и указываем ключ --auto-build, потому что мы хотим, чтобы перед стартом он сбилдился. В качестве cache-stack указываем Kubernetes.

Важно: порядок параметров имеет значение. Если мы, например, поставим --auto-build до - start, то чуда не произойдёт — об это я тоже немного побился.

Ещё пара слов про важные изменения

Разработчики обещают ещё несколько полезных изменения. Одно из них — полноценное zero downtime upgrade между версиями. На Wildfly версии rolling update для контейнеров работает нормально только в рамках одной версии. Если мы захотим обновится полностью, то нужен даунтайм. Хотя минорные версии на свой страх и риск я все же обновлял без даунтайма :)

Это важное нововведение, потому что Infinispan теперь позиционируется как внешний компонент, но при этом никакой документации как подключить внешний Infinispan нет. Понятно, что надо просто где-то настроить hotroad connector, указать ему путь к Infinispan, и дальше всё заработает. Но как и где указать Hotroad connector нигде не написано.

Следующее важное изменение связано с внешними интеграциями. Сейчас часть функционала делается плагинами, которые надо вставлять в Keycloak — это сложно, больно и зачастую не очень правильно. Нам обещают, что появится возможность интегрироваться с внешними сервисами, например, по REST. Это значит, что мы сможем настраивать логику непосредственно в Keycloak. Но никакой документации и пруфов нет. Я очень хочу всё это проверить, как только появится документация, обязательно расскажу об этом в чатике русскоязычного Keycloak комьюнити: https://t.me/keycloak_ru

Пару слов о курсе «Безопасность проекта: аутентификация в Keycloak»

Всех, кто хочет получить фундаментальные знания по работе с Keycloak, приглашаем на курс «Безопасность проекта: аутентификация в Keycloak». Вы узнаете, реализовать аутентификацию с минимальными усилиями и сосредоточиться на развитии продукта.

Вам не придётся самостоятельно искать информацию и тратить время на изобретение решений. Мы расскажем, где взять готовое и как его прикрутить.

Комментарии (8)


  1. sandersru
    04.04.2022 20:45
    +2

    Infinispan кстати еще раньше Keycloak перешел на кваркус и уже ушел в натив.

    А с болью и временем запуска Keycloak - скорее бы.

    >> у Quarkus быстрее запуск приложения, причём есть разница между первым и повторным запуском.

    В отличии от того же SpringBoot, у Кваркуса очень много чего перенесено в build time из runtime. Например, RestClient который мы подключаем в виде интерфейса и аннотации @RegisterRestClientна стадии билда готовит код, а не создает все в рантайме.

    Так же стоит заметить, что везде, где только возможно, там переходят на Vert.x и реактивное программирование, что так же сильно положительно сказывается на блокировках, трэдах и памяти в рантайм.

    Есть конечно и ложка дегтя, некоторые вещи затянуты в кваркус из vertx имеют experimental статус в последнем и в самом кваркусе - статус Preview (который видно, только если собирать из стартера), баги там жуткие местами встречаются. Но т.к. код саппортится как в Кваркусе так и в Vert.x ребятами из RedHat, то в моем случае решали все очень оперативно (завтра опять пойду просить фиксить Vert.x под Oracle, который мягко говоря не работает).

    Но в целом впечатление о Кваркус - сугубо положительные.


    1. darthslider Автор
      04.04.2022 21:11
      +1

      А какая боль времени запуска keycloak?
      Он сам по себе стартует секунд 60, кажется на wildfly и при нескольких репликах это вообще не проблема. С кваркусом становится быстрее, но это не особо важно. Поэтому, например, мы не стали делать сборку с --build.
      Долго там стартует кеш, если он большой и много офлайн токенов, но lazy loading таки завезли и он отлично решает проблему офлайн токенов.

      Про кваркус согласен — звучит классно с точки зрения разработки, но, по крайней мере пока, на код кейклока влияния нет никакого и ничего не меняется.


      1. sandersru
        04.04.2022 21:40

        чтобы не вдаваться в холивары, давайте просто скажу - контейнер или нода стартующая за 50-100ms из коробки всегда лучше чем та же но старующая 60 секунд. А keycloak даже голый стартует(или стартовал, на кваркусе пока не щупал) безумно долго.

        >>  по крайней мере пока, на код кейклока влияния нет никакого и ничего не меняется.

        Должны стать меньше расходы по memory/cpu, должен держать больше CCU. В теории. Что там на практике - надо мерять.

        P.S. Пробежался по исходникам 17го Keycloak. Они работу с базами (без реактивщины) поменяли. Местами http server/rest client (местами с реактивщиной, местами без) + подтянули мониторинг/healthcheck. Остальное пока осталось родное похоже.

        Нативом пока не пахнет, но тенденция у RedHat похоже такая есть. Перевод продуктов на кваркус и в натив.


  1. vcKomm
    05.04.2022 17:53

    Это все, конечно, здорово, но приложение пятерочки больше месяца не работает:)


    1. darthslider Автор
      05.04.2022 18:39

      А с темой статьи это как связано?


      1. sandersru
        05.04.2022 21:39

        Ну кушает человек плохо... Скоро 2й месяц... Мерещится...

        Или AI/ML так написан, что на что-то так среагировал :)


  1. amarkevich
    06.04.2022 17:08

    пока пользуемся 15.х, для конфигурации используется возможность запуска скриптов на старте через "/opt/jboss/startup-scripts/ХХХ" - проще, чем тягать конфиги целиком и мигрировать при смене версии


    1. darthslider Автор
      07.04.2022 10:20

      Мы тоже, кстати.
      И я вот вроде не вижу криминала в этом, но почему-то дописывать нужное сразу в xml мне кажется более правильным. Сердцем чую :)

      Кстати в чистом докере без кубера магия стартап скриптов не работает. При рестарте контейнера он пытается дописать в xml конфиги, которые там уже есть и грустит.