![](https://habrastorage.org/getpro/habr/upload_files/cf6/6df/512/cf66df5128fcaf52569ae22a3513be06.jpg)
На связи Игорь Помилуйко, технический директор Work Solutions. В мае я выступил на PHP-митапе, где поделился кейсом настройки тестового контура для унаследованного проекта с техническим долгом. В этом материале я подробно расскажу, с какими проблемами мы столкнулись, и почему начали их решать с помощью внедрения тестового контура.
Немного о проекте
Сейчас проект представляет собой два приложения. Фактически, одно приложение — это копия второго, но с незначительными различиями. Каждое приложение поделено на сервисы. Условно назовем их Frontend, Backend и X-API.
![](https://habrastorage.org/getpro/habr/upload_files/fea/d28/ceb/fead28cebe45aa0be30c240e44c379b8.jpeg)
Что представляют из себя эти сервисы?
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.
![](https://habrastorage.org/getpro/habr/upload_files/d06/c18/24e/d06c1824edc4d35dcfe01bcc6bdd0686.png)
Начало работы
Сперва мы провели технический аудит кодовой базы, по результату которого составили документ на сотню страниц. Для решения выявленных проблем требовался рефакторинг, но приступить к нему мы не могли.
Проект страдал от 140 критических багов, из-за которых компания несла убытки. Их нужно было как можно быстрее исправить. Поэтому следующие два месяца мы устраняли дефекты и выпускали горящие релизы. В процессе постоянно всплывали новые проблемы, рефакторинг стал восприниматься не как рекомендация по улучшению, а как необходимая мера, чтобы спасти проект от технического банкротства.
И тогда мы разработали дорожную карту по улучшению системы:
![](https://habrastorage.org/getpro/habr/upload_files/cc0/ac7/eca/cc0ac7ecab76bf67289e43a7f484d253.jpeg)
Основные моменты, которые можно выделить:
Миграция на Symfony
Обновление PHP
Обновление Mysql
Тестовый контур
Рефакторинг кода
Внедрение Unit-тестирования
Реализация Mock-API поставщиков
На схеме видно, что много факторов зависит от тестового контура, поэтому его решили делать первым.
С какими проблемами мы столкнулись
Как должно было быть:
Есть ветка master для прода и ветка dev для тестового сервера. Разработчик делает задачу в ветке, созданной от master. Отправляет ветку на code-review. Ревьювер мержит в dev и отправляет на тестирование. В случае успеха задача идет в master.
Как было на деле:
В какой-то момент в dev скопилась куча задач. Это релизы, которые еще рано выливать в master, а также задачи, которые не прошли тестирование. И тогда у нас пошел рассинхрон с веткой master и начались проблемы с тестированием.
В ветке от master код работает, но после слияния с dev работает не так, как хотелось бы. Пошли постоянные merge-конфликты, что съедает время и знатно портит настроение.
![](https://habrastorage.org/getpro/habr/upload_files/328/ed2/c3d/328ed2c3dfdfbd755ddcc62683e10571.png)
Помимо прочего, релизам нужны свои тестовые стенды. Для примера:
У нас есть релиз интеграции с API поставщика, который тестирует сам этот поставщик. Причем мы у него не единственные разработчики, поэтому тестирование разбито на слоты, которые могут быть заняты на месяц-два вперед.
И вот представьте: внутренние тестировщики все проверили, подошла наша очередь, и разработчик что-то ломает во время исправления бага в совершенно другом месте. Все, test-failed, ждем еще месяц. Неприятно, поэтому нужно что-то делать.
Тестовый контур: требования
Прежде чем приступать к реализации мы сформировали основные требования:
Одновременно может быть запущено множество стендов. У нас 9 разработчиков на проекте. В день может быть реализовано с десяток задач и нужна возможность их изолированного тестирования;
Создание стенда не должно вызывать сложностей и занимать много времени;
То же касается и удаления стенда. В противном случае есть вероятность, что старые стенды будут висеть и потреблять ресурсы;
Так как постановка задач происходит в Jira, то хотелось бы иметь с ней интеграцию: при переводе задачи на тестирование автоматически создавать площадку, при успешном прохождении тестирования удалять. И желательно еще автоматически добавлять в задачу ссылку на эту площадку;
Ну и конечно же требуется знать, какие площадки у нас запущены.
Теперь мы понимаем, чего хотим, и можно приступать к реализации.
Тестовый контур: реализация
Проектирование
Сначала составили подробный план того, как будет работать тестовый контур. Чтобы не описывать всю схему, перечислим основные тезисы:
У нас есть два сервера: сервер управления и сервер тестирования.
На сервере тестирования запускаются стенды, происходит это в Docker и поверх работает Reverse-proxy.
На сервере управления развернут CI-сервер, registry, приложения для удобного управления этим всем и интеграции с Jira.
Теперь у нас есть концепция того, как это будет выглядеть, но пока непонятно, как это сделать и какие инструменты использовать.
Инфраструктура как код
Решили автоматизировать процесс настройки серверов согласно современным DevOps практикам и инструментам.
Ansible
Создали инфраструктурный репозиторий, в него сохранили все конфигурации с использованием Ansible. Например, если нужно настроить сервер, добавить пользователей, установить cron или docker — пишем ansible-роль. Деплой компонентов управления — еще одна ansible-роль, деплой приложения тоже. Это позволило разработчикам собирать тестовые стенды локально.
![](https://habrastorage.org/getpro/habr/upload_files/535/80a/89d/53580a89db6aafe83d749440099fd058.png)
Jenkins
Чтобы каждый разработчик не занимался настройкой стейджей на своем компьютере, нужен был сервер автоматизации. Выбрали Jenkins, который выполняет всю основную работу:
1. Сборка базовых версий образов PHP, MySQL, Nginx. Базовая версия — это конкретная версия PHP и установленные на ней утилиты. В общем, все окружение кроме кода.
![](https://habrastorage.org/getpro/habr/upload_files/f68/656/52c/f6865652ca964271d000eed2731a01b2.png)
2. Снятие дампов БД и упаковка их в образы. Снятие дампов по крону происходит раз в неделю, и сама упаковка занимает около 30 минут. Но это делается один раз, а потом данные в виде готовых образов запускаются на тестовом сервере.
3. Основная задача — это сборка площадок. Сюда входит сборка образов и запуск их на тестовом сервере.
4. Удаление площадок для освобождения ресурсов. Это остановка приложения, чистка стенда и удаление образов из Registry.
В итоге получается такая схема:
![](https://habrastorage.org/getpro/habr/upload_files/314/e6d/105/314e6d105dd31bc234f83e48b2c869b8.jpeg)
На этом этапе у нас уже полностью готовый продукт, но неудобный. Кто пользовался Jenkins, знает, что конфигурировать его под каждый стенд — то еще удовольствие, и внедрить такую практику в команде будет сложно.
Кастомная панель управления
Найти готовое решение с нормальным пользовательским интерфейсом не удалось, поэтому решили написать сами. Выбрали стек Django + Vue + Vuetify. Силами одного разработчика реализовали нужную функциональность всего за две недели:
![](https://habrastorage.org/getpro/habr/upload_files/d54/e17/8d3/d54e178d300eea64414a8f014aedcf3d.png)
Сама панель управления достаточно простая и состоит из нескольких моделей:
Настройки. Например, ключ доступа к Jenkins или к GitLab. Грубо говоря, key-value хранилище данных.
Конфигурация стендов. Это три сущности: проект, сервис и площадка.
Проект — это обязательные поля, такие как тип и код, и набор параметров проекта, которые в зависимости от проекта могут различаться.
В проекте есть сервисы. У них тоже есть обязательные поля, например, символьный код и репозиторий, и дополнительные поля, которые можно добавлять в любом количестве.
Вместе проект и сервисы служат шаблоном для стенда, то есть из настроек проекта и сервисов формируются параметры стенда. Параметры можно переопределить. Эти параметры превращаются в параметры триггеров для пайплайнов в Jenkins.
Интеграции с Jira, Jenkins, Docker-registry, Gitlab и Traefik.
Traefik
Отдельного внимания здесь заслуживает Traefik. По нашему мнению, это лучший в мире reverse-proxy для Docker. Другие инструменты мы особо не проверяли, но когда изучили, как это делается в Nginx, то пришли в ужас и решили применять Traefik.
Чем он так хорош? Тем, что он может заставить приложение открываться по определенной ссылке. Например, эти 4 строчки делают доступным контейнер по нужному нам URL-адресу стейджа:
![](https://habrastorage.org/getpro/habr/upload_files/0b5/819/6e8/0b58196e8480258b3cc9541655120f3e.png)
Это вся конфигурация. У самого Traefik тоже есть настройки, но они такие же простые.
Для экономии мощностей мы решили внедрить правило, что запущенные стенды должны удалятся каждую ночь, но быстро столкнулись с проблемой. Тестировщик не всегда успевает проверить задачу до удаления площадки, и на следующий день он видит 404-ошибку.
Traefik помог и в этой ситуации. Добавили мини-приложение — обработчик 404-й ошибки. Теперь, когда тестировщик заходит на площадку, он может сам запустить создание площадки по кнопке:
![](https://habrastorage.org/getpro/habr/upload_files/ce3/1a6/51b/ce31a651ba15430ff9512821b870b576.png)
Registry + Portainer
Все наши образы хранятся в Docker-registry. В текущей ситуации он пригодился, так как Elastic и Logstash больше нельзя скачать без VPN. Но они теперь есть в нашем Registry.
Из минусов — тут не очень удобно реализовано удаление образа по тэгу. Метода удаления по тэгу попросту нет. Чтобы удалить, нужно сначала запросить хэш манифеста по тегу, удалить манифест по полученному хэшу и запустить сборщик мусора внутри registry. Только тогда registry почистит место. Неудобно, но терпимо.
Portainer предоставляет визуальный интерфейс для управления контейнерами. Он позволяет смотреть логи, заходить внутрь контейнеров без подключения к серверам и перезапускать их.
Итоги
Мы избавились от боли при merge в dev-ветку. Теперь мы больше не теряем на этом время. Можем получить столько тестовых стендов, сколько нужно, причем делается это быстро. Площадка разворачивается в среднем за 4 минуты.
Стейдж изолирован, поэтому мы еще избавились от ложных возвратов задач с тестирования. Это хорошо отразилось на духе команды — ведь неприятно, когда все сделал правильно, а задача вернулась с неудачным результатом тестирования.
Получили средство для обкатки новых инструментов. Приложение на PHP 7.4 запускается в пару кликов.