Octopus-博客中文翻译-一-

Octopus 博客中文翻译(一)

原文:Octopus Blog

协议:CC BY-NC-SA 4.0

八达通部署 1.1-八达通部署

原文:https://octopus.com/blog/1.1

今天我很高兴地宣布,Octopus Deploy1.1 版已经发布。自上一个 1.0 版本以来,已经做了很多改进,在这篇博客文章中,我想带您了解其中的一些改进。

计算机角色

这个版本中最大的变化是概念上的变化。以前,环境包含机器,步骤和变量指向这些机器。从 1.1 开始,一台机器将被标记为一个或多个“角色”,这些角色是步骤和变量所指向的。我在上一篇博客文章中详细描述了这一变化,因此我将使用这篇文章来了解该功能的工作原理。

编辑计算机时,现在可以输入角色列表:

Machine roles

您可以输入任何您喜欢的角色名称,您以前使用过的角色将使用自动完成(使用Select2控件)显示。角色可能是通用的(“web 服务器”),也可能是特定于您的应用程序的(“crm 批量导入器”)。

还要注意,上述机器属于两种不同的环境。以前,您必须复制一台机器,但现在可以轻松地在环境之间移动机器。

编辑步骤时,您现在将选择将接收包的角色:

Step with roles

但是,变量的作用域可以是角色或计算机:

Variable roles

这些变化带来了许多好处:

  1. 无需创建新版本即可添加或删除机器
  2. 机器可以在环境之间移动
  3. 现在可以更容易地将变量限定到一组计算机

自动触手注册也支持此功能——使用tentacle register-with时只需传递--role=my-role命令行参数。

项目组

本版本中引入的另一个新概念是“项目组”的概念。这为您的项目提供了一个嵌套级别,使它们能够更容易地分组和管理。

例如,在我的一个内部 Octopus Deploy 安装中,我有两个组-一个用于实际应用程序,另一个用于示例:

Project groups

项目设置页面用于配置项目组:

Set a project group

项目菜单也将使用组显示项目:

Projects in the menu

项目组有一个名称和一个环境列表,该组中的项目可以部署到这些环境中:

Project group editing

在这里,您可以看到我的“真正的应用程序”项目组只被允许部署到两个环境中——登台和生产。另一方面,我的“示例应用程序”项目组只被允许部署到一个临时环境中。这将影响仪表板的布局,仪表板现在分为多个组:

Dashboard

以及哪些环境出现在组内项目的部署屏幕中:

Deploy to environment

在未来,项目组将变得更加重要,因为它们将成为设置保留策略和大多数权限的地方。目前,它们是驯服仪表板的好方法。

最喜欢的项目

我们还引入了“收藏夹”的概念,以控制哪些项目出现在下拉菜单中。这是针对每个用户的设置。您可以通过点按星形图标将项目标记为个人收藏:

Favoriting a project

如果您设置了任何喜爱的项目,则只有您的喜爱的项目会出现在菜单中(如果您没有选择任何喜爱的项目,则前 20 个项目会出现):

Favorite projects in menu

升级

支持角色的变化需要打破旧的数据模型,因此必须引入一些变化。在安装新的 Octopus 服务器之前,请确保进行备份(您可以在 Octopus 门户网站的配置下的存储选项卡中进行备份)。然后,安装新的 Octopus 服务器后,使用 Octopus 管理工具服务器选项卡上的开始按钮。这将对您现有的项目进行一些更改,以启用角色支持。在这篇博客文章中,我写了更多关于将要做出的改变(我们选择了选项 3)

如您所见,1.1 是一个相当大的变化。我们要解决的下一个特性是让 Octopus 服务器和触须自己清理以节省磁盘空间,这个特性我们称之为“保留策略”,它可能会挂在项目组上。

请注意,由于版本中的许多变化,我们也发布了 Octo.exe 的新版本。

如果您对此升级有任何问题,请使用帮助台或跳到 Jabbr 。愉快的部署!

八达通 1.2 及保留政策-八达通部署

原文:https://octopus.com/blog/1.2-with-retention-policies

今天,我发布了Octopus Deploy 1.2,其中包括我上个月在博客上发布的保留策略功能,这也是我们Trello 板上投票最高的功能。在这篇文章中,我想演示该功能是如何工作的。

安装 1.2 之后,您将在配置区域下找到一个新选项卡,您可以在其中定义保留策略。

Retention policies

保留策略是在项目组级别设置的,这是 Octopus 1.1 中引入的功能。您可以在项目组设置对话框中选择项目组使用的保留策略:

Setting the retention policy

请注意,有一个名为“永远保留所有内容”的默认保留策略。如果删除保留策略,项目组将恢复为此“保留所有内容”策略。

保留策略有三个不同的部分:

Editing retention policies

当您部署到环境时,Tentacle 选项将生效。在本例中,一旦我将一个包部署到给定的机器上 4 次,该包(.nupkg 及其提取到的目录)也将被删除。

Retention policies on Tentacle

八达通选项按计划运行,很像自动健康检查任务。您可以在“任务”页面上看到输出:

Tasks page

任务的输出会告诉您由于保留策略而删除了哪些发布:

Retention policy output

当保留策略任务自动运行时,您也可以从保留策略页面手动运行它。您可能会发现,当您第一次升级到 1.2 时,您必须多次运行该任务,因为索引不会完全更新。

我希望你会发现这个新添加有用。

具有手动步骤、电子邮件步骤和 PowerShell 步骤的 Octopus 1.3-Octopus 部署

原文:https://octopus.com/blog/1.3

章鱼 1.3 刚刚发布。它包括许多错误修复,以及对最近发布的 RavenDB 2.0 的升级,感觉快得多。它还包括三种新类型的部署步骤:

Choices when adding steps

Octopus 仍然面向使用 NuGet 包的应用程序的自动部署,但是这三个新的步骤类型提供了很大的灵活性。

运行 PowerShell 脚本

这种步骤类型允许您在给定角色的所有触角上运行 PowerShell 片段。

Step for running a PowerShell snippet

注意,该脚本可以引用任何 Octopus 定义的变量作为 PowerShell 变量。在部署过程中,您将在部署日志中看到输出:

Output from PowerShell

发送电子邮件

这一步允许您发送电子邮件。您可以使用 Octopus 变量替换语法在您的电子邮件正文中引用 Octopus 变量。

Sending email

SMTP 服务器设置在配置菜单下单独配置:

SMTP settings

需要手动干预

虽然 Octopus 是一个自动化部署解决方案,但有些任务是无法自动化的。有时部署需要签署,无论是在开始还是结束。Octopus 中的手动步骤使这成为可能。我之前在博客中提到了我们的手动部署计划,现在这些计划已经实现了。

Manual step

当遇到该步骤时,部署将暂停:

Deployment paused

授权组中的用户将看到说明,然后可以决定部署是通过还是失败:

Deployment approval

部署任务实际上是暂停的,同时等待手动步骤完成。这意味着您可以重启 Octopus 服务器,并在一周后批准这一步骤。

Manual deployment output

我希望这些新增加的东西对你有用!

Octopus 1.6 改进了直接下载和发布功能- Octopus Deploy

原文:https://octopus.com/blog/1.6

我们刚刚发布了 Octopus Deploy 1.6,你可以从下载页面获得。这个版本将是最后一个主要版本 1。x 发布;从现在开始,我们将把注意力集中在 Octopus Deploy 2.0 上。我会在 2.0 中发布更多的想法,但是现在让我们看看 1.6 中的新内容。

从触须直接下载软件包

我在博客上写了我们上个月的计划:

第二个变化是软件包步骤设置中的一个选项,让 Tentacles 直接下载软件包。八达通不是下载软件包,而是指示触手下载软件包。下载会在部署步骤运行之前发生(同样,这样我们就不用在部署步骤之间等待复制包),但是它们会直接到达 NuGet 服务器。

创建包步骤时,您可以通过从下面的单选按钮中进行选择来加入:

Choosing who should download the package

改进的发布创建屏幕

我们还对创建发布屏幕实施了一些计划中的更改。

The new create release screen

代理服务系统

我们增加了 Octopus 和触手使用代理服务器的能力,包括使用基本和集成认证的代理。您可以在管理工具中指定代理服务器:

Specifying the proxy server

步进变量

以前,在包步骤中可以使用许多预定义的变量:

Octopus.Step.Name
Octopus.Step.Package.NuGetPackageId
Octopus.Step.Package.NuGetPackageVersion 

然而,这些变量在电子邮件或手动步骤中是不可用的,因为没有包。如果您想发送一封包含上一步的 NuGet 版本号的电子邮件,这就会引起问题。

现在,您可以通过索引来访问变量:

Octopus.Step[0].Name
Octopus.Step[0].Package.NuGetPackageId
Octopus.Step[0].Package.NuGetPackageVersion 

索引从 0 开始,并根据步骤的执行顺序进行编号。

1.6 还包括了许多其他的错误修正和改变,你可以在发布说明中读到更多关于的内容。愉快的部署!

实用 Kubernetes 部署的 10 大支柱- Octopus 部署

原文:https://octopus.com/blog/10-pillars-kubernetes-deployments

实用部署的 10 大支柱之后,我写了一本关于实用部署的 10 大支柱 Kubernetes 和 Octopus Deploy的电子书。

这 10 个支柱反映了现代 DevOps 团队的需求,他们总是被要求在更短的时间内交付更多。通过理解每个支柱的价值,并学习实际的实现,DevOps 团队可以正面迎接这些挑战。

谁将从电子书中受益?

这本电子书是构建从 Octopus 到 Kubernetes 的可重复部署管道的全面指南。

它面向那些希望构建可随以下需求增长而扩展的部署管道的人:

  • 参与部署的团队数量
  • 正在部署的应用程序的数量
  • 部署的频率
  • 对代码更快到达客户手中的期望

您需要什么来开始

为了完成书中的练习,你需要:

  • 库伯内特星团。这本书使用了一个托管在 Google Cloud 中的 Kubernetes 集群,但是除了依赖于 Google Cloud Kubernetes 集群公开的管理凭证的初始管理任务之外,任何 Kubernetes 集群都可以使用。
  • 访问管理凭据。
  • 章鱼的实例。如果你不是 Octopus 的用户,你可以注册一个免费试用来学习书中的例子。

本电子书假设您了解 pod、部署、服务、服务帐户、机密和配置图等概念。然而,书中创建的所有 Kubernetes 资源都提供了关于输入 Octopus 的值的详细说明,所以即使没有深入理解每个设置,也可以对部署过程有所了解。

下载电子书

愉快的部署!

数据库部署速度快 100 倍- Octopus 部署

原文:https://octopus.com/blog/100x-faster-db-deploys

在紧耦合系统中跳过不必要的模式比较

100x faster db deploys

问题:紧密耦合的系统

我经常被问到的一个问题是,一个服务器上的所有数据库是应该放在一个 repo/Octopus 部署项目中,还是应该放在不同的 repo/项目中。另一个相关的问题是数据库和应用程序是否应该进入同一个回购/项目。这些问题没有简单的答案。作为一名顾问,我以“视情况而定”开始我的回答。

接下来,我将询问他们是使用分布式源代码控制系统(git)还是集中式源代码控制系统(TFS、SVN 等)。).了解这一点很重要,这样我才能校准我的答案。Git 通常比一个巨大的整体更适合许多小的 repos,但是对于许多集中式源代码控制系统来说就不一样了。

然后我会问一个更重要的问题:“数据库/应用程序之间的耦合有多紧密?”如果他们要求澄清,我会问这样的问题:

  • 数据库之间有多少依赖关系?你有建筑图吗?(我会做梦,不是吗?)
  • 数据库可以独立构建吗,还是需要按照一定的顺序一起构建/部署?(我祈祷没有任何循环依赖!)
  • 单个工作项可能需要对多个数据库进行更改吗?
  • 一个数据库的错误更改会给另一个数据库带来问题吗?
  • 如果一个数据库因几个版本而与另一个数据库不同步,会有什么后果?
  • 当有人“部署数据库”时,他们通常是指单个数据库,还是可能需要将更改部署到多个数据库?

在理想的世界中,架构应该是松散耦合的。这将允许人们把数据库分割成单独的较小的回购,可以独立管理。粒度越大越好。虽然这可能会强加一些严格的体系结构规则并引入一些局部复杂性,但它将显著降低全局复杂性并降低每个部署的风险。这也可以大大减少与完成工作相关的技术和官僚挑战。

本质上,松散耦合的系统允许人们更加线性地扩展开发工作,而不是扩大开发工作,这将带来天文数字的、通常被低估的管理成本和挑战。扩大开发努力的尝试往往会陷入政治、延迟和问题的泥沼。阅读凤凰计划,大多数人会意识到他们已经为这一现象的经典案例研究工作。

人们可能会说,这很好,但对我没有帮助。我已经有一块巨石了。我没有构建它,也不能快速更改它。也许还有其他原因导致了紧密耦合的系统,这些原因太长或太复杂,无法在这里一一介绍。

在这种情况下,虽然我可能主张采取措施分离系统,但我承认这不太可能是一个快速或廉价的解决方案。与此同时,有效的源代码控制和部署很重要,即使它是一个整体。在这些情况下,最终可能会有巨大的源代码控制 repos 和部署项目,需要协调许多相关部分的部署。

症状:整体回购和部署项目

我有几个从事商业智能系统的客户。他们有多达十几个相互读取的数据库。第一个通常负责从各种来源加载原始源数据。这通常是高度规范化的,并针对存储和耐用性进行了优化。接下来是各种中间数据库,在这些数据库中,数据被逐渐清理和转换,直到最终出现在各种数据仓库和数据集市中,这些数据仓库和数据集市为分析进行了优化。

通常,数据库可以按一定的顺序部署。数据仓库从中间数据库读取数据,这些中间数据库从源数据库读取数据。因此,理论上,我们可以首先部署数据源,然后按顺序部署中间数据库,最后部署数据仓库。

然而,现实世界并不总是那么简单。出于各种原因,有一些依赖项不适合该模型,重构它们会非常困难。虽然我们通常可以按照指定的顺序进行部署,但是有些跨数据库的依赖关系有时意味着顺序需要改变,而 Octopus Deploy 项目不够智能,无法提前发现这一点。

一位客户使用了一点黑客手段来解决这个问题。他们部署的最后一步是Deploy Release步骤,如果任何数据库部署步骤由于依赖关系中断而失败,该步骤将重新运行部署。该过程可能会根据数据库的数量重新运行部署(最大重新运行次数由输出变量控制)。只要在每次迭代中至少有一个数据库被成功部署,Octopus 就会一直尝试,直到所有数据库都被部署,因此,如果数据库的部署有任何顺序,Octopus 最终都会找到。很丑但是很管用。

最大的实际问题是这需要多长时间。客户使用基于状态的部署流程。这意味着每次部署数据库时,数据库比较软件(Redgate 或 SSDT)都会执行完整的比较。对于每个数据库,这通常需要一两分钟的时间,但时间会有所不同。对于最大的数据库,可能需要 5 分钟以上。12 个数据库乘以 12 次尝试,通常可以累加起来。

但比那更糟。这个 BI 系统不是一个内部系统,而是我的客户向他们的客户销售的一项服务。他们为每个客户维护一个数据库实例。当他们部署到生产环境时,他们不只是部署一次,而是部署多次,而且通常是在严格的部署窗口内。如果部署时间太长,他们错过了机会,客户会不高兴的。一些客户是批量部署的,而另一些客户有自己独特的合同,需要更复杂、更不频繁的部署。较不频繁的部署会导致较大的部署,这很可能需要多次重新运行。

持续时间、风险和复杂性在多个轴上成倍增长。

团队也受到资源的限制。大量的处理工作被迫通过少数工人完成,这通常是瓶颈。这是一场完美的风暴。我提到过单片系统真的很可怕吗?

真正令人沮丧的是,虽然我们可能会进行数百次数据库比较,但绝大多数都是浪费时间,因为我们不知道实际上什么都没有改变。即使部署第一次成功,也可能只有一两个数据库被更新,但是所有的数据库都会按顺序进行比较。只需一两分钟的部署可能需要几个小时。

如何治疗这些症状

有一种观点认为,即使没有任何变化,进行比较仍然是有价值的。它保护你不受漂移的影响。通过强制每次都从源代码控制中重新部署所有数据库,它确保了源代码控制的真实性,并减少了由于生产中的意外变化而导致失败的机会。

总的来说,我同意这个原则,但是对于我的客户来说,进行所有这些比较的成本让他们不堪重负。如果在第一次尝试中就已经成功部署了数据库,那么在第二次或第三次尝试中重新部署数据库也是非常困难的。虽然重新部署所有数据库可能是有价值的,但是短时间的部署也是有价值的,所以最终,人们需要做出权衡。

我向我的客户建议,他们应该设计他们的部署过程,以便只在软件包编号增加时才部署数据库。这意味着两件事:

  1. 我们需要改变构建过程,以确保新的 NuGet 包只在 DB 模式实际发生变化时才被创建。(所有数据库都在一个 git 回购中。构建过程最初为每次提交构建并打包所有数据库,验证所有依赖关系。然而,这导致了非常长的构建时间(1 小时以上)。只构建已经更新的数据库并不像您想象的那样简单,因为由于依赖关系,当两个数据库同时更新时,它们需要以正确的顺序构建。我去年在我的个人博客上写了更多关于我们如何解决这个问题的内容:http://workingwithdevs . com/azure-devo PS-services-API-powershell-hosted-build-agents/
  2. 我们需要改变我们的部署过程来识别当前的包是否已经被部署。这也比你想象的要难,我很感谢 Bob Walker 花时间与我讨论各种选项和陷阱。在这篇博文的剩余部分,我将重点关注这一部分。

起初,我低估了这项任务的复杂性。我打算用章鱼。触手. previous installation . package version系统变量确定之前部署的包。我可以编写一个简单的 PowerShell 脚本来比较以前的包号和当前的包号,如果它们相同,我可以跳过部署。

然而,这是有问题的。如果之前的部署失败了怎么办?如果已经将包部署到了触手上,但是后续的数据库模式比较步骤(从包中读取文件)还没有执行,该怎么办?如果我在池中的一个工人上运行任务会怎么样?如果我在一个动态工作者上运行这个会怎么样?在我意识到这一点之前,我进行了比我最初预期的更多的 API 调用,代码开始看起来复杂得令人恼火。

经过一番思考,我决定从基于迁移的部署工具那里借用一个技巧。我在每个目标数据库上创建了 __DeployLog 表。在每次部署之后,我都将包和版本号记录到该表中,同时记录时间戳、用户 ID、部署状态和任何错误消息。

由于以前部署的数据现在安全地存储在数据库本身上,所以可以用几个快速的 SQL 命令来完成所有长的数据库部署步骤,以验证当前版本中的包是否已经部署到目标数据库。这些额外的查询会稍微增加部署的总持续时间,但是每个跳过的部署都会显著减少总部署时间。因此,对于具有许多基于状态的数据库部署步骤的项目,最终结果可能是大大减少部署时间。对于我的客户来说,这将常规部署时间减少了大约 10 倍,并且由于重新运行问题,最具挑战性的部署减少了大约 100 倍。

结果是,团队可以多次尝试部署,中间有足够的时间来调查任何问题,而不是因为一次漫长的部署尝试而错过生产部署窗口。在开发和测试领域,生产力得到了巨大的提升。开发人员可以在测试服务器上运行部署,并在几分钟内看到结果,而不是几个小时。除了显著改善开发人员的反馈循环之外,它还显著减少了共享环境中的资源占用问题。

最重要的是,事实证明 __DeployLog 表很受内部和客户操作人员的欢迎,他们在数据库中有一个简洁可靠的审计日志。

__DeployLog

代码

要在您自己的部署项目中做同样的事情,您需要在流程开始时使用类似这样的代码来读取 __DeployLog,以确定是否有必要部署数据库。您可以将它作为每个数据库的单独部署步骤运行,也可以将其添加到部署数据库的现有脚本的顶部。

注意,在脚本的顶部,有几个变量需要声明。这个任务留给了用户。如果可以的话,我推荐使用 Octopus 变量,而不是将值硬编码到脚本中。

还要注意最后一行:

Set-OctopusVariable -name "Deploy:$DLM_ServerInstance-$DLM_Database" -value $deployRequired 

这段代码假设脚本作为一个独立于现有数据库部署步骤的部署步骤运行,并设置一个输出变量,该变量决定是否应该执行数据库部署步骤。

如果您将此作为一个单独的步骤运行,则不需要修改此代码。但是,如果您已经将代码复制到了现有数据库部署脚本的顶部,那么您会希望删除上面的行,而将数据库代码移动到如下所示的 if 语句中:

If ($deployRequired){
  # put your existing db deploy code here
}
Else {
  Write-Output “Skipping database deployment.”
} 

假设您已经创建了一个单独的步骤来读取 __DeployLog,那么您现有的数据库部署步骤应该更新为使用下面的变量表达式作为运行条件。这会读取输出变量,并使用它来决定是否执行数据库部署:

#{if Octopus.Action[Read \__DeployLog].Output.Deploy:sql01-db== "True"}true#{/if} 

Run condition

记住用您自己的 SQL Server 实例和数据库名称替换“sql01”和“db”。

在您的数据库部署之后,您需要添加以下脚本来用包号和部署状态更新 __DeployLog。如果您只是将代码复制到数据库部署脚本中,那么您会希望将其包装到相同的 If 条件中。如果您将它作为一个单独的步骤运行,您将希望使用与上面相同的运行条件。除非实际执行了数据库部署,否则不希望更新 __DeployLog。

最后,为了使这一切尽可能简单,我刚刚向社区图书馆发布了几个 Octopus 部署步骤模板:

Library Step Templates

最终的过程可能如下所示:

Full deployment process

现在,如果软件包编号自上次成功部署后没有更改,则不会重新部署:

Skipped deployment

处理潜在问题

我充分意识到这篇博文只是治标不治本。而且有副作用要注意。

数据库漂移可能会持续更长时间才被发现,这并不理想。也有可能一些依赖关系会被破坏,因为我们不会定期重建/部署所有的数据库。考虑在一些测试或试运行环境中运行完整的构建和端到端的部署和集成测试是有益的,也许是每晚一次,以确保没有遗漏任何依赖项。

还有不直观的问题。由于这是一种有点不寻常和复杂的处理部署的方式,所以维护这个过程会有更大的认知和操作成本。这增加了误解的可能性,从而可能导致问题。

然而,这里真正的问题是数据库与循环依赖紧密耦合。这是一个更难解决、更昂贵的问题,我不确定我能通过一篇博文解决这个问题。

话又说回来,我不相信任何一个读到这篇文章的人都有一个完美的系统。对我们大多数人来说,这是一种权衡、错误和“这在当时看来是个好主意”的过程。对于那些只是试图保持服务器运行,而没有支持或投资来“正常运行”的人,我向你们致敬,我希望这是有用的。


自 2010 年以来,Alex Yates 一直在帮助组织将 DevOps 原则应用于他们的数据。他最引以为豪的是帮助 Skyscanner 开发了一天 95 次部署的能力,并支持联合国项目服务办公室的发布流程。亚历克斯与除南极洲以外的各大洲的客户都有过合作——所以他渴望见到任何研究企鹅的人。

作为一名热心的社区成员,他共同组织了数据接力,是【www.SpeakingMentors.com】的创始人,并自 2017 年以来被公认为微软数据平台 MVP

Alex 是官方 Octopus Deploy 合作伙伴 DLM 顾问的创始人。他喜欢为那些希望通过改进 IT 和数据库交付实践来实现更好业务成果的客户提供指导、辅导、培训和咨询。

如果你想和亚历克斯一起工作,请发电子邮件:enquiries@dlmconsultants.com

基于角色的访问控制演示- Octopus 部署

原文:https://octopus.com/blog/k8s-training/14-rbac-demo

这篇文章是我们 Kubernetes 培训系列的第 14 篇,为 DevOps 工程师提供了关于 Docker、Kubernetes 和 Octopus 的介绍。

此视频演示了如何创建一个 Octopus 目标,该目标使用服务帐户令牌向集群进行身份验证。

如果您还没有八达通帐户,您可以开始免费试用。

您可以使用下面的链接完成该系列。

示例代码

RBAC 资源公司

这是复合 YAML 文档,包含用于将服务帐户限制到单个名称空间的 RBAC 资源:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: octopub-deployer
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: octopub-deployer-role
rules:
- apiGroups: ["", "extensions", "apps", "networking.k8s.io"]
  resources: ["deployments", "replicasets", "pods", "services", "ingresses", "secrets", "configmaps"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
  resources: ["namespaces"]
  verbs: ["get"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: octopub-deployer-rolebinding
subjects:
- kind: ServiceAccount
  name: octopub-deployer
  apiGroup: ""
roleRef:
  kind: Role
  name: octopub-deployer-role
  apiGroup: ""
---
apiVersion: v1
kind: Secret
type: kubernetes.io/service-account-token
metadata:
  name: octopub-deployer-secret
  annotations:
    kubernetes.io/service-account.name: "octopub-deployer" 

目标创建脚本

该脚本通过从当前 Kubernetes 上下文中提取密码和 Kubernetes URL 来创建新的 Octopus 令牌帐户和目标:

SERVER=$(kubectl config view -o json | jq -r '.clusters[0].cluster.server')
TOKEN=$(kubectl get secret octopub-deployer-secret -n octopub -o json | jq -r '.data.token' | base64 -d)

echo "##octopus[create-tokenaccount \
  name=\"$(encode_servicemessagevalue "Octopub #{Octopus.Environment.Name}")\" \
  token=\"$(encode_servicemessagevalue "${TOKEN}")\" \
  updateIfExisting=\"$(encode_servicemessagevalue 'True')\"]"

echo "##octopus[create-kubernetestarget \
  name=\"$(encode_servicemessagevalue "Octopub #{Octopus.Environment.Name}")\" \
  octopusRoles=\"$(encode_servicemessagevalue 'Octopub')\" \
  clusterUrl=\"$(encode_servicemessagevalue "${SERVER}")\" \
  octopusAccountIdOrName=\"$(encode_servicemessagevalue "Octopub #{Octopus.Environment.Name}")\" \
  namespace=\"$(encode_servicemessagevalue "octopub")\" \
  octopusDefaultWorkerPoolIdOrName=\"$(encode_servicemessagevalue "Laptop")\" \
  updateIfExisting=\"$(encode_servicemessagevalue 'True')\" \
  skipTlsVerification=\"$(encode_servicemessagevalue 'True')\"]" 

资源

了解更多信息

如果您想在 AWS 平台(如 EKS 和 ECS)上构建和部署容器化的应用程序,请尝试使用 Octopus Workflow Builder 。构建器使用 GitHub Actions 工作流构建的示例应用程序填充 GitHub 存储库,并使用示例部署项目配置托管的 Octopus 实例,这些项目展示了最佳实践,如漏洞扫描和基础架构代码(IaC)。

愉快的部署!

Octopus Deploy 2.0 已经发布!-章鱼部署

原文:https://octopus.com/blog/2.0

昨天,我们发货了 章鱼部署 2.0 。这是 Octopus 2.0 的正式、非测试版、公开、发布到网络的版本。呜哇!

The Octopus 2.0 dashboard

Octopus 2.0 的开发工作始于去年 5 月。你能相信吗?我们并不只是添加一些功能和改变配色方案,而是着手开发一个真正的 2.0 版本。我们在制作 Octopus 1.0 的过程中学到了很多东西,我们想对产品的架构和一些核心概念做一些根本性的改变。

REST API

影响最大的变化是决定开发一个全面的 REST API。Octopus 1.0 的用户界面是在 ASP.NET MVC 中构建的。对于 2.0,我们删除了几乎所有的代码——控制器和视图——而是从头构建了一个 REST API。然后,我们使用 Angular JS 在该 API 之上构建了 UI。现在,你可以在 UI 中做的任何事情都可以通过 API 来完成。事实上,很长一段时间 2.0 代码根本没有 UI;我们构建了一套端到端运行的 API 测试,使用 REST API 执行 Octopus 的所有功能。

投票触角

另一个巨大的变化是章鱼和触须之间的通讯栈。在 1.0 版本中,我们使用 WCF——触须监听,章鱼连接。

Choosing a Tentacle communication mode

在 2.0 中,我们现在支持触须在监听轮询模式下。为了实现这一点,我们开发了一个基于消息和参与者的通信栈,在传输层使用 HTTP 和 SSL 来交换消息。对于与服务器网络的双向安全通信来说,这是一个非常强大的机制,我希望以后能更多地使用这个特性。

更轻松的 Windows 服务和 ASP.NET/IIS 部署

我们添加了一些新的约定和特性来处理 Windows 服务IIS/ASP。NET 应用程序部署

加密的数据库和变量

章鱼数据库现在被加密了,我们也增加了对 T2 加密变量的支持。如果您将连接字符串或密码存储在变量中,现在可以用一种安全的方式来实现。

同时,我们也让在项目之间共享变量成为可能。

引导失败

当出现问题时,您现在可以选择重试或跳过失败的步骤,而不是简单地部署失败。

滚动部署

滚动部署让您能够在一台服务器上运行一系列步骤,然后在另一台服务器上开始执行步骤。这使得部署到停机时间有限的 web 服务器群这样的场景变得更加容易。

Rolling deployments

新的安全模式

我们重新审视了我们的权限模型,并试图构建一个同样安全,但更有用的东西。团队是一个新概念,它允许您指定一组用户及其权限,范围是环境和/或项目。

可配置仪表板

是的,您现在终于可以更改显示在仪表板上的内容

失败步骤,以及在包下载之前运行的步骤

我们对管理部署步骤的方式进行了一些大的改变。现在,您可以在下载软件包之前运行 PowerShell 和手动步骤(例如,如果您需要建立 VPN),或者指定仅在失败时运行的步骤(例如,发送电子邮件)。

变量编辑器改进

在 1.0 中,一个变量一次只能作用于一个环境、机器、角色或步骤。现在,变量可以同时作用于多个级别。这大大减少了所需变量的数量。我们还对 UX 做了一些改进,比如一次编辑多个变量,一次删除多个变量,以及一系列其他的 UI 更改。

Variable scoped to multiple environments

摘要

因为有太多的东西被改变了,我们也做了很多其他的改进。安装程序改头换面了,发布创建页面有了一些新特性,我们在项目列表页面添加了图表来显示活动,还有很多其他的东西。

如果你目前使用的是 1.6,你绝对应该升级。我希望你喜欢使用章鱼 2.0 就像我们喜欢构建它一样!

Octopus 2.1 的新特性- Octopus 部署

原文:https://octopus.com/blog/2.1

今天我们发布了章鱼部署 2.1 的预览版。Octopus 2.0 仅在一周前发布,在那一周,我认为我们已经做了很多。让我们看看 2.1 有什么新功能。

内置的 NuGet 存储库

Octopus 现在附带了一个内置的 NuGet 存储库,你可以将应用程序包推送到这个存储库。这个知识库是基于优秀的 NuGet。Lucene ,使它成为比使用文件共享更快的选择。该存储库是只写的;你可以使用 NuGet.exe推送 NuGet 包给它,但是你不能像标准的 NuGet feed 一样查询它。

Using the internal feed

当然,您可以继续使用其他外部 NuGet 包存储库;当你正在设置一个 Octopus 服务器,并且没有你想要使用的现有存储库时,这个存储库只是提供了一个很好的缺省值。

详细审计

为了提高责任性和透明度,审计日志现在在查看文档更改时会显示不同的更改内容。例如,这里我们可以看到是 Bob 将测试环境重命名为 Staging。淘气的鲍勃!

Detailed auditing in Octopus 2.0

(有趣的是,自从 Octopus 2.0 发布以来,我们实际上一直在捕捉这些信息,但直到现在我们还没有一个 UI 来显示活动细节)

变量编辑器改进

我们有很多人要求这样做:你现在可以在变量编辑器中排序和过滤变量。

Filtering and sorting variables

作为客人登录

有时你可能希望允许人们在没有用户帐户的情况下登录 Octopus。我们引入了作为访客登录的功能。

Sign in as a guest

当然,默认情况下是禁用的。您可以从 Octopus Manager 中启用它:

Enabling guest access

来宾用户始终是只读的。您可以通过为他们分配角色来限制他们可以查看的环境和项目。

多实例管理

你知道吗,你可以在一台机器上运行多个 Octopus 服务器和触手,每个都有自己的数据库、配置、Windows 服务等等。以前只能通过命令行管理这些实例,但现在我们可以直接从 Octopus 和触手管理器管理实例。

Managing multiple instances

其他变化

2.1 还包括许多小的 bug 修复和微小的增强,比如克隆项目的能力。阅读完整的发行说明看看还有什么变化。愉快的部署!

Octopus 2.2 的新特性- Octopus 部署

原文:https://octopus.com/blog/2.2

我们刚刚发布了章鱼部署 2.2 的预发布版本。这个版本包含了许多错误修复和小的增强,以及一些新的功能。

脚本控制台

对于管理员来说,2.2 引入了一个新的脚本控制台,它可以用来使用 PowerShell 执行特定的管理任务。这使得在多台机器上运行 PowerShell 代码片段变得容易,并且可以在一个地方查看结果。

Using the Octopus Script Console to execute one-off admin tasks

当脚本运行时,所有服务器的所有输出都显示在任务输出中:

Output from the script

以这种方式运行的脚本也会出现在审核日志中,您可以随时查看运行的脚本。我认为这是使用远程桌面或 PowerShell Remoting 来远程执行任务的一个很好的替代方法。

在 Octopus 中定义部署脚本

大多数时候,当您需要在 Octopus 中执行自定义操作时,您可以使用嵌入在 NuGet 包中的 PowerShell 脚本来执行它们。我们还支持将 PowerShell 脚本作为独立步骤运行的能力。

但是有时您需要在包部署期间运行一个脚本(这样您就有了所有可用的变量),但是不需要将它们嵌入到 NuGet 包中。现在,您可以使用包装步骤中的新功能来实现这一点:

Scripts feature

启用该特性后,您的部署脚本现在可以在 Octopus UI 中编辑,而不是在 NuGet 包中编辑:

Editing PowerShell scripts

注意,通常我们仍然建议将定制的部署逻辑放在 NuGet 包中;这个特性实际上只适用于少数不可能实现这一点的场景。

全屏 PowerShell 编辑

在上面的屏幕截图中,您可能已经注意到,脚本编辑控件现在显示了一个按钮,使 PowerShell 编辑器全屏显示。不再编辑微小的文本区域!

IIS 绑定可以是...束缚

另一个变化是,当使用自动 IIS 站点创建功能时,您现在可以在为站点定义的 HTTP/HTTPS 绑定上绑定字段。

Binding the bindings

错误修复

相当多的小错误和改进也在这个版本中出现了——详情请查看版本说明。愉快的部署!

Octopus 2.3 的新特性- Octopus 部署

原文:https://octopus.com/blog/2.3

我们刚刚发布了一个预发布的章鱼部署 2.3 。自从 2.0 发布以来,我们已经习惯了,每隔几周就发布新的版本。如果你看看我们的发布历史,你可以看到我们上一次发布是在两周前,章鱼 2.2

以下是 2.3 的亮点。我想你会同意我们在这么短的时间内一直很忙!

部署时提示变量

有时,在对部署进行排队时,您需要向 Octopus 提供额外的信息。提示变量允许您定义变量以及标签和帮助文本,其值将由用户在创建部署时提供。

Adding a prompted variable

当您单击提示链接时,您将能够配置提示的详细信息:

Configuring the prompt settings

在部署时,提示将出现在“创建部署”页面上。如果您为变量提供了默认值,这将是文本框中的默认值:

Prompt values appear at deployment creation time

请注意,提示变量可以像其他变量一样确定作用域;因此,您可以对一个环境使用固定值,对生产部署使用提示值。提示变量也可以标记为敏感,在这种情况下,将出现一个密码框。

提示变量可以像任何其他变量一样在脚本和配置中使用:

Referencing prompted variables

敏感的提示变量会像其他变量一样被屏蔽。

Task output when using prompted variables

部署到特定机器

有时,您可能会向环境中添加一台新机器,并且您需要向该机器重新部署一个版本,但是不希望影响环境中的其他机器。在 Octopus 2.3 中,您现在可以选择要部署到的特定机器:

Deploying to specific machines

任务输出“有趣”模式

当任务正在运行时,您必须连续单击“Expand All”才能在添加新的日志节点时看到输出(与按顺序记录输出的构建服务器不同,Octopus 并行执行许多事情,因此日志输出是分层的,多个节点同时生成日志消息)。

我们现在已经使“全部展开/错误/无”链接“有粘性”——如果您全部展开,并且添加了新节点,它们也会自动展开。我们还创建了一个新模式,称为有趣模式,它可以自动扩展正在运行或已经失败的节点。这是默认的模式,它带来了很好的体验——当您查看任务输出时,您会自动看到您可能最感兴趣的内容。

Interesting mode

审核日志过滤

审计日志现在可以按人员、项目或日期范围过滤:

Filtering the audit log

包 ID 和源中的自定义表达式

这个更容易用图片解释。现在,您可以像这样定义部署过程中的步骤:

Defining a step with bound package details

像这样的变量:

Variables for package binding

这仍然有效:

Creating a release

这也是:

Viewing the release

这使得某些工作流变得更加容易,比如针对不同的环境使用不同的提要,但是我稍后会在博客中对此进行介绍。

为什么我的部署在排队?

有时,当您执行部署时,您的部署可能处于“排队”状态。原因通常是因为另一个部署当前正在为该环境/项目组合运行,但是很难找出原因。

为了有所帮助,我们现在显示了当前任务在执行之前正在等待的任务列表:

Stuck in the queue

基于模板的文件转换

尼克已经在博客上介绍了这个功能。我觉得挺酷的!

消除器

在以前版本的 Octopus 中,正在运行的任务上的 Cancel 按钮更多的是一个建议,而不是命令。例如,假设我有这样一个脚本:

Write-Output "Sleeping for 1 second..."
Start-Sleep 1000
Write-Output "Done!" 

哎呀!默认情况下,假设我指的是秒,而不是毫秒。现在我将永远等待我的部署完成。啊!

在以前的版本中,点击任务上的取消不会有帮助——在取消其余的动作之前,触手仍然会等待脚本完成。但是在 Octopus 2.3 中,我们现在将终止正在运行的 PowerShell 进程:

Cancel a PowerShell script

这是一个更好的体验,因为这意味着当你有一个挂起的任务时,现在取消实际上是有效的。另一方面,你在使用它的时候必须更加小心一点!

更好的任务输出和仪表板性能

仪表板和任务输出页面得到了很多关注。以前,任务输出会冻结在几百行输出中,当您有许多项目/环境时,仪表板会间歇性地冻结。这两个问题现在都得到了解决,他们应该感觉更快了!

我们还解决了此版本中的一些其他已知错误和其他性能问题。检查一下,如果遇到任何问题,请告诉我。愉快的部署!

Octopus 2.4 的新特性- Octopus 部署

原文:https://octopus.com/blog/2.4

我刚刚在一个预发布版本的章鱼部署 2.4 上按下按钮。这次发行比宾虚还大!我们上一次主要发布是在大约两个月前,它显示:我们关闭了 GitHub 中的 79 个问题,并添加了一堆大的新功能。它是如此之大,以至于我们将做一个关于 2.4 中新特性的免费网络研讨会。希望你能成功!

以下是此版本中新增内容的简要概述。

库标签

2.4 中最显著的变化是我们增加了一个新的顶级区域,叫做:

Library tab

以前,NuGet feed 设置和库变量集位于 Configuration 选项卡下,仅供管理员使用。由于我们增加了许多新的特性,这些特性将在项目间共享,我们决定是时候为这些设置提供一个专用的地方了。

更好的内置 NuGet 存储库管理

库下面有许多选项卡,默认是内置的 NuGet 存储库。

Packages tab under the library

您可以单击任何包来查看版本列表:

Package versions

您可以选择包来删除它们,或者单击某个版本来查看详细信息:

Package details

您甚至可以直接从 UI 上传软件包,这在部署一次性实用程序时非常有用:

Upload a package

作为保留策略的一部分,内置存储库中的软件包现在会自动清理。当相应的发行版被删除时,不再被任何发行版使用的软件包将被自动从磁盘中删除。

步骤模板

这个功能在我看来是 2.4 中最酷的功能。步骤模板允许您创建可以跨项目使用的可重用步骤。这个功能实际上是来自体育解决方案的大卫·桑苏姆的一个拉式请求,我们喜欢这个概念。非常感谢大卫和运动解决方案!

它是这样工作的。“步骤模板”选项卡是您的模板所在的位置:

Step templates

添加模板时,您可以选择使用任何内置步骤类型。对于这个例子,我将使用一个 PowerShell 脚本:

Template type

为您的模板命名和描述,以便其他人知道它的用途:

Name and description of a step template

我的脚本将停止一个 Windows 服务,所以我希望使用我的步骤模板的人告诉我他们想要停止哪个服务。我通过定义参数来做到这一点:

Step template parameters

定义了参数后,我现在可以定义如何运行我的步骤模板,并利用这些参数:

Defining the step template script

这是激动人心的部分。当定义任何项目的部署过程时,我可以使用我的步骤模板,就好像它是一个内置步骤:

Using a step template

使用步骤模板时,我定义的参数可以编辑,并且可以绑定到变量:

Configuring the new step template

我认为这个特性将会开启大量的可能性,围绕着能够创建可重用的脚本块或其他步骤。我们将在未来为人们建立一个共享他们的 step 模板的地方,所以请关注这个空间!😃

脚本模块

如果您的所有部署都遵循相似的模式,那么创建一组可重用的 PowerShell 函数在它们之间使用是很有诱惑力的。在过去,虽然这是可能的,但从来没有一个非常好的方法能够轻松地使用共享这些功能。

现在在 Octopus 2.4 中,您可以使用脚本模块来定义一组 PowerShell 函数——本质上是一个.psm1文件:

Defining the script

然后,您可以将脚本模块作为项目部署过程的一部分:

Using the PowerShell modules

现在,无论何时在该项目中运行 PowerShell,该模块都是可用的。这意味着您可以从脚本步骤或者在您的Deploy.ps1和相关文件中引用该模块。在运行期间,Octopus 将确保模块在触手上并被加载;没有额外的工作要做:

团队中的 Active directory 组

用户之声的第三高投票建议现在已经完成——你可以将 Octopus 中的团队链接到活动目录组:

Linking teams to AD groups

我们需要你的帮助来测试这一点,因为即使在最好的情况下,活动目录集成也是棘手的。如果你正在使用 AD,并且可以为我们试用 2.4.1 以确保群组成员工作,我们将非常感谢。

自定义角色

在 Octopus 2.0 中,我们通过创建一些高级角色极大地简化了权限系统。然而,一些客户需要修改这些内置角色的能力,或者定义新的角色。在 2.4 中,您现在可以定义自己的。

可以在团队页面中找到角色:

Configuring roles

在那里,您可以添加自定义角色,或修改内置角色的权限:

Configuring roles some more

集成 Windows 身份验证

如果您使用的是 Active Directory 身份验证,用户现在可以选择使用集成的 windows 身份验证质询登录,而不是直接键入密码:

Integrated sign in

当然,如果您有时需要以不同的用户身份登录,或者由于某种原因这不起作用,您可以继续以旧的方式登录。

保留有限数量的备份

Henrik 在他工作的第一天就实现了这一点。现在,您可以告诉 Octopus 应该保留 Octopus 数据库的多少个备份:

Keep a limited number of backups

可定制的版本号策略

当您创建一个版本时,Octopus 试图通过增加以前的版本号来为您生成一个版本号。现在,您可以通过编辑项目设置来更好地控制这种行为。您可以告诉 Octopus 使发布号与特定 NuGet 包的号相匹配:

Package version format

或者,您可以使用如下语法自行指定格式:

Custom version format

项目徽标

有时,在像 Octopus 这样的应用程序中,搞清楚自己的确切位置可能会令人困惑。在 2.4 中,我们增加了为每个项目上传自定义徽标的功能。导航您的项目时,此徽标将出现在左上角:

Logo for a project

您可以从设置页面更改徽标:

Edit the logo

试试吧,告诉我们你的想法!

现在这个版本的 Octopus 是一个预发布版本,虽然它修复了大量的问题,但它总是有可能引入一些问题。由于有这么多的变化,我们真的很希望你下载它,并采取了一个旋转。我认为新功能使它成为一个相当引人注目的升级!

Octopus 2.5 的新特性- Octopus 部署

原文:https://octopus.com/blog/2.5

作为预发布版本提供的 Octopus Deploy 2.5 已经上架(可以这么说)。关闭不到 50 个 GitHub 问题,让我们看看引擎盖下是什么。

计划部署

您是那些不得不在深夜起床运行部署的系统管理员之一吗?这是你的特色。现在,您可以躺在床上,等待计划的部署为您运行。

Scheduled Deployments

如您所见,您现在可以计划部署运行的时间。

Scheduled Deployments

提交时,“任务”页面会显示部署已排队以及何时运行。

Sheduled Deployments

您还可以在项目仪表板上查看排队部署。

有组织的任务页面,包括过滤

以前在任务页面上很难找到已经运行的特定任务,只是按日期提供所有任务的列表。但不是现在。现在,您可以选择环境、项目或活动类型,并过滤列表。它在页面顶部列出一个组中的所有活动任务,下面是可过滤和可搜索的已完成任务列表。

Filtered Task List

Octo.exe 现在显示出进步

现在,当使用 Octo.exe 进行部署时,您可以传递--progress参数,这会将部署输出到控制台。

Octo.exe showing progress

步骤模板更新

第一个步骤模板发生了变化:现在您可以查看步骤模板的使用位置,以及它在项目中是否是最新的。

Step template usage

我们还更新了步骤模板变量。它们现在允许自定义字段类型和类型化参数。

Step template variables

部署不再试图过时的触角

以前,如果触手没有与 Octopus 服务器匹配的版本,部署将继续。已通过停止部署并给出错误消息纠正了此问题。

Out of date tentacle

突破性变化!

在这个版本中有两个突破性的变化值得注意。

重大变化:内部 NuGet 存储库不再被监视

那些使用内置 NuGet 存储库的人,我们不再监视文件夹的变化,因为这导致了一些锁定问题。如果您使用 API 或 nuget.exe 将文件推送到存储库,这种变化应该不会影响您。如果你使用 XCOPY,它会有效果。使用外部馈送将是唯一的选择。不过,我们会在每次重启八达通服务器服务时刷新存储库索引。

重大变化:部署中使用的所有机器都需要唯一的 SQUID

如果部署检测到两台或多台机器具有相同的 SQUID,部署将停止,直到重复的机器从机器目标中删除。

现在就去看看吧!

我们已经更新了章鱼现场演示使用 2.5!所以去看看吧!

Octopus Deploy 2.6 的新特性- Octopus Deploy

原文:https://octopus.com/blog/2.6

Octopus Deploy 2.6 现已发布!喜欢边缘生活的可以下载 Octopus Deploy 2.6 预发布。当这个版本包含如此多的新功能时,谁不想生活在边缘呢?以下是亮点:

  • 控制推广和自动化部署的生命周期
  • 从 NuGet push 自动创建发布
  • 并行运行步骤
  • 高达 5 倍的包上传速度
  • 跳过脱机计算机

生活过程

这个标题只是没有足够的宣传这个功能。想象一下,气球弹出,小号手吹号,五彩纸屑炮弄得到处都是。

好吧,我会停止,但我喜欢这个功能!

Lifecycles main

生命周期允许您指定和控制部署到环境的进度。您不仅能够订购部署环境,还可以:

  • 当环境符合部署条件时,将环境设置为自动部署
  • 检查您的工作流程,确保在继续之前已经部署了 N QA 环境
  • 将多个环境归入一个阶段
  • 一次部署到多个环境

是的,一次部署到多个环境!

生命周期由阶段和保留策略组成。先说阶段。

生命周期阶段

Lifecycle phases

一个生命周期可以由许多阶段组成。一个阶段可以由许多环境组成。每个阶段都允许您向其中添加环境。您可以控制每个阶段,以确保在下一个阶段有资格部署之前,已经发布了 N 个环境。

Lifecycle automagic

当选择将哪个环境添加到阶段时,您可以选择是手动发布还是自动发布。如果它们被设置为自动发布,当它们到达部署链的阶段时,它们将开始部署。

生命周期和保留政策

生命周期有自己的保留策略。每个阶段都有一个总体的保留策略。但是,您可以为每个阶段覆盖它。

Lifecycle Retention Policy 1

这意味着对于一周有 1300 个版本的开发,您可以设置一个非常严格的保留策略来删除除最后 3 个版本之外的所有版本。但是对于生产来说,你可以永远保留一切。或者在中间的某个地方,如果你的需求不是那么极端的话

生命周期和项目

生命周期通过进程屏幕分配给项目。

Lifecycles project process

您可能会注意到,您的项目概览屏幕有一点大修。

Lifecycles project overview

它现在将显示您的最新版本,它们在每个环境中的位置,并提供任何部署或升级按钮。它让您可以一目了然地看到最新和以前的部署。持续绿色表示您最近的部署,中度褪色绿色表示您以前的部署,浅褪色绿色表示所有其他部署。

生命周期、发布、部署和促销

Lifecycles releases

发布页面现在为您提供了一个图形树,显示了当前部署的位置、阶段和正在部署的内容。你可能会注意到这里的一些事情。部署/升级按钮变得更智能了。它知道链条中的下一步是什么。它还允许您部署到已经部署到的任何环境中。

Lifecycles two at once

现在,只需点击一个按钮,就可以发布到多个环境中。没错。

Lifecycles multiple environments

或者你可以使用这个选择框,并选择它们!

Lifecycles smart promote

当您完成部署后,promote 按钮会知道下一步是什么,并为您提供升级到下一个环境的选项。

Lifecycle deployment

部署屏幕也变得简单了一些。更容易找到“立即部署”按钮。但是不要担心,所有的东西在高级下都是可用的,如果你愿意,你可以告诉它记住一直显示高级设置。

生命周期和阻止部署

如果您有一个坏的发布,它刚刚对您的 QA 服务器做了一些坏的事情(tm ),那么您可能想要阻止这个发布进一步向下发布,直到问题被解决。现在,您可以阻止部署。

block deployment

第 1 步阻止部署的原因。

show blocked deployment

在您的发布屏幕上,您可以看到“升级”按钮已经消失,并且您的生命周期部署树对于那些它不能升级到的环境有一个红色图标。

name

现在,您还会在概览中看到一个警告标记,而不会再看到该版本的促销按钮。事实上,所有的推广按钮都不见了。此时,您只能部署到已经部署到的环境中。问题解决后解除阻止将授予您部署该版本的完全权限。

生命周期和自动发布创建

是的,还有更多!

Lifecycles automagic settings

在 project process 屏幕上,您可以定义一个包的名称,当它被推送到或上传到内部存储库时,将自动为您创建一个版本。

lifecycles automagic create

如果您在生命周期设置中有第一个要自动部署的环境,这意味着您可以将一个 NuGet 包推送到内部存储库,让它自动创建一个发布并部署它!我们在看你们 TFS 的用户!

正如你所看到的,生命周期是 Octopus 的许多领域都有的功能,这是一个很大的功能,我们对此非常自豪。我们借此机会尝试在 UserVoice 中听取您的反馈和建议,以增加更多价值。我们真的希望你和我们一样喜欢它!

并行运行步骤

2.6 中的另一个特性允许您设置多个并行运行的项目步骤。

Project step trigger

您可以选择一个项目步骤,使其与前一个步骤并行运行。

project step together

流程页面已更新,以显示这些组合在一起的步骤。如果它们运行在同一台机器上,它们仍然会排队,除非你配置项目允许多个步骤并行运行

保留策略已经改变

如上所述,保留策略已经进入了生命周期。您将不再在“配置”下找到“保留策略”选项卡。它们也不能再为项目组设置。剩下的工作就是为内部包存储库设置保留策略。

repository retention settings

这已经被移到包所在的位置,在库->包下。

包上传流

在 2.6 版本中,当 Octopus 下载一个包,然后发送给 Tentacles 时,它现在将通过流媒体来完成。我们看到速度提高了 5 倍。这也将减少一些内存开销,在保存之前,触手使用这些内存来存储包块。

SNI 支持

除了修复 SSL 绑定的一些问题,我们还增加了 SNI 支持。

SNI support

跳过脱机计算机

目前,当在非常大的环境中进行部署时,离线机器可能会妨碍您的工作。我们现在可以继续部署,但跳过离线机器。

show offline machines

我们现在在部署屏幕上显示离线计算机(显示为红色)。这将允许您返回并检查机器的连接。或者您可以使用“忽略脱机计算机”功能。

ignore offline machines

这将自动列出除脱机计算机之外的所有计算机。

这就结束了 2.6 中的新特性之旅。我们只提到了这个版本中的主要特性,但是也有一些小的改动和错误修复,所以请查看发行说明以了解这些小项目的更多细节。我们希望你和我们一样对生命周期感到兴奋!

立即下载 Octopus Deploy 2.6 预发布版

2022 年开发运营状况加速报告- Octopus 部署

原文:https://octopus.com/blog/2022-state-of-devops-report

每年在 Octopus Deploy,我们都期待发布加速发展状态报告。来自 Google Cloud 和 DORA 的这份报告是一份学术严谨的长期研究成果,提供了实现软件交付高性能所需的实践和能力的重要见解。

从这份报告中,我们已经了解了什么样的技术和文化技术的结合推动了软件交付和组织层面的性能。从 33,000 多份调查反馈中分析了这些能力和关系。

今年我们比往年更加兴奋,因为 Octopus 赞助了 2022 年 9 月 28 日发布的 2022 年加速发展状况报告。

背景

DevOps 加速状态报告已经运行了 8 年,跟踪预测高软件交付性能和组织性能的特定实践。该报告建立了实践、能力、文化和重要成果之间的联系,目标是能够谈论与高绩效团队和组织相关的内容。

DevOps 功能协同工作效果最佳,放大了积极效果,增加了文化和组织效益。

2021 年,主要观点包括:

  • 文化如何减轻倦怠
  • 云的采用是性能的驱动因素
  • 软件供应链安全如何影响性能
  • 文档如何提高安全性、可靠性和云的使用

DORA 指标是报告中出现的实用工具之一。第五个指标是 2021 年增加的可靠性

Octopus Deploy 2022.3+ 包含 DevOps Insights,可根据前 4 项 DORA 指标显示数据,从而更好地了解贵公司的 DevOps 绩效。我们的 DevOps Insights 报告帮助您确定 DevOps 绩效的结果,并查看您可以改进的地方。

2022 年的新见解

与往常一样,最新的加速开发运维状态报告包含新的见解,您将在今年的报告中发现几处变化

头条新闻之一是,安全实践的最大预测因素之一是文化,而不是技术。如果一个组织有一种高度信任/低责备的文化,它更有可能采用适当的安全实践。这些实践提高了软件交付和操作性能,并减少了倦怠。

Google Cloud DORA 研究主管 Claire Peters 表示:“今年的 Accelerate State of DevOps 报告深入研究了安全性,发现组织的应用程序开发安全实践的最大预测因素是文化,而不是技术。“每个组织都将安全放在首位,我们发现,具有高度信任和注重绩效的低责备文化的团队更有可能成功采用有效的安全实践。比以往任何时候都更明显的是,组织文化和现代开发流程是希望改善其安全状况的组织的最佳起点。”

此外,还有以下见解:

  • 可靠性在性能中的作用
  • 云技术的采用率
  • 对受访者进行聚类的新分类

谢谢你

我们在今年的一些新闻简报中邀请您参与开发运维加速状态调查。感谢每一个回复并分享自己经历的人。

你可以在这里阅读完整报告。查看 DevOps 工程师手册以了解更多关于 DevOps 指标的信息

愉快的部署!

在 Octopus 3.0 中,我们将从 RavenDB 切换到 SQL Server - Octopus Deploy

原文:https://octopus.com/blog/3.0-switching-to-sql

Octopus 的早期测试版本使用带有实体框架的 SQL Server。2012 年,就在 1.0 之前,我改用了 RavenDB,写了一篇关于我们如何使用嵌入式版本 RavenDB 的博文。

两年多来,我们一直在基于 RavenDB 进行开发。在此期间,我们安装了超过 10,000 台 Octopus,这意味着我们负责将 RavenDB 投入生产超过 10,000 次。由于大多数客户没有内部的 Raven 专家,所以当 Raven 出现问题时,我们是第一(唯一)支持线。我们不只是在踢轮胎或“看着”乌鸦,我们把农场押在它身上。

对于 Octopus 3.0,我们将停止使用 RavenDB,而使用 SQL Server。可以理解,许多人对“为什么”感兴趣。开始了。

第一,好的

RavenDB 有很好的开发经验。与 SQL + EF 或 NHibernate 相比,使用 RavenDB 可以极快地进行迭代,并且它通常“工作正常”。如果我在一个紧张的期限内构建一个最小可行的产品,RavenDB 将是我的首选数据库。我们在 6 个月的时间里重写了 Octopus 1.6 和 2.0 之间的几乎所有内容,我不认为我们可以在 SQL + EF 上这么快地迭代。

坏事

我们通过电子邮件/论坛处理大部分支持,但当出现大问题时,我们会将其升级为 Skype/GoToMeeting 电话,以便帮助客户。通常是在早上很早的时候,或者晚上很晚的时候,所以最小化做这些事情的需求对我们的理智是至关重要的。

我们大多数支持电话的原因是什么?不幸的是,它要么是 Raven,要么是我们在使用 Raven 时犯的一个错误。而且用 Raven 的时候真的很容易出错。这些问题通常分为两类:索引/数据损坏问题,或 API/使用问题。

最重要的是,数据库需要坚如磐石,性能可靠。Raven 的底层使用 ESENT,我们通常不会丢失 Raven 事务方面的任何数据。但是指数是基于 Lucene.NET 的,这是一个不同的故事。已经损坏并需要重建的索引是如此常见,以至于我们为 1.6 版写了一篇博文解释人们如何能够重置他们的索引。我们把这篇博文发给了很多人,所以在 2.0 中我们在 UI 中为他们构建了一个完整的功能。

Repair RavenDB

当我说我们从未丢失过交易数据时,这并不完全正确。在 RavenDB 中添加一个导致大问题的索引真的很容易。拿着这个:

 Map = processes => from process in processes
                     from step in process.Steps
                     select {...}
  Reduce = results => from result in results
                      group result by .... 

你可以写这个索引,它对你来说很好,你把它投入生产。然后,您发现一个客户有 10,000 个流程文档,每个文档有 40 个步骤。

虽然 Raven 使用 Lucene 进行索引,但它也将索引记录写入 ESENT。我不知道内部的情况,但是 Raven ESENT 数据库内部有各种各样的表,有些是用来临时写这些 map/reduce 记录的。对于每个被索引的条目,它将向这些表中写入大量的记录。因此,我们从一个客户那里得到一个支持问题:他们启动 Octopus,他们的数据库文件以每秒几十或几百 MB 的速度增长,直到填满磁盘。数据库文件变得太大,他们无法修复。他们所能做的就是从备份中恢复。当我们最终获得这些巨大数据文件中的一个副本,并使用 ESENT 的一些 UI 工具对其进行研究时,这些表包含了数百万条记录,仅 10,000 个文档。

RavenDB 团队意识到这是一个问题,因为在 3.0 中他们增加了一个新功能。如果地图操作产生的输出记录超过 15 条,则该文档不会被索引。

我是说,再读一遍那一段。你写一些代码,测试它,它在开发中运行良好。你把它放到生产环境中,它对每个人都很好。然后你接到一个客户的电话:我刚刚添加了一个新流程,它没有出现在列表中。只有在许多电子邮件和支持电话之后,你才意识到这是因为 Raven 认为 15 是可以的,16 是不行的,并且该项目没有被索引。你没有阅读文档是你的错!

“默认安全”是所以生产中痛苦

Raven 有一个“默认安全”的哲学,但是 API 使得编写“安全”的代码变得如此容易,以至于在生产中中断。例如:

session.Query<Project>().ToList(); 

把它投入生产,你会得到一个支持电话:“我刚刚添加了我的第 129 个项目,但它没有出现在屏幕上”。为了避免可怕的“无界结果集”问题,Raven 限制了任何查询返回的项数。感谢不是这个:

DeleteExcept(session.Query<Project>().Where(p => !p.KeepForever).ToList()) 

无界的结果集当然不好。但是,在开发和生产中工作的代码,直到当记录数量改变时,它突然表现出不同的行为,就更糟糕了。如果 RavenDB 相信防止无限的结果集,他们根本就不应该让那个查询运行——当我在没有调用.Take()的情况下做任何查询时抛出一个异常。让它成为开发问题,而不是生产问题。

在一个会话中,您只能执行 30 个查询。结果集是有界的。每个被映射的项目只有 15 个映射结果。当你和 Raven 一起工作时,每次和 RavenDB 互动时,都要记住这些限制,否则你会后悔的。

这些限制被清楚地记录下来,但是你会忘记它们。只有当生产中发生了奇怪的事情,你去寻找时,你才会意识到它们。尽管有两年使用 Raven 的生产经验,这些意见仍然咬着我们。看到像这样的帖子出现在网站上让我很沮丧,这些帖子提倡的解决方案如果有人尝试,将会积极地打破生产。

结论

RavenDB 非常适合开发。也许我们正在经历的问题是我们的错。所有的数据库都有它们的缺点,也许这是另一边的草总是更绿的例子。切换到 SQL Server 可能看起来像是一种倒退,并且可能会使开发更加困难,但是在这一点上,我确实觉得我们在生产中使用 SQL Server 会有更少的问题。它已经存在了很长一段时间,其中的陷阱至少是众所周知和可以预见的。

关于我们为什么要离开 RavenDB 已经说得够多了。下周我将分享一些关于我们计划如何在 Octopus 3.0 中使用 SQL Server 的细节。

(*)您可以通过指定要返回的无限项来禁用无限结果集保护,如果您知道在哪里关闭它的话。但是您仍然必须在每次编写查询时显式调用.Take(int.MaxValue)

创建 Kubernetes pods、复制集和部署- Octopus 部署

原文:https://octopus.com/blog/k8s-training/4-creating-kubernetes-resources

这篇文章是我们 Kubernetes 培训系列的第四篇,为 DevOps 工程师提供了关于 Docker、Kubernetes 和 Octopus 的介绍。

此视频演示了 Kubernetes pods、副本集和部署,并展示了每种部署的示例。

如果您还没有八达通帐户,您可以开始免费试用。

您可以使用下面的链接完成该系列。

资源

样品舱 YAML

apiVersion: v1
kind: Pod
metadata:
  name: underwater
spec:
  containers:
  - name: webapp
    image: octopussamples/underwater-app
    ports:
    - containerPort: 80 

样本复制集 YAML

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: webapp
spec:
  replicas: 3
  selector:
    matchLabels:
      tier: webapp
  template:
    metadata:
      labels:
        tier: webapp
    spec:
      containers:
      - name: webapp
        image: octopussamples/underwater-app
        ports:
        - containerPort: 80 

示例部署 YAML

apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp
spec:
  replicas: 3
  selector:
    matchLabels:
      tier: webapp
  template:
    metadata:
      labels:
        tier: webapp
    spec:
      containers:
      - name: webapp
        image: octopussamples/underwater-app
        ports:
        - containerPort: 80 

了解更多信息

如果您希望在 AWS 平台(如 EKS 和 ECS)上构建和部署容器化的应用程序,那么 Octopus Workflow Builder 将用 GitHub Actions 工作流构建的示例应用程序填充 GitHub 存储库,并用示例部署项目配置托管的 Octopus 实例,这些项目展示了最佳实践,如漏洞扫描和基础设施代码(IaC)。

愉快的部署!

Selenium 系列:样本 web 页面——Octopus Deploy

原文:https://octopus.com/blog/selenium/5-a-sample-web-page/a-sample-web-page

这篇文章是关于创建 Selenium WebDriver 测试框架的系列文章的一部分。

既然我们已经有了用于编写 WebDriver 测试的框架的基础,是时候开始与网页交互了。

为了展示 WebDriver 的强大功能,我们将首先创建一个简单的 web 页面,其中包含常见的表单元素,以及其他常见的 HTML 元素,如 images 和 div。

完整的网页如下所示:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>A sample web site</title>
    <style>
        form > * {
            display: block;
        }

    </style>
    <script>
        /*
            Print a message to the page
        */
        function interactionMessage(message) {
            document.getElementById('message').textContent = message;
        }

        /*
            Create a new element on the page after 5 seconds
        */
        setTimeout(function() {
            var newDiv = document.createElement("div");
            newDiv.setAttribute("id", "newdiv_element");
            newDiv.textContent = "I am a newly created div";
            document.body.appendChild(newDiv);
            document.getElementById("div3_element").style.display = "";
        }, 5000);

    </script>
</head>
<body>
<div id="message"></div>
<form id="form_element" onsubmit="interactionMessage('Form Submitted'); return false">
    <button name="button_element" id="button_element" type="button" onclick="interactionMessage('Button Clicked')">Form Button</button>
    <input name="text_element" id="text_element" type="text" oninput="interactionMessage('Text Input Changed')">
    <select name="select_element" id="select_element" onchange="interactionMessage('Select Changed')">
        <optgroup label="Group 1">
            <option id="option1.1_element">Option 1.1</option>
        </optgroup>
        <optgroup label="Group 2">
            <option id="option2.1_element">Option 2.1</option>
            <option id="option2.2_element">Option 2.2</option>
        </optgroup>
        <optgroup label="Group 3" disabled>
            <option id="option3.1_element">Option 3.1</option>
            <option id="option3.2_element">Option 3.2</option>
            <option id="option3.3_element">Option 3.3</option>
        </optgroup>
    </select>
    <textarea name="textarea_element" id="textarea_element" name="textarea" rows="10" cols="50"
              oninput="interactionMessage('Text Area Changed')"></textarea>
    <div><input name="radio_group" id="radio1_element" type="radio" name="color" value="blue"
                onchange="interactionMessage('Radio Button Changed')"> Blue
    </div>
    <div><input name="radio_group" id="radio2_element" type="radio" name="color" value="green"
                onchange="interactionMessage('Radio Button Changed')"> Green
    </div>
    <div><input name="radio_group" id="radio3_element" type="radio" name="color" value="red"
                onchange="interactionMessage('Radio Button Changed')"> Red
    </div>
    <div><input name="checkbox1_element" id="checkbox1_element" type="checkbox" name="vehicle" value="Bike"
                onchange="interactionMessage('Checkbox Changed')"> I have a bike
    </div>
    <div><input name="checkbox2_element" id="checkbox2_element" type="checkbox" name="vehicle" value="Car" checked
                onchange="interactionMessage('Checkbox Changed')"> I have a car
    </div>
    <input id="submit_element" type="submit">
</form>
<img id="image_element" src="java.png" width="128" height="128" onclick="interactionMessage('Image Clicked')">
<div id="div_element" onclick="interactionMessage('Div Clicked')">I am a div</div>
<div id="div2_element" onclick="interactionMessage('Div 2 Clicked')">I am a div too</div>
<div id="div3_element" style="display: none" onclick="interactionMessage('Div 3 Clicked')">I am a hidden div</div>
</body>
</html> 

让我们来看看这个网页的一些有趣的方面。

我们有一个名为interactionMessage()的 JavaScript 函数,它在页面上显示一些文本。我们将通过 WebDriver 与之交互的 HTML 元素将使用像onclickonchangeoninput这样的事件将消息打印到页面上。然后,我们可以验证该消息的存在,以确保 WebDriver 确实如我们所期望的那样与元素进行了交互。

function interactionMessage(message) {
  document.getElementById('message').textContent = message;
} 

第二个 JavaScript 函数使用setTimeout()函数等待 5 秒钟,然后向页面追加一个新的<div>元素。它还重置了 ID 为div3_element的 div 的样式,这将具有显示隐藏元素的效果。

在以后的文章中,我们将使用这两种对网页的动态更新来演示如何使用隐式和显式等待:

setTimeout(function() {
  var newDiv = document.createElement("div");
  newDiv.setAttribute("id", "newdiv_element");
  newDiv.textContent = "I am a newly created div";
  document.body.appendChild(newDiv);
  document.getElementById("div3_element").style.display = "";
}, 5000); 

我们有一个<form>元素,它将保存一组常见的 HTML 表单元素,如按钮、文本框、单选按钮等。当表单提交时,onsubmit事件调用interactionMessage()方法显示一条消息。通过返回false阻止表单在提交时尝试重新加载页面:

<form id="form_element" onsubmit="interactionMessage('Form Submitted'); return false"> 

表单内部是文本框、文本区域、按钮、单选按钮、复选框和选择元素的集合。像父元素<form>一样,大多数子元素调用interactionMessage()方法来响应事件:

<button name="button_element" id="button_element" type="button" onclick="interactionMessage('Button Clicked')">Form Button</button>
<input name="text_element" id="text_element" type="text" oninput="interactionMessage('Text Input Changed')">
<select name="select_element" id="select_element" onchange="interactionMessage('Select Changed')">
    <optgroup label="Group 1">
        <option id="option1.1_element">Option 1.1</option>
    </optgroup>
    <optgroup label="Group 2">
        <option id="option2.1_element">Option 2.1</option>
        <option id="option2.2_element">Option 2.2</option>
    </optgroup>
    <optgroup label="Group 3" disabled>
        <option id="option3.1_element">Option 3.1</option>
        <option id="option3.2_element">Option 3.2</option>
        <option id="option3.3_element">Option 3.3</option>
    </optgroup>
</select>
<textarea name="textarea_element" id="textarea_element" name="textarea" rows="10" cols="50"
          oninput="interactionMessage('Text Area Changed')"></textarea>
<div><input name="radio_group" id="radio1_element" type="radio" name="color" value="blue"
            onchange="interactionMessage('Radio Button Changed')"> Blue
</div>
<div><input name="radio_group" id="radio2_element" type="radio" name="color" value="green"
            onchange="interactionMessage('Radio Button Changed')"> Green
</div>
<div><input name="radio_group" id="radio3_element" type="radio" name="color" value="red"
            onchange="interactionMessage('Radio Button Changed')"> Red
</div>
<div><input name="checkbox1_element" id="checkbox1_element" type="checkbox" name="vehicle" value="Bike"
            onchange="interactionMessage('Checkbox Changed')"> I have a bike
</div>
<div><input name="checkbox2_element" id="checkbox2_element" type="checkbox" name="vehicle" value="Car" checked
            onchange="interactionMessage('Checkbox Changed')"> I have a car
</div>
<input id="submit_element" type="submit"> 

<form>之外,我们有一些 image 和 div 元素:

<img id="image_element" src="java.png" width="128" height="128" onclick="interactionMessage('Image Clicked')">
<div id="div_element" onclick="interactionMessage('Div Clicked')">I am a div</div>
<div id="div2_element" onclick="interactionMessage('Div 2 Clicked')">I am a div too</div> 

最后一个 div 元素的display样式设置为none,实际上在页面上隐藏了它。这个元素将在 5 秒钟后由setTimeout()方法调用的 JavaScript 显示:

<div id="div3_element" style="display: none" onclick="interactionMessage('Div 3 Clicked')">I am a hidden div</div> 

最终结果是这样的。

为了在 Java 测试中使用这个页面,我们需要将它保存在src/test/resources目录中。这是找到资源文件的标准 Maven 目录。

这个目录还不存在,所以我们通过右击测试目录并选择新➜目录来创建它。

输入名称 resources,然后单击OK按钮。

正如我们已经多次看到的,在 Maven 项目中创建一个具有特殊意义的目录并不会自动更新 IntelliJ 项目。我们可以在下面的截图中看到,resources目录现在已经存在,但是在我们的项目中它看起来像一个普通的目录。

要更新 IntelliJ 项目,打开Maven Projects工具窗口并点击Reimport All Maven Projects按钮。

目录的图标被更新,以反映这样一个事实,即它将保存像我们的示例 web 页面这样的文件。

一旦配置了resources文件夹,将 HTML 代码保存到一个名为form.html的文件中。

我们现在已经完成了拼图的两个重要部分。首先,我们已经有了框架的雏形,它将允许我们创建灵活的AutomatedBrowser对象,通过这些对象我们可以与网页进行交互。第二,我们有一个样例 web 页面,其中包含了我们在编写 WebDriver 测试时可能会遇到的大多数元素。现在是时候写一些真正的 WebDriver 测试了。

让我们在类FormTest中创建一个测试方法formTestByID(),它将打开浏览器,打开我们的测试网页,然后再次关闭浏览器:

package com.octopus;

import org.junit.Test;
import java.net.URISyntaxException;

public class FormTest {

  private static final AutomatedBrowserFactory AUTOMATED_BROWSER_FACTORY = new AutomatedBrowserFactory();

  @Test
  public void formTestByID() throws URISyntaxException {
    final AutomatedBrowser automatedBrowser =
      AUTOMATED_BROWSER_FACTORY.getAutomatedBrowser("Chrome");

    try {
      automatedBrowser.init();
      automatedBrowser.goTo(FormTest.class.getResource("/form.html").toURI().toString());
    } finally {
      automatedBrowser.destroy();
    }
  }
} 

注意,我们可以通过调用FormTest.class.getResource()来访问样本 HTML 文件。因为我们将这个文件保存在标准目录src/test/resources下,Maven(以及通过扩展 IntelliJ)允许我们通过 Java 代码访问这个文件。以下代码返回样本 HTML 文件的完整 URL:

FormTest.class.getResource("/form.html").toURI().toString() 

现在让我们手动打开同一个网页。IntelliJ 为本地托管网页提供了便利的服务。当 HTML 文件在编辑器中打开时,可以通过单击屏幕右上角的快捷方式来访问它。

点击 Chrome 浏览器图标会打开 Chrome 到一个类似http://localhost:63342/web driver training/form . html?_ ijt = 2r 0 gmmveunmkptr 759 pintjfe 5。这是一种快速简单的方法来查看我们的样本网页。

IntelliJ 生成的 URL 不是我们通过调用FormTest.class.getResource()得到的 URL。使用 IntelliJ 来托管 web 页面只是为了方便我们作为最终用户,但是我们在测试中不使用这个 URL。事实上,我们不能使用这个 URL,因为最后的查询字符串是随机生成的,会阻止任何其他用户或进程访问 IntelliJ 托管的页面。

我们要测试的第一件事是使用 WebDriver 点击页面顶部的按钮。

要与按钮交互,我们需要知道它的 ID。我们知道这个按钮的 ID 是button_element,因为我们编写了 HTML。但是,并不总是能够访问您将要测试的 web 应用程序的源代码。因此,我们将假设我们无法访问 HTML 源代码,而是使用 Chrome 提供的工具来查找这些信息。

在 Chrome 中加载页面后,右键单击按钮元素并单击Inspect选项。

这将打开 Chrome 的开发者工具,并高亮显示Elements标签中的按钮 HTML 元素。

当 JavaScript 调用添加、删除和更改元素时,显示在开发工具Elements选项卡中的 HTML 元素会实时更新。这意味着您将经常从开发人员工具中获得比仅仅查看 HTML 源代码更多的信息。

您可以通过右击显示I am a newly created div的文本并选择Inspect选项来亲自查看。这将显示作为setTimeout()方法调用的结果而创建的<div>元素。您将不会在 HTML 源代码中看到那个<div>元素,因为它是在运行时动态生成的。

回到<button>元素,我们可以看到 ID 属性确实是button_element

既然我们知道了想要与之交互的元素的 ID,我们就可以开始构建我们的测试了。我们首先调用clickElementWithId(),传入我们想要点击的元素的 ID:

@Test
public void formTestByID() throws URISyntaxException {
  final AutomatedBrowser automatedBrowser = AUTOMATED_BROWSER_FACTORY.getAutomatedBrowser("Chrome");

  try {
    automatedBrowser.init();
    automatedBrowser.goTo(FormTest.class.getResource("/form.html").toURI().toString());
    automatedBrowser.clickElementWithId("button_element");
  } finally {
    automatedBrowser.destroy();
  }
} 

接下来,在WebDriverDecorator类中,我们需要添加一个clickElementWithId()方法的实现:

@Override
public void clickElementWithId(final String id) {
  webDriver.findElement(By.id(id)).click();
} 

当您将这段代码粘贴到WebDriverDecorator类中时,ItelliJ 将以红色显示By类。这是因为我们没有导入包含By类的包。

要解决这个问题,请将鼠标光标放在红色文本上,然后单击 ALT + Enter。这将显示一个带有Import class选项的上下文菜单。

IntelliJ 通常很擅长根据类的上下文来决定导入哪个包,在这种情况下,它会将import org.openqa.selenium.By;语句添加到类的顶部。

clickElementWithId()方法做了三件重要的事情。

首先,它通过调用By.id(id)找到我们希望与之交互的元素。By类用于查找网页中的元素,并提供一系列方法来执行搜索。因为我们通过 ID 搜索元素,所以我们称之为By.id()

其次,我们调用 web 驱动程序类上的findElement()方法来查找元素。

第三,我们获取由findElement()返回的元素并调用click()方法来模拟终端用户点击该元素。

定义如何用By类搜索元素,用findElement()方法找到元素,并调用类似click()的方法来模拟一个动作,这个过程是我们在构建测试框架时要反复重复的。

但是我们如何确定 WebDriver 真的点击了按钮呢?如果您回头看一下form.html页面的源代码,您会看到<button>元素具有属性onclick="interactionMessage('Button Clicked')"。这意味着当点击按钮时,用'Button Clicked'调用interactionMessage()方法,这又会在页面上显示文本Button Clicked

然后,我们可以从保存消息Button Clicked的元素中提取文本,并验证它是否表达了我们期望它表达的内容。

为此,我们调用getTextFromElementWithId()方法,传入包含我们希望返回的文本的元素的 ID,在我们的例子中是保存我们希望验证的消息文本的<div>元素的 ID。然后,我们使用 JUnit 提供的assertEquals()来验证这个方法调用的结果:

@Test
public void formTestByID() throws URISyntaxException {
  final AutomatedBrowser automatedBrowser =
  AUTOMATED_BROWSER_FACTORY.getAutomatedBrowser("Chrome");

  try {
    automatedBrowser.init();
    automatedBrowser.goTo(FormTest.class.getResource("/form.html").toURI().toString());

    automatedBrowser.clickElementWithId("button_element");
    assertEquals("Button Clicked", automatedBrowser.getTextFromElementWithId("message"));
  } finally {
    automatedBrowser.destroy();
  }
} 

然后,getTextFromElementWithId()方法需要在WebDriverDecorator类中实现。

注意,getTextFromElementWithId()方法遵循与clickElementWithId()相同的模式。事实上,唯一的区别是我们在结果元素上调用了getText(),而不是click():

@Override
public String getTextFromElementWithId(final String id) {
  return webDriver.findElement(By.id(id)).getText();
} 

这样,我们就成功地用 WebDriver 点击了一个元素,并验证了页面的响应符合我们的预期。

让我们继续用文本填充文本框和文本区域,并验证这些字段上的事件处理程序将预期的消息打印到页面上:

automatedBrowser.populateElementWithId("text_element", "test text");
assertEquals("Text Input Changed", automatedBrowser.getTextFromElementWithId("message"));

automatedBrowser.populateElementWithId("textarea_element", "test text");
assertEquals("Text Area Changed", automatedBrowser.getTextFromElementWithId("message")); 

然后,populateElementWithId()方法需要在WebDriverDecorator类中实现。在这种情况下,我们对返回的元素使用sendKeys()方法来填充文本:

@Override
public void populateElementWithId(String id, String text) {
  webDriver.findElement(By.id(id)).sendKeys(text);
} 

接下来,我们将从下拉列表中选择一个选项:

automatedBrowser.selectOptionByTextFromSelectWithId("Option 2.1", "select_element");
assertEquals("Select Changed", automatedBrowser.getTextFromElementWithId("message")); 

selectOptionByTextFromSelectWithId()方法与我们之前看到的模式略有不同。

findElement()方法返回WebElement接口的一个实例。WebElement接口反过来通过如下方法公开了一些常见的动作:

  • click()
  • sendKeys()
  • clear()
  • submit()
  • getText()

您可以通过查看 Javadoc API 文档获得这些方法的完整列表。

值得注意的是,这个动作列表中没有从下拉列表中选择选项的功能。为了与一个<select>元素交互,我们需要创建一个Select类的实例,它的构造函数接受由findElement()返回的WebElement。然后我们可以使用selectByVisibleText()方法,该方法选择带有相应文本的选项:

@Override
public void selectOptionByTextFromSelectWithId(String optionText, String selectId) {
  new Select(webDriver.findElement(By.id(selectId))).selectByVisibleText(optionText);
} 

点击单选按钮和复选框的方法与我们点击按钮的方法相同:

automatedBrowser.clickElementWithId("radio3_element");
assertEquals("Radio Button Changed", automatedBrowser.getTextFromElementWithId("message"));

automatedBrowser.clickElementWithId("checkbox2_element");
assertEquals("Checkbox Changed", automatedBrowser.getTextFromElementWithId("message")); 

我们不局限于与表单元素交互。clickElementWithId()方法同样适用于图像和普通旧 div 等元素:

automatedBrowser.clickElementWithId("image_element");
assertEquals("Image Clicked", automatedBrowser.getTextFromElementWithId("message"));

automatedBrowser.clickElementWithId("div_element");
assertEquals("Div Clicked", automatedBrowser.getTextFromElementWithId("message")); 

通过formTestByID()测试,我们已经成功地点击、检查、输入和选择了一个实时的交互式网页的选项,并验证了结果。简而言之,这就是编写 WebDrivers 测试的全部内容。然而,我们并不总是能够根据 ID 属性来定位元素。在这些情况下,WebDriver 提供了许多其他方法来定位 web 页面中的元素,接下来我们将讨论这些方法。

这篇文章是关于创建 Selenium WebDriver 测试框架的系列文章的一部分。

Actors vs. RPC:为 Octopus 3.0 构建新的(旧的)传输层- Octopus Deploy

原文:https://octopus.com/blog/actors-vs-rpc-in-octopus-3

在 Octopus 1.0 中,我们使用 WCF 在 Octopus 服务器和触手之间进行通信。当 Octopus 服务器需要告诉触手做一些事情时,比如运行 PowerShell 脚本,它看起来像这样:

var client = clientBroker.Create<IJobService>("http://some-machine");
var ticket = client.RunScript("Write-Host 'Hello'; Start-Sleep -s 100; Write-Host 'Bye'");

do
{
    var status = client.GetJobStatus(ticket);
    log.Append(status.Log);
    Thread.Sleep(4000);

    if (IsCancelled()) 
    {
        client.Cancel(ticket);
    }
} while (status.State == JobStatus.InProgress);

log.Append("Script run complete"); 

这种 RPC 风格的编程工作得很好,但是它有一个限制:这意味着触手必须始终是 TCP 侦听器,而 Octopus 必须始终是 TCP 客户端。

在设计 Octopus 2.0 的时候,当时最大的特性要求是能够拥有轮询触角;实际上,颠倒了 TCP 客户机/服务器关系。

Polling and Listening Tentacles

在上面的例子中,这意味着 Octopus 服务器需要以某种方式将一个命令排队让触手运行脚本,当触手轮询 Octopus 要做的任务时,它会找到这个命令并处理它。从概念上讲,它的意思大概是这样的:

var runScriptCommand = new RunScriptCommand("Write-Host....");
messageQueue.For("MachineA").Enqueue(runScriptCommand); 

使用消息将 TCP 客户机/服务器关系从代码中分离出来——我们将能够用 Octopus 编写代码来编排触角,而不需要大量的if/else条件来使它根据触角是监听还是轮询而不同地工作。由于轮询触角要求我们对消息进行排队以便稍后获取,我们也可以将它们用于监听触角。

围绕构建基于消息和队列的分布式系统有大量的知识,所以一旦我们决定我们需要消息队列,那些模式和实践就成为我们思考的中心。我们在消息传递方面的大部分经验来自类似 NServiceBus 和类似的框架,我们已经多次将其付诸实践。

按照预期使用消息确实使编排代码变得更加复杂,我们的编排代码开始类似于 NServiceBus sagas :

void Handle(BeginScriptRun message)
{
    Send("MachineA", new RunScriptCommand("Write-Host ...."));
}

void Handle(JobStartedMessage message)
{
    State.Ticket = message.Ticket;
}

void Handle(CancelMessage message) 
{
    Send("MachineA", new CancelCommand(State.Ticket));
}

void Handle(ScriptOutputLogged message) 
{
    log.Append(message.Log);
}

void Handle(ScriptCompletedMessage message) 
{
    log.Append("Script run complete");
    Complete();
} 

脱离请求/响应 RPC 范式而使用消息传递似乎带来了许多好处:

  1. 它可以更好地处理真正长时间运行的任务,因为您没有等待响应的线程被阻塞
  2. 服务器正常运行时间是分离的——如果触手最初是离线的,但最终恢复在线,那么本例中运行的脚本可以完成
  3. 它允许我们支持轮询和监听触角,因为我们的应用程序代码可以编写成与底层传输无关

随着时间的推移,我们的 NServiceBus 传奇类演变成了一个演员框架,类似于 Akka (尽管这是在Akka.NET开始之前大约六个月)。我们从 Akka 借用了一些概念,比如监督树,这使得错误处理变得更容易忍受。

这已经生产了 12 个月了。虽然它基本上运行良好,但我已经开始注意到这种面向参与者/消息的方法的许多缺点:

  1. actors 的单线程特性非常好,使并发变得很容易。然而,有时您确实需要某种互斥,但最终您不得不使用一些难看的消息传递方法来实现它。
  2. 遵循代码并对其进行推理要困难得多。在 Visual Studio 中,查找哪个参与者处理哪个消息始终是一个两步导航过程。
  3. 崩溃转储和堆栈跟踪变得几乎毫无用处。
  4. 管理演员的寿命真的很难。上面例子末尾的那个调用很重要,因为它告诉我们什么时候可以清理这个 actor。很容易忘记调用这些函数(我是否也应该在取消消息处理程序中调用它?)
  5. 错误处理同样令人讨厌。例如,在第一个代码示例中,如果远程机器出现故障,异常就会冒泡,或者用try/catch来处理。在第二个例子中,捕捉和处理这些错误是一个显式的步骤。
  6. 向新开发人员教授这种编程风格要困难得多

当我查看 Octopus 1.6 中的编排代码时,它真的很容易推理和遵循。也许它不能很好地扩展,也许有太多的地方我们明确地处理线程或锁。但是堆栈跟踪是可读的,我可以很容易地浏览它。当阅读 2.0 中的部署编排代码时,我必须真正集中注意力。

事实证明,我提到的“服务器正常运行时间被分离”的好处对我们来说也不是很有用。如果触须离线,我们可能需要一些自动重试,比如 30 秒或几分钟。除此之外,我们真的不需要消息排队。如果我们在部署过程中,有一台机器离线,我们需要做出决定:要么跳过它,要么部署失败。我们不会等 6 个小时。这通常在消息传递框架中通过给予消息非常短的生存时间来处理,但是这仍然使异常处理过程变得复杂。

actors 的主要好处是,与直接使用线程/锁定原语相比,您可以用更简单的代码获得更好的并发性。然而,当我查看 Octopus 1.0 编排代码时,我们使用的原语如此之少,以至于 actor 方法变得不那么简洁。这表明我们的问题域并不真正适合演员。虽然我们可能无法避免在某种级别上使用消息来处理轮询/监听配置,但请求/响应语义似乎更合适。

那么,我们为章鱼 3.0 做些什么呢?我们将回归到与章鱼 1.0 风格非常相似的东西。不同之处在于,虽然从编码的角度来看,我们将使用请求/响应,但在这些代理之下,我们仍然允许轮询或监听触角:

  • 如果触手正在监听,我们将打开一个TcpClient并连接。
  • 如果触手正在轮询,我们将把响应放入队列,并在等待句柄上等待。当请求被拾取并且响应可用时,等待句柄将完成。

您可以将这看作是两层:传输层,其中任何一方都可以是TcpClient/TcpListener,以及位于顶层的逻辑请求/响应层。

我们新的通信栈将是开源的(并将建立在我不久前写的大比目鱼的基础上),随着它的发展,我会发布更多的细节。

2019.5 活动目录突破性变化- Octopus 部署

原文:https://octopus.com/blog/ad-breaking-change

我们在 2019.5 版本中宣布 Active Directory 有一个突破性的变化,我想写一篇博客来帮助人们了解这对您的组织、infosec 团队以及最重要的 Octopus 管理员意味着什么,您可以在 Github 上看到这个问题。

背景和问题

在大多数组织中,他们使用最小特权原则。这意味着技术人员拥有一个标准用户作为活动目录域的一部分,用于他们的日常工作,如编写文档、阅读电子邮件和浏览网页。他们还可以访问他们在日常技术工作中使用的特权帐户,这些工作可能包括部署、开发、重置用户密码和访问敏感系统。

在 2019.5.0 版本之前,Octopus 将用户邮件视为一把钥匙,并期望它们是唯一的。这导致了 Active Directory 的问题,在 Active Directory 中没有这样的限制,当多个用户拥有相同的电子邮件地址时,Octopus 会认为他们是同一用户。他们是同一个人,但不是活动目录意义上的同一用户。没有其他身份验证提供者以这种方式工作,但为了防止 Active Directory 以这种方式使用时出现问题,我们需要放弃唯一性约束,并假设同一个人可以使用与一个电子邮件地址关联的不同用户帐户。

Active Directory 登录检查也需要更改,以支持检测重复项,并且仍然检测用户是否在 AD 中被修改,而不是新用户首次登录。这是 Active Directory 管理员挑选用户并将他们转移到另一个 OU 或为他们分配所有新 upn 或 SamAccountNames 的场景。我们在过去有几个客户这样做了,失去了对他们的 Octopus 实例的所有访问,因为用户突然看起来都不一样了,我们把他们当作新用户。

这次修复又影响了谁?

我们对此问题的解决方案是确保这些帐户不会根据电子邮件地址进行匹配和合并。这是为了确保如果一个叫罗伯特的用户。Jones 有一个指定的活动目录用户 Work\Robert。琼斯和一个名为“工作\管理-RJ”的管理帐户都有 Robert.Jones@Work.com 的电子邮件地址,但这两个帐户与同一个八达通帐户不匹配。

如果只有管理员帐户可以访问八达通,那么你不会受到影响。

如果您使用的用户帐户不共享电子邮件地址,则您不会受到影响。

如果您在非管理帐户和管理员帐户之间共享电子邮件帐户,那么您会受到影响,我们建议您进行概念验证升级,并测试管理员帐户在升级后是否具有所需的访问权限。

如果您之前在两个用户上使用了相同的电子邮件地址,那么您现在可以拥有独立的帐户,不会基于电子邮件地址进行匹配和合并。

如果你是在事后读到这篇文章的,并且你在一次升级的证明后不小心把自己锁在了 Octopus 之外,我们有办法在我们的文档中获得一个管理帐户。这将允许你访问你的八达通实例,但如果你卡住了,然后与支持取得联系。

结论

我们相信这是我们客户解决这一特殊问题的正确方法,但如果您有任何问题,请联系支持

Selenium 系列:添加 BrowserMob 代理- Octopus Deploy

原文:https://octopus.com/blog/selenium/11-adding-the-browsermob-proxy/adding-the-browsermob-proxy

这篇文章是关于创建 Selenium WebDriver 测试框架的系列文章的一部分。

在这篇文章中,我们将添加对 BrowserMob 代理的支持,这是一个免费的开源 Java 代理服务器。然后,我们将使用 BrowserMob 保存一个包含测试期间所有网络请求的报告,并截取一些网络请求。

为了利用 BrowserMob 库,我们需要将它作为一个依赖项添加到 Maven pom.xml文件中:

<project 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <!-- ... -->

  <properties>
    <!-- ... -->
    <browsermob.version>2.1.5</browsermob.version>
  </properties>

  <dependencies>
    <!-- ... -->

    <dependency>
      <groupId>net.lightbody.bmp</groupId>
      <artifactId>browsermob-core</artifactId>
      <version>${browsermob.version}</version>
    </dependency>
  </dependencies>
</project> 

BrowserMob 代理的一个实例将被一个名为BrowserMobDecorator的新装饰器创建和销毁:

package com.octopus.decorators;

import com.octopus.AutomatedBrowser;
import com.octopus.decoratorbase.AutomatedBrowserBase;
import net.lightbody.bmp.BrowserMobProxy;
import net.lightbody.bmp.BrowserMobProxyServer;
import org.openqa.selenium.Proxy;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;

public class BrowserMobDecorator extends AutomatedBrowserBase {

    private BrowserMobProxy proxy;

    public BrowserMobDecorator(final AutomatedBrowser automatedBrowser) {
        super(automatedBrowser);
    }

    @Override
    public DesiredCapabilities getDesiredCapabilities() {
        proxy = new BrowserMobProxyServer();
        proxy.start(0);

        final DesiredCapabilities desiredCapabilities =
                getAutomatedBrowser().getDesiredCapabilities();

        final Proxy seleniumProxy = new Proxy();
        final String proxyStr = "localhost:" + proxy.getPort();

        seleniumProxy.setHttpProxy(proxyStr);
        seleniumProxy.setSslProxy(proxyStr);
        desiredCapabilities.setCapability(CapabilityType.PROXY, seleniumProxy);
        return desiredCapabilities;
    }

    @Override
    public void destroy() {
        getAutomatedBrowser().destroy();
        if (proxy != null) {
            proxy.stop();
        }
    }
} 

让我们来分解这个类中的代码。

getDesiredCapabilities()方法中,我们创建了一个BrowserMobProxyServer类的实例,并调用它的start()方法。通过将0传递给start()方法,我们可以在任何可用的端口上公开代理:

proxy = new BrowserMobProxyServer();
proxy.start(0); 

WebDriver 代理配置信息保存在Proxy类的实例中:

final Proxy seleniumProxy = new Proxy(); 

然后,我们需要构建一个表示代理网络地址的字符串。因为代理将在本地运行,所以主机名将始终是localhost

代理暴露的端口可以在每次测试运行时改变,因为我们用0调用了start()方法,表明 BrowserMob 代理应该使用一个可用的端口。我们可以通过调用proxy上的getPort()方法来获取被占用的端口。

这两个字符串组合在一起形成一个类似于localhost:57470的字符串:

final String proxyStr = "localhost:" + proxy.getPort(); 

然后我们配置Proxy对象将 HTTP 和 HTTPS 流量定向到本地代理:

seleniumProxy.setHttpProxy(proxyStr);
seleniumProxy.setSslProxy(proxyStr); 

接下来,我们得到一个DesiredCapabilities对象的副本。如果您还记得上一篇文章,该对象用于配置可应用于任何由 WebDriver 启动的浏览器的设置:

final DesiredCapabilities desiredCapabilities =
  getAutomatedBrowser().getDesiredCapabilities(); 

然后用Proxy实例配置DesiredCapabilities实例。

在之前的文章中,我们注意到DesiredCapabilities类本质上是一个键/值存储。现在你可以在实践中看到这一点,因为我们根据CapabilityType.PROXY值保存了Proxy实例。

CapabilityType.PROXY是设置为proxy的字符串常量,该值被所有浏览器识别为包含代理配置设置:

desiredCapabilities.setCapability(CapabilityType.PROXY, seleniumProxy); 

然后返回DesiredCapabilities实例,以便其他 decorators 可以添加到它,或者使用它来构建浏览器驱动程序:

return desiredCapabilities; 

更新AutomatedBrowserFactory类以创建BrowserMobDecorator类的新实例:

private AutomatedBrowser getChromeBrowser(final boolean headless) {
  return new ChromeDecorator(headless,
    new ImplicitWaitDecorator(10,
      new BrowserMobDecorator(
        new WebDriverDecorator()
      )
    )
  );
}

private AutomatedBrowser getFirefoxBrowser(final boolean headless) {
  return new FirefoxDecorator(headless,
    new ImplicitWaitDecorator(10,
      new BrowserMobDecorator(
        new WebDriverDecorator()
      )
    )
  );
}

private AutomatedBrowser getChromeBrowserNoImplicitWait() {
  return new ChromeDecorator(
    new BrowserMobDecorator(
      new WebDriverDecorator()
    )
  );
}

private AutomatedBrowser getFirefoxBrowserNoImplicitWait() {
  return new FirefoxDecorator(
    new BrowserMobDecorator(
      new WebDriverDecorator()
    )
  );
} 

现在我们的AutomatedBrowserFactory正在配置浏览器将流量传递给 BrowserMob 代理的实例。这不会改变测试的运行方式;代理被设计成对最终用户基本不可见,因此我们的测试将像以前一样运行。然而,如果我们愿意的话,我们现在有办法监控和拦截网络请求。

我们可以通过在测试运行后保持浏览器窗口打开来确认 BrowserMob 代理正在被创建。Firefox 特别容易看到代理设置,所以在下面的测试方法中,我们通过注释掉对finally块中automatedBrowser.destroy()的调用,在测试完成后让浏览器窗口保持打开:

@Test
public void formTestByIDFirefox() throws URISyntaxException {

  final AutomatedBrowser automatedBrowser =
    AUTOMATED_BROWSER_FACTORY.getAutomatedBrowser("Firefox");

  try {
    automatedBrowser.init();

    automatedBrowser.goTo(FormTest.class.getResource("/form.html").toURI().toString());

    automatedBrowser.clickElementWithId("button_element");
    assertEquals("Button Clicked", automatedBrowser.getTextFromElementWithId("message"));

    automatedBrowser.populateElementWithId("text_element", "test text");
    assertEquals("Text Input Changed", automatedBrowser.getTextFromElementWithId("message"));

    automatedBrowser.populateElementWithId("textarea_element", "test text");
    assertEquals("Text Area Changed", automatedBrowser.getTextFromElementWithId("message"));

    automatedBrowser.selectOptionByTextFromSelectWithId("Option 2.1", "select_element");
    assertEquals("Select Changed", automatedBrowser.getTextFromElementWithId("message"));

    automatedBrowser.clickElementWithId("radio3_element");
    assertEquals("Radio Button Changed", automatedBrowser.getTextFromElementWithId("message"));

    automatedBrowser.clickElementWithId("checkbox2_element");
    assertEquals("Checkbox Changed", automatedBrowser.getTextFromElementWithId("message"));

    automatedBrowser.clickElementWithId("image_element");
    assertEquals("Image Clicked", automatedBrowser.getTextFromElementWithId("message"));

    automatedBrowser.clickElementWithId("div_element");
    assertEquals("Div Clicked", automatedBrowser.getTextFromElementWithId("message"));
  } finally {
    //automatedBrowser.destroy();
  }
} 

测试完成后,它启动的 Firefox 浏览器仍会显示。然后从菜单中,我们可以选择Preferences选项。

在首选项页面的底部是Network Proxy部分。点击Settings...按钮。

这里我们可以看到我们在代码中定义的代理设置。这是对我们的代理已经通过 WebDriver 配置的确认。

配置代理服务器使我们能够观察测试过程中的网络请求并与之交互,这是单独使用 WebDriver 无法做到的。下一步是公开对我们有用的 BrowserMob 代理的特性。但是在我们这样做之前,我们将看一看在配置代理时可能出现的常见错误配置,并了解如何调试显示的错误。

这篇文章是关于创建 Selenium WebDriver 测试框架的系列文章的一部分。

替代 Kubernetes 仪表板-八达通部署

原文:https://octopus.com/blog/alternative-kubernetes-dashboards

Alternative Kubernetes Dashboards

最初有Kubernetes 仪表板。对于任何想要监控 Kubernetes 集群的人来说,这个仪表板都是默认选项,但是多年来,已经开发出了许多值得研究的替代方案。

*在这篇博客中,我们将看看这些替代 Kubernetes 仪表板。

Kubernetes 星团样本

对于这篇文章,我在本地运行 minikube,用 Istio 提供的 Bookinfo 应用程序填充。

K8Dash

K8Dash 主页

K8Dash 是管理 Kubernetes 集群最简单的方法。

K8Dash 有一个干净、现代的界面,使用过 Kubernetes 官方仪表盘的人应该都很熟悉。K8Dash 的卖点是界面会自动更新,不需要手动刷新页面来查看集群的当前状态。

使用以下命令可以轻松完成安装:

kubectl apply -f https://raw.githubusercontent.com/herbrandson/k8dash/master/kubernetes-k8dash.yaml
kubectl port-forward service/k8dash 9999:80 -n kube-system 

K8Dash Cluster overview K8Dash Pod view

星状的

可视化 Kubernetes 应用程序

Konstellate 与其说是一个 Kubernetes 仪表板,不如说是一个创建、链接和可视化 Kubernetes 资源的工具。

主画布允许您添加新的 Kubernetes 资源,如部署、服务和入口。动态用户界面允许您构建这些资源的 YAML 描述,通过相关描述公开可用的子属性。

然后可以连接两个相关的实体,Konstellate 显示将它们链接在一起的相关属性。

如果说手工编辑 YAML 有什么挑战的话,那就是我总是在谷歌上搜索准确的房产名称和它们之间的关系。上下文感知的 Konstellate 编辑器是探索给定实体的各种可用属性的好方法。

一个杀手级的特性是能够可视化现有集群中的资源,但是这个特性还没有实现。

Konstellate 是从源代码构建的,并没有提供我所能看到的任何预构建的 Docker 映像或二进制文件。您所需要的只是 Clojure 和一个构建并运行应用程序的命令,但下载所有依赖项可能需要几分钟时间。GitHub 页面链接到一个演示,但是当我尝试的时候它关闭了。

总的来说,这是一个非常酷的应用程序,绝对是一个值得关注的项目。

库伯纳特

Kubernator 主页

与高级的 Kubernetes Dashboard 不同,Kubernator 提供了对集群中所有对象的低级控制和清晰的视图,并具有创建新对象、编辑和解决冲突的能力。

Kubernator 是一个功能强大的 YAML 编辑器,直接链接到 Kubernetes 集群。导航树显示了类似文件系统的集群视图,而编辑器提供了选项卡、键盘快捷键和差异视图等功能。

除了编辑原始 YAML,Kubernator 还将可视化基于角色的访问控制(RBAC)资源,显示用户、组、服务帐户、角色和集群角色之间的关系。

安装速度很快,Docker 映像可以随时部署到现有的 Kubernetes 集群中:

kubectl create ns kubernator
kubectl -n kubernator run --image=smpio/kubernator --port=80 kubernator
kubectl -n kubernator expose deploy kubernator
kubectl proxy 

接下来,只需在浏览器中打开服务代理 URL

Kubernetes 运营视图

Kubernetes 运营视图主页

多个 K8s 集群的只读系统控制面板

你有没有想过像电影里的超级极客一样管理你的 Kubernetes 集群?那 KOV 就是你的了。

基于 WebGL,KOV 将你的 Kubernetes 仪表盘可视化为一系列嵌套的盒子,显示集群、节点和单元。附加的图表直接嵌套在这些元素中,工具提示提供了附加的细节。可视化效果可以缩放和平移,以深入查看各个窗格。

KOV 是一个只读仪表板,因此您不能使用它来管理集群或设置警报。

然而,我用 KOV 来演示 Kubernetes 集群如何在添加和删除 pod 和节点时工作,人们说这种特殊的可视化是他们第一次理解 Kubernetes 是什么。

KOV 提供了一组 YAML 文件,可以作为一个组部署到现有集群中,从而简化了安装:

kubectl apply -f deploy
kubectl port-forward service/kube-ops-view 8080:80 

库布里克斯

库布里克斯主页

针对单个 Kubernetes 集群的可视化工具/故障排除工具

Kubricks 是一个桌面应用程序,它将 Kubernetes 集群可视化,并允许您从节点级别深入到流量视图,反映 kube-proxy 通过服务将传入请求定向到不同 pods 的方式。

我的 minikube 集群只有一个节点,看起来没什么意思:

单击节点会显示部署到该节点的单元:

这是 Kubricks 的交通视图:

我不得不承认,我很难理解库布里克斯向我展示了什么。要查看流量图中各点之间的连接,我必须缩小到标签难以阅读的位置,节点视图似乎缺少一些窗格。

macOS 和 Linux 的下载安装很容易。

八分仪

八分主页

一个基于 web 的、高度可扩展的平台,供开发人员更好地理解 Kubernetes 集群的复杂性。

Octant 是一个本地安装的应用程序,它公开了一个基于 web 的仪表板。Octant 有一个直观的界面,用于导航、检查和编辑 Kubernetes 资源,并能够可视化相关资源。它还有明暗模式。

我特别喜欢直接从接口配置端口转发的能力。

使用 Brew 和 Chocolatey 提供的包,以及针对 Linux 编译的 RPM 和 DEB 包,安装非常容易。

编织范围

编织范围主页

Docker & Kubernetes 的监控、可视化和管理

Weave Scope 提供了 Kubernetes 节点、pod 和容器的可视化,显示了有关内存和 CPU 使用的详细信息。

【T8

更令人感兴趣的是 Weave Scope 捕捉吊舱如何相互通信的能力。这种洞察力是我在这里测试的其他仪表板所没有的。

但是,Weave 范围是非常面向过程的,忽略了静态资源,如配置映射、机密等。

安装很简单,只需一个 YAML 文件就可以直接部署到 Kubernetes 集群中。

kubectl apply -f "https://cloud.weave.works/k8s/scope.yaml?k8s-version=$(kubectl version | base64 | tr -d '\n')"
kubectl port-forward -n weave "$(kubectl get -n weave pod --selector=weave-scope-component=app -o jsonpath='{.items..metadata.name}')" 4040 

结论

如果官方的 Kubernetes 仪表板不能满足您的需求,有大量高质量、免费和开源的替代品可供选择。总的来说,我对这些仪表板的安装简单印象深刻,很明显,他们的设计中投入了大量的工作,大多数都提供了至少一个令人信服的更换理由。*

为 CloudFormation - Octopus Deploy 创建 AMI 映射

原文:https://octopus.com/blog/ami-mappings-cloudformation

AWS 提供的许多服务都是针对个别地区的,亚马逊机器图像(AMIs)只是其中一个例子。虽然通用 AMI 发布到所有地区,但每个地区的 AMI ID 都是唯一的。

这在编写 CloudFormation 脚本时提出了一个挑战,因为传递给 EC2 资源的 AMI ID 是特定于区域的,这使得您的模板也是特定于区域的。

Mappings 可以用来编写通用的 CloudFormation 模板,允许 AMI IDs 映射到一个区域,并在部署模板时进行查找。不幸的是,AMI IDs 经常变化,并且没有简单的映射引用包含在您的模板中。

在本文中,您将了解如何使用最新的区域 AMI IDs 生成最新的映射,以便包含在您的 CloudFormation 模板中。

先决条件

脚本需要jqjq 下载页面包含为主要 Linux 发行版安装工具的说明。

查找脚本

下面的 Bash 脚本在 YAML 构建了一个云结构图:

#!/usr/bin/env bash
echo "Mappings:"
echo "  RegionMap:"
regions=$(aws ec2 describe-regions --output text --query 'Regions[*].RegionName')
for region in $regions; do
    (
     echo "    $region:"
     AMI=$(aws ec2 describe-images --region $region --filters Name=is-public,Values=true Name=name,Values="$1*" Name=architecture,Values=x86_64 | jq -r '.Images |= sort_by(.CreationDate) | .Images | reverse | .[0].ImageId')
     echo "      ami: $AMI"
    )
done 

将脚本保存到类似于amimap.sh的文件中,然后使用命令将该文件标记为可执行文件:

chmod +x amimap.sh 

调用该脚本时,AMI 名称(或 AMI 名称的开头)作为第一个参数:

./amimap.sh amzn2-ami-kernel-5.10 

输出如下所示:

$ ./amimap.sh amzn2-ami-kernel-5.10-hvm
Mappings:
  RegionMap:
    eu-north-1:
      ami: ami-06bfd6343550d4a29
    ap-south-1:
      ami: ami-052cef05d01020f1d
    eu-west-3:
      ami: ami-0d3c032f5934e1b41
    eu-west-2:
      ami: ami-0d37e07bd4ff37148
    eu-west-1:
      ami: ami-04dd4500af104442f
    ap-northeast-3:
      ami: ami-0f1ffb565070e6947
    ap-northeast-2:
      ami: ami-0eb14fe5735c13eb5
    ap-northeast-1:
      ami: ami-0218d08a1f9dac831
    sa-east-1:
      ami: ami-0056d4296b1120bc3
    ca-central-1:
      ami: ami-0bae7412735610274
    ap-southeast-1:
      ami: ami-0dc5785603ad4ff54
    ap-southeast-2:
      ami: ami-0bd2230cfb28832f7
    eu-central-1:
      ami: ami-05d34d340fb1d89e5
    us-east-1:
      ami: ami-0ed9277fb7eb570c9
    us-east-2:
      ami: ami-002068ed284fb165b
    us-west-1:
      ami: ami-03af6a70ccd8cb578
    us-west-2:
      ami: ami-00f7e5c52c0f43726 

在云形成中使用映射

以下 CloudFormation 模板演示了如何使用脚本生成的映射:

Mappings:
  RegionMap:
    eu-north-1:
      ami: ami-06bfd6343550d4a29
    ap-south-1:
      ami: ami-052cef05d01020f1d
    eu-west-3:
      ami: ami-0d3c032f5934e1b41
    eu-west-2:
      ami: ami-0d37e07bd4ff37148
    eu-west-1:
      ami: ami-04dd4500af104442f
    ap-northeast-3:
      ami: ami-0f1ffb565070e6947
    ap-northeast-2:
      ami: ami-0eb14fe5735c13eb5
    ap-northeast-1:
      ami: ami-0218d08a1f9dac831
    sa-east-1:
      ami: ami-0056d4296b1120bc3
    ca-central-1:
      ami: ami-0bae7412735610274
    ap-southeast-1:
      ami: ami-0dc5785603ad4ff54
    ap-southeast-2:
      ami: ami-0bd2230cfb28832f7
    eu-central-1:
      ami: ami-05d34d340fb1d89e5
    us-east-1:
      ami: ami-0ed9277fb7eb570c9
    us-east-2:
      ami: ami-002068ed284fb165b
    us-west-1:
      ami: ami-03af6a70ccd8cb578
    us-west-2:
      ami: ami-00f7e5c52c0f43726
Resources: 
  myEC2Instance: 
    Type: "AWS::EC2::Instance"
    Properties: 
      ImageId: !FindInMap
        - RegionMap
        - !Ref 'AWS::Region'
        - ami
      InstanceType: m1.small 

查找 AMI 名称

您会从上面的命令中注意到,AMI 名称必须作为参数传递。然而,AWS 控制台通常会显示 AMI 描述,这对用户更友好。那么,如何从 AMI ID 或描述中找到名字呢?

一个简单的解决方案是在 EC2 控制台中打开图像链接。这允许通过其 ID 或描述来搜索公共 AMI,然后 AMI 详细信息页面显示 AMI 名称:

AMI Details page

使用参数存储

允许 CloudFormation 模板自动引用最新的 Amazon AMIs 的另一个选项是查询 AWS Systems Manager 参数存储。博客使用 AWS 系统管理器参数存储查询最新的 Amazon Linux AMI id演示了如何使用如下所示的模板引用最新的 Amazon Windows 和 Linux AMI:

Parameters:
  LatestAmiId:
    Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>'
    Default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2'

Resources:
 Instance:
    Type: 'AWS::EC2::Instance'
    Properties:
      ImageId: !Ref LatestAmiId 

结论

用最新的 AMI IDs 保持您的 CloudFormation 模板的更新是一个持续的挑战。因为每个地区都有唯一的 AMI IDs,所以情况更加复杂。

在本文中,您了解了如何使用区域 AMI IDs 生成最新的映射,以便复制并粘贴到您的 CloudFormation 模板中。

查看我们的关于云形成模板的其他帖子

阅读我们的 Runbooks 系列的其余部分。

愉快的部署!

章鱼圣诞颂歌-章鱼展开

原文:https://octopus.com/blog/an-octopus-christmas-carol

2022 年即将结束。当章鱼团队的大部分人准备休息时(不要担心,如果你需要,我们仍然可以提供帮助),我们想喘口气,反思一下。

因此,‘进来吧,进来吧,更好地了解我们’当我们与过去、现在和未来的部署幽灵一起旅行。

我们保证他们比拜访史克鲁奇的人要好得多。

过去的部署

在我们看 2022 年的章鱼之前,让我们花点时间看看 2021 年我们从哪里停下来的。

2021 年对章鱼来说是巨大的一年,有很多里程碑。我们:

  • 我们的团队从 77 人发展到 179 人
  • 宣布来自 Insight Partners 的项投资
  • 我们的收入增长了 60%
  • 通过了我们的第一次财务审计
  • 在早期访问预览版中推出了新功能,如配置为代码和 Amazon ECS 支持
  • 在布里斯班南岸开设了我们的新办公室。随便看看

部署完毕

2022 年对章鱼来说也是非凡的一年。下面我们来探讨一些值得注意的亮点。

我们如何改进章鱼

2022 年,我们推出了一系列新功能,为您的部署节省时间和精力。此外,我们为您使用的服务创建了更多集成。

我们开发了易于使用的工具,帮助您构建部署管道。

我们的主页有了新的外观

虽然我们仍在进行调整,还会有更多的改进,但我们更新了 octopus.com。

它不仅看起来很棒(如果我们自己这么说的话),它更好地解释了 Octopus Deploy 解决的问题以及它如何帮助您更快地交付软件。

我们宣布了我们的第一笔收购

在二月份,我们收购了 Dist——一个云容器和工件存储服务。

此次收购有助于我们更好地完善 Octopus 的云功能和支持。

由于我们的 ISO 27001 认证,八达通比以往任何时候都更安全

Octopus 一直非常重视安全性,但我们知道我们的客户不能只相信我们的话。我们的信任团队 2022 年的目标是获得 ISO 27001 认证,这是信息安全管理领域最著名的标准。

为此,我们的团队:

  • 设置许多新的安全审计和审查功能
  • 更新了我们的安全政策和培训
  • 确保我们的文档在开发生命周期的每一步都与我们的行动相匹配

辛勤的工作在 2022 年末得到了回报,我们成功通过了审核,并获得了 ISO 27001 官方认证。我们希望这一认证能让您安心。

证书将很快在我们的网站上下载,但我们的旅程并没有就此结束。我们的目标是在 2023 年增加我们的安全资格,所以敬请关注新的发展。

我们发布了 DevOps 工程师手册

DevOps 可能会令人困惑,因此我们创建了 DevOps 工程师手册来帮助其他人了解其流程和理念。

首先,我们非常关注我们的专业主题——连续交付——但是手册将会扩展和发展。

另外,如果你正在寻找 DevOps 书籍推荐,请查看我们的互动阅读列表

我们有一个创纪录的季度

我们将在高潮中结束 2022 年!第四季度是完成业务的创纪录季度。

我们期待以此为基础,帮助更多的组织更快、更可靠地交付软件。

2022 年统计

最后,这里有一些关于章鱼一年的有趣数据(在我写这篇文章的时候):

  • 我们的客户完成了超过 36,479,186 次部署
  • 我们有超过 9080 个新的试验
  • 我们的团队增加了 61 人,达到 240 人
  • 我们的博客有 673,058 次浏览量——如何在 Docker 上安装 Jenkins是我们最受欢迎的帖子,有 33,392 次浏览量
  • Octopus 服务器进行了以下代码更改:
    • 更改了 18,062 个文件
    • 增加了 649,364 行
    • 删除 361,422 行
    • 修改了 63,987 行

即将进行部署

我们一直在努力改进 Octopus,那么我们的未来会怎样呢?目前,我们正致力于:

  • 动态环境支持——轻松提供测试新功能分支、集成等的基础设施。
  • Octopus Runbooks 的 Config as 代码——在您的 Git 存储库中查看和管理您的 runbook 流程,就像您已经可以使用您的部署流程一样。
  • Octopus CLI vNext -我们正在添加新的功能,比如一个易于使用的交互模式。

关注我们的路线图了解关于这些特性的更多信息,并发现即将推出的其他产品。

各位,部署愉快!

宣布章鱼云-章鱼部署

原文:https://octopus.com/blog/announcing-octopus-cloud

Octopus Clouds launch fireworks illustration

章鱼云来了!是的,经过几个月的计划、编码、测试、错误修复和重新测试,我们将向全世界推出章鱼云。

它是章鱼,在云中

Octopus Cloud 是一个功能齐全的真实 Octopus 实例,由专家托管和管理。

我们在过去的一年里付出了很多努力,以确保你现在在自己托管的 Octopus 实例中享受的功能也可以在云版本中使用。我们还没有造出章鱼卫星。我们使用相同的代码,你可以从octopus.com下载,托管在云中,由久经沙场的 DevOps 老手团队管理。

章鱼云之旅

https://www.youtube.com/embed/o_GHws666_I

VIDEO

多给我讲讲!

很省事。我们负责保持基础架构的正常运行,因此您不必担心。我们做备份,我们监控性能,我们推出升级,我们做所有相关的繁重工作。如果出了问题,我们会解决它。你不必再管理那个宠物虚拟机了。只要专注于交付优秀的软件,我们就能群聚章鱼。

很实惠。我们的商业团队夜以继日地工作,想出了适用于所有人的定价模式。我们为最多五个用户的团队提供的入门版每月只需 10 美元。对于较大的团队,我们将标准版提高到每月 20 美元/用户,我们将在 2018 年第 3/4 季度推出企业产品。

很安全。您的数据对我们很重要,因此我们会安全地备份和加密您的数据。如果您希望移回私有实例,可以随时请求数据的副本。

我们已经抑制了服务器的一些更高的、完全信任的功能,但是这些任务仍然可以在完全信任的触手目标上很好地执行。

完全支持。与我们的自托管版本一样,Octopus 支持团队只需几个按键。价格里包含了支持,不要害羞。

现在有了

你现在就可以报名试用章鱼云。

快乐的云部署!

宣布 Octopus Deploy 3.0 预发布- Octopus Deploy

原文:https://octopus.com/blog/announcing-octopus-deploy-3.0-pre-release

你们期待已久的消息.....

章鱼 3.0 已经准备好让你尝试了!

正如我们之前说过的,我们最终对这个版本做了很多改变,所以我们花了更长的时间来发布一些可以使用的东西。Octopus 3.0 更快,更稳定,并且有很多好的特性,我们听到你…你想要得到它。接下来是这样的。

注册参加网上研讨会

如果你想成为第一批看到它的人,请在下周早些时候参加我们的网络研讨会。我们重复了三次,所以我们应该覆盖每个人的时区。在网上技术交流讲座中,我们将向您展示 3.0 的新特性、一些不同之处以及如何开始使用它。报名的链接是 http://octopusdeploy.com/webinars

看博客

网上研讨会后的几天,我们将发表一篇博客文章,与全世界分享链接。所以,如果你不能参加网上研讨会,也不用担心,你不会等太久。我们只是想确保我们有最常见问题的答案,所以我们发布了一点发布公告。

所以准备好!

所以对于每个焦急等待的人来说...下周你会拿到预发行版。你将能够带来 2.6 版本的备份,你将拥有 SQL Server、离线部署、SSH 和 Linux 支持、新的 Azure 网站和云服务支持,以及更快、更好的 Octopus 体验。我们等不及了!

通过 Bash 和 jq - Octopus Deploy 使用 Octopus API

原文:https://octopus.com/blog/api-bash-jq

Octopus Deploy 是 API 优先编写的,这意味着您可以在用户界面(UI)中做的任何事情,都可以通过 API 调用来完成。当我与 API 交互时,我使用 PowerShell,因为它具有将 JSON 转换为 PowerShell 对象的内置功能,这使得使用 JSON 很容易。然而,并不是所有的 Octopus 客户都使用 PowerShell,有些客户需要使用 Bash 的基于*nix 的解决方案。

在这篇文章中,我演示了如何使用 Bash 和 Octopus API。

japan quarterly 日本季刊

Octopus Deploy API 以 JSON 格式返回数据。然而,Bash 没有提供处理 JSON 数据的内置方法,而是将 JSON 视为字符串。虽然 Bash 有一些复杂的字符串操作功能,但是使用 JSON 数据仍然很困难。

为了解决这个问题,Linux 社区开发了一个强大的命令行实用程序 jq 来解析 JSON。Jq 已经成为处理 JSON 数据的首选工具,可以通过使用包管理器(如 APT 或 YUM)轻松安装。

卷曲

Wget 和 cURL 是使用 Bash 发出 web 请求的两种最常用的方法。有这么多可用的 Linux 发行版,您无法判断安装的是哪一个实用程序。本文中的例子使用了 cURL 工具。像 jq 一样,cURL 可以使用包管理器来安装。

使用分页数据

Octopus 中的许多 API 以分页格式返回数据。对于这些 API 调用,您可以提供 querystring 参数如skiptake来操纵调用的结果。请参见 https://[YourServer]/api,获取 api 方法列表和每种方法的 querystring 参数列表。

有时您希望返回给定调用的所有结果,例如当您检索所有项目时。对于这种情况,我编写了一个 Bash 函数,递归调用 API,直到返回所有结果。

虽然这篇文章的这一部分谈到了分页数据,但是Get-OctopusItems函数也适用于非分页 API 调用。

Get-OctopusItems () {
    octopusUri=$1
    apiKey=$2
    skipCount=$3

    header="X-Octopus-Apikey: $apiKey"
    items=()
    skipItemQuerystring=""

    # Adjust querysting accordingly
    if [[ "$octopusUri" == *"?"* ]]; then
        skipItemQuerystring="&skip="
    else
        skipItemQuerystring="?skip="
    fi

    # Append the amount to skip
    skipItemQuerystring+="$skipCount"

    # Get results
    resultSet=$(curl -H "$header" -H "Accept: application/json" -H "Content-Type: application/json" "$octopusUri$skipItemQuerystring")

    # Check to see if items is present
    items=$(echo "$resultSet" | jq .Items)

    if [[ ! -z "$items" ]]; then

        # Check to see if results are bigger than page count
        itemsPerPage=$(echo "$resultSet" | jq -r .ItemsPerPage)
        totalResults=$(echo "$resultSet" | jq -r .TotalResults)
        itemCount=$(echo "$(($totalResults - $skipCount))")

        if [[ "$itemCount" -gt "$itemsPerPage" ]]; then

            # Increment skip count
            skipCount=$(echo "$(($itemsPerPage + $skipCount))")

            # Recursively call
            items+=$(Get-OctopusItems "$octopusUri" "$apiKey" "$skipCount")
        fi
    else
        echo "$items"
    fi

    echo "$items"
}

OctopusUrl="https://yourserverurl"
spaceId="Spaces-1"
ApiKey="API-YourAPIKey"

# Get all the projects
projects=$(Get-OctopusItems "$OctopusUrl/api/$spaceId/projects" "$ApiKey" 0)

if [[ "$projects" == *"]["* ]]; then
    # Replace characters to make one contiguous array
    projects=$(echo "${projects//][/,}")
fi 

对 API 的每个调用都返回一个项目数组。返回的 JSON 中有多个 JSON 数组。

if语句通过测试][的字符串并在找到时用,替换它来检查是否返回了许多数组。这使得 JSON 字符串成为单个数组,更容易处理。

在返回所有项目数据之后,您可以使用更多的 jq 命令来检索元素,比如 ProjectId,并检索项目特定的数据,比如部署过程、操作手册或变量。

# Iterate over returned items
arr=( $(echo "$projects" | jq -r '.[].Id') )

for projectId in "${arr[@]}"
do
    echo "$projectId"
done 

用 Bash 发布到 API

上面的例子演示了如何向 API 发出 GET 请求。虽然有用,但是这些请求只是您与 API 交互的一部分。下面的示例创建一个 JSON 文档,以发送到中断 API:

automaticResponseReasonNotes="Because I said so!"
automaticResponseManualInterventionResponseType="Proceed"

# Create JSON document
jsonBody=$(jq -n \
        --arg notes "$automaticResponseReasonNotes" \
        --arg result "$automaticResponseManualInterventionResponseType" \
        '{"Notes": $notes, "Result": $result}' )

# Submit response
curl -H "$header" -H 'Content-Type: application/json' -X POST -d "$jsonBody" "$automaticResponseOctopusUrl/api/$spaceId/interruptions/$manualInterventionId/submit" 

结论

这篇文章演示了如何使用 Bash、cURL 和 jq 与 Octopus API 进行交互。 Octopus API 示例页面包含更多在 PowerShell、C#、Python 和 Go 中使用 API 的示例。

愉快的部署!

编写创建 Octopus API 密钥的脚本- Octopus Deploy

原文:https://octopus.com/blog/apikey-creation-automation

Octopus keyring

Octopus 的高级用户应该已经熟悉了健壮的 Octopus REST API。Octopus 首先被设计成 API,所以你可以在 Octopus 门户网站中做任何事情,你都可以用 API 来做。然而,在与 API 交互之前,您需要创建一个 Octopus API 键。这些步骤需要人工完成,但是自动化这个过程是可能的。

在这篇博文中,我将通过脚本创建一个用于 Octopus REST API 的 API 键。

最终的脚本可以在 GitHub gist 中找到。

Swagger API 文档

当我想用 Octopus API 实现自动化时,我首先去的地方是 Swagger docs。每个 Octopus 服务器都有一个内置的发布所有 API 文档的路径。只需将/swaggerui添加到您的 Octopus 服务器的基本 URL 路径中。比如:samples.octopus.app/swaggerui

生成的页面是由 API 资源组织的,数量很多。让我们做一个CTRL-F来寻找apikey:

Swagger ApiKeys resource screenshot

ApiKeys: POST /users/{userId}/apikeys下的第一行允许我们为指定的用户创建一个新的 API 键。因为 Octopus 中的 API 键与 Octopus 用户相关联,所以这些键继承分配给该用户的权限。

在提供新的 API 键时,最好设置专用于特定功能或集成的 Octopus 服务帐户

让我们假设我们知道我们的{userId},并使用一个快速的一行 PowerShell cmdlet 来创建一个 API 密钥:

> Invoke-RestMethod -Method Post -Uri https://samples.octopus.app/api/users/Users-561/apikeys -Headers @{'X-Octopus-ApiKey'='API-XXXX...'} -Body (@{'Purpose'='Just blogging'} | ConvertTo-Json)

Id      : apikeys-I6D74k9rh7eyoqXDlqJCvlsVgU
Purpose : Just blogging
UserId  : Users-561
ApiKey  : API-XXXX...
Created : 2/18/2021 2:25:49 PM
Expires :
Links   : @{Self=/api/users/Users-561/apikeys/apikeys-I6D74k9rh7eyoqXDlqJCvlsVgU} 

您可能会注意到,在这个例子中,我们已经有了一个 API 来创建一个新的 API 键。

当您还没有 API 密钥时创建 API 密钥

假设您正在编写自动化程序来供应 Octopus 服务器本身。您已经使用 Octopus Deploy Chocolatey 包Octopus.Server.exe 命令行工具,甚至新的 Octopus Deploy Terraform 提供者编写了脚本。您不希望在您的供应自动化过程中中断,因为您需要登录到您的 Octopus 服务器,创建一个 API 密匙,然后手动将密匙插入到自动化的其余部分。

当您在 Octopus 中创建新用户时,这些用户没有 API 密钥,但是 Octopus Web 门户允许他们在登录时创建 API 密钥。如果浏览器能做到,我们也能。以下是要做的事情:

  1. 使用用户名和密码模拟浏览器登录。
  2. 从 Octopus 服务器取回任何必要的 cookies。
  3. 当创建用户的第一个 API 密钥时,使用 cookies 发出与浏览器相同的请求。

当您第一次安装 Octopus 服务器时,安装程序会要求您创建一个本地系统帐户自定义域帐户。为简单起见,我们假设您有一个本地系统帐户。您也可以使用Octopus.Server.exe命令行界面的 admin 命令创建一个。

当你登录到你的 Octopus 门户网站时,你需要检查浏览器中发生了什么。让我们使用 Chrome 内置的 DevTools

  1. 浏览你的八达通网站登入页面。
  2. 在页面上点击右键,选择 Inspect ,或者使用热键 Ctrl-Shift-i 打开 DevTools。
  3. 选择截图中显示的网络选项卡。
  4. 输入用户名和密码后,点击登录

Chrome DevTools screenshot

在 DevTools 窗口中,你应该看到 Chrome 记录了登录过程中发生的许多请求。这些请求包括必要的资产文件(js、css 等)。)、初始化 Octopus 仪表板的资源请求等等。向上滚动到序列的顶部,查找标记为登录的请求:

Login Recorded screenshot

点击登录请求。请求列表右侧将出现一个窗口,显示附加信息。该窗口有自己的一组选项卡,您需要选择标题

滚动到 Headers 页面的底部会显示所使用的确切请求正文。当您在 PowerShell 中构建请求时,这将非常重要。检查响应头还会给您一些重要信息:

Response headers screenshot

Octopus 服务器在登录后向浏览器发回两个 Set-Cookie 头,浏览器将其存储在其 Cookie 存储器中。在对同一个域的后续请求中,浏览器被编程为在 Cookie 头中发送这些 Cookie。这就是 Octopus 服务器识别您的独特会话的方式。

让我们编写一些 PowerShell 来模拟这个过程的一部分:

$octopusUrl = 'https://samples.octopus.app'
$username = 'admin'
$password = 'your-password'

$loginPath = "$octopusUrl/api/users/login"

$response = Invoke-WebRequest -Method Post `
    -Uri $loginPath `
    -Body (@{'Username' = $username;'Password' = $password} | ConvertTo-Json) `
    -SessionVariable 'session' 

这里需要注意一些事情:

  • 我们在这里使用Invoke-WebRequest cmdlet 而不是Invoke-RestRequest,因为我们需要直接访问响应头来获取 cookies。
  • -SessionVariable参数创建了一个类型为 WebRequestSession 的变量$session,这使得获取 cookies 变得很容易,我们接下来会看到。
  • 响应体包含了User ID,下一个请求也需要它。

让我们存储我们需要的变量:

$userId = ($response.Content | ConvertFrom-Json).Id
$csrfToken = $session.Cookies.GetCookies($loginPath) | Where-Object { $_.Name.StartsWith('Octopus-Csrf-Token') }
$sessionToken = $session.Cookies.GetCookies($loginPath) | Where-Object { $_.Name.StartsWith('OctopusIdentificationToken') } 

现在,您可以将这两个令牌添加到Cookie头中,并希望它能够工作。但是由于我们已经在使用 Chrome DevTools,我们可以记录生成 API 键的请求,并理解浏览器如何使用这些值。

在 Octopus 门户网站中,导航到您的个人资料页面,然后是我的 API 密钥页面。在 DevTools 仍然打开并记录的情况下,让我们创建一个 API 键并查找下面截图中显示的apikeys POST 请求。仔细看看请求头:

API key generation headers screenshot

如果比较您在登录请求后收到的两个Set-Cookie响应头,您可以看到浏览器是如何使用这些值的。现在您可以完成您的请求:

$headers = @{
    'Cookie' = $sessionToken.Name + '=' + $sessionToken.Value
    'X-Octopus-Csrf-Token' = $csrfToken.Value
}

Invoke-RestMethod -Method Post `
    -Uri "$octopusUrl/api/users/$userId/apikeys" ` # $octopusUrl is defined at the top of the script
    -Headers $headers `
    -Body (@{'Purpose'='Just Blogging'} | ConvertTo-Json) 

完整的剧本

$octopusUrl = 'https://samples.octopus.app'
$username = 'admin'
$password = 'your-password'

$loginPath = "$octopusUrl/api/users/login"

$response = Invoke-WebRequest -Method Post `
    -Uri $loginPath `
    -Body (@{'Username' = $username;'Password' = $password} | ConvertTo-Json) `
    -SessionVariable 'session'

$userId = ($response.Content | ConvertFrom-Json).Id
$csrfToken = $session.Cookies.GetCookies($loginPath) `
    | Where-Object { $_.Name.StartsWith('Octopus-Csrf-Token') }
$sessionToken = $session.Cookies.GetCookies($loginPath) `
    | Where-Object { $_.Name.StartsWith('OctopusIdentificationToken') }

$headers = @{
    'Cookie' = $sessionToken.Name + '=' + $sessionToken.Value
    'X-Octopus-Csrf-Token' = $csrfToken.Value
}

Invoke-RestMethod -Method Post `
    -Uri "$octopusUrl/api/users/$userId/apikeys" `
    -Headers $headers `
    -Body (@{'Purpose'='Automation'} | ConvertTo-Json) 

结论

为 API 密钥的创建编写脚本对于新的服务用户、提供 Octopus 实例或与 Octopus Terraform 提供程序一起使用非常有用。

使用 Chrome DevTools(或 Firefox、Edge 或 Safari 等效软件)是查看 Octopus 前端门户网站(一个单页 React 应用程序)和 Octopus REST API 之间的交互的强大方法。偶尔,您可能会发现 Swagger 文档只是稍微有些偏离,或者没有您需要的所有信息,使用浏览器的工具可以给您一个明确的答案。

有兴趣了解更多关于 Octopus REST API 的信息吗?查看我们最近的网络研讨会:使用 Octopus API 通过自动化重复任务节省时间

愉快的部署!

应用服务器与 UberJAR - Octopus 部署

原文:https://octopus.com/blog/application-server-vs-uberjar

不久前,如果您想部署一个 Java web 应用程序,您可以编译一个 WAR 文件并将其托管在应用服务器中。今天,Java web 应用程序可以像自包含的 UberJARs 一样容易地部署。

什么是像 WildFly 这样的应用服务器,什么是 UberJAR,以及像 WildFly Swarm 这样的项目如何改变 Java 部署的执行方式?

什么是 Java EE 应用服务器?

考虑以下代码:

public class HelloWorld {
    public static void main(String[] args)  {
        System.out.println("Hello, World");
    }
} 

这是用 Java 编写的传统的“Hello World”应用程序。作为一名开发人员,我可以在任何有 Java 运行时环境(JRE)的地方运行这些代码。我也不需要捆绑任何额外的类库,因为这里引用的所有类都是由任何 JRE 提供的,无论它是来自 Oracle、OpenJDK、Zulu 还是 IBM 等等。

哪个供应商提供了 JRE 并不重要,Java 应用程序最终运行在什么操作系统上也不重要。因为这段代码使用了标准的 Java 类,所以它可以在任何地方运行。

现在考虑这段代码:

package com.octopus;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("/")
public class MyApplication extends Application {

    public MyApplication() {
    }
} 
package com.octopus;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;

@Path("/")
public class MyResource {

    @GET
    @Produces("text/plain")
    public String get() {
        return "Hello World";
    }
} 

我们这里有一个基于 JAX-RS 标准的简单 REST API。JAX-RS 是 Java EE 规范的一部分,这意味着作为一名开发人员,我可以将这个应用程序部署到任何 Java EE 应用服务器上。

更重要的是,我不需要捆绑任何实现 JAX-RS 标准的库。根据应用服务器的不同,JAX-RS 标准可能由像 RESTEasy 或 Jersy 这样的库提供。但是只要底层库符合规范,我的应用程序就能按预期运行。

当然,Java EE 提供的不仅仅是创建 RESTful 服务的规范。它包括数据库访问、消息传递、邮件、MVC 框架、管理 API 和更多的规范。

多个供应商提供了 Java EE 规范的实现,WildFly 只是其中一个例子。你还有 WebSphere、WebLogic、Payara、GlassFish、TomEE 等

但是我部署到哪个供应商的应用服务器并不重要。只要我的代码和应用服务器符合 Java EE 规范,我就可以在任何应用服务器上运行我的代码。

在现实世界的实践中,构建可以部署到多个 Java EE 应用服务器的 Java EE 应用程序并不容易。但是为了讨论的方便,我们假设情况是这样的。

因此,一般来说,Java EE 应用服务器提供了一个环境,在这个环境中可以部署和运行使用 Java EE 规范编写的应用程序。

重要的是,Java EE 应用服务器通常并排托管多个应用程序。例如,您可以部署多个网站,每个网站都被编译为一个单独的 WAR 文件,并排放在一个应用服务器中。可以独立地部署、启动、停止和取消部署各个应用程序,而不必启动和停止应用服务器本身。

什么是战争?

Web 档案(WAR)是 Java web 应用程序使用的一种档案格式。

看看爆炸部署 vs 战争套装关于战争和爆炸战争的讨论。

什么是 UberJAR?

UberJAR 不是官方术语,还有 FatJAR、ShadowJAR 之类的其他名称。所有这些术语都指一个 JAR 文件,其中包含运行应用程序所需的所有类和资源。

考虑一下这个小代码,它使用了 Apache Commons 库。

package com.octopus;

import org.apache.commons.lang3.StringUtils;

public class Main {
    public static void main(String[] args) {
        System.out.println(StringUtils.trim(" hello world "));
    }
} 

为了从这段代码中生成一个可执行的 JAR,Maven build 添加了一个包含以下内容的清单文件:

Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: mcasperson
Class-Path: libs/commons-lang3-3.6.jar
Created-By: Apache Maven 3.5.0
Build-Jdk: 1.8.0_131
Main-Class: com.octopus.Main 

这里重要的一行是Class-Path,它引用了libs/commons-lang3-3.6.jar下的第二个 JAR 文件。

当我们列出 JAR 文件的内容时,我们会看到它只包含了Main.class文件。

$ jar tf target/original-uberjardemo-1.0-SNAPSHOT.jar
META-INF/
META-INF/MANIFEST.MF
com/
com/octopus/
com/octopus/Main.class
META-INF/maven/
META-INF/maven/com.octopus/
META-INF/maven/com.octopus/uberjardemo/
META-INF/maven/com.octopus/uberjardemo/pom.xml
META-INF/maven/com.octopus/uberjardemo/pom.properties 

要运行这个“传统的”JAR 文件,它和它的库 JAR 文件都需要存在。在这个简单的例子中,这意味着应用程序由两个 JAR 文件组成。但是一个应用程序需要几十个 JAR 文件并不罕见。

这个应用程序的 UberJAR 版本创建了一个 JAR 文件,它是所有独立依赖项的合并结果。UberJAR 文件可能非常大,但是因为它们是一个单独的文件,所以很容易分发。

如果我们列出 UberJAR 的内容,我们可以看到我们的Main.class文件存在,还有组成 Apache Commons 库的类。

$ jar tf target/uberjardemo-1.0-SNAPSHOT.jar
META-INF/
META-INF/MANIFEST.MF
com/
com/octopus/
com/octopus/Main.class
META-INF/maven/
META-INF/maven/com.octopus/
META-INF/maven/com.octopus/uberjardemo/
META-INF/maven/com.octopus/uberjardemo/pom.xml
META-INF/maven/com.octopus/uberjardemo/pom.properties
META-INF/LICENSE.txt
org/
org/apache/
org/apache/commons/
org/apache/commons/lang3/
org/apache/commons/lang3/BitField.class
... and many more apache classes 

UberJAR 文件与 WAR 文件相似,都是可用于运行应用程序的独立文件。它们的不同之处在于 WAR 文件需要一个应用服务器来运行,而 UberJAR 文件可以从 JRE 中运行。

什么是野生蜂群?

WildFly Swarm 是一个项目,它提供了将 WildFly 应用服务器提供的相同 Java EE 库捆绑到 UberJAR 中的能力。最终结果是一个单独的 JAR 文件,当它被执行时,将启动一个 web 服务器并运行 Java EE 应用程序。

WildFly Swarm 如何将所需的类和资源嵌入到 UberJAR 中的实际机制比我上面给出的例子更复杂,但最终结果是相同的:您得到一个可从任何 JRE 运行的可执行 JAR 文件。

WildFly Swarm 团队提供了一堆例子,他们JAX-遥感的例子与我上面展示的 hello world 的例子非常相似。

一旦编译完成,WildFly Swarm UberJAR 文件就可以使用标准的java可执行文件从命令行执行。

$ java -jar example-jaxrs-war-swarm.jar
2017-11-02 03:43:29,747 INFO  [org.wildfly.swarm] (main) WFSWARM0013: Installed fraction:                  Logging - STABLE          org.wildfly.swarm:logging:2017.11.0-SNAPSHOT
2017-11-02 03:43:29,756 INFO  [org.wildfly.swarm] (main) WFSWARM0013: Installed fraction:          Bean Validation - STABLE          org.wildfly.swarm:bean-validation:2017.11.0-SNAPSHOT
2017-11-02 03:43:29,757 INFO  [org.wildfly.swarm] (main) WFSWARM0013: Installed fraction:                   JAX-RS - STABLE          org.wildfly.swarm:jaxrs:2017.11.0-SNAPSHOT
2017-11-02 03:43:29,757 INFO  [org.wildfly.swarm] (main) WFSWARM0013: Installed fraction:                 Undertow - STABLE          org.wildfly.swarm:undertow:2017.11.0-SNAPSHOT
2017-11-02 03:43:29,758 WARN  [org.wildfly.swarm] (main) WFSWARM0013: Installed fraction:                  Swagger - UNSTABLE        org.wildfly.swarm:swagger:2017.11.0-SNAPSHOT
2017-11-02 03:43:32,158 INFO  [org.jboss.msc] (main) JBoss MSC version 1.2.6.Final
2017-11-02 03:43:32,250 INFO  [org.jboss.as] (MSC service thread 1-1) WFLYSRV0049: WildFly Swarm 2017.11.0-SNAPSHOT (WildFly Core 2.2.1.Final) starting
2017-11-02 03:43:32,340 INFO  [org.wildfly.swarm] (MSC service thread 1-1) WFSWARM0019: Install MSC service for command line args: []
2017-11-02 03:43:33,172 INFO  [org.jboss.as.naming] (ServerService Thread Pool -- 18) WFLYNAM0001: Activating Naming Subsystem
2017-11-02 03:43:33,175 INFO  [org.jboss.as.security] (ServerService Thread Pool -- 12) WFLYSEC0002: Activating Security Subsystem
2017-11-02 03:43:33,193 INFO  [org.jboss.as.security] (MSC service thread 1-2) WFLYSEC0001: Current PicketBox version=4.9.6.Final
2017-11-02 03:43:33,200 INFO  [org.wildfly.extension.io] (ServerService Thread Pool -- 17) WFLYIO001: Worker 'default' has auto-configured to 2 core threads with 16 task threads based on your 1 available processors
2017-11-02 03:43:33,355 INFO  [org.jboss.as.naming] (MSC service thread 1-2) WFLYNAM0003: Starting Naming Service
2017-11-02 03:43:33,365 INFO  [org.wildfly.extension.undertow] (MSC service thread 1-2) WFLYUT0003: Undertow 1.4.11.Final starting
2017-11-02 03:43:33,432 INFO  [org.xnio] (MSC service thread 1-1) XNIO version 3.4.3.Final
2017-11-02 03:43:33,447 INFO  [org.xnio.nio] (MSC service thread 1-1) XNIO NIO Implementation Version 3.4.3.Final
2017-11-02 03:43:33,524 INFO  [org.wildfly.extension.undertow] (MSC service thread 1-1) WFLYUT0012: Started server default-server.
2017-11-02 03:43:33,585 INFO  [org.wildfly.extension.undertow] (MSC service thread 1-2) WFLYUT0006: Undertow HTTP listener default listening on [0:0:0:0:0:0:0:0]:8080
2017-11-02 03:43:33,765 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: WildFly Swarm 2017.11.0-SNAPSHOT (WildFly Core 2.2.1.Final) started in 1667ms - Started 70 of 77 services (14 services are lazy, passive or on-demand)
2017-11-02 03:43:34,002 WARN  [org.wildfly.swarm.swagger] (main) WFSSWGR0002: Ignoring package: org.wildfly.swarm.generated
2017-11-02 03:43:34,003 WARN  [org.wildfly.swarm.swagger] (main) WFSSWGR0002: Ignoring package: org.wildfly.swarm.generated
2017-11-02 03:43:34,004 INFO  [org.wildfly.swarm.swagger] (main) WFSSWGR0004: Configure Swagger for deployment example-jaxrs-war.war with package org.wildfly.examples.swarm.jaxrs
2017-11-02 03:43:34,309 INFO  [org.wildfly.swarm.runtime.deployer] (main) deploying example-jaxrs-war.war
2017-11-02 03:43:34,351 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-1) WFLYSRV0027: Starting deployment of "example-jaxrs-war.war" (runtime-name: "example-jaxrs-war.war")
2017-11-02 03:43:35,307 INFO  [org.wildfly.extension.undertow] (MSC service thread 1-1) WFLYUT0018: Host default-host starting
2017-11-02 03:43:35,660 INFO  [org.jboss.resteasy.resteasy_jaxrs.i18n] (ServerService Thread Pool -- 2) RESTEASY002225: Deploying javax.ws.rs.core.Application: class org.wildfly.examples.swarm.jaxrs.MyApplication
2017-11-02 03:43:35,692 INFO  [org.wildfly.extension.undertow] (ServerService Thread Pool -- 2) WFLYUT0021: Registered web context: /
2017-11-02 03:43:35,800 INFO  [org.jboss.as.server] (main) WFLYSRV0010: Deployed "example-jaxrs-war.war" (runtime-name : "example-jaxrs-war.war")
2017-11-02 03:43:35,808 INFO  [org.wildfly.swarm] (main) WFSWARM99999: WildFly Swarm is Ready 

应用服务器还是 UberJAR?

从应用程序的角度来看,部署到应用服务器或作为独立的 UberJAR 运行没有什么区别。您可以访问相同的库,并可以公开相同的功能。事实上,您可以将一个构建传统 Java EE WAR 文件的项目转换为 WildFly Swarm UberJAR,而无需修改代码,只需在 Maven 或 Gradle 项目文件中添加几行代码。

构建 WildFly Swarm JAX-RS 示例项目实际上会生成 UberJAR 和 WAR 文件。WAR 文件可以部署到 WildFly 应用服务器,并运行与 UberJAR 相同的代码。这突出了这样一个事实,即在构建 WAR 文件以部署到应用服务器或可以独立运行的 UberJAR 时,代码不会改变。

UberJAR and WAR

这些方法之间的区别更多地在于应用程序编译后的部署和管理方式。

多年来,应用服务器已经发展到提供丰富的管理工具,允许通过 web 界面或命令行部署、启动、停止、取消部署和升级应用程序。

WildFly Admin Console

应用服务器还可以提供资源的集中配置,如数据库连接和消息队列。这种资源和应用程序的分离在运营人员控制诸如数据库线程和凭证之类的东西的环境中工作得很好。

您还可以通过让多个应用程序共享公共库来减少应用服务器的内存消耗。我见过应用服务器运行数百个单独的 web 应用,其中应用服务器托管共享库,这将每个 web 应用减少到仅几 MB。

然而,随着微服务、不可变基础设施和 PaaS 等范例越来越受欢迎,应用服务器已经过时了。在这些情况下,自包含的 UberJARs 是更自然的选择。

扩展部署为 UberJARs 的应用程序很容易,因为每个单独的应用程序都可以在需求增加时部署到新的实例,并在需求减少时关闭。

所有支持 Java 的 PaaS 解决方案都将支持运行 UberJAR,因为 Uber jar 只需要存在一个 JRE。虽然一些 PaaS 解决方案支持应用服务器,但它们往往只支持一两家供应商,或者依赖社区贡献来构建应用服务器环境。

UberJARs 还具有将基础设施移入 Java 构建工具的效果。例如,在构建 UberJAR 时,开发人员负责添加数据库连接驱动程序和配置,而不是依赖于应用服务器所拥有的配置。

不过 UberJARs 没有管理层。部署、启动和关闭应用程序由您决定;您无法获得应用服务器提供的那种本机管理工具。PaaS 解决方案通常会提供管理 UberJARs 的工具。

如果您对 Java 应用程序的自动化部署感兴趣,下载 Octopus Deploy 的试用版,并查看一下我们的文档

Octopus Deploy 来到 AppVeyor - Octopus Deploy

原文:https://octopus.com/blog/appveyor-integration

Continous Delivery in the cloud with Octopus and AppVeyor

AppVeyor 的优秀团队最近增加了对推送和部署项目的内置支持。NET、Java、JavaScript 等)与 Octopus 一起部署。AppVeyor 是一个云托管的持续集成工具,在开源项目中非常流行,尤其是在。净空间。从与您的源代码控制直接集成到部署插件,如他们的新 Octopus Deploy 产品,AppVeyor 提供了一个简单的开箱即用的解决方案来构建您的应用程序,而不必管理您自己的构建基础设施。每个构建都有自己干净的环境,其中包含启动自动化 CI 解决方案所需的所有工具。现在有了新的 Octopus 集成,这个过程将变得更加容易。

与我们即将推出的 Octopus Cloud 产品结合使用,您将很快能够创建一个基于云的、完全可扩展的持续部署管道,而无需自己管理一台机器。那是多么令人兴奋啊!

让我们看一个如何充分利用这一新产品的示例。

appveyor 章鱼外挂程式

在一个简单的免费注册过程之后,我们可以开始创建一个新的 AppVeyor 项目,我已经添加了OctopusSamples/RandomQuotes-aspmvc4 GitHub 库作为源代码库。从 BitBucket 到 VSTS,还有一系列其他存储库选项可供选择。公共的OctopusSamples/RandomQuotes-aspmvc 4存储库提供了一个基本的 ASP.NET MVC 应用程序来显示一串明智的报价。我们的目标是建立一个交付管道,将该网站部署到我们的 IIS 服务器上,用于试运行和生产环境。

构建和打包

查看构建阶段,您应该注意到一个Package Applications for Octopus Deployment标志。

AppVeyor Build Step

这个标志确保构建完成后,内容被压缩到一个包中,这个包可以被推送到 Octopus Deploy。虽然 Octopus 将接受任何 NuGet、zip 或 tar 包,但是这个标志将利用octo.exe创建一个 zip ,使用这个 AppVeyor 项目的应用程序名称和版本来命名。

Deployment配置中,选择新的Octopus Deploy部署提供者。这个特性执行所有适当的调用,将包传递给 Octopus,并创建一个相关的 Octopus Release

AppVeyor Deployment Step

添加您的 Octopus 服务器 URL 和 API 密钥后,勾选Push Packages选项,允许 AppVeyor 自动检测上一步构建的 Octopus 包。AppVeyor 随后会将包裹推送到 Octopus 内置的 NuGet feed 。虽然 Octopus 支持在新的包可用时自动创建发布版本,但是在这个场景中,我们将通过 AppVeyor 来触发它。单击Create Release复选框并提供项目名称RandomQuotes,我们稍后将在 Octopus 中设置该项目,AppVeyor 将通过编程触发该项目。

源控制配置

AppVeyor 的另一个很棒的特性是能够通过一个appveyor.yml文件提供您的构建配置,该文件与您的代码共存,而不是通过 web 门户进行设置。这意味着该过程可以直接与源代码联系起来,这样可以简化版本管理,并且您的构建步骤可以与项目一起发展。阅读他们的文档以获得更多关于这在实践中如何工作的信息。这种方法类似于我们 Octopus 正在开发的一个功能,允许您的 CD 管道的部署端通过源代码控制配置进行存储。看看关于我们如何实现“带代码的部署配置”的想法,让我们知道你的想法。

随着我们的 AppVeyor 构建管道的建立,现在让我们进入我们的 Octopus 服务器并部署这个网站。

通过八达通继续部署

为了与我们刚刚在 AppVeyor 中提供的配置相匹配,创建一个新项目,并将其命名为RandomQuotes。对于我们的简单部署场景,我们将首先转到Process部分,简单地添加一个新的 IIS 步骤。选择Deploy to IIS步骤后,我们将添加一些设置来提供 Octopus 信息,以便创建和配置 IIS 网站。

Octopus Deploy IIS

在提供了一个映射到目标的角色之后,我们将配置哪个包将用于这一步。使用内置的 AppVeyor 将推送至该 feed ),我们可以提供 PackageId RandomQuotes

配置网站本身最简单的方法就是设置两个额外的值,Website nameAppPool。对于这个例子,我们将在同一台机器上同时托管StagingProduction(对于一个真实的项目来说,这不是一个好主意),所以我们将根据正在部署的环境提供一个不同的网站名称。名称的#{Octopus.Environment.Name}部分将在部署时被替换为环境的名称。

除了Website Name之外,我们还决定在StagingProduction之间提供不同的绑定端口。这个值#{CustomPort}是在项目的Variables部分中设置的,可以根据不同的部署环境组合(如环境、机器或租户)来设定不同的值,这里仅举几个例子。

Octopus Deploy variables

一种常见的模式是在 Octopus 中为不同的环境定义变量,这些变量在应用程序运行时使用的配置文件中被替换。在部署过程中使用它们会带来大量高级场景。

对于我们的RandomQuotes项目,我们为每个环境都有一个配置转换文件。看起来像这样的Web.Production.config转换:

<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <appSettings>
    <add key="ReleaseVersion" value="#{Octopus.Release.Number}" xdt:Transform="SetAttributes" xdt:Locator="Match(key)"/>
    <add key="EnvironmentName" value="#{Octopus.Environment.Name}" xdt:Transform="SetAttributes" xdt:Locator="Match(key)"/>
    <add key="BackgroundColor" value="#1e8822" xdt:Transform="SetAttributes" xdt:Locator="Match(key)"/>
  </appSettings>
  <system.web>
    <compilation xdt:Transform="RemoveAttributes(debug)" />
  </system.web>
</configuration> 

注意ReleaseVersion的值包含了部署期间提供的模板模式。(如果实在受不了悬念和什么来看看这是什么样子,就跳到这篇帖子的末尾)。

承诺并享受

现在,我们已经配置了自动化 CI/CD 渠道。当我们提交对项目的更改时,AppVeyor 将自动检测并从 GitHub 中提取更改,构建项目,并将其推送到我们的 Octopus 服务器。从那时起,Octopus Deploy 接管并将其部署到我们的Staging环境中。一旦我们对这个版本感到满意,我们可以通过点击一个按钮来部署到Production。已经过测试的同一个构建好的包将使用我们的变量提供的新值被推送到我们的生产环境中。

【T2 Logs Together

当部署发生时,Octopus 将在您的项目中应用任何 web.config 转换并执行变量替换,以便在每个环境中运行相同的构建工件,确保您测试的代码是您在生产中运行的代码。

分期

Deployed Staging

有了应用程序的试运行版本,我们就可以在开始生产部署之前对其进行检查和测试...

Running Deployment

生产

Deployed Production

请注意转换是如何应用的,它改变了导航条的颜色,同时端口和其他变量也根据部署到的环境进行了更新。

有关这项新功能的更多信息,请务必阅读我们的 AppVeyordocs

AppVeyor + Octopus =随时部署

AppVeyor 与 Octopus Deploy 的结合提供了一种令人兴奋的新方法,以可重复、可靠的方式自动化您的连续交付渠道。向在周五下午 5 点崩溃的手工定制脚本解决方案说再见吧。今天用一个免费八达通试用来发挥 AppVeyor 新功能的威力。

愉快的部署!

2015 年 4 月社区综述-部署八达通

原文:https://octopus.com/blog/april-2015-community-roundup

虽然我们最近在这里有点安静,埋头完成 3.0,但我们社区中的伟大人们还没有!

以下是这个月我发现的一些好事情的总结。

ray gun 中的部署跟踪。我们在 RayGun 的朋友已经实现了一个部署跟踪功能,因此您可以知道您的新版本是否已经修复了所有问题,或者让您知道您是否无意中使事情变得更糟。他们有一个很棒的一步一步的指南来指导集成 Octopus Deploy 和 Raygun ,这样你的部署就会被标记出来。很棒的东西!如果你用的是 Raygun,你肯定想看看,如果你没用 Raygun...去看看,告诉他们是我们派你去的!

再次来自不可阻挡的 Jason Brown at Domain,更多关于用 Octopus Deploy 和他的机器人军队 2.5 自动化 AWS 基础设施。

当我们讨论基础设施管理时,请查看来自 Powershell DSC 和 Octopusdevopsgoys的这篇精彩帖子。

这个月已经有一些人在谈论 SQL 部署,首先是 Redgate 的 Tugberk Ugurlu(他为我们的库贡献了 SQL 发布脚本)写了一篇关于 SQL 部署的一步一步的文章。或者,如果您正在您的环境中使用 Visual Studio 工具,您可能想要阅读科林·斯温根关于部署 Dacpacs 的帖子。

最近有一些人在 Twitter 上询问关于用 Octopus 部署 DotNetNuke 站点的问题。我们现在都应该感谢达雷尔·特纳尔,他写了一本关于如何做到这一点的精彩指南。Fantatsic 的东西!

最后,我还没有在任何地方看到关于这个的博客帖子,但是在 NPM 上有一个节点脚本可以使用 Octopus API 创建和部署版本。如果 Node 是你的东西,你可能想检查一下

别忘了,如果你发表了一篇博客文章,组织了一次用户群讨论或一次研讨会或类似的活动,并希望我们让世界了解你,给我们发一条推文或一封电子邮件,我们会让你大声喊出来!

用于基础设施测试的 arquillian-Octopus 部署

原文:https://octopus.com/blog/arquillian-for-infrastructure-testing

在之前的一篇博客文章中,我们看到了 Arquillian 如何解决测试真实应用服务器中真实对象的问题。虽然这可能是更传统的阿奎利亚语使用方式,但不是唯一的方式。

编写像 Octopus 这样的工具的挑战之一是它必须支持大量的 Java 应用服务器。目前,我们支持:

  • 野花 10
  • 野火 11
  • 红帽 JBoss EAP 6
  • 红帽 JBoss EAP 7
  • Tomcat 7
  • Tomcat 8
  • Tomcat 9

不仅如此,我们还支持独立模式或域模式下的 WildFly 和 JBoss EAP。这给了我们 10 多种不同的应用服务器配置来测试我们的代码。

Octopus 还支持将 JAR 和 WAR 文件等 Java 工件直接部署到文件系统中,这意味着 WebSphere、WebLogic、GlassFish、Payara 等应用服务器也可以集成到 Octopus 部署流程中。

我们本来可以用各种应用服务器创建虚拟机并运行测试,但是 Arquillian 能够运行真正的应用服务器并将这些服务器集成到单元测试中,这意味着我们可以用 JUnit 这样的标准单元测试库测试我们的代码,并直接从 Maven 运行这些测试。

在这篇博文中,我们将看看如何使用 Arquillian 进行基础设施测试。

Maven POM 文件

配置 Arquillian 所需的大量工作都放在 Maven POM 文件中。Arquillian 已经有了全面的入门文档,所以我将在这里重点介绍一下。

我们将使用 Java 8 进行测试,因此我们相应地设置了maven.compiler.sourcemaven.compiler.target属性。

<properties>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
</properties> 

我们将使用 Arquillian 物料清单(BOM)来提供我们稍后将定义的 Arquillian 依赖项的版本。

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.jboss.arquillian</groupId>
            <artifactId>arquillian-bom</artifactId>
            <version>1.1.14.Final</version>
            <scope>import</scope>
            <type>pom</type>
        </dependency>
    </dependencies>
</dependencyManagement> 

然后我们需要 Arquillian、JUnit 和 Apache HTTP 客户端依赖项。

注意我们使用的是 Arquillian 变色龙,它提供了一种简单的方法来管理我们将要测试的各种容器(即应用服务器)。

Apache HTTP 客户机将用于执行一个简单的测试,以确保应用服务器正在运行。

<dependencies>
   <dependency>
       <groupId>org.apache.httpcomponents</groupId>
       <artifactId>httpclient</artifactId>
       <version>4.5.3</version>
   </dependency>
   <dependency>
       <groupId>org.jboss.arquillian.junit</groupId>
       <artifactId>arquillian-junit-container</artifactId>
       <scope>test</scope>
   </dependency>
   <dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
       <version>4.12</version>
       <scope>test</scope>
   </dependency>
   <dependency>
       <groupId>org.arquillian.container</groupId>
       <artifactId>arquillian-container-chameleon</artifactId>
       <version>1.0.0.Final-SNAPSHOT</version>
       <scope>test</scope>
   </dependency>
</dependencies> 

我们将定义两个 Maven 概要文件:Tomcat8WildFly9。这些配置文件定义了arquillian.launch系统属性,该属性用于选择 Chameleon 将为我们下载的应用服务器,并设置将要运行的测试的路径。在WildFly9概要文件中,我们将运行wildfly9容器,并在src/test/java/wildfly9目录中运行测试。

<profile>
    <id>WildFly9</id>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <systemPropertyVariables>
                        <arquillian.launch>wildfly9</arquillian.launch>
                    </systemPropertyVariables>
                    <includes>
                        <include>**/wildfly9/**</include>
                    </includes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</profile> 

Tomcat8概要文件将运行tomcat8容器,并从src/test/java/tomcat8目录运行测试。

<profile>
  <id>Tomcat8</id>
  <build>
      <plugins>
          <plugin>
              <groupId>org.apache.maven.plugins</groupId>
              <artifactId>maven-surefire-plugin</artifactId>
              <configuration>
                  <systemPropertyVariables>
                      <arquillian.launch>tomcat8</arquillian.launch>
                  </systemPropertyVariables>
                  <includes>
                      <include>**/tomcat8/**</include>
                  </includes>
              </configuration>
          </plugin>
      </plugins>
  </build>
</profile> 

我们使用概要文件是因为一次只能运行一个应用服务器。概要文件允许我们选择用 Maven 命令行参数测试的服务器类型。我们还分割了测试类文件,以确保只有给定服务器的测试才能使用所选择的概要文件运行。

这是完整的 POM 文件。

<?xml version="1.0" encoding="UTF-8"?>
<project  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.octopus</groupId>
    <artifactId>arquillian-infrastructure-testing</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.jboss.arquillian</groupId>
                <artifactId>arquillian-bom</artifactId>
                <version>1.1.14.Final</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.3</version>
        </dependency>
        <dependency>
            <groupId>org.jboss.arquillian.junit</groupId>
            <artifactId>arquillian-junit-container</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.arquillian.container</groupId>
            <artifactId>arquillian-container-chameleon</artifactId>
            <version>1.0.0.Final-SNAPSHOT</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <profiles>
        <profile>
            <id>WildFly9</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-surefire-plugin</artifactId>
                        <configuration>
                            <systemPropertyVariables>
                                <arquillian.launch>wildfly9</arquillian.launch>
                            </systemPropertyVariables>
                            <includes>
                                <include>**/wildfly9/**</include>
                            </includes>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>
        <profile>
            <id>Tomcat8</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-surefire-plugin</artifactId>
                        <configuration>
                            <systemPropertyVariables>
                                <arquillian.launch>tomcat8</arquillian.launch>
                            </systemPropertyVariables>
                            <includes>
                                <include>**/tomcat8/**</include>
                            </includes>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>
</project> 

配置阿奎利亚语

Arquillian 需要一个名为arquillian.xml的文件,该文件定义了它可以运行的容器(或应用服务器)。在我们的例子中,我们需要定义两个容器来匹配 Maven 概要文件分配给arquillian.launch系统属性的值。

Tomcat 8 容器

tomcat8容器从发布给 Maven 的二进制发行版下载 Tomcat 8.0.47。

我们还需要定义 Arquillian 用来访问 Tomcat 提供的管理器应用程序的用户名和密码。在这种情况下,我们对两者都使用arquillian

为了实际定义用户arquillian,我们用一个名为tomcat8-server.xml的定制配置文件来配置 Tomcat。

<container qualifier="tomcat8">
    <configuration>
        <property name="target">tomcat:8.0.47:managed</property>
        <!-- relative to CATALINA_BASE/conf; catalinaBase is set by chameleon itself -->
        <property name="serverConfig">../../../../../src/test/resources/tomcat8-server.xml</property>
        <property name="user">arquillian</property>
        <property name="pass">arquillian</property>
    </configuration>
</container> 

tomcat8-server.xml文件中,我们引用了tomcat-users.xml文件。

<!-- Global JNDI resources
     Documentation at /docs/jndi-resources-howto.html
-->
<GlobalNamingResources>
    <!-- Editable user database that can also be used by
         UserDatabaseRealm to authenticate users
    -->
    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="${catalina.home}/../../../../src/test/resources/tomcat-users.xml"/>
</GlobalNamingResources> 

最后在tomcat-users.xml文件中,我们定义了用户arquillian

<?xml version='1.0' encoding='utf-8'?>
<tomcat-users>
    <user username="arquillian" password="arquillian" roles="manager-script"/>
</tomcat-users> 

CATALINE_BASE 和 CATALINA_HOME(被tomcat-server.xml文件中的${catalina.home}引用和arquillian.xml文件中serverConfig设置的相对位置)为target/server/tomcat_8.0.47/apache-tomcat-8.0.47,是 Arquillian Chameleon 下载 Tomcat 的位置。父目录引用的长字符串从这个目录返回到 test resources目录。

WildFly 9 容器

WildFly 容器没有那么复杂。它也疯狂地下载了发布给 Maven 的二进制发行版。唯一的其他设置是服务器配置文件的名称,我们将其设置为standalone.xml

<container qualifier="wildfly9">
    <configuration>
        <property name="target">wildfly:9.0.0.Final:managed</property>
        <property name="serverConfig">standalone.xml</property>
    </configuration>
</container> 

完整的 Arquillian 配置文件

这是完整的arquillian.xml文件。

<arquillian  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="
        http://jboss.org/schema/arquillian
        http://jboss.org/schema/arquillian/arquillian_1_0.xsd">

    <container qualifier="tomcat8">
        <configuration>
            <property name="target">tomcat:8.0.47:managed</property>
            <!-- relative to CATALINA_BASE/conf; catalinaBase is set by chameleon itself -->
            <property name="serverConfig">../../../../../src/test/resources/tomcat8-server.xml</property>
            <property name="user">arquillian</property>
            <property name="pass">arquillian</property>
        </configuration>
    </container>

    <container qualifier="wildfly9">
        <configuration>
            <property name="target">wildfly:9.0.0.Final:managed</property>
            <property name="serverConfig">standalone.xml</property>
        </configuration>
    </container>

</arquillian> 

配置阿奎利亚变色龙

Chameleon 通常不需要太多的配置,尽管我们在这里使用的版本(1.0.0.Final-SNAPSHOT)必须提供一个定制的containers.yaml文件来配置 Tomcat 8。这个版本基于来自变色龙 GitHub repo 的版本。

Chameleon 在containers.yaml文件上提供了文档,所以我在这里就不赘述了。

- name: JBoss EAP
  versionExpression: 7.*
  adapters:
    - type: remote
      gav: org.wildfly.arquillian:wildfly-arquillian-container-remote:2.0.1.Final
      adapterClass: org.jboss.as.arquillian.container.remote.RemoteDeployableContainer
    - type: managed
      gav: org.wildfly.arquillian:wildfly-arquillian-container-managed:2.0.1.Final
      adapterClass: org.jboss.as.arquillian.container.managed.ManagedDeployableContainer
      configuration: &EAP7_CONFIG
        jbossHome: ${dist}
    - type: embedded
      gav: org.wildfly.arquillian:wildfly-arquillian-container-embedded:2.0.1.Final
      adapterClass: org.jboss.as.arquillian.container.embedded.EmbeddedDeployableContainer
      configuration: *EAP7_CONFIG
  dist: &EAP7_DIST
    gav: org.jboss.as:jboss-as-dist:zip:${version}
  exclude: &EAP7_EXCLUDE
    - org.jboss.arquillian.test:*
    - org.jboss.arquillian.testenricher:*
    - org.jboss.arquillian.container:*
    - org.jboss.arquillian.core:*
    - org.jboss.arquillian.config:*
    - org.jboss.arquillian.protocol:*
    - org.jboss.shrinkwrap.api:*
    - org.jboss.shrinkwrap:*
    - org.jboss.shrinkwrap.descriptors:*
    - org.jboss.shrinkwrap.resolver:*
- name: JBoss EAP Domain
  versionExpression: 7.*
  adapters:
    - type: managed
      gav: org.wildfly.arquillian:wildfly-arquillian-container-domain-managed:2.0.1.Final
      adapterClass: org.jboss.as.arquillian.container.domain.managed.ManagedDomainDeployableContainer
      configuration: *EAP7_CONFIG
    - type: remote
      gav: org.wildfly.arquillian:wildfly-arquillian-container-domain-remote:2.0.1.Final
      adapterClass: org.jboss.as.arquillian.container.domain.remote.RemoteDomainDeployableContainer
  dist: &EAP7_DIST
    gav: org.jboss.as:jboss-as-dist:zip:${version}
  exclude: &EAP7_EXCLUDE
    - org.jboss.arquillian.test:*
    - org.jboss.arquillian.testenricher:*
    - org.jboss.arquillian.container:*
    - org.jboss.arquillian.core:*
    - org.jboss.arquillian.config:*
    - org.jboss.arquillian.protocol:*
    - org.jboss.shrinkwrap.api:*
    - org.jboss.shrinkwrap:*
    - org.jboss.shrinkwrap.descriptors:*
    - org.jboss.shrinkwrap.resolver:*
- name: JBoss EAP
  versionExpression: 6.0.*
  adapters:
    - type: remote
      gav: org.jboss.as:jboss-as-arquillian-container-remote:7.1.2.Final
      adapterClass: org.jboss.as.arquillian.container.remote.RemoteDeployableContainer
    - type: managed
      gav: org.jboss.as:jboss-as-arquillian-container-managed:7.1.2.Final
      adapterClass: org.jboss.as.arquillian.container.managed.ManagedDeployableContainer
      configuration: &EAP_CONFIG
        jbossHome: ${dist}
    - type: embedded
      gav: org.jboss.as:jboss-as-arquillian-container-embedded:7.1.2.Final
      adapterClass: org.jboss.as.arquillian.container.embedded.EmbeddedDeployableContainer
      configuration: *EAP_CONFIG
  dist: &EAP_DIST
    gav: org.jboss.as:jboss-as-dist:zip:${version}
  exclude: &EAP_EXCLUDE
    - org.jboss.arquillian.test:*
    - org.jboss.arquillian.testenricher:*
    - org.jboss.arquillian.container:*
    - org.jboss.arquillian.core:*
    - org.jboss.arquillian.config:*
    - org.jboss.arquillian.protocol:*
    - org.jboss.shrinkwrap.api:*
    - org.jboss.shrinkwrap:*
    - org.jboss.shrinkwrap.descriptors:*
    - org.jboss.shrinkwrap.resolver:*
    - "*:wildfly-arquillian-testenricher-msc"
    - "*:wildfly-arquillian-protocol-jmx"
    - "*:jboss-as-arquillian-testenricher-msc"
    - "*:jboss-as-arquillian-protocol-jmx"
- name: JBoss EAP Domain
  versionExpression: 6.0.*
  adapters:
    - type: managed
      gav: org.jboss.as:jboss-as-arquillian-container-domain-managed:7.1.2.Final
      adapterClass: org.jboss.as.arquillian.container.domain.managed.ManagedDomainDeployableContainer
      configuration: *EAP_CONFIG
    - type: remote
      gav: org.jboss.as:jboss-as-arquillian-container-domain-remote:7.1.2.Final
      adapterClass: org.jboss.as.arquillian.container.domain.remote.RemoteDomainDeployableContainer
  dist: &EAP_DIST
    gav: org.jboss.as:jboss-as-dist:zip:${version}
  exclude: &EAP_EXCLUDE
    - org.jboss.arquillian.test:*
    - org.jboss.arquillian.testenricher:*
    - org.jboss.arquillian.container:*
    - org.jboss.arquillian.core:*
    - org.jboss.arquillian.config:*
    - org.jboss.arquillian.protocol:*
    - org.jboss.shrinkwrap.api:*
    - org.jboss.shrinkwrap:*
    - org.jboss.shrinkwrap.descriptors:*
    - org.jboss.shrinkwrap.resolver:*
    - "*:wildfly-arquillian-testenricher-msc"
    - "*:wildfly-arquillian-protocol-jmx"
    - "*:jboss-as-arquillian-testenricher-msc"
    - "*:jboss-as-arquillian-protocol-jmx"
- name: JBoss EAP
  versionExpression: 6.*
  adapters:
    - type: remote
      gav: org.jboss.as:jboss-as-arquillian-container-remote:7.1.3.Final
      adapterClass: org.jboss.as.arquillian.container.remote.RemoteDeployableContainer
    - type: managed
      gav: org.jboss.as:jboss-as-arquillian-container-managed:7.1.3.Final
      adapterClass: org.jboss.as.arquillian.container.managed.ManagedDeployableContainer
      configuration: *EAP_CONFIG
    - type: embedded
      gav: org.jboss.as:jboss-as-arquillian-container-embedded:7.1.3.Final
      adapterClass: org.jboss.as.arquillian.container.embedded.EmbeddedDeployableContainer
      configuration: *EAP_CONFIG
  dist: *EAP_DIST
  exclude: *EAP_EXCLUDE
- name: JBoss EAP Domain
  versionExpression: 6.*
  adapters:
    - type: managed
      gav: org.jboss.as:jboss-as-arquillian-container-domain-managed:7.1.3.Final
      adapterClass: org.jboss.as.arquillian.container.domain.managed.ManagedDomainDeployableContainer
      configuration: *EAP_CONFIG
    - type: remote
      gav: org.jboss.as:jboss-as-arquillian-container-domain-remote:7.1.3.Final
      adapterClass: org.jboss.as.arquillian.container.domain.remote.RemoteDomainDeployableContainer
  dist: *EAP_DIST
  exclude: *EAP_EXCLUDE
- name: JBoss AS
  versionExpression: 7\.0\.[0-2]\.(.*)$|7\.1\.[0-1]\.(.*)$
  adapters:
    - type: remote
      gav: org.jboss.as:jboss-as-arquillian-container-remote:${version}
      adapterClass: org.jboss.as.arquillian.container.remote.RemoteDeployableContainer
    - type: managed
      gav: org.jboss.as:jboss-as-arquillian-container-managed:${version}
      adapterClass: org.jboss.as.arquillian.container.managed.ManagedDeployableContainer
      configuration: &AS_CONFIG
        jbossHome: ${dist}
    - type: embedded
      gav: org.jboss.as:jboss-as-arquillian-container-embedded:${version}
      adapterClass: org.jboss.as.arquillian.container.embedded.EmbeddedDeployableContainer
      configuration: *AS_CONFIG
  dist: &AS_DIST
    gav: org.jboss.as:jboss-as-dist:zip:${version}
  exclude: &AS_EXCLUDE
    - org.jboss.arquillian.test:*
    - org.jboss.arquillian.testenricher:*
    - org.jboss.arquillian.container:*
    - org.jboss.arquillian.core:*
    - org.jboss.arquillian.config:*
    - org.jboss.arquillian.protocol:*
    - org.jboss.shrinkwrap.api:*
    - org.jboss.shrinkwrap:*
    - org.jboss.shrinkwrap.descriptors:*
    - org.jboss.shrinkwrap.resolver:*
    - "*:wildfly-arquillian-testenricher-msc"
    - "*:wildfly-arquillian-protocol-jmx"
    - "*:jboss-as-arquillian-testenricher-msc"
    - "*:jboss-as-arquillian-protocol-jmx"
- name: JBoss AS Domain
  versionExpression: 7\.0\.[0-2]\.(.*)$|7\.1\.[0-1]\.(.*)$
  adapters:
    - type: managed
      gav: org.jboss.as:jboss-as-arquillian-container-domain-managed:${version}
      adapterClass: org.jboss.as.arquillian.container.domain.managed.ManagedDomainDeployableContainer
      configuration: *AS_CONFIG
    - type: remote
      gav: org.jboss.as:jboss-as-arquillian-container-domain-remote:${version}
      adapterClass: org.jboss.as.arquillian.container.domain.remote.RemoteDomainDeployableContainer
  dist: &AS_DIST
    gav: org.jboss.as:jboss-as-dist:zip:${version}
  exclude: &AS_EXCLUDE
    - org.jboss.arquillian.test:*
    - org.jboss.arquillian.testenricher:*
    - org.jboss.arquillian.container:*
    - org.jboss.arquillian.core:*
    - org.jboss.arquillian.config:*
    - org.jboss.arquillian.protocol:*
    - org.jboss.shrinkwrap.api:*
    - org.jboss.shrinkwrap:*
    - org.jboss.shrinkwrap.descriptors:*
    - org.jboss.shrinkwrap.resolver:*
    - "*:wildfly-arquillian-testenricher-msc"
    - "*:wildfly-arquillian-protocol-jmx"
    - "*:jboss-as-arquillian-testenricher-msc"
    - "*:jboss-as-arquillian-protocol-jmx"
- name: WildFly
  versionExpression: 8.*
  adapters:
    - type: remote
      gav: org.wildfly:wildfly-arquillian-container-remote:${version}
      adapterClass: org.jboss.as.arquillian.container.remote.RemoteDeployableContainer
    - type: managed
      gav: org.wildfly:wildfly-arquillian-container-managed:${version}
      adapterClass: org.jboss.as.arquillian.container.managed.ManagedDeployableContainer
      configuration: &WF_CONFIG
        jbossHome: ${dist}
    - type: embedded
      gav: org.wildfly:wildfly-arquillian-container-embedded:${version}
      adapterClass: org.jboss.as.arquillian.container.embedded.EmbeddedDeployableContainer
      configuration: &WF_EMBEDD_CONFIG
        jbossHome: ${dist}
        modulePath: ${dist}/modules
      dependencies:
        - org.jboss.remotingjmx:remoting-jmx:2.0.1.Final
        - org.jboss.logging:jboss-logging:3.2.1.Final
  dist: &WF_DIST
    gav: org.wildfly:wildfly-dist:zip:${version}
  exclude: &WF_EXCLUDE
    - org.jboss.arquillian.test:*
    - org.jboss.arquillian.testenricher:*
    - org.jboss.arquillian.container:*
    - org.jboss.arquillian.core:*
    - org.jboss.arquillian.config:*
    - org.jboss.arquillian.protocol:*
    - org.jboss.shrinkwrap.api:*
    - org.jboss.shrinkwrap:*
    - org.jboss.shrinkwrap.descriptors:*
    - org.jboss.shrinkwrap.resolver:*
    - "*:wildfly-arquillian-testenricher-msc"
    - "*:wildfly-arquillian-protocol-jmx"
    - "*:jboss-as-arquillian-testenricher-msc"
    - "*:jboss-as-arquillian-protocol-jmx"
- name: WildFly Domain
  versionExpression: 8.*
  adapters:
    - type: managed
      gav: org.wildfly.arquillian:wildfly-arquillian-container-domain-managed:${version}
      adapterClass: org.jboss.as.arquillian.container.domain.managed.ManagedDomainDeployableContainer
      configuration: *WF_CONFIG
    - type: remote
      gav: org.wildfly.arquillian:wildfly-arquillian-container-domain-remote:${version}
      adapterClass: org.jboss.as.arquillian.container.domain.remote.RemoteDomainDeployableContainer
  dist: &WF_DIST
    gav: org.wildfly:wildfly-dist:zip:${version}
  exclude: &WF_EXCLUDE
    - org.jboss.arquillian.test:*
    - org.jboss.arquillian.testenricher:*
    - org.jboss.arquillian.container:*
    - org.jboss.arquillian.core:*
    - org.jboss.arquillian.config:*
    - org.jboss.arquillian.protocol:*
    - org.jboss.shrinkwrap.api:*
    - org.jboss.shrinkwrap:*
    - org.jboss.shrinkwrap.descriptors:*
    - org.jboss.shrinkwrap.resolver:*
    - "*:wildfly-arquillian-testenricher-msc"
    - "*:wildfly-arquillian-protocol-jmx"
    - "*:jboss-as-arquillian-testenricher-msc"
    - "*:jboss-as-arquillian-protocol-jmx"
- name: WildFly
  versionExpression: 9.*
  adapters:
    - type: remote
      gav: org.wildfly.arquillian:wildfly-arquillian-container-remote:1.1.0.Final
      adapterClass: org.jboss.as.arquillian.container.remote.RemoteDeployableContainer
    - type: managed
      gav: org.wildfly.arquillian:wildfly-arquillian-container-managed:1.1.0.Final
      adapterClass: org.jboss.as.arquillian.container.managed.ManagedDeployableContainer
      configuration: *WF_CONFIG
    - type: embedded
      gav: org.wildfly.arquillian:wildfly-arquillian-container-embedded:1.1.0.Final
      adapterClass: org.jboss.as.arquillian.container.embedded.EmbeddedDeployableContainer
      configuration: *WF_EMBEDD_CONFIG
      dependencies:
        - org.jboss.remotingjmx:remoting-jmx:2.0.1.Final
  dist: *WF_DIST
  exclude: &WF9_EXCLUDE
    - org.jboss.arquillian.test:*
    - org.jboss.arquillian.testenricher:*
    - org.jboss.arquillian.container:*
    - org.jboss.arquillian.core:*
    - org.jboss.arquillian.config:*
    - org.jboss.arquillian.protocol:*
    - org.jboss.shrinkwrap.api:*
    - org.jboss.shrinkwrap:*
    - org.jboss.shrinkwrap.descriptors:*
    - org.jboss.shrinkwrap.resolver:*
    - "*:wildfly-arquillian-testenricher-msc"
- name: WildFly Domain
  versionExpression: 9.*
  adapters:
    - type: managed
      gav: org.wildfly.arquillian:wildfly-arquillian-container-domain-managed:1.1.0.Final
      adapterClass: org.jboss.as.arquillian.container.domain.managed.ManagedDomainDeployableContainer
      configuration: *WF_CONFIG
    - type: remote
      gav: org.wildfly.arquillian:wildfly-arquillian-container-domain-remote:1.1.0.Final
      adapterClass: org.jboss.as.arquillian.container.domain.remote.RemoteDomainDeployableContainer
  dist: *WF_DIST
  exclude: &WF9_EXCLUDE
    - org.jboss.arquillian.test:*
    - org.jboss.arquillian.testenricher:*
    - org.jboss.arquillian.container:*
    - org.jboss.arquillian.core:*
    - org.jboss.arquillian.config:*
    - org.jboss.arquillian.protocol:*
    - org.jboss.shrinkwrap.api:*
    - org.jboss.shrinkwrap:*
    - org.jboss.shrinkwrap.descriptors:*
    - org.jboss.shrinkwrap.resolver:*
    - "*:wildfly-arquillian-testenricher-msc"
- name: WildFly
  versionExpression: 10.*
  adapters:
    - type: remote
      gav: org.wildfly.arquillian:wildfly-arquillian-container-remote:2.0.1.Final
      adapterClass: org.jboss.as.arquillian.container.remote.RemoteDeployableContainer
    - type: managed
      gav: org.wildfly.arquillian:wildfly-arquillian-container-managed:2.0.1.Final
      adapterClass: org.jboss.as.arquillian.container.managed.ManagedDeployableContainer
      configuration: *WF_CONFIG
    - type: embedded
      gav: org.wildfly.arquillian:wildfly-arquillian-container-embedded:2.0.1.Final
      adapterClass: org.jboss.as.arquillian.container.embedded.EmbeddedDeployableContainer
      configuration: *WF_EMBEDD_CONFIG
  dist: *WF_DIST
  exclude: &WF10_EXCLUDE
    - org.jboss.arquillian.test:*
    - org.jboss.arquillian.testenricher:*
    - org.jboss.arquillian.container:*
    - org.jboss.arquillian.core:*
    - org.jboss.arquillian.config:*
    - org.jboss.arquillian.protocol:*
    - org.jboss.shrinkwrap.api:*
    - org.jboss.shrinkwrap:*
    - org.jboss.shrinkwrap.descriptors:*
    - org.jboss.shrinkwrap.resolver:*
    - "*:wildfly-arquillian-testenricher-msc"
- name: WildFly Domain
  versionExpression: 10.*
  adapters:
    - type: managed
      gav: org.wildfly.arquillian:wildfly-arquillian-container-domain-managed:2.0.1.Final
      adapterClass: org.jboss.as.arquillian.container.domain.managed.ManagedDomainDeployableContainer
      configuration: *WF_CONFIG
    - type: remote
      gav: org.wildfly.arquillian:wildfly-arquillian-container-domain-remote:2.0.1.Final
      adapterClass: org.jboss.as.arquillian.container.domain.remote.RemoteDomainDeployableContainer
  dist: *WF_DIST
  exclude: &WF10_EXCLUDE
    - org.jboss.arquillian.test:*
    - org.jboss.arquillian.testenricher:*
    - org.jboss.arquillian.container:*
    - org.jboss.arquillian.core:*
    - org.jboss.arquillian.config:*
    - org.jboss.arquillian.protocol:*
    - org.jboss.shrinkwrap.api:*
    - org.jboss.shrinkwrap:*
    - org.jboss.shrinkwrap.descriptors:*
    - org.jboss.shrinkwrap.resolver:*
    - "*:wildfly-arquillian-testenricher-msc"
# Older versions of Glassfish (before 3.1.2) are no longer supported
- name: GlassFish
  versionExpression: ^3\.1\.[2-9]{1}(\.[0-9])*$
  adapters:
    - &GF_REMOTE
          type: remote
          gav: org.jboss.arquillian.container:arquillian-glassfish-remote-3.1:1.0.1
          adapterClass: org.jboss.arquillian.container.glassfish.remote_3_1.GlassFishRestDeployableContainer
    - &GF_MANAGED
      type: managed
      gav: org.jboss.arquillian.container:arquillian-glassfish-managed-3.1:1.0.1
      adapterClass: org.jboss.arquillian.container.glassfish.managed_3_1.GlassFishManagedDeployableContainer
      configuration:
        glassFishHome: ${dist}
        outputToConsole: true
    - &GF_EMBEDDED
      type: embedded
      gav: org.jboss.arquillian.container:arquillian-glassfish-embedded-3.1:1.0.1
      adapterClass: org.jboss.arquillian.container.glassfish.embedded_3_1.GlassFishContainer
      requireDist: false
      dependencies:
        - org.glassfish.main.extras:glassfish-embedded-all:${version}
  dist: &GF_DIST
    gav: org.glassfish.main.distributions:glassfish:zip:${version}

- name: GlassFish
  versionExpression: 4.*
  adapters:
    - *GF_REMOTE
    - *GF_MANAGED
    - *GF_EMBEDDED
  dist: *GF_DIST

- name: Payara
  versionExpression: 4.*
  adapters:
    - *GF_REMOTE
    - *GF_MANAGED
    - type: embedded
      gav: org.jboss.arquillian.container:arquillian-glassfish-embedded-3.1:1.0.1
      adapterClass: org.jboss.arquillian.container.glassfish.embedded_3_1.GlassFishContainer
      requireDist: false
      dependencies:
        - fish.payara.extras:payara-embedded-all:${version}
  dist:
    gav: fish.payara.distributions:payara:zip:${version}

- name: Tomcat
  versionExpression: 6.*
  adapters:
    - type: remote
      gav: org.jboss.arquillian.container:arquillian-tomcat-remote-6:1.0.0.CR9
      adapterClass: org.jboss.arquillian.container.tomcat.remote.Tomcat6RemoteContainer
    - type: managed
      gav: org.jboss.arquillian.container:arquillian-tomcat-managed-6:1.0.0.CR9
      adapterClass: org.jboss.arquillian.container.tomcat.managed.Tomcat6ManagedContainer
      configuration: &TOMCAT_MANAGED_CONFIG
        catalinaHome: ${dist}
        catalinaBase: ${dist}
  dist:
    gav: http://archive.apache.org/dist/tomcat/tomcat-6/v${version}/bin/apache-tomcat-${version}.zip

- name: Tomcat
  versionExpression: 7.*
  adapters:
    - type: remote
      gav: org.jboss.arquillian.container:arquillian-tomcat-remote-7:1.0.0.CR9
      adapterClass: org.jboss.arquillian.container.tomcat.remote.Tomcat7RemoteContainer
    - type: managed
      gav: org.jboss.arquillian.container:arquillian-tomcat-managed-7:1.0.0.CR9
      adapterClass: org.jboss.arquillian.container.tomcat.managed.Tomcat7ManagedContainer
      configuration: *TOMCAT_MANAGED_CONFIG
  dist: &TOMCAT_DIST
    gav: org.apache.tomcat:tomcat:zip:${version}

- name: Tomcat
  versionExpression: 8.0.*
  adapters:
    - type: remote
      gav: org.jboss.arquillian.container:arquillian-tomcat-remote-8:1.0.0.CR9
      adapterClass: org.jboss.arquillian.container.tomcat.remote.Tomcat8RemoteContainer
    - type: managed
      gav: org.jboss.arquillian.container:arquillian-tomcat-managed-8:1.0.0.CR9
      adapterClass: org.jboss.arquillian.container.tomcat.managed.Tomcat8ManagedContainer
      configuration: *TOMCAT_MANAGED_CONFIG
  dist: *TOMCAT_DIST 

当你读到这篇文章的时候,Chameleon 可能已经发布了一个已经配置了 Tomcat 的版本。

运行测试

为了简单起见,我们对 Tomcat 和 WildFly 运行的测试将是对它们的根目录的 HTTP GET 请求。通常,在这些测试中,您会进行 API 调用、部署应用程序或您的应用程序需要针对这些应用服务器做的任何事情。

这些测试的格式与上一篇博文中的测试格式非常相似。但是有两个很大的不同。

首先,我们没有@Deployment方法,因为我们没有在应用服务器内部运行任何代码。

第二,我们的测试方法有@RunAsClient注释,这意味着这段代码在应用服务器之外运行。我们认为这些测试是应用服务器的客户端,而不是测试部署到它们上面的代码。通过从外向内看,我们可以测试我们的代码,就好像它是一个与 Arquillian 管理的应用服务器一起工作的外部应用程序。

package tomcat8;

import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.jboss.arquillian.container.test.api.RunAsClient;
import org.jboss.arquillian.junit.Arquillian;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.io.IOException;

@RunWith(Arquillian.class)
public class TomcatTest {
    @Test
    @RunAsClient
    public void connectToTomcat() throws IOException {
        try (CloseableHttpClient httpclient = HttpClients.createDefault()) {
            final HttpGet httpGet = new HttpGet("http://localhost:8080");
            try (CloseableHttpResponse response1 = httpclient.execute(httpGet)) {
                final int responseCode = response1.getStatusLine().getStatusCode();
                Assert.assertTrue(responseCode >= 200);
                Assert.assertTrue(responseCode <= 399);
            }
        }
    }
} 

运行测试

要针对特定的 Maven 概要文件运行测试,请提供-P参数,如下所示:

mvn clean verify -PTomcat8 

Arquillian 将为指定的应用服务器下载二进制发行版,启动服务器,并运行您的测试。这是输出的样子。

/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/bin/java -Dmaven.multiModuleProjectDirectory=/Users/matthewcasperson/Development/ArquillianInfrastructureTesting "-Dmaven.home=/Users/matthewcasperson/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/172.4574.11/IntelliJ IDEA.app/Contents/plugins/maven/lib/maven3" "-Dclassworlds.conf=/Users/matthewcasperson/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/172.4574.11/IntelliJ IDEA.app/Contents/plugins/maven/lib/maven3/bin/m2.conf" "-javaagent:/Users/matthewcasperson/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/172.4574.11/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=54994:/Users/matthewcasperson/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/172.4574.11/IntelliJ IDEA.app/Contents/bin" -Dfile.encoding=UTF-8 -classpath "/Users/matthewcasperson/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/172.4574.11/IntelliJ IDEA.app/Contents/plugins/maven/lib/maven3/boot/plexus-classworlds-2.5.2.jar" org.codehaus.classworlds.Launcher -Didea.version=2017.2.6 test -P Tomcat8
objc[12174]: Class JavaLaunchHelper is implemented in both /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/bin/java (0x1057b94c0) and /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/libinstrument.dylib (0x1077f54e0). One of the two will be used. Which one is undefined.
[INFO] Scanning for projects...
[WARNING]
[WARNING] Some problems were encountered while building the effective model for com.octopus:arquillian-infrastructure-testing:jar:1.0-SNAPSHOT
[WARNING] 'build.plugins.plugin.version' for org.apache.maven.plugins:maven-surefire-plugin is missing. @ line 75, column 29
[WARNING]
[WARNING] It is highly recommended to fix these problems because they threaten the stability of your build.
[WARNING]
[WARNING] For this reason, future Maven versions might no longer support building such malformed projects.
[WARNING]
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building arquillian-infrastructure-testing 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ arquillian-infrastructure-testing ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory /Users/matthewcasperson/Development/ArquillianInfrastructureTesting/src/main/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ arquillian-infrastructure-testing ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ arquillian-infrastructure-testing ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 4 resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ arquillian-infrastructure-testing ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ arquillian-infrastructure-testing ---
[INFO] Surefire report directory: /Users/matthewcasperson/Development/ArquillianInfrastructureTesting/target/surefire-reports

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running tomcat8.TomcatTest
Nov 28, 2017 6:59:24 PM org.jboss.arquillian.container.tomcat.managed.TomcatManagedContainer start
INFO: Starting Tomcat with: [/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/bin/java, -Djava.util.logging.config.file=/Users/matthewcasperson/Development/ArquillianInfrastructureTesting/target/server/tomcat_8.0.47/apache-tomcat-8.0.47/conf/logging.properties, -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager, -Dcom.sun.management.jmxremote.port=8089, -Dcom.sun.management.jmxremote.ssl=false, -Dcom.sun.management.jmxremote.authenticate=false, -Xmx512m, -XX:MaxPermSize=128m, -classpath, /Users/matthewcasperson/Development/ArquillianInfrastructureTesting/target/server/tomcat_8.0.47/apache-tomcat-8.0.47/bin/bootstrap.jar:/Users/matthewcasperson/Development/ArquillianInfrastructureTesting/target/server/tomcat_8.0.47/apache-tomcat-8.0.47/bin/tomcat-juli.jar, -Djava.endorsed.dirs=/Users/matthewcasperson/Development/ArquillianInfrastructureTesting/target/server/tomcat_8.0.47/apache-tomcat-8.0.47/endorsed, -Dcatalina.base=/Users/matthewcasperson/Development/ArquillianInfrastructureTesting/target/server/tomcat_8.0.47/apache-tomcat-8.0.47, -Dcatalina.home=/Users/matthewcasperson/Development/ArquillianInfrastructureTesting/target/server/tomcat_8.0.47/apache-tomcat-8.0.47, -Djava.io.tmpdir=/Users/matthewcasperson/Development/ArquillianInfrastructureTesting/target/server/tomcat_8.0.47/apache-tomcat-8.0.47/temp, org.apache.catalina.startup.Bootstrap, -config, /Users/matthewcasperson/Development/ArquillianInfrastructureTesting/target/server/tomcat_8.0.47/apache-tomcat-8.0.47/conf/../../../../../src/test/resources/tomcat8-server.xml, start]
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=128m; support was removed in 8.0
28-Nov-2017 18:59:24.858 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version:        Apache Tomcat/8.0.47
28-Nov-2017 18:59:24.860 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server built:          Sep 29 2017 13:46:41 UTC
28-Nov-2017 18:59:24.860 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server number:         8.0.47.0
28-Nov-2017 18:59:24.860 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Name:               Mac OS X
28-Nov-2017 18:59:24.860 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Version:            10.13.1
28-Nov-2017 18:59:24.860 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Architecture:          x86_64
28-Nov-2017 18:59:24.860 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Java Home:             /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre
28-Nov-2017 18:59:24.860 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Version:           1.8.0_151-b12
28-Nov-2017 18:59:24.860 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Vendor:            Oracle Corporation
28-Nov-2017 18:59:24.860 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_BASE:         /Users/matthewcasperson/Development/ArquillianInfrastructureTesting/target/server/tomcat_8.0.47/apache-tomcat-8.0.47
28-Nov-2017 18:59:24.861 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_HOME:         /Users/matthewcasperson/Development/ArquillianInfrastructureTesting/target/server/tomcat_8.0.47/apache-tomcat-8.0.47
28-Nov-2017 18:59:24.861 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.config.file=/Users/matthewcasperson/Development/ArquillianInfrastructureTesting/target/server/tomcat_8.0.47/apache-tomcat-8.0.47/conf/logging.properties
28-Nov-2017 18:59:24.861 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
28-Nov-2017 18:59:24.861 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcom.sun.management.jmxremote.port=8089
28-Nov-2017 18:59:24.861 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcom.sun.management.jmxremote.ssl=false
28-Nov-2017 18:59:24.862 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcom.sun.management.jmxremote.authenticate=false
28-Nov-2017 18:59:24.862 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Xmx512m
28-Nov-2017 18:59:24.862 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -XX:MaxPermSize=128m
28-Nov-2017 18:59:24.862 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.endorsed.dirs=/Users/matthewcasperson/Development/ArquillianInfrastructureTesting/target/server/tomcat_8.0.47/apache-tomcat-8.0.47/endorsed
28-Nov-2017 18:59:24.862 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcatalina.base=/Users/matthewcasperson/Development/ArquillianInfrastructureTesting/target/server/tomcat_8.0.47/apache-tomcat-8.0.47
28-Nov-2017 18:59:24.862 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcatalina.home=/Users/matthewcasperson/Development/ArquillianInfrastructureTesting/target/server/tomcat_8.0.47/apache-tomcat-8.0.47
28-Nov-2017 18:59:24.862 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.io.tmpdir=/Users/matthewcasperson/Development/ArquillianInfrastructureTesting/target/server/tomcat_8.0.47/apache-tomcat-8.0.47/temp
28-Nov-2017 18:59:24.862 INFO [main] org.apache.catalina.core.AprLifecycleListener.lifecycleEvent The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: /Users/matthewcasperson/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.
28-Nov-2017 18:59:24.966 INFO [main] org.apache.coyote.AbstractProtocol.init Initializing ProtocolHandler ["http-nio-8080"]
28-Nov-2017 18:59:24.985 INFO [main] org.apache.tomcat.util.net.NioSelectorPool.getSharedSelector Using a shared selector for servlet write/read
28-Nov-2017 18:59:24.987 INFO [main] org.apache.coyote.AbstractProtocol.init Initializing ProtocolHandler ["ajp-nio-8009"]
28-Nov-2017 18:59:24.989 INFO [main] org.apache.tomcat.util.net.NioSelectorPool.getSharedSelector Using a shared selector for servlet write/read
28-Nov-2017 18:59:24.989 INFO [main] org.apache.catalina.startup.Catalina.load Initialization processed in 386 ms
28-Nov-2017 18:59:25.010 INFO [main] org.apache.catalina.core.StandardService.startInternal Starting service Catalina
28-Nov-2017 18:59:25.010 INFO [main] org.apache.catalina.core.StandardEngine.startInternal Starting Servlet Engine: Apache Tomcat/8.0.47
28-Nov-2017 18:59:25.017 INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory /Users/matthewcasperson/Development/ArquillianInfrastructureTesting/target/server/tomcat_8.0.47/apache-tomcat-8.0.47/webapps/docs
28-Nov-2017 18:59:25.306 INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory /Users/matthewcasperson/Development/ArquillianInfrastructureTesting/target/server/tomcat_8.0.47/apache-tomcat-8.0.47/webapps/docs has finished in 289 ms
28-Nov-2017 18:59:25.306 INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory /Users/matthewcasperson/Development/ArquillianInfrastructureTesting/target/server/tomcat_8.0.47/apache-tomcat-8.0.47/webapps/manager
28-Nov-2017 18:59:25.336 INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory /Users/matthewcasperson/Development/ArquillianInfrastructureTesting/target/server/tomcat_8.0.47/apache-tomcat-8.0.47/webapps/manager has finished in 30 ms
28-Nov-2017 18:59:25.336 INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory /Users/matthewcasperson/Development/ArquillianInfrastructureTesting/target/server/tomcat_8.0.47/apache-tomcat-8.0.47/webapps/examples
28-Nov-2017 18:59:25.535 INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory /Users/matthewcasperson/Development/ArquillianInfrastructureTesting/target/server/tomcat_8.0.47/apache-tomcat-8.0.47/webapps/examples has finished in 199 ms
28-Nov-2017 18:59:25.536 INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory /Users/matthewcasperson/Development/ArquillianInfrastructureTesting/target/server/tomcat_8.0.47/apache-tomcat-8.0.47/webapps/ROOT
28-Nov-2017 18:59:25.552 INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory /Users/matthewcasperson/Development/ArquillianInfrastructureTesting/target/server/tomcat_8.0.47/apache-tomcat-8.0.47/webapps/ROOT has finished in 16 ms
28-Nov-2017 18:59:25.552 INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory /Users/matthewcasperson/Development/ArquillianInfrastructureTesting/target/server/tomcat_8.0.47/apache-tomcat-8.0.47/webapps/host-manager
28-Nov-2017 18:59:25.564 INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory /Users/matthewcasperson/Development/ArquillianInfrastructureTesting/target/server/tomcat_8.0.47/apache-tomcat-8.0.47/webapps/host-manager has finished in 12 ms
28-Nov-2017 18:59:25.566 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-8080"]
28-Nov-2017 18:59:25.571 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["ajp-nio-8009"]
28-Nov-2017 18:59:25.573 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in 583 ms
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 3.047 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.906 s
[INFO] Finished at: 2017-11-28T18:59:26+10:00
[INFO] Final Memory: 14M/309M
[INFO] ------------------------------------------------------------------------

Process finished with exit code 0 

结论

通过利用 Arquillian 来下载、初始化和清理它所支持的各种应用服务器,我们可以很容易地测试作为客户机与这些服务器交互的代码。这意味着只需花费一点点精力来设置 Maven 和配置 Arquillian 容器,就可以针对许多不同的应用服务器和这些服务器的许多不同版本运行测试。这种方法对我们来说非常有效,Java 代码充当了 Octopus 和 Java 应用服务器之间的粘合剂。

你可以从 GitHub 下载这篇博文的源代码。

如果您对 Java 应用程序的自动化部署感兴趣,下载 Octopus Deploy 的试用版,并查看一下我们的文档

阿奎利亚测试介绍- Octopus 部署

原文:https://octopus.com/blog/arquillian-testing

让我们用两个类创建一个简单的 EJB 应用程序。第一种称为EnterpriseJavaBean,向控制台写入一个 UUID,休眠一段时间,然后再次向控制台写入同一个 UUID。

package org.example.arquilliantest;

import javax.ejb.Asynchronous;
import javax.ejb.Singleton;
import java.util.Random;
import java.util.UUID;

@Singleton
public class EnterpriseJavaBean {
    @Asynchronous
    public void writeToConsole() {
        final UUID uuid = UUID.randomUUID();
        System.out.println(uuid.toString());

        try {
            Thread.sleep(Math.abs(new Random().nextLong()) % 100);
        } catch (InterruptedException e) {
            // ignored
        }

        System.out.println(uuid.toString());
    }
} 

第二个类叫做StartupService,在启动时构造,构造后调用EnterpriseJavaBean.writeToConsole() 10 次。

package org.example.arquilliantest;

import javax.annotation.PostConstruct;
import javax.ejb.EJB;
import javax.ejb.Singleton;
import javax.ejb.Startup;

@Startup
@Singleton
public class StartupService {
    @EJB
    private EnterpriseJavaBean enterpriseJavaBean;

    @PostConstruct
    public void postConstruct() {
        for (int i = 0; i < 10; ++i) {
            enterpriseJavaBean.writeToConsole();
        }
        System.out.println("All Done");
    }
} 

这里的代码是米尔 EJB 逻辑的运行,你可以在任何 Java EE 应用程序中找到。一旦编译并运行,类似下面的内容将被打印到控制台。

12:56:19,359 INFO  [stdout] (EJB default - 4) 2a3c2709-c556-49be-8ca7-25778b54310d
12:56:19,442 INFO  [stdout] (EJB default - 4) 2a3c2709-c556-49be-8ca7-25778b54310d
12:56:19,444 INFO  [stdout] (EJB default - 9) 4be307b4-9e4b-4f37-b1b4-a54f234a591c
12:56:19,471 INFO  [stdout] (EJB default - 9) 4be307b4-9e4b-4f37-b1b4-a54f234a591c
12:56:19,472 INFO  [stdout] (EJB default - 10) ef7a63ae-1e65-4d9e-aed0-97ddf6681956
12:56:19,519 INFO  [stdout] (EJB default - 10) ef7a63ae-1e65-4d9e-aed0-97ddf6681956
12:56:19,520 INFO  [stdout] (EJB default - 2) 8dc989b8-48e2-4128-9b3a-d80a883033a7
12:56:19,615 INFO  [stdout] (EJB default - 2) 8dc989b8-48e2-4128-9b3a-d80a883033a7
12:56:19,618 INFO  [stdout] (EJB default - 1) 4ae8e179-6b3c-44c3-9cee-62a93c069bba
12:56:19,719 INFO  [stdout] (EJB default - 1) 4ae8e179-6b3c-44c3-9cee-62a93c069bba
12:56:19,721 INFO  [stdout] (EJB default - 6) 3cf93b22-dd92-45ea-95d3-73873322579e
12:56:19,784 INFO  [stdout] (EJB default - 6) 3cf93b22-dd92-45ea-95d3-73873322579e
12:56:19,786 INFO  [stdout] (EJB default - 3) b38ce136-78dc-49b5-85db-c11f491a5f14
12:56:19,856 INFO  [stdout] (default task-1) All Done
12:56:19,877 INFO  [stdout] (EJB default - 3) b38ce136-78dc-49b5-85db-c11f491a5f14
12:56:19,878 INFO  [stdout] (EJB default - 7) f0bb9c74-fdec-4994-b66d-296e310da24d
12:56:19,945 INFO  [stdout] (EJB default - 7) f0bb9c74-fdec-4994-b66d-296e310da24d
12:56:19,946 INFO  [stdout] (EJB default - 5) e8ef8311-dd2d-45c1-af07-a3059d92f680
12:56:19,972 INFO  [stdout] (EJB default - 5) e8ef8311-dd2d-45c1-af07-a3059d92f680
12:56:19,973 INFO  [stdout] (EJB default - 8) b40cf503-c05c-4282-95be-3bf03875bc2f
12:56:20,045 INFO  [stdout] (EJB default - 8) b40cf503-c05c-4282-95be-3bf03875bc2f
12:56:20,046 INFO  [stdout] (EJB default - 4) a0a70b9f-dd13-41fd-95a9-f8b38a062377
12:56:20,085 INFO  [stdout] (EJB default - 4) a0a70b9f-dd13-41fd-95a9-f8b38a062377
12:56:20,086 INFO  [stdout] (EJB default - 9) f8069c90-c980-4593-805f-352c7fcb13ae
12:56:20,110 INFO  [stdout] (EJB default - 9) f8069c90-c980-4593-805f-352c7fcb13ae
12:56:20,111 INFO  [stdout] (EJB default - 10) e0fe386b-18e5-4883-a14f-a8af75b010ef
12:56:20,116 INFO  [stdout] (EJB default - 10) e0fe386b-18e5-4883-a14f-a8af75b010ef
12:56:20,118 INFO  [stdout] (EJB default - 2) 9d057b61-cb3f-4da4-b401-9f1e8d251669
12:56:20,215 INFO  [stdout] (EJB default - 2) 9d057b61-cb3f-4da4-b401-9f1e8d251669
12:56:20,217 INFO  [stdout] (EJB default - 1) 17b80b1e-6885-4a38-8051-7246854fd54c
12:56:20,227 INFO  [stdout] (EJB default - 1) 17b80b1e-6885-4a38-8051-7246854fd54c
12:56:20,229 INFO  [stdout] (EJB default - 6) 81dcd134-e704-4c77-b8b0-04a15e163705
12:56:20,302 INFO  [stdout] (EJB default - 6) 81dcd134-e704-4c77-b8b0-04a15e163705
12:56:20,303 INFO  [stdout] (EJB default - 3) 4e32ecb9-9e95-42b0-8077-3cb80f40faca
12:56:20,398 INFO  [stdout] (EJB default - 3) 4e32ecb9-9e95-42b0-8077-3cb80f40faca 

让我们假设上面的输出序列是有效的输出,并且我们想在单元测试中验证这个行为。

用 JUnit 测试 POJOs

EJB 3 规范的卖点之一是它允许你编写 POJOs。通过一些额外的注释,这些 POJOs 变成了完全成熟的 EJB。或者至少它们会在合适的环境中成为 EJB。但稍后会详细介绍。

因为这些类是 POJOs,我们可以很容易地将它们合并到单元测试中。

忽略实际捕获和验证控制台输出所需的工作,这就是我们的测试可能的样子。它复制了在StartupService类中找到的相同逻辑,所以您可能认为它会产生几乎相同的输出。

@Test
public void plainTest() {
    final EnterpriseJavaBean enterpriseJavaBean = new EnterpriseJavaBean();
    for (int i = 0; i < 10; ++i) {
        enterpriseJavaBean.writeToConsole();
    }
    System.out.println("All Done");
} 

当测试运行时,我们接近原始输出。

dc1d071a-18f9-4cd4-9f90-70768c818165
dc1d071a-18f9-4cd4-9f90-70768c818165
983ade5f-f514-4530-bd3e-e5c71910b3b4
983ade5f-f514-4530-bd3e-e5c71910b3b4
1ce137d0-1d9e-4f26-9b00-75ae72fb4cee
1ce137d0-1d9e-4f26-9b00-75ae72fb4cee
cd8bc751-6021-4a92-ab0a-03021f66c353
cd8bc751-6021-4a92-ab0a-03021f66c353
916e331c-2b54-4c9c-b3e6-f2625d816f7b
916e331c-2b54-4c9c-b3e6-f2625d816f7b
706bf787-d35d-49f2-8963-39010b53245d
706bf787-d35d-49f2-8963-39010b53245d
afa3d953-0599-4f8d-8e58-c22f79a7c789
afa3d953-0599-4f8d-8e58-c22f79a7c789
9ac2dc9b-dc8c-4f96-ba98-f38b299d9be8
9ac2dc9b-dc8c-4f96-ba98-f38b299d9be8
07e9bf94-6e38-4420-a175-81f436451832
07e9bf94-6e38-4420-a175-81f436451832
9027ca83-690d-4d45-beda-4ae599844f44
9027ca83-690d-4d45-beda-4ae599844f44
All Done 

敏锐的观察者会注意到,All Done消息总是打印在单元测试输出的末尾,但是在部署到服务器时,在执行EnterpriseJavaBean类的过程中随机打印。

这是因为writeToConsole()方法被标记为@Asynchronous

这些 EJB 注释会被任何不能识别它们的代码忽略,我们的 JUnit 测试就是一个不能识别 EJB 注释的执行环境的例子。这意味着单元测试将同步调用这个方法,这又意味着All Done消息将总是最后显示。

这第一次表明测试 EJB 并不像看起来那样简单。但到目前为止,差异是显而易见的;我们正在测试的方法被清楚地标记为@Asynchronous,所以我们可以很容易地复制这个行为。让我们在执行器内部调用writeToConsole()方法,它将以异步方式进行调用。

@Test
public void plainThreadTest() throws InterruptedException {
    final EnterpriseJavaBean enterpriseJavaBean = new EnterpriseJavaBean();

    try {
        final ExecutorService executor = Executors.newFixedThreadPool(10);

        final List<Callable<Void>> tasks = new ArrayList<>();
        for (int i = 0; i < 10; ++i) {
            executor.submit(() -> {
                enterpriseJavaBean.writeToConsole();
                return null;
            });
        }

        System.out.println("All Done");
        executor.shutdown();
        executor.awaitTermination(10, TimeUnit.SECONDS);
    } catch (Exception e) {
        // ignored
    }
} 

这会产生以下输出:

All Done
b887112d-d92e-4a1c-8986-0b1e25916c99
4ded12d5-a837-495a-a199-059cb58a2f11
63e12097-6c62-416c-ae60-bd873554e376
18a722fe-548a-48c1-bc6c-eaec651522fa
8e2273c0-73cc-4dfc-9964-a1c6c40055b4
c41acb02-61fc-4a4b-bd68-45545b4ed734
b74acccf-1b38-45c8-9375-1d7380698214
85b71c13-9b3c-4e4b-bdb7-32064a6a9818
c2fa0677-291a-4066-8dcb-13f4a6896489
3ed6dc3a-590e-4350-a6a4-fc0e3e799505
b74acccf-1b38-45c8-9375-1d7380698214
4ded12d5-a837-495a-a199-059cb58a2f11
18a722fe-548a-48c1-bc6c-eaec651522fa
85b71c13-9b3c-4e4b-bdb7-32064a6a9818
63e12097-6c62-416c-ae60-bd873554e376
c41acb02-61fc-4a4b-bd68-45545b4ed734
b887112d-d92e-4a1c-8986-0b1e25916c99
c2fa0677-291a-4066-8dcb-13f4a6896489
8e2273c0-73cc-4dfc-9964-a1c6c40055b4
3ed6dc3a-590e-4350-a6a4-fc0e3e799505 

现在我们从线程池中调用writeToConsole()方法,这让我们更接近 EJB 调用@Asynchronous方法的方式。All Done消息现在不再总是在输出的末尾,但是 UUIDs 都混在一起了。这不是我们在服务器上执行writeToConsole()时看到的行为,它总是一个接一个地打印匹配的 UUID 对。

这里的问题是,当EnterpriseJavaBean类上的方法被作为 EJB 注入时被调用,这些方法默认为由@Lock(WRITE)注释定义的语义。这意味着一次只能调用一个方法,这确保了 UUID 对总是一个接一个地被打印。

但是当您查看EnterpriseJavaBean类的代码时,这种行为并不明显。任何地方都没有@Lock(WRITE)标注;这是 EJB 容器采用的默认值。

然后,我们可以继续尝试围绕writeToConsole()方法添加一些同步,但是此时我们已经花费了更多的时间来复制执行 EJB 的环境,而不是验证这些方法中的业务逻辑。

阿奎利亚人请到回避区

Arquillian 项目解决了这种情况。Arquillian 允许您利用服务器环境赋予对象的任何功能,针对 EJB(或任何类型的增强对象)编写测试。

编写一个 Arquillian 测试实际上很容易。下面是一个复制了EnterpriseJavaBean类行为的例子。

package org.example.arquilliantest;

import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.Test;
import org.junit.runner.RunWith;

import javax.ejb.EJB;

@RunWith(Arquillian.class)
public class ArquillianTest {

    @EJB
    private EnterpriseJavaBean enterpriseJavaBean;

    @Deployment
    public static JavaArchive createDeployment() {
        JavaArchive jar = ShrinkWrap.create(JavaArchive.class)
                .addClass(EnterpriseJavaBean.class);
        return jar;
    }

    @Test
    public void plainThreadTest() throws InterruptedException {
        for (int i = 0; i < 10; ++i) {
            enterpriseJavaBean.writeToConsole();
        }
        System.out.println("All Done");
    }
} 

这个测试有几个重要的方面。

@RunWith(Arquillian.class)注释用 Arquillian 提供的功能丰富了 JUnit 测试。

带有@Deployment注释的createDeployment()方法用于创建工件,该工件被部署为测试环境。在本例中,我们正在构建一个包含EnterpriseJavaBean类的 jar 文件。这是使用 ShrinkWrap 库完成的,它允许我们用代码创建 Java 工件,就像 Maven 这样的构建工具在构建时所做的一样。

在测试中,我们使用@EJB注释注入了一个EnterpriseJavaBean类的实例。当测试运行时,这个变量引用的对象是一个真实的、活的 EJB。这意味着,我们不是测试一个带有被忽略的 EJB 注释的 POJO(因此测试一个没有 EJB 功能的对象),而是测试一个行为方式与部署到应用服务器时相同的对象。

最后,@Test方法本身运行我们最初试图编写的相同测试代码,但是这一次的输出正是我们在将代码部署到真实服务器时所看到的。

结论

通过使用 Arquillian 运行测试,可以测试一个对象在部署到应用服务器时所提供的实际功能。这使您不必试图复制应用服务器赋予的功能,并且意味着您的测试反映了您的生产代码。

你可以从 GitHub 获得这个项目的源代码。

如果您对 Java 应用程序的自动化部署感兴趣,下载 Octopus Deploy 的试用版,并查看我们的文档。

宣布询问章鱼视频系列-章鱼部署

原文:https://octopus.com/blog/ask-octopus

https://www.youtube.com/embed/KVxdSdYAqQU

VIDEO

去年,我们无畏的首席执行官保罗和 Jeffrey Palermo 一起在 ClearMeasure 做了几次 Q &网络研讨会。今年晚些时候,Derek Campbell、Ryan Rousseau 和我做了一个关于如何加速、横向扩展和组织您的 Octopus Deploy 服务器的网络研讨会。这只是其中的几个例子。我们全年做了很多。

希望这对你来说是一次很好的学习经历。作为一家公司,网络研讨会也是一次很好的学习经历。我们学到的第一件事是...制作高质量的网上研讨会需要做大量的准备工作。一个小时的准备内容,包括演示、幻灯片和练习,花费了 30-60 个小时。这很有道理。网络研讨会旨在提供对特定功能的深入探究。

我们注意到的另一件事是,虽然人们从准备好的内容中获得了很多价值,但最后还是有很多问答。有些问题与网上研讨会的主题有关。其他问题,嗯,没那么多。这些仍然是很好的问题,我们非常乐意回答。通常问题的答案很容易演示,只需要几分钟。

我们看到很多支持票,更多的是关于“我如何使用 Octopus Deploy 做一些事情”而不是“请帮助我,我的实例坏了。”我们收到的许多问题与我们在每次网络研讨会结束时收到的问题是一脉相承的。他们需要比几分钟更长的时间来回答。打出回应,一起截图,需要一段时间。

我们得到的一些问题对整个 Octopus Deploy 社区很有帮助。这让我们思考,与社区分享这些问题的答案不是很好吗?我们仍然希望举办网络研讨会,但这需要演讲者和观众投入大量时间(1 个多小时)。如果能有一个比较短的视频来回答这些问题就好了。我们可以在五分钟内展示一些东西,这可以帮助用户节省时间。如果能给某人一个包含问题答案的视频链接,那就太好了。

这就是为什么我们很高兴宣布我们每周问章鱼视频系列。我们在 Octopus Deploy 的一些人将回答一些上周问的更有趣的问题。每个视频将在 5 到 20 分钟之间,取决于所提的问题。我们的目标是每周五发布一个新视频。

在我们的第一个视频中,Derek 回答了我们在过去一个月中经常遇到的问题,“什么是 LTS 版本,我如何从中受益?”Ryan 接着问“我如何针对特定环境升级我的触角?”我以这样的问题结束讨论,“我如何在不影响其他人的情况下测试我的过程的变化?”

我们这个系列的计划是从简单开始。我们将通过我们通常的支持渠道回答您的问题。根据我们从社区得到的反馈,我们将进行调整。一年后,这个系列可能看起来完全一样,也可能完全不同。

也就是说,如果你有任何迫切的问题想要回答,或者如果你有一般性的反馈,请发送给support@octopus.com

ASP.NET 核心 1 建设和部署管道,采用 TeamCity 和 Octopus - Octopus 部署

原文:https://octopus.com/blog/aspnet-core-build-and-deploy

很难形容我对即将到来的ASP.NET Core 1dotnet CLI 的变革浪潮有多兴奋。虽然编程语言和 API 没有显著变化,但围绕运行时如何分布的幕后工作,首先转向命令行,并使其跨平台,是非常棒的。这是成为. NET 开发人员的大好时机。

在这篇文章中,我想解释一些我认为影响开发人员构建和部署管道的主要变化,并展示它将如何与 ASP.NET 核心 1 RC1 和 RC2 一起工作。我的计划是将这些全部放入关于 ASP.NET Core 1 构建和部署的免费电子书中,但是考虑到 T2 迁移到 dotnet CLI 并推迟了发布日期,我认为是时候在这本书等待 RC2 发布的时候进行更新了。

在我们开始讨论如何构建和部署之前,我想谈谈一个重大变化。

发布应用程序将被标准化!

当你把一个 ASP.NET 应用程序发布到产品中时,它需要一些文件——DLL、配置文件、CSS/JS、图像等等。同样,当 Windows 服务或控制台应用程序在生产环境中运行时,它需要一些文件——可执行文件、DLL、配置文件等。

直到现在,从来没有一个一致的方法来“发布”所有这些。NET 应用程序。ASP.NET 项目有一个复杂的命令行咒语,可以“发布”网站,但不适用于 Windows 服务。对于 Windows 服务,你可能只需要压缩bin\release目录。这就是为什么在过去的四年里,我们依靠 OctoPack 来沟通这一切——OctoPack 的全部目的是查看你的项目,并试图找出应该发表的内容。

我很遗憾没有一个标准的方法来发布和打包所有的内容。NET 应用程序:一个通用的软件包格式。网。

好消息是,当 dotnet CLI 发布时,最终会有一个标准的方式来发布这些应用程序。它们只发布到一个文件夹中,因此需要额外的步骤来打包应用程序,但至少这是非常接近的。

使用新的dotnet工具,你可以dotnet publish一个 web 应用程序,或者一个控制台应用程序,或者可能是一个 Windows 服务,或者最终是 WPF 应用程序(假设所有这些平台最终都被移植),它将生成一个文件夹,其中包含应用程序运行所需的文件。

这还有另一个影响: OctoPack 将成为过去的遗迹。T2 和压缩输出的结合将会是替代品。

构建和部署过程将会是什么样子

下图概述了在这个美丽新世界中,使用 Octopus 构建和部署管道的构建模块:

Building and deploying with the current ASP.NET tooling, ASP.NET 5 RC1, and ASP.NET Core 1 RC2

一个例子

这是 ASP.NET 5 RC1 的一个例子:

dnu restore source/MyApp.Web source/MyApp.Tests

dnu publish source/MyApp.Web --runtime active --out published-app --no-source

octo pack --id MyApp.Web --version 1.0.0 --basePath published-app --format zip

octo push --package MyApp.Web.1.0.0.zip --server http://octopus --apikey API-1234567

octo create-release --project MyApp --version 1.0.0 --packageversion 1.0.0 --server http://octopus --apikey API-1234567 

与团队合作

TeamCity 团队正在开发一个新插件,它包装了 DNU/DNX 和dotnet命令。不用写脚本,你可以使用这个插件来执行上面的dnu命令:

Build and publish with the DNU plugin for TeamCity

本周我们还发布了一个包装了octo.exe push的章鱼团队城市插件的更新。您可以使用它来动态创建 ZIP 文件并将其推送到 Octopus:

Octopus TeamCity push package step

一旦您发布了应用程序,对其进行了打包,并将其推送到 Octopus,就一切如常了。我们有一个新的 Octopus JSON 配置特性来利用新的appsettings.json文件格式,但是除此之外,Octopus 内部不需要做太多改变来支持 ASP.NET 核心 1。

摘要

ASP。NET Core 1 使发布成为平台的第一级特性。这消除了对 OctoPack 的需求,并将最终标准化发布应用程序以供部署的过程。如果你正在使用 TeamCity,有一个新的插件来调用这些命令,我们可以期待其他构建服务器在未来得到类似的命令。剩下的就是压缩发布的文件夹,推送到 Octopus,然后用 Octopus 部署。

欢迎来到美丽新世界!

使用 Octopus - Octopus Deploy 将 ASP.NET 核心部署到 Linux

原文:https://octopus.com/blog/aspnet-core-linux

你可能已经看过了。NET Core 和 ASP.NET Core 1.0 最近在开发上发布。

主题演讲期间,斯科特·汉斯曼演示了使用 Octopus Deploy 将. NET 核心应用程序部署到 Red Hat Linux 服务器上(斯科特在 48:15 上台,在 1:02:00 演示 Octopus Deploy 集成)。

我们很荣幸在斯科特的报告中被提及。我们对这种可能性感到兴奋。网芯。

斯科特可以理解地跳过了血淋淋的细节。对于那些感兴趣的人来说,这篇文章将更深入地探讨 ASP.NET 核心应用程序在 Linux 服务器上的真实部署。

免责声明: IANALG(我不是 Linux 的家伙)。但我觉得这才是重点。大多数。NET 开发人员(至少目前)最熟悉 Windows。对于我们许多人来说,Linux 是一个陌生的(老实说,是可怕的)新世界。来吧,让我们手牵手...

最佳食用期

写一篇技术程序文章的问题是,当你写完它的时候,它通常已经过时了(这个问题在关注。网芯)。

只要有可能,我都会参考官方文档,这些文档更容易维护。

佐料

  • 显然,我们将需要一个 Octopus 部署服务器。如果你手头没有,那么下载一个试用实例或者从 Azure Marketplace 上升级一个。

  • 要部署到的 Linux 服务器。在 Hanselman 先生的带领下,我们将使用运行 Red Hat Enterprise Linux 7.2 的服务器。你可以在 Azure 中轻松创建一个 RHEL 虚拟机。

  • 要部署的 ASP.NET 核心应用程序。我们将使用一个为此而创建的演示项目:https://github.com/MJRichardson/aspnetcoredemo
    它很简单,但是包含两个相关的特性:

    • 它使用来自appsettings.json的配置设置(我们将把 Octopus 变量代入其中)
    • 它包含一些位于\conf目录下的配置文件,我们将使用这些文件来配置我们的 Linux 服务器。

创建一个包

如果你想跳过创建包,你可以从 GitHub 库的发布页面下载 zip 文件。

如果您还没有,克隆示例应用程序 repo:

git clone https://github.com/MJRichardson/aspnetcoredemo.git 

移动到您克隆项目的目录。下面的命令将从那里运行。

注:我们章鱼总部有句话:“朋友不让朋友右键-发布”。在一个. NET 核心世界里我们可能要把它更新为:“朋友不让朋友 dotnet 发布”。创建您的包并将其推送到 Octopus 应该由您的构建服务器来执行。插件可用于大多数流行的构建服务器(例如团队城市詹金斯TFS )。

此处获取我们向 Octopus 发布 ASP.NET 核心应用的官方文档。

恢复 NuGet 包:

dotnet restore src 

将应用程序发布到目录:

dotnet publish src --output published 

目录的内容将是我们的包的内容。你可以用你喜欢的归档工具(我推荐 7-Zip )将\published(不是目录)的内容归档到一个名为aspnetcoredemo.1.0.0.zip的文件中。

现在把包上传到 Octopus Deploy。

Upload Package

您现在应该可以看到您发布的包:

Published Package

在 Octopus 中创建一个 SSH 目标

八达通要求

Red Hat Linux 服务器必须满足几个要求才能被添加为 Octopus 中的 SSH 目标:

单声道的

必须安装 Mono。最新说明可在单声道文档中找到。对于 RHEL 服务器,请遵循“CentOS 和衍生品”一节。

在一个根外壳中,执行:

yum install yum-utils
rpm --import "http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF"
yum-config-manager --add-repo http://download.mono-project.com/repo/centos/
yum intall mono-complete 

SSH 密钥

Octopus 将使用 SSH 密钥对与 RHEL 服务器进行认证。创建密钥对的指南可以在这里找到。

在您的 Linux shell 中,生成一个 SSH 密钥对:

ssh-keygen -t rsa 

将公钥添加到授权密钥中:

cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys 

确保 SSH 目录的正确所有权:

chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys 

其他要求

。网络核心

显然我们需要。网芯。安装说明。RHEL 上的 NET Core 可以在这里找到。为了充分披露,这位作者遵循了 CentOS 说明,而不是使用订阅管理器。

NGINX

对于我们的小演示应用程序,没有理由不让 Kestrel 直接服务于请求。但是共识似乎是最佳实践是使用生产级 web 服务器作为 ASP.NET 核心应用程序前面的反向代理。在 Windows 上,这将是 IIS。

我们将使用 NGINX 。按照位于https://www . nginx . com/resources/wiki/start/topics/tutorials/install/#的说明,我创建了一个文件/etc/yum.repos.d/nginx.repo,并将其编辑为包含:

[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/rhel/7/$basearch/
gpgcheck=0
enabled=1 

我还修改了/etc/nginx/nginx.conf,加入了下面一行:

include /etc/nginx/sites-enabled/*.conf; 

我们还应该创建目录:

mkdir /etc/nginx/sites-enabled 

这在我们部署应用程序时非常重要。

因此http部分显示为:

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/sites-enabled/*.conf;
    include /etc/nginx/conf.d/*.conf;
} 

监督者

通过执行dotnet实用程序来运行 ASP.NET 核心应用程序。当您在本地测试时,通过终端直接执行它是没问题的,但是对于部署到服务器,我们需要:

  • 为了能够启动和停止服务
  • 对于服务器重新启动时自动启动的服务

所以我们要用主管

遵循安装说明:

yum install python-setuptools
easy_install supervisor 

我们将使用默认设置创建一个管理员配置文件,如下图所示:

echo_supervisord_conf > /etc/supervisor/supervisord.conf 

现在,我们将创建一个目录来保存特定于应用程序的 supervisor 配置:

mkdir -p /etc/supervisor/conf.d 

并编辑/etc/supervisor/supervisord.conf并在末尾添加:

[include]
files = /etc/supervisor/conf.d/*.conf 

注意:使用easy-install安装监控程序似乎没有向系统和注册监控程序。这似乎是你肯定想做的事情。点击可查看完整的主管设置。

安全性

默认情况下,您的 RHEL 服务器可能会被锁定(理应如此)。因为我们将把它用作 web 服务器,所以我们需要放松束缚。

我不能说我没有资格提供 Linux 安全建议。请咨询您当地的系统管理员。并提前道歉。

我们需要打开端口 80:

firewall-cmd --zone=public --add-port=80/tcp --permanent
firewall-cmd --reload 

默认情况下,SELinux 阻止 NGINX 将 HTTP 请求代理给 Kestrel。关于这方面的信息可以在找到

运行以下命令应该可以建立连接:

setsebool httpd_can_network_connect on -P 

创造环境

因为所有目标都需要属于一个环境,所以让我们先创建一个环境。

Create Environment

创建 SSH 密钥对帐户

章鱼文档

Create SSH KeyPair Account

私钥可以通过以下方式获得:

cat ~/.ssh/id_rsa 

您需要将文本保存到一个文件中,该文件将提供给“私钥”字段,如下所示。

SSH KeyPair Account Details

创建 SSH 目标

Create SSH Target

SSH Target Details

创建目标后,您可以运行运行状况检查以确保连接。

在 Octopus 中创建一个项目

现在我们将创建一个项目来部署我们的 ASP.NET 核心演示应用程序。

Create Project

添加部署包步骤

向您的项目添加一个部署包步骤。

Add Package Step

它将引用我们之前上传的包。

Package Step Details

我们将为此步骤启用两个功能:

Package Step Features

JSON 配置变量

我们将使用 JSON 配置变量特性来转换我们的appsettings.json文件。

在这种情况下,我们只是简单地改变了呈现的消息,但这演示了 Octopus 如何在 JSON 配置文件中转换层次变量。

替换文件中的变量

我们还将使用文件中的替代变量特性为我们的 NGINX 和 Supervisor 配置文件提供变量。

如果您查看这些文件(位于src\aspnetcoredemo\deployment),您会看到它们包含 Octopus 变量的占位符。例如,nginx.conf文件包含了#{IPAddress}变量:

server {
 listen 80;
 server_name #{IPAddress};
 location / {
     proxy_pass http://localhost:5000;
     proxy_http_version 1.1;
     proxy_set_header Upgrade $http_upgrade;
     proxy_set_header Connection keep-alive;
     proxy_set_header Host $host;
     proxy_cache_bypass $http_upgrade;
 }
} 

配置 NGINX

接下来,我们将添加一个运行脚本步骤,将我们的 NGINX 配置文件移动到/etc/nginx/sites-enabled,并告诉 NGINX 重新加载。

NGINX Script Step Details

脚本来源是:

installed=$(get_octopusvariable 'Octopus.Action[Deploy Pkg].Output.Package.InstallationDirectoryPath')
nginxConf='/conf/nginx.conf'
dest='/etc/nginx/sites-enabled/aspnetcoredemo.conf'
echo "Moving $installed$nginxConf to $dest"
sudo mv -f $installed$nginxConf $dest

echo 'Reloading NGINX'
sudo nginx -s reload 

第一行获取解压后的包的路径。

能够将文件放入sites-enabled目录使我们不必修改现有的nginx.conf

显然,一种常见的方法是同时拥有一个sites-availablesites-enabled目录。实际的配置文件被部署到sites-available,符号链接被添加到sites-enabled。然后是包含在nginx.conf中的sites-enabled。我会把这个留给家庭作业。

运行主管

现在我们将添加另一个运行脚本步骤,将我们的 Supervisor 配置文件移动到/etc/supervisor/conf.d/aspnetcoredemo.conf并告诉 Supervisor 重新加载。

Supervisor Script Step

脚本来源是:

installed=$(get_octopusvariable "Octopus.Action[Deploy Pkg].Output.Package.InstallationDirectoryPath")
supervisorConf='/conf/supervisor.conf'
dest='/etc/supervisor/conf.d/aspnetcoredemo.conf'
echo "Moving $installed$supervisorConf to $dest"
sudo mv -f $installed$supervisorConf $dest

echo 'Reloading supervisor'
sudo supervisorctl reload 

变量

最后,我们需要添加将被替换到配置文件中的变量。

Variables

部署

此时,您的部署过程应该类似于:

Deployment Process

你还在等什么?创建一个版本,然后部署!

Deployment Summary

如果一切顺利,您已经将 ASP.NET 核心应用程序部署到 Red Hat Enterprise Linux 服务器上。

Browser serving application

自我提醒:将 balloons.png 添加到示例应用程序主页;)

XPlat

虽然这超出了本文的范围,但值得一提的是,只需很少的努力,我们就可以将同一个包部署到 Windows 2012 R2 服务器和 Azure Web 应用上。

Windows, Azure and Linux Deployment Log

未来

。NET Core 为我们改进部署到 Linux 目标的故事提供了一个很好的机会。

无单声道

事实上,目前您必须在您的 Linux 服务器上安装 Mono 框架,以使其成为 Octopus 部署目标,这在许多情况下是很尴尬的。我们正朝着使用。NET 核心,完全消除了对 Mono 的依赖。

无脚本

我们主要关注的是提供将您想要的部署到您想要的位置的能力,而无需您自己编写脚本。章鱼在历史上一直是目标。NET,还有。NET 一直是针对 Windows 的。。NET Core 已经突破了 Linux 的防火墙。我们应该能够逐步完成并实现一些更多的以 Linux 为中心的部署步骤。

反馈

如果您正在使用 Octopus(甚至考虑使用它)进行部署。NET 核心应用程序移植到 Linux 上,我们希望收到您的来信。

请告诉我们哪些可行,哪些不可行,以及我们如何才能让您的部署更简单、更可靠。

愉快的(跨平台)部署!

Selenium 系列:异步 Lambdas - Octopus 部署

原文:https://octopus.com/blog/selenium/33-asynchronous-lambdas/asynchronous-lambdas

这篇文章是关于创建 Selenium WebDriver 测试框架的系列文章的一部分。

我们现在有能力用 AWS Lambda 函数运行 Gherkin 特性。但到目前为止,我们不得不从 AWS 控制台触发测试。当我们测试 Lambda 工作时,这是好的,但是这不是一个非常方便的运行测试的方法。

为了允许在不使用 AWS Lambda 控制台的情况下执行测试,我们将在无服务器配置文件中配置一个 HTTP 事件。这个事件允许我们从 HTTP POST 请求中执行 Lambda 函数,这又允许我们使用任何标准的 HTTP 工具或库来触发测试。

为了配置 HTTP 事件,我们更新了serverless.yml文件:

service:
  name: cucumber-chrome-aws

provider:
  name: aws
  runtime: java8
  region: us-east-1

package:
  artifact: target/webdrivertraining-1.0-SNAPSHOT.jar

functions:
  runCucumber:
    handler: com.octopus.LambdaEntry::runCucumber
    timeout: 300
    memorySize: 512
    events:
      - http:
          method: post
          path: runCucumber
          integration: lambda
          request:
            parameters:
              headers:
                'X-Amz-Invocation-Type': true
            template:
              text/plain: "\"$util.escapeJavaScript($input.body).replaceAll(\"\\'\",\"'\")\""
          response:
            headers:
              Content-Type: "'application/json'"
            template: $util.parseJson($input.body) 

新的events部分添加了一个http事件。HTTP methodpost,我们可以访问的path被称为runCucumber,因为这个事件触发了一个 Lambda 函数,所以integration被设置为lambda:

events:
  - http:
      method: post
      path: runCucumber
      integration: lambda 

当我们执行 HTTP POST 时,我们在请求体中传递将要运行的 Gherkin 特性文件。但是正如我们之前看到的,Lambda 函数期望所有输入都是 JSON 数据。在从控制台测试 Lambda 时,我们通过使用一个简单的 web 页面将纯文本转换为 JSON 字符串来解决这个需求。我们可以使用请求模板实现同样的转换。

这里我们已经配置了一个请求模板,当发出一个包含text/plain内容的请求时会用到这个模板。关键字text/plain是多用途互联网邮件扩展(MIME)类型,这是一种表示文档性质和格式的标准化方法:

request:
  template:
    text/plain: "\"$util.escapeJavaScript($input.body).replaceAll(\"\\'\",\"'\")\"" 

处理纯文本请求的模板,没有最外面的引号和里面的转义引号,是这样的:

"$util.escapeJavaScript($input.body).replaceAll("\'","'")" 

这个模板的目的与我们编写的将纯文本转换成 JSON 字符串的 web 页面相同。但是,这个模板是使用 Velocity 模板语言编写的,这是 AWS 使用的模板库。该模板的逻辑如下:

模式 意义
" 打印一个字面双引号
$util.escapeJavaScript($input.body) 从保存在$input.body中的请求中获取原始文本(即作为 HTTP POST 主体发送的文本),并传递给$util.escapeJavaScript()函数。
.replaceAll("\'","'") escapeJavaScript()函数将转义单引号,这不是有效的 JSON。所以用单引号替换所有转义的引号。
" 打印一个字面双引号

作为响应,我们希望传回 Lambda 函数返回的 JSON。我们通过将Content-Type头设置为 MIME 类型application/json来确定响应返回 JSON。

我们再次使用模板来转换响应。该模板获取 Lambda 函数返回的 JSON 字符串,该字符串被公开为$input.body,并将其传递给$util.parseJson()函数,该函数将 JSON 解析为一个对象以返回给发送方:

response:
  headers:
    Content-Type: "'application/json'"
  template: $util.parseJson($input.body) 

通过对serverless.yml文件的这些更改,我们可以再次用无服务器部署命令重新发布 Lambda 函数。该命令的输出如下所示:

Serverless: Packaging service...
Serverless: WARNING: Function runCucumber has timeout of 300 seconds, however, it's attached to API Gateway so it's automatically limited to 30 seconds.
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service .zip file to S3 (22.67 MB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
........................
Serverless: Stack update finished...
Service Information
service: cucumber-chrome-aws
stage: dev
region: us-east-1
stack: cucumber-chrome-aws-dev
api keys:
None
endpoints:
POST - https://hkm5i7fmlc.execute-api.us-east-1.amazonaws.com/dev/runCucumber
functions:
runCucumber: cucumber-chrome-aws-dev-runCucumber 

注意,日志文件显示了我们可以用来触发 HTTP 事件的端点的 URL。在上面的例子中,URLhttps://hkm 5i 7 fmlc . execute-API . us-east-1 . amazonaws . com/dev/run cumber是我们将通过 HTTP POST 请求调用的,以执行一个小黄瓜特性。

要发出 HTTP 请求,我们将使用一个名为 Postman 的工具,你可以从 https://www.getpostman.com/下载。Postman 提供了一个简单的接口,我们可以用它来创建定制的 HTTP 请求,在这种情况下,我们将向上面找到的 URL 发出 POST 请求。

下面的屏幕截图显示了需要填写的重要字段,以便提出发布请求。

  • 从选项卡顶部的下拉列表中选择POST
  • 在选项卡顶部的文本框中输入 URL。
  • 选择Body选项卡。
  • 选择raw选项。
  • 从内容类型下拉列表中选择Text
  • 将小黄瓜特性粘贴到标签底部的文本区域。
  • 点击Send按钮。

/C:/941191ba7e622160892a5123816cf59a

如果你幸运的话,你可能会看到这个回复。

/C:/643a4cd7e0389640c129bc0a2485278f

我说幸运,因为您可能已经看到了Endpoint request timed out错误消息。

/C:/6258b00237e59450e4167ba1a9104d2c

那么Endpoint request timed out错误信息是什么意思呢?

这个错误是由于在 Lambda 函数前面提供 HTTP 端点所实现的服务的不同限制造成的。

如果您打开 Lambda 控制台并查看该函数,您可以看到一个名为 API Gateway 的服务位于 Lambda 函数的前面。

/C:/edb84ea8b782041656cd633e9368260c

API Gateway 是另一个用于构建 HTTP APIs 的 AWS 服务,它是无服务器应用程序在我们添加 HTTP 事件时构建的服务。API Gateway 接收我们的 HTTP 请求,用我们定义的模板转换它们,将它们传递给 Lambda 函数,转换 Lambda 函数的响应,并将结果传递回调用者。

然而,虽然 Lambda 函数可以运行 5 分钟,但是通过 API 网关发出的请求只能保持打开 29 秒。这些是 AWS 对这些服务施加的限制,我们没有扩展它们的选项。

当 API 网关调用的 Lambda 函数花费了超过 29 秒的时间来完成时,就会生成Endpoint request timed out错误消息。

这对我们来说是个问题,因为我们不太可能写出能在 29 秒内可靠完成的 WebDriver 测试。通常我们编写测试来完成 web 应用程序的整个旅程,这可能需要几分钟。那么如何才能克服 API Gateway 强加的 29 秒时间限制呢?

解决方案是异步运行我们的 Lambda。这意味着 API Gateway 启动 Lambda,但不等待返回值,而是直接向调用者返回一个空结果。这意味着我们不再受 Lambda 运行时间的限制,因为它是在后台运行的。

为了指示 API Gateway 异步调用 Lambda,我们需要传递值为EventX-Amz-Invocation-Type头。

这里是新的serverless.yml文件:

service:
  name: cucumber-chrome-aws

provider:
  name: aws
  runtime: java8
  region: us-east-1

package:
  artifact: target/webdrivertraining-1.0-SNAPSHOT.jar

functions:
  runCucumber:
    handler: com.octopus.LambdaEntry::runCucumber
    timeout: 300
    memorySize: 512
    events:
      - http:
          method: post
          path: runCucumber
          integration: lambda
          request:
            parameters:
              headers:
                'X-Amz-Invocation-Type': true
              template:
                text/plain: "\"$util.escapeJavaScript($input.body).replaceAll(\"\\'\",\"'\")\""
          response:
            headers:
              Content-Type: "'application/json'"
            template: $util.parseJson($input.body) 

在以下配置中,我们定义了需要X-Amz-Invocation-Type标题(将该值设置为true意味着它是必需的):

request:
  parameters:
    headers:
      'X-Amz-Invocation-Type': true 

使用无服务器重新部署功能。更新后,通过点击Headers选项卡并添加一个带有关键字X-Amz-Invocation-Type和值Event的头,将X-Amz-Invocation-Type头添加到 Postman 中的 HTTP 请求。这个头和值对就是我们如何指示 API Gateway 以异步方式执行相关的 Lambda 函数。

X-Amz-Invocation-Type头值设置为RequestResponse会恢复之前的默认同步行为,如果您确定您的测试将在 29 秒内完成,这可能会很有用。

https://docs . AWS . Amazon . com/API gateway/latest/developer guide/integrating-API-with-AWS-services-lambda . html 中更详细地记录了X-Amz-Invocation-Type头。

/C:/c702d2a86f09873ae9edd675c653e749

这个请求几乎会立即返回,并以一个空对象作为响应。

太好了,我们现在可以通过 HTTP 调用来运行 Gherkin 脚本,而不需要担心通过 API 网关发出的请求的时间限制。但是现在我们无法知道测试是通过了还是失败了。异步运行 Lambda 意味着我们得到一个空响应。由于来自runCucumber函数的响应从未被捕获,我们不再知道结果是什么。

这个难题的最后一部分是为runCumumber函数提供一种返回测试结果的方法,因为它是以异步方式调用的。

这篇文章是关于创建 Selenium WebDriver 测试框架的系列文章的一部分。

鲍勃·沃克掌舵-章鱼部署

原文:https://octopus.com/blog/at-the-helm-bob-walker

这篇文章是我们系列文章的下一篇,与 Octopus 的人们谈论他们的角色,他们正在为我们的客户改进产品,等等。

在这里,我们采访了我们的客户成功技术总监 Bob Walker。

你在 Octopus 工作了多久,是什么吸引你来这里工作的?

在我加入八达通之前,我一直在使用八达通部署。我亲眼看到了在两家不同的公司部署 Octopus 的好处。

在这些组织使用 Octopus 之前,我们每个季度部署一次主要版本,需要几个小时。这些部署需要在几天后进行紧急修复。很容易错过数据库更改或忘记更新配置文件。

在实现 Octopus 之后,我们每 10 天部署一次,花了 15 分钟,没有紧急修复(除了遗漏的错误)。

作为一个用户和粉丝,我在 Twitter 上关注了章鱼。2017 年底,我看到他们在美国招聘售前工程师。有机会在 CI/CD 工作,并帮助客户使用我喜欢的工具-销售!那天晚上我申请了。

有趣的事实:我第一天就填了一些表格,然后登上了去澳大利亚布里斯班的飞机。首席解决方案架构师 Ryan Rousseau 在同一天被聘用,我们是第一批在美国工作的员工。我们今天拥有的入职基础设施还不存在,所以入职必须在澳大利亚进行。

客户成功团队做什么?

我们按照罐头上说的做。我们帮助客户在购买许可证后获得成功。成功有多种形式,需要不同领域的专业知识。我们有:

  • 客户成功经理和客户经理与我们的客户建立关系
  • 技术客户经理帮助我们的客户充分利用他们的许可证
  • 解决方案架构师帮助克服技术挑战
  • 许可和续订专家回答这些棘手的许可问题
  • 销售运作和培训员工保持一切顺利运行

作为客户成功的技术总监,典型的一天是怎样的?

如前所述,我是八达通公司首批美国员工之一。在最初的几年里,我每天醒来都抱着一个简单的目标,那就是决定如何最好地帮助顾客。我身兼多职,执行不同的任务。我回答支持问题,撰写博客文章,主持网络研讨会以展示新功能,并一对一地帮助客户克服障碍。

我现在的日常工作重点是利用我的经验和观点来支持客户成功团队中的每个人。我可能会和一个新的客户成功经理一起经历突发场景;与解决方案架构师和技术客户经理分享我们产品的历史知识,以便他们能够更好地帮助他们的客户;向我们的产品团队提供反馈,以便他们能够确定我们的下一个重点;确保每个人都知道新的特性和功能。

顾客最常见的问题是什么?

我们的问题每个星期和每个版本都不一样。

我们最近发布了 2022.1,所以我们收到了很多关于如何最好地使用 Config 作为代码的问题。

当我们收到关于某个主题的类似问题时,您会看到我们更新我们的文档或在我们的公共示例实例上添加新示例。

不久前,我们收到了许多关于升级的问题,因此我们重新编写了升级指南,以包括客户询问的主题。去年年底,我们通过在我们的示例实例中创建一个新空间,并举办一个网络研讨会来演示回滚,解决了回滚示例的需求。

您的团队如何跟上软件世界和我们不断发展的产品的所有变化?

我们凭借“磨砺时间”保持领先。锐化来自亚伯拉罕·林肯的一句名言:

如果我有六个小时砍树,我会花四个小时磨斧子。

我们鼓励团队通过钻研他们感兴趣的主题或我们下一季度发布的功能来磨利他们的斧子。

非结构化锐化时间的伟大之处在于,你不知道它会走向何方。

例如,Shawn 深入研究了 Kubernetes,并在 Raspberry Pis 上建立了他的集群。与此同时,Adam 深入 AWS 并配置了团队的 VPC、子网、IAM 和其他任何共享的东西。在将 Config 作为代码发布之前,我花了一些时间来研究我们支持和不支持的东西。

您如何在内部以及为我们的客户衡量成功?

因为每个人使用 Octopus 的方式不同,所以很难用一个指标来衡量我们客户的成功。与拥有一套仅供内部使用的应用程序的保险公司相比,为每个客户创建独特基础架构的 SaaS 公司面临着不同的挑战。

最终,我们希望我们的客户在使用 Octopus Deploy 时感到愉快和成功。我们衡量成功的一种方法是看更新和扩展的百分比。但这不是唯一的衡量标准。我讨厌有人“愤怒地”更新他们的执照。我们评估许多其他指标,如重复问题、问题数量、版本使用、功能使用等。

在严酷的萨比基-章鱼部署下掌舵

原文:https://octopus.com/blog/at-the-helm-harsh-sabikhi

这篇文章是我们系列的下一篇,与 Octopus 的人们谈论他们的角色,他们正在为我们的客户改进产品,等等。

在这里,我们采访了税务高级副总裁 Harsh Sabikhi。

你在 DevOps 公司工作的记录令人印象深刻。你能告诉我们一点你在八达通之前的职业生涯吗?

在加入 Octopus 之前,我的职业生涯是跟踪软件开发的演变,从瀑布到敏捷再到 DevOps。我在 PTC、Rally Software、GitHub 和 HashiCorp 工作过。

你为什么加入八达通部署?

在我的职业生涯中,我花了很多时间在开发阶段。在 HashiCorp,我接触到了运营方面,但是是从基础设施的角度。我注意到的一个常见主题是,部署到生产环境绝不是小事。运营团队在周六下午 6 点仍有发布窗口。Octopus Deploy 通过允许团队随时随地部署来帮助组织消除这种摩擦。

作为收入部高级副总裁,您的职责是什么?

从收入角度来看,我负责我们的整个上市(GTM)战略。我的团队包括销售、解决方案工程、销售开发、客户成功、专业服务和技术客户管理。

最终,无论是售前还是售后,我们都有责任确保我们的客户在与 Octopus 互动时获得良好的体验。

您如何为我们的客户提供价值并改善销售体验?

首先,我们将组织结构调整为面向企业细分市场(> 2500 名员工)的指定客户模型,其中每个客户都有一个专门的客户经理(AE)。AE 是售前阶段的主要联系人。如果是现有客户,客户还有一个客户成功经理(CSM)作为售后联系人。

我们下一步要做的是通过免费试用和直接从 octopus.com 购买八达通来帮助改善自助服务体验。

最后,我们正在投资于我们的渠道合作伙伴,并允许他们转售 Octopus。我们还与亚马逊和微软合作,因此我们的客户可以直接从他们各自的市场购买八达通。

你如何激励你的团队?

我试着以身作则,走在前面。例如,我刚刚结束了为期 3 周的旅程,拜访了许多客户和合作伙伴。我喜欢和我的团队一起打电话,不管客户在销售周期中处于什么位置,我都会努力参与进来。

人们可能不知道你的哪些令人惊讶的事情?

我实际上是一名电气工程师,在进入销售行业之前,我做了 6 年的软件开发人员。

杰西卡·罗斯掌舵-章鱼部署

原文:https://octopus.com/blog/at-the-helm-jessica-ross

这篇文章是我们系列文章的下一篇,与 Octopus 的人们谈论他们的角色,他们正在为我们的客户改进产品,等等。

在这里,我们采访了用户体验和设计总监杰西卡·罗斯。

在 Octopus,用户体验和设计可以涵盖很多方面——你的团队关注什么?

我们的设计师融入了营销和 R&D 团队,他们与每个团队的使命保持一致。

产品设计师与产品经理和工程师一起工作。设计师从用户体验(UX)的角度审视我们正在解决的问题,以提供满足客户需求的解决方案。

我们还确保我们的设计师花时间与我们的客户在一起,了解他们的痛点,以便他们能够提供最好的体验。

如何知道设计和用户体验工作是否成功?

衡量用户体验和设计工作的成功对我们来说是一件新鲜事。我们 2022 年的目标是进行实验,找出最重要的指标。

每个团队决定他们如何衡量自己的成功,UX 指标构成了整体衡量的一部分。因此,如果最终目标是提高激活率,我们的团队将努力减少用户需要做出的决定数量和用户在任务上花费的时间。

我们鼓励我们的设计师和他们的团队避免基于他们自以为了解的东西来创造体验。相反,他们在早期花时间研究、共同设计和测试解决方案。这样,他们可以交付确信解决方案会成功的特性。

你的团队今年从事的最令人兴奋的项目是什么?

缩小到一个项目太难了!我真的很高兴看到专门的团队来改善入职体验,改善整体用户体验和我们的设计系统,并定义用户体验的规模。

我们有一些新用户说东西很难找到,有些概念很难理解。今年,我们正在做研究以更好地理解这些问题。我们将尝试渐进式的改变,以帮助我们所有的新老用户更快地找到他们想要的东西,并理解 Octopus 如何为他们服务。

你对 UX 和章鱼的设计有什么长远的设想?

我们一直试图使 Octopus 成为一个易于使用的部署工具,处理我们客户需要的复杂性。我们希望我们的用户相信 Octopus 可以通过直观的产品指导他们设置和维护复杂的部署场景,这样他们就可以利用最新的 DevOps 技术推进他们的业务。

我们将继续与专注于 UX 质量和设计体验的优秀设计师合作,帮助用户尽可能高效地完成任务。

从内部来说,设计真的很有价值,有助于我们向客户提供的功能和改进的成功。

我们计划通过以下方式继续投资于 Octopus 的设计:

  • 构建 UX 和设计平台,为世界级用户体验的成功奠定基础
  • 在我们的产品团队中包括产品设计师
  • 确保我们团队的结构允许职业发展
  • 发展我们的品牌和设计标准

对想加入章鱼的设计师有什么建议?

八达通有一个伟大的远程文化,每个人都感到支持。要想在 Octopus 成为一名成功的设计师,你需要在一个跨职能团队中与营销人员、产品经理和工程师很好地合作。你需要能够建立共识,在用户研究和测试的基础上对用户体验有自己的看法,但不要拘泥于这些看法,并提高 UX 的质量。

在与我们合作之前,大多数设计师都不知道 Octopus 或 DevOps。Octopus 的设计师需要接受一定程度的模糊性,并依赖主题专家来理解产品并将其转化为成功的体验。

如果你是一个曾经致力于简化和转化复杂性的产品设计师,Octopus Deploy 可能很适合你。

作为用户体验和设计总监,你最喜欢的部分是什么?

当我想提升和扩大研发部门的设计职能时,Octopus 创造了我的角色。在过去的 12 个月里,我的重点是发展团队,完善设计职能,并建立团队以提供世界级的 UX。

我喜欢为团队建立仪式,看设计师们合作。尽管我们是一个分散的团队,我们鼓励设计师从彼此那里获得反馈,并有适当的渠道和仪式来帮助促进这一点。

随着 Octopus 的成长和设计功能的成熟,我喜欢与一些优秀的设计师一起工作,建立为我们工作的流程,同时挑战一些现有的工作方式。

我和团队最难忘的时刻是今年的 Q2 设计周。(设计周每个季度都有。)6 月,我们的团队聚集在一起,与一些营销人员、产品经理和工程师合作。我们讨论了品牌的未来,企业的 UX,并重新设计了产品的导航和信息架构。

凯尔·杰克逊掌舵-章鱼部署

原文:https://octopus.com/blog/at-the-helm-kyle-jackson

这篇文章是我们系列的下一篇,与 Octopus 的人们谈论他们的角色,他们正在为我们的客户改进产品,等等。

在这里,我们采访了安全运营经理凯尔·杰克逊。

安全运营团队是做什么的?

安全运营团队执行许多职能,但我们将我们的工作分为两大类:

  • 国内治安
  • 产品安全性

内部安全主要关注保护我们的员工免受攻击,并保护我们的员工使用的系统。

产品安全专注于保护客户使用的产品,包括与我们的工程团队合作,与安全研究人员合作开发 bug bounty 计划,以及向客户传达产品漏洞。

作为安全运营经理,您每天都在忙些什么?

每天都不一样,这也是我喜欢从事安全运营工作的原因之一。

作为安全运营经理,一天中很大一部分时间是作为其他安全运营团队成员的上报点,此外还要与其他团队合作完成他们即将到来或正在进行的项目,并进行日常管理。

虽然每天都不一样,但始终如一的是多次见面!

最大的安全威胁是什么,您如何管理它们?

作为软件供应商,我们最大的安全威胁都围绕着我们的产品。然而,我们支持业务的内部系统也会吸引他们自己的威胁。

为了管理我们的产品威胁,安全运营和工程团队紧密合作。协作至关重要,这样工程团队才能意识到当前和新出现的威胁,安全运营团队才能意识到可能吸引新威胁的新功能。

对于我们的内部系统,我们严重依赖身份和网络隔离来减少恶意活动,实施几乎零信任的架构。然而,关键是不断审查风险和我们为减轻风险而实施的控制措施。

是什么吸引你从事安保工作?

我没打算做保安,我是有点陷进去了。我是一名顾问,主要负责公共云安全和身份管理,并执行许多安全事件响应。

我离开咨询公司,加入 Octopus Deploy,成为首批全职安全员工之一。

你如何计划 Octopus Deploy 的未来安全?

这是一个协作过程,包括:

  • 了解产品团队计划向我们的客户交付什么
  • 了解企业的法规遵从性需求
  • 预测一般安全行业趋势
  • 识别可能影响 Octopus 部署的新威胁

收集信息和为 Octopus Deploy 的安全未来进行规划需要花费大量时间。同样重要的是,我们的团队是敏捷的,如果新的威胁出现或现有的威胁发生变化,我们可以调整路线。

你如何从工作中放松下来?

在安全运营部门工作是一项很难放松的工作,因为一切都变化得如此之快,所以我在工作时间之外花时间了解最新信息。

然而,我发现放松的关键是做点什么,把我的手机和笔记本电脑留在身后,因为这是唯一确定的断开方式。

迈克尔·理查森掌舵-章鱼部署

原文:https://octopus.com/blog/at-the-helm-michael-richardson

这篇文章开启了一个系列,在这里我们与 Octopus 的人们谈论他们的角色,他们的挑战,他们正在努力为我们的客户改进产品,等等。

首先是迈克尔·理查森,我们最初的产品管理负责人,现在的产品总监。

你在八达通待了多久,带来了哪些经验?

跟随保罗的旅程后,我于 2015 年加入八达通成为一名软件工程师——哇,时间过得真快。我有十年的经验来构建软件,并在各种形状和大小的组织中领导许多行业的开发团队。和我在 Octopus 的许多同事一样,我经常高兴地声称对为这些项目建立构建和部署管道负责。

预示着我在 Octopus 的未来,我有机会看到并帮助定义所有这些组织发布和部署软件的各种方式,并取得了不同程度的成功。这让我很容易对客户的挑战产生共鸣,并让我对 Octopus 可以帮助解决的问题类型有了直觉。

作为产品总监,你的职责是什么?

我在我们部署团队的领导团队中担任产品职务。我们的使命是让 Octopus 通过发展其功能、扩展我们集成的技术并使其尽可能易于使用来部署尽可能多的应用程序。

我的角色是为集团设定产品方向。我一直在寻找我们可以为客户解决的下一组问题,我与工程师、设计师和其他产品经理密切合作,以确保我们解决了我们押注的问题。

对你来说,典型的一天是什么样的?

典型的一天很大一部分是收集信息——分析数据、阅读客户反馈、倾听 Octopus 中其他团队的看法、与客户交谈以及跟踪行业新闻。我也经常使用 Octopus 和其他工具来模拟各种场景。

在一个典型的日子里,我很可能会见我们的一个或多个开发团队,在那里我可能只是问,“我们可以发货吗?”。

哦,还有缩放会议。这么多变焦会议。

八达通的产品团队面临的最大挑战是什么?

最大的挑战永远是我们想做的事情太多了!公司 10 个人的时候,我们说:“要是有 20 个人就好了,我们什么都能做!”。当我们增长到 50 人时,我们说:“如果我们有 100 人,我们就可以做任何事情!”。好吧,在撰写本文时,我们已经接近 200 人了,我们还远远没有能力做我们想做的每一件事。

选择正确的问题来解决,并在保持高水平的可靠性、改进和发展现有功能以及构建下一个大东西之间保持平衡,这始终是一个挑战。

你正在做什么来为我们的顾客改进产品?

嗯……(深吸一口气)

我们目前正在构建的产品的最大变化是代码为的配置。我相信你已经听过我们谈论这个很多次了,但是 Config as Code 允许 Octopus 项目在 Git 存储库中进行版本控制。为这个特性做准备是一项巨大的工程工作,在初始版本中部署过程是受版本控制的。但是我们才刚刚开始,所以请继续关注版本控制变量、runbooks 等等。

另一个重要的发展,一个处于早期发展阶段的发展,是对动态环境的支持:按需创建和销毁测试环境。如今,许多开发团队不想拥有长期的测试环境。相反,他们希望按需创建新的环境,例如,为每个“拉”请求创建一个环境,并在不再需要它们时销毁它们。

我们还在探索如何让 Octopus 成为基于云的团队的完美选择。这意味着观察团队如何部署到无服务器平台,并使用容器映像作为部署工件。

我们不断增加 Octopus 集成的产品范围,ServiceNow 是一个显著的例子,我们目前正在为其开发内置集成。ITSM 工具在大型组织中很常见,但我们从未使用它构建过第一方集成,所以这对我们来说是一种不同形式的集成。

如果你认为我们应该关注什么,请联系我们

您如何在内部以及为我们的客户衡量成功?

现在每个公司都是软件公司。能够更频繁、更可靠地部署会创建一个正反馈循环,从而在最佳团队和其他团队之间产生巨大的差异。

成功就是帮助我们的客户利用这个反馈回路为他们的客户创造价值。这听起来很老套,但却是事实。

就个人而言,我对从开发人员工作站获取代码并在生产中运行所涉及的技术和实践充满热情。至少可以说,能够在塑造行业未来的过程中发挥作用,并看到它被世界各地所有这些不可思议的公司所使用,是一种回报。

仅举一个例子来说明这一点,10 年前,作为一个行业,我们似乎仍在努力让人们相信自动化部署的好处。2022 年,这一使命似乎已经基本完成,现在的对话更加微妙。我倾向于认为章鱼在其中起了作用。

我很自豪八达通是一家澳大利亚公司。从很多方面来看,它的成长都是不可思议的,我认为它忠实于保罗最初的愿景和价值观。我称之为成功。

与 Shipra Mahindra - Octopus 一起掌舵

原文:https://octopus.com/blog/at-the-helm-shipra-mahindra

这篇文章是我们系列文章的下一篇,与 Octopus 的人们谈论他们的角色,他们正在为我们的客户改进产品,等等。

在这里,我们采访了负责代码配置的高级产品经理希普拉·马欣德拉。

在加入 Octopus Deploy 之前,您的最后一个职位是什么?

我做过一段时间的承包商,在一家初创公司 Creatively Squared 做产品工作,这家公司也是 remote-first,总部设在布里斯班。在此之前,我在 Xero 担任产品经理。

你是如何成为产品经理的,你喜欢这个角色的什么?

我的职业生涯始于软件工程师。几年后,我开始对解决影响人们的棘手问题感兴趣,这些问题被 Scrum Master、产品负责人和业务分析师等角色所涵盖。作为初级或中级软件工程师,大多数问题都很简单,通常都有正确的答案。对于业务或客户关注的问题,有更多的不确定性和许多方法来解决同一个问题。你必须考虑长期的方向和策略,然后做出正确的决定来实现这个目标。

所以,我决定去读 MBA,学习更多关于决策和企业运营的知识。MBA 毕业后,我开始从事产品管理方面的职业。

我喜欢在模糊和混乱的地方带来清晰。我喜欢弄清楚客户在纠结什么,并提供让他们高兴、让生活更轻松的解决方案。我仍然接近技术,可以看到东西是如何建造的,而不用自己建造任何东西。我也喜欢和团队一起工作,因为最好的想法通常来自合作辩论和头脑风暴。

作为 Config as Code 的高级产品经理,典型的一天是怎样的?

我一般会思考一个问题空间,如何增值。这包括与客户进行视频交谈、进行市场调查、分析电子表格中的使用数据、撰写推介文档、与设计和开发团队合作在研讨会上提出想法、解决团队正在进行的工作等即时问题。没有两天是一样的!它总是在变化,我喜欢变化。

你如何决定接下来要解决的问题和解决方案?

啊…优先排序的艺术和科学。关于这个主题的文章肯定有数百万篇。每个项目经理开发他们自己的过程,并选择他们自己的一套工具和框架。

我喜欢从各种来源收集信息并加以分析——顾客反馈、使用数据、市场趋势。我倾听线索和模式,然后把它们框定为追求的机会。然后,我会思考我们的目标和下一个让我们更接近目标的最佳方案。

我们对 Config as Code 的长期目标是达到至少 60%的特性采用率。所以关键问题是——今天是什么阻止了用户采用 Config 作为代码?在我们有了一个机会列表后,我权衡哪一个会增加足够的价值来鼓励我们的用户采用 Config as Code。我们有足够的来自用户和市场的数据或信号来押注这些机会吗?如果我们做到了,我们就开始解决这个问题。如果没有,我们可以找一些小实验来测试我们的假设。这包括更多的用户研究,测试设计,测试原型,或者发布最少的功能。

您如何评价您发布的解决方案的影响?

这是另一个广泛讨论的主题。对我来说,这是关于建立与我们的目标一致的成功衡量标准。在我们发货后,我们会监控和评估性能。然而,有时,成功也是由用户结果来定义的,比如有 10 个喜欢我们产品的 alpha 用户,或者甚至只是在我们创新或构建新东西时“推出”一些东西。

最终,我希望我们的客户会喜欢我们的产品,因为它很容易使用,并且感觉他们离不开它。

如果你必须和八达通公司的其他人换工作,你会选择谁的工作?

老实说,没有人。我热爱我的工作!如果我别无选择,我会考虑成为一名设计师,因为这是最接近产品管理的工作。

由 Trish Khoo - Octopus 负责部署

原文:https://octopus.com/blog/at-the-helm-trish-khoo

这篇文章是我们系列文章的下一篇,与 Octopus 的人们谈论他们的角色,他们的挑战,他们正在努力为我们的客户改进产品,等等。

在这里,我们采访了工程总监崔西·库。

你在八达通待了多久,带来了哪些经验?

我在 Octopus 工作一年多了。之前,我在谷歌和微软这样的大公司工作过,也在初创公司这样的小公司工作过,以及介于两者之间的任何公司。在布里斯班定居之前,我在悉尼、伦敦和旧金山工作过。

作为工程总监,典型的一天是怎样的?

我通常在喝咖啡的时候开始我的一天。我的“优先”部分有 8 个频道,而“重要”部分有 13 个频道。我尽可能地关注这两方面的最新进展。然后还有 31 个我定期关注的频道。

周一,我会与 Mike(工程副总裁)和 Roy(电子秤小组的工程总监)一起讨论影响 R&D 的问题,我们可以合作解决这些问题。

再次检查空闲时间后,我处理会议上的事情,然后休息去吃午饭。然后,我与我的工程经理和我的团队领导团队(GLT)进行同步。下午,我写政策、指导方针、建议、策略、公告或信息,并召开一对一会议。

每天使用 Slack 和 Zoom 可能听起来不令人兴奋,但我正在建立对我的团队和大 R&D 如何运作的意识,并采取小而有意义的行动来调整事情。我还利用 Zoom 会议的时间做折纸花,这有助于我集中注意力!

Photo of Trish

崔西的书桌上装饰着她的折纸作品

我学到的一个教训是,作为一名高级领导者,做出重大改变会对组织产生重大影响。有时这是必要的,但通常这只是破坏性的。当我阐明情况和政策以帮助他人更有效地履行职责时,我最有帮助。

到目前为止,你在八达通公司最大的成就是什么?

定义新的工程经理角色是一大成就。在加入之前,工程经理一般都是事必躬亲的技术主管和人事经理。定义一个纯粹专注于人员管理、团队文化和交付的经理的新角色有助于扩展团队并减轻来自技术领导的额外压力。

我还和我的 GLT 一起建立了第一个 R&D 集团,它为我们提供了更好的方式来制定长期的大计划。

我也喜欢和其他领导一起工作,创建一个工程招聘策略,让我们能够快速扩展。当我刚开始工作时,“人数”在八达通公司不是一个概念,每个经理负责他们的招聘广告和候选人面试——从招聘到录用的整个过程。它对一小部分候选人有效,但没有扩大规模。我和人事团队一起引入了新的可扩展方法来管理新来的候选人,同时确保我们首先填补了正确的团队。

我很高兴看到我在过去一年中为我们的扩展目标所做的贡献,我很自豪能成为这一旅程的一部分。

Octopus 的工程团队面临的最大挑战是什么?

我们在如此短的时间内为公司增加了如此多的工程师,并努力让我们的系统随着增长而扩展。我们正处于一个关键时刻,工程生产力充满挑战,我们雄心勃勃,我们的团队有一半是新成员。在这一点上保持我们的交付路线图需要持续的关注,但是到目前为止我们做得很好。

你对未来几年 Octopus 的工程有什么看法?

我希望看到我们在经历快速增长的过山车的同时,保留使八达通成为工程师工作的好地方的价值观——一种高绩效的文化,在这种文化中,工程师做他们一生中最好的工作。我想看到 Octopus 成为你成为更好的软件工程师的地方。我希望看到我们的组织成为一个充满活力的机器,由欣欣向荣的工程师组成,他们有信心完成大事。

我认为我们做得很好!无论我们做什么,我们正在经历的快速扩张和转型有时会很困难。但是每天和我一起工作的人都很聪明、勤奋、有同情心、诚实,这些都是八达通公司未来的重要基础。

您如何在内部以及为我们的客户衡量成功?

这是一个有趣的时刻,我们的重点是获得新的、更大的客户,同时保留现有的客户。我们发现成功对新客户意味着什么,并了解如何创造他们喜爱的引人注目的产品。

在一个团队中,我们正在试验我们的客户第一次使用 Octopus 的体验,看看我们是否可以让更多的首次用户成功部署。在另一个团队中,我们正在学习流行的企业产品,如 ServiceNow,并研究 Octopus 如何适应这种工具生态系统。最终,我们希望我们的产品能够帮助我们的客户取得成功,因为这有助于我们取得成功。

在内部,我们需要持续不断地交付成果。这意味着我们创造了一种工作环境,在这种环境中,我们可以轻松地重复完成重要的事情。有了这些和合适的人,我们几乎可以实现任何事情。

新功能:审计和历史- Octopus 部署

原文:https://octopus.com/blog/auditing-and-history

Octopus Deploy 的目标之一是成为一个信息辐射者。当您的部署过程由运行一堆批处理文件的“某个人”组成时,您团队中的其他人(以及利益相关者)很难洞察正在发生的事情。当前正在运行哪些部署?过去一小时完成了哪些部署?

Octopus 试图将这些信息放在仪表板的前面和中心。您可以查看部署的输出、正在运行的部署以及部署的历史记录。您可以在一个界面中看到一个版本如何从开发进展到生产。

直到现在,章鱼还没能回答的一个问题是执行了一个动作。今天的版本首次实现了这一特性:

Project activity screen

章鱼现在记录:

  1. 谁对部署进行了排队,谁取消了部署
  2. 谁创建或修改了版本
  3. 谁改变了项目步骤或变量

随着时间的推移,这些信息将开始变得越来越丰富,我们将有其他方法来分割它。例如,有一天,我可以想象一个“用户”页面,显示特定用户的所有活动,包括一个 RSS 提要。这只是一个开始,但希望它是一个对您有用的特性。我很想听听你的反馈!

使用 Golang - Octopus 部署向 Azure 进行身份验证

原文:https://octopus.com/blog/authenticate-to-azure-with-golang

Authenticate to Azure with Golang

当你使用任何编程或自动化语言时,很可能你首先需要解决的事情之一就是我如何从 SDK 认证到云平台?根据您使用的 SDK,该过程可能会有很大不同。

例如,PowerShell 通过Connect-AZAccount cmdlet 进行身份验证,而 Python 可以通过应用注册或使用 Azure CLI 配置文件进行身份验证。重点是,每种语言和 SDK 都不一样,那么 Golang (Go) 怎么样?如果你是当今世界的一名开发人员,你可能听说过 Golang(Terraform、Docker 和 Kubernetes 就是用它编写的),它是当今工具领域中发展最快和最流行的语言之一。

在这篇博客文章中,你将采取实践的方法来学习如何向 Azure 认证以使用虚拟机客户端。

先决条件

要跟进这篇博文,您需要:

  1. 对围棋的初级到中级理解。
  2. Azure 账户。如果你没有,你可以注册一个免费 30 天试用
  3. 一个 IDE 或者脚本编辑器,比如 GoLand 或者 VS Code

要使用哪些包

Azure authentication with Go 的第一步是找出程序中需要哪些库/包。出于本文的目的,除了标准的osfmt包,还有两个 Azure 包。

  1. 在你的桌面上创建一个目录,用 VS 代码打开它。
  2. 在目录中创建一个main.go文件。
  3. 添加以下代码来启动带有标准包的main.go文件,以及您需要连接到 Azure 并向其进行身份验证的包:
package main

import (
    "fmt"
    "os"

    "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-06-01/compute"
    "github.com/Azure/go-autorest/autorest/azure/auth"
) 

Azure connection

接下来,您将设置AzureAuth功能:

  1. main.go文件中的import下,创建如下所示的新函数。该函数为subscriptionID使用了一个os.Arg,稍后您将在main函数中定义它,并使用compute.VirtualMachinesClient类型来使用 Azure 的身份验证方法:
func AzureAuth(subscriptionID string) compute.VirtualMachinesClient {

} 
  1. 创建新功能后,您可以使用来自本地环境的身份验证来设置 Azure 的客户端身份验证:
vmClient := compute.NewVirtualMachinesClient(subscriptionID)
authorizer, err := auth.NewAuthorizerFromEnvironment() 

错误处理和验证

身份验证代码的最后一部分是错误处理和身份验证的混合体。if语句指定如果nil不为空,则打印出一个错误,否则让用户知道认证成功并启动 VM 客户机:

if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println("Auth: Successful")
        vmClient.Authorizer = authorizer
    }

    return vmClient 

配置主要功能

接下来,我们将配置main功能。因为 Go 是一种基于过程的语言,所以您将使用main函数来调用AzureAuth函数。这样,函数就按顺序运行了。

main函数是两行代码,实现了以下功能:

  • 指定运行时要传入的订阅 ID 的os.Arg
  • 调用AzureAuth函数。

AzureAuth函数上方添加以下代码,以指定它是一个main函数:

func main() {
    subscriptionID := os.Args[1]

    AzureAuth(subscriptionID)
} 

编辑器中的代码现在应该是这样的:

package main

import (
    "fmt"
    "os"

    "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-06-01/compute"
    "github.com/Azure/go-autorest/autorest/azure/auth"
)

func main() {
    subscriptionID := os.Args[1]

    AzureAuth(subscriptionID)
}

func AzureAuth(subscriptionID string) compute.VirtualMachinesClient {
    vmClient := compute.NewVirtualMachinesClient(subscriptionID)

    authorizer, err := auth.NewAuthorizerFromEnvironment()
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println("Auth: Successful")
        vmClient.Authorizer = authorizer
    }
    return vmClient
} 

要运行该程序,您需要一个 Azure 订阅 ID 在运行时传入。运行以下代码行来运行程序:

go run main.go your_subscription_id 

如果身份验证成功,您应该会看到类似如下的输出:

Terminal output showing authentication was successful

恭喜你。您已成功通过 Golang 向 Azure 认证。

结论

无论是在云中还是在内部,对任何平台进行身份验证都是重要的第一步。如果没有身份验证,如果您打算用它来连接平台,代码基本上是无用的。

在这篇博文中,你初步了解了如何使用 Go for virtual machines 认证 Azure。

自动提供安装了触手的 Amazon EC2 实例——Octopus Deploy

原文:https://octopus.com/blog/auto-provision-ec2-instances-with-tentacle-installed

Octopus 用来自动化部署的触手代理长期以来一直支持通过命令行进行配置。可以自动下载 MSI,安装它,配置触手实例,甚至向 Octopus 服务器注册它,所有这些都通过命令行完成。

当通过 Amazon EC2 配置 Windows 服务器时,您可以将 PowerShell 脚本作为用户数据传递。这将在 EC2 实例第一次启动时执行:

Provisioning the EC2 instance

我已经根据我们在自动化触手安装上的说明整理了一个示例脚本。你可以在下面找到这个脚本。将它粘贴到<powershell> XML 元素之间,然后等待机器启动。当它最终启动时,机器将注册到您的八达通服务器:

It's alive!

如果遇到任何问题,请远程访问机器并查看以下两个文件:

  • C:\TentacleInstallLog.txt
  • C:\Program Files\Amazon\Ec2ConfigService\Logs\Ec2ConfigLog.txt

以下是 PowerShell 脚本:

当您设置 EC2 实例并为其分配安全组时,请确保您指定的触手监听端口(默认为 10933)允许 TCP 流量。该脚本会自动将例外添加到 Windows 防火墙,但是您需要对 AWS 防火墙执行相同的操作。

有关 EC2 实例预配和用户数据的更多信息,请参见:

数据库部署自动化的自动批准- Octopus Deploy

原文:https://octopus.com/blog/autoapprove-database-deployments

Automatic approvals for your database deployment automation

在本文中,我将向您展示如何设置自动批准,这样您的 DBA 就不必手动批准每个部署。

当您第一次开始实现数据库部署自动化时,让 DBA 批准更改是该过程的一个重要部分,因为他们可以发现产品中可能失败并且难以回滚的事情,但这也有助于他们在该过程中建立信任。然而,总有一天,让数据库管理员批准每一项变更不再有用。

DBA 应该在什么时候参与进来?

DBA 应该只在需要的时候审查变更。

这听起来很简单,但执行起来要困难得多。我过去和 DBA 一起工作时,他们说过类似这样的话,“我希望任何违反标准的行为都会导致失败。他们甚至不应该被部署;我不需要看他们。另一方面,我希望看到那些对模式进行特定更改的脚本,我可能会在凌晨两点收到传呼。”

为了实现这一点,我们可以实施多层方法。第一层将出现在构建服务器上;它将运行必要的工具来检查是否违反了标准,例如,该工具可以检查命名约定、每个表是否有一个主键、存储过程中是否没有使用游标等等。有许多工具可以帮助实施 SQL 标准。这包括静态分析工具,如 SQL Enlight 以及使用 tSQLt 编写数据库单元测试。每种工具都有优点和缺点,但是深入研究这些工具超出了本文的范围。重要的是,当一个包到达 Octopus Deploy 时,我们将知道脚本符合我们的标准。

第二层将发生在 Octopus 部署中。自动化工具只能捕捉到这么多。可以编写一个满足所有标准和要求的 SQL 脚本,但是仍然写得很差,会导致严重的问题。例如,当在脚本中发现 drop table 命令时,不能有任何构建失败的规则。你永远无法清理旧的或不用的桌子。

难题的最后一部分是确定 DBA 应该对哪些环境执行手动干预。生产太晚了。到那时,承诺已经做出,用户的期望也已经设定。停止生产部署以便 DBA 可以检查脚本是没有意义的。如果他们发现问题,会发生什么?

让 DBA 批准部署到较低的环境,比如开发,也没有意义。这对 DBA 来说会产生太多的噪音,尤其是在每次签入后都进行构建的时候。

我的建议是在 QA、测试、试运行或 UAT 环境中进行手动干预。选择一个足够低的环境,使 DBA 可以提供建议或拒绝部署,但又足够高,使他们不会经常收到请求。测试不同的方法,因为拨入正确的环境可能需要一段时间。

Octopus 部署中的自动审批流程

对于自动化审批流程,我们将使用输出变量运行条件。该流程将有一个 PowerShell 脚本(或一系列 PowerShell 脚本)来检查增量报告。如果 PowerShell 脚本在增量报告中注意到一些有趣的事情,那么将触发手动干预。

对于本文,我希望我的脚本:

  • 如果没有更改,则自动批准。
  • 检查 Add Table、Drop Table、Drop Column、Drop View、Drop User、Add User、Alter User、Add User to Role、Create View、Create Select Stored Procedure 和 Merge 语句,如果发现任何语句,则要求进行检查。
  • 自动批准其他一切。

SQL 的好处在于

在 SQL 中只能有这么多模式变更语句,这意味着我们可以使用正则表达式来解析增量报告。

当我开始这个过程时,我忘记了部署前和部署后脚本,这是关键。他们会一直在那里。

这导致了一个有趣的决定。当检查没有变化时,它到底应该寻找什么?您会注意到报告中包含了文件的全名。例如,DbUpSample.BeforeDeploymentScripts.001_CreateSampleSchemaIfNotExists.sqlDbUpSample.PostDeploymentScripts.001_RefreshViews.sql.导致几个选项。我可以修改代码来排除部署前和部署后脚本。或者,我可以写我的修改检查来寻找匹配的文件。DbUpSample.DeploymentScripts.*.sql.就我个人而言,我喜欢将所有脚本都包括在内供 DBA 审阅的想法,而不仅仅是部署脚本。根据我的经验,完全的可见性可以在部署过程中建立信任。隐藏脚本,或者不包含脚本,是破坏这种信任的好方法。也就是说,这是我的个人偏好,如何实现这一点取决于您和您的 DBA。

更新的数据库部署自动化流程

如前所述,我希望这是一个利用输出变量的 PowerShell 脚本。我将该脚本添加到流程中,以便在生成升级报告后立即运行:

我选择让升级脚本和自动批准步骤在所有环境中运行。升级脚本将生成一个工件。自动批准步骤将使用 Octopus Deploy 提供的写高亮显示功能。通过这样做,每个环境都将在部署摘要中有一个升级报告和一个变更列表。

PowerShell 脚本在最后包含这一行来设置输出变量:

Set-OctopusVariable -name "DBAApprovalRequired" -value $approvalRequired 

访问输出变量的语法有点多,Octopus.Action[Auto Approve Upgrade Script].Output.DBAApprovalRequired.我在我的项目中添加了一个变量,使它更容易找到。此外,如果我决定更改名称,我只需在一个地方进行更改:

最后一部分是将手动干预步骤的运行条件更改为仅在该值为真时运行:

我们来测试一下。我创建了一个版本,其中包含需要审核的更改。手动干预步骤在开发或测试中不会触发,但在产品化中会触发:

作为另一个测试,我将同一个版本重新部署到 staging。DBUp 看到所有脚本都已运行。没有要批准的内容,手动干预步骤被跳过:

虽然我很想生成一个社区步骤模板供每个人使用,但每个公司都是不同的,我更愿意向您展示我整理的脚本。希望您可以从中获得一些东西,根据自己的用途进行修改,并将其添加到您的步骤模板库中:

$OctopusURL = # YOUR OCTOPUS BASE URL
$APIKey = # YOUR API KEY
$SpaceId = $OctopusParameters["Octopus.Space.Id"]
$DeploymentId = $OctopusParameters["Octopus.Deployment.Id"]
$CommandsToLookFor = "Create Table,Alter Table,Drop Table,Drop View,Create View,Create Function,Drop Function,sp_addrolemember,sp_droprolemember,alter role,Merge"
$FilePrefixToLookFor = "DbUpSample.DeploymentScripts."
$ArtifactFileName = "UpgradeReport.html"
$sqlFile = ".+\.sql"

$header = @{ "X-Octopus-ApiKey" = $APIKey }

$artifactUrl = "$OctopusUrl/api/$SpaceId/artifacts?take=2147483647&regarding=$DeploymentId&order=asc"
Write-Host "Getting the artifacts for this deployment from $artifactUrl"
$artifactResponse = (Invoke-WebRequest $artifactUrl -Headers $header).content | ConvertFrom-Json
$artifactList = $artifactResponse.Items
$fileToCheck = $null

foreach ($artifact in $artifactList)
{
    # The name of the file is UpgradeReport.html, look for that
    $fileName = $artifact.Filename

    if ($fileName -eq $ArtifactFileName)
    {
        Write-Host "Artifact containing the upgrade report found, downloading"
        $artifactId = $artifact.Id
        $artifactContentUrl = "$OctopusUrl/api/$SpaceId/artifacts/$artifactId/content"
        Write-Host "Pulling the content from $artifactContentUrl"
        $fileToCheck = Invoke-WebRequest $artifactContentUrl -Headers $header
        Write-Host "Finished downloading the file $fileName"
        break;
    }
}

if ($fileToCheck -eq $null)
{
    Write-Host "No file found, there should be a file, requiring approval"
    Set-OctopusVariable -name "DBAApprovalRequired" -value $true   
    Exit 0

    # No file, no checking
}
else
{
    Write-Host "File has been found, going to look through it now"
}
if ($filetocheck.rawcontent -like "$fileprefixtolookfor")
{
   Write-Highlight "No deployment scripts found, auto approving"
   Set-OctopusVariable -name "DBAApprovalRequired" -value $false
   Exit 0
}

Write-Host "Pulling all scripts from the report to check"
$scriptList = $fileToCheck.ParsedHtml.getElementsByTagName("div") | where {$_.className -eq "card"}
$commandsToCheck = $CommandsToLookFor -split ","
$approvalRequired = $false

foreach ($item in $scriptList)
{
    $rawHtml = $item.innerText
    $foundFileName = $rawHtml -match "$sqlFile"
    $fileName = $Matches[0]
    $foundCommandWarnings = $false

    foreach ($command in $commandsToCheck)
    {
        $foundCommand = $rawHtml -match "$command"

        if ($foundCommand)
        {
            $approvalRequired = $true

            if ($foundCommandWarnings -eq $false)
            {
                Write-Highlight "Found commands to review in file $fileName"
                $foundCommandWarnings = $true
            }

            foreach ($h in $Matches.Keys) {
                Write-Highlight "$($Matches.Item($h))"
            }
        }
    }
}

if ($approvalRequired -eq $false){
    Write-Highlight "All scripts look good, auto approving"
}
Set-OctopusVariable -name "DBAApprovalRequired" -value $approvalRequired 

对我的脚本有一个警告,我正在使用 PowerShell 解析 HTML。在后台,PowerShell 使用的是 Internet Explorer,我收到错误消息,通知我不可用,因为用户的初始设置没有运行。我通过在机器上运行初始设置来解决这个问题。但是,这仍然很烦人。

结论

输出变量运行条件是 Octopus Deploy 中的一个强大功能。它们允许您向部署过程添加逻辑。自动批准数据库部署就是一个例子。


数据库部署自动化系列文章:

使用 Chocolatey - Octopus Deploy 自动设置开发人员机器

原文:https://octopus.com/blog/automate-developer-machine-setup-with-chocolatey

Illustration showing Chocolately packages being installed on a developer machine

没有什么比全新安装 Windows 更好的了,尤其是在开发人员的机器上。有了这些天我们得到的硬件,计算机正在飞快地尖叫。然而,这种幸福的感觉很快就被打破了,因为是时候安装所需的应用程序和框架了。第一步是打开 IE Edge 下载 Chrome、Firefox 或任何其他浏览器。接下来,找到所有的网站,然后手动下载并安装所有必要的工具。所有这些都让我想起了我的大学时光。当我在大学的时候,我会每六个月左右重新安装一次 Windows。我在 Windows 9x/ME 时代上过大学。似乎每个人,从网站到 TechTV,都推荐这样做,那是大约二十年前的事了。

谢天谢地,现在是 2019 年,可用的工具比我在大学时强大得多。我们不再需要浪费几个小时或一整天来下载和安装所有的东西。在这篇文章中,我将带你了解如何使用 Chocolatey 自动设置开发人员的机器。

为什么是巧克力?

在加入 Octopus Deploy 之前,我曾在多家公司工作过。他们中的每一个人都遇到了同一个问题:我们如何让开发人员快速行动起来?提议的解决方案总是属于两种情况之一:

  • 开发者图像。
  • 像 SCCM 这样的强力工具。

第一个桶包含显影剂图像。人们谈论开发人员的图像,就像他们比炎热的夏天的冷饮更好。硬盘崩溃了?别担心,换下硬盘,使用图像,开发人员不到一个小时就可以开始工作了。新的开发者开始了吗?订购一台新的笔记本电脑,安装映像,开发人员不需要设置任何东西。他们进门之前就准备好了。

好吧,酷。除此之外,开发者工具一直在发布新版本。感觉好像一个星期都没有 Visual Studio 烦我更新到最新版本。谁来维护这一形象?该图像多久更新一次?最终经常发生的是,开发人员花费更多的时间更新和卸载旧工具,这实际上减慢了他们的速度。创建带有最新补丁的核心 Windows 映像,并让开发人员从那里开始,这更有意义。

第二个桶里装着像 SCCM 这样笨重的工具。我用过的工具有一个网络界面,我可以在那里选择我想安装在我机器上的软件,这听起来很棒。直到你看到你最喜欢的工具不见了或者列表上的版本是三年前的。将其添加到列表中涉及多个步骤,在某些情况下,还需要批准。是否要安装 Visual Studio 代码?酷,这将需要几个小时才能通过官僚程序,并且该列表永远不会更新,因为开发人员选择了阻力最小的路径。当下载和安装最新版本只需要不到 10 分钟的时间时,为什么要通过官僚主义来斗争呢?当每个人都必须有一个标准的软件包,如特定版本的 Microsoft Office 时,像 SCCM 这样的工具非常适合非开发人员的机器。

Chocolatey 不属于上述两个类别。如果你是一个. NET 开发者,那么你应该熟悉 NuGet。Chocolatey 是 Windows 的 NuGet。如果 Linux 是你的首选操作系统,那么 Chocolatey 就是包管理器,比如 apt 或者 RPM。它不属于这两个范畴,因为它是轻量级的,一个快速脚本安装它,并且它默认安装最新版本的包。它还将安装软件包需要的任何依赖项,如修补程序或另一个软件包。巧克力包装包裹着一个 MSI。它可能是章鱼触手、Visual Studio 或。NET Core SDK。

入门指南

首先,你需要安装 Chocolatey。你可以通过运行这里的脚本来实现。完成后,您就可以开始使用 Chocolatey 安装应用程序了。你可以通过进入巧克力套装页面找到可用的应用程序。我的电脑上没有安装 VLC,所以我将通过键入以下内容进行安装:

choco install vlc 

这将导致出现一个提示:

那个提示有点烦人。然而,它告诉我可以通过在我的命令中包含-y开关来避免它。让我们取消此操作,然后用-y开关重试。

【T2

现在我已经在我的机器上安装了 VLC。如果我想更新它,我需要运行命令:

choco upgrade vlc -y 

如你所见,因为我刚刚安装了它,所以我有最新的版本。

自动化开发机器设置

当我第一次开始使用 Chocolatey 时,我缺乏 PowerShell 知识,所以我的脚本看起来像这样:

Write-Host "Installing Google Chrome"
choco install googlechrome -y

Write-Host "Installing Firefox"
choco install firefox -y

Write-Host "Installing Redis"
choco install redis-64 -y

Write-Host "Installing 7-zip"
choco install 7zip -y 

对于一些软件包来说,复制/粘贴命令还不错。对于 20 多个包,复制/粘贴相同的命令很快就会过时。我想做的是将所有这些应用程序放入顶部的逗号分隔列表中,并浏览该列表:

$chocolateyAppList = "googlechrome,firefox,redis-64,7zip,dotnetcore-sdk,dotnetcore-windowshosting"
if ([string]::IsNullOrWhiteSpace($chocolateyAppList) -eq $false){   
    Write-Host "Chocolatey Apps Specified"  

    $appsToInstall = $chocolateyAppList -split "," | foreach { "$($_.Trim())" }

    foreach ($app in $appsToInstall)
    {
        Write-Host "Installing $app"
        & choco install $app /y
    }
} 

Windows 功能

如果您是 Windows 的 web 开发人员,很有可能需要在您的机器上安装 IIS。IIS 不是 MSI,而是 Windows 的一项功能。好消息是 Chocolatey 也可以安装这些软件。它通过利用所谓的 DISM 或部署映像服务管理来实现这一点。

要了解您可以使用哪些功能,您可以运行以下命令:

Dism /online /Get-Features 

通过 Chocolatey 安装 DISM 功能的命令是:

choco install [Feature Name] /y /source windowsfeatures 

例如:

choco install IIS-ManagementService -y -source windowsfeatures 

使用与上面相同的逻辑,我们可以在脚本中包含 DISM 特征:

$dismAppList = "IIS-ASPNET45,IIS-CertProvider,IIS-ManagementService"

if ([string]::IsNullOrWhiteSpace($dismAppList) -eq $false){
    Write-Host "DISM Features Specified"    

    $appsToInstall = $dismAppList -split "," | foreach { "$($_.Trim())" }

    foreach ($app in $appsToInstall)
    {
        Write-Host "Installing $app"
        & choco install $app /y /source windowsfeatures | Write-Output
    }
} 

Visual Studio 和其他缺失的应用程序

在撰写本文时,Chocolatey 中 Visual Studio 的最新版本是 Visual Studio 2017。Visual Studio 2019 于 2019 年 4 月问世。缺少最新的 Visual Studio 凸显了 Chocolatey 公共存储库的一个弱点。你只能任由开发应用程序的公司来创建软件包,或者由社区中的某个人来更新软件包。然而,你有能力创建你自己的包。您甚至可以建立一个内部存储库(就像您可以使用 NuGet 包一样)。内部存储库是免费的,但可以考虑为你的团队或公司购买巧克力。

Post 发布注意: Visual Studio 2019 在上面;我写这篇文章的那天,我很虚弱。可以找到 Visual Studio 2019 企业版Visual Studio 2019 专业版Visual Studio 2019 社区版。因为我没有找到 Visual Studio 2019,所以我添加了一个说明,说明这是 Chocolatey 公共回购的一个弱点。任何人都可以发布一个软件包,如果一家公司不这样做,人们可能会认为这是一种优势,而不是劣势。这是一个很好的反驳,任何人都能发布包是一个很大的优势。公共回购中有成千上万的应用程序,这是有原因的。由此得出的结论是:公共回购极有可能拥有你所需要的最新、最好的工具。但是,您选择的工具不在公共存储库中的可能性很小。特别是如果工具是内部构建和维护的,那么为该工具创建一个包是很容易的。但是,在哪里发布软件包将取决于您公司策略和工具。希望你能回馈社区,将这个包发布到公共回购上。但如果你不能公开回购,那么创建一个内部回购仍然是一个很大的选择。如果你选择内部回购,考虑为你的团队或公司购买巧克力。

创建可重复使用的脚本

到目前为止,所有的脚本示例都有硬编码的变量值,适用于工作良好的小型团队或公司。随着越来越多的团队使用这种方法,您需要提供一些灵活性。我见过几种情况。同一家公司的. NET 团队使用不同的工具集,这是因为他们工作的应用程序不同。一个团队可能需要 WIX 来开发 Windows 窗体应用程序,而另一个团队只使用 ASP.NET web API 后端来开发 Angular 网站。脚本应该接受参数。

另一件要考虑的事情是,我们不知道脚本何时运行,也不知道运行它的人是否已经安装了 Chocolatey。该脚本应该能够处理这种情况,并在需要时安装 Chocolatey:

Param(  
    [string]$chocolateyAppList,
    [string]$dismAppList    
)

if ([string]::IsNullOrWhiteSpace($chocolateyAppList) -eq $false -or [string]::IsNullOrWhiteSpace($dismAppList) -eq $false)
{
    try{
        choco config get cacheLocation
    }catch{
        Write-Output "Chocolatey not detected, trying to install now"
        iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
    }
}

if ([string]::IsNullOrWhiteSpace($chocolateyAppList) -eq $false){   
    Write-Host "Chocolatey Apps Specified"  

    $appsToInstall = $chocolateyAppList -split "," | foreach { "$($_.Trim())" }

    foreach ($app in $appsToInstall)
    {
        Write-Host "Installing $app"
        & choco install $app /y | Write-Output
    }
}

if ([string]::IsNullOrWhiteSpace($dismAppList) -eq $false){
    Write-Host "DISM Features Specified"    

    $appsToInstall = $dismAppList -split "," | foreach { "$($_.Trim())" }

    foreach ($app in $appsToInstall)
    {
        Write-Host "Installing $app"
        & choco install $app /y /source windowsfeatures | Write-Output
    }
} 

我们现在可以为每个团队指定一个脚本来指定要安装的应用程序:

$chocolateyAppList = "googlechrome,firefox,redis-64,7zip,dotnetcore-sdk,dotnetcore-windowshosting"
$dismAppList = "IIS-ASPNET45,IIS-CertProvider,IIS-ManagementService"

Invoke-Expression "InstallApps.ps1 ""$chocolateyAppList"" ""$dismAppList""" 

附加用法

看看上面的 PowerShell 脚本并回答这个问题,这些脚本有开发机器专用的吗?没有。现在,这是一个有趣的想法,因为您可以使用脚本来引导 Windows 服务器机器,而不仅仅是开发机器。这引出了下一个问题,为什么不创建一个图像或使用工具,如 DSC?便携性和易于配置。

就像开发人员的机器一样,服务器也需要安装不同的应用程序。试图创造“一个图像来统治他们”是不可行的。使用带有最新补丁的核心 Windows 映像并在其上安装必要的应用程序要容易得多。或者,如果您使用云提供商,请使用所提供的 Windows 映像之一。

你可以一起使用 Chocolatey 和 DSC。或者您可以只使用一种工具。比起 DSC 我更喜欢用 Chocolatey。DSC 的学习曲线比巧克力更陡。但它也更强大。我的建议是看看可用的工具,看看哪一个最有意义。

我的团队每天都在创建和破坏我们的演示基础设施。美国中部时间凌晨 2 点,基础设施上线。在美国中部时间晚上 9 点,基础设施被摧毁。我们正在建立服务器,使用 Chocolatey 安装所需的组件,并安装 Tentacles 以获得最新的代码。服务器不打算活很久,我也不需要 DSC 的开销。

结论

正如您所看到的,使用 Chocolatey,可以自动设置开发人员的机器,同时为团队选择他们自己的工具提供了灵活性。这就是我喜欢用巧克力的原因。这是一个我可以根据自己的需要来设计的框架,它让我的生活变得更加轻松。在我个人的使用中,我看到了巨大的节约。在此之前,当我得到一台新机器时,我会花一个小时左右安装 Windows 更新,然后花几个小时下载并安装我最喜欢的应用程序。浪费几个晚上或一整天去做那件事并不罕见。现在,这一比例发生了逆转。我仍然需要花一个小时左右的时间来安装 Windows 更新,但现在我启动了 PowerShell 脚本,大约半个小时后,我就可以开始了。现在我唯一的麻烦是保持我的 Chocolatey 应用程序列表更新。

使用 AWS Lambda 和订阅自动引导故障- Octopus 部署

原文:https://octopus.com/blog/automate-guided-failure-with-lambda

手动干预引导故障是 Octopus Deploy 产品的强大功能。这两个功能都暂停部署或运行手册,并等待用户选择一个选项。不利的一面是,需要有人在场做出回应。然而,有了 Octopus Deploy written API-first,就有可能自动化这些响应。

这篇文章演示了如何使用订阅特性调用 AWS Lambda 函数来自动响应引导失败事件。

示例使用案例

引导式失败旨在暂停一个动作并等待指导,自动化响应似乎违反直觉。这里有几个自动化响应有意义的用例。

从部署中排除计算机

考虑一下,如果您要部署到数千台机器上。在这种规模下,一小部分机器(通常与 Octopus 无关)的部署失败并不罕见。引导式失败为您提供了从部署中排除一台计算机的选项,这允许部署继续到剩余的目标。您可以自动化Exclude machine from deployment响应,稍后再处理故障机器,而不是照看这个过程。

自动重试

我们的团队经历了另一个场景;当 Samples 实例在夜间拆除资源时,如此多的请求同时被发送到 AWS,以至于它经历了请求速率限制并失败。在这种情况下,只需重试 runbook。对引导性故障实施自动Retry响应可确保所有资源都被取消供应。

解决办法

这篇文章向您展示了如何通过使用 Octopus 的订阅特性调用 AWS Lambda 函数来自动响应引导失败事件。

首先,您需要提供一些 AWS 资源。

OctoSubscriber repo 包含了这篇文章中描述的解决方案的源代码。

搭建 AWS 资源

本文中的解决方案利用了以下 AWS 资源:

IAM 角色可能拥有较低的权限,但这是测试解决方案所用的权限。

使用 AWS 资源可能会给您或您的组织带来成本。

使用操作手册,您可以使用 Terraform 和 AWS CLI 提供以下资源:

  • S3 水桶
  • SQS 队列
  • API 网关

Octopus UI showing Spin Up Subscriber Infrastructure steps in the Runbooks section

如果不存在,则创建 S3 时段

创建其他 AWS 资源的 Terraform 需要一个存储状态的地方。此步骤使用 AWS CLI 为 Terraform 步骤创建后端存储位置:

# Get variables
$bucketName = $OctopusParameters["Project.AWS.Backend.Bucket"]
$bucketRegion = $OctopusParameters["Project.AWS.Backend.Region"]

# Get bucket list
$awsS3BucketList = aws s3api list-buckets --query "Buckets[].Name" --output json

# Convert returned json into an object
$awsS3BucketList = ($awsS3BucketList | ConvertFrom-JSON)

# Check to see if bucket exists
if ($null -eq ($awsS3BucketList | Where-Object {$_ -eq $bucketName}))
{
    # Create the bucket
    Write-Highlight "Bucket $bucketName doesn't exist, creating ..."

    aws s3api create-bucket --bucket $bucketName --region $bucketRegion --create-bucket-configuration LocationConstraint=$bucketRegion
}
else
{
    Write-Highlight "Bucket $bucketName already exists, moving on ..."
} 

计划应用 Terraform 模板

为了帮助您快速启动, OctoSubscriber repo 包含 Terraform 脚本,用于自动创建 SQS 和 API 网关资源。这一步输出 Terraform 将按计划做什么。

main.tf

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.0"
    }
  }

  backend "s3" {
    bucket = "#{Project.AWS.Backend.Bucket}"
    key = "#{Project.AWS.Backend.Key}"
    region = "#{Project.AWS.Backend.Region}"
  }
}

provider "aws" {
    region  = var.region
}

resource "aws_sqs_queue" "subscriber_queue" {
  name                              = var.queue_name
  kms_master_key_id                 = "alias/aws/sqs"
  kms_data_key_reuse_period_seconds = 300
}

resource "aws_apigatewayv2_api" "subscriber_gateway" {
  name                              = var.api_gateway_name
  protocol_type                     = "HTTP"
} 

变量. tf

variable "region" {
    type = string
}

variable "queue_name" {
    type = string
}

variable "api_gateway_name" {
    type = string
} 

terraform.tfvars

region = "#{Project.AWS.Region}"
queue_name = "#{Project.AWS.Queue.Name}"
api_gateway_name = "#{Project.AWS.API.Gateway.Name}" 

应用 Terraform 模板

使用上面的地形,在 AWS 上创建资源。

创建资源后,您可以关注 Lambdas。

AWS 函数

该解决方案由两个不同的 Lambda 函数组成:

  • 这个函数将包含的有效载荷放到 SQS 队列中。
  • process-message:该函数处理出现在 SQS 队列中的消息,并将请求的响应提交给 Octopus 服务器。

接受消息

该函数用 NodeJS 编写,接受以下 querystring 参数:

  • 类型:已经发生的中断的类型。
    • 导向:为导向故障提供该值。
    • 结果:为手动干预提供该值。
  • 动作:这是对中断的实际响应。该值取决于类型:
    • 中止(结果)
    • 继续(结果)
    • 失败(指导)
    • 排除(指导)
    • 忽略(指导)
    • 重试(指导)
  • MaximumRetry(与重试操作一起使用):指定在放弃之前执行的最大重试次数。默认值为 1,导向故障功能最多可重试 10 次,因此任何大于 10 次的故障都会被忽略。
var AWS = require('aws-sdk');

exports.handler = function(event, context) {
  let QUEUE_URL = process.env.sqsqueue;
  let sqs = new AWS.SQS({region : process.env.sqsregion});
  let maximumretry = "1";

  if (event.queryStringParameters.maximumretry) {
    maximumretry = event.queryStringParameters.maximumretry
  }

  var params = {
    MessageBody: event.body,
    QueueUrl: QUEUE_URL,
    MessageAttributes: {
      "Type": {
        DataType: "String",
        StringValue: event.queryStringParameters.type
      },
      "Action": {
        DataType: "String",
        StringValue: event.queryStringParameters.action
      },
      "MaximumRetry": {
        DataType: "String",
        StringValue: maximumretry
      }
    }
  };

  sqs.sendMessage(params, function(err,data){
    if(err) {
      console.log('error:',"Fail Send Message" + err);
      context.done('error', "ERROR Put SQS");  // ERROR with message
    }else{
      console.log('data:',data.MessageId);
      context.done(null,'');  // SUCCESS 
    }
  });
} 

querystring 参数作为 MessageAttributes 附加到消息中。

流程消息

该函数从 SQS 队列中读取消息,并使用 Octopus。客户端获取包引用以自动响应中断。完整的解决方案可以在八月认购回购中找到。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Amazon.Lambda.Core;
using Amazon.Lambda.SQSEvents;
using Newtonsoft.Json;
using Octopus.Client;

// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]

namespace process_message
{
    public class Function
    {
        public Function()
        {

        }

        /// <summary>
        /// This method is called for every Lambda invocation. This method takes in an SQS event object and can be used 
        /// to respond to SQS messages.
        /// </summary>
        /// <param name="evnt"></param>
        /// <param name="context"></param>
        /// <returns></returns>
        public async Task FunctionHandler(SQSEvent evnt, ILambdaContext context)
        {
            foreach(var message in evnt.Records)
            {
                await ProcessMessageAsync(message, context);
            }
        }

        private async Task ProcessMessageAsync(SQSEvent.SQSMessage message, ILambdaContext context)
        {
            // Log
            LambdaLogger.Log("Begin message processing...");

            // Get environment variables
            string octopusServerUrl = Environment.GetEnvironmentVariable("OCTOPUS_SERVER_URL");
            string octopusApiKey = Environment.GetEnvironmentVariable("OCTOPUS_API_KEY");

            // Check to see if there are message attributes
            if (message.MessageAttributes.Count == 0)
            {
                // Fail
                throw new Exception("MessageAttributes collection is empty, was the queue called with querystring paramters?");
            }

            // Log
            LambdaLogger.Log(string.Format("Retrieved environment variables, Octopus Server Url: {0}...", octopusServerUrl));

            // Deserialize message JSON
            LambdaLogger.Log(string.Format("Parsing message..."));
            dynamic subscriptionEvent = JsonConvert.DeserializeObject(message.Body);
            LambdaLogger.Log("Successfully parsed message JSON...");

            // Create Octopus client object
            LambdaLogger.Log("Creating server endpoint object ...");
            var endpoint = new OctopusServerEndpoint(octopusServerUrl, octopusApiKey);
            LambdaLogger.Log("Creating repository object...");
            var repository = new OctopusRepository(endpoint);
            LambdaLogger.Log("Creating client object ...");
            var client = new OctopusClient(endpoint);

            // Create repository for space
            string spaceId = subscriptionEvent.Payload.Event.SpaceId;
            LambdaLogger.Log(string.Format("Creating repository object for space: {0}...", spaceId));
            var space = repository.Spaces.Get(spaceId);
            Octopus.Client.IOctopusSpaceRepository repositoryForSpace = client.ForSpace(space);

            // Retrieve interruption; first related document is the DeploymentId
            string documentId = subscriptionEvent.Payload.Event.RelatedDocumentIds[0];

            // Check to see if guided failure has already been invoked once, defaults to once if nothing provided
            int maximumRetry = 1;
            if (!string.IsNullOrWhiteSpace(message.MessageAttributes["MaximumRetry"].StringValue))
            {
                // Set to value
                maximumRetry = int.Parse(message.MessageAttributes["MaximumRetry"].StringValue);
            }

            var eventList = repositoryForSpace.Events.List(regarding: documentId);

            if (eventList.Items.Count(x => x.Category == "GuidedFailureInterruptionRaised") > maximumRetry && message.MessageAttributes["Action"].StringValue == "Retry")
            {
                LambdaLogger.Log(string.Format("{0} has raised Guided Failure more than {1} time(s), updating Action to Fail to break the infinite loop.", documentId, maximumRetry));
                message.MessageAttributes["Action"].StringValue = "Fail";
            }

            LambdaLogger.Log(string.Format("Processing event for document: {0}...", documentId));
            var interruptionCollection = repositoryForSpace.Interruptions.List(regardingDocumentId: documentId, pendingOnly: true).Items;

            if (interruptionCollection.Count > 0)
            {
                foreach (var interruption in interruptionCollection)
                {
                    // Check to see if responsibility needs to be taken
                    if (interruption.IsPending)
                    {
                        // Take responsibility
                        LambdaLogger.Log(string.Format("Taking responsibility for interruption: {0}...", interruption.Id));
                        repositoryForSpace.Interruptions.TakeResponsibility(interruption);

                        // The message attributes contain the type [Manual Intervention | GuidedFailure] and the desired Action to take for it
                        interruption.Form.Values[message.MessageAttributes["Type"].StringValue] = message.MessageAttributes["Action"].StringValue;

                        // Update Octopus
                        LambdaLogger.Log(string.Format("Submitting {0}:{1} for: {2}...", message.MessageAttributes["Type"].StringValue, message.MessageAttributes["Action"].StringValue, interruption.Id));
                        repositoryForSpace.Interruptions.Submit(interruption);
                    }
                }
            }
            await Task.CompletedTask;
        }
    }
} 

制造和包装兰姆达斯

只有process-message Lambda 需要构建,但是,它们都需要打包以便部署。OctoSubscriber 项目使用 GitHub 动作来完成这些操作。

# This is a basic workflow to help you get started with Actions

name: AWS Lambda

on:
  push:
    paths:
      - 'aws/accept-message/**'

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        dotnet-version: ['3.1.x']

    steps:
      - uses: actions/checkout@v2
      - name: Setup .NET Core SDK ${{ matrix.dotnet-version }}
        uses: actions/setup-dotnet@v1.7.2
        with:
          dotnet-version: ${{ matrix.dotnet-version }}

      - name: Create artifacts folder
        run: |
          mkdir "$GITHUB_WORKSPACE/artifacts"
          mkdir "$GITHUB_WORKSPACE/artifacts/process-message"
      - name: Restore dependencies for process-message
        working-directory: aws/process-message/process-message
        run: dotnet restore

      - name: Build process-message
        working-directory: aws/process-message/process-message
        run: dotnet build --configuration Release --no-restore --output "$GITHUB_WORKSPACE/artifacts/process-message"

      - name: Install Octopus CLI
        uses: OctopusDeploy/install-octopus-cli-action@v1.1.1
        with:
          version: latest

      - name: Pack and Push
        env:
          OCTOPUS_URL: ${{ secrets.OCTOPUS_SERVER }}
          OCTOPUS_API_KEY: ${{ secrets.OCTOPUS_API_KEY }}  
        run: |
          octo pack --id=OctoSubscriber-AcceptMessage-Lambda --format=Zip --version=2021.1.1.$GITHUB_RUN_NUMBER --basePath="aws/accept-message/"
          octo pack --id=OctoSubscriber-ProcessMessage-Lambda --format=Zip --version=2021.1.1.$GITHUB_RUN_NUMBER --basePath="$GITHUB_WORKSPACE/artifacts/process-message"
          octo push --package=OctoSubscriber-AcceptMessage-Lambda.2021.1.1.$GITHUB_RUN_NUMBER.zip --server=$OCTOPUS_URL --apiKey=$OCTOPUS_API_KEY --space="Target - Serverless"
          octo push --package=OctoSubscriber-ProcessMessage-Lambda.2021.1.1.$GITHUB_RUN_NUMBER.zip --server=$OCTOPUS_URL --apiKey=$OCTOPUS_API_KEY --space="Target - Serverless" 

在本例中,GitHub 动作将 Lambda 包直接推送到内置的 Octopus Deploy 包存储库。

部署兰姆达斯

这篇文章假设你熟悉如何创建一个 Octopus Deploy 项目,不会涉及这个主题。

Lambdas 的部署过程将包括以下步骤:

  • AWS - Deploy 接受消息 Lamdba 函数
  • AWS -部署流程消息 Lamdbda 函数
  • AWS -配置接受消息 Lambda 别名
  • AWS -配置流程消息 Lambda 别名
  • AWS -配置 Lambda API 网关集成

AWS -部署接受消息 Lambda 函数

向您的部署流程添加一个AWS-Deploy Lambda Function步骤。点击添加步骤,然后通过aws进行搜索。

Octopus UI showing Choose Step Template with aws in search field and AWS - Deploy Lambda Function selected

填写步骤表单字段:

  • 功能名称:要部署的功能的名称
  • AWS 帐户:用于部署的 AWS 帐户变量
  • 区域:Lambda 部署的区域
  • :包含 Lambda 的包
  • 功能角色:您为 Lambdas 创建的角色的 ARN
  • 运行时:代码运行时,nodejs14.x为接受消息
  • 处理程序:Lambda 调用来执行你的函数的方法的名字(注意:这将根据运行时选择的而不同)——对于接受消息,它是index.handler
  • 内存大小:您的函数可以访问的内存大小,必须是 64MB 的倍数,接受消息128
  • 环境变量:sqsqsqueue =[队列名称],SQS Region =[SQS 队列区域]

AWS -部署流程消息 Lambda 函数

这使用了与上面相同的步骤模板,所以我只列出不同的字段:

  • 函数名:要部署的函数名
  • :包含 Lambda 的包
  • 运行时 : dotnetcore 3.1
  • 处理程序 : process_message::process_message.Function::FunctionHandler
  • 环境变量:OCTOPUS _ SERVER _ URL =[OCTOPUS 服务器 URL],OCTOPUS_API_KEY=[API Key]
  • 超时 : 30(默认超时为 3 秒,不足以让 Lambda 完全处理)

AWS -配置访问消息 Lambda 别名

AWS 支持同时部署 Lambda 的多个版本。AWS 还支持金丝雀式的流程,在这个流程中,您可以为不同版本的 Lambda 分配一定比例的流量。这是通过为一个版本配置一个别名,并告诉 Lambda 要路由多少流量给它来实现的。

向您的流程添加一个 AWS 配置 Lambda 别名步骤。

Octopus UI showing Choose Step Template with aws in search field and AWS Configure Lambda Alias selected

填写步骤表单字段:

  • 函数名:上面的部署步骤将为部署的函数输出 ARN,这里使用输出变量- #{Octopus。动作 AWS -部署接受消息 Lambda 函数. output .λarn }
  • AWS 帐户:用于部署的 AWS 帐户变量
  • 区域:Lambda 所在的区域
  • 别名:要使用的别名,本帖使用Live
  • 别名版本百分比:该版本接收流量的百分比,本帖使用 100
  • 函数版本:别名的函数版本,使用部署步骤- #{Octopus.Action[AWS - Deploy Accept Message Lambda Function].Output.PublishedVersion}的输出变量

AWS -配置流程消息 Lambda 别名

这一步与前一步完全一样,只是针对流程消息 Lambda 进行了配置。

使用#{Octopus.Action[AWS - Deploy Process Message Lambda Function].Output.LambdaArn}将输出变量中的 AWS -部署接受消息 Lambda 函数替换为 AWS -部署流程消息 Lambda 函数

AWS -配置 Lambda API 网关集成

这一步配置 API v2 网关来连接和调用 Lambda 函数。

向您的流程添加一个AWS-Configure Lambda API Gateway Integration步骤:

Octopus UI showing Choose Step Template with aws in search field and AWS - Configure Lambda API Gateway Integration selected

填写步骤表单字段:

  • API 网关名称:要配置/创建的网关的名称
  • AWS 帐户:用于部署的 AWS 帐户变量
  • AWS 区域:Lambda 所在的区域
  • 路线键:使用/创建路线,本岗位使用/octopus/webhook
  • HTTP 方法:您正在创建的路由和集成的 HTTP 方法
  • 拉姆达 ARN :拉姆达的 ARN,这篇文章使用了创建步骤#{Octopus.Action[AWS - Deploy Accept Message Lambda Function].Output.LambdaArn}的输出变量
  • Lambda 别名:别名的名称,在本例中为Live
  • 净荷格式版本:API 网关发送给 Lambda 的净荷格式

部署

如果 Lambdas、别名或 API 网关先前不存在,则首次部署时可能会收到警告。这很正常。

Octopus UI showing Task Summary with warnings

配置剩余资源

大部分需要完成的工作已经通过使用 Terraform 的供应流程和使用 AWS 步骤模板的部署流程自动完成。但是,在解决方案准备好被调用之前,还有两个步骤:

  • 创建 API 网关阶段
  • 将流程消息 Lambda 连接到 SQS 队列

创建 API 网关阶段

创建阶段将为要调用的接受消息 Lambda 创建 URL 端点。在 AWS 中,导航到网络&内容交付部分,然后选择 API 网关

AWS Console showing Networking & Content Delivery section with API Gateways menu item highlighted

点击您想要配置的 API 网关。加载完成后,点击创建阶段

AWS Console open on API Gateways with Create stage button highlighted

给这个阶段一个名称,打开启用自动部署选项,然后点击创建

AWS console showing Stage details

创建阶段后,它将显示配置 Octopus Deploy 订阅 webhook 所需的调用 URL

AWS console showing Stage details with Invoke URL highlighted

将流程消息 Lambda 连接到 SQS 队列

现在您需要将流程消息 Lambda 配置为从 SQS 队列中触发。

导航到位于计算部分下的 AWS 控制台中的λ

AWS Console showing All services section above Compute section with Lambda menu item highlighted

选择流程消息λ,然后点击配置,然后触发,然后点击添加触发

AWS console showing Function overview

从下拉菜单中选择 SQS 。点击 SQS 队列框将显示队列列表。您可以从该列表中选择或输入队列的 ARN。完成后,点击添加

AWS console showing  Trigger configuration section with SQS section and SQS queue section highlighted

配置 Octopus 部署订阅

现在,您已经准备好配置 Octopus Deploy,以便在发生引导失败事件时调用您的 Lambda。

导航到 Octopus Deploy 的配置选项卡,点击订阅,然后添加订阅

Octopus dashboard open on the Configuration tab with Subscriptions selected from the menu and ADD SUBSCRIPTION highlighted

为订阅输入以下信息:

  • 名称:给订阅命名
  • 事件类别:引发引导失败中断
  • 有效载荷 URL : [API Gateway Invoke URL]/octopus/webhook?type=Guidance&action=Fail

完成后,它看起来会像这样:

Octopus dashboard open on the Configuration tab with Webhook Notifcations section highlighted and Payload URL showing

测试解决方案

为了测试这个解决方案,我们需要做两件事:

  • 启用引导失败
  • 将部署配置为失败

启用引导式故障

可以为部署流程或操作手册启用引导式故障。

要启用部署过程,选择设置,然后选择使用引导故障模式

Octopus dashboard showing Settings with Use guided failure mode selected and highlighted

将部署配置为失败

一种简单的方法是创建一个部署流程,其中有一个运行脚本步骤。为脚本输入以下内容:

Fail-Step "Fail on purpose" 

运行部署

部署将立即进入引导失败并等待引导。

Octopus dashboard open on the Projects tab with Task summary showing an error message about waiting to be assigned

几秒钟后,Octopus Deploy 将处理订阅事件,并将使部署失败。

Task summary with Guidance received: failed highlighted

在 AWS 控制台中,您可以查看 CloudWatch 日志,查看 Lambda 处理了消息,然后提交给 Octopus 服务器。

AWS Console showing Log events

结论

这篇文章向您展示了如何使用 Octopus Deploy 订阅调用 AWS Lambda 来自动响应引导失败事件。虽然演示的目标是引导失败,但同样的 Lambda 也可以用于自动响应手动干预

愉快的部署!

在基于 Linux 的 Docker 容器- Octopus Deploy 中运行 SQL Server Developer

原文:https://octopus.com/blog/automate-sql-server-install-using-linux-docker

SQL Server database in a Linux-based Docker container on an iceberg with a Docker container ship in the background

我的上一篇文章讲述了如何让 SQL Server 在基于 Windows 的 Docker 容器中运行。然而,Docker 是为托管基于 Linux 的容器而设计的,与基于 Windows 的容器相比,它具有以下优点。

  • 开销少得多。
  • 更多可用功能。
  • 更多图片可用。
  • 更多使用中的容器示例。

SQL Server 可以在基于 Linux 的容器上运行。在本文中,我将介绍在基于 Linux 的容器中启动和运行 SQL Server 的必要条件。

准备工作

本文使用 Docker 桌面。我之前的文章介绍了安装 Docker 桌面的步骤,所以我在这里不再重复。

Docker 桌面唯一的缺点是你不能同时运行基于 Windows 的容器和基于 Linux 的容器。如果您一直在使用基于 Windows 的容器,您可以通过右键单击任务栏中的 Docker 图标并选择Switch to Linux containers...来切换到 Linux 容器。默认情况下,Docker 桌面从 Linux 容器开始:

Docker 将所有容器视为无状态。预计对容器所做的任何更改(如创建数据库)都将被销毁。我们将通过使用体积来解决这个问题。切换到 Linux 容器后,右键单击任务栏中的 Docker 桌面图标,然后进入设置。在设置中,选择共享驱动器选项。单击复选框,与 Docker 共享您选择的驱动器。在我的情况下,我只有C:\驱动器:

我想将数据库存储在我的C:\驱动器上的文件夹C:\DockerLinux\Volumes\SQLServer中:

配置 SQL Server 开发人员容器

就像以前一样,我想用这个来进行实际的开发工作。在上一篇文章中,我创建了一个 Docker 合成文件来启动 SQL Server。与其从那里开始,我想做和以前一样的过程。有条不紊地在 Linux 容器中启动并运行 SQL Server。当我最终碰壁时,采取系统化的方法会使故障诊断更容易。

  1. 无需额外配置即可启动并运行容器。
  2. 通过 SSMS 连接到它。
  3. 持久化在容器中创建的数据库。

首次运行 SQL Server Developer 容器

首先,让我们运行一个简单的命令,从 Docker Hub 下载 SQL Server Windows 开发人员映像:

docker pull mcr.microsoft.com/mssql/server 

SQL Server 的基于 Windows 的映像需要很长时间才能下载。对于基于 SQL Server Linux 的容器来说,情况并非如此。我花了更长的时间来输入这一段并捕捉截图:

为 SQL Server 的基于 Linux 的容器提供的文档使容器的启动和运行变得很容易。请记下正在发送的--name参数。当我们需要弄清楚如何连接它时,这个参数将使它变得更容易。在命名实例的同时,我将端口设置为默认的 SQL Server 端口1433

环境变量名区分大小写,sa_password 将不起作用,它必须是 SA_PASSWORD。

docker run --name SQLServerLinux -d -p 1433:1433 -e "SA_PASSWORD=Password_01" -e "ACCEPT_EULA=Y" mcr.microsoft.com/mssql/server 

从主机上的 SSMS 连接到容器

SQL Server 容器正在运行,但是在主机上运行的 SSMS 如何连接到它呢?在上面的命令中,我们提供了-p参数,该参数将端口暴露给localhost上的主机。要从 SSMS 连接到运行在 Linux 容器中的 SQL Server,我们只需键入localhost并提供 sa 用户名/密码:

就像普通的 SQL Server 一样,一切都按预期运行。我可以毫无问题地创建数据库和表格:

持久化在容器中创建的数据库

如果容器需要重启会怎么样?

docker stop SQLServerLinux
docker start SQLServerLinux 

重新启动后,数据库仍然存在:

如果需要重新创建容器,进行升级或配置更改,那么我们需要在停止它之后调用rm命令来删除它:

docker stop SQLServerLinux
docker rm SQLServerLinux
docker run --name SQLServerLinux -d -p 1433:1433 -e "SA_PASSWORD=Password_01" -e "ACCEPT_EULA=Y" mcr.microsoft.com/mssql/server 

发生这种情况时,该容器中的所有数据库都将被删除:

如果你读过我以前的文章,这并不奇怪,就像以前一样,我们需要利用数量。Docker 的一个优点是它提供了一个抽象层。告诉 Docker 指向主机上的文件夹,C:\DockerLinux\Volumes\SQLServer指向存储 SQL Server 配置的 Linux 目录,var/opt/mssql:

docker stop SQLServerLinux
docker rm SQLServerLinux
docker run --name SQLServerLinux -d -p 1433:1433 -e "SA_PASSWORD=Password_01" -e "ACCEPT_EULA=Y" -v C:\DockerLinux\Volumes\SQLServer:/var/opt/mssql mcr.microsoft.com/mssql/server 

创建测试数据库后,它将出现在主机上的该文件夹中:

当容器重新启动时,它将挂载所有那些现有的数据库,包括 TestDatabase。

Docker 撰写

我喜欢用 Docker 作曲。将我的 Docker 容器配置存储在一个易于阅读和运行的 YAML 文件中非常好。以下是基于 Linux 的 SQL Server 容器的外观:

version: '3.7'
services:
 SQLServer:
 image: mcr.microsoft.com/mssql/server
 environment:
 - ACCEPT_EULA=Y
 - SA_PASSWORD=Password_01
 ports:
 - '1433:1433'
 volumes:
 - C:\DockerLinux\Volumes\SQLServer:/var/opt/mssql 

我把那个docker-compose.yml文件保存在 C:\DockerLinux 中。现在,我运行这个 PowerShell 脚本来让一切正常运行:

set-location C:\DockerLinux
docker-compose up 

如果您不想看到实时日志,将-d开关添加到docker-compose up来启动容器,而不附加它:

结论

当我开始写这篇文章时,我认为我必须学习很多关于 Linux 的知识。当我了解 Linux 的一些细微差别时,我在精神上为自己准备了几天的挫折。想象一下,当我所需要做的只是切换到 Linux 容器、提取不同的映像并更改主机上的路径时,我有多惊讶。我写这篇文章花的时间比启动和运行容器花的时间还多。这是为数不多的几次学习曲线比我预期的低很多的一次。

现在,我可以选择何时需要在本地运行 SQL Server。我可以将它作为 Windows 服务、基于 Windows 的容器或基于 Linux 的容器来运行。有这样的选择真是太棒了。

下次再见,愉快的部署!

自动化 SQL Server Developer 安装- Octopus 部署

原文:https://octopus.com/blog/automate-sql-server-install

Illustration showing developers installing SQL Server

几年前,我在一家公司工作,该公司使用 Redgate 的工具大力推动数据库部署自动化。我们选择了专用数据库模型,而不是共享数据库模型,这意味着公司的 100 多名开发人员都必须在他们的笔记本电脑上安装 SQL Server Developer。

我的团队是第一个采用新工具的团队。起初,我们尝试一起浏览安装向导中的每个屏幕,以确保类似的设置,但这不会扩展到 100 多名开发人员,而且我们不想每次有新的开发人员加入组织时都重复这一过程。

幸运的是,您可以使用命令行安装 SQL Server。微软的文档提供了一个很好的起点,但是文档中有很多东西需要消化。我花了相当多的时间让一切都符合我们的标准,但最终,这是值得的努力,并消除了许多潜在的头痛。

在 Octopus,我最近看到我们的一名高级工程师和一名新工程师坐在一起,帮助他们设置系统。当他们在他的机器上安装 SQL Server 时,他们通过 GUI 来选择要选择的选项。

这篇文章提供了一个 PowerShell 的工作示例,它自动安装 SQL Server Developer,使新团队成员的入职过程更加顺畅。

下载 SQL Server 开发人员 ISO

首先,您需要下载 SQL Server 开发人员 ISO。下载完成后,运行。exe 文件,并选择Download Media选项下载。iso 文件。

我建议把那个。iso 在网络上的共享目录中。这样,您可以创建一个标准的 PowerShell 安装文件供每个人使用。

SQL server GUI 安装程序

我建议浏览 GUI 安装程序并设置您希望团队中的每个人都使用的选项,但不要使用 GUI 安装 SQL Server。这样做的原因是安装程序在你安装的时候会创建一个ConfigurationFile.ini,它会告诉你文件的位置。

ConfigurationFile.ini文件从该目录复制到一个新位置。那将是文件的副本供他人使用。

修改 ConfigurationFile.ini 文件

我们需要修改T2,以便它可以通过命令行工作。

首先,将静音模式开关切换到true并删除 UIMode 条目。

旧的配置文件:

QUIET="False"

; Setup will display progress only, without any user interaction.

QUIETSIMPLE="False"

; Parameter that controls the user interface behavior. Valid values are Normal for the full UI,AutoAdvance for a simplied UI, and EnableUIOnServerCore for bypassing Server Core setup GUI block.

UIMODE="Normal"

; Specify whether SQL Server Setup should discover and include product updates. The valid values are True and False or 1 and 0\. By default SQL Server Setup will include updates that are found. 

更新的配置文件:

QUIET="True"

; Setup will display progress only, without any user interaction.

QUIETSIMPLE="False"

; Parameter that controls the user interface behavior. Valid values are Normal for the full UI,AutoAdvance for a simplied UI, and EnableUIOnServerCore for bypassing Server Core setup GUI block. 

接下来,加上IACCEPTSQLSERVERLICENSETERMS="True"。我将我的添加到文件的顶部:

;SQL Server 2017 Configuration File
[OPTIONS]

IACCEPTSQLSERVERLICENSETERMS="True" 

接下来,我们需要更新当前用户帐户,该帐户在实例上设置为 admin。如您所见,该文件显示了我的当前用户:

; Windows account(s) to provision as SQL Server system administrators.

SQLSYSADMINACCOUNTS="HOME\bob.walker"

; The default is Windows Authentication. Use "SQL" for Mixed Mode Authentication.

SECURITYMODE="SQL" 

这意味着任何人谁使用这个将有我作为管理员。幸运的是,我们正在通过 PowerShell 运行它,所以在运行安装程序之前,我们可以用当前用户替换一些文本。

; Windows account(s) to provision as SQL Server system administrators.

SQLSYSADMINACCOUNTS="##MyUser##"

; The default is Windows Authentication. Use "SQL" for Mixed Mode Authentication.

SECURITYMODE="SQL" 

最后,如果我们看看文件的底部,我们可以看到 TCP 和命名数据管道被禁用:

; Specify 0 to disable or 1 to enable the TCP/IP protocol.

TCPENABLED="0"

; Specify 0 to disable or 1 to enable the Named Pipes protocol.

NPENABLED="0"

; Startup type for Browser Service.

BROWSERSVCSTARTUPTYPE="Automatic" 

在我的开发机器上,我喜欢启用 TCP 和命名管道。这样做让我的生活轻松了很多,尤其是当我试图得到。NET 来连接它。对于我的配置,我选择混合模式身份验证,这意味着我需要添加 SA 密码。默认情况下,安装程序将 SQL Server 设置为仅使用集成安全性。我个人的偏好是为我的开发机器启用这两种模式。如果您选择仅保留集成安全性,则不必添加 SA 密码:

; Specify 0 to disable or 1 to enable the TCP/IP protocol.

TCPENABLED="1"

; Specify 0 to disable or 1 to enable the Named Pipes protocol.

NPENABLED="1"

; Startup type for Browser Service.

BROWSERSVCSTARTUPTYPE="Automatic"

; SQL Password

SAPWD="CHANGE THIS PASSWORD" 

变化真大。要查看完整的示例文件,请访问 GitHub repo,在那里您将看到为这篇博文创建的示例[configuration file . ini]](https://GitHub . com/OctopusSamples/SQL server install/blob/master/configuration file . ini)文件。

PowerShell 脚本

在本例中,我们将从 PowerShell 运行安装程序。PowerShell 让我们能够很好地控制正在发生的事情,并提供了一些有用的内置命令来挂载和卸载 ISO 文件。

我们希望 PowerShell 脚本能够:

  1. 将母版ConfigurationFile.ini复制到临时位置。
  2. 将##MyUser##替换为克隆的ConfigurationFile.ini中的当前用户。
  3. 装载映像。
  4. 运行安装程序。
  5. 捕获安装程序的输出,并将其写入 shell。
  6. 卸载图像。

请注意:PowerShell 脚本将要运行一个安装程序。因此,您必须以管理员身份运行该脚本。

 $isoLocation = ## Put the location here
$pathToConfigurationFile = ## Path to original file here
$copyFileLocation = "C:\Temp\ConfigurationFile.ini"
$errorOutputFile = "C:\Temp\ErrorOutput.txt"
$standardOutputFile = "C:\Temp\StandardOutput.txt"

Write-Host "Copying the ini file."

New-Item "C:\Temp" -ItemType "Directory" -Force
Remove-Item $errorOutputFile -Force
Remove-Item $standardOutputFile -Force
Copy-Item $pathToConfigurationFile $copyFileLocation -Force

Write-Host "Getting the name of the current user to replace in the copy ini file."

$user = "$env:UserDomain\$env:USERNAME"

write-host $user

Write-Host "Replacing the placeholder user name with your username"
$replaceText = (Get-Content -path $copyFileLocation -Raw) -replace "##MyUser##", $user
Set-Content $copyFileLocation $replaceText

Write-Host "Mounting SQL Server Image"
$drive = Mount-DiskImage -ImagePath $isoLocation

Write-Host "Getting Disk drive of the mounted image"
$disks = Get-WmiObject -Class Win32_logicaldisk -Filter "DriveType = '5'"

foreach ($disk in $disks){
 $driveLetter = $disk.DeviceID
}

if ($driveLetter)
{
 Write-Host "Starting the install of SQL Server"
 Start-Process $driveLetter\Setup.exe "/ConfigurationFile=$copyFileLocation" -Wait -RedirectStandardOutput $standardOutputFile -RedirectStandardError $errorOutputFile
}

$standardOutput = Get-Content $standardOutputFile -Delimiter "\r\n"

Write-Host $standardOutput

$errorOutput = Get-Content $errorOutputFile -Delimiter "\r\n"

Write-Host $errorOutput

Write-Host "Dismounting the drive."

Dismount-DiskImage -InputObject $drive

Write-Host "If no red text then SQL Server Successfully Installed!" 

安装后

运行该脚本后,您将拥有 SQL Server Developer Edition 的全新安装。接下来,您需要决定什么将创建您的应用程序将连接到的数据库,以及根据身份验证模式,什么应该创建数据库用户?如果您的应用程序在找不到现有数据库的情况下创建了一个空数据库,您可以跳过下一节。

对于其他人,您的 PowerShell 脚本需要创建空的数据库和用户。根据您使用的数据库部署工具,您可能能够自动创建模式。我不能为每个数据库部署工具提供脚本,但是我可以提供脚本来创建数据库、用户帐户,并将该用户分配给新创建的数据库。您可以在这篇博文的示例 GitHub repo 中找到这些脚本:

解决纷争

安装程序的输出非常冗长。如果出现故障,请准备好滚动输出,找出故障发生的原因。好消息是安装程序非常善于让你知道哪里出错了。

如果出于某种原因,PowerShell 脚本没有输出安装日志,那么您需要找到它。通常在C:\Program Files\Microsoft SQL Server\[Version]\Setup Bootstrap\Log\[DateTimeStamp]\发现。该路径取决于您正在安装的版本以及 Microsoft 是否更改了日志文件的目标。版本是内部版本号。例如,SQL Server 2017 是 SQL Server 的版本 14。

经验教训

现在,您应该有一个完全自动化的 SQL Server 安装供开发人员运行。在结束这篇文章之前,我想分享一些我把这个脚本交给开发人员后学到的经验。

第一个教训是现实的期望有多重要。在很大程度上,脚本第一次运行是正确的;对于新安装的 Windows 来说尤其如此。计算机的年龄和我在运行这个脚本时看到的问题数量之间似乎有直接的关联。一些开发人员已经安装了一个旧版本的 SQL Server,而另一些开发人员安装应用程序时使用了一个奇怪的 D:\驱动器。

这就引出了我的第二课。我想尽可能地帮助遇到问题的开发人员,我花时间研究和修改脚本,以涵盖许多不同的场景。最终,我意识到一些开发人员的机器有如此独特的配置,花费时间和精力更新脚本来解决它是没有意义的。为了说明这一点,我提供了关于选择哪些选项的文档,如果需要,我会和开发人员坐在一起,通过 GUI 向他们演示。谢天谢地,这只是少数开发者的情况。

结论

尽管我们遇到了一些问题,安装还是成功的。我们在几周内就在整个公司推广了这一技术,80%的开发人员在不到 20 分钟的时间内就完成了工作。最终,这是值得的,我们标准化了我们的配置,并提供了一种自动安装 SQL Server Developer 的方法。

在数据库部署自动化管道中使用专用脚本——Octopus Deploy

原文:https://octopus.com/blog/automated-database-deployments-adhoc-scripts

Octopus worker deploying an ad-hoc SQL script illustration

自动化数据库部署在连续交付方面实现了巨大飞跃。我无法相信自动化数据库部署解决了这么多问题。无论是添加新表、修改存储过程还是创建索引。不管是什么,我不再需要确定环境之间的差异。

尽管有这些优势,一个常见的场景不断出现;在数据库服务器上运行即席查询。我见过的最常见的用例是修复数据。通常,当用户做了意想不到的事情时,数据会处于一种奇怪的状态。在某些情况下,根本问题不会被修复(这种情况发生得不够频繁),或者问题在一周左右的时间内不会被修复,但是数据需要立即修复。

当我过去遇到这种情况时,过程是:

  1. 开发人员创建脚本来修复数据。
  2. 他们将脚本发送给 DBA 或拥有更改数据所需权限的人。
  3. 有权运行脚本的人。
  4. 通知开发人员脚本已经运行。

这个过程有很多缺陷。在我职业生涯的某个阶段,我要么是开发人员,要么是运行脚本的人,这不是一个愉快的过程:

  1. 运行脚本的人不是系统专家。大多数情况下,在运行脚本之前,只是粗略地浏览一下。
  2. 拥有必要权限的人可能已经回家了,出去吃午饭了,或者正在开会。该脚本可能几个小时都不会运行。在某些情况下,必须立即修复数据。
  3. 通知开发人员是一个手动过程,这意味着脚本可能已经运行,但通知尚未发送。
  4. 大多数公司不给初级开发人员修改产品的权利。坦率地说,运行脚本的人还有其他更重要的职责。他们可能真的专注于某件事,被打断会打断他们的思路。
  5. 如果请求是通过电子邮件或 slack 完成的,就不会进行审计,电子邮件是文档死亡的地方。

Octopus Deploy 不仅仅可以部署软件。增加了许多新功能,使 Octopus 部署了一个更完整的 DevOps 工具。在这篇文章中,我将带您完成一个自动运行即席查询的过程。

我使用 Octopus Deploy 的原因(除了我在这里工作的事实之外)是因为它可以为这个过程提供以下内容:

  • 审计:Octopus Deploy 可以告诉您谁提出了请求,谁批准了请求,以及这一切是何时发生的。
  • 工件:使用 Octopus Deploy 内置的工件功能,可以存储和捕获运行的确切脚本,但是,如果有人在文件共享之后更改了脚本,就无从得知了。
  • 认可:在某些情况下,让另一双眼睛看剧本是很重要的。Octopus Deploy 可以设置为基于一组标准有条件地批准脚本。
  • 自动化:不再需要手动发送电子邮件。不再需要手动发送确认。不再打开 SSMS 和运行脚本。
  • 可重复:将在所有环境中使用相同的过程来运行脚本。

用例

为了这篇博文的目的。以下是使用案例:

  • 作为一名开发人员,我需要运行一个特别的查询来添加一个索引,看看这是否能解决一个性能问题。如果是,那么将该索引添加到数据库定义中,并将其推送到所有环境中。
  • 作为一名 DBA,我需要运行一个特殊查询来创建一个 SQL 登录。
  • 作为一名支持工程师,我需要运行一个特别的查询来授予开发人员选择权限。
  • 作为一名业务分析师,我需要为用户解决一个数据问题。

要求

考虑到用例,以下是流程的要求:

  • 章鱼展开。
  • 没有源代码管理。许多 DBA、支持工程师和业务分析师不熟悉源代码控制工具。
  • 自动化。当脚本准备好时,它们应该在五分钟内运行,而不必填写表格或通知任何人。
  • 对脚本的分析,如果脚本包含某些关键字,那么人应该在运行它之前检查脚本。
  • 在任何环境下工作。我们希望鼓励人们在任何环境下运行这个。甚至是戴夫。

设置

触须

我们的数据库部署文档建议您在 Octopus Deploy 和数据库服务器之间的跳转框上安装触角。当使用集成安全性时,这些触角在有权处理部署的服务帐户下运行。这些触角将处理正常部署。

您有几个选项来设置临时流程和权限:

  1. 继续使用部署触角,但赋予他们执行额外任务的提升权限。
  2. 创建一组具有提升权限的新服务帐户,并为这些新服务帐户创建新的触角。
  3. 选项 1 和选项 2 的组合。创建两条管道。一个用于数据修复,另一个用于其他更改。数据修复通过常规部署目标运行,但是其他更改通过一组新的部署目标运行,这些目标具有新的服务帐户。

生命周期

这个过程允许人们在生产中直接运行脚本。使用默认的开发生命周期来测试预生产到生产没有太大意义。创建新的生命周期,允许部署到任何环境。我称我的脚本生命周期为:

您可以通过创建一个阶段并将所有环境添加到该阶段来实现这一点:

项目和流程

对于这个过程,我创建了许多步骤模板。我不想把它们提交给社区图书馆,因为它们不够通用,但是你可以在我们的 GitHub 示例库中找到它们。

摄取脚本

我将为这个用例编写一个数据库脚本:

作为一名业务分析师,我需要为一名用户解决一个数据问题。

我想到了几个问题:

  1. 问:什么环境?答:生产。
  2. 问:什么 SQL 服务器?答:127.0.0.1。
  3. 问:SQL Server 上的什么数据库?答:RandomQuotes_Dev。
  4. 问:谁在提交剧本?鲍勃·沃克。

好了,我们知道了答案,我们如何把这些从我们的大脑中释放到章鱼的大脑中呢?为此,我将使用一个名为元数据的 YAML 文件,其中包含所有这些信息:

---
DatabaseName: RandomQuotes_Dev
Server: 127.0.0.1
Environment: Dev
SubmittedBy: Bob.Walker@octopus.com
... 

下一个问题是如何将 YAML 文件和 SQL 脚本发送到 Octopus Deploy 来运行?为了使提交脚本的人尽可能容易,我将使用一个热文件夹。我编写了一个 PowerShell 脚本,它将:

  1. 在常用文件夹中查找任何新目录。
  2. 使用 Octo.exe 打包文件夹。
  3. 将包推至 Octopus Deploy。
  4. 创建新版本。
  5. 使用 MetaData.yaml 文件确定要部署到哪个环境。
  6. 将文件夹移动到已处理的位置,以便脚本不会再次运行。

我可以设置一个在服务器上运行的计划任务。但是这个任务没有真正的可视性。如果它开始失败,我不会知道它失败了,直到我 RDP 到服务器上。

我没有经历那个噩梦,而是在 Octopus Deploy 中建立了一个新项目,名为“即席查询构建数据库包”该过程只有一个步骤,即运行 PowerShell 脚本来构建数据库包。记下生命周期,它只运行在一个虚拟环境中,我称之为SpinUp:

【T2

它有一个触发器,每五分钟创建一个新版本,并运行以下流程:

在事件中,我想扩展这个过程以支持其他类型的脚本,我将它作为一个步骤模板:

眼尖的读者会看到参数Octopus项目。这是运行脚本的项目。

运行脚本

为了满足上述要求,我希望该流程执行以下操作:

  1. 将软件包下载到跳转框中。
  2. 获取包中的所有文件,并将它们作为工件添加(如果需要审查的话)。
  3. 对脚本执行一些基本的分析。如果任何脚本没有使用事务,或者使用关键字 DropDelete ,那么我想触发一个手动干预。
  4. 需要手动干预时发出通知。我喜欢的工具是 slack。
  5. 运行脚本。
  6. 如果脚本失败,发送失败通知。
  7. 如果脚本成功,发送成功通知。

【T2

下载软件包的步骤非常简单。将软件包下载到服务器。不要运行任何配置转换。不要替换任何变量。只需部署软件包:

“从要检查的包中获取脚本”是一个步骤模板,它执行以下操作:

  1. 读取 YAML 文件并设置输出参数。
  2. 将包中的所有文件作为工件添加。
  3. 对 SQL 文件执行一些基本的分析。
  4. 如果分析失败,设置一个输出变量ManualInterventionRequired

这都是在一个步骤模板中完成的。唯一需要的参数是下载包的步骤:

Octopus Deploy 输出参数的格式可能很难记住。我知道我会输错一些东西,所以与其这样做,我使用变量。这样,如果我真的要改变什么,我只需要改变一个地方:

当我通知某人时,我可以很容易地包含这些信息。另外,请注意,该步骤将基于ManualInterventionRequired输出变量运行:

【T2

人工干预也是如此。运行条件基于ManualInterventionRequired输出变量:

运行 SQL 脚本步骤将遍历所有 SQL 文件并运行它们。同样,为了使它更容易,我使用了一个步骤模板。该流程使用了invoke-sqlcmd,它将捕获输出并添加任务历史:

假设一切顺利,成功通知可以发出:

否则,失败通知可能会发出:

流程演示

我准备了一个可以运行脚本:

MetaData.yaml 文件将脚本设置为在 Dev:

剧本本身没什么特别的。我不打算用一个事务来表明流程会选择它并强制进行手动干预:

我已将该文件夹复制到常用文件夹中:

章鱼拿起那个文件夹:

我现在看到 demo 文件夹已经移动到 processed 文件夹中。我在上面贴了一个时间戳,这样我就能确切地知道那个文件夹是什么时候被处理的:

查看运行脚本的项目,我可以看到已经创建了一个新的版本,并且有一个手动干预正在等待我:

我可以检查松弛通道,并看到批准消息已发送:

进入发布,我可以看到工件已经生成。如果我愿意,我可以下载它们并查看将要运行的确切脚本。当我查看审批详细信息时,我可以看到该消息与时差通知相同:

批准部署后,将运行脚本并捕获输出:

由于脚本成功运行,成功通知被发送到 slack:

常见问题解答

我如何阻止某人向开发环境提交脚本,但允许它用于生产 SQL Server?

使用集成安全性时,每个环境都要有一个触手。该触手只能访问其环境中的 SQL 服务器。使用 SQL 身份验证时,每个环境有单独的用户和密码。无论哪种情况,脚本都将失败,因为用来登录 SQL Server 的用户将无法登录。

如果我想让每个剧本在进入前期制作和制作阶段时都接受审查,该怎么办?

将手动干预步骤更改为始终运行。此外,将环境更改为生产前和生产环境。有条件批准是为了仅在满足某些条件时才要求批准。事实上,从一开始,我建议所有发送到前期制作和生产的脚本都要经过人工批准。当在流程中建立了信任后,就应该引入有条件的批准了。

这看起来有点过了。你不能在 Octopus Deploy 中使用提示变量吗?

绝对的!我有另一个项目来做这个。问题是,谁来提交这些脚本?他们应该有权利创建一个版本并投入生产吗?每个人都可以使用 Octopus Deploy 吗?对于我的用例,我的答案是否定的。我在这个过程中的主要目标是消除尽可能多的手动步骤。使用提示变量手动创建版本增加了太多的手动步骤。

结论

我第一个承认这个过程远非完美。这并不适用于每家公司。这篇文章的目的是提供一个流程的例子,你可以修改这个流程以用于你的公司。

下次再见,愉快的部署!


数据库部署自动化系列文章:

数据库部署自动化方法- Octopus Deploy

原文:https://octopus.com/blog/automated-database-deployments-iteration-zero

Database deployment automation approaches

希望在阅读完之后,为什么要考虑数据库部署自动化?您已经准备好投入数据库部署自动化。根据您的公司,自动化您的数据库部署可能是一个很大的变化,它可能会引起摩擦。摩擦是变化的敌人;摩擦力越高,采用速度越慢。这篇文章的目的是帮助消除这种摩擦。

我用 Microsoft SQL Server 演示了这些原则,但是这些原则也适用于您选择的数据库技术。

这篇文章讨论了以下内容:

数据库部署自动化方法

部署数据库可能非常复杂,有多种方法。Octopus Deploy 集成了多种第三方工具和方法,但这种灵活性意味着有很多选择,当您评估第三方工具时,您会发现它们以两种方式之一进行部署,每种方式都有其优缺点。

排名第一的基于状态的数据库部署方法

使用基于状态或模型驱动的数据库部署方法,定义数据库的期望状态,并将状态保存到源代码控制中。在部署期间,该工具将所需状态与部署目标进行比较,并生成增量脚本。将为每个环境执行此过程。

数据库所需的状态作为文件存储在源代码管理中。根据您使用的工具,具有所需状态的文件可以是一系列创建脚本、XML 文件或完全不同的东西。重要的是要知道该工具将负责更新和维护这些文件。

基于州的优势

基于状态方法的工具通常与您的 IDE 集成在一起。例如,Redgate 的工具与 SQL Server Management Studio 集成,微软的 SSDT 工具与 Visual Studio 集成。使用 IDE 对模式进行更改,然后由 IDE 的插件接管。它运行一个比较来确定变更和当前在源代码控制中的内容之间的差异。然后,它对文件系统上的必要脚本进行更改。

所有文件系统交互都发生在幕后。该工具跟踪所有的更改,这使您可以专注于数据库的更改和测试。在您测试了这些更改之后,您可以使用该工具来更新源代码控制中的文件。

最后,一些工具允许您将一个表标记为静态数据,数据本身被签入到源代码控制中。在部署期间,该工具将检查目标表中的数据,如果目标表缺少数据或数据不正确,增量脚本将包括数据更改 T-SQL 语句。

基于州的骗局

在每个环境的部署过程中会生成一个唯一的增量脚本。这是因为一个变更可能应用于一个环境(开发),而不是一个更高的环境(生产前或生产)。这使得工具变得更加复杂,并且每隔一段时间,该工具将生成一个包含意外更改的增量脚本,尤其是在权限设置不正确的情况下。

工具想要控制关于数据库的一切,从表到模式再到用户。您必须配置该工具以忽略数据库的某些部分。

尽管工具很聪明,但它很难处理更复杂的变化。例如,当将一个列从一个表移动到另一个表时,该工具不知道这是您的意图,它将从旧表中删除该列,并在新表中创建一个新的空列。该工具通常包括某种迁移脚本功能,您可以在其中编写自己的迁移脚本,但是迁移脚本有自己的规则,您必须遵守。

这种缺乏控制有时会成为一种负担。您可能最终会创建一个与该工具一起工作的定制流程。例如,该工具可能不支持后期部署脚本,为了获得这一点,您必须创建一个可以打包并发送给 Octopus 的后期部署文件夹。然后,您必须在 Octopus 中更新您的流程,以查找文件夹并运行它找到的任何脚本。它起作用了,但是现在你要负责维护这个过程。

#2 数据库迁移脚本方法

数据库迁移脚本方法是手写所有必要的增量脚本。这也称为变更驱动或基于脚本。这些脚本被签入源代码控制。在部署期间,该工具将查看哪些迁移脚本尚未在目标数据库上运行,并以特定的顺序运行它们。

迁移脚本优点

使用迁移脚本方法,您可以完全控制所有脚本。当部署变更时,您确切地知道将要运行什么脚本。复杂的变化更容易处理;您只需要编写脚本并将其保存到源代码控制中。一些迁移框架允许您编写代码来进行迁移,以便更容易地实现更复杂的更改。此外,从部署中排除项目要容易得多。只是不要包含您想要排除的项目的脚本。

迁移脚本缺点

基于状态的方法确保整个目标数据库与期望的状态相匹配。基于脚本的方法则不然。可以在进程之外向目标数据库添加一个新表。每个有权更改数据库的人都必须参与并使用这个过程,因为一两个流氓开发人员可能会造成大混乱。

查看特定对象(如表或存储过程)的历史要困难得多。您必须进行搜索以找到对象发生更改的所有文件,而不是转到单个文件并查看历史记录。根据表更改的数量,可能很容易错过关键的更改。

最后,很多开发人员都不是 SQL 开发专家。他们使用 SQL Server Management Studio 用户界面来创建表和索引,他们不知道如何编写大量手工更改。记住 T-SQL 语法需要大量的练习。如果该工具允许您为更复杂的更改编写代码,那么理解语法和规则还有一条学习曲线。

选择一种方法

正确的方法是非常主观的。

当下列任一情况适用时,基于状态的方法效果最佳:

  • 你正处于一个项目的早期阶段,数据库有很多变动。
  • 有多个人/团队在更改数据库。
  • 您的公司首次涉足自动化数据库部署。
  • 你的代码库由成熟的数据库组成,你不期望有太多的变化。
  • 您希望强制开发人员自动遵循该过程(如果对目标数据库进行了更改,并且没有签入,该更改将被删除;对于某人来说,只需要发生一次就可以学会)。
  • 大多数将进行更改的开发人员缺乏制作复杂 T-SQL 语句的经验。

迁移脚本方法在以下情况下最有效:

  • 每个做出改变的人都足够自律,总是遵循这个过程。
  • 进行更改的人有进行复杂数据库更改的经验。
  • 你会不断碰到基于州的方法所强加的限制。
  • 每个人都想尽可能地控制过程。

如果您最初从基于状态的方法开始,几年后,决定转向变更驱动的方法,不要感到惊讶。当你选择一个供应商,红门,微软等。,确保他们提供的软件套件支持这两种方法。

转向专用数据库

无论您选择哪种工具,我都建议将开发人员转移到专用数据库。专用数据库的典型方法是在开发人员的笔记本电脑上安装 SQL Server,这提供了以下优势:

  1. 开发人员可以尝试有风险的改变,而不必担心会影响到其他人。如果他们打碎了什么东西,只有一个人会受到影响。
  2. 支持分支。对于共享模型,只有一个数据库。如果在没有相应代码更改的情况下对数据库进行了重大更改,那么每个人都将停止工作。现在,所有的更改都可以在一个分支上进行并签入,同时进行部署。
  3. 开发人员在准备好的时候应用新的变更。对于共享模型,突破性的变更要求开发人员停止他们正在做的事情,并应用最新的代码变更。有了专用的数据库,他们可以专注于当前的任务,并在准备好使用它的时候进行更改。

共享模式与此相反。当使用共享模型时,您不会获得分支的灵活性;每个人都必须更新他们的代码。不要误解我的意思,共享模型也可以工作,但是它最适合使用静态数据库模式的少数开发人员。随着团队向外扩展,共享模型很快瓦解。

沟通

自动化数据库部署引入了一个有趣的挑战。在自动化之前,开发人员可以对共享数据库进行更改,每个人都会立即看到。这使得更难踩到对方的脚趾。但是对于自动化数据库部署和专用数据库,风险要高得多。开发人员 A 可以在他们的分支中对一个表进行更改,开发人员 B 可以在他们的分支中对同一个表进行更改,这两种更改可能会相互冲突。这意味着需要有一种机制让其他人知道正在进行什么样的更改。它可以是一些简单的事情,比如一个松散的信息或者一次日常谈话中的讨论。重要的是确保每个人都在同一页上。

建立信任

过去,DBA 是在生产环境中运行脚本的人,因为他们有权限。现在,一个自动化的过程将完成这项工作,这可能真的很可怕。如果出了问题,数据丢失了,就很难恢复。每个人都必须信任流程和工具。根据我的经验,建立信任的最佳方式是使用 Octopus Deploy 工件并在 SQL Server 上设置权限。

章鱼部署神器

使用 Octopus Deploy 工件,您可以创建一个包含所有即将在触手上运行的脚本的文件,并将它上传到服务器。DBA 可以在数据库上运行脚本之前批准它。

在某些情况下,第三方提供的步骤模板直接内置了工件创建。例如,下面是使用 Redgate 部署工具的过程:

【T2

在部署过程中,它会自动创建工件:

工件通过不允许批准过程来帮助建立信任,但是它也提供了审计历史。三个月后,我可以回到这个部署,查看数据库发生了什么变化。

许可

在实现自动化数据库部署时,我听到的一个常见问题是,“我们如何防止有人插入脚本来授予自己 sysadmin 权限?”使用工件是一个好的开始,但并不能完全解决问题。如果 DBA 或审批人员忙得不可开交,他们很容易错过工件中特定的 SQL 语句。防止这种情况发生的最好方法是限制执行部署的帐户的权限。我们的文档提供了几个例子,从限制性最小到限制性最大。请记住,这些只是建议。我鼓励与你的团队交流,以确定你对什么满意。

触手安装在哪里

您不希望将触手直接安装在 SQL Server 上。SQL Server 通常是一个集群或高可用性组,触角将尝试同时将更改应用到所有节点。您不希望让部署 Windows 服务或 IIS web 应用程序的触角处理数据库部署。那些触角可能在非军事区。触手应该在特定的服务帐户下运行,并具有执行部署所需的权限。大多数工具利用端口 1433,并简单地运行一系列 T-SQL 脚本。触手可以安装在任何机器上,只要它连接到数据库。出于这些原因,我建议您使用位于 Octopus Deploy 和 SQL Server 之间的跳转框。

请参考我们的文档了解更多信息。

结论

乍一看,这似乎需要做很多准备工作,但重要的是要记住这些是指导方针。不要花几周的时间讨论和辩论,拿出一个初步的计划,迭代。在这个过程的开始,我们花了大约两天时间讨论和研究我们的初步计划。

我对此的建议是:

  1. 讨论上述项目,并提出一个初步计划。
  2. 建立一个试验团队来迭代任何问题。
  3. 等待试点团队成功部署到生产环境中。
  4. 一次一个地向其他项目推广。如果每次你把它推广到一个新项目时,你遇到一些新的东西,需要做一些改变,不要感到惊讶。
  5. 不要害怕接触和询问专家。我们可以提供一些初步的指导,但有时,你会需要更多的帮助。在这种情况下,有几家公司提供咨询服务,可以提供帮助。

数据库部署自动化系列文章:

使用基于状态的 Redgate SQL 变更自动化- Octopus Deploy 实现数据库部署自动化

原文:https://octopus.com/blog/automated-database-deployments-redgate-sql-change-automation-state-based

Database deployment automation using state-based Redgate SQL Change Automation

我之前的博客文章讨论了为什么您应该考虑自动化数据库部署入门技巧

本文将使用基于状态的方法Redgate 的 SQL 变更自动化建立一个数据库部署自动化管道。我选择这个工具是因为它易于设置,可以与 SSMS 集成,而且我已经有了一个演示设置。我也偏向【Redgate 的工装。

在本文结束时,您将拥有一个可以演示的概念证明。

准备工作

对于这个演示,您需要一个正在运行的 SQL Server 实例、一个 Octopus Deploy 实例和一个 CI 服务器。我建议使用一个开发环境或您的本地机器来进行概念验证。

你需要以下工具。给出的例子使用了团队城市和 VSTS/TFS,但是即使你使用不同的工具,所有 CI 工具的核心概念和用户界面都是非常相似的。

  • 八达通部署:
  • Redgate SQL 工具带
  • 构建服务器/持续集成(CI)工具(选择一项):
  • SQL Server Management Studio (SSMS):
  • SQL Server:

安装软件

如果您在安装这些工具时遇到问题,请访问供应商网站寻求帮助。如果您在安装 Octopus Deploy 时需要任何帮助,请从我们的文档开始,或者联系支持

开发者工作站

这是您将用来进行模式更改并将它们签入源代码控制的机器。当你安装 Redgate 的 SQL Tool-belt 时,会提示你安装相当多的软件。您只需要安装以下软件:

  • SQL 源代码管理。
  • SQL 提示符(这不是必需的,但它使事情变得容易得多)。
  • SSMS 集成包。

Octopus Deploy 和 Redgate 都有主要构建服务器/持续集成工具的插件:

  • 詹金斯:
  • 团队城市:
  • VSTS/TFS:
  • 竹子:

部署目标或数据库工作者

在 SQL Server 上安装 Octopus 触手是一个大禁忌。我们的文档会更详细地解释为什么。

首选的解决方案是在 Octopus Deploy 和 SQL Server 之间配置一个跳转框。Octopus 为此支持两个选项:

  • 部署目标
  • 数据库工作者

在这篇文章中,我将添加一个部署目标,但是我想提到 workers 也是一个不错的选择。它们对于进行大量数据库部署的团队特别有用。

Workers 使您能够将部署工作转移到在池中运行的其他机器上,数据库部署是一个常见的用例。您可以创建一个专门的工作人员池,供多个项目和团队用于数据库部署。

更多信息见我们的文档

出于安全考虑,我建议以特定用户帐户运行触手/Worker。这样,您可以利用集成的安全性。您可以配置活动目录或者使用 SQL 用户来代替。

对于跳线盒,您需要安装以下项目:

  • SQL 变更自动化 PowerShell 3.0。
  • SQL 变更自动化。

示例项目

对于这个演练,我修改了 RandomQuotes 项目。这个示例的源代码可以在这个 GitHub repo 中找到。派生存储库,以便在阅读本文时可以进行修改。

配置 CI/CD 管道

您需要的一切都已经签入到源代码控制中。我们需要做的就是构建它并将其推送到 SQL Server。

Octopus 部署配置

您需要从 Redgate 到创建数据库版本部署数据库版本的步骤模板。当您浏览步骤模板时,您可能会注意到步骤模板直接从包中部署。SQL 变更自动化的基于状态的功能通过比较存储在 NuGet 包中的数据库的状态和目标数据库来工作。每次运行时,它都会创建一组新的增量脚本来应用。推荐的流程是:

  1. 将数据库包下载到跳转框中。
  2. 通过将跳转框上的包与 SQL Server 上的数据库进行比较来创建增量脚本。
  3. 查看 delta 脚本(这在 dev 中可以跳过)。
  4. 使用跳转框上的触手在 SQL Server 上运行脚本。

使用步骤模板从包中部署会阻止查看脚本的能力。

这是我为部署数据库而组织的流程。

该过程执行以下操作:

  • 数据库的主要 SQL 用户。
  • 数据库。
  • 将 SQL 用户添加到数据库中。
  • 将用户添加到角色中。

如果您希望您的流程这样做,您可以从 Octopus 社区步骤模板库下载这些步骤模板。

如果这是自动化数据库部署之旅的开始,您不必添加所有这些功能。上面截图中需要的主要步骤是:

让我们逐一介绍一下。下载包的步骤非常简单,除了选择包名之外没有自定义设置:

Redgate - Create 数据库发布步骤更有趣一些。导出路径是增量脚本将被导出到的位置。这必须是 Octopus Deploy 触手文件夹之外的目录,因为Redgate-Deploy from Database Release步骤需要访问该路径,而触手文件夹对于每个步骤都是不同的:

我喜欢使用项目变量:

该变量的完整值为:

 C:\RedGate\#{Octopus.Project.Name}\#{Octopus.Release.Number}\Database\Export 

该屏幕上的其他建议:

  • 我已经提供了用户名和密码。我建议使用集成安全性,并让触手作为一个特定的服务帐户运行。我的测试机器上没有配置 Active Directory,所以我在这个演示中使用了 SQL 用户。
  • 查看一下默认 SQL 比较选项,确保它们符合您的需求。如果没有,您需要在SQL Compare Options (optional)变量中提供您想要的。你可以在这里查看文档。如果您决定使用定制选项,我建议在库变量集中创建一个变量,这样这些选项可以在许多项目中共享。
  • 如果您希望限制部署过程可以更改的内容,请使用自定义过滤器。我写了一篇关于如何做到这一点的博文。我个人倾向于过滤掉所有用户,让 DBA 管理他们。更好的是,让章鱼来管理它们,因为它可以处理环境差异。

下一步是批准数据库发布。我建议创建一个定制团队来负责此事,但我更喜欢在开发和 QA 中跳过这一步:

创建数据库发布步骤利用了 Octopus Deploy 中内置的工件功能。这允许批准者下载文件并检查它们:

最后一步是部署数据库版本。这一步将 delta 脚本放在导出数据路径中,并在目标服务器上运行它,这就是为什么我建议将导出路径放在一个变量中:

这就是 Octopus 部署配置。现在是时候转移到构建服务器了。

构建服务器配置

在这篇博文中,我使用了 VSTS/TFS 和团队城市。至少,构建应该做到以下几点:

  1. 使用 Redgate 插件构建一个包含数据库状态的 NuGet 包。
  2. 使用 Octopus Deploy 插件将包推送到 Octopus Deploy。
  3. 为刚刚使用 Octopus Deploy 插件推出的包创建一个发布版本。
  4. 使用 Octopus Deploy 插件部署该版本。

VSTS / TFS 大楼

在 VSTS/TFS,构建和部署数据库只需三个步骤:

第一步将从源代码控制构建数据库包。突出显示的项目是您需要更改的项目。子文件夹路径变量是相对的。我正在使用一个示例 Git repo,这就是为什么redgatesqlchangeautomationstate based文件夹位于路径:

【T2

push package to Octopus 步骤要求您知道上一步生成的工件的完整路径。我不能 100%确定不经过反复试验你怎么会知道:

这是全部价值,如果你想复制的话:

 $(Build.Repository.Localpath)\RandomQuotes-SQLChangeAutomation.1.0.$(Build.BuildNumber).nupkg 

必须在 VSTS/TFS 配置 Octopus Deploy 服务器。你可以在我们的文档中看到如何操作。

最后一步是创建一个发布,并将其部署到 dev。用 Octopus Deploy 连接 VSTS/TFS 后,您可以读取所有项目名称。您还可以配置这个步骤,将发布部署到 dev。单击显示部署进度将停止构建并强制等待 Octopus 完成:

团队城市

团队城市的设置与 VSTS/TFS 的设置非常相似。只需要三个步骤:

第一步是构建数据库包步骤,它有类似于 VSTS/TFS 的选项。您需要输入文件夹以及包的名称:

您必须在高级选项中输入一个包版本,否则您将从 Redgate 工具中得到一个关于无效包版本的错误:

发布包步骤需要填充所有三个选项。默认情况下,Redgate 工具将在根工作目录中创建 NuGet 包:

最后一步是创建和部署版本。提供项目名称、版本号和您要部署到的环境:

查看 CI/CD 管道的运行情况

现在是时候看看这一切是如何运作的了。对于这个演示,我创建了一个新的数据库,RandomQuotes _ BlogPost _ Dev:

如您所见,我没有任何同名的数据库。我将该 SQL Server 用作自动化部署的测试平台:

让我们快速看一下存储在源代码控制中的表:

如果我们打开其中一个文件,我们可以看到由 Redgate 的 SQL 源代码控制生成的创建脚本:

启动一个构建,让我们看看整个管道运行情况。构建看起来很成功:

毫无疑问,在 Octopus Deploy 中部署是成功的。VSTS/TFS 版本被设置为等待 Octopus Deploy 完成数据库部署。如果部署失败,构建也会失败:

回到 SSMS,我们现在可以看到数据库和表已经创建:

更改数据库模式

这适用于现有的项目,但是让我们对数据库模式做一个小的更改,并测试这个过程。这涉及到更多的设置:

  1. 将分叉的回购克隆到本地机器上。
  2. 打开 SSMS,在你的本地机器上创建一个随机报价数据库。
  3. 在 SSMS,将受源代码管理的数据库绑定到新创建的数据库。你可以在文档中阅读如何操作。

将数据库链接到源代码管理时,需要提供存储源代码管理的文件夹的完整路径。我将所有代码存储在一个名为 C:\Code.git 的文件夹中。

C:\Code.git\AutomatedDatabaseDeploymentsSamples\RedGateSqlChangeAutomationStateBased\db\src\ 

现在我们可以对数据库进行更改了。对于这个测试,让我们添加一个将返回值的存储过程:

现在我们可以将更改提交到源代码控制:

假设 CI/CD 管道被设置为在提交时触发,您应该看到新的存储过程出现在 dev 中。

结论

数据库部署自动化确实需要一些准备工作,但是付出的努力是值得的。光是审计就值得了。有了这个工具,我现在可以看到谁做了更改,何时做了更改,以及更改何时投入生产。过去,它保存在另一个位置,有 50%的更新机会。

当您开始这一旅程时,我的建议是将手动验证步骤添加到所有环境中,直到建立信任为止。这将确保您不会意外地签入一个会吹走团队一半数据库变更的变更。

下次再见,愉快的部署!


数据库部署自动化系列文章:

为什么要考虑数据库部署自动化?-章鱼部署

原文:https://octopus.com/blog/automated-database-deployments-series-kick-off

Why consider database deployment automation?

这是关于数据库部署自动化系列文章的第一篇。

对我来说,数据库是任何部署中最伤脑筋的部分。部署代码的压力要小得多。如果有什么不对劲,代码可以回滚。当它进入生产阶段时,如果它是在开发、QA 和预生产中测试过的相同代码,应该不会有任何意外。

数据库没有那么灵活。假设数据库脚本中有一个错误,所有用户的名字都被删除了。没有一个好的方法来回滚。备份可以恢复,但是备份是在什么时候进行的,自备份以来系统中是否有任何用户?如果恢复备份,哪些数据将会丢失?

手动数据库部署的问题

在 Octopus Deploy 工作之前,我是美国一家大型金融机构贷款发放系统的首席开发人员。在任何时候,都有数亿美元的贷款在外逃。这些都是大额贷款,需要许多个人来处理。每个人可能会在贷款上花费 15 分钟到 1 小时不等的时间。财务人员可能会花半个小时输入客户的财务状况,而承销商可能会花一个小时研究客户,并在贷款上添加注释和条件。不用说,恢复数据库备份是最后的手段。告诉人们他们必须重新输入信息,这种情况只会发生很多次,然后他们才会想要找到我。

我们有自动化的代码部署,但是数据库增量脚本是由数据库开发人员在部署之前手工创建的。对他们来说,花上一两个工作日的时间来整理剧本是很平常的事情。手动创建也意味着很少会发生测试。当脚本在生产中运行时,它很可能是第一次运行。我们还使用了 Redgate 的 SQL Compare 等工具,并在可能的情况下对恢复的备份进行了测试,但这只能做这么多。

由于存在风险,部署只能在下班后进行,这意味着晚上或周末,但这确实意味着我们可以为回滚进行备份。除了进行部署的开发团队之外,我们还有一名在线 DBA 和操作人员。操作人员必须等待 DBA 运行所有脚本,这可能需要一秒钟或二十分钟才能完成。

时间安排上的风险和困难意味着我们只能每个季度部署一次主要版本,在此期间进行小的 bug 修复。在一个主要版本之后,我们通常会发布几个错误修复版本,通常是因为错过了一些模式更改。每季度部署一次意味着一次要做很多改变。变更的验证需要很长时间,通常每个版本都需要两三个小时。

数据库模式更改被遗漏,因为数据库没有真实的来源。索引可能存在于生产前,但不存在于 QA 中,那么哪种环境是正确的呢?那个索引是什么时候添加的?谁加的?更糟糕的是,数据库中有将近 6,000 个对象(大部分是 CRUD 存储过程)。数据库开发人员不得不求助于手动跟踪所有的更改。80%的时间是数据库开发人员进行更改,另外 20%是开发人员进行更改。如果数据库开发人员那天不在,我们试图记住通过电子邮件将更改发送给他们,但是想想您在上一个季度对代码所做的所有更改。你记得他们所有人吗?

简而言之,除了应用程序中最关键的部分,我们已经实现了所有的自动化。完全相同的代码在环境中移动时被测试了多次。为每个环境手动创建了唯一的数据库增量脚本。数据库模式没有真实的来源,数据库管理员正在拔头发,试图让一切保持运行。

总结一下我们面临的挑战:

  • 环境都不一样。
  • 为每个环境创建了自定义脚本。
  • 数据库对象存在于一个环境中,而不存在于另一个环境中。
  • 部署耗时数小时。
  • Big bang 多个 bug 修复版本的季度发布。
  • 手动跟踪更改。

数据库变更控制是狂野的西部。

数据库部署自动化

有些东西必须放弃。数据库更改必须进入源代码控制,这些脚本需要打包并在部署期间自动运行。经过大量的讨论、研究和测试,我们找到了一个工具。工具本身并不重要。重要的是,我们自动化了数据库部署。

这种影响几乎立刻就能被察觉。

将数据库放在源代码控制中可以让我们看到什么时候有人做了更改。我们可以将它与故事联系起来,我们知道为什么要进行更改,我们设置它以便删除不在源代码控制中的更改。如果一个索引在 QA 中,但不在源代码控制中,我们就删除它。这有点苛刻,但它保证了数据库模式匹配源代码控制中的内容。

由于随机视图或存储过程丢失而导致的紧急修复几乎降到了零。我们在 Octopus Deploy 中设置了一个手动干预步骤,这允许我们在部署之前查看和批准数据库增量脚本。每个人真正喜欢手动干预步骤的地方是,如果 delta 脚本中出现意外的数据库更改,可以取消部署。这有助于每个人、开发人员、QA 和 DBA 信任这个过程。

对部署的信心开始增加。很快,我们每月进行一次部署。然后一周一次。一旦通过 QA 和业务所有者的验证,功能就可以交付给用户。可以报告一个 bug,一旦通过验证,修复程序就可以交给用户了。我们发布得如此频繁,以至于每次发布的更改数量都显著减少,部署时间从两到三个小时减少到五到二十分钟。

将这一切都放在 Octopus Deploy 中还有一个好处,那就是让数据库管理员和运营团队的夜晚和周末时光重新回来。现在,他们可以安排部署,并且只在出现问题时才需要上线。

博客系列

这篇文章是我带领您建立数据库生命周期管理(DLM)和数据库部署自动化的系列文章的第一篇。本系列的目标是为您提供一些使用各种数据库部署工具的真实示例。除此之外,我们将讨论一些你会遇到的常见陷阱。


数据库部署自动化系列文章:

还是手动部署?你错过了什么-章鱼部署

原文:https://octopus.com/blog/automated-deployment-means-more-frequent-deployment

当决定是否投资自动化一项任务时,我们通常会权衡设置自动化所需的时间和自动化预期节省的时间。但自动化带来了另一个好处:自动化减少了痛苦,这让你更有可能更频繁地执行任务。

作为一个基本示例,假设您手动部署到生产环境中,平均需要大约三个小时。您目前大约一个月部署一次产品。如果自动化部署将这一时间缩短到 10 分钟,那么您一个月只节省了 2 小时 50 分钟。如果您需要几天时间来实现部署的完全自动化,那么需要几个月的时间才能看到投资回报。值得吗?

但是故事还没完。如果您的部署现在只需 10 分钟和几次点击(而不是管理多个远程桌面连接,等等。),并且您的部署是可靠的,那么您就不会不愿意部署到生产环境中。您甚至可以每周开始部署到生产环境中。甚至可能一周多次。这意味着您可以更快地将功能交付给用户。通过减少周期时间,您改善了反馈回路,并减少了您的软件库存

看到一个已建立的项目,很容易说“自动化这个部署将花费比我们节省的时间更长的时间”。但是不自动化的机会成本是多少?

演练:通过 TFS 预览版、MyGet 和 Octopus Deploy - Octopus Deploy 自动部署到 Amazon EC2

原文:https://octopus.com/blog/automated-deployment-with-tfspreview-octopack-myget

TFS 预览版是微软新推出的云托管 Team Foundation Server 即服务解决方案(TFSaaS?).您将获得一个 Team Foundation Server 实例,带有团队构建(持续集成)和工作项跟踪。

微软把 TFS 不仅仅作为一个源代码控制系统来推销,而是作为一个应用程序生命周期管理解决方案。这张来自 TFS 预览主页的图片很好地概括了这一点:

TFS and ALM

右边的箭头代表不同渠道的“部署”——毕竟,“应用程序生命周期管理”确实应该包括某种部署能力。然而,问题是在 TFS 预览版的情况下,箭头实际上只是意味着“自动部署到 Azure”。这绝对是一个很酷的特性,但是如果你的目标服务器没有运行在 Azure 上呢?如果您使用的是 Amazon EC2,或者您的服务需要在数据中心的一些本地服务器上运行,该怎么办?

不要担心,Octopus 在这里将“部署”放在应用程序生命周期管理中。我们将扩展图像,如下所示:

TFS and ALM

在这篇文章中,我将采用 ASP.NET 网站,并让它在 TFS 预览下建设。我需要做几件事:

  • 创建一个 TFS 预览账户
  • 创建一个 MyGet 账户。我们将使用 MyGet 来托管我们的 NuGet 包。
  • 创建 ASP.NET 站点,并使用 OctoPack 对其进行打包
  • 建立一个团队构建来构建包并将它们发布到 MyGet
  • 配置 Octopus 使用 MyGet 并部署应用程序

虽然我将在本例中使用 TFS 预览版,但这些步骤(或类似步骤)中的大部分也适用于内部 TFS 安装。

步骤 1:创建项目

访问 TFS 预览版主页,注册账号。这是免费的,而且只需要几分钟,挺好的。完成后,您将被带到一个帐户控制面板:

TFS Preview home

我将创建一个新的团队项目:

Creating a TFS Preview project

创建项目大约需要一分钟,之后您将看到项目主页:

TFS Preview project dashboard

接下来,我将把 TFS 服务器添加到我的 Visual Studio 团队资源管理器中:

Add to Team Explorer

现在,我将创建一个 ASP.NET MVC 4 项目,并勾选“添加到源代码控制”。我将使用互联网模板,我也将添加一个单元测试项目。

Creating the VS project

项目创建完成后,我会被问到代码应该在 TFS 的什么地方。我添加了一个Trunk\Source文件夹:

Add project to source control

在我签入之前,我将在解决方案上启用 NuGet 包恢复,这样我就不必签入我的所有 NuGet 包:

Enable NuGet package restore

从 pending changes 窗口中,我将排除packages目录,并签入我的代码:

Exclude packages folder

通过互联网办理入住手续出奇的快。我过去在互联网上使用 TFS 2010 有过不好的经历,但是这个非常快。

步骤 2:持续集成

签入我的初始代码后,是时候建立团队构建了。我将创建一个名为“CI”的新团队构建定义。在“Trigger”选项卡上,我将选择“CI”选项:

Setting up team build

在“过程”选项卡上,Team Build 会自动找到我的解决方案。我要做的唯一更改是显式指定发布配置:

Release build

我将保存构建配置,然后让它排队运行:

Queue build

构建在运行之前在队列中停留了大约一分钟,大概是构建机器正在被提供或者其他什么。实际构建花费了大约 2.5 分钟,并成功完成:

Build complete

如果我在 Visual Studio 中使用源代码管理器窗口,我可以看到构建的输出。在一个_PublishedWebsites目录下是我的项目的目录,其中包含了我运行我的 ASP.NET 网站所需的所有文件。

Published web site

这是 TFS 的一个特色,很不错,但是我该拿它怎么办呢?手动下载文件,然后像 1998 年一样用 FTP 传输到某个地方?不,我们可以做得更好!

步骤 3:打包网站

到目前为止,我有一个使用 Team Build 构建的 ASP.NET 站点,该站点的内容被发布到_PublishedWebsites文件夹中。现在,我将这个文件夹压缩到一个 NuGet 包中,这样我就可以使用 OctopusDeploy 来部署它。

为此,我将使用 OctoPack ,这是一个特殊的 NuGet 包,它将为我处理这个问题。我将在软件包管理器控制台中发出install-package OctoPack命令,注意选择我的网站作为目标项目:

Add OctoPack

OctoPack 修改网站的.csproj文件,添加一个特殊目标文件的链接。这个目标文件将在发布构建完成后负责打包。

由于我使用的是 NuGet 包恢复,OctoPack 在我的解决方案的根目录下添加了一个.octopack文件夹,紧挨着.nuget文件夹,这就是保存目标文件的地方。令人烦恼的是,这并没有出现在团队资源管理器的 Pending Changes 窗口中,所以我将使用 Source Explorer 手动添加它。

我还将在我的项目中添加一个.nuspec文件,这是 OctoPack 所需要的。这将作为我的包裹的舱单。

<?xml version="1.0"?>
<package >
  <metadata>
    <id>Columbia</id>
    <title>A sample web application</title>
    <version>1.0.0</version>
    <authors>Paul Stovell</authors>
    <owners>Paul Stovell</owners>
    <licenseUrl>http://octopusdeploy.com</licenseUrl>
    <projectUrl>http://octopusdeploy.com</projectUrl>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>A sample project</description>
    <releaseNotes>This release contains the following changes...</releaseNotes>
  </metadata>
</package> 

这将被添加到我的 web 项目的根目录,我将把构建操作设置为 None:

Add NuSpec

最后,我将提交更改,再次确保不要添加 NuGet packages 文件夹(我认为待定更改需要一个‘永久忽略’按钮)。

Add OctoPack

签入后,我的团队构建被自动触发。我在准备这个演示时犯了一个错误(忘记添加.nuspec),这就是为什么会有一个代表以前失败构建的条。添加它修复了构建:

Build finished

现在,如果我检查 drops 文件夹,我会发现一个Columbia.1.0.0.0.nupkg文件:

Drop folder with package

如果我使用 NuGet 包浏览器,我可以看到它包含了我运行网站所需的文件,以及来自我的.nuspec的清单信息:

NuGet package explorer contents

步骤 4:增加版本号

NuGet 包从项目的输出程序集的[assembly: AssemblyVersion]属性中获取它的版本号,这个属性默认设置为1.0.0.0。我们可以在AssemblyInfo.cs文件中改变这一点,给每个包一个唯一的版本号:

[assembly: AssemblyVersion("1.0.*")] 

我将签入它,并让 CI 构建再次运行,几分钟后,我有了一个新的 drops 文件夹,其中包含一个新的包:

Package with version stamp

步骤 5:托管 NuGet 包

我的 CI 构建现在正在生成与 Octopus 兼容的 NuGet 包。在部署它们之前,我需要将 NuGet 包托管在 Octopus 可以访问它们的地方。不幸的是,TFS 预览版不能兼作 NuGet 包库,所以我们必须看看外面。

Octopus 支持任何 NuGet 存储库,所以一种选择是手动将每个包复制到 Octopus 可以引用的本地文件共享。如果我们只是偶尔进行部署,这可能是一个不错的选择,但它并不是完全自动化的。

另一个选择是将我们的包发布到 NuGet 服务器上。我们可以通过设置一个运行 NuGet Gallery 的虚拟机来托管它,但这需要一些努力。一种更简单的方法是使用MyGet.org,一种“NuGet 即服务”的解决方案。

我将使用我的 Live ID 创建一个帐户,然后填写注册页面:

MyGet signup

接下来,我将创建一个由 MyGet 托管的 NuGet 提要。在这个例子中,我将使用公共提要,但是您也可以选择使用私有提要:

MyGet feed

创建了我的 feed 后,我可以使用 MyGet Feed Details 选项卡来获取 Feed 的信息:

MyGet details

步骤 6:将包推送到 MyGet

现在,我将设置 Team Build 来自动将我的包推送到 MyGet 提要。我将从编辑团队构建定义开始:

Edit build definition

进程选项卡上,我将展开高级部分并设置 MSBuild Arguments 属性:

MSBuild arguments

我已经设置为:

/p:OctopusPublishPackageToHttp=http://www.myget.org/F/columbia/api/v2/package /p:OctopusPublishApiKey=156372ea-b731-42f6-ba3d-65269e7b01a8 

注意:这些变量在 OctoPack 2.0 中已经改变——它们现在是Octo**Pack**PublishPackageToHttpOcto**Pack**PublishApiKey

我正在使用几天前引入 OctoPack 的两个属性。第一个设置要发布到的 URI——在本例中是我的 MyGet 提要——第二个是要使用的 API 键。我从 MyGet.org Feed 详细信息页面获得了这两个值。

再次排队我的构建,它通过:

Build passes

如果我检查“我的获取包”选项卡:

MyGet package listing

呜哇!

步骤 7:向 Octopus 添加 MyGet 提要

让我们回顾一下我们的现状:

  1. 我们有一个网站
  2. 我们有一个 CI 构建
  3. 我们已经把网站打包了
  4. 我们把这个包发布到了一个 NuGet 服务器上
  5. 现在我们需要部署这个包

我已经安装了一个 Octopus Deploy 服务器(参见安装指南了解如何安装的细节),我有三个服务器,都运行在 Amazon EC2 上。一台服务器位于暂存环境中,另外两台位于生产环境中:

Octopus machines and environments

这些机器是 Amazon EC2 Windows Server 2008 R2 映像,我已经安装了 IIS 组件以及在本地运行的 Octopus 部署代理。我已经使用 Octopus 内置的公钥加密支持建立了一个信任关系,这允许我安全地将包从我的 Octopus 服务器推送到我的机器上。

在 Octopus web portal 配置区域,我将转到 NuGet 选项卡,添加一个新的提要,粘贴我的 MyGet 提要的详细信息:

Adding the feed

如果我使用的是私人的 MyGet feed,或者 TeamCity feed,我会在下面的框中添加用户名/密码。在这种情况下,我使用的是公共提要,所以我将它们留空。

添加提要后,我可以通过单击 Test 并执行搜索以列出包来验证它是否工作:

Testing the feed

步骤 8:在 Octopus 中定义项目

接下来,我将在 Octopus UI 中创建新项目:

Creating an Octopus project

在步骤选项卡上,我将单击添加包步骤。在这个页面上,我将输入我的 NuGet 包的 ID,选择提要,并选择应该将它部署到哪些机器上:

Adding a step

定义好我的项目后,我将创建一个新的版本,选择要部署的 NuGet 包的版本:

Creating a release

在 release details 页面上,我有一个中心位置,在这里我可以看到我的发行说明,以及关于该版本中包含的 NuGet 包的信息。

Release details

接下来,我将单击部署该版本...并选择要部署到的环境:

Deploy release

部署将会运行。在这个阶段,Octopus 通过几个步骤工作:

  1. 它从 MyGet 提要下载包,并验证散列
  2. 它将它上传到目标机器
  3. 它告诉目标机器上的部署代理安装软件包

部署成功:

Deploy success

步骤 8:配置 IIS

Octopus 提取了 NuGet 包,但是还没有修改 IIS。如果我浏览部署日志,我会看到:

WARN 在本地计算机上找不到名为“Columbia”的 IIS 网站或虚拟目录。如果你希望 Octopus 为你更新,你应该手动创建站点和/或虚拟目录。否则,您可以忽略此消息。

在这个阶段,我有两个选择。我可以远程桌面到我的所有三台机器上,并用正确的应用程序池设置创建一个名为 Columbia 的新 IIS 站点。Octopus 然后会自动找到这个 IIS 站点(因为它与 NuGet 包同名)并改变主目录指向它提取 NuGet 包的路径。

但是,我懒。因此,我将把一个 PostDeploy.ps1 文件添加到我的项目中。这是一个 PowerShell 脚本,因为它被命名为 PostDeploy.ps1 ,运行在远程机器上的触手代理将自动为我执行它。我不需要摆弄 PowerShell remoting,我的机器也不需要在同一个 Active Directory 域上;是 automagic!

我将在 Visual Studio 的项目中创建该文件,以便我团队中的开发人员可以看到它。我还会将它标记为 Content,这样它就会包含在 NuGet 包的文件列表中:

PostDeploy.ps1 PowerShell

该脚本将如下所示:

# Define the following variables in the Octopus web portal:
#
#   $ColumbiaIisBindings = ":80:columbia.octopusdeploy.com"
#
# Settings
#---------------
$appPoolName = ("Columbia-" + $OctopusEnvironmentName)
$siteName = ("Columbia - " + $OctopusEnvironmentName) 
$siteBindings = $ColumbiaIisBindings
$appPoolFrameworkVersion = "v4.0"
$webRoot = (resolve-path .)

# Installation
#---------------
Import-Module WebAdministration

cd IIS:\

$appPoolPath = ("IIS:\AppPools\" + $appPoolName)
$pool = Get-Item $appPoolPath -ErrorAction SilentlyContinue
if (!$pool) { 
    Write-Host "App pool does not exist, creating..." 
    new-item $appPoolPath
    $pool = Get-Item $appPoolPath
} else {
    Write-Host "App pool exists." 
}

Write-Host "Set .NET framework version:" $appPoolFrameworkVersion
Set-ItemProperty $appPoolPath managedRuntimeVersion $appPoolFrameworkVersion

Write-Host "Set identity..."
Set-ItemProperty $appPoolPath -name processModel -value @{identitytype="NetworkService"}

Write-Host "Checking site..."
$sitePath = ("IIS:\Sites\" + $siteName)
$site = Get-Item $sitePath -ErrorAction SilentlyContinue
if (!$site) { 
    Write-Host "Site does not exist, creating..." 
    $id = (dir iis:\sites | foreach {$_.id} | sort -Descending | select -first 1) + 1
    new-item $sitePath -bindings @{protocol="http";bindingInformation=$siteBindings} -id $id -physicalPath $webRoot
} else {
    Set-ItemProperty $sitePath -name physicalPath -value "$webRoot"
    Write-Host "Site exists. Complete"
}

Write-Host "Set app pool..."
Set-ItemProperty $sitePath -name applicationPool -value $appPoolName

Write-Host "Set bindings..."
Set-ItemProperty $sitePath -name bindings -value @{protocol="http";bindingInformation=$siteBindings}

Write-Host "IIS configuration complete!" 

这个脚本看起来很长,但是阅读起来非常简单——它检查应用程序池是否存在,然后创建或更新它,然后对 web 站点做同样的事情。这些都是使用 IIS PowerShell 模块完成的。

该脚本使用了两个变量。第一个是$OctopusEnvironmentName,它由触手部署代理自动传递给脚本,在这个例子中它将是“Staging”或“Production”(顺便说一下,您可以在 Octopus Deploy 中创建任意多个环境)。

第二个变量是一个自定义变量,$ColumbiaIisBindings。我使用我的 web 服务器来托管多个站点,所以我将配置 IIS 绑定来只监听一个主机名。

  • 在制作中,我会使用"columbia.octopusdeploy.com"
  • 在 Staging 中,我将使用"staging.columbia.octopusdeploy.com"

在 PowerShell 脚本中,您会注意到我实际上没有为这个 PowerShell 变量设置值。既然每个环境都不一样,我该如何设置呢?答案是章鱼变量

我将在 Octopus UI 中定义两个变量,并为每个变量设置环境:

Add variables

最后,我将创建一个新版本,并选择新的包版本:

Create new release

步骤 9:重新部署到暂存

我将再次将其部署到暂存:

Staging successful

展开部署日志,我可以看到 PowerShell 脚本的输出:

2012-07-18 13:05:22 INFO   Calling PowerShell script: 'C:\Apps\Staging\Columbia\1.0.4582.23418\PostDeploy.ps1'
2012-07-18 13:05:37 DEBUG  Script 'C:\Apps\Staging\Columbia\1.0.4582.23418\PostDeploy.ps1' completed.
2012-07-18 13:05:37 DEBUG  Script output:
2012-07-18 13:05:37 DEBUG  App pool does not exist, creating...

Name                     State        Applications
----                     -----        ------------
Columbia-Staging         Started
Set .NET framework version: v4.0
Set identity...
Checking site...
Site does not exist, creating...

Name         : Columbia - Staging
ID           : 2
State        : Started
PhysicalPath : C:\Apps\Staging\Columbia\1.0.4582.23418
Bindings     : Microsoft.IIs.PowerShell.Framework.ConfigurationElement

Set app pool...
Set bindings...
IIS configuration complete! 

如果我浏览网站,我会看到:

Staging preview

步骤 10:部署到生产环境

既然我们已经验证了试运行部署可以工作,那么让我们将其部署到生产环境中。当一个环境的部署完成时,会出现一个升级按钮:

Promote

点击它,让我们选择生产:

Promote to prod

当部署完成时,我们可以展开第三步来查看包实际上被部署到两台机器上,它们位于 Amazon 负载平衡器的后面:

Deploy to production

让我们来看看生产现场:

Production preview

摘要

在这篇文章中,我创建了一个 ASP.NET MVC 4 站点,并将其签入到我托管的 TFS 预览实例中。然后,我使用 OctoPack 从 Team Build 创建 NuGet 包,使用 MyGet 托管包。最后,我配置 Octopus 将应用程序部署到一台登台机器和两台生产机器上。

TFS and ALM

结合我之前的图片,我们已经使用 Octopus 扩展了 TFS 预览版以部署到 EC2。但不止于此。Octopus 被设计成能够部署到云中、私有数据中心的虚拟机上,甚至部署到你桌子下面的机器上。我们已经扩展了 ALM,使其不仅仅意味着“部署到 Azure web 角色”。我们现在能够部署到托管在任何地方的虚拟机,并跨环境推广我们的产品。现在是连续交货。

关于 TFS 预演的最后想法

正如我所说,我以前使用过 TFS 2010 的主机版本,体验并不好。然而,TFS 预览版已经走过了漫长的道路。源代码控制有了很大的改进,一旦我习惯了 UI,使用起来真的很棒。我可能会继续使用 GitHub 和 TeamCity 来完成我的大部分个人工作,但我认为微软已经为企业开发者创造了一个非常好的解决方案。试试看。

自动触手安装-章鱼部署

原文:https://octopus.com/blog/automated-tentacle-installation

触手安装器由两部分组成:

  1. MSI,它将文件安装到 C:\Program Files
  2. 一个配置工具,它安装 Windows 服务,设置信任关系,让您配置端口号,等等

配置工具是独立的,因为它可以随时重新运行,这类似于通过 MSI 安装 SQL Server Reporting Services 的方式,但随后通过 Reporting Services 配置管理器进行配置。

MSI 可以通过组策略或其他企业软件安装工具轻松部署,但配置工具是一个 GUI,必须手动运行。

在 Octopus 聊天室justFen_ 建议应该可以在完全无人值守的情况下安装触手。从版本 1.0.19.1298 开始,现在这是可能的。配置触手由几个命令组成,我将一一介绍。

更新:此功能的文档已被移至文档页面

昨晚,我与一位拥有超过 100 个触须的客户进行了交谈,每个触须都是手工精心安装的。虽然我怀疑人们在安装少量的触手服务器时仍然会使用 GUI 安装程序,但是我认为这个特性在管理大量的部署代理时会非常方便。

使用 CloudFormation 模板为 Linux 安装自动化触手——Octopus Deploy

原文:https://octopus.com/blog/automating-linux-tentacle-with-cloudformation

Tentacles rising from the ocean to install software on a Linux server with AWS Cloudformation

在一个具有扩展能力的基于云的应用程序的世界中,实现基础架构自动化至关重要。Amazon Web Services (AWS)通过提供 CloudFormation 模板来自动提供基于云的资源,从而消除了繁重的工作,但是您仍然需要一种方法来自动将新创建的 EC2 实例连接到 Octopus Deploy,以便可以部署您的应用程序和服务。在这篇文章中,我将演示在使用基于 Linux 的 EC2 实例时,如何为 Linux 安装和配置一个触手。

CloudFormation 模板中的用户数据

AWS 在 CloudFormation 模板中提供了一个部分,我们可以在其中包含一个名为 UserData 的脚本。在这个例子中,我创建了一个 EC2 Linux 实例来托管一个. NET 核心应用程序 OctoPetShop 。为此,我需要:

  • 为 Linux 安装触手。
  • 配置触手。
  • 在我的章鱼服务器上注册触手。
  • 创建单位文件。
  • 将触手配置为作为 Linux 服务运行。
  • 安装。网芯。

为 Linux 安装触手

在提供 EC2 实例之后,我们需要为 Linux 安装触手。首先,我们将 Octopus 公钥和 Octopus 存储库添加到 apt 的授权列表中。在这些命令运行之后,我们可以安装用于 Linux 的触手:

sudo apt-key adv --fetch-keys https://apt.octopus.com/public.key # Add Octopus public key to apt
sudo add-apt-repository "deb https://apt.octopus.com/ stretch main" # Add Octopus repository to apt
sudo apt-get update # Make sure everything else is up-to-date
sudo apt-get install tentacle # Install Tentacle for Linux 

配置触手

当处理可以动态启动的云托管虚拟机(VM)时,将触手配置为轮询触手是最有意义的,这样我们就不必处理那么多防火墙配置:

serverUrl="https://YourOctopusServer" # Url to our Octopus server
serverCommsPort=10943 # Port to use for the Polling Tentacle
apiKey="API-XXXXXXXXXXXXXXXXXXXXXXXXXXX" # API key that has permission to add machines
name=$HOSTNAME # Name of the Linux machine
environment="Dev"
role="AWS-MyApplication"
configFilePath="/etc/octopus/default/tentacle-default.config" # Location on disk to store the configuration
applicationPath="/home/Octopus/Applications/" # Location where deployed applications will be installed to

# Create a new Tentacle instance
/opt/octopus/tentacle/Tentacle create-instance --config "$configFilePath"

# Create a new self-signed certificate for secure communication with Octopus server
/opt/octopus/tentacle/Tentacle new-certificate --if-blank

# Configure the Tentacle specifying it is not a listening Tentacle and setting where deployed applications go
/opt/octopus/tentacle/Tentacle configure --noListen True --reset-trust --app "$applicationPath" 

向 Octopus 服务器注册触手

既然我们已经配置了触手,我们需要向 Octopus 服务器注册它。该脚本使用了上一节中定义的一些变量:

# Display that we’re going to register the Tentacle and to where with environments and roles
echo "Registering the Tentacle $name with server $serverUrl in environment $environment with role $role"

# Register the Tentacle with our Octopus server - note that we've included more environments and roles than the ones defined in variables above
/opt/octopus/tentacle/Tentacle register-with --server "$serverUrl" --apiKey "$apiKey" --name "$name" --env "$environment" --env "TearDown" --role "$role" --role "OctoPetShop-Web" --role "OctoPetShop-ProductService" --role "OctoPetShop-ShoppingCartService" --comms-style "TentacleActive" --server-comms-port $serverCommsPort 

安装并配置触手作为 Linux 服务运行

接下来,我们将触手配置为在操作系统启动时启动:

sudo /opt/octopus/tentacle/Tentacle service --install --start 

安装。网络核心

我们的脚本需要做的最后一件事是安装。NET 核心,因此我们的OctoPetShop应用程序将运行:

# Download and install the Microsoft packages
wget -q https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb

# Add universe repository
sudo add-apt-repository universe

# Install apt-transport-https
sudo apt-get install apt-transport-https --assume-yes

# Run an update
sudo apt-get update

# Install .NET core SDK
sudo apt-get install dotnet-sdk-2.2 --assume-yes 

样本云形成模板

以下节选自 CloudFormation 模板。完整的模板可在 octopus samplesGitHub Repo中获得。

Resources:
  EC2Instance:
    Type: 'AWS::EC2::Instance'
    Properties:
      InstanceType: !Ref InstanceType
      SecurityGroups:
        - !Ref InstanceSecurityGroup
      KeyName: !Ref KeyName
      ImageId: ami-06f2f779464715dc5
      UserData:
        Fn::Base64:
          !Sub |
            #!/bin/bash -xe
            serverUrl="https://YourOctopusServer"
            serverCommsPort=10943
            apiKey="API-XXXXXXXXXXXXXXXXXXXXXXXXXXX"
            name=$HOSTNAME
            environment="Dev"
            role="AWS-MyApplication"
            configFilePath="/etc/octopus/default/tentacle-default.config"
            applicationPath="/home/Octopus/Applications/"

            sudo apt-key adv --fetch-keys https://apt.octopus.com/public.key
            sudo add-apt-repository "deb https://apt.octopus.com/ stretch main"
            sudo apt-get update
            sudo apt-get install tentacle

            /opt/octopus/tentacle/Tentacle create-instance --config "$configFilePath"
            /opt/octopus/tentacle/Tentacle new-certificate --if-blank
            /opt/octopus/tentacle/Tentacle configure --noListen True --reset-trust --app "$applicationPath"
            echo "Registering the Tentacle $name with server $serverUrl in environment $environment with role $role"
            /opt/octopus/tentacle/Tentacle register-with --server "$serverUrl" --apiKey "$apiKey" --name "$name" --env "$environment" --env "TearDown" --role "$role" --role "OctoPetShop-Web" --role "OctoPetShop-ProductService" --role "OctoPetShop-ShoppingCartService" --comms-style "TentacleActive" --server-comms-port $serverCommsPort

            sudo /opt/octopus/tentacle/Tentacle service --install --start

            wget -q https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
            sudo dpkg -i packages-microsoft-prod.deb

            sudo add-apt-repository universe
            sudo apt-get install apt-transport-https --assume-yes
            sudo apt-get update
            sudo apt-get install dotnet-sdk-2.2 --assume-yes 

添加项目触发器

我们可以进一步自动化,在 Octopus 项目中使用项目触发器来配置OctoPetShop应用程序,以便在新机器可用时自动部署。

结论

现在,任何时候使用这个 CloudFormation 模板创建一个新的 EC2 实例,它都会自动下载、安装和配置用于 Linux 的触手,将触手连接到您的 Octopus 服务器,将触手设置为 Linux 服务,并安装。NET Core 使我们的新实例准备好托管OctoPetShop应用程序。

用 Azure 功能自动化 Octopus-Octopus Deploy

原文:https://octopus.com/blog/automating-octopus-with-azure-functions

随着微软和亚马逊分别发布 Azure Functions 和 T2,托管小型服务和脚本的努力和成本壁垒已经降低。这些服务可以与 Octopus REST API订阅功能相结合,提供高于 Octopus 现成功能的自动化。

这篇文章探讨了两个这样的集成。第一个示例升级无人认领的手动干预,第二个示例响应机器离线。两个例子都使用了 Octopus。客户端带 C# Azure 函数的库。也使用了 Twilio SMS 集成,但这可以用您选择的通知方法来代替,或者只是一条日志消息。

示例 1 -无人认领的干预

该示例定期查询 Octopus 服务器,并查找任何具有待定手动干预的部署。如果在检查时,那些手动干预没有被分配给任何人,并且已经超过一分钟,则发送 SMS。引导故障也包括在内,因为它们是人工干预的特殊情况。

功能设置

Function Editor

使用您的 Azure 帐户创建新功能应用后,添加新功能并选择TimerTrigger-CSharp模板。给它一个名称,并保持默认的时间表。

以章鱼为参照。客户端 nuget 包,选择View Files(见上图 sceenshot),添加一个名为project.json的文件,内容如下。更多细节请看 Azure 功能包管理

{
  "frameworks": {
    "net46":{
      "dependencies": {
        "Octopus.Client": "4.5.0"
      }
    }
   }
} 

接下来设置 Octopus 公共 URL 和 API 键。为此,选择Function app settings项(见截图),然后选择Configure app settings。添加两个 app 设置,OctopusUrlOctopusApiKey

最后,设置 Twilo 集成。这是可选的,所以如果跳过它,从代码中删除对 Twilo 和SMSMessage的引用。再次选择该功能,然后选择Integrate。创建新的Output,选择Twilio SMS。输入您的 Twilo 详细信息。请注意,SID 和令牌是对应用程序设置的引用,因此您需要到那里输入实际的 SID 和应用程序设置。

该功能

将函数体(run.csx)替换为以下内容:

#r "Twilio.Api"

using System.Net;
using Octopus.Client;
using Octopus.Client.Model;
using Twilio;

public static async Task Run(TimerInfo myTimer, TraceWriter log, IAsyncCollector<SMSMessage> message)
{
    var endpoint = new OctopusServerEndpoint(GetSetting("OctopusUrl"), GetSetting("OctopusApiKey"));
    var client = await OctopusAsyncClient.Create(endpoint);

    var tasks = await client.Repository.Tasks.GetAllActive();
    foreach (var task in tasks.Where(t => t.HasPendingInterruptions))
    {
        var interruptions = await client.Repository.Interruptions.List(pendingOnly: true, regardingDocumentId: task.Id);
        var unhandled = from i in interruptions.Items
                        where i.IsPending && i.ResponsibleUserId == null
                        select (TimeSpan?) DateTimeOffset.Now.Subtract(i.Created);
        var oldest = unhandled.Max();
        if (oldest.HasValue && oldest > TimeSpan.FromMinutes(1))
        {
            var sms = new SMSMessage();
            sms.Body = $"The task {task.Description} has not been actioned after {oldest.Value.TotalMinutes:n0} minutes";
            log.Info(sms.Body);
            await message.AddAsync(sms);
        }
    }  
}

public static string GetSetting(string name) =>  System.Environment.GetEnvironmentVariable(name, EnvironmentVariableTarget.Process); 

点击Save and run按钮,查看日志输出,应该运行成功。

测试

为了测试集成,创建一个带有手动干预步骤的新项目(或者使用一个现有的项目)并部署它。或者导致部署因导向故障而暂停。等待一分钟,然后再次手动运行该功能(或等待该功能在计时器上运行)。

示例 2 -脱机计算机

这个例子是由一个订阅触发的,当一台机器由于运行状况检查或部署而从可用转换到不可用时。然后,它确定是删除该机器还是采取一些补救措施。如果机器无法恢复,将发送短信。为了简单起见,下面的实现总是确定不应该删除该机器,补救措施是什么也不做。

设置

基于HttpTrigger-CSharp模板添加一个新功能(保持授权级别为Function),并按照上述说明设置 Octopus。客户和 Twilio。

接下来在 Octopus 实例中添加一个订阅,带有一个事件过滤器Machine found to be unavailable和一个 Azure 函数的有效负载 URL。

Subscription

测试

由于该函数响应外部事件和有效负载,为了加速该函数的开发和测试,可以使用内置测试功能捕获有效负载,然后重放。首先,用以下内容替换函数体(run.csx)并保存:

#r "Twilio.Api"

using System.Net;
using Twilio;

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log, IAsyncCollector<SMSMessage> message)
{
    dynamic data = await req.Content.ReadAsAsync<object>();
    log.Info(data.ToString());
    return req.CreateResponse(HttpStatusCode.OK);
} 

接下来,停止服务器的一个触角,并对该触角运行健康检查。这项检查应该会失败。

查看您的函数的日志输出,您应该看到 Octopus 请求有效负载。如果没有,检查服务器日志中的警告。

从日志输出中复制请求有效负载,并选择 Test 菜单项(在 View Files 旁边)。将有效负载粘贴到请求正文部分。现在再次单击 Run 按钮,有效负载将再次打印到输出中。

该功能

为了简单起见,这个函数有两个占位符方法HasBeenDecommissionedAttemptToBringOnline。在完整的实现中,这些方法将与外部系统交互,如 Azure 管理 API 或一些基础设施监控软件。

将函数体(run.csx)替换为以下内容:

#r "Twilio.Api"

using System.Net;
using Octopus.Client;
using Octopus.Client.Model;
using Twilio;

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log, IAsyncCollector<SMSMessage> message)
{
    var endpoint = new OctopusServerEndpoint(GetSetting("OctopusUrl"), GetSetting("OctopusApiKey"));
    var client = await OctopusAsyncClient.Create(endpoint);

    var machineIds = await GetAffectedMachineIds(req);
    log.Info($"Machines {string.Join(", ", machineIds)} have gone offline");

    var failedMachines = new List<MachineResource>();
    foreach(var id in machineIds)
    {
        var machine = await client.Repository.Machines.Get(id);
        if(HasBeenDecommissioned(machine))
        {
            log.Info($"Machine {machine.Id} is no longer required, removing");
            await client.Repository.Machines.Delete(machine);
        }
        else
        {
            log.Info($"Machine {machine.Id} should not have gone offline");
            failedMachines.Add(machine);
        }
    }

    await SendOutageSms(failedMachines.Count, message);
    await AttemptToBringOnline(failedMachines);
    var task = await client.Repository.Tasks.ExecuteHealthCheck();

    return req.CreateResponse(HttpStatusCode.OK);
}

public static string GetSetting(string name) =>  System.Environment.GetEnvironmentVariable(name, EnvironmentVariableTarget.Process);

static async Task<string[]> GetAffectedMachineIds(HttpRequestMessage req)
{
    dynamic data = await req.Content.ReadAsAsync<object>();
    var ids = (string[]) data.Payload.Event.RelatedDocumentIds.ToObject<string[]>();
    return ids.Where(i => i.StartsWith("Machines-")).ToArray();
}

// Call out to cloud provider or monitoring software
static bool HasBeenDecommissioned(MachineResource machine) => false;

static Task SendOutageSms(int count, IAsyncCollector<SMSMessage> message)
{
    var sms = new SMSMessage();
    sms.Body = $"{count} machines has become unavailable";
    return message.AddAsync(sms);
}

// Take some remedial action
static Task AttemptToBringOnline(List<MachineResource> machines) => Task.CompletedTask; 

点击Save and run按钮并检查日志输出,它应该报告机器已经离线。

更多信息

参见协调多个项目文档页面,该页面概述了一些进一步的场景以及一些示例代码。

有关更多代码示例,请参见 OctopusDeploy-Api GitHub 资源库。

最后, LinqPad 是编辑和测试部分功能的绝佳工具。

使用 Octopus Runbooks 自动化支持电子邮件- Octopus Deploy

原文:https://octopus.com/blog/automating-support-emails-runbooks

你可以用 Octopus Runbooks 自动完成许多有用的任务,特别是关于基础设施的管理。一个微妙但同样有用的事情是章鱼可以让人们知道什么时候有问题。

根据您需要的信息和对您的支持团队有用的信息,有几种方法可以实现这一点。

让我们看两个例子。我们用两个 run book 创建了一个样本 Octopus 实例,所以您可以很容易地理解。

在 Octopus 中设置 SMTP 连接

您必须在 Octopus 中输入 SMTP 设置,runbook steps 才能向您的支持团队发送电子邮件。如果您不知道您的 SMTP 服务器或端口的详细信息,请咨询您的运营团队。

  1. 点击顶部菜单中的配置
  2. 从左侧菜单中点击 SMTP
  3. 填写以下字段,点击保存并测试:
    • SMTP 主机
    • SMTP 端口
    • 使用 SSL/TLS -强制安全 SSL 和 TLS 协议。如果禁用,八达通仍然使用这个选项,如果你的服务器支持任一协议。
    • 发件人地址 -你希望八达通卡发送的地址。
    • 凭证 -电子邮件帐户的用户名和密码。

如果测试失败,请检查您的详细信息并重试。一些电子邮件服务,如 Gmail,可能需要更改设置以允许外部应用程序发送电子邮件。

简单的支持邮件

如果您的步骤或脚本足够简单,问题很容易解决,电子邮件提醒可能就足够了。在这种情况下,创建自动化的支持电子邮件步骤非常简单。

我们在示例实例中创建了一个简单的 runbook,它运行一个注定要失败的“Hello World!”剧本。失败时,runbook 会向支持地址发送一封电子邮件。

像这样增加一个步骤会让那些能够修复它们的人注意到过时和破损的运行手册。

下面是我们如何设置发送电子邮件步骤:

  1. 打开您想要触发支持电子邮件的项目。
  2. 点击操作
  3. 点击运行手册
  4. 点击现有的运行手册进行编辑,或使用添加运行手册按钮创建新的运行手册。如果创建新的运行手册,输入名称和描述,然后点击保存
  5. 点击添加步骤
  6. 搜索email,悬停在上,从结果中发送电子邮件,点击添加
  7. 填写以下字段并点击保存:
    • 步骤名称 -给步骤起一个描述性的名称。
    • 抄送密件抄送 -输入电子邮件地址或从下拉列表中选择一个团队。关于创建团队的更多信息,请参见我们的文档。
    • 主题
    • 正文——使用原始文本或 HTML 格式的邮件。
    • 运行条件 -如果警告某人部署或运行手册步骤失败,选择失败:仅在前一步骤失败时运行
    • 启动触发器 -通常最好离开等待前一步完成,然后启动

当创建或编辑一本运行手册时,您必须点击发布以使其对其他团队和 Octopus 触发事件可用。

新步骤总是出现在 runbook 流程的底部。如果您需要对步骤重新排序:

  1. 打开你的跑步手册。
  2. 转到进程选项卡。
  3. 单击任何步骤打开流程编辑器。
  4. 点击按名称过滤搜索框旁边的菜单按钮(3 个垂直点)并选择重新排序步骤
  5. 将您的步骤拖至您需要的顺序,然后单击完成

高级支持电子邮件

对于容易解决的问题,一封简单的支持邮件就可以了,但是如果您的支持团队需要更多信息呢?

有了输出变量和一点努力,发送电子邮件步骤也可以包括开始故障诊断的一切。

在本例中,我们的高级示例操作手册从 Kubernetes 集群中抓取信息,以便在电子邮件中发送,包括:

  • 部署信息
  • Pod 日志
  • 部署目标的描述

这种类型的操作手册非常适合:

  • 从您的部署目标中自动提取技术信息
  • 帮助不太懂技术的员工在不知道复杂术语或命令的情况下获取信息
  • 加速系统恢复

开始之前

该示例使用:

  • Kubernetes 作为部署目标,尽管您可以为其他目标类型定制这个想法。
  • PowerShell 脚本从 Kubernetes 抓取数据。您可能需要在 Linux 发行版上安装 PowerShell,这些脚本才能工作。参见微软的 PowerShell 安装文档了解如何安装。

创建项目操作手册

  1. 打开您想要触发支持电子邮件的项目。
  2. 点击操作
  3. 点击运行手册
  4. 点击添加 RUNBOOK
  5. 输入名称和描述,点击保存

创建 Kubernetes 步骤

现在我们创建从 Kubernetes 集群中获取信息的步骤。这些步骤使用输出变量,因此我们可以将信息添加到电子邮件中。

使用添加步骤按钮创建 3 个 Kubectl CLI 脚本步骤,它们具有以下名称和 PowerShell 脚本。

要创建步骤:

  1. 在新操作手册中,点击流程选项卡,然后添加步骤
  2. 点击 Kubernetes ,然后安装的步骤模板下运行一个 kubectl CLI 脚本
  3. 填写以下字段并点击保存:
    • 步骤名称 -从下面的步骤信息中获取步骤名称。
    • 代表——从下拉列表中选择您的目标角色。
    • 工作池——检查是否在特定工作池的工作机上运行,并从下拉菜单中选择托管的 Ubuntu 。(仅在使用 Octopus Workers 时需要。)
    • 容器镜像 -检查在容器内部运行,在一个工作器上,然后点击使用最新的基于 Linux 的镜像来自动完成字段。(仅在使用 Octopus Workers 时需要。)
    • 内联源代码 -检查 Powershell 并输入下面每个步骤的代码。

步骤 1:获得部署

称这个步骤为Get deployment。我们在设置支持电子邮件步骤时会引用该名称。

内联源代码字段中输入以下代码。将的名称空间换成你自己集群的名称空间。

ps
$output = (kubectl describe deployment -n) -join "`n"
Set-OctopusVariable -name "Describe[$($_.metadata.name)].Content" -value $output

exit 0 

步骤 2:获取 pod 日志

把这个步骤叫做Get pod logs。我们在设置支持电子邮件步骤时会引用该名称。

内联源代码字段中输入以下代码,并替换部署名称您自己的 Kubernete 部署名称。

ps
$pods = kubectl get pods -o json | ConvertFrom-Json
$items = if ($pods.items -ne $null) {$pods.items} else {@($pods)}

$items | 
    ? {$_.metadata.name -like "deployment-name*"} |
    % {
        $logs = (kubectl logs $_.metadata.name) -join "`n"
        Set-OctopusVariable -name "Logs[$($_.metadata.name)].Content" -value $logs
    }

exit 0 

第三步:获取描述

这一步叫做Get description。我们在设置支持电子邮件步骤时会引用该名称。

内联源代码字段中输入以下代码,并替换部署名称您自己的 Kubernete 部署名称。

ps
$pods = kubectl get pods -o json | ConvertFrom-Json
$items = if ($pods.items -ne $null) {$pods.items} else {@($pods)}

$items | 
    ? {$_.metadata.name -like "deployment-name*"} |
    % {
        $logs = (kubectl describe pod $_.metadata.name) -join "`n"
        Set-OctopusVariable -name "Describe[$($_.metadata.name)].Content" -value $logs
    }

exit 0 

创建电子邮件步骤

现在,我们添加了发送电子邮件步骤,并将其配置为包含收集的信息。

  1. 点击添加步骤
  2. 搜索email,悬停在上,从结果中发送电子邮件,点击添加
  3. 填写以下字段并点击保存:
    • 步骤名称 -给步骤一个描述性名称。
    • 抄送密件抄送 -输入电子邮件地址或从下拉列表中选择一个团队。有关创建团队的更多信息,请参见我们的文档。
    • 主题
    • 正文 -使用以下原始文本:
Issue context:

The description of the deployment is included below:

#{each deployment in Octopus.Action[Get deployment].Output.Describe}
Description of #{deployment}
#{deployment.Content}

-----------------------------------------------------------

#{/each}

The pod logs are included below:

#{each pod in Octopus.Action[Get pod logs].Output.Logs}
Logs for pod #{pod}
#{pod.Content}

-----------------------------------------------------------

#{/each}

The pod descriptions are included below:

#{each pod in Octopus.Action[Get description].Output.Describe}
Description of #{pod}
#{pod.Content}

-----------------------------------------------------------

#{/each} 

查看支持电子邮件

查看由我们的示例触发的电子邮件,我们可以发现 Kubernetes 集群的几个问题:

  • 端口号不是一个数字
  • 该窗格具有未定义的 URL

这意味着支持团队很快就知道了部署目标的问题。

Issue context:

The description of the deployment is included below:

Description of
Name:                   random-quotes
Namespace:              default
CreationTimestamp:      Fri, 25 Mar 2022 02:59:41 +0000
Labels:                 Octopus.Action.Id=6f79e0b9-ec3d-4071-8d42-54dbc9d5ee1b
                        Octopus.Deployment.Id=deployments-1426
                        Octopus.Deployment.Tenant.Id=untenanted
                        Octopus.Environment.Id=environments-101
                        Octopus.Kubernetes.DeploymentName=random-quotes
                        Octopus.Kubernetes.SelectionStrategyVersion=SelectionStrategyVersion2
                        Octopus.Project.Id=projects-162
                        Octopus.RunbookRun.Id=
                        Octopus.Step.Id=b9777e71-4818-482e-8cb8-79ebf9b9960b
Annotations:            deployment.kubernetes.io/revision: 2
Selector:               Octopus.Kubernetes.DeploymentName=random-quotes
Replicas:               1 desired | 1 updated | 1 total | 1 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod Template:
  Labels:  Octopus.Action.Id=6f79e0b9-ec3d-4071-8d42-54dbc9d5ee1b
           Octopus.Deployment.Id=deployments-1426
           Octopus.Deployment.Tenant.Id=untenanted
           Octopus.Environment.Id=environments-101
           Octopus.Kubernetes.DeploymentName=random-quotes
           Octopus.Kubernetes.SelectionStrategyVersion=SelectionStrategyVersion2
           Octopus.Project.Id=projects-162
           Octopus.RunbookRun.Id=
           Octopus.Step.Id=b9777e71-4818-482e-8cb8-79ebf9b9960b
  Containers:
   octopussamples:
    Image:      index.docker.io/octopussamples/randomquotesnodejs:1.0.2
    Port:       8080/TCP
    Host Port:  0/TCP
    Environment:
      PORT:  notanumber
    Mounts:  <none>
  Volumes:   <none>
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    NewReplicaSetAvailable
OldReplicaSets:  <none>
NewReplicaSet:   random-quotes-5db56b95dc (1/1 replicas created)
Events:          <none>

-----------------------------------------------------------

The pod logs are included below:

Logs for pod random-quotes-5db56b95dc-xvhnn
App listening at http://undefined:undefined

-----------------------------------------------------------

The pod descriptions are included below:

Description of random-quotes-5db56b95dc-xvhnn
Name:         random-quotes-5db56b95dc-xvhnn
Namespace:    default
Priority:     0
Node:         aks-agentpool-33894862-vmss000001/10.240.0.5
Start Time:   Fri, 25 Mar 2022 03:07:56 +0000
Labels:       Octopus.Action.Id=6f79e0b9-ec3d-4071-8d42-54dbc9d5ee1b
              Octopus.Deployment.Id=deployments-1426
              Octopus.Deployment.Tenant.Id=untenanted
              Octopus.Environment.Id=environments-101
              Octopus.Kubernetes.DeploymentName=random-quotes
              Octopus.Kubernetes.SelectionStrategyVersion=SelectionStrategyVersion2
              Octopus.Project.Id=projects-162
              Octopus.RunbookRun.Id=
              Octopus.Step.Id=b9777e71-4818-482e-8cb8-79ebf9b9960b
              pod-template-hash=5db56b95dc
Annotations:  <none>
Status:       Running
IP:           10.244.2.11
IPs:
  IP:           10.244.2.11
Controlled By:  ReplicaSet/random-quotes-5db56b95dc
Containers:
  octopussamples:
    Container ID:   containerd://07a18a2468da4324be06c11eaeb7cdcc90d0cf67f701f7b140823cc8a7b3b80d
    Image:          index.docker.io/octopussamples/randomquotesnodejs:1.0.2
    Image ID:       docker.io/octopussamples/randomquotesnodejs@sha256:b2104dd603ef648f92cdd605ecf814e4cb24a2e1827f036b004985d06a380644
    Port:           8080/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Fri, 25 Mar 2022 03:07:56 +0000
    Ready:          True
    Restart Count:  0
    Environment:
      PORT:  notanumber
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-v5rgh (ro)
Conditions:
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True
Volumes:
  kube-api-access-v5rgh:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
QoS Class:                   BestEffort
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute for 300s
                             node.kubernetes.io/unreachable:NoExecute for 300s
Events:                      <none> 

结论

这些例子是对 Octopus Runbooks 如何帮助你获得关键信息的一个小小的了解。

如果你想亲自尝试章鱼手册,注册免费试用,体验乐趣。

阅读我们的 Runbooks 系列的其余部分。

愉快的部署!

使用 CloudFormation - Octopus Deploy 在 AWS 中创建 EC2 实例

原文:https://octopus.com/blog/aws-cloudformation-ec2-examples

Creating EC2 instance in AWS with CloudFormation

云平台为开发人员和运营人员带来了一些非常有用的工作流。启动用于测试的临时基础架构的能力消除了维护本地虚拟机的负担,并意味着您可以扩展测试,以包括许多不同容量的机器,安全地知道您不会用洪水般的流量淹没企业网络。

在过去的美好时光中,“经典”AWS 在单一的共享网络空间中创建 EC2 虚拟机。这使得创建虚拟机变得非常容易,因为几乎不需要网络配置。

如今,最佳实践要求即使一台虚拟机也需要 VPC、互联网网关、安全组、子网和路由表。

在这篇博客文章中,我们将研究两个 CloudFormation 模板,以在它们自己的 VPC 中创建 Windows 和 Linux EC2 实例。

免费试用 Octopus,自动化您的 AWS 部署。

Windows CloudFormation 模板

此示例 CloudFormation 模板在 VPC 中创建了一个 Windows EC2 实例:

AWSTemplateFormatVersion: 2010-09-09
Parameters:
  InstanceTypeParameter:
    Type: String
    Default: t3a.medium
    Description: Enter instance size. Default is t3a.medium.
  WorkstationIp:
    Type: String
    Description: The IP address of the workstation that can RDP into the instance.
  AMI:
    Type: String
    Default: ami-05bb2dae0b1de90b3
    Description: The Windows AMI to use.
  Key:
    Type: String
    Description: The key used to access the instance.
Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: Windows Target VPC
  InternetGateway:
    Type: AWS::EC2::InternetGateway
  VPCGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway
  SubnetA:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: us-east-1a
      VpcId: !Ref VPC
      CidrBlock: 10.0.0.0/24
      MapPublicIpOnLaunch: true
  RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
  InternetRoute:
    Type: AWS::EC2::Route
    DependsOn: InternetGateway
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
      RouteTableId: !Ref RouteTable
  SubnetARouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref RouteTable
      SubnetId: !Ref SubnetA
  InstanceSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: "Octopus Target Group"
      GroupDescription: "Tentacle traffic in from hosted static ips, and RDP in from a personal workstation"
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  20.188.245.156/32
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  52.147.25.42/32 
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  52.147.31.180/32 
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  20.188.244.132/32 
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  52.147.25.94/32 
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  52.147.25.173/32 
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  20.188.245.171/32 
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  20.188.245.7/32
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  20.188.244.147/32
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  20.188.244.240/32
        - IpProtocol: tcp
          FromPort: '3389'
          ToPort: '3389'
          CidrIp:  !Sub ${WorkstationIp}/32
      SecurityGroupEgress:
        - IpProtocol: -1
          CidrIp: 0.0.0.0/0
  ElasticIP:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
      InstanceId: !Ref Windows
  Windows:
    Type: 'AWS::EC2::Instance'
    Properties:
      ImageId: !Ref AMI
      InstanceType:
        Ref: InstanceTypeParameter
      KeyName: !Ref Key
      SubnetId: !Ref SubnetA
      SecurityGroupIds:
        - Ref: InstanceSecurityGroup
      BlockDeviceMappings:
        - DeviceName: /dev/sda1
          Ebs:
            VolumeSize: 250
      UserData:
        Fn::Base64: !Sub |
          <powershell>
          Write-Host "Hello World!"
          </powershell>
      Tags:
        -
          Key: Appplication
          Value:  Windows Server
        -
          Key: Domain
          Value: None
        -
          Key: Environment
          Value: Test
        -
          Key: LifeTime
          Value: Transient
        -
          Key: Name
          Value:  Windows Server Worker
        -
          Key: OS
          Value: Windows
        -
          Key: OwnerContact
          Value: "@matthewcasperson"
        -
          Key: Purpose
          Value: MattC Test Worker
        -
          Key: Source
          Value: CloudFormation Script in Octopus Deploy
Outputs:
  PublicIp:
    Value:
      Fn::GetAtt:
        - Windows
        - PublicIp
    Description: Server's PublicIp Address 

让我们来分解这个代码。

模板版本定义为:

AWSTemplateFormatVersion: 2010-09-09 

最终用户可能定制的任何值都被定义为参数。这里我们公开了四个参数:

  • 实例类型,它定义了与我们的 VM 相关联的硬件。
  • 可以通过远程桌面连接到实例的工作站的 IP 地址。拥有这个 IP 意味着我们可以只限制一个工作站的访问,而不是整个互联网。
  • 用于创建虚拟机的 AMI。ami 可以在 AWS 市场找到。
  • 用于保护实例的密钥对。我们希望在帐户中已经创建了一个密钥对:
Parameters:
  InstanceTypeParameter:
    Type: String
    Default: t3a.medium
    Description: Enter instance size. Default is t3a.medium.
  WorkstationIp:
    Type: String
    Description: The IP address of the workstation that can RDP into the instance.
  AMI:
    Type: String
    Default: ami-05bb2dae0b1de90b3
    Description: The Windows AMI to use.
  Key:
    Type: String
    Description: The key used to access the instance. 

模板的下一部分定义了要创建的资源。

我们从一个 VPC 开始,它本质上是一个保存我们资源的隔离网段。该 VPC 将保存10.0.0.0/16范围内的私有 IP 地址:

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: Windows Target VPC 

为了让我们的 EC2 访问互联网,我们需要一个互联网网关:

 InternetGateway:
    Type: AWS::EC2::InternetGateway 

然后将互联网网关连接到 VPC:

 VPCGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway 

在 VPC 内部,我们可以有一个或多个子网。虽然 VPC 可以跨越多个可用性区域,但是子网是单个 AZ 中的网络地址范围。我们正在创建一个 EC2 实例,因此我们只创建一个子网来容纳它:

 SubnetA:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: us-east-1a
      VpcId: !Ref VPC
      CidrBlock: 10.0.0.0/24
      MapPublicIpOnLaunch: true 

流量路由由路由表处理:

 RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC 

然后,我们将所有外部流量路由到互联网网关。这将为我们的 EC2 实例提供互联网访问:

 InternetRoute:
    Type: AWS::EC2::Route
    DependsOn: InternetGateway
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
      RouteTableId: !Ref RouteTable 

路由表随后与子网相关联。其流量通过互联网网关路由的任何子网被称为公共子网:

 SubnetARouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref RouteTable
      SubnetId: !Ref SubnetA 

进出 EC2 实例的流量由一个安全组控制。下面的规则允许在端口10933(监听触手端口)上访问我托管的 Octopus 实例可以使用的所有静态 IP

远程桌面访问被允许从一个特定的工作站通过端口3389进入。这意味着只有一个工作站可以远程登录。

我们还允许所有出站流量:

这些 IP 地址将根据您的具体 Octopus Cloud 实例而变化,因此请参考文档中适用于您的列表。

 InstanceSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: "Octopus Target Group"
      GroupDescription: "Tentacle traffic in from hosted static ips, and RDP in from a personal workstation"
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  20.188.245.156/32
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  52.147.25.42/32 
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  52.147.31.180/32 
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  20.188.244.132/32 
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  52.147.25.94/32 
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  52.147.25.173/32 
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  20.188.245.171/32 
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  20.188.245.7/32
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  20.188.244.147/32
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  20.188.244.240/32
        - IpProtocol: tcp
          FromPort: '3389'
          ToPort: '3389'
          CidrIp:  !Sub ${WorkstationIp}/32
      SecurityGroupEgress:
        - IpProtocol: -1
          CidrIp: 0.0.0.0/0 

监听触角需要静态主机名或 IP 地址。虽然我们的 EC2 实例在创建时将获得一个公共 IP 地址,但是如果实例停止并再次启动,该地址将会改变。为了确保实例总是有一个静态 IP 地址,我们创建了一个弹性 IP :

 ElasticIP:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
      InstanceId: !Ref Windows 

我们现在已经创建了托管 EC2 实例所需的所有网络,该实例具有 Internet 访问和静态 IP。现在我们创建 EC2 实例。

我们通过BlockDeviceMappings部分给了这个 EC2 实例一个更大的硬盘,而UserData部分保存了一个在启动时运行的脚本。这个示例脚本不做任何事情,但是如果需要的话可以被替换:

 Windows:
    Type: 'AWS::EC2::Instance'
    Properties:
      ImageId: !Ref AMI
      InstanceType:
        Ref: InstanceTypeParameter
      KeyName: !Ref Key
      SubnetId: !Ref SubnetA
      SecurityGroupIds:
        - Ref: InstanceSecurityGroup
      BlockDeviceMappings:
        - DeviceName: /dev/sda1
          Ebs:
            VolumeSize: 250
      UserData:
        Fn::Base64: !Sub |
          <powershell>

          </powershell>
      Tags:
        -
          Key: Appplication
          Value:  Windows Server
        -
          Key: Domain
          Value: None
        -
          Key: Environment
          Value: Test
        -
          Key: LifeTime
          Value: Transient
        -
          Key: Name
          Value:  Windows Server Worker
        -
          Key: OS
          Value: Windows
        -
          Key: OwnerContact
          Value: "@matthewcasperson"
        -
          Key: Purpose
          Value: MattC Test Worker
        -
          Key: Source
          Value: CloudFormation Script in Octopus Deploy 

输出捕获实例 ID 和实例可用的弹性公共 IP:

Outputs:
  PublicIp:
    Value:
      Fn::GetAtt:
        - Windows
        - PublicIp
    Description: Server's PublicIp Address 

现在,我们可以在 Octopus 中通过部署 AWS CloudFormation 模板步骤来部署该模板:

结果是在一个孤立的 VPC 出现了一个新的 EC2 实例:

Linux CloudFormation 模板

创建 Linux 虚拟机的模板非常相似:

AWSTemplateFormatVersion: 2010-09-09
Parameters:
  InstanceTypeParameter:
    Type: String
    Default: t3a.medium
    Description: Enter instance size. Default is t3a.medium.
  WorkstationIp:
    Type: String
    Description: The IP address of the workstation that can SSH into the instance.
  AMI:
    Type: String
    Default: ami-08f3d892de259504d
    Description: The Linux AMI to use.
  Key:
    Type: String
    Description: The key used to access the instance.
Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: Linux VPC
  InternetGateway:
    Type: AWS::EC2::InternetGateway
  VPCGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway
  SubnetA:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: us-east-1a
      VpcId: !Ref VPC
      CidrBlock: 10.0.0.0/24
      MapPublicIpOnLaunch: true
  RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
  InternetRoute:
    Type: AWS::EC2::Route
    DependsOn: InternetGateway
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
      RouteTableId: !Ref RouteTable
  SubnetARouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref RouteTable
      SubnetId: !Ref SubnetA
  InstanceSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: "Internet Group"
      GroupDescription: "SSH and web traffic in, all traffic out."
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  20.188.245.156/32
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  52.147.25.42/32 
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  52.147.31.180/32 
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  20.188.244.132/32 
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  52.147.25.94/32 
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  52.147.25.173/32 
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  20.188.245.171/32 
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  20.188.245.7/32
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  20.188.244.147/32
        - IpProtocol: tcp
          FromPort: '10933'
          ToPort: '10933'
          CidrIp:  20.188.244.240/32
        - IpProtocol: tcp
          FromPort: '22'
          ToPort: '22'
          CidrIp:  !Sub ${WorkstationIp}/32
      SecurityGroupEgress:
        - IpProtocol: -1
          CidrIp: 0.0.0.0/0
  ElasticIP:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
      InstanceId: !Ref Linux
  Linux:
    Type: 'AWS::EC2::Instance'
    Properties:
      SubnetId: !Ref SubnetA
      ImageId: !Ref AMI
      InstanceType:
        Ref: InstanceTypeParameter
      KeyName: !Ref Key
      SecurityGroupIds:
        - Ref: InstanceSecurityGroup
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            VolumeSize: 250
      Tags:
        -
          Key: Appplication
          Value: Linux Server
        -
          Key: Domain
          Value: None
        -
          Key: Environment
          Value: Test
        -
          Key: LifeTime
          Value: Transient
        -
          Key: Name
          Value: Linux Server
        -
          Key: OS
          Value: Linux
        -
          Key: OwnerContact
          Value: "@matthewcasperson"
        -
          Key: Purpose
          Value: Support Test Instance
        -
          Key: Source
          Value: CloudForation Script in Octopus Deploy
      UserData:
        Fn::Base64: |
          #cloud-boothook
          #!/bin/bash
          echo "Hello World!"
Outputs:
  PublicIp:
    Value:
      Fn::GetAtt:
        - Linux
        - PublicIp
    Description: Server's PublicIp Address 

该模板的大部分内容与 Windows 模板相同。有一些小差异值得一提:

  • InstanceSecurityGroup现在定义了一个规则,允许工作站在端口22 (SSH)而不是端口3389 (RDP)上。
  • EC2 实例中的BlockDeviceMappings改变它映射到 Linux 主机的路径。
  • UserData脚本现在是 Bash 脚本,而不是 PowerShell。

结论

这些模板多年来为我提供了很好的服务,作为 AWS 中自助服务 Windows 和 Linux 虚拟机的一种方式。通过创建一个 VPC 来保存虚拟机,我们可以访问更新的实例类型,这些类型不支持 EC2-Classic 环境。VPC 还允许我们创建两个或更多可以相互通信但仍与任何其他虚拟机隔离的虚拟机。

将容器部署到 AWS Fargate - Octopus Deploy

原文:https://octopus.com/blog/aws-fargate

Deploying containers to AWS Fargate

亚马逊网络服务(AWS) Fargate 已经成为部署容器化应用的流行技术,而无需担心后端基础设施管理。我的团队经常被问到,你能用 Octopus Deploy 搭配 AWS Fargate 吗?答案是,可以!不仅可以使用 Fargate,还可以使用 AWS 弹性容器注册中心(ECR)作为 Octopus Deploy 的外部提要。在本文中,我使用 TeamCity、ECR、Octopus Deploy 和弹性容器服务(ECS) Fargate 演示了整个 CI/CD 管道。

创建 AWS 资源

对于这篇文章,我们需要在 AWS 中创建一些资源:

  • 资格证书
  • ECR 回购
  • VPC
  • 子网
  • 安全组
  • ECS 集群

创建 AWS 凭据

TeamCity 和 Octopus Deploy 都需要凭证才能使用 AWS 服务。您需要做的第一件事是登录到 AWS 管理控制台,并使用身份和访问管理(IAM)创建一个帐户。创建帐户后,点击用户,然后点击安全凭证来创建一个Access Key

AWS user access key

创建访问密钥后,保存Secret Key,因为值只会显示一次Access KeySecret Key的组合用于向 AWS 认证。

除了 Octopus 将外部 feed 部署到 ECR 之外,可以将 AWS IAM 角色用于 TeamCity 和 Octopus Deploy。因此,这篇文章使用了访问密钥/秘密密钥的方法。

检索 AWS ECR 注册表信息

您创建的每一个容器都将在其特定区域的 ECR repo 中结束。回购 URI 由以下部分组成:

<RegistryId>.dkr.ecr.<RegionName>.amazonaws.com/<RepoName> 

RegistryIdRegionName将用于我们将在 TeamCity 中创建的连接,当我们标记我们的图像时,将使用整个 URI。

要检索这些值,导航到 AWS 控制台中的弹性容器注册表

查找服务框中输入ecr会让你更快到达那里。

如果您没有任何 ECR 存储库,请点击开始按钮:

ECR getting started

如果您有现有的 ECR 存储库,点击创建存储库按钮或复制现有存储库的 URI:

An existing ECR repo

创建或使用现有的 VPC

Fargate 不需要唯一的虚拟私有云(VPC),因此不需要创建新的 VPC;然而,使用现有的 VPC 就可以了。这篇文章假设你已经对 AWS 有了一些了解,并且知道如果需要的话如何创建 VPC。

创建或使用现有子网

除了 VPC,Fargate 不需要定义子网。就像 VPC 一样,法盖特可以使用现有的物品。同样,本文假设您已经知道如何根据需要创建子网。

创建或使用现有的安全组

您可能需要考虑创建新的安全组,因为您可能需要定义端口来访问您的容器,而这些端口对于其他 AWS 资源来说是不必要的。

Octo Pet Shop 应用程序是一个. NET 核心应用程序,其中 web 前端使用内置的 Kestrel web 服务器,该服务器使用默认端口5000。Octo 宠物店前端配置为自动将 HTTP 流量重定向到 HTTPS 和端口5001。这两个端口没有被任何其他资源使用,所以我为 Octo Pet Shop 创建了一个新的安全组。

要创建新的安全组,导航到 AWS 控制台中的 VPC 服务,并单击左侧的安全组。如果您正在关注这篇文章,请为端口50005001创建两个入站规则。

AWS VPC Security Groups

创建 ECS 群集

我们需要的最后一个资源是一个 ECS 集群来托管我们的容器。

  1. 在 AWS 控制台中导航到 ECS。
  2. 选择创建集群
  3. 在下一个屏幕上,选择仅联网模板。
  4. 点击下一步
  5. 给你的集群命名,然后点击创建

这个过程非常快,所以最多需要一分钟就可以完成。

随着 AWS 资源的创建,我们可以继续构建了。

定义我们的团队建设

在这篇文章中,我使用 TeamCity 作为构建服务器来构建 Octo Pet Shop 应用程序作为 Docker 容器,并将它们推送到 AWS ECR。

创建项目连接

为了将我们的容器映像推送到 ECR,我们需要配置一个连接到 AWS ECR 的 TeamCity 项目。这是通过以下步骤实现的:

  1. 点击项目
  2. 选择要添加连接的项目。
  3. 点击编辑项目

TeamCity edit project button

  1. 点击连接然后添加连接
  2. 从下拉菜单中选择Amazon ECR,并填写以下值:
  • AWS 区域
  • 访问密钥 ID
  • 秘密访问密钥
  • 注册表 Id

TeamCity ECR connection settings

启用构建功能

在为 Docker 容器创建了构建定义之后,您需要启用 Docker 支持构建特性。为此,导航到您的构建定义并单击构建特性选项卡。点击添加构建特征按钮,选择对接支持。选择您为 ECR 创建的注册表并点击保存

添加您的构建步骤

Octo Pet Shop 应用程序由四个主要组件组成:

  • Web 前端
  • 产品服务
  • 购物车服务
  • DbUp 数据库迁移器

我的构建只包含六个步骤:

  1. 设置版本号
  2. 构建网站
  3. 建立产品服务
  4. 构建购物车服务
  5. 构建 DbUp
  6. 将 Docker 图像推送到 ECR

第二步到第五步构建各自的 Docker 图像,然后使用下面的模式octopetshop-<component>:%build.number% 968802670493.dkr.ecr.us-west-1.amazonaws.com/octopetshop-<component>:%build.number%标记它们。例如,这是 web 前端的标记方式:

octopetshop-web:%build.number% 968802670493.dkr.ecr.us-west-1.amazonaws.com/octopetshop-web:%build.number% 

第六步在一个步骤中将 Docker 映像推送到 ECR。它不使用build选项,而是使用push选项,如下所示:

968802670493.dkr.ecr.us-west-1.amazonaws.com/octopetshop-web:%build.number%
968802670493.dkr.ecr.us-west-1.amazonaws.com/octopetshop-productservice:%build.number%
968802670493.dkr.ecr.us-west-1.amazonaws.com/octopetshop-shoppingcartservice:%build.number%
968802670493.dkr.ecr.us-west-1.amazonaws.com/octopetshop-database:%build.number% 

运行构建

执行构建时,您应该会得到类似于以下内容的输出:

Step 6/6: Push OctoPetShop-Web (Docker)
15:57:57
  Starting: /bin/sh -c "docker push  968802670493.dkr.ecr.us-west-1.amazonaws.com/octopetshop-web:1.0.20226.225747 && docker push  968802670493.dkr.ecr.us-west-1.amazonaws.com/octopetshop-productservice:1.0.20226.225747 && docker push  968802670493.dkr.ecr.us-west-1.amazonaws.com/octopetshop-shoppingcartservice:1.0.20226.225747 && docker push  968802670493.dkr.ecr.us-west-1.amazonaws.com/octopetshop-database:1.0.20226.225747"
15:57:57
  in directory: /opt/buildagent/work/7f2634c2d5e5df05
15:57:57
  The push refers to repository [968802670493.dkr.ecr.us-west-1.amazonaws.com/octopetshop-web]
15:57:57
  dae5c6c2d080: Preparing
15:57:57
  92dbf6df1786: Preparing
15:57:57
  9d32e62891bc: Preparing
15:57:57
  b22206f4fa7b: Preparing
15:57:57
  b701a024aaa5: Preparing
15:57:57
  0b565516ff7f: Preparing
15:57:57
  91ab7edbc80b: Preparing
15:57:57
  9262398ff7bf: Preparing
15:57:57
  804aae047b71: Preparing
15:57:57
  5d33f5d87bf5: Preparing
15:57:57
  4e38024e7e09: Preparing
15:57:57
  0b565516ff7f: Waiting
15:57:57
  91ab7edbc80b: Waiting
15:57:57
  9262398ff7bf: Waiting
15:57:57
  804aae047b71: Waiting
15:57:57
  5d33f5d87bf5: Waiting
15:57:57
  4e38024e7e09: Waiting
15:58:00
  b22206f4fa7b: Pushed
15:58:01
  9d32e62891bc: Pushed
15:58:01
  dae5c6c2d080: Pushed
15:58:04
  92dbf6df1786: Pushed
15:58:16
  804aae047b71: Pushed
15:58:16
  91ab7edbc80b: Pushed
15:58:31
  5d33f5d87bf5: Pushed
15:58:52
  0b565516ff7f: Pushed
15:58:56
  9262398ff7bf: Pushed
15:59:02
  4e38024e7e09: Pushed
15:59:24
  b701a024aaa5: Pushed 

将图像推送到 ECR 后,我们可以跳到 Octopus Deploy 来创建部署流程。

章鱼部署

为了使用在我们的部署过程中被推送到 ECR 的图像,我们需要在 Octopus Deploy 中配置一个指向我们的 ECR 注册中心的外部提要。

创建 ECR 外部进料

在 Octopus Deploy 中创建外部提要非常容易。首先,导航到,然后选择外部馈送。在外部进给屏幕上,点击添加进给。从下拉框中选择 AWS 弹性容器注册表,并填写以下内容:

  • 名字
  • 存取关键字
  • 秘密钥匙
  • 地区

然后点击保存

创建 AWS 帐户

因为我使用访问密钥/秘密密钥组合方法对 AWS 进行认证,所以我需要在 Octopus 中创建一个 AWS 帐户来引用。

  1. 导航至基础设施,然后点击账户
  2. 账户屏幕上,点击添加账户按钮并选择 AWS 账户
  3. 填写所需的值:
  • 名字
  • 存取关键字
  • 秘密钥匙
  1. 点击保存

定义部署流程

这篇文章假设你对 Octopus Deploy 足够熟悉,可以创建一个项目,并将重点放在部署的 AWS Fargate 特定组件上。

变量

在开始添加步骤之前,我们首先需要添加一些在流程中使用的变量。我创建了一个库变量集来保存 AWS 的公共变量:

  • AWS.Account:这引用了我们在创建 AWS 帐户部分创建的 AWS 帐户。
  • AWS.Region.name:我们正在使用的地区名称。
  • AWS.SecurityGroup.Id:我们为 Octo 宠物店创建的安全组的 ID。
  • (可选)AWS.Subnet1.Id:要使用的子网 1 的 ID。
  • (可选)AWS.Subnet.Id:要使用的子网 2 的 ID。

其余的变量是项目变量:

  • Project.AWS.ECS.Cluster.Name:集群名称(如OctpousSamples-ECS)。
  • Project.AWS.ECS.Service.Name:要创建或更新的服务的名称(例如octopetshop)。
  • Project.AWS.Task.Database.Name:任务定义的数据库容器的名称(如octopetshop-database)。
  • Project.AWS.Task.ProductService.Name:任务定义的产品服务容器的名称(如octopetshop-productservice)。
  • Project.AWS.Task.ShoppingCartService.Name:任务定义的购物车服务容器的名称(如octopetshop-shoppingcartservice)。
  • Project.AWS.Task.Web.Name:任务定义的 web 容器名称(如octopetshop-web)。
  • Project.Container.Environment.ConnectionString:用作容器环境变量的数据库连接字符串(如Data Source=localhost;Initial Catalog=OctoPetShop; User ID=#{Project.Database.User.Name}; Password=#{Project.Database.User.Password})。
  • Project.Database.User.Name:数据库连接的用户名(如sa)。
  • Project.Database.User.Password:数据库连接的密码(如My$uper$3cretPassw0rd!)。

步伐

以下过程可以通过使用部署 Amazon ECS 服务步骤来替代(参见我们的示例以获取详细信息。)下面的脚本步骤是出于历史目的而保留的。

这个部署将包括两个步骤,它们都使用运行一个 AWS CLI 脚本。在撰写本文时,还没有任何 ECS 或 Fargate 特定的模板可用。

  • 创建任务定义
  • 运行任务
创建任务定义

此步骤为 ECS 运行创建任务定义。为了部署我们上传到 ECR 的图像,这个步骤将所有的图像作为包引用添加进来。这允许将来自 ECR 的图像添加到 Fargate 容器定义集合中。如果被引用的服务不存在,它将创建它,但是如果它存在,它将更新现有的服务。在注册任务定义之后,它将TaskDefinitionArn保存到一个输出变量中,以便在下一步中使用:

Package References

使用 Fargate 时,HostPort 和 ContainerPort 值必须匹配,否则将报告错误。

在步骤中引用包时,请务必将其标记为The package will not be acquired

$Region = $OctopusParameters["Octopus.Action.Amazon.RegionName"]
$TaskName = $OctopusParameters["Project.AWS.Task.Web.Name"]
$ExecutionRole = $(Get-IAMRole -RoleName "ecsTaskExecutionRole").Arn

# Add web settings
$webPortMappings = New-Object "System.Collections.Generic.List[Amazon.ECS.Model.PortMapping]"
$webPortMappings.Add($(New-Object -TypeName "Amazon.ECS.Model.PortMapping" -Property @{ HostPort=5000; ContainerPort=5000; Protocol=[Amazon.ECS.TransportProtocol]::Tcp}))
$webPortMappings.Add($(New-Object -TypeName "Amazon.ECS.Model.PortMapping" -Property @{ HostPort=5001; ContainerPort=5001; Protocol=[Amazon.ECS.TransportProtocol]::Tcp}))

$webEnvironmentVariables = New-Object "System.Collections.Generic.List[Amazon.ECS.Model.KeyValuePair]"
$webEnvironmentVariables.Add($(New-Object -TypeName "Amazon.ECS.Model.KeyValuePair" -Property @{ Name="ProductServiceBaseUrl"; Value="http://localhost:5011"}))
$webEnvironmentVariables.Add($(New-Object -TypeName "Amazon.ECS.Model.KeyValuePair" -Property @{ Name="ShoppingCartServiceBaseUrl"; Value="http://localhost:5012"}))

$ContainerDefinitions = New-Object "System.Collections.Generic.List[Amazon.ECS.Model.ContainerDefinition]"
$ContainerDefinitions.Add($(New-Object -TypeName "Amazon.ECS.Model.ContainerDefinition" -Property @{ `
Name=$OctopusParameters['Project.AWS.Task.Web.Name'];`
Image=$OctopusParameters["Octopus.Action.Package[octopetshop-web].Image"]; `
PortMappings=$webPortMappings; `
Environment=$webEnvironmentVariables;}))

# Add product service settings
$productServicePortMappings = New-Object "System.Collections.Generic.List[Amazon.ECS.Model.PortMapping]"
$productServicePortMappings.Add($(New-Object -TypeName "Amazon.ECS.Model.PortMapping" -Property @{ ContainerPort=5011; Protocol=[Amazon.ECS.TransportProtocol]::Tcp}))

$serviceEnvironmentVariables = New-Object "System.Collections.Generic.List[Amazon.ECS.Model.KeyValuePair]"
$serviceEnvironmentVariables.Add($(New-Object -TypeName "Amazon.ECS.Model.KeyValuePair" -Property @{ Name="OPSConnectionString"; Value=$OctopusParameters['Project.Container.Environment.ConnectionString']}))

$ContainerDefinitions.Add($(New-Object -TypeName "Amazon.ECS.Model.ContainerDefinition" -Property @{ `
Name=$OctopusParameters['Project.AWS.Task.ProductService.Name']; `
Image=$OctopusParameters["Octopus.Action.Package[octopetshop-productservice].Image"]; `
PortMappings=$productServicePortMappings; `
Environment=$serviceEnvironmentVariables;}))

# Add shopping cart service settings
$shoppingCartServicePortMappings = New-Object "System.Collections.Generic.List[Amazon.ECS.Model.PortMapping]"
$shoppingCartServicePortMappings.Add($(New-Object -TypeName "Amazon.ECS.Model.PortMapping" -Property @{ ContainerPort=5012; Protocol=[Amazon.ECS.TransportProtocol]::Tcp}))

$ContainerDefinitions.Add($(New-Object -TypeName "Amazon.ECS.Model.ContainerDefinition" -Property @{ `
Name=$OctopusParameters['Project.AWS.Task.ShoppingCartService.Name']; `
Image=$OctopusParameters["Octopus.Action.Package[octopetshop-shoppingcartservice].Image"]; `
PortMappings=$shoppingCartServicePortMappings; `
Environment=$serviceEnvironmentVariables;}))

# Add sql server settings
$sqlPortMappings = New-Object "System.Collections.Generic.List[Amazon.ECS.Model.PortMapping]"
$sqlPortMappings.Add($(New-Object -TypeName "Amazon.ECS.Model.PortMapping" -Property @{ HostPort=1433; ContainerPort=1433; Protocol=[Amazon.ECS.TransportProtocol]::Tcp}))

$sqlEnvironmentVariables = New-Object "System.Collections.Generic.List[Amazon.ECS.Model.KeyValuePair]"
$sqlEnvironmentVariables.Add($(New-Object -TypeName "Amazon.ECS.Model.KeyValuePair" -Property @{ Name="ACCEPT_EULA"; Value="Y"}))
$sqlEnvironmentVariables.Add($(New-Object -TypeName "Amazon.ECS.Model.KeyValuePair" -Property @{ Name="SA_PASSWORD"; Value=$OctopusParameters['Project.Database.User.Password']}))

$ContainerDefinitions.Add($(New-Object -TypeName "Amazon.ECS.Model.ContainerDefinition" -Property @{ `
Name="sqlserver"; `
Image=$OctopusParameters["Octopus.Action.Package[mssql-server-linux].Image"]; `
PortMappings=$sqlPortMappings; `
Environment=$sqlEnvironmentVariables;}))

# Add DBUp project
$dbupEnvironmentVariables = New-Object "System.Collections.Generic.List[Amazon.ECS.Model.KeyValuePair]"
$dbupEnvironmentVariables.Add($(New-Object -TypeName "Amazon.ECS.Model.KeyValuePair" -Property @{ Name="DbUpConnectionString"; Value=$OctopusParameters['Project.Container.Environment.ConnectionString']}))

$ContainerDefinitions.Add($(New-Object -TypeName "Amazon.ECS.Model.ContainerDefinition" -Property @{ `
Name="octopetshop-database"; `
Image=$OctopusParameters["Octopus.Action.Package[octopetshop-database].Image"]; `
Essential=$false; `
Environment=$dbupEnvironmentVariables;}))

$TaskDefinition = Register-ECSTaskDefinition `
-ContainerDefinition $ContainerDefinitions `
-Cpu 512 `
-Family $TaskName `
-TaskRoleArn $ExecutionRole `
-ExecutionRoleArn $ExecutionRole `
-Memory 4096 `
-NetworkMode awsvpc `
-Region $Region `
-RequiresCompatibility "FARGATE"

if(!$?)
{
    Write-Error "Failed to register new task definition"
    Exit 0
}

$ClusterName = $OctopusParameters["Project.AWS.ECS.Cluster.Name"]
$ServiceName = $OctopusParameters["Project.AWS.ECS.Service.Name"]

# Check to see if there is a service already
$service = (Get-ECSService -Cluster $ClusterName -Service $ServiceName)

if ($service.Services.Count -eq 0)
{
    Write-Host "Service $ServiceName doesn't exist, creating ..."
    $ServiceCreate = New-ECSService `
        -Cluster $ClusterName `
        -ServiceName $ServiceName `
        -TaskDefinition $TaskDefinition.TaskDefinitionArn `
        -DesiredCount 1 `
        -AwsvpcConfiguration_Subnet @($OctopusParameters['AWS.Subnet1.Id'], $OctopusParameters['AWS.Subnet2.Id']) `
        -AwsvpcConfiguration_SecurityGroup @($OctopusParameters['AWS.SecurityGroup.Id'])
}
else
{
    $ServiceUpdate = Update-ECSService `
        -Cluster $ClusterName `
        -ForceNewDeployment $true `
        -Service $ServiceName `
        -TaskDefinition $TaskDefinition.TaskDefinitionArn `
}

if(!$?)
{
    Write-Error "Failed to register new task definition"
    Exit 0
}

# Save task definition to output variable
Set-OctopusVariable -Name "TaskDefinitionArn" -Value $TaskDefinition.TaskDefinitionArn 
运行任务

注册任务定义后,我们使用另一个Run an AWS CLI script步骤来创建在 Fargate 中运行的任务:

# Assign local variables
$clusterName = $OctopusParameters['Project.AWS.ECS.Cluster.Name']
$securityGroups = @($OctopusParameters['AWS.SecurityGroup.Id'])
$subnets = @($OctopusParameters['AWS.Subnet1.Id'], $OctopusParameters['AWS.Subnet2.Id'])
$taskDefinitionArn = $OctopusParameters['Octopus.Action[Create task definition].Output.TaskDefinitionArn']

# Create launch type object
$launchType = $(New-Object -TypeName "Amazon.ECS.Launchtype" -ArgumentList "FARGATE")

# Create Public IP object
$assignPublicIp = $(New-Object -TypeName "Amazon.ECS.AssignPublicIp" -ArgumentList "ENABLED")

# Create new task
New-ECSTask -Cluster $clusterName `
    -AwsvpcConfiguration_SecurityGroup $securityGroups `
    -AwsvpcConfiguration_Subnet $subnets `
    -TaskDefinition $taskDefinitionArn `
    -LaunchType $launchType `
    -AwsvpcConfiguration_AssignPublicIp $assignPublicIp 

The deployment process steps

部署

部署完成后,您将看到部署的任务摘要:

Deployment task summary

法尔盖特

部署完成后,任务将显示为挂起状态。给任务一些时间达到运行状态。在数据库容器达到停止状态后,Octo Pet Shop 应用程序将可用。

【T2 AWS ECS Fargate running task

此时,我们可以打开浏览器,进入http://[PublicIP]:5000。在此导航会自动将我们重定向到https://[PublicIP]:5001。你会看到一个关于无效证书的警告,但是 Octo 宠物店使用的是自签名证书,所以这个警告是正常的,去 Octo 宠物店是安全的!

Octo Pet Shop

结论

在本文中,我带您创建了一个 CI/CD 流程,将 Octo Pet Shop 应用程序部署到 AWS Fargate。
愉快的部署!

使用 AWS FSx - Octopus Deploy 配置 Octopus 服务器高可用性

原文:https://octopus.com/blog/aws-fsx-ha

Configuring Octopus Server High Availability using AWS FSx

当你为 Octopus 在 AWS 上配置高可用性时,你需要像微软 DFS 这样的共享存储(如文档所述)或者你可以使用亚马逊的 FSx。在本文中,我演示了如何将 FSx 与安装在 EC2 实例上的 Octopus Deploy 一起使用。

亚马逊 FSx

FSx 是一种网络文件系统,有两种类型:

在这篇文章中,我们使用 FSx 的 Windows 文件服务器。

配置 FSx

像许多其他亚马逊 AWS 产品一样,FSx 附带了一个方便易用的配置向导。首先,打开您的 AWS 管理控制台并点击位于存储类别下的 FSx。

与 AWS 拥有的其他文件存储选项不同,用于 Windows 文件服务器的 FSx需要 Active Directory。如果您正在创建活动目录,请注意活动目录服务不是免费的,请参考他们的定价页面了解详情。

步骤 1:选择文件系统类型

这个过程的第一步是选择您将要使用的文件系统类型。如前所述,您的选择是:

  • 用于 Windows 文件服务器的 FSx
  • 这篇文章使用的是 Windows 文件服务器的 FSx。

步骤 2:指定文件系统详细信息

接下来,您将定义由几个部分组成的文件系统细节。

文件系统详细信息

本节定义了以下内容:

  • 部署类型
  • 存储类型
    • (同 solid-statedisk)固态(磁)盘
    • HDD(单 AZ 文件系统不支持)
  • 存储容量(最低 32 GB)

网络与安全

在本部分中,您将选择网络和安全组(防火墙)组件:

  • VPC
  • VPC 安全集团
  • 子网络

Windows 身份验证

对于此部分,您需要选择活动目录的详细信息。您可以选择:

  • AWS 管理的 Microsoft Active Directory
  • 自我管理的 Microsoft Active Directory

您还可以创建新的活动目录(如果使用 AWS 管理的 Microsoft 活动目录)。

加密

这是您定义加密密钥的部分,以便您的数据在静态时得到保护。您的选择是:

接下来的两个部分是可选的:

  • 备份和维护首选项
  • 标签

步骤 3:检查和创建

该过程的最后一步是在创建之前检查您为文件服务器选择的选项。在此页面上,它将向您显示在创建文件系统后哪些选项可以更改,哪些不能编辑。当您对自己的选择感到满意时,点击创建文件系统

连接到共享

创建 FSx 文件系统后,它将在 Active Directory 网络上可用。

获取 DNS 名称

为了连接到共享,我们需要知道端点是什么。创建文件系统后,单击Storage类别下的FSx:

找到您创建的文件系统,然后单击名称链接:

复制文件系统共享的 DNS 名称:

连接到 FSx

现在我们有了 FSx 文件系统的 DNS 名称,我们可以使用该 DNS 名称连接到它。

RDP 呼叫章鱼服务器。登录后,打开命令提示符,并确保通过 pinging 名称可以访问 FSx 文件系统:

根据您的安全组,ping 命令可能会失败,但我们可以看到它解析到了正确的 IP 地址。现在我们知道 EC2 实例可以找到 FSx 文件系统,打开一个文件资源管理器窗口,UNC 到 FSx。FSx 的默认共享名是简单的share,所以您的 UNC 命令将如下所示:

\\fs-01d557023313c0f5a.octo.fsx\share 

配置 Octopus Deploy 以使用 FSx

一旦配置完成,FSx 就像一个文件服务器。这意味着您加入域的 EC2 实例可以通过 UNC 引用它。使用配置高可用性(HA) 共享存储的指导,我们可以将 Octopus 存储工件、任务日志、内置存储库和遥测数据的位置从 EC2 实例转移到 FSx。配置共享存储有两个选项:

  • 设置根共享存储目录。
  • 单独设置每个目录。

要设置共享存储根目录,请执行以下操作:

Octopus.Server.exe path --clusterShared \\fs-01d557023313c0f5a.octo.fsx\share 

要单独设置每个文件夹:

Octopus.Server.exe path --artifacts \\fs-01d557023313c0f5a.octo.fsx\share\Artifacts
Octopus.Server.exe path --taskLogs \\fs-01d557023313c0f5a.octo.fsx\share\TaskLogs
Octopus.Server.exe path --nugetRepository \\fs-01d557023313c0f5a.octo.fsx\share\Packages
Octopus.Server.exe path --telemetry \\fs-01d557023313c0f5a.octo.fsx\share\Telemetry 

如果您的 Octopus 服务器不是一个全新的实例,您需要将工件、任务日志和包文件夹中的内容复制到新的位置,以便它们在 Octopus 中显示。

现在,您的 Octopus 服务器使用共享位置,您就可以添加新节点来配置 HA 了!

结论

在这篇文章中,我向您展示了如何创建和配置 AWS FSx,并将其与 Octopus Deploy 连接。愉快的部署!

在 Octopus - Octopus 部署中使用 AWS IAM 角色

原文:https://octopus.com/blog/aws-iam-roles

Using AWS IAM roles in Octopus

管理云提供商的凭证是一项挑战,尤其是当您考虑到您没有足够的物理安全性时,这意味着一个泄露的管理密钥就可以允许从世界任何地方访问您的整个帐户。云帐户也不能幸免于众所周知的“rm -rf”场景,即管理员帐户意外删除了他们不应该删除的资源。

IAM 角色可用于提供特定于任务的授权,当角色被分配给 EC2 实例时,有权访问该 VM 的用户可以继承该角色。

在这篇博文中,我们将看看 AWS 中的 IAM 角色,并了解如何在 Octopus 中使用它们。

创建一个角色

可以在 AWS IAM 控制台中创建角色。当您创建一个新角色时,您将看到该角色将应用到的服务列表。在默认选择 AWS 服务的情况下,点击 EC2 链接:

Create role

我们不会为该角色附加任何权限或标签,因此请跳到最后,为该角色命名并创建它:

Finish creating the role

打开新创建的角色,点击信任关系选项卡,点击编辑信任关系:

Trust relationship

信任关系是一个 JSON 结构,它配置哪个服务或用户可以继承角色。因为我们在创建角色时选择了 EC2 服务,所以 EC2 服务被授予了承担该角色的能力:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
} 

将角色分配给 EC2 实例

在创建新的 EC2 实例时,可以将该角色分配给它:

Assign a role to an EC2 instance

当您登录到这个实例时,可以从实例元数据 HTTP 接口获得分配给虚拟机的角色名称,命令如下:

curl http://169.254.169.254/latest/meta-data/iam/security-credentials/ 

我们还可以通过 AWS CLI 使用以下命令来验证角色:

aws sts get-caller-identity 

在这两种情况下,我们都看到了角色 mytestrole :

Terminal output

这意味着知道实例元数据 HTTP 接口的工具可以使用分配给 EC2 实例的角色进行操作。只要您能够访问 VM,就可以使用关联的 IAM 角色与 AWS 进行交互。

AWS CLI 是了解实例元数据的工具的一个例子,Octopus Tentacles 和 Workers 是另一个例子。我们可以利用 EC2 IAM 角色和 Octopus Workers 在没有任何 AWS 凭证的情况下对 AWS 服务运行命令。

EC2 作为八达通工人

为了连接到 Linux 虚拟机,我们需要用创建虚拟机时使用的证书创建一个 SSH 密钥对帐户。对于使用 Amazon AMIs 创建的虚拟机,用户名为 ec2-user :

Key pair account

然后,我们可以创建一个 SSH 工作线程并连接到虚拟机:

Creating a Worker

现在,我们可以添加一个运行 AWS CLI 脚本步骤,设置脚本在包含我们刚刚创建的 worker 的 Worker 池上运行,并选择选项使用 AWS 服务角色执行 EC2 实例:

AWS service role

现在,如果我们在这个脚本中运行命令aws sts get-caller-identity,我们会看到与之前相同的结果:

Task log

我们现在能够执行部署和脚本,而不需要通过 Worker 和 IAM 角色共享 AWS 凭据,它从底层虚拟机中承担 IAM 角色。

承担 Kubernetes 目标的角色

使用 Octopus 2020.4.0,还可以使用与虚拟机关联的 IAM 角色与 EKS 集群进行交互。

但是,为了让 IAM 角色在 Kubernetes 集群中拥有任何权限,我们需要将 IAM 角色映射到 Kubernetes 角色。这个映射是在kube-system名称空间中的aws-auth配置映射中完成的。

该文件的默认内容类似于下面的例子。但是,请注意,现有的角色映射特定于每个 EKS 集群,因为它允许分配给节点的角色访问集群。您需要从集群中修改配置映射,而不是复制并粘贴如下所示的配置映射,因为直接使用此示例会导致错误:

apiVersion: v1
data:
  mapRoles: |
    - groups:
      - system:bootstrappers
      - system:nodes
      rolearn: arn:aws:iam::968802670493:role/eksctl-k8s-cluster-nodegroup-ng-1-NodeInstanceRole-1FI6JXPS9MDWK
      username: system:node:{{EC2PrivateDNSName}}
  mapUsers: |
    []
kind: ConfigMap
metadata:
  name: aws-auth
  namespace: kube-system 

在我的例子中,我在mytestrole IAM 角色和 Kubernetes system:masters角色之间添加了一个新的角色映射:

apiVersion: v1
data:
  mapRoles: |
    - groups:
      - system:bootstrappers
      - system:nodes
      rolearn: arn:aws:iam::968802670493:role/eksctl-k8s-cluster-nodegroup-ng-1-NodeInstanceRole-1FI6JXPS9MDWK
      username: system:node:{{EC2PrivateDNSName}}
    - rolearn: arn:aws:iam::968802670493:role/mytestrole
      username: admin
      groups:
        - system:masters
  mapUsers: |
    []
kind: ConfigMap
metadata:
  name: aws-auth
  namespace: kube-system 

在应用了这个配置映射之后,我们可以使用 EC2 实例的 AWS 服务角色将我们的 Kubernetes 目标配置为执行:

EKS IAM Role

我们还需要确保目标使用 Worker 连接到应用了 IAM 角色的 EC2 实例:

EKS Worker

我们的 Kubernetes 目标现在将在没有任何 AWS 凭证的情况下完成运行状况检查,取而代之的是,使用分配给虚拟机的 IAM 角色,工作人员连接到:

EKS health check

结论

在本文中,我们创建了一个可以分配给 EC2 实例的 IAM 角色。我们登录到 EC2 实例,并验证 AWS CLI 显示本地用户具有 EC2 角色。然后,我们将一个 Worker 连接到 EC2 实例,并从 Octopus 运行 AWS CLI 脚本和 Kubernetes 健康检查,Octopus 还承担了分配给 EC2 实例的角色。

为 EC2 实例分配角色是消除共享 AWS 凭证需求的一种便捷方式,在 Octopus 2020.4 中,在 steps 和 Kubernetes 目标中提供了对承担这些角色的完全支持。

相信我-分配和承担 IAM 角色- Octopus 部署

原文:https://octopus.com/blog/aws-roles

AWS 允许向 EC2 实例这样的资源分配 IAM 角色。实际上,这给了在 EC2 实例上运行的应用程序该角色的权限。这意味着代码本身和运行代码的进程都不需要提供任何凭证或密钥,这在设计部署实践时非常方便。

在这篇博文中,我们将看看如何将角色分配给 EC2 实例,然后用来承担次要角色。

将角色分配给 EC2 实例

我们将从一个没有角色的 EC2 实例和一个名为ExampleRole的 IAM 角色开始,该角色没有附加策略。可以使用以下命令将角色分配给现有 EC2 实例:

aws ec2 associate-iam-instance-profile --instance-id i-0123456789abcdef0 --iam-instance-profile Name="ExampleRole" 

输出将类似于:

{
    "IamInstanceProfileAssociation": {
        "AssociationId": "iip-assoc-0123456789abcdef0",
        "InstanceId": "i-0123456789abcdef0",
        "IamInstanceProfile": {
            "Arn": "arn:aws:iam::123456789012:instance-profile/ExampleRole",
            "Id": "ABCDEFGHIJKLMNOPQRSTU"
        },
        "State": "associating"
    }
} 

EC2 实例现在被分配了一个角色。

您可以在创建 EC2 实例的过程中使用IAM role下拉菜单分配一个角色。

EC2 Role

创建信任策略

在 EC2 实例可以使用分配的角色之前,该角色需要给予 EC2 服务这样做的权限。这是通过将以下策略分配给ExampleRole信任关系来实现的。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
} 

Trust Relationships

从实例角色生成密钥

分配给 EC2 实例的角色列表可以从实例元数据中找到。这是通过一个私有的 HTTP API 访问的,该 API 可以在HTTP://169 . 254 . 169 . 254/latest/metadata下访问。

要获得与实例相关联的角色,请从 EC2 实例本身运行这个命令。这个 URL 末尾的斜线很重要。

curl http://169.254.169.254/latest/meta-data/iam/security-credentials/ 

输出应该是带有角色名称的单行。

ExampleRole 

该命令将返回访问、机密和会话令牌密钥。

curl http://169.254.169.254/latest/meta-data/iam/security-credentials/ExampleRole 

该调用的结果是以下 JSON:

{
  "Code" : "Success",
  "LastUpdated" : "2018-01-25T23:15:40Z",
  "Type" : "AWS-HMAC",
  "AccessKeyId" : "AKJBD787KHV7JHV7JGC9",
  "SecretAccessKey" : "oqfqoufbqow/qwobOUBuIViyciycIY7ivy7gcUCj",
  "Token" : "FQoDYXdzEID//////////wEaDMb+nhXW1CzKSssr7iK3A6xOpwS5AQ+Rx/3ZsvWa2mcnZv/e3LHOSU9oNShnnL91uu8NHiiZyKVhoH/G/58WwEUtMf5tZKLT05Rv7ihLNdhsyJ9YkIGplsnl3KQUGEVbg1yfjOLzEHcBcqzYorwVyqDyeo7xoo4CGqxMzjcBApKDTRRA8X146raCb1/marnHzhqDqpfJykKr8WXAhbIfxHTNPdKFIa7Fm9h1mNsbTZOy8obwMR3tOf88HtmxVSPpISSn3VTzcEpZT9VHDe6O7HEtTrTn9Phid0/Fq5KUh4KOgxDfzLSfJiGGTqs8wF99310DUdK8bQFLemrrOmq2iUhVv0SkH4MKlkMY8B7/T1gqoN1JaQ9xUXZL2J6ZDPFSmRmx7GiR8emN17wQAH0+VMVlrl0sXX8IkMoZyd3M5S9VRF4csRYyzPGhTBwYuBsf/85ANKc/k/K2/uBLqpabsqt+ccleU7A2PrcFqRL+MpZDfPotWspT6b9Q2By7D3X/5NtnIjIxutvdcn2MN38CCi1l/cYuc9iJwZVY2eluV5HXZbyV0oMwi9bYy7BZz0e/54tauABSf3s6JSmfBw0NByUEn5aZeT4o88mp0wU=",
  "Expiration" : "2018-01-26T05:40:31Z"
} 

要使用这些键,通常要将它们分配给环境变量。

JSON 字段 环境变量
AccessKeyId AWS_ACCESS_KEY_ID
秘钥 AWS_SECRET_ACCESS_KEY
代币 AWS _ 会话 _ 令牌

在 AWS CLI 中使用角色凭据

尽管可以查询实例元数据并根据键创建环境变量,但是很多工具已经知道如何自己查询实例元数据。AWS CLI 就是一个很好的例子。

不创建任何环境变量或运行aws configure将任何键保存在本地配置文件中,运行命令:

aws sts get-caller-identity 

将导致:

{
    "Account": "123456789012",
    "UserId": "ABCDEFGHIJKLMNOPQRSTU:i-0123456789abcdef0",
    "Arn": "arn:aws:sts::123456789012:assumed-role/ExampleRole/i-0123456789abcdef0"
} 

在这种情况下,AWS CLI 知道如何从实例元数据生成密钥,如果没有其他密钥、环境变量或配置文件,它会自动生成密钥。

承担次要角色

从分配给 EC2 实例的角色中,我们可以假设一个次要角色。次要角色可能用于测试权限,或者以与使用sudo命令相同的方式运行具有额外权限的流程。

让我们假设我们有第二个角色叫做ExampleAssumedRole,我们想从ExampleRole开始承担这个角色。

第一步是给ExampleRole承担ExampleAssumedRole的权限。这是通过ExampleRole上的以下策略完成的:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Stmt1512947264000",
            "Effect": "Allow",
            "Action": [
                "sts:AssumeRole"
            ],
            "Resource": [
                "arn:aws:iam::123456789012:role/ExampleAssumedRole"
            ]
        }
    ]
} 

那么ExampleAssumedRole需要更新为信任ExampleRole:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::123456789012:role/ExampleRole"
      },
      "Action": "sts:AssumeRole"
    }
  ]
} 

有了这些策略,运行以下命令来承担一个角色:

aws sts assume-role --role-arn arn:aws:iam::123456789012:role/ExampleAssumedRole --role-session-name MySession 

然后结果包含AccessKeyIdSecretAccessKeySessionToken,它们可以按照Generating Keys from an Instance Role中描述的相同方式分配给环境变量:

{
    "AssumedRoleUser": {
        "AssumedRoleId": "ABCDEFGHIJKLMNOPQRSTU:MySession",
        "Arn": "arn:aws:sts::123456789012:assumed-role/ExampleAssumedRole/MySession"
    },
    "Credentials": {
        "SecretAccessKey": "oqfqoufbqow/qwobOUBuIViyciycIY7ivy7gcUCj",
        "SessionToken": "FQoDYXdzEIH//////////wEaDB9lgc8b8VS+LXRmliLtAdYWQNM1RnhGG/UdRszkg1xOtCIVevt7W34A4Lu1McUpEMVsFUrhEYIZR3fVFbPP6dwnxQ/H78jN1jKZuXgXPIH00NA3PtvxR8zcHDmkVeeCrnz+TiNk5k8/Tzh1qyzaH29sPY6oXhLCfsKSaQkw3nGd5RoslByOnNywVtJc762ke4F9YXAZffelSmQIhKdntqQj7L+DDAijRmjxCjadItJz7oxRdkN11ez13dny1wzIdPC7vuszivgF9+uACjZFQxgPS95f7w1VOhcCtmSMt9ErZd29BWdiO5CPr2ytBVEhNG7URgljEup2zqLCTCjc1qnTBQ==",
        "Expiration": "2018-01-26T00:42:20Z",
        "AccessKeyId": "AKJBD787KHV7JHV7JGC9"
    }
} 

结论

AWS 提供了一个灵活的安全系统,允许将角色分配给 EC2 实例,并允许承担次要角色。这允许在应用程序或脚本中不嵌入密钥的情况下分配权限,并允许使用不同的权限运行进程,就像使用sudo命令一样。

使用 Octopus - Octopus Deploy 部署 AWS SAM 模板

原文:https://octopus.com/blog/aws-sam-and-octopus

Illustration showing an AWS SAM deployment with Octopus Deploy

随着微服务等开发模式越来越受欢迎,云提供商正在大力投资无服务器计算平台,作为管理和执行许多小型独立应用程序的一种方式。

AWS 无服务器应用程序模型(AWS SAM)将部署无服务器应用程序时常用的 AWS 服务联系在一起。AWS SAM 构建在 CloudFormation 之上,删除了许多常见的样板代码,使得无服务器应用程序的部署变得快速而简单。

在这篇博客文章中,我们将探讨如何从 AWS SAM 提供的简单部署流程过渡到 Octopus 中跨多个环境的可重复部署。

这篇博文的源代码是这里

Hello World 应用程序

我们将从使用 SAM CLI 工具创建的 Python Hello World 应用程序开始。创建该应用程序的过程记录在这里

由 SAM CLI 命令sam init --runtime python3.7sam build生成的样本代码已经提交给了一个 GitHub repo ,我们将使用这个 repo 作为我们部署过程的起点。

用 Github 动作构建应用程序

如果您遵循典型的 AWS SAM 工作流,在运行了sam initsam build之后,您将运行类似于sam package --s3-bucket <yourbucket>的命令,这将:

  • 将您的源代码与任何依赖项捆绑在一起。
  • 将包上传到 S3。
  • 将 SAM 模板中的CoreUri字段替换为文件在 S3 的位置。

值得注意的是,CloudFormation 和 SAM 不会从您的本地 PC 部署代码;一切都必须上传到 S3。sam package命令的好处是它为您自动完成工作,产生一个经过处理的模板,然后您可以用 CloudFormation 部署它。

然而,当您需要跨多个环境实现可重复的部署时,sam package命令可能有点笨拙。上传的 S3 文件有一个类似于fecddec7c6c40bd9de28f1775cd11e0e的随机生成的名字,这使得几乎不可能找出哪个代码包是为给定版本部署的。您还负责保存一份处理过的模板文件的副本(也就是更新了CoreUri字段的那个),这样您就可以跟踪哪个模板与哪个代码相关联。

或者引用 SAM 开发者自己的话:

是的,“山姆包”是初步的。真正的解决方案是创建一个更好的包命令,更好地进行内容寻址(可以使用 git sha、content sha 或客户提供的命名函数)。

我们将采用稍微不同的方法,管理打包和上传包,并创建一个通用模板文件,可以在部署期间由 Octopus 更新。这将为我们提供合理的文件名,并创建可重用的模板。

我们将通过 GitHub 操作实现这一点,工作流 YAML 如下:

name: Python package

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v1
    - name: Get Git Version
      uses: docker://mcasperson/gitversion:5.0.2-linux-centos-7-netcoreapp2.2
      with:
        args: /github/workspace /nofetch /exec /bin/sh /execargs "-c \"echo $GitVersion_FullSemVer > /github/workspace/version.txt\""
    - name: Set up Python 3.7
      uses: actions/setup-python@v1
      with:
        python-version: 3.7
    - name: Package dependencies
      # Permissions are documented at
      # https://docs.aws.amazon.com/lambda/latest/dg/deployment-package-v2.html
      run: |
        python -m pip install --upgrade pip
        cd hello_world
        pip download -r requirements.txt
        unzip \*.whl
        rm *.whl
        chmod 644 $(find . -type f)
        chmod 755 $(find . -type d)
    - name: Extract Octopus Tools
      run: |
        mkdir /opt/octo
        cd /opt/octo
        wget -O /opt/octo/octopus.zip https://download.octopusdeploy.com/octopus-tools/6.12.0/OctopusTools.6.12.0.portable.zip
        unzip /opt/octo/octopus.zip
        chmod +x /opt/octo/Octo
    - name: Pack Application
      run: |
        cd /home/runner/work/AWSSamExample/AWSSamExample/hello_world
        zip -r /home/runner/work/AWSSamExample/AWSSamExample/AwsSamLambda.$(cat /home/runner/work/AWSSamExample/AWSSamExample/version.txt).zip *
    - name: Push to Octopus
      run: >-
        /opt/octo/Octo push
        --server ${{ secrets.MATTC_URL }}
        --apiKey ${{ secrets.MATTC_API_KEY }}
        --package /home/runner/work/AWSSamExample/AWSSamExample/AwsSamLambda.$(cat /home/runner/work/AWSSamExample/AWSSamExample/version.txt).zip
        --overwrite-mode IgnoreIfExists
        --space Lambda
    - name: Pack Templates
      run: >-
        /opt/octo/Octo pack
        --outFolder /home/runner/work/AWSSamExample/AWSSamExample
        --basePath /home/runner/work/AWSSamExample/AWSSamExample
        --id AwsSamLambdaTemplates
        --version $(cat /home/runner/work/AWSSamExample/AWSSamExample/version.txt)
        --include s3bucket.yaml
        --include template.yaml
        --format zip
    - name: Push to Octopus
      run: >-
        /opt/octo/Octo push
        --server ${{ secrets.MATTC_URL }}
        --apiKey ${{ secrets.MATTC_API_KEY }}
        --package /home/runner/work/AWSSamExample/AWSSamExample/AwsSamLambdaTemplates.$(cat /home/runner/work/AWSSamExample/AWSSamExample/version.txt).zip
        --overwrite-mode IgnoreIfExists
        --space Lambda 

这个工作流程有两个部分,允许我们复制由sam package命令提供的功能。

第一部分是下载 Python 依赖项,提取它们,并在文件上设置权限。我们使用pip命令下载依赖项并提取下载的车轮文件(或whl文件,它们是具有不同扩展名的 ZIP 文件),其中包含依赖项代码:

- name: Package dependencies
  # Permissions are documented at
  # https://docs.aws.amazon.com/lambda/latest/dg/deployment-package-v2.html
  run: |
    python -m pip install --upgrade pip
    cd hello_world
    pip download -r requirements.txt
    unzip \*.whl
    rm *.whl
    chmod 644 $(find . -type f)
    chmod 755 $(find . -type d) 

第二部分是我们创建带有有意义的版本号的 zip 文件。如果你回头看看工作流 YAML,你会看到我们已经使用 GitVersion 生成了这个版本号。博客文章向你的 GitHub 动作添加版本更详细地介绍了版本控制是如何工作的。

我们使用zip工具打包 Python 代码,而不是octo cli,因为 AWS 对 zip 文件中文件的权限非常挑剔。zip工具创建正确的权限,而octo pack命令会产生一个无法部署的 ZIP 文件。

- name: Pack Application
  run: |
    cd /home/runner/work/AWSSamExample/AWSSamExample/hello_world
    zip -r /home/runner/work/AWSSamExample/AWSSamExample/AwsSamLambda.$(cat /home/runner/work/AWSSamExample/AWSSamExample/version.txt).zip * 

我们创建第二个包来保存模板。我们这里有两个模板(s3bucket.yamltemplate.yaml)。稍后将更详细地介绍这些模板:

- name: Pack Templates
  run: >-
    /opt/octo/Octo pack
    --outFolder /home/runner/work/AWSSamExample/AWSSamExample
    --basePath /home/runner/work/AWSSamExample/AWSSamExample
    --id AwsSamLambdaTemplates
    --version $(cat /home/runner/work/AWSSamExample/AWSSamExample/version.txt)
    --include s3bucket.yaml
    --include template.yaml
    --format zip 

然后这些包通过任务调用octo push被推送到 Octopus 服务器。

至此,我们已经将应用程序代码和模板上传到 Octopus 服务器,准备进行部署。我们已经复制了sam package命令的捆绑功能,下一步是复制到 S3 的推送。

用八达通上传包裹

在我们推进到 S3 之前,我们将创建 S3 桶。这将通过标准的云形成模板来实现。在此模板中,我们将指定在一定时间后删除此 S3 存储桶中的文件。

通常情况下,sam package命令会将文件推送到 S3,并永久保存在那里。然而,一旦该文件被用于完成部署,它就不再是必需的了,并且由于这些文件花费了我们的钱,所以在一段时间后清理它们是有意义的。

如果我们需要重新部署某个版本的应用程序,Octopus 会重新上传文件:

AWSTemplateFormatVersion: 2010-09-09
Description: Creates an S3 bucket that cleans up old files
Resources:
  CodeBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: "#{S3BucketName}"
      AccessControl: Private
      VersioningConfiguration:
        Status: Enabled
      LifecycleConfiguration:
        Rules:
          - NoncurrentVersionExpirationInDays: 3
            ExpirationInDays: 5
            Status: Enabled 

注意BucketName属性已经被定义为一个章鱼变量。在部署期间,标记#{S3BucketName}将被替换为S3BucketName章鱼变量的值。

该模板与Deploy an AWS CloudFormation template步骤一起部署。

CloudFormation 设置将区域和堆栈名称定义为变量。随着我们转向跨多个环境的部署,这一点将非常重要:

CloudFormation 模板来自名为AwsSamLambdaTemplates的包,包中有一个名为s3bucket.yaml的文件:

创建了 bucket 之后,应用程序包将通过Upload a package to an AWS S3 bucket步骤上传。

在这一步中唯一需要注意的是,我们再次为时段名称和 AWS 区域使用了一个变量:

至此,我们已经复制了sam package命令的功能,使用 GitHub Actions 捆绑了一个自包含的应用程序包,并使用 Octopus 将其推送到 S3。

我们还确保了我们上传的包有像AwsSamLambda.0.1.0+71.zip这样易读的名字,这清楚地表明了它们包含的应用程序和版本。从下面的截图可以看出,我们(AwsSamLambda.0.1.0+xx.zip)上传的包比sam package ( fecddec7c6c40bd9de28f1775cd11e0e)上传的包提供了更多的上下文:

用 Octopus 部署模板

最后一步是将 SAM 模板部署为 CloudFormation 模板。

这个模板几乎是由sam init --runtime python3.7命令生成的模板的翻版。

第一个区别是我们将OpenApiVersion的值设置为2.0。这修复了这里描述的问题,SAM 创建了第二个不需要的 API 网关阶段Staging

第二个区别是我们将CodeUri属性设置为"s3://#{Octopus.Action[Upload Lambda to S3].Aws.S3.BucketName}/#{Octopus.Action[Upload Lambda to S3].Output.Package.FileName}"。这些变量替换组合起来为我们提供了在前面的Upload Lambda to S3步骤中上传到 S3 的文件的名称。

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Example SAM application

# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
  Api:
    OpenApiVersion: '2.0'
  Function:
    Timeout: 3

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      CodeUri: "s3://#{Octopus.Action[Upload Lambda to S3].Aws.S3.BucketName}/#{Octopus.Action[Upload Lambda to S3].Output.Package.FileName}"
      Handler: app.lambda_handler
      Runtime: python3.7
      Events:
        HelloWorld:
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            Path: /hello
            Method: get

Outputs:
  # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
  # Find out more about other implicit resources you can reference within SAM
  # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
  HelloWorldApi:
    Description: "API Gateway endpoint URL for Prod stage for Hello World function"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
  HelloWorldFunction:
    Description: "Hello World Lambda Function ARN"
    Value: !GetAtt HelloWorldFunction.Arn
  HelloWorldFunctionIamRole:
    Description: "Implicit IAM Role created for Hello World function"
    Value: !GetAtt HelloWorldFunctionRole.Arn 

和以前一样,这个模板是通过Deploy an AWS CloudFormation template步骤部署的。然而,我们需要启用CAPABILITY_IAM的附加功能,因为该模板创建 IAM 资源,而CAPABILITY_AUTO_EXPAND是因为 CloudFormation 需要将 SAM 模板转换成标准的 CloudFormation 模板。

因为我们已经为CodeUri字段使用了变量,Octopus 会将模板指向正确的 S3 文件。这样,我们复制了由sam package命令提供的第二部分功能,它通常会生成一个处理过的 YAML 文件,其中包含更新后的 S3 位置。

SAM 模板不需要任何特殊的工具来部署。标准的 CloudFormation 工具可以部署 SAM 模板,只要定义了CAPABILITY_AUTO_EXPAND功能。

和以前一样,CloudFormation 模板将从一个文件部署。同样,我们使用了AwsSamLambdaTemplates包,但是这一次,我们部署了template.yaml文件。

章鱼变量

在整个模板和步骤中,我们使用了变量来定义 AWS 区域、S3 桶名称和云形成堆栈名称:

下表描述了这些变量。

名字 描述
AWSRegion AWS 区域。
CloudFormationStackS3Bucket 桶 用于创建 S3 桶的云生成堆栈的名称。
云格式堆栈 Sam 将部署 SAM 应用程序的 CloudFormation 堆栈的名称。
S3BucketName 将上载应用程序的 S3 存储桶的名称。

部署到单一环境

现在我们已经拥有了使用 Octopus 将 SAM 应用程序部署到一个环境(称为UAT)所需的一切。

我们已经在 Octopus 中成功创建了一个部署流程,该流程复制了 SAM CLI 工具的功能。

能够在单个环境中执行可重复的部署是很棒的,但是 Octopus 的真正优势是可以扩展到多个环境。

部署到第二个环境

因为我们已经将所有特定于环境的配置转移到 Octopus 变量中,所以更新我们的项目以部署到第二个环境就像将变量值作用于新环境一样简单。

在这种情况下,我们为下一个名为Prod的环境中的CloudFormationStackS3BucketCloudFormationStackSamS3BucketName变量添加新值。

这意味着新的Prod环境将创建其自己的特定云形成堆栈,以创建新的 S3 桶,并为 SAM 应用程序创建第二个特定于环境的云形成堆栈:

通过这些少量的更改,我们能够将应用程序的独立副本部署到两个不同的环境中:

结论

SAM CLI 工具是引导和部署无服务器应用程序堆栈的便捷方式。通过简化部署无服务器应用程序的常见任务,您可以使用以下命令部署“hello world”应用程序:

  • sam init --runtime python3.7
  • sam build
  • sam package --output-template packaged.yaml --s3-bucket bucketname
  • sam deploy --template-file packaged.yaml --region region --capabilities CAPABILITY_IAM --stack-name aws-sam-getting-started

在这篇博文中,我们已经看到了如何使用由sam initsam build生成的代码和模板,并用 GitHub Actions 和 Octopus Deploy 替换sam packagesam deploy命令。

最终结果是一个 CI/CD 管道,它创建了跨多个环境的版本化、可重复的部署。然后,可以轻松扩展该管道,以满足生产环境的需求。

使用 CloudFormation - Octopus Deploy 创建一个私有 AWS VPC

原文:https://octopus.com/blog/aws-vpc-private

虚拟专用云(VPC)是部署到 AWS 的任何基础设施的支柱。几乎所有的资源都需要一个 VPC,并且大多数资源隔离都是通过 VPC 完成的。

不幸的是,尽管它们无处不在,创建 VPC 并不像想象的那么简单。

在本文中,您将了解 AWS 中可用的不同类型的 VPC,并找到一个示例 CloudFormation 模板,该模板可用于部署带有私有子网的简单 VPC。

AWS 子网的类型

AWS 有两种类型的子网:公共子网和私有子网。

公共子网通过互联网网关连接到互联网,并且可以托管具有公共 IP 地址的资源。

AWS 将互联网网关定义为:

一个水平扩展、冗余且高度可用的 VPC 组件,允许您的 VPC 和互联网之间的通信。

私有子网不会将流量路由到互联网网关。私有子网中的资源没有公共 IP 地址,只能与同一 VPC 中的其他子网中的资源通信。

一个或多个子网可以放在一个 VPC 中。可以在 VPC 中混合搭配公共和私有子网,允许 VPC 中的一些资源访问互联网,而一些资源只能访问 VPC 中的其他资源。

带有专用子网的 VPC 是最容易配置的,您将在下一节中进行配置。

创建带有私有子网的 VPC

以下 CloudFormation 模板创建了一个带有两个专用子网的 VPC:

Parameters:
  Tag:
    Type: String

Resources: 
  VPC:
    Type: "AWS::EC2::VPC"
    Properties:
      CidrBlock: "10.0.0.0/16"
      Tags:
      - Key: "Name"
        Value: !Ref "Tag"

  SubnetA:
    Type: "AWS::EC2::Subnet"
    Properties:
      AvailabilityZone: !Select 
        - 0
        - !GetAZs 
          Ref: 'AWS::Region'
      VpcId: !Ref "VPC"
      CidrBlock: "10.0.0.0/24"

  SubnetB:
    Type: "AWS::EC2::Subnet"
    Properties:
      AvailabilityZone: !Select 
        - 1
        - !GetAZs 
          Ref: 'AWS::Region'
      VpcId: !Ref "VPC"
      CidrBlock: "10.0.1.0/24"

  RouteTable:
    Type: "AWS::EC2::RouteTable"
    Properties:
      VpcId: !Ref "VPC"

Outputs:
  VpcId:
    Description: The VPC ID
    Value: !Ref VPC 

VPC 的名称由Tag参数定义:

 Tag:
    Type: String 

VPC 被定义为一种 AWS EC2 VPC 资源。

CidrBlock属性定义无类域间路由 IP 块,该块定义与 VPC 相关联的子网可用的 IP 地址范围。10.0.0.0/16定义了一组以10.0开头的 IP 地址。

请注意,VPC 有一个名为Name的标签。此标记的值显示在 AWS web 控制台中:

 VPC:
    Type: "AWS::EC2::VPC"
    Properties:
      CidrBlock: "10.0.0.0/16"
      Tags:
      - Key: "Name"
        Value: !Ref "Tag" 

接下来,使用 AWS EC2 子网资源定义两个子网。

子网被放置在可用区域 (AZs)中,这些区域是一个区域中的隔离位置。az 具有类似于us-east-1ap-southeast-2的代码,这些代码基于 az 所在的区域。

您可以使用 Select内在函数GetAZs数组中返回项目,这将返回您的 VPC 正在创建的区域的可用 AZ,而不是硬编码这些 AZ 名称。

每个子网都有自己唯一的 CIDR 块。

  • 第一个子网定义了块10.0.0.0/24,这意味着该子网中的所有资源都有以10.0.0开头的 IP 地址。

  • 第二个子网定义了块10.0.1.0/24,这意味着第二个子网中的所有资源都有以10.0.1开头的 IP 地址:

 SubnetA:
    Type: "AWS::EC2::Subnet"
    Properties:
      AvailabilityZone: !Select 
        - 0
        - !GetAZs 
          Ref: 'AWS::Region'
      VpcId: !Ref "VPC"
      CidrBlock: "10.0.0.0/24"

  SubnetB:
    Type: "AWS::EC2::Subnet"
    Properties:
      AvailabilityZone: !Select 
        - 1
        - !GetAZs 
          Ref: 'AWS::Region'
      VpcId: !Ref "VPC"
      CidrBlock: "10.0.1.0/24" 

子网之间的网络连接由路由表定义,路由表由AWSEC2route table资源创建。默认路由表允许每个子网中实例之间的连接,因此您无需在此处指定任何其他路由:

 RouteTable:
    Type: "AWS::EC2::RouteTable"
    Properties:
      VpcId: !Ref "VPC" 

要部署该模板,请使用部署 AWS CloudFormation 模板步骤。

下面的屏幕截图显示了创建后 AWS 控制台中的 VPC:

AWS VPC Console

结论

带有专用子网的 VPC 最容易创建。在本文中,您看到了一个简单的 CloudFormation 模板来创建一个带有两个私有子网的 VPC。

在下一篇文章中,你将学习如何创建带有公共子网的 VPC。

阅读我们的 Runbooks 系列的其余部分。

愉快的部署!

使用 CloudFormation - Octopus Deploy 创建混合 AWS VPC

原文:https://octopus.com/blog/aws-vpc-public-private

在我们的第一篇文章中,用 CloudFormation 创建一个私有的 AWS VPC,你看了如何创建一个带有私有子网的 VPC,然后,在我们的第二篇文章中,添加一个互联网网关以允许在公共子网内访问互联网

通过混合私有子网和公共子网,可以创建一个公开暴露一些实例的 VPC,同时限制对私有实例的访问。这是托管公共网站的 VPC 的常见配置,该网站访问私有数据库。

在本文中,您将创建一个混合了公共和私有子网的 VPC。

子网的类型

AWS 有两种类型的子网:公共子网和私有子网。

公共子网通过互联网网关连接到互联网,并且可以托管具有公共 IP 地址的资源。AWS 将互联网网关定义为:

一个水平扩展、冗余且高度可用的 VPC 组件,允许您的 VPC 和互联网之间的通信。

私有子网不会将流量路由到互联网网关。私有子网中的资源没有公共 IP 地址,只能与同一 VPC 中的其他子网中的资源通信。

一个或多个子网可以放在一个 VPC 中。可以在 VPC 中混合搭配公共和私有子网,允许 VPC 中的一些资源访问互联网,而一些资源只能访问 VPC 中的其他资源。

在具有公共和私有子网的 VPC 中,可以通过 NAT 网关路由来自私有子网的互联网流量。与您的家用路由器非常相似,NAT 网关允许建立出站互联网流量,并将对这些出站请求的响应路由回私有子网中的设备。但是不能通过 NAT 网关从外部连接发起连接。

具有公共和私有子网的 VPC 是最复杂的,但是在部署可以从公共互联网访问或者只能从 VPC 访问的实例时,它提供了最大的灵活性。

创建包含公有子网和私有子网的 VPC

以下 CloudFormation 模板创建了一个包含一个公共子网和一个私有子网的 VPC:

Parameters:
  Tag:
    Type: String

Resources: 
  VPC:
    Type: "AWS::EC2::VPC"
    Properties:
      CidrBlock: "10.0.0.0/16"
      InstanceTenancy: "default"
      Tags:
      - Key: "Name"
        Value: !Ref "Tag"

  InternetGateway:
    Type: "AWS::EC2::InternetGateway"

  VPCGatewayAttachment:
    Type: "AWS::EC2::VPCGatewayAttachment"
    Properties:
      VpcId: !Ref "VPC"
      InternetGatewayId: !Ref "InternetGateway"

  SubnetA:
    Type: "AWS::EC2::Subnet"
    Properties:
      AvailabilityZone: !Select 
        - 0
        - !GetAZs 
          Ref: 'AWS::Region'
      VpcId: !Ref "VPC"
      CidrBlock: "10.0.0.0/24"

  SubnetB:
    Type: "AWS::EC2::Subnet"
    Properties:
      AvailabilityZone: !Select 
        - 1
        - !GetAZs 
          Ref: 'AWS::Region'
      VpcId: !Ref "VPC"
      CidrBlock: "10.0.1.0/24"

  RouteTable:
    Type: "AWS::EC2::RouteTable"
    Properties:
      VpcId: !Ref "VPC"

  InternetRoute:
    Type: "AWS::EC2::Route"
    Properties:
      DestinationCidrBlock: "0.0.0.0/0"
      GatewayId: !Ref InternetGateway
      RouteTableId: !Ref RouteTable

  SubnetARouteTableAssociation:
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties:
      RouteTableId: !Ref RouteTable
      SubnetId: !Ref SubnetA

  EIP:
    Type: "AWS::EC2::EIP"
    Properties:
      Domain: "vpc"

  Nat:
    Type: "AWS::EC2::NatGateway"
    Properties:
      AllocationId: !GetAtt "EIP.AllocationId"
      SubnetId: !Ref "SubnetA"

  NatRouteTable:
    Type: "AWS::EC2::RouteTable"
    Properties:
      VpcId: !Ref "VPC"

  NatRoute:
    Type: "AWS::EC2::Route"
    Properties:
      DestinationCidrBlock: "0.0.0.0/0"
      NatGatewayId: !Ref "Nat"
      RouteTableId: !Ref "NatRouteTable"

  SubnetBRouteTableAssociation:
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties:
      RouteTableId: !Ref NatRouteTable
      SubnetId: !Ref SubnetB

Outputs:
  VpcId:
    Description: The VPC ID
    Value: !Ref VPC 

该模板建立在上一篇文章的基础上,因此请参考该文章,了解有关互联网网关、路由和路由关联的详细信息。

上面的模板将SubnetA视为公共子网,将SubnetB视为私有子网。

为了使SubnetB成为私有,将公共流量导向互联网网关的路由关联已被删除。

然而,SubnetB中的实例仍然可以通过 NAT 网关访问互联网。

NAT 网关需要一个公共 IP,由 AWS EC2 EIP 资源表示:

 EIP:
    Type: "AWS::EC2::EIP"
    Properties:
      Domain: "vpc" 

然后创建一个 Nat 网关,由AWSEC2Nat gateway资源表示。NAT 网关创建在公共子网中,为其提供互联网接入:

 Nat:
    Type: "AWS::EC2::NatGateway"
    Properties:
      AllocationId: !Ref "EIP"
      SubnetId: !Ref "SubnetA" 

AWSEC2route table资源表示的第二个路由表被创建来保存将流量定向到 NAT 网关的网络规则:

 NatRouteTable:
    Type: "AWS::EC2::RouteTable"
    Properties:
      VpcId: !Ref "VPC" 

AWS EC2 Route 资源定义的新路由将所有互联网流量导向 NAT 网关:

 NatRoute:
    Type: "AWS::EC2::Route"
    Properties:
      DestinationCidrBlock: "0.0.0.0/0"
      NatGatewayId: !Ref "Nat"
      RouteTableId: !Ref "NatRouteTable" 

新路由表通过AWSEC2SubnetRouteTableAssociation资源与SubnetB相关联;

 SubnetBRouteTableAssociation:
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties:
      RouteTableId: !Ref NatRouteTable
      SubnetId: !Ref SubnetB 

创建后,VPC 包含公共子网和私有子网。在SubnetA中创建的任何实例都可以通过互联网网关访问互联网,并且可以通过公共 IP 地址访问。在SubnetB创建的实例可以通过 NAT 网关访问互联网,但是流量不能从互联网发起。

结论

当放置必须从 internet 访问的实例或受益于不暴露于公共流量所提供的额外安全性时,在 VPC 中包括公共和私有子网提供了最大的灵活性。即使私有子网不允许公共流量发起连接,私有子网中的实例仍然可以通过 NAT 网关发出出站网络请求。

在本文中,您看到了一个示例 CloudFormation 模板,该模板创建了一个包含公共子网和私有子网的 VPC。这与创建带有公共子网的VPC 和带有私有子网VPC 的模板一起,为您在 AWS 中创建资源提供了一个快速的起点。

阅读我们的 Runbooks 系列的其余部分。

愉快的部署!

使用 CloudFormation - Octopus Deploy 创建公共 AWS VPC

原文:https://octopus.com/blog/aws-vpc-public

在我们的第一篇文章用 CloudFormation 创建一个私有的 AWS VPC 中,您看到了如何创建一个带有私有子网的 VPC。此 VPC 中的实例无法访问互联网,只能与同一 VPC 的子网中的实例通信。

在本文中,您将创建一个带有公共子网的 VPC,允许实例访问互联网以及从互联网被访问。

子网的类型

AWS 有两种类型的子网:公共子网和私有子网。

公共子网通过互联网网关连接到互联网,并且可以托管具有公共 IP 地址的资源。AWS 将互联网网关定义为:

一个水平扩展、冗余且高度可用的 VPC 组件,允许您的 VPC 和互联网之间的通信。

私有子网不会将流量路由到互联网网关。私有子网中的资源没有公共 IP 地址,只能与同一 VPC 内其他子网中的资源通信。

带有公共子网的 VPC 允许实例访问互联网。如果这些实例有公共 IP 地址,也可以从互联网访问它们。

创建带有公共子网的 VPC

以下 CloudFormation 模板创建了一个包含两个公共子网的 VPC:

Parameters:
  Tag:
    Type: String

Resources: 
  VPC:
    Type: "AWS::EC2::VPC"
    Properties:
      CidrBlock: "10.0.0.0/16"
      InstanceTenancy: "default"
      Tags:
      - Key: "Name"
        Value: !Ref "Tag"    

  SubnetA:
    Type: "AWS::EC2::Subnet"
    Properties:
      AvailabilityZone: !Select 
        - 0
        - !GetAZs 
          Ref: 'AWS::Region'
      VpcId: !Ref "VPC"
      CidrBlock: "10.0.0.0/24"

  SubnetB:
    Type: "AWS::EC2::Subnet"
    Properties:
      AvailabilityZone: !Select 
        - 1
        - !GetAZs 
          Ref: 'AWS::Region'
      VpcId: !Ref "VPC"
      CidrBlock: "10.0.1.0/24"

  RouteTable:
    Type: "AWS::EC2::RouteTable"
    Properties:
      VpcId: !Ref "VPC"

  InternetGateway:
    Type: "AWS::EC2::InternetGateway"

  VPCGatewayAttachment:
    Type: "AWS::EC2::VPCGatewayAttachment"
    Properties:
      VpcId: !Ref "VPC"
      InternetGatewayId: !Ref "InternetGateway"

  InternetRoute:
    Type: "AWS::EC2::Route"
    Properties:
      DestinationCidrBlock: "0.0.0.0/0"
      GatewayId: !Ref InternetGateway
      RouteTableId: !Ref RouteTable

  SubnetARouteTableAssociation:
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties:
      RouteTableId: !Ref RouteTable
      SubnetId: !Ref SubnetA

  SubnetBRouteTableAssociation:
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties:
      RouteTableId: !Ref RouteTable
      SubnetId: !Ref SubnetB

Outputs:
  VpcId:
    Description: The VPC ID
    Value: !Ref VPC 

上面的模板建立在上一篇文章中的模板之上,使用 CloudFormation 创建一个私有 AWS VPC,添加一个互联网网关和将流量定向到互联网所需的路由表。参考以前的帖子阅读 VPC、子网和路由表资源的详细信息。

要将 VPC 连接到互联网,您必须连接一个互联网网关,由AWSEC2internet gateway资源表示。除了添加自定义标记之外,此资源不支持任何配置属性:

 InternetGateway:
    Type: "AWS::EC2::InternetGateway" 

互联网网关通过AWSEC2VPCGatewayAttachment资源连接到 VPC:

 VPCGatewayAttachment:
    Type: "AWS::EC2::VPCGatewayAttachment"
    Properties:
      VpcId: !Ref "VPC"
      InternetGatewayId: !Ref "InternetGateway" 

要通过 internet 网关引导外部流量,您必须创建一个路由,由 AWS EC2 Route 资源表示。

下面的路由定义了一个0.0.0.0/0DestinationCidrBlock,匹配所有流量。此路由将在连接同一 VPC 子网中实例的默认路由之后应用,因此只有不是发往 VPC 中另一个实例的流量才会受到此路由的影响。实际上,这意味着任何外部流量都通过互联网网关进行定向:

 InternetRoute:
    Type: "AWS::EC2::Route"
    Properties:
      DestinationCidrBlock: "0.0.0.0/0"
      GatewayId: !Ref InternetGateway
      RouteTableId: !Ref RouteTable 

然后,该路由通过AWSEC2SubnetRouteTableAssociation资源与两个子网相关联,这使它们成为公共子网:

 SubnetARouteTableAssociation:
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties:
      RouteTableId: !Ref RouteTable
      SubnetId: !Ref SubnetA

  SubnetBRouteTableAssociation:
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties:
      RouteTableId: !Ref RouteTable
      SubnetId: !Ref SubnetB 

要部署该模板,请使用部署 AWS CloudFormation 模板步骤。请注意放置在此 VPC 中的 EC2 实例如何选择接收公共 IP 地址:

Auto-assign Public IP option

分配给 EC2 实例的 IP 地址允许您从本地 PC SSH 到它:

EC2 public IP

结论

公共子网允许实例访问互联网,并为它们提供分配公共 IP 地址的选项。创建带有公共子网的 VPC 需要构建一个 internet 网关,将其连接到 VPC,定义一个路由以引导公共流量通过 internet 网关,并将路由分配给子网。

在这篇文章中,您看到了一个创建带有两个公共子网的 VPC 的云形成模板。在下一篇文章中,您将学习如何创建一个包含公共和私有子网的 VPC,以及一个 NAT 网关,以允许私有子网中的实例访问互联网。

阅读我们的 Runbooks 系列的其余部分。

愉快的部署!

深入探讨- Azure 应用程序集成和八达通帐户-八达通部署

原文:https://octopus.com/blog/azure-app-integration

Octopus Deploy 集成了流行的云服务,如亚马逊网络服务(AWS)微软的 Azure 平台,可以轻松安全地部署您的应用程序。与 Amazon 集成就像添加你的 AWS 访问密钥和秘密密钥一样简单,然而,Azure 需要更多的细节,并且如何设置还不是很明显。这是通过 Azure Active Directory (AD)注册的应用程序(或应用程序注册)实现的,但设置起来可能很棘手,因此我们将深入探讨并完成整个过程。

Octopus Accounts

Octopus 中的 Azure 帐户需要四个值,用于对 Azure 进行身份验证并与之安全交互。

  • Azure 订阅 ID
  • Azure AD 租户 ID
  • Azure AD 注册的应用程序 ID
  • Azure AD 注册的应用程序密码/密钥

前三个值是 GUIDs,最后一个是密码。这些值可以在 Azure 门户或通过 Powershell 找到。就我个人而言,我从来不记得所有的值是在哪里找到的,所以我们录制了一个视频来帮助大家!

章鱼深潜

https://www.youtube.com/embed/KnN-ahD6nN4

VIDEO

进一步阅读

查看我们的 Azure 文档获取更多信息,包括如何创建 Octopus Azure 账户的逐步说明以及其他重要问题,如安全和许可

总结

现在你可以走了。您可以将所有这些值输入到 Octopus 中,或者在您自己的应用程序中使用它们来与 Azure 集成。如果你曾经很难添加一个八达通 Azure 帐户,我希望这有所帮助!如果章鱼的另一部分让你困惑,请在评论中告诉我们,我们会更深入地研究。

不要忘记订阅我们的 YouTube 频道,因为我们会定期添加新视频。愉快的部署!😃

改进的 Azure 应用服务部署- Octopus Deploy

原文:https://octopus.com/blog/azure-app-service-step

Octopus, a zip file and Azure logo connected in the clouds

章鱼部署 Q2 版本包括一个新的 T2 部署 Azure 应用服务的步骤。这一步对部署 Azure web 应用程序进行了重大改进,包括:

  • 部署到 Linux 应用服务计划(没有晦涩的配置黑客)。
  • 部署容器映像。
  • 在 Linux Octopus workers 上执行部署。
  • 配置应用程序设置和连接字符串。

Add Azure App Service step tile

什么变了?

部署 Azure 应用服务步骤旨在取代现有的部署 Azure Web 应用步骤,然而,原始步骤仍然可用。

部署 Azure Web App 步骤依赖于微软 Web Deploy 作为部署机制。这限制了在 Windows workers 上执行的步骤,需要特殊的配置来使用 Linux 应用服务计划,并且不支持容器映像。新的步骤依赖于用于基于文件的包(zip、nupkg、war)的 zip 部署 API ,并且还支持部署容器映像。

应用程序设置和连接字符串

新的步骤还带来了配置 Azure 应用程序设置和连接字符串的能力。

Configuring app settings and connection strings

这是通过以 JSON 的形式提供设置和/或连接字符串来实现的。

使用 JSON 格式的好处是:

在未来,我们希望在这些字段上添加一个更友好的 UI,并保持 JSON 配置为“高级”模式。

转换配置文件

当部署打包为 zip、nupkg 或 war 文件的应用程序时,有多个选项可用于转换包中的配置文件。Octopus 将提取包并在推送到 Azure 之前执行这些转换。

Azure app service configuration file options

提供了以下用于转换配置文件的选项:

结论

我们向 Clear Measure 的朋友们致敬,感谢他们帮助我们开发这个新的 Azure 应用服务集成。

部署 Azure 应用服务步骤现在在 Octopus Cloud 实例和自托管下载页面上的 Octopus 2021.1 中都可用。我们希望这使得部署 Azure web 应用程序比以往任何时候都更容易。

愉快的部署!

蓝色二头肌和章鱼部署-章鱼部署

原文:https://octopus.com/blog/azure-bicep-octopus-deploy

2020 年末,微软公布了他们的新项目, Bicep ,一种用于部署 Azure 资源的领域特定语言(DSL)。Bicep 旨在简化创作体验,使其易于学习。它也是模块化和可重用的。

Bicep 在 IT 界非常流行。你可以在官方的 Bicep GitHub 空间上找到博文、推文、会议和大量互动。从 v.0.3 开始,Bicep 已经得到了微软支持计划的支持,并被认为可以投入生产。

在这篇文章中,我将介绍 Bicep 模板,并向您展示如何使用Octopus run book来自动化它们的部署。

二头肌和章鱼入门

先决条件

您需要以下工具来开始使用二头肌:

创建您的第一个二头肌模板

微软提供了 Bicep 微软学习路径来帮助你入门。

在这篇文章中,我解释了如何创建一个基本的模板。这篇文章假设你有 ARM 模板或类似的经验。我介绍了 Azure 应用服务计划和 Azure Linux Web 应用的创建过程。

首先,在模板中,您需要声明您正在使用的参数和变量:

// Declare parameters
param sku string
param linuxFxVersion string = 'node|14-lts' // The runtime stack of web app
param location string
param resourceTags object = {
  Environment: 'Tutorial'
  Owner: 'Me'
} // Tags for all resources
param appServicePlanName string
param webSiteName string 

您可以为这些参数声明静态条目,也可以将它们留空,然后在部署期间输入值。在本例中,您将一些声明为 static,而将其他的留空,以便您可以在部署过程中传递信息。

接下来,定义您希望如何部署 Azure 应用服务计划:

// Deploying the Azure App Service Plan
resource appServicePlan 'Microsoft.Web/serverfarms@2021-02-01' = {
  name: appServicePlanName
  location: location
  tags: resourceTags
  properties: {
    reserved: true
  }
  sku: {
    name: sku
  }
  kind: 'linux'
} 

我来给你分解一下:

  • 资源标识符(resourceappServicePlan)——告诉 Bicep 创建一个名为appServicePlan的新资源。这个名称标识 Bicep 模板中的资源。这不是您将在 Azure 中创建的资源的名称。
  • Microsoft.Web/serverfarms@2021-02-01——定义资源提供者Microsoft.Web,然后是资源类型serverfarms,最后是要使用的 API 版本2021-02-01。查看微软官方文档,看看是否有更新的 API 总是值得的。
  • 名称——这是 Azure 资源的实际名称。
  • 位置——这是您将要部署的 Azure 区域。
  • 标记-标记你的资源有助于你合理地组织它们。
  • 属性-您可以从这里开始根据自己的需求配置应用服务计划。在这里,您定义了 SKU 和种类(Linux 或 Windows)。

接下来,您将部署 Azure Web 应用程序:

// Deploying the Azure Web App
resource appService 'Microsoft.Web/sites@2021-02-01' = {
  name: webSiteName
  location: location
  tags: resourceTags
  properties: {
    serverFarmId: appServicePlan.id
    siteConfig: {
      linuxFxVersion: linuxFxVersion
    }
  }
} 

我来给你解释一下:

  • 资源标识符(resourceappService)——告诉 Bicep 创建一个名为appService的新资源。这个名称标识 Bicep 模板中的资源。这不是您将在 Azure 中创建的资源的名称。
  • Microsoft.Web/sites@2021-02-01——定义资源提供者Microsoft.Web,然后是资源类型sites,最后是要使用的 API 版本2021-02-01。查看微软官方文档,看看是否有更新的 API 总是值得的。
  • 名称——这是 Azure 资源的实际名称。
  • 位置——这是您将要部署的 Azure 区域。
  • 标记-标记你的资源有助于逻辑地组织它们。
  • 属性-这是您开始配置应用程序服务的地方。您定义了此 web 应用使用的应用服务计划以及您想要的 Linux 版本。您可以配置更多的设置,但在本例中我们保持简单。

模板现在完成了。你可以在 GitHub 上看到完成的模板。

准备在 Octopus Deploy 中使用的二头肌模板

要在 Octopus Runbook 中运行您的二头肌模板文件,您首先需要使用 Octopus CLI 将其保存在 ZIP 文件中。

octo pack --id="BicepTemplate" --format="zip" --version="1.0.0.0" --basePath="c:\Bicep\" --outFolder="c:\Bicep" 

您可以通过门户将 ZIP 文件上传到 Octopus 库或再次使用 Octopus CLI。

octo push --package="c:\bicep\BicepTemplate.1.0.0.0.zip" --server="https://MyOctopusServer" --apiKey="API-MyApiKey" 

从 Octopus Runbook 运行二头肌模板

有了 Octopus 中的 Bicep ZIP 文件,是时候自动化其部署了。

这篇文章假设你已经设置了一个 Octopus 环境,并且已经连接了你的 Azure 账户。

在一个新的或现有的项目中,点击左侧菜单中的变量

您需要为部署定义变量。输入变量:

  • 资源组
  • 网站名称
  • 应用服务计划名称
  • 位置
  • SKU 应用服务计划

Declared variables

输入变量后,导航至操作并点击操作手册

选择添加 Runbook 创建新的 Runbook 来部署您的二头肌模板。

Add a new Runbook

在该操作手册中,点击定义您的操作手册流程

Define the Runbook Process

添加的第一步是运行一个 Azure 脚本。这一步创建 Azure 资源组来保存您的资源。

Azure Run a Script Deployment Step

为该步骤命名以供参考。然后,输入您的 Azure 帐户的详细信息。

接下来,使用 Azure CLI 命令输入将创建您的资源组的脚本。

az group create -l $OctopusParameters["Project.location"] -n $OctopusParameters["Project.ResourceGroupName"] 

您正在使用在前面步骤中声明的一些变量。

现在你需要添加另一个运行 Azure 脚本的步骤。这将部署您的二头肌模板,因此要适当地命名它。然后,滚动到 Azure 部分,再次添加您的 Azure 帐户详细信息。

接下来,滚动到脚本部分,输入以下脚本:

# Tell the deployment to use the package we’ve associated and the file path
$filePath = $OctopusParameters["Octopus.Action.Package[BicepTemplate].ExtractedPath"]

# Change Directory to extracted package
cd $filePath

# Get current date and set the deployment name
$today=Get-Date -Format "dd-MM-yyyy"
$deploymentName="BicepDeploy"+"$today"

# Deploy Bicep template and pull in the variable information
New-AzResourceGroupDeployment -Name $deploymentName -ResourceGroupName $OctopusParameters["Project.ResourceGroupName"] -TemplateFile webapp.bicep  -sku $OctopusParameters["Project.sku"] -location $OctopusParameters["Project.location"] -appServicePlanName $OctopusParameters["Project.appServicePlanName"] -webSiteName $OctopusParameters["Project.webSiteName"] 

如果你使用自己的 Workers ,请检查你是否安装了 Azure CLI、Azure PowerShell 和 Bicep。

上面的脚本使用了 Bicep ZIP 文件。输入部署名称,以便可以跟踪部署的历史。该脚本使用 PowerShell 命令部署模板文件,并拉入您在 Octopus 中声明的变量。

在脚本框的正下方,导航到引用的包部分,然后单击添加。用你的 Bicep 文件选择 ZIP 文件。

现在,您可以保存此部署流程并运行部署。

Runbook Success

结论

您可以通过为更复杂的部署创建二头肌模块来构建这个基本的二头肌部署。

如果您正在使用 Bicep 将资源部署到您的环境中,请在下面的评论中或通过我们的 Slack 社区联系我们,如果您有任何问题,请告诉我。

阅读我们的 Runbooks 系列的其余部分。

愉快的部署!

3.1 中的 Azure 变化- Octopus 部署

原文:https://octopus.com/blog/azure-changes

我们最近在 Octopus 3.0 中对部署目标和 Azure 云服务进行了重大更改,结果证明我们的设计是错误的。在 3.1 中,我们将回归到 2.6 时代部署 Azure 云服务的方式。这一变化也将影响我们部署 Azure web 应用的方式。

在以前版本的 Octopus 中,Azure 云服务是使用特殊的步骤类型部署的:

The old Azure step type

可以绑定订阅 ID 和其他字段,只需更改项目变量就可以使用不同的值。它工作得很好,但是它总是感觉与 Octopus 的其他工作方式“不同”。

在 3.0 中,我们增加了对不同部署目标的支持,而不是触角,并决定将 Azure 云服务作为一个部署目标:

New deployment targets

作为开发人员,这感觉像是一个更干净的设计,因为它统一了我们处理所有部署目标的方式。但在现实中,虽然触手和 SSH 目标是可重用的(你可能会向它们部署多个应用),但 Azure 云服务和 Azure web 应用不是;使用变量的老方法对许多人来说效果更好。让 Azure 云服务和 web 应用像触角一样工作是一种有漏洞的抽象,我们的设计是错误的。

我们不会坚持一个糟糕的设计,而是要推翻这个决定。对于 Octopus 3.1,我们将带回旧的 Azure step 类型,并让您使用变量而不是机器来控制目标。我们将保留我们在 3.0 中所做工作的一些功能:

  • Octopus 服务器仍将运行部署(不需要单独的触手工人)
  • 配置转换和替换仍然有效。cspkg 文件
  • 我们仍然会提供一个不错的 UI 来选择目标,但是让您用变量覆盖它
  • 运行 Azure PowerShell 脚本会有非常好的体验

这对你意味着什么

如果你使用 Azure 网站或云服务,现在就坚持使用 2.6 版本。我们将确保有一条从 2.6 到 3.1 的 Azure 步骤迁移路径。如果你没有使用 Azure steps,那就升级吧。

与 Azure DevOps - Octopus Deploy 更好地集成 Octopus

原文:https://octopus.com/blog/azure-devops-extension-v4

Illustration showing Azure DevOps and Octopus Deploy work great together

Octopus 补充了 Azure DevOps 服务套件,我们最近发布了一个针对 Azure DevOps 的 Octopus Deploy 集成的更新,以使集成更好。对于团队来说,使用 Azure Pipelines 来构建他们的应用程序,并使用 Octopus 来部署它们是非常常见的。这种方法利用了这两种工具的优势,它们一起使团队能够更好地从想法到产品获得对其软件管道的端到端可见性。

这个版本使我们的 Azure DevOps 扩展与我们的 TeamCity 和 Bamboo 插件不相上下。我们添加了对构建信息和工作项跟踪的支持,这开启了一些奇妙的新场景:

这个版本还包括 增强空间的支持 ,这样队伍可以更容易地选择自己队伍的空间。

了解如何安装或更新扩展

查看版本详细信息,包括新功能和修复

Octopus release with Azure DevOps work items

对于使用 Azure DevOps 的团队来说,使用其工作项跟踪(包括看板和积压工作)来计划和组织他们的项目是很常见的。此更新支持识别与 Azure DevOps 版本和 Octopus 版本相关的工作项,从而提供从工作项到部署的端到端可追溯性。你也可以点击 Azure DevOps 了解更多信息。这有助于识别将 bug 引入到版本中的变更。

注:八达通还支持 GitHub 发行吉拉发行阅读我们的文档了解更多信息。

查看生产中的新功能

Octopus deployment with Azure DevOps work items

要弄清楚自上次部署到环境中以来发生了什么变化,有时可能需要一点猜测。现在不再是这种情况了,因为 Octopus 可以智能地确定自上次部署以来哪些工作项发生了更改。此功能使您能够查看生产或任何其他环境中的“新特性”,并且它汇总了发行说明和所有相关的更改。

这有助于一目了然地了解部署过程中发生了什么变化,并且在处理大型项目和查看其他团队做出的变化时非常方便。

与您的团队和经理共享发行说明

Share Octopus release notes automatically

编写发布说明通常是一项手动任务,虽然使用像 Azure Boards吉拉这样的工具更容易,但仍然很耗时。集成 Octopus 和 Azure DevOps 使这一过程完全自动化。Octopus 知道哪些问题已经部署到环境中,因此它可以快速生成发布说明,显示测试或生产环境中的新内容。

用 Octopus 阅读发布说明很方便,但通过电子邮件、Slack 或其他媒介分享更好。使用我们的电子邮件步骤、Slack 或 Microsoft Teams 步骤模板,您可以在每次成功部署到生产环境后向您的团队或经理发送发布说明。这个特性使得每个人都很容易了解情况。

改进的空间支持

Configure your Octopus Space in an Azure Pipelines build process

我们更新的扩展包括完全支持从你的章鱼服务器中检索所有的空间,所以现在很容易为你的团队选择一个。

如何安装或更新您的 Azure DevOps 扩展

如果你正在使用 Azure DevOps,但还没有安装 Octopus Deploy 扩展,可以在 Visual Studio Marketplace 上免费获得:Octopus Deploy integration for Azure devo PS

如果你的团队已经在使用 Azure DevOps 和 Octopus Deploy,我强烈推荐升级到我们扩展的最新版本。这个版本将扩展的版本从 v3 提升到 v4,因此您需要为每个任务选择较新的版本。阅读我们的文档了解更多信息。

如果你正在使用 Azure DevOps 并希望改进你的部署,你可以安装我们的扩展免费试用 Octopus Deploy

总结

总之, Azure DevOpsOctopus Deploy 协同工作,为您提供从创意到生产的软件管道的更好的端到端可视性。

愉快的部署!

用于 Octopus Deploy v6 的 Azure DevOps 中的新功能- Octopus Deploy

原文:https://octopus.com/blog/azure-devops-octopus-v6

在这篇文章中,你将了解 Octopus Azure DevOps 插件 v6 的新特性,了解更新中的一些决定,并快速浏览一下更新后的插件

这些更新包括:

  • Octopus CLI 不再是必需的,同样,也不再是。NET 框架
  • Octopus 2022.3+现在是一个依赖项(这只适用于 v6 的步骤,不适用于早期版本)
  • 运行 Runbook 有了新的步骤

如果你阅读了我们的第三个版本,我们为 Octopus Deploy 开发的 GitHub Actions。

不再需要 Octopus CLI

移除对 Octopus CLI 的依赖是 Octopus Azure DevOps 插件版本 6 最大的架构变化。

我们的步骤不再使用 Octopus CLI 来执行工作。相反,它们直接从 TypeScript 与 Octopus API 交互。这意味着您的管道启动和执行比以前快得多。

这也意味着步骤的 v6 依赖于 Octopus Server 2022.3+,因为它引入了执行 API

您仍然可以使用 Octopus CLI,但是如果您只需要使用我们的其他步骤,就不再需要在您的管道中包含 Octopus CLI 安装程序

如果你有自己需要的脚本,Octopus CLI 安装程序仍然可以使用。详见下一节。

我们实现了完全 NodeJS 支持的长期目标,消除了对。NET 框架。这消除了由于各种。NET Framework 版本,这在过去一直困扰着客户。向 NodeJS 的过渡意味着更平滑、更可靠的软件发布。

Octopus CLI 安装程序现在安装基于 Go 的 CLI

我们最近将 CLI 实现从 C#转移到了 Go(关于原因的更多信息,请参见构建 Octopus CLI vNext )。Octopus CLI ( octo)将继续得到支持,直到 2023 年年中。

Octopus CLI 安装程序 v5 将继续安装 Octopus CLI ( octo)。如果您有使用基于 C#的 CLI 的现有管道,您可以继续使用 v5。

Octopus CLI 安装程序 v6(或更高版本)将只安装新的基于 Go 的 CLI ( octopus)。如果您正在编写新的管道,我们推荐使用 v6。基于 Go 的 CLI ( octopus)具有基于 C#的 CLI 所没有的新特性和改进,但是,也有一些微小的差异。如有必要,您可以同时使用这些 CLI。也就是说,您可以在同一个管道中包含该步骤的 v5 和 v6,以获得两个 CLI。

我们没有将调用 Octopus CLI 命令步骤更新到 v6。新 Go CLI 的设计倾向于脚本概念,如链接输入/输出,您可以在脚本步骤中直接使用它,因此我们决定不更新调用 Octopus CLI 命令步骤。我们建议在您的管道中使用脚本步骤,并直接调用octopus

部署和运行手册运行

在插件的早期版本中,创建发布步骤支持来自 Octopus CLI ( octo)的deploy-to参数。不幸的是,这带来了 Octopus CLI ( octo)支持的所有其他与部署相关的问题。然而,这些额外问题的开关并不是直接可用的。您必须知道您需要哪些开关,并将它们直接输入到“附加参数”中,这使得步骤变得复杂和混乱。

执行 API 的一个关键设计考虑是简化事情并形式化特定动作的需求/输入。对 Azure DevOps 步骤的 v6 更新直接反映了这些变化,现在有了所有支持设置的显式字段。

需要注意的关键事项:

  • 创建章鱼释放只是创建一个释放
  • Deploy Octopus Release 只是对一个部署进行排队(新的 Deploy Octopus Release to 租户步骤也是如此,我们稍后会详细讨论)
  • 新的跑步章鱼跑手册步排队跑步
  • 如果您想等待您排队等待部署或 runbook 运行的任务,您可以使用新的等待 Octopus 任务完成

租赁部署与“标准”部署具有不同的语义。首先,它们支持您可以部署到的环境的不同多样性(标准版可以部署到多个环境,租用版只能部署到一个环境)。为了使这一点更清楚,我们将它们分成单独的步骤,在字段上有清晰的名称,以使每个步骤支持的内容更清楚。同样,这也直接反映了执行 API 的工作方式。

虽然这是其中一些步骤的初始版本,但我们决定将它们全部发布为 v6,以便更容易将它们作为匹配集进行推理。随着时间的推移,版本可能会再次出现分歧,因为我们会对各个步骤分别进行修补和更新。

链接是一种内置功能

现在,许多步骤都会产生输出,从而实现链接。输出如下:

步骤 输出 描述
Pack(Zip 和 NuGet) package_file_path 创建的包的完整路径
package_filename 只是创建的包的文件名
Create Octopus Release release_number 创建的发布号(版本)
Deploy Octopus Release server_tasks 带有serverTaskIdenvironmentName的 JSON 对象数组
Deploy Octopus Release to Tenants server_tasks 带有serverTaskIdtenantName的对象的 JSON 数组
Run Octopus Runbook server_tasks 带有serverTaskIdenvironmentNametenantName的对象的 JSON 数组
Await Octopus Deploy Task completed_successfully 任务是否成功完成
server_task_results 任务及其成功的 JSON 表示。架构:{ "serverTaskId": ," tenantName": ," environmentName": ," successful": <真/假> }。这是为错误处理等场景提供的。例如,如果您有一个后续步骤,有条件地在该步骤失败时运行,它可以使用 JSON 来记录日志、向 Slack 发送消息等
<context>.completed_successfully 使用环境或租户名称的每个任务的上下文成功标志,例如Production.completed_successfullyUAT_Tenant_A.completed_successfully。名称中的空格被替换为下划线

我们将在下面的例子中更详细地展示如何使用输出。

其他变化

包装已被分割

出于与上述类似的原因,我们决定将 Zip 和 NuGet 包的创建步骤分开。将 2 放在一个步骤中会导致配置字段列表更加复杂和混乱。

没有选择器了

除了 Octopus 服务连接选择器,我们在 v6 版本的步骤中删除了所有选择器。选择器需要 Azure DevOps 本身来连接 Octopus 实例。对于在防火墙后面有一个自托管 Octopus 实例的客户来说,这是令人困惑的,看起来好像不能工作,但是如果他们只是在字段中键入一个名称,它可能在运行时工作(也就是说,当使用自托管构建代理时,如果该代理在防火墙后面并且可以连接到 Octopus,它可能工作)。

对于一些客户来说,这种潜在的小收益感觉过于复杂,所以我们将所有字段恢复为字符串。

创建发布项目名称

在执行这些步骤时,我们注意到创建发布字段的命名不一致。在经典视图中,你不会注意到这一点,但当你使用 YAML 时,这一点就变得很明显了。创建发布步骤使用了ProjectName,但是所有其他步骤都使用Project作为该字段的名称。

我们决定,虽然这对于升级来说有点不方便,但是在 Create Release 步骤中重命名该字段会使将来的工作更加容易。

迁移到 v6

没有更改任何现有的步骤版本。你应该不会在插件升级时遇到任何行为改变。

但是,当从旧版本迁移到 v6 时,您会遇到一些变化。我们试图尽量减少这种情况,同时兼顾一致性和易用性。

在经典编辑器中,如果没有保存管道,则没有丢失任何数据。因此,如果您切换到 v6 并且丢失了一些东西或者对新的设置不满意,您可以刷新页面以重新加载原始版本和数据。在 YAML,你只需回复文本更改即可返回。

字段使用名称而不是 id

任何在前面的步骤中使用经典视图,然后查看其 YAML 表示的人可能会注意到有些字段包含名称,有些包含 id。有些甚至两者都有,这是选择器的副作用。如果您从列表中选择一个值,如果您直接在同一字段中键入,它将包含一个 ID,尽管您可能会输入一个名称(就像您在 YAML 可能会做的那样)。

Executions API 只支持名称,不支持 id,从而消除了大量查找的需要。因此,步骤现在也只支持名称。

如果您正在将一个早期版本的 step 升级到经典管道中的 v6,并且在过去使用了一个选择器,那么您将会看到一个 ID 出现在字段中,并且您必须将该值编辑为名称。

构建信息特性首次发布时,它被称为包元数据。为了向后兼容,我们保留了一些残余内容。这在今天的 YAML 步骤中是可见的,在这里您使用OctopusMetadata作为步骤标识符。

从 v6 开始,该步骤的 YAML ID 将为OctopusBuildInformation。(OctopusMetadata将继续为 v4 和 v5 工作,因此现有管道不应受到影响)。

在经典编辑器中,您将无法从现有 v4/v5 步骤的下拉列表中选择 v6。插件现在认为它们是独立的,你必须添加一个新的构建信息步骤(在标题/描述中没有legacy的步骤)并复制这些值。

对于由此造成的任何不便,我们深表歉意,因为我们权衡了这一点与今后理解的难易程度,特别是考虑到 YAML 管道的普遍存在。

附加参数

在先前版本的步骤中,该字段用作输入未由特定字段表示的参数的后备选项。随着从 CLI 的过渡,此字段已过时,不再推荐使用。尽管它已经过时,但还是决定保留该领域的步骤,在这些步骤中,它可能被用来最大限度地减少干扰。

对于关键的东西,比如发布创建期间的包和部署发布时的变量,我们尽力解析现有的值,并将数据合并到这些值的新字段所提供的数据中。您将在日志中看到一条警告,让您知道这种情况已经发生,我们建议您在方便的时候尽早将值移动到新字段中。

管道演练示例

在本演练中,我们使用一个简单的 ASP.NET web 应用程序作为示例。在高层次上,管道是:

  • 使用“包装”。网络工具
  • 使用 Octopus Pack 步骤将输出放入 Octopus 可以使用的包文件中
  • 将这个包文件以及一些构建信息推送到 Octopus
  • 使用该包创建一个版本
  • 该版本的排队部署
  • 等待那些排队的任务完成

为了简洁起见,我们包括了。NET 构建/打包步骤,但是跳过经典模式中相同步骤的屏幕截图。

让我们从完整的 YAML 开始,然后我们会提到感兴趣的部分。

steps:
- task: DotNetCoreCLI@2
  displayName: 'dotnet restore'
  inputs:
    command: restore
    projects: 'source'

- task: DotNetCoreCLI@2
  displayName: 'dotnet build'
  inputs:
    command: build
    projects: 'source'
    arguments: '--configuration Release'

- task: DotNetCoreCLI@2
  displayName: 'dotnet publish'
  inputs:
    command: publish
    projects: 'source'
    arguments: '--configuration Release --output $(build.artifactstagingdirectory)'
    publishWebProjects: false
    zipAfterPublish: false
    modifyOutputPath: false

- task: OctopusPackZip@6
  name: pkg
  displayName: 'Package Zip'
  inputs:
    PackageId: $(pkgId)
    PackageVersion: '$(Build.BuildNumber)'
    SourcePath: '$(build.artifactstagingdirectory)'
    OutputPath: drop

- task: OctopusPush@6
  displayName: 'Push Packages to Octopus'
  inputs:
    OctoConnectedServiceName: $(connectionName)
    Space: $(spaceName)
    Packages: '$(pkg.package_file_path)'

- task: OctopusBuildInformation@6
  displayName: 'Push Package Build Information to Octopus'
  inputs:
    OctoConnectedServiceName: $(connectionName)
    Space: $(spaceName)
    PackageId: $(pkgId)
    PackageVersion: '$(Build.BuildNumber)'

- task: OctopusCreateRelease@6
  name: create_release
  displayName: 'Create Octopus Release'
  inputs:
    OctoConnectedServiceName: $(connectionName)
    Space: $(spaceName)
    Project: $(octoProject)
    Packages: '$(pkgId):$(Build.BuildNumber)'

- task: OctopusDeployRelease@6
  name: deploy
  displayName: 'Deploy Octopus Release'
  inputs:
    OctoConnectedServiceName: $(connectionName)
    Space: $(spaceName)
    Project: $(octoProject)
    ReleaseNumber: '$(create_release.release_number)'
    Environments: |
     Dev
     Staging

- task: OctopusAwaitTask@6
  displayName: 'Await Octopus Deploy Task'
  inputs:
    OctoConnectedServiceName: $(connectionName)
    Space: $(spaceName)
    Step: deploy 

需要注意的关键技术是:

  • 您想要引用输出变量的步骤必须定义一个name,例如name: create_release。然后在后面的步骤中使用该名称引用该变量。例如,ReleaseNumber: '$(create_release.release_number)'
  • Await 步骤理解由我们的 deploy/runbook 运行步骤发出的输出变量的结构,并在内部处理变量绑定。因此,您只需要向它提供 deploy/runbook 运行步骤的名称,而不是变量绑定
  • 具有复数名称的输入支持多个值,每个值作为单独一行输入(见示例中的Environments)

命名变量时要小心。我们艰难地认识到,声明一个名为packageId的变量对dotnet restore有特殊的意义。

经典管道

下面是在传统管道中建模的相同流程的视图。

Classic Pipeline

为了简洁起见,我们将只挖掘重要的细节。它们每个都包含与 YAML 管道中所示的输入对齐的字段。对于经典的管道,理解输出变量链接是如何工作的很重要。在支持输出变量的步骤中,您会在步骤的底部找到以下面板。

Output Variables

这是 Azure DevOps 检测到我们声明的步骤支持输出变量,并提供了一种将步骤连接在一起的方法。它要求获得与 YAML 的name属性相当的名称,这是该步骤中的唯一名称。然后,在变量绑定的后续步骤中使用该名称,就像在 YAML 中引用它们一样。

Using output variable

正如在 YAML 的例子中所提到的,Await 步骤具有来自 deploy/runbook 运行步骤的输出变量结构的内部知识,因此您使用步骤引用名。

Await step

结论

Octopus Azure DevOps 插件的新步骤和更新改进了部署过程、任务执行和包创建的自动化。这为您提供了更高的一致性和更无缝的用户体验。

为了帮助您使用 YAML 管道,我们还更新了市场列表,提供了所有输入和输出变量的详细信息。

此版本的目标是为您提供强大且用户友好的步骤来管理您的部署,我们相信它将显著增强您的体验。

愉快的部署!

Azure 环境- Octopus 部署

原文:https://octopus.com/blog/azure-envs

在过去的几个月里,我们收到了越来越多的请求,要求支持 Azure 环境而不是 AzureCloud,例如 Azure 德国、Azure 中国、Azure 美国政府等。

最近,我们接受了一个针对 Calamari 的 Pull 请求,这样它就可以使用备用端点连接到其他 Azure 环境。这种配置是通过设置特殊变量来完成的,这些变量在部署执行时传递给 Calamari。举个例子,

这种方法有效,但是有两个限制。首先,变量的名称和值不容易被发现,其次,这些变化只允许 Calamari 处理不同的 Azure 环境。后一个限制的含义是,你不能在 Azure 步骤的 UI 中使用选择器,例如选择资源组或 Web 应用程序,因为它们不知道特殊变量或如何处理它们。解决方法是插入包含资源组名称等字符串文字的变量,也就是说,您只需知道要部署到的内容的字符串名称。例如,对于一个 Azure Web 应用程序,你必须有这样的东西

所以你可能已经从我所有的过去时态的谈话中猜到了,我们已经更新了 Octopus Deploy server 来更好地处理 Azure 环境。该更新(在 Octopus Deploy 3.9 中可用)建立在之前的工作基础上,但允许您将环境和端点覆盖指定为 Azure 帐户定义的一部分。这样,当连接到 Azure 时,服务器和 Calamari 都可以看到并使用这些值。这是一个新的 Azure 账户页面的例子

对于那些目前使用变量将值传递给 Calamari 的人来说,这种方法在升级后应该可以继续工作,只是现在不需要这样做了。服务器现在将根据帐户设置自动设置相同的变量,因此升级后您可以逐步使用帐户,然后您将拥有能够在 UI 中选择值的优势。

理论上,这一切都很好

你可能已经注意到了上一节中的几个“应该”。全面测试这种类型的功能是棘手的,我们没有订阅任何替代 Azure 环境,所以完全有可能会有一些小问题需要解决。这也是该功能的初始版本。我们最终希望能够让用户从下拉列表中选择环境,并在后台填充所有端点覆盖,但是我们还没有一个可靠的方法来以编程方式加载这些数据。目前,这应该可以让那些需要部署到这些 Azure 环境中的人畅通无阻,我们会随着时间的推移进一步改进 UI。

我们将密切关注反馈,并尽可能对这一领域的问题做出回应。

如果您需要部署到备用 Azure 环境

了解更多关于配置 Azure 环境的信息。

用 Octopus Deploy 部署 Azure 函数

原文:https://octopus.com/blog/azure-functions

Azure Functions in the Cloud

根据许多云提供商的说法,无服务器计算应用模型是未来的发展方向(需要引用)。AWS Lambdas 和 Azure 函数都允许你编写代码,根据它们的实际使用情况付费。虽然这意味着您现在将被迫为编写松散的代码付出代价,但它也允许您编写和交付松散耦合的服务,这些服务只在代码执行时增加您的账单,而在代码空闲时没有任何成本。

在 Octopus Deploy,我们希望在未来几个月为 AWS Lambdas 提供一流的支持,所以请继续关注他们的到来。事实证明,Azure 功能基本上只是在引擎盖下的 Azure Web 应用程序,上面有一些额外的处理程序,所以我们现有的部署 Azure Web 应用程序的步骤仍然符合要求。

WebAppStep

为了证明这一点,并表明我并没有试图避免添加新的 Azure 函数步骤,让我们来看看如何通过 Octopus Deploy 构建和部署一个基本的 Azure 函数。

创建并打包一个简单的 Azure 函数项目

对于我们简单的 Azure 函数,我们将创建一个 HTTP 触发的端点,它返回一个 JSON 有效负载,其中包含一些我们希望 Octopus 在部署期间提供的值。

Visual Studio 项目

如果您通过 Visual Studio 创建函数,请确保您拥有 Visual Studio 2017 v15.4 或更高版本,其中包括 Azure SDKs。

创建一个新项目并选择Azure Functions项目类型。右击项目,添加新项目,添加一个 Azure 函数

用以下内容替换生成的类:

 public static class ReticulateSplines
    {
        [FunctionName("ReticulateSplines")]
        public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Function, "get", Route = null)]
            HttpRequestMessage req, TraceWriter log)
        {
            log.Info("Incoming Request request.");
            var myName = Environment.GetEnvironmentVariable("MyName", EnvironmentVariableTarget.Process);
            var release = Environment.GetEnvironmentVariable("Release", EnvironmentVariableTarget.Process);
            var reponse = new {Message = $"Hello {myName}", Release = release};
            return req.CreateResponse(HttpStatusCode.OK, reponse);
        }
    } 

当被调用时,这个函数将提取名为MyNameRelease的变量,并在 JSON 响应中将它们返回给用户。

在解决方案资源管理器中打开local.settings.json文件,并添加以下属性:

{
  "IsEncrypted": false,
  "Values": {
    "MyName": "Steve",
    "Release":  "0.0.0"
  }
} 

这些值在本地开发过程中使用。如果您从 Visual Studio 运行该解决方案,Azure Functions 开发环境应该会启动并提供一个本地端点,您可以用它来测试您的代码。

【T2 Running Azure Developer SDK

running localhost

章鱼包装

遗憾的是,由于 Azure 函数项目的输出,标准的 OctoPack 生成的 NuGet 包将无法工作。这些函数的配置文件是在构建阶段之后生成的,也就是 Octopack 被配置为生效的时候。我们建议使用dotnet publish将项目发布到一个目录,然后打包生成的文件:

folder

幸运的是,由于 Octopus 会很乐意部署任何打包到 zip 中的东西,我们可以利用另一个名为 Octopus CLI 的 Octopus 命令行工具。使用您的标准构建工具(或者甚至在本地进行测试),确保当前工作目录集是项目目录的,并调用:

dotnet publish --output bin\Release\PublishOutput --configuration Release
cd bin\Release\PublishOutput
octo pack --id=AcmeFunctions --format=zip --outFolder=./dist --version=9.14.159-pi
octo push --server=http://myoctopusserver.acme.com --apiKey=API-ABC123IS4XQUUOG9TWDXXX --package=dist/AcmeFunctions.9.14.159-pi.zip 

替换 Octopus 服务器、API 密钥和版本信息的相关值。或者,你可以使用我们为 TeamCityVSTSBamboo 或即将推出的 AppVeyor 开发的插件,将项目的内容打包成 zip 文件。

创建 Azure 函数

虽然我可以在一个单独的部署项目中使用部署 Azure 资源组步骤来构建 Azure 功能,但为了保持演示的简单,我将直接通过 Azure 门户创建该功能。

在门户中点击创建资源按钮,搜索功能 App 。填写细节并记下App nameResource Group值,因为我们需要很快将它们添加到我们的 Octopus 项目中。创建功能应用程序后,将其打开,进入功能应用程序设置页面并启用插槽。这个特性目前被标记为预览,虽然不是必需的,但它将允许我们创建一个蓝色\绿色部署模式。采用这种策略,我们首先部署到一个插槽,并确认其配置和运行正确,然后将其与生产插槽交换。在这种情况下,术语生产不同于生产环境和 Octopus 生产环境。它只是指 Azure 功能有多个可以独立配置的端点。启用该功能后,创建一个名为蓝色的新插槽:

CreateFunction

创建一个章鱼项目

我们现在将在 Octopus deploy 中创建项目,该项目将使用蓝色\绿色部署策略将我们的包推送到 Azure,并提供在我们的函数中使用的适当范围的变量。

注意:在 Octopus Deploy 中跨多个环境部署 Azure 功能的合适模型是为每个环境提供一个单独的 Azure 功能。这使我们能够在每个阶段安全地配置功能,而不会在环境中泄漏潜在的更改。我们建议您不要试图在一个函数上使用多个插槽来模拟环境。Azure 的功能很便宜,除了在使用的时候,你不需要花费任何东西,所以没有理由像其他云资源一样,把它们“挤”在一起。

添加变量

由于我们将需要编写几个部署后步骤来处理插槽交换,因此将所有配置放入项目的变量部分允许我们将它们整合到一个地方,并在不同的环境中对它们进行调整。在标准部署生命周期的情况下,我们通常会在不同的 Octopus 环境中使用不同的 Azure 资源组和/或 Azure 功能应用。

对于我们简单的单一环境场景,这些值是:

- AzureFunctionName = "OctoAcmeFunction"
- AzureResourceGroupName = "OctoAcmeRG"
- AzureStagingSlotName = "Blue"
- MyName = "Max Powers" 

T32

步骤 1:部署功能

如上所述,Azure 功能有效地使用了与标准 Azure Web 应用相同的架构,因此我们可以在 Octopus 中创建一个项目,该项目使用部署 Azure Web 应用步骤来推送包。

使用上面定义的项目变量,设置资源名称和 Web app。因为我们计划首先部署到暂存槽,所以这一步的 Web 应用程序名称采用了<WebAppName>(<SlotName>)的格式:

Step 1: Deploy Function

步骤 2:更新 AppSettings

虽然我们可以在包上传过程中对配置文件执行变量替换,但推荐的处理 Azure 函数配置值的方式是通过 AppSettings。这些将自己作为环境变量暴露给正在运行的函数进程。

AppSettings 还包含 Azure Functions 本身使用的其他环境变量,因此我们不能删除其中包含的任何值。最安全的方法是首先加载现有变量,更新我们想要更改的几个关键属性,然后更新整个集合(Azure PowerShell cmdlets 不提供修改单个值的粒度方法)。

创建一个运行一个 Azure PowerShell 脚本步骤,并提供以下脚本:

function UpdateAppSettings {
 param( [string]$ResourceGroup, [string]$FunctionAppName, [string]$Slot, [hashtable]$AppSettings )

    Write-Host "Loading Existing AppSettings"
    $webApp = Get-AzureRmWebAppSlot -ResourceGroupName  $ResourceGroup -Name $FunctionAppName -Slot $Slot

    Write-Host "Applying New AppSettings"
    $hash = @{}
    ForEach ($kvp in $webApp.SiteConfig.AppSettings) {
        $hash[$kvp.Name] = $kvp.Value
    }

    ForEach ($key in $AppSettings.Keys) {
        $hash[$key] = $AppSettings[$key]
    }

    Write-Host "Saving AppSettings"
    Set-AzureRMWebAppSlot -ResourceGroupName $ResourceGroup -Name $FunctionAppName -AppSettings $hash -Slot $Slot | Out-Null
    Write-Host "AppSettings Updated"
}

UpdateAppSettings -AppSettings @{"MyName" = $OctopusParameters["MyName"]; Release = $OctopusParameters["Octopus.Release.Number"]} `
    -ResourceGroup $OctopusParameters["AzureResourceGroupName"] `
    -FunctionAppName $OctopusParameters["AzureFunctionName"] `
    -Slot $OctopusParameters["AzureStagingSlotName"] 

请注意我们是如何提供要应用于 AppSettings 的 Octopus 变量的。这允许我们精确地知道我们的函数需要什么,而不是盲目地遍历所有的章鱼变量,并假设可能需要某些东西。

一旦在部署过程中运行了这个步骤和前面的步骤,蓝色的插槽就会用最新的包及其变量进行更新。该功能先前部署的版本(假设这不是流程第一次运行)仍可从生产插槽中获得。去往https://octoacmefunction.azurewebsites.net/api/ReticulateSplines的所有流量仍将去往之前的版本,但是https://octoacmefunction-blue.azurewebsites.net/api/ReticulateSplines的端点现在将使用新的部署,我们可以对其进行测试并确保其按预期工作。下一步将交换这些插槽,这样没有插槽名称的请求就可以转到我们当前部署在蓝色插槽中的插槽:

Live Browser

第三步:互换位置

还有一个为 Azure Web 应用程序构建的现有步骤,我们也可以很好地利用 Azure 函数。添加一个新步骤,并在步骤库中搜索Switch Azure Staging Deployment Slot步骤。为上述第一步中提供的ResourceGroupNameAppNameSlotName提供变量。对于AzureAccount字段,您目前需要获得您在 Octopus 中配置的 Azure 帐户的帐户 ID。当你通过八达通门户网站查看帐户时,可以在 URL 中看到这一点。在接下来的几周内,我们预计这一需求将会消失,因为我们将为 Azure 账户提供一个类型化变量,就像我们为 AWS 账户所做的一样。

Step 2: Slot Swap

SmokeTest配置将命中该函数的主机地址,尽管它更适合预热 Web 应用程序,但确保该函数已成功部署也无妨。

部署

对于每个部署,蓝色的槽将作为更新目标,然后指向两个不同槽的外部指针将被交换(记住这实际上是一个名称交换,内容本身不会移动)。如果新的部署开始遇到问题,可以选择将插槽交换回,以便流量再次被传送到以前的版本(尽管我们总是鼓励尽可能使用前滚方法):

Slot Swap

Octopus 中的 Azure 函数

正如我们所见,虽然 Azure 函数提供了一种开发和托管代码的新机制,但底层基础设施主要是建立在 Azure 网站产品的基础上,因此它已经在 Octopus Deploy 开箱即用中工作。通过 Octopus 管理这些部署提供了一个简单易懂的流程,允许任何人利用这种新的无服务器计算方法的能力。随着我们未来计划很快为 AWS Lambdas 提供一流的支持,没有理由不尝试这些新产品。

微软贬低 Azure 服务管理 API 对八达通用户意味着什么?-章鱼部署

原文:https://octopus.com/blog/azure-management-certs

TL;dr;

Azure 已经否决了 Azure 服务管理 API,为了更好地利用新工具,从版本2018.12.0开始,Octopus 将不再在使用 Azure 证书帐户时主动从门户中检索云服务或存储帐户详细信息。尽管有这些 UI 变化,部署将继续工作,并且仍然可以创建使用它们的新步骤。

更长的故事

Azure 服务管理(ASM) API 是以编程方式与您的 Azure 资源进行交互的原始机制,直到 Azure 资源管理器(ARM) API 在 2014 可用。

ARM API 是用于与 Azure 交互的新(更)API,引入了资源组等概念,以更好地对相关资源进行建模,并为旧 API 中不可用的一些新服务提供支持。因此,他们已经开始反对旧的 ASM。引用 Naveed Aziz 在 Azure 团队博客上的话

...服务管理 API 已经过时,不太适合现代云。

那很好,对吗?放弃旧的 API 而支持新的 API 通常不是什么大问题,只需修改几行代码,很快,您就会遇到一些新的端点,并且您的 XML 有效负载变成了 JSON...问题是 Azure 并没有将所有可用的功能从 ASM 转移到 ARM。

管理证书

首先,ARM 不支持使用管理证书进行身份验证,而是指导用户使用 Azure Active Directory 并创建服务主体。这种认证机制在 Octopus 中已经有一段时间了,我们建议任何仍在使用 Azure 证书的人今天就升级到服务主体。对这一变化的一个警告是由于一个仅适用于管理证书的特性,因为它在 ASM 中仅支持

Azure 云服务

Azure 云服务是 Azure 为托管客户网站提供简单 PaaS 模型的首批尝试之一。当 Azure 去年概述 ASM 的退役时,不太明显的是,这也对使用 Azure 云服务本身的能力产生了影响,因为没有办法使用新的 ARM APIs 来管理这些服务。在与 Azure 团队进一步讨论后,实际的 HTTP API 端点似乎仍然可用,因为 Visual Studio 本身实际上需要使用它们。然而,由于唯一可以利用它们的工具已经被弃用,我认为可以肯定 Azure 渴望人们放弃云服务。

这些变化将如何影响我的部署?

简而言之,它根本不会影响的部署。😃对于 Octopus 用户来说,上述可用 API 的变化意味着,为了使用新的库,我们将不再能够在门户中使用旧的。因此如果你使用 Azure 管理证书,那么在配置目标或云服务部署步骤时,Octopus 服务器将不再自动填充任何 Azure 云服务或存储帐户。然而,部署将继续工作,并且仍然可以创建新的目标&步骤

不要像这样选择您的云服务:

before

将为您提供一组文本字段。

before

(顺便说一下,这实际上与我们最初公开这些特性的方式没有太大的不同。)

这种变化将同时出现在 Azure 云服务目标和 Azure 云服务部署步骤上(如果选择了“遗留”模式)

T2 T4 target

这些变化将从 Octopus 服务器版本2018.12.0开始出现。

向前和向上

为了继续使您的部署与 Azure 提供的越来越多的功能更容易集成,当他们说:

支持服务管理 API 将会阻碍我们在全球范围内交付优质的开发人员体验和控制。

虽然在幕后,我们将继续提供使用云服务管理证书执行部署的能力,但从 Octopus 门户中删除这些库意味着我们必须删除一些以前可用的功能。尽管努力保持向后兼容性是我们决策的一个关键部分,但有时旧的功能不得不被放弃。

八达通部署在 Azure Marketplace -八达通部署

原文:https://octopus.com/blog/azure-marketplace

Octopus Deploy 现已在 Azure Marketplace 上市!

Octopus Deploy Marketplace Search Result

开始使用 Octopus Deploy 的最简单方法

Octopus Deploy marketplace 产品是一个解决方案模板,它创建了一个托管 Octopus Deploy 服务器的虚拟机和一个 Azure SQL 数据库实例。

您可以配置虚拟机的大小、DNS 名称以及管理员用户名和密码。点击“创建”,在一杯咖啡(it 专业人员的国际标准时间单位)中,您将有一个 Octopus Deploy 服务器可供使用。

Octopus Deploy Azure Infrastructure Blade

BYO 许可证

Octopus Deploy 实例是用 45 天的试用许可创建的。试用期过后,你可以继续免费使用我们的社区版(针对小团队),或者你可以购买一个许可(或者使用你现有的许可)并将其输入到运行在 Azure 中的 Octopus Deploy 实例中。

祝 Azure 驱动的部署愉快!

为复杂安装使用 Azure 自定义脚本扩展- Octopus Deploy

原文:https://octopus.com/blog/azure-script-extension

Azure custom script extensions

首次启动虚拟机(VM)时,运行自定义脚本通常很有用。该脚本可能会安装附加软件、配置虚拟机或执行一些其他管理任务。

在 Azure 中,自定义脚本扩展提供了运行脚本的能力。当 Windows Azure 虚拟机与 Chocolatey 等工具相结合时,几乎可以用任何您需要的软件来初始化新的虚拟机。

然而,你需要考虑 Windows 的一些边缘情况,在这篇博客文章中,我们将深入研究通过 Azure 自定义脚本扩展执行复杂安装的细节。

一个简单的例子——配置一个 Windows Azure 虚拟机

让我们从一个非常简单的例子开始。以下 Terraform 示例脚本使用自定义脚本扩展配置 Windows 虚拟机:

variable "resgroupname" {
  type = "string"
}

resource "azurerm_resource_group" "test" {
  name     = "${var.resgroupname}"
  location = "Australia East"
}

resource "azurerm_public_ip" "test" {
  name                    = "test-pip"
  location                = "${azurerm_resource_group.test.location}"
  resource_group_name     = "${azurerm_resource_group.test.name}"
  allocation_method       = "Dynamic"
  idle_timeout_in_minutes = 30
}

resource "azurerm_virtual_network" "test" {
  name                = "acctvn"
  address_space       = ["10.0.0.0/16"]
  location            = "${azurerm_resource_group.test.location}"
  resource_group_name = "${azurerm_resource_group.test.name}"
}

resource "azurerm_subnet" "test" {
  name                 = "acctsub"
  resource_group_name  = "${azurerm_resource_group.test.name}"
  virtual_network_name = "${azurerm_virtual_network.test.name}"
  address_prefix       = "10.0.2.0/24"
}

resource "azurerm_network_interface" "test" {
  name                = "acctni"
  location            = "${azurerm_resource_group.test.location}"
  resource_group_name = "${azurerm_resource_group.test.name}"

  ip_configuration {
    name                          = "testconfiguration1"
    subnet_id                     = "${azurerm_subnet.test.id}"
    private_ip_address_allocation = "Dynamic"
    public_ip_address_id          = "${azurerm_public_ip.test.id}"
  }
}

resource "azurerm_storage_account" "test" {
  name                     = "${var.resgroupname}"
  resource_group_name      = "${azurerm_resource_group.test.name}"
  location                 = "${azurerm_resource_group.test.location}"
  account_tier             = "Standard"
  account_replication_type = "LRS"
}

resource "azurerm_storage_container" "test" {
  name                  = "vhds"
  resource_group_name   = "${azurerm_resource_group.test.name}"
  storage_account_name  = "${azurerm_storage_account.test.name}"
  container_access_type = "private"
}

resource "azurerm_virtual_machine" "test" {
  name                  = "acctvm"
  location              = "${azurerm_resource_group.test.location}"
  resource_group_name   = "${azurerm_resource_group.test.name}"
  network_interface_ids = ["${azurerm_network_interface.test.id}"]
  vm_size               = "Standard_D2_v3"

  storage_image_reference {
    publisher = "MicrosoftWindowsServer"
    offer     = "WindowsServer"
    sku       = "2019-Datacenter"
    version   = "latest"
  }

  storage_os_disk {
    name          = "osdisk"
    vhd_uri       = "${azurerm_storage_account.test.primary_blob_endpoint}${azurerm_storage_container.test.name}/osdisk.vhd"
    caching       = "ReadWrite"
    create_option = "FromImage"
  }

  os_profile {
    computer_name  = "hostname"
    admin_username = "testadmin"
    admin_password = "passwordoeshere"
  }

  os_profile_windows_config {
    enable_automatic_upgrades = false
    provision_vm_agent = true
  }
}

resource "azurerm_virtual_machine_extension" "test" {
  name                 = "hostname"
  location             = "${azurerm_resource_group.test.location}"
  resource_group_name  = "${azurerm_resource_group.test.name}"
  virtual_machine_name = "${azurerm_virtual_machine.test.name}"
  publisher            = "Microsoft.Compute"
  type                 = "CustomScriptExtension"
  type_handler_version = "1.9"

  protected_settings = <<PROTECTED_SETTINGS
    {
      "commandToExecute": "powershell.exe -Command \"./chocolatey.ps1; exit 0;\""
    }
  PROTECTED_SETTINGS

  settings = <<SETTINGS
    {
        "fileUris": [
          "https://gist.githubusercontent.com/mcasperson/c815ac880df481418ff2e199ea1d0a46/raw/5d4fc583b28ecb27807d8ba90ec5f636387b00a3/chocolatey.ps1"
        ]
    }
  SETTINGS
} 

这个脚本的重要部分是azurerm_virtual_machine_extension资源。在settings字段中,我们有一个 JSON blob 列表脚本要下载到fileUris数组中,在protected_settings字段中,我们有另一个 JSON blob,它带有一个commandToExecute字符串,定义了我们将要运行的脚本的入口点。

在本例中,我们从 GitHub Gist 下载一个 PS1 PowerShell 脚本文件,GitHub Gist 安装 Chocolatey,然后安装 Notepad++和 Chocolatey 客户端:

Set-ExecutionPolicy Bypass -Scope Process -Force
iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
choco install notepadplusplus -y 

下载的脚本文件然后由分配给commandToExecute字段的字符串运行,带有一个exit 0以确保脚本扩展注册我们的脚本成功运行:

 "commandToExecute": "powershell.exe -Command \"./chocolatey.ps1; exit 0;\"" 

试图将 PowerShell 脚本编码到commandToExecute JSON 字符串中很快变得难以管理。使用fileUris下载脚本是一个更好的解决方案,如果需要,脚本可以托管在 Azure blob 存储中以获得更好的安全性。

这个例子非常简单,对于简单的软件安装,这就是我们所需要的。不幸的是,并不是所有的软件都这么容易安装。

在 Azure 虚拟机上自动安装 SQL Server

为了查看此示例的失败之处,我们将尝试安装 Microsoft SQL Server Express:

Set-ExecutionPolicy Bypass -Scope Process -Force
iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
choco install sql-server-express -y 

SQL Server 显然是一个比 Notepad++更复杂的软件,在试图安装它时,我们遇到了一些错误。在 Chocolatey 日志中,我们可以看到 SQL Server 安装失败:

2019-11-06 05:47:59,622 2240 [WARN ] - WARNING: May not be able to find 'C:\windows\TEMP\chocolatey\sql-server-express\2017.20190916\SQLEXPR\setup.exe'. Please use full path for executables.
2019-11-06 05:47:59,751 2240 [ERROR] - ERROR: Exception calling "Start" with "0" argument(s): "The system cannot find the file specified" 

出现此错误是因为运行自定义脚本的系统帐户不能与 SQL Server 安装一起使用。我们需要一种方式来运行安装作为一个普通的管理员帐户。

在新帐户下运行 PowerShell 脚本

PowerShell 为以不同用户身份运行代码提供了一个方便的解决方案。通过调用Invoke-Command,我们可以作为我们选择的用户在本地(或者远程,如果需要的话)VM 上执行一个脚本块。

下面的代码显示了我们如何构建一个凭证对象,并将其传递给Invoke-Command命令,以管理员用户的身份执行 Chocolaty 安装:

用户名必须采用machinename\username格式,该命令才能正常工作。

$securePassword = ConvertTo-SecureString 'passwordgoeshere' -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential 'hostname\testadmin', $securePassword
Invoke-Command -ScriptBlock {choco install sql-server-express -y} -ComputerName hostname -Credential $credential 

这个几乎和一样有效,但是现在我们得到了这个错误:

Inner exception type: System.InvalidOperationException
       Message:
               There was an error generating the XML document.
       HResult : 0x80131509 

不幸的是,对Invoke-Command的默认调用没有授予足够的权限来完成安装。我们遇到了双跳问题,这导致了上面显示的异常。

支持 PowerShell 双跃点

在远程机器上执行代码,或者在同一台机器上执行“远程”代码,被认为是第一跳。让代码继续访问另一台机器被认为是一个 PowerShell 双跳,当使用Invoke-Command时,这个第二跳被默认阻止。我们的 SQL Server 安装遇到的其他调用也被视为第二跳。

为了允许 SQL Server 安装完成,我们需要允许这种双跃点发生。我们通过利用 CredSSP 身份验证来做到这一点。

第一步是让我们的机器承担Server角色,这意味着它可以接受来自客户端的凭证:

Enable-WSManCredSSP -Role Server -Force 

然后我们需要让我们的机器承担Client角色,这意味着它可以向服务器发送凭证。我们同时启用客户机和服务器角色,因为我们使用Invoke-Command在同一台机器上运行命令:

Enable-WSManCredSSP -Role Client -DelegateComputer * -Force 

因为我们运行代码的帐户是本地帐户(与域帐户相反),所以我们需要允许使用 NTLM 帐户。这是通过设置注册表项来实现的:

New-Item -Path HKLM:\SOFTWARE\Policies\Microsoft\Windows\CredentialsDelegation -Name AllowFreshCredentialsWhenNTLMOnly -Force
New-ItemProperty -Path HKLM:\SOFTWARE\Policies\Microsoft\Windows\CredentialsDelegation\AllowFreshCredentialsWhenNTLMOnly -Name 1 -Value * -PropertyType String 

有了这些更改,我们就可以使用 CredSSP 身份验证运行 Chocolaty 安装了。这将启用双跃点,我们的 SQL Server 安装将成功完成:

$securePassword = ConvertTo-SecureString 'passwordgoeshere' -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential 'hostname\testadmin', $securePassword
Invoke-Command -Authentication CredSSP -ScriptBlock {choco install sql-server-express -y} -ComputerName hostname -Credential $credential 

完整的脚本已经保存在这个要点中。

结论

虽然使用 Azure 自定义脚本扩展,大多数脚本都将按预期运行,但有时您需要以管理员用户的身份运行脚本。在本文中,我们看到了如何利用 CredSSP 身份验证来确保脚本以安装 SQL Server 等复杂应用程序所需的最高权限运行。

Octopus 部署的 Azure VM 扩展- Octopus 部署

原文:https://octopus.com/blog/azure-vm-extension

今天 ScottGu 宣布 Octopus Deploy 触手代理现在可以作为 Azure VM 的的扩展使用:

Octopus 通过自动配置 IIS、安装服务和进行配置更改,简化了 ASP.NET web 应用程序、Windows 服务和其他应用程序的部署。Azure 的 Octopus 集成是 Azure UserVoice 上最受欢迎的功能之一,通过这一集成,我们将简化 octopus 在 VM 上的部署和配置。

当然,即使在这个扩展之前,你也可以通过脚本手动或自动安装触手。这个扩展只是在它周围放了一个漂亮的 UI。在引擎盖下,扩展使用我们的开源 PowerShell DSC 资源用于触角

The extension on Azure

为什么 Azure 虚拟机上有触须?

在 Microsoft Azure 上托管应用程序有许多不同的方式:网站、云服务或常规方式。NET 应用程序在虚拟机上运行。

当你在 Azure 上配置一个虚拟机时,你会得到一个正在运行的操作系统、一个远程桌面连接和一个 PowerShell 远程连接。仅此而已。如果您想在机器上部署、配置和重新部署应用程序,您要么需要手动完成,要么编写定制脚本来复制文件、更新配置文件等等。

当然,这些都是【Octopus Deploy 解决的所有问题,并且解决。通过将触手代理添加到 Azure VM,您可以立即开始部署到它,就像 Octopus 中的任何其他机器一样。

关于使用扩展或者通过 PowerShell 从命令行添加扩展的更多信息,查看我们的文档

集装箱化的好处-八达通部署

原文:https://octopus.com/blog/benefits-of-containerization

作为一种新技术,容器已经成为一种工具,可以帮助您的企业在软件开发生命周期中变得更加敏捷。与更传统的软件交付方法相比,容器有很多好处,可以给你带来竞争优势。

在这篇文章中,我解释了什么是容器,分享了容器对于软件开发的主要好处,并讨论了为什么您可能会考虑将它们添加到您的 DevOps 过程中。

什么是集装箱化?

容器是一个轻量级的、可移植的计算环境,它包含了独立运行所需的所有文件。

容器化是使应用程序作为容器运行的过程。一旦应用程序可以作为容器运行,它就可以运行,而不管用来执行容器的基础设施是什么。容器加载了容器映像,这些映像在容器内部运行特定的应用程序。如果你想建立一个现代化的应用程序,从建立数据库,到加载不同的操作系统,到访问深度学习平台,你将需要容器化。

集装箱化近年来被广泛采用,部分原因是云技术的可用性。云技术让你可以扩展和复制容器,并且降低了进入门槛。

如果你在 DevOps 工作,你可能以前和容器一起工作过。如果你正在尝试开始,查看一下 DockerHub 图像库看看你能使用什么图像或者我们关于开始使用容器的帖子

集装箱化的好处是什么?

容器化可以成为您增强软件开发生命周期的有用工具。

好处包括:

  • 容器补充了您的开发运维流程
  • 容器是可伸缩的,并且有效地分配资源
  • 容器是可移植的,所以你可以构建一次,在任何地方运行

容器补充了您的开发运维流程

在我们对 DevOps post 的介绍中,我们讨论了 DevOps 作为一个概念是如何消除软件交付中的障碍的。

DevOps 细化了开发人员和客户之间的每个过程,并鼓励更快的反馈循环、实验和学习。DevOps 是一种专注于敏捷性和自动化的实践。

容器化补充了 DevOps,因为软件可以更快地部署和测试,改善了反馈循环。容器化也是微服务流行的一个主要因素,微服务是一种提高灵活性和敏捷性的软件架构。您可以使用容器化来加快开发新功能和获得反馈的时间。改善产品的反馈循环会带来更好的产品和更满意的客户。

容器是可伸缩的,并且有效地分配资源

平台即服务(PaaS)解决方案和 Kubernetes 等容器编排工具让开发人员可以大规模操作容器。容器编排器可以根据需求和负载放大和缩小软件应用程序中的单个组件。由于组件只在需要时运行,因此可以节省成本。伸缩还提高了可靠性,因为容器编排器可以将足够的资源分配给应用程序的高需求部分。

当决定迁移到集装箱化时,规模和成本节约是重要的因素。许多云提供商都有云资源的成本计算器,如果您希望您的部门转换到容器,您可以使用它。

容器是可移植的:构建一次,在任何地方运行

因为容器是可移植的,所以它们可以在任何基础设施上的任何地方运行,比如云中、虚拟机上或裸机上。

开放集装箱倡议(OCI) 为集装箱设计开放标准,确保任何符合 OCI 标准的集装箱在任何基础设施上都能以相同的方式运行。

为了运行应用程序,容器加载了容器映像。容器映像是一个静态文件,包含在 IT 基础设施上运行流程的可执行代码。有针对不同用例的容器映像,例如数据库、web 服务器、操作系统等等。容器映像存储库是容器映像的公共访问点,这使得开发人员可以使用这些映像来加载容器。

如果您想为您的应用程序使用一个容器,您可以确保您使用的任何 OCI 映像都可以在您的基础结构上工作,即使您的基础结构发生了变化。

哪些是顶级容器图像?

Docker Hub 提供了一个流行的容器图片列表。一些顶级容器图像是:

  • Ubuntu:一个基于 Debian 的 Linux 操作系统。
  • NGINX:一个开源的 web 服务器、负载平衡器和反向代理,在许多应用程序中使用。
  • Postgres:一个使用 SQL 语言的开源关系数据库系统。
  • Redis:一种开源的内存数据结构存储,用作数据库、缓存和消息代理。
  • Alpine:围绕 musl libc 和 BusyBox 构建的 Linux 发行版。

流行的容器映像通常是开源的,可以满足软件应用程序的基本需求,比如数据库、web 服务器或缓存。这些用例对于大多数软件项目来说是常见的,并且已经构建了工具来解决它们。

如果你正在开始一个软件项目,你不会想重新发明轮子,自己想出如何构建一个关系数据库或 web 服务器——容器化意味着你不必这样做!容器化的力量有助于开发人员在现有解决方案的基础上解决新问题。

微软 Azure、亚马逊 Web 服务和谷歌云平台等云 PaaS 解决方案提供了运行 Docker 和 Kubernetes 等技术的基础设施。开源的 Docker 容器技术于 2013 年推出。从那以后,它作为领先的容器技术被广泛采用。Kubernetes 是最流行的容器编排技术,与 Docker 一起用于管理和扩展容器解决方案。Kubernetes 是一个管理层,它组织和提供基础设施来托管容器和执行工作负载。

容器化的前景是不断变化的,因此您应该监控主要的更新,以检查您的软件栈是否受到任何变化的影响。例如,虽然 Docker 一直是 Kubernetes 上运行的最常见的容器技术,但 Kubernetes 更新版本1.24 不赞成使用 Docker shim——一个提供 Docker 和 Kubernetes 之间兼容性的底层模块。此次更新主要是由于 Docker 与容器运行时接口的兼容性。Docker 为 Dockershim 开发了一个名为 cri-dockerd 的替代品,为仍然希望使用 Docker 引擎在 Kubernetes 中运行容器的用户解决了兼容性问题。CRI-Docker 适配器允许您通过容器运行时接口使用 Docker 引擎。

Datadog 在 2021 年的一份报告显示,集装箱采用量增加了 6%,码头使用率也相应下降。随着 Kubernetes 不再完全支持 Docker,容器采用率的增长可能会继续。集装箱化和集装箱编排领域正在快速发展。技术工具和受欢迎程度可能会改变,但容器化和容器编排概念会一直存在。

Octopus 部署中的集装箱化支持

部署流程可以使用某种形式的容器或容器编排来部署应用程序。Octopus 是一个支持容器化的部署管理工具。Octopus Deploy 与容器注册中心、PaaS 提供商、Docker 和 Kubernetes 合作,提供了一个一流的部署管理工具。无论哪种容器技术最受欢迎,Octopus Deploy 都可以与所有这些技术一起提供更好的部署。

结论

容器是独立的计算环境,容器化将应用程序转换成可运行的容器。容器化赋予了开发过程灵活性和敏捷性,这有助于开发过程。容器具有高度的可移植性,符合 OCI 标准的容器只需构建一次,就可以在任何地方运行。有了 PaaS 解决方案和像 Kubernetes 这样的容器编排工具,容器是可伸缩的,可以有效地分配资源。

集装箱化是一个不断变化的研究领域。特定工具的流行程度可能会改变,但是 Octopus Deploy 是容器和云不可知的。它与一系列容器注册中心、PaaS 提供商、Docker 和 Kubernetes 一起工作,帮助您简化复杂的部署。

愉快的部署!

CI/CD - Octopus 部署的最佳实践

原文:https://octopus.com/blog/best-practices-for-cicd

持续集成和交付(CI/CD)将软件开发从代码转化为实际产品。CI/CD 是 DevOps 流程的一部分,其中包含许多公认的最佳实践,您可以遵循这些实践来改进您的部署渠道。

如果您在 DevOps 中工作,您可能已经使用过像 Jenkins 这样的构建服务器和像 Octopus Deploy 这样的部署工具来完成您的部署过程。Octopus 支持 CI/CD 的持续交付方面,提供了同类最佳的产品,使复杂的部署变得更加容易。

在八达通,我们相信 8 的力量。章鱼有 8 条肢体,因此这里有 8 个最佳实践来帮助您的部署之旅。

您还可以在我们的 DevOps 工程师手册中了解更多信息。

采用敏捷方法

敏捷方法对 CI/CD 和 DevOps 至关重要。敏捷是一种项目管理方法,包括与利益相关者的持续协作以及在部署过程的每个阶段的持续改进。

敏捷的原则是通过小的开发迭代获得频繁的反馈,这样开发人员就可以使最终产品与用户的需求紧密结合。敏捷方法与传统的瀑布方法形成对比,在传统的瀑布方法中,项目是在一个阶段中确定范围和交付的。

我们建议根据敏捷和精益原则管理软件项目,这样持续的反馈循环可以改进产品。我们已经看到敏捷被实现为上层管理人员的核对清单。在初始阶段,软件团队应用敏捷来满足清单。随着团队获得探索敏捷空间的许可,他们开始看到真正的好处。

使用受版本控制的代码,连接到部署过程,经常提交

如果你从事软件工作,你几乎肯定用过 Git。关于源代码控制的战争已经打响并取得了胜利,Git 现在是源代码控制的同义词。

受源代码控制的代码允许完整的历史记录和代码回滚到以前的版本。您还可以通过使用 Git 的合并方法来解决冲突。

提交代码更改应该会触发 CI/CD 管道构建。该触发器允许开发人员更早地测试和验证对代码库的更改。在代码变更被设置为触发自动构建之后,应该鼓励开发人员至少每天提交一次他们的代码。每日提交更频繁地触发自动化测试,因此开发人员可以更快地发现任何错误。

将配置用作部署流程的代码

Config as Code 表示您在基于 Git 的系统中的部署过程。部署继承了 Git 的所有优点,比如分支、版本控制和作为拉请求的批准。

Config as Code 允许您将部署过程存储在 Git 中。您可以在分支中测试对部署的更改,并通过拉请求来验证它们。基于 Git 的部署使得将部署设置从一个环境转移到另一个环境变得更加容易。

在 2022 年 Q1,我们发布了作为 Octopus Deploy 代码的 Config,并相信我们设立了一个行业标准。其他配置为代码的解决方案牺牲了功能的可用性。在 Octopus 中,无论您使用 UI 还是版本控制的实现,您都可以通过代码获得 Config 的所有特性。

CI/CD 管道中的绿色构建意味着每个测试都通过了,并且发布已经进展到下一个阶段。软件团队的目标是保持构建绿色。

您应该选择一个展示信息的部署工具来帮助保持构建绿色。许多部署流程只使用一个构建服务器,将发布版本推入生产环境。实际上,仅使用构建服务器会使管理不同部署阶段之间的发布变得更加困难。使用专用的部署工具为您提供了一个专用的管理层来保持构建的绿色性。

构建服务器不包括部署阶段的概念。然而,Octopus Deploy 将一个发布分为测试、开发和生产环境,并且在每个阶段,环境可以存在于不同的发布版本中。我们的 UI 显示了每个版本的部署阶段,并在阶段之间转换版本。Octopus UI 还显示日志和错误消息,帮助开发人员快速识别失败的构建。

持续自动化您的测试

测试代码变更对于生产可靠的版本是必不可少的。测试套件应该覆盖产品的所有用例,从功能测试到非功能测试。这些测试应该是自动化的,这样代码变更就可以触发自动化的测试和构建。自动化测试提高了软件开发项目的敏捷性,从而更快地发布产品。

Mabel 进行的一项关于 DevOps 测试状态的调查表明,自动化测试(至少 4 到 5 种不同类型的测试)是客户满意度的关键。在 2021 年 DevOps DORA 报告中,持续测试是成功的一个指标。达到可靠性目标的精英级员工利用持续测试的可能性是普通员工的 3.7 倍。

通过监控加强反馈回路

开发人员使用遥测数据(日志、指标和跟踪)来了解他们系统的内部状态。遥测技术开启了可观测性,因此开发人员可以根据数据来修复他们的系统。当您拥有遥测数据时,您可以使用可观测性工具为您的系统添加监控功能。

监控关键系统指标有助于诊断系统漏洞并确定改进措施。在 DevOps 社区中, DORA 指标被普遍认为是部署管道成功的关键指标。

Octopus 让您可以测量结果、比较项目状态,并通过 DevOps 专注于 DORA 指标的洞察力持续改进。

使用适用的技术

每年,都有一项新技术被人们宣称将彻底改变 IT 领域。无论是容器化、机器学习还是区块链,一些技术改变了游戏场,而另一些技术则太不成熟,无法产生真正的影响。在管理 CI/CD 渠道时,只选择适合用途的技术至关重要。

虽然云优先对某些部分来说是有意义的,但强迫所有东西都放在云上可能不是正确的解决方案。采用新技术可以带来显著的改进,但是当采用的成本超过收益时,采取谨慎的方法可以避免不必要的痛苦。

认真对待安全性

随着软件项目变得越来越大,安全风险随着更多的数据处理、用户和依赖性而增加。您的部署过程应该有一个安全策略。

许多云提供商,如 AWS、Azure 和 Google,都有内置的安全功能,如 IAM、secrets 和基于角色的权限。您可以使用这些特性来管理一些安全问题。

客户越来越关注安全性,公司需要投资 ISO 27001 和 SOC II 等认证,以证明他们符合安全法规。

2021 年 5 月 12 日,美国政府发布第 14028 号行政命令,“提高国家网络安全”。该命令要求政府软件项目的所有供应商提供软件材料清单(SBOM)。SBOMs 详细列出了所有的软件组件,以便政府可以对软件进行网络安全筛选。如果你想要一个如何制作一个 SBOM 并将其附加到你的部署流程中的例子,我们创建了一个名为 Octopus Workflow Builder 的免费工具可以帮到你。

结论

CI/CD 是 DevOps 模型的一部分,有助于将软件项目从代码带到客户面前。如果您在 DevOps 中工作并实施 CI/CD,您应该遵循管道的行业标准最佳实践。为了有所帮助,这篇文章涵盖了你可以用来充分利用 CI/CD 的 8 个最佳实践。

许多工具可以帮助您使用 CI/CD,从构建服务器和部署工具到监控解决方案。Octopus Deploy 作为连续部署解决方案适合 CI/CD,使复杂的部署变得更加容易。

要了解更多关于 CI/CD 的最佳实践,请查看我们的 DevOps 工程师手册

愉快的部署!

八达通空间的最佳实践-八达通部署

原文:https://octopus.com/blog/best-practices-spaces

在 Octopus 的这些年里,我们总是很高兴地看到我们的客户扩大了对 Octopus Deploy 的使用,以包括新的项目和团队。在早期,很难想象这会引起任何问题。然而,在 Octopus 中有太多的项目可能会分散注意力,特别是对于一个只负责其中几个项目的团队来说。

Octopus Spaces 功能可帮助您组织和保护您的项目、环境和基础架构,并控制哪些团队成员可以访问它们。对于安装多个 Octopus 部署实例来说,它们是一种有用的替代方法。

在本文中,您将学习如何有效地使用空间来组织部署。

空间概述

空间是在你的 Octopus 服务器中创建硬墙的分区。无法从另一个空间看到或使用分配给该空间的部署资源。您可以使用空间而不是创建多个 Octopus Deploy 实例,每个空间都是独立的。

以下项目的作用域为一个空间:

  • 环境
  • 生活过程
  • 项目
  • 可变集合
  • 部署目标
  • 房客

不能从其他空间访问这些空间范围的项目。

团队是一个特例。创建团队时,您可以选择是将其范围限定在单个空间,还是让团队跨越所有空间。

所有事件都被写入系统级审核日志,您可以按空间筛选日志。

八达通管理员可以将管理每个空间的全部责任交给空间管理员,这可以减少管理员的工作量。

空间的管理指南有管理空间的说明。

空间的好处

空间有两种主要的使用情形:

  • 组织部署资源
  • 控制对这些资源的访问

出于这两个原因中的一个或两个,您可能会选择使用空格。

组织部署资源

您的 Octopus Deploy 仪表板为每个项目显示一行,为生命周期中的每个阶段显示一列。当您添加更多项目时,仪表板会变高,当您创建更多阶段时,仪表板会变宽。如果你发现你的仪表板太大了,把项目移到共享空间会让它变得整洁并减少你滚动的次数。

在空间中的所有屏幕上,当从列表中选择资源时,您将获得相同的好处,例如编辑流程步骤,因为只显示当前空间中的项目。

空间允许您通过将相关资源组合在一起来限制每个区域的增长。

控制对部署资源的访问

在我们引入空间之前,您可以使用具有作用域角色的团队来控制项目的可见性。然而,这可能变得难以管理。很难阻止像部署目标这样的项目被新项目重用。

空间为您提供了一种便捷的方式来控制对一组相关资源的访问,而无需复杂的权限。您可以授予团队成员对他们需要的空间的完全或只读访问权限,然后他们可以使用空间切换器在这些空间之间快速切换。

The space switcher appears in the top-left of the Octopus Deploy navigation bar

如何设计你的空间

空间代表应用程序的逻辑组。如果您有几个密切相关的组件,它们很可能在一个变量集中共享一些变量,或者被部署到同一个基础设施。这些因素将自然地指导它们是否应该被分组到一个空间中或者保存在单独的空间中,以防止它们以不期望的方式变得相关。

为组织中的每个团队创建一个空间似乎是个好主意,但这并不总是最好的设计。特别是,在多个团队参与同一个应用程序的情况下,这可能会导致您将项目分割到太多的空间中。

相反,使用以下尺寸之一来设计您的空间:

  • 客户
  • 应用程序组
  • 应用受众
  • 公司部门

请继续阅读,了解关于这些选项的更多信息。

客户

如果您是一家为多个客户管理应用程序的机构,每个客户使用一个空间可以确保客户之间不会共享任何数据。所有部署目标、项目、变量和生命周期都是客户空间独有的。

您可以决定使用其他选项之一将客户端细分为多个空间。

当每个客户端有不同的应用程序时,此选项有效。如果您将同一个应用程序部署到特定于客户端的基础设施上,租户提供了一种更好的管理方式。

应用程序组

应用程序组是一组可能部署到同一位置的相关组件。

应用程序组是组织空间的理想选择,因为类似的设计考虑也适用于会影响应用程序设计的空间。例如,您可以将内容管理系统(CMS)的组件归入一个空间,而将计费系统的组件归入另一个空间。

你可能会发现这种空间设计与你组织团队的方式相匹配。如果您已经考虑了您的应用程序组,那么选择这个解决方案是很好的。

如果一个应用程序组中有不止一个团队,您应该维护一个与软件一致的空间,而不是团队。您可以让每个团队访问该空间,两个团队都可以看到整个系统的部署视图,这样就可以清楚地看到一个应用程序部署是否阻塞了另一个应用程序部署。

应用受众

一种粒度较小的方法是拥有与目标受众相匹配的独立空间,例如内部和面向公众的应用程序。这种方法通过将内部资源转移到不同的空间来清理面向公众的空间。

这种设计可以快速改善面向公众的空间的信息,并可以作为将资源划分到每个应用程序套件的空间的第一步。

公司部门

如果你的公司被组织成开发独立应用的部门,这可能会提供一个自然的空间设计。例如,如果公司有向不同行业提供软件的部门,每个部门可以有一个单独的空间,有一个专门的空间经理。

有了自己的空间管理器,每个部门都可以自给自足地管理自己的空间,而不会让 Octopus 部署到其他部门。

有用的设计指标

理想的场景是,空间中的应用程序是独立的,部署到专门的目标,并有一个自治的团队负责它们。虽然你可能不会发现自己处于这种完美的状态,但它为你决定如何设计你的空间提供了有益的指导。

如果您将多个应用程序部署到相同的部署目标,您应该将部署放在相同的空间中。使用监听触手可以在多个空间中设置同一个部署目标。然而,这使得权限变得复杂,团队成员不会看到所有针对共享基础设施的部署。

如果有很强的理由将部署划分到多个空间,这些理由可能意味着您也应该有不同的部署目标。

要避免什么

您应该避免在每个环境中使用空格,因为您需要在每个空格中重复这个过程。当您进行更改时,很难在每个空间保持过程的一致性,并且您不会从发布快照中受益。发布快照确保在整个环境中使用相同版本的包、变量和流程,这提高了部署的可靠性。

你也应该避免使用更适合使用租户的空间。

当一个应用程序包含几个组件时,最好把它们放在一个空间里;否则,将很难从整体上跟踪应用程序的当前部署状态。

结论

Spaces 是一个有价值的工具,用于组织和保护您使用 Octopus Deploy 管理的与部署相关的资源。它们提供了关于部署和资源的较小视图,但是您应该适当考虑它们的设计。

愉快的部署!

我们 2021 年最受欢迎的网络研讨会- Octopus Deploy

原文:https://octopus.com/blog/best-webinars-of-2021

今年在 Octopus,我们举办了各种主题的网络研讨会。举办网络研讨会是一种有趣的方式,可以为我们的客户和更广泛的 DevOps 社区分享我们的知识并深入探讨主题。

在这篇文章中,我分享了我们最喜欢和最受欢迎的网络研讨会,如果你在假期期间工作,可以陪着你。

您可以在我们的 YouTube 网络研讨会播放列表中找到我们所有的 55 场网络研讨会。

从技术上深入了解配置代码

代码为的配置已经成为章鱼的概念有一段时间了。Config as Code 允许您将脚本存储在源代码控制中,并在您的部署目标或工作器上运行它们。

2021 年,我们推出了 Config as Code 作为早期访问预览版,为您提供 Git 对 Config as Code 的内置支持。在本次网络研讨会中,我们使用 Config 作为代码从头开始了一个项目,并向您介绍了如何使用这一新功能。

我们讨论了以下主题:

  • 什么是配置代码以及如何使用它
  • 如何使用 Config 作为代码在 Octopus 中建立你的第一个项目
  • Config as Code 如何帮助您的团队在最有效的地方工作,无论是在 Octopus UI 中还是在项目的 GitHub repo 中

https://www.youtube.com/embed/oZfxlbpSP14

VIDEO

戴夫·法利的持续交付:使用快速反馈获得高质量的发布

连续交付在现代开发团队中已经很好地建立起来了,然而,没有两个团队是以同样的方式完成的。定期发布软件的一个主要好处是反馈循环,这将导致高质量的发布。大多数团队在某种程度上都是这样做的,但是你如何最大化这一点呢?

在本次网络研讨会中,Bob Walker 和《持续交付》的顾问和合著者 Dave Farley 讨论了如何充分利用 CI/CD 渠道,以及如何优化快速反馈,从而实现高质量的发布。

我们讨论了以下主题:

  • 持续交付的基础
  • 可能影响反馈周期的常见问题
  • 优化 CI/CD 渠道以获得快速反馈的技巧
  • 公司积极应用这一点的具体例子

https://www.youtube.com/embed/IhUyUIiMrNs

VIDEO

数据库开发应用入门

很少有人喜欢执行手动部署,尤其是数据库部署。然而,许多人仍然手动进行数据库部署。大多数人都认为这是一个问题,但是我们如何解决它呢?DevOps 是答案吗?

在本次网络研讨会中,我们展示了如何开始使用数据库开发运维来节省时间和减少麻烦。

我们讨论了以下主题:

  • 什么是 DevOps,如何将其应用于数据库
  • 如何开始使用数据库开发运维
  • 常见的障碍以及如何克服它们

https://www.youtube.com/embed/Ho3p7gP4zW4

VIDEO

通过 Octopus 和 TeamCity 实现云中的 CI/CD

我们在 2018 年推出了章鱼云,过去三年一直在迭代。我们最喜欢的构建服务器 TeamCity (我们在 Octopus 内部使用的)现在有了云产品,消除了对本地虚拟机的需求。现在,您可以在完全托管的云产品上同时使用 Octopus 和 TeamCity。

我们讨论了以下主题:

  • 如何使用团队城市云
  • 如何使用章鱼云
  • 如何在云中集成这两者以实现完全托管的 CI/CD 管道

https://www.youtube.com/embed/5o3uBES2-i8

VIDEO

使用 Octopus API 通过自动化重复任务来节省时间

Octopus Deploy 被设计为一个 API 优先的应用程序,这意味着您可以在 Octopus Web 门户中做的任何事情,都可以用 REST API 来完成。

在本次网络研讨会中,我们展示了 Octopus REST API 如何通过自动化重复任务来节省您的时间。

我们讨论了以下主题:

  • Octopus API 的概述以及它为什么有用
  • 如何使用 API 密钥管理访问
  • 进行 API 调用的不同方法(Octopus。客户端、CLI、您选择的语言的自定义脚本)
  • 如何通过自动化定制或重复任务来节省时间

https://www.youtube.com/embed/ACb2sHWoZto

VIDEO

摘要

我们在 2021 年举办了一些世界级的网络研讨会,对此我们深感自豪。我对我们将在 2022 年做的事情感到兴奋,团队中有很棒的新人,新功能,以及我们打算在社区中做的有趣的事情。

我们在 2021 年第一季度的渠道已经满了,但是如果您有关于网络研讨会的想法,或者我们可以通过网络研讨会做出的改进,请发送电子邮件至 webinars@octopus.com。

愉快的部署!

通过 Octopus Deploy 实现更好的多租户- Octopus Deploy

原文:https://octopus.com/blog/better-multi-tenancy-with-octopus

大多数使用 Octopus 的人会将项目部署到一个或多个环境中。对于提供软件即服务(SaaS)应用程序的客户,他们通常需要为每个客户部署应用程序的多个实例。

幸运的是,自从 Octopus 3.4 以来就有一个专门为这些类型的部署设计的功能,即多租户

在本文中,我将介绍两种在没有租户的情况下部署应用程序的方法,并讨论使用多租户特性的好处。

介绍

这篇文章假设读者了解一些关键的 Octopus 概念,包括:

  • 项目
  • 环境
  • 变量
  • 生活过程

如果你是章鱼的新手,我推荐你阅读我们的章鱼指南

为了演示如何使用 Octopus 对一个应用程序的多个实例的部署进行建模,我使用了一个名为 Vet Clinic 的虚构公司,部署 Java 应用程序 Pet Clinic

无租户部署

在为每个客户部署同一应用程序的多个实例时,我们看到了两种主要的实现方式:

  1. 使用多个项目
  2. 使用多种环境

虽然易于设置,但它们不能很好地扩展,并且可能导致重复。

使用多个项目

在这个场景中,您用多个项目配置 Octopus,每个项目代表您的一个客户。

Multi-tenancy using multiple projects

新客户入职通常需要在 Octopus 中创建为客户成功部署所需的所有资源,包括:

此外,应用程序部署过程中的任何公共步骤都需要在新项目中重复。这些通常是手动干预和通知步骤。

多个项目优势

那么为什么要选择多个 Octopus 项目来为每个客户部署一个应用程序的实例呢?

  1. 清除客户发布仪表板概述

    这种方法允许您在仪表板概览上看到每个客户的哪个版本部署到了哪个环境。

  2. 变量和部署过程隔离

    多个项目允许对客户的变量和部署过程进行完全隔离。例如,对一个项目的过程进行变更只会影响到那个客户。您还可以根据客户注册的功能为他们定制部署流程。

    在以下示例中,只有首都动物医院有一个应用定制品牌的步骤:

    【T2 Multi-tenancy multiple projects customised deployment process

  3. 更简单的环境和变量范围

    不需要为每个客户复制环境,从而简化了生命周期配置。变量也可以被限定在每个环境中,而不会有选择错误的“客户”范围的风险。

    Multi-tenancy multiple projects variable scoping

多个项目缺点

虽然可以使用多个项目来分别部署客户实例,但是这种方法存在许多问题。

  1. 重复的项目配置

    对于每个客户项目,您最终会复制项目配置。这些包括变量、部署流程步骤、操作手册、渠道和生命周期。随着复制而来的是不一致性和管理这种不一致性的开销。例如,如果您想要修改所有客户的部署过程,您需要更改多个项目。

  2. 每个客户不同的部署目标角色

    如果您的客户有独立的基础设施,您需要一种独特的方式让 Octopus 知道哪些部署目标属于您要部署到的客户。这导致每个客户的部署目标都需要包含每个客户的独特优势的目标角色。这通常是客户名称、代码或 ID。

    Multi-tenancy multiple projects customer target roles

  3. 每个客户多个项目无法扩展

    当您有很多客户时,基于项目的方法就不能很好地扩展。如果您为每个客户部署更多的应用程序,这可能会有问题。每个应用程序都需要建模n times,其中n是您拥有的客户数量。

  4. 没有护栏确保提供变量

    使用多个项目时,没有护栏来确保所有项目配置设置正确。例如,如果没有添加变量(或者变量的值不正确),您可能直到部署客户的实例时才发现问题。

使用多种环境

没有租户的部署的一种替代方法是对每个应用程序使用一个 Octopus 项目,并用一组客户部署到的环境对每个客户进行建模。

Multi-tenancy using multiple environments

新客户入职通常包括:

  • 创建一组新的环境,以客户命名。
  • 创建一组新的部署目标,或者重用现有的部署目标,并用相关的客户环境标记它们。
  • 添加新的环境范围变量
  • 更新项目生命周期以包括新的客户环境。

多种环境优势

那么为什么要在 Octopus 中选择一个或多个环境来代表你的客户呢?

  1. 单组项目资源管理

    与多个项目相比,这种方法只需要管理一个项目、一个部署过程、一组变量和一个生命周期。当需要更改时,可以一次完成,而不是按项目完成。

    例如,如果您想要添加一个所有客户都需要的步骤,比如在部署到生产环境之前添加一个手动干预步骤,那么它可以被快速而轻松地添加。

  2. 明确模拟客户环境

    使用这种方法,必须明确地对客户环境建模。当添加新客户时,必须创建他们部署到的环境,以允许为该客户进行部署。还可以在控制面板概览的一行中看到客户可以部署到哪个环境。

多重环境冲突

尽管您可以使用多种客户环境,但这种方法通常会带来许多问题:

  1. 为每个客户创建多个环境

    对于每个客户,您需要为每个客户环境创建一个新的环境记录,这是不可伸缩的。例如,如果您有 10 个客户和 4 个环境(开发、测试、试运行和生产),您需要创建 40 个客户环境。

  2. 复杂的变量范围

    因为每个应用程序只有一个项目,所以需要使用不同的环境范围来处理每个客户的多个变量值。这可能会很快变得势不可挡。添加新值或编辑现有值必须小心谨慎,以确保对每个值应用正确的范围。在这种模式下,跨租户通信的风险很高。

    【T2 Multi-tenancy multiple environments variable scoping

  3. 刚性部署流程

    在使用多个客户的项目中定制部署过程需要您为需要为特定客户运行的每个步骤添加环境运行条件。这不灵活,也不可扩展,因为当您添加新客户或更改环境时,您需要每一步都修改这些条件。

    Multi-tenancy multiple environments run conditions

  4. 不明确的客户发布仪表板概述

    与每个项目的客户模型相比,如果没有无休止的滚动,很难在仪表板和项目概览屏幕上一眼看出哪个版本已经部署给了哪个客户。

  5. 复杂、笨拙的生命周期

    随着新客户的加入,您通常会将新的客户环境添加到项目的生命周期中。然后,您必须定义笨拙的生命周期阶段,以适应每个客户(现有的和新的)所需的环境。通常,这是通过一个阶段来处理的,该阶段允许在进入下一个阶段之前部署任何一个客户环境。随着客户数量的增长,生命周期及其阶段的复杂性也在增加。

    Multi-tenancy multiple environments lifecycles

与租户一起部署

在 Octopus 中使用租户允许您轻松地创建客户特定的部署管道,而无需复制项目配置。您可以在单个 Octopus 项目中管理多个环境中应用程序的单独实例。

使用我们的兽医诊所公司,以下是使用租户为每个客户建模的仪表板概览:

Tenanted dashboard overview

这给了我们一个简明的概述,显示了哪个版本在哪个环境中。不再是每个客户项目有多行,取而代之的是已经部署到每个环境的租户的离散计数。

如果我们导航到该项目,我们会看到一个更细粒度的概述,这次显示了每个环境中哪个租户拥有哪个版本:

Tenanted project overview

样本 Octopus 项目
您可以在我们的样本实例中看到兽医诊所租赁项目的示例。

有许多多租户功能协同工作来实现这一点:

房客

Octopus 中的租户是多租户功能的支柱。他们通常代表您的应用程序的客户,尤其是当涉及到 SaaS 产品时。

Tenants screen

虽然我们在这篇文章中讨论了使用租户来模拟客户,但是我们将租户设计为通用的,以便它们可以满足多种用例。租户还可以代表:

  • 地理区域或数据中心
  • 开发人员、测试人员或团队
  • 特征分支

租户概览提供了一个中心位置来管理哪些项目连接到租户,以及哪些环境。

Tenant overview

请注意,每个项目都可以控制它与租户的交互。默认情况下,多租户部署功能处于禁用状态。您可以允许有/没有租户的部署,这是一种混合模式,在您过渡到完全多租户项目时非常有用。

还有一种模式,您可以要求所有部署都有租户,这将禁用该项目的未租户部署。

Tenant project settings

在 Octopus 中,让新客户成为租户非常简单,只需创建您的租户,将您的项目连接到每个适用的环境,输入您的变量值,然后进行部署。

在 Octopus 中,租户标签帮助您使用自定义标签对租户进行分类,并为您的项目和环境定制租赁部署。

租户标签还使租户作为一个组而不是个人工作变得更加容易。由于租户标签是完全可定制的,因此您可以将有意义的元数据应用于租户。这允许您使用自己的术语描述它们,并根据它们的需求定制部署过程。

在下面的租户概述中,首都动物医院包含了Branding标签:

Tenant tag for branding

这表明他们已经决定加入他们的兽医诊所应用程序实例的定制品牌。

当您构建部署流程时,您可以包含一个租户标记作为运行条件,以便为您的客户定制流程。通过将标记应用于步骤,您能够指定只应为匹配所选租户标记的客户运行的步骤。

Tenant tag applied to step

您可以将多个租户与同一个标记相关联。这将自动将这些租户分组在一起,并使任何带有Branding标记的租户都能够将定制品牌步骤作为该租户的任何部署的一部分。

租户标记还可以用于将多个租户与部署目标和通道相关联,甚至可以选择部署到哪些租户。它们是帮助您简化和扩展部署的强大方法。

租户变量

您通常希望为每个客户定义不同的变量值。例如:

  • 数据库服务器名称或连接字符串
  • 特定于租户的 URL
  • 租户的联系方式

使用未租用的项目,您可以在项目本身中定义这些值。对于租赁的项目,您可以直接在租户上为任何连接的项目设置这些值。

对于租户,您可以指定两种类型的变量:

  • 项目变量模板
  • 常见变量

这两者都使用了变量模板功能。

项目变量模板

项目变量允许您指定租户可以更改的变量。一个完美的例子是连接字符串或数据库服务器。对于项目变量,您可以使用项目模板在项目级别定义它们。

您可以为项目模板指定变量类型,就像常规变量一样。您还可以提供租户可以覆盖的默认值。

Tenant project variable edit

然后,在租户变量屏幕上,您可以设置这些变量。

Tenant variables

公共变量

公共变量类似于项目变量。主要的区别是公共变量可以跨多个项目使用,并且它们不限于环境。使用库变量集模板定义公共变量

例如,要定义租户在部署或运行手册中使用的缩写,您可以为库集配置一个变量模板。

Common variable template

要包含租户的公共变量,必须在租户连接项目中添加库变量集。

就像项目变量一样,公共变量值是在租户级别提供的。

Common variable tenant value

可变快照

当您创建一个发布版本时,Octopus 会对部署过程和项目变量的当前状态进行快照。

然而,租户变量不包含在任何快照中。这很有帮助,因为您可以随时添加新的租户并部署到它们,而无需创建新的版本。

这也意味着您对租户变量所做的任何更改都将立即生效。

缺少变量

租户变量的一大优点是它们为您的部署设置了防护栏。定义没有默认值的项目模板或公共变量意味着任何租户都必须为该变量提供值。Octopus 不允许部署没有一个:

Missing tenant variable

但是这些护栏并不只是在部署时才开始。Octopus 还会警告您租户变量概览中的任何缺失值:

Warning of missing tenant variable

这种安全级别降低了由于缺少变量值或变量值不正确而导致租户部署失败的可能性。

租赁部署目标

您为同一项目的多个实例托管基础设施的方式通常因您的应用程序和客户而异。我们看到的两种常见实现是:

  1. 专用托管:您为每个客户都制定了专用的部署目标。
  2. 共享托管:您创建服务器场或服务器池来托管您的所有客户,实现更高的密度。

如果您的客户有独立的基础设施,在一个未租用的配置中,您需要在 Octopus 中定义唯一的目标角色,以确保没有跨客户的通信,即把一个客户的应用程序部署到另一个客户的基础设施中。

使用租户,不需要特定于客户的目标角色。您可以选择目标可以参与的部署:

  • 从租赁部署中排除(默认)-部署目标永远不会包括在租赁部署中。
  • 仅包括在租用的部署中-部署目标将仅包括在关联租户的部署中。它将被排除在未租用的部署之外。
  • 包括在已出租和未出租的部署中-部署目标将包括在未出租的部署和对关联租户的部署中。

Tenant target restrictions

要选择将哪些租户与部署目标相关联,请执行以下操作:

  1. 导航到部署目标的 限制➜关联租户 部分。
  2. 选择允许单独部署到的一个或多个租户,或者从任何已配置的租户标记中进行选择。

Tenant target restrictions

我们建议将租用的和未租用的部署目标分开,尤其是在生产环境中。您可以对其他环境使用相同的部署目标,但是通常最好避免这种情况。

结论

这篇文章介绍了当客户为每个没有租户的客户部署同一个应用程序的多个实例时的常见方法。它还详细介绍了如何使用多租户特性对此进行建模。

我希望您能看到 Octopus 多租户特性如何解决在没有租户的情况下部署时出现的一些问题,以及如何利用它进行可伸缩、可重用、简化的部署。

了解更多信息

观看网络研讨会:使用 Octopus Deploy 实现更好的多租户部署

https://www.youtube.com/embed/dD8psiK1wL4

VIDEO

我们定期举办网络研讨会。请参见网络研讨会第页,了解过去的网络研讨会和即将举办的网络研讨会的详细信息。

愉快的部署!

比特斗管道-Octo.exe 集装箱 Redux -八达通部署

原文:https://octopus.com/blog/bitbucket-pipelines-redux

Bitbucket pipelinse and Octopus Deploy

自从这篇文章首次发表以来,我们已经重新命名了 Octo.exe,现在它是 Octopus CLI,更多信息,请参见这篇文章: Octopus release 2020.1

早在二月份(几年前在互联网领域),我们发表了一篇关于如何通过 Octopus Deploy 将你的 Bitbucket 管道构建过程与部署联系起来的文章。自从我们写这篇文章以来,我们已经开始发布我们的octo.exe命令行工具的最新容器映像,当在 Octopus 门户之外编写脚本时,它将加速您的持续部署过程,特别是对于这些基于容器的构建链。

The Original Pipeline

对于那些错过了前一篇文章的人来说,Bitbucket Pipelines 提供了一种非常简单且低成本的方法,可以从 Bitbucket 代码仓库自动构建。将它与 Octopus Deploy 结合起来意味着您可以正确地管理您的部署,同时仍然减少从代码到应用程序的开销。它使用容器来执行每一步,这是构建工具的一种令人敬畏的新方法。许多供应商已经开始使用这种策略,这意味着每个构建过程都可以精确地适应所讨论的应用程序的需求。多个不同的平台和工具版本可以按需在同一个构建基础设施上并行运行,而不会在构建之间产生任何冲突或污染。

octo.exe 容器现在发布到 DockerHub 上,同时提供了 alpine 和 nanoserver 基础映像。这意味着您可以在类似于 BitBucket 提供的容器化构建链中使用 octo.exe。扩展 Andy 在上一篇文章中概述的思想,我们现在可以避免安装任何压缩工具或执行任何复杂的 curl 请求。

节点根中的bitbucket-pipelines.yml。JS 项目可以简单到

image: node:6.9.4

pipelines:
  default:
    - step:
        name: Build And Test
        caches:
          - node
        script:
          - npm install
          - npm test
          - npm prune --production
        artifacts:
          - "*/**"
    - step:
        name: Deploy to Octopus
        image: octopusdeploy/octo:4.37.0-alpine
        script:
          - export VERSION=1.0.$BITBUCKET_BUILD_NUMBER
          - octo pack --id $BITBUCKET_REPO_SLUG --version $VERSION --outFolder ./out --format zip
          - octo push --package ./out/$BITBUCKET_REPO_SLUG.$VERSION.zip  --server $OCTOPUS_SERVER --apiKey $OCTOPUS_APIKEY 

在我们构建并测试了我们的节点应用程序之后(这一步可以通过提取测试结果等来改进,但是现在让我们忽略它),我们指示管道将工作目录中的所有文件作为工件传递。这些文件将出现在后续步骤的工作目录中。注意,名为Deploy to Octopus的第二步使用了octopusdeploy/octo:4.37.0-alpine容器。这里基本上只需要运行两个命令;octo packocto push。我们可以使用 Bitbucket 提供的环境变量以存储库命名包,并根据构建号生成一个版本,然后使用自定义的安全变量通过项目设置来设置服务器 URL 和 apiKey。

与其重复 Andy 在上一篇文章中关于 Bitbucket 如何工作的内容,我建议您阅读他的文章,了解更多关于建立 Bitbucket 管道和 Octopus 端到端部署 CI 流程的详细信息。

上面的配置变化显示了如何比以往更容易地将 Octopus Deploy 集成到您的构建系统中,并且该方法将在大多数容器化的构建系统中工作,从 Bitbucket 管道到 CircleCI 。立即尝试,简化您的部署,现在容器数量增加了一倍!

了解更多信息

Bitbucket 管道:管道和与 Octopus Deploy 集成- Octopus Deploy

原文:https://octopus.com/blog/bitbucket-pipes-and-octopus-deploy

Bitbucket Pipelines

Atlassian 的 Bitbucket Pipelines 是一个轻量级的云持续集成服务器,它使用预配置的 Docker 容器,允许您将基础架构定义为代码。 Pipes 允许您向管道添加配置,对于第三方工具尤其有用。

在本文中,我为一个 Octopus CLI 命令创建了一个实验管道,在我们的示例 node.js 应用程序 RandomQuotes-Js 的 Bitbucket 管道中使用它,最后,将管道与 Octopus 集成。

在这篇文章中

什么是比特桶管道?

亚特兰蒂斯人:

管道提供了一种配置管道的简单方法。当您想使用第三方工具时,它们尤其强大。只需将管道粘贴到 YAML 文件中,提供一些关键信息,剩下的事情就为您完成了。您可以在步骤中添加任意数量的管道,因此可能性是无穷的!

管道建立在管道、容器的核心概念上。管道使用位于 Docker 容器中的脚本,它通常包含在管道可用之前您在管道 YAML 文件中编写的命令。

管道使用示例

这是 Atlassianbit bucket-upload-file管道在您的管道 YAML 文件中的样子:

- pipe: atlassian/bitbucket-upload-file:0.1.3
  variables:
    BITBUCKET_USERNAME: '<string>'
    BITBUCKET_APP_PASSWORD: '<string>'
    FILENAME: '<string>'
    # ACCOUNT: '<string>' # Optional
    # REPOSITORY: '<string>' # Optional
    # DEBUG: '<boolean>' # Optional 
  • atlassian/bitbucket-upload-file:0.1.3是包含要运行的管道的 Docker 图像的名称。
  • BITBUCKET_USERNAME是一个您需要提供的变量的例子,该变量包含管道在容器内部执行时要使用的值。

参照管线步骤中的管道

有两种方法可以在管线内的步骤中引用管道:

  1. 直接参考 Docker 图像:
pipe: docker://<Docker_Account_Name>/<Image_Name>:<tag> 
  1. 引用托管在 Bitbucket 上的管道存储库:
pipe: <Bitbucket_account>/<Bitbucket_repo>:<tag> 

这个方法从引用的<Bitbucket_account>/<Bitbucket_repo>管道库中的pipe.yml文件中寻找 Docker 图像的位置。

管道为什么有用?

为什么要大费周章写管道呢?

管道都是关于再利用。它们允许您在管道的多个步骤中重复相同的操作。通过将核心操作集中到一个管道中,您最终会得到一个更简单的管道配置。与在管道中直接编写脚本相比,管道的另一个关键特性是能够包含主管道不需要的依赖项。

创建位桶管道

管道由构成 Docker 映像的一组文件组成。我创建的管道的图像基于预先存在的 octopusdeploy/octo 图像。成品管已在 Docker Hub 上发布为 octopipes/pack

起初,创建一个管道可能看起来相当令人生畏,但是 Atlassian 提供了一个有帮助的分步指南。

我在 Ubuntu 机器上使用 Bash 终端创建了这个管道。如果您使用不同的平台,您可能需要调整您使用的命令。

选择烟斗的候选人

我经常听人说,对于软件来说,命名是最困难的事情,选择一个命令来包装在管道中也是如此。然而,在大多数 CI/CD 管道中,在您对代码构建并运行任何测试之后,您可能想要打包您的应用程序。所以选择 Octopus CLI pack 命令来创建管道是很自然的。

额外的好处是pack命令只有几个必需的参数,可选的参数可以用一些管道魔术来处理(稍后的会有更多)。

可以创建两种类型的管道:

我选择了一个完整的管道,这样我就可以发布它并在其他存储库中使用它。

创建管道存储库

接下来,我需要在 Bitbucket 中创建一个新的 octopusdeploy/pack Git 存储库,并在本地克隆它。

有关创建新的 Git 存储库的更多信息,请参见 Atlassian 文档

创建管道骨架

Atlassian 提供了一种使用 Yeoman 生成管道库框架的方法。当您安装了所有的先决条件(nodejsnpm)后,您可以从终端使用yo命令运行生成器:

yo bitbucket-pipe 

这将提示您选择要创建的管道。我选了新高级管(Bash)

Bitbucket pipe generator -welcome

系统会提示您一些问题,帮助您为管道的使用者填写元数据和其他有用的信息。完成后,它将生成您开始工作所需的文件:

Bitbucket pipe generator - complete

至少,您需要编辑以下文件以满足您的管道要求:

提示:检查其他的库,看看他们是如何编写管道的!

每个 Bitbucket 管道的一个优点是代码是公开的,所以你可以浏览它。例如,您可以在位桶上查看bitbucket-upload-file管道的源代码。

这是了解其他作者如何构建管道的一个非常好的方式。

当你创建一个完整的管道时,Atlassian 要求你创建一个pipe.yml文件。本文档包含有关管道的元数据,包括以下内容:

  • 管道的名称。
  • 管道的 Docker 中心图像,格式为:account/repo:tag
  • 可以在其中指定默认值的管道变量列表。

如果您使用管道生成器选择了一个高级管道,将会为您创建一个pipe.yml文件,其中已经添加了所有相关信息。以下是我自动生成的 pipe.yml 文件的内容:

name: Octo Pack
image: octopipes/pack:0.0.0
description: Creates a package (.nupkg or .zip) from files on disk, without needing a .nuspec or .csproj
repository: https://bitbucket.org/octopusdeploy/pack
maintainer: support@octopus.com
tags:
    - octopus
    - package
    - deployment 

创建管道脚本

管道的主要部分是在容器中执行时将运行的脚本或二进制文件。它将包括执行管道任务所需的所有逻辑。你可以选择任何你熟悉的语言。当我早先创建我们管道的骨架时,我使用了 Bash ,并且创建了一个示例pipe/pipe.sh文件供我完成。

TL;博士

如果您想查看完整的管道脚本,直接跳到结尾或查看源代码。如果没有,请继续阅读!

管道脚本文件的一般结构遵循以下约定:

pipe script flow

强制管道变量

pack命令有五个我希望管道处理的参数:

  1. 要创建的包的--id
  2. 包装的--format,如NuPkgZip
  3. 封装的--version(SEM ver)。
  4. --basePath指定包含要打包的文件和文件夹的根文件夹。
  5. --outFolder指定生成的包将被写入的文件夹。

为了支持这些参数,我将每个参数映射到一个位桶管道变量

我还遵循了变量的验证,如 Atlassiandemo-pipe-bash脚本所示:

NAME=${NAME:?'NAME variable missing.'} 

这将检查一个$NAME变量值,并在变量不存在时显示一条错误消息。

对于我创建的五个变量,我的变量验证如下所示:

# mandatory parameters
ID=${ID:?'ID variable missing.'}
FORMAT=${FORMAT:?'FORMAT variable missing.'}
VERSION=${VERSION:?'VERSION variable missing.'}
SOURCE_PATH=${SOURCE_PATH:?'SOURCE_PATH variable missing.'}
OUTPUT_PATH=${OUTPUT_PATH:?'OUTPUT_PATH variable missing.'} 

可选管道变量

接下来是一些可选变量,管道消费者可以根据自己的意愿选择提供。

我包含了一个EXTRA_ARGS数组变量来为pack命令包含多个额外的参数。您可以通过在管道中使用特殊的位存储桶数组类型来指定此变量:

variables:
  EXTRA_ARGS: ['--description', 'text containing spaces', '--verbose'] 

数组类型非常有用,因为它提供了一种向管道提供任何其他参数的简单方法。在我的例子中,这允许pack管道的消费者能够提供我没有显式处理的任何附加参数选项。

高级管道写作技巧:

要了解更多关于数组类型及其值如何传递给 Docker 容器的信息,请参阅 Atlassian 的文档

最后,我包含了一个布尔变量DEBUG来包含额外的调试信息。您在管道中指定它的值,如下所示:

variables:
  DEBUG: 'true' 

下封隔管

管道的唯一目的是打包一组文件,为了实现这个目的,我们引用了我用作管道 Docker 映像基础的octopus deploy/octoDocker 映像。这使我们能够在管道中访问完整的 Octopus CLI。

为了将文件打包到 Bitbucket 管道中,我们的脚本需要运行pack命令并传入我们的变量:

run octo pack --id "$ID" --version "$VERSION" --format "$FORMAT" --basePath "$SOURCE_PATH" --outFolder "$OUTPUT_PATH" "${EXTRA_ARGS[@]}" 

最后,我们检查命令是否成功。如果是,我们将显示一条成功消息,并用所创建的包的文件名设置一个变量。如果没有,我们会显示一条错误消息并暂停执行。

run命令是一个在单独的common.sh文件中指定的辅助函数。看起来是这样的:

run() {
  output_file="/var/tmp/pipe-$(date +%s)-$RANDOM"

  echo "$@"
  set +e
  "$@" | tee "$output_file"
  status=$?
  set -e
} 

该函数包装对所提供命令的调用,在本例中是octo pack,将输出记录到一个临时文件中,并捕获退出状态。

完整管道脚本

这就是我们剧本的全部内容。下面是完成的pipe.sh文件:

#!/usr/bin/env bash

# Creates a package (.nupkg or .zip) from files on disk, without needing a .nuspec or .csproj
#
# Required globals:
#   ID
#   FORMAT
#   VERSION
#   SOURCE_PATH
#   OUTPUT_PATH
#
# Optional globals:
#   EXTRA_ARGS
#   DEBUG

source "$(dirname "$0")/common.sh"

# mandatory parameters
ID=${ID:?'ID variable missing.'}
FORMAT=${FORMAT:?'FORMAT variable missing.'}
VERSION=${VERSION:?'VERSION variable missing.'}
SOURCE_PATH=${SOURCE_PATH:?'SOURCE_PATH variable missing.'}
OUTPUT_PATH=${OUTPUT_PATH:?'OUTPUT_PATH variable missing.'}

FORMAT=$(echo "$FORMAT" | tr '[:upper:]' '[:lower:]')

# Default parameters
EXTRA_ARGS_COUNT=${EXTRA_ARGS_COUNT:="0"}
DEBUG=${DEBUG:="false"}

enable_debug

if [ "${EXTRA_ARGS_COUNT}" -eq 0 ]; then
  # Flatten array of extra args
  debug "Setting EXTRA_ARGS to empty array"
  EXTRA_ARGS=
fi

debug "Flattening EXTRA_ARGS"
init_array_var 'EXTRA_ARGS'

debug ID: "${ID}"
debug FORMAT: "${FORMAT}"
debug VERSION: "${VERSION}"
debug SOURCE_PATH: "${SOURCE_PATH}"
debug OUTPUT_PATH: "${OUTPUT_PATH}"
debug EXTRA_ARGS_COUNT: "${EXTRA_ARGS_COUNT}"
debug EXTRA_ARGS: "${EXTRA_ARGS}"

run octo pack --id "$ID" --version "$VERSION" --format "$FORMAT" --basePath "$SOURCE_PATH" --outFolder "$OUTPUT_PATH" "${EXTRA_ARGS[@]}"

if [ "${status}" -eq 0 ]; then
  OCTO_PACK_FILENAME="$ID.$VERSION.$FORMAT"
  success "Packaging successful. Created package $OUTPUT_PATH/$OCTO_PACK_FILENAME."

else
  fail "Packaging failed."
fi 

创建管道 Dockerfile 文件

现在我们有了要运行的主脚本,我们需要使用 docker 文件创建我们的图像。如果您运行了管道生成器,那么您已经准备好了 docker 文件来进行编辑以适合您的管道。

对于pack管道,docker 文件如下所示:

FROM octopusdeploy/octo:7.3.2

RUN apk add --update --no-cache bash

COPY pipe /
RUN chmod a+x /*.sh

ENTRYPOINT ["/pipe.sh"] 

Dockerfile 以octopusdeploy/octo为基础,然后将bash添加到图像中。然后,它复制pipe文件夹的内容,并授予所有用户执行当前的.sh文件的权限。最后,它将容器的ENTRYPOINT设置为我们之前创建的 pipe.sh 文件。

创建管道自己的管线

完成管道后,您可以将 Docker 映像手动部署到 Docker Hub。然而,当您使用自己的bitbucket-pipelines.yml文件将更改推送到 Bitbucket 存储库时,也可以让 Bitbucket 管道自动为您完成繁重的工作。

对于pack管道,在自动生成的文件中,我只修改了推送步骤:

 push: &push
  step:
    name: Push and Tag
    image: python:3.7
    script:
    - pip install semversioner==0.7.0
    - chmod a+x ./ci-scripts/*.sh
    - ./ci-scripts/bump-version.sh
    - ./ci-scripts/docker-release.sh octopipes/$BITBUCKET_REPO_SLUG
    - ./ci-scripts/git-push.sh
    services:
    - docker 

该步骤安装seversioner,这是一个 python 工具,帮助自动生成发行说明,并根据 SemVer 为您的管道版本化。之后,它增加管道的版本,创建一个新的 Docker 映像,并将其推送到 Docker Hub。最后,它标记新版本并将其推回 Bitbucket 存储库。

你可以在位桶上查看pack钻杆的完整bitbucket-pipelines.yml文件。

无双 Docker 推送:
push步骤在自己提交并推回到位存储库时,不会触发将两个 Docker 映像推送到 Docker Hub。这样做的原因是,如果提交消息中的任何地方包含了[skip ci][ci skip],位桶管道支持跳过管道运行的选项。

创建管道自述文件

您可能会想,“为什么要花时间创建自述文件呢?”嗯,亚特兰蒂斯人自己推荐它:

你的自述是你的用户如何知道如何使用你的管道。我们可以在 Bitbucket 中显示它,所以它需要用 markdown 以特定的格式编写。

包含一个信息丰富的README会增加你的用户成功使用你的管道的机会。

README更重要的部分之一是 YAML 定义。这告诉用户向他们的bitbucket-pipeline.yml文件添加什么。

下面是pack的样子:

script:
  - pipe: octopipes/pack:0.6.0
    variables:
      ID: "<string>"
      FORMAT: "<string>"
      VERSION: "<string>"
      SOURCE_PATH: "<string>"
      OUTPUT_PATH: "<string>"
      # EXTRA_ARGS: "['<string>','<string>' ..]" # Optional
      # DEBUG: "<boolean>" # Optional 

点击可以查看octopipes/packREADME全文

运行管道

因为管道只是一个 Docker 映像,在您构建了映像之后,您可以使用docker run执行管道,将任何需要的参数作为环境变量传入。

下面是运行pack管道从我们的RandomQuotes-JS应用程序打包根目录的命令:

sudo docker run \
   -e ID="randomquotes-js" \
   -e FORMAT="Zip" \
   -e VERSION="1.0.0.0" \
   -e SOURCE_PATH="." \
   -e OUTPUT_PATH="./out" \
   -e DEBUG="false" \
   -v $(pwd):$(pwd) \
   -w $(pwd) \
 octopipes/pack:0.6.0 

输出显示成功打包了randomquotes-js.1.0.0.0.zip文件:

docker run octopipes pack

测试管道

为了确保您的管道如您所愿,为它编写测试是一个好主意。在 Atlassian 的带领下,我选择使用 BATS (Bash 自动化测试系统)。

就像许多测试框架一样,一个测试文件(通常以.bats结尾)包含以下结构:

  • 一个setup方法来为你的测试设置任何需要的变量或者共享资源。
  • 多个单独的@test声明;这些是你的测试案例。
  • 一个teardown方法来删除你使用过的任何资源。

下面是我的 test.bats 文件:

#!/usr/bin/env bats

setup() {
  DOCKER_IMAGE=${DOCKER_IMAGE:="test/pack"}

  echo "Building image..."
  run docker build -t ${DOCKER_IMAGE}:test .

  # generated
  RANDOM_NUMBER=$RANDOM

  # locals
  ID="MyCompany.MyApp"
  FORMAT="zip"
  VERSION="1.0.0.0"
  SOURCE_PATH="test/code"
  OUTPUT_PATH="test/out"

  echo "$FORMAT"

  EXPECTED_FILE="$ID.$VERSION.$FORMAT"

  # Create test output dir
  rm -rf test/out
  mkdir test/out/extract -p

  echo "Create file with random content"
  echo $RANDOM_NUMBER > test/code/test-content.txt
}

teardown() {
  echo "Cleaning up files"
  chmod -R a+rwx test/out/
  rm -rf test/out
}

@test "Create Zip package using Octo pack command" {

    echo "Run test"
    run docker run \
        -e ID="${ID}" \
        -e FORMAT="${FORMAT}" \
        -e VERSION="${VERSION}" \
        -e SOURCE_PATH="${SOURCE_PATH}" \
        -e OUTPUT_PATH="${OUTPUT_PATH}" \
        -e DEBUG="false" \
        -v $(pwd):$(pwd) \
        -w $(pwd) \
        ${DOCKER_IMAGE}:test

    echo "Status: $status"
    echo "Output: $output"
    [ "$status" -eq 0 ]

    # Verify
    unzip "test/out/$EXPECTED_FILE" -d test/out/extract
    run cat "test/out/extract/test-content.txt"
    echo "Output: $output"
    [[ "${status}" -eq 0 ]]
    [[ "${output}" == *"$RANDOM_NUMBER"* ]]

} 

在我的test.bats文件中,setup构建了一个名为test/pack的管道的本地 Docker 映像。接下来,它用随机数创建一个文件。

然后我的test执行docker run(正如我在本地测试中所做的那样)并验证容器运行完成,最后从包中提取文件并检查内容是否与我在setup中生成的随机数匹配。

测试作为在我的bitbucket-pipeline.yml中配置的自动化 CI/CD 管道的一部分运行。如果你安装了bats,你也可以在本地运行测试。

下面是我的test.bats文件测试运行的输出:

bats test run output

这就是测试你的烟斗的全部内容!

发布管道

当您对 Pipe 的工作感到满意时,您可以通过运行docker push直接发布 Docker 图像。

如果您已经像我们之前所做的那样建立了一个自动化的管道,那么您可以利用semversioner来创建一个变更集:

semversioner add-change --type patch --description "Initial Docker release" 

提交变更集后,将它们推送到 Bitbucket,剩下的工作应该由管道来处理。它还会自动更新以下位置的版本号:

  • CHANGELOG.md文件。
  • README.md文件。
  • 元数据pipe.yml

你可以在这里看到一个pack位桶管道创建0.5.1版本的例子:

Bitbucket pipe pipeline result

将管道集成到管道中

如果您已经做到这一步,那么您已经将管道发布到 Docker Hub,但是如何将该管道集成到另一个存储库的管道中呢?

为了找到答案,我将我们现有的 node.js 示例之一RandomQuotes-JS导入到一个新的 Bitbucket 存储库中,该示例驻留在 GitHub 上,并使用相同的名称

用管子

接下来,我创建了一个bitbucket-pipelines.yml文件并设置了我的管道。在构建和测试步骤之后,我插入了一个包步骤,如下所示:

- step:
    name: Pack for Octopus
    script:
      - export VERSION=1.0.0.$BITBUCKET_BUILD_NUMBER
      - pipe: octopusdeploy/pack:0.6.0
        variables:
          ID: ${BITBUCKET_REPO_SLUG}
          FORMAT: 'Zip'
          VERSION: ${VERSION}
          SOURCE_PATH: '.'
          OUTPUT_PATH: './out'
          DEBUG: 'false'
    artifacts:
      - out/*.zip 

该步骤看起来类似于管道的README文件中的示例。我在pipe指令前添加了一行来设置变量:

- export VERSION=1.0.0.$BITBUCKET_BUILD_NUMBER 

export命令告诉 Bitbucket 创建VERSION变量并在管道中使用它。

我还使用了 Bitbucket 工件,在这里我指定了一个 glob 模式来选择存在于out文件夹中的任何 zip 文件:

artifacts:
    - out/*.zip 

这允许管道创建的包被管道中的后续步骤使用。

用 Octopus 整合管道

在我创建了一个包之后,我想通过集成 Bitbucket 和 Octopus 来完成 Bitbucket 管道;具体来说,我想将我创建的包和相关的提交信息推送给 Octopus。

把包推给八达通

在我之前创建的打包步骤之后,我添加了另一个步骤,将包推送到 Octopus 内置的存储库

这个步骤利用了 Bitbucket 中的一个特性,该特性允许您指定一个容器映像,该映像可以不同于管道中其他地方使用的默认映像。在这种情况下,我选择了octopusdeploy/octo:7.3.2 Docker 图像。

这意味着我可以运行octo push命令并指定我在前面的Pack for Octopus步骤中创建的包,如下所示:

octo push --package ./out/$BITBUCKET_REPO_SLUG.$VERSION.zip  --server $OCTOPUS_SERVER --space $OCTOPUS_SPACE --apiKey $OCTOPUS_APIKEY 

您可以在下面看到实现推送至 Octopus 所需的最小 YAML:

- step:
    name: Push to Octopus
    image: octopusdeploy/octo:7.3.2
    script:
      - export VERSION=1.0.0.$BITBUCKET_BUILD_NUMBER
      - octo push --package ./out/$BITBUCKET_REPO_SLUG.$VERSION.zip  --server $OCTOPUS_SERVER --space $OCTOPUS_SPACE --apiKey $OCTOPUS_APIKEY 

将构建信息推送到 Octopus

为了完成集成,我想在 Octopus 中提供构建信息

对我来说,Octopus 最棒的一点是它是 API 优先的。这允许我们在它的基础上构建一流的 CLI。推送构建信息被证明是非常容易的。一旦我知道了 JSON 有效载荷的格式,我就使用 build-information 命令创建了一个 Bash 脚本来完成这个任务。

提示:构建信息有效负载
为了帮助揭开构建信息有效负载的一些复杂性,我按照我的同事 Shawn 的优秀片段对其结构进行了描述。

为了添加到我们前面的Push to Octopus步骤中,我们也可以包含这个脚本来推送构建信息。完整的步骤如下所示:

- step:
    name: Push to Octopus
    image: octopusdeploy/octo:7.3.2
    script:
      - apk update && apk upgrade && apk add --no-cache git curl jq
      - export VERSION=1.0.0.$BITBUCKET_BUILD_NUMBER
      - octo push --package ./out/$BITBUCKET_REPO_SLUG.$VERSION.zip  --server $OCTOPUS_SERVER --space $OCTOPUS_SPACE --apiKey $OCTOPUS_APIKEY
      - /bin/sh create-build-info.sh $BITBUCKET_REPO_OWNER $BITBUCKET_REPO_SLUG $BITBUCKET_BUILD_NUMBER $BITBUCKET_COMMIT $BITBUCKET_BRANCH $BITBUCKET_GIT_HTTP_ORIGIN
      - octo build-information --package-id $BITBUCKET_REPO_SLUG --version $VERSION --file=octopus.buildinfo --server $OCTOPUS_SERVER --space $OCTOPUS_SPACE --apiKey $OCTOPUS_APIKEY 

create-build-info.sh脚本创建一个名为octopus.buildinfo的 JSON 有效负载输出文件,然后我们在build-information命令中使用它来推送提交信息。

在提交被推送到 Octopus 之后,您可以在库的 Packages 部分看到它们:

randomquotes-js buildinfor

就是这样!

您可以在位桶上查看完整的bitbucket-pipelines.yml文件。

样本 Octopus 项目
您可以在我们的样本实例中查看 RandomQuotes-JS Octopus 项目设置。

结论

创建我的第一个 Bitbucket 管道非常简单。我肯定能看到在 Bitbucket 中创建管道的优势。也就是说,我发现当对管道进行更改时,反馈周期需要一段时间才能恢复正常。我的建议是只写你需要的最少的功能,然后随着时间的推移在此基础上构建。使用 Octopus CLI 将 Bitbucket 管道与 Octopus 集成是轻而易举的事情,对于任何更复杂的事情,您总是可以随意使用 API。

了解更多信息

观看我们最近的网络研讨会,了解如何将 Atlassian 云管道与 Octopus Deploy 集成。我们涵盖了这篇文章中的许多概念,所以请查看:

https://www.youtube.com/embed/yPjooXDJUA0

VIDEO

欢迎发表评论,让我们知道您对 Bitbucket 管道、管道或基于容器的构建链的看法!

蓝/绿和红/黑部署有什么区别?-章鱼部署

原文:https://octopus.com/blog/blue-green-red-black

当部署新版本的集中式应用程序(如 web 服务)时,有一种策略可以用来在成功部署和测试新版本后将生产流量定向到新版本。这种策略被称为蓝/绿或红/黑,每种颜色代表目标环境的一个副本。流量被路由到一种颜色或另一种颜色(或者在金丝雀部署中或在 A/B 测试期间,但这是另一个故事)。让两个环境并行运行,托管不同版本的应用程序,意味着如果发现问题,流量可以切换,然后再切换回来,几乎不停机。

那么为什么这个策略同时被称为绿/蓝和红/黑呢?这些颜色是否暗示了技术差异?

StackOverflow 说...

我们的第一站是 StackOverflow,在那里我们发现了一个问题红/黑部署和蓝/绿部署有什么区别?

投票率最高的答案表明这两个术语之间确实存在差异:

在蓝绿色部署中,两个版本可能会暂时同时收到请求,而在红黑色部署中,在任一时间点只有一个版本收到流量

答案接着说:

但是红黑部署是一个较新的术语,由网飞、Istio 和其他支持容器编排的框架/平台使用

我经常看到红/黑这个术语被归因于网飞和容器平台创建的工具,所以让我们去看看他们的文档,看看他们是如何定义这些策略的。

网飞,库伯内特斯和伊斯迪奥说...

Spinnaker 是由网飞编写的部署工具,它是开源的,任何人都可以使用。该工具包括一些关于其实现的概念的文档,包括关于部署策略的部分:

Spinnaker 支持红/黑(又名蓝/绿)策略,滚动红/黑和金丝雀策略正在积极开发中。

因此,与 StackOverflow 上的答案相反,网飞把蓝/绿和红/黑视为同一事物。文档甚至包括一个漂亮的蓝色和绿色的图表来说明这一点。

Deployment Strategies

由云计算原生计算基金会制作的名为在 Kubernetes 上的部署策略的 Kubernetes 演示文稿中的幻灯片也记录了蓝色/绿色和红色/黑色的同义词。

Deployment Strategies on Kubernetes

同样地, Istio 博客将蓝色/绿色和红色/黑色视为同一事物:

这种方法只有在我们拥有想要部署的经过适当测试的版本时才有用,也就是说,更多的是蓝色/绿色,也就是红色/黑色,而不是“把脚伸进水里”的金丝雀部署。

结论

这些年来,我听到了很多关于蓝/绿和红/黑之间区别的不同描述。有些涉及如何定向流量(DNS 与负载平衡器),有些参考了针对物理服务器与容器化应用程序的解决方案,有些区分了所有流量的硬切换与会话流失。然而,我从未见过这些区别被一致地使用,事实上,当你深入研究实现它们的工具是如何使用蓝/绿和红/黑这样的术语时,它们经常被互换使用,或者被明确地称为可互换的。

可以肯定地说,您不能可靠地确定部署策略的特定技术方面,仅仅因为它被称为实现蓝/绿或红/黑策略。在高层次上(在 Spinnaker、Kubernetes 和 Istio 等工具的产品文档中明确指出),这两个术语指的是同一个东西,两者之间的任何技术差异可能只在特定的团队或公司中有意义。

Bootstrap 一个用于 Linux - Octopus 部署的. NET 核心开发环境

原文:https://octopus.com/blog/bootstrap-dotnet-core-dev-on-linux

Bootstrap a .NET Core development environment for Linux

微软。NET Core 有很大的跨平台支持,给。NET 开发人员能够在 Linux 发行版或 macOS 上进行大部分开发。在这篇文章中,我将介绍如何开始以及有哪些工具可以支持开发人员。

在这篇文章中

在 Octopus Deploy 工作,我真正欣赏的一件事是,我被鼓励以我选择的方式工作,最大限度地提高我的快乐和效率。

今年早些时候,我决定将我的日常工作环境切换到 Linux,我并不后悔。类似 Unix 的操作系统一直是我喜欢的东西,但是作为一名. NET 开发人员,我从来没有能够在开发工作中选择它。

我将讨论一些选择。NET 开发人员,并向您展示我是如何将我的堆栈和一些我经常使用的脚本组合在一起的。

使用容器管理开发时数据库和日志服务器

很像我的同事鲍勃·沃克,我个人喜欢使用docker-compose来加速处理依赖关系的过程,比如我的 SQL &日志服务器。我最喜欢它的什么?数据库和日志服务器的设置不到 20 行 YAML!

例如,这让我可以快速启动一个 SQL serverSeq ,这是我们在开发、测试和生产中使用的首选日志记录工具:

---
version: "3"
services:
  # this is my database, there are many others like it, but this one is mine
  db:
    image: "mcr.microsoft.com/mssql/server:2017-latest-ubuntu"
    volumes: # Mount volumes like this: host/dir:container/dir
      - /home/user/.devenv/sql:/var/opt/mssql
    ports:   # Expose ports like this: host_port:container_port
      - "1433:1433"
    env_file: .env  # more on this later ;)

  # seq is an easy to use log server - how simple is this?
  seq:
    image: datalust/seq:latest
    environment: # declare environment variabels inline
      - "ACCEPT_EULA=Y"
    volumes:
      - /home/user/.devenv/seq:/data
    ports:
      - "5341:80" 

您可能还会注意到,这个文件中没有数据库密码或 API 键。那是因为我可以用一个.env文件把那些敏感的东西藏起来。环境文件是docker-compose支持的,所以你可以在一个地方声明你的环境变量。

.env文件是由换行符分隔的键值对:

SA_PASSWORD=ForYourEyesOnly007#
ACCEPT_EULA=true
MSSQL_PID=Developer 

要调出环境,请使用docker-compose up。您可以通过提供-d来保持您的 shell 在以后可用:

docker-compose up -d
docker stats        # show how the containers are operating (CTRL+C to exit)
docker-compose down # stop the stack you've created 

如果你喜欢,微软有一个相当不错的 VS 代码扩展来给你那种右击菜单的感觉!

docker-compose in vs code terminal

基于此,我创建了一个便利脚本的集合来演示一些可能性,并且我录制了一个截屏来展示如何使用它们。

数据库管理

当然,微软 SQL Server 并不是 Linux 上唯一的游戏,远非如此。然而,如果你像我一样,并且你的家乡是 MS SQL,那么你会很高兴知道有一些工业级的选择来使用它。

我使用的是 Datagrip,这是一个非常棒的工具,可以处理各种各样的数据库,并且具有强大的智能感知和重构功能。我最喜欢的特性是能够为每个数据库连接分配一种颜色(例如,测试时为绿色,生产时为红色)。

微软提供的一款免费的 SQL Server 替代产品是 Azure Data Studio 。它为那些不想花钱买更重的工具的人提供了一个美好而简单的体验。

Azure data studio

开发工具有很多选择,但这里是我喜欢使用的。八达通部署的网络工程。

集成开发环境(IDE)

有一些很好的 IDE 选项。NET 开发者现在就上 Linux!

我个人很喜欢 IDE 和数据库工具的Jetbrains 工具箱RiderDatagripWebstorm是我每天的工具。我也在业余时间用Clion来学习铁锈的发展。我发现的一个好处是,每一个都是根据他们所代表的开发风格定制的,同时保持一致的键盘快捷键方案。

如果我确实需要使用 Windows,我也可以把它们安装在那里,而不必重新学习任何击键。Rider 有一个很棒的可视化调试器和许多与 Visual Studio 相当的特性。

rider debugging some tests on Linux

我也喜欢用VS Code来完成不需要调试器的任务,比如解析日志和写博客或文档。

不会吧!Vim 或半身像。

好吧,所以,如果你在这个营地,你可能已经知道你在做什么!也就是说,我可以证实。使用 lofi 工具箱,净生产率是可能的。如果你有兴趣探索这个选项,这里有一些工具可以帮助你开始。

ranger, vim & omnisharp

Git 源代码控制

对于简单的事情,我仍然坚持在命令行使用 Git,zsh提供了一个可爱的插件,几乎涵盖了一切,包括 Git。

对于处理复杂的树,我认为 GitKraken 是一个非常巧妙的选择,它看起来很棒,性能很好,并且与 GitHub 集成。

从命令行安装应用程序

Windows 开发者信誓旦旦地用 boxstarterchocolatey 安装自己喜欢的工具是很常见的。这使得他们可以保存自己喜欢的工具的脚本,并在新机器上运行它们。你可以在 Linux 上做同样的事情。

大多数发行版都有自己的包管理器用于安装工具,每个包都有自己的命令界面。还有 snapd 这是一个交叉分布选项。

在 Ubuntu 和其他支持它的发行版上,你可以使用snapd来安装一些流行的工具。如果你不喜欢他们,他们会干净地卸载:

sudo snap install --classic code
sudo snap install gitkraken

# optional non-free IDE options
sudo snap install --classic rider
sudo snap install --classic datagrip
sudo snap install --classic webstorm 

要移除物品:

sudo snap remove dont_want_this 

开始使用 snapd 很容易,它带有一些有趣的安全特性并且是 Linux 生态系统独有的!

用于隔离环境的流浪箱

容器很棒,但是它们不像虚拟机那样孤立。例如,当我在 triage 期间处理一些不可信的二进制文件时,我将使用一台独立于我的主开发环境的机器。我喜欢使用 HashiCorp 的vagger来管理临时的虚拟机生存期;这几乎和使用 docker 一样简单!

下面是一个轻量级 Arch Linux 环境的例子。它有一些基本的东西,可以随时调整、使用,然后销毁:

  • i3:一个轻量级的平铺窗口管理器
  • sakura:一个轻量级终端仿真器
  • 火狐浏览器
  • 饭桶
  • dotnet sdk
  • 一个文本编辑器(如果你喜欢,用你最喜欢的编辑器替换掉vim)

只需根据自己的喜好调整config.vm.provision中的脚本,然后运行vagrant up来构建它:

Vagrant.configure("2") do |config|
  config.vm.box = "archlinux/archlinux" # from their official repository

  config.vm.provider "virtualbox" do |vb|
    # show console
    vb.gui = true
    # RAM
    vb.memory = 4096
    # CPU
    vb.cpus = 2
  end

  # Hook the provision event and run an inline shell script to install your favorite tools here
  config.vm.provision "shell", inline: <<-SHELL
    echo "installing tools"
      pacman -Sy \
      xorg-server \
      xorg-xinit \
      xorg-apps \
      lxdm \
      i3 \
      dmenu \
      firefox \
      sakura \
      git \
      vim \
      dotnet-sdk \
      --noconfirm
      sed -i 's|# session=/usr/bin/startlxde|session=/usr/bin/i3|g' /etc/lxdm/lxdm.conf

    systemctl enable lxdm
    systemctl start lxdm
  SHELL
end 

vagger CLI 提供了从脚本或命令行快速自动化虚拟机各种操作所需的所有工具:

# change into a directory with a Vagrant file
cd /my/box/
# bring it up
vagrant up
# push a new snapshot onto the stack
vagrant snapshot push
# Tweak a Vagrant file and re-provision while a VM is running
vagrant provision
# easy login via ssh
vagrant ssh
# rollback to the previous snapshot
vagrant snapshot pop
# bring it down
vagrant halt
# save a named snapshot
vagrant snapshot save [vm_name] [snapshot-name]
# rollback to a named snapshot
vagrant snapshot restore [vm_name] [snapshot-name] 

浅谈非官方流浪乞讨人员救助箱的使用

如果可以的话,最好使用官方邮箱。虽然从图书馆使用别人的盒子非常方便,但也有一定的风险。如果他们在上面安装了密码挖掘器呢?

如果你因为这种风险而被困住了,通过一点额外的工作,你可以创作你自己的图片。我强烈推荐看一下 Matt Hodgkins 写的这篇关于使用 Hashicorp packer最佳实践来构建你自己的图像的文章,你可以在《流浪》中使用它。

包扎

感谢阅读。如果你对让 Linux 成为你的家庭操作系统感兴趣,或者对在你喜欢的操作系统中利用dockervagrant感兴趣,我希望这篇文章能在某种程度上帮助你开始。请在评论中告诉我们你最喜欢的工具!

Powershell DSC - Octopus 部署的引导触手安装

原文:https://octopus.com/blog/bootstrap-tentacle-powershell-dsc

Tentacles rising from the ocean to install software on a server

当您有少量机器时,在部署目标上手动安装触角是相当容易的,只需几次点击就可以完成。然而,随着 Octopus Deploy 的采用越来越普遍,尤其是在大型组织中,机器数量呈指数级增长并不罕见。在更高级的实现中,目标有时是动态创建的,手动安装触角是不可行的。这就是基础设施即代码(IaC)派上用场的地方。

作为代码的基础设施是一个可怕而强大的概念。它使您能够以编程方式定义应该如何设置基础设施,从而实现应用程序部署的一致性和可预测性。IaC 的一种方法是 PowerShell 期望状态配置(DSC)。DSC 不仅可以配置机器,它还为我们提供了额外的好处,可以监控机器的漂移,并使用 Octopus 自动将机器返回到所需的状态。在这篇文章中,我将带你使用 OctopusDSC 模块来安装和配置一个触手机器。

安装 NuGet 包提供程序

使用 DSC 有一个缺点,在 DSC 脚本运行之前,您使用的任何外部模块都需要存在于机器上。这意味着您必须将外部模块的安装与 DSC 脚本本身分开。

为了下载外部模块,我们首先需要安装 NuGet 包提供程序。根据您的服务器配置,可能需要包括 TLS 1.2:

# Include use of TLS 1.2
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor [System.Net.SecurityProtocolType]::Tls12

# check to see if Nuget is installed
if($null -eq (Get-PackageProvider | Where-Object {$_.Name -eq "NuGet"}))
{
    # install the nuget package provider
    Install-PackageProvider -Name NuGet -Force
} 

下载 OctopusDSC 模块

现在我们已经安装了 NuGet 包提供程序,我们可以联系 PowerShell gallery 下载并安装我们需要的模块:

if ($null -eq (Get-Package | Where-Object {$_.Name -eq "OctopusDSC"}))
{
    # download specified module
    Install-Module -Name "OctopusDSC" -Force
} 

设置 DSC 资源

既然我们已经处理了先决条件组件,那么是时候设置我们的 OctopusDSC cTentacleAgent 资源了。这里有一堆我们可以传递给这个资源的参数。对于这个例子,我们将配置一个监听触手作为部署目标。如果我们希望它成为一个 worker,我们将清空角色和环境数组,并在 WorkerPools 数组中定义我们想要的条目:

 # configure Ocotpus Deploy
Configuration OctopusSetup
{
    # define parameters
    Param([string]$OctopusAPIKey,
        [string[]]$OctopusRoles,
        [string[]]$OctopusEnvironments,
        [string]$OctopusServerUrl,
        [string]$DefaultApplicationDirectory,
        [string]$TentacleHomeDirectory,
        [string[]]$WorkerPools,
        [PSCredential]$TentacleServiceCredential,
        [string]$TentacleInstanceName = "Default",
        [ValidateSet("Listen", "Poll")]
        [string]$CommunicationMode = "Listen",
        [ValidateSet("PublicIp", "FQDN", "ComputerName", "Custom")]
        [string]$PublicHostNameConfiguration = "ComputerName"
    )

    # import necessary resources
    Import-DscResource -Module OctopusDSC
    Import-DscResource -ModuleName PSDesiredStateConfiguration

    # create localhost configuration node
    Node 'localhost'
    {
        cTentacleAgent OctopusTentacle
        {
            Name = $TentacleInstanceName
            DisplayName = $env:COMPUTERNAME
            Ensure = "Present"
            State = "Started"
            ApiKey = $OctopusAPIKey
            OctopusServerUrl = $OctopusServerUrl
            Environments = $OctopusEnvironments
            Roles = $OctopusRoles
            CommunicationMode = $CommunicationMode
            DefaultApplicationDirectory = $DefaultApplicationDirectory
            TentacleHomeDirectory = $TentacleHomeDirectory
            WorkerPools = $WorkerPools
            PublicHostNameConfiguration = $PublicHostNameConfiguration
            TentacleServiceCredential = $TentacleServiceCredential
        }
    }
}

# Example roles
$OctopusRoles = @("ExampleRole")

# Example Environments
$OctopusEnvironments = @("Development")

# Example worker pools
$WorkerPools = @()

# Set the APIKey
$OctopusAPIKey = "API-XXXXXXXXXXXXXXXXXXXXXX"

# Set the Octopus Server URL
$OctopusServerUrl = "https://<YourOctoupsServer>"

# Set directories
$DefaultApplicationDirectory = "c:\octopus"
$TentacleHomeDirectory = "c:\octopus\tentaclehome"

# run configuration
OctopusSetup -OctopusAPIKey $OctopusAPIKey -OctopusRoles $OctopusRoles -OctopusEnvironments $OctopusEnvironments -OctopusServerUrl $OctopusServerUrl -OutputPath "c:\dsc" -DefaultApplicationDirectory $DefaultApplicationDirectory -TentacleHomeDirectory $TentacleHomeDirectory -WorkerPools $WorkerPools -TentacleServiceCredential $serviceCredential

# start the configuration
Start-DscConfiguration -Path "c:\dsc" -Verbose -Wait 

把所有的放在一起

到目前为止,我们已经设置了单独的组件来演示我们想要实现的目标。现在,让我们将所有这些放入一个脚本中。

尽管我们将在远程计算机上运行脚本块,但是 PowerShell 首先在本地计算机上进行评估。如果您还记得,DSC 资源必须存在才能工作,这意味着我们需要在运行脚本的机器上有 OctopusDSC 资源。我知道这很烦人,但事情就是这样。

# Get credentials that have rights to the server
$elevatedCredentials = Get-Credential

# Create remote session
$remoteSession = New-PSSession -ComputerName <ComputerName> -Credential $elevatedCredentials

# Download and install the Nuget package provider
Invoke-Command -Session $remoteSession -ScriptBlock {
    # Include use of TLS 1.2
    [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor [System.Net.SecurityProtocolType]::Tls12

    # check to see if Nuget is installed
    if((Get-PackageProvider | Where-Object {$_.Name -eq "NuGet"}) -eq $null)
    {
        # install the nuget package provider
        Install-PackageProvider -Name NuGet -Force
    }
}

# Download and install the OctopusDSC module
Invoke-Command -Session $remoteSession -ScriptBlock{
    if ($null -eq (Get-Package | Where-Object {$_.Name -eq "OctopusDSC"}))
    {
        # download specified module
        Install-Module -Name "OctopusDSC" -Force
    }
}

# Run the DSC configuration
Invoke-Command -Session $remoteSession -ScriptBlock{

    # configure Ocotpus Deploy
    Configuration OctopusSetup
    {
        # define parameters
        Param([string]$OctopusAPIKey,
            [string[]]$OctopusRoles,
            [string[]]$OctopusEnvironments,
            [string]$OctopusServerUrl,
            [string]$DefaultApplicationDirectory,
            [string]$TentacleHomeDirectory,
            [string[]]$WorkerPools,
            [PSCredential]$TentacleServiceCredential,
            [string]$TentacleInstanceName = "Default",
            [ValidateSet("Listen", "Poll")]
            [string]$CommunicationMode = "Listen",
            [ValidateSet("PublicIp", "FQDN", "ComputerName", "Custom")]
            [string]$PublicHostNameConfiguration = "ComputerName"
        )

        # import necessary resources
        Import-DscResource -Module OctopusDSC
        Import-DscResource -ModuleName PSDesiredStateConfiguration

        # create localhost configuration node
        Node 'localhost'
        {
            cTentacleAgent OctopusTentacle
            {
                Name = $TentacleInstanceName
                DisplayName = $env:COMPUTERNAME
                Ensure = "Present"
                State = "Started"
                ApiKey = $OctopusAPIKey
                OctopusServerUrl = $OctopusServerUrl
                Environments = $OctopusEnvironments
                Roles = $OctopusRoles
                CommunicationMode = $CommunicationMode
                DefaultApplicationDirectory = $DefaultApplicationDirectory
                TentacleHomeDirectory = $TentacleHomeDirectory
                WorkerPools = $WorkerPools
                PublicHostNameConfiguration = $PublicHostNameConfiguration
                TentacleServiceCredential = $TentacleServiceCredential
            }
        }
    }

    # Example roles
    $OctopusRoles = @("ExampleRole")

    # Example Environments
    $OctopusEnvironments = @("Development")

    # Example worker pools
    $WorkerPools = @()

    # Set the APIKey
    $OctopusAPIKey = "API-XXXXXXXXXXXXXXXXXXXXXX"

    # Set the Octopus Server URL
    $OctopusServerUrl = "https://YourOctopusServer"

    # Set directories
    $DefaultApplicationDirectory = "c:\octopus"
    $TentacleHomeDirectory = "c:\octopus\tentaclehome"

    # run configuration
    OctopusSetup -OctopusAPIKey $OctopusAPIKey -OctopusRoles $OctopusRoles -OctopusEnvironments $OctopusEnvironments -OctopusServerUrl $OctopusServerUrl -OutputPath "c:\dsc" -DefaultApplicationDirectory $DefaultApplicationDirectory -TentacleHomeDirectory $TentacleHomeDirectory -WorkerPools $WorkerPools -TentacleServiceCredential $serviceCredential

    # start the configuration
    Start-DscConfiguration -Path "c:\dsc" -Verbose -Wait
} 

使用这个脚本,我们可以安装和配置一个触手机器,而不必 RDP 到它。这在动态扩展基础设施时尤其有用。

漂移和 Octopus 机器策略测试

顾名思义,DSC 就是我们想要的状态。例如,上面的配置将触手配置为具有 ExampleRole 的角色(并且只有角色)。如果有人给这个触手添加了额外的角色,它将不再处于期望的状态。我们可以通过运行以下命令来确定:

(Test-DscConfiguration -Detailed).ResourcesNotInDesiredState 

使用 Octopus Deploy 的机器策略功能,我们可以创建一个新的策略,如果检测到漂移,它将自动纠正。

为此,导航到基础架构选项卡并单击机器策略。在那里,单击“添加机器策略”按钮:

选择“使用自定义脚本”单选按钮,然后粘贴以下内容:

$tentacleConfiguration = (Test-Configuration -Detailed)

if ($null -ne $tentacleConfiguration.ResourcesNotInDesiredState)
{
    # Display what resources are not in desired state
    foreach ($resource in $tentacleConfiguration.ResourcesNotInDesiredState)
    {
        Write-Warning "Resource $($resource.ResourceId) is not in desired state!"
    }

    Write-Output "Running last DSC configuration to correct drift..."
    Start-DscConfiguration -Path "c:\dsc"
} 

现在,当检测到漂移时,它将自动运行最后一次 DSC 配置,并将机器恢复到所需状态。在上面的例子中,如果有人向一个触手添加了一个额外的角色,这个策略将检测到这个变化并删除这个添加的角色。如果您需要这个角色,您可以将它添加到我们原始脚本中的$OctopusRoles变量中,然后运行新的配置来设置新的期望状态。

结论

在这篇文章中,我用一个简单的脚本演示了如何安装和配置一个触手机器,以及检测和纠正漂移的方法。使用像 DSC 这样的方法有助于确保所有的安装都是一致的。

章鱼 2.0:绑定领域 UX -章鱼部署

原文:https://octopus.com/blog/bound-fields-ux

现在,Octopus 已经支持在定义步骤属性时引用变量的能力。例如,当定义一个 FTP 步骤时,我们会询问您应该将文件上传到哪个根目录。

Binding the FTP root directory

您可以键入原始值:

/websites/foo 

或者您可以引用一个变量:

/website/#{path} 

这是一个强大的特性,因为您引用的变量可以在不同的环境中使用范围。这样,您可以在每个环境中使用不同的根目录路径。

然而这个特性的一个限制是它只适用于显示为文本框的值。对于通常显示为复选框、单选按钮或选择框的值,我们没有办法让您将这些字段“绑定”到表达式。对于八达通 2.0,我想改变这一点。

这是我目前掌握的情况。默认情况下,我们仍将这些字段显示为复选框:

Checkboxes

但是如果你需要做一些更高级的事情,你可以点击右边的“绑定灯”按钮,并将其绑定到一个自定义表达式:

Bound fields

下面是嵌入的 JSFiddle:

几乎所有的步骤属性都可以像这样绑定。复选框、选择框、自动完成、单选按钮组和其他任何东西都有一个绑定灯,允许您输入自定义表达式。

UX 还能改进吗?请在下面的框中留下您的评论。

打破单一部署- Octopus 部署

原文:https://octopus.com/blog/breaking-up-monolith-deployments

Breaking up monolithic deployments

术语整体部署通常带有负面含义,然而,整体部署是应用程序随着时间增长的自然发展。在这篇文章中,我演示了如何将一个整体流程分解成更小的可部署组件。

进站

在这篇文章中,我将使用 Pitstop 应用程序作为例子。我选择 Pitstop 是因为它包含多个移动部件:

  • Web 前端
  • 蜜蜂
  • 微服务
  • 数据库
  • 引用第三方 docker 容器

这个应用程序的最初版本将数据库和表的创建嵌入到了 C#代码中。为了进行更有用的演示,我提取了数据库活动,并将其放入部署流程中。此外,API 和 web 前端都被 dockerized 化了。同样,我对此进行了修改,以便它们可以部署到 Azure 而不是容器。下面是修改版

整体部署流程

对 Pitstop 应用程序的修改是为了在部署过程中添加更多内容。部署 Pitstop 应用程序包括以下步骤:

  • 宽限通知-开始部署
  • 创建 CustomerManagement 数据库(如果不存在)
  • 创建发票数据库(如果不存在)
  • 创建通知数据库(如果不存在)
  • 创建车辆管理数据库(如果不存在)
  • 创建车间管理数据库(如果不存在)
  • 创建 WorkshopManagementEventStore 数据库(如果不存在)
  • 部署发票微服务
  • 部署审计日志微服务
  • 部署通知微服务
  • 部署时间服务微服务
  • 部署车间管理微服务
  • 部署客户管理数据库
  • 部署发票数据库
  • 部署通知数据库
  • 部署车辆管理数据库
  • 部署车间管理数据库
  • 部署车间管理事件存储数据库
  • 部署 Pistop web 应用程序
  • 部署客户管理 API
  • 部署车辆管理 API
  • 部署车间管理 API
  • 宽限通知-部署完成
  • 松弛通知-部署失败(仅在失败时)

在这种形式下,完成部署大约需要 15 分钟。虽然这不是一个天文数字,但如果有一个组件停机并需要更新,这可能是一个永恒的数字。

打破巨石

查看我们的部署流程,我们可以确定一些相关的步骤,而其他步骤是独立的。例如,发票服务的步骤都是相互关联的,因此这些步骤可以分解到它们自己的 Octopus 项目中:

  • 宽限通知-开始部署
  • 创建发票数据库(如果不存在)
  • 部署发票微服务
  • 部署发票数据库
  • 宽限通知-部署成功
  • 松弛通知-部署失败(仅在失败时)

发票组件的部署现在只需 1 分钟,而在 monolith 中需要 15 分钟。这大大节省了时间,尤其是在发票组件是唯一需要更新的部分的情况下。

将整个过程分解成不同的项目将会产生类似这样的结果:

应用程序的每个组件都被分解到各自的项目中,现在可以单独部署了。

部署流程编排

既然我们已经将应用程序分解成了独立的可部署组件,那么当我们需要部署整个解决方案时,我们就面临着协调多个项目的问题。

创建业务流程项目

Octopus Deploy 包含一个名为 Deploy a Release 的内置步骤模板。该特性允许您选择不同的项目,并从其中部署最新的版本(社区模板链部署具有类似的功能)。 Deploy a Release 步骤能够在出现变量提示时将变量传递给子项目。

使用部署发布模板有几个注意事项:

  • 必须在创建业务流程项目发布之前创建子项目发布。这可以通过我们的构建服务器插件来完成,以从构建中创建发布,或者使用 CLI。
  • 无法选择子项目的特定发布版本。

结论

自动化应用部署是您开发运维之旅中的关键一步。如果您的部署过程已经达到了整体规模,下一步就是确定哪些组件可以单独部署,这通常是说起来容易做起来难。这种做法的一个副作用是,可以单独部署的相同组件通常可以单独构建,这可以大大减少构建持续时间,并缩短交付周期。随着构建持续时间和部署时间的减少,您可以更快、更频繁地部署应用程序。

超越 Hello World:构建一个真实的 Docker CI/CD 管道- Octopus Deploy

原文:https://octopus.com/blog/build-a-real-world-docker-cicd-pipeline

Build a real-world CI/CD Docker pipeline

Docker 容器和 Kubernetes 是您 DevOps 工具箱中的优秀技术。这个超越 Hello World 博客系列涵盖了如何在现实应用中使用它们。


在上一篇文章中,我向您展示了如何将 OctoPetShop web 应用程序容器化,并为其生成 Docker 图像。

在这篇文章中,我配置了一个完整的 Docker CI/CD 管道来自动化这个过程。这包括配置与 JetBrains 的 TeamCity 的持续集成和与 Octopus Deploy 的持续交付的步骤。

在这篇文章中

配置 Docker 与 JetBrains 的 TeamCity 的持续集成

持续集成发生在构建服务器上。连续部分通常与触发构建的事件相关联,例如源代码提交或预定义的时间表。对于我们的构建服务器,我们将执行以下任务:

  • 创建一个项目。
  • 创建一个生成定义。
  • 定义构建步骤。

向构建代理添加 Docker 构建功能

大多数主要的构建服务器都可以通过内置的步骤或可下载的插件来构建 Docker 映像。对于这个演示,我使用 TeamCity,因为我的大部分经验都是使用 Azure DevOps,我想扩展我的视野。

我没有创建一个新的虚拟机(VM ),安装并配置一个操作系统,安装构建代理,最后安装 Docker,而是选择了一个更有趣的方法。团队城市(以及其他产品)的制造商 JetBrains 为他们的构建代理提供了一个 Docker 图像。他们不仅有一个构建代理映像,而且这个映像还可以运行 Docker 来执行 Docker 构建(在上面的链接文档中,我选择了运行需要 Docker 的构建下的选项二)。

提示
我第一次尝试运行时遇到了代理容器无法解析本地 DNS 条目的问题,但是 Robin Winslow 的文章 Fix Docker 的网络 DNS 配置向我展示了一个解决这个问题的巧妙技巧。

随着 DNS 问题的解决,容器启动并在我的 TeamCity 服务器上注册到未授权的代理类别下:

单击授权按钮完成连接,代理就可以执行构建了。

创建团队城市项目

我的第一步是创建一个新的 TeamCity 项目,并将我的 git 存储库连接到它。GitHub 上有 OctoPetShop 的源代码,但我使用了一个本地 Azure DevOps 实例来托管我的。这篇文章假设你已经知道如何在 TeamCity 中创建一个项目,并且关注构建和部署过程。

添加到 Docker Hub 的连接

要将您的图像推送到 Docker Hub,您需要配置一个授权用户到 Docker Hub 的连接。

为此,导航到 OctoPetShop 项目的连接选项卡:

点击添加连接:

从下拉菜单中选择 Docker 注册表,并填写用户名和密码。不要忘记使用测试连接来确保您的凭证有效:

有了这些整理工作,您就可以继续您的构建定义了。

创建构建定义

创建项目后,您可以创建一个新的构建定义来执行 Docker 构建操作。这个构建定义需要执行以下步骤:

  • 构建 OctoPetShop web 前端。
  • 构建 OctoPetShop 产品服务。
  • 构建 OctoPetShop 购物车服务。
  • 构建 OctoPetShop 数据库 DbUp。
  • 将映像推送到 Docker Hub 以用于部署。

添加 Docker 支持构建功能

您需要将 Docker Hub 连接连接到您的构建定义。为此,您可以点击构建特性选项卡和添加构建特性:

从下拉菜单中选择 Docker Support :

检查在构建之前登录 Docker 注册表,选择您为项目创建的连接,然后点击保存:

添加构建步骤

步骤 1 到 4 几乎相同;唯一的区别是您将构建的 dockerfile 文件。点击构建步骤选项卡,并点击添加构建步骤按钮:

对于该步骤,请填写以下内容:

  • 转轮类型:Docker
  • Docker 命令:build
  • 回购协议中 dockerfile 的路径:即Development-Active/OctopusSamples.OctoPetShop.web/dockerfile
  • 图像名称:标签:即octopusexamples/octopetshop-web:1.0.0.0

对于 Docker 图片,用DockerId/ImageName:version标记你的图片被认为是最佳实践。省略标签的version部分并不罕见,但是每当一个图像的新版本上传到 Docker Hub 时,如果没有指定版本号,它会自动附加latest作为版本。Octopus Deploy 将 SemVer 用于包版本,在这个例子中,我将1.0.0.0硬编码为版本号,但是您也可以轻松地使用 TeamCity 参数来动态分配版本号。

您将为产品服务、购物车服务和数据库添加三个类似的步骤。

您将添加的最后一步是将您构建的映像推送到 Docker Hub(即执行 Docker push 命令)。对于这一步,您需要填写以下内容:

  • 转轮类型:Docker
  • Docker 命令:push
  • 图像名称:标签

对于推送步骤,您可以指定在步骤 1 到 4 中构建的所有映像,并在一个步骤中推送它们。除非出现任何输入错误,否则构建应该会成功执行:

恭喜你!您刚刚完成了本文的 CI 部分。剩下唯一要做的事情就是添加一个触发器,这样当有人提交到源代码控制时,一个构建就会自动执行。

使用 Octopus Deploy 配置 Docker 连续交付

对于本文的连续交付部分,您将使用 Octopus Deploy。使用 Octopus,您将执行以下操作:

  • 添加 Docker Hub 作为外部 feed。
  • 创建新项目。
  • 定义我们的部署步骤。

添加 Docker Hub 作为外部 feed

您需要添加 Docker Hub 作为外部提要,以便 Octopus Deploy 可以从 Docker Hub 中提取您的图像,并将它们部署到运行 Docker 的服务器上。

登录 Octopus 后,点击标签:

部分,点击外部进给,点击添加进给按钮:

创建 Feed 表单上,填写以下内容:

  • 进料类型:Docker Container Registry
  • 名称:Docker Hub
  • 用户名:your-username
  • 密码:your-password

测试提要以确保 Octopus 可以登录 Docker Hub:

配置好外部提要后,您可以定义我们的步骤。

创建 Octopus 部署项目

要创建新项目,点击项目选项卡,并点击添加项目按钮:

给项目命名,点击保存:

与我们的构建类似,Octopus 中的步骤也基本相同,只有微小的差异。我将带您完成您将添加到流程中的第一步,并指出其余步骤中的差异。

在项目的流程选项卡上,点击添加步骤:

选择 Docker 类别和运行 Docker 容器步骤:

对于此演示,使用 Microsoft SQL Server 2017 Docker 映像作为您的数据库服务器。这将是您在部署中配置的第一个容器。

Docker 容器步骤的表单相当长,所以我把截图分成了几个部分。对于第一部分,给步骤一个名称以及它将被部署到的角色。在这个演示中,我使用了一个简单的角色Docker,但是在生产场景中,这个角色可能更有意义,比如:OctoPetShop-Web-Container

选择我们的 Docker Hub 外部馈送,并选择microsoft/mssql-server-linux图像。

提示
在搜索框中键入mssql查找图片。

选择Bridge的网络类型:

要使您的数据库服务器可访问,您需要添加一个端口映射。指定默认的 SQL Server 端口1433:

向下滚动到变量部分,并添加以下显式变量映射。这个图像需要传递给它两个环境变量:SA_PASSWORDACCEPT_EULA。为SA_PASSWORD创建一个类型为 Sensitive 的项目变量:

这个容器就这样了。点击保存将该步骤提交给流程。

以下是其余容器的详细信息:

OctoPetShop 网站

  • Docker 图片:octopussamples/octopetshop-web
  • 网络类型:Host
  • 显式变量映射:
    • ProductServiceBaseUrl = http://localhost:5011/
    • ShoppingCartServiceBaseUrl = http://localhost:5012

OctoPetShop 产品服务

  • 码头工人图片:octopussamples/octopetshop-productservice

  • 网络类型:Bridge

  • 端口映射:5011:5011

  • 显式变量映射:

    • OPSConnectionString = "Data Source=#{Octopus.Machine.Hostname}; Initial Catalog=OctoPetShop; User ID=sa; Password=#{Project.Database.SA.Password}
  • Docker 图片:octopussamples/octopetshop-shoppingcartservice

  • 网络类型:Bridge

  • 端口映射:5012:5012

  • 显式变量映射:

    • OPSConnectionString = "Data Source=#{Octopus.Machine.Hostname}; Initial Catalog=OctoPetShop; User ID=sa; Password=#{Project.Database.SA.Password}

OctoPetShop 数据库

  • Docker 图像:octopussamples/octopetshop-database
  • 网络类型:Host
  • 显式变量映射:
    • DbUpConnectionString = "Data Source=#{Octopus.Machine.Hostname}; Initial Catalog=OctoPetShop; User ID=sa; Password=#{Project.Database.SA.Password}

定义好所有步骤后,您就可以创建并部署一个发布了:

提示
关于如何为 Linux 设置触手的信息,请参见我们的文档

如果您导航到您刚刚部署到的服务器,您应该看到您的 OctoPetShop 应用程序正在运行。与前面的演示一样,OctoPetShop 使用自签名证书重定向到 https,因此您很可能会收到关于它不安全的警告。在这种情况下,可以忽略警告并继续:

完成 Docker CI/CD 管道

到目前为止,我们已经完成了 CI 和 CD 部分,但是您仍然需要连接它们。要将这些片段组合在一起,请回到您的 TeamCity 服务器。

安装 Octopus 部署团队城市插件

首先,导航并下载 Octopus 部署插件

下载完成后,进入 TeamCity 服务器中的管理➜插件列表。从这里,点击上传插件 zip 按钮添加插件:

这将添加一些步骤,您可以使用构建定义与 Octopus Deploy 进行交互:

添加创建发布步骤

接下来,向您的构建定义添加一个新步骤,octopus deploy:Create release:

现在,当您运行一个构建时,它会自动在 Octopus Deploy 中创建一个发布:

如果您愿意,您可以直接从构建定义中将该版本的自动部署添加到开发中。

结论

这篇文章展示了使用 Docker 容器为现实世界的应用程序创建完整的 CI/CD 管道是可能的。它讲述了如何使用 JetBrains TeamCity 自动化 Docker 构建管道,以及如何使用 Octopus Deploy 配置 Docker 连续交付。

超越 Hello World:构建一个真实的 Kubernetes CI/CD 管道- Octopus Deploy

原文:https://octopus.com/blog/build-a-real-world-kubernetes-cicd-pipeline

Beyond Hello World - Build a real-world Kubernetes CI/CD pipeline

Docker 容器和 Kubernetes 是您 DevOps 工具箱中的优秀技术。这个超越 Hello World 博客系列涵盖了如何在现实应用中使用它们。


的上一篇文章中,我向您展示了如何使用我们的 OctoPetShop 容器建立一个 Kubernetes 集群。我们为 web 前端、产品服务和购物车服务创建了 YAML 文件。

在这篇文章中,我调整了 YAML 文件并建立了持续集成和持续交付(CI/CD)管道。

创建构建定义

Kubernetes 没有任何需要构建的东西,除了它使用的 Docker 图像。但是,我们创建的 YAML 文件可以放在源代码控制中并进行版本控制,因此使用构建服务器仍然是有意义的。我们的 OctoPetShop repo 包含一个 k8s 文件夹,我们在其中放置了创建集群所需的所有 YAML 文件。我们会用这个回购作为我们的来源。为了一致性,我们使用 TeamCity 作为我们的构建服务器。

设置版本号

在 Docker CI/CD 帖子中,我们将容器的版本号硬编码为 1.0.0.0。在这篇文章中,我们将为 YAML 文件的每个版本创建唯一的版本号。为简单起见,我将把版本号设置为四位数字的年份、两位数字的月份、两位数字的月份日期和修订版(yyyy。MM.dd.r)。

将 PowerShell 步骤添加到我们的构建定义中

输入以下 PowerShell 来设置版本号

Write-Host "##teamcity[buildNumber '$(Get-Date -Format "yyyy.MM.dd").%build.counter%']" 

这将允许我们在后续步骤中使用%build.number%来指定版本号。

调整 YAML

我们的 repo 中的 YAML 拥有 SQL Server 的 SA 帐户的明文密码。让我们利用 Octopus 部署文件中的替代变量特性,用 Octopus 部署项目中的变量替换密码。首先,我们将占位符包含在 YAML 文件中。例如,打开 octopetshop-database-job.yaml,更改连接字符串的密码部分:

apiVersion: batch/v1
kind: Job
metadata:
  name: octopetshop-dbup
spec:
  template:
    spec:
      containers:
        - name: dbup
          image: octopussamples/octopetshop-database
          command: [ "dotnet", "run", "--no-launch-profile" ]
          env:
            - name: DbUpConnectionString
              value: Data Source=octopetshop-sqlserver-cluster-ip-service;Initial Catalog=OctoPetShop; User ID=sa; Password=#{Project.SA.Password}
      restartPolicy: Never 

对以下文件重复此过程:

  • 章鱼小铺-产品服务-部署。YAML
  • octopetshop-shoppingcartservice-部署。YAML
  • octopetshop-SQL-deployment。YAML

如果要运行本地 k8s 实例,可能需要在 octopetshop-loadbalancer.yaml 文件中指定外部 IP 地址(托管解决方案如 Azure 或 AWS 通常会自动为您连接)。将 externalIPs 组件添加到 YAML 文件中,并将其设置为占位符:

apiVersion: v1
kind: Service
metadata:
  name: web-loadbalancer
spec:
  selector:
    component: web
  ports:
    - port: 5000
      targetPort: 5000
      name: http-port
    - port: 5001
      targetPort: 5001
      name: https-port
  type: LoadBalancer
  externalIPs:
  - #{Project.Kubernetes.LoadBalancer.ExertnalIp} 

打包 YAML

使用 Octopus Deploy Pack 步骤,我们可以将所有用于部署的 YAML 打包到一个 NuGet 包中。

将包推至 Octopus 部署

通过 Octopus Deploy 推送步骤,我们可以将 NuGet 包发送到 Octopus Deploy。

出于演示的目的,我们使用 Octopus Deploy 的内置 NuGet 存储库。

我们的构建定义现在将为我们的部署打包所有的 YAML 文件,并将它们发送到我们的 Octopus Deploy 服务器!现在是连续交付的部分。

使用部署的 Octopus 配置连续交付

使用 Octopus Deploy 中的 YAML 文件包,我们可以创建我们的部署过程。在本节中,我们将执行以下操作:

  • 创建新项目。
  • 定义我们的部署步骤。

创建 Octopus 部署项目

要创建新项目,点击项目选项卡,并点击添加项目按钮:

给项目命名,点击保存:

让我们为我们的项目添加一些步骤。在项目的流程选项卡上,点击添加步骤:

除了指定的文件之外,我们的过程中的步骤几乎是相同的。我会带你看第一个。

在我们的流程中添加一个部署原始 Kubernetes YAML 步骤:

第一步将部署 SQL Server 群集 IP 服务。部署到 Kubernetes 是通过它的 REST API 完成的,它使用了幕后的kubectl CLI 工具。Octopus 在 workers 上执行这个部署工作,而不是在部署目标上,所以你需要确保在 workers 上安装了一个版本的 kubectl 来完成这个工作。

对于 YAML 源,选择包内文件,指定包和包内的文件:

如果您要部署到一个名称空间,请确保填写表单的该部分。

这一步就到此为止!除了文件名之外,其余步骤完全相同。完成后,您应该会看到这样的内容:

我们现在剩下要做的就是添加项目变量。单击左侧的变量选项卡:

创建一个名为 Project 的新变量。SA .密码并使其敏感:

给它一个符合 SQL Server 2017 密码复杂性要求的值。

在我们准备好创建一个发布之前,最后要做的是改变发布的编号以匹配我们的构建版本。这将允许我们将一个发布直接绑定到创建它的构建中。为此,请单击设置选项卡:

展开“版本控制”部分:

选择使用随附软件包中的版本号,然后选择我们的 OctoPetShop。K8s 包装:

我们现在准备创建一个版本!点击创建发布按钮!

在此页面上不需要做任何更改,只需点击保存然后点击部署开发环境:

最后,通过点击部署确认我们想要部署:

部署完成后,您应该会看到类似这样的内容:

通过部署,我们可以导航到我们的服务器来查看我们的应用程序。

我们的。NET 核心应用程序将重定向到 SSL,如果你得到一个关于网站不安全的警告,在这种情况下是可以的。

只能选择连续交货

到目前为止,我们的过程依赖于我们创建 YAML 来定义我们的 Kubernetes 集群。使用 Octopus Deploy,有一种替代的方法部署到 Kubernetes,不需要使用构建服务器或了解 YAML。

部署 Kubernetes 容器步骤模板包含一个表单,我们可以使用它来定义我们的 Kubernetes 集群所需的所有属性,而无需编写一行 YAML:

该表单非常广泛,允许您创建 Kubernetes 集群所需的一切,例如:

  • 集群 IP 服务
  • 入口服务
  • 部署
  • 服务端口映射
  • 配置映射
  • 秘密
  • 容器
  • 容器环境变量
  • 容器端口映射
  • 还有更多!

T11

这种方法让我们可以完全跳过 CI 部分,专注于 Kubernetes 管道中的 CD 部分。

form 方法在部署时动态编写 YAML,您需要确保安装在 worker 上的 kubectl 版本使用与您要部署到的 Kubernetes 版本相同的 API 格式。在我的情况下,我在 Ubuntu 18.04 上使用 MicroK8s,它似乎没有引用相同的 API 版本。

结论

在本文中,我演示了如何在 CI/CD 管道中使用 Kubernetes。我还演示了一种部署到 Kubernetes 的方法,该方法只使用管道的 CD 部分。

浏览 DevOps 工程师手册以了解有关 DevOps、CI/CD 和部署管道的更多信息。

使用构建信息实现 CI/CD 管道的可见性——Octopus Deploy

原文:https://octopus.com/blog/build-information-and-your-ci-cd-pipeline

Octopus with build information

持续集成(CI)通常包括三个部分:

  1. 源代码管理服务器
  2. 问题跟踪
  3. 构建服务器

Azure DevOps 等工具将所有组件组合成一个解决方案,而其他配置将它们分开。例如,您可以使用 GitHub 进行源代码控制,使用 TeamCity 进行构建,使用吉拉进行问题跟踪。当谈到连续交付(CD)时,提交和问题跟踪对于确保部署软件的正确版本是很重要的。使用 Octopus Deploy,您可以将构建信息,比如提交和发布,作为您发布的一部分。

值得注意的是,虽然提交在所有情况下都有效,但只有在 Octopus Deploy 中配置了以下集成之一时,问题跟踪才有效:

  • Azure DevOps 问题跟踪
  • GitHub 问题跟踪
  • 吉拉一体化

在本文中,我将介绍如何配置 TeamCity 和 Octopus Deploy 以包含构建信息,以及如何在部署过程中使用这些信息。

示例场景

在这个演示中,我使用了上面描述的场景:GitHub ( OctoPetShop )、TeamCity 和吉拉。我将介绍这些特定的技术,但是无论您使用哪种工具,整个过程都是相似的。

这篇文章假设你已经知道如何在吉拉创建一个项目。

制造一个问题

我们将从在吉拉为 OctoPetShop 应用程序记录一个 bug 开始。

The create an issue screen in Jira

创建问题后,我们需要记下key值,因为我们需要它来正确标记我们的提交。在这种情况下,值为OPS-1

The Jira issue key

将提交与问题关联

如何将提交与问题关联取决于您使用的问题跟踪器。对于吉拉,您的提交消息需要使用以下格式:

git commit -m "[key-value] Commit message" 

对于我们的例子,我们的提交消息看起来像这样:

git commit -m "[OPS-1] Fixed tax rate calculation.  Tax rate now pulled using new tax rate service" 

配置生成以推送生成信息

Octopus Deploy 提供官方插件来集成以下构建服务器:

  • Azure DevOps
  • 团队城市
  • 詹金斯
  • 竹子

除了可用的插件,还有一些社区支持的与在线构建服务器的集成:

  • 切尔莱西
  • GitHub 操作
  • 比特桶管道
  • 应用程序供应商

在这个演示中,我们使用 TeamCity。

  1. 通过选择Octopus Deploy:Build Informationrunner,向构建定义添加一个新步骤。
  2. 填写所需的值:
  • 八达通网址:你的八达通服务器的网址
  • API 键:具有推送构建信息权限的 API 键
  • 空间名称:推送到的空间名称(默认为空)
  • 包 id:应用构建信息的包列表

TeamCity push build information step

发布一个构建,我们可以看到我们的变更已经被构建服务器接受了。

TeamCity commit message

配置问题跟踪集成

如前所述,在 Octopus Deploy 中配置相应的集成之前,构建信息的问题跟踪不会起作用。对于本演示,我们需要配置吉拉集成

  1. 导航到 Octopus Deploy 中的配置选项卡,点击设置
  2. 点击Jira,填写所需信息:
  • 吉拉实例类型:云还是服务器
  • 吉拉基地网址:吉拉网址
  • 吉拉连接 App 密码:连接的密码
  • Octopus 安装 ID :你在吉拉的 Octopus 部署插件中的 Octopus 安装 ID
  • 八达通服务器 URL :你的八达通服务器的 URL
  • 启用:启用集成的复选框

配置好我们的集成后,导航到选项卡并选择构建信息

在这个页面上,我们可以看到 Octopus Deploy 已经为 OctoPetShop 的包上传了构建信息。单击其中一个将显示提交和与之相关的问题。

OctoPetShop build information

构建信息包含:

  • 链接到它来自的版本。
  • 提交。
  • 它所关联的工作项(问题)。

发行说明前缀

你可能已经注意到了,我们还没有讨论的发布说明前缀。发行说明前缀提供了一种重写工作项标题的方法。Octopus Deploy 将在工作项的注释中查找我定义为Release note:的指定前缀。当它找到带有前缀的注释时,它会用前缀后面的任何文本覆盖工作项的标题。

所有 Octopus 部署问题集成都包含了Release Note Prefix特性。

我们这期的标题是Incorrect tax calculated,这并不是一个非常有用的发行说明。相反,我们希望它显示为Improved tax rate calculation based on location.

为此,我们使用我们定义的前缀向吉拉问题添加一条注释:

Jira issue comment

在 Octopus 中,我们可以看到工作项的标题已经更新为所需的值。

Work item with the comment

让每个人都知道

到目前为止,我们已经展示了如何通过 Octopus Deploy Web 门户访问构建信息。然而,并不是组织中每个人都能访问 Octopus Deploy,例如质量保证(QA)团队。Octopus Deploy 有一些内置变量,可以用来共享构建信息。

项目发布说明模板

在项目的设置中,你可以定义一个发布说明模板。该模板允许您定制与包相关的构建信息的显示。下面是一个示例模板:

#{each workItem in Octopus.Release.WorkItems}#{if Octopus.Template.Each.First == "True"}WorkItems:#{/if}
- [#{workItem.Id}](#{workItem.LinkUrl}) - #{workItem.Description}
#{/each}

Packages:
#{each package in Octopus.Release.Package}
- #{package.PackageId} #{package.Version}
#{each commit in package.Commits}
    - [#{commit.CommitId}](#{commit.LinkUrl}) - #{commit.Comment}
#{/each}
#{/each} 

Octopus.Release.Package变量是版本说明模板中唯一可用的。它在部署过程中不可用,例如运行脚本步骤。

有了定义的模板,在部署期间可以通过Octopus.Release.Notes变量访问发行说明。使用类似 Slack 的东西,您可以在通知消息中包含构建信息。

Slack notification step

因为每个包都标记了构建信息,所以消息看起来像这样:

Slack message

如果没有发行说明模板(或者手动输入的发行说明),Octopus.Release.Notes变量为空。

章鱼。部署.变化

另一种访问构建信息的方法是Octopus.Deployment.Changes变量。使用运行脚本步骤,您可以迭代修改,构造一条消息并设置一个输出变量:

$changeListRaw = $OctopusParameters["Octopus.Deployment.Changes"]
$changeList = $changeListRaw | ConvertFrom-Json

foreach ($change in $changeList)
{
    $emailBody = "Release Number: $($change.Version)`r`n"

    $emailBody += "    Build Information`r`n"
    foreach ($buildInformation in $change.BuildInformation)
    {
        $emailBody += "        Package: $($buildInformation.PackageId)`r`n"
        $emailBody += "        Version: $($buildInformation.Version)`r`n"
        $emailBody += "        BuildUrl: $($buildInformation.BuildUrl)`r`n"
        $emailBody += "        VCSRoot: $($buildInformation.VcsRoot)`r`n"
        $emailBody += "        VCSCommitNumber: $($buildInformation.VcsCommitNumber)`r`n"
    }

    $emailBody += "    Commit Information`r`n"
    foreach ($commit in $change.Commits)
    {
        $emailBody += "        Git Hash: $($Commit.Id)`r`n"
        $emailBody += "        Git Comment: $($Commit.Comment)`r`n"
        $emailBody += "        Git Link: $($Commit.LinkUrl)`r`n"
    }

    $emailBody += "    Work Items`r`n"
    foreach ($workItem in $change.WorkItems)
    {
        $emailBody += "        Id: $($WorkItem.Id)`r`n"
        $emailBody += "        Link Url: $($WorkItem.LinkUrl)`r`n"
        $emailBody += "        Source: $($WorkItem.Source)`r`n"
        $emailBody += "        Description: $($WorkItem.Description)`r`n"
    }
}

Set-OctopusVariable -name "EmailBody" -value $emailBody 

使用发送电子邮件模板,您可以将电子邮件的正文设置为输出变量的值。

An Octopus email step

这将允许您向风险承担者发送电子邮件,通知他们部署的进度。

Email message

结论

在 Octopus Deploy 中包含构建信息是一个非常强大的通信工具。我希望这篇文章展示了它在您的 CI/CD 管道中的不同使用方式。

浏览 DevOps 工程师手册了解更多关于 DevOps 和 CI/CD 的信息。

愉快的部署!

比赛:建立章鱼图书馆-章鱼部署

原文:https://octopus.com/blog/build-octopus-library

正如我上周宣布的,Octopus 2.4 中最酷的新增功能是创建可重用部署步骤的能力,这些步骤可以在项目之间共享。

Step templates

章鱼 2.4 今天毕业稳定发布,可以从下载页面获取。这意味着您现在可以开始自己创建和使用步骤模板,以及导出它们与他人共享,或导入他人制作的步骤。

为了更容易地共享 step 模板,我们创建了一个新站点,名为。在撰写本文时,包含了 11 个模板,您可以导入到您的 Octopus 2.4+服务器中,并从今天开始使用,包括:

  • 安装巧克力包
  • 使用 Git 推送至 Azure 网站或 AppHarbor
  • 运行 SQL 脚本
  • 在文件中查找和替换
  • 启动和停止 Windows 服务

我对这项功能非常非常兴奋,原因有二:

  1. 这意味着我们能够在不改变产品的情况下为产品添加功能
  2. 这意味着可以与他人分享你的成果,并从他们的成果中获益

想象一个开放的存储库,其中任何常见的 Windows/。您可能遇到的. NET 部署任务已经编写好脚本,打包好,随时可以使用。这将是一个非常有用的资源。

帮助我们收藏图书馆!

我们喜欢你的帮助来扩大这个库,并用有用的、可重用的脚本和步骤来填充它。库网站是的一个 GitHub 库:如果你有一个想法,并能把它变成一个可重用的步骤,只需导出它,并向我们发送拉请求!

如果分享你的剧本还不足以让世界变得更美好,我们将举办一场小小的比赛:

Win an Octopus Deploy mug

没错:你可以赢得你自己的超级时尚章鱼部署杯! 😃 你所要做的就是提交一个步骤模板的拉请求,如果它被接受,你就可以参加抽奖了。在六月底,我会随机抽取 3 名获奖者,我们会给你邮寄一个杯子。

关于如何贡献你的模板的说明在 GitHub readme 上。快乐脚本!

你构建二进制文件一次吗?-章鱼部署

原文:https://octopus.com/blog/build-your-binaries-once

不管你的软件交付管道看起来像什么,如果你正在使用编译语言,你的软件交付过程可能包含这些共同的步骤:

  1. 从源代码控制中获取代码
  2. 编译它(假设您使用的是编译语言)
  3. 配置软件并将其部署到特定环境中的机器上

对于任何重要的应用程序,您都需要将软件部署到多个环境中。这意味着您需要选择:是一次性构建二进制文件,还是在每次部署之前构建它们?

第一个策略是在每次部署之前进行构建。这个过程看起来像下面的图表,其中代码被获取、编译和(希望如此!)在每次部署前进行测试。为了确保相同的特性/修复在每个部署中都得到体现,您可能每次都要构建一个特定的修订版或标签。

Building your binaries many times

或者,您可能决定遵循更类似于下图的方法,其中构建发生一次,并且在部署到每个环境时重用相同的二进制文件集(可部署的工件)。

Building your binaries once

虽然一次构建二进制文件可以节省效率,但在我看来,这样做的主要原因是为了降低风险。如果您在每次部署之前进行编译,您将冒新的服务包或库更新偷偷进入的风险,从而导致最终部署到每个环境的内容之间的微小差异(漂移)。生产前和生产部署之间保持的一致性越强,生产部署就越有可能顺利进行。

重要的是要认识到构建过程不仅仅是“代码输入”、“二进制代码输出”的确定性函数。生成的二进制文件不仅仅依赖于输入的代码,还依赖于库、编译器、操作系统更新以及许多其他可能随时间变化的环境因素。

可能导致问题的变化的真实例子是。NET framework 4.0 和 4.5。如果在编译后部署测试。在没有安装 4.5 的机器上安装. NET 4.0,然后安装。NET 4.5(因为您计划在即将发布的版本中使用它),然后尝试在升级到生产之前进行编译,除非您非常明确您正在编译的版本,否则输出结果之间会有微小的差异,这可能会导致代码在测试中完美运行,而在生产中失败。

构建一次二进制文件可能有点复杂,因为它需要在两次部署之间的某个地方存储工件。然而,这不一定是一件坏事;在任何时间点,都可以轻松找到旧版本的应用程序,而不必构建它们。如果您需要回滚,这非常方便,尤其是如果您同时对构建服务器进行了更改。

部署自动化的目的是提高我们将工作软件更可靠、更频繁地投入生产的能力。构建一次二进制文件是一个简单的实践,可以帮助实现这一点。

BuildDeploySupport:在 Octopus - Octopus Deploy 中共享 PowerShell 脚本

原文:https://octopus.com/blog/builddeploysupport

在 Octopus 中有多个项目需要在部署期间完成相同的任务是很常见的,比如配置 IIS。复制和粘贴脚本当然是一种方法,但并不理想。在这篇客座博文中, Jonathan Goldman 向我们展示了他在 Octopus 项目中分享 PowerShell 脚本库的技巧。


最近,我们已经将越来越多的项目转移到 Octopus Deploy,其中一件很快变得明显的事情是,需要在多个项目之间共享相同的部署脚本,例如安装服务、设置 windows 时间表或配置应用程序池。当脚本在项目之间复制和粘贴时,事情会变得很快变得混乱,所以我们寻找一个简单的解决方案。

为了解决这个问题,我创建了一个内部的 nuget 包,它包含了我们的部署脚本和一个 Init.ps1 步骤,将它们复制到一个解决方案文件夹中。然后,我们可以将脚本添加到需要它们的项目中,并从我们的 Octopus Deploy.ps1 脚本中引用它们。当一个脚本被更改时,我可以直接更新包并获得所有最新的更改。

因为我们创建的许多脚本最终都非常通用,所以我创建了一个每个人都可以利用的 nuget 包:

install-package BuildDeploySupport 

如果将 DeployWeb.ps1 作为链接添加到 Web 项目中(添加现有项->添加为链接)。并编辑您的 Deploy.ps1,如下所示

. .\DeployWeb.ps1

InstallAppPool 'my-app-pool' 'v4.0' {
    SetCredentials 'username' 'password'
}

InstallWebSite $OctopusWebSiteName 'my-app-pool' 'www.yourdomain.com' {
        SetWindowsAuthentication $true
        SetAnonymousAuthentication $false        
} 

您将不再需要登录服务器来配置应用程序池或创建网站。一切都应该正常。这类似于 Octopus 文档中概述的方法。

这不仅有助于人们开始使用 Octopus,还有助于围绕创建可重用的部署脚本培养社区。

如果你有兴趣投稿,github 上的所有内容都是开源的:

https://github.com/jonnii/BuildDeploySupport

构建 Apache 可移植运行时(APR) - Octopus 部署

原文:https://octopus.com/blog/building-apr-for-tomcat

Tomcat 使用 Apache Portable Runtime (APR) 来提供许多增强的特性和性能。例如,为了获得 OpenSSL 为 HTTPS 提供的增强性能,APR 需要存在。

Tomcat 为 Windows 用户提供了 APR 的预编译版本,但是 Linux 用户经常被要求从他们的操作系统分发包库中安装 APR。

这些是可从 Centos 7 的 EPEL 回购获得的 APR 和 Tomcat 原生包:

$ yum info apr
Loaded plugins: fastestmirror, langpacks
Determining fastest mirrors
 * base: centos.mirror.ausnetservers.net.au
 * epel: fedora.melbourneitmirror.net
 * extras: mirror.nsw.coloau.com.au
 * updates: mirror.nsw.coloau.com.au
Installed Packages
Name        : apr
Arch        : x86_64
Version     : 1.4.8
Release     : 3.el7
Size        : 221 k
Repo        : installed
From repo   : anaconda
Summary     : Apache Portable Runtime library
URL         : http://apr.apache.org/
License     : ASL 2.0 and BSD with advertising and ISC and BSD
Description : The mission of the Apache Portable Runtime (APR) is to provide a
            : free library of C data structures and routines, forming a system
            : portability layer to as many operating systems as possible,
            : including Unices, MS Win32, BeOS and OS/2.

Available Packages
Name        : apr
Arch        : i686
Version     : 1.4.8
Release     : 3.el7
Size        : 107 k
Repo        : base/7/x86_64
Summary     : Apache Portable Runtime library
URL         : http://apr.apache.org/
License     : ASL 2.0 and BSD with advertising and ISC and BSD
Description : The mission of the Apache Portable Runtime (APR) is to provide a
            : free library of C data structures and routines, forming a system
            : portability layer to as many operating systems as possible,
            : including Unices, MS Win32, BeOS and OS/2.

$ yum info tomcat-native
Loaded plugins: fastestmirror, langpacks
Loading mirror speeds from cached hostfile
 * base: centos.mirror.ausnetservers.net.au
 * epel: fedora.melbourneitmirror.net
 * extras: mirror.nsw.coloau.com.au
 * updates: mirror.nsw.coloau.com.au
Installed Packages
Name        : tomcat-native
Arch        : x86_64
Version     : 1.1.34
Release     : 1.el7
Size        : 172 k
Repo        : installed
From repo   : epel
Summary     : Tomcat native library
URL         : http://tomcat.apache.org/tomcat-8.0-doc/apr.html
License     : ASL 2.0
Description : Tomcat can use the Apache Portable Runtime to provide superior
            : scalability, performance, and better integration with native server
            : technologies.  The Apache Portable Runtime is a highly portable library
            : that is at the heart of Apache HTTP Server 2.x.  APR has many uses,
            : including access to advanced IO functionality (such as sendfile, epoll
            : and OpenSSL), OS level functionality (random number generation, system
            : status, etc), and native process handling (shared memory, NT pipes and
            : Unix sockets).  This package contains the Tomcat native library which
            : provides support for using APR in Tomcat. 

所以我们可以访问提供 APR 版本 1.4.8 和 Tomcat 原生版本 1.1.34 的包。但是安装了这些软件包后,Tomcat 9.01 会报告以下错误:

org.apache.catalina.core.AprLifecycleListener.init An incompatible version [1.1.34] of the APR based Apache Tomcat Native library is installed, while Tomcat requires version [1.2.14] 

这意味着我们可以从 Centos 软件包管理器安装的版本对于 Tomcat 的更高版本来说不够新。

解决方法是自己编译这些库。这听起来很复杂,但实际上很简单。

首先,您需要安装开发工具和 Java 开发工具包。在 Centos 中,这是通过以下命令完成的:

sudo yum groupinstall "Development Tools"
sudo yum install java-1.8.0-openjdk-devel 

接下来运行下面的脚本,它将下载、提取、编译和安装 Tomcat 所需的各种库。

这个脚本中的 URL 指向[https://Tomcat . Apache . org/download-Native . CGI](Tomcat Native)库和 APR 库的镜像。您可以更改这些 URL 以指向一个更近的服务器,或者如果此处列出的服务器已关闭,则指向一个不同的服务器。

#!/usr/bin/env bash
cd /tmp
wget http://apache.mirror.amaze.com.au//apr/apr-1.6.3.tar.gz
tar -zxvf apr-1.6.3.tar.gz
cd apr-1.6.3
./configure
make
make install

cd /tmp
wget http://apache.mirror.amaze.com.au//apr/apr-util-1.6.1.tar.gz
tar -zxvf apr-util-1.6.1.tar.gz
cd apr-util-1.6.1
./configure --with-apr=/usr/local/apr
make
make install

cd /tmp
wget http://apache.melbourneitmirror.net/tomcat/tomcat-connectors/native/1.2.14/source/tomcat-native-1.2.14-src.tar.gz
tar -zxvf tomcat-native-1.2.14-src.tar.gz
cd tomcat-native-1.2.14-src/native
./configure --with-apr=/usr/local/apr --with-java-home=/usr/lib/jvm/java-1.8.0-openjdk
make
make install 

最后,在 Tomcat 目录下创建一个名为bin/setenv.sh的文件,内容如下。这允许 Tomcat 找到由上面的脚本编译的库。

CATALINA_OPTS="-Djava.library.path=/usr/local/apr/lib" 

完成这些更改后,您应该会在logs/catalina.out文件中看到以下消息。这表明 Tomcat 成功地加载了 APR 库。

31-Oct-2017 18:21:08.930 INFO [main] org.apache.catalina.core.AprLifecycleListener.lifecycleEvent Loaded APR based Apache Tomcat Native library [1.2.14] using APR version [1.6.3]. 

如果您对将 Java 应用程序自动部署到 Tomcat 感兴趣,下载 Octopus Deploy 的试用版,并查看一下我们的文档

构建 Octopus CLI vNext - Octopus Deploy

原文:https://octopus.com/blog/building-octopus-cli-vnext

Octopus 命令行界面(CLI)多年来为我们提供了很好的服务。然而,它有一些我们想要解决的限制。

在这篇文章中,我分享了我们用 Octopus Deploy 改进 CLI 体验的计划。

Octopus CLI 的状态(octo)

Screenshot of Octopus CLI (octo)

Octopus CLI ( octo)建立在 C#之上,并且严重依赖Octopus client。NET 库。它提供命令来促进部署和 runbook 执行的自动化。值得注意的命令包括:

  • build-information
  • create-release
  • deploy-release
  • pack
  • push
  • run-runbook

Octopus CLI 仍然是与 Octopus REST API 交互的最有效方式之一。它使我们的客户能够自动执行重复性任务,同时提供执行一次性命令的灵活性。我们为支持的平台构建和维护的大多数集成,如 Azure DevOps、GitHub Actions 和 TeamCity,都使用它来执行与 Octopus REST API 一致的操作。

尽管 Octopus CLI 多年来为我们提供了很好的服务,但我们希望解决它的一些局限性:

  • 为自动化而建,而不是为人而建:当你使用 Octopus CLI 时,你可以看出它主要是作为一个自动化工具而建的。CLI 需要不断发展,以包含以人为本的设计。
  • 命令结构和输出不一致:Octopus CLI 命令结构不一致是由于这些年来添加的命令。产出不是由总体战略决定的。在命令结构中有一些明显的例外会损害一致性,例如pack。此外,这些命令是按操作而不是按目标资源分组的。这损害了整体用户体验,并使命令集的发展变得困难。
  • 运行时依赖:尽管 Octopus CLI 是一个自包含的可执行文件,但它要求在运行二进制文件之前必须安装平台库(例如,依赖于。Alpine 上的. NET 自包含可执行文件)。这反映在用于各种 Linux 发行版的 Octopus CLI vNext 所需的安装脚本中。
  • 跨平台编译困难:构建脚本与 Nuke 捆绑在一起,需要 Windows 专用工具(即SignTool.exe),这使得构建跨平台变得困难。例如,参见问题#124,macOS 构建失败
  • 不支持执行 API:Octopus CLI(octo)不支持这些操作,添加对这些操作的支持需要大量代码路径(有关更多信息,请阅读我们的帖子使用执行 API 加快部署)
  • 缺乏自助故障排除:Octopus CLI(octo)不支持内置功能,让客户排除网络连接问题等故障(参见问题#220 )。

我们纠结于是否继续构建 Octopus CLI 的功能,并加入我们想要做出的改变以推动事情向前发展。经过仔细考虑,我们决定重新开始,不再受 Octopus CLI 10 多年设计决策的约束。这个决定也减轻了更新 Octopus CLI 的挑战,并使我们暴露于现有集成的下游问题。我们希望更有主见,专注于以客户为中心的工作流,我们经常遵从我们的 API。

介绍新的八达通 CLI(八达通)

New Octopus CLI

新的 Octopus CLI ( octopus)代表了 Octopus CLI 的发展。首先,可用命令的数量将显著增加。新的 Octopus CLI 将支持管理资源的操作,如帐户、生命周期、项目和空间。它还将具有用户交互的新功能——我们是提倡这种功能的命令行界面指南的支持者。此功能旨在通过一系列问题来指导用户以最简单的方式执行他们想要的操作:

Demo: Create Release with Octopus CLI vNext

交互式 CLI 将引导新手和有经验的用户找到他们想要的结果。也就是说,新的 Octopus CLI 将支持自动化作为其主要用例。

Demo: Deploy Release with Octopus CLI vNext

新的 Octopus CLI 基于 Go 编程语言。该语言及其支持库非常适合构建 CLI。我们还想利用这个机会改进 Octopus Deploy 的 Go API 客户端,它是 Octopus DeployTerraform 提供者的基础。

为什么使用 Go 构建 CLI?

Go 是一种高度并发的语言,非常适合构建 CLI。Octopus Deploy 的 Go API 客户端也是为了支持 Octopus DeployTerraform Provider 而构建的。它已经通过了考验。

最后,通过基于 C++的多平台支持,Go 有助于减少运行时内存占用。这提供了较小的可执行文件大小和对目标环境的少量要求——这结合起来支持客户希望通过curl将 CLI 与 Bash(例如)一起使用的场景。

我们需要你的帮助

我们希望构建一个客户喜欢使用的 CLI,因此我们非常感谢您的反馈。我们希望听到您希望我们支持的场景。

提供反馈

您可以通过 GitHub 上的 cli 资源库来跟踪我们构建 CLI 的进度。请随时关注或启动存储库进行更新。我们也开始向流行的软件包库如 Chocolatey 和 Homebrew 发布可分发的版本。

愉快的部署!

在 AWS - Octopus Deploy 中构建 Octopus 云

原文:https://octopus.com/blog/building-the-octopus-cloud-in-aws

Octopus Deploy in the clouds illustration

介绍

在我们开始构建章鱼云平台之前,我们讨论了它应该托管在哪里。我们最终归结为两个选择:亚马逊网络服务(AWS)或微软 Azure。

从那时起,我们选择了 AWS,主要是因为团队中的大多数人都有使用 AWS 服务的经验,而不是因为两家云提供商之间有任何具体的技术差异。

兴奋地开始,我们在 Windows 上使用 Docker 和 Kubernetes 一头扎进了这个项目。这是大约八个月前的事了,虽然我们取得了一些成功,但感觉像是一场持续的艰苦战斗。当时 Windows 上的 Docker 和 Kubernetes 感觉没有 Linux 上的 Docker 和 Kubernetes 成熟,我们也不能用 Linux,因为 Octopus 不是用。网络核心...还没有。

我们不得不做出一个艰难的决定:在花费了大量时间和精力之后继续使用 Docker 和 Kubernetes,或者转向一种更传统的屡试不爽的方法,在这种方法中,每个客户都有自己的 EC2 实例。我们决定暂时使用 EC2 实例。这篇博文中的推理,理解 MVP(最小可行产品),以及为什么我更喜欢 Henrik Kniberg 的《最早可测试/可用/可爱》,对我们的思维有很大的影响。我们意识到从一开始就试图开发完美的产品对自己要求太高了。

建立工作关系网

创建 AWS 帐户后的首要任务之一是删除默认子网和默认 VPC,这样我们就可以创建自己的更大的网络掩码来容纳更多的客户。我们的想法是扩大规模,考虑到这一点,我们将生产 VPC 网络掩码设置为/16,将每个子网的网络掩码设置为/20。允许我们在每个 VPC 中容纳多达 16 个子网,每个子网容纳 4,091 个 IP 地址,总共提供 65,456 个 IP 地址。

我们的 VPC 和子网的设计基于 AWS 提供的场景 2:具有公共和私有子网(NAT)的 VPC示例,如下所示:

Octopus Cloud Production VPC

非高可用性 Octopus 云服务器分布在我们的公共子网中,并直接暴露于互联网。未来的高可用性 Octopus 云服务器将分布在我们的私有子网中,并通过应用负载平衡器暴露于互联网。

在撰写本文时,Octopus 云服务器可以在俄勒冈州(us-west-2)地区的 t2.medium EC2 实例上提供,但没有高可用性。然而,在不太遥远的将来,悉尼(ap-southeast-2)、伦敦(eu-west-2)和法兰克福(eu-central-1)地区可能会成为可用的,以及将 Octopus 云服务器从一个地区迁移到另一个地区并增加高可用性的能力。

为了创建我们的 VPC 和子网,我们使用了 Terraform ,因为它的配置语法。

Terraform 使用 HashiCorp 配置语言(HCL ),这意味着在人类可读和可编辑以及机器友好之间取得平衡。

来源:地形配置语法

例如,下面是一个为 VPC 配置公共和私有子网的模板。

provider "aws" {
  region  = "us-west-2"
}

resource "aws_vpc" "production" {
  cidr_block           = "10.10.0.0/16"
  instance_tenancy     = "default"
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags {
    Name = "Production VPC"
  }
}

resource "aws_internet_gateway" "production" {
  vpc_id = "${aws_vpc.production.id}"

  tags {
    Name = "Production Internet Gateway"
  }
}

resource "aws_route_table" "public" {
  vpc_id = "${aws_vpc.production.id}"

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = "${aws_internet_gateway.production.id}"
  }

  tags {
    Name = "Production Subnet (Public A/B/C)"
  }
}

resource "aws_eip" "public_a" {
  vpc = true
}

resource "aws_subnet" "public_a" {
  vpc_id                  = "${aws_vpc.production.id}"
  cidr_block              = "10.10.0.0/20"
  availability_zone       = "us-west-2a"
  map_public_ip_on_launch = true

  tags {
    Name = "Production Subnet (Public A)"
  }
}

resource "aws_route_table_association" "public_a" {
  subnet_id      = "${aws_subnet.public_a.id}"
  route_table_id = "${aws_route_table.public.id}"
}

resource "aws_nat_gateway" "public_a" {
  allocation_id = "${aws_eip.public_a.id}"
  subnet_id     = "${aws_subnet.public_a.id}"

  tags {
    Name = "Production NAT Gateway (Public A)"
  }
}

resource "aws_subnet" "private_a" {
  vpc_id            = "${aws_vpc.production.id}"
  cidr_block        = "10.10.16.0/20"
  availability_zone = "us-west-2a"

  tags {
    Name = "Production Subnet (Private A)"
  }
}

resource "aws_route_table" "private_a" {
  vpc_id = "${aws_vpc.production.id}"

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = "${aws_nat_gateway.public_a.id}"
  }

  tags {
    Name = "Production Subnet (Private A)"
  }
}

resource "aws_route_table_association" "private_a" {
  subnet_id      = "${aws_subnet.private_a.id}"
  route_table_id = "${aws_route_table.private_a.id}"
} 

如果你想将上面的 Terraform 模板与类似的 CloudFormation 模板(YAML)进行比较,AWS 在这里提供了一个例子。

安全性

对于章鱼云来说,安全性是最重要的。

我们聘请失眠安全对章鱼云进行渗透和漏洞测试,结果非常积极,没有检测到重大或中度风险。

我们通过允许入站和出站连接所需的最少协议和端口,为生产 VPC 实施了严格的网络访问控制列表(ACL)。对于附属于每个 Octopus 云服务器和防火墙的安全组(SG)也是如此。

我们还禁用了不安全和脆弱的 TLS 密码套件 Octopus 云服务器仅支持 TLS 1.2 和 TLS 1.1,这意味着 Windows XP 等旧客户端和旧浏览器无法访问 UI。

EC2 实例

之前我提到过,我们目前在 t2.medium EC2 实例上运行 Octopus 云服务器,我认为这对大多数人来说会是一个惊喜。一个 t2.medium EC2 实例有 2 个 vCPUs 和 4GB RAM,并且:

与传统的 EC2 实例不同,T2 实例提供了基准级别的 CPU 性能,并且能够在该基准级别之上爆发。基准性能和突发能力由 CPU 信用控制。

来源: AWS 用户指南- T2 实例

t2 .培养基的基线水平是 40%。当 CPU 利用率低于基线水平时,将累积 CPU 信用,当 CPU 利用率高于基线水平时,将消耗 CPU 信用。如果你的 Octopus 云服务器消耗了所有的信用,CPU 将无法超过其基线水平。

在我们的封闭 alpha、封闭 beta 和可用性会议期间,我们发现,在空闲时,Octopus 云服务器使用大约 1%的 CPU 在执行同步部署时,CPU 利用率通常低于基线水平。

在我们的负载测试中,让我们措手不及的是,我们发现:

T2 标准实例可以获得启动信用的次数是有限制的。默认限制是每个帐户、每个地区、每个连续 24 小时内启动或开始所有 T2 标准实例的次数总和为 100 次。

来源: AWS 用户指南- T2 标准

为了解决这个问题,我们在 EC2 实例被提供后查询 CloudWatch,以查看它是在有信用还是没有信用的情况下启动的。如果它启动时没有配额,我们会在 EC2 实例上临时启用 T2 无限制,直到它完成配置。

下一步是什么?

允许客户在俄勒冈州(us-west-2)以外的 AWS 地区提供他们的 Octopus 云服务器是我们许多客户的要求。我们根据以下因素调查了哪些地区最有意义:

  • 冗余的可用区域的数量,
  • 最低的性能延迟。
  • 什么能给我们最好的全球覆盖。

我们选择了悉尼(ap-southeast-2)、伦敦(eu-west-2)和法兰克福(eu-central-1)地区,它们将在我们推出章鱼云后不久可用。

在支持其他地区之后,我们还将提供在这些地区之间迁移 Octopus 云服务器的能力。

高可用性也是我们优先考虑的问题,可能会在我们支持悉尼和伦敦地区后推出。

我们在封闭测试版和封闭测试版中注意到,有一小部分客户在创建他们的章鱼云服务器时使用了像testdemosandbox这样的词作为他们子域的一部分。这让我们想到了客户想要更改他们的子域的场景,这个特性肯定会在不久的将来变得可用。

我们还没有放弃 Docker 和 Kubernetes。我们相信,这些技术将使我们能够更快地供应 Octopus 云服务器,增加额外的安全层,并帮助我们充分利用共同租赁来节省成本。

在自动化数据库部署过程中建立信任- Octopus Deploy

原文:https://octopus.com/blog/building-trust-in-automated-db-deployments

Building trust in an automated database deployment process

当我开始自动化数据库部署时,我担心工具会在不应该的时候删除列或表。我不禁总在想,我是不是把一切都配置正确了?核心问题是我没有在我的数据库部署过程中包括建立信任的必要步骤。在这篇博文中,我介绍了一些我用来帮助建立信任的技术和配置,这样我就可以放心地自动部署数据库。

假设第一天的信任为零

本文重点介绍如何使用数据库工具和 Octopus Deploy 在部署管道中建立信任。

请注意,诸如验证数据库更改在语法上是否正确,或者数据库更改是否通过了静态分析之类的任务应该在 CI 或构建服务器上完成,这超出了本文的范围。

在设计和实现自动化数据库部署过程时,我从这种心态开始:

我需要向我自己和我团队中的每个人证明,部署会导致数据丢失或生产中断的是 而不是

在本文中,我从头开始创建了一个自动化的数据库部署过程。每一节都建立在前一节的基础上,利用 Octopus Deploy 中的一个新特性。我意识到很容易忘记每个特性,所以为了使事情变得简单,下面是本文将使用的特性列表:

  • 手动干预暂停部署,等待某人批准或拒绝变更。
  • 工件存储增量脚本,供 DBA 在手动干预期间审查。
  • 运行条件始终将部署结果通知开发团队,并在生产失败时呼叫数据库管理员。
  • 自定义日志级别由运行脚本步骤使用,用于通知数据库管理员脚本中的潜在问题。
  • 电子邮件和其他通知选项让批准者了解待定的变更。
  • 引导故障模式暂停故障部署,让人们调查故障,并可能再次尝试。
  • 输出变量以跳过部署中不必要的步骤。例如,如果工具报告没有数据库变更,那么运行部署数据库变更步骤就没有意义。
  • 审计日志了解谁启动了部署、谁批准了部署,以及部署是何时发生的。
  • 用户和团队仅允许 DBA 启动生产部署,但仍允许开发人员部署到较低的环境中。

基本部署流程

如果我们完全信任这个过程,我们将只需要部署步骤。让我们从那里开始。

本文使用 DbUp ,一个跨平台的数据库部署工具来处理数据库部署。本文的核心概念适用于任何数据库服务器的任何数据库部署工具。

现在,这个过程将获取包中的脚本,并在数据库上运行它们。如果能知道这些脚本是什么就好了。让我们添加一个生成增量脚本或报告的步骤。

Create Delta Report步骤在脚本末尾有这样一行,它将脚本作为工件上传到 Octopus Deploy:

New-OctopusArtifact -Path "$generatedReport" -Name "$environmentName.UpgradeReport.html" 

创建增量报告的方法将根据您的数据库部署工具而有所不同。请查阅该工具的文档来了解如何操作。在某些情况下,我们的文档提供了您可以利用的示例。

在部署期间,该工件将出现在屏幕上的两个地方。

点击其中任何一个都会下载神器。

部署屏幕还显示谁启动了部署以及何时启动的。该审计日志和工件都将保留到时间结束,除非您已经配置了保留策略,在这种情况下,它们将只保留到部署被保留策略删除。

现在我们有所进展;我们有一份 delta 报告,只要该版本存在,它就会被保留,供任何人(包括审计员)下载和审查。

批准

但是如果 delta 报告显示一个带有类似于drop tabledrop column的命令的脚本呢?可以吗?也许是,也许不是。断然拒绝剧本是没有意义的。更有意义的做法是暂停部署,检查脚本,如果有些事情看起来不对劲,找到创建脚本的人并与他们讨论。为此,我们可以添加手动干预。

当创建手动干预时,一个有趣的想法出现了。必须选择一个团队。我知道你在想什么。“团队应该是 DBA!”这一步将在所有环境中运行。DBA 应该在什么时候审查变更?他们正忙于保持数据库服务器正常运行。不得不审查每个部署的每个增量脚本将是一项全职工作。

将开发人员和数据库管理员分成两个团队是行不通的,因为这意味着开发人员或数据库管理员可以批准部署。这对于DevelopmentTest环境来说是可以接受的,但对于Production来说却不是:

在这个例子中,DBA 批准StagingProduction的增量脚本是有意义的。同时,开发人员可以批准DevelopmentTest的增量脚本。

我喜欢在所有人工干预中加入章鱼经理团队。这样,如果所有数据库管理员都不可用,他们可以在紧急情况下承担责任。

最终的部署过程将有两个手动干预步骤。

根据我的经验,在 delta 脚本运行之前暂停并检查它的能力对建立信任大有帮助。如果批准者看到他们不同意的东西,他们可以中止部署。更好的是,负责批准的人在审计日志中。我们可以看到我开始了部署并批准了它(这在演示/测试环境之外是一个大禁忌)。

通知

DBA 很可能不会每天都整天看着 Octopus 部署。他们需要被告知他们所负责的审批。但是他们在批准部署后应该做什么呢?看完?那可能要花很长时间。这似乎不是对他们时间的有效利用。如果部署成功,他们可能不需要做任何其他事情。部署失败是一件大事;应该通知他们。

对于这个例子,我将电子邮件通知直接包含在流程中。您还可以利用 Octopus Deploy 中的订阅特性。

电子邮件表单中的初始字段相当简单。主题和正文是导致我停顿的原因。数据库管理员可能一天会收到几十封这样的电子邮件。他们应该很容易批准部署,所以主题行应该包括关于部署的详细信息。如果有些事情看起来不对劲,他们可以将电子邮件转发给您,并提供他们的反馈。正文应包含一个深层链接,供审批者点击。

重新输入截图中的内容并不好玩。下面是主题和正文的文本。

Subject:
Pending approval for #{Octopus.Project.Name} #{Octopus.Release.Number} to #{Octopus.Environment.Name}

Body:
Please approve the release #{Octopus.Project.Name} #{Octopus.Release.Number} to #{Octopus.Environment.Name} here:

#{Octopus.Web.DeploymentLink} 

失败通知有点不同。电子邮件的优先级需要从正常更改为高,并且运行条件应该配置为仅在失败时运行步骤。

这个示例使用电子邮件通知,但是您也可以利用其他工具,例如 Slack、Teams、VictorOps 等等。

最后,负责应用程序的团队应该总是被告知部署的状态。如果部署团队使用 Slack,我们可以在他们的 Slack 通道中发送类似的消息。这个例子使用了库中的 Slack - Send 简单通知步骤模板。

现在我们向关键人物发出通知。典型地,关键人物也是忙碌的人;如果您需要拨入通知来降低信噪比,请不要感到惊讶。

帮助审批者

随着时间的推移,数据库管理员必须批准的部署数量将呈指数级增长。这是自动化的本质。当团队对工具感到满意并信任流程时,他们自然会进行更多的部署。

部署多了是把双刃剑;不利的一面是审查数据库变更的时间变少了。DBA 或开发人员可能会错过导致重大损失的模式更改,但是这个过程应该对他们有所帮助。幸运的是,Octopus Deploy 是一个 API 优先的应用程序,使用该 API 可以下载 delta 报告或 SQL 工件来查找任何潜在的破坏性语句:

$CommandsToLookFor = "Drop Table,Drop Column,Alter Table,Create Table,Create View"

$OctopusURL = ## YOUR URL
$APIKey = ## YOUR API KEY
$FileExtension = "SQL"
$SpaceId = $OctopusParameters["Octopus.Space.Id"]
$DeploymentId = $OctopusParameters["Octopus.Deployment.Id"]

Write-Host "Commands to look for: $CommandsToLookFor"
Write-Host "Octopus Url: $OctopusUrl"
Write-Host "SpaceId: $SpaceId"
Write-Host "DeploymentId: $DeploymentId"
Write-Host "File Extension: $FileExtension"

$header = @{ "X-Octopus-ApiKey" = $APIKey }

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$artifactUrl = "$OctopusUrl/api/$SpaceId/artifacts?take=2147483647&regarding=$DeploymentId&order=asc"
Write-Host "Getting the artifacts from $artifactUrl"
$artifactResponse = Invoke-RestMethod $artifactUrl -Headers $header
$fileListToCheck = @()

foreach ($artifact in $artifactResponse.Items)
{
    $fileName = $artifact.Filename

    if ($fileName.EndsWith($FileExtension))
    {       
        Write-Host "The artifact is a SQL Script, downloading"
        $artifactId = $artifact.Id
        $artifactContentUrl = "$OctopusUrl/api/$SpaceId/artifacts/$artifactId/content"
        Write-Host "Pulling the content from $artifactContentUrl"
        $fileContent += Invoke-RestMethod $artifactContentUrl -Headers $header
        Write-Host "Finished downloading the file $fileName"

        $sqlFileToCheck = @{
            FileName = $fileName;
            Content = $fileContent;
        }

        $fileListToCheck += $sqlFileToCheck                
    }    
}

if ($fileListToCheck.Length -le 0)
{
    Write-Highlight "No sql files were found"

    Exit 0
}

Write-Host "Looping through all commands"
$commandsToCheck = $CommandsToLookFor -split ","
foreach ($sqlFile in $fileListToCheck)
{       
    foreach ($command in $commandsToCheck)
    {
        Write-Host "Checking $($sqlFile.FileName) for command $command"
        $foundCommand = $sqlFile.Content -match "$command"

        if ($foundCommand)
        {
            Write-Highlight "$($sqlFile.FileName) has the command '$command'"            
        }
    }
} 

当找到某些命令时,脚本将利用 Octopus Deploy 提供的Write-Highlight 日志命令。该消息将出现在部署日志中。

希望看到这些消息能让审批者轻松一点。

处理错误和故障

错误和失败将会发生。其中许多是由于 Octopus Deploy 无法控制的情况而发生的,例如,网络故障、SQL Server 重启、不正确的权限和帐户丢失是最常见的情况。问题解决后,最好再次尝试失败的步骤,避免不必要的返工。如果 DBA 已经批准了一个脚本,他们不需要再次批准它。

引导性故障就是为这样的场景设计的。可以为每个项目或每个环境启用引导式故障。对于本例,它是在项目级别启用的:

就像手动干预一样,当检测到故障时,部署将暂停。Octopus Deploy 提供了选择FailIgnoreRetry的步骤。

通过这个小小的改变,我们给了用户优雅地处理失败的机会。

关注点分离

关注点分离是一种常见的做法。修改代码的人不应该是批准它或者将它部署到Production的人。请注意,开发人员应该能够部署到DevelopmentTest,在 Octopus Deploy 中,可以配置团队来支持这一点。

在 Octopus Deploy 中有五个角色可以帮助实现这一点。

  • 部署创建者:被分配到这个角色的人可以开始部署。在本例中,DBA 将被分配到Production的这个角色。
  • 项目参与者:被分配到这个角色的人可以编辑项目,但是不能创建或部署发布。在本例中,开发人员将被分配到Production的这个角色。
  • 项目部署者:被分配到这个角色的人可以做项目贡献者可以做的任何事情,包括部署一个发布。在这个例子中,开发人员将被分配到较低环境的这个角色。
  • 项目查看者:分配给这个角色的人对项目有一个只读视图。非常适合 QA、企业主和其他没有权限更改流程但仍想查看状态的人。

开发团队用户角色将包括:

DBA 的权限包括:

调整数据库管理员在流程中的角色

有了这些权限,DBA 在这个过程中的角色仍然有点奇怪。是他们触发了对Production的部署。他们是否也必须手动批准部署?乍一看,很有道理。他们应该批准一个Production版本。

真正的问题是:在Production部署期间,开发团队多久会因为 DBA 发现他们不喜欢的东西而改变一些东西?

答案大概是永远不会。当中断窗口已经传达给用户和客户时,Production部署对于 DBA 来说已经太晚了,无法表达他们的担忧。除非数据库管理员能够 100%确定某个问题将会发生,否则生产部署将会继续进行。

对于 DBA 来说,部署已经太晚了。更好的方法是在Staging部署期间为Production创建一个增量脚本。这样,DBA 就可以同时批准这两个项目。

当 DBA 创建Production部署时,他们还可以查看在Staging部署期间生成的增量报告。额外的好处是数据库管理员现在可以安排部署,他们不必在线。

也就是说,不要期望 DBA 在开始时安排部署并且不在线。在他们信任团队、信任过程、信任自己理解工具之前,需要进行相当多的部署。

未来迭代

所有的人工批准和通知都会产生噪音。刚开始时,这种噪音是好的。团队仍在学习工具。最终,团队将会对工具感到满意,而手工批准和通知将会困扰并最终阻碍团队。

取消这些通知和批准是不明智的。在拉取请求过程中可能会出现一些破坏性的东西。流程有一个自动发现潜在问题的步骤Check SQL Artifacts for Schema Change Commands。可以修改该脚本来设置输出变量。如果发现潜在的破坏性命令,则部署必须经过批准过程:

$ApprovalRequired = $false
Write-Host "Looping through all commands"
$commandListToCheck = $CommandsToLookFor -split ","
foreach ($sqlFile in $fileListToCheck)
{       
    foreach ($command in $commandListToCheck)
    {
        Write-Host "Checking $($sqlFile.FileName) for command $command"
        $foundCommand = $sqlFile.Content -match "$command"

        if ($foundCommand)
        {
            Write-Highlight "$($sqlFile.FileName) has the command '$command'"
            $ApprovalRequired = $true
        }
    }
}

if ($approvalRequired -eq $false)
{
    Write-Highlight "All scripts look good"
}
else
{
    Write-Highlight "One of the specific commands we look for has been found"
}

Set-OctopusVariable -name "DBAApprovalRequired" -value $ApprovalRequired 

输出变量有点冗长。为了使它们更容易使用,为结果设置另一个变量。

在通知和手动干预步骤中,变量运行条件可以设置为:

#{if Octopus.Deployment.Error == ""}#{Project.DBA.ApprovalRequired}#{/if} 

防止坏演员

这个过程中的一个缺陷是,它假设开发人员不会进入部署过程并禁用或删除手动干预步骤。信任,但验证是我们推荐的方法。

首先,我们正在开发一个与 Git 集成的代码为的流程。如果它与 Git 集成,那么它可以通过一个拉请求过程。

本视频还概述了自动审计项目的流程。

最后,您可以配置订阅以便在发生更改时通知管理员。

结论

在文章的开头,我设定了这个目标:

我需要向我自己和我团队中的每个人证明,特定的部署将 而不是 导致数据丢失或生产中断。

本文中创建的流程通过执行以下操作来帮助实现这一目标:

  1. 它生成一个 delta 报告,并将其作为 Octopus 工件发布,供任何人查看。
  2. 跟踪谁触发了部署,谁批准了部署,以及一切发生的时间。
  3. 一个脚本将下载增量报告并寻找潜在的问题命令。
  4. 除了Production之外,部署将在每个环境中暂停,以便批准者审查和批准增量报告。
  5. 通知会在流程的不同时间发送给关键人员。
  6. 开发人员有权部署到DevelopmentTestStaging,但是数据库管理员必须批准部署到Staging
  7. Staging部署期间,DBA 批准并审查StagingProduction的变更。这给了他们更多的时间来回顾变化,并在Production推动之前提出任何问题。

我在以前的公司工作时也实现了类似的东西。这比之前的流程好多了,之前的流程是一封电子邮件,说“请运行这些 SQL 脚本。”我对即将进行的数据库更改充满信心,最终,DBA 也是如此。他们会说“我会把它安排在晚上 7 点,我不会在线,但如果有任何问题,它会传呼我,我会跳上去。”

我没有天真到相信这将解决所有潜在的信任问题。这个过程并不完美;例如,它没有与生产变更请求系统集成,也没有与问题跟踪器集成。虽然很重要,但我觉得这些东西把本文的水搅浑了。本文的目标是为您提供一些技巧和技术,让您有信心开始自己的 POC 或自动化数据库部署试点。

本文中的部署过程可以在我们的示例实例中找到。

下次再见,愉快的部署!

使用蛋糕构建脚本为您的。NET 核心 web 应用程序- Octopus 部署

原文:https://octopus.com/blog/cake-build-scripts

Illustration showing building a cake w/ code, markdown and images in a mixing bowl

Cake 是一个为。NET 开发人员使用 C#领域特定语言(DSL)编写构建过程的脚本。在本文中,我们将通过一个具体的工作示例来探索 Cake 的好处及其主要特性,以实现灵活、可维护、自动化的构建过程。

您可能在过去听说过 Make 和 makefiles,但是如果您没有听说过也不用担心,因为您就要知道了。Make 是一个构建自动化工具,而 makefile 是一个包含 Make 构建应用程序所需的指令的文件。它还可以用来运行相关的任务,比如清理构建目录。

随着开发人员希望使用他们喜欢的语言来定义他们的构建过程,这些年来出现了许多 Make 的变体。Rake (Ruby Make)和 Ruby on Rails 一起变得非常流行。

在。NET 世界中,根据您选择的语言,您有几个选项。有PSake(PowerShell)山寨 (F#),还有蛋糕 (C#)。我们今天主要讨论蛋糕,但是如果你想用 PowerShell 或者 F#编写脚本,可以看看其他的。

蛋糕的好处

使用 Cake 的主要好处之一是您的构建脚本将使用 C# DSL 编写。您的团队可以使用他们最熟悉的语言来自动化他们的构建,而不是使用 XML、JSON 或 YAML。

另一个不容忽视的好处是能够在本地和 CI 服务器上执行 Cake 脚本。想一想。除非您的构建代理存在任何环境问题,否则相同的 Cake 脚本将在您的机器、团队成员的机器和 CI 服务器上运行。您的 CI 项目配置可以简化为:

  1. 从源代码管理中签出。
  2. 运行这个蛋糕脚本。

说到源代码控制,您的 Cake 脚本位于您的项目存储库中。您的构建过程是版本化的,并且可以使用与您的应用程序代码相同的代码评审过程进行更改和评审。提交您的脚本还将您的应用程序代码与构建过程结合起来,因此您不必在 CI 服务器中单独更改构建步骤。应用程序和构建脚本的这种链接是 YAML 成为构建管道建模的流行选择的原因之一。Cake 还有一个额外的好处,就是可以在您的机器上运行这些构建步骤。

Cake 通过社区贡献的插件内置了对许多工具(包括 Octopus Deploy)和许多其他工具的支持。您用于构建的工具很有可能是受支持的,如果不支持,您可以创建一个外接程序在脚本中使用。

蛋糕脚本示例

我们的示例项目 OctoPetShop 有一个完整的蛋糕脚本示例,我们将在本文中探讨。该链接指向撰写本文时使用的版本。如果您想查看最新版本,您可以查看此链接

Cake 脚本的第一部分导入您在构建过程中使用的任何外部工具、插件或模块。在我们的例子中,我们添加了一个#tool 指令,并指定我们需要 NuGet 的 OctopusTools 版本 6.13.1。然后我们为Cake.Common.Tools.OctopusDeploy名称空间添加了一个using语句:

#tool "nuget:?package=OctopusTools&version=6.13.1"

using Cake.Common.Tools.OctopusDeploy; 

到目前为止,我们在这个脚本中只使用了 Octopus Deploy 工具,但是 Cake 内置了对许多工具的支持,包括 NuGet、测试框架等等。

参数和全局变量

在下一节中,我们将设置一些在脚本执行期间使用的参数和变量。

有了Argument别名,Cake 会给你一个从命令行提供的参数值或者你指定的默认值。我们有要运行的目标任务的参数、要使用的构建配置、用于版本控制的版本和预发布标签,以及与 Octopus 服务器集成的信息。

之后,我们有一个简单的类来收集关于我们项目的信息和一些变量,我们将在Setup中填充它们:

var target = Argument("target", "Default");
var configuration = Argument("configuration", "Release");
var version = Argument("packageVersion", "0.0.1");
var prerelease = Argument("prerelease", "");
var databaseRuntime = Argument("databaseRuntime", "win-x64");
var octopusServer = Argument("octopusServer", "https://your.octopus.server");
var octopusApiKey = Argument("octopusApiKey", "hey, don't commit your API key");

class ProjectInformation
{
    public string Name { get; set; }
    public string FullPath { get; set; }
    public string Runtime { get; set; }
    public bool IsTestProject { get; set; }
}

string packageVersion;
List<ProjectInformation> projects; 

设置

让我们来看看那个Setup方法。

我们检查是否在本地运行构建,如果是,并且没有提供预发布标签,我们将预发布标签设置为"-local。"

然后我们设置我们的全局变量packageVersionprojects:

Setup(context =>
{
    if (BuildSystem.IsLocalBuild && string.IsNullOrEmpty(prerelease))
    {
        prerelease = "-local";
    }

    packageVersion = $"{version}{prerelease}";

    projects = GetFiles("./**/*.csproj").Select(p => new ProjectInformation
    {
        Name = p.GetFilenameWithoutExtension().ToString(),
        FullPath = p.GetDirectory().FullPath,
        Runtime = p.GetFilenameWithoutExtension().ToString() == "OctopusSamples.OctoPetShop.Database" ? databaseRuntime : null,
        IsTestProject = p.GetFilenameWithoutExtension().ToString().EndsWith(".Tests")
    }).ToList();

    Information("Building OctoPetShop v{0}", packageVersion);
}); 

任务

任务定义您的构建过程。它们类似于传统持续集成(CI)项目或管道中的构建步骤。

让我们来看看我们的第一个任务,Clean。我们用Task方法定义它,并提供一个名称。然后我们使用Does方法来定义这个任务做什么。在这种情况下,我们清理我们的发布和打包目录,然后为我们的项目调用DotNetCoreClean:

Task("Clean")
    .Does(() =>
        {
            CleanDirectory("publish");
            CleanDirectory("package");

            var cleanSettings = new DotNetCoreCleanSettings { Configuration = configuration };

            foreach(var project in projects)
            {
                DotNetCoreClean(project.FullPath, cleanSettings);
            }
        }); 

属国

让我们直接跳到Build任务。看起来和Clean差不多,但是多了一个新棋子:IsDependentOn。这个方法让我们在任务之间创建一个依赖链。当我们调用Build任务时,Cake 将确保CleanRestore都被首先调用:

Task("Build")
    .IsDependentOn("Clean")
    .IsDependentOn("Restore")
    .Does(() =>
    {
        foreach(var project in projects)
        {
            var buildSettings = new DotNetCoreBuildSettings()
                {
                    Configuration = configuration,
                    NoRestore = true
                };

            if (!string.IsNullOrEmpty(project.Runtime))
            {
                buildSettings.Runtime = project.Runtime;
            }

            DotNetCoreBuild(project.FullPath, buildSettings);
        }
    }); 

我们还有另一个名为RunUnitTests的任务,它依赖于Build。运行RunUnitTests任务将触发CleanRestoreBuild:

Task("RunUnitTests")
    .IsDependentOn("Build")
    .Does(() =>
    {
        foreach(var project in projects.Where(p => p.IsTestProject))
        {
            DotNetCoreTest(project.FullPath, new DotNetCoreTestSettings { Configuration = configuration });
        }
    }); 

如果您继续通读脚本,您将看到发布应用程序、使用 Octopus 工具打包应用程序、将包推送到 Octopus 以及使用 Octopus 创建和部署发行版的任务。

最后,我们在剧本的结尾有这些台词。这创建了一个Default任务,它将运行RunUnitTests任务及其依赖项。

调用RunTarget方法的最后一行代码启动了构建过程。这里我们传入由用户、CI 服务器提供的全局变量target,或者默认为名为Default的任务:

Task("Default")
    .IsDependentOn("RunUnitTests");

RunTarget(target); 

在本地执行蛋糕

Cake 提供了一个 PowerShell 或 Shell 引导脚本,您可以使用它来执行您的 Cake 脚本:

.\build.ps1 -Target Pack -ScriptArgs '--packageVersion=1.2.3 --prerelease=-dev' 

就是这样!脚本启动了,在短暂的等待之后,我们在本地构建了 NuGet 包,并得到了这个方便的报告:

Task                          Duration
--------------------------------------------------
Setup                         00:00:00.1432566
Clean                         00:00:05.4768163
Restore                       00:00:06.1162465
Build                         00:00:09.6114684
RunUnitTests                  00:00:04.3110846
Publish                       00:00:06.9924016
Pack                          00:00:12.7274733
--------------------------------------------------
Total:                        00:00:45.3787473 

我们可以将这些包直接上传到我们的 Octopus 服务器,或者提交我们的更改,因为我们知道我们的构建是有效的。

还有针对 Visual Studio 和 Visual Studio 代码的扩展,这些扩展提供了智能感知、语法突出显示以及从 ide 运行脚本的能力。

从 CI 服务器执行 Cake

现在我们有了本地运行的 Cake 脚本,我们可以把它带到 CI 服务器上。在这个例子中,我们使用 Azure DevOps,它有一个运行 Cake 脚本的扩展。

下面是创建新的 ASP.NET 核心构建管道时生成的步骤片段。这与我们在蛋糕脚本中创建的步骤非常相似。

Screenshot showing an Azure DevOps Build Pipeline with standard steps configured

在安装了蛋糕扩展后,我们可以在构建中添加蛋糕步骤,在这种情况下,这是我们需要的唯一步骤。我们提供了蛋糕脚本的路径,我们想要运行的目标,以及一些版本号和章鱼服务器信息的附加参数。

Screenshot showing an Azure DevOps Build Pipeline with a Cake step configured

运行构建后,我们不仅可以在日志中获得蛋糕脚本的完整输出,还可以像在本地运行时一样获得任务摘要:

Task                          Duration
--------------------------------------------------
Setup                         00:00:00.0434025
Clean                         00:00:18.1795863
Restore                       00:01:07.9769173
Build                         00:00:36.6475174
RunUnitTests                  00:00:21.3958462
Publish                       00:00:06.2555954
Pack                          00:00:12.0804766
PushPackages                  00:00:16.0161892
CreateRelease                 00:00:05.4893287
DeployRelease                 00:02:09.6799635
--------------------------------------------------
Total:                        00:05:13.7648231 

结论

像蛋糕这样的构建自动化框架给你和你的团队带来了很多好处。有了 Cake,您可以使用熟悉的 C# DSL 编写构建脚本。它使您能够将开发过程应用到您的构建中。您可以在本地和配置项服务器上运行相同的步骤。而且,Cake 广泛的内置工具支持和社区插件应该能够满足大部分(如果不是全部)脚本需求。

卡拉马里:开源触手部署-章鱼部署

原文:https://octopus.com/blog/calamari

我最近写了关于作为 Octopus 3.x 一部分的新部署目标,我解释说我们将把触手变成一个“壳”:

从 3.0 开始,除了支持轮询和监听模式之外,可以把触手看作 SSH 的面向 Windows 的版本。它只知道如何执行 PowerShell 脚本或传输文件。其他的东西——配置转换的东西,IIS 的东西——将是 PowerShell 脚本,Octopus 将确保在部署执行之前存在于 Octopus 服务器上。

这导致了一个合乎逻辑的结论:

既然我们将触手的通信通道与触手的部署引擎分离,我们获得了一个新的可能性:触手在部署期间执行的所有脚本和部署逻辑现在都可以开源

在过去的几个月里,实现这一点一直是我们的主要关注点,并且表现为 Calamari ,一个开源的、约定驱动的部署运行器:

Calamari GitHub repository

从一开始,我们就认为部署管道需要大量的时间投入,并且对团队的运作至关重要。因此,避免供应商锁定非常重要。Octopus 一直建立在开放/标准技术上,如 NuGet 和 PowerShell,以尽量减少您对 Octopus 的依赖——开源 Calamari 和我们在部署期间使用的所有约定是这一目标的自然发展。

Calamari 是一个控制台应用程序,包含许多命令,例如:

Calamari deploy-package --package MyPackage.nupkg --variables Variables.json 

这个包是一个 NuGet 包,变量 JSON 文件如下所示:

{
    "MyVariableA": "My value A",
    "MyVariableB": "My value B"
} 

部署现在是这样工作的:

  1. Octopus 获取包并生成变量文件
  2. 这些被推到触手上
  3. 触手被告知运行一个 PowerShell 脚本,该脚本只调用 Calamari
  4. 卡拉马里负责部署,并终止

现在,Calamari 是开源的,它可能有助于回答您在触手上部署期间发生的任何问题。例如,你有没有想过惯例是按照什么顺序运行的?

Conventions

或者你可能一直想知道触手(现在的卡拉马里)如何调用 PowerShell,并传递变量给它

Calamari 是在 Apache 许可下发布的,我们将继续在公开的环境中开发它。我最喜欢这个架构的一个特点是,你可以派生项目,做出自己的改变,然后告诉你的 Octopus 3.0 服务器使用你自己的 Calamari 包。

使用 Runbooks - Octopus Deploy 计算 DORA 指标

原文:https://octopus.com/blog/calculating-dora-metrics-runbooks

devo PS Research and Assessment(DORA)年度报告收集了全球数千名专业人士的意见,并使用四个关键指标对软件开发团队的表现进行了排名:

  • 部署频率——一个组织成功发布产品的频率。
  • 变更的交付周期——投入生产所需的时间。
  • 变更失败率-导致生产失败的部署的百分比。
  • 恢复服务的时间-组织从生产故障中恢复需要多长时间。

团队面临的挑战是如何计算这些指标。幸运的是,Octopus 捕获了生成这些指标所需的大部分原始信息。

在本文中,您将学习如何查询 Octopus API 来生成带有自定义 runbook 的 DORA 记分卡。

入门指南

这篇文章使用 GitHub Actions 作为 CI 服务器。GitHub Actions 对公共 git 库是免费的,所以你只需要一个 GitHub 账户就可以开始了。

示例 runbook 脚本是针对 Python 3 编写的,可以从 Python 网站下载

示例 runbook 源代码可以在 GitHub 上找到。对脚本的调整和更新可以在 GitHub repo 上找到,所以一定要查看最新版本。

生成构建信息

构建信息为 Octopus 部署或 runbook 中引用的包提供额外的元数据。构建信息包是存储在 Octopus 服务器上的单独的工件,具有与它们所代表的包相同的包 ID 和版本。这使得 Octopus 可以跟踪各种包的元数据,无论是存储在内置提要中还是托管在外部存储库中。

构建信息捕获信息,例如包含在编译的工件中的提交,以及关闭的工作项列表(在 GitHub 中称为问题)。

XO-energy/action-octopus-build-information 动作提供了创建和上传构建信息包的能力。以下步骤显示了一个操作示例:

 - name: Generate Octopus Deploy build information
      uses: xo-energy/action-octopus-build-information@v1.1.2
      with:
        octopus_api_key: ${{ inputs.octopus_api_token }}
        octopus_project: Products Service
        octopus_server: ${{ inputs.octopus_server_url }}
        push_version: 0.1.${{ inputs.run_number }}${{ env.BRANCH_NAME != 'master' && format('-{0}', env.BRANCH_NAME) || ''  }}
        push_package_ids: com.octopus.octopub:products-service
        push_overwrite_mode: OverwriteExisting
        output_path: octopus
        octopus_space: "Octopub"
        octopus_environment: "Development" 

Octopus 只需要推送构建信息包就可以将元数据链接到发行版。当构建信息包 ID 和版本与 Octopus 步骤中使用的包相匹配时,构建信息被链接到发布。

随着提交和工作项目现在与每个 Octopus 版本相关联,下一个任务是确定如何使用这些信息来度量四个关键指标。

解读 DORA 指标

DORA 指标是高层次的,没有定义具体的衡量规则。这是有意义的,因为每个团队和工具链对什么是部署,或者什么是生产失败有稍微不同的解释。

因此,要计算度量标准,您必须首先决定如何用您现有的数据准确地度量它们。

出于本文的目的,DORA 指标计算如下:

  • 部署频率——部署到生产环境的频率。
  • 变更的交付周期——与发布相关的最早提交和部署到生产环境之间的时间。
  • 变更失败率——解决问题的生产环境部署的百分比。
  • 恢复服务的时间-问题打开和关闭之间的时间。

您会注意到,为了方便起见,其中的一些测量已经被简化了。

例如,变更失败率指标在技术上跟踪导致问题的部署,而不是解决问题的部署,正如我们在这里定义的那样。然而,构建信息包所公开的数据使得跟踪给定版本中已解决的问题变得容易,并且我们假设部署解决问题的速度是部署引入问题的速度的良好代理。

此外,恢复服务的时间指标假定所有问题都代表部署到生产环境中的错误或倒退。事实上,问题倾向于跟踪从 bug 到增强的大范围变化。然而,这里给出的解决方案没有进行这种区分。

只要您在问题跟踪平台中创建了所需的自定义字段,并努力填充它们,就一定可以跟踪哪些部署导致了生产问题。还可以区分记录 bug 的问题和代表增强的问题。然而,这种级别的跟踪不会在这篇文章中讨论。

计算指标

用于生成 DORA 指标的完整脚本如下所示:

import sys
from datetime import datetime
from functools import cmp_to_key
from requests.auth import HTTPBasicAuth
from requests import get
import argparse
import pytz

parser = argparse.ArgumentParser(description='Calculate the DORA metrics.')
parser.add_argument('--octopusUrl', dest='octopus_url', action='store', help='The Octopus server URL',
                    required=True)
parser.add_argument('--octopusApiKey', dest='octopus_api_key', action='store', help='The Octopus API key',
                    required=True)
parser.add_argument('--githubUser', dest='github_user', action='store', help='The GitHub username',
                    required=True)
parser.add_argument('--githubToken', dest='github_token', action='store', help='The GitHub token/password',
                    required=True)
parser.add_argument('--octopusSpace', dest='octopus_space', action='store', help='The Octopus space',
                    required=True)
parser.add_argument('--octopusProject', dest='octopus_project', action='store',
                    help='A comma separated list of Octopus projects', required=True)
parser.add_argument('--octopusEnvironment', dest='octopus_environment', action='store', help='The Octopus environment',
                    required=True)

args = parser.parse_args()

headers = {"X-Octopus-ApiKey": args.octopus_api_key}
github_auth = HTTPBasicAuth(args.github_user, args.github_token)

def parse_github_date(date_string):
    if date_string is None:
        return None
    return datetime.strptime(date_string.replace("Z", "+0000"), '%Y-%m-%dT%H:%M:%S%z')

def parse_octopus_date(date_string):
    if date_string is None:
        return None
    return datetime.strptime(date_string[:-3] + date_string[-2:], '%Y-%m-%dT%H:%M:%S.%f%z')

def compare_dates(date1, date2):
    date1_parsed = parse_octopus_date(date1["Created"])
    date2_parsed = parse_octopus_date(date2["Created"])
    if date1_parsed < date2_parsed:
        return -1
    if date1_parsed == date2_parsed:
        return 0
    return 1

def get_space_id(space_name):
    url = args.octopus_url + "/api/spaces?partialName=" + space_name.strip() + "&take=1000"
    response = get(url, headers=headers)
    spaces_json = response.json()

    filtered_items = [a for a in spaces_json["Items"] if a["Name"] == space_name.strip()]

    if len(filtered_items) == 0:
        sys.stderr.write("The space called " + space_name + " could not be found.\n")
        return None

    first_id = filtered_items[0]["Id"]
    return first_id

def get_resource_id(space_id, resource_type, resource_name):
    if space_id is None:
        return None

    url = args.octopus_url + "/api/" + space_id + "/" + resource_type + "?partialName=" \
          + resource_name.strip() + "&take=1000"
    response = get(url, headers=headers)
    json = response.json()

    filtered_items = [a for a in json["Items"] if a["Name"] == resource_name.strip()]
    if len(filtered_items) == 0:
        sys.stderr.write("The resource called " + resource_name + " could not be found in space " + space_id + ".\n")
        return None

    first_id = filtered_items[0]["Id"]
    return first_id

def get_resource(space_id, resource_type, resource_id):
    if space_id is None:
        return None

    url = args.octopus_url + "/api/" + space_id + "/" + resource_type + "/" + resource_id
    response = get(url, headers=headers)
    json = response.json()

    return json

def get_deployments(space_id, environment_id, project_id):
    if space_id is None or environment_id is None or project_id is None:
        return None

    url = args.octopus_url + "/api/" + space_id + "/deployments?environments=" + environment_id + "&take=1000"
    response = get(url, headers=headers)
    json = response.json()

    filtered_items = [a for a in json["Items"] if a["ProjectId"] == project_id]
    if len(filtered_items) == 0:
        sys.stderr.write("The project id " + project_id + " did not have a deployment in " + space_id + ".\n")
        return None

    sorted_list = sorted(filtered_items, key=cmp_to_key(compare_dates), reverse=True)

    return sorted_list

def get_change_lead_time():
    change_lead_times = []
    space_id = get_space_id(args.octopus_space)
    environment_id = get_resource_id(space_id, "environments", args.octopus_environment)
    for project in args.octopus_project.split(","):
        project_id = get_resource_id(space_id, "projects", project)
        deployments = get_deployments(space_id, environment_id, project_id)
        for deployment in deployments:
            earliest_commit = None
            release = get_resource(space_id, "releases", deployment["ReleaseId"])
            for buildInfo in release["BuildInformation"]:
                for commit in buildInfo["Commits"]:
                    api_url = commit["LinkUrl"].replace("github.com", "api.github.com/repos") \
                        .replace("commit", "commits")
                    commit_response = get(api_url, auth=github_auth)
                    date_parsed = parse_github_date(commit_response.json()["commit"]["committer"]["date"])
                    if earliest_commit is None or earliest_commit > date_parsed:
                        earliest_commit = date_parsed
            if earliest_commit is not None:
                change_lead_times.append((parse_octopus_date(deployment["Created"]) - earliest_commit).total_seconds())
    if len(change_lead_times) != 0:
        return sum(change_lead_times) / len(change_lead_times)
    return None

def get_time_to_restore_service():
    restore_service_times = []
    space_id = get_space_id(args.octopus_space)
    environment_id = get_resource_id(space_id, "environments", args.octopus_environment)
    for project in args.octopus_project.split(","):
        project_id = get_resource_id(space_id, "projects", project)
        deployments = get_deployments(space_id, environment_id, project_id)
        for deployment in deployments:
            deployment_date = parse_octopus_date(deployment["Created"])
            release = get_resource(space_id, "releases", deployment["ReleaseId"])
            for buildInfo in release["BuildInformation"]:
                for work_item in buildInfo["WorkItems"]:
                    api_url = work_item["LinkUrl"].replace("github.com", "api.github.com/repos")
                    commit_response = get(api_url, auth=github_auth)
                    created_date = parse_github_date(commit_response.json()["created_at"])
                    if created_date is not None:
                        restore_service_times.append((deployment_date - created_date).total_seconds())
    if len(restore_service_times) != 0:
        return sum(restore_service_times) / len(restore_service_times)
    return None

def get_deployment_frequency():
    deployment_count = 0
    earliest_deployment = None
    latest_deployment = None
    space_id = get_space_id(args.octopus_space)
    environment_id = get_resource_id(space_id, "environments", args.octopus_environment)
    for project in args.octopus_project.split(","):
        project_id = get_resource_id(space_id, "projects", project)
        deployments = get_deployments(space_id, environment_id, project_id)
        deployment_count = deployment_count + len(deployments)
        for deployment in deployments:
            created = parse_octopus_date(deployment["Created"])
            if earliest_deployment is None or earliest_deployment > created:
                earliest_deployment = created
            if latest_deployment is None or latest_deployment < created:
                latest_deployment = created
    if latest_deployment is not None:
        # return average seconds / deployment from the earliest deployment to now
        return (datetime.now(pytz.utc) - earliest_deployment).total_seconds() / deployment_count
        # You could also return the frequency between the first and last deployment
        # return (latest_deployment - earliest_deployment).total_seconds() / deployment_count
    return None

def get_change_failure_rate():
    releases_with_issues = 0
    deployment_count = 0
    space_id = get_space_id(args.octopus_space)
    environment_id = get_resource_id(space_id, "environments", args.octopus_environment)
    for project in args.octopus_project.split(","):
        project_id = get_resource_id(space_id, "projects", project)
        deployments = get_deployments(space_id, environment_id, project_id)
        deployment_count = deployment_count + len(deployments)
        for deployment in deployments:
            release = get_resource(space_id, "releases", deployment["ReleaseId"])
            for buildInfo in release["BuildInformation"]:
                if len(buildInfo["WorkItems"]) != 0:
                    # Note this measurement is not quite correct. Technically, the change failure rate
                    # measures deployments that result in a degraded service. We're measuring
                    # deployments that included fixes. If you made 4 deployments with issues,
                    # and fixed all 4 with a single subsequent deployment, this logic only detects one
                    # "failed" deployment instead of 4.
                    #
                    # To do a true measurement, issues must track the deployments that introduced the issue.
                    # There is no such out of the box field in GitHub actions though, so for simplicity
                    # we assume the rate at which fixes are deployed is a good proxy for measuring the
                    # rate at which bugs are introduced.
                    releases_with_issues = releases_with_issues + 1
    if releases_with_issues != 0 and deployment_count != 0:
        return releases_with_issues / deployment_count
    return None

def get_change_lead_time_summary(lead_time):
    if lead_time is None:
        print("Change lead time: N/A (no deployments or commits)")
    # One hour
    elif lead_time < 60 * 60:
        print("Change lead time: Elite (Average " + str(round(lead_time / 60 / 60, 2))
                         + " hours between commit and deploy)")
    # Every week
    elif lead_time < 60 * 60 * 24 * 7:
        print("Change lead time: High (Average " + str(round(lead_time / 60 / 60 / 24, 2))
                         + " days between commit and deploy)")
    # Every six months
    elif lead_time < 60 * 60 * 24 * 31 * 6:
        print("Change lead time: Medium (Average " + str(round(lead_time / 60 / 60 / 24 / 31, 2))
                         + " months between commit and deploy)")
    # Longer than six months
    else:
        print("Change lead time: Low (Average " + str(round(lead_time / 60 / 60 / 24 / 31, 2))
                         + " months between commit and deploy)")

def get_deployment_frequency_summary(deployment_frequency):
    if deployment_frequency is None:
        print("Deployment frequency: N/A (no deployments found)")
    # Multiple times per day
    elif deployment_frequency < 60 * 60 * 12:
        print("Deployment frequency: Elite (Average " + str(round(deployment_frequency / 60 / 60, 2))
                         + " hours between deployments)")
    # Every month
    elif deployment_frequency < 60 * 60 * 24 * 31:
        print("Deployment frequency: High (Average " + str(round(deployment_frequency / 60 / 60 / 24, 2))
                         + " days between deployments)")
    # Every six months
    elif deployment_frequency < 60 * 60 * 24 * 31 * 6:
        print("Deployment frequency: Medium (Average " + str(round(deployment_frequency / 60 / 60 / 24 / 31, 2))
                         + " months bbetween deployments)")
    # Longer than six months
    else:
        print("Deployment frequency: Low (Average " + str(round(deployment_frequency / 60 / 60 / 24 / 31, 2))
                         + " months between commit and deploy)")

def get_change_failure_rate_summary(failure_percent):
    if failure_percent is None:
        print("Change failure rate: N/A (no issues or deployments found)")
    # 15% or less
    elif failure_percent <= 0.15:
        print("Change failure rate: Elite (" + str(round(failure_percent * 100, 0)) + "%)")
    # Interestingly, everything else is reported as High to Low
    else:
        print("Change failure rate: Low (" + str(round(failure_percent * 100, 0)) + "%)")

def get_time_to_restore_service_summary(restore_time):
    if restore_time is None:
        print("Time to restore service: N/A (no issues or deployments found)")
    # One hour
    elif restore_time < 60 * 60:
        print("Time to restore service: Elite (Average " + str(round(restore_time / 60 / 60, 2))
                         + " hours between issue opened and deployment)")
    # Every month
    elif restore_time < 60 * 60 * 24:
        print("Time to restore service: High (Average " + str(round(restore_time / 60 / 60, 2))
                         + " hours between issue opened and deployment)")
    # Every six months
    elif restore_time < 60 * 60 * 24 * 7:
        print("Time to restore service: Medium (Average " + str(round(restore_time / 60 / 60 / 24, 2))
                         + " hours between issue opened and deployment)")
    # Technically the report says longer than six months is low, but there is no measurement
    # between week and six months, so we'll say longer than a week is low.
    else:
        print("Deployment frequency: Low (Average " + str(round(restore_time / 60 / 60 / 24, 2))
                         + " hours between issue opened and deployment)")

print("DORA stats for project(s) " + args.octopus_project + " in " + args.octopus_environment)
get_change_lead_time_summary(get_change_lead_time())
get_deployment_frequency_summary(get_deployment_frequency())
get_change_failure_rate_summary(get_change_failure_rate())
get_time_to_restore_service_summary(get_time_to_restore_service()) 

让我们分解这段代码,了解它在做什么。

处理参数

您的脚本接受来自命令行参数的参数,使其可以跨多个 Octopus 实例和空间重用。参数由 argparse 模块解析。你可以在 Real Python 关于主题的帖子中找到更多关于使用argparse 的信息:

parser = argparse.ArgumentParser(description='Calculate the DORA metrics.')
parser.add_argument('--octopusUrl', dest='octopus_url', action='store', help='The Octopus server URL',
                    required=True)
parser.add_argument('--octopusApiKey', dest='octopus_api_key', action='store', help='The Octopus API key',
                    required=True)
parser.add_argument('--githubUser', dest='github_user', action='store', help='The GitHub username',
                    required=True)
parser.add_argument('--githubToken', dest='github_token', action='store', help='The GitHub token/password',
                    required=True)
parser.add_argument('--octopusSpace', dest='octopus_space', action='store', help='The Octopus space',
                    required=True)
parser.add_argument('--octopusProject', dest='octopus_project', action='store',
                    help='A comma separated list of Octopus projects', required=True)
parser.add_argument('--octopusEnvironment', dest='octopus_environment', action='store', help='The Octopus environment',
                    required=True)

args = parser.parse_args() 

API 认证

该脚本向 Octopus 和 GitHub APIs 发出许多请求,所有请求都需要认证。

Octopus API 使用X-Octopus-ApiKey头来传递用于认证请求的 API 密钥。你可以在 Octopus 文档中找到更多关于如何创建 API 的信息。

GitHub API 使用标准的 HTTP 基本认证,密码使用个人访问令牌。GitHub 文档提供了创建令牌的细节。

下面的代码捕获包含凭证的对象,这些凭证通过脚本的其余部分随每个 API 请求传递:

headers = {"X-Octopus-ApiKey": args.octopus_api_key}
github_auth = HTTPBasicAuth(args.github_user, args.github_token) 

日期处理

GitHub API 以特定的格式返回日期。parse_github_date函数获取这些日期,并将其转换为 Python datetime 对象:

def parse_github_date(date_string):
    if date_string is None:
        return None
    return datetime.strptime(date_string.replace("Z", "+0000"), '%Y-%m-%dT%H:%M:%S%z') 

Octopus API 以自己特定的格式返回日期。parse_octopus_date函数将 Octopus 日期转换成 Python 日期时间对象。

Octopus API 以 ISO 8601 格式返回日期,类似于2022-01-04T04:23:02.941+00:00。不幸的是, Python 3.6 不支持包含冒号的时区偏移量,所以您需要在解析和比较日期之前去掉冒号:

def parse_octopus_date(date_string):
    if date_string is None:
        return None
    return datetime.strptime(date_string[:-3] + date_string[-2:], '%Y-%m-%dT%H:%M:%S.%f%z') 

compare_dates函数将两个日期作为字符串,将它们解析为 datetime 对象,并返回一个值10-1,指示date1date2相比如何:

def compare_dates(date1, date2):
    date1_parsed = parse_octopus_date(date1["Created"])
    date2_parsed = parse_octopus_date(date2["Created"])
    if date1_parsed < date2_parsed:
        return -1
    if date1_parsed == date2_parsed:
        return 0
    return 1 

查询章鱼资源

这个脚本(以及大多数使用 Octopus API 的脚本)中使用的一个常见模式是查找命名资源的 ID。get_space_id函数获取 Octopus 空间的名称,并查询 API 以返回空间 ID:

def get_space_id(space_name): 

/api/spaces端点返回 Octopus 服务器中定义的空间列表。partialName查询参数将结果限制为名称包含所提供值的空格,而take参数被设置为一个较大的数字,因此您不需要循环任何分页的结果:

 url = args.octopus_url + "/api/spaces?partialName=" + space_name.strip() + "&take=1000" 

对端点发出 GET HTTP 请求,包括 Octopus 身份验证头,JSON 结果被解析到 Python 嵌套字典中:

 response = get(url, headers=headers)
    spaces_json = response.json() 

返回的结果可以匹配名称为或包含所提供的空间名称的任何空间。这意味着如果我们搜索名为MySpace的空间,将返回名为MySpaceMySpaceTwo的空间。

为了确保用正确的名称返回空间的 ID,一个列表理解将返回的空间过滤为与提供的空间名称完全匹配的空间:

 filtered_items = [a for a in spaces_json["Items"] if a["Name"] == space_name.strip()] 

如果没有空格与提供的空格名称匹配,该函数将返回None:

 if len(filtered_items) == 0:
        sys.stderr.write("The space called " + space_name + " could not be found.\n")
        return None 

如果有匹配的空格,则返回 ID:

 first_id = filtered_items[0]["Id"]
    return first_id 

空间是 Octopus 中的顶级资源,而您在该脚本中与之交互的所有其他资源都是空间的子资源。正如您对get_space_id函数所做的一样,get_resource_id函数将一个已命名的 Octopus 资源转换成它的 id。这里唯一的区别是所请求的端点在路径中包含了空间 ID,并且提供了资源类型来构建路径中的第二个元素。否则get_resource_id遵循与get_space_id功能相同的模式:

def get_resource_id(space_id, resource_type, resource_name):
    if space_id is None:
        return None

    url = args.octopus_url + "/api/" + space_id + "/" + resource_type + "?partialName=" \
        + resource_name.strip() + "&take=1000"
    response = get(url, headers=headers)
    json = response.json()

    filtered_items = [a for a in json["Items"] if a["Name"] == resource_name.strip()]
    if len(filtered_items) == 0:
        sys.stderr.write("The resource called " + resource_name + " could not be found in space " + space_id + ".\n")
        return None

    first_id = filtered_items[0]["Id"]
    return first_id 

您需要访问完整的 Octopus 发布资源来检查构建信息元数据。get_resource函数使用上述函数返回的资源 id 从 Octopus API 返回一个完整的资源定义:

def get_resource(space_id, resource_type, resource_id):
    if space_id is None:
        return None

    url = args.octopus_url + "/api/" + space_id + "/" + resource_type + "/" + resource_id
    response = get(url, headers=headers)
    json = response.json()

    return json 

get_deployments函数返回在给定项目的给定环境中执行的部署列表。对部署列表进行排序,以确保最新的部署是列表中的第一项:

def get_deployments(space_id, environment_id, project_id):
    if space_id is None or environment_id is None or project_id is None:
        return None

    url = args.octopus_url + "/api/" + space_id + "/deployments?environments=" + environment_id + "&take=1000"
    response = get(url, headers=headers)
    json = response.json()

    filtered_items = [a for a in json["Items"] if a["ProjectId"] == project_id]
    if len(filtered_items) == 0:
        sys.stderr.write("The project id " + project_id + " did not have a deployment in " + space_id + ".\n")
        return None

    sorted_list = sorted(filtered_items, key=cmp_to_key(compare_dates), reverse=True)

    return sorted_list 

计算变更的提前期

现在已经有了代码来查询 Octopus API 的部署和发布,并使用这些信息来计算 DORA 指标。

要解决的第一个指标是变更的交付时间,它是在get_change_lead_time函数中计算的:

def get_change_lead_time(): 

计算每次部署的交付时间,并获取数组中的值。这使您可以计算平均提前期:

 change_lead_times = [] 

空间和环境名称被转换为 id:

 space_id = get_space_id(args.octopus_space)
    environment_id = get_resource_id(space_id, "environments", args.octopus_environment) 

项目参数是以逗号分隔的项目名称列表。因此,您必须对每个单独的项目进行循环,并将项目名称转换为 ID:

 for project in args.octopus_project.split(","):
        project_id = get_resource_id(space_id, "projects", project) 

收集空间和环境的项目部署列表:

 deployments = get_deployments(space_id, environment_id, project_id) 

这是您计算每次部署的交付时间的地方:

 for deployment in deployments: 

变更的交付周期度量与将提交部署到生产中所花费的时间有关。您必须找到与部署相关联的最早提交:

 earliest_commit = None 

一个部署代表一个版本在一个环境中的执行。它是包含构建信息元数据的发布,而构建信息元数据又包含与发布中包含的任何包相关联的提交的细节。您必须从部署持有的发布 ID 中获取发布资源:

 release = get_resource(space_id, "releases", deployment["ReleaseId"]) 

版本包含一个数组,该数组包含零个或多个构建信息对象。您必须循环遍历该数组以返回提交的详细信息:

 for buildInfo in release["BuildInformation"]: 

每个构建信息对象包含零个或多个提交。您必须循环遍历该数组,寻找最早的提交:

 for commit in buildInfo["Commits"]: 

当使用 GitHub 提交时,与每个提交相关联的 URL 链接回一个可以用 web 浏览器打开的页面。这些链接看起来像:https://github . com/OctopusSamples/OctoPub/commit/dcaf 638037503021 de 696d 13 B4 C5 c 41 ba 6952 e 9 f

GitHub 维护一组并行的 URL,这些 URL 公开了用于查询 GitHub 资源的 API。API 使用的 URL 通常类似于可公开浏览的 URL。在这种情况下,API URL 看起来像:https://API . github . com/repos/OctopusSamples/OctoPub/commits/dcaf 638037503021 de 696d 13 B4 C5 c 41 ba 6952 e 9 f。因此,您将可浏览链接转换为 API 链接:

 api_url = commit["LinkUrl"].replace("github.com", "api.github.com/repos") \
                        .replace("commit", "commits") 

然后查询提交的详细信息:

 commit_response = get(api_url, auth=github_auth) 

您需要以 Python datetime 对象的形式返回提交日期:

 date_parsed = parse_github_date(commit_response.json()["commit"]["committer"]["date"]) 

然后保存最早提交的日期:

 if earliest_commit is None or earliest_commit > date_parsed:
                        earliest_commit = date_parsed 

假设上面的代码找到了提交日期,则部署日期和提交日期之间的差值计算如下:

 if earliest_commit is not None:
                change_lead_times.append((parse_octopus_date(deployment["Created"]) - earliest_commit).total_seconds()) 

假设在构建信息元数据中找到任何提交,则计算并返回最早提交和部署日期之间的平均时间:

 if len(change_lead_times) != 0:
        return sum(change_lead_times) / len(change_lead_times) 

如果没有发现提交,或者发布没有相关的构建信息,则返回None:

 return None 

计算恢复服务的时间

下一个要计算的指标是恢复服务的时间。

正如引言中所提到的,这个指标被认为是通过部署一个解决了问题的版本所花费的时间来衡量的。get_time_to_restore_service函数用于计算该值:

def get_time_to_restore_service(): 

同样,您维护一个值数组来计算平均值:

 restore_service_times = [] 

空间和环境名称被转换为它们的 id:

 space_id = get_space_id(args.octopus_space)
    environment_id = get_resource_id(space_id, "environments", args.octopus_environment) 

项目以逗号分隔的列表形式提供,您可以在该列表中循环:

 for project in args.octopus_project.split(","): 

项目名称被转换为一个 ID,并返回该环境的项目部署列表:

 project_id = get_resource_id(space_id, "projects", project)
        deployments = get_deployments(space_id, environment_id, project_id) 

返回与每个部署相关的发布,遍历所有的构建信息对象,然后遍历与发布中的每个包相关的所有工作项(在 GitHub 中称为问题):

 for deployment in deployments:
            deployment_date = parse_octopus_date(deployment["Created"])
            release = get_resource(space_id, "releases", deployment["ReleaseId"])
            for buildInfo in release["BuildInformation"]:
                for work_item in buildInfo["WorkItems"]: 

可浏览问题的 URL 被转换为 API URL,通过 API 查询问题,并返回创建日期:

 api_url = work_item["LinkUrl"].replace("github.com", "api.github.com/repos")
                    commit_response = get(api_url, auth=github_auth)
                    created_date = parse_github_date(commit_response.json()["created_at"]) 

问题的创建和部署之间的时间是这样计算的:

 if created_date is not None:
                        restore_service_times.append((deployment_date - created_date).total_seconds()) 

如果发现任何问题,则计算问题创建和部署之间的平均时间:

 if len(restore_service_times) != 0:
        return sum(restore_service_times) / len(restore_service_times) 

如果没有发现问题,则返回None:

 return None 

计算部署频率

部署频率是最容易计算的指标,因为它只是生产部署之间的平均时间。这是由get_deployment_frequency函数计算的:

def get_deployment_frequency(): 

部署频率的计算方法是执行部署的持续时间除以部署次数:

 deployment_count = 0
    earliest_deployment = None
    latest_deployment = None 

空间、环境和项目名称被转换为 id:

 space_id = get_space_id(args.octopus_space)
    environment_id = get_resource_id(space_id, "environments", args.octopus_environment)
    for project in args.octopus_project.split(","):
        project_id = get_resource_id(space_id, "projects", project) 

返回在环境中执行的部署:

 deployments = get_deployments(space_id, environment_id, project_id) 

计算部署的次数:

 deployment_count = deployment_count + len(deployments) 

找到最早和最新的部署:

 for deployment in deployments:
            created = parse_octopus_date(deployment["Created"])
            if earliest_deployment is None or earliest_deployment > created:
                earliest_deployment = created
            if latest_deployment is None or latest_deployment < created:
                latest_deployment = created 

假设找到了任何部署,则部署间隔时间除以部署次数。

注意:您可以用几种不同的方法来测量这个值。未注释的代码测量从最早的部署到当前时间点之间的平均部署时间。

另一种方法是测量最早部署和最近部署之间的平均时间:

 if latest_deployment is not None:
        # return average seconds / deployment from the earliest deployment to now
        return (datetime.now(pytz.utc) - earliest_deployment).total_seconds() / deployment_count
        # You could also return the frequency between the first and last deployment
        # return (latest_deployment - earliest_deployment).total_seconds() / deployment_count
    return None 

计算变更失败率

最后一个指标是变更失败率。

正如简介中所提到的,这段代码度量的是解决问题的部署数量,而不是引入问题的部署数量。使用构建信息捕获的信息来计算前一个度量是微不足道的,而后一个度量需要更多的元数据才能被问题公开。

尽管存在技术上的差异,但是您可以假设度量解决问题的部署是引入问题的部署的良好代理。减少生产问题会提高这两个分数,虽然“坏”部署在此逻辑中代表不足,因为单个版本可以解决许多问题,但“坏”部署在需要多次部署来解决单个先前部署的问题时代表过多。

get_change_failure_rate函数用于计算变更失败率:

def get_change_failure_rate(): 

失败率是指出现问题的部署数量与部署总数的比率:

 releases_with_issues = 0
    deployment_count = 0 

空间、环境和项目名称被转换为 id:

 space_id = get_space_id(args.octopus_space)
    environment_id = get_resource_id(space_id, "environments", args.octopus_environment)
    for project in args.octopus_project.split(","):
        project_id = get_resource_id(space_id, "projects", project) 

返回在环境中执行的部署,并累计部署总数:

 deployments = get_deployments(space_id, environment_id, project_id)
        deployment_count = deployment_count + len(deployments) 

然后,您需要找到与部署相关的任何问题,如果找到任何问题,您将增加有问题的部署的计数:

 for deployment in deployments:
            release = get_resource(space_id, "releases", deployment["ReleaseId"])
            for buildInfo in release["BuildInformation"]:
                if len(buildInfo["WorkItems"]) != 0:
                    # Note this measurement is not quite correct. Technically, the change failure rate
                    # measures deployments that result in a degraded service. We're measuring
                    # deployments that included fixes. If you made 4 deployments with issues,
                    # and fixed all 4 with a single subsequent deployment, this logic only detects one
                    # "failed" deployment instead of 4.
                    #
                    # To do a true measurement, issues must track the deployments that introduced the issue.
                    # There is no such out of the box field in GitHub actions though, so for simplicity
                    # we assume the rate at which fixes are deployed is a good proxy for measuring the
                    # rate at which bugs are introduced.
                    releases_with_issues = releases_with_issues + 1 

如果发现任何有问题的部署,将返回有问题的部署占部署总数的比率:

 if releases_with_issues != 0 and deployment_count != 0:
        return releases_with_issues / deployment_count 

如果没有发现有问题的部署,则返回None:

 return None 

衡量部署绩效

每个指标的测量分为四类:

DORA 报告描述了如何对每个测量进行分类,并且get_change_lead_time_summaryget_deployment_frequency_summaryget_change_failure_rate_summaryget_time_to_restore_service_summary函数打印结果:

def get_change_lead_time_summary(lead_time):
    if lead_time is None:
        print("Change lead time: N/A (no deployments or commits)")
    # One hour
    elif lead_time < 60 * 60:
        print("Change lead time: Elite (Average " + str(round(lead_time / 60 / 60, 2))
                         + " hours between commit and deploy)")
    # Every week
    elif lead_time < 60 * 60 * 24 * 7:
        print("Change lead time: High (Average " + str(round(lead_time / 60 / 60 / 24, 2))
                         + " days between commit and deploy)")
    # Every six months
    elif lead_time < 60 * 60 * 24 * 31 * 6:
        print("Change lead time: Medium (Average " + str(round(lead_time / 60 / 60 / 24 / 31, 2))
                         + " months between commit and deploy)")
    # Longer than six months
    else:
        print("Change lead time: Low (Average " + str(round(lead_time / 60 / 60 / 24 / 31, 2))
                         + " months between commit and deploy)")

def get_deployment_frequency_summary(deployment_frequency):
    if deployment_frequency is None:
        print("Deployment frequency: N/A (no deployments found)")
    # Multiple times per day
    elif deployment_frequency < 60 * 60 * 12:
        print("Deployment frequency: Elite (Average " + str(round(deployment_frequency / 60 / 60, 2))
                         + " hours between deployments)")
    # Every month
    elif deployment_frequency < 60 * 60 * 24 * 31:
        print("Deployment frequency: High (Average " + str(round(deployment_frequency / 60 / 60 / 24, 2))
                         + " days between deployments)")
    # Every six months
    elif deployment_frequency < 60 * 60 * 24 * 31 * 6:
        print("Deployment frequency: Medium (Average " + str(round(deployment_frequency / 60 / 60 / 24 / 31, 2))
                         + " months bbetween deployments)")
    # Longer than six months
    else:
        print("Deployment frequency: Low (Average " + str(round(deployment_frequency / 60 / 60 / 24 / 31, 2))
                         + " months between commit and deploy)")

def get_change_failure_rate_summary(failure_percent):
    if failure_percent is None:
        print("Change failure rate: N/A (no issues or deployments found)")
    # 15% or less
    elif failure_percent <= 0.15:
        print("Change failure rate: Elite (" + str(round(failure_percent * 100, 0)) + "%)")
    # Interestingly, everything else is reported as High to Low
    else:
        print("Change failure rate: Low (" + str(round(failure_percent * 100, 0)) + "%)")

def get_time_to_restore_service_summary(restore_time):
    if restore_time is None:
        print("Time to restore service: N/A (no issues or deployments found)")
    # One hour
    elif restore_time < 60 * 60:
        print("Time to restore service: Elite (Average " + str(round(restore_time / 60 / 60, 2))
                         + " hours between issue opened and deployment)")
    # Every month
    elif restore_time < 60 * 60 * 24:
        print("Time to restore service: High (Average " + str(round(restore_time / 60 / 60, 2))
                         + " hours between issue opened and deployment)")
    # Every six months
    elif restore_time < 60 * 60 * 24 * 7:
        print("Time to restore service: Medium (Average " + str(round(restore_time / 60 / 60 / 24, 2))
                         + " hours between issue opened and deployment)")
    # Technically the report says longer than six months is low, but there is no measurement
    # between week and six months, so we'll say longer than a week is low.
    else:
        print("Deployment frequency: Low (Average " + str(round(restore_time / 60 / 60 / 24, 2))
                         + " hours between issue opened and deployment)") 

最后一步是计算指标并将结果传递给上面的函数:

print("DORA stats for project(s) " + args.octopus_project + " in " + args.octopus_environment)
get_change_lead_time_summary(get_change_lead_time())
get_deployment_frequency_summary(get_deployment_frequency())
get_change_failure_rate_summary(get_change_failure_rate())
get_time_to_restore_service_summary(get_time_to_restore_service()) 

在操作手册中运行脚本

第一步是公开传递给脚本的两个变量:

  • GitHubToken是一个保存 GitHub 个人访问令牌的秘密,用于认证 GitHub API 调用。
  • ReadOnlyApiKey是分配给对 Octopus 服务器具有只读访问权限的帐户的 Octopus API 密钥(因为这个脚本只查询 API,从不修改任何资源)。

runbook 是一个单独的运行脚本步骤,包含以下 bash 脚本:

cd DoraMetrics

echo "##octopus[stdout-verbose]"
python3 -m venv my_env
. my_env/bin/activate
pip --disable-pip-version-check install -r requirements.txt
echo "##octopus[stdout-default]"

python3 main.py \
    --octopusUrl https://tenpillars.octopus.app \
    --octopusApiKey "#{ReadOnlyApiKey}" \
    --githubUser mcasperson \
    --githubToken "#{GitHubToken}" \
    --octopusSpace "#{Octopus.Space.Name}" \
    --octopusEnvironment "#{Octopus.Environment.Name}" \
    --octopusProject "Products Service, Audits Service, Octopub Frontend" 

这个脚本中发生了一些有趣的事情,所以让我们来分析一下。

您输入 Octopus 解压包含 Python 脚本的包的目录:

cd DependencyQuery 

打印服务消息 ##octopus[stdout-verbose]指示 Octopus 将所有后续日志消息视为冗余:

echo "##octopus[stdout-verbose]" 

创建并激活一个名为my_env的新 Python 虚拟环境,并安装脚本依赖项:

python3 -m venv my_env
. my_env/bin/activate
pip --disable-pip-version-check install -r requirements.txt 

服务消息##octopus[stdout-default]被打印,指示 Octopus 再次以默认级别处理后续日志消息:

echo "##octopus[stdout-default]" 

然后执行 Python 脚本。有些参数,如octopusUrlgithubUseroctopusProject,需要根据您的具体用例进行定制。将octopusSpaceoctopusEnvironment参数设置为运行 runbook 的空间和环境,可以让您在运行 runbook 的任何环境中计算 DORA 指标:

python3 main.py \
    --octopusUrl https://tenpillars.octopus.app \
    --octopusApiKey "#{ReadOnlyApiKey}" \
    --githubUser mcasperson \
    --githubToken "#{GitHubToken}" \
    --octopusSpace "#{Octopus.Space.Name}" \
    --octopusEnvironment "#{Octopus.Environment.Name}" \
    --octopusProject "Products Service, Audits Service, Octopub Frontend" 

执行操作手册

当 runbook 被执行时,它会扫描每个项目以获取当前环境的最新部署,从构建信息中找到 GitHub Action run 链接,下载依赖项工件,提取工件,并扫描文本文件以获取搜索文本。

只需点击一下运行按钮,您就可以快速衡量项目的绩效:

Runbook run

Octopus 中的 DORA 度量

DORA metrics and DevOps Insights in Octopus

Octopus Deploy 2022.3+包括对内置 DORA 指标的 DevOps Insights 的支持。该内置报告通过呈现 4 个关键的 DORA 指标,让您更好地了解贵公司的开发运维绩效:

  • 部署提前期
  • 部署失败率
  • 部署频率
  • 平均恢复时间

这些指标有助于您做出明智的决策,以改进和庆祝您的成果。

项目洞察可用于所有 Octopus 项目,包括现有项目。

空间级洞察可用于企业客户的空间级,并涵盖该空间中的所有项目。

空间级洞察可通过 Insights 选项卡获得,并为跨一组项目、环境或租户的更复杂场景提供可操作的 DORA 指标。这使经理和决策者能够根据他们的业务环境,如团队、组合或平台,更深入地了解他们组织的 DevOps 性能。

空间层面的见解:

  • 汇总整个空间的数据,以便您可以比较和对比项目间的指标,从而确定哪些可行,哪些不可行
  • 为更好的决策提供信息:发现问题,跟踪改进,庆祝成功
  • 根据数据显示的实际情况,帮助您量化 DevOps 性能

这些指标一起帮助您在您的项目和投资组合中鉴定您的 DevOps 性能的结果。

了解更多关于 DevOps Insights 的信息

结论

DORA 指标代表了可用于衡量您团队的 DevOps 绩效的少数经过严格研究的见解之一。在 Octopus build 信息包和 GitHub Actions 等问题跟踪平台捕获的信息之间,您可以将您的表现与全球数千个其他软件开发团队进行比较。

在本文中,您看到了一个示例 Python 脚本,它查询 Octopus 和 GitHub APIs 来计算四个 DORA 指标,然后将该脚本作为 Octopus runbook 运行。该示例脚本可以很容易地应用于任何使用 GitHub Actions 的团队,或者修改为查询其他源代码控制和问题跟踪平台。

阅读我们的 Runbooks 系列的其余部分。

愉快的部署!

GitHub Actions 能代替你的 CI 服务器吗?-章鱼部署

原文:https://octopus.com/blog/can-github-actions-replace-your-ci-server

Illustration showing GitHub CI processes with versions

随着 GitHub Actions(尽管是测试版)的引入,世界源代码库现在包括了托管和执行 CI/CD 管道的能力。既然托管代码本质上已经商品化了,那么运行构建脚本接下来也会商品化,这是很自然也是不可避免的。

关于 CI 有趣的事情是,它是一个机器驱动的过程,当输入(比如你最近的提交)可用时运行,并且在没有干预的情况下生成输出(比如你的工件和测试结果)。这些年来,我们看到每一个主要的 CI 平台都继续通过管道作为代码的概念将构建管道提炼到这个公式,结果是 CI 用户界面被用作只读仪表板,CI 服务器成为构建代理编排器。

因此,假设 GitHub Actions 托管代码,将构建管道作为代码公开,提供运行这些管道的执行环境,并提供一个存储库来托管结果工件,那么您还需要 CI 服务器吗?

放弃 CI 服务器并转向 GitHub Actions 的主要原因

作为实验,我决定将一个开源项目迁移到 GitHub Actions。这使我能够复制一个相当复杂的测试,并在 GitHub 的新服务上构建管道,这揭示了很多喜欢它的理由。

GitHub Actions 的核心思想是从 Docker 容器组合构建环境。概括地说,您的工作流是一系列作业,每个作业可以是直接在底层虚拟机上运行的脚本,也可以是通过卷装载来执行共享虚拟机文件系统的 Docker 容器。

突出显示的区域显示了将虚拟机文件系统安装到 Docker 容器中的操作。

以这种方式构建您的构建环境是明智的,因为每个主要的开发工具都已经有一个支持的 Docker 容器可供使用,并且您被迫不再维护那些艺术作品一样的构建代理(也就是说,这些构建代理是多年来手工调整和维护的)。将工作流工作做得恰到好处会有一些开销,但是从长远来看,这种努力会得到数倍的回报。这也意味着构建过程的增量变化可以在一个分支中测试,而不需要构建定制的构建代理,当你考虑到甚至像 Java 和。NET 现在每六个月就有一个主要版本。

让 GitHub 托管执行环境意味着 forks 也继承了构建环境。这对开源项目维护者来说是一个巨大的胜利,他们不再需要为了在他们自己的构建环境中运行复杂的回归测试而消耗代码变更,贡献者可以确信他们的变更将通过任何需要的测试。

让我们面对现实吧,每个人都将跳上 GitHub Actions 列车。您最喜欢的集成工具肯定会有一个自定义的 Action one 或一个 Docker 容器可以轻松地用作 Action。

但是 GitHub 的动作还没有完全准备好

GitHub 操作中有一些缺口,您需要在跳转之前考虑一下。

首先,版本化是一种痛苦。 GitVersion 提供了一个解决方法,但是缺少一个在你的工作流中使用的增量编译号变量是一个令人惊讶的疏忽。

没有简单的方法可以在存储库之间共享秘密,这意味着如果您的微服务 CI/CD 管道包括向云提供商的推送,则每个存储库都需要包括您的凭据的副本。这将很难维持,因为钥匙是循环的。

完全不支持捕获输出变量。如果你看看我们是如何实现 GitVersion 的,你会看到命令的输出被保存到一个文件中,因为文件系统是在动作之间共享输出的最容易的地方。最好是在变量中捕获该版本,而不是打印文件内容并将结果传递给后续的命令行参数。

虽然 GitHub 提供了一个令人印象深刻的操作系统列表来在上运行您的构建,但是如果您必须测试 Ubuntu 或旧版本的 Windows 和 MacOS 之外的任何版本的 Linux,您仍然需要提供您自己的测试环境。

最后,GitHub UI 仍然以存储库为中心。知道哪些 repos 定义了操作,更不用说试图查看您的构建状态了。如果你有很多用 GitHub Actions 构建的项目,就要期望点击很多次

你不会在这个主页上找到你的构建状态。

结论

如果您维护少量开源项目,GitHub Actions 是理想的选择。贡献者可以派生您的代码和构建环境,您不再需要维护任何单独的构建基础设施。

但是一旦你开始扩展到几个项目之外,GitHub UI 就会对你不利。此外,任何复杂的构建都必须找到捕获输出变量和共享秘密的变通方法。

话虽如此,GitHub Actions 仍处于测试阶段,许多不便之处可能会在未来得到解决。如果 GitHub 可以提供一个以构建为中心的仪表板,并消除工作流中的一些粗糙边缘,您将不得不认真考虑是否要维护一个专用的 CI 服务器。

GitHub Actions 是行业商品化低挂水果的又一个例子。结合开发人员已经完成了将构建管道放入代码的艰苦工作这一事实,云计算将不经常执行的代码的成本降低到几乎为零,代码库提供了执行这些管道的方法也就不足为奇了。现在的挑战是消费构建工件的提供者继续为 CI/CD 过程增加价值。

硒系列:捕捉 HAR 文件-章鱼部署

原文:https://octopus.com/blog/selenium/13-capturing-har-files/capturing-har-files

这篇文章是关于创建 Selenium WebDriver 测试框架的系列文章的一部分。

HTTP 存档(HAR)文件是浏览器与外部 web 应用程序交互的标准 JSON 格式日志。

你可以通过打开 Chrome 开发者工具,点击Network标签,打开一个网站,然后在网络调用列表中右击并选择Save as HAR with content来生成一个 HAR 文件。

这将生成一个 HAR 文件,其中包含开发人员工具捕获的所有网络调用的详细信息。

因为 HAR 文件具有开放的格式,所以您可以使用许多在线工具来检查其内容。一个这样的工具是 HAR 分析器。该工具允许您上传一个 HAR 文件,然后通过 HTTP 响应代码进行过滤,检查与请求相关的内容,并查看每个网络调用的时间。当调试 web 应用程序的错误或了解站点的响应情况时,这种信息是无价的。

BrowserMob 中一个有用的特性是能够根据通过它的网络请求生成 HAR 文件。

为了允许测试捕获 HAR 文件,我们将向AutomatedBrowser接口添加两个方法:

void captureHarFile();

void saveHarFile(String file); 

captureHarFile()方法用于指示 BrowserMob 开始捕获网络流量,而saveHarFile()方法将获取任何捕获的流量并将其保存到指定的文件中。

然后默认方法被添加到AutomatedBrowserBase:

@Override
public void captureHarFile() {
  if (getAutomatedBrowser() != null) {
    getAutomatedBrowser().captureHarFile();
  }
}

@Override
public void saveHarFile(final String file) {
  if (getAutomatedBrowser() != null) {
    getAutomatedBrowser().saveHarFile(file);
  }
} 

这些方法是在BrowserMobDecorator类中实现的。

为了开始捕获网络流量,我们调用 BrowserMob 代理对象上的newHar():

@Override
public void captureHarFile() {
  proxy.newHar();
} 

通过调用getHar().writeTo()保存捕获的流量:

@Override
public void saveHarFile(final String file) {
  try {
    proxy.getHar().writeTo(new File(file));
  } catch (final IOException ex) {
    throw new SaveException(ex);
  }
} 

被检查的异常IOException被捕获并作为未检查的异常SaveException被重新抛出。这允许我们匹配接口方法签名,它不指定任何检查的异常。

这里是SaveException的代码。它扩展了RuntimeException类,这意味着它是一个未检查的异常:

package com.octopus.exceptions;

public class SaveException extends RuntimeException {

  public SaveException() {

  }

  public SaveException(final String message) {
    super(message);
  }

  public SaveException(final Throwable cause) {
    super(cause);
  }

  public SaveException(final String message, final Throwable cause) {
    super(message, cause);
  }
} 

现在,剩下的就是使用这些方法作为测试的一部分:

@Test
public void captureHarFile() throws URISyntaxException {
final AutomatedBrowser automatedBrowser =
  AUTOMATED_BROWSER_FACTORY.getAutomatedBrowser("Chrome");

  try {
    automatedBrowser.init();
    automatedBrowser.captureHarFile();
    automatedBrowser.goTo("https://octopus.com/");
  } finally {
    try {
      automatedBrowser.saveHarFile("test.har");
    } finally {
      automatedBrowser.destroy();
    }
  }
} 

在打开网页之前,我们先给automatedBrowser.captureHarFile()打了个电话。这可确保我们在测试中捕获所有网络流量。

finally块中,我们嵌套了一个额外的try / finally块。在嵌套的try块中,我们称之为automatedBrowser.saveHarFile("test.har"),它将任何捕获的流量写入文件test.har。这个调用是在finally块中进行的,因为即使测试失败,我们也想保存这个网络流量。HAR 文件通常包含可用于调试失败测试的信息,因此我们希望确保在测试过程中保存该文件,而不考虑错误。

在嵌套的finally块中,我们调用automatedBrowser.destroy()。因为对destroy()的调用释放了资源,所以我们需要确保它在测试失败和保存 HAR 文件失败的情况下运行。以这种方式嵌套try / finally块保证了destroy()方法被调用,而不管任何其他失败。

生成的test.har文件捕获了由于打开【https://octopus.com/】的而产生的网络流量。然后可以使用 HAR 分析器等工具检查该文件,以快速识别任何失败的请求(即 4xx 或 5xx 范围内的任何 HTTP 响应代码),并提供可视的呼叫时间表。这些信息对于调试 web 应用程序的错误和理解性能瓶颈是非常宝贵的。

要用 HAR 分析仪打开文件,转到https://toolbox.googleapps.com/apps/har_analyzer/并点击CHOOSE FILE按钮。

选择test.har文件。

然后,您将得到一个表格,显示测试过程中发出的所有网络请求。

【T2

您可以使用这些复选框通过 HTTP 响应代码过滤结果。

这些代码是 100 到 599 之间的数字,以 100 为一组。0 响应代码是一种特殊情况,表示没有返回响应。

下表显示了响应代码的一般含义。

代码范围 意义
1xx 信息反应
2xx 成功
3xx 重寄
4xx 客户端错误
5xx 服务器错误

该表显示了 7 列。

圆柱 价值
时间 提出请求时的 UTC 时间。
反应 HTTP 响应代码。这是使用上面的复选框过滤的代码。
请求。大小 浏览器发送的字节数。
Resp。大小 浏览器接收的字节数。
分析 显示代表与请求相关联的数据、请求状态以及请求是否由浏览器缓存提供的图标。
时机 显示一个图表,显示构成请求的事件以及这些事件花费的时间。

响应时间显示在最后一列的图表中。将光标悬停在每个彩色列上可以显示发生了什么类型的事件,以及花费了多长时间。

如果选择了Relative计时类型,图表显示所有请求所用的总时间,彩色列显示单个请求相对于总时间所用的时间。

如果选择了Independent计时类型,图表只显示每个事件相对于单个请求所花费的时间。

单击任何一行都会在页面右侧显示有关该请求的更多详细信息。

您会注意到,当选择一个请求时,许多数据都丢失了。这是因为默认情况下,BrowserMob 只捕获一小部分可以保存在 HAR 文件中的数据。为了获取所有可用的信息,我们在AutomatedBrowser接口中创建了一个名为captureCompleteHarFile()的新方法:

void captureCompleteHarFile(); 

我们将默认实现添加到AutomatedBrowserBase类中:

@Override
public void captureCompleteHarFile() {
  if (getAutomatedBrowser() != null) {
    getAutomatedBrowser().captureCompleteHarFile();
  }
} 

然后我们在BrowserMobDecorator类中实现该方法:

@Override
public void captureCompleteHarFile() {
  final EnumSet<CaptureType> captureTypes =
    CaptureType.getAllContentCaptureTypes();
  captureTypes.addAll(CaptureType.getHeaderCaptureTypes());
  captureTypes.addAll(CaptureType.getCookieCaptureTypes());
  proxy.setHarCaptureTypes(captureTypes);
  proxy.newHar();
} 

这里的更改是指示 BrowserMob 捕获发送和接收的某些附加数据。BrowserMob 公开了一个名为CaptureType的枚举,它定义了可以保存在 HAR 文件中的各种细节。此外,CaptureType enum 有一些静态方法,这些方法提供了方便的 enum 组,这些 enum 组表示相关种类的细节的集合。

我们从代表内容的一组预定义的CaptureType枚举开始:

final EnumSet<CaptureType> captureTypes =
  CaptureType.getAllContentCaptureTypes(); 

然后,我们添加代表头部的预定义的CaptureType枚举组:

captureTypes.addAll(CaptureType.getHeaderCaptureTypes()); 

最后,我们添加代表 cookies 的一组预定义的CaptureType枚举:

captureTypes.addAll(CaptureType.getCookieCaptureTypes()); 

然后将这些CaptureType枚举传递给setHarCaptureTypes()方法,以配置 BrowserMob 将所有这些细节保存到生成的 HAR 文件中:

proxy.setHarCaptureTypes(captureTypes); 

更新测试以调用captureCompleteHarFile()方法,并再次运行它:

@Test
public void captureCompleteHarFile() throws URISyntaxException {

  final AutomatedBrowser automatedBrowser =
    AUTOMATED_BROWSER_FACTORY.getAutomatedBrowser("Chrome");

  try {
    automatedBrowser.init();
    automatedBrowser.captureCompleteHarFile();
    automatedBrowser.goTo("https://octopus.com/");
  } finally {
    try {
      automatedBrowser.saveHarFile("test.har");
    } finally {
      automatedBrowser.destroy();
    }
  }
} 

您会注意到,HAR 文件现在有几兆字节大小,这是针对一个相对简单的页面请求的。更完整的测试可能会生成相当大的 HAR 文件,所以尽量少用captureCompleteHarFile()方法。

当我们分析新的 HAR 文件时,我们可以看到不再有任何关于丢失信息的警告。

HAR 文件是记录和分析测试过程中网络迭代的非常有用的方法,而且由于 BrowserMob 代理,生成 HAR 文件非常容易。但是 BrowserMob 可以做的不仅仅是记录流经它的流量,在下一篇文章中,我们将看到 BrowserMob 如何阻止或修改请求。

这篇文章是关于创建 Selenium WebDriver 测试框架的系列文章的一部分。

证书功能-八达通部署

原文:https://octopus.com/blog/certificates-feature

Octopus Certificates

3.11 版证书成为一流的八达通公民!

去年,我们就此功能的初始设计征求了您的反馈。反应是积极的,很多。很明显,自动化证书是一个棘手的问题...部署管道。希望我们能稍微缓解一下。

Certificates in Octopus

证书功能允许您:

商店证书

Add Certificate

证书可以以 PFX、PEM 或 DER 格式上传,并且可能包含私钥。它们的范围可以是环境和/或租户。您可以搜索证书,当证书即将到期时,它们会提供直观的指示。

证书变量

您可以使用证书作为值来创建变量。

Certificate Variables

这种类型化变量的概念对 Octopus 来说是新的。理解变量的类型打开了许多可能性,这些可能性在变量只是文本时是不存在的。你可以期待在未来看到其他类型的变量。

证书变量由新的导入证书步骤使用,也可以在 IIS 绑定配置中使用。

它们也可以用在您自己的自定义脚本中。正如我们在 RFC 文章中对该特性的建议,在部署时,证书变量被扩展成许多变量,本质上是使用点符号模拟 O-O 属性。有关可用属性,请参见我们的文档。

导入证书部署步骤

Import Certificate Step

我们添加了一个新的导入证书步骤,使得将由 Octopus 管理的证书导入 Windows 证书存储成为部署过程的一部分变得更加容易。

Import Certificate Step details

配置 HTTPS 绑定时引用证书变量

你可以...正如上面的标题所暗示的。

Certificate in HTTPS binding

出口证书

证书可以以 PFX、PEM 或 DER 格式导出。或者,您可以完全按照它们最初上传时的样子导出它们。

对于 PEM 格式,您可以选择是否包含私钥。

Download Certificate

配置到期通知

可以创建 Octopus 订阅以在证书到期后 20 天、到期后 10 天或到期时触发。

订阅可以配置为发送电子邮件或张贴到 URL。请参见下面的集成部分了解这方面的一些想法。

更换证书

当获得即将过期的证书的替换时,您可能希望用新证书替换过期证书的所有用法。替换功能通过允许您上传新的证书文件来支持这一点。新证书将继承旧证书的所有 Octopus 属性(名称、环境等),包括 ID。这使得所有引用变量现在都指向新证书。以前的证书被赋予了一个新的 ID 并被存档(因此如果需要,您仍然可以访问它)。

综合

像 Octopus 中的所有东西一样,证书功能通过我们的 HTTP REST API 公开,并可通过我们的使用。NET 客户端库。这应该可以将 Octopus 证书集成到您的组织用来管理 X.509 证书的任何流程中。

特别是,通过订阅的到期通知,结合 Replace API 端点,应该会带来一些有趣的可能性。这是我们希望在未来进一步写的东西。

反馈

一如既往,请告诉我们您的想法。我们喜欢听取用户的意见。

愉快的(密码安全)部署!

变革顾问委员会不起作用-八达通部署

原文:https://octopus.com/blog/change-advisory-boards-dont-work

Change Advisory Boards Don’t Work

以质量的名义,许多组织有一个变更顾问委员会或变更批准委员会(CAB ),他们在针对生产执行变更之前对变更进行审查。它们有时是为了遵守某些法规,如 2002 年萨班斯-奥克斯利法案(SOX),和/或强制执行“职责分离”。其他时候,为了提高可靠性,在一系列部署失败后引入 cab。

这个想法是提供额外的审查,通常是为了捕捉错误、糟糕的代码或欺诈性的更改。这是一个崇高的目标,但不幸的是,出租车通常弊大于利。

这一点在 2018 年由妮可·福斯格伦、吉恩·金和杰兹·亨布尔在 Accelerate 中清晰地展示出来。他们分析了 2014–2017 年开发运营状况报告中的数据,他们:

“发现外部批准与交付时间、部署频率和恢复时间呈负相关,与变更失败率无关。简而言之,外部机构(如经理或 CAB)的批准根本不能提高生产系统的稳定性,这是通过恢复服务和变更失败率的时间来衡量的。然而,它肯定会减慢速度。事实上,这比根本没有变更审批流程更糟糕。”

上面的报价包含一些加载的条款。让我们来讨论它们的含义以及为什么它们意义重大。

研制周期

对于许多人来说,DevOps 运动开始于一个模糊的认识,即快速地将变更投入生产以及减少任何时候的在制品(WIP)量的重要性。“DevOps”这个名称是对典型的反模式的拒绝,这种反模式是在开发人员和运营人员之间的“混乱之墙”上抛出变更,在这种情况下,变更将不可避免地与政治心理剧纠缠在一起,导致延迟和错误。

基于精益制造和敏捷软件开发,人们认识到了与长开发周期和大量 WIP 水平相关的巨大挑战和浪费。在 DevOps 运动的早期,经常听到人们亲切地称交付周期为从“啊哈!去挖沟!”他们认识到,任何能够以小增量快速实现其想法的企业,都将在市场上拥有显著的优势。

我们不再生活在一个大打小闹的世界。快打败了慢。如果你能尽早交付价值,你就有能力扰乱市场。有可能在开发周期的更早阶段就开始销售产品,并根据现实世界的反馈逐步完善想法。这大大降低了对前期资本投资的要求,并带来了更好的产品和服务。一个企业只能从速度、质量和成本中选择两者的古老说法已经过时了。

缩短交付周期与减少 WIP 密切相关。如果一个组织试图一次进行太多或太大的变更,变更管理开销会很快变得很大,失败的风险也会激增。开发周期短,变更少,效率更高,反之亦然。

然而,cab 通常会显著增加交付周期和 WIP,因为更新会被延迟,并在经过正式的审批流程时进行批量处理。例如,如果 CAB 每周召开一次会议,那么只花了一个小时编写的变更在交付给最终用户之前可能会延迟 40 个工作小时,或实际 168 个小时。对企业来说,意想不到的后果是交付周期明显缩短,WIP 增加,导致质量下降,反馈变慢,开发成本增加,现金流减少。

部署频率

一个组织部署的频率并不是一个难以理解的概念,但它是一个重要的指标,会带来一些反直觉的后果。

每个部署都有一些风险,失败的部署是一件可怕的事情。根据《可见运营手册》、《开发运营手册》和各种 Gartner 报告,大约 80%的生产中断是有人做出改变的结果。如果一个组织遭受了一系列代价高昂的部署失败,自然希望减少失败次数,并采取额外的措施来测试和验证它们。

出租车通常就是在这种背景下形成的。然而,正如 Chuck Rossi 在脸书工作时观察到的,当部署增长超过一定规模时,成功执行它们几乎是不可能的。如果将许多小的变更捆绑到一个大的部署中,那么这个部署本质上会变得更复杂、更有风险、更难修复。他总结道,“如果我们想要更多的改变,我们需要更多的部署”。

不幸的是,cab 经常产生一个瓶颈,许多变更在一个批处理中被评审,然后在一个部署窗口中被部署,通常是由对每个变更的上下文了解很少的人进行的。因此,尽管出发点是好的,但现实是,由于降低部署频率的直接结果,cab 通常会增加痛苦的部署失败的风险,从而导致更大、更复杂和更危险的部署。

恢复时间和更改失败率

恢复时间,或平均恢复时间(MTTR),是一个衡量团队从中断中恢复的速度的指标。因为我们预计 80%的停机是变更的结果,所以明智的做法是特别关注团队从失败的部署中恢复的速度,并投资于改善 MTTR 的工作。

然而,从历史上看,组织更关注平均无故障时间(MTBF)。虽然态度正在改变,但人们仍然更可能谈论部署失败的频率,而不是故障可以多快修复。

数据库可靠性工程中,Laine Campbell 和 Charity Majors 将此称为“弹性对健壮性”。他们解释说,当人们设计健壮的系统从不/很少崩溃时,系统就变得脆弱了。因为失败很少发生,所以当失败真的发生时,团队准备不足,而且通常失败是复杂的,难以理解和修复。相反,当团队接受失败会发生并设计一个系统来处理失败时,他们更有可能快速恢复。他们提倡诸如金丝雀部署模式、自动故障转移、混沌工程以及使系统能够自动切换到小规模模式等实践。例如,当服务处于重负载时,适度地关闭资源密集型功能。通过采用这些弹性实践,部署失败的影响可能会小得多。

因此,虽然变更失败率低当然是可取的,但是试图消除所有风险是徒劳的。您可以将整个 IT 预算投资于避免失败,但失败仍然会发生。与此同时,寻找完美部署记录的开销可能会削弱交付时间和创新。老话说,只要完美是不可能的或者不切实际的,“完美就是好的敌人”。

相比之下,首先关注 MTTR,其次关注变更失败率的组织,将与风险有更健康的关系。如果失败的部署不再是一个问题,并且您的团队能够更快地修复它们,那么变更失败率是 1%还是 2%就不那么重要了。也可能是 25%,因为在客户眼中,这个组织仍然可能比 MTTR 差的组织表现更好。

不幸的是,大多数 cab 更重视停止中断的部署,而不是创建一个失败时更安全的系统。这是没有效果的。

四个关键指标

这四个概念(交付时间、部署频率、MTTR 和变更失败率)共同构成了 Accelerate 的“四个关键指标”。选择这些指标是因为它们被证明能够带来积极的业务成果。

虽然听起来不太直观,但是那些在速度指标(交付周期和部署频率)上得分高的组织也是在质量指标(MTTR 和变更失败率)上得分高的组织。在交付周期、部署频率和 MTTR 方面表现较好的组织具有较好的业务成果,这可以通过盈利能力、生产力和市场份额来衡量。(有趣的是,变更失败率似乎没有其他三个指标重要。)

Forsgren、Humble 和 Kim 已经证明了速度产生质量,质量产生速度,它们都会带来更好的业务成果。如果您有一个 CAB,那么 CAB 与四个关键指标和业务成果负相关的事实应该引起您、您的 IT 经理和您的股东的关注。

一个理想的代码审查过程

让知识渊博的专家审查代码无疑是同时提高质量和避免知识囤积的一个极好的方法。这也是促进指导、个人发展以及与具有不同技能或经验的人(如数据库管理员、信息安全等)合作的好方法。代码审查是一件美妙的事情,这篇博客文章不应该被解释为对这种有价值的实践的批评。

大多数出租车的目标无疑是积极的。问题出在它们的实现上。

当由理解代码和上下文的人来完成时,代码评审工作最好。例如,它们可以由开发团队的另一个成员来完成。也许是坐在邻桌的人或更有经验的同事?如果您是一名 web 开发人员,但是您需要更新数据库中的存储过程,那么您可能更希望获得 DBA 的审核。对于代码来说,由一个了解上下文的有知识的专家来评审比由一群不了解上下文的人组成的委员会来评审要有效得多。

代码评审也应该在小范围的变更中进行。有人被要求评审的代码越多,他们就越不仔细,提出的建议也越少。因此,由委员会在相对较短的会议中审查大量的变更,不太可能提供高质量的审查或反馈。相比之下,频繁的小代码审查要有效得多。

无论是谁来评审你的工作,都应该理解缩短交付周期和减少在制品的重要性。简单地说,评审应该快速进行,完成工作比开始工作更重要。因此,如果您正在处理一个新的开发任务,并且有人要求您评审他们的“开发完成”变更,您通常应该优先评审他们的代码,而不是您自己的工作。虽然这可能会引入一个上下文切换开销,但是这比在一周结束时对变更进行批处理要小得多。因此,理想的评审者应该服从于开发者,而不是相反。任何评审者都应该在变更准备好接受评审时,近乎实时地评审变更,而不是让变更以适合评审者的节奏进行批量评审。如果评审者是个人,这应该是可以管理的,但是如果评审者是一个大的委员会,这就变得不切实际了。

布线过程

不幸的是,cab 很少反映上述理想的代码评审过程。通常,来自整个组织的各种极度规避风险的角色会聚集在会议室或通过视频会议,讨论一长串在过去一周左右分批进行的变更。CAB 可能包括不是主题专家的人,他们可能不了解上下文。

这些会议可能会按照固定的时间表进行,所以工作是根据人们的日程安排的,而不是人们根据手头的任务来安排他们的时间。

在大多数 CAB 会议中,由于要审查的变更的数量和规模,单个变更不会受到太多审查。

每个变更可能只与 CAB 的一两个成员相关,因此其他与会者很可能是在浪费时间。

CAB 会议往往成为例行公事,每个项目都要问一两个相同的问题。“这会导致停机吗?”“您有回滚计划吗?”很好,下一个。

也许最糟糕的是,这些会议对所有与会者来说,可能会变得极其乏味,占用日程表。

不管最初的意图是什么,出租车通常会变成一个安全的剧院,而不是真正试图创建更可靠的系统。它们在提供有限价值的同时,往往会造成严重的破坏。

职责分离、法规遵从性和欺诈

在我们继续之前,请明确一个事实,我不是律师,虽然我在本文前面提到了 SOX,但我对技术实践的建议不应被解释为对任何特定法律的法律建议。如果你对任何法律问题有任何疑虑,你应该寻求专业的法律意见。我的建议纯粹是关于特定技术实践的有效性。

正如本文开头所提到的,cab 的存在通常是因为监管要求,通常是因为一个关于“职责分离”的特定条款。这些法规通常是为了防止欺诈而存在的,它们非常重要。

不幸的是,cab 通常不是发现欺诈的有效方法,并且由于上述原因,它们给组织带来了可怕的负担。上面描述的理想的代码审查过程是对欺诈的更好的防御,也是交付创新软件的更有效的方法。

在不对任何具体法律做出任何评论的情况下,根据我的经验,大多数关于防止欺诈的法律不太关心代码审查的形式,而更关心其有效性和可审计性。如果您要审查管理您的法律,您很可能会发现传统的 CAB 并不是绝对必要的,更轻量级但同样严格的审查过程可能更合适,它更自然地适合您的开发周期并包含更彻底的审查。

例如,上面描述的理想代码评审过程非常适合在源代码控制中使用拉请求。通过这种方式,审核者和他们的注释被审核,并且通常可以配置各种规则来定义需要审核每个变更的人员的种类。如果没有进行适当的代码评审,就不可能更新源代码控制中的主分支。

或者,您可以在部署管道中审核您的评审,比如通过使用 Octopus Deploy 中的“手动干预”步骤。这些都是经过全面审核的,包括关于谁审核了变更、何时审核的详细信息,并且任何评论都存储在日志中。

对于不同种类的审查,您可能希望在部署管道中同时使用源代码控制拉请求和手动审查步骤,但是要小心添加太多的手动批准门,因为这可能会增加交付时间和 WIP,并且会导致更大和更有风险的部署。评论很重要,但是保持轻量级和避免不必要的官僚主义也很重要。

如果您对自己的法律要求有任何疑问,请寻求专业的法律建议。

如果您的组织正在考虑组建一个 CAB,该怎么办

首先,保持冷静。做出这些决定的人很可能是出于好意。他们只是不理解出租车可能产生的反模式。你应该设法教育他们,而不是压制他们。大多数人喜欢学习和被说服,但人们很少对被击败而屈服做出好的反应。

你可以从教他们上述四个关键指标的反直觉本质开始。从这里开始,你可以解释你同意适当的监督是重要的,并且你渴望尽可能有效和高效地交付必要的监督。

你可能要考虑拥抱最厌恶风险的人和最了解主要风险的人。通过要求他们帮助您设计自动化的方法来标记您的自动化测试框架或部署管道中的潜在问题,让他们站在您的一边。最了解风险的人通常会成为优秀的盟友。

特别注意与驾驶室相关的任何表格填写或售票系统。这些通常设计得很差,或者没有给用户提供所有必要信息的机会,或者它们引入了难以忍受的开销,即使是简单和低风险的更改。当这种情况发生时,这个过程将不可避免地中断,没有人会赢。

确保 CAB 具有明确定义的范围,并且如果可能的话,低风险,甚至可能是中等风险的变更可以完全跳过 CAB。这将使 CAB 有更多的时间关注最有风险的变更。例如,尝试获得一个承诺,即如果变更符合以下标准,它可以绕过 CAB:

  • 开发团队在过去的 30 天里一直保持在他们的“误差预算”内(参见现场可靠性工程第 3 章关于误差预算的更多细节)。
  • 所有变更都通过自动化部署管道进行部署。
  • 所有变更都已经由团队的另一名成员进行了同行评审。
  • 所有的变更都通过了相关的测试,代码库保持了至少 x%的代码覆盖率。

除了节省 CAB 时间,这将激励开发团队投资于适当的质量控制和测试自动化,以避免经历 CAB。

最后,为每个关键决策者购买一份 Accelerate 可能不会有什么坏处。如果他们不想读,和他们达成一个协议,你会在他们的推荐下读一本书(而且一定要读!),如果他们答应看一本你推荐的书。然后约定一个见面的时间,也许可以吃顿大餐,讨论一下这两本书。你们可能都学到了一些东西,这可能会引发一场友好而有价值的讨论。

如果你已经有出租车了该怎么办

不得不随着出租车的节拍跳舞并不理想。然而,在阅读这篇博客后,你应该避免任何下意识的反应。在没有投资任何轻量级批准过程的情况下,您很可能会依赖 CAB 来进行高风险的变更。

不管你喜欢它还是讨厌它,对你现有过程的任何改变都应该反复地、小心翼翼地进行。开发您的部署过程,就像构建一个软件一样,应该小批量地完成,以避免意外地做出灾难性的大规模更改。

承认人为因素也很重要。你可能已经有了一群有价值、有技能的同事,他们已经是 CAB 的成员或者投资了 CAB——你想把他们带在身边。也可能有一些有权有势的人对缩减或解散 CAB 的想法感到不安。与他们为敌可能不是个好主意。

作为第一步,与 CAB 的支持者谈谈如何让它更有效率。毕竟,很有可能他们和你一样厌倦了出租车。它们可能是相当乏味的会议,大多数与会者可能会认为它们分散了他们对更重要任务的注意力。

也许你可以问他们以下几个问题:

  • 变更是否分为高风险、中风险和低风险变更?低风险和中等风险的变更有什么区别?阈值是否设置在适当的水平,使 CAB 能够将注意力集中在最重要的问题上?
  • 怎样才能让 CAB 满意地自动批准低风险变更或允许他们完全绕过 CAB?
  • 要将中等风险的变更降级为低风险的变更,您能做些什么?需要什么样的验证或测试?为了预先批准中等风险变更,这些测试可以自动化吗?
  • 如何简化文书工作或表格?
  • 是否要求所有 CAB 成员都到场审查所有变更?例如,如果 CAB 通常在周三下午开会,您是否可以在每天上午 11 点与一两名 CAB 成员一起额外运行一个缩小版的 CAB,以便在低/中风险变更可用时尽快进行审核?这将使您有更多的时间在周三的主要会议上仔细检查高风险的变更,并改善中低风险变更的准备时间。这可能会显著减少 WIP,并推动所有四个关键指标的改进。
  • 为了使系统相互分离,您可以投资什么样的架构变化?这可以降低一个故障影响多个系统的风险。因此,可能需要更少的人来审查每个变更。
  • 您可以进行哪些投资来改善 MTTR 或降低部署风险?如果部署失败的后果显著减少,这是否会使 CAB 允许变更更自由地流向生产?
  • 您如何报告您对 CAB 流程所做的任何更改的成功/失败?当您迭代您的 CAB 流程时,您能跟踪这四个关键指标,或者任何其他关键性能指标,以确保您的更改产生积极的效果吗?

自 2010 年以来,Alex Yates 一直在帮助组织将 DevOps 原则应用于他们的数据。他最引以为豪的是帮助 Skyscanner 开发了一天 95 次部署的能力,并支持联合国项目服务办公室的发布流程。亚历克斯与除南极洲以外的各大洲的客户都有过合作——所以他渴望见到任何研究企鹅的人。

作为一名热心的社区成员,他共同组织了数据接力,是【www.SpeakingMentors.com】的创始人,并自 2017 年以来被公认为微软数据平台 MVP

Alex 是官方 Octopus Deploy 合作伙伴 DLM 顾问的创始人。他喜欢为那些希望通过改进 IT 和数据库交付实践来实现更好业务成果的客户提供指导、辅导、培训和咨询。

如果你想和亚历克斯一起工作,请发电子邮件:enquiries@dlmconsultants.com

更改 TFS/VSTS 扩展- Octopus 部署

原文:https://octopus.com/blog/changes-to-vsts-extension

如果您是许多使用 Octopus Deploy 构建和发布任务扩展的 Visual Studio Team Services (VSTS)或 Team Foundation Server (TFS)用户之一,您可能会注意到一些变化。我想我应该花一点时间来解释这些变化,并让您知道我们在该领域的其他工作。

等等,什么新版本?

该扩展的新版本是2.0.6(在撰写本文时),但是如果您当前使用的是该扩展的版本1.2.81,您不会马上看到变化。

No change initially

VSTS 的部分 UI(包括构建任务和扩展)仍然在快速变化,最近的一些变化都是关于一个更安全的更新过程来打破变化。简而言之,如果一个扩展的主要版本发生了变化,在使用它之前,您必须显式地更新这个扩展。

新版本要求您

establish a new connection to Octopus

。如果您没有权限从 VSTS 生成新的 API 密钥,

it's still safe to update!

您可以继续使用旧版本的构建步骤,直到您准备好为止。

要获得新版本,您必须转到管理扩展页面进行更新。

Manage extensions

单击告诉您需要更新的链接。

Update the extension

最后,检查更改和更新。当然,要做到这一点,您需要有正确的权限。

Review the permissions and update

使用新版本

如果您已经在构建定义中使用了 Octopus Deploy 步骤,现在您会在这些步骤旁边看到一个小小的粉红色标记(您可能需要在浏览器中强制刷新)。这表明您可以使用构建和发布步骤的新版本。次要版本和补丁版本更新将自动应用,我保证我们不会在没有更新主要版本的情况下做出任何突破性的改变!

Pink flag indicating a new version available

要移动到最新版本,在每个步骤的配置中,您会看到一个新版本下拉列表。这允许您选择想要使用的步骤版本。

Version picker

这些步骤的旧版本将继续工作,但是当您从“添加构建步骤”菜单添加一个步骤时,它将默认为最新版本。感谢杰斯,你可能还会注意到漂亮的新标识!

New Steps - Packaging

New Steps - Deploying

要开始使用新的步骤,您必须从一个新的 Octopus 连接开始。

八达通服务端点

我们的扩展现在包括了 Octopus 的特定服务端点类型!

Octopus Endpoint

拥有我们自己的端点使得连接的目的更加清晰,并且将 API 密匙放在“API 密匙”字段而不是密码字段中也有明显的优势,但是在幕后还有更多的事情要做。

定义一个特定的 Octopus 端点让我们能够提供可以在 VSTS 的各个地方使用的“数据源”。目前,我们使用这些来为构建和发布步骤中的下拉列表提供信息(见下面的),毫无疑问,我们将在未来更多地使用它们。

Octopus Connection

任务域中的下拉列表

使用新服务端点的一大优势是能够从 Octopus 服务器检索数据,并在 VSTS UI 中公开这些数据。

这意味着,当您在使用构建和发布任务时选择 Octopus 端点时,项目名称字段(现在是一个下拉列表)会填充您的 Octopus 服务器上的所有项目。如果环境、渠道和租户适用于您的场景,情况也是如此。这样你就不用打开 Octopus 来确保你得到了正确的答案,而且应该会让你更难打错字!

Project Dropdown

关于安全性的简要说明

每当 VSTS/TFS 连接到 Octopus 时,它就使用您为新的服务端点提供的 API 密钥。这意味着您只能看到该用户可以看到的项目、环境、渠道和租户。如果用户的权限受到了限制,那么您应该期望通过该端点进行类似的限制。

该扩展从两个不同的地方与 Octopus 对话——VSTS UI(当您配置您的构建/发布时),以及构建/发布代理本身(当构建/发布正在运行时)。

当您在 UI 中配置任务时,是 VSTS 或 TFS 服务器与 Octopus 进行通信。这意味着为了让新的下拉框正常工作,服务器必须能够访问 Octopus。如果没有,你可以直接输入详细信息。

这与您执行构建或发布时是不同的。在这种情况下,是代理连接到 Octopus。如果您不想让 Octopus 对云托管的构建代理可用,您可以使用本地代理。这个本地代理可以存在于你的网络中,因此它可以访问 Octopus 和你可能需要的其他资源。

那么下一步是什么?

我们计划很快推出一个仪表板小部件,这样您就可以在不离开 VSTS 的情况下看到您的部署状态。

在 VSTS 还有很多其他可用的扩展点,所以我们很想听听您还会发现什么有用的东西!接下来我们该看什么?

更改 runbook 流程以使用执行容器- Octopus Deploy

原文:https://octopus.com/blog/changing-a-runbook-to-use-exec-containers

Changing a runbook process to use execution containers

继我的上一篇文章workers的执行容器之后,我想将一本操作手册从直接在 worker 上运行步骤改为使用执行容器。这样做意味着我可以在我的工作机器上安装最少的软件,而不是在我用作执行容器的 Docker 映像中维护软件版本。

我正在使用项目 PetClinic Infrastructure ,它加速了 Google Cloud (GCP)基础设施,以便其他项目部署到我们的示例实例上的模式滚动空间中。

设置工作机

该项目已经使用了它启动的特定工作机,我将使用创建工作机的 runbook 并确保它安装了 Docker。为此,我需要更新在新创建的 GCP 虚拟机上用作启动脚本的引导脚本。我采用了一个在新机器上安装所需软件的现有脚本,并添加了以下内容来安装 Docker:

# Install Docker
apt-get update
apt-get -y install apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable"
apt-get update
apt-get -y install docker-ce docker-ce-cli containerd.io 

完整的脚本可以在 GitHub 上找到。

然后,为了让 runbook 使用新脚本,我更新了Project.GCP.Targets.StartupScriptUrl项目变量以指向原始版本:https://raw . githubusercontent . com/OctopusSamples/IaC/master/GCP/bootstrap/GCP-Linux-listening-tentacle-wildly . sh。该变量由Create Compute Engine Instance Worker步骤使用。

Create worker

创建 Docker 图像

我正在做的项目中的大部分工作都使用了谷歌云(GCP) 。这个项目中的所有脚本都在 PowerShell 中,对于第一次更新,我将坚持使用它。这意味着我需要一个带有 Google SDKPowerShell 的图片。这是我的文档:

FROM ubuntu:18.04

ARG DEBIAN_FRONTEND=noninteractive
ARG Azure_Cli_Version=2.9.0\*
ARG Powershell_Version=7.0.0\*

# get `wget` & utils & software-properties-common
# https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-core-on-linux?view=powershell-7#ubuntu-1804
RUN apt-get update && \ 
    apt-get install -y wget apt-utils curl && \
    apt-get install -y software-properties-common 

# get powershell for 18.04
RUN wget -q https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb && \
    dpkg -i packages-microsoft-prod.deb && \
    apt-get update && \
    add-apt-repository universe && \
    apt-get install -y powershell=${Powershell_Version}

# Install Google SDK
RUN echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list && \
  apt-get -y install apt-transport-https ca-certificates gnupg && \
  curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key --keyring /usr/share/keyrings/cloud.google.gpg add - && \
  apt-get update && apt-get -y install google-cloud-sdk

RUN apt-get clean 

我已经构建了它,并将其发布到 Docker hub 上的 Docker 存储库中:octocrock/gcp-tools

更新 runbook 以使用执行容器

我要转换成使用执行容器的操作手册是摧毁 GCP 北海巨妖。该操作手册删除了所有部署目标基础设施。

Initial runbook state

该 runbook 还会注销并删除工作机。重构的一部分是将这些步骤提取到一个单独的操作手册中。我已经创建了一个新的 runbook, Destroy Ubuntu worker 来做这个,我可以复制任何有用的步骤。

谷歌云软件开发套件

在上面的 Dockerfile 文件中,我安装了google-cloud-sdk。在 runbook 中,有一个在工作机器上安装 SDK 的步骤。因为我将使用 Docker 映像作为执行容器,所以不再需要安装 SDK。在我将这个步骤复制到我的销毁 Ubuntu worker runbook 之后,我将删除安装 GCloud SDK 步骤。

证明

runbook 有一个设置认证范围的步骤。这需要改变,因为我们需要在每个步骤上设置身份验证范围。我们可以将其重构为一个可重用的脚本模块。

我将前往 库➜脚本模块➜添加脚本模块 ,并添加以下我从激活 GCloud 服务帐户步骤中复制的 PowerShell 代码:

function Set-GCPAuth() {
  $JsonKey = $OctopusParameters["GCP.ServiceAccount.Key"]
  $JsonFile = [System.IO.Path]::GetTempFileName()

  if (Test-Path $JsonFile)
  {
      Remove-Item $JsonFile -Force
  }

  New-Item $JsonFile -Type "File" -Force

  $JsonKey | Set-Content $JsonFile
  $gcpServiceAccountEmail = $OctopusParameters["GCP.ServiceAccount.Email"]
  $gcpProjectName = $OctopusParameters["Project.GCP.ProjectName"]
  Write-Host "Activating service account $gcpServiceAccountEmail"

  Write-Host "##octopus[stderr-progress]"
  gcloud auth activate-service-account $gcpServiceAccountEmail --key-file=$JsonFile --project=$gcpProjectName --quiet
  Test-LastExit "gcloud auth activate-service-account"

  if (Test-Path $JsonFile)
  {
      Write-Host "Clearing up temp auth file"
      Remove-Item $JsonFile -Force
  }
} 

然后我只需要进入我的操作手册,通过点击Change并选择 GCP 认证来包含新创建的脚本模块:

Include script module

现在,在我将步骤复制到我的销毁 Ubuntu worker runbook 之后,我可以删除步骤激活 GCloud 服务帐户

指定执行容器

我要使用执行容器的第一步是获取 GCP·NLB 的 IP 。在使用容器之前,我必须为 DockerHub 设置一个外部进料。为此,导航至 库➜外部进给➜添加进给

docker feed

在 runbook 中,大多数步骤都在运行 GCP 脚本,但有一个步骤使用 Azure CLI 来管理 DNS。我将设置两个变量来指定 Docker 图像。

  • Project.Default.Worker.DockerImage:该值为octopusdeploy/worker-tools:1.0-ubuntu.18.04。我将使用的默认图像是 Octopus worker-tools 图像。这将运行 Azure CLI 步骤。不过,该映像没有安装 GCP 命令行界面。
  • Project.GCP.Worker.DockerImage:它的值为octocrock/gcp-tools:1.0.0,指向从上面创建的 Dockerfile 创建的图像。

获取 GCP·NLB IPrun book 步骤运行一个使用 GCloud 的脚本。我可以设置步骤来使用 Worker 池,该池包含我配置的安装了 Docker 的机器。我选择 Linux 工人池。我还通过指定Project.GCP.Worker.DockerImage变量来设置要使用的容器图像。

T32

正如我上面提到的,每个步骤都需要通过 GCP 认证,所以现在我们需要在脚本中设置它。该步骤的当前脚本代码是:

CheckForGCPSDK

$projectName = $OctopusParameters["Project.GCP.ProjectName"]
$loadbalancerIPName = $OctopusParameters["Project.GCP.LoadBalancer.ExternalIP.Name"]

Write-Host "Getting compute address matching name: $loadbalancerIPName"
Write-Host "##octopus[stderr-progress]"
$ipAddress=(& $GCloudExecutable compute addresses list --project=$projectName --filter="name=($loadbalancerIPName)" --format="get(address)" --quiet)
Test-LastExit "gcloud compute addresses list"

if( -not ([string]::IsNullOrEmpty($ipAddress))) 
{
    Write-Highlight "Found $loadbalancerIPName of: $ipAddress"
    Set-OctopusVariable -name "IPAddress" -value $ipAddress
}
else {
    Set-OctopusVariable -name "IPAddress" -value "" 

这里的第一个命令现在已经过时,正在安装 GCP 检查,因为我们知道它包含在我们正在使用的 Docker 映像中。我将用一个对为 GCP 认证创建的函数Set-GCPAuth的调用来替换它。

接下来,因为我们直接在容器中使用 GCP CLI,我可以将引用$GCloudExecutable改为gcloud

更新后的脚本如下所示:

Set-GCPAuth

$projectName = $OctopusParameters["Project.GCP.ProjectName"]
$loadbalancerIPName = $OctopusParameters["Project.GCP.LoadBalancer.ExternalIP.Name"]

Write-Host "Getting compute address matching name: $loadbalancerIPName"
Write-Host "##octopus[stderr-progress]"
$ipAddress=(& gcloud compute addresses list --project=$projectName --filter="name=($loadbalancerIPName)" --format="get(address)" --quiet)
Test-LastExit "gcloud compute addresses list"

if( -not ([string]::IsNullOrEmpty($ipAddress))) 
{
    Write-Highlight "Found $loadbalancerIPName of: $ipAddress"
    Set-OctopusVariable -name "IPAddress" -value $ipAddress
}
else {
    Set-OctopusVariable -name "IPAddress" -value ""
} 

这就是我们在容器中运行这个步骤所需要的全部内容。使用 GCP 脚本的所有其他步骤都需要进行相同的更改。

Azure CLI 步骤

此 runbook 引用的 DNS 记录在 Microsoft Azure 中管理。删除 DNS 记录的步骤需要安装了 Azure CLI 的不同 Docker 映像。

我将设置删除所有负载平衡器 DNS 记录步骤,以使用我在上面设置的Project.Default.Worker.DockerImage变量。

set exec container default

这个 Docker 图像也可以用于松弛消息步骤。

正如我上面提到的,有一个新的 run bookDestroy Ubuntu worker,它会拆掉 worker 机器。除了前面的步骤之外,我还复制了通知步骤,并将从工人池中注销工人步骤从销毁北海巨妖操作手册中移走。

worker runbook

最后,我们需要确保在销毁 GCP 北海巨妖运行手册中删除计算引擎实例时,不会从 GCP 中删除该工人。这包括更改选择计算资源的行以忽略工作机:

 $instanceList=(& gcloud compute instances list --project=$gcpProjectName --filter="tags.items=$machineTag AND -tags.items=$workerTag" --format="get(name)" --quiet) 

通过添加AND -tags.items=$workerTag子句,查询发生了变化,带有我们的 worker machine 标签的项目将不会被选择。

相反,在销毁 Ubuntu worker runbook 中,删除 worker compute 实例的步骤中的行只选择那些带有$workerTag的资源:

 $instanceList=(& $GCloudExecutable compute instances list --project=$gcpProjectName --filter="tags.items=$workerTag" --format="get(name)" --quiet) 

收尾工作

这是完成的摧毁北海巨妖的操作手册:

finished runbook

结论

将这个 runbook 转换成使用执行容器是非常直接的。我们现在可以在其他地方使用 GCP Docker 映像,相信通过更新映像的版本以在变量中选择,工具版本的更新将在所有项目中获得。为了进一步减少步骤,我们可以设置一个库变量集并全局设置值。我们甚至可以将版本设置为latest,尽管不建议这样做,因为对于何时吸收对图像所做的更改控制较少。

更改每个部署上的网站端口- Octopus Deploy

原文:https://octopus.com/blog/changing-website-port-on-each-deployment

想象你在一个网站上工作,你有一个测试环境供测试人员玩。测试可能需要一段时间,并且您希望部署得更频繁一些,因此如果站点的旧版本在新版本部署后仍然可用,那就更好了。您可能还想设置某种限制——例如,只保留最近的三个部署可用。

这里有一个直观的例子:

Deployments to different ports

假设我们的版本号自动递增,对此有一个非常简单的解决方案:要决定使用哪个端口号,我们可以获取版本号的最后一部分,并使用它来决定端口号。如果我们想要限制发布/端口的数量,我们可以对它应用操作符。我们可以在部署之前运行的 PowerShell 脚本步骤中做到这一点,并设置一个输出变量,然后我们可以在后续的打包步骤中使用它。

下面是部署过程的样子:

The deployment process

计算端口号步骤获取发布版本号,使用System.Version解析它,并获取版本号的第三部分。然后,它使用模来计算要使用的端口号:

# Example: 3.1.1
$release = $OctopusParameters['Octopus.Release.Number']
$version = New-Object System.Version $release
$build = $version.Build    # Major, Minor, Build, Revision

$sitesToKeep = 3
$port = 8080 + ($build % $sitesToKeep)

Write-Host "Website will be available on port: $port"
Set-OctopusVariable -Name "Port" -Value "$port" 

多亏了Set-OctopusVariable,变量$port现在可以在后续步骤中作为#{Octopus.Action[Calculate port number].Output.Port}使用。在配置包步骤的 IIS 功能时,我们可以使用该变量设置端口号:

The IIS binding

这就是全部了!对于每个部署,将使用不同的 IIS 网站。#{Octopus.Action[Calculate port number].Output.Port}甚至可以用在电子邮件步骤中,通知测试人员有一个新版本可供测试,以及它在哪个端口号上。为胜利输出变量和算术!

渠道-八达通部署

原文:https://octopus.com/blog/channels-walkthrough

当您部署您的项目时,您可以将项目的发布分配给特定的通道。当您希望根据您设置的标准以不同的方式处理项目的发布时,这很有用。没有渠道,您可能会发现自己为了实现多种发布策略而复制项目。当然,这将使您不得不尝试管理多个重复的项目。Channels 允许您使用一个项目,多个发布策略。

通道在下列情况下很有用:

  • 特性分支(或实验分支)被部署到测试环境中,而不是生产环境中。
  • 软件的早期访问版本将发布给早期访问计划的成员。
  • 热修复直接部署到生产环境中,然后在修复发布后部署到基础设施的其余部分。

当您实施使用通道的部署流程时,您可以将以下内容限定在特定的通道范围内:

您还可以为每个通道定义版本控制规则,以确保只有符合特定标准的版本才会被部署到特定的通道。

管理渠道

每个项目都有一个默认通道。

通过选择您正在处理的特定项目并点击通道,可从项目概述页面管理通道。

当您添加更多频道时,您会注意到它们在频道页面上按字母顺序排列。

创建新频道

  1. 在频道页面中,点击添加频道按钮。
  2. 为频道命名并添加描述。通道名称在项目中必须是唯一的。
  3. 选择渠道将使用的生命周期,或者允许渠道继承项目的默认生命周期。有关创建新生命周期的信息,请参见生命周期文档
  4. 如果您想使其成为项目的默认通道,单击默认通道复选框。
  5. 设计版本规则,该规则将用于强制将您的包的哪些版本部署到该通道。

设计版本规则

版本规则有助于为渠道选择正确的软件包版本。它们仅在手动或通过自动发布创建创建发布时使用。

  1. 新频道画面中,点击添加版本规则
  2. 选择将应用版本规则的程序包步骤(以及相应的程序包)。
  3. 版本范围字段中输入版本范围。您可以使用 NugetMaven 版本化语法来指定要包含的版本范围。

您可以使用完整的语义版本作为版本范围规范的一部分。例如:[2.0.0-alpha.1,2.0.0)将匹配所有 2.0.0 预发布版本(其中预发布组件为>= alpha.1),并将排除 2.0.0 版本。

  1. 输入您想要包含的任何预发布标签。

遵循标准 2.0.0 SemVer 语法,预发布标签是字母数字文本,可以出现在标准 major.minor.patch 模式之后,紧跟在连字符之后。为这个字段提供一个 regex 模式允许通道以一种非常灵活的方式根据它们的标签过滤包。regex 模式也将对 SemVer 构建元数据进行评估。一些例子是。

图案 描述 示例用例
[+].* 匹配任何预发布版本 通过指定在试运行时停止的生命周期,强制实施无法推向生产
^(|+.*)$ 匹配任何非预发布版本,但允许构建元数据 确保脚本步骤仅针对非预发布包运行
$ 匹配没有预发布或元数据组件的版本 官方版本被过滤掉,只包含核心版本组件(例如 1.0.0)
贝塔 匹配预发布版本,如betabeta0003 使用直接进入预发布环境的生命周期来部署预发布
贝塔 匹配标签中带有 beta anwhere 的预发布版本,如betamybeta 使用直接进入预发布环境的生命周期来部署预发布
^(?!beta)。+ 匹配不以 beta 开头的预发布版本 将除“测试版”之外的任何东西都视为功能分支包,以便您可以提供短期基础架构并部署到其中
^bugfix- 匹配任何带有*bugfix-*前缀的(例如bugfix-syscrash) 当对主线分支进行紧急 bug 修复并直接从阶段发布到生产时,绕过开发和 UAT 环境
贝塔 匹配以beta开头但不是包含beta元数据的预发布版本 防止服务器元数据意外匹配规则

如果将预发布标签添加到频道,您还需要将标签^$添加到您的default频道

  1. 点击设计规则

设计版本规则窗口将显示一个包列表,这些包将作为之前选择的部署包步骤的一部分进行部署。将用您设计的版本规则在该通道中部署的软件包版本将以绿色突出显示,而不用部署的软件包版本将以红色显示。您可以在此窗口中继续编辑版本规则。

【T2

  1. 点击保存

使用频道

一旦一个项目有一个以上的渠道,有许多地方可以使用它们。

控制部署生命周期

每个渠道都定义了在不同环境之间发布时使用哪个生命周期。您可以为每个通道选择一个生命周期,或者使用项目定义的默认生命周期。

例如,当您将预发布软件交付给您的早期访问用户时,您可以使用早期访问(或测试版)渠道,该渠道使用将软件部署到您的早期访问用户可以访问的环境中的生命周期。

修改部署流程

部署步骤可以被限制为仅在特定通道上运行。

例如,当软件的更新版本可用时,您可能决定通过电子邮件通知您的早期用户。这可以通过在您的部署流程中添加电子邮件步骤并将该步骤纳入早期访问渠道来实现。这样,该步骤将仅在一个版本被部署到早期访问渠道时运行,并且您的早期访问用户将仅接收关于相关版本的电子邮件。

变量

当您向不同的渠道发布软件时,这些渠道中的一些变量可能需要有所不同。变量可以作用于特定的通道。

部署到租户

您可以使用通道控制将哪些版本部署到某些租户。在本例中,该渠道中的版本将仅部署到标记有Early access program/2.x Beta的租户。

创建版本

Octopus Deploy 中的每个释放都必须放入一个通道中。只要有可能,八达通将为您的释放选择最佳的渠道,或者您可以手动选择一个渠道。

手动创建版本

创建发布时,您可以选择一个频道。

选择渠道将导致发布使用与渠道相关的生命周期(或者项目默认,如果渠道没有生命周期)。它还将导致部署过程和变量被修改,如上所述。

包列表允许您选择部署中涉及的每个包的版本。最新列显示与为该通道定义的版本规则相匹配的最新软件包(更多信息,请参见版本规则)。

使用构建服务器扩展或 Octopus CLI

当使用一个构建服务器扩展Octopus CLI 来创建发布时,您可以让 Octopus 自动为您的发布选择正确的通道(这是默认行为),或者自己选择一个特定的通道。

自动发布创建

为您的项目启用自动发布创建时,您需要选择一个通道(如果项目有多个通道)。

任何自动创建的版本都将使用配置的通道。此外,为渠道配置的任何版本规则都将用于决定是否自动创建版本。

例如,如果软件包的版本 3.1.0 是 Acme。Web 被推送到 Octopus 内部 NuGet 存储库,并且为自动版本创建选择的通道具有不包括 3.1.0 的版本规则范围,则不会创建任何版本。

离散渠道发布

信道用于建模的场景可以分为两类。首先,渠道控制发布版本的部署方式(不同的生命周期、部署步骤等),但是部署的版本不应该被区别对待。这方面的一个例子是 Hotfix 通道,用于选择一个旨在快速发布到产品的生命周期。

在第二种使用模式中,通过不同渠道部署的版本是不同的,应该如此对待。举个例子,假设一家公司将部署工具作为可下载的自托管产品和云托管的软件即服务产品。在这个例子中,self-hostedcloud通道不仅选择不同的生命周期和部署步骤,而且还希望在仪表板上将它们作为单独的版本来查看。

在项目➜设置中,有一个名为离散渠道发布的选项,旨在模拟该场景。

Discrete channel releases project setting

将此项设置为Treat independently from other channels将导致:

  • 要在仪表板上显示的每个频道的版本
  • 在应用发布保留策略时,每个渠道将被单独对待

下图显示了启用离散渠道发布的仪表板示例:

Discrete channel releases on dashboard

需要支持?我们是来帮助你的。

混沌工程和运行手册-章鱼部署

原文:https://octopus.com/blog/chaos-engineering-and-runbooks

Octopus 中的操作手册将 Ops 置于 DevOps 中。这篇文章是一系列文章的一部分:


Octopus 2021 Q3 包括对 Kubernetes 部署的更新支持,以及针对 Google Cloud、AWS 和 Azure 用户的 runbooks。在我们的发布公告中了解更多信息。

自动将自身重新配置到所需状态的声明式系统的承诺是:

由停止或毁坏的资源引起的大多数故障将在没有任何人工干预的情况下被纠正。

Kubernetes 就是一个很好的例子,因为当部署将集群恢复到期望的状态时,部署中被删除的单元会被重新创建。

证明一个系统可以容忍单个组件的故障是混沌工程的本质。

在他的演讲 BoF 深潜:混沌工程中,混沌工具包的创建者 Sylvain Hellegouarch 概述了如何将混沌工程应用于 Kubernetes。虽然这个演讲的技术细节很有价值,但他关于如何以及何时进行混沌实验的建议是 Octopus 用户最感兴趣的(这个演讲已经进行了 24 分钟):

不要把你的混沌工程和你的部署联系起来。正交运行该部分。

你应该总是有持续运行的混沌工程,因为在部署时做[混沌工程]的问题是你的系统存在于你的部署之间。它在进化,所以在你跑步的时候要不断改变系统。

直到最近,在 Octopus 中运行任何类型的自动化流程都意味着创建一个部署。有可能破解部署的想法来实现管理任务,但这很尴尬。

有了 Octopus Runbooks,Octopus 现在可以一流地支持与部署并行运行管理任务。Runbooks 可以访问所有现有的环境、变量、目标、步骤、安全性、审计和报告,但不受部署或生命周期概念的约束。

这使得 runbooks 非常适合运行像混沌工程工具包这样的工具。在这篇文章中,我们创建了一个非常简单的混沌实验作为运行手册,并指出运行手册为这种任务提供的优势。

Kubernetes 部署

我们首先创建一个部署流程,该流程在 Kubernetes 集群上旋转多个 pod。这是通过 Octopus 中的部署 Kubernetes 容器步骤实现的。

在下面的截图中,您可以看到我创建了一个部署,它创建了 10 个 NGINX pods。

混沌运行手册示例

除了部署之外,我还有一个 runbook,它执行一个简单的 Chaos Toolkit 实验,删除一个 pod,并使用微服务 _ 可用 _ 健康功能确保部署是健康的。

测试完成后,生成一份 PDF 报告,显示实验结果。由 chaos 工具生成的输出和报告被捕获为工件。

Set-Content -Path experiment.json -Value @"
{
    "title": "Do we remain available in face of pod going down?",
    "description": "We expect Kubernetes to handle the situation gracefully when a pod goes down",
    "tags": ["kubernetes"],
    "steady-state-hypothesis": {
        "title": "Verifying service remains healthy",
        "probes": [
            {
                "name": "all-our-microservices-should-be-healthy",
                "type": "probe",
                "tolerance": true,
                "provider": {
                    "type": "python",
                    "module": "chaosk8s.probes",
                    "func": "microservice_available_and_healthy",
                    "arguments": {
                        "name": "myapp"
                    }
                }
            }
        ]
    },
    "method": [
        {
            "type": "action",
            "name": "terminate-db-pod",
            "provider": {
                "type": "python",
                "module": "chaosk8s.pod.actions",
                "func": "terminate_pods",
                "arguments": {
                    "label_selector": "app=my-app",
                    "name_pattern": "my-app-[0-9]$",
                    "rand": true
                }
            },
            "pauses": {
                "after": 5
            }
        }
    ]
}
"@
chaos run experiment.json
docker run `
    -v "$(Get-Location):/tmp/result" `
    -it `
    chaostoolkit/reporting
New-OctopusArtifact journal.json
New-OctopusArtifact report.pdf 

下面是 runbook 执行的结果,输出 JSON 和报告 PDF 作为工件提供:

我们已经成功地实现了一个简单的混沌工程实验。

那么 runbooks 给这个过程带来了什么好处呢?

部署和操作手册并行

通过在单个项目中定义部署和操作手册,我们有一个单一的上下文来捕获部署过程和任何正在进行的测试或部署管理。

从方便的角度来看,这意味着:

  • 单一用户界面
  • 一组共享的变量
  • 紧密链接的概览仪表板
  • 合并报告

从管理的角度来看,只有一个项目需要配置安全规则和一个整合的审计日志。

Octopus dashboard open on Configuration tab and Audit page showing audit log

Runbooks 将针对部署运行的管理任务与部署本身放在一起。这使得运行、检查和跟踪基础设施的状态变得容易。

共享上下文

复杂的 Kubernetes 部署将实现名称空间来保持资源分离,并对服务帐户进行 RBAC 控制,以确保流氓部署定义不会干扰集群的其余部分。当这些权限边界在 Octopus Kubernetes 目标中表示时,它们通过确保管理任务也受到约束而很好地适应了 runbooks。

像几乎所有使用 Kubernetes 的 CLI 工具一样,Chaos Toolkit 可以从 Kubernetes config文件的细节中访问集群。Octopus 根据部署或 runbook 执行的目标提供了这个配置文件的本地副本。因此,无论部署使用专门的 Kubernetes 步骤还是 runbook 实现通用的 Kubernetes 脚本,两者都共享一个只定义一次的目标。

独立的执行工作流程

虽然共享相同的底层上下文,但 runbooks 的执行独立于部署。Runbooks 定义了自己的计划触发器,也可以在任何环境中手动运行,而不受生命周期的限制。

这是混沌工程的理想选择。正如 Sylvain 在他的演讲中提到的,应该在部署之间连续运行混沌实验来验证集群。

在下面的截图中,混沌实验每十分钟运行一次,以持续验证集群。

结论

我惊喜地发现,在 Octopus 中安装和运行混沌工具包是如此简单。在已经定义了 Kubernetes 部署的情况下,针对现有目标和环境编写另一个工具几乎不费吹灰之力。

除了简单执行工具的初始能力之外,Octopus Runbook automation 还提供了跨领域的功能,如日志记录、审计、安全、用户管理、报告和仪表板,作为一个成熟可靠的基础来扩展组织内的流程,如混沌工程。

愉快的部署!

使用 Chocolatey、PowerShell 和 Octopus Runbooks 配置 Windows 服务器- Octopus Deploy

原文:https://octopus.com/blog/chocolatey-powershell-and-runbooks

Runbooks 自动执行日常任务。其中一项任务是设置和安装 Windows 服务器。

在这篇文章中,我演示了如何使用 Octopus Runbooks 在 Windows 服务器上设置和安装开发者依赖。可以执行 runbook 来设置任意数量的 Windows 机器。

什么是巧克力?

Chocolatey 是一个用于 Windows 的软件包管理器。这是一个开源项目,它为开发人员、操作人员以及介于两者之间的所有人提供了一种管理、安装和升级其 Windows 资产中的软件的方法。

Chocolatey 致力于使 Windows 软件变得更简单、更流畅,让每个使用 Windows 计算机的人都能使用。要了解更多关于不使用 runbooks 安装 Chocolatey 的信息,请查看 Chocolatey 安装文档

使用巧克力

你不需要 runbooks 来使用 Chocolatey,它就像打开一个管理员 Windows PowerShell 窗口并运行一个脚本来安装 Google Chrome 这样的东西一样简单:

choco install googlechrome -y 

如果您想要安装多个应用程序,可以编写 PowerShell 脚本并在本地执行它们:

Write-Host "Installing Chocolatey Apps"
choco install sql-server-management-studio sql-server-2019 github-desktop git firefox -y 

您可以将它扩展到所有需要的应用程序,并在某个具有读取权限的地方对脚本进行源代码控制,以便脚本可以由用户运行或在机器供应期间运行。这将自动化您的大部分应用程序安装。

巧克力包装

Chocolatey 是一个开源工具,你可以从网站上获得很多预先配置好的软件包。然而,根据我的经验,大多数组织都编写自己的包,你也可以这样做。如果你不熟悉这个过程,请提供关于创建你自己的巧克力包的信息。

编写自己的包的主要原因是:

  • 您的公司购买了需要包含在软件包中的许可证
  • 自定义配置(如备份代理)需要从站点 B 复制到站点 A
  • 社区包可能不存在

如果你正在创建自己的包,考虑与 Chocolatey 社区共享它。

您可以从 Chocolatey 软件包中安装 Octopus。一旦有了新版本,我们会尽快发布。这是在我们的网站上发布后,从我们的TeamCity构建服务器自动生成的。阅读更多关于章鱼部署巧克力套装的信息。

要将 Octopus Deploy 安装为 Chocolatey 软件包,请运行以下命令:

choco install OctopusDeploy -y 

为什么要用 Runbooks 和 Chocolatey?

Runbooks 是我最喜欢的章鱼功能。凭借我的运营背景,我很欣赏它如何自动化平凡而耗时的运营任务。

说实话,你能安装多少次 IIS 或者 SQL 才变得繁琐且容易出错?

Chocolatey with Octopus Runbooks 还支持自助申请。您可以授予人们访问 Octopus 项目和 runbook 的权限,这样他们就可以运行自己安装的 Chocolatey 软件包。

您可以使用 runbooks 来自动化以下任务,这样您就可以专注于更有趣的问题:

这篇文章不包括在你的 Windows 服务器上安装操作系统。有很棒的工具可以用来加载最新的 Windows 桌面和服务器操作系统,这篇文章假设你在这个过程中添加了 Octopus 触手

我在准备 Windows 服务器时使用的一些工具包括:

通常,公共云提供商也提供基础架构即代码(IaC)工具,例如:

准备

开始之前,你需要在你的服务器上安装一个章鱼触手

我正在使用 Octopus 示例实例

接下来,添加一个名为 Target - Windows 的新空间。

作为新空间配置的一部分,我执行了以下操作:

Adding an Octopus environment

Adding a deployment target

  • 检查了显示我的 Windows 服务器的基础架构选项卡,它运行正常:

A Healthy deployment target

  • 创建了一个名为Computer Provisioning项目:

Adding an Octopus project

  • 创建了一个名为Computer Lifecycle生命周期并为其添加了供应环境,然后将其分配给项目:

Adding a Provisioning lifecycle

运行手册配置

首先,您需要找到项目并添加操作手册:

Adding a runbook

我制作了一本名为Install Developer Machine Dependencies的手册:

Naming the runbook

设置时区、输入和区域

设置 Windows 时,配置非默认区域可能会令人沮丧。我使用 PowerShell 脚本为所有服务器设置这个。您可以使用我下面的示例,并根据您的需求进行调整:

#Set home location to the United Kingdom
Set-WinHomeLocation 0xf2

#override language list with just English GB
$1 = New-WinUserLanguageList en-GB
$1[0].Handwriting = 1
Set-WinUserLanguageList $1 -force

#Set system local
Set-WinSystemLocale en-GB

#Set the timezone
Set-TimeZone "GMT Standard Time" 

美国东海岸的一个类似的地方看起来会是这样的:

#Set home location to the United States
Set-WinHomeLocation 0xf4

#override language list with just English US
$1 = New-WinUserLanguageList en-US
$1[0].Handwriting = 1
Set-WinUserLanguageList $1 -force

#Set system local
Set-WinSystemLocale en-US

#Set the timezone
Set-TimeZone "Eastern Time Zone" 

检查是否安装了 Chocolatey

接下来,我使用了一个社区贡献的步骤模板,名为 Chocolatey -确保安装了。这一步检查是否安装了 Chocolatey,如果没有,就安装它。

Installing Chocolatey

安装 Chocolatey 软件包步骤

Chocolatey 的 Paul Broadwith 更新了 Chocolatey 社区步骤模板,以便在一个步骤中安装所有的 Chocolatey 软件包。

我在 Windows 服务器上需要的应用程序有:

git vscode sql-server-management-studio slack github-desktop rdmfree googlechrome firefox dotnetfx dotnetcore 7zip visualstudio2019professional nordvpn lastpass-chrome lastpass docker-desktop chromium googledrive google-drive-file-stream kubernetes-helm kubernetes-cli minikube zoom notepadplusplus nugetpackageexplorer sdio virtualbox jre8 vlc python foxitreader putty.install sysinternals snagit vagrant packer terraform 

Chocolatey Package install step

安装 Chocolatey 软件包步骤参数

以下参数可用:

  • 版本(可选):您可以使用它来指定您想要安装的软件版本。如果您在每个步骤中使用了多个软件包,并且想要设置特定的软件版本,那么您需要配置 Chocolatey 安装并在附加步骤中添加版本。
  • 缓存位置(可选):您可以使用它来指定一个非默认的缓存位置。这在安装 SQL 时很有用,不需要让触手以管理员身份运行。如果不以本地管理员的身份运行触手服务,SQL 的安装可能会很棘手。您可以指定一个像C:\Octopus\Applications这样的文件夹作为缓存,本地系统用户可以完全访问它。
  • 包来源(可选):这是本步骤中最关键的参数。如果您在家里做这个,使用 Chocolatey 包存储库可能是可以接受的,这是默认设置。然而,如果你是为一家公司做这件事,请考虑使用你自己的包源代码库,比如 NexusArtifactory 或者 MyGet

Chocolatey 包资源是社区为社区而建的。如果您使用社区存储库进行企业或大规模软件包安装,您可能会受到费率限制。小心,善待社区。

您可以在日志中指定是否要禁用下载进度。我通常启用这个选项来避免数千个日志文件。最后一个选项允许您指定附加参数:

【T2 Specifying Chocolatey Parameters

安装 IIS 和依赖项

下一步是配置 IIS 及其依赖项。我使用了我们的IIS run book示例并运行了这个安装 IIS 及其所有功能。

可选步骤

我更喜欢避免 IIS 中的默认网站,所以我默认移除它。我使用名为 IIS 网站的社区步骤模板——删除然后指定Default Web Site。它在 Octopus 中删除了默认网站作为该供应操作手册的一部分。

我尽可能使用 HyperV 作为虚拟机管理程序,并在服务器配置过程中启用它。我使用这个任务的运行脚本内置模板,并使用 PowerShell 来启用 HyperV:

Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All 

最后,如果我的服务器准备好了,我会避免安装所有发布的 Windows 更新。我使用一个名为Windows-Apply Windows Updates的社区步骤模板,如果您将参数设置为True,它会自动安装并重启您的机器。

出版操作手册

运行手册可以处于草稿或已发布状态。在执行之前,您需要发布操作手册。

运行操作手册

在 Octopus 中,您拥有空间、项目、生命周期、环境、服务器和操作手册。下一步是运行和测试 runbook,以确保它能达到您的要求。

要运行操作手册:

  1. 打开 runbook 项目
  2. 选择操作,然后选择运行手册
  3. 选择您创建的运行手册

Running the runbook

  1. 选择运行
  2. 选择环境
  3. 点击运行按钮

Running runbook

您现在可以喝杯咖啡了,因为安装应用程序和依赖项需要时间。喝完咖啡后,操作手册应该就完成了。您的服务器将得到完全配置,您可以避免下一步、下一步、完成安装和应用程序配置的痛苦。

Completed runbook

现在,您可以看到服务器上安装的所有新应用程序。

在其他情况下使用操作手册

Runbooks 对于安装应用程序非常有用,不仅对于 ops、DevOps 和开发人员如此,对于其他工作类型也是如此。您可以为需要不同应用程序的其他作业类型创建额外的操作手册。例如,业务分析师可能想要 PowerBI ,而 DBA 可能希望安装 SQL Toolbelt 。您甚至可以允许人们访问 runbooks 来安装和配置预先批准的软件。

您也可以对所有的服务器使用这种方法,这样您就可以在数据库服务器上安装 SQL ,或者在 web 服务器上安装 Tomcat

安装 SQL 时,创建自己的 Chocolatey 包。SQL 的安装比较棘手,因为它需要一个管理员帐户才能快速安装,并且您需要配置默认用户、组以及数据库和日志文件的位置。

使用 Octopus 安装 SQL Developer Edition 或 SQL Express 时,无需触手以本地管理员身份运行即可完成。您仍然需要使用文件的可选位置。

另一个问题是,如果您在具有指定服务帐户的服务帐户下运行安装,默认情况下,它会使用该用户作为默认的 SQL 管理员。您需要使用该帐户进行连接,以获得访问权限。

升级巧克力套装

我最喜欢的 chocolatey 功能之一是只需一个命令就可以将软件升级到最新版本:

choco upgrade all -y 

这个命令在服务器上运行,根据您配置的 Chocolatey 存储库检查最新版本,下载新的包并安装它。这就像一个 Windows 更新,但你的巧克力包。您可以使用 runbooks 进行设置,使用部署脚本步骤,并使用升级命令:

【T2 Upgrading Chocolatey apps runbook

创建 runbook 后,选择 Run ,它将运行 Chocolatey 脚本并升级您的所有应用程序:

Upgraded Chocolatey apps

在预定触发器上升级 Chocolatey 包

随着升级的 Chocolatey runbook 的工作,您可以发布 runbook 并设置执行脚本的时间表,这很像一个 CRON 作业或一个 Windows 任务调度器

为此,选择操作下的触发器选项,并选择添加预定触发器

Add a Scheduled Trigger

新增预定触发页面,您需要输入:

  • 名称
  • 描述
  • 选择在预定触发器上运行的运行手册
  • 选择环境
  • 选择每日日程或替代日程
  • 选择执行的时间间隔
  • 选择时间表应该执行的时间

T38

这将在每天您设置的时间触发。我选择了下午 12:30,因为那是很多人去吃午饭的时间。

本文中的所有配置都可以在我们的示例实例中找到,方法是以访客身份登录并选择目标- Windows 空间。

结论

Octopus Runbooks 和 Chocolatey 配合得很好,使您能够灵活地自动安装和配置本地和云中的服务器。它们消除了在您组织的基础架构中安装数千个应用程序的需要。

要了解这一点,请观看我们的网络研讨会,利用 Runbooks 和 Chocolatey 实现基础设施&应用的自动化,由 Chocolatey 的 Paul Broadwith 主讲。

阅读我们的 Runbooks 系列的其余部分。

愉快的部署!

您的 CI/CD 流程应该作为代码文件在单个管道中实现吗?-章鱼部署

原文:https://octopus.com/blog/cicd-pipeline-as-code-one-file

CI/CD Pipeline as Code

管道即代码(PaC)的概念是由构建工具(即 Jenkins 管道、Azure 管道、GitLab CI/CD 管道等)开创的。)作为将正在开发的代码与构建和测试代码所需的脚本搭配起来的一种方式。这个过程立即吸引了开发人员,对于这个用例,PaC 是理想的,因为它创建的 PaC 逻辑和基础设施与它所搭配的代码非常接近。

作为构建和测试代码的管道

作为代码文件的管道与正在提交的代码共享相同的生存期。像单元测试一样,PaC 逻辑被设计成构建和测试它所提交的代码。正如您不会在单元测试中使用以前提交的代码来验证代码库的当前状态一样,您也不会使用以前版本的 PaC 文件来构建和测试当前的代码库。

如果 PaC 逻辑创建了任何测试基础设施,该基础设施也将是短命的,可能不会超过一个小时左右。超过一个小时的测试会让开发人员感到沮丧,通常不被认为是好的实践。

因为 PaC 逻辑仅限于构建和测试代码,所以 PaC 过程由开发人员拥有和管理,就像他们拥有和管理代码库的其余部分一样。

当 PaC 工作流被用作构建和测试周期的扩展时,它是一个自然的选择。与代码库的其余部分一样,负责 PaC 代码的是同一批人,PaC 文件随着每次提交而生或死,并且由 PaC 逻辑创建的基础结构是短命的。

将管道作为代码文件进行扩展以进行部署的挑战

自然,有一种倾向是将管道作为代码从构建和测试扩展到部署。乍一看,这似乎是 PaC 不可避免的演变,但是有很好的理由不为您的整个 CI/CD 工作流建立一个管道。

虽然构建代码并通过自动化测试来验证代码的过程最多只能用几个小时来衡量,但是将一个版本部署到产品中的过程却要长得多;以月为单位来衡量发布周期并不是没有听说过。

因此,与用于自动化测试的临时基础设施相比,部署中涉及的基础设施的生命周期也将成倍增长。

部署也是开发团队之外的许多人的责任;任何给定的发布都可能受制于产品所有者、QA、安全团队、技术作者和发布经理的流程。

安全性也成为一个问题,因为不同的团队最终负责部署过程的不同阶段。

当您将单个 PaC 文件从构建和测试扩展到部署时,它就从开发人员寻求解决编译、验证和打包代码的特定且有时间限制的问题的领域转移到满足多个团队在更长时间内的需求。现代软件交付实践鼓励我们区分发布和部署;分离概念缓解了开发和操作之间的紧张关系。

如果您的部署过程不是完全自动化的,那么根据定义,它需要人工输入,并且任何涉及多个团队几天、几周或几个月的过程将不可避免地产生大量的决策点、相互冲突的目标和围绕系统状态的不确定性。开创了 PaC 概念的以开发人员为中心的工具通常不太适合处理管理长时间运行的部署工作流的非常人性化的需求,这迫使团队试图在 PaC 文件中表示这些长时间运行的手动过程,该文件旨在支持短期和一次性的交互。

结论

尽管用单个 PaC 实现来表示整个 CI/CD 工作流很诱人,但是任何试图这样做的人都必须首先考虑,从业务的角度来看,这两个过程是否足够兼容以至于可以合并,以及托管 PaC 的工具是否充分支持部署过程的非功能性需求。

许多团队会发现这两个过程具有根本不同的时间表、责任方、报告要求和安全限制。即使 CI 和 CD 流程最终是在代码中定义的,它们也可能更容易作为单独的实体进行管理,这些实体可以通过更符合其受众的流程进行编辑、部署和保护。

浏览 DevOps 工程师手册了解更多关于 DevOps、CI/CD 和部署管道的信息。

在自定义步骤模板中使用类- Octopus Deploy

原文:https://octopus.com/blog/classes-in-custom-step-templates

powershell logo and database beside an open laptop showing code on screen

我最近决定创建一个新的 DACPAC 步骤模板,以支持 Azure Active Directory 托管身份认证(修改现有模板会带来重大变化)。

在这篇文章中,我将向您展示我是如何用 PowerShell 和一个自定义类来完成这个任务的。

准备

首先,您需要提供一个 Azure SQL Server 和一个 Azure VM。本文假设您熟悉这些类型的资源的供应,并且不会涉及这些主题。

为托管身份配置虚拟机

创建 Azure VM 后,您需要将其配置为使用托管身份特性。

导航到虚拟机资源并单击左侧窗格中的标识

在身份面板上,将上的状态切换到

虚拟机现在可以向 Azure 资源(如 Azure SQL Server)进行身份验证。

为托管身份认证配置 Azure SQL Server

有两种方法可以向 SQL server 授予托管身份的身份验证:

  • 将虚拟机管理的身份配置为 Azure SQL Server 的活动目录管理员。
  • 将受管身份作为外部登录添加到数据库。

配置 Active Directory 管理员

导航到 Azure SQL Server 资源并点击活动目录管理

由于您只能将一个帐户配置为活动目录管理员,所以我不建议在生产中使用这种方法。

活动目录管理屏幕上,点击设置管理。选择您的虚拟机并点击选择。该帐户现在将显示为选定的管理员帐户。点击保存保存您的更改。

将受管身份作为外部登录添加到数据库

使用类似 SQL Server Management Studio (SSMS)的工具,您可以执行一个脚本来授予虚拟机权限。

选择要添加权限的数据库,注意USER是虚拟机的名称,并运行以下命令:

CREATE USER [Octo-AAD-Worker] FROM EXTERNAL PROVIDER
ALTER ROLE db_owner ADD MEMBER [Octo-AAD-Worker] 

检查您是否针对正确的数据库执行脚本。

您的 VM 现在拥有所选数据库的db_owner角色。

测试连通性

要验证受管身份是否正常工作,请执行以下操作:

  1. 登录 Azure VM 并打开 PowerShell 或 PowerShell ISE。
  2. 运行以下脚本来验证它正在连接:
$response = Invoke-WebRequest -Uri 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fdatabase.windows.net%2F' -Method GET -Headers @{Metadata="true"}
$content = $response.Content | ConvertFrom-Json
$AccessToken = $content.access_token

$connectionString = "Data Source=aad-sql-test.database.windows.net; Initial Catalog=TestDB2;"

$sqlConnection = New-Object System.Data.SqlClient.SqlConnection
$sqlConnection.ConnectionString = $connectionString
$sqlConnection.AccessToken = $AccessToken

$command = $sqlConnection.CreateCommand()
$command.CommandType = [System.Data.CommandType]'Text'
$command.CommandText = "SELECT @@VERSION"

$sqlConnection.Open()

$command.ExecuteNonQuery();

$sqlConnection.Close() 

这个脚本应该成功,没有错误消息,并将返回一个-1,在这种情况下,这意味着成功。

该脚本调用内部 Azure 身份服务来返回用于对数据库服务器进行身份验证的访问令牌。

DACPAC 步骤模板

DACPAC 步骤模板使用。NET 对象与 SQL Server 交互,而不是调用sqlpackage.exe命令行程序。为了建立与数据库服务器的连接,代码创建了一个 DacServices 对象,将一个连接字符串传递给构造函数。

与上面的脚本类似,使用托管身份进行身份验证需要一个不能添加到连接字符串本身的访问令牌。上面的脚本显示, SqlConnection 对象包含属性AccessToken,该属性是为身份验证目的而分配的。然而DacServices不包含这样的属性。

要使用托管身份认证方法,您必须使用一个重载构造函数实例化一个DacServices对象,该构造函数接受一个实现Microsoft.SqlServer.Dac.IUniversalAuthProvider接口的对象。

在 PowerShell 中创建类

从 PowerShell 版本 5 开始,您可以在 PowerShell 本身中创建自定义类:

class AzureADAuth : Microsoft.SqlServer.Dac.IUniversalAuthProvider
{
    [string] GetValidAccessToken()
    {
      # Call Azure API to retrieve the token
      $response = Invoke-WebRequest -Uri 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fdatabase.windows.net%2F' -Method GET -Headers @{Metadata="true"} -UseBasicParsing
      $content = $response.Content | ConvertFrom-Json
      $azureAccessToken = $content.access_token
      # Return access token
      return $azureAccessToken
    }
} 

在代码执行之前评估的定义可能会出现问题。尽管在类定义之前加载了适当的程序集,但是如果 PowerShell 找不到类型,它就会失败。如果 DLL 在全局程序集缓存(GAC)中,它可能会工作,但是,对于 step 模板,您不能这样假设。

+ class AzureADAuth : Microsoft.SqlServer.Dac.IUniversalAuthProvider
+                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Unable to find type [Microsoft.SqlServer.Dac.IUniversalAuthProvider].
    + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : TypeNotFound 

替代类创建方法

我发现我可以通过使用 C#语法在字符串变量中定义类来创建类!我也可以在添加类型时传入依赖程序集引用:

# Define C# class
$authClass = @"
public class AzureADAuth : Microsoft.SqlServer.Dac.IUniversalAuthProvider
{
    public string GetValidAccessToken()
    {
        System.Net.HttpWebRequest request = (System.Net.HttpWebRequest)System.Net.WebRequest.Create("http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://database.windows.net/");
        request.Headers["Metadata"] = "true";
        request.Method = "GET";
        string accessToken = null;

        System.Net.HttpWebResponse response = (System.Net.HttpWebResponse)request.GetResponse();

        System.IO.StreamReader streamResponse = new System.IO.StreamReader(response.GetResponseStream());
        string stringResponse = streamResponse.ReadToEnd();
        System.Web.Script.Serialization.JavaScriptSerializer j = new System.Web.Script.Serialization.JavaScriptSerializer();
        System.Collections.Generic.Dictionary<string, string> list = (System.Collections.Generic.Dictionary<string, string>) j.Deserialize(stringResponse, typeof(System.Collections.Generic.Dictionary<string, string>));
        accessToken = list["access_token"];

        return accessToken;
    }
}
"@

# Create new object
Add-Type -TypeDefinition $authClass -ReferencedAssemblies @("$($DacDLL.FullName)", "System.Net", "System.Web.Extensions", "System.Collections")

$dacServices = New-Object Microsoft.SqlServer.Dac.DacServices $connectionString, $azureAuth 

由于 DACPAC 步骤模板很长,本文只包括与类创建相关的代码部分。这里不包括搜索$DacDLL的值。

部署

测试我们的代码显示了使用托管身份方法的成功部署。

结论

在这篇文章中,我演示了在 PowerShell 中创建定制类,包括接口的实现。希望这对你以后使用 PowerShell 的工作有所帮助。

愉快的部署!

保持环境清洁-八达通部署

原文:https://octopus.com/blog/clean-environments

这篇文章是我们 Octopus 3.4 博客系列的一部分。在我们的博客或我们的推特上关注它。

Octopus Deploy 3.4 已经发货!阅读博文今天就下载


Octopus 3.4 引入了机器策略的概念。机器策略允许在可配置的时间段过后自动删除不可用的机器。

什么是机器?

在 Octopus land(海底深处)中,我们使用术语部署目标来描述可以部署到的事物类型。它们可能是触角、SSH 端点、离线点或云区域。过去,我们有 Azure 相关的部署目标。机器策略中的功能对于一些部署目标没有意义,因此我们决定使用单词 machine 来标识功能有意义的部署目标子集。目前,机器策略最适用于触手和 SSH 部署目标。

为什么要自动删除机器?

与 Octopus 相关的基础设施的典型生命周期是:

  • 调配基础架构
  • 安装触手或 SSH
  • 向 Octopus 服务器注册触手或 SSH 端点
  • 部署!
  • 终止基础设施
  • 从 Octopus 服务器中删除端点

小规模和长寿命的基础设施在 Octopus 中无需机器策略即可轻松管理。如果 Octopus 只注册了两个触手,或者一个触手的寿命是几年,那么当一个基础设施被终止时,从 Octopus 移除触手并不会带来太大的负担。

随着规模的扩大和寿命的缩短,管理 Octopus 中的机器变得越来越困难。考虑一下自动扩展的基础架构,在这种架构中,每天有数百台机器被供应和终止。试图让 Octopus 与底层基础设施的变化保持同步,这种情况很快就变成了一场噩梦。通过使用机器策略,Octopus 可以配置为在基础架构终止时自动移除机器。

配置 Octopus 自动删除机器

要配置机器策略以自动删除机器:

在环境屏幕上查找机器策略:

Machine Policies

通过选择添加机器策略或选择默认机器策略来创建新的机器策略:

Create a Machine Policy

将“清理不可用的机器”设置更改为“自动删除不可用的机器”。将“不可用小时数”更改为计算机在被删除之前可以不可用的最小小时数:

Automatically delete machines

现在,当分配了此计算机策略的计算机在指定的小时数内不可用时,将被永久删除。

将机器策略分配给机器

可以通过 Octopus 门户将机器策略分配给机器,方法是从“环境”页面中选择一台机器,然后使用策略下拉菜单选择一个机器策略:

Assign a Machine Policy

使用--policy参数注册机器时,命令行可用于分配机器策略:

Tentacle.exe register-with --instance "Tentacle" --policy "Transient machines" --server "http://YOUR_OCTOPUS" --apiKey="API-YOUR_API_KEY" --role "web-server" --environment "Staging" --comms-style TentaclePassive 

还有一个名为MachinePolicyId的属性可以在MachineResource上设置,因此可以通过 Octopus API 或 Octopus 来分配机器策略。客户:

$machine = New-Object Octopus.Client.Model.MachineResource
$machine.Endpoint = $tentacleEndpoint
$machine.Name = $machineName
$machine.MachinePolicyId = $machinePolicyId
$machine.EnvironmentIds.Add($environmentId) 
$machine.Roles.Add($role) 

如果创建了一台机器,但尚未指定机器策略,则将为该机器分配默认的机器策略。

它是如何工作的

机器清理使用运行状况检查的结果作为标准来确定是否应该删除机器。当一个机器的生命值被取走,章鱼无法联系机器时,它的生命值状态会变成Unavailable。Octopus 每 5 分钟检查一次Unavailable机器,以确定应该删除的机器。如果机器的机器策略已配置为自动删除Unavailable台机器,并且机器已Unavailable超过机器策略中指定的时间,则机器将从 Octopus 中永久删除。Octopus 将只删除已启用的机器,因此如果一台机器即将脱机并且不应该被删除,请将其禁用。

进一步的信息

从我们的环境清理指南中了解更多关于自动删除机器的信息。使用我们的深入文档探索机器政策。如果您有意见、建议或只想打声招呼,请联系

使用 Octopus API - Octopus Deploy 克隆空间

原文:https://octopus.com/blog/cloning-a-space-with-octopus-api

Cloning a space using the Octopus API

我很高兴为我与 Redgate网络研讨会收集了分支示例。我在 Redgate 的合作伙伴非常喜欢它,他们想要一个自己的副本来四处看看。那个示例项目位于 Octopus Cloud 上,所以我不能使用迁移器,老实说,我也不想为这个特定的用例这么做。对于那些不知道的人来说,Octopus Web 门户中所做的一切都涉及 Octopus Deploy RESTful API。我开始编写一个 PowerShell 脚本,使用 Octopus API 将一个项目从 sample 实例复制到 Redgate 的实例中。在这篇文章中,我分享了我是如何做到这一点的,以及你可以做些什么来编写自己的脚本。

TL;DR;我在章鱼样本组织下的 GitHub 上发布了我的脚本。接下来,我们的团队将在内部使用这个脚本来加快在那个实例中创建样本的速度。你可以自己试一试,叉一叉,修改一下,满足你公司的需求。

捆绑的 Octopus 数据迁移工具完全按照它在 tin 上显示的那样做。它帮助将 Octopus 项目从一个实例迁移到另一个实例。但是我经常看到人们试图将工具用于非设计用途的用例。这是一种钝器。您给它一个项目名称,它就变成了一个巨大的真空吸尘器,吸取关于该项目的所有东西,并导出它,准备导入到另一个实例中。

对于我的用例,迁移器有几个限制:

  • 它必须与 Octopus Deploy 实例运行在同一个服务器上,因为它需要直接访问数据库来检索敏感变量。它被设计成只为迁移工作,这意味着所有敏感的变量都会随之而来。然而,我无权访问云实例(它们运行在 Kubernetes 容器中)。
  • 对目标实例上部署流程的任何更新都将被覆盖。想象一下,我在 Redgate 的朋友选择修改他们的部署过程来满足他们自己的特定需求。他们要求重新同步。迁移者会进来用核武器摧毁他们的任何改变。
  • 它不保留部署过程中的现有步骤。如果我在 Redgate 的朋友选择更改工人池名称或变量名,一个新的同步也会覆盖它。
  • 它为后续运行导出了太多数据。在初始运行之后,我不再关心生命周期阶段、工人池、环境或任何类似的东西。我只想确保他们拥有部署流程中的最新步骤以及任何缺失的变量。

有了 PowerShell、Octopus Deploy API 和一些 if/then 语句(好的,有很多 if/then 语句),我可以实现 90-95%的目标。

Octopus 部署 API 的局限性

Octopus Deploy API 无法解密敏感变量。这包括帐户变量、标记为敏感的变量以及外部源用户名和密码。对于我的用例,所有这些都是完全可以接受的。我不想把所有敏感信息都给雷德盖特。他们有自己的数据库服务器,自己的 AWS 云账号等等。

像任何 RESTful API 一样,它不太擅长以高效的方式处理大量的 BLOB 数据,比如包、任务日志、工件、项目图像和租户图像。最后,这对于我的用例来说很好,因为这些项目不是我想克隆到 Redgate 实例的东西。他们想要这个过程,但是他们不关心版本、部署、快照等等。

脚本的目标

如果您编写的代码试图满足所有人的所有需求,那么您最终得到的代码几乎不能为任何人工作,或者它是一个没有人能够维护或理解的复杂的混乱。

我剧本的目标是:

  1. 支持多次运行-我应该能够运行脚本多次,而不是让它核武器和铺平一切视线。
  2. 挑选要克隆的项目——一开始,我想克隆很多数据来打基础。之后,我只想克隆数据的一个子集。
  3. 保持简单愚蠢(吻)——不要试图依赖的树走路。按名称查找项目,如果找到匹配项,很好,就使用它。假设数据不在那里,并处理它。
  4. 关注这个用例——我不得不多次提醒自己只关注特定的用例。不要试图让它适用于所有场景。只做好一个场景。

API 基础

当我写剧本的时候,我注意到了一些通用规则,我想分享一下以助一臂之力。

相同的属性集

所有核心对象(环境、项目、工人等。)具有相同的三个属性:

它们将被命名为。我编写了几个助手函数来帮助在列表中按 ID 或名称查找项目:

function Get-OctopusItemByName
{
    param (
        $ItemList,
        $ItemName
        )    

    return ($ItemList | Where-Object {$_.Name -eq $ItemName})
}

function Get-OctopusItemById
{
    param (
        $ItemList,
        $ItemId
        ) 

    Write-VerboseOutput "Attempting to find $ItemId in the item list of $($ItemList.Length) item(s)"

    foreach($item in $ItemList)
    {
        Write-VerboseOutput "Checking to see if $($item.Id) matches with $ItemId"
        if ($item.Id -eq $ItemId)
        {
            Write-VerboseOutput "The Ids match, return the item $($item.Name)"
            return $item
        }
    }

    Write-VerboseOutput "No match found returning null"
    return $null    
} 

从 Octopus Deploy API 返回的所有对象都有一个 Links 对象。当您需要更新现有项目或需要转到列表中的下一页时,这非常有用。

一致的端点参数

对于我需要创建或查询的项目,我注意到 Octopus Deploy 的 API 遵循相同的规则集。

因此,我能够将一些辅助函数放在一起查询 Octopus API:

function Get-OctopusUrl
{
    param (
        $EndPoint,        
        $SpaceId,
        $OctopusUrl
    )  

    if ($EndPoint -match "/api")
    {        
        return "$OctopusUrl/$endPoint"
    }

    if ([string]::IsNullOrWhiteSpace($SpaceId))
    {
        return "$OctopusUrl/api/$EndPoint"
    }

    return "$OctopusUrl/api/$spaceId/$EndPoint"
}

function Invoke-OctopusApi
{
    param
    (
        $url,
        $apiKey,
        $method,
        $item
    )

    try 
    {
        Write-VerboseOutput "Invoking $method $url"

        if ($null -eq $item)
        {            
            return Invoke-RestMethod -Method $method -Uri $url -Headers @{"X-Octopus-ApiKey"="$ApiKey"}
        }

        $body = $item | ConvertTo-Json -Depth 10
        Write-VerboseOutput $body
        return Invoke-RestMethod -Method $method -Uri $url -Headers @{"X-Octopus-ApiKey"="$ApiKey"} -Body $body
    }
    catch 
    {
        $result = $_.Exception.Response.GetResponseStream()
        $reader = New-Object System.IO.StreamReader($result)
        $reader.BaseStream.Position = 0
        $reader.DiscardBufferedData()
        $responseBody = $reader.ReadToEnd();
        Write-VerboseOutput -Message "Error calling $url $($_.Exception.Message) StatusCode: $($_.Exception.Response.StatusCode.value__ ) StatusDescription: $($_.Exception.Response.StatusDescription) $responseBody"        
    }

    Throw "There was an error calling the Octopus API please check the log for more details"
}

Function Get-OctopusApiItemList
{
    param (
        $EndPoint,
        $ApiKey,
        $SpaceId,
        $OctopusUrl
    )    

    $url = Get-OctopusUrl -EndPoint $EndPoint -SpaceId $SpaceId -OctopusUrl $OctopusUrl    

    $results = Invoke-OctopusApi -Method "Get" -Url $url -apiKey $ApiKey

    Write-VerboseOutput "$url returned a list with $($results.Items.Length) item(s)" 

    return $results.Items
}

Function Get-OctopusApi
{
    param (
        $EndPoint,
        $ApiKey,
        $SpaceId,
        $OctopusUrl
    )    

    $url = Get-OctopusUrl -EndPoint $EndPoint -SpaceId $SpaceId -OctopusUrl $OctopusUrl    

    $results = Invoke-OctopusApi -Method "Get" -Url $url -apiKey $ApiKey

    return $results
}

Function Save-OctopusApi
{
    param (
        $EndPoint,
        $ApiKey,
        $Method,
        $Item,
        $SpaceId,
        $OctopusUrl
    )

    $url = Get-OctopusUrl -EndPoint $EndPoint -SpaceId $SpaceId -OctopusUrl $OctopusUrl 

    $results = Invoke-OctopusApi -Method $Method -Url $url -apiKey $ApiKey -item $item

    return $results
}

function Save-OctopusApiItem
{
    param(
        $Item,
        $Endpoint,
        $ApiKey,
        $SpaceId,
        $OctopusUrl
    )    

    $method = "POST"

    if ($null -ne $Item.Id)    
    {
        Write-VerboseOutput "Item has id, updating method call to PUT"
        $method = "Put"
        $endPoint = "$endPoint/$($Item.Id)"
    }

    $results = Save-OctopusApi -EndPoint $Endpoint $method $method -Item $Item -ApiKey $ApiKey -OctopusUrl $OctopusUrl -SpaceId $SpaceId

    Write-VerboseOutput $results

    return $results
} 

不要忘记伐木

起初,我没有太多的记录。这是一个错误,但我很快意识到我需要记录一切。不仅如此,记录发送到 API 的 JSON 请求。这使得调试进行得更快。

我想让用户很容易知道一般的信息性消息、警告和错误。我用简单的Green表示好,Yellow表示警告,Red表示坏。我还添加了一个详细日志,将所有内容写到一个文件中。我创建了几个有用的函数来帮助日志记录:

$currentDate = Get-Date
$currentDateFormatted = $currentDate.ToString("yyyy_MM_dd_HH_mm")
$logPath = "$PSScriptRoot\Log_$currentDateFormatted.txt"
$cleanupLogPath = "$PSScriptRoot\CleanUp_$currentDateFormatted.txt"

function Write-VerboseOutput
{
    param($message)

    Add-Content -Value $message -Path $logPath    
}

function Write-GreenOutput
{
    param($message)

    Write-Host $message -ForegroundColor Green
    Write-VerboseOutput $message    
}

function Write-YellowOutput
{
    param($message)

    Write-Host $message -ForegroundColor Yellow    
    Write-VerboseOutput $message
}

function Write-RedOutput
{
    param ($message)

    Write-Host $message -ForegroundColor Red
    Write-VerboseOutput $message
}

function Write-CleanUpOutput
{
    param($message)

    Write-YellowOutput $message
    Add-Content -Value $message -Path $cleanupLogPath
} 

翻译 ID 值

源实例的Production可能有Environments-123,而目的地被设置为Environments-555。要进行翻译,您需要:

  1. 将源 ID 转换为名称。
  2. 将名称转换为目的地 ID。

同样,我为此编写了一个助手函数:

function Convert-SourceIdToDestinationId
{
    param(
        $SourceList,
        $DestinationList,
        $IdValue
    )

    Write-VerboseOutput "Getting Name of $IdValue"
    $sourceItem = Get-OctopusItemById -ItemList $SourceList -ItemId $IdValue
    Write-VerboseOutput "The name of $IdValue is $($sourceItem.Name)"

    Write-VerboseOutput "Attempting to find $($sourceItem.Name) in Destination List"
    $destinationItem = Get-OctopusItemByName -ItemName $sourceItem.Name -ItemList $DestinationList
    Write-VerboseOutput "The destination id for $($sourceItem.Name) is $($destinationItem.Id)"

    if ($null -eq $destinationItem)
    {
        return $null
    }
    else
    {
        return $destinationItem.Id
    }
} 

走阻力最小的路

对于您的脚本,首先关注最重要的项目。完美是完成的敌人。

我在 API 脚本中遇到了一些棘手的问题。具体围绕:

  • 脚本包引用
  • 将手动干预分配给团队
  • 租户变量
  • 处理不同版本的 Octopus Deploy(从 2020.2 克隆到 2020.1)

我选择走阻力最小的路。我要么写代码删除项(包引用),设置默认值(手动干预),跳过它们(租户变量),要么添加一个 guard 子句阻止它运行(不同版本的 Octopus Deploy)。如果我对这些特定的项目有足够高的需求,我可能会考虑添加它们。但是现在,让他们工作的复杂性与回报的对比是不值得的。

收集数据

我的目标是将该项目从我们的 samples 实例克隆到一个新实例上。一个项目不仅仅是一个项目。它依赖于 Octopus 中的许多其他数据,所以我将重点放在我在建立新空间或新实例时必须创建的项目上。

这是我得到的数据列表:

  • 环境
  • 工人池(不是工人,只是池)
  • 项目组
  • 外部源
  • 租户标签
  • 步骤模板(社区和自定义步骤模板)
  • 帐目
  • 库变量集
  • 生活过程
  • 项目
    • 设置
    • 部署流程
    • 运行手册
    • 变量
  • 租户(无租户变量)

我故意在清单上遗漏了一些关键项目。有些可能会让你吃惊:

  • 目标
  • 工人
  • 部署
  • 包装
  • 用户
  • 角色
  • 外部身份验证提供者

我的用例是将一个项目从我的 samples 实例复制到 Redgate 运行的一个新实例中。上面列表中的每一项在我们的两个实例中都是不同的。我想保持简单。我将它们标记为已排除,然后继续。

敏感值和虚拟数据

我知道我会碰到一些敏感的价值观。我也知道我得不到实际价值,也不想得到。然而,在很多情况下,特别是账户,他们希望输入的东西。我在脚本中创建了以下两条规则:

  1. 如果数据不存在,输入虚拟数据。如果可能,使用DUMMY VALUE
  2. 如果数据确实存在,就不要管它。不要试图覆盖它。

过滤数据

我想克隆的每个项目基本上都遵循了这个工作流程。我将以环境为例:

  1. 加载源中的所有环境。
  2. 加载目标中的所有环境。
  3. 使用用户提供的过滤器过滤源环境。
  4. 将过滤后的列表与目的地进行比较,如果存在,则跳过,如果不存在,则创建。

对于一些物体来说,这要复杂得多。我必须转换某些属性,或者删除某些属性。在某些情况下,我想覆盖现有的数据。

保持过滤器简单,但功能强大,对我来说至关重要。我选择了以下方式:

  • All -关键字,将尝试克隆该对象的所有数据。
  • 逗号分隔列表——指定一个 CSV,比如test,staging,production,它将克隆这三个特定的值。
  • 使用正则表达式的通配符支持——可以与 CSV 结合使用,因此您可以为变量集指定AWS*,Notification,这将包含所有以 AWS 和通知变量集开头的变量集。

变量和部署流程

变量和部署过程是克隆中最复杂的部分。最初的运行是直接的,把源中的内容复制到目的地。

随后的运行是棘手的。我从三个基本规则开始:

  1. Redgate 很可能修复了最初由我的脚本创建的所有虚拟数据。
  2. Redgate 不愿意一遍又一遍地修复我的脚本创建的相同虚拟数据。
  3. Redgate 很可能会以某种方式改变变量和部署过程。

你可以用我的用户替换 Redgate 作为你自己的脚本。我想对你来说也是如此。

源项目是真理的来源。我写这个脚本是为了遵循这些规则:

  1. 遍历源数据。对于每一项,检查数据是否存在于目标上,如果不存在,从源克隆。否则,使用现有数据。
  2. 遍历目标数据。对于每一项,检查源代码,看它是否存在。如果源中不存在该项目,则将其添加回来。

我们用一个实实在在的例子。我在目标实例上有一个部署过程。我的目标实例上的部署流程有一个不在源中的新步骤。

为了清楚起见,目标实例使用黑暗模式。源实例正在使用光照模式。

我的源实例上的流程有步骤 3,它没有出现在目标流程中。

同步运行后的过程遵循上述规则,现在看起来如下所示。来自源的新步骤 3 被添加到适当的位置,然后仅在目标上找到的新步骤被添加到后面。

扩展使用案例

当我写完我的脚本时,我意识到我可以支持迁移器之外的许多用例。这些使用案例包括:

  • 从云复制到自托管——我家里有一个管理程序,有一个我可以克隆到的地方将使本地测试变得更容易。
  • 从自托管复制到云——在我完成修改后,从我的虚拟机管理程序复制到示例实例。
  • 创建新空间时复制默认变量集——客户成功团队在我们的示例实例中创建了许多新空间。
  • 将一个巨大的空间分割成几个小空间——我们已经在我们的示例实例中这样做了几次,有了它会使事情变得容易得多。
  • 保持父/子项目过程同步——这是我们几个示例中常见的一个,我们将克隆一个项目,并有一个不同的目标(使用 AWS 而不是 Azure)。通过脚本保持两个项目同步将使我们的生活更加轻松。

帮助入门的示例

我意识到从头开始写剧本是一项艰巨的任务。这就是为什么我发布了我的团队 Customer Success 用来管理我们的示例实例的脚本。你可以在 Octopus Samples 组织中找到那个项目。

我希望你会叉回购,并修改它,以满足您的特定需求。也许您想要克隆目标,或者也许您想要排除所有变量。这不是我们计划对该脚本进行的更改,但是通过分叉,您可以对该脚本做任何您喜欢的事情。希望该脚本有足够的内容来帮助您满足基本的克隆需求。

下次再见,愉快的部署!

Octopus 云连接中断-报告和学习- Octopus 部署

原文:https://octopus.com/blog/cloud-connectivity-disruption-report-learnings-feb23

世界标准时间 2023 年 1 月 25 日星期三上午 07:05 至 12:43 之间,部分地区的章鱼云客户遇到了网络连接问题。这导致他们在尝试连接到 Octopus 云实例时出现长时间的网络延迟和/或超时,并影响 Octopus Deploy 的一些功能。特别是,如果一些 Octopus 部署操作涉及受影响网络上的网络调用,则这些操作将会失败。这次中断影响了章鱼云、Octopus.com/OctopusID,和 octopus.com 控制中心。

我们的上游云提供商 Azure 的网络中断导致了中断。

在本帖中,我们详细讲述了发生的事情和我们学到的知识。

由于这一事件,我们正在加强与 Azure 的关系,并改进对其可用性的监控。

按键计时

事件 期间
检测时间 40 分钟
事件申报时间 69 分钟
解决问题的时间 4 小时 32 分钟

事故时间表

2023 年 1 月 25 日星期三(以下所有日期和时间均以 UTC 表示)

7:45: 一名 Octopus 工程师注意到,由于请求超时,对 Azure 门户的访问中断。Azure 的状态页面对于所有服务仍然是绿色的(工作中)。该工程师发现,不管状态页面如何,连接问题很可能是在 Azure 的内部网络中,因为它并不总是准确地反映当前状态。他们还指出,可能是网络问题导致 Azure 无法更新他们的状态页面。该工程师注意到 octopus.com/OctopusID,使用的数据库负载增加,如果这种情况持续下去,客户可能会面临登录问题。

7:51: 一名支持工程师注意到,大约一小时前,一名客户报告无法使用 OctopusID 登录。

7:51 - 8:14: 八达通工程师继续监控内部服务,并注意到外部服务的类似问题。所有观察到的问题都与云提供商网络中断一致。

8:14: 一名 Octopus 工程师宣布发生事故,启动事故响应流程。

8:14 - 8:19: 待命工程师调查了客户影响并更新了我们的状态页面以反映 octopus.com/OctopusID,章鱼云和云控制中心的部分中断。他们还暂停了任何可能加剧事故的内部流程(例如,将 Octopus 服务器部署到 Octopus Cloud)。

8:33: 一名随叫随到的工程师指出,Azure 的状态页面现在显示 Azure 正在调查所有地区的重大网络问题。

8:33 - 8:42: 待命工程师继续监控上游网络中断,并指导支持团队将受影响的客户指向我们的状态页面。

8:42: 基于持续停电和重大客户影响,在八达通的状态页面上,停电升级为重大停电。

9 时 33 分 - 9 时 59 分:待命工程师注意到来自 Azure 的状态更新,表明中断的原因已经确定,事件的解决正在进行中。

10:57: 待命工程师与支持工程师确认,客户开始看到他们与受影响的八达通服务的连接恢复。工程师证实,同样受到上游断电影响的内部服务正在开始恢复。

11:37: 事件已宣布解决,状态页面也相应更新。

技术细节

众所周知,上游云提供商停机很难为客户解决,因为这个问题通常超出了客户的控制范围。

Azure 的内部网络承载着运行在云中的各种 Octopus 服务的流量。在这种情况下,受影响的服务是章鱼云,octopus.com/OctopusID 和云控制中心。

影响主要是对外和跨区域交通。我们依赖该网络来承载该流量,并且不打算为该特定云提供商服务构建后备选项。

Azure 的技术细节

我们确定,对 Microsoft 广域网(WAN)所做的更改会影响互联网上的客户端与 Azure 之间的连接、跨区域的连接以及通过 ExpressRoute 的跨场所连接。作为更新 WAN 路由器上 IP 地址的计划更改的一部分,向该路由器发出的命令导致它向 WAN 中的所有其它路由器发送消息,这导致所有路由器重新计算它们的邻接和转发表。在这个重新计算过程中,路由器无法正确转发通过它们的数据包。导致问题的命令在不同的网络设备上有不同的行为,并且该命令没有在执行该命令的路由器上使用我们的完整认证流程进行审查。"

来源:https://status.azure.com/en-us/status/history/,2023 年 1 月 31 日星期二检索。

Octopus 非常重视服务可用性。尽管上游云提供商停机困难重重,但我们会全面审查和补救发生的任何停机。我们这样做是为了不断改进和保持尽可能最好的服务。

云提供商关系

当向 Azure 提出问题时,我们与他们一起审查我们的上报流程选项。我们打算通过将我们的内部升级流程与他们的建议保持一致来跟进此事,因此我们简化了与 Azure 关于停机的所有沟通。

内部监控

我们的可用性监控发现了这个问题,但是因为网络问题是偶发性的,所以故障没有达到我们内部的警报阈值。我们正在审查我们的事件响应指南,以更密切地监控我们的云提供商的可用性仪表板。

结论

我们为任何中断和不便向我们的顾客道歉。我们已经采取措施来完善我们与上游云提供商的问题上报流程。我们还在审查我们的仪器和 Azure 网络中断警报,以便我们能够尽早有效地向客户传达影响,使他们能够专注于愉快的部署。

云-原生数据库部署认证- Octopus 部署

原文:https://octopus.com/blog/cloud-native-database-template-authorization

Amazon Web Services (AWS)、Azure 和 Google Cloud Platform (GCP)引入了无密码身份验证机制,可以为虚拟机(VM)等资源分配一个身份(Azure)、一个服务帐户(GCP)或一个角色(AWS),这些身份或角色可用于对数据库服务器实例等其他资源进行身份验证。

Octopus Deploy 有几个内置步骤支持使用这种方法对资源进行身份验证,但是,数据库部署几乎完全是使用社区步骤模板完成的。

Octopus 社区一直在努力更新模板以支持云原生身份验证。

在这篇文章中,我列出了更新了云提供商认证功能的模板。

支持云原生身份验证方法的数据库技术

只有云提供商提供的一些数据库技术支持云原生身份验证。下面,我按提供商列出了支持的技术:

自动警报系统

  • 亚马逊极光
  • MariaDB
  • 关系型数据库
  • 一种数据库系统

蔚蓝的

  • 天蓝色宇宙数据库
  • 关系型数据库
  • 一种数据库系统
  • Microsoft SQL Server

GCP

GCP 和 AWS 都支持 Microsoft SQL Server 的 Windows 身份验证,但是,服务器必须连接到云提供商上的 Active Directory 域。

Amazon Aurora 和 Azure Cosmos 尚未使用本文中列出的模板进行测试。

支持云身份验证的 Step 模板

以下社区步骤模板已更新,支持使用云原生数据库身份验证:

除了SQL-Deploy DAC PAC with AAD Auth support之外,上述模板可在 Windows (PowerShell、PowerShell Core)和 Linux (PowerShell Core)上运行,并且已更新为支持以下类型的身份验证方法选择器:

  • AWS EC2 IAM 职责
  • Azure 托管身份
  • GCP 服务帐户
  • 用户名\密码
  • Windows 身份验证

Authentication Selector

Flyway 数据库迁移仅与 PowerShell Core 兼容。

SQL -部署具有 AAD 身份验证支持的 DACPAC

使用SQL-Deploy DAC PAC with AAD Auth support模板的云原生身份验证仅限于 Azure 云提供商,并支持以下方法:

  • Azure Active Directory 集成版
  • Azure Active Directory 托管身份
  • Azure 活动目录用户名/密码
  • SQL 身份验证
  • Windows 集成版

SQL - Deploy DACPAC with AAD Auth support Authentication Selector

SQL-Deploy DAC PAC with AAD Auth support用 PowerShell 编写,利用了。NET 标准 DLL 文件,这些文件是 SQL Server 安装或 SQL Server PowerShell 模块的一部分。正因为如此,它不能在 Linux 操作系统上运行。

结论

Octopus 社区认识到使用云提供商提供的认证机制的优势。

在这篇文章中,我列出了更新了云提供商认证功能的模板。

了解更多信息

我们的示例实例更新了使用云原生身份验证的示例:

愉快的部署!

云形成,野火和部署 Maven 工件-章鱼部署

原文:https://octopus.com/blog/cloudformation-and-java

在过去的几个月里,Octopus 增加了许多新特性,允许您部署 Java 应用程序,使用 Maven feeds 中的工件,以及部署 AWS CloudFormation 模板。在这篇博文中,我们将探讨如何将所有这些元素结合起来,在基于云的环境中部署 Java 应用程序。

Maven 提要

我们将要部署的应用程序将来自 Maven central。要访问它,我们需要在 Octopus 中配置 Maven 提要。这在库➜外部进料中完成。Maven Central 的网址是 https://repo.maven.apache.org/maven2/的。

Maven Feed

AWS 帐户

Octopus CloudFormation 步骤通过 AWS 帐户向 AWS 进行身份验证。这些账户由基础设施➜账户➜亚马逊网络服务账户管理。你可以通过我们的文档找到更多关于创建 AWS 账户的信息,记住账户需要有一些通用权限才能有效地用于部署 CloudFormation 模板。

AWS Account

SSH 帐户

我们还需要配置一个帐户,用于通过 SSH 连接到 WildFly EC2 实例。这些账户由基础设施➜账户➜ SSH 密钥对管理。在这里,我们将创建一个 SSH 帐户,用户名为bitnami(因为这是 Bitnami AMIs 配置的用户名——下面将详细介绍)和 PEM 文件,您需要在 AWS 中创建该文件并将其分配给 EC2 映像。你可以在他们的文档中找到更多关于创建 AWS 密钥对的信息。

SSH Account

机器政策

我们需要配置的最后一个全局 Octopus 设置是机器策略,可在基础设施➜机器策略下访问。

与轮询触角不同,SSH 目标必须有准确的 IP 地址或主机名才能参与 Octopus 部署。然而,将由 CloudFormation 模板创建的 EC2 实例没有固定的 IP 地址,并且当 EC2 实例停止并再次启动时,它们所具有的 IP 地址将会改变。这意味着我们需要做两件事来确保我们的 EC2 实例在 Octopus 中正确配置:

  1. 每次 EC2 实例启动时,将 EC2 实例添加到 Octopus 中(如果它尚未注册)。
  2. 让 Octopus 清理任何未通过健康检查的部署目标。

我们将在后面的章节中用 CloudFormation 模板中的一些脚本来处理第一步。通过编辑默认机器策略中的Clean Up Unavailable Deployment Targets部分来启用Automatically delete unavailable machines来配置步骤 2。

Machine Policy

野花 AMI

我们将利用 Bitnami 提供的 ami 作为我们 CloudFormation 模板的基础。Bitnami 提供了大量免费的最新图像,并预装了流行的开源应用程序,这使我们能够快速启动并运行 EC2 WildFly 实例。

我发现获得 AMI ID 的最简单方法是在 AWS 控制台的Public images下搜索WildFly。请记住,这些 AMI IDs 是特定于地区的,所以ami-5069332a的 ID 只在北弗吉尼亚州有效。

Bitnami AMIs

云形成模板

拥有一个 AMI 是成功的一半。另一半是从中构建 EC2 实例,为此我们将利用 Octopus 2018.2 中引入的 CloudFormation 步骤。

这个 CloudFormation 模板必须执行许多步骤:

  1. 将 AMI 部署为 EC2 实例。
  2. 配置一些标准标签。
  3. 安装支持 DotNET Core 2 应用程序所需的软件包。
  4. 配置文件系统权限以允许 WildFly 静默身份验证。
  5. 向 Octopus 服务器注册 EC2 实例。

这是完整的模板

AWSTemplateFormatVersion: 2010-09-09
Resources:
  WildFly:
    Type: 'AWS::EC2::Instance'
    Properties:
      ImageId: ami-5069332a
      InstanceType: m3.medium
      KeyName: DukeLegion
      Tags:
        -
          Key: Appplication
          Value: WildFly
        -
          Key: Domain
          Value: None
        -
          Key: Environment
          Value: Test
        -
          Key: LifeTime
          Value: Transient
        -
          Key: Name
          Value: WildFly
        -
          Key: OS
          Value: Linux
        -
          Key: OwnerContact
          Value: "#{Contact}"
        -
          Key: Purpose
          Value: Support Test Instance
        -
          Key: Source
          Value: CloudForation Script in Octopus Deploy
        -
          Key: scheduler:ec2-startstop
          Value: true
      UserData:
        Fn::Base64: |
          #cloud-boothook
          #!/bin/bash
          echo "Starting" > /tmp/cloudhook
          sudo apt-get --assume-yes update
          sudo apt-get --assume-yes install curl libunwind8 gettext apt-transport-https jq
          getent group deployment || sudo groupadd deployment
          sudo usermod -a -G deployment wildfly
          sudo usermod -a -G deployment bitnami
          echo "Editing permissions" >> /tmp/cloudhook
          sudo chgrp deployment /opt/bitnami/wildfly/standalone/tmp/auth
          sudo chmod 775 /opt/bitnami/wildfly/standalone/tmp/auth
          role="WildFly"
          serverUrl="#{ServerURL}"
          apiKey="#{APIKey}"
          environment="#{Environment}"
          accountId="#{AccountID}"
          localIp=$(curl -s http://169.254.169.254/latest/meta-data/public-hostname)
          existing=$(wget -O- --header="X-Octopus-ApiKey: $apiKey" ${serverUrl}/api/machines/all | jq ".[] | select(.Name==\"$localIp\") | .Id" -r)
          if [ -z "${existing}" ]; then
            fingerprint=$(sudo ssh-keygen -l -E md5 -f /etc/ssh/ssh_host_rsa_key.pub | cut -d' ' -f2 | cut -b 5-)
            environmentId=$(wget --header="X-Octopus-ApiKey: $apiKey" -O- ${serverUrl}/api/environments?take=100 | jq ".Items[] | select(.Name==\"${environment}\") | .Id" -r)
            machineId=$(wget --header="X-Octopus-ApiKey: $apiKey" --post-data "{\"Endpoint\": {\"DotNetCorePlatform\":\"linux-x64\", \"CommunicationStyle\":\"Ssh\",\"AccountType\":\"SshKeyPair\",\"AccountId\":\"$accountId\",\"Host\":\"$localIp\",\"Port\":\"22\",\"Fingerprint\":\"$fingerprint\"},\"EnvironmentIds\":[\"$environmentId\"],\"Name\":\"$localIp\",\"Roles\":[\"${role}\"]}" -O- ${serverUrl}/api/machines | jq ".Id" -r)
          fi
Outputs:
  PublicIp:
    Value:
      Fn::GetAtt:
      - WildFly
      - PublicIp
    Description: Server's PublicIp Address 

为了将 AMI 部署为 EC2 实例,我们配置了一个类型为AWS::EC2::Instance的资源,将ImageId设置为我们正在部署的 AMI ID。

WildFly:
  Type: 'AWS::EC2::Instance'
  Properties:
    ImageId: ami-5069332a 

在 Octopus 内部,我们有一堆需要在任何 EC2 实例上设置的标签。至少您需要设置Name标记,因为这是出现在 AWS 控制台中的名称。

注意在 Octopus 中使用变量替换来设置OwnerContact标签值。我们将在后面的步骤中定义这个变量。

Tags:
  -
    Key: Appplication
    Value: WildFly
  -
    Key: Domain
    Value: None
  -
    Key: Environment
    Value: Test
  -
    Key: LifeTime
    Value: Transient
  -
    Key: Name
    Value: WildFly
  -
    Key: OS
    Value: Linux
  -
    Key: OwnerContact
    Value: "#{Contact}"
  -
    Key: Purpose
    Value: Support Test Instance
  -
    Key: Source
    Value: CloudForation Script in Octopus Deploy
  -
    Key: scheduler:ec2-startstop
    Value: true 

为了将这个 EC2 实例用作 Octopus 部署目标,它需要安装 Mono,或者安装支持 DotNET Core 2 所需的包。在这个例子中,我选择支持后者。因为 Bitnami AMI 运行的是 Debian,所以我们使用apt-get来安装在先决条件中列出的依赖项。Linux 上的 NET Core

#cloud-boothook标记被cloud-init服务用来识别应该在每次引导时运行的脚本

在生产环境中,像这样的依赖项将被放入基本 AMI 映像中,而不是在实例启动时安装。

UserData:
  Fn::Base64: |
    #cloud-boothook
    #!/bin/bash
    sudo apt-get --assume-yes update
    sudo apt-get --assume-yes install curl libunwind8 gettext apt-transport-https jq 

Bitnami 映像在首次启动时会为 WildFly 管理控制台创建一个随机密码。您可以使用 Bitnami 提供的指令找到这些凭证。然而,我们可以通过启用静默认证来避免需要知道这些凭证。静默认证允许访问/opt/bitnami/wildfly/standalone/tmp/auth目录的进程在不提供用户名和密码的情况下通过 WildFly 进行认证。因为运行 WildFly 部署的代码将从 WildFly EC2 实例本身执行,所以我们可以授予对该目录的权限,并且不再需要知道 Bitnami 生成的随机密码。

这里我们创建一个名为deployment的组,将wildflybitnami用户添加到该组,将/opt/bitnami/wildfly/standalone/tmp/auth目录的组所有权分配给deployment组,并授予该组对该目录的完全权限。这意味着当 Octopus 使用bitnami用户连接到 EC2 实例时,它将完全控制/opt/bitnami/wildfly/standalone/tmp/auth目录,因此可以利用静默认证。

getent group deployment || sudo groupadd deployment
sudo usermod -a -G deployment wildfly
sudo usermod -a -G deployment bitnami
sudo chgrp deployment /opt/bitnami/wildfly/standalone/tmp/auth
sudo chmod 775 /opt/bitnami/wildfly/standalone/tmp/auth 

最后,我们需要这个 EC2 实例向 Octopus 服务器注册自己,如果它还没有这样做的话。脚本的这一部分查询 Octopus API,以确定 EC2 实例的当前主机名是否存在一个部署目标,如果没有找到部署目标,就会添加它。

该脚本中的许多变量是使用变量替换提供的。这些将在下一节中定义。

role="WildFly"
serverUrl="#{ServerURL}"
apiKey="#{APIKey}"
environment="#{Environment}"
accountId="#{AccountID}"
localIp=$(curl -s http://169.254.169.254/latest/meta-data/public-hostname)
existing=$(wget -O- --header="X-Octopus-ApiKey: $apiKey" ${serverUrl}/api/machines/all | jq ".[] | select(.Name==\"$localIp\") | .Id" -r)
if [ -z "${existing}" ]; then
  fingerprint=$(sudo ssh-keygen -l -E md5 -f /etc/ssh/ssh_host_rsa_key.pub | cut -d' ' -f2 | cut -b 5-)
  environmentId=$(wget --header="X-Octopus-ApiKey: $apiKey" -O- ${serverUrl}/api/environments?take=100 | jq ".Items[] | select(.Name==\"${environment}\") | .Id" -r)
  machineId=$(wget --header="X-Octopus-ApiKey: $apiKey" --post-data "{\"Endpoint\": {\"DotNetCorePlatform\":\"linux-x64\", \"CommunicationStyle\":\"Ssh\",\"AccountType\":\"SshKeyPair\",\"AccountId\":\"$accountId\",\"Host\":\"$localIp\",\"Port\":\"22\",\"Fingerprint\":\"$fingerprint\"},\"EnvironmentIds\":[\"$environmentId\"],\"Name\":\"$localIp\",\"Roles\":[\"${role}\"]}" -O- ${serverUrl}/api/machines | jq ".Id" -r)
fi 

默认安全组

上面的 CloudFormation 模板没有定义安全组。这意味着使用默认的。为了通过 SSH 访问 EC2 实例并打开 WildFly 托管的网站,默认安全组需要打开端口2280

Default security group

在生产环境中,应该使用专用的安全组。

变量

CloudFormation 脚本有许多使用变量替换定义的变量。这些变量在我们的 Octopus 项目的变量➜项目部分中定义。

通过获取 URL https://octopusserver/app#/infrastructure/accounts/sshkeypair-bitnami的最后一个元素找到sshkeypair-bitnamiAccountID变量,这是从基础设施➜帐户➜ SSH 密钥对打开 Bitnami SSH 帐户时显示的 URL。

注意AWS Account变量被设置为之前创建的AWS Account。该变量由 Octopus 步骤使用,而不是由 CloudFormation 模板直接使用。

你可以从文档中获得更多关于创建 Octopus API 密匙的信息。

Project Variables

开始没有目标的部署

因为我们正在创建我们将作为 Octopus 项目的一部分部署到的基础设施,所以我们需要配置一些设置,以允许 Octopus 在没有任何预先存在的有效目标的情况下开始部署。这是在Deployment Targets下的项目设置中完成的。将值设置为Allow deployments to be created when there are no deployment targets意味着即使没有可用的目标,项目也可以开始部署。

Allow deployments with no targets

云形成步骤

现在是时候开始定义项目步骤了。我们将从部署 CloudFormation 模板开始,这是通过Deploy an AWS CloudFormation template步骤完成的。

CloudFormation Step

下面是填充步骤的屏幕截图。

CloudFormation WildFly

健康检查步骤

一旦部署了 CloudFormation 模板,它所创建的 EC2 实例将启动并向 Octopus 注册自己作为部署目标。我们现在需要将这个新目标添加到项目将要部署到的目标列表中。这是使用Health Check步骤完成的。

Health Check

下面是填充步骤的屏幕截图。

Health Check WildFly

野火部署步骤

既然我们新创建或更新的 EC2 实例是我们的部署目标列表的一部分,我们可以向它部署我们的 Java 应用程序。这是使用Deploy to WildFly or EAP步骤完成的。

WildFly Step

下面是填充步骤的屏幕截图。

com.github.gwtmaterialdesign:gwt-material-demo工件是由 gwt-material 项目发布的 WAR 文件。我们在这里使用它是因为它是一个已经托管在 Maven Central 上的方便的示例项目。

请注意,我们没有提供Management userManagement password。这意味着我们依赖 WildFly 静默认证功能。

WildFly Deployment

最终输出步骤

为了方便运行此部署的人员,我们将显示一些有用的摘要信息。这是通过Run a Script步骤完成的。

Run a Script

部署 CloudFormation 模板时,Octopus 会捕获任何输出变量,并将其用于后续步骤。我们利用这一点,基于 EC2 实例的公共 IP 地址构建一些 URL。

Write-Host "Open application at http://$($OctopusParameters["Octopus.Action[WildFly CloudFormation].Output.AwsOutputs[PublicIp]"])/gwtdemo"
Write-Host "Establish an SSH tunnel with:"
Write-Host "ssh -L 9990:localhost:9990 bitnami@$($OctopusParameters["Octopus.Action[WildFly CloudFormation].Output.AwsOutputs[PublicIp]"]) -i YourAWSKeyPair.pem"
Write-Host "Then open http://localhost:9990"
Write-Host "Find the credentials using the instructions from https://docs.bitnami.com/aws/faq/starting-bitnami-aws/find_credentials/" 

下面是填充步骤的屏幕截图。

Run Script WildFly Deployment

部署项目

下面是这个项目的一个部署结果的截图。

CloudFormation Output

请注意 CloudFormation 模板部署的输出中的这几行:

Saving variable "Octopus.Action[WildFly CloudFormation].Output.AwsOutputs[StackId]"
Saving variable "Octopus.Action[WildFly CloudFormation].Output.AwsOutputs[PublicIp]" 

这些日志消息提供了一种简单的方法来获取作为 CloudFormation 部署的结果而创建的任何输出变量的完整变量名。

还要注意运行状况检查步骤的输出。在这个部署中,我通过在 UserData 脚本中添加一个注释,对 CloudFormation 模板进行了一些调整。虽然这种变化不影响 EC2 实例的部署方式,但 CloudFormation 将其视为对现有堆栈的更改,因此关闭并重新启动 EC2 实例。这又给了 EC2 实例一个新的公共 IP,这意味着 EC2 实例将在启动时向 Octopus 注册自己。然后,运行状况检查步骤检查旧的部署目标和新的部署目标,确定旧的目标不再有效并将其删除,并成功完成对新目标的运行状况检查,并将其包括在用于剩余部署的目标列表中。

打开 Web 应用程序

最后一个脚本步骤的输出生成了一个 URLhttp://107.20.112.198/gwtdemo/。打开它显示 GWT 材料演示应用程序。

这个 URL 实际上对您不起作用,因为这个演示 EC2 实例已经关闭。为您生成的 URL 将具有不同的 IP 地址。

GWT Material

结论

通过将部署 CloudFormation 模板和 Java 应用程序的许多新步骤整合在一起,我们可以非常容易地创建一个 Octopus 项目,该项目动态地构建云基础架构并向其部署应用程序。

如果您对自动化部署 Java 应用程序或创建云基础设施感兴趣,下载 Octopus Deploy 的试用版,并查看我们的文档。

posted @ 2024-11-02 15:51  绝不原创的飞龙  阅读(9)  评论(0编辑  收藏  举报