Continuous Delivery hecho en Alawar

    Около года назад перед нашей командой была поставлена задача стартовать разработку серверных частей ряда игровых 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!
    Метки:
    Alawar Entertainment 51,72
    Компания
    Поделиться публикацией
    Похожие публикации
    Комментарии 4

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

    Самое читаемое