Сейчас трудно представить разработку программного обеспечения без автоматизированных сборок проекта и тестирования. Для минимизации временных затрат на интеграцию изменений разработчиков в проект, существуют разные готовые решения. В данной статье я расскажу о замене сервера непрерывной интеграции CruiseControl.NET на Jenkins в команде разработчиков PVS-Studio. А также о том, что нас к этому побудило, какие цели мы преследовали и с какими проблемами столкнулись.
Введение
Непрерывная интеграция (англ. Continuous Integration, далее CI) — автоматизированный процесс сборки, развёртывания и тестирования разрабатываемого программного обеспечения. Эта практика разработки популярна как в больших коллективах, так и у индивидуальных разработчиков. Для подобной практики есть много готовых решений. В данной статье пойдёт речь о бесплатных проектах с открытым исходным кодом CruiseControl.NET и Jenkins.
CruiseControl.NET ( CCNet ) — инструмент для непрерывной интеграции программного обеспечения, реализованный на .NET Framework. Также существуют варианты инструмента на Java (CruiseControl) и версия для Ruby-сред (CruiseControl.rb). Управление и просмотр информации о сборках осуществляется через веб-интерфейс или desktop утилиту. Интегрируется с различными системами управления версиями. Является проектом с открытым исходным кодом и, к сожалению, не развивается примерно с 2013 года.
Jenkins — инструмент для непрерывной интеграции с открытым исходным кодом, написанный на Java. Был ответвлён от проекта Hudson после спора с Oracle. Обеспечивая функции непрерывной интеграции, позволяет автоматизировать часть процесса разработки программного обеспечения, в котором не обязательно участие человека. Возможности Jenkins можно расширять с помощью плагинов. В данный момент проект активно развивается и поддерживается, и разработчиками, и сообществом.
Хоть статья немного и похожа на обзор в стиле «CCNet Vs. Jenkins», акцент сразу будет сделан на выборе в пользу сервера с Jenkins. Главной причиной смены инструмента непрерывной интеграции для нас является отсутствие развития у проекта CruiseControl.NET. В статье будут описаны и другие моменты работы с CCNet, с которыми возникали трудности.
Недавно у проекта PVS-Studio был юбилей 10 лет, о котором можно прочесть в статье "Как 10 лет назад начинался проект PVS-Studio". Больше половины времени существования продукта мы использовали CCNet. Его интерфейс, настройки и функции стали настолько привычными, что Jenkins казался крайне неудобным. Впервые мы стали его использовать, когда появился PVS-Studio для Linux. Переходу на Jenkins предшествовало длительное изучение этого инструмента. Часть времени мы потратили на поиск аналога привычных нам функций из CCNet. Далее будут описаны интересные моменты из проделанной работы.
Наши претензии к CCNet
- CCNet больше не развивается. Им можно до сих пор пользоваться, но расширять функционал и исправлять существующие/потенциальные ошибки придётся собственными силами.
- Нестабильно работает режим опроса SCM (Source Code Management) об изменениях, а именно для автоматического запуска при наличии изменений в системе контроля версий. При проблемах с сетью, в этом режиме проект получает статус «Failed», даже если не запускался. На практике проблема возникает так часто (к сожалению, в нашем офисе не самый стабильный доступ в интернет), что этим режимом становится невозможно пользоваться.
- При опросе SCM об изменениях, в случае наличия ошибки системы контроля версий (например, если из репозитория был удалён какой-нибудь каталог, прописанный в настройках и возник tree conflict), выполнение проекта сразу прерывается, сохраняя статус «Success» — проект перестаёт работать, но его статус в web интерфейсе и десктоп утилите остаётся «зелёным». В таком режиме запуск, например, тестов, может не выполняться неделями, и есть риск, что никто не обратит на это внимание, думая, что тесты успешно работают.
- Общий лог работы сервера слишком вербозный и неструктурированный: сложно понять, какой шаг сборки отвалился и найти лог именно для этого шага. При работе нескольких проектов параллельно, лог сборки «смешивается». XML лог сборки отдельного проекта доступен в web интерфейсе, но он наоборот зачастую недостаточно подробен и не содержит всех запускаемых команд.
- Неэффективное распараллеливание подзадач внутри проекта. Подзадачи запускаются параллельно группами по количеству ядер процессора. Если в группу попадают длительные и быстрые задачи, то новые задачи запускаться не будут, пока не завершатся все задачи из предыдущего запуска.
Сравнение сценариев использования
Настройки сервера
Настройки проектов CCNet (конфигурация сервера) хранились в одном xml файле, а различные пароли в другом. Хоть файл настроек и достиг размера ~4500 строк, пользоваться им было довольно удобно. Лёгким нажатием Alt+2 в Notepad++ список всех проектов можно свернуть и редактировать нужный (рисунок 1).
Рисунок 1 — Редактирование CCNet настроек в Notepad++
Хоть файл и содержал дублирующийся код, при поддержке сервера особых затруднений не возникало.
Вот так заполнялся блок SCM:
<svn>
<username>&SVN_USERNAME;</username>
<password>&SVN_PASSWORD;</password>
<trunkUrl>&SVN_ROOT;...</trunkUrl>
<workingDirectory>&PROJECT_ROOT;...</workingDirectory>
<executable>&SVN_FOLDER;</executable>
<deleteObstructions>true</deleteObstructions>
<cleanUp>true</cleanUp>
<revert>true</revert>
<timeout units="minutes">30</timeout>
</svn>
Так заполнялся блок MSBuild:
<msbuild>
<description>PVS-Studio 2015</description>
<workingDirectory>&PROJECT_ROOT;...</workingDirectory>
<projectFile>...\PVS-Studio-vs2015.sln</projectFile>
<buildArgs>/p:Configuration=Release</buildArgs>
<targets>Build</targets>
<timeout>600</timeout>
<executable>&MSBUILD14_PATH;</executable>
</msbuild>
А так заполнялся блок для общих задач:
<exec>
<description>PVS-Studio 2015 sign</description>
<executable>&PROJECT_ROOT;...\SignToolWrapper.exe</executable>
<baseDirectory>&PROJECT_ROOT;...</baseDirectory>
<buildArgs>"&SIGNTOOL;" ... \PVS-Studio-vs2015.dll"</buildArgs>
<buildTimeoutSeconds>600</buildTimeoutSeconds>
</exec>
На основе такого проектного файла CCNet потом удобно отображает все выполняемые шаги (правда, только в десктопной tray утилите. Web интерфейс это почему-то не поддерживал). В Jenkins с «высокоуровневым» отображением этапов прохождения интеграционного проекта пришлось повозиться, но об будет рассказано позже.
Для Jenkins необходимо хранить достаточно много файлов настроек: конфиг сервера, файлы настроек некоторых плагинов, каждый проект имеет свой собственный файл конфигурации. Хоть все эти файлы заданы в формате xml, для просмотра и редактирования они не очень удобны (по крайней мере по сравнению с CCNet), т.к. все команды внутри тэгов прописаны сплошным текстом. Правда, в большей степени это связано с идеологией использования инструмента. В CCNet конфиг пишется вручную, и поэтому, может быть «красиво» отформатирован. Jenkins же предполагает редактирование настроек проекта через свой web интерфейс, а конфиги генерирует автоматически.
Примерно так выглядят команды в конфигах Jenkins'а:
<hudson.tasks.BatchFile>
<command>CD "%BUILD_FOLDERS%\Builder"
PVS-Studio_setup.exe /VERYSILENT /SUPPRESSMSGBOXES ...
Publisher_setup.exe /VERYSILENT /SUPPRESSMSGBOXES</command>
</hudson.tasks.BatchFile>
И это ещё очень маленький пример.
Просмотр статусов задач
Как я писал ранее, в CCNet проекты заполняются Task блоками. Вот так выглядит успешно выполненная задача с отображением шагов (рисунок 2).
Рисунок 2 — Просмотр статуса задачи в CCTray (desktop клиент для CCNet)
Ошибку в любом из блоков хорошо видно в иерархии подзадач. Это очень удобная и наглядная визуализация интеграционного процесса. Почти никогда не было необходимости искать логи, по описанию задачи было сразу понятно, что стоит проверить на локальном компьютере. Прямо в таком виде в Jenkins ничего не нашлось, поэтому пришлось подробно изучить этот момент перед переездом на новый сервер.
Можно провести такую аналогию между CCNet и Jenkins: в CCNet есть проект (Project), шагом в этом проекте является Task (как видно на рисунке выше). В Jenkins также есть проект (Job), а его шагами являются Step'ы (рисунок 3).
Рисунок 3 — Соответствие именований проектов в CCNet и Jenkins
К сожалению, web интерфейс Jenkins'а не умеет визуализировать работу отдельных шагов — у Job'а есть только полный консольный лог всех шагов вместе. Большим неудобством здесь является то, что из Jenkins невозможно посмотреть, какой из шагов завершился с ошибкой — нужно смотреть полный лог сборки Job'а. И так как к хорошему быстро привыкаешь, от старого сценария использования отказываться не хотелось. Тогда на помощь к нам пришёл Multijob Plugin.
Этот плагин позволил нам внести следующие нововведения:
1. Использование Job'ов в качестве Step'ов в других Job'ах. Таким образом появились универсальные Job'ы, которые позволили отделить лог конкретных подзадач и, самое главное, отдельно просматривать статусы конкретных подзадач. Web интерфейс Jenkins умеет хорошо визуализировать выполнение отдельных Job'ов в рамках Multijob'а — как раз то, что мы искали. На рисунке 4 показан пример выполненного Multijob'а.
Рисунок 4 — Просмотр выполненного Multijob'а
2. С использованием универсальных Job'ов удалось избавиться от дублирующего кода. Например, есть компиляция какой-нибудь утилиты: для дистрибутива, для запуска тестов и для запуска анализа кода. В CCNet это были одинаковые Task блоки в 3-х разных проектах. В Jenkins для компиляции этой утилиты сделан Job, который используют несколько Multijob'ов.
3. При создании проектов в Jenkins используется следующая идеология. Все Job'ы мы разделяем на Multijob «проекты» и универсальные «шаги». Имена универсальных Job'ов имеют префикс «job_» и не подразумевают использование как самостоятельного проекта. Также они не содержат загрузку исходного кода из репозитория. Имена Multijob'ов имеют префикс «proj_» и включают в себя загрузку исходного кода, и запуск только других Job'ов. (Step'ы мы стараемся избегать, т.к. они не визуализируются).
Универсальные Job'ы запускаются со следующим параметром:
WORKSPACE=$WORKSPACE
Это означает, что Job будет запущен в рабочем каталоге Multijob'а.
Таким образом удаётся получить отдельно лог обновления исходных файлов и логи всех этапов сборки по отдельности. Следовать этой идеологии для всех проектов сложно и бессмысленно. Так сделано только для нескольких самых больших и важных проектов, которые необходимо подробно изучать при возникновении проблем.
4. В Multijob'е можно настроить условные и параллельные запуски Job'ов. Multijob'ы умеют запускать Multijob'ы по тем же правилам. Так можно объединять запуски проектов: например, запустить сборку всех инсталляторов или все тесты.
Просмотр логов сборки
В CCNet просматривать логи сборки проектов было крайне неудобно, т.к. они перемешивались с выводом сервера, и имели особую разметку. В Jenkins такой проблемы нет, и дополнительно появилась возможность разделять логи для подзадач в некоторых проектах.
Получение ревизии исходного кода
В Jenkins для каждой добавленной ссылки на SVN репозиторий определяется своя версия ревизии. Т.е. если добавить несколько каталогов, то номера могут сильно отличаться, а нужен один максимальный.
Согласно документации, работать с этим надо следующим образом:
If you have multiple modules checked out, use the svnversion command. If you have multiple modules checked out, you can use the svnversion command to get the revision information, or you can use the SVN_REVISION_<n> environment variables, where <n> is a 1-based index matching the locations configured.
Так и сделали: из всех выставленных значений SVN_REVISION_<n> берётся максимальное и добавляется в собранные программы.
Полезные плагины для Jenkins
Расширение возможностей с помощью плагинов позволяет максимально гибко настроить сервер. Плагины для запуска тестов в Visual Studio, пожалуй, единственные, которые мы отказались использовать. Они имели дополнительные обязательные параметры запуска, которые мы не использовали, поэтому проще было сделать универсальный Job, просто запускающий тесты из командной строки. К сожалению, «из коробки» Jenkins не умел многое из того, к чему мы привыкли в CCNet. Однако, с помощью плагинов удалось «вернуть» всю требуемую нам функциональность.
Далее приведен список плагинов с небольшим описанием, которыми нам было удобно воспользоваться:
- Multijob plugin — позволяет использовать в качестве этапов сборки другие Job'ы с возможностью последовательного и параллельного выполнения.
- Environment Injector Plugin — с помощью этого плагина можно задавать глобальные пароли. Используются они как переменные окружения, при этом плагин скрывает значения такой переменной в логе.
- Pre SCM BuildStep Plugin — добавление дополнительных шагов перед выполнением команд системы контроля версий.
- MSBuild Plugin — удобный плагин для сборки проектов с помощью MSBuild. В настройках один раз указываются пути к разным версиям MSBuild. Далее в проекте можно легко добавлять шаги сборки.
- Parameterized Trigger plugin — добавляет параметры запуска проектов. Можно, например, сделать выбор trunk/stable ветки для сборки дистрибутива.
- Post-Build Script Plug-in — выполнение дополнительных этапов после сборки.
- Throttle Concurrent Builds Plug-in — данный плагин позволяет регулировать количество параллельно запущенных сборок проекта глобально или в рамках заданной категории. Он позволяет получить в Jenkins функциональность, подобную очередям в CCNet — возможность выполнять параллельно несколько проектов из разных категорий (очередей), при этом обеспечивая последовательное выполнение проектов в рамках одной очереди. Например, у нас есть очереди Installers (дистрибутивы) и Tests (тесты). Мы хотим иметь возможность при работе тестов параллельно собирать какой-нибудь дистрибутив, но при этом тесты параллельно работать не должны — не хватит «ядер» на сервере.
- Build Name Setter Plugin — позволил задать имя сборки в нужном нам формате: Major.Minor.Revision.Build.
- Dashboard View — позволяет добавить своё отображение Job'ов в браузере. Т.к. у нас есть универсальные Job'ы, которые нет смысла запускать вручную, то мы создали список без них с помощью этого плагина.
- ZenTimestamp Plugin — удобный плагин, добавляющий временные метки в логах сборки.
Обзор десктопных клиентов
Для получения уведомлений от CCNet мы пользовались клиентом для Windows — CCTray.
Вот какие варианты теперь есть для работы с Jenkins:
1. CCTray — эту программу можно использовать и для сервера с Jenkins. Выглядеть проекты будут примерно, как и раньше (рисунок 5).
Рисунок 5 — Скриншот CCTray
Описание как клиента для Jenkins:
- Не развивается, как и CCNet;
- Не умеет показывать подзадачи (работает только для CCNet);
- Не умеет запускать проекты;
- По нажатию на название можно перейти на страницу проекта;
- Настраивается вид отображения проектов (Icons, List, Details);
- С открытым исходным кодом.
2. CatLight (рисунок 6)
Рисунок 6 — Скриншот CatLight
Описание клиента:
- На данный момент Beta версия, финальная версия станет платной;
- Ещё есть падения при установке, работе и глюки в интерфейсе;
- При выводе компьютера из гибернации статус проектов на dashboard'е не обновляется автоматически;
- Не умеет показывать подзадачи для Multijob'ов;
- Не умеет запускать проекты;
- Не настраивается вид отображения проектов (единственный возможный вид — рисунок 6);
- По нажатию на название можно перейти на страницу проекта;
- Можно видеть статус последних 5-и запусков и переходить к ним;
- Можно видеть прогресс запущенного проекта;
- При добавлении нескольких серверов они удобно отделены чертой;
- Есть для Windows, Linux и Mac.
3. Kato (рисунок 7)
Рисунок 7 — Скриншот Kato
Описание клиента:
- Не умеет показывать подзадачи для Multijob'ов;
- Умеет запускать проекты. К сожалению, не поддерживает проекты с параметризованным запуском — утилита «падает» при попытке запустить такой проект;
- Проекты с разных серверов отображаются в одном списке и неразличимы (не всегда удобно);
- Настраивается вид отображения проектов (List, Grid);
- По нажатию на название можно перейти на страницу проекта;
- Можно просматривать последний лог прямо в клиенте, но из-за отсутствия моноширинного текста это не очень удобно;
- С открытым исходным кодом;
- Только для Windows.
4. CCMenu — клиент только для Mac с открытым исходным кодом. Для нас не актуален, но может кому-нибудь пригодится.
Заключение
Использование CI полезно в любом проекте. Для этого есть замечательный бесплатный инструмент Jenkins, который был рассмотрен в статье, а также много других бесплатных и платных CI. Приятно пользоваться развивающимся проектом: для Jenkins и плагинов периодически выходит множество обновлений. Появляются новые решения, как, например, сейчас на главной странице приводится новый проект Blue Ocean, который ещё находится на стадии Beta.
Клиенты для мониторинга проектов Jenkins не очень меня порадовали. Отсутствует много напрашивающихся функций. Возможно, десктопные клиенты не особо востребованы и правильнее пользоваться только web-интерфейсом.
При переезде на новый сервер не получилось использовать Jenkins как службу Windows, т.к. в этом режиме не выполняются UI тесты. Вышли из положения, настроив запуск сервера как консольного приложения со скрытым окном.
Если у вас есть дополнения к изложенному материалу или интересные решения приведённых проблем, то мы будем рады, если вы оставите комментарий к статье или напишите нам через форму обратной связи.
Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Svyatoslav Razmyslov. Moving from CruiseControl.NET to Jenkins in the PVS-Studio development team
Комментарии (23)
Seekeer
07.02.2017 21:42-1А чем не устроил TeamCity?
По беглому впечатлению от Jenkins ТС в разы лучше.SvyatoslavMC
08.02.2017 12:48Нам подошло более просто решение на Jenkins. Приобрести можно разве что десктопный клиент CatLight, когда его доработают, самый удобный из рассмотренных.
anatolykern
08.02.2017 08:06concourse.ci в качестве альтернативы не рассматривали?
https://concourse.ci/concourse-vs.htmlSvyatoslavMC
08.02.2017 12:32Не встречали такой. Возможно, эта CI не так популярна, как Jenkins. Глянем.
0x00fe
08.02.2017 09:12+1Есть ещё declarative pipeline в jenkins (по быстрому глянуть можно https://jenkins.io/blog/2017/02/07/declarative-maven-project ). Шаги сборки не на web страничке конфигурируются, а в файле, лежащем в одном с кодом репозитории
varnav
08.02.2017 12:24Pipeline as a code появилась в апреле 2016. Использую на части job-ов, есть свои плюсы (файл лежит в том же репозитории, его изменения традиционно отслеживаются системой контроля версий, переносимость), и минусы (неполная и порой неоднозначная поддержка плагинами, большая трудоёмкость создания такого job-а)
ggo
08.02.2017 10:12Примерно так выглядят команды в конфигах Jenkins'а:
Чтобы не править xml руками, можно использовать Job DSL и/или Pipeline
Будет выглядеть как-то так
... steps { batchFile('''CD "%BUILD_FOLDERS%\Builder" PVS-Studio_setup.exe /VERYSILENT /SUPPRESSMSGBOXES ... Publisher_setup.exe /VERYSILENT /SUPPRESSMSGBOXES''') } ...
Плюсы — наглядность конфигов
Минусы — для нетривиальных случаев надо знать особенности Groovyvarnav
08.02.2017 12:24А зачем вообще править конфиги jenkins руками?
SvyatoslavMC
08.02.2017 12:37Незачем. Через WEB очень удобно. Не соглашусь, что править конфиги руками в общем случае это зло, но у Jenkins они действительно не предназначены для этого.
mayorovp
08.02.2017 13:21Да ладно? По мне, так какой угодно конфиг лучше чем этот веб-интерфейс.
Чтобы поднять 10 стендов — в конфиге надо сделать 10 копи-паст и заменить параметры. А в веб-интерфейсе это — полдня тыканья мышкой.
SvyatoslavMC
08.02.2017 13:40С переиспользованием Job'ов как раз отпала необходимость много копировать. Да, мышкой попользоваться придётся, но после полного развёртывания, иногда вносить изменения совсем не накладно.
bellaTOR
Что бы использовать как службу любое приложение можете попробовать nssm
alexeibs
Тут проблема скорее не в службе, а в том что для запуска UI тестов нужен залогиненный пользователь и запущенный десктоп.
bellaTOR
alexeibs
Вы сами-то настраивали запуск GUI-тестов на windows-машине? Нужен запущенный рабочий стол — именно этого у службы нет. Консольным тестам это неважно, а gui — важно. Вот к примеру первая ссылка в гугле по запросу «jenkins run gui tests»:
http://stackoverflow.com/questions/19441324/how-to-run-gui-tests-on-a-jenkins-windows-slave-without-remote-desktop-connectio
SvyatoslavMC
Служба может запускаться от имени системы, текущего пользователя или дополнительно созданного, но запуску GUI-тестов это не помогло, хотя запустить какое-нибудь графическое приложение так получалось.
bormotov
кажется у Jenkins были агенты которые можно запускать на отдельных хостах или я путаю?
у нас GoCd, у него агенты, и для запуска UI-тестов есть виртуалки, с автологином, и запуском агента из «Автозапуска». Сам же GoCd при это работает на линуксовой машине.
SvyatoslavMC
Jenkins поддерживает распределённые запуски, наверняка это очень удобно, но из-за небольшого числа таких тестов поддерживать ещё один компьютер или виртуалку не хочется.