По мере развития любая система усложняется, растет количество данных, интеграции становятся более запутанными, накапливается технический долг. Справиться с растущей сложностью легче, если вы стояли у истоков с момента планирования архитектуры. Но если проект в таком состоянии приходит на поддержку от другой команды, сложно понять, с какого конца за него браться, с чего начать улучшения чтобы упростить работу.
В этой статье мы расскажем, как к нам пришел проект с техническим долгом, с какими проблемами мы столкнулись, и почему решение проблем начали с внедрения тестового контура.
Немного о проекте
Сейчас проект представляет собой два приложения. Фактически, одно приложение — это копия второго, но с незначительными различиями. Каждое приложение поделено на сервисы. Условно назовем их Frontend, Backend и X-API.
![Изображение статьи](https://worksolutions.ru/uploads/Skrin1_9bd041b41a.jpg)
Что представляют из себя эти сервисы?
Frontend
Уже наверное все привыкли, что frontend — это приложение на angular/react/vue или чем-нибудь подобном. В нашем случае это стандартное приложение на php + jquery, в котором работают пользователи.
Backend
Предоставляет API для нашего фронта, у него есть своя база данных, а также большое кол-во интеграций с внешними API.
X-API
По сути, это выделенная часть бизнес-логики из монолитного backend, так же со своей базой данных.
- Взаимодействие между сервисами происходит по SOAP.
- Устаревший стек: PHP 7.1, Zend 2, MySQL 5.6;
- Объемная кодовая база: 400 тысяч строк кода на одно приложение, без учета шаблонов;
- Отсутствие автотестов;
- Сложная предметная область и несколько бизнес-доменов;
- Десятки интеграций API от различных поставщиков услуг;
- Отсутствие моковых данных для API;
- Плохо структурированный код с классами на пять тысяч строк кода;
- Сотни предупреждений от PHPStorm;
![Изображение статьи](https://worksolutions.ru/uploads/Skrin2_17ebcf7fb1.png)
Начало работы
Сперва мы провели технический аудит кодовой базы, по результату которого составили документ на сотню страниц. Для решения выявленных проблем требовался рефакторинг, но приступить к нему мы не могли.
Проект страдал от 140 критических багов, из-за которых компания несла убытки. Их нужно было как можно быстрее исправить. Поэтому следующие два месяца мы устраняли дефекты и выпускали горящие релизы. В процессе постоянно всплывали новые проблемы, рефакторинг стал восприниматься не как рекомендация по улучшению, а как необходимая мера, чтобы спасти проект от технического банкротства.
И тогда мы разработали дорожную карту по улучшению системы:
![Изображение статьи](https://worksolutions.ru/uploads/Skrin3_1ba3bd57ad.jpg)
Основные моменты, которые можно выделить:
- Миграция на Symfony
- Обновление PHP
- Обновление Mysql
- Тестовый контур
- Рефакторинг кода
- Внедрение Unit-тестирования
- Реализация Mock-API поставщиков
На схеме видно, что много факторов зависит от тестового контура, поэтому его решили делать первым.
С какими проблемами мы столкнулись
Как должно было быть:
Есть ветка master для прода и ветка dev для тестового сервера. Разработчик делает задачу в ветке, созданной от master. Отправляет ветку на code-review. Ревьювер мержит в dev и отправляет на тестирование. В случае успеха задача идет в master.
Как было на деле:
В какой-то момент в dev скопилась куча задач. Это релизы, которые еще рано выливать в master, а также задачи, которые не прошли тестирование. И тогда у нас пошел рассинхрон с веткой master и начались проблемы с тестированием.
В ветке от master код работает, но после слияния с dev работает не так, как хотелось бы. Пошли постоянные merge-конфликты, что съедает время и знатно портит настроение.
![Изображение статьи](https://worksolutions.ru/uploads/Skrin5_596e12accd.png)
Помимо прочего, релизам нужны свои тестовые стенды. Для примера:
У нас есть релиз интеграции с API поставщика, который тестирует сам этот поставщик. Причем мы у него не единственные разработчики, поэтому тестирование разбито на слоты, которые могут быть заняты на месяц-два вперед.
И вот представьте: внутренние тестировщики все проверили, подошла наша очередь, и разработчик что-то ломает во время исправления бага в совершенно другом месте. Все, test-failed, ждем еще месяц. Неприятно, поэтому нужно что-то делать.
Тестовый контур: требования
Прежде чем делать тестовый контур, мы сформировали основные требования:
- Одновременно может быть запущено множество стендов. У нас 9 разработчиков на проекте. В день может быть реализовано с десяток задач и нужна возможность их изолированного тестирования;
- Создание стенда не должно вызывать сложностей и занимать много времени;
- То же касается и удаления стенда. В противном случае есть вероятность, что старые стенды будут висеть и потреблять ресурсы;
- Так как постановка задач происходит в Jira, то хотелось бы иметь с ней интеграцию: при переводе задачи на тестирование автоматически создавать площадку, при успешном прохождении тестирования удалять. И желательно еще автоматически добавлять в задачу ссылку на эту площадку;
- Ну и конечно же требуется знать, какие площадки у нас запущены.
Теперь мы понимаем, чего хотим, и можно приступать к реализации.
Тестовый контур: реализация
Проектирование
Сначала составили подробный план того, как будет работать тестовый контур. Чтобы не описывать всю схему, перечислим основные тезисы:
У нас есть 2 сервера: сервер управления и сервер тестирования.
На сервере тестирования запускаются стенды, происходит это в Docker и поверх работает Reverse-proxy.
На сервере управления развернут CI-сервер, registry, приложения для удобного управления этим всем и интеграции с Jira.
Теперь у нас есть концепция того, как это будет выглядеть, но пока непонятно, как это сделать и какие инструменты использовать.
Инфраструктура как код
Решили автоматизировать процесс настройки серверов согласно современным DevOps практикам и инструментам.
Ansible
Создали инфраструктурный репозиторий, в него сохранили все конфигурации с использованием Ansible. Например, если нужно настроить сервер, добавить пользователей, установить cron или docker — пишем ansible-роль. Деплой компонентов управления — еще одна ansible-роль, деплой приложения тоже. Это позволило разработчикам собирать тестовые стенды локально.
![Изображение статьи](https://worksolutions.ru/uploads/Skrin6_a077345737.png)
Jenkins
Чтобы каждый разработчик не занимался настройкой стейджей на своем компьютере, нужен был сервер автоматизации. Выбрали Jenkins, который выполняет всю основную работу:
1. Сборка базовых версий образов PHP, MySQL, Nginx. Базовая версия — это конкретная версия PHP и установленные на ней утилиты. В общем, все окружение кроме кода.
![Изображение статьи](https://worksolutions.ru/uploads/Skrin7_beeedd6adc.png)
2. Снятие дампов БД и упаковка их в образы. Снятие дампов по крону происходит раз в неделю, и сама упаковка занимает около 30 минут. Но это делается один раз, а потом данные в виде готовых образов запускаются на тестовом сервере.
3. Основная задача — это сборка площадок. Сюда входит сборка образов и запуск их на тестовом сервере.
4. Удаление площадок для освобождения ресурсов. Это остановка приложения, чистка стенда и удаление образов из Registry.
В итоге получается такая схема:
![Изображение статьи](https://worksolutions.ru/uploads/Skrin8_d1d20fbdd9.jpg)
На этом этапе у нас уже полностью готовый продукт, но неудобный. Кто пользовался Jenkins, знает, что конфигурировать его под каждый стенд — то еще удовольствие, и внедрить такую практику в команде будет сложно.
Кастомная панель управления
Найти готовое решение с нормальным пользовательским интерфейсом не удалось, поэтому решили написать сами. Выбрали стек Django + Vue + Vuetify. Силами одного разработчика реализовали нужную функциональность всего за две недели:
![Изображение статьи](https://worksolutions.ru/uploads/unnamed_5_de636d176a.png)
Сама панель управления достаточно простая и состоит из нескольких моделей:
- Настройки. Например, ключ доступа к Jenkins или к GitLab. Грубо говоря, key-value хранилище данных
- Конфигурация стендов. Это три сущности: проект, сервис и площадка.
Проект — это обязательные поля, такие как тип и код, и набор параметров проекта, которые в зависимости от проекта могут различаться.
В проекте есть сервисы. У них тоже есть обязательные поля, например, символьный код и репозиторий, и дополнительные поля, которые можно добавлять в любом количестве.
Вместе проект и сервисы служат шаблоном для стенда, то есть из настроек проекта и сервисов формируются параметры стенда. Параметры можно переопределить. Эти параметры превращаются в параметры триггеров для пайплайнов в Jenkins.
- Интеграции с Jira, Jenkins, Docker-registry, Gitlab и Traefik.
Traefik
Отдельного внимания здесь заслуживает Traefik. По нашему мнению, это лучший в мире reverse-proxy для Docker. Другие инструменты мы особо не проверяли, но когда изучили, как это делается в Nginx, то пришли в ужас и решили применять Traefik.
Чем он так хорош? Тем, что он может заставить приложение открываться по определенной ссылке. Например, эти 4 строчки делают доступным контейнер по нужному нам URL-адресу стейджа:
![Изображение статьи](https://worksolutions.ru/uploads/Skrin9_5abe309bbf.png)
Это вся конфигурация. У самого Traefik тоже есть настройки, но они такие же простые.
Для экономии мощностей мы решили внедрить правило, что запущенные стенды должны удаляться каждую ночь, но быстро столкнулись с проблемой. Тестировщик не всегда успевает проверить задачу до удаления площадки, и на следующий день он видит 404-ошибку.
Traefik помог и в этой ситуации. Добавили мини-приложение — обработчик 404-й ошибки. Теперь, когда тестировщик заходит на площадку, он может сам запустить создание площадки по кнопке:
![Изображение статьи](https://worksolutions.ru/uploads/12345_493950c82e.png)
Registry + Portainer
Все наши образы хранятся в Docker-registry. В текущей ситуации он пригодился, так как Elastic и Logstash больше нельзя скачать без VPN. Но они теперь есть в нашем Registry.
Из минусов — тут не очень удобно реализовано удаление образа по тэгу. Метода удаления по тэгу попросту нет. Чтобы удалить, нужно сначала запросить хэш манифеста по тегу, удалить манифест по полученному хэшу и запустить сборщик мусора внутри registry. Только тогда registry почистит место. Неудобно, но терпимо.
Portainer предоставляет визуальный интерфейс для управления контейнерами. Он позволяет смотреть логи, заходить внутрь контейнеров без подключения к серверам и перезапускать их.
Итоги
Мы избавились от боли при merge в dev-ветку. Теперь мы больше не теряем на этом время. Можем получить столько тестовых стендов, сколько нужно, причем делается это быстро. Площадка разворачивается в среднем за 4 минуты.
Стейдж изолирован, поэтому мы еще избавились от ложных возвратов задач с тестирования. Это хорошо отразилось на духе команды — ведь неприятно, когда все сделал правильно, а задача вернулась с неудачным результатом тестирования.
Получили средство для обкатки новых инструментов. Приложение на PHP 7.4 запускается в пару кликов.
![Изображение статьи](https://worksolutions.ru/uploads/1555_196_59162f0caf.jpg)
Материал был создан на основе выступления в рамках RND PHP митапа, прошедшего 14 мая 2022г. Запись трансляции доступна по ссылке.