TeamCity deploy
Потратив много времени на поиски, разобрался наконец как осуществить деплой с помощью TeamCity, которая забирает исходники из TFS.
Входные данные:
- TFS 2010 с доступом для чтения
- Пакеты в NuGet
- TeamCity server (Windows Server 2008 R2)
- TeamCity agent (Windows Server 2012)
- Web server (Windows Server 2012)
Ниже подробная инструкция о том, как сделать, чтобы это всё работало и собиралось без использования MSDeploy (настройки проекта менять нельзя).
Для начала предположим, что ТимСити агент и сервер уже связаны и работоспособны и опустим эту настройку (есть в документации ТимСити). А также ТимСити агент поддерживает TFS (решается установкой TFS на агента и подключения (если его нет) плагина TFS)
Тогда наша настройка начнется с создания проекта в TeamCity и добавления Build Configuration.
P.S. Если параметр не оговорен в инструкции - его значение на ваш выбор.
Version Control Settings - указываем TFS и заполняем согласно настройкам вашего TFS и пользовательских данных.
Checkout directory - CustomPath
Build steps
1. Get NuGet
Runner type - NuGet installer
NuGet.exe - default(2.7.0)
Packages Sources
https://<your nuget source>
https://www.nuget.org/api/v2/
Здесь надо не забыть указать стандартный путь nuget, а то можно не найти некоторых необходимых стандартных пакетов.
Path to solution - <путь к солюшену, в котором описаны необходимые пакеты>
Restore Mode - Install
Добавляем Additional Build Features:
type = NuGet Feed Credentials
Feed URI = <your nuget source>
И указываем User/Pass
2. Build
Здесь настраиваем параметры билда - у всех свои.
Так вышло, что у меня солюшн содержит несколько различных проектов, поэтому сначала собирается он, а потом отдельно проект:
3.(opt) Build Site
Runner type = MSBuild
Build file path = <path-to-csproj>
MSBuild Version = Microsoft .NET Framework 4.5
MSBuild ToolsVersion = 4.0
Targets = _WPPCopyWebApplication
Command line parameters:
/p:WebProjectOutputDir=%system.teamcity.build.tempDir%\%<build_name>.Project.Name%\package\site\
/p:OutDir=%system.teamcity.build.tempDir%\%<build_name>.Project.Name%\temp\site\
/p:Configuration=%WebSites.BuildConfig%
4. Deploy
Runner type = MSBuild
Build file path = <custom_path>/Deploy/Deploy.xml, где <custom_path> - директория на сервере, где находится папка Deploy. В моем случае, <custom_path>==<VCSDir>/ProjectName/ (Нужен единоразовый доступ к TFS) (Для тестирования можно просто положить в по данному пути на TeamCity agent)
Working directory = %system.teamcity.build.tempDir%\%<build_name>.Project.Name%\package\site\
MSBuild = Microsoft .NET Framework 4.0
MSBuild ToolsVersion = 4.0
Run platform = x64
Здесь стоит описать содержимое директории Deploy:
- MSBuildExtensionPack - директория dll-ек. ExtensionPack последней версии. Взять можно здесь
- Deploy.xml
<?xml version="1.0" encoding="utf-8" ?>
<Project DefaultTargets="Default" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ExtensionTasksPath>$(MSBuildThisFileDirectory)\MSBuildExtensionPack\</ExtensionTasksPath>
</PropertyGroup>
<ItemGroup>
<Servers Include="YOUR_WEB_SERVER_NAME" />
<!-- Здесь указать один или больше Web-серверов, на которые собираемся деплоить -->
</ItemGroup>
<ItemGroup>
<!-- Тут описываем общие параметры деплоя: адрес сервера, адрес пула, порт и пути -->
<WebProject Include="/">
<HttpPort>9880</HttpPort>
<AppName>www.example.com</AppName>
<AppPool>www.example.com</AppPool>
<PhysicalPath>C:\www\example.com\site</PhysicalPath>
<AppRootDirectory>C:\www\example.com\site</AppRootDirectory>
</WebProject>
<!-- В моём случае между сайтами есть сетевая шара со статическими файлами, подключим их как Virtual Directory -->
<VirtualDirectory Include="/Files">
<ApplicationPath>/</ApplicationPath>
<PhysicalPath>\\web\Files\example.com\Files</PhysicalPath>
</VirtualDirectory>
<VirtualDirectory Include="/FilesSys">
<ApplicationPath>/</ApplicationPath>
<PhysicalPath>\\web\Files\example.com\FilesSys</PhysicalPath>
</VirtualDirectory>
<HttpResponseHeaders Include="X-Server">
<Value>%(Servers.Identity)</Value>
</HttpResponseHeaders>
</ItemGroup>
<Import Project="$(ExtensionTasksPath)MSBuild.ExtensionPack.tasks"/>
<Target Name="Default">
<MSBuild Projects="$(MSBuildProjectFile)"
Properties="Server=%(Servers.Identity)"
Targets="Single" />
</Target>
<Target Name="Single">
<Iis7AppPool TaskAction="Create"
Name="%(WebProject.AppPool)"
ManagedRuntimeVersion="v4.0"
IdentityType="SpecificUser"
PipelineMode="Integrated"
PoolIdentity="$(Username)"
IdentityPassword="$(Password)"
Enable32BitAppOnWin64="True" <!-- Если приложение 32-х битное -->
Force="True"
MachineName="$(Server)" />
<Iis7Website TaskAction="Create"
Name="%(WebProject.AppName)"
Path="%(WebProject.PhysicalPath)"
Port="%(WebProject.HttpPort)"
Force="true"
Applications="@(WebProject)"
VirtualDirectories="@(VirtualDirectory)"
LogExtFileFlags="3526607" <!-- Стандартное логирование IIS + Host + Referrer -->
MachineName="$(Server)" />
<Iis7Website TaskAction="AddResponseHeaders"
Name="%(WebProject.AppName)" HttpResponseHeaders="@(HttpResponseHeaders)"/>
<!-- Добавляем биндинги для сайта -->
<Iis7Binding TaskAction="Add"
Name="%(WebProject.AppName)"
BindingInformation="*:80:www.example.com"
ContinueOnError="false"
MachineName="$(Server)" />
<Iis7Binding TaskAction="Add"
Name="%(WebProject.AppName)"
BindingInformation="*:80:example2.com"
ContinueOnError="false"
MachineName="$(Server)" />
<Iis7Binding TaskAction="Add"
Name="%(WebProject.AppName)"
BindingInformation="*:88:$(Server)"
ContinueOnError="false"
MachineName="$(Server)" />
<!-- Останавливаем пул и сайт, копируем файлы и запускаем сайт -->
<Iis7AppPool TaskAction="Stop" Name="%(WebProject.AppPool)" Force="True" MachineName="$(Server)"/>
<Iis7Website TaskAction="Stop" Name="%(WebProject.AppName)" Force="True" MachineName="$(Server)"/>
<PropertyGroup>
<p>C:\\BuildAgent\temp\BuildTmp\<build_name.Project.Name>\package\site\</p>
<d>\\$(Server)\c$\www\example.com\site\</d>
</PropertyGroup>
<RemoveDir Directories="$(d)" />
<MakeDir Directories="$(d)" />
<ItemGroup>
<UploadFiles Include="$(p)**\*.*" Exclude="" />
</ItemGroup>
<Copy SourceFiles="@(UploadFiles)" DestinationFiles="@(UploadFiles->'$(d)%(RecursiveDir)%(Filename)%(Extension)')" />
<Iis7AppPool TaskAction="Start" Name="%(WebProject.AppPool)" Force="True" MachineName="$(Server)"/>
<Iis7Website TaskAction="Start" Name="%(WebProject.AppName)" Force="True" MachineName="$(Server)"/>
</Target>
</Project>
Здесь надо отметить, что в Windows 2012 безопасность настроена таким образом, что удаленное управление IIS через MSBuild не разрешается. Для начала необходимо установить дополнение роль Management Tools и IIS Management Console, после чего оказывается, что у нас не хватает прав на управление сайтом. Многие обманываются, начиная искать решение в правах (Администратор тоже не может) и в UAC (даже при отключенном, операция все равно не позволяется, выдавая всё ту же ошибку Acces denied 80070005).
Проблема оказалась совсем не в этом, а в том, что по умолчанию, в Windows 2012 запрещен доступ к DCOM объектам, и как следствие - Management console IIS. Причём, сам DCOM разрешен, но заблокирован на файрволе.
Так как отключение файрвола - не наш метод, а настраивать DCOM - не только морока, но и требует дополнительных прав на систему, просто добавим правило на файрвол, позволяющее любые соединения между сервером ТимСити Билдагента и Веб-сервером.
Inbound, Allow all, All programs, All ports, Local:Any, Remote: <agent_ip>
5. Fix Physical Credentials
MSBuildExtensions по какой-то причине могут устанавливать пользователя для запуска пула, но не для сайта. Поэтому Physical Path Credentials придется править вручную, но нам на помощь приходит Powershell
Создаем шаг:
Runner type = Powershell
Version = Any
Bitness = x64
Error output = warning
Script = Source Code
Script Source:
Invoke-Command -ComputerName YOUR_WEB_SERVER_NAME -ScriptBlock {Import-Module webAdministration; $iisSitePath = "IIS:\Sites\" + "www.example.com"; $website = get-item $iisSitePath; $website.virtualDirectoryDefaults.userName = "${env:Username}"; $website.virtualDirectoryDefaults.password = "${env:Password}"; $website | set-item }
Script execution mode = -File
Тут главное не забыть на Web Server разрешить выполнение удаленного кода PowerShell
Set-ExecutionPolicy RemoteSigned (или даже Unrestricted)
Build Parameters
WebSites.BuildConfig = Release
WebSites.Project.Name = Example
WebSites.Site.Name = example.com
env.VisualStudioVersion 10.0
env.Username = <Username>
env.Password = <Passw0rd> (type = Password)
Тогда TeamCity запомнит пароль, но при этом не будет его нигде отображать.
Заключение
После всех манипуляций все должно заработать. Если появляются какие-то проблемы, то скорее всего связаны с рабочим окружением агента или деплойного сервера. Также, стоит отметить, что билдагент должен быть запущен из под доменного пользователя с правами администратора на веб-сервере, а также на веб-сервере должен быть опубликован диск С:. В ином случае, стоит заранее позаботиться о предоставлении сетевого доступа и поменять пути в Deploy.xml параметре $(d)