Меня зовут Эдуард Мацуков, я делаю Таксометр — приложение для водителей Яндекс.Такси. Занимаюсь инфраструктурой и всем, что с ней связано. Какое-то время назад я выступил с докладом — рассказал об опыте дружбы TeamCity с нашим проектом и с разработчиками в целом. Отдельная часть доклада посвящена тому, при чем здесь Kotlin.
— Практически каждый день ко мне лично и к нашим разработчикам приходят с вопросами. А где достать сборку? А где взять такую-то ветку? А почему что-то упало? Где в моем коде проблема? Почему что-то работает неправильно? Для этого у нас в проекте есть много самописной инфраструктуры, плагинов, различных хаков и трюков, которые мы используем. С одной стороны — чтобы облегчить жизнь разработчика, с другой — чтобы реализовать конкретные бизнес-задачи.
И в какой-то момент у нас, конечно, используется в том числе и CI, и TeamCity. Мы заморочились — научили дружить TeamCity с Kotlin и вывели, можно сказать, весь CI и всю сборку на принципиально новый уровень.
Но сначала немножко истории — чтобы понять, как мы к этому пришли и почему такой уровень я называю отдельным каноном. TeamCity в Яндексе существует уже много лет. Нам приходилось жить в этом общем сервере, где хостится весь бэкенд, весь фронтенд и, с недавнего времени, все мобильные приложения. Года два назад мы все съехались. И каждый разработчик настраивает каждый проект даже не как хочет, а как может или насколько понимает, насколько хочет разбираться в системе. И нет какого-то одного человека, который все знает и умеет. Мало кто хочет заморачиваться, изучать отдельно шаблоны, дебри TeamCity. Поэтому каждый пилит, кто во что горазд.
Мы жили в этом едином сервере, и в прошлом году у нас на TeamCity произошла авария. Около недели был полный простой. Сборки не собирались, тестирование постоянно жаловалось. Кто-то исхитрялся, собирал локально.
Всё из-за того, что наш TeamCity-сервер был, грубо говоря, наколеночным решением, которое внезапно выросло в большой сервис. Им пользуются тысячи разработчиков в Яндексе. Конечно же, там была какая-никакая отказоустойчивость, но она тоже отказала. При очередном обновлении TeamCity после рестарта обнаружилось, что несколько жестких дисков просто развалилось, и подняться заново мы уже не смогли. Пришлось выкручиваться.
Нужно делать выводы из всего, что произошло. И мы эти выводы, конечно, сделали: проанализировали, почему так случилось и как убедиться, что такого не произойдет снова.
В первую очередь важно то, что мы очень долго поднимались, восстанавливали наш сервис. Под сервисом я имею в виду как технический процесс, так и отчасти бизнес-процесс по банальной доставке релизов, по сборке пул-реквестов. Мы потеряли очень много артефактов, включая релизные сборки, потеряли очень много времени на пул-реквестах, на том, что тестирование не могло нормально заниматься своими делами. И конечно, мы потратили довольно много времени на то, чтобы восстановить проект с нуля, настроить заново всю структуру, всю систему сборки. И тогда мы поняли, что пора что-то менять и настроить свой собственный сервер.
Шли мы к этому долго. Не сказать, что только одна авария привела к этому выводу. В общем, мы решили, что пора идти к горе, делать всё это дело самим. Мы начали развертку сервиса. Это делается крайне быстро: пару дней и готово. Когда все это разворачиваешь уже сам и можешь покопаться во внутрянке, поадминить немножко, то бросаются в глаза интересные особенности. Одна из них — новый TeamCity позволяет настроить версионирование.
Версионирование налажено очень примитивно, но в то же время очень надежно, красиво и круто. Все, что хранится в TeamCity касательно вашего или любого другого проекта, можно спокойно отгрузить в Git, и сча́стливо жить на этом. Но есть пара проблем.
Первая проблема — все люди привыкли работать с TeamCity исключительно через интерфейс, и эту привычку тяжело искоренить. Здесь есть маленький лайфхак: можно просто запретить любые изменения из интерфейса и заставить всех людей переучиваться. В нашей команде 2000 разработчиков. Не очень хороший путь, правда?
На самом деле, на этом минусы заканчиваются. Самый главный минус — людям приходится переучиваться на что-то новое. А значит, им нужно дать почву для того, чтобы сделать личный вывод о том, зачем это вообще нужно. А нужно это затем, что TeamCity благодаря версионированию не позволяет применить изменения, которые так или иначе ломают систему. TeamCity сам фоллбэчит на последнюю стабильную ревизию.
В TeamCity можно каждый проект завести под это версионирование и настроить его достаточно гибко.
Чуть-чуть ликбеза. Все проекты в TeamCity устроены в виде дерева. Есть некий общий root, и дальше от него идет такая простая структура. Каждый проект — вершина этого графа. Он может выступать как неким набором конфигураций, которые что-то билдят, так и родителем для других проектов.
В Git можно отгрузить либо всё сразу, либо конкретный кусочек. Например, если коллеги из бэкенда с фронтендом не хотят пользоваться версионированием — пожалуйста, на них можно не рассчитывать, и просто обезопасить свой личный проект.
Можно настроить довольно сложную иерархическую систему, к которой в итоге пришла наша команда. У нас есть один общий большой root и несколько маленьких roots. Бэкенд, мобильная разработка, фронтенд, Яндекс.Еда — они все живут каждый в своем отдельном репозитории. В то же время информация о всех этих проектах хранится в большом общем репозитории — в корневом.
После того, как это версионирование наконец подключаешь, устаканиваешь со всеми коллегами, где кто и как будет жить, кто будет заниматься поддержкой, — после всего этого приходится сделать сложный выбор.
TeamCity поддерживает всего два формата конфигов. С XML, я подозреваю, никто работать не захочет, поэтому мы выбрали второй формат. Он позволяет сделать эти конфиги на Kotlin-скрипте.
ТeamCity создает maven-проект, некоторое подобие любого обычного проекта. С ним можно сделать одно из двух: либо загрузить в свой проект — Android, бэкенд, неважно, — либо оставить standalone-проектом. Тогда у вас будет независимый репозиторий с независимым проектом.
В чем плюс такого подхода? Лично меня и тех ребят, которые занимаются у нас инфраструктурой на бэкенде и фронтенде, кое-что подкупило сразу. И даже те, кто не знаком с Kotlin, кто услышал о нем впервые, пошли и начали его учить.
Эти две строчки создают весь проект. Здесь указывается диалект API TeamCity. API меняется каждую мажорную версию. Есть 2018-2, 2018-1, 2017 и т. д. Скоро, надеюсь, выйдет 2019-й.
Вторая строчка просто декларирует проект.
Вот сам проект. Это абсолютно реальный код. Именно так сейчас выглядит наш корневой репозиторий. Ничего лишнего, ничего сложного. Единственная ручная работа, которая здесь потребуется — это создать самому вручную UUID. TeamCity требует, чтобы каждый объект, каждый проект имел свой уникальный идентификатор. Написать туда можно что угодно. Я просто пользуюсь стандартной никсовой командой uuidgen.
Здесь начинаются приключения в Kotlin DSL. Думаю, это совершенно несложный для освоения язык. Загрузив его в IDEA, в Eclipse или в любой другой IDE, можно получить всю документацию, highlighting, autocomplete, подсказки. На самом деле, многих из них не хватает в интерфейсе. Поэтому лично мой опыт говорит, что работать с кодом гораздо удобнее, проще и интуитивнее. Мы же все-таки разработчики.
Примерно так выглядит настоящий существующий сейчас и работающий в это самое время конфиг, поддерживающий сами конфиги TeamCity. То есть TeamCity билдит свои собственные конфиги в своем собственном окружении. Если все хорошо и все сбилдилось, он это спокойно отгружает in memory и реплицирует изменения в PostgreSQL. База коннектится уже в сам сервис. И здесь будет грешно не использовать все возможности Kotlin.
В данном случае, в отличие от XML, эти конфиги можно описать с использованием полиморфизма, наследования — разрешены любые фичи языка Kotlin. Единственный важный момент — все это в итоге может превратиться в хаос, который существовал у нас до того, как мы ввели версионирование конфигов на Kotlin-скрипте.
Но, как ни странно, такого хаоса стало гораздо меньше. Потому что раньше было не совсем очевидно, как сделать то, что я хочу, как добиться той или иной фичи? Из кода, на моей практике, гораздо проще понять, как реализовать какую-либо фичу.
Здесь начинаются самые интересные приключения: а как мы реализовываем какие-то вещи и как в принципе сделать взаимодействие проекта с TeamCity проще?
Все здесь присутствующие в том или ином виде готовят релиз, участвуют в его сборке, в публикации. Мы публикуем свои релизы в разные каналы в Google Play.
У нас есть бета, есть эксперименты, есть stable. У нас используется специальный плагин с роботом, который постит комментарии с отчетом о релизной сборке в релизный тикет. И все это настраивается таким красивым окошком. Оно появляется, как только ты пытаешься собрать релиз. От этих вопросов никуда не уйти.
Из интерфейса TeamCity это выглядит примерно так. Чтобы сходу понять, что, где, куда и как, нужно вчитываться в каждый параметр, нужно экспериментировать. Из документации, кроме того, что видно на экране, больше ничего не почерпнуть.
В коде это выглядит так. По крайней мере до сих пор, за полгода, еще никто не пришел и не спросил — а как мне сделать какую-то фичу? Чаще всего из кода это все интуитивно понятно.
В то же время некоторые вещи сделаны довольно просто, но спрятаны за несколькими слоями интерфейса. Приходится погулять, походить туда-сюда.
Вот пример, как в TeamCity реализовывается security. На моей практике, TeamCity у большинства людей представляется достаточно простой холодной системой, которая из коробки не поддерживает интеграцию, например, с сервисами безопасности. Поэтому все токены, все ключи, все credentials у нас чаще всего торчали наружу в открытом виде. Почему бы и нет?
На самом деле, TeamCity умеет в безопасность. Он умеет создавать на своем сервере собственный специальный файлик, который так и называется — credential json, как показано. И он создает для каждого токена, для каждого credential, который мы специальным образом генерируем через интерфейс, такой ключик. Его уже можно положить в код и быть уверенным, что этот credential никогда не всплывет наружу ни в логах TeamCity, ни в интерфейсе TeamCity. Система умеет вырезать эти ключи буквально отовсюду. Весь интерфейс проходит, грубо говоря, некое декорирование.
Окей, мы настроили какие-то свои параметры, сделали проброс обязательных параметров, допустим, для сборки релиза. Но что если мы хотим пойти дальше? А мы хотели пойти дальше. У нас при сборке запускается очень много разных steps. Мы запускаем несколько вложенных библиотек, которые билдятся из совершенно других репозиториев. И мы хотели подтягивать просто свежие изменения. Всё, что есть сейчас. Не заморачиваться — например, не собирать для пул-реквестов вспомогательную библиотеку, не заливать ее в maven-репозиторий, не внесить дополнительные телодвижения в пул-реквест.
Мы просто настроили chain-сборку. Я буду до конца показывать, насколько неочевидно и неудобно это делать из интерфейса, на мой личный взгляд. А там уже сами судите.
Примерно так выглядит chain-сборка в интерфейсе.
Примерно так она выглядит в коде. Простоуказываем, какая именно конфигурация является зависимой и что делать, если какая-то из конфигураций не отработала либо была отменена пользователем снаружи. В данном случае я не хочу, чтобы сборка вообще стартовала. Потому что какой в ней смысл, если мы не собрали все зависимые библиотеки?
Примерно в таком же духе делаются и все остальные вещи. И весь проект в TeamCity занимает буквально 500 строк кода.
Оказывается, можно и пробросить какой-нибудь интересный параметр через все зависимости. Я показал chaining не просто так. Сhains — это удобно, но их тяжело готовить в интерфейсе. И TeamCity не документирует такую важную особенность, как пробрасывание сквозных параметров. Для чего это нужно? Допустим, в нашей сборке в Gradle или еще где-то мы хотим завязаться на какое-нибудь специфическое поле, пробросить тот же адрес на релизный тикет. И хотим сделать это один раз, а не для каждой вложенной сборки.
У TeamCity есть не совсем очевидный и совершенно не документированный параметр — reverse.dep (reverse dependency). Он пробрасывает во все вложенные билды все эти параметры, которые идут после звездочки.
На выходе мы получаем примерно такую простую структуру. Можно ее усложнять и делать вложенности настолько глубоко, насколько хватит фантазии или потребностей. И быть уверенным, что во все эти зависимости, во все эти конфигурации будут проброшены все наши параметры, которые мы ожидаем на каждом шаге выполнения сборки. Готов ответить на ваши вопросы. Всем спасибо!
— Практически каждый день ко мне лично и к нашим разработчикам приходят с вопросами. А где достать сборку? А где взять такую-то ветку? А почему что-то упало? Где в моем коде проблема? Почему что-то работает неправильно? Для этого у нас в проекте есть много самописной инфраструктуры, плагинов, различных хаков и трюков, которые мы используем. С одной стороны — чтобы облегчить жизнь разработчика, с другой — чтобы реализовать конкретные бизнес-задачи.
И в какой-то момент у нас, конечно, используется в том числе и CI, и TeamCity. Мы заморочились — научили дружить TeamCity с Kotlin и вывели, можно сказать, весь CI и всю сборку на принципиально новый уровень.
Но сначала немножко истории — чтобы понять, как мы к этому пришли и почему такой уровень я называю отдельным каноном. TeamCity в Яндексе существует уже много лет. Нам приходилось жить в этом общем сервере, где хостится весь бэкенд, весь фронтенд и, с недавнего времени, все мобильные приложения. Года два назад мы все съехались. И каждый разработчик настраивает каждый проект даже не как хочет, а как может или насколько понимает, насколько хочет разбираться в системе. И нет какого-то одного человека, который все знает и умеет. Мало кто хочет заморачиваться, изучать отдельно шаблоны, дебри TeamCity. Поэтому каждый пилит, кто во что горазд.
Мы жили в этом едином сервере, и в прошлом году у нас на TeamCity произошла авария. Около недели был полный простой. Сборки не собирались, тестирование постоянно жаловалось. Кто-то исхитрялся, собирал локально.
Всё из-за того, что наш TeamCity-сервер был, грубо говоря, наколеночным решением, которое внезапно выросло в большой сервис. Им пользуются тысячи разработчиков в Яндексе. Конечно же, там была какая-никакая отказоустойчивость, но она тоже отказала. При очередном обновлении TeamCity после рестарта обнаружилось, что несколько жестких дисков просто развалилось, и подняться заново мы уже не смогли. Пришлось выкручиваться.
Нужно делать выводы из всего, что произошло. И мы эти выводы, конечно, сделали: проанализировали, почему так случилось и как убедиться, что такого не произойдет снова.
В первую очередь важно то, что мы очень долго поднимались, восстанавливали наш сервис. Под сервисом я имею в виду как технический процесс, так и отчасти бизнес-процесс по банальной доставке релизов, по сборке пул-реквестов. Мы потеряли очень много артефактов, включая релизные сборки, потеряли очень много времени на пул-реквестах, на том, что тестирование не могло нормально заниматься своими делами. И конечно, мы потратили довольно много времени на то, чтобы восстановить проект с нуля, настроить заново всю структуру, всю систему сборки. И тогда мы поняли, что пора что-то менять и настроить свой собственный сервер.
Шли мы к этому долго. Не сказать, что только одна авария привела к этому выводу. В общем, мы решили, что пора идти к горе, делать всё это дело самим. Мы начали развертку сервиса. Это делается крайне быстро: пару дней и готово. Когда все это разворачиваешь уже сам и можешь покопаться во внутрянке, поадминить немножко, то бросаются в глаза интересные особенности. Одна из них — новый TeamCity позволяет настроить версионирование.
Версионирование налажено очень примитивно, но в то же время очень надежно, красиво и круто. Все, что хранится в TeamCity касательно вашего или любого другого проекта, можно спокойно отгрузить в Git, и сча́стливо жить на этом. Но есть пара проблем.
Первая проблема — все люди привыкли работать с TeamCity исключительно через интерфейс, и эту привычку тяжело искоренить. Здесь есть маленький лайфхак: можно просто запретить любые изменения из интерфейса и заставить всех людей переучиваться. В нашей команде 2000 разработчиков. Не очень хороший путь, правда?
На самом деле, на этом минусы заканчиваются. Самый главный минус — людям приходится переучиваться на что-то новое. А значит, им нужно дать почву для того, чтобы сделать личный вывод о том, зачем это вообще нужно. А нужно это затем, что TeamCity благодаря версионированию не позволяет применить изменения, которые так или иначе ломают систему. TeamCity сам фоллбэчит на последнюю стабильную ревизию.
В TeamCity можно каждый проект завести под это версионирование и настроить его достаточно гибко.
Чуть-чуть ликбеза. Все проекты в TeamCity устроены в виде дерева. Есть некий общий root, и дальше от него идет такая простая структура. Каждый проект — вершина этого графа. Он может выступать как неким набором конфигураций, которые что-то билдят, так и родителем для других проектов.
В Git можно отгрузить либо всё сразу, либо конкретный кусочек. Например, если коллеги из бэкенда с фронтендом не хотят пользоваться версионированием — пожалуйста, на них можно не рассчитывать, и просто обезопасить свой личный проект.
Можно настроить довольно сложную иерархическую систему, к которой в итоге пришла наша команда. У нас есть один общий большой root и несколько маленьких roots. Бэкенд, мобильная разработка, фронтенд, Яндекс.Еда — они все живут каждый в своем отдельном репозитории. В то же время информация о всех этих проектах хранится в большом общем репозитории — в корневом.
После того, как это версионирование наконец подключаешь, устаканиваешь со всеми коллегами, где кто и как будет жить, кто будет заниматься поддержкой, — после всего этого приходится сделать сложный выбор.
TeamCity поддерживает всего два формата конфигов. С XML, я подозреваю, никто работать не захочет, поэтому мы выбрали второй формат. Он позволяет сделать эти конфиги на Kotlin-скрипте.
ТeamCity создает maven-проект, некоторое подобие любого обычного проекта. С ним можно сделать одно из двух: либо загрузить в свой проект — Android, бэкенд, неважно, — либо оставить standalone-проектом. Тогда у вас будет независимый репозиторий с независимым проектом.
В чем плюс такого подхода? Лично меня и тех ребят, которые занимаются у нас инфраструктурой на бэкенде и фронтенде, кое-что подкупило сразу. И даже те, кто не знаком с Kotlin, кто услышал о нем впервые, пошли и начали его учить.
Эти две строчки создают весь проект. Здесь указывается диалект API TeamCity. API меняется каждую мажорную версию. Есть 2018-2, 2018-1, 2017 и т. д. Скоро, надеюсь, выйдет 2019-й.
Вторая строчка просто декларирует проект.
Вот сам проект. Это абсолютно реальный код. Именно так сейчас выглядит наш корневой репозиторий. Ничего лишнего, ничего сложного. Единственная ручная работа, которая здесь потребуется — это создать самому вручную UUID. TeamCity требует, чтобы каждый объект, каждый проект имел свой уникальный идентификатор. Написать туда можно что угодно. Я просто пользуюсь стандартной никсовой командой uuidgen.
Здесь начинаются приключения в Kotlin DSL. Думаю, это совершенно несложный для освоения язык. Загрузив его в IDEA, в Eclipse или в любой другой IDE, можно получить всю документацию, highlighting, autocomplete, подсказки. На самом деле, многих из них не хватает в интерфейсе. Поэтому лично мой опыт говорит, что работать с кодом гораздо удобнее, проще и интуитивнее. Мы же все-таки разработчики.
Примерно так выглядит настоящий существующий сейчас и работающий в это самое время конфиг, поддерживающий сами конфиги TeamCity. То есть TeamCity билдит свои собственные конфиги в своем собственном окружении. Если все хорошо и все сбилдилось, он это спокойно отгружает in memory и реплицирует изменения в PostgreSQL. База коннектится уже в сам сервис. И здесь будет грешно не использовать все возможности Kotlin.
В данном случае, в отличие от XML, эти конфиги можно описать с использованием полиморфизма, наследования — разрешены любые фичи языка Kotlin. Единственный важный момент — все это в итоге может превратиться в хаос, который существовал у нас до того, как мы ввели версионирование конфигов на Kotlin-скрипте.
Но, как ни странно, такого хаоса стало гораздо меньше. Потому что раньше было не совсем очевидно, как сделать то, что я хочу, как добиться той или иной фичи? Из кода, на моей практике, гораздо проще понять, как реализовать какую-либо фичу.
Здесь начинаются самые интересные приключения: а как мы реализовываем какие-то вещи и как в принципе сделать взаимодействие проекта с TeamCity проще?
Все здесь присутствующие в том или ином виде готовят релиз, участвуют в его сборке, в публикации. Мы публикуем свои релизы в разные каналы в Google Play.
У нас есть бета, есть эксперименты, есть stable. У нас используется специальный плагин с роботом, который постит комментарии с отчетом о релизной сборке в релизный тикет. И все это настраивается таким красивым окошком. Оно появляется, как только ты пытаешься собрать релиз. От этих вопросов никуда не уйти.
Из интерфейса TeamCity это выглядит примерно так. Чтобы сходу понять, что, где, куда и как, нужно вчитываться в каждый параметр, нужно экспериментировать. Из документации, кроме того, что видно на экране, больше ничего не почерпнуть.
В коде это выглядит так. По крайней мере до сих пор, за полгода, еще никто не пришел и не спросил — а как мне сделать какую-то фичу? Чаще всего из кода это все интуитивно понятно.
В то же время некоторые вещи сделаны довольно просто, но спрятаны за несколькими слоями интерфейса. Приходится погулять, походить туда-сюда.
Вот пример, как в TeamCity реализовывается security. На моей практике, TeamCity у большинства людей представляется достаточно простой холодной системой, которая из коробки не поддерживает интеграцию, например, с сервисами безопасности. Поэтому все токены, все ключи, все credentials у нас чаще всего торчали наружу в открытом виде. Почему бы и нет?
На самом деле, TeamCity умеет в безопасность. Он умеет создавать на своем сервере собственный специальный файлик, который так и называется — credential json, как показано. И он создает для каждого токена, для каждого credential, который мы специальным образом генерируем через интерфейс, такой ключик. Его уже можно положить в код и быть уверенным, что этот credential никогда не всплывет наружу ни в логах TeamCity, ни в интерфейсе TeamCity. Система умеет вырезать эти ключи буквально отовсюду. Весь интерфейс проходит, грубо говоря, некое декорирование.
Окей, мы настроили какие-то свои параметры, сделали проброс обязательных параметров, допустим, для сборки релиза. Но что если мы хотим пойти дальше? А мы хотели пойти дальше. У нас при сборке запускается очень много разных steps. Мы запускаем несколько вложенных библиотек, которые билдятся из совершенно других репозиториев. И мы хотели подтягивать просто свежие изменения. Всё, что есть сейчас. Не заморачиваться — например, не собирать для пул-реквестов вспомогательную библиотеку, не заливать ее в maven-репозиторий, не внесить дополнительные телодвижения в пул-реквест.
Мы просто настроили chain-сборку. Я буду до конца показывать, насколько неочевидно и неудобно это делать из интерфейса, на мой личный взгляд. А там уже сами судите.
Примерно так выглядит chain-сборка в интерфейсе.
Примерно так она выглядит в коде. Простоуказываем, какая именно конфигурация является зависимой и что делать, если какая-то из конфигураций не отработала либо была отменена пользователем снаружи. В данном случае я не хочу, чтобы сборка вообще стартовала. Потому что какой в ней смысл, если мы не собрали все зависимые библиотеки?
Примерно в таком же духе делаются и все остальные вещи. И весь проект в TeamCity занимает буквально 500 строк кода.
Оказывается, можно и пробросить какой-нибудь интересный параметр через все зависимости. Я показал chaining не просто так. Сhains — это удобно, но их тяжело готовить в интерфейсе. И TeamCity не документирует такую важную особенность, как пробрасывание сквозных параметров. Для чего это нужно? Допустим, в нашей сборке в Gradle или еще где-то мы хотим завязаться на какое-нибудь специфическое поле, пробросить тот же адрес на релизный тикет. И хотим сделать это один раз, а не для каждой вложенной сборки.
У TeamCity есть не совсем очевидный и совершенно не документированный параметр — reverse.dep (reverse dependency). Он пробрасывает во все вложенные билды все эти параметры, которые идут после звездочки.
На выходе мы получаем примерно такую простую структуру. Можно ее усложнять и делать вложенности настолько глубоко, насколько хватит фантазии или потребностей. И быть уверенным, что во все эти зависимости, во все эти конфигурации будут проброшены все наши параметры, которые мы ожидаем на каждом шаге выполнения сборки. Готов ответить на ваши вопросы. Всем спасибо!