65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Octopus Deploy 进行 AWS 部署

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.55/5 (9投票s)

2014 年 2 月 2 日

CPOL

10分钟阅读

viewsIcon

41874

本文重点介绍了一种部署和更新运行在 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 DeployEC2 结合使用,需要引导实例安装 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 脚本如下所示。

install.ps1
$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.xml
<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 角色和环境。

OctopusLoadBalanced.template
"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 模板和引导程序文件。

OctopusLoadBalancedSample.txt
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 应用程序或数据库模式更新不是问题的情况,ElasticBeanstalkAWSDeploy 可能更适合。但请记住这些工具的更新机制的局限性以及它们缺乏控制力。通常,要开箱即用地使用这些工具,您需要打破最佳实践,并将环境配置烘焙到您的发布构件中。

使用此技术将 Octopus Deploy 与 Amazon EC2 结合使用,与直接使用 AWSDeploy 相比具有许多优势,并且重要的是,无论基础设施如何,都能为部署过程提供极大的灵活性和控制力。通过这种方式,可以实现复杂的 BlueGreen 部署,实现零停机/最小停机时间并遵循最佳实践。窗口服务和 Web 应用程序都可以轻松安装,并且可以根据需要通过角色重新分配 AWS 资源。本文几乎没有涉及 Octopus Deploy 管理应用程序部署的功能,因此,如果您不熟悉该工具,值得进一步阅读

© . All rights reserved.