На связи Игорь Помилуйко, технический директор Work Solutions. В мае я выступил на PHP-митапе, где поделился кейсом настройки тестового контура для унаследованного проекта с техническим долгом. В этом материале я подробно расскажу, с какими проблемами мы столкнулись, и почему начали их решать с помощью внедрения тестового контура.
Немного о проекте
Сейчас проект представляет собой два приложения. Фактически, одно приложение — это копия второго, но с незначительными различиями. Каждое приложение поделено на сервисы. Условно назовем их Frontend, Backend и X-API.
Что представляют из себя эти сервисы?
Frontend
Уже наверное все привыкли, что frontend — это приложение на angular/react/vue или чем-нибудь подобном. В нашем случае это стандартное приложение на PHP + JQuery, в котором работают пользователи.Backend
Предоставляет API для фронтенда, у него есть своя база данных, а также большое количество интеграций с внешними API.X-API
Выделенная из монолитного backend часть бизнес-логики со своей базой данных.
Какие еще характеристики проекта следует выделить?
Устаревший стек: PHP 7.1, Zend 2, MySQL 5.6;
Объемная кодовая база: 400 тысяч строк кода на одно приложение, без учета шаблонов;
Отсутствие автотестов;
Сложная предметная область и несколько бизнес-доменов;
Десятки интеграций API от различных поставщиков услуг;
Отсутствие моковых данных для API;
Плохо структурированный код с классами на пять тысяч строк кода;
Сотни предупреждений от PHPStorm.
Начало работы
Сперва мы провели технический аудит кодовой базы, по результату которого составили документ на сотню страниц. Для решения выявленных проблем требовался рефакторинг, но приступить к нему мы не могли.
Проект страдал от 140 критических багов, из-за которых компания несла убытки. Их нужно было как можно быстрее исправить. Поэтому следующие два месяца мы устраняли дефекты и выпускали горящие релизы. В процессе постоянно всплывали новые проблемы, рефакторинг стал восприниматься не как рекомендация по улучшению, а как необходимая мера, чтобы спасти проект от технического банкротства.
И тогда мы разработали дорожную карту по улучшению системы:
Основные моменты, которые можно выделить:
Миграция на Symfony
Обновление PHP
Обновление Mysql
Тестовый контур
Рефакторинг кода
Внедрение Unit-тестирования
Реализация Mock-API поставщиков
На схеме видно, что много факторов зависит от тестового контура, поэтому его решили делать первым.
С какими проблемами мы столкнулись
Как должно было быть:
Есть ветка master для прода и ветка dev для тестового сервера. Разработчик делает задачу в ветке, созданной от master. Отправляет ветку на code-review. Ревьювер мержит в dev и отправляет на тестирование. В случае успеха задача идет в master.
Как было на деле:
В какой-то момент в dev скопилась куча задач. Это релизы, которые еще рано выливать в master, а также задачи, которые не прошли тестирование. И тогда у нас пошел рассинхрон с веткой master и начались проблемы с тестированием.
В ветке от master код работает, но после слияния с dev работает не так, как хотелось бы. Пошли постоянные merge-конфликты, что съедает время и знатно портит настроение.
Помимо прочего, релизам нужны свои тестовые стенды. Для примера:
У нас есть релиз интеграции с API поставщика, который тестирует сам этот поставщик. Причем мы у него не единственные разработчики, поэтому тестирование разбито на слоты, которые могут быть заняты на месяц-два вперед.
И вот представьте: внутренние тестировщики все проверили, подошла наша очередь, и разработчик что-то ломает во время исправления бага в совершенно другом месте. Все, test-failed, ждем еще месяц. Неприятно, поэтому нужно что-то делать.
Тестовый контур: требования
Прежде чем приступать к реализации мы сформировали основные требования:
Одновременно может быть запущено множество стендов. У нас 9 разработчиков на проекте. В день может быть реализовано с десяток задач и нужна возможность их изолированного тестирования;
Создание стенда не должно вызывать сложностей и занимать много времени;
То же касается и удаления стенда. В противном случае есть вероятность, что старые стенды будут висеть и потреблять ресурсы;
Так как постановка задач происходит в Jira, то хотелось бы иметь с ней интеграцию: при переводе задачи на тестирование автоматически создавать площадку, при успешном прохождении тестирования удалять. И желательно еще автоматически добавлять в задачу ссылку на эту площадку;
Ну и конечно же требуется знать, какие площадки у нас запущены.
Теперь мы понимаем, чего хотим, и можно приступать к реализации.
Тестовый контур: реализация
Проектирование
Сначала составили подробный план того, как будет работать тестовый контур. Чтобы не описывать всю схему, перечислим основные тезисы:
У нас есть два сервера: сервер управления и сервер тестирования.
На сервере тестирования запускаются стенды, происходит это в Docker и поверх работает Reverse-proxy.
На сервере управления развернут CI-сервер, registry, приложения для удобного управления этим всем и интеграции с Jira.
Теперь у нас есть концепция того, как это будет выглядеть, но пока непонятно, как это сделать и какие инструменты использовать.
Инфраструктура как код
Решили автоматизировать процесс настройки серверов согласно современным DevOps практикам и инструментам.
Ansible
Создали инфраструктурный репозиторий, в него сохранили все конфигурации с использованием Ansible. Например, если нужно настроить сервер, добавить пользователей, установить cron или docker — пишем ansible-роль. Деплой компонентов управления — еще одна ansible-роль, деплой приложения тоже. Это позволило разработчикам собирать тестовые стенды локально.
Jenkins
Чтобы каждый разработчик не занимался настройкой стейджей на своем компьютере, нужен был сервер автоматизации. Выбрали Jenkins, который выполняет всю основную работу:
1. Сборка базовых версий образов PHP, MySQL, Nginx. Базовая версия — это конкретная версия PHP и установленные на ней утилиты. В общем, все окружение кроме кода.
2. Снятие дампов БД и упаковка их в образы. Снятие дампов по крону происходит раз в неделю, и сама упаковка занимает около 30 минут. Но это делается один раз, а потом данные в виде готовых образов запускаются на тестовом сервере.
3. Основная задача — это сборка площадок. Сюда входит сборка образов и запуск их на тестовом сервере.
4. Удаление площадок для освобождения ресурсов. Это остановка приложения, чистка стенда и удаление образов из Registry.
В итоге получается такая схема:
На этом этапе у нас уже полностью готовый продукт, но неудобный. Кто пользовался Jenkins, знает, что конфигурировать его под каждый стенд — то еще удовольствие, и внедрить такую практику в команде будет сложно.
Кастомная панель управления
Найти готовое решение с нормальным пользовательским интерфейсом не удалось, поэтому решили написать сами. Выбрали стек Django + Vue + Vuetify. Силами одного разработчика реализовали нужную функциональность всего за две недели:
Сама панель управления достаточно простая и состоит из нескольких моделей:
Настройки. Например, ключ доступа к Jenkins или к GitLab. Грубо говоря, key-value хранилище данных.
Конфигурация стендов. Это три сущности: проект, сервис и площадка.
Проект — это обязательные поля, такие как тип и код, и набор параметров проекта, которые в зависимости от проекта могут различаться.
В проекте есть сервисы. У них тоже есть обязательные поля, например, символьный код и репозиторий, и дополнительные поля, которые можно добавлять в любом количестве.
Вместе проект и сервисы служат шаблоном для стенда, то есть из настроек проекта и сервисов формируются параметры стенда. Параметры можно переопределить. Эти параметры превращаются в параметры триггеров для пайплайнов в Jenkins.
Интеграции с Jira, Jenkins, Docker-registry, Gitlab и Traefik.
Traefik
Отдельного внимания здесь заслуживает Traefik. По нашему мнению, это лучший в мире reverse-proxy для Docker. Другие инструменты мы особо не проверяли, но когда изучили, как это делается в Nginx, то пришли в ужас и решили применять Traefik.
Чем он так хорош? Тем, что он может заставить приложение открываться по определенной ссылке. Например, эти 4 строчки делают доступным контейнер по нужному нам URL-адресу стейджа:
Это вся конфигурация. У самого Traefik тоже есть настройки, но они такие же простые.
Для экономии мощностей мы решили внедрить правило, что запущенные стенды должны удалятся каждую ночь, но быстро столкнулись с проблемой. Тестировщик не всегда успевает проверить задачу до удаления площадки, и на следующий день он видит 404-ошибку.
Traefik помог и в этой ситуации. Добавили мини-приложение — обработчик 404-й ошибки. Теперь, когда тестировщик заходит на площадку, он может сам запустить создание площадки по кнопке:
Registry + Portainer
Все наши образы хранятся в Docker-registry. В текущей ситуации он пригодился, так как Elastic и Logstash больше нельзя скачать без VPN. Но они теперь есть в нашем Registry.
Из минусов — тут не очень удобно реализовано удаление образа по тэгу. Метода удаления по тэгу попросту нет. Чтобы удалить, нужно сначала запросить хэш манифеста по тегу, удалить манифест по полученному хэшу и запустить сборщик мусора внутри registry. Только тогда registry почистит место. Неудобно, но терпимо.
Portainer предоставляет визуальный интерфейс для управления контейнерами. Он позволяет смотреть логи, заходить внутрь контейнеров без подключения к серверам и перезапускать их.
Итоги
Мы избавились от боли при merge в dev-ветку. Теперь мы больше не теряем на этом время. Можем получить столько тестовых стендов, сколько нужно, причем делается это быстро. Площадка разворачивается в среднем за 4 минуты.
Стейдж изолирован, поэтому мы еще избавились от ложных возвратов задач с тестирования. Это хорошо отразилось на духе команды — ведь неприятно, когда все сделал правильно, а задача вернулась с неудачным результатом тестирования.
Получили средство для обкатки новых инструментов. Приложение на PHP 7.4 запускается в пару кликов.
Комментарии (5)
tolyan_ekb
14.07.2022 09:37В тексте описана проблема, когда скопились "релизы, которые еще рано выливать в master " и я не увидел решения. Как решили проблему? Разве концепция релизов не предусматривает планирование и реализацию только тех задач, которые должны быть в релизе?
worksolutions Автор
14.07.2022 09:52В тексте описана проблема, когда скопились “релизы, которые еще рано выливать в master ” и я не увидел решения. Как решили проблему?
Раньше была была ветка dev и под нее была одна площадка, туда сливались все задачи.Теперь под каждую задачу, создается отдельная площадка, т.е. получается, что площадка это master + код по задаче/релизу.
Разве концепция релизов не предусматривает планирование и реализацию только тех задач, которые должны быть в релизе?
Тут другая система. Условно есть несколько видов релизов/задач и несколько команд. Например, есть текущие задачи/исправления. А есть крупные релизы(например, интеграция с поставщиком), которые тоже должны быть на площадке(потому что сам поставщик их тестирует)
tolyan_ekb
14.07.2022 10:11Раньше была была ветка dev и под нее была одна площадка, туда сливались все задачи.Теперь под каждую задачу, создается отдельная площадка, т.е. получается, что площадка это master + код по задаче/релизу.
Если я правильно, понимаю, то конфликтов при объединении при таком подходе не избежать. Ведь master уже изменился на предыдущем релизе и текущая сделанная задача, может не работать или работать не так как надо.
second_try
14.07.2022 10:25Но в данном случае в общую ветку идёт мерж уже протестированного, "финального" кода задачи. А не каждого фикса.
lxsmkv
Отлично! Все больше людей понимают, что автоматизация [тестирования] это не опция, а способ повышения эффективности бизнеса, жизненная необходимость, чтобы оставатья конкуррентноспособным.