Всем привет! Меня зовут Дмитрий, я релиз-инженер в команде CI/CD Speed Авито. Вот уже несколько лет мы с коллегами отвечаем за всё, что связано с релизами наших мобильных приложений и не только. Про наши «релизные поезда» и как мы к этому шли уже очень подробно рассказывал Алексей Шпирко.
Но мы не стоим на месте и сегодня постараемся рассказать, как наша система CI/CD эволюционировала из набора скриптов и TeamCity-билдов в полноценный сервис мобильных релизов, позволяющий управлять всем процессом выпуска приложений через удобный интерфейс.
Немного контекста
Мобильное приложение Авито — это:
- Десятки продуктовых команд.
- 20+ разработчиков на каждую из платформ.
- Тысячи UI-тестов.
- Десятки тысяч UNIT-тестов.
- Сотни тысяч строк кода.
- Еженедельные релизы Android.
- Релизы iOS раз в две недели.
Процесс релиза состоит из следующий частей:
- Срез релизной ветки из develop и простановка тега в git.
- Прогон всех автоматических проверок кода и прогон всех видов тестов.
- Сборка релиз-кандидата.
- Загрузка релиз-кандидата в AppStore/GooglePlay и внутреннее хранилище артефактов.
- Отправка необходимой информации в системы мониторинга.
- Загрузка данных в систему управления фича-тоглами.
- Сборка what's new для QA и редакторов.
- Подготовка Jira-артефактов — простановка версии в задачи, создание задач для редакторов, QA и релиз-инженеров.
- Нотификация всех заинтересованных лиц о готовности релиз-кандидата.
- Регрессионное тестирование.
- Выпуск приложения на часть пользователей и нотификация об этом.
- Выпуск приложения на 100% пользователей и снова нотификация.
Так устроен наш релизный поезд
В начале 2019 года всё это успешно обслуживалось несколькими десяткам скриптов на разных языках и сложными цепочками TeamCity-билдов. Каждое воскресенье cron запускал стартовую TeamCity-конфигурацию, скрипты и билды выполняли всю работу из пунктов 1-9.
Если всё шло хорошо, продуктовые команды разбирали результаты автотестирования и что-то допроверяли руками. Редакторы писали красивые тексты, а релиз-инженеры следили за первыми и вторыми и ждали, когда можно будет нажать на заветную кнопку «релиз» в маркете. Идиллия и победа автоматизации над рутинным трудом.
Но в кажущемся благополучии таился ряд проблем.
Проблема 1. Сложные цепочки билдов в TeamCity
Релизный процесс состоит из многих этапов. Каждый следующий этап зависит от предыдущего или нескольких предыдущих. И ко всему этому добавляется множество внешних систем.
Организация подобной системы на базе TeamCity отлично работает и выполняет поставленную задачу, но при возрастающей сложности и скорости вы неизбежно сталкиваетесь с рядом проблем:
- Много билдов и сложные связи между ними.
- Сложность поддержки и внесения изменений.
- Сложность тестирования всего процесса или его частей.
- Сложно или нельзя запустить процесс начиная с «точки отказа».
Например билд-1 завершает работу, билд-3 ждёт результаты выполнения билдов 1 и 4, а в билде-7 поменяли параметр, и вся система через час работы рухнула. Садишься, разбираешься что куда нужно пробросить и кому что скормить или перезапускаешь весь процесс с нуля. В итоге теряешь много сил и времени и получаешь временами автоматизацию на ручном приводе.
Это усугублялось другой проблемой или же особенностью организации нашей работы.
Проблема 2. Зоны ответственности
Так сложилось, что наша большая команда состоит из двух независимых команд поменьше. Это собственно мы — CI/CD team и наши коллеги Testing team. Мы отвечаем за всю общую часть релиза или же CD — как взять нужный срез кода и донести его пользователям. Ребята из Testing team отвечают за всю платформа-специфичную часть — как собрать приложение, прогнать на нём нужные тесты и отдать это нам.
И, соответственно, релизная цепочка билдов содержала как наши билды (срез ветки, Jira-задачи, оповещалки, подготовку артефактов для ручных проверок) так и билды коллег. Билды зависели друг от друга, и это мешало быстро развивать как наши системы и процессы, так и системы и процессы ребят, потому что любое косвенное изменение потенциально ломало весь процесс.
Частично эту проблему мы постарались решить с помощью ночных тестовых релизов. Каждую ночь весь процесс запускался на тестовых данных, и утром мы могли видеть состояние системы. Но была ещё одна проблема.
Проблема 3. Люди
У нас есть часть релизного процесса, в которой участвуют люди. Это непосредственные участники: тестировщики, редакторы, релиз-инженеры. И косвенные, но заинтересованные в том, чтобы пользователи получили приложение: продакт-менеджеры, разработчики, маркетологи, аналитики. Раньше вся коммуникация осуществлялась через Slack-каналы, а актуальное состояние релиза было разбросано по разным местам (Jira, Slack), его знал только релиз-инженер. Поэтому ему приходилось тратить много времени отвечая на вопросы «когда поедем на 100%?», «релиз стартанул?», «так уже можно тестировать или нет?», «а следующий релиз когда?».
Настало время подойти к вопросу не эволюционно, а революционно, и решить все проблемы разом, взяв лучшее, что у нас есть.
Разграничиваем ответственность
Как уже говорилось, наша большая команда состоит из двух команд поменьше, каждая из которых отвечает за CI или CD часть всего процесса.
Давайте посмотрим, что мы вкладываем в эти понятия.
CD:
- срез релизной ветки в git;
- простановка тегов в git;
- запуск CI-части;
- подготовка релизных артефактов (Jira-задачи, Release Notes);
- подготовка регрессионных артефактов;
- оповещения о стадиях релиза;
- релиз.
CI:
- прогон всех тестов;
- сборка приложения;
- сборка платформ-специфических артефактов;
- загрузка приложения в маркет.
Видно, что есть разграничение зон ответственности на процессном уровне и есть четкое разграничение на организационном уровне. Но в общем релизном процессе на уровне TeamCity всё перемешивалось и доставляло очень много проблем.
При этом, в большинстве своём, CI ничего не знает про CD. CD запускает CI-часть с требуемыми параметрами и ждёт завершения билдов, чтобы получить из них необходимые артефакты. Получается, что жёсткая связность между билдами и сложные зависимости «многие-ко-многим» были не оправданы. Мы решили явно разграничить CI и CD, сделать одну точку взаимодействия между ними и связать её «контрактом».
Контракт по своей сути — это пара JSON-файлов, один из которых CD передаёт в CI-часть, а второй ожидается как результат работы CI.
CD в этой концепции является менеджером процесса, он полностью управляет релизом, конфигурирует CI и ожидает определённых результатов его работы. CI же просто выполняет работу, которую «заказывает» CD. Обе части могут существовать и разрабатываться абсолютно автономно при условии соблюдения контракта.
Пример входного файла контракта config.json:
{
"schema_version": 1,
"project": "avito",
"release_version": "777.5",
"output_descriptor": {
"path":"http://artifactory.ru/releases/avito_android/777.5_1/output.json",
"skip_upload": false
},
"deployments": [
{
"type": "google-play",
"artifact_type": "bundle",
"build_variant": "release",
"track": "beta"
}
]
}
Тут мы сообщаем CI-части, что хотим собрать релиз проекта «Авито» с номером 777.5, ожидаем, что выходной файл в результате работы будет загружен по пути, описанному в output_descriptor, а также «заказываем», какие артефакты и в каком виде должны быть собраны и куда загружены после.
Пример выходного файла контракта output.json:
{
"schema_version": 1,
"teamcity_build_url": "https://tmct.ru/viewLog.html?buildId=17317583",
"build_number": "777",
"release_version": "777.5",
"git_branch": {
"name": "release-avito/777.5",
"commit_hash": "2c54c50c220bf91bc1a6ca10b34f53a540c80551"
},
"test_results": {
"report_id": "5f3e94fd23d67bf434e5c1b8",
"report_url": "https://
tests.avito.ru/report/AvitoAndroid/FunctionalTests/2c54c50c220bf91",
"report_coordinates": {
"plan_slug": "AvitoAndroid",
"job_slug": "FunctionalTests",
"run_id": "2c54c50c220bf91"
}
},
"artifacts": [
{
"type": "apk",
"name": "avito-777.5-777-release.apk",
"uri": "http://example.com/artifactory/android/avito/777.5-777/avito-777.5-777-release.apk",
"build_variant": "release"
},
]
}
В нём содержатся результаты выполнения работы CI и все значимые для дальнейшего процесса данные вроде ссылок на артефакты и результатов тестов.
Nupokati: сервис релизов мобильных приложений
Благодаря контракту нам удалось решить проблему жёсткой связности и сложных зависимостей между разными частями релизного поезда. Но это не решало проблемы прозрачности, поддерживаемости и управляемости всего процесса. У нас всё ещё оставались «клубки» из тимсити-билдов на нашей стороне поезда.
Поэтому мы решили отказаться от TeamCity в CD и реализовывать собственный сервис релизов мобильных приложений.
Что мы хотели получить от нового сервиса?
- Отсутствие сложных связей и неявных зависимостей.
- Перезапуск релиза, начиная с точки отказа.
- Прозрачность процесса релизов для всех участников.
- Простую поддержку, кастомизацию и тестирование.
- Возможность использования на разных мобильных проектах компании.
Так появился сервис мобильных релизов Nupokati — рабочее название прижилось и осталось с нами.
Он состоит из управляющего CD-сервиса на Python и дашборда мобильных релизов.
По крону CD-сервис проверяет календарь релизов и стартует нужный релиз, запускает CI, выполняет все необходимые взаимодействия с внешними сервисами и извещает всех заинтересованных участников процесса о каждой стадии.
Основная управляющая сущность в CD-сервисе — это Release
.
Он, как конструктор, собирается из различных шагов:
Вот пример небольшой части релиза:
Это позволяет нам сохранять модульность и гибкость и быстро подключать новые проекты к сервису, просто набирая нужный pipeline из шагов. При этом каждый релиз имеет явный стейт, который позволяет перезапускать релиз с любой точки релизного поезда и получать предсказуемый результат.
Дашборд же служит панелью управления всем процессом и показывает актуальное состояние конкретного релиза:
Здесь есть вся необходимая информация по релизу и ссылки на артефакты
Также отсюда осуществляется всё управление релизом
И отображается актуальное положение релизного поезда
Помимо этого на дашборде можно всегда найти информацию о прошлых релизах и календарь с планируемыми
Итоги
Такая простая на первый взгляд вещь как контракт между CI и CD оказалась для нас революционной и позволила получить удобную для разработки, поддержки и использования систему релизов мобильных приложений. Контракт позволил нам вылечить все проблемы и не прибавить в процессе новых.
Нужно понимать, что подход «в любой непонятной ситуации пили свой велосипед» не является универсальным, и любую проблему можно решить разными путями. Но в нашем случае свой велосипед и время на его разработку окупились через удобство использования, поддержки и сотни сэкономленных человеко-часов. Он просто работает и выполняет свою функцию, радуя всех участников релизного процесса.
eugenekrivobokov
Дополню для контекста, CI часть для Android доступна в Github: CI steps plugin (исходники)