TFS2010构建定义如何部署到多个服务器?

9

我一直在研究TFS2010新的构建和使用MSDeploy进行部署的功能。目前为止一切顺利(虽然很难找到特定场景的信息)。

我能否修改我的构建定义以指定要部署到的2个或更多服务器?我需要部署到多个服务器(因为我在测试环境中有两个使用NLB的服务器)。

我现在拥有一个构建定义,它构建、运行我的测试,然后将其部署到我的一个测试服务器上(该服务器上运行着MsDeployAgentService)。它工作得很好,并且每个Web项目都按照其项目文件中的配置进行部署。我使用的MSBuild参数是:

* /p:DeployOnBuild=True
* /p:DeployTarget=MsDeployPublish
* /p:MSDeployServiceURL=http://oawww.testserver1.com.au/MsDeployAgentService
* /p:CreatePackageOnPublish=True
* /p:MsDeployPublishMethod=RemoteAgent
* /p:AllowUntrustedCertificated=True
* /p:UserName=myusername
* /p:Password=mypassword 

注意:我不使用/p:DeployIISAppPath="xyz",因为它无法部署我所有的项目并覆盖了我的项目配置。
我能否添加另一个构建参数来调用多个MSDeployServiceURL?比如第二个/p:MSDeployServiceURL参数指定另一台服务器?
还是我必须寻找另一个解决方案,比如编辑WF?
我在这里看到一个几乎完全相同的问题,两个月前发布:TFS 2010 - Deploy to Multiple Servers After Build,所以看起来我不是唯一一个试图解决这个问题的人。
我还在IIS.NET论坛上发布了有关MSDeploy的讨论:http://forums.iis.net/t/1170741.aspx。它已经有很多阅读量,但是没有答案。
5个回答

7
您不必两次构建项目才能部署到两个服务器。构建过程将会产生一组部署文件,您可以使用 InvokeProcess 对多台服务器进行部署。
首先创建一个名为 ProjectName 的变量。然后在“尝试编译项目”的序列中添加一个 Assign 活动。该活动位于“编译项目”序列中。这是 Assign 活动的属性:
To: ProjectName
Value: System.IO.Path.GetFileNameWithoutExtension(localProject)

以下是部署到测试服务器的InvokeProcess活动的属性:
Arguments: "/y /M:<server> /u:<domain>\<user> /p:<password>"
FileName: String.Format("{0}\{1}.deploy.cmd", BuildDetail.DropLocation, ProjectName)

You will need to change <server>, <domain>, <user>, and <password> to the values that reflect your environment.

如果需要手动部署到服务器,您可以从构建文件夹运行以下命令:
deploy.cmd /y /M:<server> /u:<domain>\<user> /p:<password>

如果我有机会重新实现,这看起来是正确的做法。只可惜它不像这样的东西一样自带。谢谢! - Arkiliknam
我已经更新了我们的流程,现在这些都是参数化的。对于MSBuild参数,您还可以指定IIS应用程序路径。如果您需要在同一服务器上部署多个实例,这也可以进行参数化。 - 37Stars

6

我没有找到我需要的解决方案,但最终我想出了以下方法。

我想保持解决方案的简单性,并且使其可配置在TFS参数中,同时与已提供的MSBuildArguments方法保持一致。因此,我创建了一个新的构建模板,并在工作流的参数选项卡中添加了一个名为MSBuildArguments2的新的TFS WorkFlow参数。

alt text

我搜索了构建模板工作流中所有使用MSBuildArguments的地方(有两个出现)。

使用MSBuildArguments的两个任务分别称为Run MSBuild for Project。在此任务的正下方,我添加了一个新的"If"块,并设置条件:

Not String.IsNullOrEmpty(MSBuildArguments2)

我随后复制了“运行MSBuild项目”任务,并将其粘贴到新的If的“Then”块中,相应地更新其标题。您还需要更新新任务的CommandLineArguments属性以使用您的新参数。
CommandLineArguments = String.Format("/p:SkipInvalidConfigurations=true {0}", MSBuildArguments2)
在这些修改之后,工作流如下所示:
保存并检入新的工作流。更新您的构建定义以使用此新工作流,在构建定义的“进程”选项卡中,您将找到一个名为Misc的新部分,其中包含准备好使用的新参数。因为我仅将这个新参数用于部署,所以我复制了完全相同的参数,我用于MSBuild Arguments,并将MSDeployServiceURL更新为我的第二个部署服务器。
就是这样。我想一种更优雅的方法是将MSBuildArguments转换为字符串数组,然后在工作流过程中循环遍历它们。但这适合我们的2个服务器要求。
希望这有所帮助!

2

我的解决方案是创建一个在 Package 后运行的新目标。每个需要生成包的项目都包括这个目标文件,我选择使 Include 取决于外部设置的“DoDeployment”属性。此外,每个项目都定义了 DeploymentServerGroup 属性,以便根据项目类型正确过滤目标服务器。

正如您在底部看到的那样,我只是使用服务器列表执行命令文件,非常简单。

<!-- 
This targets file allows a project to deploy its package  

As it is used by all project typesconditionally included from the project file 

-->

<UsingTask TaskName="Microsoft.TeamFoundation.Build.Tasks.BuildStep" AssemblyFile="$(TeamBuildRefPath)\Microsoft.TeamFoundation.Build.ProcessComponents.dll" />

<!-- Each Server needs the Group metadatum, either Webservers, Appservers, or Batch. -->
<Choose>
    <When Condition="'$(Configuration)' == 'DEV'">
        <ItemGroup>
            <Servers Include="DevWebServer">
                <Group>Webservers</Group>
            </Servers>
            <Servers Include="DevAppServer">
                <Group>Appservers</Group>
            </Servers>
        </ItemGroup>
    </When>
    <When Condition="'$(Configuration)' == 'QA'">
        <ItemGroup>
            <Servers Include="QAWebServer1">
                <Group>Webservers</Group>
            </Servers>
            <Servers Include="QAWebServer2">
                <Group>Webservers</Group>
            </Servers>
            <Servers Include="QAAppServer1">
                <Group>Appservers</Group>
            </Servers>
            <Servers Include="QAAppServer2">
                <Group>Appservers</Group>
            </Servers>
        </ItemGroup>
    </When>
</Choose>

<!-- DoDeploy can be set in the build defintion -->
<Target Name="StartDeployment" AfterTargets="Package">

    <PropertyGroup>
        <!-- The _PublishedWebsites area -->
        <PackageLocation>$(WebProjectOutputDir)_Package</PackageLocation>

        <!-- Override for local testing -->
        <PackageLocation Condition="$(WebProjectOutputDirInsideProject)">$(IntermediateOutputPath)Package\</PackageLocation>

    </PropertyGroup>

    <Message Text="Tier servers are @(Servers)" />

    <!-- A filtered list of the servers.  DeploymentServerGroup is defined in each project that does deployment -->
    <ItemGroup>
        <DestinationServers Include="@(Servers)" Condition="'%(Servers.Group)' == '$(DeploymentServerGroup)'" />
    </ItemGroup>

    <Message Text="Dest servers are @(DestinationServers)" />

</Target>

<!-- Only perform the deployment if any servers fit the filters -->
<Target Name="PerformDeployment" AfterTargets="StartDeployment" Condition="'@(DestinationServers)' != ''">

    <Message Text="Deploying $(AssemblyName) to @(DestinationServers)" />

    <!-- Fancy build steps so that they better appear in the build explorer -->
    <BuildStep
                    TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
                    BuildUri="$(BuildUri)"
                    Message="Deploying $(AssemblyName) to @(DestinationServers)...">
        <Output TaskParameter="Id" PropertyName="StepId" />
    </BuildStep>

    <!-- The deployment command will be run for each item in the DestinationServers collection.  -->
    <Exec Command="$(AssemblyName).deploy.cmd /Y /M:%(DestinationServers.Identity)" WorkingDirectory="$(PackageLocation)" />

    <BuildStep
                    TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
                    BuildUri="$(BuildUri)"
                    Id="$(StepId)"
                    Status="Succeeded"
                    Message="Deployed $(AssemblyName) to @(DestinationServers)"/>
    <OnError ExecuteTargets="MarkDeployStepAsFailed" />
</Target>

<Target Name="MarkDeployStepAsFailed">
    <BuildStep
            TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
            BuildUri="$(BuildUri)"
            Id="$(StepId)"
            Status="Failed" />
</Target>


有意思……你把工作从 TFS 工作流中拿出来,放回到了 MSBUILD 脚本中。你的解决方案中每个项目在构建后都调用这个目标吗?我还以为 MSBUILD 已经被淘汰了 :) - Arkiliknam
一旦我在编写MSBuild文件方面变得更加熟练,我开始更喜欢使用它们而不是使用笨重且容易崩溃的工作流设计器。每个需要部署其软件包的项目都会从其csproj中包含此目标文件,并且目标调用是自动的,因为它是在Package目标之后完成的。 - David Peters

0

0

我是另一篇类似帖子的作者。我还没有找到解决方案。我认为需要修改工作流程来添加后处理MSBUILD同步任务。这似乎是最优雅的解决方案,但仍然希望找到更少侵入性的方法。


我本来也想走那条路。这里有一篇博客文章:http://blogs.blackmarble.co.uk/blogs/rfennell/archive/2010/08/13/running-msdeploy-to-a-remote-box-from-inside-a-tfs-2010-build-part-2.aspx,介绍了如何做到这一点。但最终我添加了第二个 MSBUILD 调用,并重复使用了他们为我们提供的参数想法(详见我的答案)。 - Arkiliknam

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接