Как-то в конторе появилась мысль, что надо бы продумать как нам распараллелить работу над одни микриком, так чтобы команды не пересекались друг с другом. Есть некоторые api, над которыми работают несколько команд. Каждая работает над своей фичей локально и пишет тесты, а вот при деплое на стэнд получается столпотворение потому, что нужно изменения слить в одну ветку аля develop и её закинуть на тест. При этом могут быть конфликты при мерже кода или измениться проперти, которые не совместимы между разными ветка.

Мобильный банк сейчас обслуживает 450+ микриков. Над которыми работают более 90 команд. Так как у нас в проекте нет code ownership, то каждая команда вносит изменения в нужные им микрики. Чтобы избежать различного рода сложностей, которые приводят к увеличению времени time to market, нужно было развести разработку отдельных команд так, чтобы они не влияли друг на друга и могли работать параллельно.

Untitled

Проблема

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

При этом мы живём в K8S вместе с Istio. В статье «Практическая магия Istio при построении архитектуры крупных микросервисных систем» коллеги уже писали про Istio и почему выбран именно он. Можно ознакомиться для понимания, что это за зверь такой.

Главное, что у Istio есть мощный механизм маршрутизации запросов, который может помочь нам сделать параллельную разработку и тестирование более безопасным.

Как было сделано

Рассматривали несколько вариантов, такие как организация очереди, отдельный релизный кластер или кластер (namespace) для каждой команды. Но с таким большим количеством команд потребуется огромное количество ресурсов, а также возрастает сложность деплоя и поддержки.

Остановились мы на одном решении, которое назвали Feature Branches. Частично это решение у нас уже применялось для фронтов, которые разрабатывали web. Его надо было доработать и масштабировать для разработки под Andriod, iOS, а также для бэка.

Прежде чем перейдём к техническим подробностям немного терминологии:

  • Feature instance: экземпляр сервиса, для которого настроены правила маршрутизации.

  • Feature name: имя фичи, признак для маршрутизации в feature instance.

  • Feature branch: набор feature instance, объединённый одним feature name.

  • Master branch: ветка по умолчанию, набор релизных версий.

Реализация решения может быть разная, но мы используем Кубер и Istio. В Istio есть отдельные ресурсы, которые отвечают за маршрутизацию — VirtualService (VS) и DestinationRule (DR). Для каждой фичи создаётся свой DR, а VS маршрутизирует трафик между ними. Например, DR для demo-api для мастер ветки выглядит так:

apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
...
spec:
host: demo-api.default.svc.cluster.local
  subsets:
    - labels:
        app.kubernetes.io/instance: demo-api
      name: default

Здесь указан дефолтный subset, который ведёт на задеплоенный master branch.

При этом для фичи DR:

apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
...
spec:
  host: demo-api.default.svc.cluster.local
  subsets:
    - labels:
        app.kubernetes.io/instance: demo-api.feature-31337
      name: feature-31337

Имя фичи у нас задано в subset, а так же попадает в label через точку. VS и DR работают в паре. Для нашего сервиса VS будет выглядеть так:

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
...
spec:
  hosts:
    - demo-api.default.svc.cluster.local
  http:
    - match:
        - headers:
            X-FEATURE-NAME:
              exact: feature-31337
      route:
        - destination:
            host: demo-api.default.svc.cluster.local
            subset: feature-31337
    - route:
        - destination:
            host: demo-api.default.svc.cluster.local
            subset: default

Получается следующая картина. Чтобы понять, куда перенаправить запрос, Istio сначала смотрит в VS, а потом на основе правил матчинга выбирает нужный subset, который задан в DR и ведёт в нужный сервис.

И вот мы подошли к тому, на основе чего происходит маршрутизация HTTP запросов, а именно, учитывается наличие HTTP хедера X-FEATURE-NAME. Так как в VS в секции match проверяется наличие этого хедера в запросе на точное (exact) совпадение его значения с feature-31337, то запрос полетит в под с лэйблом app.kubernetes.io/instance: demo-api.feature-31337.

Сервисы можно объединить в цепочку если задеплоить их с одним feature name. При этом, если в цепочке вызовов в сервисе нет заданной фичи, то запрос полетит в дефолтный subset потому, что не сработает ни одна секция матчинга в VS.

Untitled
Untitled

Такая конфигурация Istio присутствует не только для каждого бэка, но также и для микрофронта. Фронты не используют запросы с хедером X-FEATURE-NAME напрямую. У них есть возможность деплоить фичи под выделенным урлом. Например, feature-31337.demo.net.

Таким урлом удобно делиться с тестировщиками или дизайнерами, чтобы показать свои текущие наработки. В данном случае запрос прилетает в сервис с настроенным Spring Cloud Gateway. GW парсит урл и находит там имя фичи, которое затем проставляет в хедер X-FEATURE-NAME и перенаправляет запрос в нужный микрофронт:

spring:
  cloud:
    gateway:
      routes:
        - id: ignored
          uri: http://demo-ui
          order: 1
          predicates:
            - Host={branch}.demo.net
            - Path=/demo-ui/**
          filters:
            - SetRequestHeader=X-FEATURE-NAME, {branch}
            - RewritePath=/demo-ui/(?<segment>.*), /$\\{segment}

Вот так устроены и работают фича ветки.

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

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

Подытожим коротенечко, что у нас получилось:

№1. Реализации фича веток основывается на правилах маршрутизации, предоставляемых Istio.

№2. Что бы feature instance заработал нужно:

  • Создать DestinationRule, который содержит subset для выбора сервиса по лэйблу.

  • Создать VirtualService, который на основе HTTP хедера перенаправит запрос в нужный subset.

№3. Для обращения к feature instance, нужно:

  • На бэке использовать заголовок X-FEATURE-NAME в запросах.

  • На фронте можно использовать выделенный урл по типу feature-31337.demo.net, если заранее сконфигурировать Spring Cloud Gateway как было показано выше.

Вывод

После внедрения фича веток больше нет необходимости создавать общую ветку в Git и деплоить её в мастер ветку, что значительно повысило удобство разработки и стабильность тест среды.

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

Команды быстро адаптировались под новый рабочий флоу и начали плодить feature instance сотнями. Чтобы не завалить кластер, под них были выделены отдельные ноды, а так же написана джоба, которая занимается очисткой фича веток. Она также задеплоена в кластер Куба и запускается по крону.

Немного цифр. Как я говорил выше, сейчас на проекте работает 90+ команд. При этом общее количество фича веток превышает 300 единиц. Также среди разрабов проводился опрос про удобство работы/использования фича веток. Одним из вопросов просили поставить субъективную оценку по пятибальной шкале. Средняя оценка получилась 4.3 что даже очень хорошо. В опросе приняло участие более половины инженеров.

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

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

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

В данный момент механизм фича веток реализован только для api-х, но при этом у нас ещё есть базы, очереди и кэши, которые пока не разведены. Есть задачки. Будем думать как это сделать красиво и при этом, чтобы было удобно пользоваться.

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


  1. chemtech
    15.03.2024 11:55
    +3

    Спасибо за пост. Вопрос про мониторинг istio. У istio метрики с наибольшим количеством серий это: istio_request_bytes_bucket, istio_request_duration_milliseconds_bucket, istio_response_bytes_bucket. Уменьшали ли вы их кардинальность? Если да, то как?


    1. AliveSubstance Автор
      15.03.2024 11:55

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


  1. BcTpe4HbIu
    15.03.2024 11:55
    +1

    Но если базы, кэши и очереди не разделены, то получается все эти фичаветки используют общие базы, кэши и очереди? А как быть с миграциями, изменениями контрактов в очередях и всем таким?


    1. ashkraba
      15.03.2024 11:55

      Согласен. Тоже не понимаю как это может работать с единой базой.


    1. AliveSubstance Автор
      15.03.2024 11:55

      С очередями пока самое сложное. Явного какого-то способа не нашли. Есть возможность поднять отдельную кафку и натравить на неё микрик.

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

      Если нужен отдельный кеш, то он разводится по имени мапы.

      Но, к сожалению, эти все действия нужно совершать в ручную.


  1. ideological
    15.03.2024 11:55

    >Есть некоторые api, над которыми работают несколько команд. Каждая работает над своей фичей локально и пишет тесты, а вот при деплое на стэнд получается столпотворение потому, что нужно изменения слить в одну ветку аля develop и её закинуть на тест.

    Наверное проще чтобы каждая команда делала свои фичи в отдельном проекте на отдельных роутах. А уже кто-то выше вызывал те что нужны.

    Проще убрать пересечение чем разруливать его.


    1. AliveSubstance Автор
      15.03.2024 11:55

      Тогда сложно будет подружить фронт и бэк. Если фронтовый и бэковый микрик задеплоить с одинаковой фичей, например feature-31337, то можно будет вызывать feature-31337.demo.net и попасть в фича ветку фронта и бэка.