Меня зовут Андрей Рождествин, я QA-специалист СберМаркета. После роста заказов аутентификация на монолите перестала с ними справляться. Я расскажу, как мы перевели её на микросервис и подружили с ним мобильное приложение.

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

Почему решили переезжать на микросервис

У СберМаркета два приложения: клиентское для заказов и приложение для сборщиков и курьеров Shopper App. Давайте рассмотрим кейс с Shopper App.

Изначально аутентификация пользователей в Shopper App происходила на стороне монолита. Бэкэнд выполнял не только основную логику, но и регулярный рефреш токенов, хранение ключей и прочее. Сейчас количество заказов выросло до 200 000 в день, и нагрузка на монолит существенно возросла. 

Поэтому мы начали перестраивать монолитную архитектуру на микросервисы. Решили создать единый сервис аутентификации, который подружился бы сначала с бэкендом монолита, а затем позволил бы безболезненно переехать на отдельные микросервисы. Изменения начали со связки Shopper App +  Shopper Backend.

Так выглядит схема взаимодействия Shopper App и монолита. Пользователи авторизуются через Istio Sidecar, получают JWT от Shopper Backend и уходят с ним на Shopper Backend также через Istio. В сторонние сервисы пользователи заходят напрямую, минуя Istio
Так выглядит схема взаимодействия Shopper App и монолита. Пользователи авторизуются через Istio Sidecar, получают JWT от Shopper Backend и уходят с ним на Shopper Backend также через Istio. В сторонние сервисы пользователи заходят напрямую, минуя Istio

При переходе на микросервисы мы должны были решить проблемы монолита:

  • монолит не мог обеспечить единую авторизацию для всех связанных сервисов;

  • хранилось более 100 тысяч учеток и живых токенов на бэкенде «Шоппера»;

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

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

Переезд занял 4 месяца, при этом еще несколько месяцев мы определяли требования к микросервису и разрабатывали новую архитектуру. 

Мы внедрили API Gateway для аутентификации в микросервисе, и вот что у нас происходит теперь:

Общая схема работы приложения с микросервисом и бэкендом. Все запросы от партнеров идут через API Gateway. При аутентификации сервис сначала проверяет, есть ли уже номер телефона в SSO, не заблокирован ли он. Затем генерирует JWT, если аутентификация разрешена. Приложение получает JWT и передает запросы с ним на Shopper Backend
Общая схема работы приложения с микросервисом и бэкендом. Все запросы от партнеров идут через API Gateway. При аутентификации сервис сначала проверяет, есть ли уже номер телефона в SSO, не заблокирован ли он. Затем генерирует JWT, если аутентификация разрешена. Приложение получает JWT и передает запросы с ним на Shopper Backend

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

Шаг 1. Миграция из старой базы в SSO 

Вначале база учеток в SSO была абсолютно пустая. Первым делом мы перенесли в неё все учетные записи пользователей из старой БД. Каждая запись в базе — это один пользователь приложения. В ней записан идентификатор для связи сущностей с базой данных на бэкенде, номер телефона, набор ролей и признак блокировки.

Вот какие трудности могут встретиться при миграции. 

Проблема: большая нагрузка на базу данных. Её можно обнаружить до миграции, если проанализировать объем экспортируемых данных или сделать тестовую миграцию.   

Решить проблему нагрузки на БД можно тремя способами:

  • прочитать данные из реплики;

  • мигрировать частями;

  • оптимизировать запросы в базу.

Проблема: потеря информации из-за неполной миграции базы данных. Для решения нужно проверить результат тестовой миграции и ревью данных после миграции на бою. 

Проблема: рассинхрон данных из-за появления новых записей и изменения имеющихся во время миграции. Вот тут уже посложнее. Мы исправили так.

  1. Провели миграцию на бою до запуска новой стратегии аутентификации «приложение + API Gateway». Так команда получила новую базу, которая не была доступна пользователям приложения. Это дало возможность проанализировать результат и при необходимости его доработать.

  2. Внедрили механизм периодической синхронизации новых записей. Это помогло досинхронизировать оставшиеся записи после выполнения миграции.

  3. Запустили алертинг на случай невыполнения синхронизации, чтобы можно было вручную стартовать в случае провала.

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

Шаг 2. Синхронизация учетных записей между SSO и бэкендом 

Теперь, когда все учетные записи попали в SSO, их нужно постоянно синхронизировать с Shopper-Backend:

  1. Ряд запросов ориентируется на набор ролей, зашитых в JWT. А в JWT они попадают из SSO. При этом набор ролей, который отображается в админке, приходит с бэкенда Shopper App. Поэтому важно, чтобы набор ролей в SSO и на бэке «Шоппера» всегда совпадали.

  2. При изменении номера телефона оператором в админке нужно, чтобы пользователь смог авторизоваться по новому номеру в приложении и не мог зайти по старому. Для этого номера телефонов должны быть одинаковыми в SSO и на бэкенде.

  3. Для новых учеток необходимо создавать записи на бэкенде и в SSO для разных механизмов регистрации. Потому что после выхода приложения в продакшен в нем доступно три варианта регистрации.

Так выглядят целевая схема регистрации нового пользователя и редактирование учетных записей в Shopper App. После релиза приложения в нем работают три варианта регистрации: через старое приложение, через новое приложение (через SSO) и через административную панель
Так выглядят целевая схема регистрации нового пользователя и редактирование учетных записей в Shopper App. После релиза приложения в нем работают три варианта регистрации: через старое приложение, через новое приложение (через SSO) и через административную панель

Мы синхронизируем данные аутентификации через Kafka. Она сообщает разработчикам о проблемах при аутентификации нового пользователя. Во время тестирования мы нашли два странных поведения.

Проблема: в SSO и на бэкенде существуют записи с одним номером телефона и разными uuid, потому что Kafka не сработала.

В таком случае нужно воспользоваться одним из вариантов:

  • изменить номер телефона в учетной записи на бэкенде через админку и заблокировать эту учетку. Затем выполнить повторный вход через приложение, чтобы пройти синхронизацию от SSO на бэкенде;

  • вручную внести изменения в одном из компонентов.

Проблема: Kafka не срабатывает при смене номера телефона оператором.

Это произошло, когда в администраторской панели пользователю поменяли номер телефона и он авторизовался в приложении с новым номером раньше, чем Kafka доставила сообщение в SSO. То есть два человека выполняли разные действия одновременно. Kafka отработала, но в итоге мы получили две пары учетных записей, которые сложно синхронизировать. 

Эту проблему мы решили с помощью логов. С их помощью получилось быстро восстановить хронологию событий и исправить ситуацию. Оператор получил инструкцию по действиям в админке.  

Еще мы внедрили несколько способов мониторинга проблем и оповещений. Например, создали Runbook — инструкцию на случай возникновения проблем. Настроили метрики в Grafana, чтобы наблюдать успешные и неуспешные операции в SSO. Подключили алертинг на случай ошибок в SSO. Алерты прилетают в специальный канал в Slack со ссылкой на Grafana и Runbook.

Это часть собираемых метрик в Grafana. Они помогают видеть успешные и неуспешные операции в SSO
Это часть собираемых метрик в Grafana. Они помогают видеть успешные и неуспешные операции в SSO

Шаг 3. Одновременная работа авторизации в монолите и микросервисе

Дальше пользователей Shopper App надо было перевести на новую архитектуру так, чтобы они не заметили технических работ. Поэтому мы ненадолго оставили одновременную работу с двумя алгоритмами авторизации — через монолит и микросервис. 

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

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

Шаг 4. Тестирование авторизации на тестовом окружении

Тестирование авторизации проходило до проведения всех работ на проде. Для начала я составил общий чек-лист с проверками на основе пользовательских сценариев для быстрого прогона end-to-end-тестов. Часть этих тестов были дополнены дублирующими API-тестами. 

Этап 1. К тестированию я получил новые ручки для работы в SSO через API Gateway и напрямую. Я собрал их в отдельную коллекцию в Postman, добавив нужное в переменные окружения. Также прикрутил тесты, чтобы вытаскивать из ответа token и refresh_token и использовать их в последующих запросах.

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

Этап 2. Следующим этапом было тестирование хранилища SSO и синхронизации с бэкендом «Шоппера». Здесь я проверял соответствие данных на бэкенде, созданном в SSO, и наоборот. 

Всего я протестировал 20 сценариев, которые учитывали разные варианты использования версий приложения, регистрации и аутентификации, — это только случаи, когда возможны сбои и рассинхрон. Например, создавал новые записи в базе данных, либо апдейтил существующие и затем проверял, как отработает сервис в таком случае.

Тестирование помогло: 

  • доработать логику сервиса и хранилища в SSO;

  • разработать инструкцию на те случаи, когда исправление ситуации возможно только вручную. Например, когда в SSO и на бэкенде существуют записи с одним номером телефона и разными uuid;

  • проверить, все ли было учтено во взаимодействии компонентов, как мы умеем с этим работать. Например, валидация дублирования номеров телефонов на стороне SSO и на бэкенде при синхронизации, работа старой версии мобильного приложения и нового сервиса или переход с одной версии приложения на другое;

  • понять риски выхода из строя Kafka или микросервиса.

Этап 3. Затем я тестировал обновленную сборку приложения. Было важно проверить ее работу с новым API, изучить реакцию на невалидный токен — когда он уже протух или у токена сменились ключи. 

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

Этап 4. Процесс доработки микросервиса далее был тесно связан с тестированием сборки мобильного приложения, так как в новой версии изменили API и ответ бекенда. Эти изменения напрямую могли повлиять на работу новой сборки мобильного приложения. Поэтому требовались доработки сборки, а тестирование требовало дополнительных проверок на каждую корректировку. 

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

Шаг 5. Выпуск в продакшен

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

1. Выкатить SSO

На этом этапе мы столкнулись с подводным камнем со стороны защиты от Akamai. Его мы не заметили на предыдущих этапах. Сказалось инфраструктурное отличие тестовое среды от продакшена. Пришлось оперативно внести изменения в API, но существенных правок не потребовалось.

2. Синхронизировать учетные записи между бэком «Шоппера» и SSO

Мы выкатили сервис SSO на продакшен и синхронизировали все учетки, что были на проде. После я на своей учетной записи проверил еще раз возможные сценарии и синхронизацию данных. Ошибок не обнаружил. 

3. Отдать приложение тестовой группе для тестирования в реальных условиях

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

4. Раскатить приложение на всех пользователей с учетом части не обновившихся.

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

Итоги: что мы получили 

  • Работающий сервис на пути к переходу к микросервисной архитектуре;

  • Аутентификацию пользователей приложения Shopper App в SSO;

  • Гибкий механизм управления доступом;

  • Единый механизм аутентификации для разных сервисов;

  • Аутентификацию запросов перед обращением к микросервисам;

  • Возможность роутить запросы к нужным микросервисам.

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

Итог всей работы. Схема старой и новой аутентификации в Shopper App до и после выкатки в продакшен
Итог всей работы. Схема старой и новой аутентификации в Shopper App до и после выкатки в продакшен

Я рассказал вам про работу по созданию и внедрению всего лишь одного кирпичика микросервисной архитектуры. На него мы потратили несколько месяцев. Сложным было как взаимодействие разных компонентов, так и сотрудничество разных команд, каждая из которых имеет навыки в своей части и свои приоритетные задачи. Было интересно, и работающий на продакшене результат —  это классный итог нашей работы.

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


  1. tumbler
    22.04.2022 16:30
    +5

    нагрузка на монолит существенно возросла. 

    Поэтому мы начали перестраивать монолитную архитектуру на микросервисы

    Всегда интересовал логический переход между этими двумя агрегатными состояниями


    1. fso
      23.04.2022 09:35
      +2

      Коротко: разные части монолита, по-разному нагружены. Вынос таких частей в сервисы позволяет их масштабировать отдельно. Обработка запроса в сервисе почти всегда "короче" и "легче" чем в монолите - отрабатывает только то, что нужно. Никакого общего "бутстрепа", на запрос не влияет и не мешается "соседняя логика".

      Да и сам сервис проще и чище по коду чем монолит, хотябы ввиду размера кодовой базы.


  1. makar_crypt
    22.04.2022 17:03
    -1

    ну и зачем? явно видно работы не было , делали ради чтобы сделать


  1. Hemml
    22.04.2022 20:26
    +2

    О! Вот кто мне нужен! Доколе ваш сервис (веб-клиент) будет терять продукты из корзины?! Ты сидишь, вспоминаешь, всё, что нужно, забиваешь корзину на 25 позиций, заказываешь доставку, доставка приезжает и ты внезапно обнаруживаешь, что некоторых позиций нет, а ты их точно добавлял! В чеке их нет, то есть они теряются или в момент заказа или еще до него. Написал в саппорт, меня попросили прислать скриншот ошибки [facepalm.jpg], по итогам переписки мне сообщили, что в корзине этих позиций не было, а то я и так не знаю. Еще часто бывает, что заказываешь, например, полтора кило груш, а привозят одну (одну!!!) грушу, потому что вес не сохранился в корзине. Сейчас я опытный покупатель, дважды просматриваю корзину перед отправкой, но, тем не менее иногда еще накалываюсь. У меня сильное подозрение, что у вас где-то сидит race condition, как минимум, потеря количества по заказываемой позиции может быть с этим связано -- когда кликаешь несколько раз на плюсик, иногда цифры меняются непредсказуемо.


  1. OkunevPY
    22.04.2022 20:43
    -1

    Я конечно не великий специалист))))

    НО. В мире high-load и BigData о которых нам кричат на каждом углу и которые анонсирую в дата центрах сбера, мтс и прочих крупных игроков, жалкие 100к записей даже за данные не считают. Это семечки.

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

    Мы поднимали реплики между совершенно не совмесимыми БД, и они прокачивали гигабайтные базы данные в оба направления практически не имея нагрузок.

    Что и зачем делали эти 15 человек? Почему было не взять готовое, проверенно решение? Или свой велосипед всегда лучше?

    Хотя да, наверное у таких серьёзных продуктов есть и серьёзное финансирование, поэтому работа ради работы это в порядке вещей.


  1. Inlore
    22.04.2022 21:28

    Какие решения для api gateway рассматривали и на чём в результате остановились?

    Можете пояснить предназначение хидеров GATEWAY_JWT_PAYLOAD и GATEWAY_SECRET? Почему не просто Authorization?


    1. makapohmgn
      23.04.2022 16:32
      -1

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


  1. makapohmgn
    23.04.2022 16:20

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