javascript

Как DevSpace ускорил проверку микрофронтендов

  • пятница, 4 сентября 2026 г. в 00:00:04
https://habr.com/ru/articles/1078234/

Я фронтенд-разработчик и в основном работаю с Angular. Проекты, над которыми я работаю, находятся в монорепозитории, развёрнуты в Kubernetes, и часть из них использует микрофронтендную архитектуру: основное приложение (хост) загружает отдельные микрофронтенды.

Суть проблемы

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

Самый надёжный вариант проверить, как работает микрофронтенд с хостом — это выполнить его деплой на тестовый стенд штатными средствами. То есть нужно запушить ветку с изменениями в репозиторий и пройти все шаги CD (continuous delivery) пайплайна. Например они могут быть такими: выполнение статического анализа и форматирование кода, прогон тестов для проектов, на которые каким-либо образом повлияли изменения, сборка проекта, сборка образа, сохранение образа в хаб, развёртывание контейнера, выполнение скриптов постобработки. И если находишься в активной разработке, то хочется незамедлительно видеть внесённые изменения в браузере. И не хочется ждать, когда освободится CD раннер для прогона твоего пайплайна, когда в очередной раз выполнятся все-все тесты, когда соберётся образ и запишется в хаб.

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

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

Нужно руками смапить реальные домены на локалхост, то есть прописать в hosts домены хоста и микрофронтенда. Например

127.0.0.1 frontend-host.test-12.dev #домен хоста на тестинге
127.0.0.1 frontend-remote.test-12.dev #домен микрофронтенда на тестинге

Без этого ломаются куки, CORS и API.

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

Для микрофронтенда нужно задать параметр deploy-url, иначе говоря микрофронтенд должен знать, на каком домене и порту он находится, в противном случае чанки, стили и другие ресурсы он будет запрашивать с домена хоста — ведь он работает на странице хоста.

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

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

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

Решение: синхронизировать локальную сборку с pod

Идея применить DevSpace к фронтенду пришла ко мне после работы над бэкенд-задачами. На бэкенде для ускорения разработки в тестовом окружении, тоже развёрнутом в Kubernetes, широко использовался DevSpace. А ускорялась разработка в том числе потому, что DevSpace сам замечает изменения файлов и синхронизирует их между локальной средой разработки и контейнерами в Kubernetes. А фронтенд в моём случае — это всего лишь статичные файлы сборки внутри пода, которые отдаёт nginx. Так почему бы не обнаруживать изменения локальной сборки и не подменять её в pod, используя DevSpace? Я так и сделал.

Настройка

Сначала нужно настроить kube-context тестового стенда с помощью kubectl и убедиться, что у пользователя есть доступ к нужному namespace:

kubectl get pods -n <namespace>

Далее нужно установить DevSpace и добавить файл конфигурации devspace.yaml в целевой проект. Ниже приведён сокращённый пример devspace.yaml. Значения namespace, меток, имени контейнера и путей зависят от проекта.

version: v2beta1
name: frontend-remote

vars:
  NAMESPACE: frontend-remote-k8s-namespace

pipelines:
  dev: |-
    start_dev remote

dev:
  remote:
    namespace: ${NAMESPACE}
    labelSelector:
      app: frontend-remote
    container: nginx
    logs:
      enabled: false
    sync:
      - path: ./dist:/opt/app/dist
        initialSync: preferLocal
        disableDownload: true
    onUpload:
      exec:
        - name: update-deploy-url
          command: sh ./devspace/post-upload/update-deploy-url.sh

namespace и labelSelector должны однозначно выбирать нужный pod, а container — контейнер со статикой внутри него. Параметр path связывает локальный каталог сборки с каталогом в контейнере. initialSync: preferLocal отдаёт приоритет локальным файлам при начальном сравнении.

Сначала нужно запустить локальную сборку и дождаться появления полного ./dist, а затем выполнить devspace dev. Локальную сборку и синхронизацию удобно запускать в двух терминалах:

# терминал 1 — локальная пересборка фронтенда
npm run build --watch

# терминал 2 — синхронизация со стендом
devspace dev

В момент сборки микрофронтенда он ещё не знает, по какому deploy-url он будет задеплоен. А не знает это он потому что может быть задеплоен на различных окружениях и на проде и на разных тестингах. deploy-url становится известным только при старте контейнера nginx. Поэтому при штатном старте это делается внутри контейнера в процессе CD: скрипт постобработки ищет в сборке и заменяет шаблонную строку на реальное значение параметра. Чтобы не ломать устоявшийся процесс, нужно сделать то же самое и при синхронизации сборки с помощью DevSpace. Это делается с помощью onUpload.exec — выполняется скрипт постобработки update-deploy-url.sh. Параметр disableDownload установлен в true, так как нет никакой необходимости забирать из pod постобработанные версии сборки.

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

Импортировать можно vars, pipelines, dev, functions, commands и ещё несколько соседних секций. Важно, что если в проекте снова объявить dev.remote или pipelines.dev, локальное определение целиком заменит импортированное. Поэтому различия лучше прятать в переменные, а сам блок dev не дублировать.

Одинаковыми у однотипных микрофронтендов оказываются пайплайн, контейнер nginx, синхронизация dist, отключение логов и onUpload. В общий файл уходит весь раздел dev и pipelines, а отличающиеся namespace и метка приложения задаются через vars:

# shared/devspace-frontend.yaml
version: v2beta1
name: frontend-shared

pipelines:
  dev: |-
    start_dev remote

dev:
  remote:
    namespace: ${NAMESPACE}
    labelSelector:
      app: ${APP}
    container: nginx
    logs:
      enabled: false
    sync:
      - path: ./dist:/opt/app/dist
        initialSync: preferLocal
        disableDownload: true
    onUpload:
      exec:
        - name: update-deploy-url
          command: sh ./devspace/post-upload/update-deploy-url.sh

В самом микрофронтенде остаётся только то, что у него своё: name, импорт и значения переменных. Раздел dev здесь уже не описывается.

# frontend-remote/devspace.yaml
version: v2beta1
name: frontend-remote

imports:
  - path: ../shared/devspace-frontend.yaml

vars:
  APP: frontend-remote
  NAMESPACE: frontend-remote-k8s-namespace

Для монорепозитория это оказалось очень удобным.

Применимость

Хотя я и рассматриваю здесь микрофронтенды, но они ничем не отличаются от обычных SPA — для них это работает одинаково и даже проще — не нужно делать работу, характерную для микрофронтендов. Для обычных SPA DevSpace также может сократить время и сложность интеграционных проверок, хотя не столь существенно, как для микрофронтендов. Я не делаю различий для множества фреймворков, принимая, что суть процессов для всех одинакова: получение сборки → отправка сборки в pod.

Стоит отметить, что и для SSR-проектов такой подход тоже применим, но со своими сложностями и особенностями, на практике я это не проверял и здесь это рассматривать не буду.

Недостатки

С какими-то критичными недостатками при синхронизации сборки в pod за время применения данного подхода я не сталкивался. Однако они есть и кажутся довольно естественными и поверхностными. Основной недостаток данного подхода, на мой взгляд, заключается именно в подмене содержимого контейнера, о которой никто больше не знает. То есть тестинг будет показывать — задеплоена свежая целевая ветка, а на самом деле там уже другой код. Для общих тестингов это не очень хорошо и может привести к путанице. Также не стоит забывать, что после быстрой проверки работы кода путём подмены содержимого pod стоит выполнить штатный деплой, в процессе которого выполняются тесты и другие проверки. Если приложение имеет более чем одну реплику, то синхронизация одной не гарантирует, что ответы на запросы будет отдавать именно она, но, кажется, это совсем экзотический случай для фронтенда и особенно для тестового окружения.

Что изменилось

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

Рабочий цикл стал выглядеть так:

изменение кода
  → локальная сборка микрофронтенда или обычного SPA
  → синхронизация dist с pod через DevSpace
  → проверка через хост на тестовом стенде

Моей обычной практикой при работе над проектом стало просто запускать сборку приложения и в соседнем терминале синхронизацию DevSpace — без указания каких-либо дополнительных параметров, то есть одинаково для всех проектов. Также возможно запустить сразу несколько приложений — синхронизация работает одновременно для всех.

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