Pull to refresh

«Нюансы» использования TeamCity

Build automationDevOps

Картинка


Всем привет.


Статья написана в простом стиле "DevOps для домохозяек" от таких же домохозяек. В ней будет описано с какими неожиданностями можно столкнуться при настройке проекта в TeamCity. Также приведу рекомендации как эти проблемы можно обойти.


Нижеописанное основано на моём двухлетнем опыте настройке TeamCity сборок, чтению баг репортов и обмене мнений с коллегами по цеху. Не претендую на истину в последней инстанции, так как в работе в основном использовался подход SDD (Stackoverflow Driven Development).


Небольшая справка:


  • TeamCity — CI (Continous Integration) инструмент. "Аналог" Gitlab CI, Github Actions с прицелом на возможность полной настройки автоматизации из графического интерфейса.
  • Проект (Project) — агрегирующая сущность, в неё связываются несколько сборок. В TeamCity древовидная структура проект->подпроект->сборка с частичным наследованием настроек.
  • Сборка (Build) — Атомарная сущность автоматизации. Примеры сборок "Запуск автотестов", "Установка конфигурации", "Сборка дистрибутива". Каждая сборка состоит из нескольких шагов.
  • Шаг (Build step) — Описание "а что нужно делать" используя разные "runner type". Это может быть как простой Bash скрипт, так и запуск Docker контейнера.

Вкратце по проекту TeamCity с которым я работаю:


  • ~30 сборок, шаги сборок состоят из вызовов Bash, Ansible и Python.
  • Никаких сборок Android приложений, Web проектов, Docker, k8s и прочего. Просто заказ облачных серверов, подъем базы данных из дампа, установка программ и конфигурации.
  • Настройка ведётся из графического интерфейса, без Kotlin DSL (переход на него в планах).

Про достоинства инструмента я думаю можно почитать в официальной документации и маркетинговых материалах, нет смысла их здесь повторять. Перейдём сразу к "нюансам".


1 Нельзя изменить параметры сборки при запуске по триггеру


Сборки можно запускать по триггеру (внешнему событию). Триггеры могут быть разные: была завершена другая сборка, обновилась git ветка, cron задача. При этом в сборке можно задать параметры по умолчанию.


Так вот: запуск по триггеру можно сделать только с параметрами по умолчанию. На эту проблему есть нерешённая задача 2008 года.


Опишу простой пример, на котором можно понять масштаб проблемы: у нас есть сборка по поднятию стенда и сборка по запуску автотестов на стенде. Вторая сборка получает на вход имя стенда и прогоняет автотесты. Первую сборку нужно запустить 1 раз, чтобы получить 1 стенд. Вторую сборку нужно запускать часто, чтобы понимать в каком состоянии стенд. Ииии такую простую штуку нельзя сделать.


Но мне возразят, что нужно использовать build chain (цепочку сборок). Окей, давайте посмотрим что там не так.


2 Build chain or not — просто скопируй ещё сборки


В случае вышеописанного кейса (заказ стенда + прогон автотестов) мы настраиваем build chain. В таком случае если нам нужен стенд, то мы его получаем из сборки запуска автотестов. Интуитивно понятно, не правда ли?


Но тут приходит другая проблема: а как ещё раз запустить сборку автотестов (мы же её запускаем периодически для каждого стенда). Иииии никак. Ну точнее можно, но тогда будет заказан ещё один стенд (если у вас не решён полностью вопрос с идемпотентностью). Или нужно указать во вкладке зависимостей, что не запускай зависимую сборку ещё раз.


Но как тогда брать имя стенда, на котором запускать автотесты? Если у нас две сборки отдельных, то мы указываем имя в параметрах. Если цепочка сборок — то проще всего использовать артефакт. А теперь если мы желаем запускать и отдельно, и вместе, то нужен специальный огород, который будет это всё обрабатывать. Осталось только представить, как будет "интересно" настраивать такой огород, когда у нас появится две разные сборки на заказ стенда (заказ идёт в разных облаках, разные программы), а сборка с запуском автотестов будет одна.


К сожалению, я не смог найти "пруфов" решения, которое предлагают разработчики, но если мне не изменяет память оно было такое: для каждого случая создавайте отдельную сборку. По мне это решение приводит к дублированию кода и больше похоже на костыль. Итого можно констатировать: нельзя сделать так, чтобы одну сборку можно было запустить и в цепочке и отдельно.


3 Переопределение параметров зависимой сборки


Рассмотрим другой пример: у нас есть три зависимые последовательные сборки, которые связаны в build chain. Скажем заказ в облаке стенда, установка дампа базы данных и установка программ. Мы "интуитивно" запускаем сборку по установке программ и возникает вопрос: а как пробросить размер заказываемой машины в зависимой сборке? И тут нам на помощь приходит переопределение параметров.


Теперь у нас в одном диалоговом окне по запуску сборке возникает 3*N параметров, которые никак не отличаются друг от друга. Ещё стоит учитывать, что описание и параметры по умолчанию таких параметров не копируются, их нужно копировать отдельно. Особенно это "радует", когда эти описания и значения меняются. Их тогда нужно будет обновлять в N местах, если сборки можно вызывать из разных мест как в нашем случае. Например, нужен человеку только стенд с дампом базы данных, он тогда вправе заказать его со второй сборки. А там в параметрах версия дампа устаревшая, в отличие от последней сборки цепочки, и пойдёт разбор полётов на полдня почему дамп кривой.


И конечно тут не будет никакой валидации, что вы не ошиблись в имени переопределяемого параметра, всё в лучших традициях YAML Developer'ов.


4 Конфигурация сборки может быть только одна


Проблема не относится к Kotlin DSL (у меня нет опыта его использования, не могу сказать насколько эта проблема действительно решается). Если мы настраиваем сборки в графическом интерфейсе как настоящие домохозяйки, то сталкиваемся со следующей проблемой: а как плавно поменять настройки проекта, так чтобы это не коснулось пользователей?


Первый и самый простой вариант: объявить технологические работы и править "на горячую". Второй вариант: скопировать сборку в отдельное место и делать изменения в ней (наш вариант).


Потом нужно после внесения изменения в основную ветку кода и в сборке как-то проинформировать всех пользователей, чтобы они обновили свою ветку кода. В общем не будьте как мы — используйте Kotlin DSL.


Рекомендация: не архивируйте/удаляйте старые сборки, а обновляете их после внесения изменений в основную ветку кода. Всегда найдутся люди, у которых ссылки на сборки хранятся где-то в закладках и если вы поменяется ID сборки, то все ссылки побьются.


5 TeamCity API


Я думаю многие здесь знакомы с "главными" 4 метриками DevOps. И TeamCity кажется идеальным местом, чтобы собрать всю информацию хотя бы о половине из них ("Deployment Frequency" и "Lead Time for Changes").


Иии вот нельзя в API быстро узнать процент упавших сборок, частоту запуска и причины падения. Да, там есть какие-то дашборды, но информация в них именно та, которая не нужна. То есть вот начали у нас падать все сборки на основной ветке, и мы можем только вручную "Assign investigation", либо придумывать как реализовывать такой инструмент в каждой сборке. Не очень удобно.


Однако, в чём хорош API — так это в формировании build chain из-за вышеперечисленных недостатков с "нативным" способом. В таком случае можно сборки создавать для независимого запуска. А их связывание делать в отдельной сборке с запуском тупого Python скрипта. И много кто так обходит проблему.


6 При запуске Bash скрипта проверяется только результат последней команды


Есть у нас простой скрипт, написанный в интерфейсе:


./command_1.sh # always fail

ls # always success

В таком случае этот шаг сборки будет всегда зелёный. Но если мы добавим мантру:


./command_1.sh # always fail

if [ $? -ne 0 ]; then
  echo "##teamcity[buildProblem description='Build failed']"
fi

ls # always success

И тогда уже шаг будет красным, и сборка дальше не пойдёт (тут уже как выставлен "Execute step"). Иными словами, всегда нужно учитывать особенность работы Bash скриптов.


7 Работа с шифрованными параметрами сборки


В TeamCity можно добавить параметры-секреты, такие как пароли и API ключи. Чтобы такие данные не утекли, в логах сборки ищутся значения таких параметров и заменяются на символы *. И возникает следующий нюанс: а если нам эти параметры нужно записать в другой файл. Команда echo в таком случае не сработает — фильтр перехватит. В итоге мы пришли к следующему варианту:


cat > constants.json <<- EOM
{
    "key": "%value%"
}
EOM

Задача, в которой это понадобилась, была следующая. Есть Python скрипт, который выполняет запросы к нескольким системам, и вся его конфигурация сохранена в JSON. Как к нему подать секреты? Можно через командную строку, но тогда у нас растекается конфигурация сразу по четырём местам: JSON, командная строка, значение по умолчанию в скрипте и ещё значение по умолчанию в параметре TeamCity. Поскольку скрипт должен быть тупым и одноразовым, то решили максимально упростить: всю конфигурацию втащить в JSON. JSON прям с вписанными именами параметров TeamCity сохраняем в репозиторий и копируем как есть в шаг сборки. В итоге мы сразу формируем нужный JSON и не разбираемся какой параметр, где нужен.


8 Скорость прогрузки графического интерфейса


Это будет очень субъективный пункт. Поскольку основная работа по настройке проводится в интерфейсе (для тех кто не пользуется связкой Kotlin DSL + TeamCity API), то производительность работы пропорционально скорости работы этого интерфейса. А он ооочень медленный. Я постарался в меру своих интеллектуальных способностей замерить скорость прогрузки и вот какие цифры получил (был использован браузер Firefox и инструмент Network).


  • Загрузка окно проекта со всеми сборками и вызов окна запуска сборки
    • load: 9.87 s
    • DOMContentLoaded: 4.92 s
    • Finish: 34.39 s
    • Size/transferred size of all requests: 10.69 MB / 2.42 MB
    • Requests: 345
  • Загрузка окна отдельной сборки и вызов окна запуска сборки
    • load: 4.59 s
    • DOMContentLoaded: 1.27 s
    • Finish: 27.42 s
    • Size/transferred size of all requests: 11.53 MB / 2.23 MB
    • Requests: 120

Время Finish — до момента как у меня появляется возможность запустить сборку. Так как после этого ещё в фоне прогружается страница и идут запросы. Полминуты чтобы просто запустить одну сборку, неплохо?


9 Информация по упавшей сборке


Это будет ещё один субъективный пункт. Для каждой сборки есть вкладка Overview. В ней в случае падения сборки указывается ошибка. И эта ошибка в 99% случаев определяется неправильно. В нашем случае (может у кого по-другому) — ошибка определяется как "первое, что попало в stderr", хотя было бы логичнее сделать "последнее, что попало в stderr". В случае Ansible это всегда будет какой-нибудь "WARNING: Deprecation setting...". И в итоге это вызывает стабильный поток вопросов у людей слабо знакомых с TeamCity. Пусть лучше вообще там ничего не писалось.


10 Вечные проблемы с агентами


Здесь я бы хотел написать про все проблемы, которые связанные с агентами сборки (Build agents). Как известно в TeamCity есть master сервер, который выполняет роль планировщика, а сами сборки запускаются на отдельных серверах. И нужно уметь правильно с этими агентами работать (обслуживать), TeamCity тут мало что может сделать.


Первая проблема — удаление агентов. У агентов чаще всего есть какой-то срок жизни, их лучше не делать долгоживущими, иначе потом появляется дрейф конфигурации. Например, кто-то поменял JAVA_HOME и понеслось. И вот это удаление может прилетать вообще неожиданно. Планировщик выдал агента для тяжеловесной ночной десятичасовой сборки. Но ночью чаще всего проводятся все работы по обслуживанию и какой-то сервис, не проверив что там с агентом просто его грохает. Мы так и не победили эту проблему (администрированием TeamCity занимается отдельная "команда").


Вторая проблема — закончившееся место на агенте, либо неработающая базовая утилита. Пересекается с первым пунктом, но тут "веселее". Запускаем сборку, на самом первом шаге это всё падает и агент снова бодро готов браться за новую работу. Мы раздражённо запускаем сборку заново и по великому везению попадается тот самый агент и по новой. "Но у нас же есть великая настройка по выбору агента!" — скажете вы и будете правы. Только один нюанс: а если у нас build chain? Тутуту руру тутуту, а оказывается, что выбрать агент можно только на текущую сборку, на зависимые может браться какой угодно и мы знаем какой будет выбран по закону подлости. Но это в случае, если вы не выставили при установке зависимой сборки "Run build on the same agent". Но вы же выставили, правда?


Третья проблема — дрейф конфигурации на агентах. На агентах должно быть неизменяемое окружение (то, что не должно быть root прав, я думаю объяснять не надо). Кто-то поменял переменную окружения, поменял локальные пути до утилит и понеслось. Начинаешь после каждого такого случая перестраховываться и потом у тебя 90% сборки — это подготовка агента к твоему print("Hello, World!").


И конечно зайти на агент и интерактивно разбираться в чём там проблема не получится (в моём конкретном случае). Ещё лучше, когда на локальном линуксе всё отрабатывает. В общем "кто дебажил непонятную проблему на билд агентах — тот в цирке не смеётся".

Only registered users can participate in poll. Log in, please.
Хотелось бы узнать есть ли у кого ещё проблемы с использованием CI инструментов по похожим проблемам (или есть ещё интереснее случаи)
29.31% Использую другой CI и не страдаю 17
3.45% Использую другой CI и страдаю ещё больше 2
18.97% Использую TeamCity и столкнулся с такими же проблемами 11
37.93% Использую TeamCity, полёт нормальный 22
10.34% Какой ещё CI, собираем локально по инструкции в Word 6
58 users voted. 19 users abstained.
Tags:teamcityci
Hubs: Build automation DevOps
Total votes 14: ↑13 and ↓1 +12
Views3.8K

Popular right now

Основы DevOps
June 28, 20219,200 ₽Luxoft Training
DevOps Engineer
June 29, 202132,990 ₽Level UP
QA Automation
August 15, 2021799 $Careerist
UI-дизайнер
June 25, 202147,940 ₽Нетология

Top of the last 24 hours