Компания
69,97
рейтинг
10 декабря 2013 в 06:58

Разработка → Настройка TeamCity для новичков tutorial

Эта статья в первую очередь пригодится тем, кто использует тот же стек технологий, что и наша команда, а именно: ASP.NET, C#, NUnit, Selenium 2, git, MSBuild. Будут рассмотрены такие задачи, как интеграция с git, сборка C#-проектов, NUnit-тесты (как модульные, так и тесты UI), а также деплой на сервер. Впрочем, наверняка найдётся интересное и для других пользователей, кроме разве что съевших на этом вопросе собаку. Но они опять же смогут обратить внимание на ошибки в статье или что-то посоветовать: например, как оптимизировать фазу деплоя.

Что такое «непрерывная интеграция», отлично рассказано здесь и вот здесь, повторять эту тему в сотый раз вряд ли нужно.

Ну и для начала – что может TeamCity (далее – просто TC)? А может он следующее: при появлении изменений в указанной ветке репозитория (или ином событии) выполнить сценарий, включающий в себя, например, сборку приложения, прогон тестов, выполнение других сценариев, заливку файлов на удаленный сервер и т.п.

Важным моментом является то, что «сервер интеграции» и «машина, на которой будет проходить этот процесс», – обычно (не обязательно) разные серверы. Более того, машинок, на которых запускаются сборки и тесты, может быть несколько, даже много, и все на разных ОС – в общем, есть, где развернуться.

Для запуска процесса сборки используется программа-агент, принимающая команды от TC-сервера, запустить ее можно на любой из основных ОС (слава мультиплатформенности Java). На один компьютер можно установить несколько агентов и запускать их параллельно, но важно помнить, что один агент единовременно может обрабатывать только один проект. При старте задачи TC выбирает первый подходящий незанятый агент, причем можно устанавливать «фильтры», например, выбирать агента только с ОС Windows или только с установленным .NET версии не ниже 4.0 и т.п.

Теперь нужно придумать сценарий работы. Мы используем в работе следующие ветки:
  1. release – содержит актуальный код, рабочую версию, которая находится на боевом сервере;
  2. dev – в неё идут все новые фичи, позже вливается в release;
  3. отдельная ветка на каждую фичу, которая отпочковывается от dev и в неё же возвращается.

В общем, практически стандартный git-flow, о котором более подробно можно прочитать, например, здесь.

В связи с этим наш сценарий будет выглядеть так:

  1. забрать свежие изменения из репозитория ветки dev;
  2. скомпилировать проект;
  3. если всё прошло успешно на предыдущем шаге – прогнать юнит-тесты;
  4. если всё прошло успешно на предыдущем шаге – прогнать функциональные тесты;
  5. если всё прошло успешно на предыдущем шаге – залить изменения на тестовый сервер.

Для релизной ветки делаем всё то же самое, но на время заливки новых данных приостанавливаем сервер и показываем заглушку.

А теперь вперёд – реализовывать сценарий!


Забрать свежие изменения из репозитория


Начинается всё с немудрёного создания проекта.



После – создание «build configuration» (конфигурации сборки). Конфигурация определяет сценарий сборки.



На втором же шаге создания конфигурации нас спросят об использованной VCS, так что отвечаем честно, что у нас тут git. У вас может быть другая VCS – не теряйтесь. Добавление нового репозитория производится кнопкой Create and attach new VCS root.



Итак, ключевые настройки:



  • VCS root ID можно не трогать – уникальный код, как ни крути. Если оставить пустым, генерируется автоматически;
  • Fetch URL – тот адрес, с которого мы будем забирать содержимое репозитория;
  • Default branch – ветка, с которой будет браться информация;



  • Authentication method – самое интересное – способ аутентификации. Тут может быть и доступ без авторизации (если данные лежат на внутреннем сервере, например), и по ключу, и по паролю. Для каждого варианта дополнительные поля будут свои, сами понимаете.

Остальное многообразие опций – на ваш вкус и цвет.

Далее, нужно настроить автоматический запуск задания. Идём в «Build Triggers» (условия сборки) и выбираем условие VCS Trigger – при настройках по умолчанию он будет раз в минуту проверять наличие новых коммитов в репозитории, а если таковые найдутся – запустит задание на выполнение.




Скомпилировать проект


Поскольку у нас проект на ASP.NET в виде Solution’а из Visual Studio – тут тоже было всё просто.

Идём в меню «Build Steps» (Шаги сборки), выбираем runner type (а их тут действительно много) и останавливаемся на MSBuild. Почему именно на нём? Он даёт достаточно простой способ описать процесс сборки, даже достаточно сложный, добавляя или удаляя различные шаги в простом XML-файле.





Далее всё элементарно.



Build file path – путь к sln-файлу.
MSBuild version, MSBuild ToolsVersion и Run platform выбираем под требования своего проекта.

Если в проекте несколько конфигураций, то можно использовать опцию Command line parameters для ввода примерно такого ключа:

/p:Configuration=Production

где Production заменить на нужный конфиг.


Включить скачивание NuGet-пакетов


Важный пункт, в случае если вы используете NuGet-пакеты; если нет – можно переходить сразу к следующему пункту.

Поскольку NuGet-пакеты весят немало, да и хранить в репозитории бинарники библиотек без особой необходимости не хочется, можно использовать замечательную опцию NuGet Package Restore:



В этой ситуации бинарники библиотек в репозиторий не включаются, а докачиваются по мере необходимости в процессе сборки.

Но MSBuild – настоящий джентльмен и не будет без разрешения делать лишних телодвижений, поэтому и докачивать пакеты просто так не будет – ему сей процесс нужно разрешить. Для этого придется либо на клиенте установить переменную окружения Enable NuGet Package Restore в значение true, либо пойти в меню Build Parameters и выставить его там.




Прогнать юнит-тесты


У нас юнит-тесты являются отдельным проектом внутри решения. Так что на предыдущем шаге они уже скомпилированы – осталось их запустить.

Добавляем новый шаг, только теперь Runner – это NUnit. Обратите внимание на параметр Execute step: он указывает, при каких условиях нужно выполнять шаг, и имеет 4 значения:
  • If all previous steps finished successfully (zero exit code) – если все предыдущие шаги завершились без ошибок. При этом проверка выполняется чисто на агенте;
  • Only if build status is successful – аналогично предыдущему, но при этом агент ещё и уточняет у сервера TC статус билда. Нужно для более тонкого управления логикой задания, например, если нулевой код возврата конкретного шага для нас является ошибкой;
  • Even if some of the previous steps failed – даже если какой-то из предыдущих шагов завершился с ошибкой;
  • Always, even if build stop command was issued – выполнять шаг, даже если подана команда на отмену выполнения сборки.

Здесь самое важное – указать верный путь к сборке с тестами внутри проекта в графе Run tests from. У нас, например, он выглядит вот так:

%teamcity.build.checkoutDir%\project\project.FuncTests\bin\Dev\project.FuncTests.dll

%teamcity.build.checkoutDir% – это переменная, указывающая на папку, в которую скачиваются данные из репозитория. В принципе, она не обязательна для указывания, т.к. по умолчанию путь идёт именно относительно этой директории, поэтому путь можно было бы сократить до:

project\project.FuncTests\bin\Dev\project.FuncTests.dll




Отдельно отмечу опцию Run recently failed test first – если в предыдущем прогоне какие-то тесты упали, то в следующем запуске первыми запустятся именно они, и вы быстро узнаете об успешности последних изменений.




Прогнать тесты интерфейса (функциональные тесты)


Здесь все гораздо интереснее, чем с юнит-тестами. Кэп тут подсказывает, что, чтобы протестировать проект в браузере, его, т.е. проект, необходимо запустить. Но тут мы схитрили, запуская веб-сервер прямо из кода тестов Selenium:

	[SetUpFixture]
	class ServerInit
	{
		private const string ApplicationName = "justtest";
		private Process _iisProcess;

		private string GetApplicationPath(string applicationName) {
			var tmpDirName=AppDomain.CurrentDomain.BaseDirectory.TrimEnd('\\');
			var solutionFolder = Path.GetDirectoryName(Path.GetDirectoryName(Path.GetDirectoryName(tmpDirName)));
			string result = Path.Combine(solutionFolder, applicationName);
			return result;
		}

		[SetUp]
		public void RunBeforeAnyTests()
		{
			[…]
			var applicationPath = GetApplicationPath(ApplicationName);
			var programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);

			_iisProcess = new Process
			{
				StartInfo =
				{
					FileName = string.Format("{0}/IIS Express/iisexpress.exe", programFiles),
					Arguments = string.Format("/path:\"{0}\" /port:{1}", applicationPath, UrlProvider.Port)
				}
			};
			_iisProcess.Start();
		}

		[TearDown]
		public void RunAfterAnyTests()
		{
			[…]
			if (_iisProcess.HasExited == false)
			{
				_iisProcess.Kill();
			}
		}
	}]


А сам запуск выглядит абсолютно точно так же, как на шаге с юнит-тестами.




Залить изменения на тестовый сервер


Сервер, на котором находится агент TC, и сервер, на котором установлен IIS, – разные серверы, причем, более того, находятся они в разных сетях. Поэтому файлы нужно как-то на конечный сервер доставить. И вот тут решение выбрано, может, не очень элегантное, зато крайне простое. Используем заливку через FTP, причем делает сие за нас MSBuild.

Схема такая:
  1. настраиваем на сервере FTP аккаунт для заливки файлов. Для дополнительной безопасности можно запретить заливку для всех IP, кроме внутреннего, если TC-сервер лежит во внутренней сети, конечно;
  2. устанавливаем на агента MSBuild Community Tasks, чтобы получить возможность использовать задачу «Залить по FTP». Качать тут;
  3. подготовить файл-сценарий для MSBuild, который будет производить следующие действия:
    1. сборка приложения во временной папке;
    2. подмена конфигурационного файла;
    3. заливка файлов по FTP.

Вот так будет выглядеть этот файл (deploy.xml)
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0" DefaultTargets="Build">

	<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v11.0\Web\Microsoft.Web.Publishing.Tasks.dll"/>
	<Import Project="$(MSBuildExtensionsPath32)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>

    <PropertyGroup>
        <OutputDir>bin</OutputDir>
		<PublishDir>../output</PublishDir>
		<Configuration>Dev</Configuration>
        <TransformInputFile>..\project\project\Web.template.config</TransformInputFile>
        <TransformFile>..\project\project\Web.$(Configuration).config</TransformFile>
        <TransformOutputFile>..\project\output\Web.config</TransformOutputFile>
        <StackTraceEnabled>False</StackTraceEnabled>
    </PropertyGroup>

    <ItemGroup>
        <ProjectToBuild Include="../project/project.sln">
            <Properties>WebProjectOutputDir=$(PublishDir);OutputPath=$(OutputDir);Configuration=Dev</Properties>
        </ProjectToBuild>
    </ItemGroup>

    <Target Name="Build">
        <MSBuild Projects="@(ProjectToBuild)"/>
    </Target>

	<Target Name="CreateWebConfigs" AfterTargets="Build">
	<TransformXml
		Source="$(TransformInputFile)"
		Transform="$(TransformFile)"
		Destination="$(TransformOutputFile)"
	/>
	</Target>

	<Target Name="AfterBuild" AfterTargets="CreateWebConfigs">
		<PropertyGroup>
			<ftpHost>dev.example.com</ftpHost>
			<ftpUser>ЛОГИН</ftpUser>
			<ftpPass>ПАРОЛЬ</ftpPass>
			<LocalDirectory>..\project\output</LocalDirectory>
		</PropertyGroup>
        <FtpUploadDirectoryContent
			ServerHost="$(ftpHost)"
			Port="21"
			Username="$(ftpUser)"
			Password="$(ftpPass)"
			LocalDirectory="$(LocalDirectory)"
			RemoteDirectory=""
			Recursive="true"
        />
	</Target>
</Project>


А так – задание по его вызову:



Недостаток решения – заливаются ВСЕ файлы, а не только новые изменившиеся. На проекте с кучей файлов верстки или множеством модулей это может стать проблемой из-за затрачиваемого на заливку времени.

Ещё один замеченный недостаток – при встрече файла с нелатиницей в названии падает с ошибкой. Латиница и буквы/цифры обрабатываются нормально. Ноги этой проблемы, похоже, растут из специфики протокола FTP: тот основан на ASCII, но, как именно кодировать не ASCII-символы, он не описывает, предлагая «делать так, как будет понимать ваш сервер». Соответственно, чтобы вылечить проблему, не меняя схему, нужно пропатчить MSBuild Community Tasks, благо, исходники открыты. Ну, или воспользоваться альтернативным способом заливки файлов, например через WinSCP.


Остановка и запуск сервера для релизного сценария


Мы её решаем чуть диким, но симпатичным способом. У IIS’а есть особенность: если в корень сайта положить файл с именем app_offline.html, то сайт отрубается, при обращении ко всем файлам будет выдаваться содержимое этого файла.

Минус – обращение именно что КО ВСЕМ файлам, включая статичные. Так что, если хочется сделать заглушку с оформлением, CSS и картинками, используйте инлайн-стили и data:url, ну, или как вариант – выложите их на отдельном сервере.

Включаем-отключаем мы сервер через WinSCP-сценарий и такие вот файлики:

server_off.cmd
winscp.exe /console /script=server_off.txt

server_on.cmd
winscp.exe /console /script=server_on.txt

server_off.txt
option batch abort
option confirm off

open ftp://ЛОГИН:ПАРОЛЬ@dev.example.com

mv _app_offline.htm app_offline.htm

close

exit

server_on.txt
option batch abort
option confirm off

open ftp://ЛОГИН:ПАРОЛЬ@dev.example.com

rm app_offline.htm

close

exit

Т.е., изначально файл лежит в корне и называется _app_offline.html. Когда нужно заблокировать доступ на время апдейта, мы переименовываем его в app_offline.html. При заливке файлов заливается новый файл _app_offline.html, а после окончания – удаляется файл app_offline.html. И получаем именно то, что было изначально.

В тексте страницы-заглушки крайне рекомендую воспользоваться мета-тегом refresh, который периодически будет обновлять страницу. Если к этому времени процесс обновления завершился, пользователь вернётся обратно в сервис, чему наверняка будет несказанно рад.

Вызов сценария включения заглушки (отключение заглушки происходит аналогично):



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

Данные настройки используются нами на протяжении где-то полугода, и пока ни единой проблемы не возникло – всё работает как часы, что не может не радовать.

На этом всё. С удовольствием выслушаю комментарии и советы по улучшению этих шагов, а также ваши рассказы о том, какие возможности TC вы используете у себя.

Update от 3 ноября 2014 года.
Выбирая Runner Type «Command line» не требуется как-либо экранировать пробелы — Team City озаботится об этом самостоятельно.
Автор: @Newbilius

Комментарии (12)

  • +2
    Отличный туториал, я считаю.

    Порадовали остроумные «фишечки»: запуск браузера из фикстуры функциональных тестов (идеально подходит для простых веб-приложений) и установка заглушки с автообновлением, которая рано или поздно приведёт в обновлённый веб-сервис.
  • +2
    Возникло несколько вопросов:
    1. У Вас вот в конфигах скрипты из папки DeployScripts берутся, эта папка в самом проекте находится, в том же репозитории что и основной проект? (У нас для скриптов отдельный репозиторий, но и проектов много, для избегания дублирования выделили все скрипты и инфраструктурные штуки туда)
    2. А в чем преимущество автоматического рестора пакетов перед отдельным шагом рестора? (на мой взгляд нагляднее это делать отдельным шагом, так как в build result появляется информационная вкладка о установленных в проекте пакетах)
    3. Селениум. На каком браузере происходит тестирование? И тестируете ли различные браузеры? У нас регулярно тесты проходят под хромом, но sheduler trigger стоит и ночью другие браузеры тоже гоняют тесты, удобно.
    4. Пользуетесь ли «Branch specification» для многоветочнисти? Или как Вы организовали тестирование фича веток?
    • 0
      1. Да, в том же. Не было резона выделять в отдельный репозиторий, у нашей команды это был первый проект, использующий TC, пока ничего принципиально общего, что стоило бы выносить там нет. Когда будут новые проекты, тогда будет смысл.
      2. Мне кажется, этот процесс не частый и достаточно «фоновой», что бы выделять его в отдельный шаг, статистика по пакетам — не очень критичная информация, хотя и любопытная. Благодарю за идею!
      3. Тесты проводим в Firefox, спасибо за идею по ночам отдельно запускать тесты на других браузерах.
      4. Нет, не пользуемся. Ветки с фичами мы на TC не запускаем, после того, как фича готова — она просто вливается в dev, и будет протестирована уже в комплексе со всеми последними изменениями.
      • 0
        По 4-ому пункту — у нас бывает что кто нибудь хочет отрефакторить жутко сильно, и, предположим, делает это на протяжении недели, не спеша, планомерно, а основная ветка сама по себе развивается. Проект довольно сложный и так сразу сложно понять, все ли нюансы учтены при рефакторинге, и хотят видеть сразу тесты как проходят. А так, спасибо за ответ)

        А вообще, мне, почему то, не очень с msbuild понравилось работать, то ли я его не понял в полной мере. Управляющие скрипты пишу на Powershell, ну дико удобно.
    • +2
      Отвечу про свой вариант по пункту 4. У нас структура в TFS немножко проще, редко нужны отдельные ветки для фич — поэтому вполне достаточно сделать две конфигурации. Вообще, не вижу труда быстро клонировать конфигурацию и слегка подправить VCSRoot.
      • +2
        У нас платформа, на которой пишутся остальные модули. Есть конечные проекты и есть куча мелких модулей(не то чтобы куча, штук 30). Все это имеет кучу версий, где для разной версии платформы, где для функционала совсем нового. В общем, если для каждого проекта и версии вводить билды — будет полный ад. Простые модули поставляются в конечные — Nuget пакетами, как и сама платформа, да и друг от друга иногда имеют зависимости.

        А вообще, клонировать конфигурации — плохо. Вот надо где то массово все поменять, перенастроить, обязательно что то где то будет упущено. Советую писать шаблоны, завязаться в билд степах на параметры и вуаля, надо билд — от шаблона сделал, уже подсвечено что и где надо вписать, чтоб все заработало. Ну и правки вносить — в шаблоне.

        Жаль, но с TFS не работал и тем более на настраивал, сказать особо по нему нечего.
  • +2
    Туториал вполне хороший.

    Выбор release/debug осуществляем через переменную env.Configuration = Release, а не через ключ для command-line.

    Из возможностей ТС, которые используются — добавляем отдельный шаг «поиск дубликатов в коде» (Runner type = Duplicates finder (.Net), настройка runner'а тривиальна)
    • +2
      Ага, а еще StyleCop, FxCop, NDepend…
    • +2
      уточнение:
      все property вида env.* и system.* доступны внутри msbuild скрипта

      задали property system.Configuration
      а внутри msbuild можете получить значение $(Configuration)

      ну и ещё нюанс вдогонку. если property например такое system.A.B.C то переменная будет ${A_B_C}

      • +1
        Уточнение к уточнению: property вида env.* доступны внутри msbuild проекта всегда, а вот system.* — только если внутри нет русских символов.
  • +4
    Покритикую немного конечно же :)

    1. Enable Nuget Package Restore начиная с NuGet 2.7 уже не нужен. Здесь подробнее написано (поиск по «Automatic Package Restore»). Выставлять environment variable в TeamCity в этом случае не требуется.
    Вообще лучше использовать отдельный шаг «NuGet Installer», как тут уже писали, он даёт няшный отчёт по используемым nuget-пакетам.

    2. MSBuild Community Tasks не нужно ставить на билд-агента, давно уже есть nuget-пакет
    Референсите его из deploy.xml и всё будет работать.
    На будущее лучше посмотрите в сторону psake — там всё что нужно пишется руками на PowerShell очень быстро, не нужны никакие third-party task library.
    Писать руками MSBuild скрипты это очень громозко, сплошной копипаст, высокий порог вхождения, поддерживать и развивать тяжело.

    3. Выкладку на FTP можно писать не руками, а используя механизм publish profiles.
    Попробуйте на любом web проекте правой кнопкой нажать и выбрать «Publish...» — это встроенный механизм деплоя web проектов, FTP там тоже есть.
    На будущее откажитесь от FTP в сторону MSDeploy — там есть из коробки и offline page и много чего другого.

    4. Выставлять Configuration=Local не требуется если использовать publish profiles. Configuration это нечто глобальное на уровне всего решения, а publish profile это локальная вещь для отдельного web проекта. У меня есть подозрение что это вам больше подойдёт.

    А вообще очень полезная статья для новичков, спасибо вам за труд.
  • +2
    1) при настройке VCS Root очень удобно в поле default branch подставить какое-нибудь property %system.BranchName%

    таким образом в разных конфигурациях можно использовать повторно один и тот же VCS Root
    просто задавая через property имя нужного бранча

    2) если у вас стоит Checkout mode = on server, то удобно использовать VCS Checkout Rules отфильтровывая всё лишнее.
    Если репозитори большой, а собирается маленькая часть, то за счёт этого clean checkout будет банально быстрее.

    3) кажется этот плагин confluence.jetbrains.com/display/TW/Deployer+plugin вам пригодится в фазе заливки на сервер

    PS: список плагинов confluence.jetbrains.com/display/TW/TeamCity+Plugins

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое Разработка