使用 Octopus Deploy 进行 AWS 部署






4.55/5 (9投票s)
本文重点介绍了一种部署和更新运行在 AWS EC2 实例上的 .NET 应用程序的替代方法,并弥补了 AWSDeploy 独立工具的不足。
引言
在我之前的一篇文章中,我曾探讨过 Amazon AWSDeploy
工具的不足之处,以及在 AWS 云中部署 .NET 应用程序以支持自动修复和自动伸缩时遇到的问题;特别是要在一个服务器上预配多个网站和/或 Windows 服务。
本文重点介绍了一种更新运行在 EC2
实例上的 .NET 应用程序的替代方法,并进一步弥补了 AWSDeploy
独立工具的不足。通过这样做,它为推出更新提供了更强大的机制,并为更新过程提供了更高程度的灵活性和控制力。
丢失的更新信号
尽管 AWSDeploy
的基本限制可以克服,但 AWSDeploy
和其更新机制仍然存在固有问题。过去,这一点在免责声明中得到了明确说明,即 AWSDeploy
仅是一个尽力而为的工具,不应用于生产部署。但现在似乎已被删除,并且我已得到 Amazon 支持的保证,它现在已为生产做好准备。
这可能取决于您的要求,但这里有一些重要的背景信息。AWSDeploy
未为生产做好准备的原因是它在更新机制上存在一个问题,即到特定服务器的更新信号有时会丢失。结果是,自动伸缩组内的任何数量的服务器都可能无法更新,并继续运行旧代码。
事实上,直到今天仍然是这样,并且服务器数量的增加会加剧这个问题。AWS 实施的解决方案是服务器上运行的 HostManager
会轮询更新。最终所有服务器都应收到更新,但何时发生这种情况是您无法控制的。AWSDeploy
工具实际上会在第一次传递时立即报告成功,而不管最终状态如何。对于简单的纯代码部署来说,这可能是可以接受的,但当引入模式升级、数据迁移任务或其他破坏性更改时,这可能会有问题甚至导致灾难。
关于 ElasticBeanstalk
AWS 提供 ElasticBeanstalk
作为一种高级解决方案,用于预配应用程序和推送更新。过去,它使用与独立工具相同的机制,并遭受与 AWSDeploy
相同的更新症状。较新的 Beanstalk 容器使用不同的软件实现,并提供更可靠的更新机制。缺点是它通过充当黑盒来剥夺了部署过程的精细控制,并且阻止了自定义 AMI 的使用。
尽管 Beanstalk 提供了 DNS 级别的 BlueGreen
部署实现,但它并不总是适用于与数据迁移或数据库升级相关的实际 BlueGreen
场景。即使是 BlueGreen
的最佳实践也可能需要占位符页面或更大的事件协调,以防止数据丢失,特别是考虑到 DNS 的缓存方式。对于简单的只读应用程序或模式固定的应用程序,它非常适合部署 .NET 应用程序。在其他情况下,它可能过于严格,需要替代解决方案。
介绍 Octopus Deploy
Octopus Deploy 是一款专注于 .NET 的部署工具,自 2012 年以来一直存在,并以 API 优先的设计快速发展。它解决了与配置管理、部署协调和基础设施抽象相关的多个实际问题。这些方面使其适用于支持云中的自动伸缩和自动修复,并提供许多其他优势。
与 WebDeploy 或 WinRM 等竞争工具不同,您不需要预先了解您的服务器就可以自动化对它们进行操作。相反,可以自动化 Octopus Tentacle
的安装。它会注册到一个中央 Octopus
服务器,并将其标识为特定的 Octopus
定义的环境和角色。基于这些元数据,服务器随后可以同步且安全地推送任意数量的更新包/脚本,同时注入中央管理的配置数据。
Octopus
的发布流程基于一组用户定义的步骤,这些步骤构成一个版本化的发布构件。每个发布都可以包含任意数量的步骤,包括任意脚本或基于 Nuget 的负载。每个步骤按顺序执行,并且可以根据其指定的服务器角色限定目标服务器组。每个步骤中的更新会并行针对匹配的机器执行,但这可以限制为按顺序更新较小的服务器集群。这对于负载平衡服务器组的热部署非常有用。一旦发布被定义,它就可以跨环境提升,以实现可重复构建。
Chef
等其他工具也能够基于基于角色的分配推送更新,但与 AWSDeploy
一样,它们依赖于轮询代理,并在推送更新后自动执行。这对于预配基础设施和软件更新/配置更改非常有效,但对于生产 Web 应用程序来说不太适合,因为在生产 Web 应用程序中,通常需要对事件顺序进行更严格的控制。在这种情况下,通常需要跨多个服务器进行协同编排,例如,设置占位符页面、逐个将数据库模式升级、将服务器从负载均衡器中移除等。Octopus
支持轮询的 Tentacle,但与其他工具不同的是,代理仍在更大的编排发布流程中执行任务。轮询 Tentacle 的真正目的是绕过防火墙,而不是改变底层更新行为,后者继续保持高度集中的控制。
一旦安装了 Octopus Tentacle
,部署过程就完全从基础设施中抽象出来。相同的过程可以用于部署到内部环境或任何其他环境,无论是云中的还是物理托管的。这为您的应用程序和部署脚本提供了极大的可移植性,并避免了在更改托管供应商时需要重新设计。
Octopus
的另一个优势是它提供了丰富的配置管理功能,而这正是 AWSDeploy
所缺乏的。在部署过程中,Octopus
能够替换发布构件外部定义的 web.config 值,并支持标准的 .NET 转换文件。这些变量也提供给 PowerShell 部署前/后脚本,以增加灵活性。这意味着该解决方案支持一次构建构件、随处部署的最佳实践。
变量可以根据服务器角色/环境进行范围限定,并在需要时在特定的部署步骤中进行覆盖。由于 Octopus
服务器是 API 优先设计的,因此可以将环境变量导出并存储在 SVN 中,或作为持续交付管道的一部分进行导入。
引导 EC2
要将 Octopus Deploy
与 EC2
结合使用,需要引导实例安装 Octopus Tentacle
。为了支持自动修复和自动伸缩,诀窍在于保留预配期间提供的 Octopus
角色/环境分配。使用 EC2Config
、自定义 AMI 甚至 AWSDeploy
,可以在 AWS 中实现这一点。本文将侧重于使用 AWSDeploy
以简化操作,但这会限制 AMI 的选择,仅限于启用 HostManager
的实例。在此情况下,AWSDeploy
仅用于预配 Octopus Tentacle
,而不是用于其不太可靠的更新机制。
使用 EC2Config
和自定义预配脚本是更好的替代方案,但它实际上复制了 AWSDeploy
的行为,同时消除了对 HostManager
的依赖。在这两种方法中,角色/环境元数据都可以存储在 EC2 UserData
中,或者作为资源标签。这允许引导程序保留注册 Octopus Tentacle
所需的指定元数据,而无需将环境配置烘焙到引导程序构件中。
引导程序脚本所需的更敏感数据应烘焙到构件本身,例如 Octopus
端点/APIKey 等。这仍然允许单个引导程序预配多个环境和服务器角色。
作为安装 Octopus Tentacle
的一部分,自定义 PowerShell 脚本可以使用 Octopus
命令行工具将最新版本拉取到特定服务器。这可以同步执行,以确保应用程序在健康检查之前即可运行。
创建引导程序
引导程序是使用先前文章中讨论的技术创建的。它允许通过 AWSDeploy
部署非 Web 应用程序,以便运行脚本和安装软件。在这种情况下,它会读取当前 EC2
实例的资源标签,并使用这些标签来安装 Octopus Tentacle
,将其注册到 Octopus
服务器,并拉取最新版本。如果您不熟悉 AWSDeploy
独立工具,请下载 AWS Toolkit for Visual Studio 并查看位于 C:\Program Files (x86)\AWS Tools\Deployment Tool\Samples
的示例模板。
Octopus
的文档提供了用于自动化 Tentacle 安装的 PowerShell 示例。一个基本的部署后 PowerShell 脚本如下所示。
$octopusThumbPrint = "xxx" $octopusServer = "https://OctopusServer" $octopusAPIKey = "xxx" $octopusProject = "Sample Project" $scriptDir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent Set-location $scriptDir write-host "Install Tentacle" Start-Process -FilePath msiexec -ArgumentList /i, Octopus.Tentacle.2.0.13.1100-x64.msi, /Q, -wait write-host "Read meta-data" $webClient = new-object Net.WebClient $publicHostName = $webClient.DownloadString("http://169.254.169.254/latest/meta-data/public-hostname") $instanceId = $webClient.DownloadString("http://169.254.169.254/latest/meta-data/instance-id") $tags = (Get-EC2Instance $instanceId).RunningInstance.Tag write-host "Extract meta-data from tags" $environment = ($tags | ?{$_.key -eq "Environment"}).Value $role = ($tags | ?{$_.key -eq "Role"}).Value write-host "Open firewall port" netsh advfirewall firewall add rule name="Octopus Tentacle" dir=in action=allow protocol=TCP localport=10933 write-host "Configure Tentacle" set-alias Tentacle "C:\Program Files\Octopus Deploy\Tentacle\Tentacle.exe" Tentacle create-instance --instance "$instanceId" ` --config "C:\Octopus\Tentacle\Tentacle.config" ` --console Tentacle new-certificate --instance "$instanceId" ` --console Tentacle configure --instance "$instanceId" ` --home "C:\Octopus" ` --console Tentacle configure --instance "$instanceId" ` --app "C:\Applications" ` --console Tentacle configure --instance "$instanceId" ` --port "10933" ` --console Tentacle configure --instance "$instanceId" ` --trust $octopusThumbPrint --console Tentacle register-with --name="$instanceId" ` --instance="$instanceId" ` --server="$octopusServer" ` --apiKey="$octopusAPIKey" ` --environment="$environment" ` --publicHostname="$publicHostName" ` --comms-style TentaclePassive ` --role="$role" ` --console Tentacle service --instance "$instanceId" --install --start --console write-host "Get latest release for project $octopusProject and environment $environment" Set-Alias Octo "$scriptDir\Octo.exe" -scope Script $release = (Octo list-latestdeployments --project="$octopusProject" ` --server="$octopusServer" ` --apikey="$octopusAPIKey" ` --environment="$environment" | ` ?{$_.contains(" Version:") } | select-object -first 1) if (!$release) { write-warning "Current release for $octopusProject not found" return } write-host "Pull current release $release" $release = $release -replace "\s+Version:\s+", "" Octo deploy-release --project="$octopusProject" ` --server="$octopusServer" ` --apiKey="$octopusAPIKey" ` --releaseNumber="$release" ` --specificmachines="$instanceId" ` --deployto=$environment ` --waitfordeployment ` --deploymenttimeout="01:00:00"
将脚本与 Octo.exe
命令行工具和 Tentacle 安装程序一起放入一个名为 Bootstrapper 的目录中。创建一个虚拟参数文件,以便包与 AWSDeploy
兼容,如下所示。
<parameters>
<parameter name="IIS Web Application Name" defaultValue="Default Web Site/" tags="IisApp" />
</parameters>
创建一个 WebDeploy 清单文件,该文件将在安装打包文件后执行安装脚本。
manifest.xml<siteManifest>
<contentPath path="c:\Bootstrapper" />
<runCommand
path="%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe
-executionPolicy Unrestricted
-inputformat none
-outputformat text
-file c:\Bootstrapper\Install.ps1"
waitInterval="300000"
successReturnCodes="0x0" />
</siteManifest>
使用 DOS 命令提示符中的以下命令打包引导程序。
msdeploy.exe -verb:sync
-source:manifest=manifest.xml
-dest:package=bootstrapper.zip
-declareParamFile=parameters.xml
生成的引导程序文件应在预配 AWS 资源时与 AWSDeploy
工具和 Cloudformation
模板一起使用。
自定义 CloudFormation
AWSDeploy
提供了几个将 .NET 应用程序部署到云端的示例。这包括多个示例配置文件,这些配置文件引用存储在 AWS 服务器上的 Cloudformation
模板。这些模板可以下载并根据需要进行自定义,以供 AWSDeploy
使用。负载均衡模板是一个不错的起点。
下载模板并添加 2 个额外的参数用于 Octopus
角色和环境。
"Parameters" : {
"OctopusRole" : {
"Type" : "String",
"Description" : "The Octopus role to assign resources."
},
"OctopusEnvironment" : {
"Type" : "String",
"Description" : "The Octopus environment to assign resources."
},
...
}
接下来,扩展自动伸缩组以传递 Octopus
角色和环境作为资源标签。
"WebServerGroup" : {
"Type" : "AWS::AutoScaling::AutoScalingGroup",
"Properties" : {
"AvailabilityZones" : { "Fn::GetAZs" : "" },
"LaunchConfigurationName" : { "Ref" : "LaunchConfig" },
"MinSize" : { "Ref" : "MinSize" },
"MaxSize" : { "Ref" : "MaxSize" },
"Cooldown" : { "Ref" : "Cooldown" },
"LoadBalancerNames" : [ { "Ref" : "ElasticLoadBalancer" } ],
"Tags": [
{ "Key": "Environment", "Value": { "Ref": "OctopusEnvironment" }, "PropagateAtLaunch": true },
{ "Key": "Role", "Value": { "Ref": "OctopusRole" }, "PropagateAtLaunch": true }
]
}
}
部署引导程序
您现在可以通过自定义 Cloudformation
模板部署引导程序,并传递参数来创建新环境和服务器角色,具体取决于自定义 AWSDeploy
配置文件。在配置文件中设置新的 Cloudformation
参数,如下所示,并通过名称引用自定义 Cloudformation
模板和引导程序文件。
DeploymentPackage = Bootstrapper.zip
Template = OctopusLoadBalanced.template
Template.OctopusEnvironment = Production
Template.OctopusRole = Authoring
...
然后创建堆栈。
AWSDeploy.exe /wait OctopusLoadBalancedSample.txt
堆栈创建完成后,AMI 上运行的 AWSDeploy
HostManager
将自动将引导程序下载到每个 EC2
实例。自定义脚本将安装 Octopus Tentacle
并将每个服务器注册到中央 Octopus
服务器。然后,自定义脚本将同步推送最新版本到实例,以便在开始 ELB 健康检查之前安装所有应用程序。
Octopus Deploy
工具将后续更新推送到所有服务器,而无需进一步依赖 AWSDeploy
。
Octopus 的局限性
Octopus
服务器的一个局限性是它无法很好地处理终止的 EC2
实例,这会导致自动伸缩和自动修复出现问题。Octopus
会自动检查其环境中实例的健康状况,但如果发现不健康的实例,它会一直挂起。这可能会在未来得到改进,以自动忽略失败的实例。
在此期间,为了完全支持自动修复和自动伸缩,在推出任何 Octopus
发布之前,需要额外的逻辑。这是为了以编程方式清除 Octopus 中注册的任何孤立 EC2
实例,这可以通过简单的 PowerShell 脚本来实现。建议在任何发布之前自动运行此脚本,可以作为外部持续交付管道的一部分,甚至可以作为 Octopus
步骤本身。
以下展示了如何使用 .NET 的 Octopus
SDK 和 PowerShell 来实现这一点。您也可以直接使用 RESTful Octopus
API。
$environment = "Production"
$octopusServer = "https://octopusServer"
$octopusAPIKey = "xxx"
$scriptDir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
Add-Type -Path "$scriptDir\Lib\Sprache.dll"
Add-Type -Path "$scriptDir\Lib\Newtonsoft.Json.dll"
Add-Type -Path "$scriptDir\Lib\Octopus.Client.dll"
Add-Type -Path "$scriptDir\Lib\Octopus.Platform.dll"
$endpoint = new-object Octopus.Client.OctopusServerEndpoint "$octopusServer","$octopusAPIKey"
$repository = new-object Octopus.Client.OctopusRepository $endpoint
$envId = $repository.Environments.FindByName($environment)
$machines = $repository.Environments.GetMachines($envId)
$machines | %{
$instanceId = $_.Name
$instance = Get-EC2Instance -instance $instanceId
if (!$instance -or $instance.Instances.State.Name.Value -ne "running") {
write-Host "Removing EC2 instance $instanceId from $environment"
$repository.Client.Delete($_.Links["Self"])
}
}
摘要
本文展示了将 Octopus Deploy
与 Amazon EC2
结合使用的基本前提,以及如何通过相当通用的 Cloudformation
模板和引导程序来实现这一点。
在某些情况下,对于简单的只读 Web 应用程序或数据库模式更新不是问题的情况,ElasticBeanstalk
或 AWSDeploy
可能更适合。但请记住这些工具的更新机制的局限性以及它们缺乏控制力。通常,要开箱即用地使用这些工具,您需要打破最佳实践,并将环境配置烘焙到您的发布构件中。
使用此技术将 Octopus Deploy
与 Amazon EC2
结合使用,与直接使用 AWSDeploy
相比具有许多优势,并且重要的是,无论基础设施如何,都能为部署过程提供极大的灵活性和控制力。通过这种方式,可以实现复杂的 BlueGreen
部署,实现零停机/最小停机时间并遵循最佳实践。窗口服务和 Web 应用程序都可以轻松安装,并且可以根据需要通过角色重新分配 AWS 资源。本文几乎没有涉及 Octopus Deploy
管理应用程序部署的功能,因此,如果您不熟悉该工具,值得进一步阅读。