Релизим фронтенд несколько раз в день
Меня зовут Петр Солопов, я руковожу фронтенд-разработкой в SuperJob. В этой статье хочу рассказать об опыте ежедневных релизов у нас в компании, зачем мы это делаем и почему это не так страшно, как кажется.
История разбита на пять частей: что нас к этому привело, как это сделать, сколько нужно тестов и каких, что следует автоматизировать в процессе деплоя и как мониторить продакшн.
Немного контекста
Каждый день нашим сервисом пользуется более одного млн активных пользователей. В команде работает 62 инженера, в том числе 10 фронтенд-разработчиков.
Мы пишем большое SPA с поддержкой серверного рендеринга, в котором более сотни роутов. Под капотом используем Node.js для SSR, пишем на React-Redux без бойлерплейта. Весь код у нас статически типизирован с помощью Flow и TypeScript, собираем проект Webpack«ом.
Инструменты
Зачем нужны ежедневные релизы и что для этого надо
Первоначально у нас были нерегулярные релизы, каждый из которых был целым событием. Постепенно мы пришли к одному релизу в неделю, что тоже нас не устраивало. Сейчас у нас два релиза в день, все автоматизировано и ничего не болит. Есть только направления для улучшения.
Начну с самого простого вопроса: почему мы вообще пришли к ежедневным релизам? Ответ очевиден: бизнес хочет «фичи на бою». Таким образом он оценивает динамику разработки. Для разработчиков это тоже удобно. Если система построена на ежедневных релизах, а значит, на небольших изменениях, намного проще и быстрей находить проблемы.
Минимальный джентльменский набор, который понадобится:
Далее про все это более подробно.
Настраиваем три вида тестов
Начнем с тестов нескольких видов: юнит-тестирование, скриншотные тесты и интеграционные тесты.
Юнит-тесты
У нас написано более 1000 юнит-тестов. Используем тестовый фреймворк mocha. Также есть метрика покрытия кода тестами, но к ней относимся без фанатизма. Достаточно определенного не слишком высокого порога, ниже значений которого тесты упадут.
Скриншотные тесты
Так как на фронтенде мы пишем интерфейсы, то нужно проверять, что верстка нигде не ломается и компоненты выглядят именно так, как мы задумывали. В этом помогает скриншотное тестирование.
В рамках скриншотного тестирования у нас тоже более 1000 тестов. Они проходят за 11 минут в CI. Мы даже написали собственное решение, которое занимает порядка 100 строк кода (чуть подробнее про него — ниже). Начать стоит с того, что у нас своя библиотека компонентов на React и есть витрина, в которой эти компоненты представлены. Этой витриной пользуются разработчики, дизайнеры и менеджеры.
Витрина компонентов
На изображении выше представлена страница одного из компонентов — «Баннер». Таких компонентов в нашей витрине сотни. У каждого есть описание и интерактивные примеры. Мы подумали, что раз у нас уже есть место, где находятся все компоненты с разными состояниями, то почему бы его и не «фотографировать».
Вернемся к нашему решению. С помощью Playwright (безголовый браузер) и тест-раннера от этой же команды запускаем скриншотные тесты. Можно использовать любой тест-раннер, который умеет параллелить и параметризировать тесты. Без параллелизации скриншотные тесты на больших объемах могут проходить несколько часов. А параметризация нужна, чтобы написать один тест, но с разными данными, т.к. действия с каждым компонентом будут одни и те же: зайти на страницу компонента, найти интерактивный пример по индексу, сделать скриншот:
it('components', async ({ browserName, component, version, page }) => {
const [componentName, index] = component.split('.');
await page.goto(`${STYLEGUIDE_URL}/#/${componentName}`);
const elements = await page.$$(STYLEGUIDE_PREVIEW_SELECTOR);
const screenshot = await elements[index].screenshot();
expect(screenshot).toMatchSnapshot(
`${componentName}-${index}-${version}-${browserName}.png`
);
});
Далее скриншот сравнивается с существующим изображением. Если вдруг что-то пойдет не так, то мы увидим актуальное изображение, ожидаемое и разницу. В данном случае, например, исчез крестик с баннера, и на дифе это сразу видно:
Актуальное изображение и диф
Интеграционные тесты
Всего вышеизложенного недостаточно для того, чтобы выявить проблемы в бизнес-логике. Для этого у нас еще есть интеграционные тесты. Их пишет команда тестирования. Подробнее о том, как они устроены, рассказал в блоге наш QA Lead. Я лишь отмечу, что их более 2000.
Все тесты, о которых шла речь, запускаются в CI на ветках автоматически. А когда тесты успешно прошли на ветке и появилось два аппрува в пулл-реквесте, то ветку можно мерджить. Теперь о том, куда мерджить и как код попадает в продакшн.
Автоматизация процессов деплоя
Мы работаем по классическому Git flow. Есть ветка master (код из мастера находится на продакшн), и есть ветка dev, где ведется основная разработка, и от нее делаются фиче-ветки.
Также есть релизная ветка. Это ветка, которая создается от ветки dev и потом вливается в master и dev. Таким образом на продакшн поставляется новый код.
Тут есть что автоматизировать. Важно понимать, какое сейчас состояние у релиза. Для этого у нас есть отдельный канал в слак, куда приходят отбивки по текущим состояниям. Сначала приходит сообщение, что релиз зафиксировался. Это происходит автоматически два раза в день. Далее приходит сообщение, что на релизной ветке успешно прошли все тесты.
Git flow и уведомления
Затем приходит уведомление, где отмечается дежурный фронтенд-разработчик. Его задача — пройти по ссылке и нажать кнопку «Катим релиз». После этого релизная ветка вливается в master и dev.
Все, что вливается в master, автоматически начинает собираться и раскатываться на продакшн, о чем также приходит уведомление: «У нас запланирован деплой». После того как все собралось и успешно раскатилось на боевые сервера, нам приходит уведомление, что все прошло успешно.
Онлайн мониторинг
Выше вкратце описан процесс попадания новых изменений на бой. Но это еще не все. Нужно убедиться, что после релиза все работает. В этом нам помогает мониторинг. Как я говорил в начале, у нас сервер на Node.js. И нам нужны стандартные метрики сервера.
Мониторинг сервера
Среди метрик — количество 200-х, 300-х, 400-х, 500-х ошибок и другие данные: сколько потребляется памяти и так далее. Также на графиках вертикальной зеленой линией отмечается релиз и есть соответствующие значения за предыдущий период. После релиза нас больше всего интересуют ошибки.
Мониторинг ошибок
Чтобы видеть детали ошибок, у нас настроен Sentry — клиентский и серверный. Он нужен для мониторинга ошибок в рантайме. Там можно отфильтровать проблемы по релизу и убедиться, что нет всплеска ошибок. Если появляется новая ошибка или какой-то аномальный всплеск, также сразу приходит уведомление в слак.
Важно чтобы релизы не ухудшили производительность. Для этого у нас есть отдельный дашборд в графане. Слева направо идут следующие графики: сколько страница формируется на сервере, сколько парсится и загружается JavaScript и как быстро страница становится интерактивной. Снизу это дублируется для мобильного веба, потому что у нас два приложения: Desktop и Mobile.
Мониторинг производительности
Также у нас настроены ночные тесты производительности Lighthouse-ом. Они довольно долгие из-за большого количества проверяемых страниц. Тут мы уже постфактум убеждаемся, что ничего не сломали.
Lighthouse CI
Проблемы в релизе
Мониторинг может сообщить о том что после релиза возникли проблемы. План действий зависит от масштаба трагедии. Если проблема локальная и не влияет на базовые сценарии пользователя, то возможен хотфикс.
Если проблема критическая, то предусмотрена возможность переключения на предыдущий релиз в считаные секунды. К счастью, мы этим пользуемся крайне редко.
Итоги и планы
Ежедневные релизы — это не страшно, когда код покрыт тестами, настроен онлайн мониторинг, вся рутинная работа с деплоем автоматизирована и есть мгновенная возможность откатить изменения.
В планах — запускать тесты производительности на пулл-реквестах, сделать канареечные релизы и прийти к полностью автоматическому развёртыванию релиза без участия разработчика.