Команда должна фокусироваться на создании прекрасных и успешных игр, для всего остального есть CI.
Где мы применяем CI? Какие подходы и концепции используем? Зачем собирать и тестировать билды? Развернутый рассказ о CI и о том, как он устроен в Playrix, потянет на курс лекций. Под катом — краткая выжимка и немного акцентов.
Для начала небольшая разминка: что такое Continuous Integration? Если команда использует репозиторий и собирает ночные билды — это уже CI? А в чем отличие Continuous Deployment от Delivery? Это почти неважно. Детали — для узкого круга специалистов. Если вы хотите вовлечь в какой-то процесс всю компанию, придумайте ему простое и хорошее название. В Playrix все эти подходы мы называем CI. Это такой локальный бренд и клевый логотип:
Идея
CI — это не цель, это инструмент. Должна быть цель, которую хочется достигнуть с помощью Continuous Integration, проблема, которую требуется решить. Бывает, что процессы разработки и релизов в команде построены по принципу «помолились и в продакшн». Иногда это оправдано, но нечасто.
Мы сформулировали для себя цель так: минимизировать вероятность появления интеграционных проблем, минимизировать ресурсы, требуемые для исправления найденных ошибок, сократить время команд разработки проектов на поддержку и сопровождение процессов CI.
CI — это автоматизация процессов сборки билдов, тестирования кода и его доставки в различные окружения, автоматизация рутинных процессов разработки, взаимная интеграция сервисов, которыми мы все пользуемся.
Идея — в системе, которая все автомагически собирает, делает это часто, тестирует и доставляет билд, а еще точечно таргетирует удобный репорт, если что-то пошло не так.
Где мы применяем CI?
- Движок и утилиты,
- наши игры для всех платформ,
- серверный код,
- сервисы аналитики, маркетинга, разнообразная автоматизация, сервисы CI,
- инфраструктура.
То есть везде или почти везде.
Сборка и тестирование контейнеров, автоматический деплой в Test, Staging и Prod, Rolling и Canary updates — это все у нас есть и больше применимо для сервисов и веб-приложений. Сегодня мы сконцентрируемся на CI для игр: сборке билдов, их тестировании и доставке.
Парадигмы
Чтобы достичь сформулированных выше целей, нужно решить несколько задач. Ниже — план, по которому мы идем, когда автоматизируем какой-то процесс в разработке, например, сборку клиента мобильной игры. Это очень удобно — иметь список вопросов, ответив на которые, можно решить любую задачу, которая попадает в команду CI.
-
Документирование
Инструкция по сборке билда — это документация, описание нашего автоматизированного процесса. Часто такая документация находится в голове у программистов. Если в команде есть суперспециалист по сборке билдов и без него быстро и без ошибок билд никто больше собрать не может — пора что-то менять, сабмита не будет.
Хорошо, если такая документация оформлена в виде скрипта: ввел командную строку и набор параметров на машине с подготовленным окружением — получил билд.
Лучшая документация процесса — это кот код. Даже если вам зачем-то нужно повторить операцию вручную, вы всегда сможете это сделать, глядя в него.
-
Журналирование
Журнал билдов позволяет всегда точно сказать: кто, когда, из какого коммита и с каким результатом собрал тот или иной билд. Вчера билд собирался, а сегодня — нет. Смотрим в журнал, находим первый сфейленный билд, видим список коммитов, которые туда попали, — профит.
Журнал еще больше полезен, когда речь идет, например, о серверном коде. Если нет сведений о том, кто и когда обновлял прод, то в общем случае неизвестно, какой именно код сейчас у вас там трудится. А иногда это бывает очень важно, очень.
Можно вести такой журнал в гроссбухе, лучше в таблице или wiki. Список билдов в системе CI — бесценно.
-
Безопасность
Когда речь идет о сборке билда и деплое в какое-то окружение, обязательно появляется вопрос: а где хранить логины/пароли доступов? Их обычно нужно много: к репозиторию, чтобы скачать исходные данные, к файловому хранилищу, чтобы залить игровые ресурсы, к HockeyApp, чтобы отправить символы, к серверу, чтобы обновить код, и т.п.
Бывает, что все нужные доступы хранятся в репозитории. Есть гипотеза о том, что это не очень хорошо. Часто можно увидеть поле «введите пароль», скажем, в Jenkins, куда автор билда вводит сокровенные символы.
Помнить все пароли наизусть — хороший навык. Наш CI-сервер сам получает необходимые доступы в зависимости от сборки. Обычно — это короткоживущие токены, которые генерируются при старте билда и дают минимальные права ровно туда, куда мы что-то деплоим или откуда что-то читаем.
Централизованное управление сборкой и деплоем позволяет решить задачу разграничения прав доступа к инфраструктуре. Зачем давать кому-то доступ к серверу, если можно дать только доступ к сборке соответствующего билда, который делает нужную операцию на этом сервере? А раз есть билд, значит у нас есть документация и журналирование, ну вы поняли.
-
Traceability
Во время сборки билда обычно остается много следов. Нет, не так: во время сборки билда необходимо оставлять как можно больше следов. В репозитории, в таск-трекере, в системе раздачи билдов. Везде, где бы вы ни встретили билд, должны быть следы, которые приведут вас к полной информации о нем.
Эти следы не нужно заметать, наоборот, их нужно аккуратно оставлять и бережно сохранять. Дальше я расскажу об этом подробнее, но сначала нам нужно собрать наш билд. Поехали.
Pre-Commit Hooks
Повторюсь, идея заключается в системе, которая все собирает, тестирует и репортит. Но зачем собирать билд, если его можно не собирать?
У всех разработчиков наших игр установлены pre-commit хуки, т.е. набор проверок, которые выполняются при попытке что-то закоммитить. Проверки запускаются только для измененных файлов, но мы реализовали весьма хитрую систему поиска кросс-зависимостей, чтобы проверить весь связанный контент тоже. Т.е. если художник добавил текстуру, то хуки проверят, что ее не забыли прописать везде, где нужно, и ни разу не опечатались.
Оказывается, хуки ловят значительную часть мелких ошибок. Они экономят ресурсы билд-системы и помогают разработчику быстро исправить проблему: он видит сообщение, где подробно сказано, что пошло не так. А еще ему не нужно переключаться между задачами: он буквально только что вносил изменения и находится в контексте. Время исправления ошибки — минимально.
Нам это настолько понравилось, что мы даже сделали систему, которая проверяет, были ли выполнены хуки для коммита, попавшего в репозиторий. Если нет — автор такого коммита автоматически получит задачу с просьбой их настроить и подробной инструкцией, как это сделать.
Хуки унифицированы для всех проектов. Количество кастомных тестов — минимальное. Есть удобная кастомизация, в том числе в зависимости от пользователя, у которого выполняется запуск: это весьма удобно для тестирования тестов.
Build
Чтобы увидеть проблему в билде как можно раньше, собирать и тестировать эти билды нужно как можно чаще. Клиенты наших игр собираются под все платформы, на каждый коммит, для всех проектов. Наверное, есть какие-то исключения, но их немного.
Обычно клиент, особенно мобильный, имеет несколько разных версий: с читами и без, по-разному подписанный и т.д. На каждый коммит мы собираем «регулярные» билды, которыми разработчики и тестировщики пользуются постоянно.
Есть билды, которые используются совсем редко, например, магазинный ios-билд — всего один раз в сабмит, т.е. примерно раз в месяц. Однако мы считаем, что нужно собирать регулярно все билды. Если с данным типом сборки произойдет какая-то проблема, на стороне разработки или инфраструктуры, команда проекта узнает об этом не в день сдачи билда, а гораздо раньше, и сможет отреагировать и исправить проблему заранее.
В результате у нас есть простое правило: любой билд запускается минимум раз в сутки. О наличии любых проблем на любой платформе команда разработки проекта узнает в худшем случае на следующее утро после того, как эта проблема появилась в репозитории.
Подобная частота сборок и тестов требует особого подхода к оптимизации времени их выполнения.
- Все регулярные сборки клиентов выполняются инкрементально.
- Упаковка атласов и подготовка ресурсов — тоже инкрементальная.
- Сборки гранулированы: часть шагов вынесена в отдельные билд-конфигурации — это позволяет выполнять их параллельно, а также переиспользовать промежуточные результаты.
Так выглядит почти полный скриншот цепочки билдов и тестов для WildScapes. Полный сделать не удалось: он примерно вдвое больше.
Static Tests
После сборки выполняется статическое тестирование: берем папку с билдом и выполняем набор проверок всего контента, который там есть. Код — это тоже контент, поэтому его статический анализ (cppcheck + PVS-Studio) тоже тут.
На хабре выходил подробный материал о том, как у нас реализовано статическое тестирование, рекомендую. Акцентирую только, что статические тесты после билда и в прекоммитных хуках выполняются одним и тем же кодом. Это сильно упрощает поддержку системы.
Runtime Tests
Если статические тесты билд прошел успешно, можно идти дальше и попробовать запустить собранный билд. Тестируем билды на всех платформах, кроме UWP, т.е. Windows, MacOs, iOS, Android. UWP — тоже будет, но чуть позже.
Зачем тестировать десктопные билды, если они вроде бы нужны только в разработке? Ответ в вопросе: плохо, если художник или левел-дизайнер получит билд, который падает при запуске по какой-то нелепой причине. Поэтому Smoke-Test, минимальный набор проверок на запускаемость и базовый геймплей, выполняется для всех платформ.
Все, что выше было написано про билды, справедливо и для тестов на устройствах — минимум раз в сутки. За единичными исключениями: встречаются очень длительные тесты, которые за сутки не успевают выполниться.
Smoke-Test’ы выполняются на каждый коммит. Успешное выполнение базовых проверок является обязательным условием для попадания билда в систему раздачи. Обычно нет никакого смысла давать кому-нибудь доступ к билду, который заведомо не работает. Тут можно возразить и придумать исключения. У проектов есть обходной путь, чтобы дать доступ к нерабочему билду, но они им почти не пользуются.
Какие еще есть тесты:
- Benchmark: проверяем производительность по FPS и памяти в разнообразных ситуациях и на всех устройствах.
- Модульные тесты match-3: каждый элемент и каждая механика проверяются как в отдельности, так и во всех комбинациях взаимодействия.
- Прохождение всей игры от начала до конца.
- Разнообразные регрессионные тесты, например, тесты локализаций, или что все окна UI корректно открываются, или что сценки с рыбками в Fishdom проигрываются без ошибок.
- Все то же самое, но с AddressSanitizer’ом.
- Тесты совместимости версий игры: берем файл сохранения пользователя с предыдущих версий, открываем его в новой версии и убеждаемся, что все хорошо.
- Разные кастомные тесты, актуальные для механик конкретного проекта.
Для запуска тестов мы используем собственный тестовый стенд ios и android устройств. Это позволяет нам как угодно гибко запускать на устройствах нужные нам билды, взаимодействовать с устройством из кода. Мы имеем полный контроль, понятный уровень надежности, знаем, с какими проблемами мы можем столкнуться и сколько времени займет их решение. Ни один из облачных сервисов, предоставляющих устройства для тестирования, такого комфорта не предлагает.
CATS
Тесты, которые перечислены выше, реализуются внутри кода проекта. Это позволяет в теории сделать тест любой сложности, но требует усилий и ресурсов со стороны разработки проекта на реализацию и поддержку этих тестов. Ресурсов этих часто нет, а тестировать многочисленную регрессию руками сложно и не нужно. Очень хотелось, чтобы автоматизацией тестирования занимались сами тестировщики. И мы придумали для них фреймворк — Continuous Automation Testing System, или CATS.
В чем идея: дать возможность авторам тестовых сценариев взаимодействовать с игровым приложением, абсолютно не заботясь о том, как это все работает. Сценарии пишем на примитивном python’е, к приложению обращаемся через набор абстракций. Например: «Homescapes, открой мне окно с покупками и купи такой-то продукт». Проверяем результат, бинго.
Вся реализация команд сценария скрыта за набором абстракций. API, реализующее взаимодействие с приложением, позволяет сделать любое действие несколькими способами:
- отправить http-запрос серверу, который встроен в игровой движок, с какой-то командой. Эта команда обрабатывается кодом игры. Обычно этой какой-то чит, он может быть сколько угодно простым или сложным. Например, «дай мне координаты центра кнопки с указанным идентификатором». Или «пройди мне игру отсюда и до уровня с указанным номером».
- Мы можем через чит открыть окно или узнать координаты кнопки, по которому это окно открывается, можем эмулировать нажатие на эту кнопку, можем выполнить виртуальный клик по ней.
- Наконец, мы можем выполнить «настоящий» клик по указанным координатам так, будто это было сделано пальцем по экрану.
Последний метод открывает пространство для фантазии тестировщиков, которые часто хотят тестировать «боевые» билды, где никаких читов нет. Поддерживать такие сценарии сложнее, но «боевой билд» — это «боевой» билд.
Работать с координатами центра кнопки оказалось очень удобно: координаты, бывает, меняются, а вот идентификаторы кнопок — редко. Это привело к еще одному важному свойству системы: возможности написать один тестовый сценарий для всех платформ и всех разрешений экрана.
Delivery, Reports & Traces
С доставкой все оказалось совсем просто: мы используем единое разделяемое хранилище для артефактов билдов и для хранения в системе раздачи. «Загрузка» билда сводится к вызову пары запросов к api сервиса раздачи билдов, по сути — регистрации. Этим мы сэкономили немного времени на прокачку билдов и денег на их хранение.
Помните, говорили про минимизацию ресурсов, требуемых для исправления найденных в билдах ошибок? Отчеты и следы — как раз про это:
- Отчет о найденной проблеме — это задача в Asana. Ее легко контролировать, назначить на нужного разработчика, передать в команду CI, если что-то пошло не так в инфраструктуре.
- Мы собираем билды на каждый коммит. Автора этого коммита мы знаем, поэтому эту задачу увидит только он. Так мы экономим время других разработчиков: им не нужно отвлекаться на проблемы, к которым они не имеют отношения и помочь в решении которых, скорее всего, не смогут.
- Если собрали билд из следующего коммита, то, скорее всего, он все еще сломан. В задаче будет комментарий: «Билд все еще сломан», автор нового коммита задачу не увидит и не будет тратить время на чужую проблему.
- Мы отправляем отчеты в Slack. Обязательно — лично тому, кто «сломал» билд, и при желании проекта — в специальный канал или любому сотруднику Playrix. Все максимально гибко.
Следы нужны для того, чтобы везде была полная информация о билде и изменениях, из которых он был собран. Чтобы ничего не искать, чтобы все было под рукой и не нужно было тратить время на поиск деталей, которые часто необходимы при исследовании какой-то проблемы.
- Отчет содержит ссылку на билд, на лог билда, текст найденной ошибки компиляции, названия сфейленных тестов. Часто программист, получив задачу-репорт, может сразу исправлять ошибку: название файла, строка и текст ошибки есть в репорте.
- Сообщение в Slack содержит все то же самое + ссылку на задачу в Asana.
- В Teamcity — ссылка на задачу. Билд-инженер может сразу пройти в задачу, в один клик, ничего не нужно искать.
- В github — status со ссылкой на билд, в комментарии к коммиту — ссылка на задачу, для которой этот билд был сделан. В задаче — комментарий со ссылкой на коммит.
- В сервисе раздачи билдов: ссылка на билд, ссылка на коммит.
Тут всего не вспомнить, но идею вы поняли: ссылки на все, везде. Это очень ускоряет изучение любой непонятной ситуации.
FARM
Мы собираем и тестируем билды под все платформы. Для этого нам нужно много разных агентов. Следить и обслуживать их вручную долго и сложно. Все агенты готовятся автоматизированно. Мы используем Packer и Ansible.
Все логи всех агентов, Teamcity, всех сервисов, которые вокруг, мы сохраняем (в нашем случае — в ELK). Все сервисы, обрабатывая какой-то билд, добавляют в каждую строчку логов номер этого билда. Мы можем в один запрос увидеть весь жизненный цикл билда от появления его в очереди до окончания отправки всех отчетов.
Мы реализовали собственный механизм оптимизации очереди. Тот, что в Teamcity, не очень хорошо работает на наших числах. Кстати, о числах:
- Мы собираем около 5000 билдов каждый день. Это около 500 машино-часов работы.
- Трехмиллионный билд был месяц назад.
- У нас 50+ билд-серверов в 10 разных локациях.
- 40+ мобильных устройств на тестовом стенде.
- Ровно 1 сервер Teamcity.
CI as a Service
CI в Playrix — это сервис. Проектов много, идей тоже много.
Мы оптимизируем время от попадания билда в очередь до окончания его выполнения, потому что именно так считает «время билда» пользователь сервиса, разработчик. Это позволяет нам искать и находить баланс между временем выполнения билда и временем его нахождения в очереди. Вроде бы логично, что с ростом компании и количества проектов будет расти и билд-ферма, которая эти проекты собирает. Но благодаря оптимизациям скорость роста фермы сильно отстает от скорости роста компании.
Любая оптимизация начинается с мониторинга, с методичного сбора статистики. Статистики мы собираем много и знаем про наши билды решительно все. Но кроме объема билд-фермы есть еще команда, которая поддерживает систему CI и делает так, чтобы никому не нужно было думать о том, откуда берутся билды.
Оптимизация процессов в этой команде — тоже интересный и занимательный процесс. Например, мы пишем тесты на настройки билд-конфигураций, потому что этих конфигураций много, без подобных тестов найти все места, требующие правок, непросто. Почти на любые изменения мы сначала пишем тест, а потом их вносим, т.е., по сути, у нас TDD. Есть много процессов, связанных с дежурствами, инцидент-менеджментом, диспетчеризацией потока входящих задач.
Разработчики должны фокусироваться на создании прекрасных и успешных игр, не думая о том, откуда берутся билды. Для этого в Playrix есть CI. Должна быть цель, которую хочется достигнуть с помощью Continuous Integration, проблема, которую нужно решить. Важно не придумать проблему, а именно найти ее. А когда вы ее найдете, вспомните про наш опыт и сделайте лучше. И помните:
CI never sleeps
Увидимся!
Автор: s_orlov