持续交付至 Windows Azure 网站(或 IIS)
在本教程中,我们将介绍这些基础知识,看看我们如何使用 WebDeploy 从我们的 TeamCity 服务器将一个 ASP.NET MVC 项目部署到 IIS 或 Windows Azure Web Sites。
部署 ASP.NET 应用程序可以通过多种方式完成。 一些人在工作站上构建应用程序,然后将其通过 xcopy 复制到目标服务器。 有些人使用构建服务器,下载制品,更改配置文件,并将这些复制到服务器上。 当出现问题时,情况就变得棘手了:部署变得无法预测。
如果我们正在从那个工作站复制的过程中存在不必要的或旧的遗留程序集怎么办? 如果我们忘记更改 Web.config 中的数据库连接字符串,而搞砸了那个发布会怎么办呢? 如果发生这种情况,我们如何快速回滚? .NET 技术栈为此提供了解决方案:Configuration Transforms 和 WebDeploy。
配置转换

在部署过程中,通常需要进行的一项工作是更改配置内容。 更改数据库连接字符串,更改 ASP.NET 设置以不再向我们显示 YSOD's 等等。 请不要硬编码这些内容,或者根据服务器的主机名编写大型 if-else 语句来确定配置。 反而,您应该使用像配置转换这样的东西。
配置转换是描述对 Web.config进行“转换”的文件,基于正在使用的构建配置。 构建发布配置? 然后, Web.config 将根据 Web.Release.config 中描述的规则进行更新。 在进行 Release 构建时,让我们从我们的配置中移除 debug 属性:

在 Visual Studio 中创建的典型 ASP.NET 应用程序将包含用于调试和发布构建的转换,但可以通过创建新的构建配置(通过 生成 | 配置管理器菜单)并使用上下文菜单 添加配置转换来添加它们。
对于这个教程,我创建了两个新的配置:开发和生产,并生成了两个新的配置转换(即 Web.Development.config 和 Web.Production.config)。
要测试配置转换,我们可以使用上下文菜单 预览转换 ,这将准确显示生成的配置文件的外观。 以下是运行 Web.Release.config 转换的结果:

我们可以用这个来虚拟更改或添加我们想要更改的任何设置。 连接字符串、文件路径、应用设置、诊断配置等等。 这里有一些更多的 关于您可以如何使用配置转换的文档。
WebDeploy
在多个版本中,Visual Studio 提供了为任何 ASP.NET 应用程序创建所谓的“web 包”的选项,其中包含运行应用程序所需的所有文件。 页面、图片、CSS、JavaScript 和应用程序二进制文件都可以在此类软件包中导出。 甚至可以包含数据库和 IIS 设置!
这些部署包可以与 WebDeploy 一起使用,这是一个可以使用多种协议将包上传到服务器,并且可以应用我们前面谈到的配置转换的工具。
但在我们进行部署之前,让我们先看看我们如何创建一个部署包。 为了我们能了解包的格式,让我们首先手动调用 msbuild 来做这个。
手动创建部署包
部署包可以通过在项目上运行 Package 构建目标来创建,这可以很容易地使用 msbuild 完成:
该项目将被编译,同时会创建一个新的目录,其中包含我们的部署包。 还有更多!

ZIP 文件包含了我们的应用程序,其他文件是用于部署至目标机器的支持文件。 一个有趣的文件是 AcmeCompany.Portal.SetParameters.xml。 它包含了我们配置转换的结果,但允许覆盖这些值。 为什么? 嗯,构建部署包的人可能不知道连接字符串。 想象一下,只有管理员知道? 那个人可以通过这个文件,用正确的,最终的生产连接字符串来覆盖设置。
批处理文件 AcmeCompany.Portal.deploy.cmd 可以运行以部署到目标环境,但是......它是如何工作的呢?
WebDeploy 可以使用几种方法将部署包传输到远程服务器并更新配置。 可以通过使用 WebDeploy(一种基于 HTTPS 的协议)、FTP 或使用文件共享来完成。 对于第一种选项,目标 IIS 服务器上应启用一些 附加工具。 有充分的理由:WebDeploy服务器端工具会在站点之间进行实时同步,并从服务器删除多余的内容。 对于 FTP 或文件共享,不需要额外的工具。
在本教程的剩余部分,我们将介绍如何使用 WebDeploy 部署到 Windows Azure Web Sites,这与其在 IIS 上的工作方式完全相同。
步骤1:使用 Visual Studio 配置部署包 / WebDeploy
在上一步中,我们已经手动创建了一个部署包,我们也需要手动调用 WebDeploy。 不过,有一种更简单的方法:从 Visual Studio 同时配置部署包和 WebDeploy。
从需要部署的 Web 应用程序中,在项目节点上使用上下文菜单并点击 发布。 这将会打开一个对话框,我们可以在其中进行与部署相关的一些配置。 我们甚至可以创建多个部署配置文件,例如一个用于暂存,一个用于生产。
在第一步中,我们必须指定目标服务器的详细信息。 这通常是指向 WebDeploy 主机的 HTTPS 端点(如果选择了该选项,也可以是 FTP 或文件共享详情)。 在提供所有的细节后,我们可以验证这个连接是否有效。

密码需要指定吗? 不! 如果开发人员不了解此事,可以留下空白的凭据;我们将在之后从 TeamCity 部署时提供用户名和密码。
在下一步中,我们可以指定一些部署细节:是否应该从目标服务器中删除不在部署包中的文件? 应该预编译应用程序吗? 数据库连接字符串应该被覆盖吗? 当使用 Entity Framework Code First 时:是否应该执行迁移?

我们可以在此步骤后关闭向导,并将刚刚创建的发布设置保存到我们的项目中的一个文件:

这只是一个 XML 文件,如果需要,我们可以编辑它。 实际上,我们应该这样做,以便以后让我们的生活变得更加轻松。 打开 XML 文件并找到 <DesktopBuildPackageLocation> 元素。 当从命令行运行 WebDeploy 打包步骤(TeamCity 会有效地执行此操作)时,将找不到此位置。 要解决这个问题,更改元素值,并在路径前加上 _$(SolutionDir)_。 这就是这个元素可能的样子:
保存文件并确保它已添加到源代码控制中,以便我们在 TeamCity 上运行部署时可以使用它。
步骤2:在 TeamCity 上设置持续集成构建
我们希望为项目创建一个持续集成(CI)构建,可以在每次 VCS 提交时触发。 这个 CI 构建将为我们提供项目构建状态和健康状况的即时反馈。
TeamCity 允许我们根据 VCS URL 创建项目。 我们可以简单地输入 git 、Mercurial 、Subversion 等仓库的 URL:

此仓库将被分析并扫描构建步骤。 在我们的案例中,TeamCity 发现了一个 Visual Studio 2013 构建步骤,我们可以立即将其添加到我们的构建配置中:

添加建议的构建步骤,如果我们运行它,将产生一个有效的构建。 我们可以指定工件路径、版本号等等。 不过,还缺少一件事情! WebDeploy 部署包在哪里都找不到。 这样做的原因是我们正在构建 Rebuild 目标,它只是重建我们的项目,而不进行打包。 为了解决这个问题,我们可以在构建步骤中添加一些额外的命令行参数:

这些参数的作用如下:
/p:DeployOnBuild=True — 触发 WebDeploy 打包操作
/p:PublishProfile="Development"—— 指定打包时使用的部署配置文件
/p:ProfileTransformWebConfigEnabled=False—— 让我们详细讨论一下这个!
如果我们现在再次运行构建(已指定 artifacts\webdeploy\Development => Webdeploy 作为工件路径,这是我们早些时候在发布配置文件中配置的路径),我们将会看到一组熟悉的文件作为工件被发布:

现在让我们看看是否也能设置实际的部署!
步骤3:在 TeamCity 上设置部署
我们将在部署中使用的策略在 How To 中有所描述。 我们将为我们希望部署到的每个目标环境创建新的构建配置。 这些新的构建配置将会:
运行构建
执行部署
我们想要实现的是这样一个优雅的瀑布流程,我们可以将我们的构建从 CI 提升至开发,再到测试,最后是生产环境,或者在 CI 和生产环境之间的任何其他环境。

从 TeamCity 管理界面复制 CI 构建配置,并为其命名不同的名称,例如“部署到 Windows Azure Web Sites - Development”。 接下来,我们将对构建配置进行一些更改。
让我们首先确定构建依赖项。 在构建配置的 Dependencies 下,添加一个对我们的 CI 构建的新快照依赖项。 这将确保只有在相匹配的 CI 构建完全通过后,才可能进行部署,并且部署将基于我们在 CI 中构建的完全相同的 VCS 修订版本。

我们希望能够在整个部署链中识别出构建编号。 例如,如果 CI 构建 1.0.0 被部署到暂存环境,我们想要确认这实际上是版本 1.0.0,而不是某个中间版本。 在 通用设置 下,将构建号格式更改为使用与原始 CI 构建相同的版本号。 构建号的格式必须与 %\dep.WebAcmeCorpPortal\_ContinuousIntegration.build.number% 类似,复制来自 CI 构建的版本号。
我们的 CI 构建正在为我们的解决方案构建默认配置。 由于我们现在正在部署到一个不同的环境,并且我们已经为开发和生产创建了部署配置(和配置转换),让我们通过 Visual Studio 构建步骤改变构建配置。

现在来到了实际的部署步骤! 到目前为止,我们已经构建了我们的项目,但我们还没有真正做任何事情将其发布到实际的服务器上。 让我们通过添加一个基于命令行运行器的新构建步骤来改变这一点。 作为构建脚本,请输入以下内容:
那相当多,对吗? 让我们来浏览一下这个命令:
"C:\Program Files\IIS\Microsoft Web Deploy V3\msdeploy.exe"是 msdeploy.exe 的路径,该文件必须在构建代理上可用。-source:package='artifacts\WebDeploy\<target environment>\AcmeCompany.Portal.zip'指定我们要上传的部署包。-dest:auto,computerName="https://<windows azure web site web publish URL>:443/msdeploy.axd?site=<windows azure web site name>",userName="<deployment user name>",password="<deployment password>",authtype="Basic",includeAcls="False"指定部署服务的 URL。 对于 Windows Azure Web Sites,将会采用上述格式。 对于 IIS,这可能会有所不同(请参阅 Sayed Ibrahim Ashimi 的精彩文章关于 WebDeploy parameters)-verb:sync指示 WebDeploy 仅同步更改的文件(这将显著减少部署时间,因为并非每次部署都会上传所有文件)。-disableLink:AppPoolExtension -disableLink:ContentExtension -disableLink:CertificateExtension用于禁用远程计算机上的某些配置步骤。 这些对于您的环境可能会有所不同,具体请参见 MSDN 上的完整列表。-setParamFile:"msdeploy\parameters\<target environment>\AcmeCompany.Portal.SetParameters.xml"是一个重要的配置。 它指定了将在远程服务器上部署的 Web.config 文件中被替换的 WebDeploy 参数,例如连接字符串。 稍后会有更多关于该文件的信息。
传递给 msdeploy.exe 的参数文件必须以某种方式创建。 我们已经看到我们的 CI 构建的构建工件中包含了这个文件的副本,如果部署秘密(如生产数据库连接字符串)在源代码控制中可用,则可以使用它。 我们可能不希望这样,至少在所有开发人员都在使用的同一源代码控制根目录中不希望这样。
对于我的设置,我已经定制了 AcmeCompany.Portal.SetParameters.xml 文件,并在第二个 VCS 根目录中放置了针对不同目标环境的配置,这些配置只对 TeamCity 服务器可用。 这使得数据库连接字符串对除 TeamCity 之外的所有人保密。

我们可以重复这些步骤,为分期,为 QA ,为生产等创建构建配置。 由于我们想要在整个链上推广构建,这些配置应该都对前一个环境有快照依赖。
这可能是什么样子:3种不同的构建配置,代表部署到每个目标环境的不同版本:

步骤4:推广 CI 构建
现在我们已经做好了一切准备,让我们看看如何将构建从一个环境推广到另一个环境。 当我们导航到 CI 构建的构建结果时,可以使用 Actions 下拉菜单将我们的构建提升到下一个环境。

在为我们的构建配置配置了快照依赖项后,TeamCity 知道下一个环境应该是什么:开发环境。

这将触发一个新的构建,将版本 1.1.3 部署到开发环境中。 一旦验证通过,我们可以导航到该构建的结果,并将构建推送到下一个环境。
由于我们创建的快照依赖关系,我们现在还可以转到任何构建的 Dependencies 标签页,查看它已部署到的环境。 这是从开发中看到的版本 1.1.3。 我们可以看到,一个 CI 构建已经完成,开发环境的部署已经完成,而生产环境的部署仍在进行中:

结论
将部署想象为一系列的构建,那么从 TeamCity 进行部署并不难。 在本教程中,我们以 WebDeploy 为例,用作将构建工件传输到目标环境的一种手段,但也可以换用其他解决方案(如 xcopy)。
使用 VCS 标签,当特定部署发生时,也可以对源进行标签。 通过固定构建(可选通过 TeamCity API),我们可以确保构建清理不会删除某些构建和构建工件。