宝玉的blog

专注于web开发技术
随笔 - 81, 评论 - 1563, 引用 - 157

导航

关于


目前致力于ChinaCommunityServer的开发。

msn: junminliu(at)msn.com

标签

每月存档

最新留言

  • re:发布一个爱心小软件——网页抓图
    <p>你好 我看了你的代码 不错啊,请问在asp.net C#中 没有了webbrowser 该怎么实现?</p> <p><a href="http...
    by weblogical(注册) on 2009/9/9 17:22:55
  • re:Openlab V2.0 Beta
    <p>宝玉你好: &nbsp; &nbsp; &nbsp; 我是个.net新手,最近看了openlab(openlab_V2.0_Beta)的源码。 ...
    by isforge(注册) on 2009/6/28 10:10:37
  • re:Openlab V2.0 Beta
    <p>宝玉你好: &nbsp; &nbsp; &nbsp; 我是个.net新手,最近看了openlab(openlab_V2.0_Beta)的源码。 ...
    by isforge(注册) on 2009/6/28 10:10:31
  • re:Openlab V2.0 Beta
    <p>宝玉你好: &nbsp; &nbsp; &nbsp; 我是个.net新手,最近看了openlab(openlab_V2.0_Beta)的源码。 ...
    by isforge(注册) on 2009/6/28 10:10:30
  • re:Openlab V2.0 Beta
    <p>宝玉你好: &nbsp; &nbsp; &nbsp; 我是个.net新手,最近看了openlab(openlab_V2.0_Beta)的源码。 ...
    by isforge(注册) on 2009/6/28 10:10:29
  • re:Openlab V2.0 Beta
    <p>宝玉你好: &nbsp; &nbsp; &nbsp; 我是个.net新手,最近看了openlab(openlab_V2.0_Beta)的源码。 ...
    by isforge(注册) on 2009/6/28 10:10:25
  • re:Openlab V2.0 Beta
    <p>宝玉你好: &nbsp; &nbsp; &nbsp; 我是个.net新手,最近看了openlab(openlab_V2.0_Beta)的源码。 ...
    by isforge(注册) on 2009/6/28 10:10:25
  • re:Openlab V2.0 Beta
    <p>宝玉你好: &nbsp; &nbsp; &nbsp; 我是个.net新手,最近看了openlab(openlab_V2.0_Beta)的源码。 ...
    by isforge(注册) on 2009/6/28 10:10:25
  • re:Openlab V2.0 Beta
    <p>宝玉你好: &nbsp; &nbsp; &nbsp; 我是个.net新手,最近看了openlab(openlab_V2.0_Beta)的源码。 ...
    by isforge(注册) on 2009/6/28 10:10:25
  • re:Silverlight中,防止ComboBox抢焦点
    <p>我是初学者,您已经写了一个 组件上传的功能 。。我在2008下测试通过,,,但是弄2005测试的时候 发现 progress.aspx.cs页面的</p> <p&...
    by jxh12345j(注册) on 2009/4/7 8:55:12
  • ufnnutdh - Google Search
    ufnnutdh - Google Search
    by (匿名) on 2008/10/27 17:44:45
  • veysaync - Google Search
    veysaync - Google Search
    by (匿名) on 2008/10/5 5:20:49
  • mzgmhgio - Google Search
    mzgmhgio - Google Search
    by (匿名) on 2008/9/22 23:34:49
  • rhmhnyma - Google Search
    rhmhnyma - Google Search
    by (匿名) on 2008/9/22 7:48:44
  • re: 发布一个爱心小软件——网页抓图
    Maxthon应该有这个功能
    by passos(匿名) on 2008/7/21 20:05:23

广告

使用TFS来自动部署站点和Window Service

前言

先问各位看官两个问题:
1. 你们用TFS么?
2. 你们做自动部署么?怎么做的?

这写博客不同讲课,没法及时互动,那我只好自问自答一把了:
1. 用,当然用,按我了解的情况来看,源码管理我想一定是使用率最高的,甚至很多公司只用了TFS的源码管理功能
2. 做,最开始想用TFS来做,但是发现TFS做Build很方便,但是部署貌似不支持,就改用cc.net了,最后研究发现用TFS也是可以实现自动Build + 自动部署的,不仅WebSite,而且Window Service。

那么接下来说说如何来用TFS做自动部署。

使用TFS自动生成Build

使用Team Explorer的向导来创建一个生成定义很方便:

  • 首先设置生成代理:Build->Manage Build Agents
  • 创建生成定义:Build->New Build Definition

这个我想对于绝大部分人都不会有问题或障碍,网上相关文章也很多,也不在此赘述。

到TFS2008之后,就支持设置自动生成了,可以设置每天或者每次Check In生成一次最新的Build。但是当你满怀期望的去看生成的结果,却发现自动生成是很强大,但是部署还得手动去做,在我看来主要有以下几个问题:

  1. Build出来的结果是按照日期和版本来生成目录,所以不可能直接将Build的结果部署到网站中
  2. 对于Window Service程序,如果一个解决方案下有多个Window Service项目,所有项目的编译结果会放到根目录下,比较混乱,不利于分别部署。

基于以上的原因,我不得不寻找其他方案,于是开始使用cc.net来作为自动编译部署工具。

使用CC.Net来自动生成部署

CC.Net全名CruiseControl.Net,可以方便的来进行自动编译部署,相关使用说明推荐看看《[原创]如何用CruiseControl.Net来进行持续化集成》,使用CC.Net来部署WindowService和网站,我的CC.Net配置流程如下:

image

参考配置文件:

<cruisecontrol xmlns:cb="urn:ccnet.config.builder">

  <!-- This is your CruiseControl.NET Server Configuration file. Add your projects below! -->

  <!--

         <project name="MyFirstProject" />

     -->

 

  <project name="MyProject">

    <workingDirectory>d:\dailybuild</workingDirectory>

    <artifactDirectory>d:\dailybuild</artifactDirectory>

    <category>MyProject</category>

    <sourcecontrol type="vsts" autoGetSource="true"  applyLabel="false">

      <server>http://tfsserver:8080</server>

      <domain>mydomain</domain>

      <project>$/MyProject/</project>

      <workingDirectory>d:\dailybuild\MyProject</workingDirectory>

      <cleanCopy>true</cleanCopy>

    </sourcecontrol>

    <tasks>

      <msbuild>

        <executable>C:\WINDOWS\Microsoft.NET\Framework\v3.5\MSBuild.exe</executable>

        <workingDirectory>D:\dailybuild\MyProject\src</workingDirectory>

        <projectFile>mysolution.sln</projectFile>

        <logger>d:\dailybuild\Rodemeyer.MsBuildToCCnet.dll</logger>

        <buildArgs>/v:quiet /noconlog /p:Configuration=Debug</buildArgs>

        <targets>Build</targets>

        <timeout>900</timeout>

      </msbuild>

      <exec>

        <executable>iisreset</executable>

        <baseDirectory>c:\windows\system32\</baseDirectory>

        <buildArgs>/stop</buildArgs>

        <buildTimeoutSeconds>6000</buildTimeoutSeconds>

      </exec>

      <buildpublisher>

        <sourceDir>D:\dailybuild\MyProject\PrecompiledWeb</sourceDir>

        <publishDir>D:\MyProject\WebSites</publishDir>

        <useLabelSubDirectory>false</useLabelSubDirectory>

      </buildpublisher>

      <buildpublisher>

        <sourceDir>D:\dailybuild\MyProject\WebApps\MyComPlusProject\bin\Debug</sourceDir>

        <publishDir>D:\MyProject\Components</publishDir>

        <useLabelSubDirectory>false</useLabelSubDirectory>

      </buildpublisher>

     <!-- 在服务器上注册生成的Com+组件-->

      <exec>

        <executable>Regasm.exe</executable>

        <baseDirectory>C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\</baseDirectory>

        <buildArgs>D:\MyProject\Components\MyComPlus.dll /tlb:D:\MyProject\Components\MyComPlus.tlb /CodeBase</buildArgs>

        <buildTimeoutSeconds>6000</buildTimeoutSeconds>

      </exec>     

      <exec>

        <executable>net</executable>

        <baseDirectory>c:\windows\system32\</baseDirectory>

        <buildArgs>stop "My Service1"</buildArgs>

        <buildTimeoutSeconds>6000</buildTimeoutSeconds>

      </exec>

      <exec>

        <executable>net</executable>

        <baseDirectory>c:\windows\system32\</baseDirectory>

        <buildArgs>stop "My Service 2"</buildArgs>

        <buildTimeoutSeconds>6000</buildTimeoutSeconds>

      </exec>

      <buildpublisher>

        <sourceDir>D:\dailybuild\MyProject\Services\MyService1\bin\Debug</sourceDir>

        <publishDir>D:\MyProject\Services\MyService1</publishDir>

        <useLabelSubDirectory>false</useLabelSubDirectory>

      </buildpublisher>

      <buildpublisher>

        <sourceDir>D:\dailybuild\MyProject\Services\MyService2\bin\Debug</sourceDir>

          <publishDir>D:\MyProject\Services\MyService2</publishDir>

        <useLabelSubDirectory>false</useLabelSubDirectory>

      </buildpublisher>

      <exec>

        <executable>net</executable>

        <baseDirectory>c:\windows\system32\</baseDirectory>

        <buildArgs>start "My Service2"</buildArgs>

        <buildTimeoutSeconds>6000</buildTimeoutSeconds>

      </exec>

      <exec>

        <executable>net</executable>

        <baseDirectory>c:\windows\system32\</baseDirectory>

        <buildArgs>start "My Service1"</buildArgs>

        <buildTimeoutSeconds>6000</buildTimeoutSeconds>

      </exec>

      <exec>

        <executable>iisreset</executable>

        <baseDirectory>c:\windows\system32\</baseDirectory>

        <buildArgs>/start</buildArgs>

        <buildTimeoutSeconds>6000</buildTimeoutSeconds>

      </exec>

      <email from="tfsreport(a)openlab.net.cn" mailhost="mailserver" mailhostUsername="tfsreport" mailhostPassword="password"  includeDetails="TRUE">

        <users>

          <user name="宝玉" group="buildmaster" address="xxx(a)openlab.net.cn"/>       

        </users>

        <groups>

          <group name="developers" notification="change"/>

          <group name="buildmaster" notification="always"/>

        </groups>

      </email>

    </tasks>

    <triggers>

      <scheduleTrigger time="17:00" buildCondition="ForceBuild" name="Scheduled">

        <weekDays>

          <weekDay>Monday</weekDay>

          <weekDay>Tuesday</weekDay>

          <weekDay>Wednesday </weekDay>

          <weekDay>Thursday </weekDay>

          <weekDay>Friday</weekDay>

        </weekDays>

      </scheduleTrigger>

    </triggers>

    <labeller type="dateLabeller" />

  </project>

 

</cruisecontrol>

 

 

总的来说,CC.Net还是不错的,基本能满足自动部署要求,如果说有什么不足,那么主要就是:

  • 如果编译失败或者部署失败,那么会导致服务需要手动去启动,因为在部署配置中,首先会停止所有相关的服务,如果后续失败,那么会直接跳出,导致后面启动服务的命令不能执行,只能手动执行(也许有其他办法?)
  • 需要额外的客户端,不能直接通过Team Explorer来管理

对TFSBuild进行自定义

偶然发现TFS的Build定义是可以扩展的,在创建一个生成定义后,TFS默认会在源代码管理的TeamBuildTypes目录下创建一个生成定义目录,里面包含两个文件:“TFSBuild.proj”和“TFSBuild.rsp”。其中TFSBuild.proj是XML格式,包含了生成定义的配置,可以对起进行编辑扩展。MSDN中对它的格式有详细说明,感兴趣的朋友可以去了解一下:《Understanding Team Foundation Build Configuration Files》,对于我来说,要求很简单,就是在编译完成的时候,可以通过配置,来进行部署操作,要达到这个目的,就需要用到"target”元素了,通过这个元素,可以让TFSBuild在编译过程的特定时刻执行相应任务,例如:

<Project …> 

  <!--   在Build后,条件为编译没被中断  -->

  <Target Name="AfterDropBuild" Condition=" '$(BuildBreak)'!='true' "> 

    <!-- 删除ctemp目录下所有文件-->

    <Exec Command='del /f /s /q c:\temp\*'/> 

  </Target> 

</Project> 

 

从编译部署流程上来说,基本上和CC.Net类似,通过类似于cc.net配置的方式,我们可以加上相应的Target元素,让它完成停止服务、拷贝文件、启动服务等功能。但是一般我们的TFS是单独的服务器,TFS的Build是在TFS服务器(BuildServer默认都是安装在一起的)上,而需要部署的Application Server是在另外的服务器上,那么如果使用TFSBuild进行自动部署,就必须跨服务器进行部署,包括:停止目标应用服务器的相关服务、拷贝编译后文件到目标文件、启动目标应用服务器相关服务。

为什么CC.Net没有这个问题?一般来说CC.Net的Server直接是安装在待部署的目标服务器上,直接通过它去TFS中获取代码并本机编译部署,所以不需要跨服务器进行部署,网络结构图如下:

image

那么这个问题有两种解决方案:

1. 在需要部署的应用服务器上,安装TFS Build Server,并新建对应的Build Agent,将,这样网络结构基本类似于CC.Net,同样是本机编译本机部署,不需要进行跨服务器操作。但是这只是在理论层面觉得可行,但是我在目标服务器上安装Build Server都失败了,所以并没有验证是否可行,如果有朋友验证过这种方案,也请反馈,谢谢。

2. 借助PSTools这个第三方工具,PSTools是一个很有名的远程操作工具,借助它的PSExec可以在远程服务器中执行命令,当然需要有目标服务器的管理员权限的账号密码、开通目标服务器的139和445端口,借助它,我们就可以让TFS Build在目标服务器中执行停止和启动相关服务器的命令,从而完成部署。相关网络结构图如下:

image

借助PSTools配置TFSBuild进行自动部署

首先需要在TFS服务器上安装PSTools,PSTools下载地址:http://technet.microsoft.com/en-us/sysinternals/bb896649.aspx 

要小心被杀毒软件干掉哦,解压后放到TFS服务器的本地磁盘:

image

前面说了,TFS Build有一个问题是,如果一个解决方案中多个Window Service项目,在Build后,会一起放在Build目录的根目录,不方便分别部署,所以我一般的做法是每个Window Service一个单独的解决方案。

对于我们来说,一般需要自动部署的,有两个环境,一个是开发环境,一个是测试环境,两个环境的配置是不一样的,如果要让自动部署时可以自动选择对应的配置,我的做法是这样的:在解决方案中有多个配置文件,每个环境对应不同的配置文件,在对应环境生成Build的时候,选择对应的配置文件,如下图所示:

image

并且对于每一种部署环境,都会有一个对应的Build定义,不同环境相同服务的配置参数是略有不同的,如下图所示:

TeamBuild定义:
image  
SourceControl中对应的TeamBuildTypes定义

image

接下来我们可以来完成对Window Service和网站的自动部署,参考配置如下:

Windows Service更新配置

    <!--TFSBuild完成后执行-->

    <Target Name="AfterDropBuild">

        <!--定义变量,表示远程服务器地址-->

        <CreateProperty value="\\192.168.1.3">

            <Output TaskParameter="Value" PropertyName ="RemoteServer" />

        </CreateProperty>

        <!--定义变量,表示部署的环境,dev表示开发环境,qa表示测试环境-->

        <CreateProperty value="dev">

            <Output TaskParameter="Value" PropertyName ="DeployEnvironment" />

        </CreateProperty>

        <!--上面两个变量主要是为了方便开发环境和测试环境共用部署配置,只需要修改各自以上两个变量值即可,下面都一样-->

        <PropertyGroup>

            <!--要部署服务在目标服务器中的部署地址-->

            <MyDropLocation>$(RemoteServer)\Services\SSOService</MyDropLocation>

            <!--TFS Server中的本地PSTools文件路径-->

            <RemoteExecutePathFilename>d:\pstools\psexec.exe</RemoteExecutePathFilename>

        </PropertyGroup>

 

        <ItemGroup>

            <MyDropFiles Include="$(OutDir)\**\*.*"/>

        </ItemGroup>

 

        <!--第一步,借助PSExec远程停止目标服务器相关服务-->

        <Exec Command='$(RemoteExecutePathFilename) $(RemoteServer)   -i 0 -s -accepteula net stop "nop SSOService"'  IgnoreExitCode="true" ContinueOnError="true"/>

 

        <!--第二步,将编译后的文件复制替换到目标服务器的指定文件夹-->

        <Copy

            SourceFiles="@(MyDropFiles)"

            DestinationFiles="@(MyDropFiles->'$(MyDropLocation)\%(RecursiveDir)%(Filename)%(Extension)')"

            ContinueOnError="true"

        />

        <!--第三步,根据当前Build对应的环境选择相关配置文件,并删除多余的配置文件-->

        <Copy SourceFiles="$(MyDropLocation)\Configs\connectionStrings.$(DeployEnvironment).config"

          DestinationFiles="$(MyDropLocation)\Configs\connectionStrings.config"

          ContinueOnError="true" />

        <Delete Files="$(MyDropLocation)\Configs\connectionStrings.dev.config" ContinueOnError="true" />

        <Delete Files="$(MyDropLocation)\Configs\connectionStrings.qa.config" ContinueOnError="true" />

 

        <Copy SourceFiles="$(MyDropLocation)\Configs\ncc.$(DeployEnvironment).config"

          DestinationFiles="$(MyDropLocation)\Configs\ncc.config"

          ContinueOnError="true" />

        <Delete Files="$(MyDropLocation)\Configs\ncc.dev.config" ContinueOnError="true" />

        <Delete Files="$(MyDropLocation)\Configs\ncc.qa.config" ContinueOnError="true" />

 

        <!--第四步,借助PSExec远程启动目标服务器相关服务-->

        <Exec Command='$(RemoteExecutePathFilename) $(RemoteServer)   -i 0 -s -accepteula net start "nop SSOService"' IgnoreExitCode="true" ContinueOnError="true" />

 

    </Target>

 

站点更新配置

    <!--TFSBuild完成后执行-->

    <Target Name="AfterDropBuild">

        <!--定义变量,表示要部署的目标远程服务器地址-->

        <CreateProperty value="\\192.168.1.3">

            <Output TaskParameter="Value" PropertyName ="RemoteServer" />

        </CreateProperty>

        <!--定义变量,表示部署的环境,dev表示开发环境,qa表示测试环境-->

        <CreateProperty value="dev">

            <Output TaskParameter="Value" PropertyName ="DeployEnvironment" />

        </CreateProperty>

        <!--上面两个变量主要是为了方便开发环境和测试环境共用部署配置,只需要修改各自以上两个变量值即可,下面都一样-->

        <PropertyGroup>

            <!--要部署服务在目标服务器中的部署地址-->

            <MyDropLocation>$(RemoteServer)\WebSites</MyDropLocation>

            <!--TFS Server中的本地PSTools文件路径-->

            <RemoteExecutePathFilename>d:\pstools\psexec.exe</RemoteExecutePathFilename>

        </PropertyGroup>

        <!--编译后的站点目录-->

        <ItemGroup>

            <MyDropFiles Include="$(OutDir)_PublishedWebsites\**\*.*"/>

        </ItemGroup>

 

        <!--第一步,借助PSExec远程停止要部署的目标服务器IIS-->

        <Exec Command='$(RemoteExecutePathFilename) $(RemoteServer)  -i 0 -s -accepteula iisreset /stop'  IgnoreExitCode="true" ContinueOnError="true"/>

 

        <!--第二步,拷贝网站程序到要部署的目标服务器的站点目录-->

        <Copy

            SourceFiles="@(MyDropFiles)"

            DestinationFiles="@(MyDropFiles->'$(MyDropLocation)\%(RecursiveDir)%(Filename)%(Extension)')"

            ContinueOnError="true"

        />

 

        <!--第三步,根据当前Build对应的环境选择相关配置文件,并删除多余的配置文件-->

        <Copy SourceFiles="$(MyDropLocation)\SSO\appSettings.$(DeployEnvironment).config"

          DestinationFiles="$(MyDropLocation)\SSO\appSettings.config"

          ContinueOnError="true" />

        <Delete Files="$(MyDropLocation)\SSO\appSettings.dev.config" ContinueOnError="true" />

        <Delete Files="$(MyDropLocation)\SSO\appSettings.qa.config" ContinueOnError="true" />

 

        <!--第四步,借助PSExec远程启动要部署的目标服务器IIS-->

        <Exec Command='$(RemoteExecutePathFilename) $(RemoteServer)  -i 0 -s -accepteula iisreset /start' IgnoreExitCode="true" ContinueOnError="true" />

 

    </Target>

 

一个小Tips:有的项目需要特殊的Build参数,例如要允许Unsafe代码,那么需要修改MSBuild参数,这个时候,就要去修改对应的TFSBuild.rsp文件,例如

 

# This is a response file for MSBuild
# Add custom MSBuild command line options in this file
/v:quiet /noconlog /p:Configuration=Debug

 

后记

配置过程是比较麻烦一点,不知道TFS2010是否有所改进,不过配置好后,部署就成了一件非常方便的事情,现在对于我们来说,每天下午5点,TFS会自动去编译部署QA环境,编译完了发通知给相关人士(需要自己主动订阅TFS Alert),在开发环境中,有新的代码CheckIn,需要部署,只要手动选择相应的Build定义,让它开始Build就好了,不再需要去服务器上手动操作。

希望以上说明对各位有所帮助,如果有问题,也请反馈。

posted on 2009-09-26 18:07:37 by 宝玉  评论(0) 阅读(3458)

Powered by: Joycode.MVC引擎 0.5.2.0