Привет, Хабр!
TeamCity — пожалуй, один из самых популярных CI/CD-серверов, который используют разработчики по всему миру. В этой статье я расскажу про базовые концепции, на которых он построен, и на примерах покажу, как можно настроить CI/CD в вашей команде.
План
Основы
Настраиваем пайплайны
Build Agent
Перед тем как начать разбираться в TeamCity, стоит понять, как в нем построен процесс сборки и выполнения CI/CD задач.
В TeamCity есть такое понятие, как build agent — это какая-то машина, на которой происходит процесс сборки. Единовременно агент может выполнять всего одну сборку.
Сборка проходит в несколько шагов:
После некоторого события, которое было поймано триггером сборки (build trigger), сборка попадает в очередь и будет начата сразу, как только появится свободный агент.
Начинается сборка, которая поочередно выполняет все шаги сборки (build steps), которые были указаны в конфигурации (build configuration).
Сборка заканчивается, и ее результаты сохраняются в истории конфигурации.
Project
Один из основных концептов в TeamCity — проект, именно он содержит в себе так называемые конфигурации сборки (build configurations), которые и выполняют нужные нам задачи (запуск автотестов, линт-проверок и других суперважных и полезных вещей).
Проекты могут быть вложены друг в друга и принимать древовидную структуру.
Настройки проекта
У проекта есть множество настроек и параметров, к которым можно перейти, кликнув на название проекта и нажав Edit project в правом верхнем углу. Не у всех есть доступ к настройкам, так что, если он вам нужен, обращайтесь к людям, отвечающим за CI/CD в вашей команде.
Давайте разберем самые важные настройки.
General Settings
Здесь мы видим следующее:
Name | Имя проекта |
Project ID | Уникальный ID, который потом можно будет использовать в REST API |
Description | Описание (опционально) |
Все настройки и параметры (о них мы поговорим позже), которые были заданы в проекте, распространяются на все вложенные в этот проект подпроекты.
Под этими настройками мы видим список конфигураций сборки (build configurations) — о них тоже поговорим чуть позже.
VCS Roots
Здесь содержится набор настроек и правил, нужных для того, чтобы TeamCity мог подключиться к вашей системе контроля версий. При добавлении нового VCS Root он становится доступен внутри всех конфигураций, которые содержатся в проекте.
Для «соединения» TeamCity с системой контроля версий необходимо указать следующие параметры:
Type of VCS | Тип системы контроля версий (например, Subversion или Git) |
VCS root name | Уникальное имя нового рута |
VCS root ID | Уникальный ID рута, который затем может использоваться в REST API |
Fetch/Push URL | URL до репозитория в формате https://, ssh://git@ |
Default branch | Основная ветка, изменения которой будет «отлавливать» TeamCity |
Далее нужно указать способ авторизации. Это может быть пароль или приватный ключ. На наших агентах установлены ключи, поэтому выбран default private key.
Крайне рекомендую на каждый репозиторий заводить всего лишь один VCS Root.
Parameters
Для изменения поведения билда можно использовать параметры TeamCity.
Некоторые из них позволяют передать настройки непосредственно в процесс, в котором идет билд. Другие служат для подстановки настроек, например в build steps.
Параметры можно использовать при сборке, они делятся на три категории:
Параметры окружения (env.) | Передаются в окружение процессу, в котором стартуется билд, и в дальнейшем их можно будет использовать в конфигурациях сборки |
Системные параметры (system.) | Используются раннерами сборки (build runners) и автоматически передаются в них. Например, в случае с Gradle любая переменная, указанная в system., будет передана в gradle.properties, но без префикса system. |
Параметры конфигурации | Эти параметры не передаются в сборку, к ним можно только обращаться из конфигураций сборки. TeamCity делает подстановку параметра через %param_name% в полях, отмеченных специальным значком |
Итак, мы поняли, чем параметры отличаются друг от друга: системные передаются автоматически в систему сборки (gradle, например), параметры конфигурации можно только считывать, обратившись к ним по ссылке, а параметры окружения добавляются в окружение процесса и доступны на всех шагах сборки.
Теперь поговорим о создании параметров.
Создание параметров
Здесь все очень просто:
Нажимаем на Add new parameter на вкладке с параметрами. В появившемся окне видим несколько полей, которые необходимо заполнить:
Name | Имя параметра |
Kind | Тип параметра (environment, system, configuration) |
Value | Значение параметра, в нем можно ссылаться на другие параметры в формате %parameter_name% |
Spec | Самое интересное поле.В нем можно настроить тип параметра, его видимость, ограничить его редактирование, добавить к нему описание и сделать его обязательным для указания |
Parameter spec
Здесь мы можем настроить следующее:
Label | Метка параметра, которая поясняет, зачем этот параметр вообще нужен |
Description | Описание параметра, которое будет видно при мануальном запуске сборки |
Display | Normal, Hidden (его не будет видно при мануальном запуске сборки), Prompt — этот параметр обязательно нужно будет указывать при запуске сборки |
Read-only | Параметр нельзя будет изменять в течение сборки |
Type | Интересное поле: можно указать password — и тогда значение поля будет скрыто от всех пользователей. Полезно для всяких паролей и хуков |
Build Configurations
Мы разобрались, что такое проект, как его «соединять» с системой контроля версий, как добавлять параметры в сборку. И теперь, думаю, можно обсудить самое важное: как эту сборку вообще конфигурировать и запускать?
Итак, конфигурация сборки — это набор правил, которые определяют сценарий сборки. Каждая сборка, которая была выполнена по заданному сценарию, может иметь два статуса: Success либо Failed.
Правила, по которым сборка заканчивается с ошибкой, можно настроить — об этом мы поговорим позже. Все сборки в заданной конфигурации попадают в Build Configuration Home, где можно отслеживать их статус.
Как обычно это происходит в TeamCity — все нужно настраивать, и конфигурации сборки не являются исключением. Нажимаем на Edit Configuration в правом верхнем углу и видим список настроек:
Давайте подробнее остановимся на самых важных пунктах.
General Settings
Здесь мы видим список обязательных для заполнения полей, а именно:
Name | Уникальное имя конфигурации (например, Development, Upload To Testers, Nightly Builds) |
Build Configuration ID | Уникальный ID конфигурации, который можно будет использовать в REST API |
Build number format | Каждой сборке TeamCity присваивает свой номер. В этом поле его можно отформатировать. Например, выводить не «1984», а «какая-нибудь-строка-1984» |
Artifact paths | После каждой сборки TeamCity может оставлять артефакты (подробнее о них расскажу позже), здесь мы прописываем пути к ним. Например, все, что будет лежать в */build/reports/, TeamCity заберет к себе и сохранит в конфигурации в качестве артефакта. А все, что лежит в rko/build/outputs/apk, TeamCity поместит в отдельную папку apk и также отдаст нам ее содержимое в качестве артефакта |
Version Control Settings
Конфигурации сборки тоже нужно «соединить» с системой контроля версий. Здесь все происходит по тем же правилам, что и в проекте.
На скриншоте видно, что VCS Root был унаследован (inherited) у того, что мы создали в настройках проекта (Mobile Bank For Business), к которому относится данная конфигурация.
Таким образом, эта конфигурация может взаимодействовать с веткой dev.
Build Steps
Вот мы и подобрались до самой интересной и важной части всех конфигураций сборки — к месту, в котором поочередно выполняются все нужные нам вещи (сборки приложений, прогон lint-проверок, прогоны тестов и так далее), — шагам сборки (build steps).
Итак, каждый шаг сборки представлен в виде раннера сборки (build runner), который предоставляет TeamCity интеграцию с инструментом для сборки (build tool), например с Gradle, MSBuild, командной строкой и так далее. То есть, по сути, каждый шаг сборки позволяет запускать .sh-скрипты (если в качестве раннера сборки была выбрана командная строка), Gradle-таски, Bazel-таски, .py скрипты и так далее.
Настройки каждого шага сборки индивидуальны в зависимости от раннера, поэтому давайте выберем самый популярный раннер — командную строку — и посмотрим, как создать с ним шаг сборки.
Нажимаем Add build step на вкладке Build Steps.
Далее видим следующую страницу:
В качестве Runner type выбираем Command Line:
Здесь мы видим, что нам нужно указать Step name — имя шага сборки, благодаря которому мы будем понимать, на каком этапе находится сборка. Давайте назовем его Hello, World.
Далее мы видим Execute step — это поле позволяет настроить условия, при которых данный шаг будет выполнен. По умолчанию там стоит If all previous steps finished successfully, однако нам есть из чего выбрать:
Далее мы видим настройку Run, в которой можно выбрать либо Custom Script (туда прямо можно запихнуть код на bash), либо Executable with parameters, сказав TeamCity, что мы хотим запустить скрипт, который уже лежит где-то в проекте, который он зачекаутил.
Давайте выберем Custom Script, а внутрь поместим следующее:
#!/bin/bash
echo "Hello, World!"
Нажимаем Save и видим, что наш шаг сборки сохранился. Теперь каждый раз, когда мы будем запускать конфигурацию сборки, в ее логах мы будем наблюдать милое “Hello, world!”, полученное из .sh-скрипта.
С другими типами раннеров все примерно так же.
Передача параметров в шаги сборки
Давайте создадим какой-нибудь параметр внутри конфигурации, которую мы рассматривали ранее, и попробуем передать его в шаг сборки. Открываем вкладку Parameters в настройках конфигурации, нажимаем Add new parameter и заполняем поля:
Так как мы не собираемся его нигде менять и запихивать в окружение, обозначим, что это параметр конфигурации, и назовем его cool_parameter.
Нажимаем Save, идем на вкладку Build Steps и нажимаем на шаг сборки Hello, World, который создали до этого, и добавляем в скрипт следующее:
#!/bin/bash
echo "Hello, World, %cool_parameter%"
Завернув название параметра в “%”, TeamCity сможет обращаться к нему и считывать.
Артефакты
Еще одна интересная фича в TeamCity — артефакты. Это все те файлы, которые были получены в результате сборки: файлы с логами, WAR-файлы и так далее.
Ранее, настраивая конфигурацию сборки, мы уже видели поле, в котором нужно было прописать путь к артефактам. Давайте остановимся на них подробнее.
Представим такой кейс: в проекте есть некий скрипт, который прогоняет ряд lint-проверок. Результаты этих проверок сохраняются в какой-нибудь файл прямо в проекте, допустим в ../build/outputs/lint.
Мы не хотим прогонять lint вручную, потому что он занимает много времени, и решаем автоматизировать его запуск. Допустим, у нас уже создан проект и конфигурация сборки.
Открываем General Settings проекта и находим поле Artifact paths. Прописываем там следующее:
Таким образом, все, что будет лежать в папке lint, TeamCity добавит в артефакты сборки, которые будут доступны после того, как она завершится.
Далее нужно добавить шаг сборки, который будет прогонять lint. Настройки этого шага зависят от раннера.
Agent Requirements
Еще одна важная настройка каждой конфигурации — Agent Requirements.
С ее помощью мы можем сказать TeamCity, на каких агентах он может запускать конфигурацию. Например, чтобы наш агент мог собирать Android-проекты, мы можем потребовать, чтобы в переменных окружения этого агента обязательно был указан ANDROID_HOME.
Или, чтобы запускать bash-скрипты, мы можем потребовать, чтобы ОС агента была только Linux. Agent Requirements можно наследовать из проекта или для каждой конфигурации создавать свои.
Промежуточные итоги
Итак, давайте закрепим несколько важных моментов, которые мы только что прошли:
TeamCity состоит из проектов, каждый из которых может содержать в себе вложенные проекты или конфигурации сборки.
Если мы хотим что-то запустить (линт-проверки, тесты, какие-нибудь скрипты), нужно создать конфигурацию сборки.
Каждая конфигурация состоит из шагов сборки (build steps).
Шаги сборки настраиваются с помощью раннеров сборки (build runners).
Взаимодействие с системой контроля версий происходит через VCS roots.
Информацию об окружающем мире TeamCity забирает из параметров.
Каждая сборка может оставлять после себя артефакты.
О Pipeline и Build Chains
Давайте теперь затронем несколько более сложные и важные вещи, освоив которые можно самостоятельно настраивать взаимодействие конфигураций сборки между разными проектами, а также работать с артефактами.
Итак, конфигурация сборки состоит из шагов сборки (build steps), которые выполняются в рамках одного билда. Ты можешь добавить в одну конфигурацию сколько угодно шагов, но это плохая практика. Пихать большое количество шагов в одну конфигурацию — большая ошибка, и если ты все-таки ее совершил, имеет смысл задуматься: может, одна конфигурация берет на себя слишком много всего?
Но что делать, если ты понял, что шагов действительно много, причем некоторые из них независимы друг от друга и могут запускаться параллельно, что существенно ускорило бы твой билд? К счастью, для решения этой проблемы TeamCity дает нам простой механизм прямо «из коробки» — build chains!
Но прежде чем я расскажу про этот механизм, давай введем несколько других определений, которые помогут погрузиться в тему.
Pipeline
Здесь все довольно просто. Пайплайн — это некоторый (последовательный) набор действий, совершаемых во время сборки. И хотя пайплайн — это классическое определение для процессов, происходящих на CI/CD, нигде — ни в документации TeamCity, ни в веб-интерфейсе — ты не встретишь это слово.
Дело в том, что у TC есть собственный термин — build chain, цепочка сборки. Цепочка сборки в TeamCity представляет собой направленный ациклический граф, поэтому пайплайн, подразумевающий последовательное выполнение шагов, — лишь частный случай цепочки сборки из TC.
Но кроме того, что шаги в цепочках сборки не всегда могут быть последовательными, есть и другие отличия от стандартного пайплайна, о которых расскажу чуть позже.
Snapshot Dependencies
Еще один термин, который я введу, — снепшот-зависимость. Это как раз то, что позволяет нам связать между собой две конфигурации.
Устанавливая снепшот-зависимость для какой-нибудь сборки (например, сборки B) от другой сборки (сборки A), ты можешь быть уверен в том, что сборка B запустится только после того, как будет запущена и завершена та сборка, от которой она зависит (сборка А).
Простыми словами: если в сборку B добавить снепшот-зависимость на сборку A, то B будет запускаться только после того, как запустится и завершится сборка А. Сборка B в этом случае называется зависимой.
Обрати также внимание на слово «снепшот» — это про синхронизацию исходников. При использовании таких зависимостей все они будут запускаться на одной ревизии VCS root (или на ревизиях, сделанных в одно время в разных VCS).
Snapshot dependencies формируют тот самый ациклический граф, о котором мы говорили ранее, а это не что иное, как цепочка сборки.
Build Chains
Наконец-то мы добрались до самих цепочек сборки — build chains.
Как я уже говорил, build chains позволяет разносить шаги сборки по разным конфигурациям, связывая их при помощи снепшот-зависимостей.
То, как TeamCity работает с цепочками сборки, открывает для нас несколько интересных фичей: параллельная сборка конфигураций (круто, да?), переиспользование результатов сборки, синхронизация конфигураций между разными репозиториями. Но самое главное: build chains существенно облегчают поддержку и развитие конфигураций.
Попрактикуемся?
Чтобы создать цепочку сборки, достаточно выбрать две конфигурации сборки и настроить между ними снепшот-зависимость. В качестве примера возьмем две конфигурации:
Одна будет собирать релизный apk-файл с android-приложением мобильного банка для юридических лиц.
Вторая будет отправлять этот apk на сервер Bishop — сервиса для проверки android-приложений на наличие уязвимостей.
Итак, открываем конфигурацию сборки Bishop-scan:
Переходим на вкладку Dependencies и нажимаем кнопку Add new snapshot dependency:
Как видим, кроме snapshot dependency можно добавить artifact dependency, но об этом мы поговорим несколько позже.
В появившемся окне нам предстоит настроить зависимость, указав несколько настроек:
Depend on | Выбираем ту конфигурацию, от которой будет зависеть текущая |
Enforce revisions synchronization | Если включить эту опцию, все конфигурации будут работать, используя одни и те же сорсы, и никакие изменения в репозитории, возникшие на момент запуска зависимого билда, не будут браться в расчет |
Do not run build if there is a suitable one | С этой опцией TeamCity не будет запускать зависимую сборку, если существует другая запущенная или завершенная подходящая (suitable) сборка. О том, что такое suitable build, мы поговорим чуть позже |
Run build on the same agent | TeamCity будет собирать зависимую сборку на том же агенте, на котором собиралась основная сборка |
Only use successful builds from suitable ones | Из suitable-билдов будут использоваться только те, которые были завершены без ошибок |
On failed dependency/ On failed to start/canceled dependency | Здесь указываем, что делать, если зависимый билд упал: игнорировать, сообщать об ошибке, отменять билд |
Нажимаем Save и вуаля — мы успешно построили свой первый build chain!
Полный граф цепочки сборки можно посмотреть, нажав на build chain тут:
Откроется отдельная вкладка, которая визуализирует для нас этот граф:
По графу видно, что перед тем, как мы запустим Bishop-scan, сначала будет собран Development, а только потом запустится Bishop-scan. Круто? Конечно!
Но вот проблема: Development собирает для нас артефакт. Как его использовать в Bishop-scan?
Artifact Dependencies
Все просто: кроме зависимости от конфигурации сборки мы можем зависеть и от артефактов, которые конфигурация сборки генерирует.
Конкретно в моем примере мне нужно будет забрать apk-файл, который был собран при сборке Development. Для этого снова идем во вкладку Dependencies конфигурации Bishop-scan:
Нажимаем на Add new artifact dependency:
В появившемся окне указываем следующие настройки:
Depend on | У какой конфигурации мы будем забирать артефакт? |
Get artifacts from | Откуда будем брать этот артефакт? Можем выбрать несколько вариантов: из этой же цепочки (тогда ждем, когда зависимый билд соберется); из последней успешной сборки, из последней сборки, из последней закрепленной сборки и из сборки с определенным номером |
Artifact rules | Путь к артефакту. Очень удобно его указать, нажав на «веточку» справа. После «→» мы указали путь к environment-переменной, в которую положили этот apk |
Стоит также обратить внимание на то, что зависимая конфигурация сборки (Development) должна объявить у себя в настройках, что этот артефакт она отдает.
Для этого идем в настройки Development и в графе Artifact Paths указываем путь к этому артефакту:
Теперь все, что лежало в rko/build/outputs/apk, будет перемещено в папку apk в артефактах, сгенерированных этой конфигурацией.
И все! Мы успешно связали между собой две конфигурации.
Теперь перед каждым запуском Bishop-scan сначала будет собираться Development, а только потом — Bishop-scan, использующий артефакт, полученный на предыдущем шаге.
Но что, если мы хотим как-то по-особому настроить правила запуска конфигурации в цепочке сборки?
Build Triggers
Снова открываем настройки конфигурации Bishop-scan и нажимаем на вкладку Triggers:
Здесь нажимаем Add new trigger и в появившемся окне видим следующее:
VCS Trigger | Запускаем билд только тогда, когда TeamCity обнаружил какое-то изменения в содержимом VCR Root'а |
Schedule Trigger | Запускаем билд точно по расписанию (можно указать любые промежутки времени между билдами) |
Finish Build Trigger | Запускаем билд только тогда, когда закончился билд какой-то определенной конфигурации (по сути, это частный случай нашей цепочки сборки, состоящей из двух конфигураций) |
Допустим, мы хотим, чтобы сборка в Bishop отсылалась всегда, когда в VCS Root'е Development произошло какое-либо изменение. Для этого выбираем VCS Trigger и в появившемся окне нажимаем на Trigger a build on changes in snapshot dependencies:
В нашем примере две конфигурации сборки, однако понадобится лишь один VCS Trigger. Все потому, что мы можем сказать триггеру, чтобы он отслеживал изменения в каждой из конфигураций, когда находится в цепочке сборки.
Таким образом, если хотя бы в одной из них происходит изменение, TeamCity запускает целую цепочку. Ставить триггер нужно на последнюю конфигурацию цепочки, и тогда ТС сам запустит все билды из нее.
Checkout Rules
Вообще, существует довольно много способов оптимизировать пайплайн. Один из них — настроить правила, по которым TeamCity скачивает репозиторий.
Вернемся к предыдущему примеру: мы сделали так, чтобы любое изменение в Development триггерило Bishop-scan. Но что, если мы не хотим, чтобы изменение в каком-нибудь .gradle-файле влекло за собой полную сборку .apk и анализ ее на серверах Bishop?
Для этого можно правильно настроить Checkout Rules в конфигурации Development.
Открываем Version Control Settings и жмем на Edit checkout rules:
В появившемся окне мы можем, например, исключить папку gradle, в которой лежат все наши .gradle файлы:
Для этого достаточно прописать -:gradle в правилах, и папка gradle будет игнорироваться при мониторинге VCS Root'а.
Переиспользование результатов сборки
Еще один способ оптимизировать пайплайн — переиспользование артефактов. Допустим, цепочка сборки успешно завершилась и мы запустили ее снова (или она сама запустилась по какой-либо причине), но на этот раз TeamCity будет немного хитрее.
Что, если мы не будем пересобирать конфигурацию зависимости в цепочки, если никаких изменений в кодовой базе не было? Действительно, возвращаясь к нашему примеру, вряд ли нужно каждый раз собирать apk на Development, если на самом деле там ничего не изменилось.
К счастью, чтобы все работало именно так, не нужно ничего делать: все работает прямо «из коробки». Но мы можем ее отключить.
Заходим в Build Configuration Home нашего Bishop-scan:
Нажимаем на многоточие, которое находится рядом с Run. В появившемся окне переходим на вкладку Dependencies, нас интересует настройка Rebuild snapshot dependencies:
Нажимаем Run Build, и вуаля — теперь TC заново соберет артефакты в цепочке, даже если не было никаких изменений в кодовой базе.
Параллельный запуск конфигураций
И вот мы добрались до самой интересной и важной фичи, которую даруют нам цепочки сборки, — параллельный запуск.
Допустим, у нас в пайплайне добавилось еще две конфигурации, которые совсем не зависят друг от друга, а значит, могут выполняться параллельно. Обе эти конфигурации в зависимостях имеют снепшот-зависимость на Bishop-scan.
Помните, я говорил, что build chain — ациклический граф?
Так вот, сейчас самое время подумать, почему цепочки были спроектированы именно так: независимые ноды графа могут обрабатываться параллельно при наличии ресурсов для их обработки (то есть билд-агентов).
TeamCity сам позаботится о параллельной сборке независимых конфигураций и настраивать руками ничего не нужно.
Но что теперь делать с триггером сборки? В прошлый раз мы выставляли его на Bishop-scan, а сейчас что, придется добавить еще два — на каждую из новых конфигураций?
Можно, но есть более удобное решение: Composite Build.
Composite Build
Еще одна классная фича в TC — композитная конфигурация сборки.
Ее задача заключается в том, чтобы агрегировать результаты сборки других конфигураций, от которых она зависит. А самое крутое, что, создавая такую конфигурацию, нам не нужно отводить место под еще один агент сборки — он просто не нужен.
Создание такой конфигурации отличается от обычной лишь одной настройкой:
В поле Build configuration type необходимо выбрать Composite, а далее просто добавить снепшот-зависимости на наши новые конфигурации.
Далее добавляем две конфигурации в зависимости нашего композитного билда:
Добавляем Generate Build Number и Bishop-scan в зависимости и смотрим итоговый граф:
Вуаля! Теперь эта конфигурация будет частью нашей цепочки сборки, и мы сможем настроить лишь один триггер — на нее.
Заключение
Итак, мы с ознакомились с основными вещами, с которыми сталкивается каждый разработчик, настраивая CI/CD в своей команде:
Настройка проектов
Настройка конфигураций сборки
Взаимодействие конфигураций сборки между собой
Параллельный запуск конфигураций сборки
Надеюсь, статья окажется вам полезной!
Кстати, недавно у команды Тинькофф.Бизнес появился свой Telegram-канал, в котором мы рассказываем о наших процессах и технических решениях, подписывайтесь!