Continuous Delivery hecho en Alawar

в 8:18, , рубрики: continuous delivery, continuous integration, php, Блог компании «Alawar Entertainment», Веб-разработка, метки: , ,

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

  • необходимость A/B-тестирования разных версий одной и той же игры
  • возможность по максимуму переиспользовать функционал от одной игры в другой
  • высокую вероятность географической удаленности от разработчиков занимающихся клиентским функционалом

Более того, в дальнейшем нашу команду предполагалось расширить, возможно за счет аутсорс разработчиков, в том числе и для задач поддержки. В этих условиях для успешной реализации было решено наравне с версионированием проектов, пакетированием и стандартизацией ряда шагов разработки внедрить и практику continuous delivery.

Цель данной статьи – рассказать о проделанных шагах, принятых решениях и описать полученный результат.

image

Инфраструктура

Исторически сложилось, что основным языком разработки серверных веб-приложений в нашей компании является PHP, поэтому это во многом предопределило выбор инструментов.

Итоговый список:

Модель ветвления

При выборе модели ветвления за основу была взята “A successful Git branching model”, описанная здесь с одним небольшим отличием: проводить A/B-тестирование было решено путем подготовки отдельных релизных веток, формирующихся из разного набора feature-веток. В результате роль ветки develop была полностью возложена на релизные ветки, и сама эта ветка исчезла. В противном случае при создании следующего релиза мы были бы вынуждены включать в него все выпущенные до этого feature, что не всегда являлось приемлемым.

Эту ситуацию можно продемонстрировать на следующем примере. Напомним, что согласно оригиналу:

At least all features that are targeted for the release-to-be-built must be merged in to develop at this point in time. All features targeted at future releases may not—they must wait until after the release branch is branched off.

И допустим, что уже выпущены два релиза – релиз 1.0 с фичей A и релиз 2.0 с фичами A и B, и необходимо выпустить релиз 1.1 с фичами A и C. Так как develop ветка на данный момент уже содержит в себе фичи А и B, то наиболее простым решением будет создание feature ветки С от ветки релиза 1, и последующий ее merge обратно:

image

Пакетирование и версионирование

Все проекты оформлены как composer-пакеты.

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

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

Этот тип версионирования поддерживается в composer с использованием символа “~”, например:

 "require": {
        ...
        "alawar/packet-post-process-server": "~1.3",
        ...
    },

“Сборка” проекта

В случае с PHP, говорить о сборке в классическом смысле, как процессе конвертации исходников проекта в исполняемый код, нельзя. Тем не менее, так как основной задачей по-прежнему является получение готового к использованию ПО, то название “сборка” вполне корректно.

Этапы сборки:

  • выкачивание зависимостей через composer
  • миграция БД, — обновление только структуры и статических данных базы данных

Для реализации сборки в корне каждого проекта находится сборочный phing-скрипт с target'ами:

  • build – для выполнения этапов сборки
  • runtests и runtest-with-coverage – для выполнения как сборки так и запуска тестов и сбора метрик

Сборочный скрипт для большинства проектов одинаков и отличается лишь названием проекта: аттрибутом name, тега project.

Тестирование

Реализация автоматического тестирования проектов сделана при помощи двух фреймворков: Behat и PHPUnit.

Использование первого дает существенное преимущество не только для тестирования, но и для создания так называемой living documentation. Тесты на Gherkin являются одной из отправных точек при знакомстве с проектом нового программиста, при проведении code review, а также ряде других работ.

Несмотря на знакомство с материалами тут и тут, единых рекомендаций относительно глубины и детальности этих тестов у нас нет, поэтому их содержимое может варьироваться от например таких:

Сценарий: Получение пользовательского бонус кода и списка возможных наград
  # Пусть мы получили бонус код для какого-то игрока
  Пусть мы успешно отправили запрос:
	| action              | uid     |
	| get-user-bonus-code | player1 |
  Тогда мы получим ответ в соответствии с шаблоном "GetUserBonusCodeResponse.txt"
  # Тогда запросив список возможных наград мы получим награды за использование выбранного бонус кода
  Пусть мы успешно отправили запрос:
	| action           | code                 |
	| get-rewards-info | Полученный бонус код |
  Тогда мы получим ответ в соответствии с шаблоном "template8.txt"

до таких:

Сценарий: Получение и обработка данных
	Пусть веб-сервис приложения получает данные по отчетам
	И после этого запускается команда на обработку данных
	Тогда в БД появятся обработанные согласно схеме данные

PHPUnit используется не только для реализации unit-тестов, наличие и содержимое которых полностью остается за программистом, но и для запуска Behat тестов, с использованием небольшого workaround'а. Это дает возможность запускать все тесты одной командой, а также иметь единые отчеты по результатам работы тестов и покрытию ими кода.

Сборочный сервер

Сборка производится с использованием CI-сервера Jenkins. При этом для каждой релизной ветки releases/X.Y заведено отдельное сборочное задание, которое на staging среде:

  • выполняет сборочный phing-скрипт с target'ами “build runtests-with-coverage”
  • собирает отчеты тестирования и результатов работы вспомогательных утилит
  • в случае безошибочного завершения процесса создает в репозитории новый тег вида $VERSION_NO.$BUILD_NUMBER, где $VERSION_NO – номер версии, получаемый из названия ветки, например 2.1, а $BUILD_NUMBER – порядковый номер сборки для данного сборочного задания

Само сборочное задание, равно как и сборочный скрипт были построены на основе описанных здесь. Именно этим и обусловлен столь богатый список дополнительных утилит.

image

В дополнение к указанному по ссылке выше списку плагинов были установлены:

Деплоймент

Требовалось найти решение позволяющее одновременно управлять развертыванием нескольких приложений, каждое из которых может быть установлено на несколько групп серверов (testing, production).

Первоначально deployment осуществлялся phing-скриптом, который согласно файлу настроек выполнял ряд действий:

  • создавал файлы, папки и симлинки
  • делал checkout исходников нужной ветки/тега
  • выполнял сборку проекта
  • и так как каждый раз checkout выполнялся в новую папку вида 2012-01-01T23:59:59, то обновлял симлинк latest, указывающий на последнюю развернутую версию

Это было не совсем удобно в силу полного отсутствия поддержки инсталляции на удаленные сервера.

После нескольких экспериментов с Capistrano, Magallanes и другими инструментами, в дополнение к этому скрипту было реализовано консольное приложение Installer. Оно копирует на нужную удаленную группу серверов инсталляционный скрипт с нужными настройками и выполняет его там.

Также в это приложение были заложены команды по получению возможных версий приложения и запросу установленной на серверах версии (на картинке показа возможность обновления проекта в production environment'е с версии 1.0.19 до 1.0.20):

image

А формат файлов настроек был заменен на более удобный .yml:

image

Данное консольное приложение было развернуто на сборочном сервере, к нему был сделан веб-интерфейс в виде параметризуемого сборочного задания Jenkins, выполняющего консольную команду:

/home/projects/installer/installer.phar $command $recipe $environment

где,

  • $command — имя выполняемой команды, например install, status, versions
  • $recipe — код присваемый версии проекта, предназначенной для инсталляции
  • $environment — опциональное имя группы серверов, на которые необходимо установить проект

И это задание в свою очередь было отмечено как downstream project для сборочных заданий релизных веток с использованием плагина Parameterized Trigger Plugin.

В итоге

В итоге нами была успешно решена задача реализации continuous delivery со следующей последовательностью шагов:

  • разработчик вносит изменения в релизную ветку
  • post-receive hook gitolite инициирует соответствующее этой ветке сборочное задание Jenkins
  • сборочное задание проводит тестирование и помечает успешную версию тегом
  • Jenkins запускает downstream project Installer с нужными параметрами для проекта и группы серверов, на которых проект надо обновить
  • Installer, последовательно пройдя по всем серверам группы, разворачивает на них свежую версию и обновляет симлинк latest

В дальнейшем

Используемая модель ветвления способствует тому, что разные ветки со временем начинают сильно отличаться друг от друга, это приводит к проблемам внедрения новых фич в старые релизы. Пока это не стало критичным, но есть мысль попробовать вернуть интеграционную ветку develop, а для подготовки A/B-версий использовать другую технику, возможно, что-то навроде feature toggles.

Есть интерес попробовать различного вида интеграции с трекером Jira. Как-то например автоматизировать:

  • создание веток под новые тикеты определенного типа
  • обновление статуса и/или комментариев к тикетам в соответствии с результатами тестирования
  • формирование change log'ов

Текущее время работы composer'а составляет порядка нескольких минут, а большое количество собственных пакетов приводит к сильно разросшейся секции repositories файлов composer.json. Хочется поэкспериментировать с Satis, для решения этих проблем.

Заключение

Мы успешно решили поставленные перед нами проблемы:

  • для создания A/B-версий используются отдельные ветки в системе контроля версий
  • пакетный менеджер позволяет переиспользовать функционал от проекта к проекту
  • тесты на Gherkin и их реализация помогают существенно упростить подключение к проекту новых разработчиков
  • а описанная выше схема continuous delivery позволяет минимизировать время получения фидбека от разработчиков клиентской части игры

Следует заметить, что данная схема почти не используется для фактического обновления проектов в production, так как не покрывает всех проблем выпуска нового и возможно не совместимого с предыдущей версией релиза. Ее основное применение быстрая и автоматизированная доставка нового функционала на все сервера, где развернуто приложение, и обновление в тех местах, где это возможно, либо не критично – тестовые и предназначенные для закрытого бета-тестирования сервера.

Suerte!

Автор: grelkin

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js