Фронтенд-разработка может жить без независимого деплоя, пока у нее не больше 7 микрофронтендов. Но, чем выше число, тем сильнее страдают процессы. Наша команда в Mindbox прошла через это с Octopus, когда деплоила в Yandex Cloud S3. Причем на все обновления был один свободный бакет. Заливаешь код в мастер, а в это время то же самое делают еще пять разработчиков. Скапливается очередь, код еле ползет, а через час деплой вообще обваливается — Octopus не справился с нагрузкой. Пока чинишь это, оказывается, что твои обновления уже попали в продакшен заодно с чужими.  Когда число проектов возросло до 14, все это повторялось с каждым разработчиком по несколько раз в день. Поэтому мы решили вслед за коллегами-бэкендерами перейти на независимый деплой в Kubernetes. В этой статье собран опыт платформы автоматизации маркетинга Mindbox по реформированию фронтенда: Kubernetes вместо Yandex Cloud S3: деплоим микрофронтенды без сбоев Автоматизированный вывод метаданных: экономим ресурсы разработки Постепенный переход: меняем деплой без вреда для пользователей Хот-тестинг: ускоряем обновление фронтенда Советы: как улучшить деплой без микрофронтендов и Kubernetes Команды: 68 бэкенд-разработчиков, 12 фронтенд, 10 SRE. Бэкенд: CDP (customer data platform) как основной монолит и пара десятков микросервисов вокруг него. Фронтенд: старый — смесь C# Razor и React, который выдается из монолитного бэка; новые микрофронтенды на React, которые разделены по бизнес-доменам и выдаются из двух бакетов в Yandex Cloud S3 — А и B. Репозитории: код разных МКФ хранится в отдельных репозиториях. CI/CD для деплоя: GitHub actions и Octopus. В Mindbox долгое время микрофронтенды деплоились созависимо. После пуша в мастер Github Action упаковывал обновленный код в бандл. Octopus подхватывал этот бандл и забирал из хранилища другие — с актуальным кодом всех микрофронтендов. Дальше он проверял, какой из бакетов в Yandex Cloud S3 сейчас свободен — А или B. Предположим, А. Тогда Octopus выгружал в него бандлы и направлял туда трафик. У такой системы два недостатка. Первый в том, что Octopus сбоит, когда число микрофронтендов переваливает за 7 и все они деплоятся одновременно. Он не умеет работать с микрофронтендами последовательно. Вместо того, чтобы целиком обновить один, а потом браться за следующий, Octopus постоянно переключается между ними. Из-за этого деплой зависает — приходится перезапускать его вручную. Вот как выглядит эта проблема на примере двух приложений — C и D: Запущен деплой обновлений приложения C.  Octopus направляет код приложения C в свободный бакет A. Запущен деплой обновлений приложения D. Обновления C еще не выгрузились в бакет. Octopus бросает C, переключается на D и несет его код в тот же бакет A, который по-прежнему свободен. Код приложения D выгружается в бакет A. Octopus возвращается к деплою C. Но к тому времени бакет A уже занят, код C не может в него попасть и деплой прерывается. У системы с двумя бакетами есть и второй недостаток: нарушается принцип независимого деплоя. Поскольку Octopus собирает данные всех известных микрофронтендов, команды разработчиков не могут выкладывать свой код автономно и зависят друг от друга. Представьте, что приложения C и D обновляются одновременно: В продакшене — версия 1 приложения C и 1 — приложения D.  Запускается деплой новых версий — C 2 и D 2. Код обеих направляется в единственный свободный бакет А. Octopus сначала выкладывает С 2 и попутно собирает весь актуальный код, какой находит, в том числе D 2. В продакшене — версии C 2 и D 2. Octopus собирается выложить D 2, но ее деплой уже обвалился, поскольку бакет А был занят. Тем не менее код D 2 ранее попал в продакшен вместе с C 2. Чем больше обновлений так пересекаются, тем сложнее отследить их статус. Деплой в Octopus Статус деплоя Продакшен Запуск     C 2                   D 2 С 2 — активный D 2 — активный C 1 D 1 Выкладка C 2 С 2 — завершен D 2 — прерван C 2 D 2 Выкладка D 2 С 2 — завершен D 2 — прерван C 2 D 2 При деплое версии 2 приложения C Octopus выложил актуальный код всех микрофронтендов. В продакшен попала версия 2 приложения D, хотя ее деплой прервался. Из-за этого статус обновлений D 2 отобразился неверно Чтобы микрофронтенды выкладывались независимо и без сбоев, можно запустить деплой в Kubernetes. Схема следующая. Когда обновляется код микрофронтенда, Github Action все так же собирает его в бандл. А дальше бандл упаковывается не в папку, а в докер-контейнер с Nginx внутри. Octopus переносит контейнер в Kubernetes, где он запускается в нужном окружении. Helm chart пода микрофронтенда То есть вместо двух бакетов в Yandex Cloud S3 мы получаем десяток изолированных контейнеров в Kubernetes и таким образом решаем проблему с деплоем большого количества микрофронтендов. Их можно обновлять независимо друг от друга. Каждый микрофронтенд предоставляет свой файл с метаданными. На основе этих файлов выстраивается роутинг и наполняется основное меню страницы. Если браузер станет загружать их все по отдельности, чтобы вывести меню пользователю, это займет много времени. Чтобы ускорить процесс, все метаданные нужно предварительно объединить в один файл. С прежним деплоем Octopus собирал метаданные одновременно с релизом кода. Он запускал скрипт, в котором был список микрофронтендов, и этот скрипт находил файлы с метаданными — remoteEntry.js. Затем Octopus склеивал все в один файл initial.js и создавал index.html со ссылкой на него. Все это вместе с бандлами отправлялось в бакет в Yandex Cloud S3. По запросу пользователя браузер выводил index.html с актуальными метаданными. В Kubernetes сервис Initial builder собирает метаданные только тех микрофронтендов, которые находятся в рантайме. Можно было бы, как раньше, использовать сервис с вложенным списком микрофронтендов и обновлять этот список вручную. Но мы вместо этого автоматизировали процесс с помощью специального класса сервисов под названием headless services. Теперь все докер-контейнеры помечены тегами. Initial builder с помощью headless services считывает их и находит контейнеры с данными, которые запросил пользователь. Дальше Initial builder достает файлы с метаданными и склеивает в один, который затем передает по назначению. Получение списка подов микрофронтендов Создание единого файла с метаданными initial.js Чтобы переход от старого деплоя к новому не сказался на пользователях, мы вводили изменения постепенно. Какое-то время Initial builder не собирал файл с метаданными целиком, а обновлял тот, что собирался в Yandex Cloud S3 старым способом. То есть Initial builder получал запрос от пользователя и скачивал из Yandex Cloud S3 файл с метаданными. Дальше он с помощью headless services находил и скачивал все доступные файлы с метаданными микрофронтендов. В начале каждого такого файла есть системное имя — точно такое же указано в исходном файле из Yandex Cloud S3. С помощью регулярного выражения фрагменты метаданных в старом файле заменялись на новые. После этого браузер получал и выводил актуальные метаданные. Таким образом у нас какое-то время часть микрофронтендов жила на новом деплое, часть на старом. Пользователи ничего не заметили. Хот-тестинг во фронтенде — это нулевая стадия тестирования, которая позволяет с ходу показать изменения продакт-менеджерам и получить обратную связь. Изначально код тестировали в четырех окружениях: стейджинг — рабочая среда с одним проектом, на котором все проверяют гипотезы и смотрят результат; стандард — проект с очищаемой базой, только для е2е-тестов; бета — 10% клиентов, которые готовы мириться с возможными багами ради того, чтобы получить обновления первыми; стейбл — все остальные клиенты. Чтобы ускорить процесс, мы ввели для фронтенда что-то вроде престейджинга — хот-тестинг. Когда мы открываем pull request, GitHub Action собирает код в бандл, упаковывает в докер-образ и ставит на него тег, равный хешу ветки. Дальше докер-образ запускается в Kubernetes, где отличается от докер-контейнеров только тем, что отвечает на запросы от одного определенного домена. Чтобы этот домен получил все актуальные данные плюс обновление, в GitHub Action собирается отдельный под Initial builder, для которого указывается два headless service — основной и дополнительный. Основной находит любые докер-контейнеры в стейджинге, а дополнительный — только новый докер-образ. Helm chart для поднятия специальной версии Initial builder для в режиме хот-тестинга Helm chart для поднятия пода с тестируемым МКФ Helm chart для маршрутизации трафика в хот-тестинге Получается, чтобы посмотреть обновления, продакт-менеджер открывает специальную ссылку, в домене которой написан хеш ветки. В этот момент в Kubernetes уходит три запроса: запрос метаданных, кода тестируемого приложения и остального кода. Запрос метаданных проходит по цепочке: Трафик направляется в под Initial builder, созданный для тестового домена. Initial builder с помощью основного headless service собирает метаданные всех микрофронтендов, а с помощью дополнительного — метаданные тестового микрофронтенда. В общем файле с метаданными Initial builder заменяет один из фрагментов на более новый — от тестового микрофронтенда. Итоговый файл initial.js уходит к пользователю. В то же время под с тестовым микрофронтендом выдает пользователю код тестируемого приложения, а другие поды — весь остальной код. В результате продакт-менеджер видит страницу со свежими обновлениями. Так устроен изолированный проект с версией фронтенда, доступной для тестирования. Примерно такая же схема у нас с е2е в pull request. Мы хотим получить обратную связь об обновлениях еще до того, как зальем код в мастер. Поэтому создаем в проекте разработки отдельный под и проводим на нем тесты. Если у вас еще нет микрофронтендов и весь фронтенд — это один большой монолит, можете позаимствовать из статьи хот-тестинг. Собирайте отдельную версию статики и отдавайте ее по запросу с каким-нибудь маркером. У нас это хеш в поддомене, но можно сделать и квер-параметры или заголовки. Если у вас есть микрофронтенды и они деплоятся созависимо, можно создать много мелких подов в Kubernetes и настроить независимый деплой. В дальнейшем можно реализовать, например, АB-тесты разной статики, если это нужно бизнесу. Внедрить независимый деплой можно и без Kubernetes — используйте AWS Lambda или отдельные инстансы приложений на виртуальных компьютерах.