Octopus-博客中文翻译-六-
Octopus 博客中文翻译(六)
原文:Octopus Blog
带有模板和自动生成的更好的发行说明- Octopus 部署
这篇文章是继我们的 Octopus Deploy 2019.4 关于从代码到部署跟踪你的工作的公告之后的。
跟踪的一个重要部分是可见性和可追溯性,我们已经在上一篇文章中看到了吉拉集成如何对此有所帮助。在这篇文章中,我们将看看另一个新特性,发布说明模板和自动发布说明生成。
这是来自吉拉集成的一个特性,但是不依赖于那个集成,它本身可以使管理你的发布说明更简单。因此,如果你正在管理 Octopus 中的发行说明,请继续阅读。
发行变更和发行说明模板
在我们开发吉拉集成的过程中,很明显,从我们现在可以访问的新的包元数据中获得的发布和部署本身也有真正的价值。
考虑到这一点,我们开始在部署中添加新的变量,以访问发布版本的变化。我们做的第一件事是使用它来创建电子邮件步骤,并使用变量来创建电子邮件的 HTML。
这太棒了。感觉真的很有用。但是输出只有在您收到电子邮件时才有用。那么在 Octopus 门户中,在那里看到这些信息不是很好吗?是的,它会,所以我们这样做了。
在第一次迭代中,我们将其呈现在固定的只读控件中。这很棒,感觉真的很有用,但它是只读的。它适合人们会想到的所有用例吗?
历史告诉我们没有😃所以我们反复强调“我们如何允许定制发布说明的布局?”结果是发行说明模板。
发行说明模板在项目设置中定义,示例可能如下所示:
Here are the notes for the packages
#{each package in Octopus.Release.Package}
- #{package.PackageId} #{package.Version}
#{each workItem in package.WorkItems}
- [#{workItem.Description}](#{workItem.LinkUrl})
#{/each}
#{/each}
您可以使用任何有效的降价,就像您一直能够使用发行说明一样,但是现在变量替换作为发行创建的一部分被应用,所以您将立即在门户中看到它。
另请注意,如果您编辑一个版本,您将会看到创建时产生的文本,而不是原始的模板内容。您可以在编辑中使用变量绑定,它们将在保存时应用。
部署变量
正如我们上面提到的,部署已经扩展到包括“发布变更”关于这一点很重要的一点是部署总是将来自发布的发布说明聚集到发布变更中,即使没有元数据和工作项。换句话说,即使您没有使用任何其他的包元数据和工作项功能,您仍然可以在部署期间利用累积的发行说明。
对于与部署相关的每个发布,发布变更包括一个版本(发布版本)、发布说明(以 markdown 格式)和一个工作项列表。
就像我们前面的例子一样,这个信息的一个常见用途是在电子邮件步骤中。下面是一个示例电子邮件模板,包括一个返回到该版本的链接,以及从 markdown 重新格式化为 HTML 的版本说明:
<p>Here are the notes customized for email</p>
#{each change in Octopus.Deployment.Changes}
<strong><a href="(#{Octopus.Web.ServerUri}#{Octopus.Web.ReleaseLink}">#{change.Version}</a></strong></br>
#{change.ReleaseNotes | MarkdownToHtml}</br>
#{/each}
包裹
我们收到了许多对发行说明增强的请求,我们真的很兴奋最终能发布它。如果你正在使用或渴望使用 Octopus 的发行说明,请尝试一下,并告诉我们你的想法。
八达通发布和长期支持(LTS) -八达通部署
我们正在迭代我们发布 Octopus Deploy 的方式。亮点是:
- 我们每年将发布六个功能版本(
2020.1
、2020.2
,...). - 每个功能发布都会收到六个月的关键补丁。
- 我们将不再明确地将版本标记为 LTS(长期支持)。
- 版本将在 Octopus Cloud 实例中推出,然后供自托管实例下载。
我们相信,这将为我们的客户和我们带来更清晰的信息,甚至更多稳定的版本,以及更好的整体体验。
这些更改会立即生效。2020.1 版本目前正在向章鱼云实例推出,并将很快提供下载。
此版本将接收补丁(2020.1.1
、2020.1.2
等)。)接下来的六个月。下一个功能版本(2020.2
)将于 4 月开始推广到 Octopus Cloud 实例,并将于 5 月初提供下载。
以上几点是这篇文章的关键信息。但是对于那些感兴趣的人来说,请继续读下去,了解一些历史背景,并希望能有助于了解我们的想法。
0 版本的挑战
软件公司发布新版本的方式是客户关系的基础。在 Octopus,我们的交付流程已经发生了变化,虽然每个决定在当时都是合理的,但我们对当前的状态并不满意。
原来章鱼只有一个自托管的产品(没有章鱼云)。对于一个用户下载并安装在他们自己的基础设施上的产品来说,有一种紧张:你最想制造噪音的版本也是最不稳定的。包含新功能的版本是最激动人心的,但也是最具颠覆性的。它们的安装率最高,但也最有可能出现问题。这对我们的用户或我们来说都不是一个理想的组合。对于我们的许多客户来说,稳定性比最新的特性更重要,当他们问“我们可以升级到哪个最稳定的版本?”我们不能总是诚实地回答,“最新的。”于是在 2018 年, LTS 计划诞生了。
通过将某些版本标记为 LTS,我们试图给人们这样的选择:
- 使用最新的功能。
- 使用最稳定的版本。
从某种意义上说,这个计划是成功的;它达到了目的。但当我们的大部分用户没有最新的功能时,这让我们很难过。我们也不认为这对新用户来说是最好的体验。当用户第一次访问 Octopus 下载页面时,我们会让他们在稳定性和最新功能之间做出选择。
我们想给他们两个!
扩展长期支持
我们将不再指定 LTS 版本。这并不是删除 LTS 程序,而是将它扩展到每个版本。我们将为每个功能发布提供六个月的补丁。octopus.com 上的下载页面现在将显示一个下载最新版本的选项(如果需要,您仍然可以下载以前的版本)。
但是那些.0
版本的稳定性呢?
2018 年我们发布了章鱼云。这改变了游戏。
也许运送自托管软件的最大缺点是,如果一个版本包含一个问题,没有办法自动升级每个人(我们的许多客户在没有互联网连接的环境中使用 Octopus)。对于我们来说,看到用户遇到我们已经发布修复的问题总是令人痛苦的。但是当我们托管 Octopus 时,如果一个用户发现一个问题,我们可以立即向所有受影响的实例推出一个解决方案,从而大大降低影响。
出于这个原因,我们将首先向章鱼云实例推出新版本。这允许我们管理部署的速率(最初部署到少量实例),并快速稳定发布。当我们对该版本的稳定性有信心时,我们将部署到所有 Octopus 云实例,并让自托管客户下载。
对每个人都更好
我们相信这对每个人来说都是一个进步。
- 自托管用户将能够更有信心地安装最新版本,他们将不必在功能或稳定性之间做出选择。
- 八达通云用户将更快地获得新功能。以前,我们会在新功能可供下载时发布。那时,这些特性在 Octopus Cloud 实例上还不可用,我们甚至不能确切地说它们什么时候会可用。从现在起,当我们宣布一项新功能时,它将对所有八达通客户开放。
- 在我们的测试中出现的任何问题都会在较短的时间内影响少量的客户。
如果您对这些变化有任何疑问或担心,请联系我们 support@octopus.com。
愉快的部署!
发布、部署和可变快照- Octopus 部署
作为一个自动化的发布管理和部署解决方案,Octopus 区分了项目、发布和部署。
一个项目就像是一个模板,用来决定部署什么。它包含:
这个想法是你定义你的项目一次,然后创建这个项目的许多版本。
然后,一个版本仅仅是一个 NuGet 包的选择(ID 和版本)。由于一个项目可以有许多步骤,一个单一的发布通常会有许多包。例如,“Contoso 网站”项目的“1.0”版本可能会引用 NuGet 包“Contoso。Web.1.0.315.nupkg "和" Contoso。Mailer.1.0.201.nupkg "。
一个部署是当一个版本被部署到一个环境(比如开发、测试、试运行或生产)时发生的事情。单个版本可以在许多环境中部署多次。
这种设计的好处在于,您可以保证部署到测试环境中的包与部署到生产环境中的包是相同的。发布实际上是一个“发布候选”,它有一个系统跟踪的生命周期。Octopus web UI 允许您查看一个版本的生命周期——谁创建了它,谁将它部署到每个环境中。
快照
当发布被创建时,它是只读的。您可以编辑发行版本号(因为它实际上只是一个标签)和发行说明,但是一旦部署了一个发行版本,您就不能更改包的版本。
另一方面,一个项目的可以被编辑。您可能会决定更改项目变量。或者您可以决定添加一个新的步骤,即一个新的 NuGet 包来部署。
问题是,在编辑一个项目之后,当您试图部署一个旧版本时会发生什么?您可能已经创建了包含两个 NuGet 包的 1.0 版,并将其部署到 Staging。然后,有人通过添加要部署的新 NuGet 包来编辑项目。您的 1.0 版本没有为 NuGet 包选择版本,并且您没有在 Staging 中部署它,所以您也不想将它部署到生产中。
为了解决这个问题,Octopus 在创建一个版本时会创建一个项目步骤和变量的“快照”。您可以创建发布1.0
,然后修改项目以准备发布1.1
,而不中断发布1.0
。对大多数人来说,这似乎很直观,因为它允许旧版本不受新版本变化的影响。该快照包含:
- 所有变量的副本
- 所有步骤的副本(获取包 ID 和版本)
令人惊讶的是
尽管仔细想想,这似乎是一种合乎逻辑的方法,但它有点出乎意料,容易让人措手不及。有人可能创建了一个版本,然后编辑了一个变量,但是当他们重新部署这个版本时,却发现这个新变量没有任何效果。答案很简单,就是创建一个新的发布(例如1.0-patch1
),这将导致任何更改都在一个新的快照中被拾取。
解决方法
Octopus 有两个变化来帮助解决这个问题。首先,当编辑步骤或变量时,我们将显示一个提示,告诉人们他们需要创建一个新的版本。这样,当重新部署没有任何效果时,就不会感到惊讶了。
其次,我正在想办法减少一个项目需要编辑的次数。一个常见的问题是,当您添加一台新机器时——比方说,一台新的生产前端 web 服务器来帮助处理负载——您需要编辑项目步骤,以指定您的 web 包必须部署到新机器上。这也意味着创建一个全新的版本。
将来,这些步骤将引用用户可定义的“角色”,如“网络服务器”、“电子邮件发送者”、“计算工作者”等。当您将机器添加到环境中时,您将能够将机器“标记”为服务于一个或多个这些角色。变量的工作原理是一样的,因为一个变量可以作用于一个角色。这样,机器可以在不影响任何现有版本的情况下从一个环境中进出。只需将机器添加到一个角色,重新部署当前版本,就可以开始了。
“角色”的引入对 Octopus 的架构来说是一个很大的改变,特别是因为我们需要确保现有的版本(包含直接指向机器的步骤的快照)在这个过程中不会被破坏。我正在一个分支机构中进行这一改变,随着进展,我将继续在博客上发表更多关于这一改变的内容。
可靠地部署大型 Azure Web 应用——Octopus Deploy
原文:https://octopus.com/blog/reliably-deploying-large-azure-web-apps
在 Octopus Deploy 3.0 中,我们发布了对部署 Azure Web 应用的一流支持。与直接从 Visual Studio 发布不同,您可以管理特定于环境的配置,并以受控的方式通过环境提升您的应用程序版本。
我们一直在与一些客户合作,他们部署 Azure Web 应用变得不可靠。当向 Azure 部署大型 web 应用程序时,或者当通过缓慢或有损耗的网络连接进行部署时,尤其如此。在这些情况下,部署到现有的 Azure Web 应用程序可能会变得不可靠,在某些情况下会挂起,通常需要多次尝试才能成功部署。我希望不言而喻- 这不是我们想要的那种体验任何人使用 Octopus Deploy -我们都是为了愉快的部署!
如果你对 Azure Web 应用的不可靠、缓慢或挂起的部署有困难,这篇文章是为你准备的。总而言之:
在被窝里探索一下...
当你用 Octopus 部署 Azure Web 应用时,我们将通常的 Octopus 优点应用到你的配置文件,运行你的定制脚本,然后依靠 WebDeploy 将结果文件同步到 Azure。我们使用微软的。Web.Deployment 直接在代码中获取包,而不是分发给MsDeploy.exe
。
时间戳而不是校验和
在 Octopus 3.0 中,我们决定将校验和作为与 WebDeploy 同步文件的默认(也是唯一的选择)。使用校验和,我们将只部署实际上改变了的文件,而不需要任何特殊的配置。这减少了后续部署到同一个 web 应用程序所需的时间和带宽。这对于中等规模的应用程序来说确实很好,但是,正如我们发现的,对于大型应用程序来说,这变得极其缓慢和不可靠。
如果你发现自己处于这种情况,你可以使用 Octopus Deploy 3.3.3 或更新版本,切换到使用时间戳进行文件比较,而不是使用校验和。
如果你有任何现有的 Azure Web 应用程序步骤,你将需要更新它们以使用时间戳并创建一个新版本。默认情况下,你创建的任何新 Azure Web 应用步骤都将使用时间戳,但是如果更适合你的需要,你可以随时切换到校验和。
如果你更喜欢校验和,我们计划将升级到Microsoft.Web.Deployment.3.6.0
,它有几个针对校验和比较的错误修复。
保留时间戳
使用时间戳进行文件比较的缺点是可靠性——你很容易得到误报——这就是校验和如此吸引人的原因。如果您使用的是 NuGet 包,文件的时间戳将在解压缩时丢失,这意味着每个文件看起来都比更新,并且无论是否更改,都会上传- 更多详细信息。要解决这个问题,可以考虑使用 zip 包——我们已经进行了自动测试,证明时间戳在您的部署中端到端地保留。
尝试部署到新的插槽并切换
我们通常将这些称为蓝绿色部署,在这里,您部署 web 应用程序的新副本,运行冒烟测试,切换插槽,最后删除旧插槽。在现有插槽上进行部署很方便,但无法提供与蓝绿色部署相同的停机时间。
我们已经为写了一个简短的指南,使用 Azure Web Apps 的部署槽。对于在部署过程中实现一个额外步骤的成本来说,这是在部署期间保持 web 应用程序正常运行的一个非常好的模式。
当您部署到一个全新的插槽时,WebDeploy 将只上传所有内容,实际上不需要进行文件比较,从而使大型 Web 应用部署更加可靠。
Azure Web 应用部署的未来
我们希望确保您可以在最终版本发布后立即将您的 ASP.NET 核心 1 应用部署到 Azure 应用服务中。我们已经支持 JSON 配置文件,以及使用 zip 包的构建和部署管道。
接下来:
- 我们正计划升级到
Microsoft.Web.Deployment.3.6.0
( 测试版发布说明)——关注这个 GitHub 问题如果你有兴趣:- 部署到
wwwroot
以上的文件夹 - 代理支持
- 使用校验和同步的错误修复
- 部署到
- 我们将对 JSON 配置文件实现更深层次的支持
还有问题吗?
如果您仍然遇到 Azure 应用服务部署不可靠的问题,请联系我们的支持团队,我们将与您一起解决您遇到的具体问题。
5 种远程桌面替代方案- Octopus 部署
如果您构建在 Windows 服务器上运行的应用程序,并且您参与了部署,那么您很可能会花时间在远程桌面上。
在过去,当船是木头做的,人是钢铁做的,我们会有几个服务器,在上面运行尽可能多的应用程序。拥有十几个站点或应用程序的 IIS 服务器不仅仅是常见的,而是标准的。
如今,虚拟化和云计算意味着不再是一台服务器运行许多应用程序,而是一台服务器运行许多虚拟服务器,每台服务器运行一个应用程序。这意味着我们很少同时处于单个远程桌面会话中。
以下工具列表帮助您一次管理多个远程桌面会话。
微软已经更新了他们的远程桌面客户端,现在它支持 Windows,Android,iOS 和 macOS。这是一个伟大的选择,支持多种平台,能够保存密码等。这是免费的,而且是微软提供的。有什么不喜欢的?
如果你愿意,它可以保存凭证,并且非常适合在队友之间共享连接。它唯一缺乏的功能是不能保存远程桌面网关的凭证。这就是为什么我们换成了…
mRemote 的一个开源分叉,这是我们目前使用的工具。Octopus 团队是分布式的,因此我们将 mRemoteNG 设置文件保存在 Dropbox 中,以便团队中的每个人都可以使用它们轻松连接到我们的任何虚拟机。
RoyalTS 是一个非常好看的商业替代品,有一个杀手级的功能:一个可以让你远程点击“开始”的按钮。我不知道是谁忘记告诉 UX Windows 团队,人们通常不会在平板电脑上运行 Windows Server 2012,但我肯定他们有理由让通过远程桌面启动程序变得几乎不可能。别害怕,皇家队来了。
另一个开源的选项卡式会话管理器,但它看起来正在积极开发,源代码是 C#!
好吧,是不要脸的塞 😃
Octopus Deploy 是一种远程桌面替代方案,就像 TeamCity/Team Build 是 Visual Studio 替代方案一样。
远程桌面工具对于诊断和一些配置任务是必不可少的;不可否认。也就是说,我们在 Octopus Deploy 的全部存在理由是使典型的部署不涉及任何远程桌面。通过更好的可见性、责任性和可靠性,我们的目标是减少您花费在远程桌面会话上的时间。
你对上面的工具有什么体验,我错过了什么?
其他一些值得一看
如果你正在寻找一个有用的 Linux 开源工具,你应该考虑 Remmina。更何况 Ubuntu 是预装的。
切换到 Mac OS X,另一个很好的检查是线。像 Remmina 一样,它是免费和开源的,但也很好地让您在自己的窗口中查看每个会话。或者,您可以在一个窗口中查看所有会话,节省空间并允许您根据需要进行缩放。
远程桌面管理器 10
远程桌面管理器被设计成集中所有的连接和凭证。它比香草 RDP 或 mRemoteNG 更好地处理高 DPI,并有两种口味,免费和企业。Free 限制用户之间可以共享的信息量,而 Enterprise 解锁用户权限、角色、高级日志记录等功能。
了解更多信息:
远程发布促销 RFC - Octopus 部署
这篇文章是作为我们当时正在开发的一个功能的评论请求而写的。但是,该功能尚未开发,在 Octopus Deploy 中不可用。关于 Octopus 中可用功能的高级概述,请访问我们的功能页面。
我们目前正在设计一个功能,我们称之为远程发布推广。
这篇文章是对我们现阶段想法的征求意见。
这篇文章是前两篇文章的后续:
问题是
在有些场景中,不同的 Octopus 服务器实例根据要部署到的环境来执行部署是有意义的。例如:
- Octopus 服务器 1 部署到
Development
和Test
环境 - Octopus 服务器 2 部署到
Staging
和Production
环境
电梯推销术
我们正在计划一个功能,使您能够促进跨多个八达通服务器的版本...以一种美好的方式😃如果你今天正在这样做,那么你知道这是可能的...但是不太愉快。
两个最常见的原因是:
- 隔离环境
- 地理位置遥远的环境
隔离环境
出于安全目的,许多组织将其生产和开发环境分开。例如,这是实现 PCI DSS 合规性的常见方式。
安全网络甚至可能完全断开(也称为空气间隙)。
这些组织仍然想要章鱼的所有优点,比如通过环境、整体编排和在仪表板上看到进度来推广同一个版本。但是他们不希望开发 Octopus 服务器连接到生产环境。希望不同的用户(可能来自不同的 Active Directory 域)拥有生产 Octopus 服务器的权限也很常见。
地理位置遥远的环境
其他组织可能部署到地理位置较远的环境中。
比如他们的开发环境可能位于澳大利亚布里斯班(很适合居住的地方!),而他们的生产环境由美国和欧洲的数据中心托管。
这有两个主要问题,都与性能有关:
- 包在部署时传输。如果包很大,这可能需要很长时间。
- 在部署期间,信息必须在 Octopus 服务器和部署目标之间来回传送。这些通信中的高延迟会对部署持续时间产生重大影响。
这些客户希望在他们选择的时间推广该版本,让包自动高效地传输到适当的数据中心,然后尽快执行部署。
其他示例
出于各种其他原因,我们的一些客户决定跨多个 Octopus 服务器管理他们的部署。我们认为我们建议的解决方案还将帮助以下客户:
- 将他们的工作分配给多个团队,可能分布在世界各地
- 使用面向服务(SOA)或微服务架构
- 使用 Octopus 将他们的软件直接部署到客户的网络中
- 取决于所有这些例子的组合
拟议解决方案
我们建议的解决方案将使您能够将您的整个部署管道分布到多个空间,允许您将发布提升到其他空间,并且将部署结果再次流回以显示在仪表板上。
想象一下,如果您可以将一个空间添加到您的生命周期中,就像您可以添加环境一样,然后将一个版本升级到另一个空间。当您向另一个空间推广一个版本时,Octopus 可以将部署该版本所需的一切打包到另一个空间的环境中。我们还会考虑到空间之间有严格分隔的情况(想想 PCI DSS)。这就是为什么我们称这个功能为远程发布促销。
我们认为有三个主要的概念在发挥作用,使所有这些走到一起:空间、信任和生命周期。
间隔
一个空间是我们在之前的RFC中引入的概念。每个空间都有自己的一套项目、环境、生命周期、团队、权限等。
稍后我们将更多地讨论配置空间,但同时我们要明确:我们将支持可以相互通信的空间,以及不能相互通信的空间。
连通空间
如果您对您的交流空间感到满意,那么我们将致力于提供超级流畅的体验。将发布升级到一个远程空间就像按一个按钮(或者点击 API)一样简单。同样,您可以让部署结果自动流回,以便您的仪表板总是最新的。
不连续的空间
我们还将支持隔离空间,因为这是一个常见的安全场景。事情必然会更加手动;你可能要多打一会儿字(甚至可能还要走一会儿路!).
信任其他空间
我们已经有了在章鱼服务器和触手之间建立信任的概念:它只会执行从可信的章鱼服务器发出的命令。我们还认为,在两个空间开始共享部署一个版本所需的一切和部署一个版本的结果之前,在它们之间建立信任关系是很重要的。在我们最近介绍空间概念和章鱼数据中心经理(ODCM)的博文中,我们谈到了分享。
这种关系的核心将包括一个名称和一个证书。这将使每个空间能够唯一地识别信息的来源,并验证信息的完整性,就像章鱼服务器和触手今天所做的。我们认为配置这种关系的最佳方式是使用 ODCM,因为它的核心能力是管理空间。
这意味着您可以控制不同空间之间的信息流,并且可以在一个地方对其进行审计。
生活过程
我们认为生命周期应该在一个空间内定义,并且能够在多个空间内组合——你可以把它想象成把不同空间的生命周期链接在一起。
在一个空间内定义:这使每个空间中的团队能够以他们认为合适的方式管理他们自己的环境和生命周期。例如,一个空间的成员可能决定将一个环境引入他们的生命周期。我们不希望将环境引入一个空间的生命周期的决定对任何其他空间产生任何影响。
跨空间组合:这使您能够将整个部署管道建模为一个复合生命周期,它是通过将不同空间中定义的生命周期连接在一起而形成的。例如:
-
您可能希望通过您的测试环境来提升一个版本,然后将该版本提升到一个不同的空间来管理您的生产环境。
-
您可能做同样的事情,但是在不同的地理位置托管您的生产环境。
-
您可能想要通过您的开发团队的测试环境来提升一个版本,然后将该版本提升到由 QA 团队管理的另一个空间。当他们完成测试时,您希望开发团队将同一个版本提升到另一个空间,在那里运营团队管理您的生产环境。
-
您可能想做同样的事情,但是一旦 QA 团队完成了,他们就直接将发布提升到操作团队的空间,而不通过开发团队返回。
定义
在 RFC 的剩余部分,我们将引入一些新的术语。让我们在这里定义它们,这样我们就不会被弄得非常混乱。
- 空间:包含一组项目、环境、变量、团队、权限等,由单个 Octopus 数据库限定。在我们最近的 RFC 中了解更多信息。
- 发布包:一个包,包含了部署一个项目的特定发布所需的一切。
- 部署收据:包含显示项目特定版本部署结果所需的所有内容的文档。
- Source Space: 拥有项目及其发布的空间,如果您决定跨越空间边界,那么在这里创建发布包。
- 目标空间:发布包将被导入的空间。然后,可以将该版本部署到这一领域的环境中。
- 远程环境:对另一个空间所拥有的环境的引用。
- 远程项目:对另一个空间拥有的项目的引用。
- 远程空间:由不同的 ODCM 管理的空间的引用,通常在不同的网络中。一个远程空间的概念将使你能够跨越安全的网络边界来促进发布。
- 可变模板:我们在多租户部署中引入了这个概念。在这种情况下,您可以表达为每个可以部署项目的环境都需要一个变量值。
一次漫游
让我们使用我们之前提到的隔离环境例子来探索这个概念,在这个例子中,您希望在开发和生产环境之间进行严格的隔离。在这种情况下,我们将使用两个空间来模拟这种分离:
- 为了开发和测试目的而部署应用程序的地方
Prod Space
:您的应用程序的生产部署将被部署,并且需要严格的合规性控制
让我们考虑一下你的组织中的每个不同的人可能如何与 Octopus 交互,以促进跨这两个空间的发布,一直到生产。
配置空间之间的信任
TL;DR 使用 ODCM 来配置空间之间的关系。
一个好的起点是配置您的共享空间,并在它们之间建立信任关系。在类似分离环境的情况下,我们认为您最终会在每个网络中安装一个 ODCM 实例。这将允许您的团队独立管理每个网络中的空间,并根据需要在同一网络或不同网络中的空间之间配置信任。
我们认为整个过程会是这样的:
- 在每个网络中配置一个 ODCM 实例,用于管理该网络中的空间
- 使用生产网络中的 ODCM 创建
Prod Space
- 在开发网络中使用 ODCM 创建
DevTest Space
- 在每个网络中使用 ODCM 来配置您的共享空间之间的信任关系
在连接的场景中,这将是管理空间的单个 ODCM 实例,证书交换自动发生。
然而,在我们的隔离场景中,网络之间有严格的隔离,因此您必须配置两个远程空间来手动相互信任,方法是为每个空间交换证书:
- 使用您的开发网络中的 ODCM 为
DevTest Space
下载证书。 - 去你的生产网络中的 ODCM,创建一个新的远程空间名为
DevTest Space
,给它你为DevTest Space
下载的证书。 - 在您的生产网络中使用 ODCM,并下载
Prod Space
的证书。 - 到你的开发网络中的 ODCM,创建一个新的远程空间,名为
Prod Space
,给它你为Prod Space
下载的证书。
现在,您已经配置了共享空间并交换了公钥,您可以:
- 在
DevTest Space
中配置您的生命周期,以将版本升级到Prod Space
- 将
Prod Space
配置为信任从DevTest Space
升级的版本 - 配置
DevTest Space
以信任来自Prod Space
的部署结果
你将完全控制空间之间的信任(以及信息流)。
使用项目
TL;博士没什么太大的变化——一切都会觉得很熟悉。
我们看不到太多变化——生活将和以前一样。作为维护项目的人,您仍然能够像平常一样更改部署过程、管理变量、创建发布并将其部署到DevTest Space
环境中。然而,这引发了一些问题:
- 当部署到
Production
环境时,如何提供将被使用的变量值? - 如何配置部署过程的特殊步骤,以便它们只在部署到
Production
环境时执行? - 您如何在仪表板上显示部署到
Production
环境的结果?
有请可变模板和远程环境!
可变模板
TL;我们将扩展变量模板,以支持每个环境的变量值。
想象一下,如果您是将一个发布包导入到您的空间中的人——您将需要知道您的空间中的每个环境需要哪些变量的值。
现在想象一下,作为一个项目贡献者,如果您可以表达一个变量值对于一个项目可以部署到的每个环境都是必需的。想象一下,您可以为变量定义一个数据类型,提供帮助文本,决定该值是强制的还是可选的,甚至提供一个默认值。
可变模板将使人们更容易将一个发布包导入到他们的空间中来“填补空白”。
即使你只是在你自己的空间里推广发布,这也是非常方便的。使用变量模板,如果你把一个新的环境引入你自己的空间,Octopus 会提示你输入那些变量值。
我们在 Octopus 3.4 中为多租户部署引入了可变模板的概念。作为这组特性的一部分,我们将进一步构建这个概念。
注意:这也将允许租户变量根据环境而变化(这是非常需要的特性)。
远程环境
TL;DR 您将能够将部署步骤和变量值扩展到其他空间拥有的环境,并在仪表板上显示这些环境。
我们希望能够实现这样的场景,您可以将发布提升到其他空间,而无需了解该空间中的任何环境。但是,我们可以看到您想要了解其他空间环境的场景:
- 您希望在将发布部署到
Production
环境时执行某些步骤 - 您已经知道了在将一个版本部署到
Production
环境时所需的一些变量值(也许它们不是秘密) - 您希望在自己的仪表板上看到将发布部署到
Production
环境的结果
这里的问题是,Production
环境归Prod Space
所有,所以你的DevTest Space
不知道Production
环境的存在!想象一下,如果你能给DevTest Space
添加一个远程环境。这个远程环境将是真实Production
环境的占位符。Octopus 甚至可以将其命名为Prod Space: Production
,因此我们都清楚这个环境的所有权。把这个想象成名称空间:你可以在多个空间中拥有一个Production
环境。
我们认为这个过程应该是这样的:
- 转到环境页面并点击
Add environment
按钮 - Octopus 可以显示它知道的空间列表,在这个例子中是
Prod Space
- 选择
Prod Space
(表示这是一个远程环境) - 选择或命名环境
Production
现在您已经配置了Prod Space: Production
环境:
- 您可以将步骤的范围扩大到
Prod Space: Production
,当一个版本最终被部署到那个环境中时,这些步骤将会运行。 - 您可以在您的
DevTest Space
中设置变量值,将它们限定在Prod Space: Production
的范围内,当一个版本最终被部署到那个环境中时,它们将被使用。
配置包含其他空间的生命周期
TL;DR 其他空间和远程环境可以添加到生命周期中。
为了将一个发布升级到Production
环境,您将需要配置一个能够针对Prod Space
的生命周期。我们认为您应该能够将空间添加到生命周期的各个阶段,就像您现在可以在 Octopus 中添加环境一样。对于我们的示例场景来说,这将很好地工作,在这个场景中,您只想将发布升级到Prod Space
。
如果你想创建一个更复杂的生命周期呢?例如,在将发布升级到Prod Space
之前,您将发布升级到QA Space
供 QA 团队测试。我们认为你应该能够将远程环境添加到你的生命周期中,让章鱼表现得就像那个环境是同一个空间的一部分一样。
仪表板和远程环境
TL;灾难恢复远程环境可以显示在仪表板上。
通过将远程环境添加到您的生命周期中,Octopus 会将该环境添加到您的仪表盘中。我们计划让你的部署结果流回源空间。这意味着您可以看到整个部署管道中的部署摘要,即使它跨越了多个空间界限。
最终您想要将一个发布部署到Production
环境中!既然您已经将Prod Space
添加到您的生命周期中,那么现在您可以将您的发布升级到Prod Space
。此时,Octopus 将创建一个我们称之为的发布包:一组文件,包括将该发布部署到其他空间所拥有的环境中所需的一切。
在我们的例子中,有人必须手动将发布包转移到Prod Space
并导入它。如果你的空间是连通的,Octopus 可以为你自动完成这个过程。
发布捆绑包
TL;DR 包含将一个版本部署到另一个空间所拥有的环境中所需的一切。敏感部分被加密,包被签名以验证完整性和信任。
我们仍在研究发行包的细节,欢迎您的反馈。
本质上,发布包将包含向远程空间发布所需的一切。这将包括:
- 发布详细信息:版本、注释、渠道
- 部署流程
- 变量
- 包裹清单
发布包将不包含包本身,而是包含发布所需的包的清单,包括 ID、版本和散列。这将使得能够以最有效的方式(可能使用增量压缩)将包转移或复制到其他空间,或者使得能够使用外部包馈送。这也将使目标空间能够验证被部署的包的身份和完整性——它们保证是被测试的包。
发布包将包含以前空间中已完成部署的摘要,允许它们选择性地显示在远程空间的仪表板上。
当构建发布包时,源空间将使用目标空间的证书加密任何敏感信息,因此它只能由目标空间解密。它还将使用源空间的私钥对包进行数字签名,因此目标空间可以在导入包之前验证包的来源和完整性。
发布包将有一个模式版本。包可以在具有兼容模式版本的空间之间转移。我们的希望是,发布包模式版本的更改频率将远低于 Octopus 服务器版本,允许一系列 Octopus 服务器版本之间的兼容性。
将版本导入您的共享空间
TL;DR 导入远程项目、发布、流程和变量快照。在导入时,您将选择发布的生命周期。
一旦发布包对Prod Space
可用,你将需要导入它。将会有许多细节需要解决,但是在最高级别上,我们期望该过程看起来像这样:
- 您将看到一个准备导入的发布包列表,您选择导入一个。
- 然后,您将看到一个显示,包括发布所需的包、捆绑变量值、变量模板和部署过程。
- 该项目将作为远程项目导入。与远程环境类似,您的项目将像
DevTest Space: My Project
一样命名。我们还认为远程项目在很大程度上应该是只读的,并且可能会使用与普通项目完全不同的 UI。 - 发布本身将与创建发布时冻结的部署流程快照和变量快照一起导入。
- 您将需要选择您想要通过
Prod Space
中的环境来推广这个版本的生命周期。如果您的项目只使用一个生命周期,它可以被自动选择。 - Octopus 将提示您在部署该版本之前为您的环境和租户设置任何缺失的变量值。
导入的内容将是只读的
TL;其他空间拥有的东西通常是只读的。
我们认为值得一提的是:几乎所有将要导入的内容都是只读的,一些概念不会跨越空间界限进行传输。最终目标是将一个版本可靠地部署到您的环境中,尽可能避免人工参与。仍有许多细节需要整理,但我们认为一个好的经验法则是:
- 用于构建发布的任何内容在远程空间都是只读的
- 用于自定义部署的任何内容都可以在远程空间中编辑
例如,我们预计您将希望部署使用创建版本时的流程(可重复性),但有机会为您的环境/租户设置正确的数据库连接字符串(可变性)。
以下是我们不打算进口的一些具体例子:
- 项目触发器都是关于自动触发部署的——它们应该在部署发生的地方进行配置
- 租户是关于分配部署目标和定义部署变量值的——它们应该在部署发生的地方进行配置
- 只有在您创建一个版本的时候,通道才真正重要,并且您将需要在导入时选择一个生命周期——通道应该在创建版本的地方进行配置
比较版本
TL;我们将添加查看特定版本的部署过程和项目变量的能力,并查看两个版本之间的差异。
我们认为这个特性的一个重要部分将是查看和理解部署过程和项目变量的能力,这些变量在创建版本时被冻结在一个快照中。想象一下,试图导入和批准一个发布以进行部署,却看不到将在部署期间使用的流程和变量值?
这实际上是我们很久以前就想解决的一个问题:在今天的 Octopus 中,您可以看到变量 snapshot(如果您能找到正确的链接),但是您看不到部署过程,因为它是在创建发布时定义的。想象一下,如果您甚至可以并排查看版本,相互比较它们!
部署版本
TL;DR 您将能够部署该版本,就像它是在这个空间中创建的一样。
现在该版本已经被接受,可以部署到Prod Space
中的环境中了。对于所有意图和目的,这将像在Prod Space
中创建版本一样工作:所有相同的规则将适用于部署该版本,包括:
- 项目权限 -团队可以被限制为远程项目,就像普通项目一样-毕竟,它们只是普通项目,但是属于另一个空间
- 环境权限-
Prod Space
中的队伍可以被授予Prod Space
中环境的适当权限,就像平常一样 - 生命周期进展 - Octopus 将确保每个版本在
Prod Space
中通过适当的生命周期,就像平常一样
房客
TL;灾难恢复租户是部署时的问题,不会跨越空间界限。
我们想特别指出租户,因为他们可以像环境一样被对待。此时,我们还不能 100%确定我们将如何处理租户,但我们目前的想法是:
- 租户将归执行部署的空间所有。
- 一个空间中的租户无法连接到另一个空间拥有的环境。
我们很想在这里听到你的想法。请在下面的评论中告诉我们。
替代解决方案
此功能旨在取代当前用于迁移或部署到远程机器的方法:
- 章鱼迁移器导入
- 离线丢弃
octo.exe
出口/进口- 使用 Octopus REST API 的定制解决方案
- 手动迁移所有内容
如果您已经使用了 migrator 或octo.exe
import/export 来在 Octopus 实例之间移动发布,您就会知道这两种方法都有好处,但是仍然需要大量的脚本或交互来实现您的目的。这两者都将被弃用,并被远程版本升级所取代。对于那些使用源代码控制中的 JSON 文件来检测更改和备份过程的用户,迁移器导出仍然存在。
我们知道有些客户编写了自己的迁移脚本和流程。我们已尝试解决这些解决方案包含在远程发布功能中的所有方法和问题;如果您认为此功能缺少了什么,请告诉我们。我们的目标是您可以使用远程发布促销,而不必维护自己的脚本。
这项功能也将取代离线下降。虽然这可能看起来没有直接的关联,并且您将需要另一端的 Octopus 服务器来捕捉发布包,但是许多关于离线放置的建议和限制是 Octopus 服务器所提供的缺失部分。这些包括基本的编排、输出变量、日志记录和部署状态等等。它将允许您将发布移动到网络边界内的集中 Octopus 服务器,并通过部署到本地触角来利用扩展的编排。
参考架构
在一个远程促销的世界中,你将能够使用多个 Octopus 服务器来模拟许多有趣的场景,即使 Octopus 服务器是连接的还是断开的,它们之间也会有释放流。
示例:连接的网络
互联架构将考虑多个团队在他们自己的空间中工作,然后将发布推送到在美国本地托管的生产空间,以及在澳大利亚和欧洲的公共云中。所有的空间都由同一个 ODCM 管理,这使得全球管理团队和信托变得很容易。由于所有空间都是相互连接的,因此您可以实现部署的高度自动化,甚至跨多个空间。
【T2
示例:断开的网络
不相连的网络有多个团队在各自的空间工作,然后将发布内容推送到托管在其他数据中心的生产空间。这里的重要区别在于,每个数据中心的空间都由各自的 ODCM 管理。您仍然可以获得与连接架构完全相同的最终结果,唯一的缺点是您无法在不连接的空间中实现相同级别的自动化。
反馈
希望你能看出我们在这组特性上花了很多心思。我们试图涵盖我们在与各种客户的关系中遇到的所有场景和细微差别。这里有很多东西需要消化,所以谢谢你坚持到最后。你是王牌。
我们非常希望收到您的来信!哪些特征让你产生共鸣,会让你的处境变得更好?也许我们在你的场景中遗漏了一些拼图?
欢迎在下面留下你的评论,或者如果你真的想参与到设计的细节中,请到我们的规格库来加入讨论吧!
真实的 devo PS——用目标角色表示内部环境名称——Octopus Deploy
原文:https://octopus.com/blog/representing-internal-environment-names-with-roles
你好!我是 Ryan,Octopus 的解决方案架构师。我经常与客户合作,为他们在采用 Octopus 时遇到的工作流程和问题寻找解决方案。
今天,我们将看一个围绕 Octopus 的多租户特性的场景。对于多租户项目,您可以在不同的时间使用不同的配置向多个租户(客户、数据中心、区域等)部署相同应用程序的不同版本。这在 SaaS 环境中很常见,每个客户都有自己的软件实例。当您准备好部署新版本的软件时,您可以先将其推广到一些 alpha 客户,然后推广到 beta 组,然后继续推广到每个客户/租户,直到他们都被更新。您可以在我们的多租户文档中了解更多关于多租户部署的信息。如果您有任何问题,您可以通过我们的支持渠道或请求演示。我很乐意与您一起体验多租户。
场景
好吧,让我们开始吧!
在这个场景中,我们有一组客户租户(海王星、木星、土星)。在内部,我们的基础架构团队以特殊的约定命名了客户服务器所属的环境。该约定是“年创建的 - 客户名称 - 环境名称”。所以海王星的测试机器在一个名为 2017-Neptune-Test 的环境中。基础设施团队还希望用 Octopus 表示这些内部环境名称。
让我们看看一些选择和它们带来的一些副作用。
痛点
环境视图
在 Octopus 中对特定于租户的环境建模最明显的方法是在基础设施部分将每个环境都创建为一个环境。每个环境中的目标仍然与租户相关联,因为这是多租户工作所必需的。
在这种情况下,环境页面看起来不错。我们可以可视化我们的环境、它们属于哪个租户,并看到我们的内部名称。所有的框都被选中了!
然而,我们发现在 Octopus 中建模大量环境会导致与一些进程和页面的冲突。因此,我们建议使用少量环境来代表您的基础设施。
生命周期
建立生命周期引入了第一个问题。为了让所有这些环境都只有一个生命周期,我们将这些阶段设置为只需要在提升到下一个阶段之前部署到其中一个环境。这意味着我们可以部署到 2017-海王星-测试,然后推广到 2018-土星-产品。这不是一个理想的场景,因为这意味着我们可能会在测试之前意外地将一个版本部署到客户的生产环境中。
为了解决这个问题,我们可以为每个租户创建一个生命周期,然后为每个租户创建一个通道来选择生命周期。你可以看到它的发展方向。添加一个新租户变得很麻烦。我们必须添加新的环境、生命周期和渠道,以确保我们可以安全地部署我们的应用程序。
项目概述
这也为我们的项目概述创建了一个稀疏的表格。我们最终会有太多的环境使这个页面变得无用,而且随着我们每增加一个新租户,这个问题会变得更糟。一个很好的经验是,如果你发现自己在 Octopus 中水平滚动,伸出手看看是否有另一种方法来做事情可能是一个好主意。
所以问题是,我们是否可以配置我们的实例,使其具有更标准的开发、测试和生产环境设置,同时仍然跟踪这些内部环境名称?答案是,希望不出意外,是的!
输入目标角色
我们可以用目标角色对此建模。您只能使用角色来确定对哪些目标执行哪些步骤。但是您也可以使用角色用元数据来标记机器。在本例中,我们用机器的内部环境名来标记机器。
我们不会将部署目标放在特定于租户的环境中,而是放在通用环境中(开发、测试、生产)。我们将把内部环境名称作为一个角色添加到部署目标中,然后修正我们项目的生命周期。之后,我们将有一个清晰的项目概述,并且我们仍然能够在基础结构页面上看到我们的内部环境名称。
更新部署目标
让我们来看看 2017-Neptune-Dev 环境中的部署目标之一。
由于这实际上只是一台开发机器,我们将把它从 2017-Neptune-Dev 移到开发环境中。然后我们给它一个角色“2017-neptune-dev”来代表我们的内部环境名。图中没有包括租户仍然连接到目标。这在整个配置中不会改变。
现在,我们将冲洗并重复所有部署目标。
更清洁的环境视图
我们已经更新了所有的目标,并删除了未使用的环境。现在环境看起来又好又整洁。
清洁器项目概述
同样,在更新项目生命周期以使用我们的默认约定之后,项目概述就更容易管理了。不再水平滚动!
项目层面还有一点要提。因为新角色仅用于传递信息,所以我们没有更改用于部署到目标的角色。我们的部署步骤继续以“projectx-web”为目标。
按内部环境名称查看目标
我们可以使用基础架构页面上的高级筛选器来搜索这些角色,以查看属于内部环境名称的计算机。
包扎
我们所有的框都被选中,我们的项目是干净的,我们在向我们的项目添加新租户时没有任何摩擦!
我们处理了一个涉及租户的非常具体的案例,但是您可以使用这种方法来建模目标属于哪个项目,而无需为每个项目创建单独的环境。你在 Octopus 中使用过角色来建模信息吗?在下面留言告诉我们吧!
重置 RavenDB 索引- Octopus 部署
如果您运气不好,在升级您的 Octopus Deploy 服务器之后,或者在数据库恢复之后,您可能会注意到一些奇怪的行为:
- 用户、项目或环境可能已经完全从 UI 中消失了
- 尝试导航可能会导致不断的错误,如“等待 15,103 毫秒,查询返回非过时的结果。”
为什么会出现这些情况?Octopus 建立在文档数据库 RavenDB 之上。Raven 中的数据被保存到 ESENT 中,这是支持微软 Exchange 和 Active Directory 的相同数据库技术。但是 Raven 也在 Lucene 中保存数据的索引,它在执行查询时会用到这些索引。
当这些 Lucene 索引出现问题时,通常会出现上述错误。我注意到客户升级 Octopus 或恢复备份,却发现数据丢失。但是如果你进入 RavenDB 管理工作室,你经常可以看到这些数据。它就在那里,只是没有索引。
在 Octopus 中连接到 RavenDB 管理工作室
一个可行的快速解决方案是重置索引,然后重启 Octopus,以便重建所有索引。为此,首先需要连接到 RavenDB management studio。
studio 是在 Silverlight 中构建的,所以您需要确保无论您从哪台机器上安装了 Silverlight 浏览器插件(或者您可以安装它)。
RavenDB Management Studio 通常位于 Octopus 服务器的 10930 端口上,因此浏览到http://_your-octopus_:10930
通常是有效的。如果没有,并且您可以访问 Octopus 门户网站,请尝试:
- 前往八达通门户网站
- 进入配置菜单,然后进入存储选项卡
- 按照页面上的链接访问 Raven
如果这不起作用,你需要确保防火墙规则不会阻止你在那个端口浏览 Octopus。或者,您需要 RDP 到 Octopus 服务器并在本地浏览到它(即http://localhost:10930
)。
最后,当您连接时,会要求您进行鉴定:
您需要在这里提供的凭证不是您的八达通凭证。您需要作为分配给 Octopus 服务器上本地管理员组的 Windows 用户进行身份验证(例如,机器上的管理员帐户)。
重置索引
现在您已经在 Raven 中了,您可以转到索引选项卡并单击“删除所有索引”:
注意,这并没有删除任何数据;它只是删除了索引,这些索引将在下一步自动重建。
重启章鱼
要让 Octopus 重建索引,您需要重启它。
- RDP 到你的八达通服务器
- 打开服务管理控制台, services.msc
- 选择“八达通”服务
- 点击停止,等待停止
- 点击启动,并等待其启动
您还需要重启 Octopus web 门户,这是一个 IIS 应用程序。
- 打开 IIS 管理器,inetmgr.exe
- 在树中,展开您的服务器,并转到应用程序池
- 选择八达通门户应用池
- 执行停止和启动
给它一分钟左右的预热时间,然后通过你的浏览器进入 Octopus 门户网站,希望一切都正常。如果您仍有问题,请联系我们的支持表单。
保留策略- rfc - octopus 部署
Octopus 的下一个版本将包括保留政策的概念。这些允许您控制保留多少版本和部署,以便保持对磁盘空间使用的控制,并帮助提高性能。在这篇博文中,我想分享一些关于这个新功能如何工作的计划,以便得到你的评论。
设置保留策略
保留政策将插入最近发布的项目组功能中。这允许它们被配置用于一组相似的项目,而不需要重复太多次。
前两个设置与 Octopus web 门户相关,它们控制何时从 RavenDB 数据库中删除发布、部署和任务对象。例如,如果您有一个 CI 服务器,它使用 Octo.exe 在每个版本中创建发布,这些设置就很有用。如果这样一个版本从未在一周内部署,您可能会想要删除它。另一方面,您可能希望将已经部署到环境中的版本保留更长时间。
第三个设置影响触须,并控制何时删除提取的包。如果你还不知道的话,触手会根据版本号把包放到不同的文件夹中——例如:
C:\Apps\Production\MyApp\1.0.0
C:\Apps\Production\MyApp\1.0.1
C:\Apps\Production\MyApp\1.0.2
C:\Apps\Production\MyApp\1.0.2_1 // This was a retry
C:\Apps\Production\MyApp\1.0.3
如果将此设置设置为保留 3 个部署,则在部署 1.0.3 时,将删除文件夹 1.0.0 和 1.0.1。注意,第三个设置是使用部署的数量而不是天数来指定的,以避免在每个触手上运行计时器/调度作业只是为了清理。
NuGet 包(在 Octopus 和触手上)也将被清理,但这将是一个自动功能,每 12 小时运行一次,清理超过三天的包,所以我不希望它是可配置的。这是因为 NuGet 包总是可以从 NuGet 服务器上重新下载。
请求反馈
我想确保这个特性是强大和有用的,但我也想让它尽可能简单。你觉得上面的模型怎么样?项目组是定义保留策略的正确级别吗?我将非常感谢您在下面的框中的评论!
重新思考功能分支部署- Octopus 部署
原文:https://octopus.com/blog/rethinking-feature-branch-deployments
我在 2013 年过渡到 Git。从那时起,我一直在做错误的特性分支测试。问题是,我在同样的静态环境中工作, 开发➜测试➜筹备➜生产 。每个环境都有我的应用程序的一个实例,它们都反映了master
分支中的内容。QA 测试新特性的唯一方法是将代码合并到master
中。在一个完美的世界里,我会为特性分支建立一个沙箱,让 QA 进行测试。 开发➜测试➜筹备➜生产 生命周期代表了我的前 Git 生活。在本文中,我介绍了我是如何调整自己的思维来更好地利用 Git 的。
特征分支和特征标志
本文讨论了特性分支工作流,但是本文也可以用于那些遵循 Gitflow 工作流的工作流。这种方法还有一个替代方案,特性标志。在深入本文的其余部分之前,我想讨论一下特性分支和特性标志之间的区别。
正如我之前提到的,我在 2013 年开始使用 Git。那时,我为一家银行开发贷款发放系统。当有人来贷款时,信贷员会将他们的详细信息输入到我的团队负责的贷款发放系统中。在任何时候,银行都有数百名用户在使用该软件。他们对它应该如何工作都有不同的想法,他们会提交各种各样的特性请求、错误报告和其他各种各样的信息。贷款发放系统很复杂。看似简单的功能需要大量的测试。
假设有一个功能请求,其中声明“对于两个或更多人的贷款,显示每个人在过去六个月中的最低信用评分,而不是显示当前的信用评分。”要实现该功能,需要解决许多实现问题和细节。开发人员将创建一个名为feature/lowest-credit-score
的新分支,并开始他们的实现。最终,开发人员会到达这样一个点,他们对变更感觉良好,并准备好接受来自 QA 和特性业务所有者的反馈。他们应该如何获得反馈?
我们遇到了两个主要问题:
- 代码未准备好用于生产。用于部署的唯一分支是
master
。在它被并入master
之后,如果有人不小心的话,它可能最终会进入生产。 - QA 有几个自动测试来验证显示当前信用评分的旧业务规则。
通常,我见过两种解决这个问题的方法:
- 创建一个名为
LowestCreditScore
的特征标志。当特征标志设置为True
时,它将使用新的逻辑。当该标志被设置为False
时,它将使用旧的逻辑。 - 在测试或 QA 环境中创建应用程序的新实例,也称为创建沙箱,以获得反馈和 QA 更新测试的地方。
这两种方法各有利弊。特性标志以一种讨厌的方式嵌入到代码的每一个地方,并且它们引入了复杂性。代码中的每个 if/then 语句都需要额外的单元测试。但是特性标志允许您在关闭新特性的情况下部署代码。当你准备好的时候,你可以打开这个功能。或者,您可以为一组人打开该功能以获取他们的反馈。
创建沙箱需要额外的资源,如 CPU、磁盘空间和 RAM。如果沙盒没有清理干净,很容易达到资源耗尽的地步。在 web 服务器中只能创建这么多网站,在数据库服务器中只能创建这么多数据库。但是它们允许某人得到反馈,并允许 QA 验证,而不需要将未完成的代码合并到master
中。
令人高兴的是,这些选项并不相互排斥。你可以两样都要,也可以两样都不要。以我的经验来看,拥有一个独立的沙箱来测试和反馈一个新的特性(或者热修复)会避免很多麻烦。在这个例子中,我会推荐这两种方法。我可以预见许多来回,这一变化可能会对贷款的批准方式产生影响。如果在生产中结果是负面的,能够快速关闭变更是非常有益的。
部署生命周期
我一直忙于配置一个应用程序来演示功能分支。我开始了两个生命周期以及相应的通道。这些生命周期代表了我从 2004 年开始作为开发人员以来一直使用的相同过程。
- 默认: 开发➜测试➜暂存➜生产
- 特征分支: 开发➜测试
预期的开发工作流程是:
- 所有的特性分支都转到
Dev
环境,如果成功了,它们就转到Test
让 QA/业务所有者验证工作。新的基础设施仅用于该特征分支。分支名称存储在预发布标签中。 - 当特征分支看起来正常时,PR 会将特征分支合并到主分支中。与特性分支不同,这个包没有预发布标签。这将把合并的代码部署到静态的
Test
数据库中。然后备份和删除特征分支基础结构。 - QA 和业务负责人将验证在静态
Test
环境中一切看起来仍然良好。 - 该版本将被推到
Staging
并重新验证。 - 假设一切顺利,该版本将被提升到
Production
。
随着我为这篇文章设置的例子越来越深入,同一个问题不断出现在我的脑海里:
为什么我要在合并到 master 后重新部署到 dev 和 test?为什么这些环境有静态资源,如数据库、网站和文件存储?
静态与动态环境
静态环境是这样一种环境,其中特定于应用程序的资源(数据库、文件存储等。)必须是静态的。启动和使用新资源需要大量的协调和沟通,以避免中断或停机。任何影响非 IT 人员(外部用户、业务用户等)的停机时间。)被认为是大事。部署到这些环境的时间间隔以天为单位,而不是以分钟为单位。
通过 Octopus Deploy 部署的大多数应用程序使用 RDMS 数据库,如 SQL Server、Oracle、PostgreSQL 和 MySQL。数据库的Production
实例必须是静态的。为每个部署创建一个新的数据库副本是不可行的。尽管这很酷,但时间和资源(磁盘空间、CPU 使用率、RAM 使用率)成本太高。每周部署一个应用程序到Production
被认为是快速的。通常,每两周一次到每季度一次。
基于这些原因,Production
是一个静态环境。
对于公司来说,在进入Production
之前,有一个类似生产的环境来运行最终测试是很常见的。这些环境有类似于Staging
、Pre-Prod
、Integration
或UAT
的名字。出于本文的目的,我将使用Staging
来表示这些环境。
Staging
尽可能的生产化。这包括向外部用户公开网站,以测试即将推出的功能。即使它没有公开,非 it 人员也可以在前往Production
之前访问它以尝试新功能。Staging
被部署到比Production
更频繁的地方,也许一天一次,也许甚至两次。
根据静态环境标准,Staging
也是一个静态环境。
动态环境是特定于应用程序的资源不断变化的环境。资源可以根据需要上下旋转。或者身份验证管道中是否有错误。或者一个人在做部署,还没完成。停机时间虽然恼人,但却是可以容忍的,而且仅限于 IT 人员。部署一天发生多次。
Test
是一个充满活力的环境。
静态测试环境的缺点
想到Dev
和Test
环境,我就想到一个词:搅动。
测试环境很少是稳定的
我曾经参与过这样的项目,在关键时刻,我们在 10 小时内完成了 20-25 次Test
部署。也就是说,每 24-30 分钟部署一次,而与此同时,我们对Dev
的部署是原来的 1.5 倍。这是因为应用程序只有一个Dev
实例和一个Test
实例。当 QA 测试新特性的时候,我们将我们的更改合并到 master 中,并将其部署到Test
。信不信由你,我们第一次尝试并不完美。
这些 20-25 个部署实际上是每个特性 4-5 个部署。有些部署是由于错误,而有些是由于不正确的合并冲突解决方案。在合并到 master 之前,特性只完成了一半,而且在同一天完成多个特性的情况很少见。计划何时发布代码有点像噩梦。在预定发布之前,我们会冻结《T21》的特色工作,花几天时间研究 bug。冻结意味着没有新功能可以合并到主模块中。
我们的团队区域有一块白板,告诉我们是否可以合并到 master 中。
这也使得部署补丁变得非常困难。我们必须创建一个单独的通道,绕过Dev
和Test
,直接进入Staging
。
很少有应用程序是完全孤立的
随着 SOA 和微服务的出现,应用程序不再是这些巨大的(孤立的)整体。以前,每个应用程序都有自己的日志、身份验证、配置和通知模块。当您只有一个应用程序时,这没问题,但是当您将两个或更多的应用程序混合在一起时,维护这些重复模块的成本就会增加。幸运的是,公司明白了这种重复的努力,现在他们有了单一的日志服务器或单一的认证服务,等等。
这就引出了我的下一个观点,应用程序似乎比以往任何时候都更加依赖于其他应用程序才能正常运行。我的应用程序所依赖的服务同时被更改。如果一个依赖的服务由于一个 bug 而关闭,我的应用程序就会进入降级状态,某些功能会受到限制,直到那个依赖的服务恢复。
我们这样做是因为我们将应用程序指向同一个环境中的服务。当每个环境都是静态的时,这是有意义的。我的应用在Test
,一个依赖服务在Test
。该服务的 URL 从不改变;我应该在我的申请中指出这一点。
开发环境的意义是什么?
我工作过的大多数地方很少使用Dev
环境。它没有太多的数据来测试。它几乎总是停机,没有人真正使用它。它就在那里。一些团队对它进行了自动化测试,但是结果通常被忽略,因为数据太过时了。如果部署失败,我们会创口贴解决方案,这样它就可以到达Test
环境,QA 就可以继续工作。
Dev
一直是一个橡皮图章的环境。
重新思考功能分支部署的环境
Production
和Staging
不会去任何地方,也不应该去,但是Dev
和Test
需要完全重新考虑。
- 将
Dev
和Test
组合成一个环境:Test
。 - 当一个新的特征分支被检入时,该特征分支的新基础设施在
Test
中启动。 - 该功能分支的后续签入将转到该基础结构。
- 默认情况下,
Test
中的所有应用程序都使用其依赖应用程序的Staging
实例。当 1 到 N 个应用程序的工作紧密耦合时,这可以被重写以指向Test
。 - 当功能分支被合并到主分支中时,一个部署就开始了,它将测试基础设施拆除,然后自动将合并的代码部署到
Staging
。 - 在最终验证和签署后,代码被推送到
Production
。
生命周期将是:
- 默认: 暂存➜生产
- 特征分支:
Test
未完成的代码将不再被合并到主代码中。这将使发布的时间安排变得更加容易。主文档中的内容已经由 QA 和任何业务或产品负责人签署。您可以在星期一将特性 A 合并到主服务器中,在星期二部署,然后在星期三将特性 B 合并到主服务器中,在星期五部署。每次部署也会小得多。
这同样适用于错误修复。它们将被视为特征分支。当一个热修复分支被检入时,新的基础设施被建立以验证该修复确实修复了问题。当它准备就绪时,它被合并到 master 并被推送到Staging
。
如果您使用的是 Gitflow,那么您可以配置构建服务器,当一个变更被签入到一个发布分支中时,启动到Staging
的发布。
合并冲突并引入最新变更
每个特性分支都有自己的沙箱来测试变化。一个功能可能需要一天或一个月才能完成。拥有单独的沙箱来测试变更有很多好处,但是通过定期从master
分支获取变更来保持他们的特性分支是最新的,这取决于开发人员。随着时间的推移,将master
中的最新变化合并到一个特性分支中会变得更加困难。我的团队开发的一个特性在 QA 测试之前花了四周时间。开发人员一直等到最后才把 master 合并到他们的分支中;解决所有合并冲突花了四天时间。
我不是说你需要停止你正在做的事情,每小时从master
合并。我建议建立一个有规律的节奏,每天一次,每两天一次,只要有事情就行。你开始得越早,遇到的头痛就越少。除非有人对代码进行大规模重构。无论你开始得多快,合并都不会有意思。
理想情况下,合并到master
中的变更数量会随着新的工作流程而减少。记住,在被合并到master
的变更完成一半之前。他们已经为测试做好了准备,但是我很少看到没有至少一个修改就通过测试的变化。不再将每天 20-25 次的变化合并到master
中,而是减少到每周 3-4 次。
会发生合并冲突。开发人员需要互相交流,让每个人都知道他们在代码的哪个区域工作。产品负责人或业务分析师应该与开发人员合作,确保他们不会提交涉及相同代码的特性请求。这个新的工作流程将有助于确保生产就绪代码被合并到主代码中,而不是帮助更好地组织开发人员。
外部服务和数据库
在这种配置中,Staging
环境尽可能与Production
匹配,这意味着它应该是稳定的。唯一一次Staging
与Production
不同是在Production
部署之前。
根据经验,所有依赖于外部或下游服务的Test
中的服务都应该指向Staging
。我意识到这说起来容易做起来难。
应用程序通常存储从外部服务获得的唯一标识符。在旧的工作流中,因为Staging
和Test
是相互隔离的,所以它们会有不同的标识符。好消息是这些标识符通常存储在数据库中。这就引出了我的下一点,数据库。没有数据测试的新沙盒是没有用的。这些数据需要来自某个地方。当您创建特性分支沙箱时,我建议恢复最近备份的Staging
数据库,而不是复制同一个Test
数据库。
跨多个应用程序的紧密耦合的变化
对于一个大型项目来说,涉及多个应用程序是很常见的,这些变化紧密地耦合在一起。在这种特定情况下,将创建一个沙箱中的沙箱:
毫无疑问,这种情况会让事情变得更加棘手。为特定应用程序的特性分支构建沙盒相对容易,因为您可以控制该应用程序。其他团队会有不同的节奏。你可能会在另一个团队的沙箱准备好之前就把自己的沙箱放好。当你等待另一个团队时,你的沙箱需要工作,所以你将指向Staging
。当所有相关的沙箱上线时,您需要更新您的配置条目以在Test
中指向它们。
简单的方法是说,“不要像那样紧密耦合应用程序的变化。”从发布的角度来看,部署这个项目要困难得多,因为应用程序必须按照特定的顺序部署。延迟会自行复合,你会听到,“我的应用程序无法修改,直到[Team X]的服务被修改。“虽然解耦是最终目标,但在实践中,紧密耦合的应用程序会发生变化。
对于依赖服务,您需要担心两件事:
- 服务的 URL。
- 服务的数据协定。
URL 需要修改为指向Test
中的特性分支沙箱。有多种方法可以实现这一点:
- 配置转换——您创建一个配置转换,它指向您特性分支中的
Test
中的特定服务。在相关服务被提升到Staging
之后,配置转换被删除。 - 新配置值——添加一个指向
Test
中服务 URL 的新配置值。在相关服务被提升到Staging
之后,新的配置值将被删除。这种方法的缺点是还需要修改代码。 - 利用多租户,将功能分支部署到特定租户。服务 URL 存储为租户变量。优步写了一篇关于他们如何利用多租户的精彩文章。
- 利用负载平衡器或 URL 来动态路由流量——我在这方面没有太多经验,但我听说有人用这种方法取得了成功。
数据契约是另一个问题,即使对于 JSON 和 RESTful 服务也是如此。像向 JSON 对象添加属性这样简单的事情都会产生影响。更不用说新的端点、新的对象或更改的属性了。关于这个主题的书层出不穷。我发现最有效的方法是分析所做的更改,如果是新属性,编写代码时假设新属性为空。如果是新的端点或对象,请查看功能标志。这样,应用程序可以独立于下游服务进行部署。
一个独立的沙箱使得项目的测试和验证更加容易,但是这将涉及更多的准备工作。
新基础架构与重用现有基础架构
这是一个你必须回答的问题。
当您启动沙盒时,您会启动新的基础架构还是重用现有的基础架构?
建设新的基础设施有一个好处,那就是完工后更容易拆除。尤其是,如果你使用的是 AWS、Azure 或 GCP。
然而,无论是在云上运行还是自托管,新的基础架构都有相关的成本。对于云来说,每天的费用是真实的。对于自托管,存在时间问题和资源利用问题。例如,SAN 是否有足够的空间容纳另一个虚拟磁盘,或者虚拟机管理程序中是否有足够的 v-CPU 可用?一般来说,Test
中的大多数服务器都没有得到充分利用。即使在 QA 进行测试时,如果查看资源监视器,CPU 使用率也可能低于 20%。大多数 web 服务器、数据库服务器和应用服务器允许您托管多个应用程序、网站或数据库。
这个问题没有对错之分。这是做对你、你的理智和你的公司最有利的事情。
利用 Octopus 部署
在我的下一篇文章中,我将介绍构建服务器和 Octopus 部署的完整设置。
一般而言,该解决方案将使用 Octopus Deploy 中的以下功能:
结论
在阅读 Git 工作流时,您会经常看到短语分支和主干。分支的概念是短暂的,一段时间后,它要么合并回主干,要么被修剪。树干是长寿的,它是树枝的分支。
本文建议将您的测试环境视为分支,将登台/生产环境视为主干。这是一种思想转变,说起来比实际做起来容易得多。正如我在上一节中所说的,在我的下一篇文章中,我将提供一个使用这个新工作流配置 CI/CD 管道的指南。它将包括构建服务器和 Octopus 部署。
下次再见,愉快的部署!
可重复使用的 YAML 与 CircleCI 球体-章鱼部署
原文:https://octopus.com/blog/reusable-yaml-with-circleci-orbs
持续集成和持续交付平台中的一个增长趋势是提供将管道定义为代码的能力,通常使用 YAML。这个领域的领导者之一是 CircleCI。在这篇文章中,我们将看看 CircleCI 配置,包括如何使用和创作 CircleCI orbs
。orb 是一个可重用的 YAML 块,可以跨 CircleCI 管道使用。您可以从 orb 中引用功能,而不是在多个文件之间复制和粘贴相同的代码,并保持管道畅通。
什么是 CircleCI
CircleCI 是一个持续集成平台,它使用基于 YAML 的配置在 Docker 容器或虚拟机上执行作业。这些工作可以被编织到一个或多个工作流中,这些工作流将在您将代码提交到源代码控制时执行。
下面是一个名为build
的 CircleCI 作业示例:
jobs:
build:
docker:
- image: mcr.microsoft.com/dotnet/core/sdk:2.1.607-stretch
environment:
PACKAGE_VERSION: 1.3.<< pipeline.number >>
steps:
- checkout
- run:
name: Install Cake
command: |
export PATH="$PATH:$HOME/.dotnet/tools"
dotnet tool install -g Cake.Tool --version 0.35.0
- run:
name: Build
command: |
export PATH="$PATH:$HOME/.dotnet/tools"
dotnet cake build.cake --target="Publish" --packageVersion="$PACKAGE_VERSION"
- persist_to_workspace:
root: publish
paths:
- "*"
让我们一点一点地把它分解。
首先,我们定义在哪个 docker 映像上执行我们的工作:
docker:
- image: mcr.microsoft.com/dotnet/core/sdk:2.1.607-stretch
我们为包含 CircleCI 管道号的包版本定义了一个环境变量:
environment:
PACKAGE_VERSION: 1.3.<< pipeline.number >>
我们定义我们的步骤。第一步是检查我们的源代码:
steps:
- checkout
第二步在执行作业的容器上安装 Cake:
- run:
name: Install Cake
command: |
export PATH="$PATH:$HOME/.dotnet/tools"
dotnet tool install -g Cake.Tool --version 0.35.0
第三步调用 cake 来构建我们的项目:
- run:
name: Build
command: |
export PATH="$PATH:$HOME/.dotnet/tools"
dotnet cake build.cake --target="Publish" --packageVersion="$PACKAGE_VERSION"
最后一步是将我们在publish
文件夹中发布的应用程序保存到 CircleCI 工作区。工作区用于跨多个作业共享资产:
- persist_to_workspace:
root: publish
paths:
- "*"
让我们来看一个使用该作业的工作流。在定义工作流的作业时,我们可以将一个作业设置为依赖于一个或多个其他作业。我们已经创建了一个名为build
的工作流,它将依次运行作业build
、package
、push
、create-release
和deploy-release
:
workflows:
version: 2
build:
jobs:
- build
- package:
requires:
- build
- push:
requires:
- package
- create-release:
requires:
- push
- deploy-release:
requires:
- create-release
可重复使用的 YAML 和 CircleCI 球体
比方说,我们有多个使用 Cake 构建的项目,步骤都差不多。对于每个项目来说,使用一个标准的过程而不在每个配置中定义相同的 YAML 不是很好吗?
这就是球体出现的地方!orb 定义了可重用的执行器(您的作业将在其中运行的环境)、可在作业中使用的命令以及可在工作流中使用的作业。
使用球体
要使用 orb,需要在配置中导入一个orbs
部分。在下一节中,我们添加了松弛球,命名为slack
,并将其映射到球circleci/slack@3.4.2
。circleci
是包含 orb 的名称空间。slack
是宝珠的名字。3.4.2
是我们要用的 orb 的版本。
在我们的build
工作中,我们添加了一个新步骤- slack\notify
。这一步是来自那个球的notify
命令。您可以查看命令的来源。这是相当多的 YAML,我们不需要包括在我们的配置中。
version: 2.1
orbs:
slack: circleci/slack@3.4.2
jobs:
build:
docker:
- image: mcr.microsoft.com/dotnet/core/sdk:2.1.607-stretch
environment:
PACKAGE_VERSION: 1.3.<< pipeline.number >>
steps:
- checkout
- run:
name: Install Cake
command: |
export PATH="$PATH:$HOME/.dotnet/tools"
dotnet tool install -g Cake.Tool --version 0.35.0
- run:
name: Build
command: |
export PATH="$PATH:$HOME/.dotnet/tools"
dotnet cake build.cake --target="Publish" --packageVersion="$PACKAGE_VERSION"
- persist_to_workspace:
root: publish
paths:
- OctopusSamples.OctoPetShop.Database
- OctopusSamples.OctoPetShop.Infrastructure
- OctopusSamples.OctoPetShop.ProductService
- OctopusSamples.OctoPetShop.ShoppingCartService
- OctopusSamples.OctoPetShop.Web
- slack/notify:
message: Build ${PACKAGE_VERSION} succeeded
因此,让我们为我们的蛋糕步骤创建一个球体。
创建内联球体
在上面的例子中,我们在配置中导入了一个 orb。我们也可以在配置中直接定义一个 orb:
orbs:
cake:
executors:
cake-executor:
docker:
- image: mcr.microsoft.com/dotnet/core/sdk:2.1.607-stretch
jobs:
build:
executor: cake-executor
parameters:
cake_version:
default: "0.35.0"
type: string
package_version:
default: "$PACKAGE_VERSION"
type: string
publish_path:
default: "publish"
type: string
target:
default: "Publish"
type: string
steps:
- checkout
- run:
name: Install Cake
command: |
export PATH="$PATH:$HOME/.dotnet/tools"
dotnet tool install -g Cake.Tool --version << parameters.cake_version >>
- run:
name: Build
command: |
export PATH="$PATH:$HOME/.dotnet/tools"
dotnet cake build.cake --target="<< parameters.target >>" --packageVersion="<< parameters.package_version >>"
- persist_to_workspace:
root: << parameters.publish_path >>
paths:
- "*"
在上面的 orb 中,我们创建了一个执行器,它定义了要使用的 Docker 映像。我们还定义了一个build
作业,它接受 Cake 版本、包版本、发布路径和 Cake 目标作为参数。然后,它以类似于我们之前的步骤使用这些参数。
定义 orb 后,我们可以将工作流更新为以下内容:
workflows:
version: 2
build:
jobs:
- cake/build:
package_version: 1.3.<< pipeline.number >>
- package:
requires:
- cake/build
- push:
requires:
- package
- create-release:
requires:
- push
- deploy-release:
requires:
- create-release
现在我们的工作流将使用我们在配置中定义的蛋糕 orb 中的build
作业。
发布球体
为了在多个项目中使用这个 orb,我们需要发布它。为此,我们需要安装和配置 CircleCI CLI 。
完成后,我们为我们的组织创建一个名称空间:
circleci namespace create octopus-samples github OctopusSamples # replace with your values
然后,我们在名称空间中创建 orb:
circleci orb create octopus-samples/cake # replace with your values
现在要将内嵌的 orb 发布到 CircleCI。我们需要将 orb 定义从我们的配置中移到它自己的 YAML 文件中。我们就把它命名为 orb.yml。
version: 2.1
description: |
Orb for using Cake with OctopusSamples.
executors:
cake-executor:
docker:
- image: mcr.microsoft.com/dotnet/core/sdk:2.1.607-stretch
jobs:
build:
executor: cake-executor
parameters:
cake_version:
default: "0.35.0"
type: string
package_version:
default: "$PACKAGE_VERSION"
type: string
publish_path:
default: "publish"
type: string
target:
default: "Publish"
type: string
steps:
- checkout
- run:
name: Install Cake
command: |
export PATH="$PATH:$HOME/.dotnet/tools"
dotnet tool install -g Cake.Tool --version << parameters.cake_version >>
- run:
name: Build
command: |
export PATH="$PATH:$HOME/.dotnet/tools"
dotnet cake build.cake --target="<< parameters.target >>" --packageVersion="<< parameters.package_version >>"
- persist_to_workspace:
root: << parameters.publish_path >>
paths:
- "*"
然后我们可以发布它:
circleci orb publish orb.yml octopus-samples/cake@0.0.1
转眼间。我们有一个发布的 orb 和我们的蛋糕制作工作。现在我们可以更新项目的配置,用发布的 orb 替换内联 orb:
orbs:
cake: octopus-samples/cake@0.0.1
实验章鱼 CLI orb
说到已发布的球体,如果你同时使用 CircleCI 和 Octopus,看看我们的实验 Octopus CLI 球体。有了这个 orb,您可以使用 Octopus CLI 中的命令子集来管理您的包、发布和部署:
orbs:
octo: octopusdeploylabs/octopus-cli@0.0.2
jobs:
package:
docker:
- image: ubuntu:18.04
environment:
PACKAGE_VERSION: 1.3.<< pipeline.number >>
steps:
- octo/install-tools
- attach_workspace:
at: publish
- octo/pack:
id: "OctopusSamples.OctoPetShop.Database"
version: "$PACKAGE_VERSION"
base_path: "publish/OctopusSamples.OctoPetShop.Database"
out_folder: "package"
- persist_to_workspace:
root: package
paths:
- OctopusSamples*
push:
docker:
- image: ubuntu:18.04
environment:
PACKAGE_VERSION: 1.3.<< pipeline.number >>
steps:
- octo/install-tools
- attach_workspace:
at: package
- octo/push:
package: "package/OctopusSamples.OctoPetShop.Database.${PACKAGE_VERSION}.zip"
server: "$OCTOPUS_SERVER"
api_key: "$OCTOPUS_API_KEY"
debug: true
- octo/build-information:
package_id: "OctopusSamples.OctoPetShop.Database"
version: "$PACKAGE_VERSION"
server: "$OCTOPUS_SERVER"
api_key: "$OCTOPUS_API_KEY"
debug: true
create-release:
docker:
- image: ubuntu:18.04
environment:
PACKAGE_VERSION: 1.3.<< pipeline.number >>
steps:
- octo/install-tools
- octo/create-release:
project: "Octo Pet Shop"
server: "$OCTOPUS_SERVER"
api_key: "$OCTOPUS_API_KEY"
release_number: $PACKAGE_VERSION
另外,我们可以继续使用内联 orb 来进一步减少重复。让我们围绕octo-exp\pack
做一个包装:
orbs:
cake: octopus-samples/cake@0.0.1
octo: octopusdeploylabs/octopus-cli@0.0.2
octopetshop:
orbs:
octo: octopusdeploylabs/octopus-cli@0.0.2
commands:
pack:
parameters:
id:
type: string
steps:
- octo/pack:
id: "<< parameters.id >>"
version: $PACKAGE_VERSION
base_path: "publish/<< parameters.id >>"
out_folder: "package"
jobs:
package:
executor:
- name: octo/default
environment:
PACKAGE_VERSION: 1.3.<< pipeline.number >>
steps:
- octo/install-tools
- attach_workspace:
at: publish
- octopetshop/pack:
id: "OctopusSamples.OctoPetShop.Database"
- octopetshop/pack:
id: "OctopusSamples.OctoPetShop.Infrastructure"
- octopetshop/pack:
id: "OctopusSamples.OctoPetShop.ProductService"
- octopetshop/pack:
id: "OctopusSamples.OctoPetShop.ShoppingCartService"
- octopetshop/pack:
id: "OctopusSamples.OctoPetShop.Web"
- persist_to_workspace:
root: package
paths:
- OctopusSamples*
因为我们对 octo/pack 的调用将遵循相同的格式,但对id
的值不同,所以我们可以使用octopetshop\pack
来抽象这个逻辑。
结论
CircleCI Orbs 是为基于 YAML 的管道创建可重用命令或作业的一种方式。您可以将 orb 发布到 CircleCI,以便跨项目或与其他组织共享。您还可以使用内联 orb 来帮助开发或者减少您自己的配置中的干扰。
查看 CircleCI Orbs 了解更多信息,查看实验章鱼 CLI orb 了解如何一起使用 CircleCI 和章鱼的详细信息。
Selenium 系列:重用 POM 类- Octopus Deploy
原文:https://octopus.com/blog/selenium/24-reusing-pom-classes/reusing-pom-classes
这篇文章是关于创建 Selenium WebDriver 测试框架的系列文章的一部分。
既然我们已经使用 POM 设计模式重写了测试,那么让我们继续添加我们前面提到的第二个测试,验证不同部分的价格。
对于这个测试,我们需要进入结帐屏幕,但不是真正进行结帐。相反,我们希望遍历每个部分选项,并获得它们的票价。
为了支持这个新的测试,我们需要向CheckoutPage
类添加一些额外的方法:
public class CheckoutPage extends BasePage {
private static final Pattern TICKET_PRICE_REGEX = Pattern.compile("@ \\$(\\d+\\.\\d+)");
private static final String ADULT_TICKET_PRICE = ".input-group-addon";
// ...
public CheckoutPage selectSection(final String section) {
automatedBrowser.selectOptionByTextFromSelect(section, SECTION_DROP_DOWN_LIST, WAIT_TIME);
return this;
}
public float getSectionAdultPrices() {
final String price = automatedBrowser.getTextFromElement(ADULT_TICKET_PRICE);
return getPriceFromTicketPriceString(price);
}
// ...
private float getPriceFromTicketPriceString(final String input) {
final Matcher matcher = TICKET_PRICE_REGEX.matcher(input);
if (matcher.matches()) {
final String priceString = matcher.group(1);
return Float.parseFloat(priceString);
}
throw new IllegalArgumentException("String " + input + " does not match the regex");
}
}
首先,我们定义一个正则表达式Pattern
,它匹配应用程序显示的价格字符串。价格显示为类似于@ $219.5
、@ $149.5
的字符串(末尾缺少的零可能是 Ticketmonster 中的一个错误,但我们现在忽略它)。我们希望将美元金额从 at 和美元符号中分离出来,因此我们将字符串的这一部分捕获到一个组中:
private static final Pattern TICKET_PRICE_REGEX = Pattern.compile("@ \\$(\\d+\\.\\d+)");
新方法selectSection()
允许我们从下拉列表中选择一个事件部分。这将更新显示票价的元素:
public CheckoutPage selectSection(final String section) {
automatedBrowser.selectOptionByTextFromSelect(section, SECTION_DROP_DOWN_LIST, WAIT_TIME);
return this;
}
新的getSectionAdultPrices()
方法首先从显示价格的元素中获取原始文本。这个原始文本看起来像是@ $219.5
或@ $149.5
。
然后,它将该文本传递给方法getPriceFromTicketPriceString()
,该方法将它转换成一个float
值:
public float getSectionAdultPrices() {
final String price = automatedBrowser.getTextFromElement(ADULT_TICKET_PRICE);
return getPriceFromTicketPriceString(price);
}
最后添加的是getPriceFromTicketPriceString()
方法。这个方法的存在是为了支持getSectionAdultPrices()
方法,所以它是private
。
在这个方法中,我们试图将输入与之前定义的正则表达式Pattern
进行匹配。如果匹配成功,我们提取组 1,它将包含类似于219.5
或149.5
的字符串。这些字符串然后被转换成float
值并返回。
如果输入与正则表达式不匹配,则会引发异常:
private float getPriceFromTicketPriceString(String input) {
final Matcher matcher = TICKET_PRICE_REGEX.matcher(input);
if (matcher.matches()) {
final String priceString = matcher.group(1);
return Float.parseFloat(priceString);
}
throw new IllegalArgumentException("String " + input + " does not match the regex");
}
有了对CheckoutPage
的这些更改,我们现在可以编写测试了:
@Test
public void verifyPricesPageObjectModel() {
final AutomatedBrowser automatedBrowser =
AUTOMATED_BROWSER_FACTORY.getAutomatedBrowser("ChromeNoImplicitWait");
try {
automatedBrowser.init();
final EventsPage eventsPage = new MainPage(automatedBrowser)
.openPage()
.buyTickets();
final VenuePage venuePage = eventsPage
.selectEvent("Theatre", "Shane's Sock Puppets");
final CheckoutPage checkoutPage = venuePage
.selectVenue("Toronto : Roy Thomson Hall")
.book();
checkoutPage.selectSection("A - Premier platinum reserve");
final float platinumAdultPrices = checkoutPage.getSectionAdultPrices();
Assert.assertTrue(platinumAdultPrices > 10);
Assert.assertTrue(platinumAdultPrices < 1000);
checkoutPage.selectSection("B - Premier gold reserve");
final float goldAdultPrices = checkoutPage.getSectionAdultPrices();
Assert.assertTrue(goldAdultPrices > 10);
Assert.assertTrue(goldAdultPrices < 1000);
checkoutPage.selectSection("C - Premier silver reserve");
final float silverAdultPrices = checkoutPage.getSectionAdultPrices();
Assert.assertTrue(silverAdultPrices > 10);
Assert.assertTrue(silverAdultPrices < 1000);
checkoutPage.selectSection("D - General");
final float generalAdultPrices = checkoutPage.getSectionAdultPrices();
Assert.assertTrue(generalAdultPrices > 10);
Assert.assertTrue(generalAdultPrices < 1000);
} finally {
automatedBrowser.destroy();
}
}
为了让 web 应用程序能够找到票价,我们重用了MainPage
、EventsPage
和VenuePage
POM 类。这里的代码与之前的测试几乎相同,但是在这个例子中,我们选择了一个不同的事件,称为Shane's Sock Puppets
:
@Test
public void verifyPricesPageObjectModel() {
final AutomatedBrowser automatedBrowser =
AUTOMATED_BROWSER_FACTORY.getAutomatedBrowser("ChromeNoImplicitWait");
try {
automatedBrowser.init();
final EventsPage eventsPage = new MainPage(automatedBrowser)
.openPage()
.buyTickets();
final VenuePage venuePage = eventsPage
.selectEvent("Theatre", "Shane's Sock Puppets");
final CheckoutPage checkoutPage = venuePage
.selectVenue("Toronto : Roy Thomson Hall")
.book();
现在,我们开始骑自行车通过部分,以获得他们的票价。
我们调用selectSection()
来改变当前选择的部分。对getSectionAdultPrices()
的调用然后获得显示的票价作为float
。然后,两条Assert
语句验证价格在下限和上限之间:
checkoutPage.selectSection("A - Premier platinum reserve");
final float platinumAdultPrices = checkoutPage.getSectionAdultPrices();
Assert.assertTrue(platinumAdultPrices > 10);
Assert.assertTrue(platinumAdultPrices < 1000);
对其他 3 个部分重复这些检查:
checkoutPage.selectSection("B - Premier gold reserve");
final float goldAdultPrices = checkoutPage.getSectionAdultPrices();
Assert.assertTrue(goldAdultPrices > 10);
Assert.assertTrue(goldAdultPrices < 1000);
checkoutPage.selectSection("C - Premier silver reserve");
final float silverAdultPrices = checkoutPage.getSectionAdultPrices();
Assert.assertTrue(silverAdultPrices > 10);
Assert.assertTrue(silverAdultPrices < 1000);
checkoutPage.selectSection("D - General");
final float generalAdultPrices = checkoutPage.getSectionAdultPrices();
Assert.assertTrue(generalAdultPrices > 10);
Assert.assertTrue(generalAdultPrices < 1000);
然后,我们完成了清理finally
块中的资源的测试:
} finally {
automatedBrowser.destroy();
}
}
我们用 POM 类MainPage
、EventsPage
和VenuePage
创建的公共 API 使得编写这个测试变得快速而简单。让 web 应用程序显示票价所需的大部分逻辑已经实现,只剩下一些新方法要添加到CheckoutPage
类中。
这就是 POM 设计模式的强大之处。它将实现细节封装到 Java 对象中,让测试自由地描述他们在测试什么,而不是如何测试。如果 TicketMonster 将来要更新新的元素 id、名称属性或重新排列的屏幕,我们有一个集中的重用 API,可以在那里进行更新,而不是通过复制和粘贴代码进行多次测试。
这篇文章是关于创建 Selenium WebDriver 测试框架的系列文章的一部分。
RFC: Azure 资源管理器支持- Octopus 部署
在过去的某个时候,Azure 团队对 Azure 中管理资源的方式进行了明确的方向改变。结果当然是 Azure 资源管理器。
因此,现在有两个不同的接口与 Azure 交互。最初的接口被称为 Azure 服务管理(ASM),Azure 资源管理器(ARM)是新的。分裂是深刻的;有两个 HTTP API,两套 PowerShell 模块,甚至两个 web 门户。
目前,Octopus Deploy 只与服务管理接口交互。
当您在 Octopus 中创建新的 Azure 帐户时,您会附加一个证书,该证书用于通过 Azure 进行身份验证。这种认证模式只能与服务管理 API 交互。若要使用资源管理器,需要通过 Azure Active Directory 进行身份验证。
为了提供对 Azure 资源管理器的支持,提出了许多更改:
Azure 服务主帐户
我们建议为 Octopus 添加一个新的帐户类型:一个 Azure 服务主体帐户(现有的 Azure 帐户将被称为 Azure 管理证书帐户)。Azure 服务主体帐户的创建过程将包括在你的 Azure Active Directory 中创建一个服务主体(参见这篇帖子了解上下文),并捕获必需的字段(应用程序 ID、租户 ID、密码)。我们会尽可能让这次经历顺利。
一个新的“部署 Azure 资源组”步骤
通过资源管理器,可以使用 JSON 模板作为一个组来提供和部署资源。
我们提出了一个新的部署步骤,可以使用 JSON 模板提供/部署资源组。
编辑步骤可能类似于:
需要注意的事项:
- “参数”部分是动态的。该表单会进行调整,以匹配模板中定义的参数。
- Octopus 变量可以绑定到任何字段,包括参数。在上面的示例中,siteName 参数被绑定到一个变量。这是强大的,因为它允许您的参数根据环境、通道等进行调整,而无需修改步骤。
- “验证”按钮将允许根据 Azure 资源管理器来验证您的部署。这比创建一个版本并进行部署却发现 JSON 无效要好。
在我们目前的想法中,这些步骤不包括 NuGet 包。我们为这些资源组步骤设想的用例更多的是关于供应资源,而不是部署它们。例如,您可能有一个创建 Azure 网站(可能还有 SQL 数据库等)的“部署资源组”步骤,然后是一个“部署 Azure Web App”步骤,该步骤将获取包含您的应用程序的 NuGet 包并部署它。
例如
Azure PowerShell 脚本
目前,对于“运行 Azure PowerShell 脚本”步骤,我们自动导入 Azure PowerShell 模块并根据订阅进行身份验证。但是这不允许使用资源管理器 cmdlets。我们将为资源管理器做类似的事情:如果帐户是服务主体帐户,我们将自动进行身份验证。是否自动加载资源管理器模块是一个悬而未决的问题,因为它们已经变得更细粒度了。
此时,服务主体帐户无法访问服务管理 API(包括 PowerShell 模块)。这意味着您无法在同一个步骤中组合服务管理和资源管理器 cmdlets。要使用服务管理器 cmdlet,您需要一个选择 Azure 管理证书帐户的步骤,而要使用 ARM cmdlets,您需要一个选择 Azure 服务主体帐户的步骤。鉴于微软似乎已经有效地停止了服务管理方面的开发,我们认为这将是一个越来越小的问题。
饭桶
我们还在考虑允许从 Git 存储库中检索模板。这种情况可能会发生,也可能不会发生,即使发生,也可能不会包含在此功能的初始版本中。我们很想知道这是否有趣。
反馈
告诉我们你的想法?
RFC: Azure 服务结构支持- Octopus 部署
为了迎接 2017 年,Octopus 的云团队陷入了一个流行的用户声音建议: Azure 服务架构!
本 RFC 的目的是讨论从 Octopus Deploy 到 Azure Service Fabric 部署的集成支持。如果您希望看到服务结构的某个特定子功能,现在是参与进来的最佳时机,让我们知道我们是否在正确的轨道上。
什么是 Azure 服务结构?
微软在他们的概述中对此做了最好的解释:
Azure Service Fabric 是一个分布式系统平台,可以轻松打包、部署和管理可伸缩且可靠的微服务。Service Fabric 还解决了开发和管理云应用程序的重大挑战。开发人员和管理员可以避免复杂的基础架构问题,并专注于实施可扩展、可靠和可管理的任务关键型高要求工作负载。Service Fabric 代表了用于构建和管理这些企业级、第 1 层、云规模应用程序的下一代中间件平台。
术语
包裹
在这个讨论中,有两个不同的概念与名词Package
相关。
第一个是 Octopus Deploy 在其包提要中考虑的包,它将被称为 NuGet/zip 包。
第二个是 Azure Service Fabric 应用程序包上下文中的包,将简称为包(对于熟悉 Service Fabric 的人来说,这实质上是您提供给 Service Fabric PowerShell Cmdlets 的ApplicationPackagePath
参数的内容)。
新的步骤模板
提议的解决方案将依赖于 NuGet/zip 打包服务结构应用程序的部署。NuGet/zip 包的必需内容将在这里讨论。然而,NuGet/zip 包的创建是一个构建责任,因此不在本讨论的范围之内。
假设您的 SF 应用程序包含两个服务,ServiceA 和 ServiceB,NuGet/zip 包将包含具有以下文件/文件夹结构的内容:
该步骤将包括以下输入:
- 账户
- 轮廓
- 证书
- 默认
Deploy-FabricApplication.ps1
文件中各种可用标志的用户界面选项(该文件包含在所有服务结构项目中)
建议的步骤模板用户界面如下:
处理
部署过程将:
- 打开包装内容
- 在
ApplicationParameters
和PublishProfiles
xml 文件中执行变量替换(我们最有可能考虑所有的变量替换。xml/。解压缩后的包中的配置文件) - 使用 Azure Service Fabric SDK 和 PowerShell 脚本的捆绑版本(注意:我们仍在调查这里涉及的内容)
- 为了与现有的 Azure PowerShell 模块保持一致,如果你愿意,可以通过设置系统变量 Octopus 来使用 Azure Fabric SDK 的定制版本。action . azure . usebundledazurepowershellmodules到 False ,然后将使用安装在您的 Octopus 服务器上的 SDK 版本
- 调用
DeployAzureFabricApplication.ps1
,它将被打包成卡拉马里的一部分- 喜欢 Calamari 的 Azure 云服务脚本】(https://github . com/octopus deploy/Calamari/blob/master/source/Calamari)。azure/Scripts/deployazurecloudservice . PS1),您将能够在您的 NuGet/zip 包中覆盖该脚本,并根据需要对其进行自定义
版本控制
用 Octopus 和 Service Fabric 应用程序对 NuGet/zip 包进行版本控制产生了一个有趣的讨论点,因为 Service Fabric 中的版本控制是每个服务、每个应用程序的代码和配置。换句话说,如果你从 Octopus 发布一个新的 NuGet/zip 包版本,你需要确保你理解版本控制和升级如何与 Azure Service Fabric 应用程序一起工作,以确保最小的(或没有)停机时间。
我们仍在积极研究八达通如何最好地处理这一问题。我们还不知道答案:)
反馈
这是您指导此功能开发的机会。
这对你有用吗?
你的愿望清单上有什么我们没提到的吗?
RFC:分支- Octopus 部署
频道已经在章鱼 3.2 发货了!
在 Octopus 的下一个版本中,我们计划改进对从事不同代码分支工作的人们的支持。我们仍处于这个项目的规划阶段,所以这是一个分享我们正在做的事情并获得您的评论的好时机。
每次在 Octopus 中创建一个版本时,您可以选择要包含的每个包的版本,默认为最新版本:
当不同的团队同时在不同的分支上工作时,这会使得创建发布页面难以使用。例如,假设团队按以下顺序发布包:
Acme.Web.1.9.3.nupkg
Acme.Web.1.9.4.nupkg
Acme.Web.1.9.5.nupkg
Acme.Web.2.3.1.nupkg
Acme.Web.2.3.2.nupkg
Acme.Web.1.9.6.nupkg
Acme.Web.1.9.7.nupkg
Acme.Web.2.3.3.nupkg
Acme.Web.2.3.4.nupkg
当查看创建发布页面时,由于 Octopus 默认为最新版本,这意味着人们必须寻找选择正确的包版本-这不是一个很好的体验。
为了帮助解决这个问题,我们引入了分支机构。您将像这样定义一个分支:
一个分支有一个简单的名称,然后是一组规则,这些规则决定了在创建一个发布时应该出现哪些版本。在步骤名称字段中,您可以选择将规则应用于哪些步骤——项目中的一个、多个或所有步骤。版本范围字段使用 NuGet 版本语法来指定要包含的版本范围。标签可以包含一个正则表达式,并在 SemVer 预发布标签上使用。
在 create release 页面上,您可以选择使用哪个分支,它将控制显示哪些包:
更复杂的分支定义可能是:
这里,项目有多个步骤。对于的极致。Util 步骤,我们有一个规则,其他步骤有不同的规则。我们使用标签只显示带有-vnext
SemVer 标签的包。
我们这个特性的目标是保持简单——分支实际上只从发布创建页面(和Octo.exe create-release
)的角度来看是重要的。如果您根据分支需要不同的环境或部署步骤,那么最好克隆一个项目。
你觉得怎么样?这样的功能对你有用吗?请在下面的方框中告诉我们。
RFC: X.509 证书管理- Octopus 部署
X.509 证书的管理是一个常见的部署难题。
推动这一特性的具体例子是在 IIS 中创建 HTTPS 绑定。
目前在 Octopus 中,当配置 HTTPS 绑定时,通过指纹引用证书。
假设证书已经安装到 IIS 服务器上。当指纹所引用的证书不可避免地过期时,你必须记住更新八达通中的指纹以匹配新证书。
我们相信,我们可以改进这一点,并(希望)改进许多其他与证书相关的场景。
Octopus ==证书库
Octopus 服务器非常适合作为 X.509 证书的中央管理点。
我们打算允许你上传你的证书到八达通。
我们将支持上传各种文件格式(。pfx,。pem,。cer),您将能够查看和搜索您已上传的证书。
证书的范围可以是环境(和租户)。例如,这将防止意外地将您的生产证书部署到开发环境中。
私钥
从技术上讲,X.509 证书只包含公共密钥。然而,在许多情况下(包括配置 HTTPS IIS 绑定),证书及其对应的私有密钥都是必需的。
PFX (PKCS #12)和 PEM 格式都支持存储私钥和证书,Octopus 将支持上传这些内容。
所以对于学究们(我们不都是这样),当我们在这个上下文中谈到证书时,我们实际上是指证书及其相关的私钥(如果提供的话)。
因为证书可能包含私钥,它们将被保存在用您的主密钥加密的 Octopus 数据库中。
第一阶段
我们希望在初始阶段提供的功能是:
证书变量
为了支持证书,我们计划引入类型变量的概念。当创建一个变量时,您将能够指定它将代表一个证书。
使用证书时,有许多潜在的有用属性:
- 个性特征
- 公开密钥
- 私人密钥
- 完整的原始证书(可能包括私钥)
- 等等
在部署时,证书变量将被扩展成许多变量。
即:MyCertificate
=>
MyCertificate.Type
:Certificate
MyCertificate.Name
:将证书导入八达通
MyCertificate.Thumbprint
时提供的友好名称:证书指纹。例如A163E39F59560E6FE33A0299D19124B242D9B37E
MyCertificate.Subject
:主体的 X.500 可分辨名称
MyCertificate.Issuer
:发行者的 X.500 可分辨名称
MyCertificate.NotBefore
:例如2016-06-15T13:45:30.0000000-07:00
MyCertificate.NotAfter
:例如2019-06-15T13:45:30.0000000-07:00
MyCertificate.Pfx
:base64 编码的证书,如【PKCS # 12】格式,包括私钥(如果存在)。
MyCertificate.PublicKey
:base64 编码的 DER ASN.1 主键。
MyCertificate.PrivateKey
:base64 编码的 DER ASN.1 私钥(如果有)。
MyCertificate.PublicKeyPem
:公钥的 PEM 表示(即带表头\表尾的公钥)。
MyCertificate.PrivateKeyPem
:私钥的 PEM 表示(即带表头\表尾的 private key)。
这是一个可能的变量列表。创建的确切变量尚未最终确定。
私钥变量将以与敏感变量相同的方式存储和传输;从不明文。
将来,我们将把类型变量的概念扩展到其他实体。一个明显的候选人是账户。
IIS 绑定
在为 IIS 部署配置 HTTPS 绑定时,目前必须从外部管理证书安装(或者至少通过自定义脚本)。然后将证书的指纹输入到绑定配置中。
此功能将允许您选择证书变量,而不是指纹。
有趣的是注意到绑定是到证书变量,而不是直接到证书。我们的推理是,常见的(也是推荐的)场景是拥有一个证书变量,每个环境有不同的实际证书值。
例如
因此,将使用OctoFX Certificate
变量创建 IIS 绑定,并根据部署到的环境安装正确的证书。
部署时,证书将安装在Cert:\LocalMachine
存储中(如果还没有安装的话),HTTPS 绑定将在 IIS 中配置。
在自定义脚本中引用证书变量
无论您选择哪种脚本语言(PowerShell、ScriptCS、F#、Bash),证书变量都应该使使用定制脚本中的证书变得既好又容易。
变量替换将起作用:
Write-Host #{MyCertificate.Thumbprint}
威尔也一样:
Write-Host $OctopusParameters["MyCertificate.Thumbprint"]
为了。基于. NET 的脚本语言,我们可能会提供一个 helper 函数来获取 X509Certificate2 对象,因为即使使用 pfx 表示,在创建这些对象时也有一些常见的陷阱。
未来
我们考虑在后续阶段交付的功能包括:
圣杯
我们梦想中的工作流程如下:
- 创建新证书(从“让我们加密”中自动请求)
- 在 IIS 部署步骤的 HTTPS 绑定配置中引用它
- 当证书接近到期时,会自动从 Let's Encrypt 请求一个新的证书,并作为替换安装在所有使用过过期证书的机器上
反馈
这是您指导此功能开发的机会。
这对你有用吗?
你的愿望清单上有什么我们没提到的吗?
RFC -云和基础设施自动化支持- Octopus 部署
原文:https://octopus.com/blog/rfc-cloud-and-infrastructure-automation-support
Octopus 有许多客户在云 IaaS 环境中进行基础设施自动化,如 Amazon EC2 和 Azure。我们知道许多客户正在使用 Octopus Deploy 作为这方面的核心组件,但我们也从与人们的交谈中了解到,这在一些领域会引起一些摩擦。我们有一些可以添加到产品中的想法,可以使这变得更容易,但这是一个巨大的领域,我们知道每个人的做法略有不同,所以我们希望您的反馈。
我们所知道的是,我们不可能提供一个适合大多数人的简单界面,有太多的变量和不同的模式。我们还知道,想要走这条路的客户是一群非常笨拙的人,他们不害怕在编写一些脚本来管理机器时弄脏自己的手。
最后,我们知道客户希望使用 Octopus Deploy 来完成更多的流程编排工作,这就是我们想要解决的问题。
两种思想流派
谈到基础架构自动化,我们看到了两种截然不同的模式,这是关于环境是作为部署的一部分进行更改,还是以更松散的方式进行更改。
场景 1 -将环境与部署联系起来。
一个新的版本即将上线。全新的实例被创建和配置,tentacles 被安装并注册到 Octopus 服务器。该版本被部署到新的机器上,新的机器被放入负载平衡的服务器池中,运行旧版本的实例被从服务器池中删除,并被 Octopus 注销和销毁。
目前这方面的主要问题是 Octopus 在部署开始时评估部署目标机器。因此,如果您的部署中有一个创建新机器的 PowerShell 步骤,它们不会是部署的一部分。因此,部署的编排需要在 Octopus 之外进行。许多人为此使用他们的构建服务器,运行脚本来创建环境,然后通过 Octopus API 调用部署。这是可行的,但是对于部署的控制权在哪里,这有点违背直觉。
场景 2 -单独管理环境
另一种方法是单独定义和管理环境。在非常有弹性的环境中,机器可以根据需要自动添加或删除,这种情况很常见。
例如,AWS CloudFormation 模板存储在源代码控制中。该文件定义了环境的形式及其围绕自动扩展的行为和策略。当配置改变时,运行脚本将新配置推送到 AWS,使 EC2 根据需要添加、移除或改变机器。
虽然这对于今天的人们来说是可行的,但是它需要为新实例部署和运行额外的脚本来请求任何部署。此外,当实例被销毁时,Octopus 将继续轮询它们以执行运行状况检查,因此会创建计划任务来注销已取消配置的机器。
输入环境策略
场景#2 中问题的解决方案是在每个环境的基础上建立一些行为规则。
动态注册
如果计算机不响应运行状况检查或者在部署时无法联系到,那么允许对环境进行动态注册将会使计算机被取消注册并被“遗忘”。如果动态环境中的机器没有响应,Octopus 会将它从环境中移除,并停止尝试接近它。Octopus 可以在环境页面上保留一个“最近移除的”机器的列表,作为一个可视的指示。
自动部署
为环境启用自动部署将在计算机联机时启动新的部署。如果一个新的机器实例是由于自动伸缩或机器回收策略而创建的,那么当注册了触手 Octopus 时,它将评估它应该拥有什么版本,并为它创建一个部署。这在间歇性连接的环境中也很有用,可能会错过发布(例如,我们有一些客户需要将 VPN 部署到经常离线的客户端机器上)。
重新评估机器步骤
场景#1 的一个简单解决方案是 Octopus 能够在部署中途重新评估部署目标。创建新机器的 PowerShell 脚本可以在一个变量中设置新的实例名称,该变量可用于重新定位部署。
对 IaaS 平台的直接支持
上面概述的功能是一些基本的功能,这些功能将为我们的许多客户提供更多的场景。为了更深入地了解各种 IaaS 平台,我们认为最好在我们的库中发布 Step 模板,并可能发布一些开源脚本、工具和示例项目,以帮助您最好地定制 Octopus Deploy 项目来满足您的需求。
我们需要您的反馈
如果您正在进行基础设施自动化,这些选项会让您的生活和/或脚本更容易吗?如果你想知道如何做,这会有帮助吗?如果你正在做的事情完全没有得到这些想法的帮助,我们错过了什么?
RFC:云区域- Octopus 部署
TL;dr;
我们正在考虑引入一个新的目标类型,被称为云区。云区域目标将使在 Octopus 服务器上执行的步骤能够多次执行,变量的作用范围是每次迭代。这种设计支持的特定场景是将 Azure 步骤(Web 应用、云服务、PowerShell)部署到多个地区。
一点历史
在 Octopus Deploy 3.0 版本中,我们为 Azure 引入了两个特定的部署目标,即 Web 应用和云服务。然后我们改变了我们的想法,在3.1 版本中,回复到使用步骤而不是 Azure 部署的目标,放弃那些目标(尽管它们目前仍然可用)。
虽然我们对任何不便或困惑深表歉意,但我们总体上对这一变化非常满意。然而...
有一个场景,许多客户反映使用目标比步骤建模更好:将相同的组件(web app、云服务等)部署到多个 Azure 区域。在 3.0 模型中,他们简单地为每个区域创建了一个目标。目前,可以选择每个区域使用一个 Octopus 环境,或者每个区域使用一个 step。客户反馈说这两个选项都不可取。我们同意。这导致我们在删除已被否决的 Azure 目标时犹豫不决。
介绍云区域
创建新目标时,云区域将作为一个选项出现。
与任何目标一样,可以为云区域目标分配名称和角色。
云区域将出现在您的环境中,就像任何目标一样。
您可以像对待任何目标一样,将变量作用于它们。
但是现在你的 Azure 步骤要么只运行一次,要么为每个匹配指定角色的目标运行一次。
请注意在下面的部署 Azure Web 应用程序步骤中配置的角色和 Web 应用程序名称变量的使用。
部署步骤时,它将针对每个匹配的云区域目标运行一次。
注意:使用带有 Azure 步骤的云区域目标将是完全可选的。如果你目前正在使用 Azure 步骤,你不需要做任何改变。他们将完全按照目前的方式工作。
不仅仅是蔚蓝
尽管这种目标类型的动机来自 Azure regions,但是这些目标也可以被常规的运行脚本步骤使用。这意味着任何人都可以使用不同的变量值多次运行一个脚本步骤,而无需安装触须,从而利用云区域。例如,在生产和灾难恢复数据库服务器上运行相同的 SQL 脚本。
反馈
一如既往,我们非常重视您的反馈。
如果你目前使用的是被否决的 3.0 Azure 目标,我们特别想听听你的意见。我们不喜欢用两种方法来部署 Azure 组件(步骤和目标)。这让用户感到困惑,我们需要做更多的工作来维护。我们的希望是,这将解决区域问题,并允许我们(问心无愧地)逐步删除不赞成的 3.0 Azure 目标。
RFC:复合步骤模板- Octopus 部署
复合步骤模板 RFC
复合步骤模板和相关提案可继承模板,是我们计划在未来几个月解决的两个用户声音建议。我们希望听取用户的意见,以确保我们以正确的方式解决您的问题。在整个 RFC 中,我们用粗体字突出显示了一些我们仍在考虑的问题(这些问题在内部引起了很多争论),我们希望听到您的想法,尤其是在您的部署和您所面临的问题的背景下。
这是什么?
通常情况下,您会有几个步骤需要一起包含在多个项目中。虽然将单个步骤转换成可共享的步骤模板很容易,但是以单一统一的方式处理多个步骤通常需要较高的维护成本。也许您希望在整个项目中重用一组部署步骤,当需要添加新的包部署步骤时,这些步骤都应该得到更新。这就是复合步骤模板发挥作用的地方。通过有效地将多个步骤捆绑成一个单一的、可重用的步骤,该步骤可以包含在您的项目中(或者其他复合步骤模板!),您可以减少整个系统中的重复配置,并确保跨项目的更改保持同步。
我们目前的计划包括创建和处理复合步骤模板,就像一个典型的步骤模板一样,只是有一些关键的不同。模板的内部细节由其他步骤组成,这些步骤可以是自定义步骤、其他步骤模板甚至是其他复合步骤模板!
当添加到项目中时,复合步骤模板被视为单个步骤块。有什么原因使单步阻塞概念对你不起作用吗?您是否希望能够在复合模板步骤之间运行项目级步骤? 内部步骤的目标角色继承自项目步骤编辑屏幕上的项目级角色配置。在项目层次上设置角色有意义吗,或者会导致比单独设置角色更复杂的问题吗?您希望在项目级别上公开的任何参数都需要在复合级别上显式配置,否则将使用复合级别配置中显式提供的值。 您认为您会想要在项目级别上公开内部步骤参数吗?
这不是什么
这些并不是完全成熟的项目模板,尽管您可以通过模板化部署所需的所有共享步骤来很好地实现这一点。共享变量最好通过创建一个共享库变量集来处理,这个库变量集已经存在。渠道目前仍需要在逐个项目的基础上进行管理。UserVoice 中的一些评论者认为这个解决方案与 TeamCity 构建模板类似。鉴于部署带来的不同背景,我们认为这不一定是同一个概念。
复合步骤模板功能并不旨在为您的部署提供安全性或业务遵从性。它们按照配置的顺序包含在您的部署过程中,不能重新安排步骤或在它们之间散布项目步骤。如果您希望确保在每次部署的开始或结束时都执行类似电子邮件步骤的操作,那么我们建议您编写自定义脚本来定期检查您的部署,以了解您业务中可能需要的所有变化。复合步骤模板是关于代码重用和共享部署配置的简化管理的。
尽管 UserVoice 的评论建议在模板更新时自动更新所有使用模板的项目,但我们认为,随着更好的解决方案即将出现将解决更新的问题,更新将很快通过更简单的手动升级过程得到更好的解决。
我们目前不认为导出复合步骤模板包含在该特性的第一个版本中。导出复合步骤模板会引发几个棘手的问题。如果您导出由其他步骤模板组成的复合步骤模板,如果再次导入,该链接将如何保留?似乎您需要将内部步骤作为新的独立模板导入。除了现有的步骤模板导出之外,导出会提供更多的价值吗?
问题
希望您可以看到这个特性在简化您的部署配置方面带来的一些好处。如您所知,我们仍有一些未解决的问题需要考虑,可能需要您的意见。将复合模板视为一个单独的步骤是否有意义,或者您是否认为有必要将它作为单独的步骤在项目级别上进行管理?如果有的话,内部步骤的参数应该如何在项目级别公开?让我们知道您认为我们遗漏了什么,并确保描述一些具体的问题,这些问题可能会解决,也可能不会解决,以便我们可以更好地制定未来的计划。
RFC:Octopus-Octopus 部署中的远程增量压缩
许多自主开发的部署解决方案利用了 Robocopy 等工具,这些工具可以在执行部署时执行“同步”。这节省了时间和带宽,因为这意味着只需要在网络上推送已更改的文件。另一方面,Octopus Deploy 使用 NuGet 包,总的来说,我们认为将单个文件——包——作为部署单元比同步松散的文件目录有很多优势。
这个决定是有代价的——我们今天可能上传一个 100 MB 的包到一个服务器上,只是对包中的几个文件做了一个小的改变,却不得不再次重新上传整个包。将此乘以许多服务器,很明显会有潜在的带宽节省。
如果我们能两全其美,那不是很好吗?
上周,我花了一些时间阅读了两篇关于 delta 压缩的优秀论文:
这个概念在应用于章鱼和触手时非常简单:
关键的好处是,当部署一个 100 MB 的文件,其中只有 1 MB 的数据实际发生了变化时,我们会将大约 1.3%的文件作为签名(1.3 MB)进行传输,加上增量,再加上由于填充而产生的少量额外数据,但总共不超过大约 3 MB。
一旦我理解了这些概念,我就开始寻找我们可以使用的实现。我真的很喜欢 rdiff 的外观,作为一个命令行工具,它有我们需要的那种语义,但是对在 Windows 上运行的支持似乎不是很好。此外,如果我们要这样做,我想这是我们想要维护自己的那种代码,我已经有一段时间没有坐下来编写这样的代码了。
Octodiff 简介
Octodiff.exe 是远程增量压缩的 100%托管实施。用法受 rdiff 启发,与 rdiff 一样,算法也基于 rsync。Octodiff 可以通过比较远程文件和本地文件来制作文件的增量,而不需要两个文件都存在于同一台机器上。
Octodiff 在 GitHub 上,并获得 Apache 许可。它有三个不同的命令——创建一个签名,创建一个增量,应用一个增量(补丁)。
我需要你的帮助
NuGet 包是 ZIP 文件,我认为 ZIP 文件不太适合 delta 压缩——我认为改变 ZIP 中的单个文件会导致整个 ZIP 变得不同。然而,事实证明并非如此——zip 文件(或者至少是使用System.IO.Packaging
创建的文件)使用块级压缩,因此对包中单个文件的更改往往被限制在包文件中的单个区域。在我所有的测试中,Octodiff 运行得非常好。
然而,将 Octodiff 添加到 Octopus 和 touchs 会带来一些风险,所以在我们开始实现它之前,如果发现在现实世界中它实际上并不能改善任何事情,我会非常感激您能帮助我:
- 下载 Octodiff
- 试着用 Octopus 部署的一些包,尤其是较大的包,但也有较小的包
- 告诉我结果——增量有多大,你会节省多少带宽
- 如果你认为这个功能对你有帮助,请告诉我
一旦我们有了一些真实世界的数据,我们将更容易把它集成到 Octopus 中。
此外,目前在较大的文件上创建增量确实需要几秒钟的时间——很遗憾没有人阅读这篇博文来优化它并发送拉取请求;——)
RFC:改进部署流程- Octopus Deploy
在这篇文章中,我想分享一些我正在考虑的对 Octopus Deploy 使用的部署工作流程的改变。
目前在 Octopus 中,任何部署都包括三个阶段:
- Octopus 服务器从 NuGet feed 下载所有必需的包
- Octopus 服务器安全地将包上传到所需的触角上
- 部署步骤运行(包被提取和配置)
这可以总结为下图:
我们这样做的原因很简单:包可能很大,上传它们可能需要很长时间。我们不想部署一个包(例如,一个数据库模式更改),然后等待 20 分钟,直到下一个包的上传完成,然后再继续。相反,我们上传一切,然后配置一切。
但是这种架构也有一些缺点,我将讨论这些缺点以及我正在考虑的一些解决方案。
上传前的步骤
这种架构的第一个问题是,在上传包之前,目前不可能运行任何步骤(包括专门的 PowerShell 脚本)。这阻止了使用脚本来完成有用的任务,如建立 VPN 连接。最好插入在下载/上传活动发生之前运行的步骤。
带宽
由于 Octopus 将每个包的副本推送到相关的触角,因此可能会浪费很多带宽。例如,想象一个场景,Octopus 服务器在本地网络中,它被部署到云中的许多机器上。同一个包将在互联网上推送多次:
另一种节省带宽的方法是将包复制到云中的 NuGet 服务器上,让每个触手从服务器上提取包:
当使用 DNS 或分布式文件系统部署到不同数据中心的机器上时,这种架构也可以很好地工作,以确保机器选择离它们最近的 NuGet 服务器。
改进部署流程
为了支持这些场景,我考虑对部署流程进行两项更改。
第一个是预包上传步骤的概念,它将在下载 NuGet 包之前运行。您可以在这里添加 PowerShell 步骤,以便在包下载过程开始之前建立 VPN 连接或执行其他任务。
第二个变化是软件包步骤设置中的一个选项,让 Tentacles 直接下载软件包。八达通不是下载软件包,而是指示触手下载软件包。下载会在部署步骤运行之前发生(同样,这样我们就不用在部署步骤之间等待复制包),但是它们会直接到达 NuGet 服务器。
希望这些变化能使分布式环境中的部署变得更加容易。我很乐意在下面的评论框中得到您对提议的更改的反馈。
RFC: Docker 集成- Octopus 部署
早在 2014 年 10 月,微软宣布将在 Windows Server 2016 上支持 Docker ,自那以来,已经做了大量工作来让 Docker 在服务器和 Windows 10 上运行。由于预计 Windows Server 2016 将于本月发布 RTM,我们认为这是确保我们的 Docker 故事符合标准的绝佳时机。
当时 Paul Stovell 写了一些关于这种集成的初步想法,从那以后,这个领域发生了很多变化,尤其是在跨平台方面。NET 核心和 Windows 纳米服务器。
我们已经完成了一系列内部测试,并围绕我们认为 Docker 和 Octopus 如何最好地合作进行了大量讨论,为了确保我们继续尽可能地提供最好的 Octopus,我们认为这是一个向社区提出我们的想法并确保我们走上正轨的合适时机。
章鱼还有意义吗?
尽管我们可能有点偏见,但我们绝对相信章鱼在 Docker 世界有一席之地。虽然还有其他原因,但我们的主要想法是:
- 环境进展(Environment progression):Octopus 提供了您的环境和您的应用程序经历的生命周期的一个很好的视图。这使得查看每个环境中运行的容器版本变得容易。
- 编排:当你部署一个新版本的容器化应用程序时,运行一个容器是主要的事情,通常你会有数据库迁移、电子邮件、空闲通知、手动干预等等。
- 配置:一个 app 在不同环境下配置完全相同的情况很少见。Octopus 让您轻松组织配置,并确定每个值的范围,以获得想要的结果。
- 集中审计 : Octopus 为您的部署集中安全、审计和日志。
- 非容器化应用:最后,虽然有些人可能会迁移到 Docker,但其他系统仍然会出现长尾现象,至少其中一些系统没有有效的迁移案例。
但这不就是给*nix 的人看的吗?
作为一个主要针对。NET 开发人员(尽管它在其他语言中也能很好地工作!)我们知道传统的码头工人观众没有太大的交集。因此,我们想让你的脚浸入水中变得超级容易,并鼓励你以“Docker 方式”做事,但我们也知道很多应用程序并不是以这种心态编写的。我们很可能会实现一些违背 Docker 原则的特性(比如不可变容器),以允许您在启动新的容器实例时修改应用程序配置(web.config
或app.config
)。
而且,随着越来越多的人瞄准跨平台。NET Core,使其易于在多个平台上部署和测试变得更加重要。
部署 Docker 容器
为了尽可能地符合 Docker 原则,我们正在寻求将容器作为一个包来支持(方法 3 来自最初的博客文章)。容器映像将由您的构建工具构建,并被推送到 DockerHub 之类的容器注册中心。这有助于确保我们在环境之间推送完全相同的位。
这意味着一个新的包源类型叫做Docker Container Repository
,允许访问经过认证和未经认证的注册中心。
我们计划使用标签作为包版本。这将约束您可以用于标签的值,只使用 SemVer2 有效的版本号(Docker 允许自由格式的文本)。然而,由于这个版本号已经被用于 NuGet 包,我们不认为这是一个有争议的点。
在这一点上,我们期望为 Linux 使用现有的 SSH 目标,为 Windows Server 使用普通的触手。使用这种方法,Octopus 将在本地实例上执行 Docker 命令,使用 Unix 套接字或 windows 命名管道与本地 Docker 守护进程通信。
在部署期间,包将在包获取步骤期间通过docker pull
直接从注册表下载到目标机器。Docker 内置的 delta 功能意味着我们在 Octopus 服务器上下载并将 diff 发送到触手可能不会有太大的收益。
最初,我们看到将添加两个新的步骤类型——一个运行容器,一个停止容器。Docker Run
步骤将允许您从注册表中指定一个映像,传入哪些环境变量,映射哪些端口(或者使用临时端口)。Docker Stop
步骤将允许您停止一个正在运行的容器并(可选地)删除它。
此时,它将要求您显式地定义您想要传入的环境变量,尽管我们正在考虑将所有可用变量作为环境变量传入的想法。
Docker Run
步骤公开了容器 id、端口(如果使用临时端口)和docker inspect
输出的输出变量,这些变量可以在后面的步骤中解析。
开放式问题
关于如何处理敏感变量,我们仍然有一些悬而未决的问题——将它们作为环境变量传递有点安全问题,因为这会使它们暴露在不同的地方。不幸的是,关于将秘密传递到容器中的最佳实践仍然被 Docker 社区认为是一个公开的问题。
输出变量也需要一点润色——哪些变量对你来说很重要?另外,我们已经考虑过是否应该支持变量作为对象,所以将整个docker inspect
结果作为 PowerShell 对象公开可能会有用?
Docker 支持容器健康检查的想法,以了解您的应用程序何时启动并运行。章鱼是否应该与此融合?这里什么是有用的行为?
如前所述,我们知道很多使用 Octopus 部署的应用程序。NET 应用程序,并且依赖于web.config
或app.config
中的配置,因此需要在环境之间进行修改。大多数时候,这是通过配置转换来完成的。然而,这违背了“容器应该是不可变的”原则。虽然我们建议坚持这一原则,但我们正在讨论是否支持容器启动时的配置转换。这可能会在应用程序加载期间导致应用程序回收。这是我们希望得到您反馈的最大方面之一。
我们最初的实现旨在使用 SSH 和传统的触须与 Docker 守护进程进行本地交互。潜在地,这可以被转换成一个 Docker 目标,并使用 HTTPS API。这将给我们带来一系列好处,如允许我们进行健康检查,消除 Docker 主机上的触手需求,并使支持云容器主机变得更容易,如 Azure 容器服务和亚马逊的弹性容器服务。然而不利的一面是,这意味着您必须在 HTTPS 上公开 Docker 守护进程,并在两端配置证书。可能是为了支持 ACS 和 ECS,我们无论如何都需要这样做。
房间里的那头大象呢?
我们熟知许多先进的容器编排工具,如 Docker Swarm 和 Kubernetes 。试图复制这些工具的功能是疯狂的,这不是我们的意图。相反,我们认为 Octopus 最能让人们快速启动并运行 Docker 部署。随后可以使用 Step 模板实现与这些高级工具的集成。但是,由于我们不是这里的专家,请随意提出我们可以很好地集成的方法。
留下评论
我们的方法符合您希望看到的吗?哪些部分听起来很棒,哪些部分需要更多关注?我们遗漏了什么?请在下面的评论中告诉我们。
如果你已经在使用 Docker(如果你已经在使用 Octopus 的话就更是如此了),我们特别希望听到你的意见,因为我们希望确保我们尽可能构建最有用的东西。
征求意见-动态基础设施-八达通部署
云提供商使供应新的基础设施变得容易。这意味着可以轻松启动环境来测试新功能或为客户进行演示。然而,保持你的 Octopus 目标同步可能是有挑战性的,因为这个基础设施来来去去。
Octopus 目前提供了一个动态基础设施特性,允许您在部署过程中注册目标。我们给你必要的部件,你把它们粘在一起以满足你的要求。这包括定制脚本、处理输出变量和切换设置。
这种复杂性使它成为一个专家用例,需要多次尝试将所有东西按正确的顺序排列。对于许多用户来说,手动注册这些目标更容易。因为功能不清楚,有些人甚至不知道还有另一种方法。
我们想改变这一切。我们希望利用用户已经与他们的云提供商和 Octopus 交互的方式,创建一个感觉不显眼和直观的解决方案。
我们的解决方案
我们希望 Octopus 为您的部署找到并注册目标。我们已经分解出关键部分,使其全部工作。
标签驱动的目标发现
我们将在需要时发现目标,而不是直接在 Octopus 中注册部署目标。
您将对您的云资源应用标记来表示您的意图,Octopus 将在执行您的部署过程时发现并注册这些目标。
首先,您的资源将需要两个标签:octopus-environment
和octopus-role
来分别表示环境和角色。您还可以指定标签来限制项目、空间和租户的发现。
认证上下文
为了发现目标,Octopus 需要向合适的云提供商进行认证。您将能够使用 Octopus 变量提供这种上下文。例如,您将能够配置一个名为Octopus.Azure.Account
的变量来引用 Azure 帐户以用于发现。默认情况下,我们将在发现过程中将身份验证上下文中的帐户链接到目标。
为此,我们喜欢使用变量,因为它们具有完善的作用域行为,可用于为每个适用的作用域配置不同的帐户。例如,您可以通过环境来确定范围,确保测试和生产环境使用不同的身份验证上下文。
目标生命周期
云的一个关键优势是,当您使用完新环境时,可以非常轻松地启动新环境并拆除它们。Octopus 将跟踪这些发现的目标,如果它们不再存在,就将它们清理掉。如果由于某种原因,他们回来了,Octopus 将重新发现他们,以便进行下一次部署。
FAQ
我所有的云资源会成为 Octopus 中的目标吗?
不会。只有映射到内置步骤使用的 Octopus 目标的资源才会被发现。Octopus 还需要资源来设置标签octopus-role
和octopus-environment
。Octopus 会在发现过程中忽略任何没有这些标签的资源。
传统的处理部署目标的方式还有效吗?
现有的自定义脚本将继续工作,用于创建新目标的 PowerShell commandlets 将保持不变。迁移到这种新方法只会涉及:
- 在您的云资源上设置适当的标签
- 删除现有的脚本步骤
- 配置身份验证上下文变量
目标仍然可以手动注册。
如果找不到我的所有目标,它们会被自动删除吗?
只有通过此机制发现而创建的目标才会被自动删除。通过 UI 或现有 commandlets 创建的目标将像现在一样工作。
如果我需要使用不同的帐户进行部署和发现,该怎么办?
您可以通过设置其他标签来指定 Octopus 配置目标所需的额外细节。例如,要在部署到资源时使用不同的帐户,您可以指定octopus-account
标记来引用 Octopus 中所需的帐户。
将支持哪些目标?
最初,我们将致力于支持 Azure Web 应用、服务架构和 AWS ECS 集群。然后,随着我们在未来一年扩大对更多云目标的支持,我们将确保他们为动态基础架构做好准备。
我们计划接下来支持在 AWS、Azure 和 Google Cloud 上发现 Kubernetes 容器。
是否支持 AWS 实例/假定角色?
Octopus 目前为 AWS 认证提供了广泛的选择。除了使用访问密钥和秘密密钥的帐户之外,我们还支持承担 IAM 角色并使用分配给 Worker 的角色。
我们仍在研究如何最好地支持这一点,并在依赖实例角色时平衡工人池依赖性。
我们需要您的反馈
我们正在积极开发这一功能,并希望采纳您的反馈。我们已经创建了一个 Github 问题来收集您的评论。
具体来说,我们想知道:
- 关于标记方面我们可能没有预料到的边缘情况
- 该解决方案适合您的使用情形吗?
- 你希望 Octopus 解决其他动态基础设施挑战吗?
这些反馈将有助于我们提供最佳解决方案。
结论
总之,我们改进动态基础设施的计划包括:
- 定义在云资源上使用的标签,以描述发现过程中的意图
- 通过具有众所周知的名称的变量提供认证上下文
- 在部署过程中注册目标
- 清理云中不再存在的旧目标
感谢阅读。我们希望您和我们一样对这项功能将释放的潜力感到兴奋。
非常感谢您的任何反馈。
愉快的部署!
征求意见- ECS 与 Octopus 集成- Octopus 部署
原文:https://octopus.com/blog/rfc-ecs-integration-with-octopus
Octopus 是实现世界级部署的工具。我们一直拥有业界领先的功能来部署到您的内部基础架构,并且我们多年来一直支持 Azure 应用程序部署。
最近,我们扩展到其他主要的云服务提供商,允许您使用 Kubernetes 和 Terraform 等工具部署到 Azure 和 AWS。但是我们可以做得更多——仍然有 Octopus 没有提供一流集成的云原生服务。
我们希望 Octopus 成为您的首选,无论您是部署在本地、云还是两者的混合。这意味着为更多云原生服务产品提供一流的支持,从而简化复杂的部署。
为了实现这一目标,Octopus 建立了一个团队,致力于将 Octopus 与最受欢迎的云原生服务相集成。经过几个月的开发框架,使这些集成能够快速交付,我们现在可以与我们的客户,合作伙伴和其他内部八达通部门分享我们的目标和计划。
我们希望这个博客是许多征求意见(RFC)帖子中的第一个,在那里我们讨论提议的功能并提供反馈的机会。
我们被反复要求支持的一项云服务是 AWS ECS,这篇文章概述了我们目前正在讨论的一些新步骤和目标。
我们建议如何提供一流的 ECS 支持
一种高度可伸缩的快速容器管理服务,可以轻松运行、停止和管理集群上的容器。您的容器在任务定义中定义,用于运行单个任务或服务中的任务。
Octopus 已经提供了许多功能来协调容器部署,包括通过 feeds(包括 ECR feeds)使用 Docker 图像的能力,以及通过帐户安全存储 AWS 凭证的能力。通过针对 AWS CLI 编写脚本,即使不方便,现在也有可能部署到 ECS。但是我们可以做得更好。
一个新的 ECS 目标
提议的 ECS 支持从一个代表 ECS 集群的新目标开始。该目标引用用于访问 ECS 集群的 AWS 凭据、AWS 区域和集群名称:
ECS 目标实体模型。
新的服务部署步骤
概括地说,将应用程序部署到 ECS 集群需要三个组件。
首先你需要一个 Docker 图片。我们设想您的持续集成(CI)服务器将继续构建、标记和部署这些映像到 Docker 注册表。
然后一个任务定义引用一个特定的图像标签,并为生成的容器定义许多设置,比如内存和 CPU 需求、环境变量、公开的端口等等。任务定义是不可变的,每个新的图像标签必须由任务定义的新版本捕获。
然后一个服务引用一个任务定义,以及额外的运行时细节,比如运行多少个实例,实例如何在集群中分布,在哪个 VPC 中运行,负载平衡器和伸缩需求。
我们提议的步骤提供了一个自以为是的部署工作流,它将一个 Fargate 任务定义和服务合并到一个步骤中。
您将从定义贡献给任务定义的值开始。值得注意的是,与 AWS 控制台不同,在此步骤中定义的 Docker 映像不包括标记,因为映像标记的选择将推迟到创建发布之后:
显示 Docker 图像选择的步骤实体模型。
同一步骤定义了服务属性的值:
显示服务属性和任务定义容器的步骤模型。
然后,ECS 部署将执行以下流程:
- 创建发布时,选择要在任务定义中定义的 Docker 图像标签。
- 使用特定于部署到给定环境的详细信息创建新的任务定义。
- 使用步骤 2 中的任务定义配置服务。
拟议方法的好处
上述目标和步骤旨在帮助那些协调 ECS 部署的人落入成功的陷阱,我们将其总结为务实部署的十大支柱。
在这第一个里程碑中,我们特别关注基础,包括实现:
所有这些特性的核心思想是,部署将通过一系列环境进行,规范环境集包括开发、测试和生产环境。
尽管 ECS 没有环境的概念,因此为了实现可重复部署,我们必须对新的步骤和目标进行建模,以促进环境进展,同时考虑到诸如环境范围变量和更新发布快照的能力等因素。
为什么使用目标?
通过将 ECS 集群的详细信息捕获为一个目标,它被限定在一个环境的范围内,并由一个角色公开,将在哪里进行部署的具体细节被从步骤中提取出来。一个步骤简单地定义它部署到的目标角色,Octopus 将确保部署发生在当前环境的正确范围的目标上。
我们认为,如果您希望采用 AWS 推荐的关于使用多个帐户的一些最佳实践,这将是非常有益的:
AWS 帐户为您的 AWS 资源提供了自然的安全性、访问和计费边界,并使您能够实现资源独立性和隔离。
对于 ECS 目标,无论是将多个逻辑环境部署到一个共享的 ECS 集群,为每个环境部署一个专用集群,还是将多个环境划分到多个 AWS 帐户,都没有什么区别。只需将每个 ECS 目标指向适当的群集,您的部署就可以跨您使用的任何环境分区进行扩展:
【T2
抽象出任务定义版本
如果您曾经部署过一个新的 Docker 映像,首先创建一个新的任务定义版本,然后更新服务以引用它,那么您就会理解手动 ECS 部署是多么乏味。
我们的目标是让新的 ECS 部署只涉及创建一个 Octopus 版本和选择要包含的新 Docker 图像标签。通过为每个部署创建一个新的任务定义,我们消除了部署到 ECS 的人员考虑任务定义的需要。
无论您的任务定义是否包含特定于环境的值,或者每个环境是否由新 AWS 帐户中的一个集群表示,都没有关系,因为 Octopus 将代表您创建必要的任务定义。这简化了您的工作流程,以便:
- 创建新的 Docker 图像
- 用这些 Docker 图像创建一个 Octopus 版本
- 在您的环境中推广您的版本
它还确保您可以通过重新部署旧版本从失败的部署中恢复。
租户和渠道的高级部署
提议的步骤和目标还集成了高级 Octopus 特性,如租户、通道和生命周期。
新的 ECS 目标可以针对租户,再次从步骤中抽象出部署位置的细节,并将其封装在目标中:
同时,渠道规则可应用于 Docker 图像标签,通过生命周期促进部署模式,如热修复,允许直接部署到生产环境:
退回到云层结构
在幕后,新的步骤将生成 CloudFormation 模板,然后为您执行。这确保了所有资源都可以用现有的云生成工具进行审计和跟踪。
然而,任何固执己见的步骤最终都会遇到它不支持的用例。对于那些有特殊需求的人,或者对于那些不适应建议步骤的人,我们将提供将固执己见的步骤转换成原始 CloudFormation 模板的能力。
只需在溢出菜单中选择 Convert 选项,该步骤将被转换为 CloudFormation 部署步骤,让您完全控制您的 ECS 部署,而无需从头开始重新创建它们:
步骤模型,显示从固执己见的步骤到原始 CloudFormation 模板的转换。
为了允许部署具有 Docker 图像引用的 CloudFormation 模板(这是一个常见的场景,其中有 EKS 、 ECS 、 Lightsail 、 Lambdas 和 AppRunner )),部署 AWS CloudFormation 模板步骤将被更新以支持附加包引用。这允许在 CloudFormation 模板中定义和引用 Docker 图像,同时将图像标签选择推迟到发布创建时间。
第一个 ECS 里程碑的范围是什么?
我们的目标是增量发布 ECS 集成,让客户更快地获得该特性,并收集早期采用者的反馈。
上面建议的步骤是我们对这一新功能发展方向的高层次审视,但第一个里程碑可能会有以下限制:
- 将步骤限制为仅部署到 Fargate。
- 仅支持滚动部署,不支持集成 CodeDeploy 蓝/绿部署。
- 不提供构建新负载平衡器的能力。
- 排除自动缩放设置。
- 排除应用网格和 FireLens 设置。
- 排除服务自动发现设置。
- 仅创建服务,不支持任务或计划任务。
这些特性可能会包含在后续的里程碑中,所以请关注新的 RFC 帖子。
这个什么时候发布?
我们仍处于早期规划阶段,ECS 支持目前还不是一个确定的功能,所以我们不能提供发布日期。请关注博客以获取进一步的公告。
我们需要您的反馈
ECS 支持仍处于规划阶段,因此现在正是利用您的反馈来帮助塑造这一新功能的大好时机。我们创建了一个 GitHub 问题来捕捉讨论。
具体来说,我们想知道:
- 建议的步骤和目标是否适用于您的 ECS 部署?
- 你的 ECS 架构是什么样子的?
- 你有多个集群吗?
- 你有多个 AWS 账户吗?
- 您正在部署哪些类型的应用程序?
- 您希望 Octopus 能为您解决哪些 ECS 部署挑战?
这些反馈将有助于我们提供最佳解决方案。
结论
总之,我们提议的 ECS 支持的第一个里程碑包括:
- 模拟环境和租赁部署的新目标。
- 将任务定义的创建/更新与服务的创建/更新相结合的新步骤。
- 将固执己见的步骤转换成原始云形成模板的能力。
- 跨环境的简化部署工作流,支持特定于环境的变量、渠道和生命周期。
感谢你阅读这篇文章。我们希望您和我们一样对建议的新 ECS 功能感到兴奋。
非常感谢您的任何反馈。
愉快的部署!
RFC: Linux 部署——Octopus 部署
目前我们投票最高的 Uservoice 想法是在 Octopus 中增加对 Linux 部署的支持。我们将通过添加对运行 SSH 的服务器的一流支持来实现这一点,这将非常接近今天使用 Octopus 的 Windows 部署的工作方式。
为此,我们将在 Octopus 中引入一个新术语,。无代理的机器将不会运行触手,而是使用不同的通信方法,例如 SSH。
*我们的 RFC 目标是确保我们实现这一特性的方式适合最广泛的客户。
无代理机器简介
在 Octopus 中设置一台新的无代理机器,例如运行 SSH 的 Linux 服务器,其工作方式与添加一台运行触手的新机器相同。
添加无代理机器
通过选择 SSH 而不是监听或轮询作为通信方式来配置无代理机器。
无代理机器环境
无代理计算机就像普通触须一样出现在“环境”页面上,显示它们的位置和状态(在线/离线)
检查无代理机器的运行状况
典型的 Octopus 任务,如健康检查、特别脚本等,可以在所有适当的机器上运行,包括触手和无代理机器(如果两种类型都使用的话)。
证明
我们的目标是为 SSH 目标机器支持以下认证类型
密码
无密码的密钥
带有密码短语的密钥
私钥将作为加密属性存储在 Octopus 数据库中。
网络拓扑结构
不会直接从 Octopus 服务器连接到无代理机器;取而代之的是,一个或多个触角将用于与机器进行出站连接。我们计划在 Octopus 服务器上添加一个隐藏的、运行在低特权进程中的“影子”触手,作为一个方便的默认设置,但是使用特定的触手来处理不同的网络拓扑也是我们正在考虑的一个功能。
无代理机器上的 Octopus 足迹
Octopus 会在进行任何部署之前将压缩包上传到目标机器上,所以我们需要目标机器上的一些本地存储空间,这些存储空间将转到~/.tentacle/
。我们还将把包解压到一个默认的位置,就像我们在一个触手机器上所做的一样,例如~/.tentacle/apps/{environment}/{project}/{package}/{version}
,并且我们还将支持自定义的安装位置来将文件移动到其他地方。
包装采购
因为执行 SSH 部署需要一个触手机器,所以这些部署的包获取将与今天使用 Octopus 的 Windows 部署略有不同。
触手机器将提取 NuGet 包并创建一个.tar.gz
tarball,然后上传到目标机器。
触手机器可以与目标机器位于同一位置,以优化带宽使用,即 Octopus 将包上传到触手,触手再将包发送到目标机器。
部署
包部署步骤将完全通过目标机器上的单个 shell 会话来运行。
- 我们将检查并确保八达通脚本是最新的
- 包和支持部署文件将通过 SCP 上传
- 将执行部署业务流程脚本
- 如果默认安装目录不存在,将创建该目录
- tar 文件将被解压缩
predeploy
会跑- 如果已经指定了自定义安装目录
- 如果在部署前清除目录的选项为真,我们将清除自定义安装目录
- 将提取的文件复制到自定义目录
deploy
会跑postdeploy
将运行- 运行保留策略来清理旧部署
- 删除 Octopus 变量文件(确保敏感变量不会留在服务器上)
部署脚本
主要的部署编排脚本将用 bash 编写,因为这是*nix 发行版中最少的共同点。这个脚本将寻找用户可以创建的predeploy
/ deploy
/ postdeploy
脚本,如果它们存在,就执行它们。
predeploy
/ deploy
/ postdeploy
脚本可以用用户偏好的脚本语言编写(但是用户必须确保它安装在运行部署的服务器上)。
predeploy
- 部署前需要运行的任务,例如应用程序所需的配置转换。
deploy
- 实际部署应用程序所需的任务。
postdeploy
- 部署后需要运行的任务。例如,清理应用程序部署期间创建的任何临时文件。
工作目录将是预先部署脚本的默认安装目录,也是部署和后期部署脚本的默认或自定义安装目录。
部署的环境变量
Octopus 拥有比 Linux 环境变量所能支持的更复杂的变量系统和语法。不得不在像Octopus.Action[Install website].Status.Code
这样的名字和有效的 POSIX 等价物之间进行映射,这看起来很不舒服,而且容易出错。大型 Octopus 部署也倾向于携带大量的变量,所以我们不愿意将这些任意地放入部署脚本运行的环境中。
与直接设置环境变量不同,部署脚本将可以访问一个tentacle
命令,该命令可用于检索它们需要的值。例如,要检索部署使用的自定义安装目录,用户可以像这样调用tentacle
命令:
DEST=$(tentacle get Octopus.Action.Package.CustomInstallationDirectory)
这声明了一个环境变量DEST
来保存定制安装目录(随后作为$DEST
可用于脚本)。
使用"
引号可以支持带有嵌入式空格等的值。
尽管我们不太可能在该命令的第一个版本中实现它,但我们正在考虑一些更复杂的特性,如迭代:
for ACTION in $(tentacle get "Octopus.Action[*]")
do
echo "The status of $ACTION was $(tentacle get "Octopus.Action[$ACTION].Status.Code")"
done
这突出了我们看到的使编写部署脚本变得更加愉快的机会。
tentacle
命令的其他功能
使用tentacle
助手还将提供对在 Windows 机器上使用PowerShellcmdlet 支持的命令的一致访问。
设置输出变量
可以使用tentacle set
将输出变量发送到 Octopus 服务器:
tentacle set ActiveUsers 3
或者:
ps -af | tentacle set RunningProcesses
收集工件
可以使用tentacle collect
将目标机器上的文件收集为 Octopus 工件:
tentacle collect InstallLog.txt
送入工具
当我们(或其他人)提供助手脚本时,这些脚本本身需要访问变量、路径等等,可以使用tentacle exec
调用这些脚本:
tentacle exec xmlconfig Web.config
部署功能
XML 配置转换/appsettings 支持等功能将在目标机器上运行。
支持 Octopus 脚本和可执行文件将是目标机器上默认文件夹结构的一部分,即~/.tentacle/tools/
,在这个文件夹中,我们还可以包含使用 Mono 进行支持的助手应用程序。特定于. NET 的约定,如 XML 配置转换/appsettings。
我们还可以包括不同的脚本/可执行选项来支持其他部署功能。
保留策略
部署完成后,我们将应用为项目指定的保留策略,就像我们处理 Windows 部署一样。
用户可以指定保留若干天的部署,或者特定数量的部署。如果指定了其中任何一项,我们将删除不在指定保留策略范围内的任何文件。
系统需求
Linux 发行版的默认配置和可用的软件包可能会有很大的不同。我们的目标是选择一个得到广泛支持的基线,使 Octopus 能够部署到几乎任何当前的 Linux 发行版上。
我们对目标机器的基本假设是:
- 可以使用 SSH 和 SCP 访问它
- 用户的登录 shell 是 Bash 4+
tar
可用
我们自己计划构建和测试的平台有:
- 亚马逊 Linux AMI 2014.03
- LTS Ubuntu 服务器 12.04
我们将尽最大努力保持发行版无关性,但是如果您能够为您自己的服务器选择这些选项中的一个,您将帮助我们提供有效的测试和支持。
未解决的问题
- 管理特定于平台的路径
- 当应用程序同时部署到 Windows 和 Linux 服务器时,需要为 Linux 和 Windows 分别指定“自定义安装目录”等路径。我们能让这种体验变得更好吗?
- 部署脚本的命名
- 预先部署/部署/后期部署,或
- 预先部署/部署/后期部署,或
- pre_deploy/deploy/post_deploy?
- 默认情况下,我们将上传包和提取包的路径的定制
- 有必要通过 Octopus 进行配置吗,或者像
~/.tentacle/apps
这样的位置可以根据需要由管理员链接到其他位置吗?
- 有必要通过 Octopus 进行配置吗,或者像
- 像我们在 PowerShell 中一样写出变量
- 在 PowerShell 中,我们首先使用 DPAPI 对它们进行加密,在 Linux 上有类似的标准加密功能吗?
我们需要你的帮助!
我们真正希望的是,已经在 Octopus 中使用 SSH 的客户,或者希望开始使用 SSH 的客户,能够就我们如何在 Octopus 中实现 SSH 部署的计划给我们提供反馈。
无论是对上面建议的实现的改进,还是如果我们已经做出了假设认为无法工作,那么请在下面的评论中告诉我们。*
征求意见-从 scriptcs 迁移到 dotnet-script - Octopus 部署
原文:https://octopus.com/blog/rfc-migrate-scriptcs-dotnet-script
我们收到了客户反馈和用户声音投票,要求我们更新 Octopus 用来运行 C#脚本的工具,从 scriptcs 到 dotnet-script 。这将:
- 在部署脚本中解锁较新的 C#语言功能
- 允许从脚本中直接引用 NuGet 包
- 不再需要安装 Mono 来在 Linux 部署目标上运行 C#脚本
C#脚本占了我们脚本步骤的大约 5%,所以我们想了解这个变化对我们用户的影响。
如果您在部署过程中使用 C#脚本,并且使用 SSH 和 Mono 部署到 Linux 目标,或者部署到运行早于 2012 年 R2 版的 Windows 版本的 Windows Tentacle 目标,建议的更改可能会影响您。
这篇文章概述了潜在的变化,以及迁移到 dotnet-script 和弃用 scriptcs 的权衡。我们还创建了一个 GitHub 问题,您可以在这里提供反馈,我们可以进一步评估对该功能的需求。
我们建议如何支持 dotnet-script
该意见征询书(RFC)提议移除scriptcs
以支持dotnet-script
。
为了将软件部署到您的服务器上,我们使用了触手,这是一个轻量级服务,负责与 Octopus 服务器通信,并调用卡拉马里。Calamari 是一个命令行工具,它知道如何执行部署,并且是所有部署操作(包括脚本执行)的宿主进程。我们目前为。NET Framework 4.0.0、4.5.2 和 netcore3.1。根据您的服务器操作系统、体系结构和版本,触手会接收这些 Calamari 版本之一。
历史上,Calamari 要求在 Linux 目标上安装 Mono 来执行scriptcs
,因为它是完全编译的。NET 框架。随着跨平台的引入。使用 netcore3.1 Linux 的网络应用程序现在可以本地运行。NET 应用程序消除了 Mono 的复杂性和开销。Linux 目标目前默认接收 netcore3.1 Calamari,除了 Linux SSH 目标,它可以指定在 Mono 上运行脚本。
是基于. NET 的 C#脚本的现代实现。它可以在所有支持的目标上运行。网络应用程序(netcore3.1 及更新版本)。如果我们做出这一更改,这将意味着 C#脚本将只能在支持. NET 的目标上运行。Windows Server 2012 R2 和早期版本仅支持。所以这些目标将失去运行 C#脚本的能力。
影响
增加的功能
特征 | scriptcs | 点网脚本 |
---|---|---|
C#版本 | 5 | 8 |
移除 Linux 对 Mono 的依赖 | ❌ | ✅ |
获取导入支持 | ❌ | ✅ |
允许未来。NET 5 和 6 支持 | ❌ | ✅ |
拟议方法的好处
所有包含在第 8 版之前的 C#语言特性现在都可以在你的 C#脚本中使用了。
消除对 Mono 执行脚本的依赖使我们与现代的跨平台相结合。NET 功能降低了调用 Mono 和相关问题的复杂性。
来自dotnet-script
的 NuGet 导入支持允许在脚本中直接引用 NuGet 包,而不必在脚本包中包含 dll。新方法如下所示。
#r "nuget: RestSharp, 108.0.1"
using RestSharp;
var client = new RestClient("https://pokeapi.co/api/v2/");
var request = new RestRequest("pokemon/ditto");
var response = await client.ExecuteGetAsync(request);
Console.WriteLine(response.Content);
使用 Mono 的 Linux SSH 目标
这种变化的一个代价是,在使用 SSH 和 Mono 的 Linux 部署目标上,C#脚本不再可用。
移民
要针对 SSH linux 目标运行 C#脚本,您需要重新配置 SSH 目标,以使用通过 netcore3.1 运行的独立的 Calamari。
为此,在您的 SSH 目标上选择自包含的 Calamari 目标运行时。使用 Linux 触手的目标将继续像以前一样工作。
Windows Server 2012 R2 版(及更早版本)目标
这一改变的另一个代价是dotnet-script
只适用于 netcore3.1 及以上版本。这将使 C#脚本不可用于针对安装在早于 2012 年 R2 版的 Windows 上的 Windows Tentacles 的部署,因为这些版本正在运行。Calamari 的. NET 框架构建。
工作区
我们开发了一个解决方法,因此您可以在受影响的 Windows 目标上继续使用 scriptcs,但是您必须更新您的部署过程。
- 添加 scriptcs NuGet 包作为引用包。
- 将 C#脚本的主体复制到下面 PowerShell 模板中的
$ScriptContent
变量中。
C#脚本中使用的任何参数都需要通过 scriptcs 参数传递,并在 ScriptContent 中使用Env.ScriptArgs[Index]
格式引用。下面的模板显示了如何为Octopus.Deployment.Id
执行此操作的示例。
$ScriptContent = @"
Console.WriteLine(Env.ScriptArgs[0]);
"@
New-Item -Path . -Name "ScriptFile.csx" -ItemType "file" -Value $ScriptContent
$scriptCs = Join-Path $OctopusParameters["Octopus.Action.Package[scriptcs].ExtractedPath"] "tools/scriptcs.exe"
& $scriptCs ScriptFile.csx -- $OctopusParameters["Octopus.Deployment.Id"]
这个什么时候发布?
我们仍在评估这一变化可能会影响多少用户。在我们清楚了解我们将影响谁以及他们需要采取什么行动之前,我们不会做出或发布提议的变更。
我们需要您的反馈
我们仍在考虑这一变更,因此现在正是利用您的反馈来帮助制定这一提案的大好时机。我们制作了一期 GitHub 来捕捉讨论。
具体来说,我们想知道:
- Linux SSH 目标或早于 2012 R2 的 Windows 版本的限制会对您产生影响吗?
- 如果是,您能预见到任何可能阻止您升级这些部署目标或使用替代脚本语言的挑战吗?
- 更新的语言特性,更容易的 NuGet 包引用,以及在移除 Mono 时增加的可靠性证明了这些改变的合理性吗?
您的反馈将帮助我们提供最佳解决方案。
结论
总之,从scriptcs
到dotnet-script
的迁移将导致以下变化:
- 运行 Mono 的 Linux SSH 目标不赞成使用 C#脚本
- 对于在早于 2012 年 R2 版的版本上运行的 Windows 部署目标,不推荐使用 C#脚本
- 增加对 C# 5 到 C# 8 语言特性的支持
- 脚本中 NuGet 包的直接导入
- 删除了在 Linux 目标上运行 C#脚本的 Mono 要求
感谢您阅读本 RFC。非常感谢您的任何反馈。
愉快的部署!
RFC -多租户- Take 2 - Octopus 部署
更新:多租户部署将作为 Octopus Deploy 3.4 的一部分推出,Beta 2 已经发布!参见 3.4 Beta 2 博客文章了解最新信息。
这是对原始 RFC 的后续 RFC,旨在更好地支持 Octopus Deploy 中的多租户部署。
Octopus Deploy 旨在部署软件版本,并通过一系列具有可重复结果的环境来推广它们。Octopus 很好地模拟了这种典型的场景,但是它不适合多租户应用程序。
我们之前的提议探索了标记环境的概念,以使处理许多环境更容易。这个 RFC 将探索如果我们实现了租户一级概念而不是环境假装是租户,Octopus Deploy 会如何表现。
概述
这是一个很大的 RFC,但也是一个很大的特性集!我们鼓励您花时间了解我们的提议,以及它将如何影响您的情况,并帮助我们把握方向!
为什么是另一个 RFC?
参与 RFC 过程最棒的一点是,你真的可以对 Octopus Deploy 的发展方向产生重大影响。有几个引人注目的特点促使我们起草了另一份 RFC:
- 租户感知生命周期,您希望确保在将某个版本部署到
Tenant 1
的Production
环境之前,已经将该版本部署到Tenant 1
的Staging
环境,对所有租户也是如此。 - 更容易防泄漏的,你要确保你不会意外地将
Tenant 1
的发布部署到属于Tenant 2
的环境中。 - 以租户为中心的工作流您希望从租户的角度管理租户及其项目、环境、变量和部署。
我们可以使用环境标签来构建这些特性,但是我们很快发现实现的复杂性在增加,更不用说试图描述如何配置 Octopus 来实现这些引人注目的特性了。将租户视为一级概念使得这些特性更容易实现,也更容易解释。
别忘了,Octopus Deploy 是为少数环境设计的。引入租户意味着我们可以引入一些功能,使处理大量租户变得更容易,而可以重新管理少量环境!
与原始 RFC 的差异?
你可能会注意到这两种设计有很多相似之处。如果你还没有阅读原始 RFC,我们强烈建议你阅读。在大多数情况下,你可以简单地将任何环境——假装是租户替换为租户——一级概念:
- 核心问题是多租户是痛苦的没有改变
- 通过使用租户标签而不是环境标签将租户视为租户组,您仍然可以更容易地管理大量租户
- 为了使使用标签更容易,你可以用颜色来标记你的标签
- 生命周期将恢复到今天的工作方式,明确地将环境添加到阶段中,但是生命周期可以成为租户感知的,以确保您在投入生产之前安全地将版本提升到每个租户的暂存环境中(例如)
- 仪表板仍然可以以类似的方式进行聚合,但是我们可以让它更像一个数据透视表,您可以从租户、版本或环境中选择两个选项来定制您的视图
- 您仍然可以定义变量模板,但是不是在环境上定义值——假装是租户,而是在租户本身上定义它们
- 变量检查器仍然可以像我们最初提议的那样工作
- 您可以显式地将租户映射到项目,而不是通过生命周期隐式地将租户映射到项目
您可能还会注意到一些额外的引人注目的功能,用于通过 Octopus Deploy 管理多租户部署,以及一些令人愉快的增强功能,这些增强功能与租户即一流概念很有意义。
更困难的一件事是迁移过程。有了每租户环境,就不需要迁移了——您只需开始额外使用新功能。对于租户即第一类概念,您需要将某些环境转换为租户,并带来某些变量集/变量。
我们建议如何处理 Octopus 部署中的多租户问题
作为一流概念的模型租户
我们建议将租户建模为一级概念,而不是将环境或项目纠结成一个形状来实现多租户部署。您可以:
- 创建和管理租户
- 为每个租户指定应该将哪些项目部署到哪些环境中
- 管理特定于每个租户的变量
- 定义租户感知的生命周期
像往常一样,我们希望多租户是一个附加的功能集:如果你不需要多租户,你的八达通体验将与今天一样除了一些将使每个人受益的功能。
一旦您启用了多租户部署,该界面可以向主菜单栏添加一个租户选项卡,您可以在这里管理您的租户。
您可以给每个租户一个名称和徽标,以便在列出租户时更容易区分。您还可以为每个租户明确定义将哪些项目部署到哪些环境中。在这种情况下,将把Synergy
和Mojo
项目部署到Staging
和Production
环境中。
这是我们希望得到反馈的特定区域:这种链接应该有多精细?如果租户有一个试运行和生产环境,这是否适用于所有项目?或者您希望为每个项目选择特定的环境?
当你有很多租户时(这是一个很大的问题),和他们单独工作是很痛苦的。我们(仍然)建议引入标签来支持这些类型的场景。您可能已经注意到,我们已经为上面的Customer-2
定义了几个标签,在本例中,作为一个具有已定义模块的VIP
租户,托管在Shared 2
托管组/集群/服务器场中。
首先,您可以通过在库中创建标签集来确定您想要使哪些标签可用,并为每个标签集创建有效的标签列表。创建哪个标签集和标签完全由您决定。在本例中,我们创建了几个标记集,每个标记集代表我们希望在整个 Octopus 部署中利用的不同属性。
请注意,这些标签目前意义不大——请继续阅读,看看如何利用这些不同的属性来实现一些引人注目的场景,尤其是当您的标签集代表正交关注点时。
还要注意标签是一个附加特性:如果你没有定义任何标签集,Octopus 不会提示你与标签有关的任何事情。
如果您有很多租户,那么如果您能够管理哪些标签被批量应用到租户,事情会简单得多。这将使您能够添加一个新的标记集,并非常快速地为您的所有租户添加适当的标记。
一旦您配置了一些标签集并标记了一些租户,Octopus 就可以按标签集聚集租户页面。您可以按名称或标签搜索租户,或者直接钻取其中一个标签并显示匹配的租户。
改进的仪表板
将租户作为一级概念,Octopus 可以使仪表板更像一个数据透视表,您可以从项目、环境、租户/标签集中进行选择。考虑一个项目/环境(目前的默认设置)的例子,Octopus 可以聚集关于每个组的最重要的信息,包括租户的数量、组的整体状态(如果需要的话,可以用指示器来引起您的注意)、已经部署到所包含的租户的版本范围,以及向所包含的租户推出最新版本的进度。
有些人可能对项目/客户类型或其他集合更感兴趣。
类似地,项目概述可以按环境或租户/标签集分组,以显示今天的发布历史。
部署到租户
Octopus 还可以使生命周期租户感知阻止您在Customer-1
将项目部署到Production
环境,直到您在Customer-1
将项目部署到Staging
环境。
生命周期可以像现在一样工作,除了它在多个环境中看到一个租户:在这种情况下,生命周期可以确保通过每个租户的环境链提升一个版本。
一旦您创建了一些租户,您就可以选择一个租户和一个环境来部署一个版本。您还可以选择多个租户和环境(受生命周期限制)来并行运行部署(就像您现在可以并行部署到多个环境一样)。
Octopus 还可以提供一个专门设计的屏幕,用于在所有租户之间滚动部署。请注意,我们现在可以垂直排列租户(不再需要水平滚动),并可能按名称或标签过滤租户。Octopus 可以显示每个租户最近的发布历史,以及 Deploy 按钮,这样你就可以在同一个屏幕上升级所有的租户。
按标签选择租户
在章鱼世界的某些地方,识别租户是有意义的。考虑将部署授权给特定的部署目标或帐户,或者将变量和步骤限定到特定的租户。Octopus 不局限于识别特定的租户(可能有数百个),而是允许您使用标签组合来选择一组租户。Octopus 可以在运行时解析这个租户查询,以提供匹配租户的时间点列表。
虽然在许多情况下使用这些查询来引用租户可能更方便,但是根据每种情况的复杂性,这可能会变得更加混乱。为了帮助驯服额外的复杂性,我们可以提供一个设计视图,类似于我们为设计通道的版本规则所做的。
部署目标和客户
考虑这些与多租户部署相关的场景:
- 您可以为每个租户提供专用的机器,并且您不希望将部署或敏感变量/数据泄露给错误的租户的机器
- 您有一个客户正在提供他们自己的 Azure 订阅,您希望确保其他租户的部署不会泄漏到该订阅中
- 您希望实现专用/共享托管模型,其中一些租户将在共享池/群集/场中托管,而其他租户将在专用池/群集/场中托管
Octopus 可以帮助您实现这些目标,它允许您通过名称和/或标签来确定哪些租户应该被允许部署到特定的目标和帐户。
这是在共享主机集群中设置一个节点的示例。Octopus 可以自动将这个部署目标包括在Synergy-Web-Server
环境中,为标记为Hosting: Shared 1
的租户部署Synergy-Web-Server
。此外,可能会阻止不符合此规范的部署包含此部署目标。
这是一个限制哪些部署有权使用帐户的示例。Octopus 可以授权Customer-3
到Production
环境的部署使用Customer-3 Synergy Production Subscription
帐户,拒绝不符合该规范的部署。
部署目标和向后兼容性
我们希望保持向后兼容性,并允许您选择加入多租户部署。我们认为区分普通项目和多租户项目(映射到至少一个租户的项目)会有所帮助。考虑我们如何计算部署中应该涉及哪些部署目标:
- 当部署一个普通项目时:查找所有具有匹配角色的部署目标,忽略由部署目标指定的任何租户范围。这相当于我们今天所做的。
- 为特定租户部署多租户项目时:查找所有角色匹配的部署目标;其中租户与部署目标指定的租户范围相匹配。
这意味着没有指定租户范围的部署目标将不可用于多租户项目的部署。这样做的另一个好处是,您可以提前构建基础架构,并在需要时安全地向基础架构添加租户。
范围变量和步骤
如果可以基于标记来确定范围,那么确定变量值的范围会更方便。在本例中,当我们使用Telephony
模块为任何VIP
或Early adopter
租户部署到Production
环境时,将使用变量值。
类似地,确定部署步骤的范围会变得更加简单。在这个例子中,我们已经基于几个不同的标签定制了我们的部署过程。首先,我们将把探索模块部署到标有Module: Exploration
的租户。当标记为Customer type: VIP
的 VIP 客户升级后,我们也会通知优先支持团队。一旦我们的租户成功完成生产部署,我们还会向他们发送定制的电子邮件通知。
管理特定于租户的变量
下一个难题是管理特定于每个租户的变量。为了解决这个问题,我们计划让您直接向租户添加变量,但是在我们开始之前,您如何知道哪些变量需要添加到这些租户呢?想象一下,如果项目可以为每个不同的租户定义所需的变量,然后每个租户可以提示您它需要的变量。我们提议引入变量模板的概念。
项目变量模板
变量模板可以允许项目定义成功部署所需的变量。我们认为变量模板作为复合部分将更易于管理,就像今天的项目变量和库变量集一样。每个项目可以定义在租户之间变化的变量集,可选地包括库中的公共变量模板。在本例中,项目定义了两个特定的变量模板,并包含了库中的两个变量集模板。
这些变量模板中的每一个都可以以类似于为步骤模板定义参数的方式进行定义,其中您可以提供变量名、标签、帮助文本、默认值和输入控制类型,如单行/多行文本框、敏感/密码框、复选框或下拉菜单。
租户变量集
如今,许多使用 Octopus 进行多租户部署的客户将为每个租户创建一个库变量集。我们建议将变量作为租户设置的一部分。通过这种方式,您可以将所有特定于租户的变量指定为租户本身的一部分,而 Octopus 会自动地、隐式地将这些变量限定到该租户。当你为一个特定的租户部署一个版本时,Octopus 会自动合并来自该租户的变量集。
在本例中,Customer-2
需要为Synergy
和Mojo
项目提供变量,根据两个项目的变量模板,我们需要:
TenantAlias
来自标准租户详细信息库变量集模板StorageAccountUri
从普通存储账户库变量设置模板- 来自协同项目的
SynergyDatabase
和SynergyApiKey
MojoDatabase
来自 Mojo 项目
在这种情况下,我们没有为MojoDatabase
变量定义一个值,而是被提示设置该值。
变量检查器
如今,Octopus Deploy 中的变量一般来自项目、库变量集或 step 模板。添加租户作为变量的来源将简化管理变量的某些方面,但是诊断变量的问题可能会变得更加困难。我们建议添加一个变量检查器,这将使它更容易获得一个项目中所有变量的概览,它们的来源,以及是否有任何问题。
正在打包
有了这些功能,我们希望与管理大规模多租户部署相关的痛苦将得到显著缓解。考虑创建新租户会涉及哪些内容:
- 创建名为
CustomerA
的新租户- 输入提示变量
- 将最新版本部署到
CustomerA
的环境中 - 利润!
留下评论
你怎么想呢?这是您(再次)大胆发言的机会,可以帮助我们针对您的情况构建合适的功能。
这一次,我们想提出一些具体的问题:
- 与之前的提案(环境标签)相比,您觉得这个提案怎么样?
- 您能描述一下您希望如何将租户与项目/环境联系起来以适应您的情况吗?是否要选择一个项目并指定该项目中的租户应该可以使用的环境?或者,不管项目如何,每个租户都有相同的环境吗?也许是别的什么?
- 您认为有什么令人信服的理由可以让您通过名称将项目或库变量集中的变量限定到特定的租户,或者我们应该强制您指定特定租户的值?
- 我们遗漏了什么特别的特征吗?
RFC:多租户- Octopus 部署
更新:多租户部署将作为 Octopus Deploy 3.4 的一部分推出,Beta 2 已经发布!参见 3.4 Beta 2 博客文章了解最新信息。
更新:根据一些令人信服的客户反馈,我们重新审视了这份 RFC!阅读更新的 RFC,提议将租户作为一级概念...
Octopus Deploy 旨在部署软件版本,并通过一系列具有可重复结果的环境来推广它们。Octopus 很好地模拟了这种典型的场景,但是它不适合多租户应用程序。让我们考虑一下在使用 Octopus Deploy 的多租户部署指南中讨论的例子...
为大公司客户制作人力资源软件。他们将软件作为 SaaS 产品提供给客户,并为客户托管网站和相关服务。由于应用程序的架构,对于每个客户,他们部署不同的 SQL 数据库、ASP.NET 网站的副本和 Windows 服务的副本。
这个场景中的关键问题是相同的组件需要部署多次,每个最终客户一个,这与 Octopus 通常设计处理的场景不同。
为了管理今天的这种情况,我们提出三条建议:
遗憾的是,Octopus 中的单位租户环境和单位租户项目的可扩展性都很差,这就是我们想要解决的问题,从这个 RFC 开始!
在本 RFC 中,我们将主要关注每租户环境,因为这是目前最流行的方法
概述
这是一个很大的 RFC,但也是一个很大的特性集!我们鼓励您花时间了解我们的提议,以及它将如何影响您的情况,并帮助我们把握方向!
多租户很痛苦
在帮助我们的许多客户处理 Octopus 中的多租户部署后,我们看到了以下主题:
我们错过了什么重要的东西吗?留言评论!
管理大量环境
Octopus 部署引擎和 API 可以处理数千种环境。我们客户的主要抱怨是,管理多个环境的用户体验很差。
- 环境页面本身的伸缩性不好。加载速度慢,没有过滤/搜索功能,需要大量的垂直滚动,并且当许多租户托管在共享基础架构上时,可能会有大量重复的信息。
- 仪表板和项目概述页面的伸缩性不好。加载时间慢,没有过滤/搜索,没有办法聚合状态,它们水平溢出,水平滚动很难。
- 每次添加新环境时,您都需要更新大量断开连接的资源:
- 应该包括新环境的生命周期阶段
- 为新环境托管软件的部署目标/机器
- 支持部署到新环境的帐户
- 应该适用于新环境的步骤
- 应该限定新环境范围的变量
添加新租户
根据我们对每租户环境的建议,您需要:
- 为名为
Production-CustomerA
的租户创建新环境- 将
Production-CustomerA
环境添加到所有必需的生命周期中 - 将
Production-CustomerA
环境添加到所有必需的帐户中 - 将
Production-CustomerA
环境添加到所有必需的部署目标中
- 将
- 创建一个名为
Production-CustomerA
的新库变量集,以包含特定于新租户的变量- 您需要了解新租户所需的全套变量
- 手动输入每个变量名和值,注意不要出错!
- 将所有这些变量单独纳入
Production-CustomerA
环境
- 对于每个必需的项目:
- 将
Production-CustomerA
变量集添加到所有必需的项目中 - 可选地将部署步骤限定在
Production-CustomerA
环境中 - 将最新版本部署到
Production-CustomerA
环境中
- 将
- 利润!
如果最终客户需要一个测试和批准发布到他们的生产环境的临时环境,那么您必须为Staging-CustomerA
重复所有这些。
我们的许多客户通过 Octopus API 自动完成了创建新租户的过程,但这并没有减轻租户长期持续管理带来的痛苦。
没有管理客户的单一场所
当您管理一个具有一个或多个环境的客户时,您经常需要在 Octopus 用户界面中来回切换,而在与同一客户相关的信息片段之间导航很少或没有帮助。为环境、变量集、帐户和专用机器使用命名约定确实是一个好主意,但是它只能帮到你这么多。
库变量集是全局的
考虑这样一种情况,某些重要的客户应该只由经过挑选的少数人来管理。这可以通过为环境设置权限并确保库变量集中的每个变量都正确地作用于环境来实现。然而,库变量集本身并不连接到环境——它被认为是一个全局资源。
我们建议对 Octopus 部署中的多租户做些什么
我们建议在 Octopus 部署中引入一系列新功能。我们相信,除了管理多租户部署的客户之外,这些功能还将惠及我们的绝大多数客户。
有话要说?留下评论!
使用标签分组管理环境
到目前为止,我们讨论的最常见的问题之一是处理大量的环境,尤其是您不能在仪表板上聚合环境,也不能将环境作为组来处理。我们建议引入标签来支持这些类型的场景。
首先,您将通过在库中创建标签集来确定您想要使哪些标签可用,其中包含每个标签集的有效标签列表。创建哪个标签集和标签完全由您决定。在本例中,我们创建了几个标记集,每个标记集代表我们希望在整个 Octopus 部署中利用的不同属性。
请注意,这些标签目前意义不大——请继续阅读,了解如何利用这些不同的属性来实现一些引人注目的场景。
Octopus Deploy 现在可以在配置您的环境时提供这些标记集,并且您可以配置您想要应用于每个环境的实际标记。看看下面,你会注意到标签集已经按照库中的顺序进行了排序。在本例中,我们将Production-Tenant-Mobil
环境标记为属于Production
阶段,托管在Shared 2
托管组中,具有选定的模块,并作为VIP
客户。
如果您有许多环境,那么如果您能够管理哪些标签被批量应用到环境中,事情会容易得多。这将使您能够添加新的标记集,并快速标记您的所有环境。
改善环境页面
一旦你配置了一些标签集并标记了一些环境,Octopus 就可以通过标签集聚集环境页面。您可以直接钻取其中一个标记,并显示匹配的环境。
改进仪表板
在配置您的标记和环境之后,您可以将仪表板配置为按其中一个标记进行分组。Octopus 可以聚集关于该组的最重要的信息,包括环境的数量、该组的整体状态(如果必要的话,用指示器来引起您的注意)、已经部署到所包含的环境中的版本的范围、以及向所包含的环境推出最新版本的进度。
在这个例子中,我们通过阶段标签对环境进行分组。
有些人可能对客户类型或其他聚合更感兴趣。
类似地,项目概述可以按标签分组,显示今天的发布历史。
单击其中一个组可以深入到该组,并显示该组中环境的更多详细信息。在本例中,我们点击了 3.2.6-3.2.7 以查看该组的详细信息。请注意,我们现在可以垂直排列环境(不再水平滚动),并可能通过名称或其他标签过滤环境。我们还可以显示每个环境的发布历史,以及Deploy
按钮,这样您就可以从同一个屏幕升级所有的生产租户。
通过标签引用环境
当您现在想在 Octopus Deploy 中引用一个环境时,您需要通过环境名显式地引用它。在您创建了一些标签集之后,您可以开始通过标签引用环境。考虑在标记集之前将部署目标配置为多个环境的共享主机的例子。
有了标记,您可以简化这个部署目标,只需配置一次,并允许 Octopus 在运行时动态解析应用哪些环境。
虽然通过标签引用环境在很多方面会更方便,但根据每种情况的复杂性,它可能会变得更混乱。为了帮助驯服额外的复杂性,我们可以提供一个设计视图,类似于我们为设计通道的版本规则所做的。
管理生命周期、部署目标和账户
如果您可以通过标签引用环境,那么在生命周期、部署目标和帐户中管理环境将变得更加简洁和强大。例如,每当您添加一个新的环境时,Octopus 可以根据其标签自动将该环境包含在正确的部署目标、生命周期阶段和帐户中。我们刚刚看到了一个部署目标的示例。在这个例子中,我们定义了一个简单的生命周期,其中每个阶段的环境都由Phase: *
标签驱动。
在本例中,所有标有Phase: Production
的环境都将被授权使用Synergy Production Subscription
账户。
范围变量和步骤
如果可以基于标记来确定范围,那么确定变量值的范围会更方便。在本例中,当我们部署到由Phase: Production
标记定义的任何生产租户时,将使用变量值。
类似地,确定部署步骤的范围会变得更加简单。在这个例子中,我们已经基于几个不同的标签定制了我们的部署过程。首先,我们将把探索模块部署给标记为Module: Exploration
的租户。当标记为Customer type: VIP
的 VIP 客户升级后,我们还会通知优先支持团队。一旦我们的生产租户成功完成部署,我们还将向他们发送定制的电子邮件通知,标签为Phase: Production
。
管理特定于租户的变量
下一个难题是管理特定于每个租户的变量。为了解决这个问题,我们计划让您直接将变量添加到环境中,但是在我们开始之前,您如何知道哪些变量需要添加到这些环境中呢?想象一下,如果项目可以定义每个不同环境所需的变量,然后每个环境可以提示您它所需的变量。我们提议引入变量模板的概念。
可变模板
变量模板可以允许项目定义成功部署所需的变量。我们认为变量模板作为复合部分将更易于管理,就像今天的项目变量和库变量集一样。每个项目都可以定义一组在不同环境之间变化的变量,可选地包括库中的公共变量模板。在本例中,项目定义了两个特定的变量模板,并包含了库中的两个变量集模板。
这些变量模板中的每一个都可以以类似于为步骤模板定义参数的方式进行定义,其中您可以提供变量名、标签、帮助文本、默认值和输入控制类型,如单行/多行文本框、敏感/密码框、复选框或下拉菜单。
环境变量集
如今,许多使用 Octopus 进行多租户部署的客户将为每个租户创建一个库变量集。我们建议将变量作为环境设置的一部分。这样,您可以将所有特定于环境的变量指定为环境本身的一部分,而 Octopus 会自动地、隐式地将这些变量限定在该环境中。当您将一个版本部署到一个特定的环境中时,Octopus 会自动合并该环境中的变量集。
项目已经通过生命周期的方式映射到环境中,因此很自然地假设环境将使用通过生命周期链接到的任何项目中的可变模板。
在这个例子中,Synergy 项目将被部署到这个环境中,基于 Synergy 中定义的可变模板,我们需要:
CustomerName
和TenantAlias
来自Standard tenant details
库变量集模板StorageAccountUri
来自Common storage account
库变量集模板Synergy
项目中的SynergyDatabase
和SynergyApiKey
MojoDatabase
来自Mojo
项目
在这种情况下,我们没有为MojoDatabase
变量定义一个值,而是被提示设置该值。
变量检查器
如今,Octopus Deploy 中的变量一般来自项目、库变量集或 step 模板。添加环境作为变量的来源将简化管理变量的某些方面,但是诊断变量的问题可能会变得更加困难。我们建议添加一个变量检查器,这将使它更容易获得一个项目中所有变量的概览,它们的来源,以及是否有任何问题。
不仅仅是多租户
如今,在 Octopus Deploy 中管理大规模多租户部署显然是一件痛苦的事情。我们相信 RFC 中提出的功能将使我们的大多数客户受益。考虑这些场景:
- 类似于多租户的其他使用案例:
- 管理公共云中的多区域部署,您将部署到几个地理区域,但将它们都视为生产。
- 有多个测试工程师的团队,每个人都有自己的测试环境。
- 动态试运行/退役特性分支环境,用于在正常的开发/测试/生产生命周期之前测试新特性。
- 开始一个新项目,您配置开发和测试环境。几周后,您希望添加试运行和生产环境,但是忘记了需要为这些新环境添加哪些新变量。
- 出于各种原因,简单地管理大量的环境和变量。
- 出于内聚/权威/安全的原因,希望将特定于环境的变量直接保存在环境中。
- 想给你的项目变量添加更多的结构。
收尾
有了这些功能,我们希望与管理大规模多租户部署相关的痛苦将得到显著缓解。考虑创建新租户会涉及哪些内容:
- 为名为
Production-CustomerA
的租户创建新环境- 根据需要添加标签
- 输入提示变量
- 将最新版本部署到
Production-CustomerA
环境中 - 利润!
留下评论
你怎么想呢?这是您畅所欲言并帮助我们为您的环境构建合适功能的机会。
RFC:删除快照- Octopus 部署
在任何部署流程中,以下两者之间都存在紧张关系:
- 对部署流程/配置进行更改的需求;而且,
- 希望候选版本的部署在不同的环境中保持一致
例如,假设作为部署过程的一部分,您需要为 IIS 网站配置一个特殊设置。您已经编写了一些 PowerShell 来做到这一点。您将发布(让我们称之为“发布 1.1”)部署到一个测试环境中,一旦合适的人签字同意,这个发布就可以投入生产了。
在等待的时候,您决定对 PowerShell 脚本进行一些调整;也许你找到了一种新的方法,用更少的代码来改变 IIS。您发布了一个新的版本(“版本 1.2”),并将其部署到测试中。它看起来工作正常,但是你不能 100%确定——输出并不完全是你所期望的。
突然,您获得了将“版本 1.1”部署到生产环境的许可。您希望它运行您刚刚开发的实验性 PowerShell,还是运行在部署 1.1 版进行测试时运行的经过反复测试的旧 PowerShell?
快照
2012 年,Octopus 推出了快照作为这个问题的解决方案。创建“1.1 版”时,我们会拍摄以下内容的快照:
- 组成部署过程的部署步骤,以及
- 构成项目配置的变量
这样,您可以随心所欲地进行更改,并且确信旧版本不会引入任何意外的更改;测试中发生的事情就是生产中发生的事情。
然而,当我们第一次介绍快照时,它并不是我所希望的银弹。他们在不同的场景下崩溃了:
- 假设您以前从未部署到生产环境中(一个处于起步阶段的项目);现在,在投入生产之前,你意识到需要一些额外的步骤或变量。如果我们只依赖快照,就没有办法添加这些内容。
- 您的 PayPal 密钥发生变化,或者数据库服务器被重新定位。旧版本仍然使用旧的 API 密钥和旧的数据库服务器;那毫无意义。
为了解决这些问题,我们添加了一个按钮,将项目的最新变量导入到一个版本中——有效地覆盖了旧的快照。然而,它受到变量的限制。如果您添加了一个新的部署步骤,您仍然需要删除旧的发布并创建一个新的。
然而,快照的最大限制是它们是线性的。没有办法“分支”快照——比方说,保留一个“1.x”版本的部署流程,和一个“2.x”版本。
频道
2015 年末,我们增加了渠道。通道最初是为了帮助我们支持分支,但是我们很快发现了它们的许多其他用途:
- 修补程序版本,其中跳过了一些步骤和环境
- 发展部署设计:您可以在每个通道的基础上完全添加或更改步骤
- 处理功能分支和新的发布流
- 暂时禁用步骤或变量(只需将它们分配给不使用的通道)
需要快照吗?
在讨论有关快照的计划以及如何在我们即将发布的版本中应用于多租户时,我们意识到 channels 实际上解决了许多快照最初旨在解决的问题,只是更好。
在上面的例子中,“经过尝试和测试的”PowerShell 步骤可能属于一个“主”通道,而“新的和改进的”PowerShell 步骤可能属于一个“实验”通道。1.1 版是在“主”频道上发布,而 1.2 版是在“实验”频道上发布。您可以不断迭代两者的新版本,并确定哪一个将在每个版本中运行。
在这方面,通道比快照更好,因为您可以细化它们的应用方式。在所有的变量中,您可能只关心其中一些变量的“快照”行为。当然,API 键和数据库服务器总是使用“最新”的变量。但是您可能希望保留一小部分(例如,1.x 代码和 2.x 代码的不同文件路径)。您可以在没有快照的情况下,将这些变量值限定到一个通道来实现此行为。
快照行为也很烦人。如果您通过部署到一个测试环境来试验一个新的脚本,您必须不断地创建新的版本来测试每一个变化。使用“实验”频道而不使用快照会容易得多。您甚至可以配置一个生命周期,这样您的“实验性”渠道版本就永远不会进入生产阶段——它实在是太强大了。
这指向一个结论:快照似乎过时了。我就是想不出快照能处理频道不能处理的任何场景。
移除快照
在 3.4 中,我们考虑完全删除快照。这样做的原因是:
- 快照可以做的任何事情,渠道都可以做得更好
- 对于一个产品来说,用两种方法来实现相同的目标是浪费
- 快照使得对部署过程的改变的实验变得困难
- 它们让新用户感到困惑;频道是一个用户只有在寻找的时候才会看到的特性,否则就不会出现
- 如果我们今天从头开始,我们只会做频道,而不会做快照
- 快照使多租户等功能变得难以推理(快照中是否包含租户变量)?
如果我们真的移除它们,我们需要考虑:
- 升级过程/兼容性看起来像什么(因为人们目前依赖于行为)
- 如果人们忘记测试通道上的更改,我们如何帮助他们进行“回滚”——改进的源代码控制集成似乎是一个更好的方法。
- 教育。快照至少很容易解释;我们需要找到一种简单的方法来解释如何使用通道来实现相同的场景。
你怎么想呢?你愿意看到快照的背面吗?您能想到快照支持而渠道不支持的场景吗?
RFC:只在根目录调用 PowerShell 脚本——Octopus Deploy
想象一个像这样的包:
在部署期间,Octopus Deploy 将在部署期间调用所有四个Deploy.ps1
脚本。对于首先调用哪个脚本,没有确定的顺序。对于一些人来说,这导致了一些问题——他们的包中可能有一个有不同含义的Deploy.ps1
脚本(它不是为 Octopus 准备的)。
我们正在考虑做出一些改变,但我不清楚最好的前进方式是什么。我需要你的帮助来决定!请记住,我们的目标之一是让东西开箱即用。答案是不是加一堆复选框让它可选【T2:-)
选项 1:不要改变它
这是一个简单的选择——调用我们找到的任何文件。我们可以根据深度(脚本有多“根”)排序,然后按字母顺序排序,使其更具确定性。
选项 2:只调用根脚本
更新:我们决定从 Octopus 2.4 开始这样做
如果根 Deploy.ps1 脚本存在,我们将调用它,否则不调用任何东西。这意味着你必须将你的脚本放在包的根目录下(而不是子文件夹中),以便 Octopus 调用它们。
选项 3:调用最根的脚本
调用离根最近的脚本。如果根目录下没有脚本,那么我们将遍历子目录并调用找到的第一个脚本。
包装长这样怎么办?
在这种情况下,我们应该 a)两个都调用,b)都不调用,还是 c)调用我们找到的第一个?
征求意见- ECS 与 Octopus 集成里程碑二- Octopus 部署
原文:https://octopus.com/blog/rfc-second-ecs-integration-with-octopus
Octopus 中的第一个亚马逊弹性容器服务(ECS)集成里程碑正在开发中,它将提供一个新的步骤和目标,使您可以轻松地通过 Octopus 部署第一个 ECS 服务。
第一个里程碑将通过 CloudFormation 为您创建和管理 ECS 服务和任务定义。这使得开发人员和管理员不必为自己编写冗长的模板。
然而,我们早期从部署到 ECS 的团队那里收到的一个一致的反馈是,他们已经成功地管理了现有的 ECS 资源,通过手动创建的服务或通过 Terraform 等工具。挑战不是创建服务,而是用新的映像作为 CI/CD 管道的一部分来更新它们。
对于 ECS 集成的第二个里程碑,我们提议采取一个新的步骤来更新现有的 ECS 服务,而不获取它们的所有权。这为已经建立了 ECS 集群的团队提供了一个机会,在保留对资源创建方式的控制的同时,协调新映像版本到其服务的部署。
我们建议如何支持已建立的 ECS 集群
这个征求意见稿(RFC)提出了一个新的步骤,与第一个里程碑引入的 ECS 目标相集成。该步骤使用新的图像标签创建新的任务定义修订版,并使用任务定义修订版更新服务。
这个里程碑还通过展示链接现有负载平衡器的能力,增强了里程碑 1 中交付的步骤。
新的步骤
新的步骤支持人们向现有的任务定义和服务部署新的映像。
步骤:
- 定义任务定义及其关联服务的名称。
- 定义要在相关任务定义中更新的容器数量:
现有任务定义和服务的 ECS 部署将执行以下流程:
- 基于最新修订创建新的任务定义修订。
- 任务定义修订中的图像版本与步骤中的匹配容器定义一起更新。
- 然后使用新的任务定义修订版更新服务。
链接到负载平衡器
部署到 ECS 的大多数服务都暴露于网络流量,这意味着它们接收来自负载平衡器的流量。
里程碑二更新了里程碑一中引入的将服务链接到现有负载平衡器的步骤:
拟议方法的好处
这一新步骤将允许已建立 ECS 群集的客户通过 Octopus 协调映像部署,同时仍然保留对任何现有基础架构脚本的控制。
Octopus 在发布创建时选择图像版本,以及所有与频道和版本规则相关的功能。Octopus 随后会更新将新映像部署到 ECS 所需的最低设置,而不会尝试拥有任务定义或服务。
这将您的软件发布管理从您的基础设施发布管理中分离出来。
第二个 ECS 里程碑的范围是什么?
第二个里程碑主要是用已建立的 ECS 集群支持团队。它还包括对现有步骤的一些小的更新。
来自里程碑一的限制仍然适用于里程碑二:
- 将最初的步骤限制为只部署到 Fargate(但是,在这个里程碑中提出的新步骤将更新任何现有的服务和任务定义,无论是 EC2 还是 Fargate)。
- 仅支持滚动部署,不支持集成 CodeDeploy 蓝/绿部署。
- 不提供构建新负载平衡器的能力(仅选择现有的一个)。
- 排除自动缩放设置。
- 排除应用网格和 FireLens 设置。
- 排除服务自动发现设置。
- 仅创建或更新服务,不支持任务或计划任务。
这个什么时候发布?
里程碑二的工作计划在里程碑一完成后开始。我们还没有发布日期,所以请关注博客以获得进一步的公告。
我们需要您的反馈
我们仍在计划第二个里程碑,所以现在是利用您的反馈来帮助塑造这一新功能的大好时机。我们制作了一期 GitHub 来捕捉讨论。
具体来说,我们想知道:
- 更新现有任务定义和服务的能力是否支持您现有的 ECS 群集?
- 您希望 Octopus 能为您解决哪些进一步的 ECS 部署挑战?
- 您能否预见到可能会阻止您对现有 ECS 群集使用建议步骤的任何挑战?
这些反馈将有助于我们提供最佳解决方案。
结论
总之,我们提议的 ECS 支持的第二个里程碑包括:
- 将映像部署到现有任务定义和服务的新步骤。
- 在里程碑一中介绍的步骤中链接现有负载平衡器的能力。
感谢你阅读这篇文章。我们希望您和我们一样对建议的新 ECS 功能感到兴奋。
非常感谢您的任何反馈。
愉快的部署!
RFC:带有 Octopus 声明的 Octopus 配置的版本控制- Octopus Deploy
在我们的 UserVoice 中,第六高的投票项目是以某种方式在源代码控制中存储 Octopus 数据。这是我们经常思考的问题,我已经记不清这些年来我们在白板上画了多少次解决方案。
我们在 2017 年的路线图中说过我们会做一些事情,但这可能是一项重大的任务。每次我们想出如何做到这一点时,我们的方法都分为几类:
- 仅在 Git 中存储 Octopus 建模数据
- 将 Octopus 建模数据存储在数据库中,但将其同步到 Git repo——团队城市方法
- 存储单独的脚本等。让 Octopus 引用它们
我认为我们已经提出了解决这个问题的另一种方法,这种方法更简单,可以满足大多数用例,并且比默认的“将 Octopus 设置同步到版本控制”更加灵活和强大。我现在称之为“ Octopus Declarative ”,在这篇文章中,我想说明为什么这是一种更好的方法,并获得您对这是否是正确方向的反馈。
我们要解决什么?
Octopus 存储了许多不同的数据,并不是所有的数据都存储在 Git 中有意义。人们要求的主要东西是:
- 步骤模板和 PowerShell 脚本
- 部署流程
- 可变集合
您还可以证明其中一些也值得存储在 Git 中:
- 生命周期和渠道
- 环境
- 房客
在更高的层面上,人们似乎最感兴趣的用例是:
- 能够浏览历史、轻松比较和回滚更改
- 能够在 Octopus 服务器之间移动配置
- 能够创建一些东西的多个副本(例如,基于模板的项目)
为什么 Git“同步”方法如此困难
假设我们采用了将 Octopus 中的设置与 Git 存储库同步的方法。要让它真正有用,它需要双向工作(这样人们可以在 Git 中进行更改,并让它们出现在 Octopus 中)。
冲突解决
第一个问题是同步过程和冲突的处理。每次你在 Octopus 中改变一些东西,我们都需要把改变提交给 Git,并把它推送到你已经配置好的任何远程设备(比如 GitHub,VSTS)。很有可能在其中一个遥控器上有冲突性的改变,所以 Octopus 需要某种形式的冲突解决方案。
如果您改变一个部署过程,然后创建一个发布,然后部署它,并且在这个过程的某个地方我们检测到一个冲突,会发生什么?我们可以回滚,但我们已经基于该更改进行了部署。
范围
第二个问题是范围。我们有一个 Git repo 用于整个 Octopus 服务器吗?还是每个项目都有一个?
鉴于 Octopus 用于生产部署,Git 对谁可以编辑给定路径中的文件没有太多控制,我认为 Octopus 服务器范围的 Git 回购是不可能的。但是如果我们要对每个项目都有一个回购(对库也有一个),当我们需要对多个项目进行变更时会发生什么呢?如果其中一个回购协议有冲突,而其他回购协议没有冲突,会发生什么?
当我思考这些问题时,我意识到所有这些问题都有解决方案。但我的蜘蛛感官告诉我,要让它做好生产准备还需要相当大的努力。我可以很容易地想象我们花 4-6 个月的时间让这样的东西工作。
备选方案:基础设施作为代码方法
当我考虑版本控制和 DevOps 工具时,我突然意识到还有其他方法可以解决这个问题。
以亚马逊网络服务为例。AWS 是代码为的基础设施的最佳范例。见鬼,AWS 团队没有在 AWS 控制台用户界面上投入任何精力的原因是因为他们假设每个人都在使用命令行或 REST API 来管理他们的 AWS 基础设施(好吧,这是我瞎编的)。
没有人会声称“我们不应该使用 AWS,因为它不受版本控制”。然而,我在 AWS 中找不到任何页面告诉它将你的 AWS 账户同步到 Git 。相反,AWS 公开了 API,您可以针对它们编写代码——当然,您可以对代码进行版本控制。
当你仔细想想,它要优越得多:
- 范围问题消失了。由您决定哪个存储库应该包含设置 AWS 帐户各个部分的代码。
- 因为您是针对 API 执行代码,而不仅仅是将 JSON 文件推入 Git,所以您可以使用 for 循环、查询外部服务等。
诚然,某些 AWS 服务(如 CloudFormation)确实使用声明性 JSON 方法来配置它们,但同样,您不会将它们提交给存储库并期望 AWS 双向同步它们——您自己对其进行版本控制,然后调用它们的 API 来上传新的配置。
您已经可以在 Octopus 中做到这一点
Octopus 已经有了一个全面的 REST API ,你可以用它来做任何你可以在 Octopus UI 中做的事情。而且我们有一个. NET 客户端库, Octopus。客户端,你可以从 C#轻松使用 API。
例如,如果您想要对步骤模板进行版本控制,现在您可以:
- 创建使用 Octopus 的控制台应用程序。客户
- 通过在客户端调用适当的方法来定义每个步骤模板
- 从. PS1 文件加载每个步骤模板的脚本体
- 将所有这些存储在版本控制中
每次您更改. PS1 文件或 step 模板参数时,您只需重新运行您构建的这个应用程序,将新配置推送到 Octopus 中。你甚至可以建立一个 TFS 或 TeamCity build 来编译、测试并在每次修改脚本时运行它。
对于部署过程或变量集也可以这样做(敏感变量除外——我们会找到解决方案)。
问题解决了!有点儿...
今天的不足之处
虽然你可以这样做,但现在我不会向所有人推荐。首先,检查每个资源是否存在的代码,如果不存在就创建它,如果存在就更新它,等等..会变得非常乏味。
解决方法:章鱼。客户端.声明性
我提议的解决方案是在现有的“命令式”章鱼之上创建一个层。客户端名为 Octopus.Client.Declarative。您可以创建一个 C#控制台应用程序,如下所示:
【T2
每个类都声明性地定义了您在 Octopus 服务器中的期望:
public class HelloWorldScriptTemplate : ScriptStepTemplate
{
public HelloWorldScriptTemplate()
{
Name = "Hello world";
Body = FromFile("HelloWorld.ps1");
}
}
Program.cs
文件将调用 Octopus 中的一些方法。客户端通过调用我们的 REST API 来应用配置,检查资源是否存在,如果不存在就创建它们,并确保它们符合预期。
我们还会构建一些定义其他资源类型的好方法。将会有一个很好的 C#对象模型来构建部署过程或变量集。或者从 CSV 或 XLSX 文件导入变量。
作为代码,你可以循环遍历所有的东西。例如,也许您希望可以为您的每个客户克隆一个“模板”项目的副本:
public class MyProjectTemplate
{
public MyProjectTemplate(string customerName, string databaseName)
{
Name = "MyProject - " + customerName;
Steps = ...
Variables = FromSpreadsheet("Variables.xlsx");
Variables["DatabaseName"] = databaseName;
}
}
在您的Program.cs
中,您可以遍历您的客户数据库,为每个客户创建新的模板,然后将配置推送到 Octopus。
这类似于 Kotlin DSL 为 TeamCity 所采用的方法,除了您在如何和何时运行它方面有更多的选择。
为了让这个功能真正发光,让人们使用它,我认为我们需要一些额外的功能。
漂移检测
除了应用声明性配置之外,您还可以检测 Octopus 服务器中是否有任何变化,这意味着它已经“偏离”了配置。例如,您可以按计划运行它,以检测是否有人修改了他们不应该修改的内容。
锁
如果你在一个外部数据集上循环,并使用它来动态地创建 Octopus 项目,如果用户对这些项目做了修改,他们会很生气,而你在下次项目运行时就把它们删除了。我想我们会提供一个简单的机制,让您“锁定”某些资源类型,使其不能在 UI 中被编辑,并提供一个很好的消息。
出口
将我们的 UI 映射到底层的资源并不总是容易的,所以我们需要某种方法来获取一个对象并将其导出到等价的声明性 C#代码。这也可能是章鱼的一部分。客户端,或者我们构建到 Octopus UI 中使其可被发现的东西。
副作用
如果我们采用这种方法,除了最初的 UserVoice 建议能够对您的 Octopus 配置进行版本控制之外,我们还有一个解决方案可以帮助解决其他一些问题:
- 复合步骤模板 - 609 票很多时候人们只是希望多个项目重用部署步骤。跨项目重用 C#代码中的同一个对象是可能的。唯一的问题是,对于这些项目来说,它们是否可以在 UI 中不可编辑。
- 改进变量 UI - 834 票这里大部分的评论都集中在处理很多变量上,这些变量很可能在别处有定义。虽然我们仍然应该努力改进 UI(我们会的!),能够用程序创建它们或从电子表格中导入将会对许多人有所帮助。
- 可继承模板 - 171 票我上面的例子已经展示了这是如何工作的。事实上,“模板”项目可以在 Octopus 中保持可编辑状态,代码会将它拉下来,并为它的每个实例克隆它。
你怎么想呢?
作为一名产品负责人,我不害怕耗时 6-9 个月或更长时间的“停止世界重写”。我们在 Octopus 1.6 到 2.0 中做到了这一点,当时我们重写了编排层并采用了 API 优先。在 3.0 中,当我们从 RavenDB 切换到 SQL Server 时,我们又做了一次。当我确信最终结果会是一个更好的产品时,我很乐意沿着这条路走下去。
如果我们采用将 Octopus 配置作为 XML 或 JSON 文件存储在 Git 存储库中的方法,我认为要解决我之前提到的挑战需要付出巨大的努力。最后,如果您想采用“for 循环”方法来基于外部数据源动态生成您的配置,您会想要禁用 UI 编辑,并使用一种好的、强类型的语言来完成它。我认为我们可以用更少的努力来建造它。
我们正处于一年中的某个时刻,我们需要以某种方式做出决定。如果我们采用 Git sync 方法,用户体验可能会影响我们正在构建的大部分新功能,所以我们可能会首先着手于此。另一方面,如果我们采用我在这里概述的声明性 Octopus 客户端方法,我们可以在接下来的几个月中轻松发布和改进它,而不会减慢其他任何东西的速度。
我的问题是:这种声明式 C#方法对你有用吗?或者你还需要版本控制 Octopus 配置中的其他东西吗?
请求反馈-使用 Octopus - Octopus Deploy 部署到 Google 云平台
原文:https://octopus.com/blog/rff-deploying-to-google-cloud-platform-with-octopus
Octopus 2021.2 带来了许多功能来支持团队部署到谷歌云平台(GCP)。在 2021.2 中,Octopus 拥有对 AWS、Azure 和 Google 云平台的核心支持。
这篇文章介绍了 Octopus 支持 GCP 部署的新特性,并提供了如何在您自己的部署过程中使用它们的技巧。
在文章的最后,你可以提供你的反馈,告诉我们这些新功能对你来说是有效的还是无效的,并对 GCP 未来的功能提出建议。
服务客户支持
Octopus 包括一个名为谷歌云账户的新账户类型。该帐户安全地存储为服务帐户生成的 JSON 密钥:
继承 VM 服务帐户
对于喜欢在 Octopus 之外管理凭证的团队,每次与 GCP 的集成都允许从工作人员那里继承一个服务帐户。
这是一个带有相关服务帐户的 Google 计算引擎(GCE)虚拟机:
该虚拟机上安装了一个工作线程,并链接到 GCP 工作线程池:
然后,我们可以使用与该虚拟机相关联的服务帐户。下面是一个 Kubernetes 目标的例子,它被配置为继承运行它的工作者的凭证:
请注意,目标必须配置有包含 GCE 工作线程的工作线程池:
运行状况检查和部署等操作使用分配给工作虚拟机的凭据执行,无需将这些详细信息存储在 Octopus 中:
Google 容器注册支持
Google Container Registry (GCR)支持已经包含在现有的 Docker 提要类型中。将提要 URL 定义为区域 GCR URL之一,并提供一个用于认证的服务帐户 JSON 密钥。
然后,可以从 GCR 源获得图像:
支持 gcloud 脚本
名为在脚本中运行 g cloud的新步骤可用于在 GCP 帐户的上下文中运行脚本。
作为这一步骤的一部分运行的任何脚本都可以利用 Octopus 管理的登录过程。这使得脚本可以专注于它需要执行的操作,而不是登录的样板过程:
【T2
地形支撑
Terraform 步骤包括使用选定的 Google 凭据建立上下文的能力,将这种担心从 Terraform 模板转移到步骤中。
部署 Terraform 需要持久化状态的能力。对谷歌用户来说,一个方便的解决方案是将 Terraform 状态保存在谷歌云存储(GCS)桶中:
terraform {
backend "gcs" {
bucket = "octopus-tf-state" # change this to match the name of your GCS bucket
prefix = "terraform/state"
}
}
结论
Octopus 2021.2 支持 GCP 服务帐户、GCR feeds、GKE 身份验证选项、专用的 GCP 脚本步骤以及 Terraform 中的谷歌身份验证支持,可以轻松部署和管理您的 GCP 基础设施。
我们需要您的反馈
我们希望听到您的反馈!我们有一个 GitHub 问题,你可以发表评论关于这些新功能对你来说如何工作,或者不工作,以及对未来 GCP 功能的任何建议。欢迎所有反馈,我们很想知道:
- 您目前在 GCP 执行哪些部署或运营任务?
- 部署或管理 GCP 时的难点是什么?
- 你使用谷歌 Kubernetes 引擎、谷歌应用引擎、谷歌云功能、谷歌云运行或其他平台吗?
- 新功能对你有用吗?如果没有,你有什么建议可以改进它们?
愉快的部署!
八达通部署 2017 路线图-八达通部署
从 Octopus 的早期开始,我就一直坚信应该公开我们的路线图。当 Octopus 只有我一个人的时候,我经常在公共的 Trello 板上放上我计划要做的所有事情。当我们超过了一个单独的 Trello 板时,我们开始保留一个 GitHub 问题列表和一个公共的路线图页面,上面有我们的高级计划。
随着 2016 年接近尾声,我们开始规划 2017 年,我们花了很多时间思考我们今年想要完成的目标,我已经将这些纳入了 2017 年的路线图。我们今年有一些雄心勃勃的目标,我对清单上的内容感到非常兴奋。尽情享受吧!
摘要
到 2017 年底:
- Octopus 将实施所有超过 200 票的 UserVoice 项目
- 章鱼的学习曲线会更低
- Octopus 将拥有一流的 PowerShell DSC 支持,并用于管理正在运行的应用程序
- Octopus 将有一个更好的 UI 来配置部署步骤,并支持 IIS 中的每个选项
- 八达通用户应该能够通过一个聊天应用程序与八达通互动
- Octopus 将使用 Swagger 公开其 API,使最终用户更容易使用我们的 API
- Octopus 将对亚马逊网络服务有更好的内置支持
- Octopus 将为不想安装 Mono 的客户提供 Linux 部署
- Octopus 将通过发布促销使 PCI 兼容部署得到开箱即用的支持
- Octopus 将作为生产就绪的平台即服务解决方案提供
总体战略
对于 Octopus 来说,真正的长期成功是由活跃的成功安装数来衡量的。该路线图中的所有内容都旨在为此做出贡献——通过以下方式:
- 让现有客户高兴他们选择了八达通,并给他们续延的理由
- 扩展 Octopus 可以支持的场景范围
- 使其更易于使用
高级路线图
这些是我们今年路线图上的头条项目。对于其中的大部分,我们将在开始设计和工作时发布我们的传统 RFC 帖子,所以我在下面只包括它们的简短摘要。如果你有任何问题或想法,欢迎在评论中提出!
超过 200 票的所有用户声音项目
目前在我们的用户之声网站上有超过 914 个想法,我们不太可能找到它们的长尾,我不认为我们的任何客户会期望我们建立所有的想法。但是很多建议都是几百票,到现在已经开了几年了,这是不能接受的。所以今年我们发誓要对超过 200 票的所有建议采取行动。
以下是今天的名单:
章鱼行动
对于许多较小的团队来说,Octopus 是他们拥有的最接近通用“DevOps”工具的东西。像所有人一样,他们用它来部署 ASP.NET 应用程序、数据库升级和 Windows 服务。但是他们也使用它来跨机器运行特定的脚本,或者作为配置管理和机器供应工具。或者他们利用我们的健康检查作为监控工具。
虽然有许多优秀的工具专门从事配置管理或监控,我们不打算出去与它们竞争,但我们确实看到,对于较小的环境,Octopus 可以很好地完成这些工作。今年,我们想以两种不同的方式对此进行扩展。
首先,我们要确保 Octopus 拥有一流的 PowerShell DSC 体验。我已经写了关于你如何在 Octopus 中使用 PowerShell DSC 支持。我们将通过支持重启来完善这一点,并做一些 UI 工作来使其更加精简。
其次,我们将最终实现在我们的监控&服务管理 RFC 中讨论的操作特性。正如我在 RFC 中所写:
Octopus 仪表板显示您的最后一次生产部署是否成功。如果它还向您显示您部署的内容是否仍在运行,那会怎么样?...不过,它不仅仅局限于监视状态:您还可以启动/停止这些服务。您昨天部署到 30 台机器上的 Windows 服务在其中 7 台机器上突然崩溃了吗?没问题,只需点击按钮,选择你想重启的 7,点击执行按钮。肯定胜过使用远程桌面!另外,会有很好的审计记录。
这两个项目的构建块已经存在:可脚本化的健康检查、PowerShell 步骤和步骤模板运行程序已经提供了大部分实现工作。我们只需要把它们粘在一起,告诉人们如何使用它。
章鱼 Slack 应用
我们刚刚在 Octopus 中发布了“订阅”支持,这让我们可以在 Octopus 中发生事件时通知 web 服务。这一点,加上我们的 API,是我们构建梦想中的“ChatOps”体验所需的所有构件。这将采用 Slack 应用程序的形式,客户可以将其连接到他们的 Octopus 服务器。您可以将 Slack 应用程序与您的 Octopus 服务器相关联,并执行以下操作:
- 询问 it 在生产中部署了什么
- 告诉 it 部门将测试版本升级到生产版本
- 被通知部署状态
新的步骤生成器和更多的 IIS 选项
迄今为止,在 Octopus 中配置部署步骤的 UI 必须平衡:
- 允许您配置部署所需的一切
- 没有让你不知所措
对于配置 Windows 服务之类的东西的部署步骤,无论如何只有少数几个选项。但是有些步骤,比如配置 IIS 的步骤,可能有数百种不同的设置需要设置。目前,这意味着我们只公开最常见的设置,您必须编写 PowerShell 来完成其余的工作。
我们花了一些时间来考虑替代的用户界面,这将允许我们逐渐暴露你关心的设置,隐藏你不关心的设置,我认为我们已经找到了如何做到这一点。当你想在 Octopus 中配置每一个 IIS 设置时,我们就会知道我们已经做到了。
降低学习曲线
Octopus 是那种“设置好就忘了”的工具;您下载它,设置它,并花一点时间设置您的部署过程。您迭代几次,直到流程和变量正是您所需要的。当所有这些都完成后,您就可以进入创建和部署版本的良好节奏了——如果您的部署需要改变,您只需要修改过程。
最初的部署过程配置对大多数人来说是困难的部分。今年,我们将做大量的工作,努力使设置更简单、更流畅。
单一自由 Linux (SSH)部署
Octopus 目前支持使用 Bash 通过 SSH 部署到机器上,允许它用于部署到 Linux 服务器(以及其他)。然而有一个缺点:我们目前需要安装 Mono,因为我们调用这些脚本的方式。对于一些人来说,这种依赖太大,太难安装,或者他们不喜欢。
如果你只是运行一个简单的脚本,我们会让它不需要单声道就能运行。访问 Octopus 变量的方式可能会改变,但除此之外,一切都会正常工作。对于带有内置约定的完整包部署,我们仍然会使用我们的 Calamari 工具,但是我们会将它们移植到。NET Core(并且可能能够为通用架构编译它们),因此依赖性会更小。
八达通发行促销
许多客户工作的环境中,版本必须在一个以上的 Octopus 服务器之间流动,最常见的两种情况是:
- PCI 兼容环境,其中部署到生产环境的 Octopus 必须隔离
- 使用一台 Octopus 进行开发/测试,但随后需要在每个客户站点安装一台 Octopus 服务器来进行生产部署的机构
我们过去曾多次尝试解决这些问题:
- Octo.exe 出口-这是有限的,但对许多人有用
- octopus Migrator——它几乎输出了所有内容,但太吵了——人们主要关心的是推广个人版本
- 离线丢弃——这些方法在下游环境很小时有效,但是当机器数量增加时,你很快就会自己复制章鱼
我们将尝试一劳永逸地解决这个问题,让您在一个 Octopus 服务器中声明“下游”和“上游”环境,这些环境实际上是在另一个 Octopus 服务器中管理的,并促进它们之间的发布。
当您将一个版本升级到下游的 Octopus 服务器时,我们将导出该版本、包、变量和流程快照,以及下游 Octopus 服务器需要知道的所有其他内容。如果 Octopus 服务器联网,您将能够自动在它们之间传输该导出,或者将它们放在 USB 驱动器上用于 air gap 部署。当你在下游的 Octopus 中部署它们时,你会得到一个“收据”,它可以被导入到上游的 Octopus 中。
我希望通过构建这一功能,我们能够消除之前解决这一场景的所有三种尝试,并节省大量客户手动解决这一问题的时间。
“炫耀”我们的 API
我们在 Octopus 2 中冒了很大的风险,为 Octopus 中的所有东西构建了一个 REST API,然后在它的基础上重写了我们的整个 UI。这种 API 优先的思想很好地服务了我们,并且变得非常受欢迎——人们使用我们的 API 来做各种我从未想象过的事情。
我们将通过支付一些技术债务(我们的 API 对我们来说比它应该的更难维护)和增加对 Swagger 的支持,使 API 更容易被发现,这将为您提供一个出色的 UI 来探索我们的 API。这应该使构建我们的 API 更加容易。
更紧密的 AWS 集成
对于 Azure 用户,Octopus 已经内置了部署步骤和帐户支持。我们将继续添加更多的步骤(Azure Service Fabric 是最明显的),但我们也将使对 AWS 的支持达到标准。我们将增加对 AWS 账户的支持,以及使用常见 AWS 服务的步骤,如 CloudFormation、EC2、S3 和 Elastic Beanstalk。
PaaS 章鱼
当我开始使用 Octopus 的时候,云还没有获得很大的吸引力,我们的客户几乎完全是本地的。我们注意到越来越多的客户将他们的代码和 CI 环境迁移到云上,无论是通过 GitHub/AppVeyor 还是 VSTS,我们认为这种趋势只会朝着一个方向发展。我们最近将 Octopus 添加到 Azure marketplace 中,这样你就可以在自己的订阅中安装 Octopus 服务器,但我们认为我们需要更进一步。
对于作为创始人/首席执行官的我来说,这是这个路线图上最困难的一项,因为它不仅需要技术变革,还需要组织变革——从成为一个封装软件供应商到学习如何发展和支持 PaaS 产品。
关闭
我们在今年的路线图上花了很多心思,而且有很多项目。我敢肯定会有一些失望-每个人都有他们希望章鱼能做的事情,包括我自己-但我也希望这里对每个人都有价值。2017 年万事如意!
八达通部署 2018 路线图-八达通部署
昨天我贴出了我们对 2017 年的反思。我写了一些我们完成的事情,一些我们没有完成的事情和原因,以及我们面临的一些挑战。今天,我想分享我们的 2018 年路线图。
主题
我们将在 2018 年开展的工作有四个关键主题。
- 将云托管版本的 Octopus 推向市场
- 在微软生态系统之外扩展 Octopus
- 将 Octopus 发展成一个完整的 DevOps 工具
- 不断提高性能、可扩展性、稳定性和用户体验
云章鱼
当我们第一次构建 Octopus 时,世界上大部分地区仍在内部部署应用程序。Octopus 是您在企业中与数百个其他虚拟机一起运行的虚拟机。当我们与较大的客户交谈时,这实际上仍然是常态——不管云供应商希望您相信什么,仍然有大量的本地服务器。
也就是说,如果你部署的每一个应用都是 Lambda 函数、Azure 网站或 Docker 容器,那么仅仅为了 Octopus 而必须照看一个 VM 就是一种拖累。我们的朋友杰里米·凯德曾经说过:
Octopus 是我用的最重要的东西,也是我仅剩的 VM。
一个云托管版本的 Octopus 需要做大量的工作,这是我们致力于很快交付的东西。与该路线图中的许多努力不同,这些努力往往是具有“完成”定义理念的项目,这个云托管版本的 Octopus 将是一项长期投资,也是我们组织上的一个重大变化。
我们已经为此工作了几个月,我们将在 2018 年 1 月推出一个封闭的 alpha 版本,然后在 3 月底之前推出一个开放的 beta 版本。最初,我们将尽可能保持它与 Octopus 的本地版本相似,随着我们从生产中运行它中学到更多,我们将针对云对它进行改进和优化。
扩展到微软生态系统之外
在过去的几年里,我们有微软商店和 Java 商店,大多数公司的立场非常明确。Octopus 最初只是一个. NET 部署工具,但是几年前我们就意识到这需要改变。人们和公司更少认同“一个”。NET 开发人员"和更多的作为"开发人员,谁碰巧做。NET 以及一些节点和一些其他平台。
我们已经支持通过 SSH(没有 Mono 依赖)部署到非 Windows 平台,运行 Bash 脚本,2017 年我们在部署 Java 应用程序方面取得了很大进展。迄今为止,我们的成功主要是与偶尔使用非微软技术的微软商店。
今年我们将继续这个主题:
- 我们将带来一流的 AWS 支持,达到 Azure 支持的水平
- 我们将把触手引入 Linux(以允许轮询连接)
- 我们将增加对运行 Python 和 Ruby 脚本的支持(除了 PowerShell、C#、Bash 等。今天)
- 我们将扩展现有的 Docker 支持来与 Kubernetes 合作
Octopus 部署应用程序,但它可以用来部署更多应用程序。当你想一想:
- Octopus 对你的应用程序了如指掌——它们是用什么语言编写的,在什么网络服务器上运行
- 您的部署管道(开发、测试、生产等。)
- 在所有这些环境中,您的应用程序是如何配置的
- 一切赖以运行的基础设施
有了这一切,我们可以做的不仅仅是“仅仅”部署:
- 借助 AWS 云形成、Azure 资源组模板或 Terraform,我们可以为您创建的每个功能分支提供和部署到环境中
- 我们可以为测试人员提供测试环境,并在下午 5 点后取消提供
- 我们可以运行部署之外的流程。任何类型的“维护”或“操作”过程或操作手册都可以在 Octopus 中建模:
- 灾难恢复故障转移流程
- 运行状况检查和合规性检查流程
- 将生产数据库备份恢复到您的测试环境
- 针对最近部署的环境运行自动化 UI 测试
- 按计划运行这些程序,使用参数手动运行,或者在监控工具发出警报时作为挂钩运行
我们已经花了很多时间来设计这些想法和故事,我们相信我们可以在不损害章鱼“做一件事并把它做好”的哲学的情况下实现这一点。
可扩展性和持续改进
Octopus 正在成为越来越多公司的标准部署工具,我们希望确保这是一种无缝的体验。今年,我们将为那些经常使用八达通的人做出重大改进:
- 一个新的“空间”功能可以让你把你的 Octopus 服务器按团队或部门分开,给每个人独立的项目、环境、授权等等。
- “工人”将允许今天标记为“在 Octopus 上运行”的脚本在其他地方运行(就像构建代理)。这将使你的八达通服务器更容易隔离副作用。
- 我们将对 Octopus 的可扩展性和性能进行重大改进。这对于拥有云托管 Octopus 的我们来说是必要的,但也将使 Octopus 客户受益匪浅。管理云托管的 Octopus 将让我们更深入地了解大规模运行 Octopus 是什么样子。
- 我们将继续寻求性能、质量和用户体验的改进。
- 我们将建立远程发布促销。
- 我们将继续处理用户意见建议,我们将确保对任何进入前 10 名的事情做些事情(或者,明确决定我们不做这些事情)。今天这些是:
包扎
“没有一个作战计划能在与敌人的第一次接触中幸存”
赫尔穆特·冯·毛奇
我们可能无法在 2018 年完成清单上的所有事情,随着时间的推移,我们可能会做一些清单上没有的事情。我相信每个阅读这篇文章的人都会理解这就是路线图的本质——这是我们试图勾勒出今年的计划和目标,而且很可能会改变。也就是说,我希望这个清单上有适合每个人的东西。愉快的部署!
了解更多信息
角色和升级-征求意见- Octopus 部署
在之前的一篇文章中,我写了当在 Octopus 中创建一个发布时,步骤和变量是如何成为“快照”的一部分的。我提到我正在做的一个改变是在 Octopus 中引入“角色”的概念。
它过去是如何工作的
概括地说,在 Octopus Deploy 的 1.0 版本中,环境有机器,项目中的步骤引用这些机器:
在上面的例子中,我正在为 NuGet 包创建一个步骤,并且我已经从生产环境中选择了两台机器。
这种设计有缺点:
- 如果您添加或删除一台机器,您将不得不更新每个项目,并创建新的版本
- 旧版本不会被部署到新机器上,或者如果它们引用了不存在的机器,则不允许部署
在云计算和横向扩展的世界里,这是一个很大的问题,所以我一直在努力解决这个问题。
它将如何工作
在 Octopus Deploy 1.1 中,您可以将机器标记为服务于一个或多个“角色”,您可以自己定义这些角色。
当您创建一个步骤或变量时,不需要专门选择机器,您只需选择一个目标角色:
这同样适用于变量——变量现在可以作用于“角色”,而不是机器。
升级
对于 Octopus Deploy 中已有数据的客户来说,这就产生了一个问题。以前,步骤引用机器。在新模型中,步骤引用角色,机器处于角色中。这两种数据模型是不兼容的,我正在尝试决定迁移体验将如何工作。
似乎有几个选择。一个是打破向后兼容性——旧的版本/项目可能变成只读的,在定义角色和更新步骤/变量之后,用户将不得不访问每个项目来创建新的版本。只有这样,您才能再次部署发布。显然这有很多缺点。
选项 2 是通过支持两种模型来无限期地保持向后兼容性。虽然这可能是一个更好的短期体验,但从长远来看,我认为这只会造成混乱,因为有两种方法可以做同样的事情。与旧模型相比,基于角色的模型没有真正的缺点,因此无限期地支持旧模型是没有意义的。
最后,选项 3 是通过自动给每个机器一个唯一的角色(例如,“机器-123”),然后转换引用该机器的旧步骤/变量以引用其唯一的角色来“适应”数据。通过这种方式,我们将一切都转移到基于角色的模型,旧版本可以继续部署,用户可以在升级后根据需要整理/重构角色。一开始可能会令人困惑,但 Octopus Deploy 用户都非常聪明,所以我认为他们可以处理好:)
摘要
处理模式版本中的这种语义差异是一个有趣的问题,目前我倾向于选择 3。
如果你现在正在使用 Octopus Deploy,你希望升级如何进行?请在下面的框中留下您的评论:)
Octopus Deploy 的回滚策略- Octopus Deploy
当讨论回滚时,话题不可避免地转向蓝/绿、红/黑或金丝雀部署模式。这些模式使回滚变得更容易,但是,它们的实现非常耗时,有时它们并不是必需的。也许您推出了一个要测试的 API 变更,并且您想要回到一个已知的良好状态。这不是你第一次尝试实现这些模式的时候。
在这篇文章中,我将介绍一个您现在可以执行的回滚策略,而无需实现高级部署模式。
超出范围
回滚数据库更改超出了本文的范围,因为成功回滚数据库更改是一个复杂的话题,有很多陷阱。这篇文章主要关注代码回滚。它演示了如何在回滚期间跳过数据库部署步骤。实际上,代码和 UI 的变化比数据库的变化要频繁得多,尤其是在测试环境中。大多数模式变化发生在新特性的开始,在测试过程中会有一些小的调整。
什么是回滚?
这篇文章将帮助您修改现有的(工作的和经过测试的)部署过程,以支持回滚。
首先,让我们看看回滚完成了什么。
考虑这些场景:
- QA 团队因为在最近的部署测试中引入的一个 bug 而受阻,而该修复离签入还有几个小时。
- 在生产部署验证过程中,发现了一个令人瞠目结舌的错误,需要花一天时间来修复和测试。
在这两种情况下,回滚的目标是相同的;将应用程序快速恢复到已知的良好状态。
许多客户关注生产场景,但是测试场景出现得更频繁,影响也更大。如果你遵循 Octopus Deploy 的核心规则,即构建一次,在任何地方部署,那么一个引人注目的 bug 进入生产的机会是很少的。然而,测试是不同的;有一种心态认为只有少数人受到影响,但这是不真实的,因为如果 QA 一次被封锁几个小时,最后期限就会溜走。
目标是回到已知的良好状态,但这不同于部署。跳过特定步骤可以加快回滚速度。许多部署流程都是在假定没有配置任何相关软件或基础架构的情况下创建的。例如,一个部署过程可以触发一个 runbook 来创建一个数据库(如果它还不存在的话);或者安装 Node.js 的最新版本。在回滚期间,不需要这些额外的步骤。如果您在部署应用程序的2021.2.3
时检查数据库是否存在,那么在回滚到2021.2.1
时就不需要再次检查了。
For this post:
回滚是通过运行原始部署过程的修改版本来返回到已知的良好状态。
向前滚动或向后滚动
不是所有的版本都可以也应该回滚。上述场景提到修复需要几个小时或几天的时间。通常,前滚风险更小,耗时更少。一个小的补丁比回滚一个主要版本更容易测试和部署。
以下是我们建议前滚的一些典型原因:
- 您不能选择在二进制文件中回滚哪些代码。要么全部回滚,要么什么都不回滚。一个团队在一个月或一个季度的发布时间表中有几十或几百个变更。这就是为什么我们也建议更定期地发布较小的变更集。
- 通常,数据库和代码更改是紧密结合在一起的。安全地回滚数据库而不丢失数据是极其困难的。
- 用户会注意到什么时候发生了变化,然后又变了回来,尤其是由同一批人整天使用的定制业务应用程序。
- 随着面向服务架构 (SOA)和它的表亲微服务的激增,代码变更很少是孤立进行的。“适当的”SOA 和微服务架构彼此之间以及与它们的客户端之间是松散耦合的。然而,在现实世界中,耦合是存在的。回滚到后端服务可能会产生下游影响。
不过,在几种情况下,回滚可能是正确的解决方案。具有大型数据库的遗留 monolith 应用程序可以在特定情况下成功回滚。这些场景包括:
- 仅更改样式或标记
- 后端代码更改,没有公共接口或模型更改
- 与外部服务或应用程序的耦合为零或最小
- 零到最小的数据库更改(新索引、更改存储过程以提高性能、调整视图以包括已连接表上的附加列)
- 自上一版本以来的少量变化
虽然我们建议进行前滚,但是在您的 CI/CD 渠道中实施回滚过程是一个很有价值的选择,即使回滚每月发生一次。
测试您的回滚过程
许多年前,在一次生产部署后的几个小时,我被告知有一个停止显示的 bug。我很惊讶,因为这个版本已经通过了 QA 几周的验证。我们无法确定原因,并得出结论需要回滚-多年来的第一次。
部署文档中的回滚计划是:“回滚到以前版本的代码”。不幸的是,这不是一个详细的计划。我们上报了这个问题,并通知了参与发布的所有人(从 QA 到企业所有者和经理)。
我们从头开始创建了一个新的回滚计划。尽管有新的计划,我们估计成功回滚的几率为 10%。这是一个没有胜算的局面。我们有一个无法重现(因此无法修复)的错误,或者我们可以回滚并抓住机会。
虽然回滚提供了一些机会,但也不是没有机会。每个人都被分配了一项任务。我逐项检查了变更日志,并记录了回滚的影响。
在我们需要做出最终回滚决定的 15 分钟前,我发现了一个看起来可疑的代码块。我测试了那个代码块,确定它是罪魁祸首。
我们中止了回滚计划,实现了一个修复,并在当天晚些时候推出了这个修复。我们不用测试未经证实的回滚过程,这让我们松了一口气。
这个故事强调了多次测试回滚过程的重要性。理想情况下,应该每周对其进行测试和验证。在生产中断期间,您最不想做的事情就是开发一个新的回滚流程或运行一个未经测试的流程。
示例部署流程
现在我解释如何更新现有的部署过程来支持回滚。
我选择 OctoFX 示例应用程序作为这个例子,因为它与我看到和使用的许多应用程序相似。它有以下组件:
- SQL Server 数据库
- Windows 服务
- ASP。NET MVC 网站
该应用程序的部署过程是:
- 当数据库不存在时,运行操作手册来创建数据库
- 部署数据库更改
- 部署 Windows 服务
- 部署网站
- 暂停部署并验证应用程序
- 通知风险承担者部署已完成
您的数据库平台、后端服务和前端可能使用不同的技术。在这篇文章中,我更新了这个过程,在回滚过程中跳过特定步骤并运行额外的步骤。
重新部署以前的版本
我的回滚过程的核心概念是重新部署以前的版本。您可以通过以下方式做到这一点:
选择您想要重新部署到目标环境的版本。在我的例子中,我将2021.9.9.3
重新部署到测试。
点击溢出菜单,选择重新部署...。
【T2
您将被发送到部署屏幕。点击部署开始重新部署。
部署模式
按原样重新部署以前的版本意味着以前部署的所有步骤都将重新运行。如前所述,回滚的目标是通过运行稍微修改的部署流程回到已知状态。
您的回滚过程将与示例不同;我使用数据库步骤作为例子。目标是向您展示如何禁用步骤,而不是禁用什么。
要禁用回滚的特定步骤,我们需要知道回滚正在发生。但是我们将重新部署现有的版本。不过,将同一个版本重新部署到当前环境是一个有效的用例。我们需要知道的是“部署模式”。
- 部署:第一次将一个版本部署到一个特定的环境中,为应用程序添加新的特性、修复错误等等。
- 回滚:在特定环境下重新部署以前的版本,返回到已知的良好状态。
- 重新部署:当一个新的服务器上线,或者您需要“启动”应用程序时,在一个特定的环境中重新部署同一个版本。
我们需要知道这一点,因为它改变了部署过程。部署过程中的部署流程运行所有步骤:
- 当数据库不存在时,运行操作手册来创建数据库
- 部署数据库更改
- 部署 Windows 服务
- 部署网站
- 暂停部署并验证应用程序
- 通知风险承担者部署已完成
回滚将跳过前两步。
当数据库不存在时,运行运行手册创建数据库部署数据库变更- 部署 Windows 服务
- 部署网站
- 暂停部署并验证应用程序
- 通知风险承担者部署已完成
对于我的应用程序,我只在 web 场向外扩展时进行重新部署。我从不横向扩展应用服务器或数据库。我只想部署网站并通知利益相关者。
当数据库不存在时,运行运行手册创建数据库部署数据库变更部署 Windows 服务。- 部署网站
暂停部署并验证应用- 通知风险承担者部署已完成
我们需要计算“部署模式”的能力。Octopus 提供了系统变量:
Octopus.Release.Number
:当前版本号(1.2.2
)。Octopus.Release.CurrentForEnvironment.Number
:上次成功发布的 ID (1.1.1
,部署到当前环境。
比较Octopus.Release.Number
和Octopus.Release.CurrentForEnvironment.Number
来计算部署模式:
- 如果更大,那就是部署
- 如果少了,就是回滚
- 如果它们是相同的,那就是重新部署
计算部署模式步骤模板
我创建了步骤模板, 计算部署模式 ,为您执行计算。使用该结果,它将设置几个输出变量。
- 部署模式:将是
Deploy
、Rollback
或Redeploy
- 触发:指示部署是由部署目标触发还是调度触发引起的——将是
True
或False
- 版本变更:将会是
Identical
、Major
、Minor
、Build
或Revision
在使用步骤模板时,我意识到大多数人会在变量运行条件中使用 DeploymentMode 输出变量。由于错误处理,运行条件的语法可能很难正确。Octopus 总是评估变量运行条件,以确定该步骤是否应该运行,即使前一步骤中出现错误。如果我们在运行条件中不包括错误处理,它可以评估为True
并运行该步骤。我们不想那样。
当部署模式为Rollback
并进行所有必要的错误处理时,变量运行条件为:
#{unless Octopus.Deployment.Error}#{if Octopus.Action[Calculate Deployment Mode].Output.DeploymentMode == "Rollback"}True#{else}False#{/if}#{/unless}
我添加了以下输出变量,并添加了必要的错误处理和比较逻辑,使之更容易。
RunOnDeploy
:仅当 DeploymentMode 为Deploy
时运行该步骤RunOnRollback
:仅当部署模式为Rollback
时运行该步骤RunOnRedeploy
:仅当 DeploymentMode 为Redeploy
时运行该步骤RunOnDeployOrRollback
:仅当 DeploymentMode 为Deploy
或Rollback
-RunOnDeployOrRedeploy
时运行该步骤:仅当 DeploymentMode 为Deploy
或Re-deploy
时运行该步骤RunOnRedeployOrRollback
:仅当 DeploymentMode 为Redeploy
或Rollback
时运行该步骤RunOnMajorVersionChange
:仅在版本变更为Major
时运行该步骤RunOnMinorVersionChange
:仅在版本变更为Minor
时运行该步骤RunOnMajorOrMinorVersionChange
:仅在版本变更为Major
或Minor
时运行该步骤RunOnBuildVersionChange
:仅在版本变更为Build
时运行该步骤RunOnRevisionVersionChange
:仅在版本变更为Revision
时运行该步骤
对于这些输出变量,相同的回滚运行条件的语法是:
#{Octopus.Action[Calculate Deployment Mode].Output.RunOnRollback}
阻止发布进展步骤模板
我提到了我想在回滚期间运行的一个附加步骤。一个例子是阻止发布进程。即使在测试环境中,回滚也是一个重大事件。如果一个版本有多个 bug,您不希望它转移到生命周期中的下一个环境。
Octopus 让你阻止释放进程,然而,那是一个手动步骤。我避免手工步骤,所以我制作了一个新的步骤模板,Block Release Progression,以防止发布进程成为部署过程的一部分。
具有回滚步骤的部署流程
使用计算部署模式、可变运行条件和块释放进度,更新后的部署流程为:
- 计算部署模式
- 当数据库不存在时,运行运行手册以创建数据库(仅当部署模式为部署时运行)
- 部署数据库更改(仅在部署模式为部署时运行)
- 部署 Windows 服务
- 部署网站
- 阻止发布进程(仅在部署模式为回滚时运行)
- 暂停部署并验证应用程序(仅在部署模式为部署或回滚时运行)
- 通知风险承担者部署已完成
使用步骤注释功能来指示在部署、回滚或始终运行哪个步骤。
设置运行条件
标记为“仅在部署模式为回滚时运行”或“仅在部署模式为部署或回滚时运行”的步骤需要将运行条件更新为变量。该变量将是 计算部署模式 步骤的输出变量之一。
测试回滚
对于我的测试,我有两个版本:
2021.9.9.5
:目前在开发环境中。2021.9.9.6
:这是我想部署到开发的新版本。
将2021.9.9.6
部署到开发正如预期的那样。步骤 6 被跳过,因为它被设置为仅在部署模式为回滚时运行。
在我的测试场景中,在部署后发现了2021.9.9.6
中的一个停止显示的 bug。我们希望:
- 回滚到
2021.9.9.5
。 - 阻止
2021.9.9.6
上的进程,以防止其被部署到测试或生产。
重新部署2021.9.9.5
按预期进行。运行步骤 6 时,跳过步骤 2 和 3。
此外,2021.9.9.6
已经阻止了发布进程。用户将在项目仪表板上看到一个可视指示器。
自动回滚
接下来,您需要考虑触发回滚。我建议手动触发回滚并记录一个解释。当您看到一个模式时,您应该添加自动化测试来检测是否满足特定的条件。我担心的是收到一个“误报”,导致一个发布在不应该的时候回滚到生产中。
在这个场景中,我不会自动触发回滚,直到我自动执行了所有步骤来做出回滚决定。
例如,如果您的一个条件没有数据库更改,您应该让脚本检查 SQL 脚本的模式更改(例如:添加表,添加列)。如果发现模式改变,那么回滚是不可能的。
接下来,自动触发所有非生产环境的回滚。在做出几次成功的回滚决策后,将其用于生产。
结论
我认为采用蓝/绿、红/黑或金丝雀等高级部署模式是回滚的唯一方法。我曾经认为,只有在无法采用这些模式的情况下,才应该进行前滚,以节省现有应用程序的时间和金钱。采用高级部署模式有其合理的商业原因,例如,Google 永远不会宕机,因此金丝雀式的部署是有意义的。
但是,一个由几十个人在美国东部时间早上 6 点到太平洋时间晚上 10 点使用的内部业务应用程序不会获得同样的成本收益。
您可以使用可变运行条件和新的步骤模板、 计算部署模式 和 块发布进度 ,对您现有的部署流程进行一些调整来创建回滚流程。虽然它们不会支持所有可能的回滚场景,但是如果您发现一个 bug,它们会为您提供其他选项。
观看网络研讨会:Octopus Deploy 的回滚策略
https://www.youtube.com/embed/F_V7r80aDbo
VIDEO
我们定期举办网络研讨会。请参见网络研讨会第页,了解有关即将举办的活动和实时流录制的详细信息。
愉快的部署!
Octopus 如何处理回滚?-章鱼部署
这个问题出现了很多次:
当部署失败时,Octopus Deploy 如何处理回滚?
在回答这个问题时,首先要记住的是部署是复杂的,与云 PaaS 解决方案不同,Octopus 不会对您可以使用它构建或部署的软件类型施加太多限制。
当部署过程中出现问题时,我们有三种通用的处理方法:
- 回滚-恢复到使用软件的先前版本
- 前滚-修复它并部署新版本
- 翻车-再试一次,也许是一个间歇性的问题
代码/应用程序的自动回滚通常非常简单——只需找到旧的二进制文件,更新 IIS/负载平衡器等。指着旧的二进制文件,你就笑开了。
然而,最难的是持久存储——我指的是数据库。在部署过程中,您可能运行了迁移脚本来重命名列。回滚应用程序后,旧版本的应用程序可能会中断,因为它希望列使用旧名称。这是一个非常简单的模式变化。
一种选择是从备份中恢复数据库。但是数据库可能在部署期间一直在使用,并且您可能在迁移脚本运行和部署失败之间不久收到了一个命令。自动回滚数据库将意味着丢失重要数据。
设计支持回滚的应用程序
在设计应用程序和进行更改时,可以使用一些技术来使应用程序更好地支持回滚。
一种选择是确保您的更改总是向后兼容先前的版本。我们可以在不破坏旧应用程序的情况下改变模式,例如,使用视图。在第三个版本之后,您可能会开始删除旧的列或表。然而,复杂的部分是测试;仅仅假设您的模式更改是向后兼容的是不够的,您真的应该测试它,否则回滚将和不回滚一样糟糕。
另一个可能有帮助的方法是使用架构风格,比如事件源。通过这种方式,你可以用旧代码‘重放’新事件,或者用新代码‘重放’旧事件,理想情况下,应用程序的两个版本都可以工作。
失败的部署应该是例外
不幸的是,作为一个应用程序部署工具,Octopus 不能假设您编写的代码总是支持自动回滚。不能保证自动回滚会让事情变得更好;事实上,这可能会让事情变得更糟。
相反,我们认为当部署失败时,Octopus 应该:
- 提供尽可能多的信息;和
- 提供工具来帮助,但而不是去尝试和接管。
毕竟,当生产部署出错时,通常是压力很大的时候——您最不希望的就是有工具对您进行事后批评,让事情变得更糟!
要做到这一点,Octopus 可以很容易地部署以前的版本,或者部署新的版本。只需找到您想要部署的版本,然后单击 deploy。你知道这个版本包含了什么,软件包是如何构造的,所以你可以决定是尝试回滚还是前滚是安全的。对 Octopus 来说,回滚或前滚就像任何正常的部署一样。
在持续部署的世界中失败的部署
Eric Ries 最近推广的一个话题是持续部署,以及集群免疫系统的想法。这个想法是你的系统应该自我监控,如果它检测到一个问题,它可以自动回滚。
重要的是要认识到,Eric 并不提倡使用特定的部署工具来实现这种自动回滚。相反,在设计应用程序时,您需要在系统层面上进行思考。您在出现问题的第一个迹象时透明回滚的能力将更多地取决于您的物理基础架构、数据库和系统设计,而不是您选择的部署工具。
Octopus 确实提供了 API,使得自动部署项目的新老版本成为可能,因此它肯定会在恢复策略中发挥作用。但是没有一种部署工具是万能的;如果这是你的目标,你需要全面考虑你的系统。
部署失败. ps1
虽然 Octopus 永远无法为您从失败的部署中自动恢复,但我很乐意找到让您更容易创建恢复策略的方法。一个建议是部署失败的. ps1 ,如果对特定机器的部署失败,将运行该文件。脚本中会包含什么由您决定,但是支持会在那里。
最后
让我们回到最初的问题:
当部署失败时,Octopus Deploy 如何处理回滚?
简单的答案是,Octopus 使部署以前成功的版本变得容易,或者在解决问题后部署新的版本变得容易。这可以被 API 调用,所以如果你的系统是为它设计的,你可以自动化它。
也就是说,从失败的部署中自动恢复的能力在很大程度上是系统整体的属性,而不仅仅是自动化部署工具的特性。Octopus 有(并将有更多)功能可以提供帮助,但一如既往,这个行业没有灵丹妙药。
了解更多信息
回滚 Kubernetes 部署- Octopus 部署
原文:https://octopus.com/blog/rolling-back-kubernetes-deployment
在部署到 Kubernetes 时,并不是每个部署都像我们希望的那样顺利。bug、容器环境变量和硬件限制可以决定应用程序在部署到 Kubernetes 集群后是否能够运行。当修复不容易或者应用程序没有响应时,您需要回到以前的版本。这个过程称为回滚。
在这篇文章中,我使用 Octopus Deploy 描述了一些通用的和特定于 Kubernetes 的回滚策略。
示例部署流程
本文使用内置的Deploy Kubernetes Containers来部署基于 Java 的 PetClinic 应用程序的容器化版本。
这个应用程序由一个 web 前端和一个 MySQL 后端组成。MySQL 后端也被部署为一个容器,使用 Flyway 将数据库更新作为 Kubernetes 的一项工作来执行。示例流程如下所示:
- 部署 MySQL 容器
- 部署 PetClinic web
- 运行飞行路线作业
- 验证部署
- 通知利益相关方
本文假设您熟悉示例流程中的步骤。我只介绍实现这里讨论的策略的更新。
重新部署以前的版本
通过重新部署以前的版本,很容易从失败的部署中恢复。在 Octopus Deploy 中,您只需点击发布,选择发布,然后点击您想要重新部署到的环境旁边的重新部署。
回滚期间有条件地执行步骤
redeploy 方法完全按照第一次部署的方式执行部署,执行流程中的所有步骤。但是,您可能希望在回滚过程中跳过一些步骤,例如数据库步骤。
要跳过这些步骤,您需要确定正在发生什么活动;部署、重新部署或回滚,然后有条件地控制执行哪些步骤。您可能还希望您正在回滚的版本被阻止进入其他环境。
更新后的流程如下所示:
- 计算部署模式
- 部署 MySQL 容器(仅在部署模式下)
- 部署 PetClinic web
- 运行 Flyway 作业(仅在部署模式下)
- 验证部署
- 通知利益相关方
- 阻止释放进程(仅在回滚模式下)
您会注意到该过程显示了该步骤将在哪些模式下运行。这不是添加条件语句的结果。该流程利用了某个步骤的注释字段,因此您知道在哪些场景中执行了哪些步骤。
计算部署模式
为了确定部署处于哪种模式,我们的团队开发了 计算部署模式 步骤模板。该模板将正在部署的版本号与之前部署到环境中的版本号进行比较,以确定正在发生什么活动。为了方便起见,它还设置了输出变量,这些变量可以用作步骤上的条件。
回滚期间跳过数据库步骤
为了确保 MySQL 和 Flyway 步骤仅在部署期间执行,添加以下来自计算部署模式的输出变量作为变量运行条件:
#{Octopus.Action[Calculate Deployment Mode].Output.RunOnDeploy}
阻止发布进展步骤
在 Octopus Deploy 中,阻止发布进程并不新鲜,但是,它需要手动或通过 API 调用来完成。我们的团队开发了Block Release Progression步骤模板来阻止指定的发布在部署过程中进行。
为了确保此步骤不会在部署期间运行,请添加以下变量运行条件:
#{Octopus.Action[Calculate Deployment Mode].Output.RunOnRollback}
使用 Kubernetes 修订历史回滚
Kubernetes 为 pods 保留了一个滚动修订历史,因此您可以回滚到任何存储的修订(修订的数量可以用默认值 10 进行配置)。命令kubectl rollout history deployment.v1.apps/<deploymentname>
列出了 Kubernetes 部署的所有存储修订。
REVISION CHANGE-CAUSE
1 <none>
2 <none>
3 <none>
您需要修改您的部署过程,以便将一个修订绑定到一个特定的发布。更新后的流程如下所示:
- 计算部署模式
- 回滚原因(仅在回滚模式下)
- 部署 MySQL 容器(仅在部署模式下)
- 部署 PetClinic web
- 运行 Flyway 作业(仅在部署模式下)
- 验证部署
- 通知利益相关方
- 回滚到 PetClinic web 的先前版本(仅在回滚模式下)
- 阻止释放进程(仅在回滚模式下)
让我们来看一下新添加和更新的步骤。
回滚原因
回滚原因是一个 手动干预 步骤,提示您回滚的原因。指定的原因可用于块释放进程步骤中的原因字段。添加变量 run condition,以便它只在回滚期间执行。
部署 PetClinic web
在这一步中,您需要做两处修改
- 添加一个运行条件,使其仅在部署模式下运行
- 添加一个部署注释,将发布与修订联系起来
添加运行条件
我们已经看到了如何设置一个可变的运行条件,以便该步骤只在部署期间执行。
添加部署注释
在部署 Kubernetes 容器步骤中,转到部署注释并添加一个注释类型kubernetes.io/change-cause
,其值为#{Octopus.Release.Number}
【T2
运行kubectl rollout history deployment.v1.apps/<deploymentname>
现在将显示:
REVISION CHANGE-CAUSE
1 2021.09.23.0
2 2021.09.23.1
3 2021.09.23.2
回滚到 PetClinic web 的先前版本
现在CHANGE-CAUSE
列包含了修订版的发布版本,您可以使用 Run a Kubectl CLI Script 步骤来解析部署历史,以确定回滚到哪个版本。
# Init variables
$k8sRollbackVersion = 0
$rollbackVersion = $OctopusParameters['Octopus.Release.Number']
$namespace = $OctopusParameters['Project.Namespace.Name']
$deploymentName = $OctopusParameters['Project.Petclinic.Deployment.Name']
# Get revision history
Write-Host "Getting deployment $deploymentName revision history ..."
$revisionHistory = (kubectl rollout history deployment.v1.apps/$deploymentName -n $namespace)
$revisionHistory = $revisionHistory.Split("`n")
# Loop through history starting at index 2 (first couple of lines aren't versions)
Write-Host "Searching revision history for version $rollbackVersion ..."
for ($i = 2; $i -lt $revisionHistory.Count - 1; $i++)
{
# Split it into two array elements
$revisionSplit = $revisionHistory[$i].Split(" ", [System.StringSplitOptions]::RemoveEmptyEntries)
# Check version
if ($revisionSplit[1] -eq $rollbackVersion)
{
# Record version index
Write-Host "Version $rollbackVersion found!"
$k8sRollbackVersion = $revisionSplit[0]
# Get out of for
break
}
}
# Check to see if something was found
if ($k8sRollbackVersion -gt 0)
{
# Issue rollback
Write-Host "Rolling Kubernetes deployment $deploymentName to revision $k8sRollbackVersion ..."
kubectl rollout undo deployment.v1.apps/$deploymentName -n $namespace --to-revision=$k8sRollbackVersion
}
else
{
Write-Error "Version $rollbackVersion not found in cluster revision history."
}
除了集装箱启动时间,这一行动可以在几秒钟内完成。
结论
使用本文中讨论的策略,您可以在部署过程中直接配置回滚功能,包括部署到 Kubernetes 的应用程序。
观看网络研讨会:Octopus Deploy 的回滚策略
https://www.youtube.com/embed/F_V7r80aDbo
VIDEO
我们定期举办网络研讨会。请参见网络研讨会第页,了解即将举办的活动和实时视频录制的详细信息。
愉快的部署!
回滚 Tomcat 部署- Octopus 部署
DevOps 反馈循环通常有自动化的流程来尽可能早地在管道中捕获问题。虽然这些自动化过程允许早期检测,但错误仍然可以找到进入生产代码的途径。一些 bug 非常严重,足以保证退出最近部署的版本。恢复更改的过程称为回滚。
在本文中,我将讨论使用 Apache Tomcat web 服务器时的不同回滚策略。
初始部署流程
这篇文章使用 PetClinic 应用程序部署到 Apache Tomcat web 服务器。PetClinic 应用程序需要一个数据库后端,并使用 Flyway 来执行数据库迁移。
示例流程如下所示:
- 如果不存在,则创建数据库
- 部署数据库更改
- 部署 PetClinic Web 应用程序
- 验证部署
- 通知利益相关方
这篇文章假设您熟悉这个部署过程中包含的步骤,并且不会详细介绍每个步骤。
部署以前的版本
使用 Octopus Deploy,您可以通过重新部署应用程序的以前版本来回滚。您只需从 Releases 屏幕中选择以前的版本,然后单击所需环境旁边的 REDEPLOY 按钮。
部署过程中的所有步骤都按照创建发布时的配置执行。对于 Apache Tomcat,将在 Tomcat 服务器上重新提取包,执行变量替换,并在发送到 Tomcat 管理器进行部署之前重新打包。包裹的大小将决定需要多长时间。
简单回滚
如上所述,部署过程中的所有步骤都将像第一次一样重新执行。虽然内置的重新部署方法是有效的,但在执行回滚时,可能会有一些您不想执行的步骤。
如果发布有问题,您通常会回滚。在这种情况下,最好将正在回滚的版本标记为坏的,并阻止它升级到其他环境。要有条件地跳过步骤并将发布标记为坏,需要修改流程:
- 计算部署模式
- 如果数据库不存在,则创建数据库(回滚时跳过)
- 部署数据库更改(回滚期间跳过)
- 部署 PetClinic Web 应用程序
- 验证部署
- 通知利益相关方
- 阻止发布进度(仅在回滚期间)
在部署步骤中使用 Notes 字段,您可以为这些步骤以及它们将以何种模式运行提供文档。
您会注意到两个新步骤,计算部署模式和阻止发布进度,以及一些应用于现有步骤的条件。让我们更详细地看看这些。
计算部署模式
回滚过程的第一部分是确定它是部署、回滚还是重新部署操作。
我们的团队开发了社区步骤模板 计算部署模式 ,它决定了部署处于哪种模式,并生成了许多包含变量运行条件语法的输出变量(更多详细信息,请参见步骤描述中的文档)。
数据库步骤
如果不存在则创建数据库和部署数据库更改步骤不需要在回滚中运行。需要将它们配置为跳过。来自计算部署模式的RunOnDeploy
输出变量可应用于这些步骤的变量运行条件以跳过它们:
#{Octopus.Action[Calculate Deployment Mode].Output.RunOnDeploy}
阻止释放进程
阻塞发布进程在 Octopus Deploy 中并不新鲜,但是,它需要通过 UI 或 API 调用来完成。
我们的团队认识到,在处理回滚时,自动化这一活动是至关重要的,并开发了Block Release Progression模板,该模板可以自动阻止发布。
此步骤应仅在回滚操作期间运行。使用计算部署模式的以下输出变量作为变量运行条件以确保这一点:
#{Octopus.Action[Calculate Deployment Mode].Output.RunOnRollback}
复杂的回滚
内置的和简单的回滚方法都将提取并重新打包.war
文件,然后将其交付给 Tomcat 服务器进行部署。如果您的应用程序很大,这可能需要一些时间。使用 Tomcat 并行部署特性,可以在几秒钟内执行回滚。
Tomcat 并行部署
并行部署特性是在 Tomcat version 7 中引入的,它允许您将同一应用程序的多个版本部署到 Tomcat 服务器上。
在应用程序的较新版本处于运行状态后,新会话将在新版本上运行,而现有会话将继续在旧版本上运行,直到它们过期。您需要提供带有上下文路径的版本号。Tomcat 服务器结合了版本号和上下文路径,并将部署的.war
重命名为<contextpath>##<version>.war
复杂的回滚过程
要使用并行部署特性实现回滚过程,您需要修改您的过程,如下所示:
- 计算部署模式
- 回滚原因(仅在回滚期间)
- 如果数据库不存在,则创建数据库(回滚时跳过)
- 部署数据库更改(回滚期间跳过)
- 在 Tomcat 中停止应用程序(回滚时运行或部署和回滚时运行)
- 部署 PetClinic Web 应用程序(仅在部署或重新部署期间)
- 在 Tomcat 中启动应用程序(仅在回滚期间)
- 验证部署
- 通知利益相关方
- 阻止发布进度(仅在回滚期间)
让我们回顾一下为实现这一点而对流程所做的更改。
回滚原因
回滚原因步骤是手动干预,提示您回滚的原因。您指定的原因用于流程中更下一步的块释放进程中的原因字段。此步骤仅在回滚期间运行,因此需要将变量运行条件设置为以下内容:
#{Octopus.Action[Calculate Deployment Mode].Output.RunOnRollback}
停止 Tomcat 中的应用程序
停止和启动步骤都使用 Tomcat 步骤中的启动\停止应用。停止步骤在部署模式下是可选的,但在回滚模式下是必需的,因为 Tomcat 会将新会话传送到正在运行的最新版本的部署应用程序。
回滚时,我们需要停止坏的版本,这样以前部署的版本将开始获取会话。
将变量运行条件设置为仅在回滚期间运行:
#{Octopus.Action[Calculate Deployment Mode].Output.RunOnRollback}
或者,您可以使用系统变量 Octopus.Release.CurrentForEnvironment.Number
来检查是否有先前部署的版本:
#{if Octopus.Release.CurrentForEnvironment.Number}True#{/if}
Tomcat 步骤的高级选项的版本号如下,所选动作为停止应用:
#{Octopus.Release.CurrentForEnvironment.Number}
在 Tomcat 中启动应用程序
在回滚过程中,此过程会停止正在回滚的版本。
您需要一个步骤来启动您要回滚到的版本。在高级选项中为 Tomcat 步骤指定的版本号如下,选择保持应用程序运行:
#{Octopus.Release.Number}
该步骤需要配置为仅使用以下变量运行条件在Rollback Mode
中运行:
#{Octopus.Action[Calculate Deployment Mode].Output.RunOnRollback}
部署 PetClinic Web 应用程序
该步骤需要与 Tomcat 步骤中的 Start App 设置相同的高级选项,为正在部署的版本提供#{Octopus.Release.Number}
。(参考 Tomcat 中启动 App 中的图片。)
阻止释放进程
在简单回滚场景中,块进程的原因字段被静态设置。回滚原因步骤提示您回滚的原因,而注释输出变量可以作为原因的输入,对阻塞释放有更有意义的信息。
修改原因字段以使用来自回滚原因步骤的注释输出变量,如下所示:
#{Octopus.Action[Rollback reason].Output.Manual.Notes}
在 Tomcat 中启用取消部署版本功能会干扰复杂的回滚策略。
在 Tomcat 管理器中,并行部署将与此类似:
清理旧版本
Octopus Deploy 的保留策略通过删除已部署的旧版本来帮助您的目标保持干净。然而,Tomcat 管理器将.war
文件放在它自己的文件夹中,Octopus Deploy 不知道这个文件夹。
如果您没有使用并行部署特性,那么新版本会简单地覆盖.war
并部署应用程序。
Tomcat 的并行部署特性将.war
重命名为<contextpath>##<version>.war
,因此它们是独一无二的。除非对版本化的应用程序条目执行undeploy
操作,否则这些文件将继续累积。
为了帮助 Tomcat 维护,我们的团队开发了 通过管理器 取消部署 Tomcat 应用程序,这是一个社区步骤模板,在 Tomcat 服务器上执行取消部署操作,目前在 Bash 语法中可用。
结论
在这篇文章中,我介绍了几种将应用程序部署回滚到 Apache Tomcat web 服务器的方法。我希望这些方法中的一个能在你的 DevOps 之旅中帮助你。
观看网络研讨会:Octopus Deploy 的回滚策略
https://www.youtube.com/embed/F_V7r80aDbo
VIDEO
我们定期举办网络研讨会。请参见网络研讨会第页,了解即将举办的活动和现场直播的详细信息。
愉快的部署!
在 Octopus 部署- Octopus 部署中运行 AWS CLI
您是否曾经发现自己处于这样一种情况,您知道您想要自动创建一个对象,或者甚至列出对象并生成一个报告,但是您不想在编程语言之间切换?CLI 为您提供了一种获得 SDK 全部可用性的方法,它们通常在每个系统上运行相同的功能,这意味着您不必为 API 创建包装。
在这篇博文中,我们来看看如何在 Octopus Deploy 中使用 AWS CLI。演示重点是使用 Octopus Deploy 中的运行 AWS CLI 脚本步骤模板创建一个 S3 存储桶。
先决条件
要跟进这篇博文,您应该具备以下条件:
你可以从八达通服务器或八达通云免费开始使用。
创建新项目
在运行任何 AWS CLI 命令或创建步骤之前,您需要配置一个项目,以便有地方创建 AWS CLI 过程和步骤。为此,我们将使用 Octopus CLI 的强大功能。
打开终端并运行以下命令,创建一个添加了适当开关值的新项目:
octo create-project --name AWSCLIDeployments --server=octopus_server_url --apiKey=octopus_server_api_key --projectGroup project_group --lifecycle=lifecycle_name
打开网络浏览器并登录 Octopus 门户网站。您现在应该看到新项目可用:
配置变量
现在项目已经创建,您可以配置项目本身了。首先,我们将配置变量。要使 AWS CLI 步骤模板正常工作,它需要 AWS 帐户是一个变量:
- 在 Octopus 门户网站中,导航到您刚刚创建的项目,projects➜AWS clideployments。
- 在项目窗格下,点击变量。
- 在项目变量内的值下,选择下拉菜单并选择改变类型。
- 在类型选项下,选择 AWS 账户。
- 选择一个现有的 AWS 帐户,并为其命名。完成后,点击绿色的 DONE 按钮。
- 点击绿色的保存按钮,保存项目中的变量。
添加 AWS CLI 步骤
现在已经配置了 AWS account 变量,您可以开始配置 AWS CLI 步骤本身来运行 AWS CLI 命令了。为此,您将创建一个新流程:
- 在项目概述页面,选择流程:
- 在 process 页面上,您可以开始添加新步骤,特别是 AWS CLI 步骤。点击添加步骤按钮。
- 在下选择步骤模板,选择 AWS,在已安装的步骤模板下,选择名为的步骤模板运行 AWS CLI 脚本。找到后,单击该步骤。
- 在 AWS 工具部分,选择使用与 Octopus 捆绑的 AWS 工具,因为该选项包含我们需要的所有内容:
- 在 Amazon Web Services 部分,选择您之前创建的
AWSAccount
变量,并选择 us-east-1 地区:
- 在该步骤的脚本部分中,选择 inline source code 选项并键入以下代码,这些代码将用于创建 S3 存储桶。您也可以将 bucket 的名称改为您所在的环境。请记住,S3 存储桶名称必须是唯一的:
aws s3api create-bucket --bucket octopusdeploys392 --region us-east-1
- 输入代码后,点击绿色的保存按钮。
您现在已经准备好运行管道了。
运行管道
现在已经创建了使用 AWS CLI 的步骤,添加了内联代码,您已经准备好开始管道的部署过程了。
- 在项目下,点击蓝色的创建发布按钮。
- 要保存发布,点击绿色的保存按钮。
- 选择要部署到的环境。比如 Dev。
- 点击绿色的展开按钮,展开将开始。
- 完成后,您将看到任务摘要,其中完成了 S3 存储桶的创建。
恭喜你。您已经成功地使用 AWS CLI 在 AWS 中创建了 S3 存储桶。
结论
许多 CLI 为您提供了围绕任务执行简单操作的能力,而这些任务在 UI 中可能是复杂或繁琐的。使用 CLI,您仍然可以从编程的角度直接与平台进行交互,以确保您能够自动执行任务。
在这篇博文中,您不仅学习了如何在 Octopus Deploy 中启动并运行项目,还学习了如何配置 AWS CLI 任务以在 AWS 中创建 S3 存储桶。
愉快的部署!
操作手册经验教训和建议- Octopus 部署
原文:https://octopus.com/blog/runbook-recommendations-and-best-practices
当我们第一次发布 Runbooks 时,我有机会创建了一些 Runbooks,我学到了一些可能对你有帮助的经验。
在这篇文章中,我将从头开始创建一本操作手册,并分享我的建议。
定义要解决的问题
在 Octopus,我们使用 AWS 和 Azure 作为我们的基础架构。事实上,在撰写本文时(本文最初发表于 2020 年 11 月),布里斯班家庭办公室中唯一的基础设施是一个 WiFi 路由器和一个网络交换机。尽管是大量的云用户,我们中的许多人在家里的办公室里运行一个虚拟机管理程序。它让我们可以配置和运行永久虚拟机,如域控制器和 SQL 服务器,以配合 Octopus Deploy 使用。有人帮你设置是一回事,但从头开始设置是完全不同的。
要解决的问题:我有几台 SQL 服务器在运行,我想将每台服务器上的几个数据库备份到我的 NAS 上。
设计操作手册流程
定义问题是第一步。然后,我默认的下一步是向 Octopus Deploy 添加脚本步骤。这已经多次适得其反了,所以首先,我回答这些问题:
- 用例及需求是什么?有发现吗?
- 以前试过这个吗?如果是的话,什么有效,什么无效?
- runbook 应该在哪里运行?
- 谁将调用该操作手册,如何调用?
- 运行操作手册需要批准吗?
- 应该有通知吗?如果是,他们应该什么时候出门,应该包括哪些信息?如何保持较高的信噪比?
- runbook 是特定于项目的吗?还是通用的?
- 是否有任何需要捕获和保留的信息?保留策略应该是什么?我应该把任何东西推给另一个服务吗?
推荐:抓紧时间过一遍类似的题。一点点准备工作会大有帮助。
用例、陷阱和需求
我以前使用库步骤模板 SQL - Backup Database 使用 Octopus Deploy 将数据库备份到我的 NAS。效果还不错。主要问题是库步骤只支持一个数据库。备份多个数据库需要多个步骤,而且该过程需要不断调整。它还使用 SQL 管理对象( SMO ,这意味着我必须安装额外的软件。
基于这一经历,我的要求是:
- 向现有服务器添加数据库不需要更新 Octopus Deploy。
- 从过程中排除数据库应该很容易。
- 备份系统数据库应该是可选的。
- 在触手之外,运行这个进程的虚拟机不需要任何额外的软件。
- 向流程中添加新的服务器应该是微不足道的。
- 执行备份的 SQL Server 用户应该只拥有执行备份的权限。不允许模式更改或写入。
- 该过程将备份到 NAS,而不是本地硬盘。
- 该过程应清理一周前的所有备份。
在继续之前,让我们快速检查一下这个 runbook 将运行的脚本。为了解决许多需求,我需要存储配置选项。Octopus Deploy 的一个鲜为人知的特性是能够存储多个行变量值,比如 JSON。像 JSON 这样存储数据允许我定义一个对象供我的脚本使用。您可以在您的实例中通过点击打开编辑器链接来做类似的事情。
编辑器模式窗口允许您选择右上角的文本类型。选择文本类型会在值窗口中突出显示语法,从而更容易查看 JSON 的语法是否正确。
脚本本身并不复杂。对于数组中的每个对象,它将:
- 连接到服务器
- 调出数据库列表
- 排除 DatabasesToExclude 数组中的任何数据库
- 如果 ExcludeSystemDatabases 为 TRUE,则排除任何系统数据库
- 清理旧的数据库备份
$backupFolderLocation = $OctopusParameters["Project.Backup.FilePath"]
$backupFileDate = $(Get-Date).ToString("yyyy_MM_dd_HH_mm")
$backupItemList = $($OctopusParameters["Project.Backup.Information"]) | ConvertFrom-Json
$notificationContent = [System.Text.StringBuilder]::new()
foreach ($backupitem in $backupItemList)
{
$notificationContent.AppendLine("Server: $($backupItem.Server)")
$connectionString = "Server=$($backupItem.Server);Database=master;integrated security=true;"
try
{
$sqlConnection = New-Object System.Data.SqlClient.SqlConnection
$sqlConnection.ConnectionString = $connectionString
$command = $sqlConnection.CreateCommand()
$command.CommandType = [System.Data.CommandType]'Text'
$command.CommandTimeout = 60000
$command.CommandText = "select Name from sys.databases"
$tablesToBackupDataAdapter = New-Object System.Data.SqlClient.SqlDataAdapter $command
$tablesToBackupDataSet = New-Object System.Data.DataSet
Write-Host "Opening the connection to $($backupItem.Server)"
$sqlConnection.Open()
Write-Highlight "Getting list of databases to backup from $($backupItem.Server)"
$tablesToBackupDataAdapter.Fill($tablesToBackupDataSet)
$databaseToBackupList = @()
foreach ($row in $tablesToBackupDataSet.Tables[0])
{
$databaseNameToCheck = $row["Name"]
if ($backupitem.DatabasesToExclude -contains $databaseNameToCheck)
{
Write-Host "The database $databaseNameToCheck was found in the exclusion list, excluding this database."
continue
}
if ($backupitem.ExcludeSystemDatabases -eq $true -and ($databaseNameToCheck -eq "master" -or $databaseNameToCheck -eq "model" -or $databaseNameToCheck -eq "tempdb" -or $databaseNameToCheck -eq "msdb"))
{
Write-Host "The database $databaseNameToCheck is a system database and exclude system databases is set to true, excluding this database."
continue
}
$databaseToBackupList += $databaseNameToCheck
}
Write-Host "The list of databases that will be backed up on $($backupItem.Server) is $databaseToBackupList"
foreach ($databaseToBackup in $databaseToBackupList)
{
$backupFileName = "$($backupFolderLocation)\$($backupItem.Server.Replace("\", "_"))_$($databaseToBackup)_$($backupFileDate).bak"
$message = "Backing up $databaseToBackup to $backupFileName"
Write-Highlight $message
$notificationContent.AppendLine(" $message")
$command.CommandText = "BACKUP DATABASE [$($databaseToBackup)]
TO DISK = '$backupFileName'
WITH FORMAT;"
$command.ExecuteNonQuery()
Write-Host "Backup complete, removing any backups a week old"
$fileToRemoveList = Get-ChildItem -Path "$($backupFolderLocation)" -Filter "$($backupItem.Server.Replace("\", "_"))_$($databaseToBackup)_*"
foreach ($fileToRemove in $fileToRemoveList)
{
$dateDiff = $(Get-Date) - $fileToRemove.CreationTime
if ($dateDiff.TotalDays -gt 7)
{
$message = "Removing $($FileToRemove.FullName) because it is $($dateDiff.TotalDays) days old"
Write-Host $message
$notificationContent.AppendLine(" $message")
Remove-Item $fileToRemove.FullName
}
}
}
$sqlConnection.Close()
}
catch
{
$notificationContent.AppendLine($_.Exception.Message)
}
$notificationContent.AppendLine("")
$notificationContent.AppendLine("")
}
Set-OctopusVariable -name "NotificationContent" -value $($notificationContent.ToString())
runbook 应该在哪里运行?
runbook 将通过端口1433
调用t-sql
命令来备份数据库。好消息是 Tentacle 不必直接安装在 SQL Server 上(我们的文档关于为数据库部署安装 Tentacle 和 Workers建议您不要在与 SQL Server 相同的服务器上安装 Tentacle)。
一个要求是执行备份的用户应该只拥有执行备份的权限。此外,几个需求表明这个过程应该易于使用并且维护简单。这些需求通过将触手作为域帐户运行来解决。该域帐户将被分配db_backupoperator
和db_datareader
角色。
就像我们的文档推荐的一样,触手在 Octopus Deploy 中注册为工人。我创建了一个新的工人池Database Backup Worker Pool
,将这些工人与其他数据库部署工人隔离开来。
建议:让工人尽可能靠近目的地。如果你在 Azure 中,在 Azure 虚拟机上运行 Worker。如果您在 AWS 中,有一个 EC2 实例托管一个 Worker。如果您在内部运行,请让一名员工在内部运行。Runbooks 可用于执行非常低级的任务。不必穿越防火墙或配置额外的权限,增加了额外的安全层。此外,它保持低延迟。
调用操作手册
我不想考虑备份;它们应该通过触发器自动发生。需要思考的是 runbook 应该在什么环境下运行。我希望这个操作手册能够同时在所有环境中运行。为了简单起见,我为这个 runbook 创建了一个名为Maintenance
的新环境。我可以使用Production,
,但这可能会令人困惑,因为它连接到Test
环境中的 SQL 服务器。
推荐:维护环境可以方便地用于其他跨领域的运行手册。然而,这意味着有一个接触Test
和Production
环境的过程。对流程进行安全审查,以确保您不会意外地将自己暴露在额外的风险中。
批准和通知
虽然这是一个不干涉的过程,但我想知道错误何时发生。该过程总是在完成时发出一封电子邮件,即使它失败了。将运行条件设置为Always Run
可以达到这个要求。
虽然这是不干涉的,但是如果一个人在现实世界的用例中触发了 runbook,那么要求批准是有意义的。幸运的是,在 Octopus 中,批准可以是有条件的,使用可变的运行条件。当一个人调用 runbook 时触发手动干预的语法是#{unless Octopus.Deployment.Trigger.Name}True#{/unless}
。变量说,除非一个触发器触发这一点,然后运行这一步。
建议:利用可变运行条件和输出变量来帮助实施业务规则。当不符合业务规则时,需要批准;否则,自动批准运行。有条件的自动批准有助于批准者保持较高的信噪比。请参见我在上发布的关于运行特定 SQL 脚本的帖子中的真实示例。
通用操作手册
这是一个通用的操作手册,而不是项目专用的操作手册。特定于项目的操作手册旨在仅用于一个项目。它在具有特定角色的目标上运行。特定于项目的操作手册的一个例子是刷新 web 应用程序的缓存。一些应用程序公开端点来刷新缓存;其他需要应用程序池或 web 服务器重置。或者应用程序可以使用 Redis 作为缓存存储。
建议:将相似的通用运行手册归入仅运行手册项目。将这些仅运行手册的项目放入一个独特的项目组。在下面的截图中,每个项目有 1 到 N 个 runbooks。管理程序项目有管理管理程序的操作手册;NAS 项目有用于将文件备份到 NAS 上以及从 NAS 备份到 Azure 文件存储的操作手册。
【T2
日志和保留策略
您可能会注意到该脚本正在捕获日志并设置一个输出变量。该输出变量的内容在结果电子邮件中发送。它还使用 write-highlight 向任务摘要添加文本。
对于我的用例,这就是我需要的所有日志记录和保留策略。由于这种备份每天都在进行,我不需要长时间保留这些日志。我将保留策略从默认值 100 减少到 10。
保留策略会删除审核日志中的运行,但不会删除任务。为了查看运行历史,我使用高级过滤器和日期过滤器找到了审计日志。
建议:保留策略按 runbook 配置。仔细检查你公司的审计政策,以确保 Octopus Deploy 不会在应该清理运行之前清理它。
结论
Runbooks 是实现自助开发的绝佳工具。然而,像任何工具一样,它也有可能被误用。我希望这些建议能够帮助您设计可以在 Octopus Deploy 实例中使用的 runbooks。
最后一个建议:不要在筒仓中设计操作手册。如果您是开发人员,请与适当的操作人员合作(反之亦然)。在设计阶段开始合作。协作有助于充实需求,最终您会得到一个每个人都乐于支持的流程。
阅读我们的 Runbooks 系列的其余部分。
愉快的部署!
操作手册最佳实践- Octopus 部署
瀑布、敏捷或极限编程。不管你是实现它们还是同意它们,不可否认的是开发人员总是在寻求优化他们的工作流程。虽然具体的方法不同,但它们都归结为保持高速度和高质量。要实现这一点,你需要自动化。
然而,运营领域并没有像开发人员那样受到青睐,让运营团队去入侵 CI 服务器以实现任务自动化。CI 服务器是为自动化软件开发而构建的,而运营团队永远是次要考虑因素。
通过 runbooks,Octopus 将运营任务提升到了一个顶级概念,为运营团队提供了一个专为其需求而设计的工作流程。
在本帖中,我们将探讨设计操作手册的最佳实践,为运营团队从手动工作流程转向自动化工作流程提供模板。例如,我们创建了一个 runbook 来重启 Azure web 应用程序。
形容
现在是凌晨 1 点,你的寻呼机响了,让你知道你的网站关闭了。你最不想做的事情就是费力地阅读一页页的项目和脚本,以找到解决问题的方法。这使得可发现性对运行手册至关重要。
每个 Octopus 项目都有一个描述字段。runbook 应该利用该字段来记录 runbook 的目标服务以及 run book 解决的问题。描述字段可从主 Octopus 仪表板进行搜索,允许运营和支持人员根据关键字搜索找到合适的项目,而不是对所有可用脚本的死记硬背。
在下面的屏幕截图中,您可以看到示例项目包括几个关键字,如“500”和“Azure Web App ”,它们与 runbook 的预期使用场景相匹配:
在 Octopus dashboard 中搜索这些关键字会返回 runbook 项目:
检查
操作手册的第一步是检查系统的当前状态,以确定它是否降级。
对于我们的示例 runbook,我们将使用运行 Azure PowerShell 脚本步骤向网站发出 HTTP 请求并检查响应代码。HTTP 调用的结果保存在 Octopus 变量TestResult
中。
下面的脚本测试 HTTP 响应代码:
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12;
$status = [int]([System.Net.WebRequest]::Create("https://$Hostname").GetResponse().StatusCode)
Set-OctopusVariable `
-name "TestResult" `
-value ($status -eq 200)
Write-Host "Web application returned HTTP status code $status"
收集
为了补充上一步,我们需要收集诊断信息,以便日后确定问题的根本原因。
此处收集的信息也可用于下一步,以便支持人员确定系统是否降级,而不管上一步中返回的 HTTP 响应代码。
下面的脚本捕获 Azure Web App 日志文件,并将其保存为 Octopus 工件:
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12;
az webapp log download `
--name MySalesWebApp `
--resource-group SalesResourceGroup `
--log-file logs.zip
New-OctopusArtifact "logs.zip"
确认
web 应用程序关闭的原因有很多。例如,内存泄漏和资源高峰可能导致站点不可用,但偶尔仍会返回有效的 HTTP 响应代码。
如果 inspect 步骤未能识别出问题,我们会显示一个提示,询问运行手册是否应该继续,让支持人员有机会运行手动测试或查看在 collect 步骤中检索到的诊断信息,并决定是否继续。
实际上,这个步骤是作为一个手动干预步骤来实现的,该步骤在最后一个步骤中创建的TestResult
变量为真的条件下运行(换句话说,如果 inspect 步骤成功地联系了 web 应用程序,因此没有发现问题)。
值得注意的是确认步骤出现的频率。如果手动进行是正常的,这意味着检查步骤不能准确地识别系统的错误状态。准确地确定系统是否降级是自动化该过程的一个重要要求。
整流
纠正步骤是 runbook 的核心,在我们的例子中,Azure web 应用程序就是在这里重启的。这是通过运行 Azure PowerShell 脚本步骤实现的,该步骤调用以下脚本来重新启动 web 应用程序:
az webapp restart
在我们的行业里,时断时续是一个笑话,但这仅仅是因为它非常有效。
核实
验证步骤类似于检查步骤,除了这里实现的检查有望通过校正步骤。
在我们的示例中,verify 步骤进入一个循环,在五分钟内检查 HTTP 状态代码。一旦返回 200 响应代码,我们就认为应用程序正在运行。该步骤的代码如下所示:
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12;
for ($x = 0; $x -lt 30; ++$x)
{
$status = [int]([System.Net.WebRequest]::Create("https://$Hostname").GetResponse().StatusCode)
if ($status -eq 200) {
exit 0
}
Start-Sleep 10
}
# We didn't get a good response in 5 mins, so we failed
exit 1
通知
通知团队的其他成员执行了重启是很有用的。Octopus 有通过几个通信平台发送消息的步骤,这里我们使用了Slack-Send Simple Notification社区步骤来报告每个进行步骤的状态。
下面的文本循环运行操作手册的步骤,并打印它们的状态:
#{each step in Octopus.Step}
StepName: #{step}
Status: #{step.Status.Code}
#{/each}
试验
如果你有一个坏的系统和一个未经测试的手册来修复它,你有两个问题。
部署的环境进展的想法从一开始就是 Octopus 的核心租户,runbooks 可以访问所有相同的环境。正如您在进入生产环境之前部署到测试环境一样,在测试环境中执行操作手册允许流程在用于生产中断之前得到验证。
使自动化
实施前面的步骤意味着您的操作手册可以准确地识别系统何时未按预期工作,纠正问题,并验证系统是否回到了所需的状态。您还将在生产环境之外测试该操作手册。这种方法是专为在操作手册流程中注入高度信心而设计的。
此时,您有能力定期自动触发 runbook。为此,确认步骤被禁用或删除,对剩余步骤使用运行条件#{unless Octopus.Action[Inspect].Output.TestResult}true#{/unless}
,并根据需要创建一个预定触发器来执行运行手册:
结论
无论您喜欢哪种方法,实现高速度和高质量都需要自动化。本帖中概述的步骤旨在生成一本可以自信地自动运行的运行手册。
可靠地识别系统何时未处于预期状态,纠正问题,验证修复,并在测试环境中验证整个过程,确保您的操作手册与您的软件部署具有相同的质量。
阅读我们的 Runbooks 系列的其余部分。
愉快的部署!
将基础设施用作运营手册代码- Octopus Deploy
原文:https://octopus.com/blog/runbooks-with-infrastructure-as-code
基础设施即代码(IaC)是一种现代的声明性基础设施配置方法。在这篇文章中,我分享了一些关于 Runbooks 如何帮助您以简洁明了的方式管理基础设施安装和拆卸的经验教训。
在准备 Octopus 2020.1 发布网上研讨会时,我需要为我的演示上下旋转基础设施。在 runbooks 之前,我有一个单独的基础设施作为代码项目来处理这个问题。我不想再这样做了,Operations Runbooks 就是根据这种情况设计的。网上研讨会的准备工作使我能够将我的过程移植到一本操作手册上,并且它给了我一个机会来调整我如何在 Octopus Deploy 中处理基础设施代码。
什么是作为代码的基础设施?
基础设施作为代码是一种在文件中建模所需基础设施的方式,通常用 YAML、JSON 或 Hashcorp 语言(HCL)编写。云提供商拥有接收该文件并调配您所请求的基础架构的机制,但每个提供商都有自己的基础架构定义:
使用云提供商提供的工具有其优势;例如,他们通常有一个 UI 来帮助调试。该过程由 CLI 启动,但是当出现问题时,您可以通过用户界面中的日志来查找根本原因。此外,为了帮助学习曲线,它们要么提供示例,要么让您在 UI 中定义所需的基础结构并将其导出到文件中。顺便说一下,这就是我如何创建 ARM 模板。
使用所提供的工具的不利方面是锁定的风险。AWS Cloudformation 的术语不能翻译成 Azure ARM 模板。这就是第三方工具如 Hashcorp 的 Terraform 有用的地方。它们提供了一个通用的框架,可以跨所有云提供商使用。
基础设施作为运行手册之前的代码
在 runbooks 之前,我的基础设施代码流程是一个正常的部署流程,尽管我没有部署任何东西,而是针对云提供商运行脚本。我的过程目标很简单;只有一个项目可以摧毁我的基础设施。
这种方法导致了一些怪癖。首先,我必须创建一个生命周期,因为我正在使用一个部署过程,而部署过程有生命周期。在这个例子中,我有两个环境,Test
和Production
。然而,我需要一个环境来摧毁一切。这导致了我的第二个怪癖。我需要另一个叫做Teardown
的环境。
我的部署过程有点复杂,因为它包括以下步骤:
- 作用于除
Teardown
之外的任何环境 - 范围扩大到
Teardown
你会注意到在我的生命周期中Test
和Production
是可选的。这让我想到了我的下一个怪癖。第一次配置 IaC,无论是对于 AWS CloudFormation,Azure ARM Templates,GCP 部署管理器,还是 Hashicorp 的 Terraform,都需要大量的试错。通常,我在一两次尝试后就创建好了基础设施,但之后的一切都令人头疼。创建了一个虚拟机,但引导脚本遗漏了一些内容。正确测试修复需要拆除和旋转基础设施。有时错误发生在Test
而其他时间发生在Production
。如果部署真的失败了,我需要一个到达Teardown
的方法。因此,Test
和Production
是可选的。
AWS 区域特定设置
对于本文,我使用 AWS CloudFormation 来启动一个 Ubuntu VM,在该 VM 上安装一个触手,并向 Octopus Deploy 注册触手。在选择 AWS 时,我无意中发现了另一个怪癖:每个地区都是隔离的。绝大多数情况下,资源不能在地区之间共享;这包括:
- 虚拟机的 SSH 密钥
- AMI 图像
- 子网
- 安全组
这不是一份包罗万象的清单。
我并不希望每个地区都有一个环境(可扩展性很差),但是我需要一种方法来进行特定地区的设置,所以我使用了 Octopus Deploy 的多租户功能。租户可以是:
- SaaS 应用程序的客户
- 每个开发人员一个沙箱
- 数据中心
- AWS 区域
我最喜欢的租户特性之一是,每个环境中的每个租户都可以有一个唯一的变量值。这对于数据库连接字符串来说是有意义的,但是在我的 IaC 例子中,这会导致大量的重复值。我不想在Test
、Production
、Teardown
上定义同一个 AMI。幸运的是,可以配置特定于租户的变量,以便在所有环境中使用。
备选方案 1。利用租户标签集
首先,我创建了一个名为AWS Region
的租户标签集:
我这样做是因为租户标签的作用域可以是一个变量。您可以通过点击Open Editor
链接来访问该变量范围:
这将打开一个模态窗口,让您选择租户标记:
我创建了一个名为AWS
的变量集,其中填充了我的 CloudFormation 模板所需的变量:
选项 2。利用变量集变量模板
我选择了选项 1,因为它可以更容易地创建截图并向新用户演示。我发现大多数人看到上面的截图就明白了。它对于显示每个 AWS 区域的差异也很有用。但是,您必须复制租户名称作为租户标记。
另一个选择是利用变量集变量模板。在变量集中,有一个Variable Templates
选项卡。首先,添加一个变量模板:
在租户变量屏幕上,您会看到一个Common Templates
选项卡。将出现您添加到变量集中的变量模板。加州地区给了我最大的心痛,这就是为什么它有一个阿诺德尖叫加州的图像:
租户必须连接到与变量集相连的项目。
如果我在企业环境中设置 Octopus Deploy,我会使用这个选项。
将部署过程移植到运行手册
我很快意识到我不应该从部署过程直接移植到 runbooks。我需要重新思考如何配置我的 IaC 进程。如前所述,我想要一个单一的进程来启动和关闭我的基础设施。我的基础设施即代码项目的过程反映了这个目标。然而,这个单一的项目目标让我做出了一些次优的配置。
这些次优配置是:
- 一个
Teardown
环境的整个概念以及随后的生命周期。 - 必须确定一个步骤的范围,要么在
Teardown
中运行,要么在除Teardown
之外的所有环境中运行。换句话说,我把两个进程挤成了一个。 - 那个
Teardown
环境要求我写脚本同时拆掉Test
和Production
。 - 因为我需要同时分解所有东西,所以我的变量范围变得更加复杂。
让我们花一点时间来讨论变量范围,因为它很好地揭示了我的过程中的多个问题。在Test
和Production
中,虚拟机名称的变量模板遵循以下格式[Application Name]-[Component]-[Environnment Abbreviation]
。例如,todo-web-t
用于待办应用的 web 服务器。Teardown
变量不包括环境缩写。刚好是[Application Name]-[Component]
。为所有环境取消注册虚拟机所需的Teardown
流程。该注销脚本找到并删除了所有以[Application Name]-[Component]
开头的目标。这是一个简单的例子;添加区域变得更加复杂。想出一个不会导致意外删除错误目标的变量模板花了相当多的时间。
拆分流程
我做的第一个改变是将 IaC 过程分成两个操作手册。一个运行手册启动了基础设施,另一个则删除了它。
由于这种分离,我不再需要Teardown
环境。这反过来简化了变量。我还了解到我可以将变量的范围扩展到特定的操作手册。这意味着我不会将未使用的变量传递到部署中。换句话说,我可以有简单的变量,它们只用于特定的操作手册:
这个过程也让我更容易理解。我并没有试图将两个过程混为一谈:
房客
我仍然希望保持我的租户按区域配置。然而,我的示例应用程序不是多租户的;它是小号的。NET 核心 web 应用程序运行在 NGINX 与 NoSQL 后端。它不需要任何东西,只需要在一些地区进行拆分。做一些类似金丝雀部署或蓝/绿部署的事情是多余的。
我想要的是:
- 当我部署测试时,部署到
Ohio
区域中的服务器。 - 当我部署到生产环境时,使用滚动部署部署到
Ohio
、California
和Oregon
地区的所有服务器:
这是我发现一个项目可能不允许租用部署,但是一个操作手册可以要求它们。在我的项目设置中,我将其设置为禁用租用部署:
但是,我将每个 runbook 设置为需要一个租户:
因为操作手册没有生命周期,所以概览屏幕看起来与您可能习惯的常见项目概览屏幕略有不同:
按计划启动和关闭基础架构
虚拟机需要钱。对于本例,我不需要虚拟机全天候运行。我宁愿让它们只在工作日运行。您可能对您的测试环境有类似的需求。如果没有任何东西在使用虚拟机,为什么要花钱让它们运行呢?
这让我想到了一个小问题。runbooks 的预定触发器允许我选择 1 到 N 个环境:
触发器将启动两个任务,每个任务对应我指定的一个环境。对于两个环境,这并不是什么大问题,但是当我配置另一个示例时遇到了瓶颈,我想要四个环境。正在运行的并发任务的数量将呈指数级增长。我不想让我的任务队列因我的基础设施上下旋转而过载。我的第一个想法是,我可以安排触发间隔 15 分钟。例如:
- 项目 A 启动
Development
基础设施:早上 6:00 - 项目 A 启动
Test
基础设施:上午 6:15 - 项目 B 启动
Test
基础设施:早上 6:30 - 项目 A 启动
Staging
基础设施:上午 6:45 - 项目 A 启动
Production
基础设施:早上 7:00 - 项目 B 启动基础设施:上午 7:15
这并不能很好地扩展。尤其是当我添加更多项目时。有时需要 5 分钟来启动基础设施,有时需要 20 分钟,我不想浪费时间等待,但同时,我希望避免任务队列过载。
我需要的是一本操作手册,它可以有序地启动我的所有基础架构。如果你熟悉 JQuery 承诺,本质上我想把我的承诺串起来。一次运行一本操作手册,完成后,开始下一本。
一个 runbook 启动另一个 run book 没有内置的步骤,所以我写了这个步骤模板。现在,我可以让一本操作手册有条不紊地在我的基础架构上运行。我把那本手册叫做Unleash the Kraken
:
我有两个预定的触发器,一个用来启动一切,另一个用来摧毁一切。您会注意到拆除触发器每天都在运行,而旋转触发器只在周末运行。这只是以防任何基础设施在周末启动:
结论
从使用部署流程构建基础设施到使用操作手册,有一些变化。总的来说,我对这些变化感到高兴,一切都感觉干净了许多。
关于 runbooks,我最喜欢的部分是不必为每次运行创建一个版本。为了让我的 CloudFormation 模板和 bootstrap 脚本正确,我不得不在成功之前做了大量的运行。不用跳释放舞感觉很好。
在基于 Windows 的 Docker 容器中运行 SQL Server Developer-Octopus Deploy
原文:https://octopus.com/blog/running-sql-server-developer-install-with-docker
在开发机器上运行 SQL Server 是自动化数据库部署的一个关键部分。通常,在本地运行 SQL Server 是通过安装 SQL Server Developer edition 来完成的。这样做的缺点是 SQL Server Windows 服务需要一直运行,这会消耗资源,安装程序会添加一堆额外的应用程序,开发人员负责升级它。
有可能两全其美吗?在本地运行 SQL Server,但仅在需要时运行,并使其易于升级?很长一段时间以来,SQL Server 一直是 Docker 映像,看起来它可以解决这些令人头痛的问题。真正的问题是,建立起来有多难?在本文中,我旨在回答这个问题,并帮助您让 SQL Server 在 Docker 容器中运行。
基于 Windows 的容器
我从大学开始就一直在 Windows 上开发。我知道 Linux 的核心概念,但我绝不是专家。同时学习 Linux 和 Docker 是一件很困难的事情。Docker 提供了运行基于 Linux 和 Windows 的容器的能力。微软为 SQL Server Developer Edition 提供了一个基于 Windows 的容器,所以为了让学习更容易,我将在本文中使用这个容器。
准备工作
本文假设您对 Docker 有一定的了解。如果你不熟悉 Docker 的核心概念,我鼓励你阅读 Docker 概述页面。
我的笔记本电脑运行的是 Windows 10 专业版。我将使用 Docker 桌面,有时被称为 Docker for Windows。在我开始使用 Docker 之前,需要做一些准备工作。
启用 CPU 虚拟化
归根结底,Docker 是一个虚拟化主机。就像任何其他虚拟化主机一样,CPU 必须支持虚拟化,并且必须启用该功能。通常情况下,虚拟化是在 BIOS 中启用的,这意味着您必须在 Google 上搜索如何在您的计算机制造商的 BIOS 中启用它。英特尔称他们的虚拟化技术为英特尔 VT ,以及英特尔 VTx。AMD 称他们的虚拟化技术为 AMD V ,有时你会看到它被称为 VDI 或 SVM。
安装 Docker for Windows
在快速 BIOS 更新之后,是时候安装 Docker Desktop 了,它包括 Docker Compose 和 Docker CLI。安装 Docker for Windows 的整个过程被很好地记录了下来。无需在此重复。
有一点要注意,如果你没有启用 Hyper-V,安装程序会为你启用它。那将需要重新启动计算机。
如前所述,我将使用基于 Windows 的容器。安装 Docker 桌面后,我需要切换到 Windows 容器。右键单击任务栏中的 Docker 桌面图标并选择Switch to Windows containers...
即可完成。
设置文件夹以与 Docker 容器共享
默认情况下,Docker 将所有容器视为无状态的。预计对容器所做的任何更改(如创建数据库)都将被销毁。这个问题可以通过利用 Docker 中的卷来解决。我在硬盘上建立了一个文件夹 C:\Docker\Volumes 来存储这些卷。
值得注意的是,如果我将这些作为基于 Linux 的容器运行,我需要遵循 Docker 文档中列出的关于共享驱动器的步骤。
防病毒配置
运行 Windows 容器的一个缺点是(除了空间开销之外),反病毒软件可能会阻止它们下载。发生这种阻塞是因为 Docker 在 Windows 文件系统上存储图像的方式。实际上,另一个名为 Windows 的文件夹将出现在一个看似随机的位置。当反病毒扫描仪发现它们时。确保您使用的是最新版本的防病毒软件。如果你不得不将C:\ProgramData\Docker
排除在扫描之外,不要感到惊讶。
配置 SQL Server 开发人员容器
启动并运行一个容器很容易,甚至是 SQL Server。我想将它用于实际的开发工作,这意味着我需要解决以下问题:
- 无需额外配置即可启动并运行容器。
- 通过 SSMS 连接到它。
- 持久化在容器中创建的数据库。
- 保存映像配置供他人使用。
首次运行 SQL Server Developer 容器
这份清单看起来令人望而生畏,尤其是如果你是 Docker 的新手。我想一步一步来。首先,让我们运行一个简单的命令,从 Docker Hub 下载 SQL Server Windows 开发人员映像:
docker pull microsoft/mssql-server-windows-developer
如果你是一步一步地跟着做,沏些茶或咖啡,然后坐下来,因为这可能需要一段时间来完成。它不仅下载该映像,还下载所有的依赖项。
既然已经下载了映像,现在是启动它并运行一些 SQL 脚本的时候了。幸运的是,微软添加到 Docker Hub 的文档让这变得很容易。请记下正在发送的--name
参数。该参数将使以后的工作更加容易。在命名实例的同时,我将端口设置为默认的 SQL Server 端口,1433
:
docker run --name SQLServer -d -p 1433:1433 -e sa_password=Password_01 -e ACCEPT_EULA=Y microsoft/mssql-server-windows-developer
从主机上的 SSMS 连接到容器
SQL Server 容器正在运行,但是我们如何从主机通过 SSMS 连接到它呢?在 run 命令中,我使用了开关-p
,它是 publish 的缩写。本质上,端口 1433 被发布给主机,这意味着我们可以通过localhost
访问它。要将 SSMS 连接到我的 Docker SQL Sever,我需要做的就是输入localhost
,以及上面定义的用户名/密码sa
。
就像普通的 SQL Server 一样,一切都按预期运行。我可以毫无问题地创建一个数据库和表格。
持久化在容器中创建的数据库
如果容器需要重启会怎么样?
docker stop SQLServer
docker start SQLServer
重启后,数据库和所有表仍然存在。
如果需要重新创建容器呢?通常,这是在容器配置更改或发布新版本时完成的。除了stop
命令,我还需要运行rm
命令来删除容器:
docker stop SQLServer
docker rm SQLServer
docker run --name SQLServer -d -p 1433:1433 -e sa_password=Password_01 -e ACCEPT_EULA=Y microsoft/mssql-server-windows-developer
在这种情况下,所有数据库都被删除了。
我们需要一种持久化数据的方法来处理容器的重启和容器的重建。
数据库文件需要持久化。这是使用卷完成的,将指向C:\Docker \Volumes\SQLServer
。还有多,多篇关于 Docker 卷。TL;DR;就是把--volume
开关加到docker run
来加一个卷。如果容器已经在运行,则需要在添加卷之前将其销毁:
docker stop SQLServer
docker rm SQLServer
docker run --name SQLServer -d -p 1433:1433 --volume c:\Docker\Volumes\SQLServer:c:\SQLData -e sa_password=Password_01 -e ACCEPT_EULA=Y microsoft/mssql-server-windows-developer
所有数据库创建命令都需要指定C:\SQLData\
作为数据的目录。假设我希望这个 SQL Server 容器托管 Octopus Deploy 和 TeamCity 的数据库。这些命令是:
CREATE DATABASE [OctopusDeploy]
CONTAINMENT = NONE
ON PRIMARY
( NAME = N'OctopusDeploy', FILENAME = N'C:\SQLData\OctopusDeploy.mdf' , SIZE = 8192KB , FILEGROWTH = 65536KB )
LOG ON
( NAME = N'OctopusDeploy_log', FILENAME = N'C:\SQLData\OctopusDeploy_log.ldf' , SIZE = 8192KB , FILEGROWTH = 65536KB )
GO
CREATE DATABASE [TeamCity]
CONTAINMENT = NONE
ON PRIMARY
( NAME = N'TeamCity', FILENAME = N'C:\SQLData\TeamCity.mdf' , SIZE = 8192KB , FILEGROWTH = 65536KB )
LOG ON
( NAME = N'TeamCity_log', FILENAME = N'C:\SQLData\TeamCity_log.ldf' , SIZE = 8192KB , FILEGROWTH = 65536KB )
GO
毫无疑问,数据库创建成功。
它们现在显示在主机系统的目录中。
这些数据库的名称和路径可以使用attach_dbs
环境变量传递给容器。可以使用 PowerShell 脚本引导所有这一切。对于本文,我没有这样做,因为我只需要创建数据库一次。我看不出花费精力写一个脚本来解决一个我只需要做一次的问题有什么意义。
docker stop SQLServer
docker rm SQLServer
$attachDbs = "[{'dbName':'OctopusDeploy','dbFiles':['C:\\SQLData\\OctopusDeploy.mdf','C:\\SQLData\\OctopusDeploy_log.ldf']},{'dbName':'TeamCity','dbFiles':['C:\\SQLData\\TeamCity.mdf','C:\\SQLData\\TeamCity_log.ldf']}]"
docker run --name SQLServer -d -p 1433:1433 --volume c:\Docker\Volumes\SQLServer:c:\SQLData -e sa_password=Password_01 -e ACCEPT_EULA=Y -e attach_dbs=$attachDbs microsoft/mssql-server-windows-developer
现在,当重新创建容器时,这些数据库被装载。
在 Docker Compose 中保存配置
到目前为止,我一直在大量使用命令行,特别是docker stop
、docker rm
和docker run
。老实说,我一直在复制和粘贴上面的命令,而不是重新键入它们。一种选择是利用 Docker Compose 。Docker 容器配置存储在 YAML 文件中,而不是脚本文件中。
version: '3.7'
services:
SQLServer:
image: microsoft/mssql-server-windows-developer
environment:
- ACCEPT_EULA=Y
- SA_PASSWORD=Password_01
- attach_dbs=[{'dbName':'OctopusDeploy','dbFiles':['C:\\SQLData\\OctopusDeploy.mdf','C:\\SQLData\\OctopusDeploy_log.ldf']},{'dbName':'TeamCity','dbFiles':['C:\\SQLData\\TeamCity.mdf','C:\\SQLData\\TeamCity_log.ldf']}]
ports:
- '1433:1433'
volumes:
- c:\Docker\Volumes\SQLServer:c:\SQLData
我将 docker-compose 文件保存在硬盘上 C:\Docker 文件夹中:
然后我在 PowerShell 中运行了这个命令:
Set-Location C:\Docker
docker-compose up -d
我得到了同样的结果。我更喜欢用这个,因为它更容易阅读,因此也更容易修改。跑起来也很轻松。
结论
让 SQL Server 在 Docker 中运行比我想象的要容易得多。我期待着一小时又一小时的工作,但最终,我在一小时内就完成了一些工作。公平地说,这不包括对 Docker 如何工作的研究。我的希望是这篇文章给了你足够的指导,让你自己深入 Docker,并意识到它并没有那么大和可怕。也许,仅仅是也许,您将使用 Docker 在您的开发机器上托管 SQL Server,而不是安装 SQL Server Developer。
下次再见,愉快的部署!
运行任务上限和高可用性- Octopus 部署
原文:https://octopus.com/blog/running-task-cap-and-high-availability
很快我们将推出 Octopus:高可用性(HA)版。五月份,Damian 写了一篇关于高可用性旨在支持的类场景的文章。最近,Shane 一直在将 Octopus 服务器推向极限,以确保节点分配负载并找到瓶颈。为了准备高可用性,我们正在做一些改变,这将使管理 Octopus 服务器工作负载更容易。
首先,快速回顾一下:在 Octopus: HA 中,负载均衡器在多个 Octopus 服务器之间分配请求。每个 Octopus 服务器处理 web 请求,但也会执行后台任务(比如部署和健康检查)。Octopus 服务器的背后是共享存储——一个共享的 SQL Server 数据库和一个用于任务日志、工件和 NuGet 包的共享文件系统。
部署速度与延迟
Octopus 服务器在部署期间做了大量的工作,主要是围绕包的获取:
- 下载软件包(网络绑定)
- 验证包哈希(受 CPU 限制)
- 计算程序包之间的增量(I/O 和 CPU 限制)
- 上传软件包到触须(网络绑定)
- 监控触角的工作状态,并收集日志
当执行非常大的部署时(许多大的包分发到数百台机器上),很明显,在某个时候,硬件将会限制一台 Octopus 服务器可以同时做多少这样的事情。如果服务器过量使用并达到这些限制,超时(网络或 SQL 连接)将开始发生,部署可能开始失败。
当然,Octopus: HA 通过拥有多个 Octopus 服务器来帮助解决这个问题。注意,共享存储实际上并不是部署过程中的瓶颈——在部署过程中受影响更大的是 Octopus 服务器上的本地硬件。
理想的情况是 Octopus 服务器能够执行尽可能多的并行部署,同时保持在这些限制之下。实际上,这很难预测。我们尝试了各种实验,试图通过查看系统指标来告诉我们是否应该继续消耗新任务,或者退出,但是很难可靠地做到这一点。现在可能有大量的 CPU/内存/I/O 可用,所以我们又选择了一个任务,突然,我们正在运行的其他任务开始了一个需要在数百台机器上运行的步骤,突然,我们又一次超负荷了。
为了让这种“自动调整”方法发挥作用,我们意识到我们需要不断地重新调整并行运行的任务数量。如果部署 A 突然窃取所有 CPU 来计算 SHA1 散列,部署 B 可能需要等待一会儿才能继续。有各种方法可以在这里工作——为某些活动指定专用的工作线程,或者调节执行并行活动的线程。至少最终会是美好而公平的,就像这样:
乍一看,好像比这个好吧?
我们在这条路上走得越多,我们就意识到这是错误的权衡——我们冒着降低部署速度的风险,只是为了尝试并行执行更多部署。鉴于 Octopus 是一个部署自动化工具,部署期间的停机时间是一个重要的考虑因素!如果您在 10 台机器上进行生产部署,我们希望它们快速完成,而不是因为其他人排队部署而慢慢退出。我们应该牺牲整体延迟来换取速度。
运行任务上限
到目前为止,我们想到的最简单、最可靠的方法是限制一个节点上一次可以运行的任务数量,并使其可由用户配置。为了管理这一点,我们有一个新的设置,您可以在每个节点的基础上进行配置。
这个运行任务上限将被设置为一个默认值,您可以将它提高或降低到任何适合您的值。这可能不是一个“智能”的解决方案,但是考虑到在更多的场景中部署速度可能比并发性更重要,我认为这是一个很好的权衡。该设置将基于每个节点,也可用于非 HA 设置。
服务器排水
这个设置还免费提供了另一个功能。当需要重新启动 Octopus 服务器或安装 Windows 更新时,能够优雅地关闭服务器并允许其他节点来收拾残局是很好的。
为此,我们需要:
- 停止运行任何新排队的任务,但让已经运行的任务继续运行,直到它们完成
- 继续处理 web 请求
- 从池中删除服务器
- 一旦流量不再流向服务器,并且所有正在运行的任务都已完成,请停止服务并应用任何更改
相同的运行任务上限设置可用于执行这种“消耗”——我们需要做的就是将其设置为 0!已经在运行的任务将继续运行,但排队的任务将保持排队状态,除非另一台服务器将它们取走。
Selenium 系列:对 BrowserStack - Octopus Deploy 运行测试
这篇文章是关于创建 Selenium WebDriver 测试框架的系列文章的一部分。
到目前为止,我们的测试仅限于 Chrome 和 Firefox 等桌面浏览器。根据您运行的操作系统,您也可以测试 Safari、Internet Explorer 和 Edge 等浏览器。但是不管你运行的是哪种操作系统,都没有简单的方法来测试所有流行的浏览器。Windows 不支持 Safari,MacOS 不支持 Internet Explorer 和 Edge,Linux 也不支持这些浏览器。虽然可以在桌面或服务器环境中模拟移动浏览器,但这样做很难配置和维护。
为了解决这些问题,BrowserStack 之类的服务提供了针对大量浏览器运行 WebDriver 测试的能力,包括桌面和移动浏览器。通过管理各种操作系统、浏览器和移动设备,BrowserStack 这样的服务使得大规模跨浏览器测试变得非常容易。
BrowserStack 不是免费服务,要利用它的大部分功能,你需要为一个帐户付费。幸运的是,Mozilla 和微软都与 BrowserStack 合作,提供针对 Edge 和 Firefox 浏览器的免费测试。我们将利用这个服务来构建一些可以免费运行的远程测试。好消息是,一旦你在 Edge 或 Firefox 这样的浏览器上运行了测试,重用这些代码来运行 BrowserStack 提供的任何其他浏览器的测试都是非常简单的。
要创建 BrowserStack 帐户,请前往https://www.browserstack.com。在主页或顶部菜单中,您会看到注册免费试用的链接。
输入您的电子邮件地址、密码和姓名,并继续下一页。
仅此而已。您现在有一个 BrowserStack 帐户。
为了连接到 BrowserStack,我们需要获取访问密钥。这可以通过点击Account
菜单并选择Settings
找到。
您将在 Automate 标题下找到访问键。记下Username
和Access Key
,因为我们稍后将需要这些值。
为了对 BrowserStack 远程运行测试,我们需要创建一个RemoteDriver
类的实例。与ChromeDriver
或FirefoxDiver
不同的是,RemoteDriver
被设计用来控制远程服务器上的浏览器。这意味着我们需要给RemoteDriver
一个 URL 来发送命令和凭证。
https://www.browserstack.com/automate/java 的 BrowserStack 文档显示了我们需要连接的 URL。它的格式是:https://<username>:<password>@hub-cloud.browserstack.com/wd/hub
。用户名和密码嵌入在 URL 中。
为了使测试能够在 BrowserStack 上运行,我们将创建一个名为BrowserStackDecorator
的新装饰器:
package com.octopus.decorators;
import com.octopus.AutomatedBrowser;
import com.octopus.decoratorbase.AutomatedBrowserBase;
import com.octopus.exceptions.ConfigurationException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.RemoteWebDriver;
import java.net.MalformedURLException;
import java.net.URL;
public class BrowserStackDecorator extends AutomatedBrowserBase {
private static final String USERNAME_ENV = "BROWSERSTACK_USERNAME";
private static final String AUTOMATE_KEY_ENV = "BROWSERSTACK_KEY";
public BrowserStackDecorator(final AutomatedBrowser automatedBrowser) {
super(automatedBrowser);
}
@Override
public void init() {
try {
final String url = "https://" +
System.getenv(USERNAME_ENV) + ":" +
System.getenv(AUTOMATE_KEY_ENV) +
"@hub-cloud.browserstack.com/wd/hub";
final WebDriver webDriver = new RemoteWebDriver(new URL(url), getDesiredCapabilities());
getAutomatedBrowser().setWebDriver(webDriver);
getAutomatedBrowser().init();
} catch (MalformedURLException ex) {
throw new ConfigurationException(ex);
}
}
}
因为将密码嵌入到应用程序中被认为是不好的做法,所以我们将从环境变量中获取用户名和密码。用户名将在BROWSERSTACK_USERNAME
环境变量中找到,而密码将在BROWSERSTACK_KEY
环境变量中找到。我们将为这些字符串创建常量,以便稍后在代码中访问它们:
private static final String USERNAME_ENV = "BROWSERSTACK_USERNAME";
private static final String AUTOMATE_KEY_ENV = "BROWSERSTACK_KEY";
接下来,我们构建允许RemoteDriver
联系 BrowserStack 服务的 URL。我们使用对System.getenv()
的调用从环境变量中获取用户名和密码:
final String url = "https://" +
System.getenv(USERNAME_ENV) + ":" +
System.getenv(AUTOMATE_KEY_ENV) +
"@hub-cloud.browserstack.com/wd/hub";
在RemoteDriver
类的构造和类似ChromeDriver
的类之间只有很小的区别。
RemoteDriver()
构造函数获取要连接的服务的 URL 和所需的功能。
对于RemoteDriver
类,没有类似于ChromeOptions
的等价类;它直接使用DesiredCapabilities
对象:
final WebDriver webDriver = new RemoteWebDriver(new URL(url), getDesiredCapabilities());
如果我们构造的 URL 无效,就会抛出一个MalformedURLException
。我们捕获这个异常,并将其包装在一个名为ConfigurationException
的未检查异常中:
catch (MalformedURLException ex) {
throw new ConfigurationException(ex);
}
ConfigurationException
类用于指示所需的环境变量尚未配置:
package com.octopus.exceptions;
public class ConfigurationException extends RuntimeException {
public ConfigurationException() {
}
public ConfigurationException(final String message) {
super(message);
}
public ConfigurationException(final String message, final Throwable ex)
{
super(message, ex);
}
public ConfigurationException(final Exception ex) {
super(ex);
}
}
建造RemoteDriver
只是故事的一半。因为RemoteDriver
是远程服务所公开的任何浏览器的通用接口,所以我们将想要测试的浏览器的细节定义为所需的 capabilities 对象中的值。BrowserStack 有一个在线工具,可以在https://www.browserstack.com/automate/capabilities生成这些所需的功能设置。您选择所需的操作系统、设备或浏览器以及一些其他设置,如屏幕分辨率,该工具将生成可用于填充DesiredCapabilities
对象的代码。
我们将首先针对 Windows 10 中提供的 Edge 浏览器进行测试。
这些期望的功能设置将在一个名为BrowserStackEdgeDecorator
的新装饰器中定义:
package com.octopus.decorators;
import com.octopus.AutomatedBrowser;
import com.octopus.decoratorbase.AutomatedBrowserBase;
import org.openqa.selenium.remote.DesiredCapabilities;
public class BrowserStackEdgeDecorator extends AutomatedBrowserBase {
public BrowserStackEdgeDecorator(final AutomatedBrowser automatedBrowser) {
super(automatedBrowser);
}
@Override
public DesiredCapabilities getDesiredCapabilities() {
final DesiredCapabilities caps = getAutomatedBrowser().getDesiredCapabilities();
caps.setCapability("os", "Windows");
caps.setCapability("os_version", "10");
caps.setCapability("browser", "Edge");
caps.setCapability("browser_version", "insider preview");
caps.setCapability("browserstack.local", "false");
caps.setCapability("browserstack.selenium_version", "3.7.0");
return caps;
}
}
为了将这两个新的装饰器结合在一起,我们在工厂中创建了一种新型的浏览器。
注意,在构建AutomatedBrowser
实例来运行 BrowserStack 中的测试时,我们不使用BrowserMobDecorator
类。BrowserMob 只对运行在本地机器上的浏览器可用,因为它被绑定到 localhost 接口上的一个端口,这意味着它不会像那些运行在 BrowserStack 平台上的浏览器那样暴露给外部浏览器。为了避免给远程浏览器配置他们无权访问的本地代理,我们将BrowserMobDecorator
类排除在装饰链之外。
在这里,我们嵌套装饰器的顺序很重要。BrowserStackDecorator
期望在嵌套装饰器中设置期望的功能,在本例中是BrowserStackEdgeDecorator
。这意味着BrowserStackDecorator
必须将BrowserStackEdgeDecorator
传递给它的构造函数,而不是相反:
package com.octopus;
import com.octopus.decorators.*;
public class AutomatedBrowserFactory {
public AutomatedBrowser getAutomatedBrowser(String browser) {
// ...
if ("BrowserStackEdge".equalsIgnoreCase(browser)) {
return getBrowserStackEdge();
}
if ("BrowserStackEdgeNoImplicitWait".equalsIgnoreCase(browser)) {
return getBrowserStackEdgeNoImplicitWait();
}
throw new IllegalArgumentException("Unknown browser " + browser);
}
// ...
private AutomatedBrowser getBrowserStackEdge() {
return new BrowserStackDecorator(
new BrowserStackEdgeDecorator(
new ImplicitWaitDecorator(10,
new WebDriverDecorator()
)
)
);
}
private AutomatedBrowser getBrowserStackEdgeNoImplicitWait() {
return new BrowserStackDecorator(
new BrowserStackEdgeDecorator(
new WebDriverDecorator()
)
);
}
}
现在我们可以在测试中使用这个新的浏览器。请注意,我们不是从本地磁盘访问 HTML 文件,而是打开 URLhttps://s3 . amazonaws . com/web driver-testing-website/form . HTML,这是我们在之前的帖子中在 S3 上传的文件的 URL。
如果我们尝试对本地文件运行测试,就会失败。这是因为远程浏览器试图打开的 URL 看起来类似于file:///Users/username/javaproject/src/test/resources/form.html
(取决于您的本地操作系统)。运行远程浏览器的操作系统上不存在该文件,因为远程浏览器运行在由 BrowserStack 管理的操作系统上。任何加载该文件的尝试都会失败:
@Test
public void browserStackTest() {
final AutomatedBrowser automatedBrowser =
AUTOMATED_BROWSER_FACTORY.getAutomatedBrowser("BrowserStackEdge");
final String formButtonLocator = "button_element";
final String formTextBoxLocator = "text_element";
final String formTextAreaLocator = "textarea_element";
final String formDropDownListLocator = "[name=select_element]";
final String formCheckboxLocator = "//*[@name=\"checkbox1_element\"]";
final String messageLocator = "message";
try {
automatedBrowser.init();
automatedBrowser.goTo("https://s3.amazonaws.com/webdriver-testing-website/form.html");
automatedBrowser.clickElement(formButtonLocator);
assertEquals("Button Clicked", automatedBrowser.getTextFromElement(messageLocator));
automatedBrowser.populateElement(formTextBoxLocator, "test text");
assertEquals("Text Input Changed", automatedBrowser.getTextFromElement(messageLocator));
automatedBrowser.populateElement(formTextAreaLocator, "test text");
assertEquals("Text Area Changed", automatedBrowser.getTextFromElement(messageLocator));
automatedBrowser.selectOptionByTextFromSelect("Option 2.1", formDropDownListLocator);
assertEquals("Select Changed", automatedBrowser.getTextFromElement(messageLocator));
automatedBrowser.clickElement(formCheckboxLocator);
assertEquals("Checkbox Changed", automatedBrowser.getTextFromElement(messageLocator));
} finally {
automatedBrowser.destroy();
}
}
运行该测试将生成如下异常:
org.openqa.selenium.WebDriverException: Invalid username or password
(WARNING: The server did not provide any stacktrace information) Command
duration or timeout: 1.81 seconds Build info: version: '3.11.0',
revision: 'e59cfb3', time: '2018-03-11T20:26:55.152Z' System info:
host: 'Christinas-MBP', ip: '192.168.1.84', os.name: 'Mac OS X',
os.arch: 'x86_64', os.version: '10.13.4', java.version:
'1.8.0_144' Driver info: driver.version: RemoteWebDriver at
sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at
sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at
sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423) at
org.openqa.selenium.remote.ErrorHandler.createThrowable(ErrorHandler.java:214)
at
org.openqa.selenium.remote.ErrorHandler.throwIfResponseFailed(ErrorHandler.java:166)
这是因为我们没有用 BrowserStack 用户名和密码设置环境变量。要将这些环境变量添加到测试中,点击 IntelliJ 中包含配置的下拉列表,并点击Edit Configurations...
在 Configuration 选项卡下,您会看到一个名为Environment Variables
的字段。单击该字段右侧的按钮。
在对话框中输入环境变量,并保存更改。点击OK
按钮两次保存更改。
这一次,测试将成功运行。您可以通过登录 BrowserStack 并点击产品➜自动化链接来查看测试运行情况。默认情况下,显示最后一次测试。右边的屏幕将向您显示针对远程浏览器正在运行的测试,或者如果测试已经完成,它将提供测试的录制视频。
试用 BrowserStack 帐户通常可以获得 100 分钟的免费时间,但由于 BrowserStack 和微软之间的合作关系,这些时间不会被针对 Edge 运行的测试所消耗。
既然我们有能力在 Edge 浏览器上运行测试,那么开始在 BrowserStack 提供的任何其他浏览器上运行测试就非常简单了。我们将在下一篇文章中看到这一点,届时我们将对作为 BrowserStack 服务的一部分提供的移动设备进行测试。
这篇文章是关于创建 Selenium WebDriver 测试框架的系列文章的一部分。
Selenium 系列:在移动设备上运行测试- Octopus Deploy
原文:https://octopus.com/blog/selenium/18-running-tests-on-mobile-devices/running-tests-on-mobile-devices
这篇文章是关于创建 Selenium WebDriver 测试框架的系列文章的一部分。
到目前为止,我们的测试仅限于桌面浏览器,但是如果没有测试移动浏览器的方法,任何测试策略都是不完整的。移动浏览器测试是 BrowserStack 等服务的主要功能之一。使用我们在桌面浏览器上开发和运行的相同代码,在各种各样的移动浏览器上运行测试是非常容易的。
让我们看看如何在三星 Galaxy Note 8 移动设备上测试 Chrome。
首先,我们需要构建所需的 capabilities 对象来指示 BrowserStack 对 Samsung 设备运行测试。如前所述,我们可以通过在https://www.browserstack.com/automate/capabilities提供的表格获得这些细节。
然后我们获取这些设置,并使用它们来构建一个名为BrowserStackAndroidDecorator
的新装饰器类:
package com.octopus.decorators;
import com.octopus.AutomatedBrowser;
import com.octopus.decoratorbase.AutomatedBrowserBase;
import org.openqa.selenium.remote.DesiredCapabilities;
public class BrowserStackAndroidDecorator extends AutomatedBrowserBase {
public BrowserStackAndroidDecorator(final AutomatedBrowser automatedBrowser) {
super(automatedBrowser);
}
@Override
public DesiredCapabilities getDesiredCapabilities() {
final DesiredCapabilities caps = getAutomatedBrowser().getDesiredCapabilities();
caps.setCapability("os_version", "7.1");
caps.setCapability("device", "Samsung Galaxy Note 8");
caps.setCapability("real_mobile", "true");
caps.setCapability("browserstack.local", "false");
return caps;
}
}
然后我们在AutomatedBrowserFactory
中使用这个类:
package com.octopus;
import com.octopus.decorators.*;
public class AutomatedBrowserFactory {
public AutomatedBrowser getAutomatedBrowser(String browser) {
// ...
if ("BrowserStackAndroid".equalsIgnoreCase(browser)) {
return getBrowserStackAndroid();
}
if ("BrowserStackAndroidNoImplicitWait".equalsIgnoreCase(browser)) {
return getBrowserStackAndroidNoImplicitWait();
}
throw new IllegalArgumentException("Unknown browser " + browser);
}
// ...
private AutomatedBrowser getBrowserStackAndroid() {
return new BrowserStackDecorator(
new BrowserStackAndroidDecorator(
new ImplicitWaitDecorator(10,
new WebDriverDecorator()
)
)
);
}
private AutomatedBrowser getBrowserStackAndroidNoImplicitWait() {
return new BrowserStackDecorator(
new BrowserStackAndroidDecorator(
new WebDriverDecorator()
)
);
}
}
然后,我们可以在测试中使用这个新的AutomatedBrowser
实例:
@Test
public void browserStackAndroidTest() {
final AutomatedBrowser automatedBrowser =
AUTOMATED_BROWSER_FACTORY.getAutomatedBrowser("BrowserStackAndroid");
final String formButtonLocator = "button_element";
final String formTextBoxLocator = "text_element";
final String formTextAreaLocator = "textarea_element";
final String formDropDownListLocator = "[name=select_element]";
final String formCheckboxLocator = "//*[@name=\"checkbox1_element\"]";
final String messageLocator = "message";
try {
automatedBrowser.init();
automatedBrowser.goTo("https://s3.amazonaws.com/webdriver-testing-website/form.html");
automatedBrowser.clickElement(formButtonLocator);
assertEquals("Button Clicked", automatedBrowser.getTextFromElement(messageLocator));
automatedBrowser.populateElement(formTextBoxLocator, "test text");
assertEquals("Text Input Changed", automatedBrowser.getTextFromElement(messageLocator));
automatedBrowser.populateElement(formTextAreaLocator, "test text");
assertEquals("Text Area Changed", automatedBrowser.getTextFromElement(messageLocator));
automatedBrowser.selectOptionByTextFromSelect("Option 2.1",
formDropDownListLocator);
assertEquals("Select Changed", automatedBrowser.getTextFromElement(messageLocator));
automatedBrowser.clickElement(formCheckboxLocator);
assertEquals("Checkbox Changed", automatedBrowser.getTextFromElement(messageLocator));
} finally {
automatedBrowser.destroy();
}
}
如果我们运行这个测试,可能会再次生成关于无效凭据的异常。如果您还记得,我们之前将 BrowserStack 凭证定义为环境变量,但是我们只针对单个测试这样做。将这些变量添加到每一个新的测试配置中会很繁琐,所以要将这些环境变量添加到所有测试中,我们需要为 IntelliJ 运行的所有 JUnit 测试配置默认设置。
点击配置下拉列表并选择Edit Configurations...
这一次,我们没有将环境变量添加到单个测试的配置中,而是将它们添加为任何 JUnit 配置的缺省值。
展开左侧的Defaults
菜单,选择JUnit
选项,将BROWSERSTACK_USERNAME
和BROWSERSTACK_KEY
添加到Environment variables
中。
您可能需要删除运行测试时创建的 JUnit 配置。这将在左侧菜单中的JUnit
选项下找到。选择配置,然后单击减号按钮。
当您再次运行单元测试时,IntelliJ 将创建一个新的 JUnit 配置,这将使用默认值填充环境变量。
再次运行测试,将创建一个新的 BrowserStack 会话,可通过点击 BrowserStack 中的产品➜自动化进行查看。该测试将在三星移动设备上运行。
在大量设备上运行我们的测试的能力显示了 WebDriver 是多么的灵活。通过几个简单的装饰器,我们可以配置我们的测试在 BrowserStack 支持的数百个设备上运行。但是在编写跨桌面和移动浏览器的测试时,我们仍然需要注意一些边缘情况,在下一篇文章中,我们将看到一个例子,我们需要解决环境之间的一些差异。
这篇文章是关于创建 Selenium WebDriver 测试框架的系列文章的一部分。
针对 Octopus - Octopus Deploy 部署的云基础架构运行手动测试
今年悉尼 NDC 会议期间出现的一个问题是,如何针对以前在各种环境中的部署运行 UI 测试。测试不一定是部署过程的一部分,但是可以手动运行或者按照单独的时间表运行。尽管不是这样的部署,但从 Octopus 运行测试将会很方便,因为 Octopus 拥有关于应用程序部署位置的所有信息。
所以从高层次来看,问题是这样的:
- 将应用程序部署到环境中。
- 部署会产生一个 URL。
- 在部署完成后的某个时间点,开始对最后一次部署进行 UI 测试。
为了演示这个用例,我们将首先向 AWS 部署一个 CloudFormation 模板,它创建一个带有公共 IP 地址的 EC2 实例。EC2 实例将运行 Tomcat 来模拟可测试的 web 服务器。
下面的 YAML 文件可以和Deploy an AWS CloudFormation template
步骤一起使用来启动一个安装了 Tomcat 8 的 Ubuntu 实例。
AWSTemplateFormatVersion: 2010-09-09
Parameters:
InstanceTypeParameter:
Type: String
Default: m3.medium
AllowedValues:
- t1.micro
- t2.nano
- t2.micro
- t2.small
- t2.medium
- t2.large
- m1.small
- m1.medium
- m1.large
- m1.xlarge
- m2.xlarge
- m2.2xlarge
- m2.4xlarge
- m3.medium
- m3.large
- m3.xlarge
- m3.2xlarge
- m4.large
- m4.xlarge
- m4.2xlarge
- m4.4xlarge
- m4.10xlarge
- c1.medium
- c1.xlarge
- c3.large
- c3.xlarge
- c3.2xlarge
- c3.4xlarge
- c3.8xlarge
- c4.large
- c4.xlarge
- c4.2xlarge
- c4.4xlarge
- c4.8xlarge
- g2.2xlarge
- g2.8xlarge
- r3.large
- r3.xlarge
- r3.2xlarge
- r3.4xlarge
- r3.8xlarge
- i2.xlarge
- i2.2xlarge
- i2.4xlarge
- i2.8xlarge
- d2.xlarge
- d2.2xlarge
- d2.4xlarge
- d2.8xlarge
- hi1.4xlarge
- hs1.8xlarge
- cr1.8xlarge
- cc2.8xlarge
- cg1.4xlarge
Description: Enter instance size. Default is m3.medium.
AMI:
Type: String
Default: ami-ea9b6597
Description: AMI Image
Resources:
InstanceSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Enable SSH access via port 22, and open web port 8080
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: '22'
ToPort: '22'
CidrIp: '0.0.0.0/0'
- IpProtocol: tcp
FromPort: '8080'
ToPort: '8080'
CidrIp: '0.0.0.0/0'
Ubuntu:
Type: 'AWS::EC2::Instance'
Properties:
ImageId: !Ref AMI
InstanceType:
Ref: InstanceTypeParameter
KeyName: DukeLegion
SecurityGroups:
- Ref: InstanceSecurityGroup
Tags:
-
Key: Name
Value: Ubuntu with Tomcat
UserData:
Fn::Base64: |
#cloud-boothook
#!/bin/bash
sudo apt-get update
sudo apt-get install -y tomcat8
sudo service tomcat8 start
Outputs:
PublicIp:
Value:
Fn::GetAtt:
- Ubuntu
- PublicIp
Description: Server's PublicIp Address
下面是章鱼里的步骤截图。
运行这个步骤会导致新 EC2 实例的公共 IP 被保存为一个变量。在该步骤生成的日志输出中,您可以看到文本Saving variable "Octopus.Action[Deploy Tomcat].Output.AwsOutputs[PublicIp]"
,它记录了如何访问该变量。
这样的变量在后续步骤中很容易消耗。但是在这个用例中,我们希望在以后访问这些变量,而不是作为当前部署的一部分。
为了演示使用从以前的部署中生成的 URL 运行测试,我们将创建第二个 Octopus 项目。该项目将包含以下 Powershell 的单个脚本步骤。这个脚本利用 Octopus 客户端库来查询部署的细节。
虽然在这个例子中这段代码是在 Octopus 中运行的,但是如果需要的话,它也可以在外部运行。只是要确保提供$ServerUrl
、$ApiKey
和$OctopusParameters["Octopus.Environment.Name"]
变量的替代变量,因为这些变量是由 Octopus 提供的。
[Reflection.Assembly]::LoadFrom("Octopus.Client\lib\net45\Octopus.Client.dll")
$endpoint = new-object Octopus.Client.OctopusServerEndpoint($ServerUrl, $ApiKey)
$repository = new-object Octopus.Client.OctopusRepository($endpoint)
$project = $repository.Projects.FindByName("Deploy EC2 Tomcat");
$env = $repository.Environments.FindByName($OctopusParameters["Octopus.Environment.Name"]);
$progression = $repository.Projects.GetProgression($project);
$item = $progression.Releases |
% { $_.Deployments.Values } |
% { $_ } |
? { $_.EnvironmentId -eq $env.Id } |
Sort-Object -Property CompletedTime -Descending |
Select-Object -first 1
$deployment = $repository.Deployments.Get($item.DeploymentId);
$variables = $repository.VariableSets.Get($deployment.ManifestVariableSetId);
$publicIp = $variables.Variables |
? {$_.Name.Contains("Octopus.Action[Deploy Tomcat].Output.AwsOutputs[PublicIp]")} |
Select-Object -first 1
Write-Host "$($publicIp.Name) $($publicIp.Value)"
invoke-webrequest "http://$($publicIp.Value):8080" -DisableKeepAlive -UseBasicParsing -Method head
注意,我们已经加载了代码为[Reflection.Assembly]::LoadFrom("Octopus.Client\lib\net45\Octopus.Client.dll")
的 Octopus 客户端库。这个 dll 文件已经被 Octopus 2018.8 中的一个新特性暴露了,这个特性就是允许在一个脚本步骤中包含额外的包。我们利用这一点来下载Octopus.Client
包并将其解压缩,这样我们的 Powershell 代码就可以加载 dll 了。
项目中定义了$ServerUrl
和$ApiKey
变量。您可以从文档中找到关于生成 API 密钥的更多信息。
为了方便起见,您可能希望能够在任何时候针对任何环境运行该脚本。典型的 Octopus 生命周期包括在投入生产之前通过测试和内部环境进行部署。当运行一个测试时,你可能想直接在生产中运行它。
为了实现这一点,我们创建了一个包含所有环境的单一阶段的生命周期。这意味着部署过程可以以任何顺序针对任何环境。
在下面的截图中,我们有名为Standard Lifecycle
的应用程序部署生命周期和名为Standard Lifecycle Unrestricted
的测试生命周期。请注意,无限制的生命周期将所有环境都放在一个阶段中。
您可以在文档中找到关于生命周期的更多信息。
这种不受限制的生命周期的效果是,您会得到一个要部署到的环境的下拉列表。
在这一点上,我们可以在任何时候针对任何环境部署测试脚本。Powershell 代码将询问最后一次部署,以找到 EC2 实例的 IP 地址,并将其用作测试的目标。
这是一个简单的例子,展示了如何获取以前的部署细节来运行一个简单的网络测试。但是你还可以做更多的事情。一些例子可能是:
安全模式更新——数据库交付地狱——Octopus 部署
原文:https://octopus.com/blog/safe-schema-updates-1-delivery-hell
这篇博客文章是关于安全模式更新系列文章的第 1 部分。本系列其他文章的链接如下:
批评现有系统:
想象更好的系统:
构建更好的系统:
为了理解为什么有必要做出改变,反思一下我们现在的处境是很有用的。提前道歉,这可能会让你读起来不舒服。
“这需要改变数据库。”
听到这些话,IT 人常常不寒而栗。多年的经验告诉他们,要将那个功能投入生产,将会是一场艰苦的战斗。
这种末日即将来临的感觉有几个原因。为了好玩,我要借用但丁的一点艺术许可证。(声明一下,我完全是从吉安卢卡·萨托里那里偷来的这个想法——他在的推特上,你可以去看看他的极其时髦的数据库“地狱”谈话。)
0 级:数据地狱
数据库面临的独特挑战是数据。关键业务信息没有保存在源代码控制中,所以不可能像删除一个有问题的 web 服务器那样删除和重新部署数据库。
因此,数据库回滚并不容易。可以说,没有数据库回滚这样的事情。如果生产部署出现问题,可能需要恢复备份。这将导致停机和(可能的)数据丢失。这对您的用户来说可能是一场灾难,对企业来说成本高昂。
但这不仅仅是一个关于生产的故事。开发/测试数据库很少有真正有代表性的数据。因此,bug 和性能问题通常只能在生产中发现。此外,如果无法在“类似生产”的环境中可靠地模拟部署,就很难测试数据丢失或部署脚本执行不佳的情况。
当我们未能在开发/测试环境中提供有代表性的安全测试数据时,当我们未能在部署中测试数据问题时,我们就不尊重我们的数据。这种罪恶是有后果的。
第一级:依赖地狱
“数据库”常常成为无数依赖系统的共享后端服务。久经沙场的工程师已经痛苦地认识到,改变模式可能会对那些依赖系统产生意想不到的后果。当工程师甚至不知道他们依赖的系统是什么时,他们不可能对变化有信心。不幸的是,这些依赖性很少被很好地记录或测试。
当数据库之间的依赖性突然出现时,这尤其糟糕,例如,通过存储过程、视图或(令人不寒而栗的)链接服务器。最差的违规者甚至可能看到他们的开发、测试和生产环境之间的依赖性。
由于所有的依赖性,开发/测试环境非常难以供应和维护,这一事实加剧了生产部署问题。
在理想的情况下,开发/测试环境通常是可任意处理的专用环境,开发人员可以为每个新的开发任务开发和丢弃这些环境。然而,考虑到构建大型或复杂的数据库通常需要的时间和精力,开发/测试服务器通常由大型团队共享,这使得变更控制变得困难,并且使得孤立地测试变更变得不可能。
这些共享的开发/测试“狂野西部”的垃圾箱火灾很快变得与生产系统不一致。因此,它们不能被信任为生产的真实表现,并且在它们上面进行的任何开发/测试工作从根本上来说是不可靠的。
第二级:全球失败地狱
模式部署本身尤其危险,因为由于依赖关系,数据库已经成为如此多服务的单点故障。一个被遗忘的 WHERE 子句或消耗性能的游标可能会产生全球性的后果。
如上所述,由于依赖性的复杂性,很少有适合目的的测试环境。此外,为每个依赖的服务建立一个可靠的自动化测试套件是不太可能的,也是不可行的。这使得在执行部署时不可能确信没有任何东西会被破坏。
这些问题是真实而重大的。在凤凰计划中的一次大规模部署失败中,前三个级别被很好地联系在一起。单点故障数据库中的一个巨型表上有一个缺失的索引,它位于一个错综复杂的依赖关系网络的中心。这可能是因为开发/测试数据库与生产不匹配,或者因为它们没有代表性的数据,所以没有发现性能问题。
更新运行得极其缓慢,而且无法取消。他们错过了停机时间,并且由于大量的相关服务,当系统在周一早上没有恢复在线时,他们对数千名内部用户和客户造成了巨大的中断。
我将用一个真实的灾难来支持这个虚构的灾难。我曾经为一家公司工作,该公司有一个支撑其开发环境的狂野西部、单点故障、共享数据库。对于 100 多名开发人员来说,用真实的数据测试他们正在开发的东西是至关重要的。有一次,有人不小心删除了所有的 SQL 登录。整个开发功能以及相关的服务都被封锁了。DBA 花了一周多的时间来修复它,因为与此同时,生产中出现了一个大问题。
所有那些开发人员都闲荡了一个星期。我很紧张地想象着一个超过 100 人的财务部门开发团队的典型年度预算是什么样的,但我想股东们不会喜欢一周的闲扯。
第三关:释放协同地狱
如果您只是部署数据库变更,那就已经够糟了。但是,由于存在依赖性,您可能还需要同时或按特定顺序部署一组相关系统的新版本。整个过程需要精心安排,一个部分的问题可能会危及整个工作。
糟糕的源代码控制实践和共享环境的使用加剧了这个问题。在这种情况下,要发布的变更可能需要从开发/测试数据库中存在的更大的变更集中挑选出来。这一过程通常是手动的,需要更大的复杂性,并且更有可能出错。它也可能在源代码控制中涉及到非常复杂或者不切实际的简单分支模式。这两种情况都会带来复杂性、风险和偏头痛。我在我的个人博客上更详细地谈论了这个话题。
当依赖对象/系统的发布需要仔细编排时,这是一个信号,表明您正遭受代码中的依赖噩梦的组合,以及您的团队/项目管理结构。团队拓扑更详细地讨论了这些问题。
第四级:停机窗口地狱
由于上述所有的依赖性和协调工作,您需要将整个系统脱机一段时间来完成更新。与用户/客户/ “业务”协商停机时间并不容易,所以你被迫在非社交时间做这件事。你可能没有完全清醒,也不太可能得到支持。(写代码的开发人员可能睡着了!)如果你错过了截止日期,后果自负。
更糟糕的是,由于您只能在有限的停机时间内完成工作,因此您面临着额外的压力,需要在每个时间内交付尽可能多的变更。部署变得更大、更复杂、更危险。
这个地狱的一个变种是由关于 100%正常运行时间的天真的业务假设引起的。100%的正常运行时间实际上是不可能的,也是难以想象的昂贵。高层管理人员往往没有意识到这一点。由于沟通不畅,技术人员经常被置于绝望的境地,被荒谬的期望所衡量。这导致了令人沮丧的政治活动和糟糕的决策。
第五关:官僚地狱
考虑到对单一后端数据库的任何更改都可能影响到的人数,以及失败的严重后果,许多利益相关者希望否决部署。工程师不得不花费和他们实际做测试一样多的时间来证明他们已经做了测试。
尽管这种充分的谨慎听起来很明智,但如果实施不当,通常是无效的。(而且几乎总是执行不好)。)高层领导不太可能接受较慢的工作节奏。因此,如果变更的测试/批准措施导致了延迟,那么结果将是大量的“在制品”(WIP)。这导致了更大、甚至更复杂的部署,带来了更多的依赖性和协调问题。
据 Accelerate 报道,来自 DevOps 报告的数据表明,变更咨询委员会平均“比根本没有变更批准流程更糟糕”。尽管初衷是好的,但这些安全措施导致了更复杂、更危险的恶魔部署。
第 6 关:不服从官僚地狱
在按时发布的压力下,并通过个人愿望来完成一项工作,工程师们试图绕过官方流程。中层管理者玩弄政治来保护他们团队的变化。人们篡改真相以避免官僚主义。影子 IT 的出现是因为使用官方认可的系统是令人沮丧的乏味,阻碍了团队按时完成任务的能力。
高级管理人员甚至可能支持和祝贺这样的“创新”,而不承认他们正在促成一个大泥球。这种短期进步通常是以长期表现为代价的。
第七关:疏忽地狱
随着企业越来越不顾一切地在越来越紧张的预算上完成越来越不可能的最后期限,投资任何与狭窄和具体的目标没有直接关系的东西变得越来越困难。起初,这可能不是那么糟糕。它把精力集中在最重要的工作上。然而,这不可避免地导致关键基础设施投资不足。
借用凤凰项目中的“四类工作”术语:这是 IT 人员在完成“业务项目”和“计划外工作”的巨大压力下,没有时间关注“内部项目”甚至例行“变更”(如修补服务器)。
正如我的英国同事可能会说的,高级经理变得“因小失大”。改进工作甚至日常维护都被推迟或废弃。消防成为家常便饭。该团队已经停止了积极的改进工作。他们没时间了。
一个常见的工程后果是短视的欺骗,以避免重构或删除数据库中的任何内容。如果您从不事后清理,数据库部署肯定会更容易。
你不应该删除数据库中的东西,这种想法在科技界很普遍,需要受到挑战。这种黑客认为你的 IT 基础设施中最重要的部分是你过去几年所有过时的或被误导的设计选择的垃圾场。这可能会在短期内为你节省一点时间,但它会咬人。重构是软件开发的重要部分。
我曾经在一家公司工作,那里所有的表格都有难以理解的四字名称。我问这是为什么。显然,几十年前的一些数据库技术有这种限制。他们当前的 RDBMS 并没有受到这种限制,但是他们非常害怕做出改变,以至于一些开发人员仍然坚持四字符约定——甚至对于新表也是如此。如果所有的表在 IDE 中整齐排列,看起来一定很漂亮。
这个数据库对于许多重要的服务来说是至关重要的,但是使用和维护它几乎是不可能的,尤其是对于新员工来说。没有人知道“QACD”或“FFFG”表是干什么用的。这些 5 表连接是不可能破译的。
随着软件和业务需求的发展,如果不重构我们的数据库,我们肯定会产生庞大的数据库,这些数据库很难使用,并且充满了(应该是)废弃的代码和不必要的依赖。
这是一张去…的单程票
第八关:技术性债务奇点
“计划外的工作”吞噬了一切。
随着业务水平不断下降,获得越来越多的技术债务,每个工程师花在救火上的时间比例也在增加。最终,这一比例达到 100%,甚至更高,工程师们为了维持核心运营而加班加点。一个短视的解决办法可能是雇佣更多的人,但那是行不通的。Frederick Brooks 在 1975 年的神话人月中解释了原因!时隔近半个世纪,我不打算在本帖中浪费更多的文字重复他的观点。如果你在 IT 行业工作,并且你没有听说过它,我建议你点击上面的链接。
不幸的是,问题仍在恶化。几乎没有时间做新的东西。我们已经到了深渊的底部。某种变化是不可避免的。要么企业会改变想法,要么他们输给更有能力的竞争对手只是时间问题。
以我的经验来看,每一级都会把人们推向下一级。它们相互加强。对于那些身处困境的人来说,这些问题就像西西弗斯的巨石,只不过这块巨石随着时间的推移变得越来越大、越来越重。达到顶峰,甚至保持目前的势头,一天比一天更不可能。
有些东西必须放弃。
无论你在旅途中的什么地方,认清你的轨迹并在必要时选择不同的道路是至关重要的。等得越久,就越难逃脱。而且,如果那些有权力实施变革的人不改变他们的思维方式,逃跑肯定是不可能的。
下次
下一篇文章(第 2 部分)将是四篇文章中的第一篇,旨在帮助人们重新评估他们在复杂 IT 系统中看待和评估安全的方式。我们将开始想象一个更安全的软件架构、交付过程和开发文化会是什么样子。在继续讨论弹性、健壮性和松散耦合的概念之前,我们将从探索复杂系统中失败的本质开始。
本系列其他文章的链接如下:
批判现有系统:
想象更好的系统:
构建更好的系统:
观看网络研讨会
我们的第一次网络研讨会讨论了松耦合架构如何带来可维护性、创新性和安全性。第二部分讨论了如何将一个成熟的系统从一种架构转换到另一种架构。
数据库开发:想象更好的系统
https://www.youtube.com/embed/oJAbUMZ6bQY
VIDEO
数据库开发:构建更好的系统
https://www.youtube.com/embed/joogIAcqMYo
VIDEO
愉快的部署!
安全模式更新-弹性与稳健的 IT 系统- Octopus 部署
原文:https://octopus.com/blog/safe-schema-updates-2-resilience-vs-robustness
这篇文章是我的安全模式更新系列的第 2 部分。
本系列其他文章的链接如下:
批评现有系统:
想象更好的系统:
构建更好的系统:
在第 1 部分中,我们回顾了与对数据库管理和设计的传统态度相关的常见的恶性循环。在接下来的几篇文章中,我们将探索一些重要的理论概念,这些概念有助于解释为什么最规避风险的组织通常会创建最危险的数据库。我们还会想象一个更安全的数据架构和开发文化会是什么样子。
在我们对为什么一些系统比其他系统更可靠有了更深的理解之后,我们将讨论团队可以做出的一些技术改变,这些改变应该导致显著更好的数据库可靠性和改进的业务成果,以及更人性化的工作条件。
在这篇文章中,我们回顾了软件系统中弹性和健壮性的概念。以下是我最喜欢的关于这种差异的简短表述:
“在过去的十年里,人们对构建具有三个特征的弹性系统进行了大量讨论:
- 低 MTTR 【平均恢复时间】,这是因为对监控良好的故障场景进行了自动补救。
- 由于分布式和冗余环境,在故障期间影响较小。
- 将故障视为系统中正常情况的能力,确保自动和手动补救措施得到良好记录、扎实设计和实施,并集成到正常的日常操作中。
请注意,重点不在于消除故障。没有故障的系统虽然健壮,但也会变得脆弱。当故障发生时,更有可能的情况是响应团队毫无准备,这可能会极大地增加事故的影响。此外,可靠但脆弱的系统可能会导致用户期望比 SLO 【服务水平目标】所指示的更高的可靠性,而这正是服务设计的目标。这意味着,即使没有违反 SLO,当停机发生时,客户也可能会非常不安。"
来自数据库可靠性工程Laine Campbell 和 Charity Majors。
我敢肯定,任何阅读本文的数据库管理员都会想到停机让一些利益相关者恼火的时候,即使从技术上讲,他们的 SLO 从未被违反。(假设他们首先有一个定义明确的 SLO。)
在深入研究坎贝尔和梅杰斯的评论之前,反思复杂系统中失败的本质是有价值的。为此,DevOps、 Safety 2.0 、站点可靠性工程(SRE)和数据库可靠性工程(DRE)运动中的许多人参考了 Richard Cook 的简短学术著作: 复杂系统如何失败 。
但在此之前,有必要区分真正“复杂”的系统和仅仅“复杂”的系统。
复杂系统与复杂系统
复杂的系统很难理解,但是只要有足够的努力和耐心,它们是可以理解的,结果是可以预测的。例如,加密算法很复杂。虽然我知道关于公钥加密如何工作的高级基础知识,但我不会假装熟悉具体的算法或它们的源代码。然而,我意识到,如果有足够的时间和技术能力,阅读文档并准确理解他们如何以可预测的方式加密和解密我们的数据是可能的。
复杂系统是不同的——它们是不可预测的。例如,预测天气就很复杂。我们可以进行各种测量,并将数字输入超级计算机,但即使有最精确的数据和算法,气象预测也永远不能保证。我们的测量和算法只是近似的,还有一些我们还没有完全理解的元素。因此,提前一个月准确预测天气实际上是不可能的。(尤其是我生活的地方——英国!)
根据定义,任何包含人类的系统都是复杂的,而不是复杂的。人类还没有创造出一个能够准确预测人类决策的系统。自由意志要么很难,要么不可能通过计算建模——我已经感觉自己掉进了一个哲学兔子洞。我的观点是,如果你的 it 系统依赖于人类来维护、更新或修复它,你的 IT 系统从定义上来说就是复杂的。这是因为人类是你系统的必要组成部分,人类是复杂的生物。
这还是在我们考虑在第 1 部分中提到的许多缺乏文档记录或理解的依赖关系之前。任何没有 100%准确和最新文档的系统(根据定义)都是复杂的,因为不可能真正理解和预测单一变化的后果。由于我还没有遇到一个完整记录的大规模 IT 系统,我还没有看到一个可以被归类为复杂的,而不是复杂的。
理解复杂系统中失败的本质
希望标题复杂系统如何失败现在传达了一个更具体的想法。库克说的不是可预测的系统。根据定义,他说的是包含不可预测元素的系统,就像大多数(可能是所有)企业级 IT 系统一样。
也就是说,他主要写的是医院(他工作的地方),以及其他高风险、复杂的环境,如航空业或军队。如果您是一名 IT 专业人员,认为失败的后果对您的 IT 系统影响很大,想象一下您是一名飞行员、伞兵或外科医生。
一个行动要成功,很多事情都要做好。手术室需要正确准备,所有必要的设备需要在正确的时间处于正确的位置,一群高素质的专业人员需要作为一个团队履行职责,以高标准,在紧张的条件下,经常解决意想不到的问题,并在进行中做出字面上的生死决定。
当事情进展不顺利时,我想“无可指责的验尸”这句话会让人感觉特别痛苦。我希望这是一个特别重要的实践。
虽然 it 故障很少导致生命损失,但任何有经验的 IT 专业人员都会阅读复杂系统如何失败,并本能地理解它适用于企业 IT,就像它适用于任何医院、客机或战舰一样。
复杂系统如何失败 大约是 10 分钟的阅读,在我看来,它应该是任何计算机科学学生或 it 专业人员的必读之作。在文章中,库克强调了成熟复杂系统中常见的 18 个具体的、可测量的属性:
- 复杂系统本质上是危险系统。
- 复杂系统成功地抵御了失败。
- 灾难需要多次失败,单点失败是不够的。
- 复杂的系统包含潜在的变化的故障组合。
- 复杂系统以降级模式运行。
- 灾难总是近在眼前。
- 事故后归因于“根本原因”从根本上来说是错误的。
- 事后诸葛亮会使事故后对人的表现的评估产生偏差。
- 人类操作员有双重角色:生产者和防止失败的捍卫者。
- 所有从业者的行为都是赌丨博。
- 尖端的行动解决了所有的歧义。
- 人类从业者是复杂系统中适应性强的元素。
- 人类在复杂系统中的专业知识是不断变化的。
- 变化带来了新形式的失败。
- 对“原因”的看法限制了对未来事件防御的有效性。
- 安全是系统的特性,而不是系统组件的特性。
- 人们不断创造安全。
- 无故障操作需要有失败经验。
请记住,这些是特指不可预测的复杂系统,比如假想的数据库及其来自前一篇文章的依赖项。
深呼吸,然后再读一遍那张单子。这些观察对接下来的事情很重要。
我不打算为这些说法辩护或辩解。我也不打算在这方面做更多的阐述——库克自己做得很好,我不会因为重复这个练习而增加太多价值。我会接受它们,并假设它们是真的。如果你还没有准备好迈出这一步,我建议你在继续之前先完整阅读一下 复杂系统如何失败 。
接下来的讨论是关于我们如何能够并且应该对这些关于我们的 IT 系统中故障的本质的观察做出反应。
为什么弹性 IT 系统比强健的 IT 系统更安全
DevOps 运动通常被认为是 20 世纪 70 年代和 80 年代日本汽车制造业产生的精益思想的演变。然而,可以说,尤其是在最近几年,DevOps 将同样多的遗产归功于由库克及其同时代人开创的安全 2.0 运动。
与汽车制造和供应链管理不同,安全 2.0 是在 20 世纪 90 年代和 21 世纪初在医疗保健领域开发的。安全 2.0 是一种管理哲学,旨在培养库克在复杂系统如何失败中描述的各种复杂系统中的“安全文化”。
是时候给出另一个定义了,这个定义无耻地抄袭了其他人的优秀作品:
大多数人认为安全是指没有事故和事件(或可接受的风险水平)。从这个角度来看,我们称之为安全-I,安全被定义为一种尽可能少出错的状态。“安全第一”的方法假设事情出错是因为特定组件的可识别的故障或失灵:技术、程序、人类工人以及他们所在的组织。因此,人类——单独或集体行动——主要被视为一种责任或危险,主要是因为他们是这些因素中最易变的。Safety-I 中事故调查的目的是确定不良后果的原因和促成因素,而风险评估的目的是确定其可能性。安全管理的原则是当某件事情发生或被归类为不可接受的风险时做出反应,通常通过试图消除原因或改善障碍,或两者兼而有之。
[...]
至关重要的是,安全第一的观点并没有停下来考虑为什么人类的表现几乎总是正确的。事情不顺利,不是因为人们按照他们应该做的去做,而是因为人们能够并且确实调整他们的行为来适应工作环境。随着系统不断发展并引入更多的复杂性,这些调整对于保持可接受的性能变得越来越重要。因此,安全改进的挑战是理解这些调整——换句话说,理解尽管不确定性、模糊性和目标冲突在复杂的工作环境中普遍存在,但绩效通常是如何进行的。尽管事情进展顺利非常重要,但传统的安全管理很少关注这一点。
因此,安全管理应从确保“尽可能少的事情出错”转向确保“尽可能多的事情做对”。我们称这种观点为安全 II;它关系到系统在不同条件下成功的能力。Safety-II 方法假设日常表现的可变性提供了对变化的条件作出反应所需的适应性,因此是事情顺利进行的原因。因此,人被视为系统灵活性和复原力所必需的资源。在 Safety-II 中,调查的目的变成了理解事情通常是如何变好的,因为这是解释事情偶尔变坏的基础。风险评估试图了解绩效可变性变得难以或无法监控的情况。安全管理原则是促进日常工作,预测发展和事件,并保持适应能力,以有效应对不可避免的意外(Finkel 2011)。
霍尔内格尔 e,戴斯 R.L .和布莱斯维特 j.《从安全-I 到安全-II:白皮书》。弹性医疗保健网:由南丹麦大学、美国佛罗里达大学和澳大利亚麦考瑞大学同时出版。此处可在线获得。
我从中得到的最大教训是,安全是你积极构建和完善的东西,而不是捕捉错误的看门人。增加创造安全的系统比试图抓住所有的错误更健康。毕竟,如果有足够的时间,期望你能抓住所有问题是不切实际的。
弹性 IT 系统的真实例子
实际上,那看起来像什么?嗯,它可能看起来像很多东西。网飞经常被描绘成 IT 弹性工程的典型代表,所以让我们来讨论一下他们是怎么做的。
2011 年 4 月 21 日, AWS 在弗吉尼亚州(US-East-1)地区遭遇重大停电。这次故障导致许多主要网站瘫痪,包括 Reddit、Hootsuite、Quora 和 Windows 脸书应用。然而,网飞经受住了这场风暴。他们的用户几乎没有注意到。
之后,网飞分享了一篇博客文章,解释了他们使用的一些技巧,这些技巧让他们在很多人失败的时候仍然在线。顺便说一句,2011 年并不是一次性的。网飞在 2015 又做了一次,在 2017 又做了一次。他们现在拥有在网络上生产最安全和最有弹性的系统的声誉。
为什么只有网飞能够在这场风暴中幸存下来?用他们自己的话来说(来自上面的博客),他们的“系统是专门为这类故障设计的”。他们认识到库克教给我们的东西:复杂系统本质上是危险的系统,灾难总是迫在眉睫,他们在设计系统时考虑了这些事实。
他们的系统对故障有很强的防御,有许多聪明的自动故障转移和冗余特性。他们还明确设计了在降级模式下运行的方式。例如,他们认识到为用户提供推荐是有价值的,但不是必不可少的,他们意识到这在计算上是昂贵的。因此,当系统陷入困境时,他们可以自动关闭该功能(以及许多其他“值得拥有”的功能),以保持核心运营在线。
从根本上来说,网飞以及任何其他弹性系统都认识到,在任何时候,它们都将包含潜在的各种不同的故障。他们认识到,所有的系统和依赖关系都可能在没有通知的情况下,以不可预测的方式发生故障,而不是仅仅设计专注于避免故障这一不可能任务的健壮系统。因此,最重要的是所有系统都被设计为保持在线,尽管可能处于降级模式,即使面对一个或多个故障,无论是内部基础设施/代码故障,还是依赖性。
此外,由于无故障操作需要有故障经验,因此有必要练习故障。而且有必要在不方便的时候练习。这就是为什么网飞故意随意破坏自己的服务。如果你从未听说过“混沌猴子”或“猿猴军”,你应该从创造它的人那里了解一下。
最后,正如 Cook 所明确指出的,操作员是系统不可分割的一部分。网飞的博客文章坚持软件的细节,但是人需要被重视,他们也需要被培训,保护和保持安全——就像系统的任何其他部分一样。
更重要的是,由于人类从业者是复杂系统的适应性元素,他们是投资的对象。最重要的是,我们明确认识到所有从业者的行为都是赌丨博 , 事后对人的表现的事后评估存在偏见,并且事故后对“根本原因”的归因从根本上是错误的。因此,在安全 IT 系统的开发中,指责和找特定个人的替罪羊是没有用的。如果人们犯了错误,他们会得到培训和支持,而不是绩效管理或遣散费。如果一个人会犯这个错误,其他人肯定会重复。与其试图将个人从系统中移除,不如将安全措施添加到系统中,以保护个人在未来不会重复类似的错误。毕竟,由于无故障操作需要故障经验,该个人可能已经学到了宝贵的经验,并且可能处于独特的位置,为这种测试/检查的发展做出贡献。
正如 IBM 首席执行官汤姆·沃森(1956-71)所言,当一位年轻的高管问他是否会因为一个代价高昂的错误而被解雇时:“完全不会,年轻人,我们刚刚花了几百万美元来教育你。”
实际上,这对我的数据库意味着什么?
首先,我们需要认识到变化会带来新形式的失败。这并不是说我们应该停止改变。如果有的话,我们应该更经常地做出改变!但是,我们应该设计我们的变更过程,以便尽可能有效地测试这些变更,并且当出现错误时,它们可以很容易地恢复。对于数据库来说,这是一项极具挑战性的任务,我们将在下一篇关于持续集成的文章中详细讨论,在本系列文章的末尾,我们将讨论配置环境和接近零停机时间部署的模式。
我们还应该设计有效的防火隔离带。一次错误的数据库更新不应该导致级联故障。需要控制故障,以便将其影响降至最低。这意味着我们需要避免单一的共享数据库,我们应该尝试将它们分割成能够独立运行的更小、更简单的系统,即使它们需要暂时以某种降级的能力运行。在后面关于松耦合和扼杀者模式的文章中,我们会更仔细地研究这个问题。
这些变化的结果应该是显著降低与任何数据库更新相关的风险。这种降低的风险应该减少对过度官僚化的变更管理过程的需求,允许更频繁地交付更小、更安全的更新,逆转上一篇文章中讨论的螺旋式下降,并导致一个持续改进的时期。
下次
在下一篇文章(第 3 部分)中,我们将讨论持续集成(CI)。具体来说,我们将讨论它是如何被误解的,误解可能造成的伤害,以及为任何 it 系统(包括任何关系数据库)采用“适当的”CI 的好处。
以下是本系列其他文章的链接。
批评现有系统:
想象更好的系统:
打造更好的系统:
观看网络研讨会
我们的第一次网络研讨会讨论了松耦合架构如何带来可维护性、创新性和安全性。第二部分讨论了如何将一个成熟的系统从一种架构转换到另一种架构。
数据库开发:想象更好的系统
https://www.youtube.com/embed/oJAbUMZ6bQY
VIDEO
数据库开发:构建更好的系统
https://www.youtube.com/embed/joogIAcqMYo
VIDEO
愉快的部署!
安全模式更新-持续集成被误解- Octopus 部署
原文:https://octopus.com/blog/safe-schema-updates-3-ci-is-misunderstood
这篇博文是我的安全模式更新系列的第 3 部分。本系列其他文章的链接如下:
批评现有系统:
想象更好的系统:
构建更好的系统:
许多人听到“持续集成” (CI)会立刻想到构建服务器、测试框架和自动化。Jenkins、JUnit 和 GitHub 的行为很棒,但是任何声称使用这些工具为他们赢得 CI 徽章的人都没有抓住要点。
如果持续集成是关于持续构建的,我们会称之为持续构建。
当然,测试和验证是 CI 的基础。重要的是要确保任何已经集成到我们的源代码控制主分支中的代码都已经通过了所有适当和必要的检查。
定期的、自动化的构建是相当没有争议的,并且被普遍接受。正是这一点涉及到构建服务器和测试框架。但是那些构建脚本和那些小小的、令人欣慰的绿色标记是手段,而不是目的。它们只是一个更大想法的一小部分。
为什么我们要运行自动化构建和测试?
大多数人可能会用“捕捉错误”或“快速反馈”这样的回答来回应。是的,这些都很棒。然而,这仍然只是故事的一部分。这些答案还是没有抓住根本点。
连续整合约整合。
从字面上看,就这么简单。(选词是有意的,而且准确。)
大多数人错过了摆在他们面前的答案。CI 是关于减少在制品(WIP)的数量和避免大的合并。它是将广泛的目标分解成更小的(但可交付的)任务,这些任务可以独立开发、测试和集成。一旦集成了变更,就不需要从源代码控制中“挑选”这个变更或那个更新来进行部署,因为整个集成的变更集已经被作为一个整体进行了验证。一旦变更被集成,它们应该以最小的风险发布。
这些构建纯粹是为了验证我们的常规集成工作。毕竟,如果我们每三个月才集成一次代码,也许一周的测试阶段并不痛苦?然而,如果我们计划一天集成多次,一周的测试阶段是不实际的。这些构建的存在不仅仅是为了捕捉 bugs 它们的存在是为了支持对主源代码控制分支的可部署变更的持续集成——一天多次。
这意味着任何真正的持续集成实践者,也将实践某种形式的基于主干的开发。
有些人会反对基于主干开发的想法。他们可能希望保持不同功能/工作项目/标签的交付相互隔离,原因是业务目标冲突、时间安排或协调等。例如:
- "这项功能需要在那项功能之后推出."
- "这个版本需要与一些营销发布/合同期限相协调."
- “我们需要快速跟踪这个修补程序。”
- “这个可怕的大功能还没有准备好部署。”
这就是为什么持续集成从根本上来说是一个项目管理问题,为什么那些花哨的构建工具只是一个实现细节,以及许多其他技术和管理实践。
我们需要以这样一种方式管理我们的开发、测试和部署工作,使得上述问题消失。我们需要这样做,因为真正的持续集成的好处,比仅仅持续构建的好处要小得多。
为什么我们需要持续的集成?
你曾经在一个为期 12 个月的项目中工作过,而第 11 个月是留给“整合”的吗?你觉得怎么样?我猜事情不太顺利。
集成阶段通常是痛苦的,因为我们所有人(特别是项目经理,显然)都倾向于低估关于所有子系统应该如何集成的坏假设的数量。不管我们在 Visio 或 OmniGraffle 方面有多有才华,这些微妙的复杂性往往不会在干净的架构图中引用。
这导致发现比预期更多的问题。这很烦人,但这不是世界末日。真正的问题是这些错误是基于 6 个月前写的代码。
我们现在处理的是“承重虫”。
每个问题现在都变得更加棘手,因为彻底解决问题需要复杂的重构,并且可能需要从根本上重新思考这个位应该如何与那个位一起工作。由于依赖性,我们的负载 bug 修复可能会产生意想不到的后果,每个后果都需要时间来理解和修复。不幸的是,我们没有时间或预算来打开这个潘多拉魔盒,所以我们解决了这个问题,在快速破解和管道胶带的基础上堆积了难闻的变通方法。
这一切都证明了在制品(WIP)是负债,而不是资产。是一个沉没成本。如果你的 8 人团队在某个复杂的新特性上投入了 6 个月的开发时间,那就是 4 个开发人员年的投资,可能是 6 位数的总和,这是赌在你没有问题地集成代码的能力上。
开发分支和主开发分支之间的差异越大,合并就越复杂,遇到讨厌的负载 bug 的机会就越大。这将花费时间和金钱来修复,以及(很可能)降低代码的整体质量。此外,并发 WIP 越多,管理复杂的分支模式、不一致的开发环境、令人头疼的合并和危险的大爆炸式部署所浪费的管理开销就越大。相对于整合的规模,隐性成本以非线性方式增加。
除了成本增加之外,大型集成还会带来巨大的风险。集成越大,集成失败的可能性就越大,或者该特性就越有可能被放弃。或者,它可能会吸收资源,因为沉没成本谬误或人类骄傲会影响人们推动合并,尽管存在可笑的风险和/或成本。凤凰计划就是一个典型的例子。
快速反馈很好,但是自动化构建不会突出集成问题,直到并发开发任务被集成。因此,保持小规模和频繁的集成对于交付可靠的 IT 系统至关重要。
除了构建,持续集成还需要什么?
简单地说,优先考虑合并而不是分离的开发过程会是什么样子?
当然,自动化构建和测试是必要的。然而,CI 从业者面临的问题远不止构建这么简单。例如:
- 我们如何将一个为期 12 个月的项目分解成无数个小时/天的任务,每个任务都可以单独完成?
- 我们如何平衡长期愿景、迭代学习和更敏捷的优先排序和决策?
- 那些需要一天以上才能交付的棘手的大变更,以及那些不能在不完整的状态下交付的变更,又该如何处理呢?
- 当一个子模块的依赖项被频繁更新而没有警告时,我们如何管理它的可靠性?(无需求助于痛苦的人工审查过程。)
- 如果客户/用户仍然计划年度预算、季度发布和不频繁的软件更新,我们如何管理与他们的关系?高管和股东呢?
- 我们如何管理业务、法律或合同义务要求不频繁、大量发布的情况?
- 当我们一天多次发布变更时,一个合适的评审过程是什么样的?
- 我们如何大规模实践持续集成?在一天之内挤满了多次提交变更的开发人员的超大型 IT 职能部门中,我们如何管理这一点呢?
为了解决这些问题,我们需要仔细考虑我们的软件架构和我们管理依赖的方式。我们需要集成代码和频繁部署变更的方法,同时保留按照不同的时间表向用户发布/展示这些更新的能力,该时间表针对商业目标而不是纯粹的工程问题进行了优化。我们需要官僚化的过程,这种过程基于许多短交付周期的小变更的频繁交付,而不是长交付周期的不频繁的大变更。我们需要确保开发和生产之间的差异始终保持较小。
这是一个包含一大堆想法的简短段落。在这篇文章中,我不打算一一解开。在接下来的文章中,你可以看到处理这些挑战的技术。这篇文章的重点是强调持续构建只是持续集成冰山的一角。持续集成非常重要。
现在,猜度地说:
- 如果您正在您的功能分支上运行自动化构建,但是您没有将您的大功能与主分支合并,因为它还没有准备好…对不起,那不是 CI。
- 如果您在您的主分支上运行自动化构建,但是当涉及到部署时,您只是挑选这个提交或那个文件进行部署…对不起,这不是 CI。
- 如果您正在运行自动化构建,但是在您的主要源代码控制分支和产品之间有一个巨大的差异…对不起,那不是 CI。
- 如果你的开发环境和你的生产环境大相径庭,也许有不同版本的依赖或者堆积如山的废弃 WIP…对不起,那不是 CI。
所有这些与安全数据库更新有什么关系?
这个关于 CI 的小抱怨听起来有点离题,但是在接下来的两篇文章中深入讨论松耦合之前,我们正确理解 CI 是很重要的。当然,更小的系统更容易测试,但是不仅仅是这样,如果清楚地理解弹性和持续集成的更广泛的意义和价值,进入我们关于松散耦合和领域驱动开发(DDD)的讨论将会很有用。
此外,DDD 从根本上是关于打破数据模型,数据库通常是许多其他系统的共享依赖。松散耦合要求与独立服务相关的数据分离。因此,当我们开始讨论根据 CI 考虑拆分数据库的价值时,理解完整意义上的持续集成是有用的,而不是肤浅但普遍的“持续构建”误解。
下次
在接下来的两篇文章中,我们将转换话题,从技术和人的角度讨论数据库架构。我们将讨论松散耦合和领域驱动开发,以及这些原则如何帮助我们实践持续集成并产生安全、有弹性的 IT 系统。
本系列其他文章的链接如下:
批评现有系统:
想象更好的系统:
打造更好的系统:
观看网络研讨会
我们的第一次网络研讨会讨论了松耦合架构如何带来可维护性、创新性和安全性。第二部分讨论了如何将一个成熟的系统从一种架构转换到另一种架构。
数据库开发:想象更好的系统
https://www.youtube.com/embed/oJAbUMZ6bQY
VIDEO
数据库开发:构建更好的系统
https://www.youtube.com/embed/joogIAcqMYo
VIDEO
愉快的部署!
安全模式更新——松散耦合缓解技术问题——Octopus 部署
原文:https://octopus.com/blog/safe-schema-updates-4-loose-coupling-mitigates-tech-problems
这篇博文是我的安全模式更新系列的第 4 部分。
本系列其他文章的链接如下:
批评现有系统:
想象更好的系统:
构建更好的系统:
在本系列的第 2 部分中,我们讨论了弹性和健壮性的概念。我们谈到了设计这样一个系统的价值,在这个系统中,失败是可以被承认的,被遏制的,并且是可以快速解决的。
在第 3 部分中,我们探索了自动化构建之外的“持续集成”的真正含义。中心思想是减少进行中的工作量和复杂集成或合并的需要。
在这篇文章中,我们设想一个软件和数据库架构,它本质上支持弹性系统的开发和维护。这种架构也是持续集成的一个很好的使能器,因为它显著地减少了任何一个组件上的并发工作量。因此,这种架构自然会产生更安全的系统,更易于开发、测试、部署和维护。
这篇文章将关注技术细节。然而,可以说,采用这种架构的人类和文化后果具有同等或更大的意义。我将在这篇文章中略去人的方面,不是因为我认为它们不重要,而是因为我觉得它们是如此的基本,以至于它们值得拥有自己的专用文章。您可以在第 5 部分中期待这一点。
我也不会谈论与构建或重构现有系统相关的实际问题。现在我们只是想象一下 better 可能是什么样子,我们将在以后的文章中考虑如何实现它。
在本系列的第 1 部分中,我描述了一个典型的整体数据库的创建,它支持无数的关键服务。如果您已经使用数据库有一段时间了,那么您可能以前处理过其中的一个。我不会在这里重复它们引起的问题,所以如果你想回顾一下,这将是一个很好的点来重新阅读第 1 部分。可以说,定期可靠地向单一系统交付更新是很困难的。当这些系统失败时,它们经常失败,这通常是一场灾难。
松散耦合的数据库,实现更快、更安全的交付
如果每个服务管理自己的数据会怎么样?
例如,每当我们假想的“支持”服务需要从我们同样假想的“销售”服务访问数据时,服务可以使用一些 API 或其他通信层进行通信,而不是直接调用数据库。这将使负责每个服务的团队对他们自己的数据负责,只要他们的 API 仍然可用。
每个服务的数据库管理问题都是相互隔离的。大型数据集被分割成更小、更易于管理的数据集。任何一个服务发布的复杂性都大大降低了,因为依赖性管理变得简单多了。为每个子系统需要支持的任何 API 调用创建简单的测试是相对容易的,而不需要关心哪些依赖系统实际上在使用它们。
当然,API 喋喋不休可能会增加,这是一个需要管理的新问题。然而,通过代码基础设施、自动伸缩和混沌工程等模式,这些问题比与单一的单点故障数据库重构相关的挑战更容易管理,也更安全。
领域驱动的设计和有界上下文
一些读者会认为这种架构不切实际。例如,销售和支持系统都需要访问相似的客户数据。如果他们需要访问相同的数据,如何分割数据库?
对于这些人,我建议花一个晚上的时间阅读沃恩·弗农的领域驱动设计精华,这是埃里克·埃文斯更彻底的领域驱动设计的一个简短且更容易理解的版本。
Vernon 和 Evans 描述了一个数据建模过程,该过程将有界上下文的概念放在前面和中心。事实上,我上面的销售和支持示例,以及下面的图片,都摘自 Martin Fowler 的优秀 BoundedContext 博客文章:
图片来源:【https://martinfowler.com/bliki/BoundedContext.html】T4
在这个例子中,负责销售和支持应用程序的团队已经就产品和客户的无处不在的数据结构达成一致,并且他们已经通过 API 将他们的数据提供给其他服务。例如,销售和支持数据库可能都有相同的客户和产品表,其中一些唯一的字段用于标识匹配项。API 将有一套约定的方法来检索数据,这些方法将通过构建/部署管道进行例行测试。
较小的系统更容易供应、开发和测试
现在销售和支持数据库不再直接交互,许多工程挑战变得更容易解决。
当支持系统的开发人员希望提供一个开发环境来完成一项任务时,他们不需要提供整个全局系统和所有的依赖项——他们需要的只是支持系统。
如果他们需要来自销售系统的数据,在大多数情况下可能会被嘲笑,但是如果两个系统都需要,那就不成问题了。
这两个服务可能只是众多服务中的两个,所以与部署整个 monolith 相比,开发环境的总体规模仍然大大减小了。
这种较小的开发环境部署起来更快、更便宜。数据屏蔽、隐私和存储问题显著减少。系统的整体复杂性更容易管理,并且对其他系统的依赖性是明确的和可测试的。
松散耦合支持持续集成
正如第 3 部分中所讨论的,持续集成(CI)告诉我们,我们应该优先完成和集成/部署现有的在制品(WIP ),而不是创建新的 WIP。这适用于任何地方存在的、尚未成功合并、部署到生产和验证的任何和所有开发工作。如果不优先考虑合并而不是分离,我们最终会有越来越多的在制品,这有着巨大的隐性成本。
当我们有一个大型的、单一的系统,也许有 10 多个团队的 100 个开发人员,我们的 CI 问题是很严重的。在同一个代码库上并行执行如此多的任务,很难做到面面俱到。这是我们经常看到复杂的分支计划和混乱的开发环境的地方,包含了一大堆半成品或者已经废弃很久的开发和测试代码。
然而,如果这十个团队通常在独立的、松散耦合的服务上工作,每个团队都在他们自己的存储库中,那么与管理任何给定服务的变更相关联的复杂性就会大大降低。每个服务在任何时候都只会有少量正在开发的并发任务。
举例来说,这种复杂程度实际上可以在短期的日常团队活动中理解和管理。只要所有必需的 API 仍然可用,分支计划就会变得更简单,发布协调问题也会消失。
对于更松散耦合的系统,开发人员可以更容易地持续集成他们的工作。这大大减少了与 WIP、环境漂移、分支机构地狱、政治和项目管理开销相关的挑战。
此外,松散耦合的系统以可管理的方式扩展。如果十几个团队在同一个整体后端工作的想法让你害怕,想象一下与亚马逊或谷歌规模系统相关的挑战。
如果您预计您的系统可能会增长(只要公司还在经营,他们的关键系统就会增长),那么从一开始就考虑松散耦合来设计您的系统是明智的。
松散耦合会产生防火间隙
给足够的时间,一切都会破碎。对于整体系统来说是这样,对于松散耦合的服务来说也是这样。失败仍然会发生,但是松散耦合的系统更有弹性:数据鸡蛋不再都放在同一个篮子里。
每个服务将独立托管其数据。如果流星击中托管销售服务的数据中心,支持服务可能不会受到影响。通过这种方式,可以更好地隔离故障,并且受影响的服务更小、更简单、更容易修复。所有这些都是在没有放慢发展速度的情况下实现的。其实现在开发更简单,更安全。
当然,我们需要遵循某些规则。
每项服务都需要设计为在相关服务不可用时正常失效/降级。例如,支持系统应该被设计为当销售服务不可用时,它仍然可以运行,可能具有降级的功能。也许支持团队可能会失去一些功能和销售数据的可见性,但是他们仍然能够进行核心支持操作。
作为开发人员,测试依赖关系变得至关重要。如果您依赖于对另一个服务的 API 调用,您应该确保有一个测试来验证这种依赖性,并且您应该将系统设计为在 API 调用失败时正常失败并记录一个错误。
对于 monolith 来说,这种调整不当的升级脚本可能会导致全局中断。通过松耦合,我们只关闭了销售系统,减少了失败的影响。此外,由于松散耦合的销售系统比旧的整体系统要小得多,因此很容易快速恢复在线。
下次
在这篇文章中,我们设想了一个更加松散耦合的架构。这种建筑更能抵御失败,因为它创造了自然的防火屏障。此外,较小的服务通常更易于开发和恢复。
此外,系统更精细的特性使得管理不同的工作流更加容易。任何给定服务的并发工作流的显著减少减少了项目管理/发布管理/分支开销。
单独来看,这些点将导致显著更安全的数据库版本。然而,这些好处是人为因素造成的。在下一篇文章(文章 5)中,我们将更详细地探讨这些人为因素。
本系列其他文章的链接如下:
批评现有系统:
想象更好的系统:
打造更好的系统:
观看网络研讨会
我们的第一次网络研讨会讨论了松耦合架构如何带来可维护性、创新性和安全性。第二部分讨论了如何将一个成熟的系统从一种架构转换到另一种架构。
数据库开发:想象更好的系统
https://www.youtube.com/embed/oJAbUMZ6bQY
VIDEO
数据库开发:构建更好的系统
https://www.youtube.com/embed/joogIAcqMYo
VIDEO
愉快的部署!
安全的模式更新——松散耦合减轻了人为问题——Octopus 部署
原文:https://octopus.com/blog/safe-schema-updates-5-loose-coupling-mitigates-human-problems
这篇博文是我的安全模式更新系列的第 5 部分。
本系列其他文章的链接如下:
批评现有系统:
想象更好的系统:
构建更好的系统:
“不管是什么问题,都是人的问题。” 杰拉尔德·温伯格
在前一篇文章(第 4 部分)中,我们关注了与软件/数据库架构相关的技术问题。在这篇文章中,我们关注人类的问题。
康威定律规定,组织只能设计反映其内部沟通模式的系统。正如 Eric S Raymond 在《新黑客词典》、中所言,“如果你有四个团队在开发一个编译器,你会得到一个 4 遍编译器”。
我们的团队结构显著地影响着我们的软件架构…但是反过来也是正确的:复杂的架构会滋生痛苦和有害的官僚主义和工作文化。如果任其发展,它们可能会形成恶性循环。
当关键但复杂的整体出了问题,并且很难确定原因和结果时,人们通常会迅速指责并掩盖自己的过失。如果我们对自己诚实,我们可能都会想起自己采取自卫行动的时候,即使我们不知道自己是否有错。我们都是有价值但不完美的人类,我们都有自我保护的本能。
帕特里克·兰西奥尼在团队的五大功能障碍中谈到了冲突、承诺和责任。他的第四个功能障碍明确指出逃避责任不利于有效的团队合作:
图片来源:https://medium . com/task world-blog/lencionis-5-dysfunctions-of-a-team-330 d58 B2 CD 81
在本系列的第 2 部分中,作为我们对可靠性和健壮性讨论的一部分,我们讨论了 Richard Cook 对复杂系统如何失败的深刻研究。
特别是,我们研究了复杂系统中关于责任或责备以及“根本原因分析”的传统观点的问题,在复杂系统中,故障通常是由许多看似不相关的因素引起的。这些挑战是不可避免的,但我们可以通过设计更容易观察、关系、依赖和期望定义更清晰的系统来帮助自己。
当我们将复杂的整体架构分解成更小、更松散耦合的系统时,这有助于每个人对自己的子系统负责。这些子系统应设计为独立于其他系统运行,这样,如果一个系统出现故障,故障通常会被隔离到该系统。没有必要浪费时间调查更广泛的系统或指责。一个合适的工程师团队可以将他们的注意力直接集中在行为不当的子系统上,而其他人可以监控其他子系统,以确保他们按照预期应对中断。
产生反效果的政治机会少得多,人们更容易专注于解决问题、学习和改进。每个人都受益:股东、经理和工程师都一样。
服务水平目标(SLO)和停机时间预算
DevOps 运动拒绝了开发团队应该以交付速度为目标,而独立的运营团队以稳定性为目标的想法。这些团队的目标之间的冲突不会激励合作或系统思考。
通过将大块分割成独立的服务,它允许更小的、跨职能的团队负责交付速度和他们自己服务的稳定性。当速度/稳定性目标之间存在冲突时,可以由最了解客户需求和技术细节的人来权衡,他们共同拥有速度和稳定性目标。
为了支持这一点,可以给每个服务一组特定的公共服务水平目标(SLO)。下游服务将会意识到这些 SLO,并可以根据这些 SLO 有针对性地设计自己的服务。例如,如果销售服务(来自前一篇文章)承诺 99.9%的正常运行时间,那么支持服务将不会被设计为期望 99.99%。
但是这些 SLO 是双向的。99.9%的正常运行时间,也意味着 0.1%的停机时间(大约 43 分钟/月)。了解这些限制对于设计任何 HA/DR 策略和规划部署/风险管理都是必要的。通过使用具体的数字,我们可以做出实际的、明智的决定。
“停机时间预算”为各方设定了现实的期望,无论他们是相关系统的开发者,还是最终用户。明确的 SLO 鼓励团队计划维护和设计部署模式,将停机时间保持在可接受和可行的范围内。这些停机时间预算还允许团队平衡他们对创新/风险的偏好和他们的稳定性责任。
例如,如果一个团队在一个月的第一周内用完了大部分的月度停机预算,那么现在可能是时候将他们的精力集中在与提高稳定性相关的任务上,而不是推进有风险的新功能。同样,如果他们的业绩经常比他们的 SLO 高出几个数量级,也许他们应该优先考虑下一个面向客户的特性,而不是进一步的稳定性投资。
通过发布每个服务的 SLO 和性能度量,团队对他们自己的工作负责。如果经常错过特定的 SLO,这应该会引发一场关于业务如何支持团队改进的对话。也许 SLO 是不合理的?也许团队需要一点支持?无论哪种情况,它都允许组织将精力和投资集中在最需要的地方。
有界上下文支持有限自治
在理想的世界中,评审是必要的,评审应该由能够在代码还新鲜的时候阅读代码、理解结果并提供有见地的改进建议的人来执行。就法规遵从性而言,审查应(至少)由对系统有足够了解的工程师进行,以发现任何潜在的欺诈性变更。(注意:这通常需要大量的技术技能以及一些使用代码库的实践经验。)毕竟,防止欺诈是萨班斯-奥克斯利法案的全部目的。
由于持续集成教导我们优先考虑合并而不是分离(参见第 3 部分),评审者也应该优先考虑代码评审(合并)而不是他们当前正在进行的任何新的变更(分离)。理想情况下,在提交变更以供批准的几分钟内,审阅者应该暂停他们正在做的任何事情并审阅变更。
没错。应该期望评审人员从任何新的开发工作中休息一下,以便优先考虑“开发完成”工作的合并和部署。
从这个角度来看,每周召开变革顾问委员会(CAB)会议的想法听起来应该是有害且低效的。任何出租车成员都大胆地认为任何 WIP 都应该无所事事,腐烂一周,以适应他们自己的时间表,这应该被认为是极其傲慢的。他们还在做什么比合并一周的腐败分歧更重要的事情呢?交付可靠的更新是字面上的工作重点——不是一项可以推迟到周末的任务。(我去年已经更详细地讨论了为什么变更顾问委员会不起作用,所以我在这里不再赘述。)
很明显,高级经理不应该审查部署。每当有人提交新的拉取请求时,他们可能无法放下他们正在处理的任何事情。他们的角色是支持健康的评审实践,而不是亲自进行评审。
幸运的是,通过分离我们的系统,任何特定系统的利益相关者的数量都会显著减少。为什么销售系统的工程师应该关心支持系统的发布(反之亦然)?这只会破坏他们的注意力,分散他们对自己的子系统所负责的工作的注意力。API 依赖应该在测试框架中进行编码。如果一个依赖被破坏了,那应该被自动标记,如果一个测试丢失了,那是依赖它的团队的责任。
拥有任何给定服务的速度和稳定性目标的人应该生活在团队内部。他们不需要 CAB 或任何其他外部批准者。他们是实现目标最有效的人。
通过将审查引入给定服务的跨职能团队,我们可以使审查更加及时。这并不一定意味着它们是由开发人员自己完成的,或者甚至是由具有相同角色的人完成的。毕竟,这个团队是跨职能的。也许是高级开发人员或基础设施工程师进行审查?对于数据库变更,可能是数据库专家,甚至是数据库管理员?
修订后的审核流程将更有可能发现错误和/或欺诈性变更,并保持任何“职责分离”要求。如果说有什么不同的话,那就是比传统的基于出租车的审批做法更符合我迄今为止遇到的任何立法,并且它支持高质量 it 服务的交付,而不是阻碍它。它还支持负责交付和维护服务的团队内部的协作和知识共享。
人们对放弃 CAB 的一个常见担忧是,高级管理人员或安全团队可能会失去控制、权威或监督。这种看法是没有根据的。对于那些关心的人,我鼓励你们想象这样一个世界:每个服务的状态,就其 SLO 而言,都可以在一个实时仪表板上看到。
任何经常错过 SLO 的服务都会向传统上坐在出租车上的人强调。这将是一个开始对话的线索,讨论为什么会遗漏 SLO,以及可以做些什么来提供帮助。这将允许所有那些非常关心最小化风险的高级管理人员将他们的注意力和精力集中在他们能够在提高安全性方面提供最大价值的人员和服务上。
最后,对于那些反对根据更高层次的商业需求或营销目标来安排发布时间或向客户/用户透露更新的读者,我恐怕要放弃这种想法了。您将在第 7 部分:接近零停机时间的部署中找到该异议的答案。
来自高层的坚定的文化领导不是可有可无的
起初,采用公开报道的 SLO 会让工程师感到脆弱。当事情不顺利时,坦诚面对失败需要勇气。如果他们已经在政治有毒的工作文化中工作,尤其如此,在这种文化中,失败通常会导致替罪羊和负面影响。如果我搞砸了大家都能看到,我会不会一出错就被开除?我的意思是,我们都会犯错误,经常令人尴尬。我肯定迟早会被解雇,这是不可避免的。
我想我最好在我的简历上下点功夫,试探一下。
(有趣的事实:当我开始这个系列的时候,我以为会是单篇博文!然后它变成了一个三部曲…现在它变得更大了,我已经远远落后于我最初的出版计划了!我非常感谢八达通公司令人敬畏的人们的耐心和理解!)
向有限责任的转变会带来巨大的好处,但前提是团队感到安全和支持,尤其是失败了。这是一件只有通过明确、诚实和真诚的指导才能实现的事情——来自高层的指导。如果你想得到一点帮助来想象那会是什么样子,戴上耳机,然后点按“播放”:
创建一个安全的架构,从技术上来说是安全的,对于交付可靠的服务来说是至关重要的。然而,除非你也有鼓舞人心的领导者,他们培养一种重视所有团队成员心理安全的文化,否则这一点都没用。
下次
我们从传统的数据库交付地狱,以及强化导致技术债务奇点的恶性循环的各种因素开始了这个系列。然后,我们继续设想一种更好的方式,涵盖弹性、持续集成和松散耦合的理论基础。然而,当我们进入一些细节想象一个更好的系统可能是什么样子的时候,我还没有写任何关于如何从一个架构过渡到另一个架构的技术细节。
在我的下一篇文章(第 6 部分)中,我们将改变思路。我们将从理论转向实践,三篇技术文章中的第一篇旨在帮助您开发三种重要的能力,支持您向松耦合架构的过渡。
在这三篇文章的第一篇中,我们将讨论用可用的和符合法规的开发/测试环境的自助服务和按需供应来替换共享的开发/测试实例。
在那之后,我们将用两篇讨论接近零停机时间的发布和将大块业务分解成小块业务的安全过程的文章来结束这个系列。
虽然这些功能中的每一项都很有价值,但只有将这三项结合起来,每项功能的优势才会成倍增加。
本系列其他文章的链接如下:
批评现有系统:
想象更好的系统:
打造更好的系统:
观看网络研讨会
我们的第一次网络研讨会讨论了松耦合架构如何带来可维护性、创新性和安全性。第二部分讨论了如何将一个成熟的系统从一种架构转换到另一种架构。
数据库开发:想象更好的系统
https://www.youtube.com/embed/oJAbUMZ6bQY
VIDEO
数据库开发:构建更好的系统
https://www.youtube.com/embed/joogIAcqMYo
VIDEO
愉快的部署!
安全模式更新-供应开发/测试数据库- Octopus 部署
原文:https://octopus.com/blog/safe-schema-updates-6-provisioning-databases
这篇博文是我的安全模式更新系列的第 6 部分。本系列其他文章的链接如下:
批评现有系统:
想象更好的系统:
构建更好的系统:
我们已经完成了这个系列的一半以上,到目前为止我们还只是停留在理论上。我们想象了一个更好的世界,但是我们还没有讨论实现这个世界所需的实际步骤。
现在情况变了。
从现在开始,我们将明确地讨论采用我在第 1 部分中讨论的那种 hellscape 的过程,并迭代地重构它,以便更安全地进行更改和改进。
根据记录,这段旅程可能不会很快也不会很容易。我们将讨论技术、流程和想法,这些对您现有的团队来说可能是新的。学习是困难的,大多数人需要一些时间来接受、学习和接受新技术和流程。
我们不会陷入教程、代码片段或其他细节中,但会有大量链接指向更详细的资料。
我们永远没有足够的环境
首先要做的事情:提供开发和测试环境。正如我们在第 2 部分“无故障运行需要失败经验”中所学。如果你的团队不能容易地进入一个现实的开发空间,在那里他们可以安全地实践失败,那么他们就不太可能构建出有弹性的生产系统。
通过从可快速部署和可任意处理的开发和测试系统开始,我们培养了更好地测试和预演有风险的重构的能力,这在以后会变得必要。此外,关于交付周期:
制约因素几乎总是存在的第一个地方,尤其是对于共享运营的传统 IT 组织[……]是环境创建。我们永远也吃不够,每当我们真的需要一个的时候,我们仍然要等四十个星期。
金、超越凤凰计划、
回想一下我们在之前的帖子中学到的内容:
- 个人的、可快速部署的环境更有弹性,因为它们是一次性的,并且可以隔离故障。如果一个开发人员破坏了一个环境,这个失败不会影响到其他任何人,并且可以轻松地终止和重新修复这个环境。此外,如果开发人员的个人开发实例被破坏,他们知道他们已经发现了产品中已经存在的问题,或者他们自己的代码有问题。这就减少了怀疑和指责。
- 自助服务环境减少了“在我的机器上工作/在开发中工作”的问题,因为所有环境都是从标准映像构建的,尽可能“像生产一样”。
- 个人开发环境鼓励持续的集成,因为当变更在不同的共享开发/测试环境中进行时,它们不需要被批量处理。变更是相互分离的。这导致了更小的集成、更安全的部署、更好的质量、更少的官僚作风和更快的交付周期。
如果我们想要获得这些好处,快速、轻松地进行环境资源调配至关重要。避免因依赖运营团队或审批者而导致的任何延迟至关重要。
我们的目标是创建一种现实的、现成的、预先批准的环境,开发人员和测试人员可以根据需要快速构建并抛弃它。它应该感觉像“git 克隆 f5 ”,需要大约同样多的时间和按键。
这个目标适用于任何系统的每个组件。由于数据库通常是一个共享的依赖项,其他一切都建立在它的基础上,所以在本文中,我们将重点关注快速、自助式的数据库供应。然而,这些想法和技术中的一些也可以帮助人们同时构建额外的依赖系统,允许人工或自动测试运行来构建完成手头任务所需的任何部分。
使用本文中的技术将会降低管理开销,并使产品更加安全。但是,节约成本不应该是首要目标。事实上,开发/测试托管费用(单独考虑)可能会有所增加。对于那些担心基础设施成本飙升的人,考虑一下大多数托管平台都允许支出上限。预算审批流程应该关注在哪里设置上限,而不是微观管理任何具体的计算时间。
让我们开始吧。
基础设施作为代码
如果使用云数据库,如 Azure SQL 数据库或 Amazon RDS,这一步可能没有必要。然而,出于本文的目的,我们将假设我们的整体后端数据库是运行在某个虚拟基础设施上的 SQL Server 数据库,要么在某个私有云,要么是 AWS、Azure 或 GCP 等托管提供商。
在这种情况下,您需要对一些脚本进行版本控制,以允许开发人员在他们自己的开发工作站、私有云或托管提供商上启动新的虚拟机或容器。
对于任何不熟悉这个概念的读者,我建议您阅读 Bob 的优秀系列,它从这里开始:
您的目标是为每个开发人员或测试人员提供一种简单的方法,在此基础上构建测试数据库。
SQL Server
一旦有了服务器,就需要安装 SQL Server。我建议你从阅读 Bob 的另一篇文章开始(我会永远引用它!):
Bob 解释了如何对您的 SQL Server 配置进行版本控制并自动安装 SQL Server。读完之后,如果你运行的是 Windows,我鼓励你看看巧克力。这基本上是 Windows 对 Linux 的 apt-get 的回应。它实现了与 Bob 的自动化脚本相同的功能,但代码少了:
choco 安装 SQL-server-2019-params = " '/配置文件:c:\ git \ my repo \ IAC \ SQL \ configuration 文件。ini ' "
最后,不讨论 Docker 容器是一种疏忽。
您可以在 Windows 或 Linux 上的 Linux 容器中运行 SQL Server。Docker 允许人们比在操作系统上安装新的 SQL 实例更快、更有效地运行它。这允许开发人员更频繁、更自由地启动和关闭实例,而无需提供新的开发机器。除了加快速度之外,这还可能简化整个过程,因为开发人员无需在每次想要重建环境时都启动和拆除新的服务器。
在我看来,开始使用 SQL Server 容器的最佳地方是 Andrew Pruski 的优秀博客系列,它将带你从零到 Kubernetes 的混沌工程(以及……太空入侵者)。
https://www.youtube.com/embed/HCy3sjMRvlI?start=1642
VIDEO
数据库/数据
到目前为止,我们已经实现了基础设施和 SQL Server 安装的自动化,但是我们还没有设置数据库或任何测试数据。不足为奇的是,缺乏真实测试数据的开发人员往往会写出性能很差的查询。他们的代码第一次大规模测试是在生产中!
不幸的是,在开发/测试中使用原始生产数据几乎是不可能的/不切实际的。在我们继续之前,我们需要解决两个需要解决的问题:隐私和规模。我们将依次处理这些问题。
数据隐私:提供有用但安全的测试数据
如果为他们的银行、医疗保健提供商或超市工作的所有开发人员都可以访问他们的个人财务、健康或购买数据,大多数人会认为这是对隐私的侵犯。同样值得考虑的是,与生产数据库相比,黑客更有可能攻击开发数据库。如今,网络钓鱼邮件非常具有说服力。一旦坏人获得了对开发人员机器的访问权,开发人员数据库通常就更容易成为目标。
为了应对这些问题,几乎所有地方的数据隐私立法都趋向于更加严格。除了法律制裁,每当下一个企业遭遇数据泄露时,传统媒体和社交媒体都喜欢推波助澜。没有人想成为下一个 Equifax 。
问问你自己:“想象一下有人把你的 dev 数据库上传到一个流行的黑客网站……你担心吗?”如果是这样的话,很可能您知道在不应该存在敏感数据的地方存在敏感数据,或者您不知道 dev 数据库是否包含敏感数据。根据许多最新的数据隐私法,这两种情况都是不可接受的。
无论您是维护一个单一的共享开发环境,还是使用许多可任意使用的环境,都是如此。然而,如果走一次性基础设施路线,重要的是要认识到我们可能会创建许多开发数据的副本,这可能会加剧数据监护问题。
不管我们有一个还是一百个共享开发环境,这些环境中的数据库都不应该包含任何敏感数据。您可能希望用各种类型的数据来替换您的敏感数据。在推文中总结一下:
出于本文的目的,我将提出大多数开发和测试目的的理想数据是生产数据,但是任何敏感记录都要以某种方式删除或替换。这样,可以用代表性的数据规模和分布来测试程序,更好地突出一次性环境中的潜在问题。这将导致更多的问题被提前发现,更少的生产问题。
为了实现这一点,首先我们需要进行数据审计并创建数据清单/字典/地图。需要根据数据的敏感程度对数据进行分类。(这已经是包括 GDPR 在内的许多数据隐私法的要求。如果你不知道你的敏感数据在哪里,你就无法保护它!)
在 SQL Server 中,有几种方法可以做到这一点。例如,您可以强制所有列使用一个扩展属性来定义数据敏感度的级别。这些属性相对容易放入源代码控制和查询。进一步来说,数据分类从 2016 年开始就是 SQL Server 的内置功能。
接下来,我们需要创建数据库的副本,清除所有敏感数据。我建议使用您的环境创建脚本(如上)在您的生产防火墙后面的某个地方建立一个临时的“暂存”实例。然后,您可以定期将最新的生产备份还原到该临时区域,并运行一些脚本或工具来删除所有敏感数据。(额外收获:无论如何,定期测试你的备份是一项重要的实践。)
数据屏蔽过程可能简单到用“John Doe”替换所有姓名,或者您可能使用更复杂的过程来创建真实但虚假的数据。例如,您可能想看看dbatools Invoke-dbaDbDataMasking cmdlet(开源、简单、免费)或Redgate Data Masker for SQL Server/Oracle(第三方、复杂、非免费)。在这里投入精力来创建更真实的测试数据将会在以后带来更高质量的开发和测试工作。
如果你担心敏感的生产数据泄露,这里有一些建议:
- 向部署管道中添加一个测试,确保所有列在源代码管理中都有一个数据分类。
- 向部署管道添加一个测试,确保所有敏感列都有相应的屏蔽脚本或规则。
- 在遮罩过程之后添加冒烟测试,以扫描看起来敏感的数据,如社会保险、信用卡或电话号码。
- 在您的部署管道中添加一项检查,确保对数据隐私分类或屏蔽脚本的任何更改都被标记出来,以供高级开发人员或(如果必须的话)数据库管理员、安全团队、数据隐私官或 变更顾问委员会审查。(呦!“角色分离”执法大队:我看到你了。我所要求的是采取措施确保这些审查迅速进行,而不造成长时间的拖延。
在屏蔽脚本之后,开发人员/测试人员可能想要提供他们自己的脚本来在登台实例上运行。例如,向数据库添加一组已知的测试用例,或者为开发/测试组添加一个具有管理权限的 SQL 登录。
当所有这些脚本完成后,我们可以在登台服务器上备份新的“dev-safe”数据库,并将备份复制到 dev 域中的某个共享位置。(然后我们可以删除临时数据库实例。它已经完成了它的使命。)
注意:数据屏蔽脚本可能需要很长时间才能运行,并且可能需要大量计算资源。如果逐行处理大型表,并到处创建临时表来维护复杂的外键引用,情况尤其如此。因此,整个事情需要作为一个计划的任务运行,每晚/每周/冲刺等创建新的“安全开发”备份。
现在,到开发人员早上开始工作的时候,“对开发安全的”备份应该已经准备好供他们使用了。他们已经创建的用于启动开发环境的脚本现在可以扩展,以恢复最新的掩蔽生产备份。现在,他们的开发环境已经完成了相对较新版本的屏蔽生产数据库。
数据规模:使生产数据更小、更快、更便宜
我们可能仍然面临重大的实际挑战。大多数生产数据库都非常大。
生产数据库很可能会太大,以至于无法在开发环境中完全重现。如果生产数据库是以 TB 为单位来衡量的,那么您不太可能想要购买大量的开发和测试服务器,每个服务器都足够大,可以承载全部内容。此外,大型数据库可能需要很长时间才能恢复。我们希望这个过程只需要几秒钟,而不是几个小时。
也就是说,您的开发人员和测试人员可以真正从访问大规模和相对较新的数据中受益。如果他们从来没有针对大型的、有代表性的数据集测试过他们的代码,他们怎么能预料到那些非直观的性能问题呢?这些性能问题可能会导致支撑整个生产资产的生产数据库崩溃。
这就是数据库克隆可以发挥作用的地方。
像 dbaclone (开源,免费)和 Redgate SQL Clone (第三方,不免费)这样的工具使用已经内置在 Windows 操作系统中的虚拟化功能来创建大型文件的廉价、可编辑的虚拟副本。
在 SQL Server 开发环境中,运营团队通常会从在共享位置创建大型数据库(高达 64TB)的开发安全“映像”开始。之后,开发人员可以根据需要创建“克隆”。这些克隆实际上是指向原始“映像”的指针。首次创建时,每个克隆只需要几兆字节(不管源映像的大小)。因此,我们可以在廉价的商用硬件上,几乎即时地创建几乎无限的源映像克隆。
聪明的一点是一个“差异磁盘”,它可以捕捉开发人员对克隆所做的任何更改。这感觉就像魔术一样,因为每个克隆都变成了它自己的 64 TB 源映像的可编辑副本,即使克隆运行在一个小得多的驱动器上。
主要问题是“差异磁盘”的大小会随着您修改文件而增长。因此,对小对象(如视图、过程或单行数据的更新)的更改不太可能产生大的影响。但是,如果重新索引一个大表,可能会很快耗尽磁盘空间。克隆是在小型、可任意使用的基础架构上使用的理想工具,在这种基础架构中,克隆的物理位置靠近源映像。
作为一个聚会恶作剧,我曾经在我的会议演示结束时循环运行这项克隆技术。几分钟之内,我在笔记本电脑上运行了一千多个可编辑的完整 StackOverflow 数据库副本。我的本地 SQL Server 实例认为我的 13 英寸 HP Spectre 上有将近 1pb 的 SSD 存储空间!
如果您想了解更多关于如何一起使用容器和克隆的知识,您可能想从观看我去年与 dbaclone 项目的维护者桑德·达塞的一次谈话开始:
https://www.youtube.com/embed/masJxBmgfqo
VIDEO
而且,如果这一切还不够酷,看看红门产卵。这是 SQL 克隆的托管版本。虽然它仍处于预览阶段,我还没有机会亲自使用它,但我真的对它的潜力感到兴奋!它有可能用一个命令取代本文中的大部分步骤。
如果你想开始,我在去年写了更多关于数据库克隆的细节,并且我包括了一个更详细的演练。
架构部署
到目前为止,我们有望获得一个常规的批处理作业,为我们的每个数据库生成开发安全的数据映像,以及一组自动化脚本,允许开发人员根据需要使用真实的数据来构建 SQL 实例。然而,这些数据库可能会有点过时。
数据库模式不是基于源代码管理的最新版本,而是基于产品。(生产可能还没有最新的开发变更)。此外,由于新的开发映像是提前创建的,它们可能已经存在几天或几周了。
在我们可以使用我们的新开发环境之前,我们需要在最新版本的“开发安全”数据库之上部署我们的源代码控制主分支中的最新源代码。除了这是使我们的开发环境保持最新的必要步骤之外,我喜欢这个练习,因为每次开发人员构建一个新环境时,他们都在有效地测试下一个生产部署。因此,如果有任何问题正在酝酿,你很可能提前发现它们。
我不打算在这里讨论自动化数据库部署的过程,但以下资源可能会有所帮助:
加速这一切
我们的配置过程现在已经完成。它有两个部分:
- 按照计划,我们有一个为开发人员创建新的开发安全数据库的过程。
- 开发人员可以在需要时构建他们自己的开发环境。
然而,“git,clone,f5”的体验可能还是会让开发者有点沮丧。
当开发人员想要运行他们的代码时,克隆回购相对较快,并且他们可以运行他们的脚本(或者使用 Octopus Runbook )来构建开发环境。但是,该脚本可能需要一段时间才能完成。
作为一名开发人员,我不想等待超过一分钟,最好是不超过几秒钟,就开始运行我的代码。但是,我的环境供应脚本必须完成以下所有工作:
- 构建一个新实例并启动它。(这最多需要几分钟时间。)
- 安装 SQL Server,以及其他任何需要的东西。(大概还要 5-10 分钟。如果使用容器就更少了。)
- 还原我的数据库备份或克隆数据库。(如果使用大型备份,可能需要一段时间。)
- 部署最新的源代码。(根据模式的大小/复杂程度以及部署工具/过程,这可能需要几分钟的时间。)
如果所有这些都需要 15 到 30 分钟,这并不奇怪,对于大型数据库来说,可能需要更长时间。这是一大堆无聊的事情,可能会让开发人员担心破坏他们的开发平台。如果他们犯了一个错误,他们真的想冒这么长时间延迟重生的风险吗?迭代一个设计或者测试多个实现选项可能需要很长时间,如果每个演进需要几个小时的话。
我们或许可以通过虚拟机快照来加快速度。或者,我们可以预先创建一个开发环境队列。我最喜欢队列选项,因为它省去了所有的 VM 魔法,并且可能更快。在开发人员提出请求之前,开发环境就已经准备好了——他们所需要的只是连接字符串。
摘要
根据 Gene Kim 的说法,大多数人在开发运维转型中遇到的第一个交付瓶颈是环境创建。我们在数据库交付地狱中目睹的许多问题都是共享和不一致开发环境的结果。我们知道失败是正常的,所以创建失败是安全的系统是很重要的。
亲爱的读者,我希望这篇文章中概述的各种技术实践将允许您将尽可能多的开发和测试从您的大型共享开发/测试环境转移到专用环境中,在专用环境中,可以对更改进行隔离测试,并在准备好进行部署时进行合并。
这种改进的测试能力将会派上用场,因为我们将继续关注下一篇关于近零停机部署和将整体系统分解成更松散耦合的架构的扼杀模式的文章。如果您能够在安全的、可任意处理的开发和测试环境中测试和预演这些变化,那么进行复杂和有风险的重构会容易得多。
下次
在下一篇文章中,我们将把焦点转向部署模式。
在本系列中,我一直倡导更小、更安全、更频繁的部署。但是,如果这些部署需要停机时间,我们就不太可能像我们希望的那样经常部署。如果每次部署都需要一个小时的停机时间,那么一天部署 10 次是没有意义的。在我看来,接近零停机时间的部署不应该被视为一些崇高而不切实际的目标。它们应该被认为是实践真正的持续集成和交付弹性系统的先决条件。
本系列其他文章的链接如下:
批评现有系统:
想象更好的系统:
打造更好的系统:
观看网络研讨会
我们的第一次网络研讨会讨论了松耦合架构如何带来可维护性、创新性和安全性。第二部分讨论了如何将一个成熟的系统从一种架构转换到另一种架构。
数据库开发:想象更好的系统
https://www.youtube.com/embed/oJAbUMZ6bQY
VIDEO
数据库开发:构建更好的系统
https://www.youtube.com/embed/joogIAcqMYo
VIDEO
愉快的部署!
安全模式更新——接近零停机时间的数据库部署——Octopus 部署
原文:https://octopus.com/blog/safe-schema-updates-7-near-zero-downtime-deployments
这篇博文是我的安全模式更新系列的第 7 部分。本系列其他文章的链接如下:
批评现有系统:
想象更好的系统:
构建更好的系统:
小的、频繁的和简单的改变更安全。大的、不频繁的和复杂的变化更危险。如果你不同意,从头重新阅读这个系列,从我的关于数据库交付地狱的帖子开始。
数据库很少孤立存在。在修改数据库模式时,我们通常需要考虑依赖关系。数据库通常服务于前端应用程序/服务,这意味着模式更改通常需要与其他系统的更改相协调。
可能需要停机一段时间,因为我们不能冒险提供不匹配的版本:
- 系统离线
- 所有的变更都是一次/按顺序部署的
- 系统恢复在线
在整个过程中,我们的用户被锁定。
有一百种方式会导致糟糕的结局。凤凰计划就是这样一场灾难。数据库更新花费的时间比预期的长,关键系统无法按时恢复。
尽管有风险,但是如果我们有数据库模式,避免对模式进行更改是不明智的。随着时间的推移,这种僵化的策略会导致糟糕的体系结构,不能反映不断发展的业务需求。
我们的目标是使模式能够安全地发展。因此,我们需要确保小规模且经常地执行此类部署。
不幸的是,每次部署所需的停机时间越长,我们就越不可能做到这一点。如果每次部署都需要一个小时的停机时间,我们永远不会一天部署 10 次。
更有可能的是,工程师将需要提前做好计划,并玩弄政治来协商一些停机时间。可能是一夜之间。(疲惫的员工并不以他们的可靠性、对细节的关注或解决问题的能力而闻名。)
由于这些机会不会经常出现,所以变化会分批出现。尽可能多的更改将被塞进尽可能短的窗口中。
这…太愚蠢了。(见开篇段落。)
不可避免的结论是:我们必须在尽可能短的停机时间内执行模式更改。只有通过最小化停机时间,我们才能增加部署频率,降低部署规模/复杂性,并交付更安全的模式更新。
根据我的经验,对于所有关于源代码控制和部署自动化的讨论,最小化停机时间的必要性并没有被那些拥有数据库模式并希望保护它们安全的人所理解。
这篇文章不是关于模式更新的自动化或执行的——还有许多关于这个的文章。这篇文章是关于最小化停机时间的模式。
重载术语:部署和发布
许多人交替使用“发布”和“部署”这两个词,没有考虑它们之间的区别。
如果您使用 Octopus Deploy(或类似的产品),那么您对“发布”的理解可能是工具中常见命名约定的结果。在大多数部署自动化工具中,“发布”是您的源代码的一个特定版本,一组配置变量,以及一组需要运行来执行“部署”的步骤。您可能会认为“发布”是被“部署”的东西。首先发布,然后部署。这对你来说很自然。
你是少数。
对大多数人来说,特别是对任何营销人员来说,“发布”是不同的。“发布”你的软件的一个新版本,或者最新的 iPhone,或者新的 Adele 专辑,就是要让它可用,并告诉人们。这个东西是预先创建的,后来才发布。最新的詹姆斯·邦德电影制作于 2020 年,但上映时间推迟到 2021 年。
当谈到零停机部署时,我们倾向于以第二种方式使用“发布”。部署是关于做出改变,但是发布是关于向我们的用户揭示那些改变。当我在这篇文章中使用“发布”时,我不是指部署的准备,我是指让用户看到更新。
区分部署变更和向用户发布/展示这些变更是至关重要的。这两件事不需要同时发生。事实上,正是分离这些事件的能力实现了零停机发布,以及其他各种令人兴奋的实践,如生产中的测试和一些快速回滚模式。
应用零停机模式
这篇文章是关于数据库部署的,但是数据库并不是孤立存在的。我们需要从一些背景开始。
支持零停机时间(更准确地说,接近零停机时间)的应用程序部署模式通常分为两类:
- 基于基础设施
- 基于应用的
基于基础设施的部署模式
基于基础设施的技术包括蓝/绿部署、金丝雀释放,以及集群免疫系统。它们通常基于巧妙的负载平衡技巧。新代码被部署在新的基础设施上,经过测试,并被添加到轮换中。
通过更改我们的负载平衡器中的设置,我们可以将流量发送到新的或旧的基础架构。这潜在地允许我们逐渐“发布”新版本。首先是 1%的生产流量,然后是 5%,10%,随着我们观察遥测数据、我们的社交媒体渠道和/或我们的支持票来检查一切是否顺利运行,逐渐节流。
如果一切顺利,该版本将逐步在全球推广。如果没有,我们可以通过撤销负载平衡器上的设置来立即恢复到旧版本。我们避免了任何就地升级,因此旧服务器仍在运行,并准备好在需要时接收全部负载。
基于应用程序的部署模式
基于应用的方法倾向于基于特征切换/标记。旧版本和新版本将被并排部署,但是哪个版本被执行可以通过代码和一些外部数据库来管理。
例如,也许我们已经有一个特性切换数据库在生产中运行。部署我们的新代码后,每次调用应用程序中的方法时,它都会查询 featuretoggle 数据库,以确定是否启用了某些功能。根据响应,它可以运行一个代码块或另一个代码块。也许 featuretoggle 数据库可以通过指示应用程序在 x%的时间内使用新代码来抑制首次展示。
这允许通过更改外部数据库中的设置来发布或回滚新功能。不需要额外的部署。
我们可以更进一步。也许,如果我们有一个新特性,但我们关心性能,我们可以运行两个代码块,但只在 UI 中显示旧的功能。这被称为黑暗启动,它允许工程师用现场生产工作负载测试他们的代码的性能,用一种简单的方法来调节或关闭新代码。
你可以在 Deploy!=释放。这也在devo PS 手册中有更详细的介绍。
基于基础设施的模式和基于应用程序的模式的共同点是,首先部署代码,然后以受控和可测试的方式发布,允许快速、几乎即时的回滚。
这对数据库意味着什么?向前向后兼容至关重要。
扩展/收缩,向前和向后兼容
如果我们希望在数据库中进行会影响我们的相关服务的模式更改,并且如果我们希望避免计划内停机,我们可能会遵循上面讨论的基于应用程序或基础设施的模式之一。无论哪种情况,我们都需要通过三个阶段来发展数据库。
- 扩展:对数据库的附加更改,以支持相关应用程序的新旧版本。
- 部署:部署、测试和发布新版本的应用程序。理想情况下是这个顺序。
- 契约:在首次展示之后,我们可以安全地删除旧的模式对象。
这个单一的大的重构需要多个小的模式变化。为了避免计划停机,每个变更必须具有以下属性:
- 可以独立于其他步骤或任何其他依赖项来执行
- 产生最小的风险
- 具有快速回滚选项(这可以避免数据丢失或重要和必要的数据处理,这可能会导致各种问题)
用一个例子来解释最简单:考虑将一个 fullName 列拆分成单独的 firstName 和 lastName 列。我们可以在没有任何危险的停机窗口或可怕的模式更新的情况下实现这一点,如下所示:
展开:
- 新列被添加到数据库中。(这没什么风险。)
- 如果使用存储过程来添加/更新/删除数据,则可以更新这些存储过程,以便添加/更新/删除旧列和新列。
- 现有数据在后台逐渐迁移。(这可以是无明显性能影响的滴加,如果有任何问题,可以暂停或停止该过程。)
现在数据库支持这两个版本。
卷展栏:
- 当旧列和新列中的数据可靠地同步时,任何读取的存储过程都可以指向新列。
- 如果应用程序直接引用列,而不是通过存储过程,那么使用上述的一种基础设施或基于应用程序的模式来部署新的应用程序版本。
现在新的东西在全球发行。
合同:
- 理论上,我们可以删除旧列。然而,在有许多缺乏文档记录的依赖项的系统中,我们总是有可能遗漏一些东西。最好先重命名旧列。(并更新任何更新了旧列的存储过程。)如果有人抱怨,我们可以通过恢复任何存储过程的旧版本,立即用另一个重命名来修复它。
- 在这两种情况下,经过一段时间后,我们应该安排删除旧列。没有人需要看到数百个附加了
_toDelete
的对象。(提示:试试_ToDeleteOn2021-12-01
吧。它在一定程度上集中了人们的思想,我们甚至可以包装一些自动化的过程来备份和挑选旧的对象。)
重构完成。只要按照这个顺序执行这些步骤,每个步骤都可以单独执行。这些步骤都没有产生巨大的风险。如果有任何错误,每一步都可以很容易地恢复。
摘要
这是更新模式的一种更安全的方式。至关重要的是,由于它不需要任何停机时间,这些变更不需要批量发布。
可能会有一些读到这里的人认为这需要更长的时间。恐怕这些人仍然在考虑小的改变需要很长的准备时间。也许他们正在考虑变更审批委员会,或者他们正在为每一步设想单独的 JIRA 入场券。也许他们正在考虑对每一步进行单独的为期一周的测试。
忘掉这一切。
如果这个重构需要批准,它应该作为一个整体来审查,即使它是分步执行的。并且大多数测试和部署管道应该是自动化的。
是的:这个更难。没人说这会很容易。我们正在优化安全性,这需要严谨和努力。
当然,依赖关系越多,这就越难。有些人可能认为这不可行。当然,这个过程需要在任何依赖系统中进行一定程度的防御性编程和测试/遥测。
在理想的世界中,我们将与松散耦合的系统一起工作(参见我的系列文章的第 4 部分和第 5 部分的和)。默认情况下,这些代码是防御性的,对数据库的依赖性大大降低。使这一切变得容易得多的属性。
如果您的系统是紧耦合的,也许现在您已经看到了松耦合的巨大好处。也许你也被眼前挑战的艰巨性吓到了:将你错综复杂的依赖关系网进化成更安全的东西。
下次
下一次,我们通过探索扼杀者模式来结束这个系列。一种安全重构复杂、紧密耦合系统的方法。
本系列其他文章的链接如下:
批判现有系统:
想象更好的系统:
构建更好的系统:
观看网络研讨会
我们的第一次网络研讨会讨论了松耦合架构如何带来可维护性、创新性和安全性。第二部分讨论了如何将一个成熟的系统从一种架构转换到另一种架构。
数据库开发:想象更好的系统
https://www.youtube.com/embed/oJAbUMZ6bQY
VIDEO
数据库开发:构建更好的系统
https://www.youtube.com/embed/joogIAcqMYo
VIDEO
愉快的部署!
安全模式更新——扼杀 monolith——Octopus 部署
原文:https://octopus.com/blog/safe-schema-updates-8-strangling-the-monolith
这篇博文是我的安全模式更新系列的第 8 部分,也是最后一篇。
批评现有系统:
想象更好的系统:
构建更好的系统:
到目前为止,在本系列中,我们已经详细讨论了采用更松散耦合的架构的好处,在这种架构中,子系统管理它们自己的数据。通过隐藏每个子系统的数据库内部,并强制所有的通信通过一些消息总线或 API 网关,我们避免了在数据库交付地狱中讨论的依赖性噩梦。我们还允许独立管理和更新子系统,从而降低管理开销,并简化供应和交付挑战。
在上一篇文章中,我们讨论了安全、接近零停机时间的发布模式,以及快速可靠的回滚选项。您已经有了动机和工具,但是我们还没有讨论任何从紧耦合到松耦合重构的指导性策略。
这篇文章旨在提供一个关于如何管理整个过程的概述。如题所示,我们将讨论扼杀者模式,但我们也将触及其他模式,包括:
在我们继续之前,让我们设定一些期望:这并不容易,也不是什么灵丹妙药。这将需要大量的时间/投资,这是你可能会分阶段进行的事情,并且需要考虑权衡。
天下没有免费的午餐。
扼杀者模式
让我们想象一个典型的依赖噩梦。
这种架构是多年来紧迫的期限、短期规划、技术债务、知识积累和员工流动累积的结果。即使在最好的情况下,信息共享也是困难的,但是现有的团队在没有完善的知识、可靠的文档或成熟的测试套件的情况下也在关注这个问题。
这个系统是有价值的。在这个错综复杂的网络中,有公司的“摇钱树”产品和一堆关键的内部系统,但也有一条被遗忘的、半生不熟的“次要项目”、被放弃的业务项目和退役的功能。很难只见树木不见森林。
我们需要用扼杀者程序来包装这块巨石。就像启发了这个架构的澳大利亚无花果树一样,我们的扼杀者应用程序是一种寄生虫,它会扼杀它的宿主,最终在这个过程中杀死它。
我们将从一个简单的代理开始。
起初,这个代理只是捕获所有的入站呼叫,并将它们重定向回预定的目的地。在功能上,我们没有做任何改变,但我们正在增加您的网络负载。考虑到松散耦合的架构会给网络带来更大的压力,在我们开始做更令人兴奋的事情之前,尽早发现并解决任何网络挑战是一个好主意。
接下来,我们决定将一部分功能扩展到一个独立的服务中。理想情况下,我们使用领域驱动设计来告知我们的选择(参见本系列中的第 4 篇和第 5 篇来回顾“DDD”)。也许我们正在构建的功能广泛地(或完全地)取代了一个或多个整体旧组件中的功能。无论哪种情况,这都是全新的代码,内部代码实现对其他系统是隐藏的。
注意,此时我们已经在生产中“部署”了服务,但是它还没有“发布”。(我使用了第 7 部分中“部署”和“发布”的定义,其中部署与复制文件相关联,而发布则是让最终用户可以进行更改。)这允许我们在生产中测试我们的新代码,而不将其暴露给我们的用户,同时生产流量仍然由旧代码安全地处理。
当我们测试了新代码后,我们可以通过代理中的配置更改来“发布”我们的新服务。如果我们检测到任何问题,我们可以通过恢复代理配置来立即回滚我们的更改。
还要注意,目前我们仍在使用原始数据库。在我们打破这种依赖之前,我们的服务不会真正地分离。我们现在需要分解数据库,使用第 7 部分中讨论的扩展契约模式。
作为临时措施,我们可能需要设置应用程序的旧版本和新版本,以更新旧数据库和新数据库。或者,我们可能需要创建一个额外的数据同步服务来确保两个数据库保持同步。
当我们确信不需要使用代理技巧执行快速回滚时,我们就可以开始仔细清理自己的工作了。如果我们可以很容易地确定哪些部分已经过时,我们可以试着移除它们。
我们只需要在旧的数据库(和任何数据同步应用程序)中保留我们新分离的服务的数据,直到我们确信新服务是健康的和完全分离的。例如,我们可以监视旧数据库中的读/写。假设一切都很平静,然后我们重命名我们的列或表,并等待电话铃响。如果一两个星期没有人打电话来,备份并删除旧的专栏以及任何数据同步服务可能是相当安全的。
现在,我们的新服务可以独立管理了。它更容易工作,因为开发人员没有对破坏依赖关系的持续恐惧,也没有与系统其余部分相关的认知开销。他们可以在他们喜欢的任何地方运行服务,并独立规划他们的部署时间表,不受任何与更广泛的整体相关的官僚主义的影响。
同样,整块巨石本身也变得简单了一点。
到目前为止一切顺利,但是如果我想提取的应用程序被深埋在巨石中呢?例如,如果它需要服务于来自 monolith 内部的请求呢?
抽象分支
在我们的第一个例子中,我们使用了一个代理来扼杀整个整体,但是对于一些内部组件,我们可以使用一些其他的应用程序、模块或类作为抽象点来捕获所有的内部请求。然后,与我们最初的代理一样,我们可以使用我们的抽象应用程序 it 来确定将流量转移到哪里。
在本例中,即使我们清理了旧的应用程序和数据库,我们仍然面临挑战。我们的数据库仍然相互依赖。希望我们可以重构系统,通过通信层处理这些依赖关系,数据处理由应用程序本身完成。由于本系列中讨论的所有原因,这将是有利的。但是,也不是没有取舍。
数据一致性与可用性
通过完全分离这些数据库,在许多方面我们的系统将变得更加安全和易于管理。更小的数据库,有更多的防火墙,可以独立更新。我们正在从但丁的数据库地狱中爬出来。
然而,除了与重构相关的开发成本之外,这种架构还有一系列不可忽视的缺点。
第一,性能。不管你喜欢还是讨厌关系数据库,都很难否认它们在数据处理方面做得非常好。编写良好的存储过程可以以可笑的速度处理数字。与发出网络请求、拉回大量数据并在应用程序层处理这些数据相关的延迟和网络负载相比,这是不争的事实。你的数据库依赖关系错综复杂,尽管有缺陷,但很可能为此做了很好的优化。你可能已经花了几十年来完善这种优化。
我们可以通过在我们的网络上投入一些(可能是非常需要的)投资和开发复杂的缓存系统来缓解一些性能问题,但我们需要诚实地说,即使有大量的投资,我们也不太可能达到同样的原始性能。
我们的下一个问题是一致性。外键是可爱的东西。当我们所有的数据都存在于单个关系数据库中时,我们可以使用外键来执行快速连接和强制引用完整性。在我们的数据被拆分到多个解耦的服务之后,我们(必然)牺牲了在这些服务之间使用传统外键的能力。
我们可以编写自己的外键来允许我们跨不同的服务执行连接,但是仍然没有可靠的东西来强制引用完整性。不管你喜欢还是讨厌关系数据库,它们在引用完整性方面都很棒。几十年来,RDBMS 一直是数据库领域的主导技术,对于我们大多数人来说,很难想象一个没有它的世界。
(对于某些用例,我们建议保留它。)
我们现有的整体可能是基于参照完整性的假设构建的。(因为为什么不是在 RDBMS 市场主导的时代?)但也有可能建立一个更有弹性的系统,而不是一个优先考虑稳健性的系统。(参见第 2 部分,了解弹性与稳健性的更多细节。)
如果我们的系统是为了优雅地处理破碎的依赖关系而构建的,我们能接受偶尔破碎的记录,以换取与松散耦合相关的更广泛的好处吗?我们如何处理那些破记录?也许我们可以做一些事情来发现、监控、修复或删除它们?
在我们走得太远之前,让我们先花点时间考虑一下我们可能如何在一开始就处于不一致的状态。好吧,我们在一个更加分布式的系统中面临的一个新的大问题是分区。
分割
让我们设想一个电子商务系统,它为购物车、库存和支付网关提供单独的服务。让我们想象一位顾客在他们的购物车里添加了一个可爱的章鱼玩具。购物车服务正在运行,支付服务也正在运行,因此我们可以处理订单。但是,库存服务已关闭。我们不知道我们是否有存货。
这是一个隔板。这不可能发生在一块巨石上。这是隐藏数据的松散耦合系统特有的问题。
(来源:这个例子,以及这篇文章的许多灵感,来自于《构建微服务的作者 Sam Newman 在 GoTo 2019 的一次演讲。)
https://www.youtube.com/embed/9I9GdSQ1bbM?start=2401
VIDEO
你卖玩具吗?一方面,如果你不处理订单,你会失去销售。另一方面,如果你以后需要回去退款/道歉,你可能会让你的顾客不高兴。您需要权衡一致性和可用性。
对许多人来说,性能和一致性问题将是难以下咽的苦果。老实说,这种架构可能不适合所有系统。这些都是需要解决的棘手问题。
然而,在放弃松耦合的想法之前,先考虑一下好处。还记得但丁的数据库地狱的恐怖吗?提醒你自己从这个帖子中看到的我们的原图。想象一下,如果我们只设法把你的巨石的一半功能提取出来,做成看起来更像这样的东西,那会容易得多。
【T2
系列摘要
谢谢你坚持和我在一起。
这个系列开始时(我认为是)是一个关于零停机部署的简短帖子的简单想法。但是它一直在增长,增长,增长。我不断地偏离主题,在这里或那里又增加了两千字。第一稿冗长而复杂。很明显,我的大脑已经变成了一张错综复杂的依赖关系网。
它帮助我把我的想法分解成容易处理的部分。
独石当然有它的好处。(也许我应该写本书?)微服务不应该被视为某种完美的解决方案。他们不是金子弹。正如我们所讨论的,它们提出了一系列非常现实的挑战,尤其是在数据方面。从整体服务到微服务的旅程可能既漫长又昂贵。如果你半途而废,你可能会被留在一个可怕的混合体中,充满了挑战,却没有什么好处。
然而,好处也是巨大的。回想一下第一部分中的数据库交付地狱,现状可能也是不可持续的。我们需要用自己的判断来决定我们要在这条道路上走多远。
不管我们致力于哪种架构,本系列中讨论的技术实践都会有所帮助。在紧密耦合和松散耦合的系统中,自助服务供应和接近零停机时间的部署模式在服务弹性方面带来了显著的好处。(但是像大多数事情一样,它们在更小和更松散耦合的系统中特别有用和实用。)
祝你好运,无论你决定选择哪条路。
进一步阅读
如果你想了解更多关于这篇文章的话题,我推荐你去看萨姆·纽曼的演讲(如上)。
如果您想更深入地了解本系列中讨论的概念,以下书籍是一个很好的起点:
本系列其他文章的链接如下:
批评现有系统:
想象更好的系统:
打造更好的系统:
观看网络研讨会
我们的第一个网上研讨会“想象更好的系统”讨论了松耦合架构如何带来可维护性、创新性和安全性。
第二部分,构建更好的系统,讨论了如何将一个成熟的系统从一种架构转换到另一种架构。
数据库开发:想象更好的系统
https://www.youtube.com/embed/oJAbUMZ6bQY
VIDEO
数据库开发:构建更好的系统
https://www.youtube.com/embed/joogIAcqMYo
VIDEO
愉快的部署!
节省云成本:Azure - Octopus Deploy 中的消费使用细节
更新于 2018-04-23 -这篇博客文章在我们的 Octopus 2018.4 发布后进行了更新,现在支持重复计划部署,以说明如何按计划定期运行部署。
你是否曾经在 Azure 中部署了一个虚拟机,进行了 10 分钟的快速测试,却在两个月后回来,发现它一直在运行?这篇博文展示了如何使用 Octopus 通过 Slack 通知您 Azure 资源组的成本是否超过了预期值——您可以使用资源组标签来指定。由于 Octopus 有能力认证许多不同的云平台,并向它们部署资源,它自然也有能力获取有用的数据。这使得 Octopus 成为运行我们需要查看我们的资源消耗的脚本的绝佳候选。
方案
我喜欢好的场景,所以事不宜迟,请见见 OctoFX 一家虚构的公司,由技术高超的开发人员组成,他们将资源部署到云中并对其进行测试。最近,他们注意到当测试完成时很难记得删除某些资源,并且希望在资源的成本超过成本限制时得到通知。虽然每个云平台都有自己通知成本的方式,但 OctoFX 选择使用 Octopus 来运行这些脚本,因为它允许他们遵循一致的方法,而不管他们使用的是哪个云平台。在这个场景中,我们将定义一个名为 NotifyCostLimit 的标记,当达到这个限制时,Octopus 将发出 slack 通知。如果没有应用 NotifyCostLimit,将采用默认值$100。
这篇博客文章只涵盖了查询 Microsoft Azure 的步骤,但是您也可以对 AWS 应用类似的方法。所以,让我们开始吧!如果您想了解脚本执行的过程,您可以遵循以下每个目标。或者,你可以跳到这个博客的Configure it in Octopus
部分来看完整的脚本。
限制
这里需要注意几个关键点:
- 用于检索成本项目的 cmdlet(Get-AzureRmConsumptionUsageDetail)只能获得资源管理器的详细信息,这意味着我们根本看不到任何服务管理器资源成本。
- 可能需要两周时间才能获得消费详情,我们应该考虑到我们看到的最早的数据条目可能是两周前的。
- 成本数字不含税。
目标 1:获取 Azure 中所有订阅的所有费用项目。
下面这段脚本的主要目的是检索消费使用情况的详细信息。这可以通过使用来自 Azure PowerShell 的 Get-AzureRmConsumptionUsageDetailcmdlet 来完成。这里我们提供了两个日期,开始日期(今天减去 30 天)和结束日期(今天)
write-output "Getting all cost items for this subscription in Azure"
write-output "Subscription ID: $SubscriptionId "
$now = get-Date
$startDate = $($now.Date.AddDays(-$DateRangeInDays))
$endDate = $($now.Date)
write-output "Start Date: $startDate "
write-output "end Date: $endDate "
$SubConsumptionUsage = Get-AzureRmConsumptionUsageDetail -StartDate $startDate -EndDate $endDate
目标 2:查找在此开单期间存在的所有资源组名称。
现在,我们从消费使用详细信息中取出每一行,删除每个实例 ID 的开始部分。然后,我们去掉资源组名称之后的所有内容。最后,我们完成了资源组名称。因为资源组是不区分大小写的,所以我将每个资源组的名称都改成了小写。
write-output "Finding all of the resource group names which existed during this billing period."
$SubIdPrefix = "/subscriptions/" + $SubscriptionId
$RgIdPrefix = $SubIdPrefix + "/resourceGroups/"
$resourceGroupName = @()
$resourceGroups = @()
foreach ($line in $SubConsumptionUsage) {
if ($line.InstanceId -ne $null ) {
$thisRgName = $($line.InstanceId.ToLower()).Replace($RgIdPrefix.ToLower(),"")
$toAdd = $thisRgName.Split("/")[0]
$toAdd = $toAdd.ToString()
$toAdd = $toAdd.ToLower()
$toAdd = $toAdd.Trim()
if ($resourceGroups.Name -notcontains $toAdd) {
$resourceGroupName = [PSCustomObject]@{
Name = $toAdd
}
$resourceGroups += $resourceGroupName
}
}
}
write-output "Found these Resource groups: "
$resourceGroups
目标 3:计算每个资源组的成本,然后标记超出 NotifyCostLimit 的资源组。
对于遇到的每个资源组名称,我们按资源组过滤详细信息,并将所有成本加在一起。这成为每个资源组的总成本。
Write-Output "Calculating the cost of each Resource Group, then flag ones that exceed NotifyCostLimit."
$currentResourceGroups = Get-AzureRmResourceGroup
$rgIndexId = 0
foreach ($rg in $resourceGroups) {
#$thisRg = $null
$RgIdPrefix = $SubIdPrefix + "/resourceGroups/" + $rg.Name
$ThisRgCost = $null
$SubConsumptionUsage | ? { if ( $_.InstanceId -ne $null) { $($_.InstanceId.ToLower()).StartsWith($RgIdPrefix.ToLower()) } } | ForEach-Object { $ThisRgCost += $_.PretaxCost }
$toaddCost = [math]::Round($ThisRgCost,2)
$resourceGroups[$rgIndexId] | Add-Member -MemberType NoteProperty -Name "Cost" -Value $toaddCost
if ($currentResourceGroups.ResourceGroupName -contains $rg.Name) {
$addingResourceGroup = Get-AzureRmResourceGroup -Name $($rg.Name)
$resourceGroups[$rgIndexId] | Add-Member -MemberType NoteProperty -Name "NotifyCostLimit" -Value $($addingResourceGroup.tags.NotifyCostLimit)
}
$rgIndexId ++
}
目标 4:筛选成本高于预期的项目。
为了完成这下一部分,我们将把我们的资源组传送到where-object
(它被缩短为?
,以过滤大于或等于成本通知标签NotifyCostLimit
的每个项目。
Write-Output "Filtering the items whose cost is higher than the allowed limit."
$reminderGroups = $resourceGroups | ? {
if ($_.NotifyCostLimit -ne $Null) {
$_.Cost -ge $_.NotifyCostLimit
}
else {
$_.Cost -ge $DefaultNotifyCostLimit
}
}
目标 5:使用时差通知通知正确的人。
最后,我们将提醒人们,他们可能有一个已经达到资源成本极限的测试资源。
function new-SlackMessage ( $resourceGroup ) {
$orange = "#F9812A"
$attachments = @{}
$fieldNumber = 0
$username = "Octopus Cost Reminder"
$IconUrl = "https://octopus.com/images/company/Logo-Blue_140px_rgb.png"
$payload = @{
#channel = "General";
username = $username;
icon_url = $IconUrl;
attachments = @(
);
}
$state = "Current cost: $($resourceGroup.Cost)"
$description = "ResourceGroup Name: $($resourceGroup.Name)"
$ownerContact = "OwnerContact: $($resourceGroup.OwnerContact)"
$colour = $orange
if ($fieldNumber -eq 0){
$pretext = "This Resource Group has spiked above expected cost"
}
else {
$pretext = $null
}
$thisFallbackMessage = "$description has spiked above expected cost (Current Cost: $state)"
#Some results can have multiple fields:
$fields = @(
@{
title = $description;
value = $state;
});
$thisAttachment = @{
fallback = $thisFallbackMessage;
color = $colour;
pretext = $pretext;
fields = $fields;
}
$payload.attachments += $thisAttachment
return $payload
}
function New-SlackNotification ($hook, $group)
{
$message = New-SlackMessage -resourceGroup $group
Invoke-Restmethod -Method POST -Body ($message | ConvertTo-Json -Depth 4) -Uri $hook
}
if ($reminderGroups -ne $null) {
Write-Output "Sending a Slack notification about these groups"
$reminderGroups
foreach ($group in $reminderGroups) {
New-SlackNotification -hook $slackHook -group $group
}
}
else {
Write-Output "There are no groups which need attention"
}
在 Octopus 中配置它
现在进入有趣的部分,配置 Octopus!
先决条件
请确保您已经设置了这两个先决条件,以便开始工作:
- Octopus 中的 Azure 服务主体设置。
- 使用您的备用挂钩 URL 配置的备用帐户。
设立八达通服务主账户
我们首先需要确保 Octopus 配置了一个服务主体帐户来查看您的 Azure 订阅。请随时查看我们关于创建 Azure 服务主帐户的文档,了解如何执行该任务的更多信息。出于此帐户的目的,您需要确保该帐户至少拥有对套餐的Reader
访问权限。
设置时差通知帐户
请查看 slack 文档,了解如何创建新的 slack 集成并获取 slack 挂钩 URL。一旦有了 URL,就需要确保它在 Octopus 中被设置为项目变量。
创建您的新项目
在 Octopus 中创建您的新项目,我的项目名为Cloud Cost
,然后定义这 4 个项目变量:
- SubscriptionId(类型:字符串)。可以通过运行
Get-AzureRmSubscription
从 Azure PowerShell 中检索的订阅 ID。 - DateRangeInDays(类型:整数)。该脚本采用当天的天数,并以此天数进行倒计数,以形成一个范围来检查使用成本。
- DefaultNotifyCostLimit(类型:整数)。如果一个资源组没有用
NotifyCostLimit
标记,Octopus 将默认为这个值。 - SlackHook(类型:字符串)。您的 slack hook 的完整 URL。
创建新步骤(Azure PowerShell 脚本)
使用Run an Azure PowerShell Script
步骤模板创建一个名为Get Azure subscription cost
的新步骤。将该帐户指定为您的服务主体帐户,该帐户有权访问您从中获取费用数据的订阅。将下面的完整脚本粘贴到您的脚本内容部分。省去第一步。
write-output "Getting all cost items for this subscription in Azure"
write-output "Subscription ID: $SubscriptionId "
$now = get-Date
$startDate = $($now.Date.AddDays(-$DateRangeInDays))
$endDate = $($now.Date)
write-output "Start Date: $startDate "
write-output "end Date: $endDate "
$SubConsumptionUsage = Get-AzureRmConsumptionUsageDetail -StartDate $startDate -EndDate $endDate
write-output "Finding all of the resource group names which existed during this billing period."
$SubIdPrefix = "/subscriptions/" + $SubscriptionId
$RgIdPrefix = $SubIdPrefix + "/resourceGroups/"
$resourceGroupName = @()
$resourceGroups = @()
foreach ($line in $SubConsumptionUsage) {
if ($line.InstanceId -ne $null ) {
$thisRgName = $($line.InstanceId.ToLower()).Replace($RgIdPrefix.ToLower(),"")
$toAdd = $thisRgName.Split("/")[0]
$toAdd = $toAdd.ToString()
$toAdd = $toAdd.ToLower()
$toAdd = $toAdd.Trim()
if ($resourceGroups.Name -notcontains $toAdd) {
$resourceGroupName = [PSCustomObject]@{
Name = $toAdd
}
$resourceGroups += $resourceGroupName
}
}
}
write-output "Found these Resource groups: "
$resourceGroups
Write-Output "Calculating the cost of each Resource Group, then flag ones that exceed NotifyCostLimit."
$currentResourceGroups = Get-AzureRmResourceGroup
$rgIndexId = 0
foreach ($rg in $resourceGroups) {
#$thisRg = $null
$RgIdPrefix = $SubIdPrefix + "/resourceGroups/" + $rg.Name
$ThisRgCost = $null
$SubConsumptionUsage | ? { if ( $_.InstanceId -ne $null) { $($_.InstanceId.ToLower()).StartsWith($RgIdPrefix.ToLower()) } } | ForEach-Object { $ThisRgCost += $_.PretaxCost }
$toaddCost = [math]::Round($ThisRgCost,2)
$resourceGroups[$rgIndexId] | Add-Member -MemberType NoteProperty -Name "Cost" -Value $toaddCost
if ($currentResourceGroups.ResourceGroupName -contains $rg.Name) {
$addingResourceGroup = Get-AzureRmResourceGroup -Name $($rg.Name)
$resourceGroups[$rgIndexId] | Add-Member -MemberType NoteProperty -Name "NotifyCostLimit" -Value $($addingResourceGroup.tags.NotifyCostLimit)
}
$rgIndexId ++
}
Write-Output "Filtering the items whose cost is higher than the allowed limit."
$reminderGroups = $resourceGroups | ? {
if ($_.NotifyCostLimit -ne $Null) {
$_.Cost -ge $_.NotifyCostLimit
}
else {
$_.Cost -ge $DefaultNotifyCostLimit
}
}
function new-SlackMessage ( $resourceGroup ) {
$orange = "#F9812A"
$attachments = @{}
$fieldNumber = 0
$username = "Octopus Cost Reminder"
$IconUrl = "https://octopus.com/images/company/Logo-Blue_140px_rgb.png"
$payload = @{
#channel = "General";
username = $username;
icon_url = $IconUrl;
attachments = @(
);
}
$state = "Current cost: $($resourceGroup.Cost)"
$description = "ResourceGroup Name: $($resourceGroup.Name)"
$colour = $orange
if ($fieldNumber -eq 0){
$pretext = "This Resource Group has spiked above expected cost"
}
else {
$pretext = $null
}
$thisFallbackMessage = "$description has spiked above expected cost (Current Cost: $state)"
$fields = @(
@{
title = $description;
value = $state;
});
$thisAttachment = @{
fallback = $thisFallbackMessage;
color = $colour;
pretext = $pretext;
fields = $fields;
}
$payload.attachments += $thisAttachment
return $payload
}
function New-SlackNotification ($hook, $group)
{
$message = New-SlackMessage -resourceGroup $group
Invoke-Restmethod -Method POST -Body ($message | ConvertTo-Json -Depth 4) -Uri $hook
}
if ($reminderGroups -ne $null) {
Write-Output "Sending a Slack notification about these groups"
$reminderGroups
foreach ($group in $reminderGroups) {
New-SlackNotification -hook $slackHook -group $group
}
}
else {
Write-Output "There are no groups which need attention"
}
创建您的新版本。
保存您的新步骤并创建一个新版本!
设置部署时间表(可选)
或者,您可以将云成本项目设置为定期运行。一种可能是每月在某一天运行一次该任务。在您的新项目中,选择触发器➜添加触发器➜预定触发器。
- 在名称部分,提供一个名称来描述该计划,我选择使用“每月 25 日”
- 在触发时间表部分,我已经将它设置为每月天,并将这一天设置为每月的25 日——开始时间为上午 09:00。
- 在触发动作部分,您可以选择想要运行的版本。我已经选择部署这个项目的最新版本。
- 在源环境和目的环境下选择相同的环境。我已经选择了生产环境。
这将导致在每月的 25 日重新部署源环境中的最新版本。
保存您的新计划。
部署您的新版本。
现在让我们开始部署!
解决纷争
如果您的脚本因为找不到Get-AzureRmConsumptionUsageDetails
而无法运行,请确保您的 Octopus 服务器上安装了最新的 AzureRM 模块,并创建另一个名为Octopus.Action.Azure.UseBundledAzurePowerShellModules
的 Octopus 变量,其值为False
。有关您为什么会收到此错误的更多信息,请查看关于配置 Azure PowerShell 模块版本的文档
收尾工作
恭喜你!您已经成功地在 Azure 中部署了检查成本的项目!
让我们检查一下时差通知!
计划的重复部署 RFC - Octopus 部署
原文:https://octopus.com/blog/scheduled-recurring-deployments-rfc
在我们的用户呼声中,第五高的投票项目是能够安排重复部署。
这篇文章是对我们现阶段想法的征求意见。
我们要解决什么?
我们有很多客户告诉我们,他们希望能够制定一个计划,让 Octopus 自动向某个环境推广或部署一个版本。
目前有很多方法可以通过使用我们全面的 API 来做到这一点。但是它涉及到设置一个调度任务,该任务调用一个脚本(或者创建一个 Azure 函数),该脚本调用 Octopus API 来升级/部署有问题的版本。对于许多其他 CI/CD 工具现成提供的东西来说,这可能是一个相当麻烦的解决方案。
根据我们的内部讨论和对 UserVoice 项目发表评论的客户,我们确定我们有几个如何实现该功能的选项。
项目触发器
项目触发器的优势在于,您可以指定在决定推广或部署哪个版本时应该使用哪个渠道,并且可用的环境也将由渠道中使用的生命周期决定。项目触发器也有不好的一面,因为您必须在每个项目上指定触发器。
计划的重复部署配置位于项目的Triggers
选项卡下:
创建新的调度部署触发器会将您带到一个新页面,在该页面中,您可以配置调度详细信息以及触发器运行时应该执行的操作。
添加新触发器后,它将出现在已配置的触发器列表中,并允许您编辑、禁用、克隆或删除触发器。
生命周期触发器
如果您的项目使用通道,则生命周期触发器将只能部署到默认通道(因为通道是特定于项目的)。另一方面,生命周期触发器的优点是您可以在许多项目中共享相同的计划,而不是必须在每个项目中指定它。
定期重复部署配置将位于生命周期的右侧。
【T2
创建新的计划部署触发器将打开一个对话框,您可以在其中配置计划详细信息以及在触发器运行时应该采取的操作。
触发计划
以下触发器计划用于配置触发器运行的时间
- 每天地;天天地
- 每周天数
- 每月天数
- 自定义
CRON
表达式
每日和每周天数计划允许选择时间间隔,可用的时间间隔有:
- 每天一次
- 每隔 x 小时
- 每隔 x 分钟
每日计划
从指定的开始时间开始,按照每天选择的时间间隔跑步。
每周天数计划
从指定的开始时间开始,使用在指定日期选择的时间间隔跑步。
每月天数计划
在一个月中的某一天,或一个月中的第 n 个特定工作日,在指定的开始时间运行。
自定义CRON
表达式
根据一个 CRON 表达式运行。
CRON 表达式是一个字符串,包含由空格分隔的五或六个字段,表示一组时间,通常作为执行某个例程的时间表。
触发动作
以下触发操作可用于配置运行计划部署时应该发生的情况:
- 推广最新版本
- 部署最新版本
- 部署新版本
将所选源环境中最新的成功的版本升级到所选目标环境。
部署最新版本
将项目中最新的成功版本部署到所选的环境中。
创建和部署新版本
创建一个新版本,并将其部署到所选的环境中。
反馈
我们非常希望收到您的来信!也许我们在你的场景中遗漏了一些拼图?
如果您对计划的周期性部署有任何其他想法或意见,请在下面添加您的评论或评论并关注开放的 GitHub 标签#3363 。
扩展的脚本模块支持- Octopus 部署
很长一段时间以来,我们一直听到要求脚本模块支持 PowerShell 之外的语言。我们很高兴地分享这刚刚在我们的最新版本- 2019.5.0 中发布!
对一些人来说,这是个好消息。对于其他人,你可能会问“什么是脚本模块?”
脚本模块是可以在多个项目之间共享的功能集合。通过允许集中定义和管理功能,而不是在项目之间复制和粘贴代码,这些可以使您的生活更加轻松。在库->脚本模块下找到它们。
一旦定义了脚本模块,就可以在项目中使用它。需要注意的一点是,新语言需要显式导入;它们不会像 PowerShell 脚本模块那样自动导入。这有利于使脚本模块的依赖性更加明确,并防止命名冲突和意外行为。
在 C#中,这是通过脚本顶部的#load <scriptmodulefilename.csx>
调用来实现的:
脚本模块主体上方的帮助文本将显示您需要的确切语法。
使用
另一个我们引以为豪的特性是脚本模块的使用。我们已经展示了您的脚本模块被使用的地方,因此很容易看到哪些项目可能会受到您的更改的影响。这将意味着在部署过程中减少意外,这总是一件好事。我们现在显示哪些项目和版本有参考:
我们还计划很快在变量集上展示这种用法。敬请期待!
看看脚本模块文档中每种语言的演练和示例。
愉快的部署!
脚本步骤中的包- Octopus 部署
在 Octopus 2018.8 脚本步骤正在进化,并获得一些新的超能力。
软件包++
我们增加了向脚本步骤家族添加包引用的能力:
Run a Script
Run an Azure PowerShell Script
Run an AWS CLI Script
以前,这些步骤能够引用包含要运行的脚本的单个包。他们现在也可以引用不包含脚本的包。是的,包裹,复数。哦,当我们说包装时,我们包括集装箱图片(见下面的)。
为什么?
脚本经常需要与包进行交互。他们可能需要读取/写入包中的文件(例如执行包中包含的另一个脚本),或者在某些情况下只需要包元数据(例如更新 AWS ECS 服务使用的映像版本)。
以前,当从脚本访问文件时,有两种模式:
1)“包装”包装
Octopus 早就有能力使用一个包作为要执行的脚本的源。这样做的一个副作用是,包首先被提取,然后里面的文件与正在运行的脚本文件放在一起。这使得许多精明的用户意识到他们可以将他们的脚本嵌入到他们想要使用的包中,或者反过来将包嵌入到脚本所在的包中。
这是可行的,但是我们不希望强制创建这些人为的包装包。
2)“首先部署包”模式
在这种模式中,一个或多个部署包或传输包步骤被配置为将包推送到目标服务器上的某个位置。然后,脚本步骤简单地假设已经部署了包。
这种方法的主要缺点是它只能用于在目标机器上执行脚本。它不能用于运行在 Octopus 服务器或工作程序上。这也使得部署过程变得更加复杂。
容器图像
您现在可以从脚本步骤中以一流的方式引用容器图像了!
这意味着图像的版本(图像标签)将在发布创建时被捕获,就像任何其他包一样。
容器映像可以配置为在执行目标上获取,或者根本不获取。例如,在上图中,我们正在更新 AWS ECS 服务使用的容器映像的版本。因为我们只是简单地使用元数据,所以我们不需要承担本地提取图像的成本。
一个例子:NuGet Push
我们在 Octopus 总部的一个真实示例场景是将包推送到 NuGet。
我们章鱼的释放过程。客户。NET 库将把包推送到一个 NuGet 库。对于我们的Test
环境,我们推送到一个私有的 MyGet 提要,然后对于我们的Production
发布,我们推送到nuget.org
这涉及两个包:
- 来自 nuget.org 的
NuGet.CommandLine
,我们需要提取它来运行nuget.exe push
Octopus.Client
哪一个是我们刚刚构建的包,我们想把它解压缩到一个 NuGet 存储库中
我们之前跳了“首先部署包”舞。借助新的脚本步骤功能,部署流程变成了:
当我们添加NuGet.CommandLine
包引用时,我们指定它应该被提取。我们还给它起了一个名字nuget
,方便从我们的自定义脚本中使用。
对比一下Octopus.Client
包引用,我们指定而不是被提取。
我们发布包的脚本变成了:
# Build the path to nuget.exe
$nugetPackagePath = $OctopusParameters["Octopus.Action.Package[nuget].ExtractedPath"]
$nugetExe = Join-Path -Path $nugetPackagePath -ChildPath "Tools\nuget.exe"
# nuget push
. $nugetExe push Octopus.Client.nupkg -source $DestinationRepository
自定义脚本可以通过两种方式使用引用的包。
包文件
如果包引用被配置为提取,那么包将被提取到脚本工作目录下的子目录中。该目录将被命名为与包引用相同的名称。在上面的例子中,名为nuget
的包引用将被提取到类似于C:\Octopus\Work\20180821060923-7117-31\nuget
的目录中(这显然是一个 Windows 目录;在 Linux 目标上执行的脚本可能有一个路径,比如/home/ubuntu/.octopus/Work/20180821062148-7121-35/nuget
。
如果包引用被而不是配置为提取,那么未提取的包文件将被放置在工作目录中。该文件将被命名为包引用名,扩展名与原始包文件相同。在 abolve 示例中,Octopus.Client
包引用解析为一个 nupkg 包,文件将被复制到一个路径,比如C:\Octopus\Work\20180821060923-7117-31\Octopus.Client.nupkg
(对于 Linux: /home/ubuntu/.octopus/Work/20180821062148-7121-35/Octopus.Client.nupkg
)。
这些位置被设计成便于从定制脚本中使用,因为相对路径可以预测,例如./nuget
。如果需要绝对路径,可以使用变量(见下文)。
包变量
包引用贡献变量,这些变量可以像任何其他变量一样使用。这些变量是(使用nuget
示例):
可变的 | 描述 | 示例值 |
---|---|---|
章鱼。Action.Package[nuget]。PackageId | 包 ID | 努杰。命令行 |
章鱼。Action.Package[nuget]。FeedId | 订阅源 ID | feeds-nuget-org |
章鱼。Action.Package[nuget]。包版本 | 发行版中包含的软件包版本 | 4.7.1 |
章鱼。Action.Package[nuget]。提取路径 | 提取目录的绝对路径 | c:\ Octopus \ Work \ 2018 0821060923-7117-31 \ n 获取 |
章鱼。Action.Package[nuget]。PackageFilePath | 包文件的绝对路径(不会为nuget 示例设置,因为它被配置为提取) |
c:\章鱼\工作\ 2018 08 21060923-7117-31 \章鱼。Client.nupkg |
章鱼。Action.Package[nuget]。包文件名 | 包文件的名称(不会为nuget 示例设置) |
章鱼。Client.nupkg |
变量替换
还有一点...
我们已经为脚本步骤启用了文件变量替换特性。
这允许将 Octopus 变量替换到引用包中包含的文件中。
当...的时候
Octopus 2018.8 中提供了脚本步骤的包引用,当您阅读本文时,该版本将可供下载。
多套餐部署愉快!
使用 Octopus - Octopus Deploy 将安全的 Web 应用程序部署到 Tomcat
有了 Octopus 4.1,您现在可以从 Maven 资源库部署应用程序,并配置由 Octopus 管理的证书。在这篇博文中,我们将看看如何使用这些新步骤在 Tomcat 中部署和保护 web 应用程序。
下载 Tomcat 9
在这个演示中,我们将在 Windows 2016 上使用 Tomcat 9。你可以从这里下载 Tomcat 9。获取 Windows installer,因为这是将 Tomcat 配置为 Windows 服务的最简单的方法。
默认情况下,Tomcat 9 会安装到C:\Program Files\Apache Software Foundation\Tomcat 9.0
。这个目录在后面的步骤中称为CATALINA_HOME
。
下载八达通部署 4.1
从下载页面获取 Octopus Deploy 4.1 的副本。4.1 版包括许多新的步骤和功能,用于集成 Maven repos 和部署证书。你可以从文档中找到更多关于安装 Octopus 的信息。
Octopus 4.1 目前处于测试阶段,所以如果现在还不能从下载页面获得,那也很快了。看好这个空间!
将 Maven Central 配置为外部提要
Maven central 是 Maven 构建的默认存储库,也是大多数公共 Maven 工件最终部署的地方。在 https://repo.maven.apache.org/maven2/可以找到它。我们将在 Octopus 中添加这个存储库作为名为Maven Central
的外部提要,以允许我们在部署过程中使用它的工件。
当配置外部 Maven 存储库时,我们需要链接到存储库本身,而不是用来搜索存储库的服务。例如,像 https://search.maven.org/的、https://mvnrepository.com/的或者 T2 的这样的网址是无法进入的,因为这些是用于搜索知识库的网站,而不是知识库本身。
添加证书
运行以下命令来生成自签名 ECDSA 证书。
openssl ecparam -genkey -out ecdsa.key -name prime256v1
openssl req -x509 -new -key ecdsa.key -out ecdsa.crt
openssl pkcs12 -export -out ecdsa.pfx -inkey ecdsa.key -in ecdsa.crt
然后把 PFX 的文件上传到 Octopus。
阅读博客文章Tomcat 中的组合键以获得关于这些键如何在 Tomcat 中工作的更多信息。
创建部署项目
在 Octopus 中创建新项目,并添加Deploy to Tomcat via Manager
步骤。
在这一步中,我们将部署com.github.gwtmaterialdesign:gwt-material-demo
WAR 文件。这个 web 应用程序是 GWT 材料项目的一个演示,该项目方便地将一个样例应用程序作为 WAR 文件发布到中央 Maven 存储库中。我们将利用这一点来演示如何将 Maven 存储库中托管的 web 应用程序直接部署到 Tomcat。
将Context path
设置为/demoapp
。这是我们将用来在 Tomcat 上打开应用程序的路径。
部署证书
创建一个变量来引用之前创建的 ECDSA 证书。我们需要在证书部署步骤中使用这个变量。
接下来添加Deploy a certificate to Tomcat
步骤。选择证书变量,将CATALINA_HOME
字段设置为C:\Program Files\Apache Software Foundation\Tomcat 9.0
,选择NIO2
SSL 实现,并将端口设置为8443
。
在生产场景中,您不会将证书与应用程序一起重新部署。一旦部署了证书,通常只需在证书更新时每年重新部署一次。我们将证书部署包含在同一个项目中,只是为了演示如何配置这些步骤。
重新启动服务
最后一步是重新启动 Tomcat 服务,让它获得新的证书配置。
添加一个Run a Script
步骤,并运行一个 PowerShell 脚本来重启Tomcat9
服务。
Restart-Service Tomcat9
Tomcat9
是 Tomcat 安装程序为我们创建的服务。
部署应用程序后,Tomcat 不需要重新启动。运行这个脚本是为了让 Tomcat 获得证书。
运行部署
部署运行时,Octopus 会自动确定 Maven 工件是 WAR 文件,并选择最新版本进行部署。
部署完成后,您会发现下面的 XML 已经添加到 Tomcat conf/server.xml
文件中。这就是 Tomcat 支持 HTTPS 的配置方式。
<Connector SSLEnabled="true" port="8443" protocol="org.apache.coyote.http11.Http11Nio2Protocol">
<SSLHostConfig>
<Certificate certificateKeyAlias="octopus" certificateKeystoreFile="${catalina.base}\conf\Internet_Widgits_Pty_Ltd1.keystore" certificateKeystorePassword="changeit" type="EC"/>
</SSLHostConfig>
</Connector>
测试结果
打开https://localhost:8443/demo app/index . html。您将看到正在显示的演示应用程序。
然后我们可以通过打开 Chrome 开发者工具并查看Security
标签来验证证书。
单击View certificate
确认 Tomcat 现在已经配置了自签名证书。
结论
有了 Octopus 4.1,您现在可以通过内置的步骤管理您的应用程序和证书部署生命周期,同时无缝地使用 Maven 存储库中的工件。
如果您对 Java 应用程序的自动化部署感兴趣,下载 Octopus Deploy 的试用版,并查看一下我们的文档。
使用 Kubewarden - Octopus Deploy 保护您的 Kubernetes 集群
原文:https://octopus.com/blog/securing-kubernetes-cluster-with-kubewarden
Kubernetes 正在迅速成为云的操作系统。每个主要的云提供商都有一个受支持的 Kubernetes 平台,Kubernetes 可以在内部运行,Kubernetes 甚至有一个带 Helm 的包管理器。多亏了操作模式,Kubernetes 拥有内置的支持来描述和管理几乎任何类型的工作负载。
这种灵活性是福也是祸。Kubernetes 几乎可以运行任何东西,但是几乎不可能维护任何真实世界的生产集群来托管任何随机的东西。
Kubernetes 基于角色的访问控制(RBAC)提供了对集群托管的资源的某种级别的控制。但是,RBAC 只允许创建顶级资源,如部署或 pod。
一个单元几乎可以承载任何东西,所以仅仅允许或禁止一个单元的部署通常是不够的。相反,在允许或拒绝之前,团队需要检查给定 pod 的属性。
准入控制器通过将资源传递给定制服务,提供检查、修改、接受或拒绝新资源的能力。这允许对集群中创建的资源进行细粒度的控制,并确保只部署那些满足您特定需求的资源。
这篇文章着眼于 kube warden 准入控制器,这是一个早期的项目,支持用编译成 WebAssembly 的多种语言编写的准入策略。
我将带您在 Octopus 中创建一些操作手册和部署来管理 Kubewarden,并将 pods 部署到 Kubernetes,测试定制的准入策略。
安装 Kubewarden
安装 Kubewarden 最简单的方法是通过它的舵图。
在 Octopus 中创建一个指向https://charts . kube warden . io的新 Helm Feed:
Kubewarden 是通过一本手册安装的。Runbooks 对于管理任务很有用,比如部署跨领域服务,因为它们不受生命周期进程的限制。这允许在部署生命周期的后期在环境中启动新的集群(就像新的生产集群一样),而无需首先在生命周期的早期环境中部署服务:
部署一个可疑的吊舱
为了演示 Kubewarden 如何保护您的集群的安全,请部署下面的 pod。这个 pod 赋予自己SYS_TIME
功能,并使用date
命令来设置容器中的系统时间。
pod 几乎没有理由设置系统时间,可以想象改变时间会导致应用程序出现故障。例如,如果日期倒退,仓库库存服务可能会下多个订单,或者任务调度程序可能会在不正确的时间触发作业。
尽管下面的窗格仅设置日期并退出,但它能够这样做表明其他窗格也可以部署设置日期的功能:
apiVersion: v1
kind: Pod
metadata:
name: settime
spec:
containers:
- name: ubuntu
image: index.docker.io/ubuntu
command:
- "/bin/bash"
- "-c"
- "date -s '19 APR 2012 11:14:00'"
securityContext:
capabilities:
add:
- SYS_TIME
此 pod 部署有一个部署原始 Kubernetes YAML 步骤:
果不其然,pod 被部署并更新其容器中的时间。这不是我们想要的结果,所以下一步是添加一个 Kubewarden 策略来防止这样的资源被部署。
部署准入策略
像所有 Kubernetes 资源一样,Kubewarden 政策可以在 YAML 定义。下面的例子使用 psp-capabilities 策略在创建或更新时从所有 pod 中删除SYS_TIME
功能:
apiVersion: policies.kubewarden.io/v1alpha2
kind: ClusterAdmissionPolicy
metadata:
name: psp-capabilities
spec:
module: registry://ghcr.io/kubewarden/policies/psp-capabilities:v0.1.3
rules:
- apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
operations:
- CREATE
- UPDATE
mutating: true
settings:
required_drop_capabilities:
- SYS_TIME
使用操作手册中的部署原始库本内特 YAML 步骤部署资源:
现在再次打开逃生舱。这一次部署失败了,因为 Kubewarden 策略阻止部署具有SYS_TIME
功能的 pod,并出现以下错误:
Error from server: error when creating "customresource.yml": admission webhook "psp-capabilities.kubewarden.admission" denied the request: PSP capabilities policies doesn't allow these capabilities to be added: {"SYS_TIME"}
结论
Kubewarden 是一个准入控制器的例子,它根据分配给各个资源属性的值,在创建资源时接受、拒绝或修改资源。这使得运营团队能够确保只部署满足其特定要求的资源。它提供了一个比标准库本内特斯 RBAC 更高水平的控制。
在这篇文章中,您使用 Helm chart 部署了 Kubewarden,使用 Deploy raw Kubernetes YAML 步骤部署了一个策略,并看到了不符合策略规则的 pod 部署是如何被拒绝的,从而确保了具有已知无效配置的 pod 不能被应用到集群。
观看网络研讨会
https://www.youtube.com/embed/qlsk8zdTcLA
VIDEO
我们定期举办网络研讨会。请参见网络研讨会页面,了解过去的网络研讨会和即将举办的网络研讨会的详细信息。
愉快的部署!
介绍八达通保安建议-八达通部署
不足为奇的是,软件产品有安全漏洞,或者当它们确实发生时,对于供应商来说,重要的是要有有效的沟通策略来确保系统是安全的,用户不会容易受到攻击。
目前八达通产品的安全漏洞被披露在 CVE 数据库和我们的产品发布说明,但是,我们相信我们可以加强这种沟通,以更好地保护我们的客户。
我们目前如何通知客户产品安全漏洞
当我们发现或被告知我们产品中的安全漏洞时,我们会修补漏洞,创建 CVE,创建 GitHub 问题,并使用补丁和 CVE 号更新我们的发行说明。
在通知我们的客户安全漏洞方面,我们总是假设我们的客户要么正在阅读发行说明,要么正在定期检查 CVE 数据库中的任何安全漏洞。但是,我们相信我们可以做得更多,来帮助我们的客户获得他们需要的信息,以确保他们的环境保持安全。
为了解决这个问题,我们推出了八达通安全咨询。
八达通安全咨询
Octopus 安全公告本质上是发行说明、CVE 详细信息和我们希望在一个地方向客户提供的额外信息的集合,这样客户就不必去寻找信息。
安全咨询中提供的一些信息包括:
- 基于 Octopus 部署评估的漏洞严重性。
- 关于漏洞的详细信息,包括受影响的版本、已修复的版本和其他相关信息。
- 是否有任何已知的漏洞利用。
八达通什么时候会发布安全公告?
八达通保安小组会在下列情况下发出保安通告:
- 在存在影响多个 Octopus 产品的严重或高风险漏洞的情况下,将发布初步建议。
- 发布了 CVE 和修补程序来修复 Octopus 产品中的特定漏洞。
- 如果我们认为我们的客户可能会成为不良分子的目标,例如提供识别假 Octopus Deploy 电子邮件的指导。
八达通安全警告将张贴在哪里?
安全建议将被发布到advisories.octopus.com,安全建议的链接将被发布到Octopus Deploy Twitterfeed。
更多信息
有关 Octopus Deploy 2021 年安全之旅的更多信息,您可以查看我们的信托&安全路线图。
如果您需要报告漏洞,请通过security@octopus.com联系我们。
为你八达通系统进行安全检查
你会照顾自己的章鱼吗?我们也是。我们仍然有自己的 Octopus 服务器,作为部署我们构建的大多数东西的宠物。就像我们一样,大多数人可能都有自己的宠物章鱼——但你的章鱼有多安全呢?
我们最近发布了章鱼云 alpha,在那里我们学到了,也重新学到了很多关于如何让章鱼安全的经验。在这篇文章中,我将向你展示如何给你的章鱼做安全检查。
不想再照顾自己的章鱼了?也许你应该今天就报名参加章鱼云?
想要完整的图片吗?这里是我们关于强化章鱼的深度指南。
安全地暴露你的八达通服务器
为了让 Octopus Server 做有用的事情,您需要向您的用户、您的基础设施以及可能的外部服务公开它。如果你将你的 Octopus 服务器暴露给公共互联网或第三方,你绝对应该使用 SSL 上的 HTTPS。有了对的内置支持,让我们加密,就没有借口在 HTTP 上公开你的 Octopus 服务器了。
了解如何安全地暴露你的八达通服务器。
另外,你知道 Octopus 与代理服务器 T1 一起工作吗?
安全地在你的八达通服务器上工作
您的部署过程通常会处理包并执行脚本。这些包经常被推送到一个触手或 SSH 部署目标,您的脚本将在这些机器上执行。然而,许多部署并不需要像对云服务或类似的部署那样的触手或 SSH 目标。在这种情况下,如果您不得不设置一个 Tentacle 或 SSH 目标,只是为了将一个包推送到一个 API 或运行一个脚本,但您并不关心该脚本在哪里运行,这将是令人恼火的。
在 Octopus 3.0
中,我们引入了 worker 的概念,它可以处理包和执行脚本,而不需要安装和配置触手或 SSH 目标。默认情况下,使用内置工作器的 Octopus 服务器运行在与 Octopus 服务器相同的安全上下文中。当你开始跑步时,这非常方便,但这不一定是长期坚持跑步的最佳方式。
我们建议将每台 Octopus 服务器配置为使用内置工作器作为不同的用户,或者使用外部工作器。
了解工人。
强化您的主机操作系统
你的八达通服务器的主机操作系统有多安全?如果你不确定,这里有一些提示!
强化您的网络
你允许自由进出你的八达通服务器吗?Octopus 实际上使用了少量定义明确的网络协议。我们还提供了一些提示,可以防止攻击者使用您的 Octopus 服务器作为进入您网络的媒介。
了解强化你的网络。
提升
你运行的是最新版本的八达通服务器吗?一般来说,最新版本的八达通服务器将是最安全的。你应该考虑一个保持你的 Octopus 服务器更新的策略。我们遵循负责任的披露政策,因此您有可能知道任何影响您的八达通服务器的安全性和完整性的已知问题。
结论
Octopus 是以安全为首要考虑因素的——但是你仍然需要做一些工作来保证你的 Octopus 的安全。这篇博文有一些提示和技巧。如果你想给你的章鱼做一个全面的检查,使用我们的深度指南来强化章鱼。
如果你不想照顾自己的章鱼,也许你应该今天就报名章鱼云?
云虚拟机中的自托管 Octopus 与 Octopus 云- Octopus 部署
原文:https://octopus.com/blog/self-hosted-octopus-cloud-vm-vs-octopus-cloud
考虑将 Octopus 迁移到云基础架构的客户向我们提出的一个常见问题是:
我应该迁移到虚拟机上的 Octopus 服务器还是托管的 Octopus 云?
在本帖中,我们来看看为什么你会选择其中一个。成本和安全是主要考虑因素,但我们也会考虑你如何使用八达通。
章鱼云是指 Octopus Deploy 提供的章鱼云 SaaS 和章鱼云服务器是指 Octopus Deploy 的自托管版本。在本文的上下文中, Octopus 服务器位于云中的一个虚拟机上。
体系结构
在我进入细节之前,让我们看一下我们正在讨论的 Octopus 的两个化身的架构。
服务器
Octopus 服务器作为 Windows 服务运行;这提供了对 Octopus REST API 和 Octopus 门户网站的访问。它连接到一个 SQL Server 数据库,并使用文件共享来存储任务日志、工件和包。
云
Octopus Cloud 使用一个运行在 AKS 上的 Linux 容器,数据库托管在 Azure SQL 中。文件共享使用 Azure 云存储。
管理
选择 Octopus Cloud 减少了您必须在 Octopus 实例上执行的管理任务的数量;然而,出于某些原因,您可能希望 Octopus 服务器提供更大的控制。
保留策略和存储
Octopus 保留策略在所有 Octopus 实例上都有一个默认配置,我们建议查看它们以满足您的需求。
服务器
当您在虚拟机上配置 Octopus 服务器时,您选择 Octopus 用于服务器文件夹的存储。这意味着您可以为这些文件夹设置适合您的存储限制,并相应地配置保留策略。
云
章鱼云为大多数企业提供了充足的存储;然而,你可以使用的存储量有一个限制。在法规遵从性要求长期保留的情况下,您可能会发现这是选择 Octopus Server 而不是 Octopus Cloud 的原因。
我们在 Octopus 实例管理页面的技术部分添加了监控。要查看详细信息,请登录您的 Octopus 帐户,然后在您的实例上,选择管理➜资源使用。这是每 24 小时更新一次。
Octopus 有一个对所有实例都可用的内置包存储库,除此之外,可以配置外部存储库提要来为您的部署包服务。您可能会选择使用外部 feed 来帮助进行多区域部署,或者如果您的包 feed 存储需求相当大的话。
维护窗口
云
章鱼云的一个伟大之处在于,你不必担心如何以及何时进行系统维护和升级。Octopus 为您管理这些,您可以选择适合您的业务和地区的维护窗口。
中断窗口信息位于 Octopus 实例管理页面的技术部分:
服务器
Octopus Server 允许您更具体地确定何时执行系统维护和升级。如果您实施了高可用性,那么您就可以在进行维护时实现零停机。一些客户选择通过使用章鱼的另一个实例中的run book来自动化维护和升级任务。一个免费的 Octopus Cloud 实例适合这个任务。
管理基础设施
服务器
有了 Octopus Server,您可以使用适合您企业运营架构的基础设施,并且任何基础设施管理流程和策略都可以用于您的 Octopus 实例。因为您托管您的 Octopus 服务器,所以您有责任确保基础设施运行良好、得到维护、可恢复并具有扩展能力。
云
使用 Octopus Cloud 可以让您从基础架构管理中后退一步。我们有责任确保基础架构的强健,并根据您的使用情况进行扩展。
工人
Octopus 服务器的默认工作池和 Octopus Cloud 有明显的区别。
服务器
在 Octopus Server 中,默认工作器是 Octopus 服务器本身,而 Octopus Cloud 不允许服务器执行外部操作。
当您使用 Octopus 服务器时,我们建议尽可能使用 Worker 来减少 Octopus 服务器上的资源使用,并且由于 Octopus 是在特权帐户下执行的,如果您可以以 Worker 的形式将工作卸载到另一台机器,那么为了增加安全性,这样做是明智的。
云
章鱼云从特定于其区域的工人池中租赁一台动态工人机器。
备份
云
备份是 Octopus Cloud 托管服务的一部分。备份数据库、内置软件包存储库和配置。如果您使用外部程序包馈送,则您有责任确保配置备份。
服务器
当作为 Octopus 服务器安装的一部分管理您的基础设施时,您可以设计您的备份和恢复计划来匹配您现有的基础设施计划。
监视
Octopus 订阅在 Octopus Cloud 和 Octopus Server 中都可以使用。您可以配置通知来通知您 Octopus 中的更改,例如,特定项目流程的更改、用户修改、部署目标的添加。有一个很长的名单。
REST API 有一个报告端点,您可以用它来创建一个仪表板,在 Octopus 云和 Octopus 服务器中都可用。
使用 Octopus Server,您还可以通过向 nlog config 文件添加一个日志目标来向中央日志工具发送日志。
数据库ˌ资料库
云
客户无法访问 Octopus 云数据库,因此与 Octopus 的所有交互都必须使用 web 界面或 API 来完成。最棒的是数据库维护、安全和修补都是由我们完成的。
服务器
云虚拟机上的 Octopus 服务器为您提供了几个数据库托管选项:
管理数据库的好处是,您可以选择更宽松的保留策略,能够使用集群数据库,并管理自己的备份。
安全性
云
借助 Octopus Cloud,我们可以保护基础设施并进行渗透测试,以确保 Octopus 实例的安全性和完整性。但是,您仍然要负责:
- 如何将 Octopus 连接到您的基础设施。
- 你如何识别你的用户和控制他们在八达通的活动。
- 你如何处理八达通内的敏感信息。
服务器
当你使用 Octopus 服务器时,你有责任强化Octopus 实例基础设施。如果您需要遵守特定行业的法规,这种级别的控制可能是有益的。在这种情况下,您还需要承担以下责任:
- 如何强化底层服务器操作系统。
- 如何保护操作系统上的 Octopus 服务器文件。
- 你如何储存八达通服务器生成的文件。
- 你如何保护你的 SQL 数据库和八达通服务器产生的数据。
- 你如何将你的 Octopus 服务器暴露给你的基础设施?
- 你如何识别你的用户和控制他们在八达通的活动。
- 你如何处理八达通内的敏感信息。
当向 Octopus 云或 Octopus 服务器基础设施添加部署目标和工作机时,它们的安全性和完整性是您的责任。
身份验证提供商
云
章鱼云认证使用章鱼 ID ,允许你使用在octopus.com登录时使用的同一个账户。Octopus ID 支持用户名和密码,也可以使用谷歌或微软账户。
Octopus ID 允许 Octopus 用户映射到 Google 或 Microsoft 帐户,但是它不允许映射到外部组,这在 Active Directory 中是可能的。
服务器
Octopus 服务器有几个可用的身份验证提供程序:
可以同时配置多个提供程序。
IP 限制
云
一个 Octopus 云实例有一系列在同一个 Azure 区域的客户之间共享的静态 IP 地址。这些可以在 Octopus 实例管理页面的技术部分找到:
服务器
当您在虚拟机上使用 Octopus 服务器时,您可以配置适合您业务需求的 IP 和 DNS。您可以选择配置一个负载平衡器来处理 Octopus web 接口的流量;如果您配置了高可用性,这将非常有用。
组织项目和环境
空间在八达通云和八达通服务器上都可用。空间在项目和基础设施之间建立了一堵硬墙,因此您的每个团队只能访问他们需要的项目和基础设施。
服务器
并发许可证是 Octopus Server 的一个额外好处。每个许可证可以使用三个实例,这意味着您可以运行一个 Octopus Deploy 服务用于生产,并为开发或测试设置额外的服务。或者,您可以使用两个单独的 Octopus 部署实例来隔离生产和生产前部署。
建立工作关系网
通过 WebSockets 将轮询触角连接到 Octopus Cloud 目前是不可能的,但我们确实计划很快支持轮询触角连接到 443 端口。
代理支持
章鱼服务器和章鱼云都支持代理。您可以为 Octopus 指定一个代理服务器,以便在与触手或 SSH 目标通信时使用。当触手和 Octopus 服务器向其他服务器发出 web 请求时,您也可以指定一个代理服务器。
地区
服务器
在虚拟机上使用 Octopus 服务器可以让您在自己选择的地理区域托管 Octopus 实例。
云
Octopus Cloud 还为您提供了在适合您的地区托管实例的选项。我们使用 Microsoft Azure 托管 Octopus Cloud,在撰写本文时,我们提供以下区域:
- 美国西部 2
- 西欧
- 澳大利亚东部
当您第一次创建 Octopus 云实例时,会选择该区域。
如果我们推出更适合您需求的新区域,请联系我们,以便我们将您的实例转移到该区域。
区域套餐订阅源
如果您在多个地区运营,并且担心在部署期间传输大型包时出现网络延迟问题,我们建议您选择外部包馈送并在多个地区复制它。通过 DNS 别名,您可以让触手从地理上本地的包存储库中获取包。或者您可以拥有两个独立但相同的包存储库,并使用一个变量作为提要 ID 。对于 Octopus Server 和 Octopus Cloud ,包存储库都以这种方式工作。
高可用性
可以为 Octopus 服务器配置高可用性 (HA)来运行多个 Octopus 服务器,在它们之间分配负载和任务。
Octopus HA 不可用于 Octopus Cloud,但所有实例都被配置为运行在 AKS (Azure 的托管 Kubernetes)上的 Linux 容器。 Kubernetes 帮助我们实现章鱼云的弹性和伸缩灵活性。
其他考虑
触须
章鱼触手机器可以配置为轮询或监听通信模式。您为部署选择的触角类型将取决于您的业务需求。您还应该考虑这些机器的配置是否对您的 Octopus 实例所选择的基础设施有任何影响。
触手防火墙规则
你使用的触手类型将取决于你的防火墙规则。侦听触手必须具有入站防火墙规则,以允许通过 TCP 端口 10933 进行访问。轮询触手不需要触手上的任何防火墙规则,但是 Octopus 服务器必须允许通过 TCP 端口 10943 上的防火墙入站访问。侦听触手使用较少的资源,但是如果您使用动态 IPs 或者触手位于 NAT 之后,轮询触手将是必需的。
您需要将这些规则应用于任何中间防火墙。
容器中的章鱼
在虚拟机上运行 Octopus 服务器的另一种方法是在容器中运行它。事实上,这就是我们管理 Octopus 云实例的方式。用于 Octopus 的 Windows 容器得到了完全支持,而 Linux 容器在撰写本文时是我们早期访问计划的一部分。
结论
这篇文章有望为您迁移到云基础设施的决策过程提供清晰的思路。无论您的业务需求是什么,Octopus Deploy 使发布管理变得容易,并且简化了即使是最复杂的部署,无论您在哪里部署您的软件。Runbook automation 最大限度地减少停机,并让您能够控制基础架构和应用。
如果你有任何问题或者想知道如何让八达通为你服务,请给我们写信,地址是advice@octopus.com
更多信息
利用 Octopus Runbooks 和 Redgate SQL Clone - Octopus Deploy 实现自助式数据库供应
TL;博士;医生
我想写关于自助式数据库环境创建的文章。
它对任何开发过程都是至关重要的,并且对生产率、质量和安全性有着巨大的影响。这也是一个迷人的技术挑战,通常结合了基础设施即代码、测试数据管理、自动化和有趣的数据虚拟化技术。由于理论在很大程度上是假设的,除非它被转化为实践,所以我想包括一个逐步走查,以便您可以创建自己的概念证明。
不用说,这篇文章有点长。为了分解它,我跟随了西蒙·西内克的《为什么?怎么会?什么? 概念将它分割成一口大小的部分。你可以从你最感兴趣的部分开始:
为什么?这很重要
2018 年,Nicole Forsgren、Gene Kim 和 Jez Humble 为我们带来了 Accelerate 。该书阐述了 4 个关键指标,这些指标被证明可以预测任何组织中卓越的 IT 性能和积极的业务成果(包括盈利能力、市场份额和生产力)。这些指标是:
- 部署频率
- 研制周期
- 平均恢复时间(MTTR)
- 更改失败百分比
作者清楚地解释了通过关注这些指标,企业如何逆转开发和运营之间(速度和安全之间)的核心长期冲突,并创建更小、更规则和更安全的发布的良性循环。这导致了更好的灵活性、创新性,以及更好的业务成果。
为了缩短交付周期,有必要了解将待办事项中的任务投入生产所需的所有工作。流程中的某个地方总是存在瓶颈或约束,导致延迟、严重的交付周期,并导致大量的在制品(WIP)、更大更复杂的发布,以及更频繁的灾难。
在超越凤凰计划中,Gene Kim 评论道:
我发现令人惊奇的是,当一个组织从几个月,甚至几个季度的代码部署交付时间减少到几分钟时,约束以一些相当可预测的方式移动。
他继续告诉我们,这种约束倾向于按顺序落在以下位置:
- 环境创造
- 代码部署
- 测试
- 体系结构
- 创新ˌ革新
我帮助人们管理他们的数据库代码。从我的角度来看,吉恩是个大富翁。
许多人被共享的开发数据库所困扰。对于这些人来说,管理多项工作的并发开发是一个可怕的泥潭,充满了不必要的官僚主义、繁琐的过程和代价高昂的错误。糟糕的交付周期和大量的在制品加剧了这种情况。
旧的测试代码和大的、复杂的、和/或长期以来被放弃的或未完成的工作在开发和测试数据库中徘徊。同时,生产修补程序经常缺失。这破坏了所有的测试,并且任何自动化部署的尝试都被留下所有垃圾的需求所阻碍——以防万一。这同时使得部署过程(无论是手动的还是自动的)更加复杂和不可靠,进一步加剧了核心的长期冲突。
为了打破这种恶性循环,并实现更快的交付时间和部署频率,有必要转向一种模型,在这种模型中,开发人员和测试人员可以按需使用可任意处理的开发和测试环境,包括数据库,并在工作完成后丢弃。这确保了每个人看到的是代码的真实版本,没有被共享环境中的垃圾所感染。
这对您的 IT 绩效具有变革性的影响,因为(对大多数组织而言)它有助于解决与前 3 个瓶颈相关的许多挑战,从而使交付周期大幅缩短。随之而来的必然是部署频率的大幅提高。
此外,通过在 Accelerate 的前两个(以速度为导向的)关键指标上取得重大进展,您几乎可以肯定地看到后两个(以安全为导向的)指标上的同步改进。这些指标用良性循环取代了核心的长期冲突,在良性循环中,速度提高了安全性,安全性提高了速度。
然而,可任意使用的数据库的自助供应通常感觉是最难解决的问题,尤其是当您有大量和/或敏感的数据要考虑时。同时,这可能是您显著提高 it 团队绩效,进而提高业务成效的最佳机会。
这篇文章向你介绍了一些可能有帮助的技术。最后,我使用 Octopus Deploy 和 Redgate SQL Clone 创建了一个概念验证解决方案。如果你还没有使用八达通,你可以开始免费试用。 Redgate SQL Clone 也提供免费试用。
怎么会?介绍技术
Rob 出版了一本关于运营手册的概述。
Octopus Runbooks 是一种组织、审计和共享用于执行常规操作任务的脚本和流程的方式。它们有助于提高效率、共享知识并减少错误。它是一个调度任务或使用户能够按需触发任务的平台,而不需要个人访问相关的服务器、脚本或其他工件。
这是一个为开发人员或测试人员构建自助服务环境创建流程的绝佳平台。
例如,runbooks 允许 DBA 在合适的时间管理源数据映像和配置过程。源数据映像适合开发人员或测试人员使用,开发人员/测试人员可以按照自己的计划使用源映像,而不会打扰可能(例如)忙于处理 sev-1 中断的 DBA。这同时减少了开发团队的延迟和运营人员的中断。更少的支持票对每个人都是一个胜利。更重要的是,我们刚刚移除了吉恩·金列表中的第一个主要约束(见上图)。
然而,Runbooks 本身没有任何数据库智能。用户需要弄清楚如何创建一个源映像,以及如何自动将其提供给一个适当的开发/测试实例。这是一个挑战,原因有几个。
例如,您可以从使用源映像的生产备份和运行手册开始,将该备份恢复到开发人员的工作站。这立即引发了一些挑战:
- 数据保护:在您的开发和测试环境中,并且可能在开发人员工作站上,不加选择地恢复敏感的生产数据,这是一种导致数据泄露的不良做法。
- 磁盘空间:如果您的生产数据库大于 100GB,您可能会尝试使用一个共享的开发/测试实例(不要这样做,见上文),或者您需要租用/购买大量基础设施来托管所有这些开发/测试数据库。这有可能导致高昂的前期成本以及令人头疼的管理开销。你还会浪费大量的时间和带宽到处复制那些 1 和 0。
进入第三方数据库屏蔽和克隆工具,如 dbaclone (开源)和 Redgate SQL Provision 。
Redgate SQL Provision 有两个组件:SQL Server 的数据屏蔽器(解决数据保护问题)和 SQL 克隆(解决磁盘空间问题)。
dbaclone 是 Redgate SQL Clone 的免费替代品,其工作方式大致相同。然而,它缺乏各种重要的特性,包括用户友好的 UI、细粒度的用户权限,并且(在撰写本文时)没有 Octopus Deploy 步骤模板。它不包括任何数据屏蔽功能,但它被设计为与 dbatools 、协同使用,后者负责。
为了简单起见,在本文的其余部分,我使用 SQL Clone 而不是 dbaclone,但是如果您想尝试 dbaclone,您应该会发现这里提倡的一般模式和实践也适用于您。您只需要编写自己的 PowerShell 脚本,而不是使用您将在下面读到的 SQL 克隆步骤模板。(当你这样做的时候,为什么不做一个好公民,向公众发布你的 step 模板 Octopus Deploy 库,这样其他人也能受益。)
在 Redgate 文档网站上有关于 SQL Clone 如何工作的详细解释,如果你投资于 SQL Clone 或 dbaclone(工作方式相同),我鼓励你阅读。
简而言之,SQL Clone 读取一个源数据库或备份文件,该文件最大可达 64TB,并将它恢复到虚拟硬盘(VHD)上的一个文件共享中,您的开发人员应该对该文件共享具有读取权限。这个过程很慢,而且 VHD 需要足够大才能容纳完整的数据库。
当您拥有 VHD 时,它的速度快如闪电,几乎不需要磁盘空间来创建一个差异 VHD (或差异文件),这实际上是一个返回到源 VHD 的奇特重定向。然而,它有一个重要的区别。对数据库所做的任何更改都存储在本地 diff 文件中,而不是文件共享上的源 VHD 中。这意味着每个开发人员都可以写入数据库,而不会影响源映像或任何其他开发人员。他们实际上有自己的迷你沙箱,我们称之为“克隆”。
因为复制一个克隆是如此容易,即使源数据库是巨大的,如果开发人员破坏了什么也没关系。他们简单地删除它,并拉下一个新的克隆,有点像本地 git 回购。它只需要几秒钟,几乎不占用任何磁盘空间。这是开发、测试、故障排除或试验的安全场所。
当你开始使用它时,感觉就像变魔术一样,但它是虚拟机、容器和数据中心用来有效利用可用系统资源的相同底层技术或概念。
问题是,如果开发人员开始重建大表,更改将存储在本地的 diff 文件中。该开发人员将很快用完磁盘空间。这使得它成为开发和测试大多数模式更改的一个很好的解决方案,但是不适合批量数据操作,比如测试完整的 ETL 加载或数据屏蔽操作。
这意味着数据掩蔽过程需要在创建源映像之前或期间运行,而不是在克隆本身上运行。这还有一个好处,那就是对于任何严肃的数据治理来说,这是一个更加可靠和易于管理的基础,因为敏感数据永远不需要离开生产环境。
我不打算在这里讨论数据屏蔽。这是一个足够大的话题,值得有自己的博文;我这里写的是。有关 SQL 供应的数据屏蔽的更多信息,请查看 Redgate University 。
您还希望确保克隆在物理上靠近源映像。作为一个思考练习,让我们想象一下,你的公司决定要求你所有的开发人员在家工作一段时间。您可能想考虑一下,对于开发人员来说,使用位于中央的基础设施来托管他们的开发沙箱是否更明智,而不是依赖脆弱的 VPN 和开发人员的家庭 Wi-Fi 来处理 VHD 和 diff 文件之间的连接。 Chris Unwin 详细讲述了这一点。)
总之,Octopus Runbooks 和 SQL Provision 是管理开发/测试数据库供应流程的完美组合,原因如下:
- 职责分离:DBA、ops 或数据治理人员可以管理屏蔽和想象流程,但开发和测试人员需要扣动扳机来部署/调配数据库。
- 保护安全边界:安全可以在 SQL Clone 和 Octopus Deploy 中独立管理,这意味着开发人员被意外授权访问任何生产数据的可能性极小。SQL Clone 中的安全性将完全基于数据治理考虑,而 Octopus 中的安全性将基于开发需求。
- 自助服务:开发和测试人员可以即时访问他们工作所需的数据库和数据,而不会出现交接、延迟或中断。
- 打破限制:在撰写本文时,Octopus Deploy 和 Redgate SQL Provision 是帮助您解决自助式数据库供应问题和打破最常见的第一个限制(环境创建)的最佳工具,这种限制会导致交付周期缩短并加剧核心的长期冲突。
演练:使用 Octopus Runbooks 和 SQL Clone 提供开发和测试数据库
本文假设您已经安装了 Octopus Deploy 和 SQL Clone。如果您还没有,请在继续之前遵循以下说明:
SQL 克隆中的准备工作
这篇文章还假设你已经创建了一个图像。
如果您还没有这样做,您可以使用任何您喜欢的数据库来尝试。然而,如果可能的话,我建议使用公共 StackOverflow 数据库作为您的第一个概念证明,因为它很容易掌握,足够大以看到克隆技术的好处,但足够小以避免重大挑战。在现实世界的设置中,我会使用最近的生产备份作为我的映像源。
如果您的源数据库包含不应该存在于开发或测试域中的敏感数据,请考虑在映像过程中运行 SQL 脚本或 Redgate 屏蔽集,以确保映像被适当地匿名化。我不会在这篇文章中讨论屏蔽,因为它本身就是一个大话题,而且 StackOverflow 数据库只包含公共数据。
如果您尚未使用 SQL Clone 生成映像,请遵循以下说明:
创建映像后,在启动 runbook 之前,使用 SQL Clone UI 为映像创建一个克隆。
这是一个好主意,原因如下:
- 它将帮助您理解克隆过程是如何工作的。(在尝试自动化之前,了解如何手动执行任务是有好处的。例如,在将配置抽象成 Octopus Deploy 变量之前,您可以练习上下文 UI 中的术语。)
- 有些步骤,例如添加目标服务器实例,需要从 UI 执行一次,然后才能使用 PowerShell cmdlets 自动执行。通过首先使用 UI 创建一个克隆,应该可以解决所有这些问题。
- 如果您遇到任何问题,如果您更接近失败的原因,就更容易修复它们。当您开始时,由于您仍在学习诀窍,从 SQL Clone UI 内部对 SQL Clone 进行故障诊断可能更容易。
您可以按照以下说明使用 UI 创建您的第一个克隆:
当您知道可以使用 SQL Clone 从您的映像创建一个克隆时,您就可以尝试使用 Octopus Deploy 将其自动化。
Octopus 部署的准备工作
这篇文章假设你有 Octopus 部署环境、触角和部署目标的工作知识(文档)。
配置一个具有网络连接并且可以针对 SQL 克隆进行身份验证的部署目标。为这个目标赋予角色sqlclone
,并将目标添加到一个或多个环境中(例如,开发/测试)。
要预先测试连接性,请尝试从运行触手的虚拟机上手动运行 Connect-SqlClone PowerShell cmdlet(文档)。试着以章鱼触手运行的用户身份运行这个命令。如果连接有任何问题,请检查您的网络、防火墙和 SQL 克隆凭据。
或者,简单地推进到下一步。如果您有任何连接问题,您很快就会发现。
创建操作手册
如果您已经有一个项目来部署您的数据库,您可以在那里添加您的 runbook。如果没有,在 Octopus Deploy 中为您的数据库创建一个项目(参见这些帮助说明)。
现在,您可以在项目中创建一个操作手册。从左侧菜单中选择 Runbooks ,然后点击右上角绿色 ADD RUNBOOK 按钮。
为 runbook 命名并提供合适的描述:
接下来,定义您的操作手册流程。进程是 Octopus 在 Octopus 触手上执行的一系列自动化步骤,以完成手头的任务。(类似于常规部署的过程。)
点击定义您的 RUNBOOK 流程,然后添加步骤。您将看到一系列现成的步骤,您可以将它们添加到您的 runbook 流程中。在搜索栏中键入redgate sql clone
并点击输入。您应该从 Octopus Deploy 社区步骤模板库中找到一堆 SQL 克隆步骤模板。
将鼠标悬停在Redgate–SQL Clone,Create Clone 步骤模板上,您会看到一个绿色按钮,上面写着 ADD 或 INSTALL AND ADD ,这取决于您的 Octopus Deploy 服务器上是否已经安装了步骤模板。点击绿色按钮。
(如果想在安装前检查代码,可以在这里进行。所有 4 个 SQL 克隆步骤模板主要是从 Redgate 文档页面上的示例 PowerShell 片段中复制/粘贴的作业。)
在步骤编辑器中,为角色中的目标输入sqlclone
:
对于 SQL 克隆参数,尝试使用变量来简化配置。在我的例子中,我使用 Windows Auth 对 SQL Clone 进行身份验证,所以我将 SQL Clone 用户和 SQL Clone 密码字段留空。
我没有使用一个 SQL 克隆模板 ( 文档)。但是,这些可能对以下任务有用:
- 在我的一些表格中插入一些开发/测试友好的测试用例。
- 根据数据库是在开发环境中运行还是在测试环境中运行来修改数据库安全性,尤其是当映像只包含生产数据库用户和角色成员等时。
记住,模板是针对克隆执行的,而不是映像,所以它增加了 diff 文件的大小。不要对模板文件中的任何大型表上的数据进行批量更改或重建索引,否则开发人员的硬盘将为此付出代价。
通常,您的克隆会希望使用生产数据库名称,但是,在某些情况下,这可能会导致问题。例如,要在同一个实例上创建多个克隆,您需要给它们不同的名称。您不希望意外替换任何预先存在的开发/测试数据库。为此,我对SQL Server
和Clone Name
使用了特殊的变量。
这是我的配置:
请注意,如果您的开发人员正在处理他们自己的实例,他们会希望在运行时设置SQL Server
变量。类似地,如果您的开发人员都将在一个共享的开发/测试实例上构建他们的克隆,或者如果他们可能想要同时使用多个克隆,(例如,因为他们正在同时处理几个任务,或者他们想要比较一个问题的几个不同的解决方案),他们可能想要在运行时选择一个定制的Clone Name
。
出于这个原因,您很快将#{ServerInstanceForClone}
和#{DatabaseNameForClone}
变量设置为可在运行时设置。但是在你这样做之前,你必须设置你的步骤条件。我只想在开发或测试中运行它,所以我相应地指定了:
查看所有步骤配置,并点击右上角的绿色保存按钮。
您的流程现已完成。然而,在运行它之前,您需要返回并设置您的变量。从左侧菜单中选择变量,并为我们上面使用的每个变量提供值。以下是我的:
因为我要将这个操作手册添加到一个现有的项目中,所以我已经有了#{DatabaseName}
和#{ServerInstance}
变量,但是我需要添加#{SqlCloneUrl}
、#{DatabaseNameForClone}
和#{ServerInstanceForClone}
变量。对于后 2 个,当我输入值时,我点击了打开编辑器选项:
在完整编辑器中,我可以选择值提示复选框。这将确保这些值在运行时可设置。
运行您的 runbook:
设置好所有变量后,就可以运行 runbook 了。
从您的项目中,从左侧菜单中选择 Runbooks ,然后点击 runbook 旁边的 RUN 按钮。
提供所需的环境和提示变量。幸运的话,你会看到绿色:
现在,您的团队可以非常轻松地在商用笔记本电脑或廉价的虚拟机上按需调配他们自己的 64TB 开发和测试数据库。
扩展:
在你为创造克隆创造了你的操作手册之后,2 个进一步的操作手册跃入你的脑海。
以下是我的完整列表:
此外,您可能希望将刷新 SQL 克隆映像运行手册设置为按计划运行(文档),以确保克隆始终使用相对最新的数据。请注意,虽然创建克隆应该非常快,但创建映像往往需要更长时间。这意味着这是典型的通宵/周末工作。
您应该与您的开发/测试团队就映像刷新的节奏达成一致。这一点很重要,因为他们需要在每次刷新后重新调配克隆资源。对于他们来说,在每周或 dev sprint 结束时运行映像刷新可能会很方便,这样他们就可以用新的克隆开始新的一周/sprint。
这加强了各种良好的实践,例如确保所有代码都被合理地频繁提交到源代码控制,并且开发/测试数据库保持合理的一致性。此外,每次开发人员将他们最新的开发或测试源代码部署到一个新的克隆中时,您都在有效地实践您的生产部署,因此您会发现部署问题往往会更早地被发现——“在我的机器上”。
当然,为了简单起见,整篇文章都基于一个数据库。在现实世界中,您可能有多个数据库,通常具有跨数据库依赖性(哎哟)。将 SQL Clone 和 runbook 结合起来的好处是,您的 run book 可以提供所有需要的数据库,因此您知道您永远不会缺少依赖项。
所有这些数据库/克隆都需要驻留在 SQL 实例上,而 SQL 实例需要驻留在某个地方的虚拟机或容器上。这些虚拟机和容器需要驻留在您的云提供商或您自己的裸机上的一些基础架构上。
扩展这些 run book,或者将它们与其他 run book 结合起来是一个很好的主意,这样您就可以通过点击一个按钮来有效地重生您的整个开发、测试和生产环境。如果你对此感兴趣,这里有一些进一步的阅读材料:
下一步是什么?
如果你已经做到了这一步,我希望你已经被激励去行动了。
要了解关于数据库环境创建的更多信息,以及它如何适应更广泛的 DevOps 策略,您可能会喜欢我的关于安全模式更新的博客系列。它始于数据库交付地狱。
特别是,您可能对第 6 部分感兴趣,该部分处理数据库环境供应。
阅读我们的 Runbooks 系列的其余部分。
愉快的部署!
让您的团队实现自助式应用部署- Octopus Deploy
自助应用程序部署是增加可部署到生产前环境的团队成员数量的一种方式。例子可能包括:
- 允许开发人员部署到开发环境中。
- 允许测试人员部署到测试环境中。
- 允许支持团队成员部署到重现生产缺陷的环境中。
自助部署有两个主要优势:
- 它使团队能够部署到生产前环境。
- 它减少了对发布经理能够部署的依赖。
要让自助式部署发挥作用,最基本的要素是信任;我们不想让每个人都开始不一致地修补、更改和修改服务器,因为这只会保证下一个生产部署会出现可怕的错误。
在本文中,我想谈谈规划自助服务部署时要考虑的重要事项。
首先,一个故事...
在全职从事 Octopus Deploy 之前,我在英国的一家投资银行工作。在那里,我们有一个发布经理,我叫他迈克。Mike 负责部署了我们各种应用程序的 1000 多台服务器的发布管理。
迈克很聪明。他不害怕 PowerShell 或编写代码来自动化他的工作,他是我所知道的少数几个真正能够阅读 Perl 的人之一。尽管有大量的服务器,他还是设法将部署过程从开始到结束缩减到大约六个步骤,这一点他非常了解。
我的团队在 Mike 服务的团队中是一个较小的团队,我们喜欢快速迭代。有时我们会每周部署一次,如果有问题,我们会修复它们并在几小时后重新部署。部署到我们的开发和测试服务器并不是一个问题,因为我们最终获得了远程桌面访问。然而,生产总是需要 Mike 的参与:部分原因是只有 Mike 有权部署,部分原因是只有 Mike 知道如何完成所有需要的步骤,以及如何在失败时进行恢复。
虽然我们可以部署到开发和测试自己,这导致了问题。有时我们会安装一个新的组件或运行一个迁移脚本,却忘记为 Mike 记录下来。它在测试中运行良好,但是当他部署到生产中时,这个步骤就会被错过,我们就有大麻烦了。迈克也可能相当可怕!
迈克很聪明,但他是一个瓶颈。我喜欢认为我们也很聪明,但我们做事情的方式与迈克不同,不是故意的,只是因为人类不同。
当生产部署出错时,没有人会高兴,如果它发生得太频繁,可能会导致冲突和不团结。
为了增加我们成功进行生产部署的机会,有三种选择:
- 让 Mike 做所有的部署,甚至是开发和测试。
- 让我们开发人员部署到生产环境中。
- 花更多的时间在文档上。
- 自动化部署,以便始终一致地运行部署,并实施自助服务模式。
选项 1 是不可行的;迈克实在是太忙了。选项 2...我们只能说那是不可能发生的!选项 3 通常是大型组织默认的,但是不会起作用,因为 a)我们仍然会忘记事情,b)反正没有人阅读文档。
所以只剩下第四种选择。
自助部署的工作要求
要使自助部署成功,并保持团队的信任和团结,有一些基本要求:
- 部署必须自动化。
- 部署过程应该在不同的环境中保持一致。
- 需要某种程度的审计、集中日志记录和可见性。
- 强大的权限系统限制谁可以在哪里部署什么。
让我们详细检查这些。
规则 1:部署必须自动化
如果您的部署过程涉及某人打开远程桌面、复制文件夹、编辑配置文件或修改服务,自助部署将不起作用。无论您记录得多好,总有人会忘记一个步骤,或者忘记记录一个新的步骤(比如运行 SQL 迁移脚本)。这意味着在部署到生产环境时,成功部署的机会会减少,因为很容易错过任何一个步骤。这将导致不信任和冲突。
在步骤不能自动化的情况下,您可能希望使用手动步骤来暂停部署,并提供关于需要做什么的非常非常具体的指示。
规则 2:部署过程应该是一致的
与上面类似,部署脚本必须在不同的环境中保持一致。两个或更多不同的人将执行部署。这些场景中哪一个更有可能成功?
- 开发人员和测试人员已经为他们的部署运行了一个脚本,并且运行良好。一个完全不同的脚本将在生产中运行。
- 开发人员和测试人员已经为他们的部署运行了一个脚本,并且运行良好。相同的脚本(参数略有不同)将在生产中运行。
当然,环境之间会有差异,但这些应该是部署脚本的参数/配置变量;剧本本身应该是一样的。
规则 3:审计、集中记录和可见性
如果所有的部署都由一个人完成,他们知道在哪里部署了什么版本。一旦多人开始部署,对可见性的需求——例如仪表板——就变得非常重要。理想情况下,这个仪表板不应该要求某人在部署后记得更新它。
同样,审计提供了一些责任:谁更改了部署脚本?谁触发了那次部署?谁取消的?
最后,集中日志记录是必不可少的。如果在部署过程中出现问题,团队成员能够很容易地看到这一点,而不需要请求访问所有相关的服务器,这一点很重要。在 Octopus 中,一个页面显示了来自所有服务器的整个部署的日志。
规则 4:强大的权限
这不言而喻。自助部署是给予人们将特定应用程序部署到特定环境的权限,而不是让营销实习生将任务关键型应用程序部署到生产环境中。
在 Octopus 中,您可以使用团队来控制谁有权将特定的应用程序部署到特定的环境中。
摘要
自助部署非常强大,我认为所有团队都可以从中受益。但是,要让自助部署发挥作用,需要有信任。发布工程师需要相信所有环境都遵循相同的流程,并且人们不会随意配置服务器。还需要增加可见性和问责制,以确保每个人都在同一页上,没有惊讶。最后,需要有限制,让运营团队进行生产部署可能是个好主意。
还有什么我遗漏的基本要求吗?下面留言评论!
自助操作手册示例- Octopus 部署
打破开发者和运营之间的壁垒是 DevOps 理念的基石。开发人员希望快速安全地交付他们的代码,为了做到这一点,他们需要执行常见的管理任务,这些任务通常都属于运营领域。安全性、审计以及如何正确执行操作是阻碍开发人员完成这些任务的最常见原因。
在这篇文章中,我将介绍一些例子run book,它们让开发人员能够访问自助服务任务,这将使他们继续工作,而不会授予他们不需要的额外权限。使用操作手册意味着他们对基础架构所做的更改会被捕获并可审计。
审计和安全
开箱即用,Octopus Deploy 为 runbooks 提供了一个健壮的审计机制,可以捕获谁在何时做了什么。除了审核之外,还可以将特定于 runbook 的用户角色分配给环境并确定其范围:
- Runbook 消费者 : Runbook 消费者可以查看和执行 Runbook。
- Runbook 制作者 : Runbook 制作者可以编辑和执行 Runbook。
将这些角色与范围一起使用,您可以赋予开发人员执行操作手册的能力,例如,在开发和测试中,而不是在阶段化或生产中。
自助服务任务的示例
下面列出了一些可以为自助服务实施的活动类型。这绝不是一个详尽的列表,但我希望它能给你一个可能的起点。
重新启动 web 应用程序
开发人员通常对他们的开发环境拥有提升的甚至是管理员的权限。一旦他们的应用程序被部署到服务器上,权限通常被限制为模拟类似生产的环境。IIS 能够授予远程管理权限;但是,这是针对整个 IIS 实例的,而不是针对特定站点/应用程序的。使用 runbook,您可以创建特定于项目的流程,这些流程只启动/停止与 Octopus 项目相关的应用程序。
重新启动服务
测试期间发现的错误会导致服务变得无响应。重启服务的能力通常需要相当高的权限级别。使用 runbook,可以让开发人员能够在服务器上重新启动服务,而无需为他们分配任何额外的权限。这有助于消除提交支持票证并等待操作人员执行任务的需要。
备份数据库
测试数据库更新通常是单向的。除非您在同一个事务中执行所有操作并回滚,否则更改是永久的,并且通常很难恢复。阻力最小的方法是在开始之前备份数据库,这样您就有东西可以返回。数据库服务器上的备份操作通常需要提升数据库服务器权限。数据库管理员(DBA)通常已经建立了备份过程,有时会使用第三方工具来帮助管理它们。使用 runbook,DBA 可以创建或至少提供如何进行数据库备份的输入,并使开发人员能够在需要时备份数据库。
恢复数据库
除了备份数据库的能力之外,在不需要等待 DBA 或填写支持单的情况下恢复数据库的能力可以大大缩短开发周期。使用 runbook,您不仅可以恢复备份,还可以提供从不同环境恢复数据库的方法,例如将生产拷贝恢复到测试环境。
为功能分支开发提供整个环境
在之前的一篇博客文章中,我们谈到了特性分支以及每当一个新的特性分支被创建时实现动态环境,然后当该分支被删除时再将其拆除。这种类型的自助服务允许开发人员提供类似生产的环境来测试他们的代码,然后在不再需要时删除它们。
重新启动服务器
重启服务器是确保操作系统干净启动的好方法。与其他场景一样,重启服务器的能力需要对机器的一些提升的权限。使用 runbook,您可以给开发人员重新启动机器的能力,而无需授予开发人员任何权限。
结论
操作手册提供了一种自动化操作任务的机制。让开发人员能够以自助方式执行这些任务可能会减少交付时间,并促进开发和运营人员之间的协作。
运营团队自助操作手册- Octopus Deploy
原文:https://octopus.com/blog/self-service-runbooks-from-an-operations-perspective
在这篇文章中,我从运营团队的角度谈论了 run book,它们是什么,我们所说的自助式 run book 是什么,以及它们如何帮助您和您的用户。
什么是 runbooks?
操作手册只是一个完成任务的过程。您可以使用 runbooks 来重启 web 服务器,清理旧的日志文件,甚至交换负载平衡器指向的环境,等等。
操作手册是一种标准化通常经常发生的工作的方法,确保工作以可靠、可重复的方式完成。由于工作的重复性质,操作手册通常是自动化的,允许您将完成任务所需的复杂知识“烘焙到代码中”。
我们所说的自助操作手册是什么意思?
使用 runbook,您通常会执行一项任务来响应用户的请求。例如,用户可能会提出一个帮助请求,要求您刷新他们的测试数据库中的数据。你必须花时间阅读标签,确保你完全理解用户想要什么,然后执行任务。
通过使 runbook 自助服务,您可以使用与刷新测试数据库相同的 runbook,并使需要刷新测试数据库的用户可以自己执行任务。该用户现在可以随时运行该运行手册,而无需开罚单,也不会占用您的宝贵时间。
为什么自助式运营手册会让运营团队受益?
通过向用户提供操作手册,您可以节省执行任务所需的时间。这让你有时间从事更重要、更有趣的工作。此外,由于用户将所有必需的信息直接输入到操作手册中,因此您可以节省来回查看票据以收集所有正确信息的时间(并降低流程中出错的几率)。
您还可以在自助服务操作手册中设置“防护栏”,确保用户在执行操作手册时只能走“安全路线”。例如,如果您有一个从生产中返回日志文件的 runbook,您可以对返回的文件进行硬编码,确保 runbook 不会被滥用来获得未经授权的访问(并且您不必给出对生产的访问)。当涉及到审计时,这可以使生活变得容易得多,因为您知道生产系统只能以安全和合规的方式被访问。
您还可以创建自助服务操作手册,允许用户执行非常复杂和耗时的任务,例如创建新的 AWS 帐户。通过这样做,您的用户可以很快满足他们的需求,并且您可以确保为您的用户创建的每个 AWS 帐户都具有正确的 VPC 设置、密码策略和 IAM 角色。
自助操作手册有助于减少影子 IT
影子 IT 是指在组织内未经批准使用 IT 系统和应用程序,这是许多公司面临的一个大问题。自助服务操作手册有助于减少影子 IT,因为当用户能够快速、轻松地遵循正确的路径时,他们更有可能遵循正确的路径。
这一步的目标是受控的自助式解决方案。您提供的任何软件都必须满足两个重要标准:
- 自助服务:用户必须在不打扰解决方案的情况下使用它。
- 控制:它必须仍然能够控制数据和用户访问。当您提供受控的自助服务选项时,您的企业将两全其美。用户可以快速获得他们需要的解决方案,而 IT 仍然可以保护数据和应用程序。"
通过使用自助服务操作手册,用户可以解决他们自己的问题,而无需麻烦 IT 部门,同时运营团队可以保持对数据和用户访问的控制。
运行手册能在停机期间帮助我吗?
绝对的!通过将您的监控和日志记录系统与您的运行手册结合起来,您可以让您的系统自动触发运行手册来响应停机。例如,当 HTTP 服务器响应时间超过某个阈值时,您的监控系统可能会触发一个 runbook,使它自动将日志和工件收集到一个集中的位置,然后重新启动 web 服务。
结论
自助操作手册不仅能节省你的时间,还能减少出错的可能性,让用户更开心,让你继续做更重要、更有趣的工作。
愉快的部署!
语义版本 2.0.0 - Octopus 部署
这篇文章是我们 Octopus 3.4 博客系列的一部分。在我们的博客或我们的推特上关注它。
Octopus Deploy 3.4 已经发货!阅读博文和今天就下载!
Octopus 3.4 包含对语义版本 2.0.0 的支持。
SemVer 2.0.0 中的重大变化涉及:
- 预发布版本
- 构建元数据
预发布
在 SemVer 1.0.0 中,预发布版本被限制为字母数字字符和破折号[0-9A-Za-z-]
。
例如,1.0.0-alpha
表示 alpha 预发布版本 1.0.0。
这一点的主要限制来自于语义版本规范中的第 4 条:
优先级应该由字典式 ASCII 排序顺序决定。例如:1 . 0 . 0-α1 < 1 . 0 . 0-β1 < 1 . 0 . 0-β2 < 1 . 0 . 0-rc1 < 1 . 0 . 0。
假设您有上面提到的 alpha 预发布的多个版本:
1.0.0-alpha1
1.0.0-alpha2
1.0.0-alpha3
etc...
一切都好;直到你到达1.0.0-alpha10
。字典排序将放在 1.0.0-alpha2
之前。
一个常见的解决方法是使用类似于1.0.0-alpha0001
的东西。不太优雅,对吧?哦,对了, NuGet 将预发布标签限制在 20 个字符内。所以这个解决方案并不理想。
来自语义版本 2.0.0 规范条款 9:
预发布版本可以通过在补丁版本后添加连字符和一系列点分隔的标识符来表示。标识符必须只包含 ASCII 字母数字和连字符[0-9A-Za-z-]。
根据第 11 条:
具有相同主要版本、次要版本和修补版本的两个预发布版本的优先级必须通过从左到右比较每个点分隔的标识符来确定,直到发现如下差异:仅由数字组成的标识符在数字上进行比较,而带有字母或连字符的标识符在词汇上以 ASCII 排序顺序进行比较。数字标识符的优先级总是低于非数字标识符。如果前面的所有标识符都相等,则较大的预发布字段集比较小的预发布字段集具有更高的优先级。例如:1 . 0 . 0-α< 1 . 0 . 0-α1 < 1 . 0 . 0-αβ< 1 . 0 . 0-β< 1 . 0 . 0-β2 < 1 . 0 . 0-β11 < 1 . 0 . 0-RC . 1 < 1 . 0 . 0。
换句话说,我们可以
1.0.0-alpha.1
以至
1.0.0-alpha.1.5.20
优先级的工作方式如您所料:
1.0.0-alpha.8
1.0.0-alpha.9
1.0.0-alpha.10
构建元数据
在 SemVer 1.0.0 中,预发布标签的使用不一致。
在某些情况下,它们被用于版本控制(如上所述)。在其他情况下,它们用于将元数据附加到版本中。例如,我们从客户那里看到的一个常见模式是使用预发布标签来指示代码分支。
例如,包版本的
Acme.Web.1.0.0-featureA
会指出这个包是从featureA
代码分支构建的。
SemVer 2.0.0 引入了构建元数据。来自规范的第 10 条:
构建元数据可以通过在补丁或预发布版本后附加一个加号和一系列点分隔的标识符来表示。标识符必须只包含 ASCII 字母数字和连字符[0-9A-Za-z-]。
这将添加版本的一个组件,该组件显式用于附加元数据。
例如:
1.0.0+featureA
1.0.0-alpha.1+31ef756
这本质上是版本的一个组件,没有任何预定义的语义。你可以随意赋予它任何意义。章鱼部署应用的唯一解释是平等。即1.0.0+featureA
!=
1.0.0+featureB
!=
1.0.0
。
重要的是要注意,在确定优先级时,元数据组件是而不是考虑的。对于只有元数据不同的两个版本,它们的优先级应该被认为是未定义的。
章鱼
放
在 Octopus Deploy 中,您现在可以使用 SemVer 2.0.0 版本作为发布号。
包装
Octopus Deploy 3.4 还将允许您使用使用 SemVer 2.0.0 版本的包。
NuGet 包
从 NuGet 3.5 beta 版开始,nuget.exe pack
不支持用 SemVer 2.0.0 版本创建 nu get 包。
NuGet 团队打算在某个时候增加对 2.0.0 版本的支持。
对消费 SemVer 2.0.0 NuGet 包的支持已经添加到 Octopus Deploy 中。此外,我们还在Octo.exe中添加了使用 SemVer 2.0.0 版本创建 NuGet 包的功能。
octo.exe pack --id=Acme.Web --version=1.5.0-alpha.1
这意味着对我们各种 CI 插件(如 TeamCity,TFS)的“包”任务的支持也将可用。
这些包可以被推送到内置的 Octopus 包存储库中。MyGet 也接受 SemVer 2.0.0 版本的包。
截至本帖发布之日,我们还没有实现为 OctoPack 创建 SemVer 2.0.0 包的能力(OctoPack 目前直接调用 NuGet.exe)。如果这是您特别想要的,请向我们提供反馈。
祝语义版本部署愉快!
服务结构部署目标- Octopus 部署
随着 2018.5 的发布和 Azure 服务架构集群目标的引入,我们认为这将是一个完美的机会,可以通过 Octopus Deploy 快速概述新的服务架构部署目标。
如果你刚刚开始,服务结构可能会有点让人不知所措😃尤其是如果您只是想获得创建灵活/可重复部署场景所需步骤的高级概述。这篇文章旨在通过展示 Octopus Deploy 的新 Azure 服务结构集群目标来解决这个问题!
服务结构和可预见的未来
Azure 最近宣布从 2018 年 6 月 30 日起,他们将停止对服务管理 API 的支持。在这个声明之后(关于云服务的未来,社区有很多困惑——source:me = P ),已经确认云服务仍然会被支持,但是有一个明确的推动,将人们推向更新的资源管理世界。还有这样的陈述需要考虑:“云服务在控制程度和易用性方面类似于服务结构,但它现在是一个遗留服务,建议新开发使用服务结构” ( 来源)。
随着 Service Fabric 成为 Azure 中新云服务开发的推荐路径,让我们看看使用您自己的 Service Fabric 集群进行设置和部署是多么容易。
在 Azure 上创建服务结构集群
Azure 让安全启动和运行服务结构集群变得非常容易。关于服务结构和资源的文档在过去一年左右的时间里取得了长足的进步,对于任何想要查看快速入门指南和分步教程的人来说,这都是一个很好的资源。
为了跳到有趣的部分,你可以得到一个通过 Azure 门户创建 Azure 服务架构集群的的演练,或者,如果编写脚本更适合你的话,通过 ARM 创建 Azure 服务架构集群。
对于这个例子,我们通过 Azure portal 方法手动创建了集群。
陷阱#1:初始等待时间
当在 Azure 上构建服务结构集群时,我们需要仔细观察集群的状态,并确保在尝试连接或部署任何东西之前它显示“就绪”。
这一点值得一提,因为在 Azure 上创建集群后,通常会有很长的等待时间,直到所有节点都已配置好,节点的“基线升级”也已完成,最终一切就绪。根据我们的观察,这个过程可能需要 1-6 个小时,这取决于某一天月亮和星星的排列😃
自从这篇博文发表以来,Azure 上的服务结构集群的启动时间已经显著改善,不到一个小时,有几次甚至不到半个小时。但是请记住,在尝试连接或部署任何东西之前,要等到状态为“就绪”😉(2018 年 10 月 23 日编辑)
一旦我们成功地创建了我们的集群,它应该像这样出现在我们的 Azure 门户中:
在这种情况下,我们已经在我们的集群上设置了证书和 Azure Active Directory 安全模式,这样我们就可以很容易地用任一种安全模式测试连接。
服务织物的包装
微软使它成为超级开发者演示友好的,可以通过 Visual Studio 部署到服务结构。
问题是,演示友好!=真实世界(“好友不让好友右键发布” amirite)。
当直接从 Visual Studio 部署 Service Fabric 应用程序时,微软只部分打包你所有的文件。在部署过程中,它们实际上将回调到您的源代码中用于PublishProfiles
和ApplicationParameters
(因此,默认情况下,您发布的包文件夹对除 Visual Studio 之外的任何东西都是无用的)。
为了解决这个问题,我们已经编写了特定的打包文档来帮助您在现实世界中部署(而不是来自实习生笔记本电脑上的过度开发的 IDE)。
这是一种比喻,莫蒂。他们是官僚。我不尊重他们。
对于本例,我们使用了打包文档的自定义构建目标部分来帮助我们复制服务结构包所需的PublishProfiles
和ApplicationParameters
,以确保我们拥有部署所需的一切。
有了我们的定制构建目标文件,我们现在可以构建/打包我们的 SF 项目了:
然后,我们应该会看到一个“pkg”文件夹,显示了PublishProfiles
和ApplicationParameters
(多亏了定制的构建目标文件)。这是服务结构从我们的包中需要的最终结构的示例。
我们现在可以使用一个永远友好的包文件名来压缩它,这个包文件名可以被 Octopus 使用。在这种情况下,我们创建了一个名为MarksServiceFabricAppOfAwesomenessNetCore.1.0.0.zip
的文件,其中包含了上面提到的包输出,并且我们已经将它上传到了 Octopus 服务器的内置包存储库。
安装服务结构 SDK
因为微软喜欢他们的 GAC,所以在任何与部署相关的东西可以对服务架构起作用之前,我们的部署服务器需要安装服务架构 SDK ,并且需要启用 PowerShell 脚本执行(完整说明可以在这里找到)。
是时候部署了
恭喜你。如果你已经走了这么远:
- 我们的 SF 群集说它“准备就绪”。
- 我们已经成功理解了创建独立服务结构包的打包说明。
- 我们已经在 Octopus 部署服务器上安装了 Service Fabric SDK,并如上所述设置了 PowerShell 脚本执行。
现在我们已经为部署世界做好了准备!(有趣的部分。)
在我们继续之前,如果您正在使用证书进行身份验证,请确保您已经将证书添加到 Octopus 证书库中,以准备在您的新服务结构集群目标中使用它。
创建 Octopus 服务结构集群目标
我们现在可以将我们的服务结构集群作为 Octopus 中的一个成熟的部署目标,这将使我们能够与 Azure 上的集群进行通信。
首先,我们前往Infrastructure > Deployment Targets
,点击Add Deployment Target
,从列表中选择Azure Service Fabric Cluster
:
接下来,我们填写 Azure 上的服务结构集群的详细信息,记住根据需要选择正确的安全模式。在这种情况下,我们引用先前上传到我们的证书库的证书:
我们点击Save
,然后等待健康检查完成。如果一切顺利,我们的 Octopus 服务器将使用安装在服务器上的 Service Fabric SDK 对 SF 集群运行健康检查(使用我们定义的安全模式参数),并将发现我们的目标是健康的:
我们现在可以通过角色引用这个部署目标作为我们部署过程的一部分,就像任何其他目标一样!
部署到我们新的服务结构集群目标
对于本例,我们已经创建了一个测试项目,并希望部署一个服务结构应用包,因此我们从可用步骤模板列表中键入“Fabric ”,并找到我们想要的步骤:
【T2
然后,我们需要选择该步骤将部署到的角色(分配给我们的目标的角色),并选择我们之前上传的服务结构包,包括发布配置文件的路径:
点击Save
,我们准备部署!
运行部署后,我们可以看到 Azure SF PowerShell cmdlets 成功地将我们的包部署到我们的 Azure Service Fabric 集群:
:呜呜:
Azure 部署目标
关于这些新的 Azure 目标,真正酷的事情是现在存在于基础设施关注点和部署过程关注点之间的明确分离。现在,您可以独立于项目的部署流程来设置和监控您的 Azure 基础设施,这简化了您的部署并使其更容易推理。
我们的新 Azure 目标从2018.5
开始提供。要了解更多细节,你可以查看完整的 2018.5 博客帖子,其中包括一个由真正的加拿大人制作的精彩视频。
如果您有任何想法/反馈,请告诉我们,一如既往,祝您部署愉快!
Octopus Deploy 的 ServiceNow 集成:早期访问预览- Octopus Deploy
我们的 ServiceNow 集成作为早期访问预览版(EAP)提供。
这种集成通过确保您的任务关键型部署仅根据批准的 ServiceNow 变更请求执行,简化了开发运维团队的变更管理流程。这降低了因未经批准的部署而导致系统停机的风险,并通过对高风险部署实施批准检查来帮助保护您的业务关键型应用程序。
这种集成将 Octopus 部署与变更请求联系起来,并自动创建带有预填充细节的标准变更请求,帮助您更高效地与变更管理团队合作。通过我们的集成,每个受控部署都有一个变更请求,以便您的 CI/CD 和发布管理流程符合公司政策和法规要求。有了部署的完整审计日志,变更经理还可以确保所有部署都在适当的控制级别下执行。
我们的 ServiceNow 集成让您能够:
- 轻松配置您的工作流程,将 Octopus 与一个或多个 ServiceNow 连接集成。
- 通过提示变量将部署链接到现有的变更请求,以便您可以手动将部署与变更请求相关联。
- 通过更改请求标题上的字符串匹配链接手动创建的更改请求。
- 在部署时自动创建标准变更请求。
- 使用变更模板自动创建标准变更请求,以减少手动工作并控制要填充的信息。
- 查看和导出受控部署的审核日志,以便轻松实现合规性和部署后协调。
在本文中,我将介绍我们的 ServiceNow 集成,并向您介绍如何开始。
简化变更管理的需求
对于开发运维团队来说,大规模管理部署管道既复杂又耗时,当您添加变更管理时,这变得更加困难。通常有严格的变更流程,要求进行彻底的审查,以获得发布新版本应用程序的批准。
变更顾问委员会可以被视为减慢开发团队的路障。(阅读我们关于为什么变革顾问委员会可能不起作用以及如何改进它们的帖子,了解更多信息。)
无需人工干预的批准
我们实施 ServiceNow 集成的一个原因是为了在人工干预之外提供更好、更自动化的审批流程。
我们注意到手动干预被用于批准,但它们不是为此目的而设计的。创建手动干预是为了在常见用例中暂停部署并等待用户交互,比如 DBA 审查工具生成的增量脚本,或者 web 管理员审查 Azure Web App 部署中的 stage 槽。
因此,使用手动干预进行审批存在局限性。很难对多个批准者进行建模,您不能在整个部署中重用同一个批准,不能强制 CAB 或更改批准,并且很难强制批准手动干预的人。
我们的 ServiceNow 集成不是人工干预的替代,而是对人工干预的增强。手动干预仍可用于其预期目的,并为那些需要批准的人提供一种更容易和更有效的方式来在 Octopus 中实现自动化。
ServiceNow 和 Octopus 带来了更好的变更管理
我们希望通过帮助您将 Octopus 与 ServiceNow 集成来减少摩擦并简化您的开发团队的生活,从而使变更管理变得更加容易。
我们在 Octopus 中的 ServiceNow 集成减轻了手工填写变更请求的痛苦,手工填写变更请求既耗时又容易出错。在部署中,您可以自动创建变更请求,从而更容易实现最佳实践。
Octopus 内置的变更管理
ServiceNow 集成设置很简单,因为所有东西都内置在 Octopus 中。这种集成使用 ServiceNow 提供的文档化 API,以最大限度地减少摩擦,而无需下载单独的 ServiceNow 应用程序。
设置完成后,可以在您的部署中自动创建更改请求,或者您可以使用现有的更改请求,直到有人提供批准,部署才会继续。变更请求处于实施阶段后,Octopus 会自动识别批准并继续部署。部署过程中不需要手动干预。
跨项目和环境重用批准
有时,同一个变更请求会在您的部署过程中使用,因此同一个 ID 会被多次使用。手动干预不允许变更请求被使用两次。
借助我们的 ServiceNow 集成,您可以使用提示变量在多个部署中重用变更请求。在变更请求处于实现阶段之后,您可以简单地将 ID 从一个部署复制到另一个部署。如果您手动选择一个,Octopus 会自动识别批准并继续部署。如果没有,Octopus 会为您创建一个。
使用集成的最低配置
ServiceNow 集成不需要在 ServiceNow 中进行太多设置。你所需要的只是一个特殊的 Octopus 许可证,它对早期访问预览(EAP)用户是免费的。
设置许可证后,您只需在 ServiceNow 中配置 2 项内容即可开始:
- 使用 OAuth 创建 web 服务用户帐户
- 创建用户并分配权限
任何人都可以创建一个免费的个人开发实例(PDI ),在功能齐全的 ServiceNow 中使用。
ServiceNow 集成入门
https://www.youtube.com/embed/MWrhD78xzyw
VIDEO
在注册了 EAP 之后,您需要配置一些设置来开始与 ServiceNow 集成。
导航到配置,然后许可,添加客户成功团队提供的许可密钥。
接下来,点击设置并导航至 ServiceNow 集成。通过复选框启用service now 集成,并添加一个连接。
您需要从 ServiceNow 提供一个基本 URL、OAuth 客户端 ID 和客户端机密。基本 URL 可以在您的 ServiceNow 菜单中找到。
要获得 OAuth 客户端 ID,请进入 ServiceNow 中的应用注册表,并将外部客户端的 OAuth API 端点设置为全局。然后,您可以将该 ID 粘贴到 Octopus 中的设置选项卡中。
接下来,您需要创建用户并分配权限。在 ServiceNow 的用户部分,您可以创建一个服务帐户并为该用户分配角色。给你的密码可以粘贴到 Octopus 中。
您给予用户的权限非常重要,应该符合您组织的指导原则。我们不建议给用户通用权限。
配置您的环境和项目
配置 ServiceNow 集成后,您需要配置您的环境和项目。两者都需要启用 ServiceNow,ServiceNow 集成才能工作。
配置您的环境
要设置您的环境,请转到 Infrastructure 并选择您希望 ServiceNow 使用的环境。在 ServiceNow Integration 下拉菜单下,选择变更控制复选框。
对所有适用的环境重复此操作。
配置您的项目
要设置您的项目,请转到设置-常规,在副标题 ServiceNow 集成下,勾选变更控制旁边的复选框。然后选择要使用的连接。您可以使用之前设置的连接,或者如果您有多个不同业务单位的连接,您可以为项目选择正确的连接。
如果您的组织在其变更流程中有名称模板,您也可以提供变更模板名称。这是可选的,如果不需要,您可以将其留空。如果包含变更模板名称,它将用于在部署时自动创建标准的变更请求。
为可重用的变更请求设置提示变量
如果您想要重用变更请求或使用现有的变更请求,您需要为 ServiceNow 设置一个提示变量。
如果您不打算重用变更请求,那么没有必要设置它。
在你的项目中,在变量部分,寻找章鱼。ServiceNow.Change.Number 。您可以选择打开编辑器来更改标签和描述,这样更容易使用。
在部署中创建新的变更请求
转到您设置的环境并导航到部署窗口。要创建新的变更请求,请将参数字段留空。
点击部署后,出现任务总结画面,显示消息Change Request awaiting approval
。然后,将为创建的变更请求显示一个变更请求编号,并带有指向 ServiceNow 的链接。
要使您的变更请求获得批准,请将其分配给分配组部分中的某个人。然后,选择请求批准以沿着您组织的批准过程移动变更请求。
在变更请求被批准之前,您的部署不会在 Octopus 中运行。批准人需要将变更请求移动到实施,然后才能在 Octopus 中触发自动批准。
变更请求只能处于实施阶段,以便在 Octopus 中运行部署。如果变更请求处于任何其他阶段,任务将不会开始。
要为另一个部署重用变更请求,它需要保持在实现中。
变更请求还必须在部署期间保持实施状态,因为 Octopus 会在整个任务日志中检查变更请求的状态。
使用现有的变更请求进行部署
如果您在实现阶段有一个现有的变更请求,您可以将其用于其他环境和项目(例如,您可以在测试和生产中使用相同的变更请求)。
只需复制变更请求编号,并将其粘贴到下一个环境的参数部分。
如果更改请求仍处于实施阶段,Octopus 会自动识别其已获批准。
对 ServiceNow 集成运行诊断
如果在 ServiceNow 集成的设置中有任何错误,将会创建一个错误,并且您的部署将不会运行。要找出发生这种情况的原因,请转到诊断选项卡。从这里您可以看到问题,例如,您可能有不正确的模板名称或未分配的用户角色。
结论
我们的 ServiceNow 集成作为早期访问预览版(EAP)提供。它有助于自动化您的变更管理过程,使运行变更管理变得更加容易,而不需要手动干预。
我们期待引入新功能,继续支持您的变更管理流程。
我们希望您尝试将这种方式与您的工作流程相结合,并告诉我们如何改进。
愉快的部署!
八达通部署有限公司背后的服务
原文:https://octopus.com/blog/business/services-behind-octopus
Octopus Deploy 是一家小型且相对简单的软件初创公司,没有员工(目前还没有!)即便如此,仍有数量惊人的服务来维持它的运行。在这篇文章中,我将介绍 Octopus 所依赖的关键服务。
在线状态
OctopusDeploy.com网站使用一个定制的小型 CMS 来管理主页,这个博客,文档,等等。几个月前的一个周末,我与 ASP.NET MVC 4 和 RavenDB 一起黑了它,它运行得很好。我已经公开了 GitHub 库,但是我不小心签入了一些 API 密钥等等。,因此我需要在共享之前清理历史记录。评论由 Disqus 提供,这使得 CMS 引擎非常简单。
该网站托管在弗吉尼亚数据中心两个可用区域中的两台亚马逊 EC2 机器上(这两台服务器不是为了性能,而是为了几个月前由于 EC2 中断导致网站离线后的可靠性)。RavenDB 数据库托管在 RavenHQ 的一个复制实例中。有一天,我会将 RavenDB 数据库移到 EC2 实例中,以使数据更接近,但现在由 RavenHQ 为我处理备份还是不错的。图片和下载由亚马逊 S3 托管,启用了 CloudFront CDN。亚马逊 Route 53 是 DNS 服务器。
为了支持,我使用招标,在 help.octopusdeploy.com。大部分支持都是通过招标完成的,尽管人们也可以直接给 support@octopudeploy.com 的 T4 发邮件或者在 Jabbr 上聊天。我认为支持的渠道越多越好。
我在标书中关注的一个关键指标是支持请求的响应时间。它们往往因时区(因为请求可能在我上床睡觉时发布)或难度(有些需要研究)而异。尽管现在有超过 250 个安装的 Octopus 在使用,但是支持并没有像我想象的那样占用那么多时间。我每天会收到 4-5 个问题,但大多数都很容易解决。我的目标是在 24 小时内回复所有请求。
当客户购买 Octopus 的许可证时,购买页面由 FastSpring 托管,然后由他们处理交易、申请增值税等等。购买页面使用定制的 HTML/CSS(由 FastSpring 服务器托管)来保持与主 Octopus Deploy 网站一致的外观。购买后,FastSpring 调用我们的 Amazon EC2 实例上托管的 REST API 来生成 XML 许可证密钥,该密钥包含在发送给客户的电子邮件中。
我无法告诉你我有多喜欢 FastSpring。虽然他们的主网站看起来并不令人印象深刻,但他们的管理系统非常棒,而且他们以一种非常简单的方式支持如此多的功能。他们声称有很大的支持,这在我的经历中也是真实的。在 FastSpring 之前,我使用的是 SWReg...不太好。
为了让客户了解路线图,我使用了一个公共 Trello 板。虽然大多数公司公开了他们的 bug 追踪器,但他们通常对功能保持沉默。对于该产品的未来版本,有一些“巨大的、巨大的、绝密的”想法不在列表中(主要是因为它们需要更多的思考),但在很大程度上,我看不出公开即将推出的功能有什么坏处,我认为更多的公司应该这样做。对于功能建议,我们也有一个用户之声网站。
最后,我已经开始使用 Google AdWords 来帮助提高产品的知名度。我仍然是 Adwords 的新手,我认为我还没有完全正确地使用它,但它现在负责这个网站大约 70%的流量。我在几个广告组中有大约十几个不同的广告,以及大约 30 美元/天的自动预算。大部分流量来自展示网;很少来自搜索广告(尽管很多来自有机搜索)。
发展
微软的 BizSpark 计划在为我使用的微软产品提供 MSDN 许可证方面很有帮助。我肯定在 BizSpark 还有其他优势,但我还没有真正花时间去搞清楚它们是什么。虽然 Octopus Deploy Ltd .是一家英国有限公司,但我实际上参加了澳大利亚 BizSpark 计划,因为那是我创业时的基地。
对于源代码,我为 Octopus Deploy 建立了一个 GitHub 组织,它不仅托管开源项目,还拥有 Octopus Deploy 主要产品源代码的私有存储库、这个网站和其他私有代码存储库。事实上,当客户购买 Octopus Deploy 的源代码许可证时,他们会被邀请访问私有的 GitHub 库。虽然许多软件公司提供源代码许可证,但我认为访问 GitHub 库会使定制和合并变得更加容易。
对于自动化构建,我使用 JetBrains 的 TeamCity。虽然我使用免费的专业版许可证,但我不得不购买两个额外的构建代理许可证。这是因为 Octopus Deploy 在许多不同版本的 Windows 上运行,所以我需要在不同的配置上运行集成测试,以发现任何兼容性错误。不同的测试配置如下:
Octopus Deploy 网站是使用...你猜对了...章鱼展开!每次公开发布之前,我都会在内部的 Octopus 服务器上安装发布候选,升级触手,然后重新部署公开网站。除了集成测试,这给了我信心,在发布中没有大的问题。
这些开发服务器(TeamCity 服务器、5 个构建代理和 Octopus 服务器)都托管在一个由 LeaseWeb 托管的 Hyper-V 服务器上。虽然不如托管在 EC2 上可靠(因为只有一台没有灾难恢复的服务器),但这要便宜得多,并且为我的操作系统配置提供了更多选择(例如,我运行了一段时间 Windows Server 2012 beta 服务器)。而且它们不是关键任务——如果我不得不花一两天的时间配置和重新配置一个新的构建/部署服务器,这不会是一个大问题。
Octopus Deploy 的安装程序使用 WiX 构建,代码和 MSI 使用 VeriSign 的代码签名证书进行签名。
为了监控 OctopusDeploy.com 网站,我使用了几种不同的服务。当然,谷歌分析起了很大的作用,我很幸运地得到了才华横溢的阿利斯泰尔·拉蒂摩尔的帮助来优化网站和理解分析结果。 Pingdom 给了我一个正常运行时间和停机时间通知的图表。Appfail.net与网站整合,通过电子邮件向我报告任何异常,并提供一个漂亮的错误图表。
日常重复性的电子邮件、新闻、摘要等的处理工作
我有一个 Office 365 订阅,它提供了@octopusdeploy.com
域的电子邮件以及我的个人电子邮件。像sales@octopusdeploy.com
和support@octopusdeploy.com
这样的邮箱被设置为分发列表。对于发布公告和产品更新,我使用 MailChimp (订阅此处)。
使用 Xero 对业务进行记账。Xero 可能是我在这一整页中最喜欢的服务(FastSpring 排名第二),这说明了很多,因为它是一个会计系统。我确实有会计师(丹尼斯&特恩布尔)来帮助我准备申报表,但是我喜欢自己做大部分的会计和增值税申报表。每个人都抱怨记账,但对我来说,一个月只需要几个小时,用 Xero 还挺享受的。
八达通是增值税注册的,但这主要是因为我在全职工作之前做的合同。Xero 对准备增值税申报非常有帮助。由于 FastSpring 充当记录的供应商,这意味着他们收取增值税(如果适用)并支付增值税-我的增值税申报表不包括八达通销售的增值税。我曾经和一些人交谈过,他们似乎认为使用像 FastSpring 这样的第三方来处理销售意味着他们将支付两次增值税,这是不正确的。
我要推荐的最后一个网站是 HMRC 网站。这需要一段时间来设置(你必须注册每项服务,他们会发布一个激活码,你激活),但这意味着我可以在线登录,查看该企业必须支付的每项不同税收的到期金额。那很有用。
摘要
嗯,这是一个很长的列表。如您所见,即使对于像 Octopus Deploy 这样的小型企业,也有许多服务协同工作,以使一切顺利运行。有些需要实时投资来设置,而有些只需要五分钟。有些很有趣,如果不是关键任务的话,而其他的我不能没有。养大一个初创企业绝对需要一个村子!
未来会怎样?目前,为了找到我的客户的信息,我依赖于电子邮件历史记录和上述服务,如 FastSpring/Tender 来存储详细信息——将其纳入一个单一的(基本的)CRM 系统可能会变得更加重要。如果你有给我的建议,请在下面的框中留言。
代码- Octopus 部署时的形状配置
我们最近一直忙于为 Octopus Deploy 构建配置代码(Config as Code)支持。在本帖中,我们来看看形成这一特征的一些因素:
- 为什么我们将配置构建为代码
- 我们想要避免的反模式
- 设计决策
然而,首先我们应该定义我们所说的“配置为代码”是什么意思。我们指的是 Octopus 项目的版本控制(Git)文本表示。今天,当您在 Octopus 中配置一个项目时,配置被存储为关系数据库中的记录。该特性获取一些数据,并将其作为文件保存在 Git 存储库中,而不是数据库中。
为什么配置为代码?
在过去的几年里,版本控制 Octopus 配置一直是我们最需要的特性。我们明白为什么。这些优势极具吸引力,包括:
- 历史 : Git 是代码的时间机器。能够在应用程序代码旁边查看 Octopus 配置的内容、时间和人员无疑是有用的。
- 分支:现在,部署流程只有一个实例。这使得测试变更变得困难,因为当一个版本被创建时,它将使用当前的部署过程。Git 分支使得部署过程可以有任意多的版本。这允许在不影响稳定性的情况下迭代变更。
- 单一事实来源:让应用程序代码、构建脚本和部署配置生活在一起,让每个人都感到温暖和模糊。
- 克隆:想象一下能够将一个
.octopus
文件夹复制到一个新的 Git 存储库(可能改变一些变量)并使用它作为 Octopus 项目的启动程序。
有两个投票率很高的用户声音建议( 1 、 2 ),但更有说服力的是许多客户对话。
最后,我们想要这个!我们使用 Octopus Deploy 来交付 Octopus Deploy,对一个特性的第一个试金石是我们是否对此感到兴奋。我们是。
反模式
我们当然不是第一个实现这一功能的产品。我们生态系统中的许多工具都集成了 Git。这让我们有机会尝试各种不同的实现,并感觉到是什么让一个愉快的体验和不那么愉快的体验有所不同。
很明显,有一些模式是我们想要避免的。
反模式#1: Git DB
人们很容易把 Git 想象成“另一个数据库”,简单地把一个持久层换成另一个。因此,更改被保存在 Git 存储库中,但是 Git 的真正功能却没有发挥出来。不支持分支,不能提供提交消息,文本记录不可读,等等。
在这种情况下,Git 的唯一好处是历史记录,这当然不是什么都没有,但即使这样也是有危害的。
用gitRepo.Push()
替换dbTransaction.Commit()
可能是最快的方法,但作为用户,这相当令人失望。
反模式 2:婴儿洗澡水
在这种反模式中,用户可以选择加入 Git 集成,但前提是他们愿意放弃其他特性。
花了几个月的时间构建这个特性,很容易看出这是如何发生的,有时这是不可避免的。
对于构建在关系数据库上的应用程序来说,当大量应用程序数据不再存储在数据库中并且不再有单一版本时,很难确保所有不同的功能仍然起作用。
简单地禁用它们并说服自己这是用户的选择是很有诱惑力的。老实说,在这个特性的早期版本中,我们会禁用一些功能,但是只要有可能,我们会努力确保启用 Git 的决定带来尽可能少的妥协。
反模式#3:通过抽象混淆
在这种模式中,Git 概念在应用程序中被抽象出来。一个例子可能是,分支暴露为一个“草稿”隐喻。
这没什么不好,如果做对了,它会非常强大。但是对于像 Octopus 这样的应用程序来说,这是有风险的,因为用户可能对 Git 概念有所了解。
在可能的情况下,我们使用 Git 术语和概念,而不是试图将它们隐藏在抽象之下。
反模式#4: YAMSONXML 地狱
我们坚信想要 Git 集成并不意味着想要放弃所有的 UI 帮助。我们很早就决定不强迫在 Git 集成和良好的 UX 之间做出选择。
让您的配置具有人类可读的文本表示,您可以查看历史、分支、比较和合并,这是一种授权。盯着空文本文件中闪烁的光标可不是。
Octopus 不仅仅是一个自动化工具,它还是一个协作工具。将编辑文本文件作为进行更改的唯一方式将许多人拒之门外。
感觉像是倒退了一步。
设计决策
我们决定要两全其美:Git 的超级能力和 Octopus 的可用性。让我们来看看一些具体的设计决策:
作为核心概念的分支
分支是 Git 的超能力,我们希望尽可能充分地利用它们。我们在 Octopus UI 中展示了切换分支的能力:
这允许轻松地切换到新的分支,以便对部署过程进行更改,而不会影响主分支。查看特定步骤时,它允许在分支之间快速切换。
保存时提交消息
遵循公开 Git 概念的原则,当为项目启用配置为代码时,我们将Save
按钮重新标记为Commit
。
我们还观察到有两种类型的变化。这些例子有:
- 对现有部署流程进行大规模更改
- 对你在过去三个小时里一直努力工作的流程进行第四十九次调整
在第一种情况下,我们认为您可能想要输入一个有意义的提交消息。
在情况#2 中,被提示另一个提交消息不太可能导致有意义的描述(或者至少一个安全的工作)。
我们想迎合这两种情况,所以我们引入了一个分割按钮,当点击它时会使用默认消息而不提示,但是...
提供了输入提交消息的能力。
Releases 和 Git:完美的匹配
Config as Code 完全符合 Octopus 中版本的概念。
今天,当您创建一个版本时,它会对当前的部署过程、变量和其他一些东西进行快照。
启用 Config as Code 后,当创建一个发布时,它将允许选择包含部署过程的 Git 分支(很快会选择 commit 或 tag ):
从这一点上来说,您的版本不会随着项目生命周期中的环境的进展而改变,就像今天一样。
不是 YAML,不是 JSON,不是 XML
一个明显的问题是我们将使用哪种配置语言?YAML,JSON,XML?在过去的几年里,我们询问了许多人的意见。我们的结论是,大家都讨厌他们所有人。感觉就像在问你更喜欢喝哪种清洁产品?也许这是因为每个人做出的权衡是如此明显。我们承认没有任何选择接近多数人的接受。
对于我们的配置语言,我们使用基于哈希公司的 HCL 的语言。
step "Greetings World" {
script_action {
channels = ["Release", "Beta"]
environments = ["Production"]
worker_pool = "Ubuntu 2018.4"
syntax = "Bash"
body = <<EOT
echo "#{Greeting} World!"
EOT
}
}
step "Test Status Page" {
http_test_url_action {
url = "https://#{Domain}/status"
expected_code = 200
timeout_seconds = 60
}
}
我们的主要考虑是:
- 人类可读性:在 Git 中存储配置的全部目的是让人类能够阅读和比较它。
- 复杂文档:部署流程不是琐碎的文档。它们通常有几十个步骤(或者更多),并且可以嵌套得很深。我们不设想人们从头开始创作这些,但我们相信人们会编辑它们,复制粘贴步骤,添加环境范围等。我们希望尽可能地支持这些类型的编辑。
其他明显的竞争者是 YAML、JSON 和 XML。
- 我们排除了 JSON,因为它是为表示序列化对象而设计的,并且不是特别友好(引用太多了!).
- 我们排除了 XML 尽管逆水行舟很有趣,但是 XML 太冗长了(有太多的尖括号!).
- YAML 勾选了人类可读框,但是编辑复杂文档很痛苦,我们觉得更适合简单的文档(太多空白了!).
我们喜欢盐酸。我们认为它是这项工作的合适工具。尽管我们已经公开使用 HCL 作为起点,但我们将我们的实现称为章鱼配置语言(OCL) 。
我们已经构建了自己的解析器/串行化器,我们没有义务遵循 Hashicorp 对 HCL 的任何指示,也没有任何东西阻止我们做出改变。
老实说,我们觉得配置语言的选择远不是最重要的部分。不管我们选择哪一种,好处都是相似的。
下一步是什么?
下一步是把这个交到你手里。我们在 2021 年推出了 Config as Code 的早期访问预览版。它将退出早期访问,并很快可用于生产。
观看我们的网络研讨会:在 Octopus 中将配置作为代码引入
德里克·坎贝尔和皮特·加拉格尔将带你了解在 Octopus 中配置为代码的入门知识,以及在大规模使用配置为代码时的最佳实践。
https://www.youtube.com/embed/Z4DgiJ630FU
VIDEO
我们定期举办网络研讨会。请参见网络研讨会第页,了解有关即将举行的活动和实时流媒体录制的详细信息。
愉快的部署!
跨空间共享工作人员- Octopus 部署
Octopus Deploy 中的空间是硬墙,不允许你在它们之间共享任何东西(用户除外)。这对于隔离来说很好,但对于你想在所有空间共享的东西来说可能会有问题,比如工人。共享工人是我们为支持我们的示例实例而解决的一个场景。
在这篇文章中,我解释了我们的解决方案,它可以自动共享多个空间的工作人员。
问题是
我们的 Samples 实例的基础设施每天都在创建和销毁。随着示例实例的增长,对分配云实例(一个 Windows 和一个 Linux)的动态工作器提出了更多的要求。
我们遇到了资源争用问题,因为我们用来创建基础设施的操作手册经常会使用 运行 Octopus 部署操作手册 步骤模板。您可以将此步骤模板配置为等待调用的运行手册完成;然而,这有时意味着父进程将等待子进程完成,但子进程需要独占访问(参见我们的帖子解释 Workers 了解可能发生这种情况的条件),并将等待第一个任务完成。
解决方案
我们需要更多的工人来完成这些任务。我们提出了两种解决方案:
- 敬业的工人
- 共享工人
解决方案 1:敬业的员工
我们解决这个问题的第一个迭代是让每个空间创建自己的专用工人。虽然这样做很好,但有两个问题:
- 工人们一天中 99%的时间都无所事事
- 随着空间的增加,我们的成本也增加了
我们需要重新评估我们的云资源使用情况,并寻求成本节约。我们将工作人员以及数据库服务器实例之类的东西确定为可以共享而不是专用的项目。
解决方案 2:共享工人
样本中的每个空间都创建了使用符合其需求的云提供商的工作人员。为了支持这一点,我们在所有 3 个云提供商中创建了工作人员,与所有空间共享。
通过 Terraform,我们使用云提供商的扩展功能创建了工作人员,因此,如果我们需要更多或更少的工作人员,我们只需相应地调整规模。配置工作人员使用包含以下步骤的操作手册:
- 获取空间列表
- 创建工人
- 等待工人自己注册
- 向剩余空间添加工作人员
获取空间列表
为了向所有空间添加工人,我们首先需要收集实例上所有空间的列表。获取空间列表步骤检索列表并设置以下输出变量:
InitialSpaceName
-我们的空间列表中第一个空间的名称,该值用于虚拟机上运行的脚本,以便它们可以将自己注册到实例。InitialSpaceId
-工作人员将被添加到的初始空间的 ID。RemainingSpaceIds
-以逗号分隔的剩余空间 id 列表,用于添加工人。WorkerPoolName
-要在空间中注册和添加的工人池的名称。Samples 使用 Terraform 在所有空间创建了一个一致的工人池列表。
function Get-OctopusItems
{
# Define parameters
param(
$OctopusUri,
$ApiKey,
$SkipCount = 0
)
# Define working variables
$items = @()
$skipQueryString = ""
$headers = @{"X-Octopus-ApiKey"="$ApiKey"}
# Check to see if there there is already a querystring
if ($octopusUri.Contains("?"))
{
$skipQueryString = "&skip="
}
else
{
$skipQueryString = "?skip="
}
$skipQueryString += $SkipCount
# Get intial set
$resultSet = Invoke-RestMethod -Uri "$($OctopusUri)$skipQueryString" -Method GET -Headers $headers
# Check to see if it returned an item collection
if ($resultSet.Items)
{
# Store call results
$items += $resultSet.Items
# Check to see if resultset is bigger than page amount
if (($resultSet.Items.Count -gt 0) -and ($resultSet.Items.Count -eq $resultSet.ItemsPerPage))
{
# Increment skip count
$SkipCount += $resultSet.ItemsPerPage
# Recurse
$items += Get-OctopusItems -OctopusUri $OctopusUri -ApiKey $ApiKey -SkipCount $SkipCount
}
}
else
{
return $resultSet
}
# Return results
return $items
}
# Define variables
$baseUrl = $OctopusParameters['Samples.Octopus.Url']
$apiKey = $OctopusParameters['Samples.Octopus.Api.Key']
$header = @{ "X-Octopus-ApiKey" = $apiKey }
$initialSpaceName = ""
$initialSpaceId = ""
$remainingSpaceIds = @()
$workerPoolName = "$($OctopusParameters['Project.CloudProvider.Folder.Name']) Worker Pool TF"
# Get all spaces
Write-Host "Getting list of all spaces on $baseUrl ..."
$spaces = Get-OctopusItems -OctopusUri "$baseUrl/api/spaces" -ApiKey $apiKey
# Loop through the spaces
foreach ($space in $spaces)
{
# Get worker pools of space
Write-Host "Getting all worker pools for space $($space.Name) ..."
$workerPools = Get-OctopusItems -OctopusUri "$baseUrl/api/$($space.Id)/workerPools" -ApiKey $apiKey
# Check to see if it has the pool we're looking for
if ($workerPools.Name -contains $workerPoolName)
{
# Check to see if we have an initial space
if ([string]::IsNullOrWhitespace($initialSpaceName))
{
# Assign the values
Write-Host "Initial Space: $($space.name)"
$initialSpaceName = $space.Name
$initialSpaceId = $space.Id
}
else
{
# Add to list
Write-Host "Adding space: $($space.Name)($($space.Id))"
$remainingSpaceIds += $space.Id
}
}
}
# Set output variables
Write-Host "Setting output variable InitialSpaceName to $initialSpaceName"
Set-OctopusVariable -name "InitialSpaceName" -value $initialSpaceName
Write-Host "Setting output variable InitialSpaceId to $initialSpaceId"
Set-OctopusVariable -name "InitialSpaceId" -value $initialSpaceId
Set-OctopusVariable -name "RemainingSpaceIds" -value ($remainingSpaceIds -join ",")
Write-Host "Setting output variable WorkerPoolName to $workerPoolName"
Set-OctopusVariable -name "WorkerPoolName" -value $workerPoolName
创建工人
对于我们的共享工作人员,我们将 Terraform 文件分离到云提供商指定的文件夹中。下面是我们为每个提供者创建的 Terraform 的片段(参见 GitHub 中的完整实现):
AWS 工人地形
resource "aws_iam_instance_profile" "linux-worker-profile" {
name = var.octopus_aws_instance_profile_name
role = var.octopus_aws_role_name
}
resource "aws_launch_configuration" "linux-worker-launchconfig" {
name_prefix = var.octopus_aws_launch_configuration_name
image_id = "${var.octopus_aws_linux_ami_id}"
instance_type = var.octopus_aws_ec2_instance_type
iam_instance_profile = "${aws_iam_instance_profile.linux-worker-profile.name}"
security_groups = ["${var.octopus_aws_security_group_id}"]
# script to run when created
user_data = "${file("../configure-tentacle.sh")}"
# root disk
root_block_device {
volume_size = "30"
delete_on_termination = true
}
}
resource "aws_autoscaling_group" "linux-worker-autoscaling" {
name = var.auto_scaling_group_name
vpc_zone_identifier = var.octopus_aws_subnets
launch_configuration = "${aws_launch_configuration.linux-worker-launchconfig.name}"
min_size = var.octopus_aws_autoscalinggroup_size
max_size = var.octopus_aws_autoscalinggroup_size
health_check_grace_period = 300
health_check_type = "EC2"
force_delete = true
tag {
key = "Name"
value = "Samples Linux Worker"
propagate_at_launch = true
}
}
天蓝色工人地形
// Define resource group
resource "azurerm_resource_group" "octopus-samples-azure-workers" {
name = var.octopus_azure_resourcegroup_name
location = var.octopus_azure_location
tags = var.tags
}
// Define virtual network
resource "azurerm_virtual_network" "octopus-samples-workers-virtual-network" {
name = "octopus-samples-workers"
address_space = ["10.0.0.0/16"]
location = var.octopus_azure_location
resource_group_name = var.octopus_azure_resourcegroup_name
depends_on = [
azurerm_resource_group.octopus-samples-azure-workers
]
tags = var.tags
}
// Define subnet
resource "azurerm_subnet" "octopus-samples-workers-subnet" {
name = "octopus-samples-workers-subnet"
resource_group_name = var.octopus_azure_resourcegroup_name
virtual_network_name = azurerm_virtual_network.octopus-samples-workers-virtual-network.name
address_prefixes = ["10.0.2.0/24"]
depends_on = [
azurerm_resource_group.octopus-samples-azure-workers,
azurerm_virtual_network.octopus-samples-workers-virtual-network
]
}
// Define azure scale set
resource "azurerm_linux_virtual_machine_scale_set" "samples-azure-workers" {
name = var.octopus_azure_scaleset_name
resource_group_name = var.octopus_azure_resourcegroup_name
location = var.octopus_azure_location
sku = var.octopus_azure_vm_size
instances = var.octopus_azure_vm_instance_count
admin_username = var.octopus_azure_vm_admin_username
admin_password = var.octopus_azure_vm_admin_password
disable_password_authentication = false
user_data = "${base64encode(file("../configure-tentacle.sh"))}"
identity {
type = "SystemAssigned"
}
source_image_reference {
publisher = "Canonical"
offer = "UbuntuServer"
sku = var.octopus_azure_vm_sku
version = "latest"
}
os_disk {
storage_account_type = "Standard_LRS"
caching = "ReadWrite"
}
network_interface {
name = "example"
primary = true
ip_configuration {
name = "internal"
primary = true
subnet_id = azurerm_subnet.octopus-samples-workers-subnet.id
}
}
tags = var.tags
}
GCP 工人地形
resource "google_compute_instance" "vm_instance" {
count = var.instance_count
name = "${var.instance_name}-${count.index + 1}"
machine_type = var.instance_size
boot_disk {
initialize_params {
image = var.instance_osimage
size = 30
}
}
network_interface {
network = "default" #google_compute_network.vpc_network.name
access_config {
// Ephemeral public IP - needed to send and receive traffic directly to and from outside network
}
}
metadata_startup_script = file("../configure-tentacle.sh")
service_account {
email = google_service_account.database_service_account.email
scopes = ["cloud-platform"]
}
tags = ["octopus-samples-worker"]
}
output "ip" {
value = google_compute_instance.vm_instance[*].network_interface[0].access_config[0].nat_ip
}
Terraform 包包含一个 Bash 脚本,运行在由云扩展技术创建的虚拟机上。当被执行时,VM 从获取空间列表步骤的输出变量中向空间和池注册自己。
#!/bin/bash
serverUrl="#{Samples.Octopus.Url}"
serverCommsPort="10943"
apiKey="#{Samples.Octopus.Api.Key}"
name=$HOSTNAME
configFilePath="/etc/octopus/default/tentacle-default.config"
applicationPath="/home/Octopus/Applications/"
workerPool="#{Octopus.Action[Get Samples Spaces].Output.WorkerPoolName}"
machinePolicy="Default Machine Policy"
space="#{Octopus.Action[Get Samples Spaces].Output.InitialSpaceName}"
# Install Tentacle
sudo apt-key adv --fetch-keys "https://apt.octopus.com/public.key"
sudo add-apt-repository "deb https://apt.octopus.com/ focal main"
sudo apt-get update
sudo apt-get install tentacle -y
# Install Docker
sudo apt install apt-transport-https ca-certificates curl software-properties-common -y
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable"
sudo apt-get install docker-ce docker-ce-cli containerd.io -y
# Install wget
sudo apt-get install wget -y
# Download the Microsoft repository GPG keys
wget https://packages.microsoft.com/config/debian/10/packages-microsoft-prod.deb
# Register the Microsoft repository GPG keys
sudo dpkg -i packages-microsoft-prod.deb
# Update the list of products
sudo apt-get update
# Install PowerShell
sudo apt-get install -y powershell
# Pull worker tools image
sudo docker pull #{Project.Docker.WorkerToolImage}:#{Project.Docker.WorkerToolImageTag}
# Configure and register worker
sudo /opt/octopus/tentacle/Tentacle create-instance --config "$configFilePath" --instance "$name"
sudo /opt/octopus/tentacle/Tentacle new-certificate --if-blank
sudo /opt/octopus/tentacle/Tentacle configure --noListen True --reset-trust --app "$applicationPath"
echo "Registering the worker $name with server $serverUrl"
sudo /opt/octopus/tentacle/Tentacle service --install --start
sudo /opt/octopus/tentacle/Tentacle register-worker --server "$serverUrl" --apiKey "$apiKey" --name "$name" --comms-style "TentacleActive" --server-comms-port $serverCommsPort --workerPool "$workerPool" --policy "$machinePolicy" --space "$space"
sudo /opt/octopus/tentacle/Tentacle service --restart
等待工人自己注册
在我们可以将工人添加到其他空间之前,工人需要注册他们自己。这一步监视工作线程池,直到注册了所需数量的工作线程。
# Define parameters
$baseUrl = $OctopusParameters['Samples.Octopus.Url']
$apiKey = $OctopusParameters['Samples.Octopus.Api.Key']
$header = @{ "X-Octopus-ApiKey" = $apiKey }
$spaceId = $OctopusParameters['Octopus.Action[Get Samples Spaces].Output.InitialSpaceId']
$spaceName = $OctopusParameters['Octopus.Action[Get Samples Spaces].Output.InitialSpaceName']
$workerPoolName = $OctopusParameters['Octopus.Action[Get Samples Spaces].Output.WorkerPoolName']
if ($baseUrl.EndsWith("/"))
{
$baseUrl = $baseUrl.SubString(0, $baseUrl.LastIndexOf("/"))
}
# Get worker pool
Write-Host "Getting reference to $workerPoolName in space $spaceName ..."
$workerPool = ((Invoke-RestMethod -Method Get -Uri "$baseUrl/api/$($spaceId)/workerpools/all" -Headers @{"X-Octopus-ApiKey"="$apiKey"}) | Where-Object {$_.Name -eq $workerPoolName})
# Check worker pool
if ($null -ne $workerPool)
{
# Get all workers
Write-Host "Checking to see if workers have registered themselves ..."
$workers = (Invoke-RestMethod -Method Get -Uri "$baseUrl/api/$($spaceId)/workerpools/$($workerPool.Id)/workers" -Headers @{"X-Octopus-ApiKey"="$apiKey"})
$retries = 20
$cloudProvider = $OctopusParameters['Project.CloudProvider.Folder.Name']
$numberOfWorkersKey = $OctopusParameters.Keys | Where-Object {$_ -like "*$cloudProvider*" -and $_ -like "*Instance.Count*"}
$numberOfWorkers = $OctopusParameters[$numberOfWorkersKey]
while ($workers.Items.Count -ne $numberOfWorkers)
{
if ($retries -gt 0)
{
Write-Host "Waiting 60 seconds for $numberOfWorkers workers to register themselves, $retries tries remaining ..."
Start-Sleep -Seconds 60
$retries--
$workers = (Invoke-RestMethod -Method Get -Uri "$baseUrl/api/$($spaceId)/workerpools/$($workerPool.Id)/workers" -Headers @{"X-Octopus-ApiKey"="$apiKey"})
}
else
{
Write-Error "Workers didn't show up in time!"
}
}
}
向剩余空间添加工作人员
在我们将工人添加到第一个空间之后,我们使用 API 将他们添加到剩余的空间。
function Get-OctopusItems
{
# Define parameters
param(
$OctopusUri,
$ApiKey,
$SkipCount = 0
)
# Define working variables
$items = @()
$skipQueryString = ""
$headers = @{"X-Octopus-ApiKey"="$ApiKey"}
# Check to see if there there is already a querystring
if ($octopusUri.Contains("?"))
{
$skipQueryString = "&skip="
}
else
{
$skipQueryString = "?skip="
}
$skipQueryString += $SkipCount
# Get intial set
$resultSet = Invoke-RestMethod -Uri "$($OctopusUri)$skipQueryString" -Method GET -Headers $headers
# Check to see if it returned an item collection
if ($resultSet.Items)
{
# Store call results
$items += $resultSet.Items
# Check to see if resultset is bigger than page amount
if (($resultSet.Items.Count -gt 0) -and ($resultSet.Items.Count -eq $resultSet.ItemsPerPage))
{
# Increment skip count
$SkipCount += $resultSet.ItemsPerPage
# Recurse
$items += Get-OctopusItems -OctopusUri $OctopusUri -ApiKey $ApiKey -SkipCount $SkipCount
}
}
else
{
return $resultSet
}
# Return results
return $items
}
# Define variables
$baseUrl = $OctopusParameters['Samples.Octopus.Url']
$apiKey = $OctopusParameters['Samples.Octopus.Api.Key']
$header = @{ "X-Octopus-ApiKey" = $apiKey }
$remainingSpaceIds = $OctopusParameters['Octopus.Action[Get Samples Spaces].Output.RemainingSpaceIds'].Split(",")
$initialSpaceId = $OctopusParameters['Octopus.Action[Get Samples Spaces].Output.InitialSpaceId']
$initialWorkerPoolName = "$($OctopusParameters['Project.CloudProvider.Folder.Name']) Worker Pool TF"
# Get registered workers
$initialWorkerPool = (Get-OctopusItems -OctopusUri "$baseUrl/api/$initialSpaceId/workerPools" -ApiKey $apiKey | Where-Object {$_.Name -eq $initialWorkerPoolName})
$workers = Get-OctopusItems -OctopusUri "$baseUrl/api/$initialSpaceId/workerPools/$($initialWorkerPool.Id)/workers" -ApiKey $apiKey
# Loop through the spaces
foreach ($spaceId in $remainingSpaceIds)
{
$workerPool = (Get-OctopusItems -OctopusUri "$baseUrl/api/$spaceId/workerPools" -ApiKey $apiKey | Where-Object {$_.Name -eq $initialWorkerPoolName})
# Check worker pool
Write-Host "Verifying that space Id $spaceId has a worker pool called $initialWorkerPoolName ..."
if ($null -ne $workerPool)
{
# Get default machine policy
$machinePolicy = (Get-OctopusItems -OctopusUri "$baseUrl/api/$spaceId/machinepolicies" -ApiKey $apiKey | Where-Object {$_.Name -eq "Default Machine Policy"})
# Loop through workers
foreach ($worker in $workers)
{
# Build JSON payload
$jsonPayload = @{
Name = $worker.Name
MachinePolicyId = $machinePolicy.Id
IsDisabled = $worker.IsDisabled
HealthStatus = $worker.HealthStatus
HasLatestCalamari = $worker.HasLatestCalamari
IsInProcess = $true
EndPoint = $worker.Endpoint
WorkerPoolIds = @($workerPool.Id)
}
try
{
# Add worker
Write-Host "Adding $($worker.Name) to space Id $spaceId..."
Invoke-RestMethod -Method Post -Uri "$baseUrl/api/$($spaceId)/workers" -Body ($jsonPayload | ConvertTo-Json -Depth 10) -Headers $header
}
catch
{
Write-Highlight "An error occured adding $($worker.Name) to space Id $spaceId"
Write-Warning "An error occured adding $($worker.Name) to space Id $spaceId"
Write-Host "StatusCode:" $_.Exception.Response.StatusCode.value__
Write-Host "StatusDescription:" $_.Exception.Response.StatusDescription
}
}
}
}
结论
在我们的 Samples 实例的所有空间中共享工作人员让我们更有效地整合和使用资源。我希望这篇文章能给你一些如何做同样事情的想法。
愉快的部署!
SHA1“粉碎”碰撞-章鱼部署
如果你最近一直关注科技新闻,你会看到谷歌宣布一项新的攻击,这使得几乎有可能产生 SHA1 哈希冲突。
风险似乎集中在证书用于数字签名而非加密的领域。到目前为止,我们还没有看到任何明确的报告表明这适用于 SSL/TLS——我的理解是,存在有人制作假证书的风险,它可以像真证书一样被“信任”,但 SSL/TLS 数据不能被解密。当然,我不是专家!
无论哪种方式,SHA1 已经过时一段时间了,证书颁发机构很久以前就停止颁发 SHA1 证书了。这对 SHA1 来说只是又一个致命的打击。
在这篇文章中,我想解释这对 Octopus 意味着什么,并提供一些 PowerShell 脚本来查看您部署的应用程序是否受到影响。
章鱼和触手目前使用 SHA1
当您安装 Octopus 和触手代理时,它们都会生成 X.509 证书,用于加密它们之间的连接(通过 TLS)。当我们生成这些自签名证书时,我们使用 SHA1 。这是我们在 Windows API 中调用的证书生成函数的默认设置,我们从未想过要改变。
缓解措施:
- 与此同时,如果你担心的话,有一个变通办法:你可以生成自己的证书,并告诉 Octopus 和触手使用它们。有关详细信息,请查看我们关于如何使用章鱼和触手自定义证书的文档页面。
- 很快
我们将发布一个更新,将算法改为 SHA256。这将适用于新的安装,但对于旧的安装,您必须重新生成证书并更新所有机器之间的信任。 - 稍后我们会在 Octopus 中增加一些功能,自动重新生成 Octopus 服务器和所有触手上的证书,并为您全部更新。如果您有许多机器要管理,您可能需要等待。这还需要做更多的工作,但我们会在谷歌 90 天内公布技术细节之前,尽量腾出时间来完成这项工作。
你应该检查的其他事情
你要检查 SHA1 是否在其他地方被使用。八达通用户的常见例子包括:
- 如果您使用 HTTPS,用于八达通网络前端的证书。通常这是人们自己提供的东西。
- 用于向第三方服务进行身份验证的证书,如 Azure 管理证书
- 用于为您部署的网站提供 HTTPS 的证书
使用 PowerShell 检测 SHA1 证书
给定一个X509Certificate2
对象,这里有一个 PowerShell 函数检查它是否使用 SHA1:
function Test-CertificateIsSha1{
[cmdletbinding()]
param(
[Parameter(
Position=0,
Mandatory=$true,
ValueFromPipeline=$true)
]
[System.Security.Cryptography.X509Certificates.X509Certificate2[]]$Certificate
)
process
{
foreach($cert in $Certificate)
{
$algorithm = $cert.SignatureAlgorithm.FriendlyName
$isSha1 = $algorithm.Contains("sha1")
Write-Output $isSha1
}
}
}
这里有一个 PowerShell 脚本,您可以使用它来检查网站是否正在使用 SHA1 证书。Jason Stangroome 在 Get-RemoteSSLCertificate 的初步实施中表现出色:
function Get-RemoteSSLCertificate {
[CmdletBinding()]
param (
[Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)]
[System.Uri[]]
$URI
)
process
{
foreach ($u in $URI)
{
$Certificate = $null
$TcpClient = New-Object -TypeName System.Net.Sockets.TcpClient
try {
$TcpClient.Connect($u.Host, $u.Port)
$TcpStream = $TcpClient.GetStream()
$Callback = { param($sender, $cert, $chain, $errors) return $true }
$SslStream = New-Object -TypeName System.Net.Security.SslStream -ArgumentList @($TcpStream, $true, $Callback)
try {
$SslStream.AuthenticateAsClient('')
$Certificate = $SslStream.RemoteCertificate
} finally {
$SslStream.Dispose()
}
} finally {
$TcpClient.Dispose()
}
if ($Certificate) {
if ($Certificate -isnot [System.Security.Cryptography.X509Certificates.X509Certificate2]) {
$Certificate = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList $Certificate
}
Write-Output $Certificate
}
}
}
}
$sites = @("https://www.yoursite.com", "https://anothersite.com")
$sites | ForEach-Object {
$site = $_
$cert = Get-RemoteSSLCertificate -Uri $site
if (Test-CertificateIsSha1 -Certificate $cert) {
Write-Warning "Site: $site uses SHA1"
}
}
这里有一个脚本检查您的 IIS 服务器是否正在使用任何 SHA1 证书:
Import-Module WebAdministration
foreach ($site in Get-ChildItem IIS:\Sites)
{
foreach ($binding in $site.bindings.Collection)
{
if ($binding.protocol -eq "https")
{
$hash = $binding.CertificateHash
$store = $binding.certificateStoreName
$certs = Get-ChildItem "Cert:\LocalMachine\$store\$hash"
foreach ($cert in $certs)
{
if (Test-CertificateIsSha1 -Certificate $cert)
{
Write-Warning "Site: $site.Name uses SHA1"
}
}
}
}
}
您可以在所有机器上的 Octopus 脚本控制台中轻松运行:
八达通的证书功能
这是一个为 Octopus 3.11 中的新证书功能欢呼的好时机。如果你无论如何都要更新你的网站证书,为什么不用 Octopus 来管理它们呢?
如需更新,请订阅我们的简讯。
Selenium 系列:简化的元素位置- Octopus 部署
原文:https://octopus.com/blog/selenium/9-simplified-element-location/simplified-element-location
这篇文章是关于创建 Selenium WebDriver 测试框架的系列文章的一部分。
在上一篇文章中,我们研究了在网页中定位元素的各种方法,以便测试与它们进行交互。我们构建了允许我们与通过 ID、XPath 和 CSS 选择器找到的元素进行交互的方法。
但是如果我们可以用元素标识符调用一组方法,并让 WebDriver 计算出哪些元素匹配,那就更好了。
假设您正在使用测试驱动的开发方法开发一个 web 应用程序。您可以编写如下所示的测试:
public void formTestWithSimpleBy() throws URISyntaxException {
final AutomatedBrowser automatedBrowser =
AUTOMATED_BROWSER_FACTORY.getAutomatedBrowser("ChromeNoImplicitWait");
// Populate these variables when it is known how to locate the elements
final String formButtonLocator = "";
final String formTextBoxLocator = "";
final String formTextAreaLocator = "";
final String formDropDownListLocator = "";
final String formCheckboxLocator = "";
final String messageLocator = "";
try {
automatedBrowser.init();
automatedBrowser.goTo(FormTest.class.getResource("/form.html").toURI().toString());
automatedBrowser.clickElement(formButtonLocator, 10);
assertEquals("Button Clicked", automatedBrowser.getTextFromElement(messageLocator));
automatedBrowser.populateElement(formTextBoxLocator, "test text", 10);
assertEquals("Text Input Changed", automatedBrowser.getTextFromElement(messageLocator));
automatedBrowser.populateElement(formTextAreaLocator, "test text",
10);
assertEquals("Text Area Changed", automatedBrowser.getTextFromElement(messageLocator));
automatedBrowser.selectOptionByTextFromSelect("Option 2.1", formDropDownListLocator, 10);
assertEquals("Select Changed", automatedBrowser.getTextFromElement(messageLocator));
automatedBrowser.clickElement(formCheckboxLocator, 10);
assertEquals("Checkbox Changed", automatedBrowser.getTextFromElement(messageLocator));
} finally {
automatedBrowser.destroy();
}
}
注意,测试调用了类似于automatedBrowser.clickElement()
或automatedBrowser.populateElement()
的方法。这些方法并不指定通过 ID、XPath 或 CSS 选择器来查找元素。事实上,使用测试驱动的开发方法,您很可能不知道选择这些元素的最佳方式,因为 web 页面还没有被编写。作为一名测试人员,我们所关心的是有某种方法来定位元素,当这些值已知时,实际的定位器是我们稍后填充的东西。
通过一些技巧,编写这样的测试是可能的。
我们将从创建一个名为SimpleBy
的接口开始。它有一个名为getElement()
的方法。该方法将返回匹配定位器字符串的第一个元素,无论该定位器是 ID、XPath、CSS 选择器、名称还是任何其他搜索元素的方式:
package com.octopus.utils;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
public interface SimpleBy {
WebElement getElement(WebDriver webDriver,
String locator,
int waitTime,
ExpectedConditionCallback expectedConditionCallback);
}
ExpectedConditionCallback
接口定义了一个方法,它接受一个By
对象,并返回一个ExpectedCondition
对象。我们将利用这一点来建立一个显式的等待条件:
package com.octopus.utils;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedCondition;
@FunctionalInterface
public interface ExpectedConditionCallback {
ExpectedCondition<WebElement> getExpectedCondition(By by);
}
为了表示使用通用定位器定位元素时遇到的错误,我们创建了WebElementException
类:
package com.octopus.exceptions;
public class WebElementException extends RuntimeException {
public WebElementException() {
}
public WebElementException(final String message) {
super(message);
}
public WebElementException(final String message, final Throwable ex) {
super(message, ex);
}
public WebElementException(final Exception ex) {
super(ex);
}
}
实现SimpleBy
接口的是SimpleByImpl
类。这就是奇迹发生的地方:
package com.octopus.utils.impl;
import com.octopus.exceptions.WebElementException;
import com.octopus.utils.ExpectedConditionCallback;
import com.octopus.utils.SimpleBy;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedCondition;
import java.util.concurrent.TimeUnit;
public class SimpleByImpl implements SimpleBy {
private static final int MILLISECONDS_PER_SECOND = 1000;
private static final int TIME_SLICE = 100;
@Override
public WebElement getElement(
WebDriver webDriver,
String locator,
int waitTime,
ExpectedConditionCallback expectedConditionCallback) {
final By[] byInstances = new By[] {
By.id(locator),
By.xpath(locator),
By.cssSelector(locator),
By.className(locator),
By.linkText(locator),
By.name(locator)
};
long time = -1;
while (time < waitTime * MILLISECONDS_PER_SECOND) {
for (final By by : byInstances) {
try {
final WebDriverWaitEx wait = new WebDriverWaitEx(
webDriver,
TIME_SLICE,
TimeUnit.MILLISECONDS);
final ExpectedCondition<WebElement> condition =
expectedConditionCallback.getExpectedCondition(by);
return wait.until(condition);
} catch (final Exception ignored) {
/*
Do nothing
*/
}
time += TIME_SLICE;
}
}
throw new WebElementException("All attempts to find element failed");
}
}
让我们来分解这个类。
在getElement()
方法中,我们创建了许多By
实例,传递通用定位器字符串。定位器字符串可以是任何东西:XPath、ID 或 CSS 选择器。我们不知道我们有哪种定位器,但是通过用它构建By
类的多个不同实现,我们有多种不同的方法来尝试找到匹配的元素:
@Override
public WebElement getElement(
WebDriver webDriver,
String locator,
int waitTime,
ExpectedConditionCallback expectedConditionCallback) {
final By[] byInstances = new By[] {
By.id(locator),
By.xpath(locator),
By.cssSelector(locator),
By.className(locator),
By.linkText(locator),
By.name(locator)
};
然后我们进入一个while
循环。这个循环至少会运行一次,因为我们从-1
开始time
变量。当循环中花费的时间少于我们分配的查找匹配元素的时间时,循环将继续:
long time = -1;
while (time < waitTime * MILLISECONDS_PER_SECOND) {
接下来,我们遍历之前创建的By
类的每个实例。我们这里的目的是找到一个By
实例,它可以使用所提供的定位器来实际匹配一个元素:
for (final By by : byInstances) {
在try
块中,我们执行了一个显式等待,只是这次我们等待的时间很短。TIME_SLICE
被设置为一秒钟的0.1
,这意味着循环的这次迭代所使用的By
的每个实现都有几分之一秒的时间来寻找匹配元素。
注意,我们调用传递给expectedConditionCallback
参数的函数接口,将By
类的实例转换成期望的条件。这允许方法的调用方决定元素应该处于什么状态才能被认为是匹配的。
WebDriver API 不是线程安全的,所以我们必须像这样按顺序执行代码,而不是在单独的线程中运行每个测试。
catch
块什么也不做,因为我们预计大多数寻找具有By
类的给定实现的元素的尝试都会失败。例如,如果定位器字符串被设置为类似于//*[@name="button_element"]
的 XPath,定位器By.cssSelector(locator)
将永远找不到匹配,因为它只适用于 CSS 选择器。
然而,定位器By.xpath(locator)
很可能找到匹配,在这种情况下return
ait.until(condition)`将退出循环并返回匹配元素:
try {
final WebDriverWaitEx wait = new WebDriverWaitEx(
webDriver,
TIME_SLICE,
TimeUnit.MILLISECONDS);
final ExpectedCondition<WebElement> condition = expectedConditionCallback.getExpectedCondition(by);
return wait.until(condition);
} catch (final Exception ignored) {
/*
Do nothing
*/
}
如果By
的当前实现没有成功找到匹配的元素,我们将时间(找到元素所花费的总时间)增加TIME_SLICE
(我们分配给By
的这个实现的时间),并进入下一个搜索:
time += TIME_SLICE;
如果在分配的时间内所有寻找元素的尝试都失败了,我们抛出一个异常:
throw new WebElementException("All attempts to find element failed");
这段代码的最终结果是给每个By
的实现一小段时间来找到与所提供的定位器字符串相匹配的东西。第一个找到匹配的返回它,否则抛出一个异常。
您可能想知道如果两个By
的实现可以返回一个匹配会发生什么。如果一个元素的id
属性与另一个元素的name
属性相同,就会发生这种情况。在这种情况下,第一个匹配项获胜并被返回。
但是在实践中,这种冲突很少发生,并且可以通过传递 XPath 或 CSS 选择器作为定位器来轻松解决。您的 web 页面在id
、name
或class
属性中嵌入 XPath 或 CSS 选择器的几率非常小,因此它们不会匹配多个元素。
如果你有敏锐的眼光,你可能已经注意到我们创建了一个WebDriverWaitEx
的实例,而不是通常的WebDriverWait
。WebDriverWaitEx
扩展了WebDriverWait
并添加了一个额外的构造函数,允许它被配置为等待亚秒量的时间(WebDriverWait
可以等待不少于 1 秒)。这对我们很重要,因为如果我们测试的每个By
类的实例需要 1 秒钟完成,那么整个循环至少需要 6 秒钟来处理,这太长了:
package com.octopus.utils.impl;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.ui.Clock;
import org.openqa.selenium.support.ui.Sleeper;
import org.openqa.selenium.support.ui.SystemClock;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.util.concurrent.TimeUnit;
public class WebDriverWaitEx extends WebDriverWait {
public static final long DEFAULT_SLEEP_TIMEOUT = 10;
public WebDriverWaitEx(final WebDriver driver, final long timeOutInSeconds) {
this(driver, new SystemClock(), Sleeper.SYSTEM_SLEEPER, timeOutInSeconds, DEFAULT_SLEEP_TIMEOUT);
}
public WebDriverWaitEx(final WebDriver driver, final long timeOut, final TimeUnit time) {
this(driver, new SystemClock(), Sleeper.SYSTEM_SLEEPER, timeOut, DEFAULT_SLEEP_TIMEOUT, time);
}
public WebDriverWaitEx(final WebDriver driver, final long timeOutInSeconds, final long sleepInMillis) {
this(driver, new SystemClock(), Sleeper.SYSTEM_SLEEPER, timeOutInSeconds, sleepInMillis);
}
public WebDriverWaitEx(
final WebDriver driver,
final Clock clock,
final Sleeper sleeper,
final long timeOutInSeconds,
final long sleepTimeOut) {
this(driver, clock, sleeper, timeOutInSeconds, sleepTimeOut, TimeUnit.SECONDS);
}
public WebDriverWaitEx(
final WebDriver driver,
final Clock clock,
final Sleeper sleeper,
final long timeOut,
final long sleepTimeOut,
final TimeUnit time) {
// Call the WebDriverWait constructor with a timeout of 0
super(driver, clock, sleeper, 0, sleepTimeOut);
// Now set the timeout, possibly as a sub-second duration
withTimeout(timeOut, time);
}
}
我们现在能够基于任何类型的定位器找到元素。为了利用这一点,我们向AutomatedBrowser
接口添加了以下方法:
void clickElement(String locator);
void clickElement(String locator, int waitTime);
void selectOptionByTextFromSelect(String optionText, String locator);
void selectOptionByTextFromSelect(String optionText, String locator, int waitTime);
void populateElement(String locator, String text);
void populateElement(String locator, String text, int waitTime);
String getTextFromElement(String locator);
String getTextFromElement(String locator, int waitTime);
常用的默认方法被添加到AutomatedBrowserBase
:
@Override
public void clickElement(final String locator) {
if (getAutomatedBrowser() != null) {
getAutomatedBrowser().clickElement(locator);
}
}
@Override
public void clickElement(final String locator, final int waitTime) {
if (getAutomatedBrowser() != null) {
getAutomatedBrowser().clickElement(locator, waitTime);
}
}
@Override
public void selectOptionByTextFromSelect(final String optionText, final String locator) {
if (getAutomatedBrowser() != null) {
getAutomatedBrowser().selectOptionByTextFromSelect(optionText, locator);
}
}
@Override
public void selectOptionByTextFromSelect(final String optionText, final String locator, final int waitTime) {
if (getAutomatedBrowser() != null) {
getAutomatedBrowser().selectOptionByTextFromSelect(optionText, locator, waitTime);
}
}
@Override
public void populateElement(final String locator, final String text) {
if (getAutomatedBrowser() != null) {
getAutomatedBrowser().populateElement(locator, text);
}
}
@Override
public void populateElement(final String locator, final String text, final int waitTime) {
if (getAutomatedBrowser() != null) {
getAutomatedBrowser().populateElement(locator, text, waitTime);
}
}
@Override
public String getTextFromElement(final String locator) {
if (getAutomatedBrowser() != null) {
return getAutomatedBrowser().getTextFromElement(locator);
}
return null;
}
@Override
public String getTextFromElement(final String locator, final int waitTime) {
if (getAutomatedBrowser() != null) {
return getAutomatedBrowser().getTextFromElement(locator, waitTime);
}
return null;
}
并且在WebDriverDecorator
中增加了以下实现:
private static final SimpleBy SIMPLE_BY = new SimpleByImpl();
@Override
public void clickElement(final String locator) {
clickElement(locator, 0);
}
@Override
public void clickElement(final String locator, final int waitTime) {
SIMPLE_BY.getElement(
getWebDriver(),
locator,
waitTime,
by -> ExpectedConditions.elementToBeClickable(by))
.click();
}
@Override
public void selectOptionByTextFromSelect(final String optionText, final String locator) {
selectOptionByTextFromSelect(optionText, locator, 0);
}
@Override
public void selectOptionByTextFromSelect(final String optionText, final String locator, final int waitTime) {
new Select(SIMPLE_BY.getElement(
getWebDriver(),
locator,
waitTime,
by -> ExpectedConditions.elementToBeClickable(by)))
.selectByVisibleText(optionText);
}
@Override
public void populateElement(final String locator, final String text) {
populateElement(locator, text, 0);
}
@Override
public void populateElement(final String locator, final String text, final int waitTime) {
SIMPLE_BY.getElement(
getWebDriver(),
locator,
waitTime,
by -> ExpectedConditions.elementToBeClickable(by))
.sendKeys(text);
}
@Override
public String getTextFromElement(final String locator) {
return getTextFromElement(locator, 0);
}
@Override
public String getTextFromElement(final String locator, final int waitTime) {
return SIMPLE_BY.getElement(
getWebDriver(),
locator,
waitTime,
by -> ExpectedConditions.presenceOfElementLocated(by))
.getText();
}
现在我们可以混合使用 IDs、CSS 选择器和 XPaths 来完成测试。新方法将自动找到任何匹配的元素,我们不需要担心将正确的定位器匹配到正确的方法。
这里使用ChromeNoImplicitWait
配置非常重要。如果您还记得上一篇文章,混合隐式和显式等待会导致一些不良结果,这就是曾经的情况。如果我们使用一个启用了隐式等待的配置,对我们上面实现的方法的调用可能需要将近一分钟才能完成,因为 6 个显式等待中的每一个都要等待我们为隐式等待配置的 10 秒。通过使用ChromeNoImplicitWait
配置,我们确保SimpleByImpl
类中的显式等待只需要几分之一秒:
@Test
public void formTestWithSimpleBy() throws URISyntaxException {
final AutomatedBrowser automatedBrowser = AUTOMATED_BROWSER_FACTORY.getAutomatedBrowser("ChromeNoImplicitWait");
final String formButtonLocator = "button_element";
final String formTextBoxLocator = "text_element";
final String formTextAreaLocator = "textarea_element";
final String formDropDownListLocator = "[name=select_element]";
final String formCheckboxLocator = "//*[@name=\"checkbox1_element\"]";
final String messageLocator = "message";
try {
automatedBrowser.init();
automatedBrowser.goTo(FormTest.class.getResource("/form.html").toURI().toString());
automatedBrowser.clickElement(formButtonLocator, 10);
assertEquals("Button Clicked", automatedBrowser.getTextFromElement(messageLocator));
automatedBrowser.populateElement(formTextBoxLocator, "test text", 10);
assertEquals("Text Input Changed", automatedBrowser.getTextFromElement(messageLocator));
automatedBrowser.populateElement(formTextAreaLocator, "test text", 10);
assertEquals("Text Area Changed", automatedBrowser.getTextFromElement(messageLocator));
automatedBrowser.selectOptionByTextFromSelect("Option 2.1", formDropDownListLocator, 10);
assertEquals("Select Changed", automatedBrowser.getTextFromElement(messageLocator));
automatedBrowser.clickElement(formCheckboxLocator, 10);
assertEquals("Checkbox Changed", automatedBrowser.getTextFromElement(messageLocator));
} finally {
automatedBrowser.destroy();
}
}
根据我自己的经验,我们添加到AutomatedBrowser
接口的这些新方法比绑定到特定定位器的方法要方便得多。它们消除了手动保持定位器和它们被传递到的方法同步的需要,并且代码可读性也更好。由于这个原因,未来的帖子将几乎只使用这些新方法。
这篇文章是关于创建 Selenium WebDriver 测试框架的系列文章的一部分。
使用 runbooks - Octopus Deploy 对您的基础设施进行冒烟测试
原文:https://octopus.com/blog/smoke-testing-infrastructure-runbooks
互联网坏了!
任何在帮助台呆过一段时间的人都听过这种说法,以及其他类似的对客户遇到的问题的模糊描述。在诊断问题时,获得可操作的信息是成功的一半。
然而,当支持复杂的基础设施时,可能很难知道系统是如何设计的,这使得很难知道要问什么问题以及在哪里可以找到帮助解决问题的信息。这是企业环境中常见的自定义应用程序的本质,每个应用程序都是上一个应用程序的发展,或者每次都由完全不同的团队使用独特的方法编写。这意味着关于如何支持应用程序的业务知识通常只存在于少数员工的头脑中。
Runbooks 提供了一种以自动化和可测试的方式获取这些业务知识的方法,确保支持团队能够快速诊断高级问题,并有效地响应客户请求。
在这篇文章中,我提供了一个针对 1 级支持团队的示例操作手册,旨在对 AWS 中的典型微服务应用程序进行冒烟测试。
先决条件
这篇文章假设 runbook 步骤是在一个 Linux Worker 上运行的。他们使用 dig 进行 DNS 查找,使用 hey 进行负载测试,使用 curl 与 HTTP 端点交互,使用 mysql 客户端。
要在 Ubuntu 中安装这些工具,请运行以下命令:
apt-get install curl dnsutils mysql
要在 Fedora、RHEL、Centos 或 Amazon Linux 中安装工具,请运行:
yum install curl mysql bind-utils
然后用命令手动下载hey
:
curl -o /usr/local/bin/hey https://hey-release.s3.us-east-2.amazonaws.com/hey_linux_amd64
chmod +x /usr/local/bin/hey
我们创建了一个公共 Octopus 实例,这个 runbook 是针对一个活动的微服务定义的。使用来宾帐户登录,查看操作手册步骤并列出以前执行的结果。
烟雾测试 DNS
DNS 让你把友好的名字,比如development.octopus.pub
,映射到 IP 地址,比如52.52.151.20
。
DNS 通常是一个稳定的服务,但当它失败时,很可能没有其他网络服务将正常工作。因此,您希望对暴露于 internet 的服务执行的第一个冒烟测试是验证 DNS 名称可以被解析。
下面的脚本执行dig
来检查与域名相关的 DNS 记录:
dig "#{Octopus.Environment.Name | ToLower}.octopus.pub"
# Capture the return code of the previous command. This will be used as the exit code of the entire script once we print out
# any further instructions.
RETURN_CODE=$?
echo "============================"
echo "HOW TO INTERPRET THE RESULTS"
echo "============================"
echo "The dig command returns a lot of technical details, most of which are not important from a level 1 support point of view."
echo "As long as the command passes, you can assume the DNS is correctly configured."
echo "If the command fails, escalate this issue to level 2 support."
# Exit the script with the return code from the smoke test
exit $RETURN_CODE
该脚本的输出如下所示:
像dig
这样的工具往往技术性很强,输出需要一些经验来解释。然而,您的 1 级支持团队通常不需要深入了解 DNS 等网络问题,因此脚本必须解释结果和任何进一步的行动。这是在操作手册中获取商业知识的一个例子,这意味着即使是新手也可以运行这些操作手册,并有信心对结果做出反应。
冒烟测试 MySQL
在这个例子中,我们的应用程序使用 MySQL 数据库来实现持久性。如果数据库不可访问,服务将失败,因此下一步是编写一个脚本对数据库进行冒烟测试。
下面的脚本使用mysql
命令行工具来尝试查询一个已知的数据库表。注意,结果被重定向到/dev/null
,因为我们不想用实际的数据库记录填充日志:
DATABASE_HOST=$(get_octopusvariable "Database.Host")
USERNAME=$(get_octopusvariable "Database.AuditsUsername")
PASSWORD=$(get_octopusvariable "Database.AuditsPassword")
echo "Database Host: $DATABASE_HOST"
echo "Database Username: $USERNAME"
mysql --host=$DATABASE_HOST --user=$USERNAME --password=$PASSWORD audits -e "SELECT * FROM audits" > /dev/null
# Capture the return code of the previous command. This will be used as the exit code of the entire script once we print out
# any further instructions.
RETURN_CODE=$?
echo "============================"
echo "HOW TO INTERPRET THE RESULTS"
echo "============================"
echo "This test attempts to query the audits database."
echo "If this step fails, escalate the issue to level 2 support."
# Exit the script with the return code from the smoke test
exit $RETURN_CODE
冒烟测试 HTTP 服务
下一个测试验证公共 HTTP 端点是否响应了预期的状态代码。Web 服务总是随任何响应返回一个状态代码,通常您可以假设一个公共 URL 将返回一个代码 200,这表示响应成功。
有关 HTTP 响应代码的完整列表,请参考 MDN 文档。
对于这个测试,我们利用了社区步骤模板库中一个名为 HTTP - Test URL (Bash) 的步骤。这一步定义了一个 Bash 脚本,它根据提供的 URL 调用curl
,并验证 HTTP 状态代码:
负载测试
前面的三次冒烟测试验证了我们的应用程序基础设施的基本层。你可以预期,如果他们中的任何一个失败了,就会出现严重的问题。
然而,应用程序仍有可能工作,但由于速度慢或随机请求失败而不可用。您的最终冒烟测试使用hey
执行快速负载测试,以验证应用程序对多个请求的响应是否一致。下面的脚本针对微服务 API 调用hey
:
# Warm up
hey https://#{Octopus.Environment.Name | ToLower}.octopus.pub/api/audits > /dev/null
# Real test
hey https://#{Octopus.Environment.Name | ToLower}.octopus.pub/api/audits
# Capture the return code of the previous command. This will be used as the exit code of the entire script once we print out
# any further instructions.
RETURN_CODE=$?
echo "============================"
echo "HOW TO INTERPRET THE RESULTS"
echo "============================"
echo "It is expected that the majority of requests complete in under a second."
echo "If the chart above shows the majority of requests taking longer than a second, please escalate this issue to level 2 support."
# Exit the script with the return code from the smoke test
exit $RETURN_CODE
该脚本的输出显示在下面的屏幕截图中:
这个输出需要一些解释来决定采取什么进一步的行动。直方图显示了每个请求的响应时间,在本例中,您预期大多数请求在不到一秒钟的时间内完成。这些说明指导运行该脚本的团队成员根据输出做出适当的决策。
结论
您在企业环境中遇到的每个应用程序都需要大量底层服务和基础设施才能正常运行。通过编写探测和验证这些层的冒烟测试,支持团队可以快速确认问题,并高效、自信地响应支持请求。
在本文中,您看到了验证 DNS 服务、HTTP 端点和 MySQL 数据库的冒烟测试示例。您还看到了一个简单的负载测试,它提供了对服务响应多个请求时的性能的洞察。
阅读我们的 Runbooks 系列的其余部分。
愉快的部署!
SNI 在 Tomcat -章鱼部署
服务器名称指示(SNI)已经在 Tomcat 8.5 和 9 中实现,这意味着证书可以映射到传入请求的主机名。这允许 Tomcat 在单个 HTTPS 端口上使用不同的证书进行响应。
这篇博客文章着眼于如何在 Tomcat 9 中配置 SNI。
创建自签名证书
在本例中,我们将创建两个自签名证书。这是通过openssl
命令完成的。
下面的输出显示了如何为“Internet Widgets”公司创建第一个自签名证书。
$ openssl req -x509 -newkey rsa:4096 -keyout widgets.key -out widgets.crt -days 365
Generating a 4096 bit RSA private key
......++
........++
writing new private key to 'widgets.key'
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) []:AU
State or Province Name (full name) []:QLD
Locality Name (eg, city) []:Brisbane
Organization Name (eg, company) []:Internet Widgets
Organizational Unit Name (eg, section) []:
Common Name (eg, fully qualified host name) []:
Email Address []:
Octopuss-MBP-2:Development matthewcasperson$ ls
widgets.crt widgets.key
然后,我们为“Acme”公司创建第二个自签名证书。
$ openssl req -x509 -newkey rsa:4096 -keyout acme.key -out acme.crt -days 365
Generating a 4096 bit RSA private key
..............................++
.....................................................................++
writing new private key to 'acme.key'
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) []:Au
State or Province Name (full name) []:QLD
Locality Name (eg, city) []:Brisbane
Organization Name (eg, company) []:Acme
Organizational Unit Name (eg, section) []:
Common Name (eg, fully qualified host name) []:
Email Address []:
Octopuss-MBP-2:Development matthewcasperson$ ls
acme.crt acme.key widgets.crt widgets.key
将文件acme.crt
、acme.key
、widgets.crt
和widgets.key
复制到 Tomcat 9 conf
目录中。
配置<Connector>
在conf/server.xml
文件中,我们将添加一个新的<Connector>
元素来引用这些证书。
<Connector SSLEnabled="true" defaultSSLHostConfigName="acme.com" port="62000" protocol="org.apache.coyote.http11.Http11AprProtocol">
<SSLHostConfig hostName="acme.com">
<Certificate certificateFile="${catalina.base}/conf/acme.crt" certificateKeyFile="${catalina.base}/conf/acme.key" certificateKeyPassword="Password01!" type="RSA"/>
</SSLHostConfig>
<SSLHostConfig hostName="widgets.com">
<Certificate certificateFile="${catalina.base}/conf/widgets.crt" certificateKeyFile="${catalina.base}/conf/widgets.key" certificateKeyPassword="Password01!" type="RSA"/>
</SSLHostConfig>
</Connector>
这个配置块有几个重要的方面,所以我们将逐一介绍。
defaultSSLHostConfigName="acme.com"
属性已经将<SSLHostConfig hostName="acme.com">
定义为默认值。这意味着,当请求来自不是acme.com
或widgets.com
的主机时,将使用acme.com
证书生成响应。您必须至少配置一台默认主机。
protocol="org.apache.coyote.http11.Http11AprProtocol"
属性将 Tomcat 配置为使用 Apache Portable Runtime (APR ),这意味着在生成 HTTPS 响应时将使用 openssl 引擎。通常,使用 openssl 会比使用原生 Java 协议获得更好的性能。Tomcat 文档有更多关于可用协议的细节。
然后,我们有了每个主机名的证书配置。这是acme.com
主机名的配置。
<SSLHostConfig hostName="acme.com">
<Certificate certificateFile="${catalina.base}/conf/acme.crt" certificateKeyFile="${catalina.base}/conf/acme.key" certificateKeyPassword="Password01!" type="RSA"/>
</SSLHostConfig>
certificateFile="${catalina.base}/conf/acme.crt"
和certificateKeyFile="${catalina.base}/conf/acme.key"
属性定义了证书和私钥相对于 CATALINA_BASE 目录的位置。 Tomcat 文档有关于 CATALINA_BASE 引用的更多细节:
CATALINA_HOME 环境变量应该设置为 Tomcat 的“二进制”发行版的根目录位置。
CATALINA_BASE 环境变量指定 Tomcat 的“活动配置”的根目录的位置。它是可选的。默认等于 CATALINA_HOME。
测试连接
由于我们实际上并不拥有acme.com
或widgets.com
域名,我们将编辑hosts
文件以将这些地址解析为localhost
。在 Mac 和 Linux 操作系统上,这个文件位于/etc/hosts
下。
将下面几行添加到hosts
文件中会将这些域指向 localhost。我们还将抛出somethingelse.com
主机名来查看一个未映射的主机返回哪个证书。
127.0.0.1 acme.com
127.0.0.1 widgets.com
127.0.0.1 somethingelse.com
我们现在可以打开链接https://widgets.com:62000。在 Firefox 中,我们可以看到这个请求有以下证书细节。注意显示Internet Widgets
的Verified by
字段。
然后打开https://acme.com:62000。Verified by
字段现在显示Acme
。
现在打开 https://somethingelse.com:62000。Verified by
字段仍然显示Acme
,因为该证书是默认的,用于任何没有定义特定映射的主机。
结论
因此我们可以看到,单个端口上的单个 Tomcat 实例可以根据被请求的主机使用多个不同的证书进行响应。这就是 SNI 给 web 服务器带来的好处。
如果您对 Java 应用程序的自动化部署感兴趣,下载 Octopus Deploy 的试用版,并查看我们的文档。
源代码控制你的 Azure Bicep 文件
原文:https://octopus.com/blog/source-control-azure-bicep-files
最近,我写了关于使用 Azure Bicep 文件的,使用 Octopus run book部署你的 Azure 基础设施。
在这篇文章中,我解释了如何使用 GitHub 对 Azure Bicep 文件进行源代码控制,然后使用 Octopus 部署它们。
为什么是源代码管理?
源代码控制提供了单一的事实来源,无论是对您的应用程序代码还是您的基础设施代码(IaC)。
源代码管理还允许您与处理相同代码库的其他人协作,并合并他们的更改。它还可以帮助跟踪每个更改,因此如果有人犯了错误,您可以将时钟倒回到工作版本。
将你的二头肌文件从 GitHub 下载到 Octopus
将 Bicep 文件存储在 GitHub 中,您可以使用 GitHub 操作将它们移动到 Octopus Deploy 实例,以自动化 Azure 资源的部署。
GitHub Actions 于 2019 年推出,已成为 DevOps 专业人士和开源贡献者的热门工具。
在本文中,您将创建一个 GitHub Actions 工作流来将 Bicep 文件打包成一个 ZIP 文件。然后,将 Bicep 文件推送到 Octopus 实例,准备进行部署。
八达通连接
首先,您需要设置 GitHub Actions secrets,它将保存到 Octopus 实例的连接信息。
您需要:
- Octopus 部署实例的 URL
- API 密钥
- 您要将文件推送到的空间的名称
在设置下创建 3 个秘密、秘密:
OCTOPUSSERVERAPIKEY
OCTOPUSSERVERURL
OCTOPUSSERVER_SPACE
创建 GitHub 操作工作流
现在您需要创建 GitHub 操作工作流。
您的 GitHub Actions 工作流需要位于一个名为的文件夹下。github 在你的库中,它必须使用 YAML。
从描述工作流文件的作用开始,以便于参考和他人理解。然后,定义工作流的名称以及希望它运行的时间:
# This workflow takes the Bicep files within the Bicep folder and zips them together. Then pushes/uploads them to the Octopus Instance specified.
# In the repo's secrets are the information relating to the Octopus Instance URL, API key and Space.
name: OctoPetShopBicepBuild
on:
push:
branches:
- main
每次主分支上有推时,您的工作流都会运行。
接下来,您需要定义工作流的步骤:
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "Bicep Build"
BicepBuild:
# The type of runner that the job will run on
runs-on: windows-latest
steps:
# This first step takes the code within the Repo and pulls it into the workspace
- uses: actions/checkout@v2
定义作业应该运行的工作程序或运行程序的类型。在这个例子中,我定义了最新的 Windows runner。
工作流程的第一步是将存储库中的文件放入工作区。我使用了一个叫做 checkout 的市场行为来完成这个任务。
# We install the latest version of Octopus CLI
- uses: OctopusDeploy/install-octopus-cli-action@v1.1.8
接下来,将 Octopus CLI 安装到您的 runner 上,以便您可以使用该 CLI 中的命令。默认情况下,跑步者没有安装命令。
# We take the files inside the Bicep folder and zip them together
- name: Zip Bicep files
run: octo pack --id="OctoBicepFiles" --format="zip" --version=${{ github.run_number }} --basePath=${{ github.workspace }}\Bicep\ --outFolder=${{ github.workspace }}\output
下一步是将文件放入存储库中的 Bicep 文件夹,并放入一个 ZIP 文件中。
# We take the zip file we created and push them to the Octopus Deploy server instance
- name: Push Bicep files
run: octo push --package="${{ github.workspace }}\output\OctoBicepFiles.${{ github.run_number }}.zip" --server="${{ secrets.OCTOPUSSERVERURL }}" --apiKey="${{ secrets.OCTOPUSSERVERAPIKEY }}" --space="${{ secrets.OCTOPUSSERVER_SPACE }}"
此工作流的最后一步是推送步骤。这一步获取您创建的 ZIP 文件,并将其推送到您的 Octopus 实例中。您使用之前创建的秘密来连接到您的 Octopus 实例。
你可以在 GitHub 上获得一份完整工作流程的副本。
结论
现在,您有了一个存储 Bicep 模块和基础设施部署代码的地方,以及一个将这些文件推送到 Octopus 实例以备部署的自动化过程。
如果您对此流程有任何问题或反馈,请在下面留下您的评论,我们非常希望收到您的回复。
阅读我们的 Runbooks 系列的其余部分。
愉快的部署!
空间-一种新的方式来组织你的八达通服务器-八达通部署
Octopus Deploy 帮助团队自动部署他们的应用程序和服务。随着时间的推移,随着他们不断添加项目、环境和机器,找到他们正在做的事情会变得很困难。如果这听起来很熟悉,那么您可能已经滚动了很长时间来查找您想要部署的项目,水平滚动来查找部署到环境中的最新版本,或者查看包含数百个项目的下拉列表。
这可能会令人沮丧,我们非常高兴地分享我们一直在致力于名为 Spaces 的解决方案。空间是一种组织你的八达通服务器的新方法。将您团队的项目、环境、机器、租户、图书馆资源等归入一个专为您和您的团队准备的“空间”将变得更加容易。
专为团队打造
共享空间是为团队打造的一项功能。
大多数开发人员在团队中构建和维护一个或多个项目以及相关的基础设施。这包括环境、机器、Azure 或 AWS 账户、step 模板、变量集等等。然而,公司是不同的。有些公司按部门组织团队。有些人按功能组织它们。空间使团队能够根据需要组织他们的项目和基础设施,并且他们可以是多个空间的成员。我们的 Spaces switcher 使空间之间的切换变得很容易,所以你不会看到数十或数百个不相关的项目和其他 Octopus 资源,你只会看到该空间的资源。
空间所有者
在与客户交谈后,很明显,Spaces 的关键驱动因素之一是能够委派一系列责任。通过这种方式,Octopus 管理员可以完全控制一个空间,如果他们愿意,可以完全放手。让管理员专注于系统问题,如添加新用户、管理任何系统团队和安装配置。对于希望直接参与团队空间的管理员,他们可以将自己添加为具有空间访问权限的团队成员。
坚硬的墙壁
空间 A 中的项目不应该能够从空间 B 部署到机器上
空间是关于隔离而不是分享。我们希望确保首先获得安全和隔离。很容易想象 Octopus 中的任何内容被共享的场景。当我们开始建造空间并进入实施细节时,我们清楚地意识到,如果没有真正的隔离,你的章鱼的状态会变得非常复杂。
我们在沙地上为空间划了一条线,将您的项目部署到环境和租户中的目标的所有基本要素都在一个空间内。
空间,当你需要的时候
共享空间是完全自愿加入的。我们已经做了大量的工作和测试,以确保该功能尽可能向后兼容。我们已经引入了“缺省空间”的概念,它允许我们像现在一样支持大多数 API。空间会在你需要的时候出现,转移到空间可以是一个渐进的过渡。
你今天使用八达通的方式在很大程度上不会改变,我们会有具体的细节发布。总的来说,Octopus 将更加一致地执行权限,并且围绕管理安装的一些 API 端点已经改变,例如团队/权限。
批准
在与我们的现有客户交谈后,他们对业务单位和各种团队以及基础设施的分离的想法和愿望推动了空间的设计。
每个空间中存在的代表相同物理硬件的部署目标都将计入您的机器限制。
自托管
- 使用不受限制的社区(免费)、专业、团队或企业许可的客户只能使用一个空间。你可以升级到一个新的许可证,将你的八达通分成多个空间。
- 使用不受限制的高可用性许可证的客户可以获得无限的空间。
- 拥有标准许可证的客户将获得三个车位。您可以升级到数据中心以获得更多空间。
- 拥有标准无限或数据中心许可证的客户将获得无限空间。
章鱼云
- 新的和现有的标准版客户将获得无限的空间。
- 旧入门版上的现有客户将获得 1 个空间。
结论
我们一直在努力工作空间,我们很高兴它即将完成。我们计划在 2019 年 1 月发货。请继续关注即将发布的另一篇文章,了解更多细节。
与此同时,如果您还没有看过我们最近的网络研讨会,我们在会上讨论了员工旁边的空间。
Spring Boot 即服务-部署八达通
Spring Boot 支持构建 JAR 文件,这些文件可以像普通的 bash 脚本一样在 Unix 环境中执行。这些文件被称为完全可执行的 JARs ,它们使得将 Spring Boot 应用程序部署为 Linux 服务变得非常方便。在这篇博文中,我们将看看如何将一个完全可执行的 JAR 文件部署到一个 EC2 实例中,该实例是使用 Octopus 通过 CloudFormation 模板创建的。
AWS 帐户
Octopus CloudFormation 步骤通过 AWS 帐户向 AWS 进行身份验证。这些账户由基础设施➜账户➜亚马逊网络服务账户管理。你可以通过我们的文档找到更多关于创建 AWS 账户的信息,记住账户需要有一些通用权限才能有效地用于部署 CloudFormation 模板。
SSH 帐户
我们还需要配置一个帐户,用于通过 SSH 连接到 Linux EC2 实例。这些账户由基础设施➜账户➜ SSH 密钥对管理。在这里,我们将创建一个 SSH 帐户,用户名为ec2-user
(这是 Amazon Linux 的默认用户名)和 PEM 文件,您需要在 AWS 中创建该文件并将其分配给 EC2 映像。你可以在他们的文档中找到更多关于创建 AWS 密钥对的信息。
机器政策
我们需要配置的最后一个全局 Octopus 设置是机器策略,可在基础设施➜机器策略下访问。
与轮询触角不同,SSH 目标必须有准确的 IP 地址或主机名才能参与 Octopus 部署。然而,将由 CloudFormation 模板创建的 EC2 实例没有固定的 IP 地址,并且当 EC2 实例停止并再次启动时,它们所具有的 IP 地址将会改变。这意味着我们需要做两件事来确保我们的 EC2 实例在 Octopus 中正确配置:
- 每次 EC2 实例启动时,将 EC2 实例添加到 Octopus 中(如果它尚未注册)。
- 让 Octopus 清理任何未通过健康检查的部署目标。
我们将在后面的章节中用 CloudFormation 模板中的一些脚本来处理第一步。通过编辑默认机器策略中的Clean Up Unavailable Deployment Targets
部分来启用Automatically delete unavailable machines
来配置步骤 2。
构建完全可执行的 JAR 文件
通常情况下,JAR 文件是用类似java -jar application.jar
的命令运行的。然而,Spring 使得构建完全可执行的 jar 成为可能,这些 jar 可以在 Unix 之类的系统上运行,比如 T3。
在这篇博文中,我们将使用一个简单的百里香叶 Spring Boot 应用程序,它可以从 GitHub 获得。在 Maven pom.xml
里面我们有配置:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<executable>true</executable>
</configuration>
</plugin>
设置<executable>true</executable>
指示 Spring Boot Maven 插件构建一个完全可执行的 JAR。
如果我们查看 JAR 文件的实际内容,我们可以看到它是从一个 Bash 脚本开始的,这个脚本包含了 Init 脚本的注释约定。
$ head -n 21 target/springboot.0.0.1-SNAPSHOT.jar
#!/bin/bash
#
# . ____ _ __ _ _
# /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
# ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
# \\/ ___)| |_)| | | | | || (_| | ) ) ) )
# ' |____| .__|_| |_|_| |_\__, | / / / /
# =========|_|==============|___/=/_/_/_/
# :: Spring Boot Startup Script ::
#
### BEGIN INIT INFO
# Provides: springboot
# Required-Start: $remote_fs $syslog $network
# Required-Stop: $remote_fs $syslog $network
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: springboot
# Description: Demo project for Spring Boot
# chkconfig: 2345 99 01
### END INIT INFO
这意味着我们可以像执行一个常规的 init 脚本一样执行这个文件。
使用 CloudFormation 构建 EC2 实例
我们将利用 Octopus 中的 CloudFormation 步骤来构建一个 EC2 实例,该实例将托管我们的 Spring Boot 服务。这个模板将在us-east-1
区域运行,并将从标准的 Amazon Linux AMI 创建一个 EC2 实例,在我们的例子中是ami-97785bed
。
这是完整的云形成模板。
AWSTemplateFormatVersion: 2010-09-09
Resources:
InstanceSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: SSH and Web Ports
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: '22'
ToPort: '22'
CidrIp: '0.0.0.0/0'
- IpProtocol: tcp
FromPort: '8080'
ToPort: '8080'
CidrIp: '0.0.0.0/0'
Linux:
Type: 'AWS::EC2::Instance'
Properties:
ImageId: ami-97785bed
InstanceType: m3.medium
KeyName: DukeLegion
SecurityGroups:
- Ref: InstanceSecurityGroup
Tags:
-
Key: Application
Value: Spring Boot
-
Key: Domain
Value: None
-
Key: Environment
Value: Test
-
Key: LifeTime
Value: Transient
-
Key: Name
Value: Spring Boot
-
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
sudo yum install -y libunwind libicu jq java-1.8.0-openjdk
sudo update-alternatives --set java /usr/lib/jvm/jre-1.8.0-openjdk.x86_64/bin/java
if [ ! -d /opt/springboot ]; then
sudo mkdir /opt/springboot
sudo chown ec2-user:ec2-user /opt/springboot
fi
role="SpringBoot"
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:
- Linux
- PublicIp
Description: Server's PublicIp Address
我们首先定义将分配给 EC2 实例的安全组。这个安全组为 SSH 开放端口 22,为 Spring Boot 应用程序开放端口 8080。
InstanceSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: SSH and Web Ports
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: '22'
ToPort: '22'
CidrIp: '0.0.0.0/0'
- IpProtocol: tcp
FromPort: '8080'
ToPort: '8080'
CidrIp: '0.0.0.0/0'
接下来是 EC2 实例。这个配置定义了 AMI 映像 ID、实例类型、要使用的 SSH 密钥对以及应用于实例的一些标记。
在 Octopus 内部,我们有一堆需要在任何 EC2 实例上设置的标签。至少您需要设置Name
标记,因为这是出现在 AWS 控制台中的名称。
注意,在 Octopus 中使用变量替换设置了OwnerContact
标记值。我们将在后面的步骤中定义这个变量。
Linux:
Type: 'AWS::EC2::Instance'
Properties:
ImageId: ami-97785bed
InstanceType: m3.medium
KeyName: DukeLegion
SecurityGroups:
- Ref: InstanceSecurityGroup
Tags:
-
Key: Application
Value: Spring Boot
-
Key: Domain
Value: None
-
Key: Environment
Value: Test
-
Key: LifeTime
Value: Transient
-
Key: Name
Value: Spring Boot
-
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 所需的包。在这个例子中,我选择了支持后者,所以我们使用yum
来安装先决条件中列出的依赖项。Linux 上的 NET Core。
虽然 Amazon Linux 没有得到微软官方支持运行 DotNET 核心应用,但我们在遵循 DotNET 核心文档时,可以将 Amazon Linux 与 CentOS 同等对待。然而,虽然这适用于本博客,但它是一个不受支持的配置。
我们还安装了 Java 8,并将其设置为现有 Java 7 安装的默认设置。
#cloud-boothook
标记被cloud-init
服务用来识别应该在每次引导时运行的脚本。
在生产环境中,像 Java 和 DotNET 核心依赖项这样的包将被放入基本 AMI 映像中,而不是在实例启动时安装。
UserData:
Fn::Base64: |
#cloud-boothook
#!/bin/bash
sudo yum install -y libunwind libicu jq java-1.8.0-openjdk
sudo update-alternatives --set java /usr/lib/jvm/jre-1.8.0-openjdk.x86_64/bin/java
我们将在/opt/springboot
下安装 JAR 文件,因此我们需要确保该目录存在,并且ec2-user
帐户可以修改其内容。
if [ ! -d /opt/springboot ]; then
sudo mkdir /opt/springboot
sudo chown ec2-user:ec2-user /opt/springboot
fi
最后,我们需要这个 EC2 实例向 Octopus 服务器注册自己,如果它还没有这样做的话。脚本的这一部分查询 Octopus API,以确定 EC2 实例的当前主机名是否存在一个部署目标,如果没有找到部署目标,就会添加它。
该脚本中的许多变量是使用变量替换提供的。这些将在下一节中定义。
role="SpringBoot"
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 脚本有许多使用变量替换定义的变量。这些变量在我们的 Octopus 项目的变量➜项目部分中定义。
通过获取 URL https://octopusserver/app#/infrastructure/accounts/sshkeypair-ec2user
的最后一个元素找到了sshkeypair-ec2user
的AccountID
变量,这是从基础设施➜帐户➜ SSH 密钥对打开 ec2 用户 SSH 帐户时显示的 URL。
注意,AWS Account
变量被设置为之前创建的AWS Account
。该变量由 Octopus 步骤使用,而不是由 CloudFormation 模板直接使用。
你可以从文档中获得更多关于创建 Octopus API 密匙的信息。
开始没有目标的部署
因为我们正在创建我们将作为 Octopus 项目的一部分部署到的基础设施,所以我们需要配置一些设置,以允许 Octopus 在没有任何预先存在的有效目标的情况下开始部署。这是在Deployment Targets
下的项目设置中完成的。将值设置为Allow deployments to be created when there are no deployment targets
意味着即使没有可用的目标,项目也可以开始部署。
云形成步骤
现在是时候开始定义项目步骤了。我们将从部署 CloudFormation 模板开始,这是通过Deploy an AWS CloudFormation template
步骤完成的。
下面是填充步骤的屏幕截图。
健康检查步骤
一旦部署了 CloudFormation 模板,它所创建的 EC2 实例将启动并向 Octopus 注册自己作为部署目标。我们现在需要将这个新目标添加到项目将要部署到的目标列表中。这是使用Health Check
步骤完成的。
下面是填充步骤的屏幕截图。
传输包步骤
既然我们新创建或更新的 EC2 实例是我们的部署目标列表的一部分,我们可以将我们的 Spring Boot JAR 文件转移到它。这是使用Transfer a package
步骤完成的。
我们使用Transfer a package
步骤代替Deploy Java Archive
步骤,因为后者将提取并重新打包 JAR 文件。完成提取和重新打包是为了允许 JAR 中的文件修改其内容(如果启用了变量替换)。然而,Spring Boot 完全可执行的 JAR 文件不能被jar
工具提取。Spring Boot 文档中有这样的警告:
目前,一些工具不接受这种格式,所以您可能并不总是能够使用这种技术。例如,jar -xf 可能无法提取完全可执行的 jar 或 war。
另一方面,Transfer a package
步骤只复制文件,并不试图修改它,这对于我们的用例来说是完美的。
下面是填充步骤的屏幕截图。
安装服务
有了系统上的 JAR 文件,我们现在可以将它作为 Linux 服务安装。这是通过使 JAR 文件可执行,在/etc/init.d
下链接 JAR 文件,并配置服务在 start 上运行chkconfig
来完成的。
我们将使用Run a Script
步骤运行这个脚本。
这是完整的脚本。
filePath=$(get_octopusvariable "Octopus.Action[Transfer JAR File].Output.Package.FilePath")
echo Linking $filePath to /etc/init.d/springboot
sudo chmod +x $filePath
sudo rm /etc/init.d/springboot
sudo ln -s $filePath /etc/init.d/springboot
sudo chkconfig springboot on
sudo service springboot restart
下面是填充步骤的屏幕截图。
显示摘要信息
为了方便运行此部署的人员,我们将显示一些有用的摘要信息。这是通过第二个Run a Script
步骤完成的。
部署 CloudFormation 模板时,Octopus 会捕获任何输出变量,并将其用于后续步骤。我们利用这一点来构建一个基于 EC2 实例的公共 IP 地址的 URL。
Write-Host "Open application at http://$($OctopusParameters["Octopus.Action[Linux CloudFormation].Output.AwsOutputs[PublicIp]"]):8080"
下面是填充步骤的屏幕截图。
部署项目
下面是这个项目的一个部署结果的截图。
请注意 CloudFormation 模板部署的输出中的这几行:
Saving variable "Octopus.Action[Linux CloudFormation].Output.AwsOutputs[StackId]"
Saving variable "Octopus.Action[Linux CloudFormation].Output.AwsOutputs[PublicIp]"
这些日志消息提供了一种简单的方法来获取作为 CloudFormation 部署的结果而创建的任何输出变量的完整变量名。
还要注意运行状况检查步骤的输出。在这个部署中,我通过在 UserData 脚本中添加一个注释,对 CloudFormation 模板进行了一些调整。虽然这种变化不影响 EC2 实例的部署方式,但 CloudFormation 将其视为对现有堆栈的更改,因此关闭并重新启动 EC2 实例。这又给了 EC2 实例一个新的公共 IP,这意味着 EC2 实例将在启动时向 Octopus 注册自己。然后,运行状况检查步骤检查旧的部署目标和新的部署目标,确定旧的目标不再有效并将其删除,并成功完成对新目标的运行状况检查,并将其包括在用于剩余部署的目标列表中。
打开 Web 应用程序
最后一个脚本步骤的输出生成了一个 URLhttp://184.73.104.221:8080
。打开它会显示 Spring Boot 应用程序。
这个 URL 实际上对您不起作用,因为这个演示 EC2 实例已经关闭。为您生成的 URL 将具有不同的 IP 地址。
【T2
验证服务
我们可以通过连接到 EC2 实例并运行service
命令来验证 Spring Boot 应用程序是否作为 Linux 服务运行。
$ sudo service springboot status
Running [2716]
该服务可以像任何其他服务一样启动、停止和重新启动。
$ sudo service springboot restart
Stopped [2716]
Started [4123]
$ sudo service springboot stop
Stopped [4123]
$ sudo service springboot start
Started [4235]
结论
完全可执行的 JAR 文件允许将 Spring Boot 应用程序部署为常规的 Linux 服务,使它们易于部署和管理。如果您对自动化部署 Java 应用程序或创建云基础设施感兴趣,下载 Octopus Deploy 的试用版,并查看我们的文档。
将 Spring Boot 应用程序部署为 Windows 服务- Octopus Deploy
一位客户最近询问是否可以使用 Octopus Deploy 将 Spring Boot 应用程序部署为 Windows 服务。Spring 文档确实简要提到了一种将 Spring Boot 应用程序作为 Windows 服务运行的方法,但是很多细节还是留给读者去解决。所以在这篇博文中,我将向你展示如何快速运行一个标准的 Spring Boot 应用程序服务。
Java 和 Windows 服务
要将 Spring Boot UberJAR 作为 Windows 服务运行,我们需要两件东西。
第一个是 Windows 可以作为服务运行的可执行文件。这是由 winsw 项目提供的。Winsw 并没有特别绑定到 Java,但是它可以用来执行java.exe
,这是我们启动 Spring Boot JAR 文件所需要的。
第二件事是以某种方式优雅地关闭在后台运行的 Java 应用程序。为此我们有一个简单的项目叫做 Spring Boot 停止器(基于从 Spring 文档链接的 Spring Boot 守护进程项目)。该应用程序将通过 JMX 与 Spring Boot 应用程序通信,并指示它关闭。
把所有的放在一起
这个演示项目是一个相当普通的 Spring Boot REST MVC 应用程序,使用 Spring Initializr 网站生成。该项目没有以特殊的方式配置,当构建时将产生一个股票 Spring Boot JAR 文件。
在项目的dist
文件夹中,你会发现一些文件。
XML 配置文件是最神奇的地方。在里面,您会发现以下设置:
<executable>java</executable>
<startargument>-Dspring.application.admin.enabled=true</startargument>
<startargument>-Dcom.sun.management.jmxremote.port=50201</startargument>
<startargument>-Dcom.sun.management.jmxremote.authenticate=false</startargument>
<startargument>-Dcom.sun.management.jmxremote.ssl=false</startargument>
<startargument>-jar</startargument>
<startargument>SpringBootWindowsService.jar</startargument>
这些设置定义了启动 Windows 服务时 winsw 可执行文件将运行的内容。在这种情况下,我们用一些配置 JMX 的附加系统属性来启动 Spring Boot UberJAR SpringBootWindowsService.jar
。这些系统属性启用 Spring Boot 管理功能,将 JMX 端口设置为50201
,禁用 SSL 并禁用 JMX 认证。
由于 JMX 端口上没有身份验证和 SSL,作为一项安全措施,该端口应该被防火墙阻止。
然后,我们有一些额外的设置来定义当 Windows 服务停止时 winsw 可执行文件将运行什么。
<stopexecutable>java</stopexecutable>
<stopargument>-jar</stopargument>
<stopargument>SpringBootStopper.jar</stopargument>
<stopargument>50201</stopargument>
这里,我们运行SpringBootStopper.jar
应用程序,传递启动 Spring Boot 应用程序时使用的同一个 JMX 端口。这些设置允许SpringBootStopper.jar
连接到正在运行的 Spring Boot 实例并正常关闭它。
打包服务
要为 Octopus Deploy 打包这些文件,请使用 CLI 工具。
octo pack --format=zip --id=SpringBootWindowsService --version=1.0.0
这将产生一个名为SpringBootWindowsService.1.0.0.zip
的文件,然后您可以使用以下命令将该文件推送到 Octopus 服务器:
octo push --server=http://my.octopus.server --apiKey=API-xxxxxxxxxxxxx --package=SpringBootWindowsService.1.0.0.zip
部署服务
此时,您可以将该包部署为执行SpringBoot.exe
可执行文件的传统 Windows 服务。
部署后,该服务将像任何其他 Windows 服务一样出现。
结论
启动服务,并打开浏览器到 http://localhost:8080/greeting。您的 Spring Boot 应用程序现在作为 Windows 服务运行!
了解更多信息
跨环境的 Spring 配置策略——Octopus 部署
原文:https://octopus.com/blog/spring-environment-configuration
Spring 对复杂配置有丰富的支持。对概要文件和外部化配置的内置支持为您提供了很好的自由度来构建特定于环境的配置,这些配置可以捆绑到一个独立的应用程序档案中。
但是大多数开发人员仍将面临一个基本问题,即如何在本地开发应用程序,然后将该应用程序部署到一个配置值(如数据库连接字符串(尤其是它们可能嵌入的密码))必须保密的环境中。
一种解决方案是以环境变量的形式将特定于环境的配置推入环境中。这是一个非常合理的选择,但是当一个应用服务器托管多个应用程序时,必须特别注意环境变量名,以确保特定于应用程序的配置不会冲突。
另一种解决方案是在部署应用程序时对其进行修改,以注入应用程序和环境特定的配置。例如,您可以将localhost
上数据库的连接字符串保存到配置文件中。这种配置可以安全地签入源代码控制,为开发人员提供了在本地克隆、构建和运行的能力。但是在部署期间,特定环境的连接字符串会覆盖缺省值,从而导致特定于环境的部署,其凭证不会被窥探。
Octopus 很早就支持这种配置文件修改。NET 配置文件和 JSON 文件。这是通过理解。NET XML 文件,以便进行适当的修改,或者允许专门命名的变量将值推入 JSON 文件。
不过,Java 和 Spring 应用程序通常在 YAML、XML 或属性文件中定义配置。在 Octopus 2020.4.0 中,增加了将变量推入 XML、YAML 和属性文件的支持,使 Java 和 Spring 开发人员能够从通用应用程序档案创建特定于环境的部署。
在这篇博文中,我们将探讨一些将环境无关的 Spring 应用程序部署到特定环境的常见策略。
修改 Spring application.yml 文件
让我们从一个简单的例子开始,在这个例子中,我们改变了活动概要文件的名称以匹配环境。下面显示的代码来自随机引用示例应用程序。
在下面的application.yml
文件中,我们将spring.profiles.active
属性设置为local
,以表明我们正在本地开发环境中工作:
# The configuration defines the settings used in a local development environment
# to give developers the ability to clone, develop and build without any
# additional configuration.
server:
port : 5555
spring:
profiles:
active: local
h2:
console:
enabled: true
jpa:
database-platform: org.hibernate.dialect.H2Dialect
datasource:
url: jdbc:h2:mem:testdb
dbcp2:
driver-class-name: org.h2.Driver
flyway:
locations: classpath:db/migration/{vendor}
这个配置文件中没有秘密,直接提交到源代码控制是安全的,开发人员可以删除任何额外的本地配置来开始处理代码。
通过使用 SpEL 表达式注入一个值,可以将活动概要文件公开为 Spring 对象上的一个属性:
@Value("${spring.profiles.active:unknown}")
private String activeProfile;
最终,该值显示在应用程序公开的网页中:
假设我们想要用应用程序将被部署的环境的名称替换spring.profiles.active
属性值。我们首先在步骤上启用结构化配置变量特性。
这个特性以前被称为 JSON 配置变量,但是在 2020.4.0 中,我们扩展了这个特性以支持许多新的配置文件:
然后我们配置这个步骤,用 glob **/application.yml
将值注入到application.yml
文件中:
最后,我们用值#{Octopus.Environment.Name}
定义一个名为spring:profiles:active
的变量:
在部署期间,Octopus 将提取应用程序档案(通常是一个 JAR 或 WAR 文件),将名为spring:profiles:active
的变量值注入到 YAML 文件中,重新打包应用程序,并将生成的档案上传到应用服务器或保存在磁盘上,这取决于所使用的特定步骤。我们可以看到应用程序现在报告它正在 Dev 环境中运行:
修改 web.xml 文件
Spring 定义配置文件的能力非常灵活,但是传统的 Java XML 配置文件在引用外部数据(如环境变量)或采用不同文件名的能力方面非常有限。比如传统 Java web 应用中用来定义应用设置的web.xml
文件,只能叫做web.xml
,不包含任何表达式语言来加载外部值或者有条件地定义设置。
在过去,这使得web.xml
文件成为定义环境特定配置的一种特别困难的方式,但是有了向 XML 文件注入值的新能力,Octopus 现在可以在部署过程中用环境特定的值修改这个文件。
下面是我们的示例应用程序中的web.xml
文件:
<web-app
version="3.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<distributable />
<display-name>Random Quotes</display-name>
</web-app>
为了修改这个文件,我们使用 glob **/web.xml
将它添加到结构化配置变量特性中,然后定义一个名为//*:display-name
和值为Random Quotes #{Octopus.Environment.Name}
的变量,这将把 Octopus 环境嵌入到应用程序的显示名称中。我们可以在 Tomcat 管理器中看到这个应用程序名:
//*:display-name
的 XPath 使用版本 2 风格的名称空间选择。这比通常看起来像//*[local-name()='display-name']
的版本 1 选择器要方便得多,但是两种样式都可以使用。
复杂的配置更改
到目前为止,我们已经以一对一的方式将变量注入到配置文件的现有值中。这对于简单的配置更改来说很好,但是在某些情况下,特定于环境的配置会导致一些重大的更改,全新的字段和旧的设置会被删除。例如,下面是一个application.yml
文件,它配置了一个外部 Postgres 数据库,而不是在本地开发时使用的内存中的 H2 数据库:
server:
port : 5555
spring:
profiles:
active: Dev
jpa:
database-platform: org.hibernate.dialect.PostgreSQLDialect
properties:
hibernate:
default_schema: randomquotes
datasource:
url: jdbc:postgresql://databaseserver:5432/postgres
username: postgres
password: docker
flyway:
locations: classpath:db/migration/{vendor}
所有 H2 配置都已删除,JPA 配置包括新的嵌套属性。那么我们如何创建这个新的配置文件呢?
我们可以通过用新的 YAML 对象替换jpa
和datasource
属性的主体来获得足够的接近。
下面是名为spring:datasource
的变量的值:
url: jdbc:postgresql://databaseserver:5432/postgres
username: postgres
password: docker
这里是变量spring:jpa
的值:
database-platform: org.hibernate.dialect.PostgreSQLDialect
properties:
hibernate:
default_schema: randomquotes
请注意,变量值是独立的 YAML 文档。然后,在部署过程中,这些文档被用来替换spring.datasource
和spring.jpa
属性的子属性。最终结果是一个看起来像这样的application.yml
文件:
# The configuration defines the settings used in a local development environment
# to give developers the ability to clone, develop and build without any
# additional configuration.
server:
port: 5555
spring:
profiles:
active: Dev
h2:
console:
enabled: true
jpa:
database-platform: org.hibernate.dialect.PostgreSQLDialect
properties:
hibernate:
default_schema: randomquotes
datasource:
url: jdbc:postgresql://databaseserver:5432/postgres
username: postgres
password: docker
flyway:
locations: classpath:db/migration/{vendor}
这可能会得到我们需要的结果,但是h2
设置仍然被定义。我们当然可以替换spring
属性的全部内容来删除不需要的属性,但是对于更彻底的转换,更好的解决方案可能是在部署期间用第二个文件来替换application.yml
文件。
配置文件和脚本挂钩
鉴于我们的环境特定的配置需要混合添加和删除内容,我们将创建第二个模板文件来表示这个名为postgres-application.yml
的新结构。该文件包含我们期望的结构,但它包含任何敏感内容的占位符值:
server:
port : 5555
spring:
profiles:
active: Dev
jpa:
database-platform: org.hibernate.dialect.PostgreSQLDialect
properties:
hibernate:
default_schema: randomquotes
datasource:
url: jdbc:postgresql://replaceme:5432/postgres
username: replaceme
password: replaceme
flyway:
locations: classpath:db/migration/{vendor}
我们可以使用一个定制的部署脚本将该模板复制到原始的application.yml
文件上,该脚本作为一个特性启用:
然后,cp WEB-INF\classes\postgres-application.yml WEB-INF\classes\application.yml
的预部署脚本复制特定于环境的模板,覆盖默认模板:
然后像往常一样将变量注入到application.yml
中,结果是环境特定的配置文件,其中只有我们想要的值。
模板
到目前为止,我们一直在将变量放入配置文件中。这允许我们拥有一个用于本地开发的配置文件,然后用任何特定于环境的变量替换它。
现在我们已经了解了如何在部署时使用定制部署脚本来替换配置文件,我们可以选择创建模板,利用 Octopus 变量替换将变量放入文件中。例如,我们可能有一个特定于环境的配置文件,如下所示:
server:
port : 5555
spring:
profiles:
active: Dev
jpa:
database-platform: org.hibernate.dialect.PostgreSQLDialect
properties:
hibernate:
default_schema: randomquotes
datasource:
url: jdbc:postgresql://#{DatabaseServer}:5432/postgres
username: '#{DatabaseUsername | YamlSingleQuoteEscape}'
password: "#{DatabasePassword | YamlDoubleQuoteEscape}"
flyway:
locations: classpath:db/migration/{vendor}
该模板期望在 Octopus 中定义DatabaseServer
、DatabaseUsername
和DatabasePassword
变量。使用新的过滤器 YamlSingleQuoteEscape
和YamlDoubleQuoteEscape
将这些值嵌入到 YAML 中,以确保它们在给定的上下文中被正确转义。
然后我们启用Substitute Variables in Templates
特性,允许变量被处理:
我们现在在模板中的替代变量部分匹配文件,而不是在结构化配置变量部分匹配文件:
结论
创建支持本地开发而无需特殊工具的环境不可知的应用程序包为开发人员提供了直接的克隆、构建、运行工作流,同时还允许将单个编译的应用程序部署在许多需要任何特定配置的环境中。在这篇文章中,我们看了一个将变量推入 XML 和 YAML 文件,并将变量拉入 YAML 模板的典型例子。我们还看到了如何使用定制部署脚本将本地开发配置文件替换为特定于环境的文件。
借助 Octopus 2020.4 将变量推入 XML、YAML 和属性文件的能力,创建与环境无关的 Java 包比以往任何时候都更容易。同时,新的八进制过滤器 YamlSingleQuoteEscape
、YamlDoubleQuoteEscape
、PropertiesKeyEscape
和PropertiesValueEscape
允许将变量放入模板中,处理所需的任何格式特定的转义。
我们希望这一新功能将通过使多种环境的部署变得流畅和轻松来增强您的 Java 部署。
愉快的部署!
我们如何使用 SQL Server 作为文档存储- Octopus Deploy
18 个月前,我在博客上写了我们如何决定在 Octopus 3.0 中从 RavenDB 转换到 SQL Server。那篇文章解释了我们遇到的问题以及我们决定转换的原因。我们随后发布了一篇关于我们计划如何使用 SQL Server 的帖子。Octopus 3.x 已经投入生产有一段时间了,所以在这篇文章中,我想提供一个更新,并详细介绍我们如何使用 SQL Server 作为文档存储。
目标
使用 Raven 时,我们发现文档数据库模型比关系模型更容易处理数据。尽管我们决定切换到 SQL Server,但我们希望保留许多我们在使用文档存储时已经习惯的方面。
我们心中有几个目标:
- 将整个聚合根加载/保存到单个表中
- 快速查询
- 普通的老酸,不需要担心最终的一致性
- 尽可能避免对连接的需求
典型表格结构
我们处理的每种类型的实体/聚合根(机器、环境、发布、任务)都有自己的表,遵循相似的模式:
- 一个 ID(详情如下)
- 我们查询的任何其他字段
- JSON
nvarchar(max)
专栏
例如,这里有一张Deployment
表。我们经常需要执行查询,如“哪些部署属于此版本”,或“此项目/环境的最新部署是什么”,因此表中有以下字段:
JSON 列包含文档的所有其他属性,包括任何嵌套对象。我们使用定制的 JSON.NET契约解析器来确保已经保存在表的其他列中的属性不会在 JSON 中重复。
关系
我们以不同的方式建立关系模型。对于我们不查询的关系,属性或数组只是在 JSON 中序列化。
当我们确实需要查询一个关系时,对于一对多的关系,如您所料,属性存储在一个列中:
对于多对多,我们做了一些不太好的事情:我们存储每个值之间带有|
字符的值:
这给了我们两个查询选项。对于我们期望只得到几十个、不超过一千个结果的表,我们可以简单地使用LIKE '%|' + @value + '|%'
查询它们。我们知道它会导致全表扫描,但是这些查询很少,表很小,对我们来说已经足够快了。
对于增长到数百万条记录的表(审计事件,其中每个事件引用许多相关文档),我们使用触发器。每次写入行时,我们都会分割值,并使用MERGE
语句来更新连接表:
ALTER TRIGGER [dbo].[EventInsertUpdateTrigger] ON [dbo].[Event] AFTER INSERT, UPDATE
AS
BEGIN
DELETE FROM [dbo].EventRelatedDocument where EventId in (SELECT Id from INSERTED);
INSERT INTO [dbo].EventRelatedDocument (RelatedDocumentId, EventId)
select Value, Id from INSERTED
cross apply [fnSplitReferenceCollectionAsTable](RelatedDocumentIds)
END
然后,我们可以对这个表执行连接查询,但是应用程序代码仍然可以将对象作为单个 JSON 文档加载/保存。
ID 生成
我们使用字符串和类似于高低算法的东西,而不是使用整数/标识字段或 GUIDs 作为 ID。格式总是<PluralTableName>-<Number>
。
这是从 RavenDB 借鉴来的一个想法,它意味着如果我们有一个像审计事件这样的文档,它与许多其他文档相关,我们可以这样做:
{
"Id": "Events-245",
"RelatedDocumentIds": [ "Project-17", "Environments-32", "Releases-741" ]
...
}
如果我们只是使用数字或 GUIDs,那么很难判断出17
是项目的 ID,还是环境的 ID。将文档名作为 ID 的前缀有很多好处。
为了生成这些 id,我们使用一个名为KeyAllocation
的表格。每个 Octopus 服务器使用一个可序列化的事务,通过增加下表中分配的范围来请求一个 ID 块(通常 20 个左右):
随着时间的推移,服务器可以从批中发出这些 ID,而不必每次都去数据库。
来自的用法。网
使用中的数据库时。NET 代码,我们希望对数据库操作非常谨慎。大多数 ORM 都提供了一个工作单元 (NHibernate 的ISession
,Entity Framework 的DbContext
),它跟踪对对象所做的更改,并自动更新已更改的记录。
由于工作单元的意外后果在过去一直是我们的一个性能问题,我们决定非常慎重地考虑:
- 交易边界
- 插入/更新/删除文档
我们只是抽象了一个 SQL 事务/连接,而不是一个工作单元。加载、编辑和保存文档的典型代码如下所示:
using (var transaction = store.BeginTransaction())
{
var release = transaction.Load<Release>("Releases-1");
release.ReleaseNotes = "Hello, world";
transaction.Update(release);
transaction.Commit();
}
当查询时,我们不使用 LINQ 或任何花哨的标准/规范生成器,我们只是有一个非常简单的查询助手,它只是帮助构造 SQL 查询。您可以在 SQL 中做的任何事情都可以放在这里:
var project = transaction.Query<Project>()
.Where("Name = @name and IsDisabled = 0")
.Parameter("name", "My Project");
.First();
var releases = transaction.Query<Release>()
.Where("ProjectId = @proj")
.Parameter("proj", project.Id)
.ToList();
因为我们的表存储整个文档,所以对象/关系映射很容易——我们只需要指定给定文档映射到的表,以及哪些字段应该存储为列而不是 JSON 字段。下面是一个映射示例:
public class MachineMap : DocumentMap<Machine>
{
public MachineMap()
{
Column(m => m.Name);
Column(m => m.IsDisabled);
Column(m => m.Roles);
Column(m => m.EnvironmentIds);
Unique("MachineNameUnique", "Name", "A machine with this name already exists. Please choose a different name. To add the same machine to multiple environments, edit the machine details and select additional environments.");
}
}
(虽然我们没有使用很多外键,但是我们依赖于一些约束,比如上面显示的唯一约束)
迁移
任何数据库,甚至是文档数据库,都必须在某个时候处理模式迁移。
对于一些变化,比如引入一个我们不打算查询的新属性,我们什么也不用做:JSON 序列化程序将自动开始编写新属性,并将处理旧文档中缺少的属性。类构造函数可以设置合理的默认值。
其他更改需要迁移脚本。对于这些,我们使用 DbUp 。这要么是一个 SQL 脚本,要么是 C#代码(DbUp 可以让你任意执行。NET 代码)进行更高级的迁移。我们将很快在这个数据库的基础上完成四个主要版本和几十个次要版本,到目前为止还没有发现我们不能轻松处理迁移的情况。
统计数据
使用 SQL Server 作为文档存储对我们来说是可行的,但我确信它并不适合所有人。一些粗略的统计数据可能有助于正确看待这个问题:
- 自从 3.x 发布以来,已经有大约 15,000 次成功的 Octopus 安装——约占所有安装的 68%
- 其中一些安装非常大:将 300 多个项目部署到 3,500 多台机器上
- 有些在数据库中有成千上万的部署
- 最大的表是审计事件表,通常有数百万条记录
- 自从 3.x 发布以来,我们很少收到关于性能的投诉,性能问题也很容易得到解决
我们不仅在 Octopus 中使用这种方法——我们还在我们的网站和订单处理系统中使用这种方法。
SQL Server 2016 JSON 支持
我们所遵循的方法将会变得更加普遍。 SQL Server 2016 引入了对 JSON 的支持,不是通过添加新类型(支持 XML 的方式),而是通过添加函数来处理存储在nvarchar
列中的 JSON。我们当前的模式完全可以利用这一点。
不幸的是,我们需要支持 2016 年以前的 SQL Server 版本,所以我们需要一段时间才能依赖这些功能。但是我们也许可以用它来优化某些情况——例如,如果 JSON 函数在您的 SQL Server 版本中可用,我们也许可以在服务器中做更少的处理。
为什么不用实体框架呢?
以下是我们使用 SQL 作为文档存储,而不是实体框架和关系存储的一些优势:
- 否选择 N+1 个问题;因为我们从单个表中获取整个对象树,所以不需要执行选择或连接来获取子记录
- 没有连接。在 Octopus 中有一个单一的代码路径,在这里我们连接两个表;在所有其他操作中,我们完全避免了连接。
- 轻松映射
- 您编写的查询就是您得到的查询。很容易推断出将要执行的 SQL 我们不依赖 LINQ 的翻译
- 多对多的关系更容易处理
摘要
改变存储技术和重写大部分应用程序来做到这一点是我们几年前下的一个大赌注,我对结果非常满意。性能更好,很少出现支持问题(不像以前的体系结构),而且这是一种我们的客户更容易接受的技术。
虽然许多应用程序偶尔会使用 JSON 列来存储一些数据,但当我们开始这样做时,我还没有见过许多应用程序如此假装 SQL Server 是一个文档存储库,而忽略了大多数关系方面。不过,我确实认为这将变得更加普遍。最近在上。网石!,杰瑞米·米勒谈到了 Marten ,这听起来就像我们所做的,除了在 PostgreSQL 之上。随着 SQL 2016 获得自己的 JSON 函数,我预计它会变得更加流行。
我确信这种方法并不适合所有人。写入大量数据而查询较少的应用程序可能会从关系模型中获益更多。需要水平扩展数据库的应用程序应该坚持使用为该工作而构建的文档数据库。但是,如果您所在的环境中已经有 SQL Server,并且性能要求不是很高,那么 SQL 作为一个文档存储可能就适合您。
Octopus Deploy 的 SQL Server 部署选项- Octopus Deploy
原文:https://octopus.com/blog/sql-server-deployment-options-for-octopus-deploy
如果您有一个 SQL Server 数据库并使用 Octopus Deploy,那么您有几个关于数据库源代码控制和部署工具的选项。这篇博客文章提供了最广泛使用的工具的基本概述,以帮助您决定哪个选项最适合您的团队。
基于模型和基于迁移的解决方案
大多数数据库源代码控制和部署工具可以大致分为基于模型的(T0)或基于迁移的(T5)。
基于模型的工具编写出数据库模式所需的最终状态,通常为每个对象创建一个脚本。在部署管道中的某个点,软件会将源代码与目标数据库进行比较,并生成一个部署脚本。
基于迁移的工具要求用户保存迁移脚本的有序列表。该工具将针对目标数据库执行它们。通常,该工具还会向目标数据库添加一个表,以记录已经部署了哪些脚本。
很多人对哪个更好有强烈的看法。这有点像一场激烈的战争,我不想在这里纠缠不清。然而,每种方法都有优点和缺点,如果你不知道它们是什么,我建议你在继续之前阅读一下这篇博文。
本土解决方案
基于迁移的解决方案听起来似乎很容易在内部开发。然而,自行开发的解决方案通常会导致知识囤积、复杂且不可预测的部署以及巨大的维护成本。
自行开发的基于迁移的工具通常是作为附带项目产生的,坦率地说,它们不可能像无数类似的开源基于迁移的工具那样成熟,这些工具已经由经验丰富的维护人员社区经过多年的改进,并被世界各地成千上万的开发人员所使用。
在我从事数据库 DevOps 领域工作的十年中,我还没有看到一个自行开发的解决方案比类似的开源或供应商解决方案更适合组织的例子。不要这样做。请使用本文中的工具。
使用流行的工具通常是值得的。广泛使用的工具通常得到更好的支持,更好的文档记录,并且通常有可以找到答案的活跃社区。通常也可以招募有使用工具经验的人。
为了确定最广泛使用的工具,我首先根据 Octopus 团队友好提供的一些功能使用数据,检查了各种 Octopus Deploy 社区步骤模板的相对受欢迎程度:
【T2
这个数据有局限性。例如,人们完全有可能通过 PowerShell 步骤触发了他们的数据库部署工具,而没有使用库中的步骤模板。还有一些流行的工具,比如 DbUp,它们没有 step 模板。也就是说,上述步骤模板的相对受欢迎程度与我的真实单词体验相符。
在这篇文章中,我将讨论 DbUp 和上面的每一个工具,除了 ApexSQL,它的用户更少。
SSDT
类型:基于模型
上面列出的最流行的步骤模板部署 DacPacs。这些是使用 SQL Server 数据工具(SSDT)从 Visual Studio 数据库项目中生成的构建工件。
SSDT 是由微软构建的,它是随 Visual Studio (VS)一起开箱即用的,所以假设你的开发者有适当的 MSDN 许可证,它基本上是免费的。这使得它成为大多数普通 VS 用户的自然解决方案。然而,对于更熟悉 SQL Server Management Studio (SSMS)的数据库开发人员和 DBA 来说,VS 可能会相当混乱。
Visual Studio 数据库项目有点像. NET 项目,由解决方案和项目文件组成。因此,这可能是大多数人的天然选择。NET 开发人员,但可能会让任何 SQL Server 专家感到困惑。净经验。此外,有些人对不断在 SSMS 和 VS 之间转换感到沮丧
作为一个基于状态的工具,它提供了声明式数据库开发的许多生产力优势,但有时自动生成的部署脚本可能会做一些痛苦的事情,如删除数据或在巨大的表上重建索引。这些问题可能只有在首次部署到大型生产数据库时才会被发现。
SSDT 确实有一些混合特性来解决这些问题,比如重构日志、部署前和部署后脚本,但是随着时间的推移,这些往往会变得难以管理。
最好的:SQL Server 的事实上的标准,Visual Studio 中的构建过程,SQL 环境变量。
最糟糕的事情:在膨胀的预部署/后部署脚本中管理复杂的重构,管理依赖性。
雷德盖特
Redgate 支持两种类型的 SQL Server 源代码管理项目。这两种项目类型都可以使用名为 SQL Change Automation 的 PowerShell 模块进行部署:
- SQL 源代码管理是 SSMS 的一个插件,为你的数据库创建一个基于模型的项目。
- SQL 变更自动化 VS 和 SSMS 插件为你的数据库创建一个基于迁移的项目。然而,虽然 SCA 项目主要是基于迁移的,但是它们有几个强大的混合特性。这使得 SCA 项目成为撰写本文时可用的最真实的基于混合迁移和基于模型的工具。
SQL 源代码管理项目
类型:基于模型
几乎可以肯定,SQL 源代码管理是继 SSDT 之后最流行的 SQL Server 源代码管理工具。SQL 源代码控制和 SSDT 的主要区别在于,它不是内置于 VS 中,而是内置于 SSMS 中。对于大部分时间都在 SSMS 的 SQL Server 开发人员和数据库管理员来说,这大大简化了使用过程。不要再在不同的想法间跳跃。
部署是通过 SCA PowerShell 模块处理的,有一些优秀的 step 模板使得设置 Octopus 部署项目相对简单。差异报告也非常方便。查看 Bob 最近的帖子了解更多信息。
我已经在这里写了关于 SSDT 和 SQL 源代码控制之间的差异的更详细的描述,并且还有一个关于这个主题的 30 页的白皮书。
最好的事情:可用性,与 SSMS 整合,发布差异报告。
最糟糕的事情:价格,管理复杂的重构。
SQL 变更自动化(SCA)项目
类型:迁徙型/混合型
在撰写本文时,SCA 项目(以前称为 ReadyRoll 项目)无疑是最先进的 SQL Server 项目类型。它们旨在为您提供基于模型和基于迁移的工具的最佳部分。这确实增加了一些复杂性,所以有一点学习曲线,但是 SCA 通常会兑现它的承诺。
SCA 主要是一个基于迁移的工具,但是因为它来自 Redgate,所以它内置了模式比较和脚本生成功能。它也建立在 SSDT 之上,所以你可以免费继承许多最好的 SSDT 特性(部署前/部署后脚本、SQL 环境变量等)。它带有 SSMS 和 VS 的插件,所以你可以使用你喜欢的 IDE。
基本原则是,一旦您完成了开发数据库的一些开发工作,您点击一个按钮,SCA 将生成您的脚本并将其添加到您的项目中。如果生成的脚本做了一些有点傻的事情,比如删除一列,您可以在提交到源代码控制之前编辑它。
由于覆盖,存储过程和函数通常是用基于迁移的工具管理的噩梦,所有的可编程对象被提取到项目的一个单独部分,在那里它们被视为一个基于迷你模型的子项目。这也有助于减少迁移脚本的总数,使基于迁移的部分更容易管理。这是基于迁移的项目要避免/减少的两个最大问题。
基于迁移的工具的另一个挑战是发现冲突。SCA 通过在另一个迷你子项目中保存一个模式模型提供了一个方便的解决方案,为所有的表创建脚本。如果两个迁移脚本更新同一个表,您将在模式模型中看到合并冲突。模式模型还充当了期望的最终状态的便利参考点,尽管事实上它并不用于部署。
这种混合方法确实有一些缺陷。它更复杂,所以有一个学习曲线,并且基线化现有的项目并不容易。此外,如果您在任何迁移脚本中使用存储过程,您可能会遇到一些复杂的竞争情况,存储过程更新总是在最后应用,但是这对于大多数团队来说是一种边缘情况。
坦白地说,SCA 最大的问题是你需要向别人要预算。与这篇博文中列出的所有其他工具不同,Redgate 工具不是免费的。
最佳:最佳混合解决方案,兼具基于模型和基于迁移的解决方案的优势。
最差:价格,复杂度。
实体框架(EF)迁移
类型:迁移型
实体框架 (EF)迁移是默认的部署解决方案,对于使用 EF 的人来说是现成的,EF 是最流行的对象关系映射(ORM)工具。NET/SQL Server。
EF 迁移的主要考虑不是部署功能本身,而是你是否想使用 ORM 工具。如果你问的最多。NET 开发人员,他们可能会喜欢它。如果你问大多数数据库管理员,他们可能会讨厌它。这是另一场古老的火焰战争。
EF,尤其是在代码优先而不是数据库优先(大多数人都这样做)的情况下,为。NET 开发人员,这使得他们更容易开发他们的。NET 应用程序。这使得 EF vary 很受那些没有太多 SQL Server 经验或知识的开发人员的欢迎。
然而,开发人员往往会得到关于最终被推送到数据库的代码的糟糕反馈。SQL Server 经验有限的开发人员加上对代码的糟糕反馈,常常会导致让 DBA 非常不高兴的查询。
当然,ORM 在美国很受欢迎。NET 社区,但要小心行事。此外,戴上我的 DevOps 帽子,确保你记得为整个价值流进行优化,而不是以下游工人为代价,为单个开发人员或团队/筒仓进行优化。
极品: ORMs。
最糟糕的事情: ORMs。
候鸟迁徙所经的路径
类型:迁移型
Flyway 是一个基于移植的开源工具。最初由阿克塞尔·方丹撰写,最近被 Redgate 收购。虽然它不是 SQL Server 使用最广泛的工具,但它在开源社区中非常流行。
这是一个 Java 应用程序,对于 SQL Server 部署工具来说可能有点奇怪,但这是因为,与本文中的其他工具不同,Flyway 从一开始就被设计为跨平台的。Flyway 用户可以运行 MySQL、Postgres、Oracle 或 Flyway 支持的 20 多种 RDBMSs 中的任何一种。这使得它成为支持多个关系数据库的商店的流行选择。
与大多数类似的工具相比,使用它相对简单:下载,用连接字符串更新配置文件,并将脚本放到flyway-[version]/sql
目录中。然后打开命令提示符,输入flyway migrate
。
也就是说,它没有内置的比较引擎。这意味着它不能为您生成部署脚本。您需要手动创建它们,或者使用数据库比较工具。
Flyway 是数据库迁移工具的 Marie Kondo。
最好的事情:跨平台支持,简单——没有花里胡哨。
最差: Java,没有花里胡哨。
DbUp
类型:基于迁移
DbUp 是另一个非常受欢迎的开源迁移工具,尤其是在 Octopus Deploy 用户中。这可能与它是由保罗·斯托弗写的这一事实有很大关系。如果您是 Octopus Deploy 用户,您几乎肯定也使用过它,因为它被打包到 Octopus Deploy 安装程序中。你认为你的 Octopus Deploy 数据库是如何部署的?
DbUp 是一个. NET 库,您可以添加到自己的库中。NET apps,这解释了为什么它没有一个步骤模板。
像 Flyway 一样,DbUp 没有任何巧妙的比较工具或花哨的特性。它故意是一个简单明了的迁徙者。创建和管理这些升级脚本的所有工作都取决于您。
极品:简单/可定制,. NET。
最糟糕的事情:需要一些 C#技能。
其余的…
还有很多其他的解决方案。有些人可能会感到恼火,因为我没有包括更多的供应商工具,如 Idera 、 ApexSQL 、 DBmaestro ,或者其他无数的开源迁移脚本运行程序,如 Liquibase 、 RoundhousE 或 DBDeploy 。
如上所述,我的目的是列出最受欢迎的选项。根据 Octopus 提供的数据和我的个人经验,这篇博客文章中列出的工具是我认为 Octopus Deploy 用户中最受 SQL Server 部署欢迎的工具。
结论
没有一种工具是完美的。他们都有自己的挑战,但他们也都有优势。然而,几乎可以肯定的是,在几乎所有情况下,使用这些工具中的一种比创建自己的数据库部署框架更好。
哪种工具最适合您的环境?嗯,看情况。但是这里有一个决策树,来自 DLM 顾问公司为期两天的数据库开发运维培训课程,可以在您的团队中引发一些讨论:
您在使用上述工具时有过正面或负面的体验吗?你试过其他工具吗?如果是这样,请在下面留言。我很想听听你的想法。
自 2010 年以来,Alex Yates 一直在帮助组织将 DevOps 原则应用于他们的数据。他最引以为豪的是帮助 Skyscanner 开发了一天 95 次部署的能力,并支持了联合国项目服务办公室的发布流程。亚历克斯与除南极洲以外的各大洲的客户都有过合作——所以他渴望见到任何研究企鹅的人。
作为一名热心的社区成员,他共同组织了数据接力,是【www.SpeakingMentors.com】的创始人,并自 2017 年以来被公认为微软数据平台 MVP 。
Alex 是官方 Octopus Deploy 合作伙伴 DLM 顾问的创始人。他喜欢为那些希望通过改进 IT 和数据库交付实践来实现更好业务成果的客户提供指导、辅导、培训和咨询。
如果你想和亚历克斯一起工作,请发邮件至:enquiries@dlmconsultants.com
使用 dbatools 简化 SQL Server 和 PowerShell:实用示例- Octopus Deploy
说重点!
为什么选择 PowerShell?
我们的数据在规模和复杂性方面继续呈指数级增长。DevOps 和 SRE 运动正在重新定义对弹性和响应性的期望。数据泄露、数据丢失或报告延迟的成本正在急剧增加。
四十五年前,神话中的人月告诉我们,满足指数级增长需求的答案不仅仅是增加员工数量。生产率不会以同样的方式增长。
我们这些负责管理这些数据的人需要扩展我们的能力,来大规模地设计、交付和保护我们的数据,以满足对我们服务的需求。坦率地说,如果我们的技能只是扩展到使用 SQL Server Management Studio (SSMS)中的向导,我们将跟不上。
我们拥抱自动化至关重要。
如果您的数据在 SQL Server 数据库中,这意味着有必要学习 PowerShell。如果 PowerShell 还不是您工具箱中的关键部分,那么它很快就会成为。如果您还不习惯使用 PowerShell 作为您与 SQL Server 的主要接口,并且您希望保持可雇佣性,那么是时候进行一些研发了。
为什么选择 dbatools?
几个月前,James 写了这篇关于使用 SqlServer PowerShell 模块的精彩文章。他正确地指出“微软建议使用 SqlServer 模块从 PowerShell 与 SQL Server 进行交互”。他没有错。微软确实这么说。但我没有。在我看来, dbatools 应该是任何 SQL Server 工作的默认 PowerShell 模块。
dbatools 是一个社区驱动的开源 PowerShell 模块,用于管理 SQL Server。它由克里斯·勒梅尔发起,但在克里斯鼓舞人心的指导下,已经由 189 个贡献者扩展。它继续有机增长,包括真正的最终用户需要的命令。在撰写本文时,它附带了超过 500 个 cmdlet 的,比您在 SqlServer 模块中获得的大约多 5 倍。
而且不仅仅是数量;也是品质。相对于 SqlServer 模块,dbatools 也出奇的好用。这既是因为命令更简单,也是因为有一个巨大的社区,人们热衷于相互支持,他们中的许多人整天都在公共 SQL Server 社区 Slack workspace 中放松。如果您有任何问题或遇到困难,通常会在几分钟内得到回复。虽然这是一个社区的事情,你需要尊重这些人是出于好心帮助你。你将很难找到一个官方的支持团队来帮助任何一个能够击败它的厂商。
基本示例
在本文中,我首先向您展示所有 James 示例的 dbatools 等价物,以展示使用 dbatools 的相对简单性和可维护性。然后,我将讨论除了 SqlServer 模块提供的功能之外,dbatools 将为您提供的一些更加强大的功能。
就像詹姆斯一样,我所有的脚本都在一个公共的 GitHub repo 中。如果您对如何改进它们有任何建议,我很乐意查看您的拉动请求。😛
安装 dbatools PowerShell 模块
首先,James 安装了 SqlServer 模块。对于 dbatools,过程是相同的:
#Install the dbatools module from the PowerShell gallery
Install-Module -Name dbatools
#If the module is already installed, update it using the following commands:
#For PowerShell 5.0 or later
Update-Module -Name dbatools
#For PowerShell version earlier than 5.0
Uninstall-Module -Name dbatools
Install-Module -Name dbatools
测试与 SQL Server 的连接
接下来,我们需要测试我们可以连接到我们的 SQL 实例。
我们可以通过使用Test-DBA connectioncmdlet 在实例级别对此进行测试,或者我们可以使用 Get-DbaDatabase cmdlet 在数据库级别进行测试。如同(全部/大部分?)的 dbatools cmdlets 中,cmdlet 试图为我们捕捉任何异常以避免红色的海洋堆栈跟踪,但是如果我们想要启用异常并自己处理它们,我们可以使用-EnableException
参数:
# To test connectivity to a SQL instance
Test-DbaConnection localhost
# To test connectivity to a specific database
Get-DbaDatabase -SqlInstance localhost -Database MyDatabase
就我个人而言,我发现上面的代码比 SqlServer 模块示例更容易理解和使用:
try
{
# This is a simple user/pass connection string.
# Feel free to substitute "Integrated Security=True" for system logins.
$connString = "Data Source=YourInstance;Database=YourDB;User ID=YourUser;Password=YourPassword"
#Create a SQL connection object
$conn = New-Object System.Data.SqlClient.SqlConnection $connString
#Attempt to open the connection
$conn.Open()
if($conn.State -eq "Open")
{
# We have a successful connection here
# Notify of successful connection
Write-Host "Test connection successful"
$conn.Close()
}
# We could not connect here
# Notify connection was not in the "open" state
}
catch
{
# We could not connect here
# Notify there was an error connecting to the database
}
创建 SQL Server 登录名
接下来,James 创建了一个 SQL Server 登录名。dbatools 中对应的 cmdletNew-DbaLogin非常类似。
# To run in a non-interactive mode, such as through an Octopus deployment, you will most likely need to pass the new login credentials as a PSCredential object.
$securePassword = ConvertTo-SecureString "Th!sI5Y0urP4ss" -AsPlainText -Force
# Create the login using the New-DbaLogin cmdlet
New-DbaLogin -SqlInstance localhost -Login MyLogin -SecurePassword $securePassword -PasswordPolicyEnforced -PasswordExpirationEnabled
创建 SQL Server 数据库并分配所有者
使用 SqlServer 模块创建数据库非常困难。James 不得不恢复运行定制的 SQL 脚本,或者使用 SQL Server 管理对象(SMOs)来“完成繁重的工作”。对我来说,这两种解决方案都复杂得令人恼火。
接下来,James 更改了数据库所有者,再次创建了一对 smo。正如詹姆斯正确解释的那样,这是微软官方推荐的路线。然而,dbatools 使得代码更容易阅读和维护。
在下面的脚本中,我使用了 New-DbaDatabase 和Restore-DBA databasecmdlet 来演示如何用一个命令创建一个新数据库或恢复一个新数据库。然后,我组合了 Get-DbaDatabase 和Set-DBA dbownercmdlet 来更改新数据库的数据库所有者。
# Create a new empty database
New-DbaDatabase -SqlInstance localhost -Name MyDatabase
# Create a new database from a backup
Restore-DbaDatabase -SqlInstance localhost -Path "\\Backups\MyDatabase.bak"
# Assign a new owner to your database
$db = Get-DbaDatabase -SqlInstance localhost -Database MyDatabase
$db | Set-DbaDbOwner -TargetLogin MyLogin
运行 SQL 脚本
James 最后演示了如何使用 SqlServer cmdlet Invoke-Sqlcmd 来执行一些内联 SQL 或单独的。sql 脚本。这段代码看起来足够简单,与 dbatools 的等价代码 Invoke-DbaQuery 看起来和感觉上非常相似。但是,dbatools 的等效功能旨在更方便地在管道中使用,并且与其他 dbatools 函数的行为更加一致。
# Run a query from a script
Invoke-DbaQuery -sqlinstance localhost -File "sql_script.sql" -MessagesToOutput
# Run an in-line SQL command
Invoke-DbaQuery -sqlinstance localhost -Query "PRINT 'hello world'" -MessagesToOutput
更有力的例子
如上所述,dbatools 包含数百个命令,所以不可能在本文中涵盖所有命令。到目前为止,我一直关注一些用于处理基本操作的更简单的命令。然而,真正的力量来自于一些更大的 cmdlet,它们建立在更简单的 cmdlet 之上,使我们能够交付更大更复杂任务的最佳实践实现。
迁移 SQL 实例
Start-DBA migration cmdlet 可能是 dbatools 的第一个主要特性。它是许多Copycmdlet(例如 Copy-DbaDatabase 、 Copy-DbaAgentJob 、 Copy-DbaLinkedServer 等)的包装器。)允许我们将所有 SQL Server 对象从一个实例迁移到另一个实例,包括数据库、代理作业、链接服务器和一长串其他对象类型。
试图使用 SSMS GUI 向导或者甚至普通的 T-SQL 脚本来实现这一点是一件痛苦的事情。用 SqlServer 模块做这件事并不容易。但是,使用 dbatools,我们可以像您打开 PowerShell 窗口并键入以下命令一样快速地开始这项工作:
Start-DbaMigration -Source sql01 -Destination sql02 -DetachAttach
听起来有点不可思议,对吧?这里有几个视频有更多的信息。这个有 50 秒长。这部,由克丽丝亲自拍摄,时长 50 分钟。
安全删除数据库
大胡子, Rob Sewell ,是 dbatools 最大的贡献者之一。他的第一个贡献是 Remove-DbaDatabaseSafely 。受 Grant Fritchey 的三分钟备份演讲的启发,他整理了 Grant 的可靠备份的七个步骤,这样只需一个命令,你就可以放心地安全备份和删除数据库。
Remove-DbaDatabaseSafely -SqlInstance localhost -Database MyDatabase -BackupFolder 'C:\Backups\Old databases - DO NOT DELETE'
更多最佳实践材料
上面的两个例子演示了使用 dbatools 如何帮助您同时更有效地工作和标准化更好的实践。正如我提到的,dbatools 有超过 500 个命令,并且还在增加。为了让您了解 dbatools 允许您用一个简单的命令完成的其他一些最佳实践,请查看这些博客帖子:
那些博客文章并不特别新,但我希望它们能激起你的兴趣。现在轮到您亲自动手练习使用这些命令了。
结论
我并不是说 DBA 角色已经死了。如果说有什么不同的话,那就是我们的数据问题变得越来越大,越来越复杂。数据库管理不再是一项可以外包给高度专业化的部门并从日常开发工作中抽象出来的功能。我们迫切需要懂得如何管理数据的人,我们需要他们密切参与我们数据结构的设计和开发。DBA 需要加入工程团队的其他成员。
DBA 很忙,雇佣更多的 DBA 并不是一个实际的解决方案。如果我们的数据库管理员要在开发周期的更早阶段找到时间参与进来,同时支持更大更复杂的数据资产,那么他们必须采用自动化,以便在更短的时间内高效可靠地完成更多的管理工作。能够做好这一点的数据库管理员将会非常吃香。不会的机会会更少。
从根本上说,DBA 需要努力减少使用 SSMS 向导的时间,增加使用 PowerShell 脚本和源代码控制的时间。数据库管理没有消亡,但它在不断发展。
dbatools 是支持 SQL Server 的最好、发展最快的 PowerShell 模块。
行动呼吁
由于 500 个 cmdlets 可能会令人望而生畏,您可能不知道从哪里开始。以下是一些建议:
- 请查看 Chrissy 和 Rob 的“在一个月的午餐中学习 DBA tools”。在写作的时候,它仍然是一个正在进行的工作,但是前八章已经可以使用了。这是个不错的开始。
- 从你需要的开始。下一次当你有一个数据库任务需要完成时,问问你自己这是不是那种你可以编写脚本,测试,并坚持源代码控制的事情。在你写好脚本后,如果这是一个常规任务,你可以为它创建一个章鱼手册。然后,如果你需要重复它或者完成一个类似的任务,你已经有了一个现成的模板。
- 加入 SQL 社区。我们中有一群人在闲暇时间闲逛。我们是一群傲慢友好的人,我们喜欢帮助事情进展。(如果您发现了改进 dbatools 的机会,提交一个 pull 请求!)
加入我们吧。我们很高兴见到你!😊
自 2010 年以来,Alex Yates 一直在帮助组织将 DevOps 原则应用于他们的数据。他最引以为豪的是帮助 Skyscanner 开发了一天 95 次部署的能力,并支持了联合国项目服务办公室的发布流程。亚历克斯与除南极洲以外的各大洲的客户都有过合作——所以他渴望见到任何研究企鹅的人。
作为一名热心的社区成员,他共同组织了数据接力,是www.SpeakingMentors.com的创始人,并自 2017 年以来被公认为微软数据平台 MVP 。
亚历克斯是官方八达通部署合作伙伴 DLM 咨询公司的创始人。他喜欢为那些希望通过改进 IT 和数据库交付实践来实现更好业务成果的客户提供指导、辅导、培训和咨询。
如果你想和亚历克斯一起工作,请发电子邮件:【enquiries@dlmconsultants.com
SQL Server 和 PowerShell:实践示例- Octopus Deploy
Octopus Deploy 的目标之一一直是帮助简化自动化应用程序部署,而应用程序部署通常需要在此过程中进行数据库管理。在这篇文章中,我的目标是提供一些使用 PowerShell 进行 SQL Server 数据库管理的常见示例,以便更直接地集成到部署中。
所有这些例子的源代码都可以在 GitHub 上找到。如果你遇到任何问题或者有修改的建议,请随意张贴到 GitHub 库的问题列表或者发送一个请求!
安装 SQL Server PowerShell 模块
微软建议使用 SqlServer 模块从 PowerShell 与 SqlServer 进行交互。下面的例子中并没有用到它,但是它包含了许多对 SQL Server 管理有用的 cmdlets。
可以使用以下命令从 PowerShell Gallery 安装 SqlServer 模块:
Install-Module -Name SqlServer
此外,如果已经安装了此模块,并且您使用的是 PowerShell 5.0 或更高版本,则可以使用以下命令对其进行更新:
Update-Module -Name SqlServer
如果您使用的是早于 5.0 的 PowerShell 版本,请使用以下命令:
Uninstall-Module -Name SqlServer
Install-Module -Name SqlServer
关于安装 SqlServer 模块的更多信息,请参见本微软文档:安装 SQL Server PowerShell 模块。
测试与 SQL Server 的连接
测试 SQL Server 数据库连接的最简单方法之一是使用连接字符串和基本的 SqlClient 类:
try
{
# This is a simple user/pass connection string.
# Feel free to substitute "Integrated Security=True" for system logins.
$connString = "Data Source=YourInstance;Database=YourDB;User ID=YourUser;Password=YourPassword"
#Create a SQL connection object
$conn = New-Object System.Data.SqlClient.SqlConnection $connString
#Attempt to open the connection
$conn.Open()
if($conn.State -eq "Open")
{
# We have a successful connection here
# Notify of successful connection
Write-Host "Test connection successful"
$conn.Close()
}
# We could not connect here
# Notify connection was not in the "open" state
}
catch
{
# We could not connect here
# Notify there was an error connecting to the database
}
在部署开始时对连接进行测试,您就知道可以对 SQL Server 进行所需的调用了。
创建 SQL Server 登录名
为了在服务器或实例中正确分离权限,您可能需要为应用程序或相关服务创建新的登录。来自 SQL Server 模块的 cmdlet 将完成此任务:
# To run in a non-interactive mode, such as through an Octopus deployment, you will most likely need to pass the new login credentials as a PSCredential object.
$pass = ConvertTo-SecureString "Th!sI5Y0urP4ss" -AsPlainText -Force
# Create the PSCredential object
$loginCred = New-Object System.Management.Automation.PSCredential("NewUser",$pass)
# Create login using the Add-SqlLogin cmdlet
Add-SqlLogin -ServerInstance YourInstance -LoginPSCredential $loginCred -LoginType SqlLogin
此 cmdlet 极大地简化了 SQL Server 登录的创建,并灵活地使用内置标志,如-MustChangePasswordAtNextLogin
或-ConnectionTimeout.
如果您在 Octopus 部署过程中使用此命令,您可能会发现包含以下标志很有用:
-Enable
确保稍后在您的部署中登录可用。-GrantConnectSql
允许登录连接到数据库引擎。
更多信息,参见 Add-SqlLogin 参考。
创建 SQL Server 数据库并分配所有者
令人惊讶的是,Microsoft 并没有提供现成的 cmdlet 来创建数据库,因此有两条主要途径可以在没有预先备份的情况下启动并运行数据库。两者都相当简单。使用以下命令之一可以创建新的空白数据库:
对您的实例直接运行 SQL 来创建数据库:
# This query could also come from a file
Invoke-Sqlcmd -Query "CREATE DATABASE YourDB" -ServerInstance YourInstance
或者,使用 SQL Server 管理对象(SMO)对象来完成繁重的工作:
#Name your database
$dbname = "YourDB"
# Create a SQL Server database object
$srv = New-Object Microsoft.SqlServer.Management.Smo.Server("YourInstance")
if($null -ne $srv.Databases[$dbname])
{
$db = New-Object Microsoft.SqlServer.Management.Smo.Database($srv, $dbname)
# Create the database
$db.Create()
}
如果您有想要恢复的数据库备份,而不是创建空白数据库,那么Restore-SQL databasecmdlet 是您的好朋友:
$backupFile = "\\SQLBackups\YourDBBackup.bak"
Restore-SqlDatabase -ServerInstance YourInstance -Database YourDB -BackupFile $backupFile
关于Restore-SQL databasecmdlet 的更多信息可以在这个微软文档中找到: Restore-SqlDatabase 。
现在您的数据库已经就绪,让我们将数据库所有者改为NewOwner
:
# Here we'll need a user with administrative privileges to set the owner.
# Let's say that $SqlAdmin contains the username and $SqlAdminPass contains the password as a secure string.
$dbname = "YourDB"
# Create the server connection object
$conn = New-Object Microsoft.SqlServer.Management.Common.ServerConnection("YourInstance", $SqlAdmin, $SqlAdminPass)
# Create the server object
$srv = New-Object Microsoft.SqlServer.Management.Smo.Server($conn)
# Check to see if a database with that name already exists
if($null -ne $srv.Databases[$dbname])
{
# If it does not exist, create it
$db = New-Object Microsoft.SqlServer.Management.Smo.Database($srv, $dbname)
$db.SetOwner("NewOwner")
}
else
{
# There was an error creating the database object
}
从文件运行 SQL 脚本
新数据库就绪后,可能需要对设置进行更改,或者某些表缺少默认数据。PowerShell 可以直接从文件运行 SQL 脚本来解决这些问题。让我们假设alter_script.sql
在一个已知的位置(可能在您的 Octopus 部署中的一个包内)。可以使用以下命令直接从 PowerShell 运行该脚本:
# Assumes you are working from the package directory
Invoke-Sqlcmd -InputFile .\alter_script.sql -ServerInstance YourInstance -Database YourDB
其他的Invoke-Sqlcmd
信息可以在这个微软文档中找到: Invoke-Sqlcmd cmdlet 。
运行内联 SQL 命令
最后,您可能需要运行一些 SQL 查询来验证数据库包含正确的数据(检查一切正常)。PowerShell 能够直接从控制台窗口运行内联 SQL 查询。让我们运行一个快速查询来检查表中的行数:
$rowcount = "SELECT COUNT(*) FROM YourTable"
Invoke-Sqlcmd -ServerInstance YourInstance -Database YourDatabase -Query $rowcount
结论
无论您是在每次从 Octopus 部署一个版本时自动配置新数据库,还是对现有数据库运行一次性命令,PowerShell 和 SQL Server 模块都是完成这项工作的合适工具。
你还有其他的场景或具体问题吗?在这里留下评论或者加入我们的社区松弛期。
参考
SSH 到 Kubernetes 集群——Octopus 部署
跳跃盒或堡垒主机是一种常见的网络策略,用于向公共互联网暴露单个安全入口点,以访问私有网络。这种单点入口让安全团队能够密切监视和控制对专用网络的网络访问。通常,bastion 主机公开了一个众所周知的远程访问服务,如 RDP 或 SSH,团队可以假设这些服务已经过广泛的审查,是值得信任的。
在本文中,我将解释如何在 Kubernetes 集群中托管 OpenSSH 服务器来执行管理任务。
部署 SSH 服务器
SSH 服务器长期以来一直被用来提供对 Linux 服务器的远程访问,并且将 SSH 服务器作为 Kubernetes pod 来托管相对容易。
下面显示的 YAML 文件创建了一个服务帐户,该帐户具有一个角色和角色绑定,授予对当前名称空间中公共资源的访问权限。然后,它部署一个linuxserver/openssh-server
映像实例,继承服务帐户的权限,并通过负载平衡器服务公开它:
apiVersion: v1
kind: ServiceAccount
metadata:
name: k8s-admin
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: k8s-admin-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: k8s-admin-role-binding
subjects:
- kind: ServiceAccount
name: k8s-admin
apiGroup: ""
roleRef:
kind: Role
name: k8s-admin-role
apiGroup: ""
---
apiVersion: v1
kind: Service
metadata:
name: my-ssh-svc
labels:
app: ssh
spec:
type: LoadBalancer
ports:
- port: 2222
selector:
app: ssh
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-ssh
labels:
app: ssh
spec:
replicas: 1
selector:
matchLabels:
app: ssh
template:
metadata:
labels:
app: ssh
spec:
serviceAccountName: k8s-admin
containers:
- name: ssh
image: lscr.io/linuxserver/openssh-server:latest
ports:
- containerPort: 2222
env:
- name: PUID
value: "1000"
- name: PGID
value: "1000"
- name: TZ
value: "Australia/Brisbane"
- name: USER_NAME
value: "admin"
- name: USER_PASSWORD
value: "Password01!"
- name: PASSWORD_ACCESS
value: "true"
- name: SUDO_ACCESS
value: "true"
注意,为了方便起见,这个 SSH 服务器允许密码访问,示例 YAML 文件嵌入了一个不安全的示例密码,并允许 sudo 访问。更可靠的解决方案是使用密钥文件进行身份验证。参考 Docker Hub 文档中显示如何使用密钥文件进行认证的示例。
将上面的 YAML 保存到一个名为ssh.yaml
的文件中,并使用命令应用它:
kubectl apply -f ssh.yaml
然后,您可以使用以下命令找到负载平衡器服务的 IP 地址或主机名:
kubectl get service my-ssh-svc
在我的本地 Kubernetes 集群上,该命令返回:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-ssh-svc LoadBalancer 10.96.164.169 172.21.255.202 2222:31628/TCP 29m
然后,您可以使用以下命令 SSH 到外部 IP 地址:
ssh admin@172.21.255.202 -p 2222
然后,您可以在 Kubernetes 集群上的 pod 内进行交互式会话。
安装和配置 kubectl
要对集群做任何有用的事情,您需要下载kubectl
并将其配置为从 pod 中访问集群。使用命令下载并安装kubectl
:
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
默认情况下,pod 在/var/run/secrets/kubernetes.io/serviceaccount
下挂载了许多文件,让 pod 与主机集群进行交互。要配置kubectl
使用这些文件,将以下文件保存到~/.kube.config
:
apiVersion: v1
clusters:
- cluster:
certificate-authority: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
server: https://kubernetes.default
name: localk8s
contexts:
- context:
cluster: localk8s
user: user
name: localk8s
current-context: localk8s
kind: Config
preferences: {}
users:
- name: user
user:
tokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
现在,您可以从 SSH 会话运行kubectl
并与父集群交互,为集群管理提供一个方便而安全的环境。
构建定制的 OpenSSH Docker 映像
下载kubectl
和复制配置文件非常容易,但是 Kubernetes pods 的短暂性意味着最终容器将被删除和重新创建,迫使您再次下载和配置kubectl
。
一个更好的解决方案是将kubectl
及其配置文件烘焙成一个定制的 Docker 映像。这确保了文件在容器第一次启动时是可用的。
将以下内容保存到名为Dockerfile
的文件中:
FROM lscr.io/linuxserver/openssh-server:latest
RUN curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" && \
install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
RUN printf 'apiVersion: v1\n\
clusters:\n\
- cluster:\n\
certificate-authority: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt\n\
server: https://kubernetes.default\n\
name: localk8s\n\
contexts:\n\
- context:\n\
cluster: localk8s\n\
user: user\n\
name: localk8s\n\
current-context: localk8s\n\
kind: Config\n\
preferences: {}\n\
users:\n\
- name: user\n\
user:\n\
tokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token' >> /opt/kubeconfig
RUN printf 'mkdir /config/.kube \n\
cp /opt/kubeconfig /config/.kube/config' >> /etc/cont-init.d/100-kubeconfig
然后,使用以下命令构建定制的 Docker 映像,其中的yourdockerregistry
被替换为 Docker 注册表的名称,您可以将映像推送到该注册表中:
docker build . -t yourdockerregistry/openssh-server:latest
将 Kubernetes YAML 文件中的image
属性替换为:
image: yourdockerregistry/openssh-server:latest
在使用您的定制映像创建了新的 SSH 服务器 pods 之后,kubectl
及其配置文件就可以使用了,不需要先下载它们。
结论
在 Kubernetes 集群上运行 OpenSSH 的 bastion 主机为管理和调试任务提供了一个安全的入口点。通过定制 Docker 映像来包含像kubectl
这样的公共工具,DevOps 团队可以依赖 bastion 主机来完成公共管理任务。
愉快的部署!
使用 Octopus Terraform 提供者创建标准- Octopus Deploy
原文:https://octopus.com/blog/standards-with-octopus-terraform-provider
自从我们在 2019 年末创建以来, Octopus Deploy samples 实例已经显著增长。它有 30 多个车位。然而,这种增长是一把双刃剑。这些示例对我们的客户很有帮助,但是空间太大使得日常维护任务非常耗时,比如旋转外部 feed 密码。
在这篇文章中,我描述了我们的客户解决方案团队如何使用Octopus Deploy terra form Provider来建立标准,以使示例实例更易于维护。
维护困难
在 Octopus Deploy,每个团队都有一个针对 AWS、Azure 和 GCP 的独特沙盒。在我们的沙盒里,每个人都可以自由支配。我们可以创建任何我们需要的资源来演示 Octopus Deploy 的功能。
然而,随着团队和样本实例的增长,样本之间的一致性变得更加难以实施。每个人都使用离他们最近的地区(对我来说,美国中部代表 Azure 和 GCP,美国东部代表 AWS)。如果我们的区域没有虚拟网络、VPC、数据库服务器等,我们会创建空间。当一个新的空间被创建时,创建它的人会从他们以前工作过的例子中复制值。除此之外,还要创建云帐户、工人池、外部提要和其他变量集。
维护示例实例非常耗时。例如,我们的一些样本使用 AWS AMIs。AWS 喜欢贬低 AMIs,它要求我们搜索每个样本来决定更新哪个样本。
虽然这是一种痛苦,但它并没有引起足够的摩擦来改变任何事情。我们并不是一直在创造空间。我们会尽可能地抽查商品。但是当我们的安全团队要求我们移动 AWS 和 Azure 沙箱时,我仔细研究了一下 Octopus Terraform 提供商。
空间标准
samples 实例中的每个空间都是唯一的,展示了如何在 Octopus Deploy 中使用不同的特性。空间之间的项目很少相似。范围设置为该项目的所有空间的基础资源。这些资源是:
- 帐目
- 工人池
- 外部源
- 码头工人
- 开源代码库
- 进给
- 库变量集
- 通知(与电子邮件和 Slack 一起使用的标准消息)
- GCP
- 自动警报系统
- 蔚蓝的
- API 键(从 Octopus 调用 API 脚本时使用)
在上面的列表中,您会注意到每个云提供商的库变量集。这是空间标准项目的第二部分。我们决定在美国和英国的一些地区创建一组基础资源、VPC 或虚拟网络、子网、安全组等。这些基础资源的名称存储在那些库变量集中,因此任何空间上的任何样本都可以使用它。
使用 Octopus Deploy Runbooks 运行 Terraform 命令
我知道我将使用 Octopus Deploy Runbooks 来运行terraform apply
或terraform destroy
命令。但是我不确定那些操作手册应该放在哪里。在 samples 实例上存储 runbooks 导致了“蛇吃尾巴”的情况。他们最终可能会阻止示例部署和其他操作手册。
我选择创建一个新的实例samples-admin
,来存放运行手册。
下一步是开发一个 runbook 结构,将 Terraform 模板应用于 samples 实例上的所有空间。空间列表是动态的;添加空格应该会自动添加到流程中。Octopus Terraform 提供程序在针对特定空间时效果最佳。因此,我需要为每个空间运行terraform apply
命令。拥有一个包含 34 个静态步骤的单一 runbook,每个步骤运行terraform apply
并不能很好地扩展。为了解决这个难题,我制作了以下操作手册:
实施系统范围的标准——运行 API 脚本来实施 everyone 团队、Octopus employees 团队和 Octopus managers 团队的权限,然后调用实施 samples 实例上每个空间的空间标准。
实施空间标准——接受一个 Space-ID 作为提示变量,运行几个 API 脚本来实施保留策略,并运行应用 Terraform 模板内置步骤。
如果我想在单个空间上快速测试一个变化,Space-ID 提示变量将Spaces-1
作为默认值。
创建新空间——接受一个空间名作为提示变量,并运行 API 脚本来创建一个空间。创建空间后,它调用Enforce System Wide Standardsrun book(对单个空间使用提示变量)来确保为新空间正确设置权限和资源。
预定触发器仅调用强制系统范围标准运行手册。每次该触发器运行时,都会创建以下任务:
- 实施全系统标准
- 执行空间标准(空间-1)
- 执行空间标准(空间-2)
- 执行空间标准(空间-3)
- ...
- 执行太空标准 (Spaces-xxx)
地形基础
在开始时,我对 Terraform 的体验仅限于概念验证。这一节讲述了我所学到的如何将 Terraform 与 Octopus Deploy 一起使用。
资源所有权
向 Terraform 添加资源,无论是基础设施帐户、环境还是库变量集,都意味着它现在拥有该项目的生命周期。在 Octopus UI 中对该资源的任何修改都将在下次为该空间运行terraform apply
时被覆盖。我的 runbooks 每 6 小时运行一次,因此,在被覆盖之前,修改最多会存在 6 个小时。
我想实施的一个标准是,每个生命周期的保留策略都设置为 5 天。我们的一些空间有多种生命周期来展示一个特定的功能。通过 Terraform 管理该标准是不可能的,因为:
- 资源必须在 Terraform 文件中声明。我们不想失去通过 UI 添加生命周期的能力。向 Terraform 文件动态添加资源是一项不小的工作量。
- 如果有一两个生命周期是由 Terraform 管理的,而其他的则不是,这将会非常混乱。
- Terraform 拥有对资源的完全控制权。我不能让 Terraform 只管理保留策略。
我选择了一个 API 脚本。
你不能用 Terraform 和 Octopus UI 来管理资源。非此即彼。明确哪些资源是 Terraform 管理的。我决定在每个资源名中包含后缀TF
。
状态
Terraform 使用状态文件来确定创建、更新和删除哪些资源。当资源出现在 Terraform 文件中而不是状态文件中时,Terraform 会尝试创建它。如果资源出现在状态文件中,但没有出现在 Terraform 文件中,它将被删除。
默认情况下,状态文件存储在工作目录的子目录中。Octopus Deploy 会在每个 Terraform 步骤运行后删除工作目录。该状态文件将被自动删除。Octopus Deploy 第一次运行terraform apply
命令时,一切都正常,因为这是一个全新的状态文件,资源将不存在。但是在随后的运行中它会失败,因为资源已经存在于 Octopus Deploy 中,但是状态文件认为这些资源不存在。
无论如何,不建议将状态文件存储在本地目录中。任何敏感值都不加密地存储在状态文件中。为了存储状态文件,建议使用远程后端来存储状态文件。我选择了一个加密的安全 S3 桶。
在我最初版本的 Terraform 文件中,状态选项是内联声明的。但是,您不能将变量用于状态选项。出于无知,我决定使用 Octostache 并让 Octopus Deploy 在 runbook 运行时替换这些值。
terraform {
required_providers {
octopusdeploy = {
source = "OctopusDeployLabs/octopusdeploy"
version = ">= 0.7.64" # example: 0.7.62
}
}
backend "s3" {
bucket = "#{Project.AWS.Backend.Bucket}"
key = "#{Project.AWS.Backend.Key}"
region = "#{Project.AWS.Backend.Region}"
}
}
provider "octopusdeploy" {
# configuration options
address = var.octopus_address
api_key = var.octopus_api_key
space_id = var.octopus_space_id
}
我不喜欢那样。有太多的活动部件,很容易搞砸。任何修改这个过程的人都必须知道查看 variables.tf 和 main.tf 文件,以确保他们创建了所有的变量。
在浏览 Terraform 的文档时,我发现了terraform init
命令的-backend-config
参数。Octopus Deploy 可以给terraform init
命令添加额外的参数。因为我使用的是 S3,所以额外的命令行参数是:
-backend-config="key=#{Project.AWS.Backend.Key}" -backend-config="region=#{Terraform.Init.S3.Region}" -backend-config="bucket=#{Terraform.Init.S3.Bucket}"
现在我的 main.tf 文件如下所示:
terraform {
required_providers {
octopusdeploy = {
source = "OctopusDeployLabs/octopusdeploy"
version = ">= 0.7.64" # example: 0.7.62
}
}
backend "s3" { }
}
provider "octopusdeploy" {
# configuration options
address = var.octopus_address
api_key = var.octopus_api_key
space_id = var.octopus_space_id
}
将预先存在的项目导入状态文件
除了外部源,几乎所有的空间标准项目对每个空间都是新的。一些空间有 Docker 外部提要、Github 外部提要,或者两者都有。我不想创建新的外部提要,并强迫每个人更新他们的部署流程。在运行terraform apply
步骤之前,我选择将那些预先存在的外部提要导入到状态文件中。
这可不像跑terraform import
那么简单。我希望我的过程运行多次。我的脚本需要:
- 通过调用 Octopus API 确定提要是否已经存在于 Octopus 中。如果没有,则退出。
- 确定该提要是否已存在于 Terraform 状态文件中。如果是,那么退出。
- 将有问题的提要导入状态文件。
调用 Octopus Deploy API 对我来说不是问题。我有一些使用 API 的经验。其他一切对我来说都是新的。
我想用和 Octopus Deploy 内置步骤一样的方式登录 AWS。Octopus 使用环境变量作为凭证后端。在我的例子中,我需要为AWS_ACCESS_KEY_ID
和AWS_SECRET_ACCESS_KEY
提供一个值。
$backendAccountAccessKey = $OctopusParameters["Project.AWS.Backend.Account.AccessKey"]
$backendAccountSecretKey = $OctopusParameters["Project.AWS.Backend.Account.SecretKey"]
$env:AWS_ACCESS_KEY_ID = $backendAccountAccessKey
$env:AWS_SECRET_ACCESS_KEY = $backendAccountSecretKey
terraform init -no-color
我解决了第一个问题;接下来是将状态文件读入内存。我是通过运行带有指定参数-json
的terraform show
命令和运行ConvertFrom-Json
cmdlet 来实现的。
$currentStateAsJson = terraform show -json
$currentState = $currentStateAsJson | ConvertFrom-Json -depth 10
有了 PowerShell 中的对象,花了一些试错才发现结构(很多Write-Hosts
)。我最后说:
function Get-ItemExistsInState
{
param
(
$currentState,
$itemTypeToFind,
$itemNameToFind
)
foreach ($item in $currentState.Values.root_module.resources)
{
Write-Host "Comparing $($item.Name) with $itemNameToFind and $($item.type) with $itemTypeToFind"
if ($item.name.ToLower().Trim() -eq $itemNameToFind.ToLower().Trim() -and $item.type.ToLower().Trim() -eq $itemTypeToFind)
{
Write-Host "The item already exists in the state"
return $true
}
}
return $false
}
将项目导入状态文件的命令是terraform import [address] [id]
。该地址是在 terraform 文件中定义的。
对于定义为以下内容的资源:
resource "octopusdeploy_feed" "github" {
name = "GitHub Feed TF"
feed_type = "GitHub"
feed_uri = "https://api.github.com"
is_enhanced_mode = false
}
地址是octopusdeploy_feed.github
。id 是 Octopus Deploy 中项目的 ID。比如Feeds-1070
。
传入变量值
使用 Terraform,您可以像这样定义和使用变量:
terraform {
required_providers {
octopusdeploy = {
source = "OctopusDeployLabs/octopusdeploy"
version = ">= 0.7.64" # example: 0.7.62
}
}
backend "s3" { }
}
variable "octopus_address" {
type = string
}
variable "octopus_api_key" {
type = string
}
variable "octopus_space_id" {
type = string
}
provider "octopusdeploy" {
# configuration options
address = var.octopus_address
api_key = var.octopus_api_key
space_id = var.octopus_space_id
}
向这些变量传递值有 3 个选项。
- 命令行参数:
-var="octopus_address=https://samples.octopus.app"
或-var="octopus_address=#{Octopus.Web.ServerUri}"
- 变量文件:用扩展名
.tfvars
命名一个文件,并用值填充它。例如octopus_address = "https://samples.octopus.app"
或octopus_address = "#{Octopus.Web.ServerUri}"
- 环境变量:
export TF_VAR_octopus_address=https://samples.octopus.app
或$env:TF_VAR_octopus_address = https://samples.octopus.app
Octopus Deploy 支持所有 3 个选项。这是个人喜好的问题。我不喜欢使用. tfvars 文件,因为它必须用八进制值签入源代码管理;任何变量名的改变都需要更新多个不同的地方。我选择环境变量,因为它在 Octopus UI 中比命令行变量更简洁。
迁移到新资源
我在用几个项目做一个试点模板时学到了大部分经验。试点完成后不久,所有示例实例上都创建了这些资源。这是这个项目最简单的部分。
现在是时候迁移所有现有项目来使用新资源了。这是一项重要的任务,不是我们可以用一些 API 脚本编写的。每个样本都有自己的基础设施。我们需要修改这些示例,以使用相同的云资源基础集,并且只创建需要的内容。
拥有相同的库变量集、帐户和工作池使得迁移变得更加容易,但这仍然是手工操作。我提到这一点是因为如果您想在现有实例上实现这样的东西,您可能需要额外的手工操作。
结论
在我们开始使用 Octopus Terraform Provider 之前,复制/粘贴需要几个小时来轮换密码或在所有空间上添加新的资源组。现在只需要不到 30 分钟。
Octopus Terraform 提供者使得在 Octopus Deploy samples 实例上跨所有空间建立标准变得容易。在了解了 Terraform 的细微差别后,我开始欣赏它所能提供的东西。我现在可以在空间之间共享一些资源,但是这些资源的状态由 Terraform 管理。通过这样做,我限制了 Octopus UI 中的编辑权限,并依靠分支、恢复和拉请求等版本控制特性来创建批准流程。
愉快的部署!
配置文件格式的状态:XML 与 YAML、JSON 与 HCL - Octopus Deploy
每个应用程序、环境或系统都需要某种级别的配置。配置格式,比如基于纯文本文件的ini
格式,这些年来在流行程度上有所波动。随着应用程序和系统需求的发展,所需的配置复杂性和结构也在发展。
如今,存在许多配置格式,但有些格式在云环境中更受欢迎。XML、JSON、YAML 和 HCL 支持复杂的配置,它们各有优缺点。
在第一批结构化配置语言中,XML 是在 1998 年 W3C 的 1.0 版规范中引入的。XML 是为通用和简单而设计的,它支持许多传输语言、服务和配置格式。
尽管 XML 无处不在,但更新的配置语言正在取代 XML 的主导地位。尽管如此,在许多情况下,XML 的结构化特性及其灵活性最适合复杂的配置。
JSON 等简单的配置语言适用于许多应用程序,但是当您需要适当的验证、模式和名称空间支持时,XML 通常是最好的。
XML 的优势
- XML 是一种通用语言,它可以很容易地从一个公共语法中实现不同的格式。
- 模式是为自定义类型的验证和创建而存在的,而命名空间可以避免元素之间的冲突。
- XPath 和 XQuery 简化了对 XML 文档的复杂查询。
XML 的缺点
- XML 语言很冗长,通常包含冗余的语法。
- 更高的详细程度会增加存储容量和带宽需求。
- 由于元素的描述性,不容易被人理解。
JSON 在 2013 年被认可为正式规范,自 21 世纪初就已经存在。它在许多不同的应用程序中使用,强调可读性和简单的语法。
JSON 源自 JavaScript,是独立于语言的。JSON 解析器适用于许多不同的编程语言和工具。
JSON 是 XML 更简单的替代方案。凭借简洁易用的语法,JSON 接管了许多配置设置。开发人员欣赏 JSON 提供的简单性和处理速度。
JSON 是当今使用最普遍的语言之一,尽管它的设计存在固有的缺点,但仍然很受欢迎。
Advantages of JSON
- 与 XML 相比,由于语法更紧凑,因此更易于阅读。
- 有限标记的简单语法。
- 快速系统和语言解析。
- JSONPath 和 XPath 一样,可用于复杂的查询。
JSON 的缺点
- 有限的数据类型支持,仅包含字符串、数字、JSON 对象、数组、布尔值和空值。
- 没有名称空间、注释或属性支持。
- 简单的结构可能不支持复杂的配置。
最初作为另一种标记语言而为人所知,官方定义改为表达数据焦点而不是文档焦点。YAML 创建于 2001 年,没有正式的 W3C 规范。最新的 YAML 1.2 规范自 2009 年以来一直保持稳定。
YAML 非常易读,因为它旨在定义配置,而不是作为一种数据传输语言。
尽管 YAML 看起来与 JSON 不同,但 YAML 是 JSON 的超集。作为 JSON 的超集,一个有效的 YAML 文件可以包含 JSON。此外,JSON 也可以转换成 YAML。YAML 本身也可以在其配置文件中包含 JSON。
YAML 的优势
- 异常易读的语法。
- 紧凑语法,使用 Python 风格的缩进来表示结构。
- 支持独立于语言的对象类型。
int
、binary
、str
、bool
等定标器或map
、set
、pairs
、seq
等集合。
YAML 的缺点
- 缩进格式容易出现语法和验证错误。
- 由于缺乏跨所有语言的特性,某些类型的可移植性可能不存在。
- 由于 YAML 的声明性,调试很困难。
- 不存在断点和类似的功能。
作为唯一列出的特定配置语言,HCL 并不打算取代 YAML 或 JSON 等语言。HCL 是一种工具专用语言,用于 Terraform 工具集。HCL 在视觉上类似于 JSON,只是添加了独特的数据结构。
HCL 是三个子语言的组合:
- 结构的
- 表示
- 模板
这些子语言共同创建了一个 HCL 配置。该模式有效地描述了环境配置,这是 Terraform 的主要目的。
其他工具集和语言能够使用 HCL 吗?其他语言也有解析器,比如 Go、Java 和 Python。在这种情况下,可以使用 HCL 作为配置语言。这些解析器通常不是本地包。
盐酸的优点
- 使用盐酸时,使用 Terraform 要容易得多。
- 除了兼容之外,视觉上和功能上类似于 JSON。
- 通过属性和模板等特性,HCL 比竞争语言(如 JSON)功能更加全面。
盐酸的缺点
- 正如 HashiCorp 的名字所证明的那样,HCL 主要是为其产品开发的,开发也是为了这些目的。
- 尽管声明了与 JSON 的兼容性,但两种语言之间的严格转换由于映射某些语言结构的模糊性而变得复杂。
- 作为一门新语言,许多特性仍在开发中,工具包的成熟度仍在提高。
结论
配置语言有很多种,各有优缺点。XML、JSON、YAML 和 HCL 在某种程度上是互操作的,但是根据您选择的语言,权衡是必要的。
简单的应用程序和环境受益于结构化配置。随着配置复杂性的增加,一种语言相对于另一种语言的缺点也在增加。
为了选择正确的语言,在应用程序生命周期的早期确定配置目标是有帮助的。
随着配置本身成为 DevOps 环境和云计算中的一等公民,为您的应用选择正确的语言对于确保成功变得至关重要。尽管这些配置语言都可以很好地工作,但是您需要权衡成功的配置框架的优点和缺点。
愉快的部署!
Adam Bertram 拥有 20 多年的 IT 经验,是一名经验丰富的在线商务专家。他是多家科技公司的顾问、微软 MVP、博客作者、培训师、出版作家和内容营销人员。在adamtheautomator.com关注亚当的文章,在 LinkedIn 联系,或者在 @adbertram 的 Twitter 上关注他。
使用 Seq - Octopus 部署的结构化日志记录
在一个新项目的第一天,许多团队安装一个 CI 服务器来构建和测试他们的应用程序,并安装一个部署服务器(如 Octopus)来将新版本应用到他们的测试和生产环境中。
这种设置缺少的是团队在应用程序到达时对其进行监控的方法。 Seq 是来自 Nick 的公司的一个项目,刚刚达到 1.0 ,旨在填补这一空白,从开发到生产一路平稳地提高可见性。
已经有了强大的日志收集工具,但是很少有适合 Windows 和。NET 工具链足够好,他们可以在“第一天”的几分钟内安装。Seq 附带了一个用于 Windows Server 的 MSI,以及与流行的。NET 日志框架。
如果你用像 p & p 的 SLAB 或 Serilog 这样的框架编写结构化日志,那么 Seq 的一些特性也使得过滤和关联事件变得特别好。
我最喜欢 Seq 的一点是,它让收集应用程序中发生的事件变得像写日志一样简单。这意味着在开发过程中,您可以使用结构化日志记录来记录正在发生的事情,而不必太担心以后如何使用它。然后只需打开 Seq,这些日志突然变得比磁盘上的日志文件更有用、更容易访问。这是一个非常值得使用的工具。
Seq 有几个受支持的专业版和一个免费的开发者版,你可以从 Seq 网站下载。希望您会发现结构化日志和 Seq 是开发和操作您的应用程序的有用工具!
使用八达通订阅-八达通部署
为了介绍新的 Octopus Subscriptions 特性,我们认为展示一个简单的例子是有用的,这个例子可以在 Octopus 内部发生重大事件时通知一个空闲通道。
为此,我们需要以下内容:
- 一只章鱼 3.5。*支持订阅的实例
- 一个 Zapier 帐户,用于捕获 Octopus 的 webhooks 并通知我们的 Slack 频道
- 用于接收通知的备用帐户和通道
我们将通过这些步骤来展示如何构建一个基本的订阅通知。
松弛的
在 Octopus,我们喜欢 Slack,当重要事情发生时,它是通知我们的最佳人选(因为我们几乎不再检查我们的电子邮件:)。
对于这个例子,我们设置了一个名为“critical-stuff”的通道,我们希望在这里接收我们的关键通知。
扎皮尔
我们将使用 Zapier 为我们托管一个 webhook。Zapier 将包含一个触发器(webhook),然后运行一个操作(通知 Slack)。这个想法是,Octopus 将调用这个 webhook,无论何时发生关键事件,Slack 都会得到通知。
注意:我们使用 Zapier 的试用帐户来说明我们的例子。你可以选择编写自己的 webhook,自己集成 Slack API,但是 Zapier 让这变得非常简单,可以快速启动并运行,节省了我们的时间。
要创建触发器,请执行以下操作:
- 登录 Zapier 并创建一个新的 Zap。
- 从内置应用列表中选择“Webhooks ”,然后创建一个等待新帖子的“Catch Hook”。
- 在 webhook 设置的最后一步,Zapier 会给你一个 webhook 的 URL。记住这个值以便以后使用!我们将在 Octopus 订阅中使用该值。它看起来会像这样:
https://hooks.zapier.com/hooks/catch/.../.../
为了测试这个 webhook 的连接性和有效负载,您现在需要进入 Octopus 并设置您的第一个订阅。但是请确保您的 Zapier 屏幕保持打开,以便我们可以确认测试工作,并在稍后继续添加我们的 Slack 操作。
章鱼 3.5。*
你可以从我们的下载页面下载 Octopus 的最新版本。像往常一样,在升级之前确保你有一个备份。
一旦你安装了 Octopus 3.5,你可以从配置>审计>订阅菜单访问订阅。我们现在可以创建一个订阅,一旦事件可用,它就调用 Zapier webhooks。
首先,我们将创建一个名为“通知我所有事情”的订阅,请务必粘贴您的 Zapier webhook URL:
一旦我们保存此记录,Octopus 将记录一个订阅已创建的事件。Octopus 中的订阅调度程序将处理这个事件并触发 Zapier webhook。
如果您现在跳回 Zapier 屏幕(最多等待 30 秒,让 Octopus 进行处理),您的 Zapier 测试应该已经成功接收到来自 Octopus 的有效载荷:
回到扎皮尔整合我们的懈怠行动
现在 Octopus 已经成功地通过我们的 webhook 与 Zapier 进行了对话,我们可以集成 Slack 操作了。
现在,您可以继续设置您的操作:
- 在 Zapier 的侧边菜单中添加一个动作。
- 从流行应用列表中选择“Slack”(或使用搜索功能查找 Slack)。
- 从操作列表中选择“发送频道消息”。
- 将您的 Slack 帐户连接到 Zapier(按照提示操作)。
- 到达编辑步骤屏幕后,您可以选择想要接收通知的松弛通道。在本例中,我们选择了之前创建的“关键材料”通道。
现在最酷的部分来了:我们将向 Slack 发送的消息:)
Zapier 知道 Octopus 将在其 webhook 中发送的有效负载(因为我们之前发送了一个测试),所以我们可以为消息插入一个字段,并从 Octopus 为每个事件交付的有效负载对象中进行选择。例如:
在我们的例子中,我们想知道“有效负载事件消息”。
我们现在可以继续保存 Zapier 操作,并继续进行测试,就像这样,当 Octopus 发生活动时,我们现在会在 Slack 中收到通知:
记住保存我们的 Zap 并给它一个名字(确保它已启用),我们现在可以返回到 Octopus 并修改我们的订阅,以便只根据某些标准通知我们。
回到 Octopus(完成我们的订阅)
在这种情况下,我们只想知道关键事件,如部署失败或自动部署被阻止。我们可以很容易地修改我们之前所做的订阅来实现这一点:
- 用八达通开通我们的订阅。
- 选择“通知我所有事情!”我们之前做的订阅。
- 将订阅的名称编辑为“关键事件通知”。
- 使用事件组过滤器仅选择关键事件。
- 保存。
现在,只要 Octopus 内部发生重大事件,Octopus 就会通知我们的 Slack 频道(通过我们的 Zapier webhook)
您可以在我们的订阅文档中了解更多关于订阅的信息,包括向特定团队发送电子邮件和确定订阅范围。
支持@ Octopus - Octopus 部署
支持是章鱼核心的一部分;我们不认为这是额外的或成本负担。那么,这在实践中到底是如何运作的呢?
很简单,每个人(包括我们的 CEO)都支持。我们有一个以支持为中心的运营团队来处理大部分负载;然而,每个开发人员都会回答支持问题。这是双向的,因为运营团队可以并且确实提出问题并为 Octopus 检查代码修复。
当我发送电子邮件/写论坛帖子时会发生什么?
你可能已经知道,八达通是一家澳大利亚公司,我们所有的员工(除了一个人)都在澳大利亚。一个有趣的副作用是,来自我们海外客户(尤其是美国和英国)的支持请求会在一夜之间到达,让我们早上第一件事就是处理积压的请求。
我们通过在每天早上 8:30 执行支持分类电话来处理这个问题,在那里我们检查每一张票并找到支持的最佳途径(要么在运营团队内部,要么直接升级到开发人员)。我们尽量保持简短(通常 30 分钟或更短),并且寻找关键词,而不是试图诊断每个问题。为了帮助我们,我们有一个 Trello 板,上面列出了每支球队及其支持领域。
重要的是要记住,这里的目标是速度,而不是 100%的准确性。
在这个过程中帮助我们的是两个自主开发的工具,Supportflow
和Tendersmash
。Tendersmash
是我们当前支持论坛(Tender
)的前端用户界面,显示所有新创建和未分配的论坛帖子。它允许我们以最小的摩擦将支持请求快速分配给个人和团队。Supportflow
一旦分配了请求,就开始工作,在适当的 Trello board 收件箱中创建一张卡片,提醒团队支持请求。一些团队还启用了 Trello > Slack integration,以便他们在分配球票时得到提醒。
分诊,知道了。接下来呢?
通常,我们的大多数团队还会有一个晨会,在此期间,他们会快速浏览直接分配给他们团队的任何支持票证,再次寻找关键字和任何可能有助于为任何给定问题提供快速准确解决方案的内容。
一旦我们的票证通过此流程,它们都将有一个所有者,该所有者有责任确保该问题的解决。我们说的解决方案不是指I fixed it for that customer
,而是指I fixed it for this customer and all future customers
,要么通过更新代码来解决 bug,要么改变 UI 以使操作更容易(或可能),要么更新我们的文档。
因为我们致力于为您提供最佳体验,所以在回应请求时,我们会尝试超越最显而易见的下一步。只要有可能,我们会尝试预测可能与您的案例相关的其他问题。如果我们可以提前建议可能的调查途径,通常只需指出我们认为问题在哪里就可以更快地解决问题(哦,他们认为这是网络问题,也许我应该检查一下,看看这里是否有防火墙)。
我们修好了吗?
虽然我们的目标是在第一次响应时解决每一个问题(你不能击中你不希望的,对不对?),我们知道这并不总是可能的。有时我们需要实现一个代码修复,有时我们没有足够的信息来诊断问题,有时跟踪问题并不容易。重要的是,我们始终让您了解我们正在做的事情,并根据对您的影响调整我们的响应(如果您报告生产中断,请放心,我们会有很多人帮助您尽快上线)。
我希望这能让你对 Octopus 的支持流程有所了解,如果你有任何问题,随时联系我们!
支持八达通部署客户-八达通部署
当我开始做八达通的时候,“商业计划”非常简单。归结起来有三个步骤:
- 打造伟大的产品
- 以出色的客户支持和服务为后盾
- 客户会(希望)找到我们,并告诉他们的同事我们的情况
我们经常谈论我们产品的改进,但我认为我们没有谈论太多的一件事是我们对客户服务的改进。在过去的几年里,它已经走了很长的路,所以我想花一些时间来强调这一点,并鼓励你们伸出援手。
支持
如果你在 2018 年给我们的支持团队发电子邮件,你会收到非常有用、知情的回复,但如果你在美国或英国,回复可能会在你睡觉时收到,因为支持团队位于澳大利亚。事实上,当时你只有 20%的机会在 2 小时内收到回复。
2018 年,我们开始有意壮大我们在美国和英国的支持团队。今天,我们的支持团队由 17 人组成,他们是全职的八达通员工;我们不外包或分包其中任何一项。你有 63%的几率在 1 小时内得到回复,有 90%的几率在 2 小时内得到回复。我不认为我们牺牲了质量。这是一位客户最近的报价:
“我必须说这是一次出色的支持互动,与能够真正调查和解决问题的人打交道是一个很好的改变。向你和章鱼致敬!”
我们的支持团队不是唯一提供客户服务的团队。如今,三个团队共有 39 名员工,他们致力于帮助我们的客户!
解决方法
您可能已经与我们的支持团队进行过互动,但是您可能还没有见过的一个团队是我们的解决方案团队。这是一个由美国和英国的 7 名高级工程师或运营人员组成的团队,他们不仅擅长 Octopus,而且擅长 Octopus 集成的所有 CI/CD 工具。他们可以建议你如何让 Octopus 与其他工具一起使用,了解你的总体 CI/CD 目标,以及如何帮助你实现这些目标,即使这些建议与你正在开发的其他工具有关。他们还可以帮助定制集成,例如深度利用我们的 API 来完成通常不支持的事情。
销售和销售工程
我们在 2020 年真正开始建立的下一个团队是我们的销售和销售工程团队。这个团队由 15 人组成,他们分布在美国、英国和澳大利亚。如果你与他们一起工作,你通常会发现自己在与两个人交谈:一个是销售工程师,他对我们的产品有深刻的理解,可以向你展示 Octopus 是如何工作的;另一个是客户经理或客户成功经理,他的目标是确保 Octopus 是正确的选择,确保你从产品中获得价值,并帮助评估和购买过程。
有趣的是,销售中没有人被委托,他们作为一个团队工作。虽然我们毫不掩饰我们认为 Octopus 很棒,可以帮助你,但如果它不是很合适,请放心,没有人会强迫你;没有议程。我认为与其他软件供应商相比,这使我们的销售团队脱颖而出。老实说,作为一名工程师,这是我想与之合作的销售团队。
我们是来帮忙的!
正如我在开始时所说的,我们经常对新产品的发布大做文章,但回顾过去几年,我们对客户服务的改进是我们的一个亮点。
我写这篇文章的原因是想说:如果你发现自己遇到了任何事情——一个 bug,不知道如何做某事,在产品/集成中发现了一个缺口,不确定最佳实践是什么,或者需要说服你的老板 Octopus 是正确的解决方案,请联系我们!这里有一大群人渴望展示他们能有多大的帮助。
从系统管理员到 SRE - Octopus 部署
DevOps 无处不在!它被用作标签、产品名称和职位名称。Digital Trends 将 DevOps 工程师列为 2020 年第三大最佳技术职位。Indeed.com 有超过 4000 份 DevOps 工程师职位的招聘信息,LinkedIn 有超过 3 倍的数量,超过 13000 份招聘信息。
DevOps 就不用说了,辣!
网站可靠性工程师(SRE)这个职位是近年来从谷歌中出现的。SRE 还没有它的前身那么受欢迎,但它经常被招聘人员用作同义词。
在这篇文章中,我分享了我对这个行业的新人如何渴望这些角色的想法。
看招聘信息可能只会让你困惑。从 10 年的 C#经验到对 Active Directory 域信任的深入了解,一切都列在工作描述中。面对如此广泛的需求,您应该从哪里开始呢?
首先要理解德沃普斯和 SRE 是高级工作角色。您可以通过申请服务台的入门级职位和系统管理来开始 web 开发职业生涯。德沃普斯和 SRE 还没有那个入门位置。通常,DevOps 从业者已经在技术领域工作了几年。这是因为 DevOps 和 SRE 角色需要了解开发和基础设施。很难同时学习这两个学科,这就是为什么大多数从业者都有这两个学科的背景。
我在服务台开始了我的职业生涯。晋升为系统管理员,并最终获得了一份高级系统工程师的工作。当我第一次通过 Twitter、博客帖子和会议讨论听说 DevOps 时,我认为它是初创公司的专利。所以我忽略了它,直到我对自动化的热情将我带到了作为代码的基础设施的 DevOps 实践中。我第一次听说 DSC(理想状态配置)是在嘉宾 Steve Murawski 的 PowerScripting 播客的第 275 集。将基础设施作为代码是将我带入 DevOps 世界的唯一想法。它是所有操作系统变体的系统管理员可以用来过渡到 DevOps 的门户。
DevOps 不是工具!这实际上是一些人在会议上喊的,他们看上去涂着勇敢之心的脸,敲着战鼓。他们的断言可以说是正确的。但是你使用的工具塑造了你使用的语言,决定了你如何与他人互动来完成你的工作。你实际上生活在你使用的工具中。无论是 email,Slack,还是像大多数开发者一样,Git。
在传统公司中,您会发现开发人员使用一套工具,而系统管理员使用另一套。这只会强化筒仓。
如果你曾经希望打破这些孤岛,你需要做的不仅仅是改变头衔,组建联合发行小组,并告诉人们一起工作。你需要给每个团队互动的机会。如果他们用于工作的工具重叠,这种情况就会自然发生。
共享工具创造了一种共享语言。这不是高层领导的建议,这适用于个人贡献者。如果您是一名系统管理员,希望进入 DevOps 角色,这适用于您。DevOps 与工具无关,但工具是一个很好的起点。
学习从命令行编码
自动化是起点。这是一个起点,因为它让你编写代码。如果你看一下 DevOps 工程师职位招聘中列出的技术,你会注意到一个趋势。他们都提到了某种形式的编码。这并不意味着你需要参加编码训练营,学习 Java 或 C#。任何脚本或编程语言都可以。重要的是你要选择一门实用的语言来应用到你的工作中。如果你在 Linux 上工作,Bash 和 Python 是一个天然的组合。如果你在 Windows 上工作,PowerShell 是个不错的选择。
开始学习一门编码语言很容易。有数不清的博客帖子、YouTube 视频、书籍和 Pluralsight 课程供你使用。
难的是弄清楚如何在工作中应用它。我建议你从自动化糟糕的事情开始。找到日常和平凡的任务,并努力实现自动化。你选择学习的脚本或编程语言将成为你工具箱中的锤子。与任何工具系列一样,锤子只是一个开始。您需要添加其他工具。在基础设施自动化的背景下,这些工具被命名为 Ansible、Chef、Puppet、Terraform、Azure Resource Manager 模板和 CloudFormation。这些工具很有价值,因为它们是抽象的。留给您更少的代码编写和更好的框架来管理您的基础设施。如果代码形式的基础设施是门户,那么自动化就是打开它的方式。
从源头开始
依靠 shell 历史和文件共享来存储代码的日子已经一去不复返了。你需要更好的东西。你需要源代码控制。源代码管理是一个广泛的主题,有整本书专门讨论它。好消息是不需要深入理解。你只需要足够的信息就能胜任。只需学习几个命令,您就可以立即投入使用。
Git 是目前最流行的源代码控制系统。GitHub 是目前最受欢迎的托管 Git 提供商。在 GitHub 上创建一个帐户并上传你的代码是一个很好的开始。GitHub 允许你创建公共和私有的存储库。如果你选择上传到一个公共库,确保你的代码是干净的并且可以开源。
创建公共存储库也可以作为您工作的公共工件。你可以用它们来展示你的技能和知识,也可以作为参考。不是所有你学到的东西都会留在你的脑子里,但是通过使用 Git,它会被记录在你的提交历史中。
学习 Git 会花一些功夫,但是一旦学会使用,就不会走回头路了。事实上,如果你开始在工作之外使用 Git,没有它你就不想工作了。您的组织中可能有人正在使用 Git 或其他源代码控制系统。四处询问并找出如何访问源代码控制系统,然后为您的团队创建一个存储库并帮助他们。让你的团队加入比让你的老板加入更困难。你的团队会有一个学习曲线,但在你说出审计这个词后,你的经理会同意的。
Git 的所有优势都不会马上显现出来。关注它在前期和未来增加的价值。这种直接价值是通过提交历史、增强的协作和比文件共享更容易管理的可审计代码。然而最重要的是,源代码控制开启了其他一切。
拉式请求意味着部署
传统上由发布工程师管理的工具已经成为任何编写代码的人的赌注。
是的,脚本也是代码。
发布工程是软件工程的一个分支,主要关注源代码的编译、汇编和交付。关于发布工程有很多你不需要知道的,但是有两种类型的工具是你不想离开的;持续集成和持续交付统称为 CI/CD。
TeamCity、Octopus Deploy、Jenkins、Azure DevOps 和 GitHub Actions 都是生活在这个领域的工具。专注于工具会让您感到困惑,因为这些工具都包含允许您构建 CI/CD 系统的特性。但是,持续集成和持续交付实际上是软件工程实践。
简单来说,将持续集成视为软件构建阶段的自动化,将持续交付视为软件发布和部署的自动化。持续集成、持续交付和源代码控制共同构成了一个发布管道。发布管道是一个概念性的过程,它将您的代码从源代码带到产品中。通过源代码控制和 CI/CD,您可以摆脱点击按钮的业务。
场景:您已经自动化了新基础设施的部署、供应和配置,并且所有的基础设施代码都存储在源代码控制中。您正处于可以使用这种自动化来扩展和缩减基础架构的阶段。然而,现在已经很难确定最后部署的是哪个版本的代码,部署是在命令行手动完成的,并且代码库中经常会出现输入错误。
解决方案:构建基础设施代码的发布管道。通过使基础设施代码程序化和非交互式,自动化部署基础设施代码的所有步骤。然后,您可以开始构建发布管道。从源代码控制开始,发布管道将由提交和/或拉请求触发。接下来,在构建阶段 lint 您的代码,以减少错别字和控制代码质量。在您的代码通过 lint 和其他测试之后,您可以在发布阶段部署它,这将部署基础设施代码。实现发布管道将您从自动化的手动部署中解放出来。从某种意义上说,这是自动化。目标总是点击更少的按钮。
结论
一切都指向自动化,正是自动化让这一切成为可能。没有自动化,门户就不会打开。进入门户,源码控制是你的据点。有了据点,发布管道将带你穿过迷雾,进入开发人员未知的领域。学习本文中列出的技术和实践将会在你的 DevOps 技能树上打开许多可能的路径。你下一步去哪里,将取决于你。
资源:
Josh Duffney 是一名现场可靠性工程师。他写,做演示,教,而发关于自动化、DevOps、云和优化输出同时最小化输入。
Octopus CLI 的命令行选项卡完成- Octopus 部署
原文:https://octopus.com/blog/tab-completion-for-the-octopus-cli
作为一名开发人员,我喜欢我的 IDE 和文本编辑器在我进行大量输入时提供有用的提示。这不仅在我知道我要做什么的时候加快了我的速度,而且在我学习如何使用工具或框架特性的时候也加快了我的速度。回想起来,我学习 C#编程的速度可能要归功于像 IntelliSense 这样的特性。
我最近对我们自己的 Octopus CLI 感到有点沮丧,因为我不得不在我的 shell 和浏览器之间切换,以检查我需要传递哪些标志。
为什么我的命令行体验应该与 Visual Studio 有所不同?即使在最好的情况下,我也记不住确切的 CLI 调用,更不用说在我面临修复某个东西的压力时了!
壳牌完井救援
好消息是,只需少量的前期投资,您就可以调整您的命令行体验,为您提供这类提示并自动完成短语和选项,CLI 创建者可以对他们的产品进行更改,使事情变得更加简单。
我感到沮丧的结果是,我们的 CLI 中增加了一些功能来支持和配置流行 shells 中的 tab 补全。
如果您获得了 Octopus CLI 的最新版本,您也可以将所需的脚本安装到流行的 shells 中:
# install into your ~/.zshrc
octo install-autocomplete --shell zsh
# install into your ~/.bashrc
octo install-autocomplete --shell bash
# install into your pwsh $PROFILE
octo install-autocomplete --shell pwsh
# using legacy powershell on windows?
octo install-autocomplete --shell powershell
# unsure? do a dry run first and show the result without saving
octo install-autocomplete --shell bash --dryRun
一旦安装,只要点源或重启你的外壳,你就可以完成所有的事情!这就是它的作用:
制表符补全是如何工作的?
在高层次上,大多数您喜欢的 shells 都提供了内置的命令来接受来自外部资源的建议,比如一个文件或另一个应用程序。这意味着你可以为任何命令行工具编写它们。这些内置功能的工作方式大致相同:
- 注册遇到 tab 键时要调用的命令。
- 在 tab 键之前处理文本输入。
- 从源(列表、其他命令)读入建议。
- 如果有多个建议,向用户显示选择。有些外壳甚至允许用户选择一个!
- 如果有一个建议,那就用它。
Systemd 内置了对bash
和zsh
的完成支持,但不支持pwsh
。所以,让我们实现它吧!一个小例子是子命令systemctl status
,它通过名称获取单个服务并显示其状态。
在zsh
和bash
中,我完成了可能要显示的服务的标签,所以让我们在pwsh
中实现一个类似的东西。
每当按下tab
键时,我们可以让pwsh
使用这个列表,用下面的脚本向我们提供提示:
# Register our script block against usages of systemctl
Register-ArgumentCompleter -Native -CommandName systemctl -ScriptBlock {
# Declare the expected parameters for this feature
param($wordToComplete, $commandAst, $cursorPosition)
# Split the incoming words into an array
$words = $commandAst.ToString() -split ' '
# The first word is our sub-command
$subCommand = $words[0]
# The last word is our search term
$searchTerm = $words[-1]
# if the status sub-command was chosen
if ( $words -eq 'status' ) {
# Find all enabled unit file names
$services = systemctl list-unit-files | grep enabled | cut -d' ' -f1
# Find some suggestions based on our search term
$suggestions = $services | select-string $searchTerm
# Provide parameter name suggestions to pwsh
$suggestions | % {
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterName', $_)
}
}
}
如果我们将其写入$PROFILE
,然后用. $PROFILE
对其进行“点源”,您将会看到您现在可以完成任何已启用的服务。
Octopus CLI 做了什么来简化这一过程?
上面的例子要求我提前做好处理建议的工作。如果最了解其子命令的systemctl
本身提供了这些完成,会怎么样?这就是像dotnet
、nuke
、octo
这样的工具所做的;它们提供了自己的子命令来处理建议方面的事情。您可以使用最新版本的 Octopus CLI 进行尝试:
octo complete list
# returns subcommands starting with 'list'
list-deployments
list-environments
list-latestdeployments
list-machines
list-projects
list-releases
list-tenants
list-workerpools
list-workers
这使得上面的注册更加简单。现在,它们看起来更像这样:
Register-ArgumentCompleter -Native -CommandName octo -ScriptBlock {
param($wordToComplete, $commandAst, $cursorPosition)
$parms = $commandAst.ToString().Split(' ') | select -skip 1
# throw everything at `octo complete` and let it figure things out
octo complete $parms | % {
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterName', $_)
}
}
包扎
Shell 完成是一个很好的节省时间的方法,您可以在自己的工具中编写对它的支持,或者构建对现有工具的支持。
请在评论中告诉我们你是如何使用 Octopus CLI 的!你希望在命令行中使用什么工具更容易?
Octopus CLI 现在能够帮助您在您最喜爱的 shell 中快速实现这一点。尽情享受吧!
驯服北海巨妖:如何保护您的数据-八达通部署
原文:https://octopus.com/blog/tame-the-kraken-protect-your-data
已经决定:您的公司将开始使用 Octopus Deploy 进行数据库部署。
也许你有这个想法,也许没有,但是船已经起航了。频繁发布的海啸正向您的数据库袭来,您的工作是确保数据安全存放并避免违规。
在这篇文章中,我将直接向数据守护者解释他们需要知道什么来确保数据的安全。我的希望是,虽然这些数据守护者可能最初阅读了这个标题,并想象章鱼部署为一个具有威胁性的怪物,执意攻击他们的数据,但在这篇文章的结尾,他们会想象克拉肯是一个强大而忠诚的仆人,警惕地保护他们珍贵的数据,并使用其可怕的力量来解锁之前无法想象的财富。
在我们开始之前:呼吸。一切都会好的。
Octopus Deploy 的主要目标是使部署更容易、更可靠、更安全。通过使用 Octopus Deploy 进行更改,您可以获得一些重要的安全好处:
- 更可靠的部署:许多部署因人为错误而失败。当人们转向自动化部署时,大多数人会看到故障率和“平均恢复时间”(MTTR)有一个数量级的改善。
- 锁定生产:当通过 Octopus 进行常规部署时,很可能会对生产数据库用户进行筛选。
- 职责分离:如果职责分离对您很重要,您可以配置您的 Octopus Deploy 用户,以便(例如)同一用户不可能创建发布并将其部署到生产中,或者在没有特定组或个人的审计批准的情况下执行任何生产部署。在走这条路之前,请阅读更改咨询板不起作用。
- 审计:Octopus Deploy 完成的每一项任务都进行了有效的版本控制和日志记录。审计员喜欢这个。
也就是说,任何工具都取决于使用它的人。Octopus Deploy 是一种强大的野兽,但当它的训练者理解它并有效控制它的力量时,它将是最有效的。
亲爱的 data guardian,无论您是 DBA、开发人员、架构师、系统管理员、CISO、DPO 还是其他人,如果您了解您的数据并热衷于保护它的安全,您都希望积极参与了解 Octopus Deploy 是如何配置和保护的。你可能是唯一有资格保持 Octopus 部署诚实的人,方法是审查实施/安全计划,并确保重要的检查作为管道的一部分进行,一开始可能是手动的,但从长远来看最好是自动的。
作为数据守护者,您比任何人都更了解部署失败的原因以及您的数据会受到怎样的危害。您的团队需要您的专业知识来确保避免这些错误,不仅仅是在单个部署中,而是在每个部署中。
步骤 1:理解 Octopus Deploy 如何工作
为了有助于 Octopus Deploy 的安全实施,首先理解它的工作原理是很重要的——特别关注安全性。
Octopus Deploy 之所以得名,是因为它的体系结构类似于 Octopus,以“服务器”为中心,将包和部署脚本发送到部署文件和执行命令的各种目标。
Octopus 用户可以通过 Octopus 门户网站或脚本与服务器交互,通常使用 PowerShell、REST API 或 T2 Octopus CLI。
有各种类型的部署目标。最常见的是 Octopus“触手”(或代理),这是一种运行在目标机器上的服务。例如,在服务器、虚拟机或容器等上。Tentacles 可以在你自己的基础设施或你喜欢的云提供商的 Windows 或 Linux 上运行。
Octopus 服务器和触手之间的通信使用特定端口上的安全 TLS 连接,使用公钥加密。更多详情请点击此处。这避免了使用密码的需要。只要私钥保持安全,另一个系统就不可能冒充服务器或触手。
Octopus 服务器可能位于您的生产安全边界之外,因为它可能还负责部署到较低的环境(开发、测试等)。).关于在哪里安装你的八达通服务器的更多信息可以在这里找到。
然而,触角需要生活在每个环境(开发、测试、生产)的安全边界内,以便它们可以自由地与您的目标数据库通信;您可能需要在特定端口上设置防火墙规则,以允许 Octopus 服务器和 Octopus 触手之间的通信。
如果您担心服务器或代理的安全受到威胁,可以通过删除防火墙规则来切断它们之间的通信。
Octopus Deploy 服务器还需要自己的 SQL Server 数据库。应将此数据库视为生产数据库,并像管理包含关键管理数据的任何其他生产数据库一样对其进行管理。了解更多关于章鱼数据库的信息。
大多数 Octopus Deploy 配置数据以纯文本形式存储在 Octopus Deploy 数据库中,但是敏感数据使用“主密钥”进行了加密,该密钥可以在 Octopus Deploy 服务器本身上获得。没有此主密钥,无法恢复 Octopus Deploy backup 的备份,因此必须:
- 将主密钥保存在安全的密码管理器中,以防您的八达通服务器死机。
- 管理谁可以访问 Octopus Deploy 数据库,就像它是您的一个生产数据库一样。
- 严格管理对运行 Octopus Deploy 软件的机器的访问,因为这是存储主密钥的地方。请注意,这不同于管理谁注册为 Octopus Deploy 的常规用户并可以访问 Web 门户。
第二步:使用跳转框
对于大多数东西,人们倾向于将触手安装在托管你的东西的服务器上。这使得文件的传输和脚本的执行更加简单,非常适合部署 web 应用程序。然而,对于数据库,通常使用“跳转框”。jump-box 是一台独立的机器,它位于目标数据库附近,安装了执行部署所需的所有工具。
跳转框对于数据库部署非常有用,因为数据库部署通常是通过执行部署脚本来驱动的,而不是复制文件。因为这些部署脚本不需要从数据库服务器本身执行,所以在数据库服务器上运行触角没有优势。相比之下,使用跳转框有一些好处:
- 一个额外的安全层:在跳转框上运行触手的用户需要访问目标数据库来运行脚本,以便执行部署。然而,通过使用跳转框,Octopus 组件(服务器或触手)都不需要访问数据库服务器的主机。这使得数据库管理员可以更细致地分配他们授予 Octopus Deploy 的权限,并且允许管理员通过对数据库服务器本身的简单设置来撤销该访问,而无需直接处理防火墙或 Octopus。
- 改进的数据库性能和保护:复杂、高风险或资源密集型操作(如模式比较)可以在跳转框上执行,而不是在数据库服务器上执行。这避免了占用数据库主机服务器上的系统资源,并防止数据库出现故障。如果出现问题,让跳转框崩溃要比数据库服务器崩溃好得多!
您需要授予触手更新数据库的权限。通常,这是通过创建一个“octopus”用户(或类似用户)来完成的,该用户拥有部署数据库的适当凭证。对于运行在 Windows 上的 SQL Server,这可能是一个对适当的目标数据库具有db_owner
权限的 Active Directory 用户,并且它将使用 Windows 身份验证进行身份验证。然而,如果您可以对您的用例使用较少的权限,那就太好了。
整个过程可能如下所示:
第三步:锁定它
我前面提到过,Octopus Deploy 服务器可能位于您的生产安全边界之外,但是眼尖的读者会注意到上图将其列在自己的“PROD”安全边界之内。Octopus 服务器需要能够部署到所有环境中,因此它不能完全生活在一个环境中。但是,由于它可用于更新生产,因此也应被视为生产资产,并按此进行保护。
这提出了几个需要解决的问题。
- Octopus Deploy 既不是单纯的“开发工具”,也不是单纯的“运营工具”根据定义,DevOps 是关于不同职能部门之间的协作。它是关于“整体优化”,而不是任何特定的功能筒仓,这使得这篇博客文章的主人公数据守护者有必要在 Octopus Deploy 服务器的安全中发挥积极作用,并与他们可能不经常密切合作的人合作,以确保它的设置既安全又实用,便于人们进行定期部署。
- 推动环境的过度隔离是一种有害和不成熟的症状零风险偏见 虽然将您的环境相互隔离无疑是一个明智的安全措施,但有时过分热心的安全人员会犯拒绝服务器/触手通过防火墙进行通信的错误。这使得不可能向每个环境传输和执行一组一致的部署资源。这导致了不一致、头痛和失败。这也使得部署更有可能由对隔离环境具有适当访问权限的个人手动处理,从而增加了人为错误的风险,并造成部署瓶颈和延迟。与其将统一部署服务视为风险因素,不如将其视为风险缓解者。
您需要管理谁有权使用 Octopus Deploy 用户和角色功能进行这些更改。这些角色可以映射到 Active Directory 用户帐户和组。例如,您可以对其进行配置,以便不同的用户被授予部署到不同环境的权限,从而强制执行职责分离。
虽然在某些方面,Octopus 打开了大门,允许新人以新的方式对您的数据库进行更改,但它也提供了关闭大门的机会。一旦所有或大部分部署都通过 Octopus Deploy,人们直接访问生产数据库的需求就会大大减少。推出 Octopus Deploy 的一个常见步骤是在生产中对用户进行大规模筛选。
这是一个权衡。一方面,我们给予用户更多的自由来在较小的迭代中进行部署,只要所有的变更都已经通过了自动化测试,并且它们已经被成功地部署到较低的环境中。更重要的是,所有的部署都是经过审计的,失败会敲响警钟。另一方面,我们取消了人们以不受控制的方式直接登录服务器的权限,他们可以运行他们一起设法破解的任何 SQL。
我们正在创造一种环境,在这种环境中,开发人员的“懒惰路线”是做正确的事情,而不是做错误的事情。新世界比旧世界安全得多。
步骤 4:了解部署过程
Octopus Deploy 不具备部署现成数据库的智能。相反,Octopus Deploy 充当一个协调器,将所有需要的文件转移到跳转框,并运行任何需要的命令来指示您的首选数据库部署工具执行数据库更新。
有几种工具和技术可以用来执行部署。回到三月份,我回顾了 Octopus Deploy 用户部署 SQL Server 数据库的最流行选项。对于 SQL Server,有一些微软、第三方和开放源代码选项可供选择,这些选项通常可以归类为“基于迁移”或“基于模型”的解决方案。
无论您使用哪种工具,您都可能希望在跳转框上预先安装数据库工具,或者希望在部署过程中自动安装/更新。预安装工具在短期内可能更容易,并且可以加快您的部署,但是它也带来了管理开销,并且使您的部署过程不太容易移植。出于这个原因,我通常提倡安装和更新您作为部署的一部分使用的任何数据库部署工具,或者定期使用 Octopus Runbook 。注意,这可能需要在跳转框上有一定程度的互联网接入。
如果您经常需要在一个环境中同时部署到多个数据库,您可能想要考虑使用 workers 作为您的跳转框。例如,如果您在同一个安全范围内运行生产数据库的许多拷贝,您可能会发现在每个环境中拥有一个工作人员池是有益的。这允许跨多个部署跳转框高效地横向扩展数据库部署。然而,在这个场景中,每个跳转框都需要访问每个目标数据库。
步骤 5:检查部署
上个月,我讲述了一个 DBA 的故事,每当生产数据库出现问题时,他都会受到责备。为了保持它的运行,DBA 希望每次部署时都能得到咨询。由于这将 DBA 变成了一个瓶颈,“摇滚明星”开发人员对缓慢的过程感到沮丧,并试图绕过 DBA。同时,开发人员对“DBA 的东西”不感兴趣,他们缺乏 DBA 的知识和经验。
那是一场火车事故。但是很多人都知道这是一场火车事故。我们中许多人在过去的某个时候都曾经是 DBA 或“摇滚明星”开发人员。
事实是,当人们开始定期执行人工批准和目测脚本时,他们会发现大多数时候,他们只是通过测试和检查的心理清单来工作。例如,看看这条推文的回复。
我也鼓励你听听 Jeffery Smith 今年早些时候在《云中尖叫》中与 Corey Quinn 谈论变更管理反模式,他将手动变更评审过程描述为一个思维剧本(6:30-9:30 ),这可能比你想象的更容易自动化。这引发了一场与刻薄主持人的精彩辩论!
在你研究了 twitter 上的回复或者听了 Jeffery 和 Corey 关于 DevOps 的“争论”之后,读一读 Bob Walker 三月份关于自动化审批的博文。数据监护人倾向于寻找的许多东西(例如单词“DROP”、“TRUNCATE”、“CURSOR”、“NOLOCK”或成功回滚部署的能力)可以被自动检查和验证(通过在部署管道内执行模拟运行或代码分析)。如果这些检查中的任何一个失败了,那么自动化的过程要么被设置为失败,要么请求人工审查。
这种方法同时也是一种持续捕捉这些常见问题的更可靠的方式,以及一种更有效的日常变更生产途径。这也可能证明是一种有效的方式来教导开发人员什么样的东西部署起来是安全的,什么样的东西是危险的。
这种方法实际上比许多 ITIL 从业者愿意承认的更符合 ITIL。ITIL 区分了“标准的”和“正常的”变更,在“正常的”变更上花费了大量的评审工作。相比之下,“标准”变化被认为是例行公事,可以绕过官僚机构。ITIL 框架认识到“正常的”变更有很大的开销,并主张采取措施降低特定类型变更的风险,以便将它们重新归类为“标准的”变更。这正是我们努力实现的目标。
然而,这需要数据监护人的专业知识来有效地做到这一点。这是对所有数据守护者的一次战斗召唤,让他们停止考虑手动目测每个单独的部署,开始考虑如何更有效、更彻底地验证所有部署。从长远来看,这将节省您的时间,您可以致力于向部署管道添加更多的测试,进一步强化它,或者对风险最大的部署进行更彻底的手动审查。
案例研究:希望的理由
最后,我想通过一个案例研究来展示自动化数据库部署和跨职能协作的相对安全性。
早在 2017 年,Farm Credit Mid-America 就在寻求改善其数据库部署的治理和稳定性。我和一个跨职能团队一起工作,这个团队包括(从左到右)DBA、DevOps 工程师、数据架构师和。网络开发主管。
在一周内,我们制作了三个独立的概念证明,每个都使用 Octopus Deploy 和在 jump-box 上运行的不同部署工具。团队一致同意他们的首选方案,我们创建了一个计划来推广它。我们甚至设法让节俭的高级领导委员会批准了我们的提议(其中包括一笔不小的金融投资)。获得批准的一个重要原因是,来自不同筒仓的人们(他们常常不同意)团结起来,并热情地认为该解决方案将使每个人受益。
几个月后,一个最初比较怀疑的团队成员给我发来了下面的进度报告。考虑到他们在 Octopus 之前的错误率和治理问题,通过管道的 100%成功部署记录,加上显著改善的协作和生产力优势,是我职业生涯中最令人满意的成就之一。
训练有素的海怪是强大的盟友。
“2017 年 9 月,作为更广泛的 DevOps 转型的一部分,DLM 顾问与我们一起进行了数据库生命周期管理(DLM)运行状况检查,我们测试了三个数据库源代码控制和部署概念验证(POC)解决方案,这将使我们能够更定期、更可靠地提供数据库更新。这使得我们很容易为我们的数据库选择最佳策略。在接下来的几个月里,DLM 的顾问帮助我们进行了推广。
它现在运行得非常好,为我们带来了巨大的价值。我们现在只需点击一个按钮就可以部署我们的数据库更新,在新的流程中,我们没有一次部署失败。
在我 26 年的 IT 职业生涯中,我们对 DLM 的实施是我见过的最有益的基础设施项目。数据库部署不再是我们软件交付过程中的瓶颈。这项技术实现的过程改进影响到所有团队成员。开发人员、QA&DBA 在发布通过管道时进行沟通。DLM 让我们更有效率。”
Steve Cornwell ,Farm Credit Mid-America 的企业数据库开发人员,前微软员工。
了解更多关于 https://dlmconsultants.com/dlm-health-check/ DLM 顾问公司的 DLM 健康检查:了解更多关于八达通部署的安全性:https://octopus.com/docs/security
自 2010 年以来,Alex Yates 一直在帮助组织将 DevOps 原则应用于他们的数据。他最引以为豪的是帮助 Skyscanner 开发了一天 95 次部署的能力,并支持联合国项目服务办公室的发布流程。亚历克斯与除南极洲以外的各大洲的客户都有过合作——所以他渴望见到任何研究企鹅的人。
作为一名热心的社区成员,他共同组织了数据接力,是【www.SpeakingMentors.com】的创始人,并自 2017 年以来被公认为微软数据平台 MVP 。
Alex 是官方 Octopus Deploy 合作伙伴 DLM 顾问的创始人。他喜欢为那些希望通过改进 IT 和数据库交付实践来实现更好业务成果的客户提供指导、辅导、培训和咨询。
如果你想和亚历克斯一起工作,请发电子邮件:enquiries@dlmconsultants.com
团队配置改进- Octopus 部署
团队配置改进
配置你的 Octopus 服务器不是你一直在做的事情,重点是部署你的软件和微调你的项目。Octopus 最有趣的事情是配置团队和权限。
这是 Octopus 的一个很长时间没有改变的领域,我们现在正在围绕如何管理团队进行一些改进。在这篇文章中,重点将放在团队的结构变化上,请留意即将到来的空间博客文章,在那里我们将再次谈论团队。
背景
Octopus 具有精细的权限,例如:
- 创建部署
- 编辑机器
这些权限有 100 多种,它们作为用户角色捆绑在一起,用户角色是实现特定任务的权限的逻辑集合。例如:
- 环境管理器:用于配置机器和目标。
- 项目部署器:用于部署项目。
团队是用户的集合,是这些用户角色的集合,以及这些角色适用的地方,例如特定的环境、项目或租户。目前的方式导致了管理团队和权限的合理开销。
我们现在如何配置团队
目前,如果您有一个软件开发团队,您希望将他们部署到“开发”环境中,但是您也希望他们能够配置和管理“开发”和“QA”。你需要创建两个团队。
在第一个团队中,您添加成员,授予他们适当的权限,并限制他们只能进行“开发”。
在第二个团队中,您再次添加相同的成员,授予他们不同的权限,并对“开发”和“QA”应用限制。
这导致了许多任意的团队排列,只是为了支持不同的角色范围。此时,您有两个拥有相同用户组的团队,因为这是获得与两个环境相关的不同权限集的唯一方法。
现在,您希望这些用户对“生产”环境有一定的可见性,但只能进行只读访问。希望你猜对了,你需要第三支队伍!这样一直持续下去。
当新的团队成员加入您的组织时,您必须将他们添加到三个团队中。这是一个维护负担,简化事情的一种方法是使用活动目录,但这并不适合每个人。不难看出我们为什么要进行这些改变和改进。
如果我们设想一下,这是 3 个开发团队,他们都有相同的团队成员,但是存在只是为了帮助定义范围。
未来你将如何配置团队
我们决定不必这么麻烦。作为提供空间的更大工作的一部分,我们正在努力使团队更容易管理。我们让团队成员成为团队的焦点,并让团队根据您的需要担任尽可能多的角色。
给事物命名是困难的,所以我们坚持已经存在的,希望是熟悉的,它们仍然被称为“角色”。很快你就可以在任何一个团队中扮演任意多的角色。
如果我们将变化可视化,我们可以将 3 个开发团队合并成一个包含所有开发人员的逻辑团队,然后根据需要关联尽可能多的角色和范围。
它的用户界面如下所示。
成功的陷阱
我们花了很多时间与客户讨论配置权限的复杂性。通常,当他们努力按照自己想要的方式设置时。我们这些变化和未来改进的最终目标是确保每个人在配置他们的团队和权限时都能成功。
为了实现这一点,我们将提出更多类型的角色,并改进名称和描述,以便更容易选择正确的角色。
如果你已经做出了自己的组合,因为 Octopus 附带的不适合你,我们希望听到你的意见。请告诉我们您对该角色的称呼,以及您添加的一些权限示例。
突破性变化
我们尽力不在我们的 API 中引入突破性的变化,并且在大多数情况下我们避免它。在这种情况下,它将需要一个急剧的突破性变化。团队数据结构是不同的,我们决定尝试使它部分向后兼容可能会导致更严重的损害和混乱。如果您不想利用这一变化,您仍然可以创建相同的结构化团队,只需一个定义了限制的条目。
如果您目前使用 API 来创建和编辑团队,那么在这个特性发布之后,您将需要更新使用该 API 的任何代码,因为团队的数据结构发生了变化,以支持多个角色与它们自己的范围的链接。
结论
如果你有任何问题,请在评论中告诉我们。如果你对 API 的变化有疑问,请通过我们的支持渠道告诉我们你目前正在做的一些细节,我们会带你了解如何用新的 API 配置你的团队。
如果所有这些技术细节让你想知道更多,一定要看看这个早期的帖子,关于权限如何在 Octopus 的 React UI 中工作,并关注即将到来的关于空间的更多信息。
章鱼的团队城市插件!-章鱼部署
在过去的几周里,我们花了很多时间来改进章鱼/团队城市整合的故事。为此,我们创建了一个 TeamCity 插件,您可以将它安装在您的 TeamCity 服务器上。
该插件可以从通常的 Octopus 下载页面下载。插件的源代码是开源的,托管在 GitHub 上。
该插件提供了两个主要特性。首先,它扩展了内置的 MSBuild/VisualStudio 运行类型,以添加更好的 OctoPack 支持:
其次,它添加了一对定制构建运行器,用于创建和升级发布。
在这个过程中,我们对 Octo.exe 也做了一些修改,以更容易确保发布使用正确的软件包版本。例如,如果您有一个文件夹,其中包含您想要包含在发行版中的 NuGet 包,您可以:
octo.exe create-release <....> --packagesFolder=somepath
当确定版本中每个包的版本号时,Octo 将检查该文件夹中包的版本号。您可以将它与 TeamCity 工件依赖项结合使用,以使一个构建配置中构建的包流入到由另一个构建配置创建的发布中。
这些功能在我们最近的 TeamCity/Octopus 网络研讨会中进行了演示,会议记录如下:
http://www.youtube.com/embed/JWGLEEm9Qhg
VIDEO
非常感谢 JetBrains 的团队(特别是 Eugene )帮助创建和完善这个插件。
基于当前分支机构- Octopus 部署动态设置 TeamCity 版本号
原文:https://octopus.com/blog/teamcity-version-numbers-based-on-branches
当您使用 TeamCity 构建一个包含多个分支的项目时,根据分支的不同,最好有不同的构建号。例如,除了简单的 TeamCity 构建号,如15
、16
等等,您可能有:
- 分支
master
:1.6.15
。 - 分支
release-1.5
:1.5.15
(从分支名称开始的主要/次要构建)。 - 分支
develop
:2.0.15
(不同的次要构建)。 - 分支
feature-rainbows
:2.0.15-rainbows
(特征分支为标签)。
它看起来是这样的:
处理像 GitFlow 这样的分支工作流,并使用这些版本格式,对于 TeamCity 来说是相当容易的,在这篇博文中,我将向你展示如何操作。您自己的版本控制策略可能会有所不同,但是希望这篇文章能够帮助您开始。
背景
首先,我们关心两个内置的 TeamCity 参数:
build.counter
:这是自动递增的构建计数器(上面的 15 和 16)。- 这是完整的版本号。默认是
%build.counter%
,但可以更复杂。
build.number
的格式和build.counter
的值在 TeamCity UI 中定义:
然而,您也可以在构建期间使用服务消息动态设置它。也就是说,您的构建脚本可以将以下文本写入 stdout:
##teamcity[buildNumber '1.1.15']
这将覆盖内部版本号,然后新值将被传递给内部版本中的其余步骤。
把它放在一起
根据分支名称是master
还是develop
,我们将使用不同的主/次构建号。为此,我们将在 TeamCity 中定义两个参数。这些需要成为 TeamCity 中的系统参数,以便他们可以构建脚本。
为了根据分支名称动态设置构建号,我将添加一个 PowerShell 脚本步骤作为我的构建中的第一个构建步骤:
最后,下面是 PowerShell 脚本:
# These are project build parameters in TeamCity
# Depending on the branch, we will use different major/minor versions
$majorMinorVersionMaster = "%system.MajorMinorVersion.Master%"
$majorMinorVersionDevelop = "%system.MajorMinorVersion.Develop%"
# TeamCity's auto-incrementing build counter; ensures each build is unique
$buildCounter = "%build.counter%"
# This gets the name of the current Git branch.
$branch = "%teamcity.build.branch%"
# Sometimes the branch will be a full path, e.g., 'refs/heads/master'.
# If so we'll base our logic just on the last part.
if ($branch.Contains("/"))
{
$branch = $branch.substring($branch.lastIndexOf("/")).trim("/")
}
Write-Host "Branch: $branch"
if ($branch -eq "master")
{
$buildNumber = "${majorMinorVersionMaster}.${buildCounter}"
}
elseif ($branch -eq "develop")
{
$buildNumber = "${majorMinorVersionDevelop}.${buildCounter}"
}
elseif ($branch -match "release-.*")
{
$specificRelease = ($branch -replace 'release-(.*)','$1')
$buildNumber = "${specificRelease}.${buildCounter}"
}
else
{
# If the branch starts with "feature-", just use the feature name
$branch = $branch.replace("feature-", "")
$buildNumber = "${majorMinorVersionDevelop}.${buildCounter}-${branch}"
}
Write-Host "##teamcity[buildNumber '$buildNumber']"
既然%build.number%
是基于分支的,那么您的 TeamCity 构建就有了一个一致的构建号,可以在您的其余构建步骤中使用。例如,如果您使用的是 OctoPack,那么内部版本号可以用作OctoPackPackageVersion
MSBuild 参数的值,这样您的 NuGet 包就可以匹配内部版本号。
了解更多信息
%TEMP%对于作为本地系统运行的 Windows 服务有不同的值- Octopus Deploy
原文:https://octopus.com/blog/temp-different-values-for-windows-service
您可能已经知道环境变量可以在机器范围或用户范围内定义。用户范围内的值通常会覆盖机器范围内定义的值。
但是,作为系统帐户运行的 Windows 服务有一种特殊情况。给定以下 Windows 服务:
public partial class Service1 : ServiceBase
{
public Service1()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
File.WriteAllText("C:\\Temp\\Service.txt",
"Temp: " + Environment.GetEnvironmentVariable("Temp") + Environment.NewLine +
"Temp (User): " + Environment.GetEnvironmentVariable("Temp", EnvironmentVariableTarget.User) + Environment.NewLine);
}
}
当服务作为我的用户帐户运行时,我得到了我所期望的:
Temp: C:\Users\Paul\AppData\Local\Temp
Temp (User): C:\Users\Paul\AppData\Local\Temp
然而,作为内置系统(本地系统)帐户运行服务,您会得到不同的行为:
Temp: C:\WINDOWS\TEMP
Temp (User): C:\WINDOWS\system32\config\systemprofile\AppData\Local\Temp
似乎对于在系统帐户下运行的 Windows 服务,即使有一个特定于用户的环境变量,也使用了不同的%TEMP%
。
这在昨天的 2.6 预发布中造成了一个错误,因为我们添加了一个功能,在每次脚本运行之前自动更新环境变量(以防您已经更改了环境变量,并且不想重新启动触手 windows 服务)。当然,好心没好报 😃
我找不到任何关于这个特性的文档,但是环境变量是由父进程继承的。服务归services.exe
所有,服务归wininit.exe
所有。使用 Process Explorer,wininit.exe
的环境变量将 TEMP 设置为 C:\Windows\TEMP。我猜这可能是依赖于使用C:\Windows\TEMP
的旧 Windows 服务的向后兼容特性。
(我们将在明天发布 2.6 的补丁来解决这个问题)
清理临时 ASP.NET 文件- Octopus 部署
绝大多数的 ASP.NET 网站和网络应用程序都使用动态编译来编译应用程序的某些部分。通过动态编译为 ASP.NET 网站创建的程序集存储在Temporary ASP.NET files
目录中。随着时间的推移,这些临时文件会越来越多,必须手动删除。作为影像复制的一部分,网站 bin 目录的副本也存储在该文件夹中。Octopus Deploy 的许多用户也比以前更频繁地使用持续集成或发布。这反过来意味着这些临时文件可以积累得相当快。
除了留给手动流程,我们还有一些方法可以在部署后清理。要清理这个目录,您可以使用来自 Octopus Deploy 库的文件系统-清理 ASP.NET 临时文件 PowerShell 脚本模板。作为部署过程中的一个步骤,该脚本将允许您清理临时文件目录。
如何使用脚本
从库中导入步骤并将该步骤添加到项目中之后,您可以配置框架版本和要保留的天数。
该步骤只需要两个参数Framework version
和Days to keep
。默认情况下,它会清理网站目录下的Temporary ASP.NET files
目录超过 30 天。
框架版本
指定All
将清除每个已安装的框架版本的临时文件。如果需要针对特定版本,可以指定位和/或版本。
仅指定一个位值将匹配所有版本。在这里,您只能指定以下两个选项之一。
- Framework
- Framework64
仅指定一个版本将与该版本匹配,而不考虑位(32 位和 64 位)。版本必须始终以v
开头。
- v1.1.4322
- v2.0.50727
完全指定的框架版本将只匹配该路径。
- Framework\v4.0.30319
- Framework64\v4.0.30319
保留天数
如果网站目录的上次写入时间超过了指定的保留天数,它将被删除。该目录在应用程序启动时创建。
脚本是如何工作的
Temporary ASP.NET files
下的目录结构由映射到应用程序名的目录组成,应用程序名是虚拟路径名或根目录。在这个目录下,每个网站的每个版本都有一个代码生成目录。该脚本将只识别删除代码生成目录。代码生成目录也是Days to keep
用来保留的。
删除临时 ASP.NET 文件是一项安全的操作,请记住,多个网站可以使用此文件夹,并且当前正在使用的网站可能会被回收。
您可以在部署之前或之后执行脚本。但是,建议您在部署之前运行该脚本,因为即使在部署完成后,以前的部署可能仍在使用中。任何包含锁定文件的代码生成目录都将被跳过。
还有其他解决方法吗
如果您有多个网站,或者需要尽可能避免停机配置,可以使用自定义编译目录来隔离每个网站的代码生成目录。您可以使用 web.config 中的compilation
标签上的tempDirectory
属性指定一个定制的临时文件目录
<configuration>
<system.web>
<compilation tempDirectory="C:\TempAspNetFiles\">
</compilation>
</system.web>
</configuration>
什么时候该担心这个?
如果您正在进行频繁的部署,或者您需要高度健壮的部署,您只需要担心这一点。其他要考虑的因素是您部署了多少站点、bin 目录的大小、部署的频率以及您有多少磁盘空间。
务实部署的十大支柱-八达通部署
原文:https://octopus.com/blog/ten-pillars-of-pragmatic-deployments
正如你可能想象的那样,在 Octopus,我们花了大量时间考虑部署。Octopus 的诞生是为了管理现实世界的部署流程,并通过与客户的对话、支持请求、我们自己的内部部署要求以及许多其他关于我们认为好的部署的讨论而不断形成。
这些知识已经被提炼为实用部署的 10 个支柱。
我们相当谨慎地使用“务实”一词。这个列表不是记分卡、金字塔、清单或固定的需求列表。在许多情况下,无论是作为产品还是作为公司,八达通都没有实现这些支柱。开发这些支柱的主要目标之一是找到我们自己的过程中的差距,以便形成我们产品的特性和理念,并继续正在进行的关于部署软件意味着什么的讨论。
记住这一点,第 0 个支柱,也是列表中所有其他支柱的先决条件,就是做对你有用的事情。
我们希望您喜欢这次讨论,并期待通过您的反馈塑造务实部署的未来。
十大支柱
支柱 1。可重复部署
在各种环境中推进部署的主要原因之一是获得越来越多的信心,相信您正在为最终用户提供一个可行的解决方案。这种信心可以通过测试(手动和自动)、手动签署、内部使用你自己的软件(喝你自己的香槟)、测试用户的早期发布,或者任何数量的允许问题在影响到你的最终用户之前被识别的其他过程来建立。
但是,只有当您部署到生产环境中的内容尽可能接近您在非生产环境中验证的内容时,您才能获得这种信心。
通过采用可重复部署,您可以确保您的最终用户在生产环境中使用的是您在非生产环境中测试、验证并获得信任的内容。
一般部署概念
为了理解可重复的部署,我们需要理解什么是部署,以及在典型的构建和部署管道中的哪个点发生部署。
持续集成、持续交付和持续部署
术语持续集成和持续交付或部署(CI/CD)经常被用来描述从源代码到可公开访问的应用程序的进展。
我们认为持续集成(CI)是在源代码更新时编译、测试、打包和发布应用程序的过程。
持续交付和持续部署(都缩写为 CD)有着微妙的不同含义。我们将这两个术语视为将应用程序交付到其目的地的一系列自动化步骤。区别在于这些自动化步骤是否将应用程序直接交付给最终用户,而无需人工干预或决策。
连续部署没有人工干预。当您的 CI 服务器对您的源代码进行编译、测试和打包,然后部署、测试并向最终用户公开时,您就实现了连续部署。这个过程的每个阶段的成功或失败都是自动的,从而产生一个向消费者承诺的工作流。
连续交付涉及到一个人做出向最终用户进行部署的决策。这种发展通常表现为通过一系列环境的提升。环境进展的一个典型例子是将应用程序部署到非生产开发和测试环境,最后部署到生产环境。
开发环境可以很好地配置为连续部署,CI 服务器成功构建的每个提交都是自动部署的,无需人工干预。当开发人员对他们的更改适合更广泛的受众感到满意时,就可以将部署提升到测试环境中。
在测试环境中,质量保证(QA)人员验证变更,产品所有者确保功能满足他们的需求,安全团队探查漏洞等。当每个人都对变更满足他们的需求感到满意时,部署就可以升级到生产环境了。
生产环境是部署的最终目的地,也是应用程序向最终用户公开的地方。
我们从大多数客户那里了解到,持续交付对他们来说很有效。因此,虽然大多数支柱同样适用于持续交付和持续部署,但我们将从持续交付的角度来看待它们。
什么是环境?
环境代表单个应用程序副本或整个应用程序堆栈及其支持基础架构之间的界限。
所有环境的配置应该尽可能相似,以确保应用程序无论在哪个环境中都能保持一致的行为。
环境通常代表从具有高部署频率和低稳定性的初始环境到具有低部署频率和高稳定性的最终环境的进展。
部署在整个环境中进行,以获得越来越大的信心,相信可以向最终用户交付工作解决方案。
标准的环境集被称为开发、测试和生产。下表描述了这些环境的特征:
环境 | 描述 | 部署频率 | 稳定性/信心 |
---|---|---|---|
发展 | 开发人员使用它来测试实现的单个更改。 | 高的 | 低的 |
试验 | 由开发人员、QA 和非技术人员用来验证变更是否满足需求。 | 中等 | 中等 |
生产 | 供最终用户访问,以使用公开可用的应用程序实例。 | 低的 | 高的 |
尽管您可以自由地拥有任意数量、任意名称的环境,但是这组环境将在示例中使用。
什么是部署?
我们已经讨论过将“应用程序”部署到环境中,这通常是我们描述部署的方式。但是为了理解可重复部署是如何实现的,我们首先需要明确我们实际部署了什么。
有三样东西可以部署到环境中:
- 为特定环境配置的已编译应用程序。这些被称为包。
- 定义如何配置应用程序的变量,通常有一小部分特定于特定环境。
- 内联编写的脚本和配置文件(即,不作为文件保存在包中),用于支持或配置环境中的应用程序及其相关基础架构。
软件包版本、变量值和脚本或配置文件被捕获为一个版本。
然后将该版本部署到环境中。这样,包、变量、脚本和配置文件的一致捆绑从一个环境提升到下一个环境。只有一小部分特定于环境的设置会因环境而异。
支柱 2。可核实的部署
可重复部署支柱描述了如何在环境中推广版本,从而提高对交付的解决方案的信心。我们讨论了频繁的开发环境部署如何使开发人员能够测试他们的变更,而不太频繁的测试环境部署则允许其他方验证潜在的产品发布。当每个人都满意时,生产环境就更新了,向最终用户公开了这些变化。
可验证部署的支柱描述了当部署到达新环境时可用于验证部署的各种技术。
一般测试概念
测试是一个模糊的术语,通常有不明确的子类别。我们不会试图在这里提供测试类别的权威定义。我们的目标是提供通用测试实践的高度描述,并强调那些可以在部署过程中执行的测试实践。
在部署过程中,我们不测试什么?
单元测试被认为是构建管道的一部分。这些测试与正在编译的代码紧密相关,它们必须成功,才能构建出最终的应用程序包。
集成测试也可以在构建过程中运行,以验证更高级别的组件是否如预期的那样交互。测试中的组件可以用一个双测试来代替以提高可靠性,或者可以创建组件的真实实例作为测试的一部分。
单元和集成测试由 CI 服务器运行,任何可用于部署的包都被认为已经通过了所有相关的单元和集成测试。
部署期间我们可以测试什么?
需要可访问的活动应用程序或应用程序堆栈的测试是作为部署过程的一部分运行的理想候选。
冒烟测试是快速测试,旨在确保应用程序和服务已正确部署。冒烟测试实现了确保服务正确响应所需的最小交互。一些例子包括:
- web 应用程序或服务检查成功响应的 HTTP 请求。
- 确保数据库可用的数据库登录。
- 检查目录是否已被文件填充。
- 查询基础结构层以确保创建了预期的资源。
集成测试可以作为部署的一部分来执行,也可以在构建期间执行。集成测试验证多个组件如您所期望的那样交互。部署中可能包含测试替身来代替被验证的组件,或者测试可能验证两个或更多的活动组件实例。例子包括:
- 登录 web 应用程序以验证它可以与身份验证提供程序交互。
- 向 API 查询来自数据库的结果,以确保可以通过服务访问数据库。
端到端测试提供了一种与系统交互的自动化方式,就像用户与系统交互一样。这些测试可能是长时间运行的测试,遵循应用程序中要求应用程序堆栈的大部分或所有组件正常工作的路径。例子包括:
- 自动化与在线商店的交互,以浏览目录、查看商品、将其添加到购物车、完成结账以及查看帐户订单历史。
- 完成对天气服务的一系列 API 调用,以查找城市的纬度和经度,获取返回位置的当前天气,并返回本周剩余时间的天气预报。
混乱测试包括故意移除或干扰组成应用程序的组件,以验证系统是否有足够的弹性来承受此类故障。混沌测试可以与其他测试相结合来验证退化系统的稳定性。
可用性和可接受性测试通常要求人们使用应用程序来验证它是否满足他们的需求。需求可以是主观的,例如,确定应用程序是否在视觉上有吸引力。或者测试人员可能不是技术人员,因此没有自动化测试的选项。这些测试的手动和主观性质使得它们很难(如果不是不可能的话)自动化,这意味着必须部署应用程序或应用程序堆栈的工作副本,并使其可供测试人员访问。
支柱 3。无缝部署
部署新版本的应用程序不可避免地涉及到使以前的版本脱机并将流量导向新版本。确保最终用户在此转换过程中不会受到负面影响需要一些规划。
合理的解决方案是在最终用户不太可能使用应用程序的时候执行面向公众的部署。如果您的客户位于相似的时区,那么在半夜部署新版本的应用程序可以有效地为最终用户带来无缝体验。
午夜部署可能并不吸引人,但是如果它们为你工作,这是一个非常有效的解决方案。
当必须将停机时间保持在最低限度,或者没有合适的停机时间时,可以使用一些常见的部署策略来无缝地部署新的应用程序版本。
无缝数据库部署
不首先解决数据库更新的问题,就不能开始讨论无缝部署。
大多数无缝部署策略的一个基本方面涉及到并行运行应用程序的两个版本,即使是在很短的时间内。如果应用程序的两个版本都访问共享数据库,那么对数据库模式和数据的任何更新都必须与两个应用程序版本兼容。这被称为向后和向前兼容性。
然而,实现向后和向前的兼容性并不容易。在演讲中,Edison Yanaga 介绍了重命名数据库中单个列的过程。它涉及对数据库和应用程序代码的六次增量更新,并且所有六个版本都要按顺序部署。
不用说,涉及数据库的无缝部署需要大量的规划、许多小步骤来展开更改,以及数据库和应用程序代码之间的紧密协调。
部署策略
有多种策略来管理现有部署和新部署之间的转换。
再创造
重新创建策略不提供无缝部署,但此处包含了这一策略,因为它是大多数部署过程的默认选项。该策略包括删除现有部署并部署新版本,或者在现有部署的基础上部署新版本。
这两种选择都会导致现有版本停止或删除与新版本启动之间的停机时间。但是,因为现有版本和新版本不会并发运行,所以可以根据需要应用数据库升级,而没有向后和向前的兼容性要求。
滚动更新
滚动更新策略包括用新部署增量更新当前部署的实例。这种策略确保在部署期间至少有一个当前或新部署的实例可用。这要求任何共享数据库都必须保持向后和向前的兼容性。
金丝雀部署
canary 部署策略类似于滚动更新策略,因为两者都以增量方式向更多的最终用户公开新的部署。不同之处在于,在 canary 部署中进行部署的决策要么是由监控指标和日志的系统自动做出的,以确保新部署按预期执行,要么是由人工手动做出的。
Canary 部署还可以选择暂停部署,并在发现问题时恢复到现有部署。
蓝色/绿色部署
蓝/绿策略包括将新版本(称为绿版本)与当前版本(称为蓝版本)一起部署,而不将绿版本暴露给任何流量。一旦绿色版本得到部署和验证,流量就会从蓝色版本切换到绿色版本。当蓝色版本不再使用时,可以将其移除。
绿色版本部署的任何数据库更改都必须保持向后和向前的兼容性,因为即使绿色版本不支持流量,蓝色版本也会受到数据库更改的影响。
会话排出
当应用程序维护绑定到特定应用程序版本的状态时,使用会话清空策略。
这种策略与蓝/绿策略相似,都是将新版本与当前版本一起部署,并同时运行。与蓝/绿策略不同,蓝/绿策略会在一个步骤中将所有流量切换到新部署,而会话清空策略会将新会话定向到新部署,而现有部署会为现有会话提供流量。
旧会话过期后,可以删除现有部署。
因为当前部署和新部署是并行运行的,所以任何数据库更改都必须保持向后和向前的兼容性。
功能标志
功能标记策略包括将功能构建到新的应用程序版本中,然后在部署过程之外为选定的最终用户公开或隐藏该功能。
在实践中,具有可标记特性的新应用程序版本的部署将使用上述策略之一来执行,因此特性标记策略是对其他策略的补充。
特征分支
特性分支策略允许开发人员使用他们当前正在实现的更改来部署应用程序版本,通常是在非生产环境中,与主部署一起。
可能没有必要维护数据库与功能分支部署的向后和向前兼容性。因为特性分支是用于测试的,而且往往是短暂的,所以每个特性分支部署都可以访问它自己的测试数据库。
支柱 4。可恢复部署
实施可重复且可验证的部署(在发布到生产环境之前在非生产环境中进行测试)的目的是在错误影响最终用户之前识别它们。然而,一些错误不可避免地会出现在生产中。在这种情况下,将生产环境恢复到理想状态是非常重要的。
向后或向前滚动
从不良部署中恢复意味着回滚到以前的良好部署,或者前滚到使环境恢复到理想状态的新版本。
这两种解决方案都适合无状态应用程序,但是当涉及到数据库时,回滚必须小心处理。
这是来自飞行路线项目的建议:
虽然撤销迁移的想法很好,但不幸的是,在实践中有时会失败。一旦你有破坏性的改变(删除、删除、截断等等),你就开始有麻烦了。即使您不这样做,您最终也会创建自制的备份恢复替代方案,这些方案也需要经过适当的测试。
与其在回滚计划上投入时间和精力,另一种选择是遵循一种让你不断前进的方法。
博客文章SQL 回滚和自动化数据库部署的陷阱有这样的建议:
通常情况下,成功回滚部署的努力远远超过了将修复推向生产的努力。
当部署涉及数据库更改时,我建议您进行前滚,以便从不理想的部署中恢复。
正在回滚
对于可重复的部署,可以通过重新运行之前的部署来实现回滚。这是可能的,因为包版本、脚本和变量都是由可重复的部署定义的。
回滚也是几种无缝部署策略的一个显式特性:
- Canary 部署通过将所有流量从新部署重定向到当前部署来实现回滚。
- 蓝/绿部署可以通过将流量切回蓝堆栈来回滚部署。
- 会话清空部署可以将新会话重定向到当前部署,并且可以选择终止新部署中的任何会话。
回滚有以下好处:
- 通过回滚到以前的部署,可以修复部署问题,而无需编写代码。
- 回滚使系统处于已知的已验证状态。
- 完成回滚所需的时间可以在非生产环境中测量。
回滚有以下缺点:
- 回滚是全有或全无的操作。您不能回滚单个功能,只能回滚整个部署。
- 回滚需要作为部署过程的一部分进行测试,以确保它们按预期工作,这增加了部署过程的复杂性和时间。
- 如果回滚失败,您可能需要通过前滚来解决问题。
- 数据库回滚需要特别考虑,以确保数据不会丢失。
向前滚动
前滚只是描述执行新部署的另一种方式。在这种情况下,新部署将只包含恢复环境所需的修复程序。
向前滚动有以下好处:
- 所有部署策略,不管有没有数据库,本质上都支持前滚。
- 团队在每次部署前滚的过程中获得经验。
- 向前滚动时,您可以选择更改或修复的范围。
- 在前滚时,可以连续进行多个部署,以解决不需要的部署。
向前滚动有以下缺点:
- 前滚通常需要开发人员实现一个修复,以包含在下一个部署中。
- 前滚可能涉及绕过通常用于验证部署的环境进展,以尽快获得部署的修复。
- 只要开发和部署下一个版本,生产环境就会一直处于不受欢迎的状态。
支柱 5。可见部署
部署是一个抽象的概念,通过检查系统的状态很难知道什么被部署在哪里。根据磁盘上的文件或数据库中的记录来确定应用程序的安装版本,就像计算出用于生产一罐颜料的颜色组合一样。
能够快速查看环境的当前状态对于理解以下内容至关重要:
- 向您的客户提供了哪些功能。
- 正在测试哪些功能。
- 修复了哪些问题。
- 任何变化的历史。
下面列出了全面了解部署状态所需的大量详细信息。
提交消息
提交消息捕获源代码编辑的意图,描述做了什么更改以及谁做了这些更改。当试图从较低的层次理解是什么改变了包的特定版本时,这些消息是非常宝贵的。
问题跟踪
通常,提交源代码是为了解决在专门的问题跟踪器中记录的问题。这些问题为描述、讨论和跟踪 bug 提供了空间。每个问题都有一个唯一的标识符。
捕获与包版本以及包含该包版本的任何部署中的更改相关的问题 id,可以提供对任何给定部署中已解决的问题的深入了解。
CI 构建日志
典型的 CI/CD 管道有一个 CI 服务器,用于构建、测试和打包应用程序。这些构建的日志文件包含了大量的信息,比如哪些测试通过了,哪些测试被忽略了,包含了哪些依赖项,以及创建了哪些包。从部署到 CI 构建的链接允许快速查看这些日志文件。
库依赖性
几乎今天部署的每个应用程序都是自定义代码和共享库的组合,通常由第三方提供。这些库可能是错误或安全漏洞的来源,或者提供新的有用的功能。了解促成部署的库依赖关系对于审计和调试非常重要。
部署版本
一个发布版本在一个发布版本中捕获所有上述信息,以及包版本、变量值和脚本。然后将该版本部署到环境中。
显示哪个发布版本被部署到哪个环境提供了部署状态的高级视图。有了这些信息,任何人都可以看到在哪里部署了什么,并且通过深入了解发布的详细信息,可以看到提交消息、问题、依赖项以及到 CI 构建的链接。
支柱 6。可衡量的部署
将您的软件送到客户手中是任何部署过程的主要目标。要了解您的部署过程执行得有多好,您需要衡量定义成功的标准。
拥有可度量的部署意味着定义一组通用的、一致同意的度量标准,可靠地收集这些度量标准,并以易于理解的格式呈现它们。
适用于部署的一些常见指标有:
- 部署频率:部署到生产环境的频率如何?
- 变更的准备时间:将提交部署到生产中需要多长时间?
- 部署的前置时间:一个版本部署到生产需要多长时间?
- 恢复部署的时间:在部署失败后,成功部署一个版本需要多长时间?
- 部署失败率:失败的部署和成功的部署之间的比率是多少?
- 变更失败率:热修复部署和常规部署之间的比率是多少?
- 部署持续时间:每次部署需要多长时间?
支柱 7。可审计的部署
如果可见的部署支柱是关于呈现您的环境的当前状态以及作为发布的一部分所做的更改,那么审计是关于跟踪谁随着时间的推移参与了部署过程。
审核允许团队查看部署到环境的历史记录、部署过程的更改、环境的更改、谁批准了哪些部署,以及确定环境在过去某个时间点的状态。
为了使审计事件有用,它们必须能够被搜索、过滤、导出和报告。
支柱 8。标准化部署
正如可重复的部署会随着一个版本在不同环境中的推广而建立信心一样,跨不同项目的标准化部署过程允许团队信心十足地利用已经被证实的过程。
标准化部署有两个主要方面。
第一个方面是定义各种部署使用的初始部署过程。这个基本部署过程可以是一个共享模板,允许定制非常具体的设置。或者整个过程可以被复制和粘贴以引导新的项目,但是允许他们根据自己的需要定制过程。
第二个方面是定义谁有能力编辑部署过程。通过区分查看、运行、创建和编辑部署过程的能力,团队可以保证只有那些负责创建或编辑部署过程的个人才能这样做。
支柱 9。可维护的部署
将您的部署部署到生产环境只是一个开始。诊断问题、收集日志、执行备份、重新启动服务、轮换密钥和测试连接只是日常操作任务的一部分,可让您的应用程序保持运行并让您的客户满意。
虽然您可以通过 SSH 或 RDP 连接到服务器并开始探索,但是您所做的每个更改都会使您的生产环境偏离非生产环境,从而使实现可重复部署变得更加困难。也很难跟踪所做的更改,验证它们是否有效,以及审计谁更改了什么。
像部署一样,维护任务应该是可重复的、可验证的、可见的、可测量的、可审计的、标准化的和协调的。维护任务代表了保持部署运行所需的业务知识,应该与部署过程保持相同的标准。
支柱 10。协调部署
将包部署到环境中只是部署过程的一小部分。通常,部署需要与其他业务流程相协调,以确保:
- 合适的人已经同意了。
- 部署的成功或失败会通知相关方。
- 部署按正确的顺序进行。
- 部署只能在特定时间进行。
- 高优先级部署优先于低优先级部署。
- 部署被安排在预先确定的时间进行。
- 外部事件可以触发部署。
- 部署可以触发外部事件。
实际上,部署流程可能是业务流程管理工具的更大生态系统中的单个组件。从第三方平台协调部署并报告结果的能力允许团队将复杂的部署作为更广泛的业务流程的一部分来管理。
结论
这些是实用部署的十大支柱,它们塑造了 Octopus Deploy 的特性和理念。我确信它们会随着新的用户案例的出现而不断发展,但是我们相信它们为继续构建 Octopus Deploy 提供了一个坚实的基础。
愉快的部署!
租户感知生命周期- Octopus 部署
为了充分利用多阶段部署的优势,部署生命周期是需要理解的关键概念之一。通常,发布的当前阶段可以通过生命周期环境中部署的直接线性进展来确定。然而,一旦您启用并开始使用多租户,情况可能开始变得不那么清晰。让我们看一下租户的加入如何影响部署生命周期过程。
租户感知生命周期
我们将从设置一个典型的多租户配置场景开始。我们的Synergy
项目的默认生命周期类似于Testing
->-Staging
->-Production
,我们有三个不同的租户连接到这个项目,用于不同的环境。
Customer-1
部署到Staging
和Production
Customer-2
部署到Staging
和Production
Customer-3
仅部署到Production
我们没有给机器或步骤增加任何标记范围的复杂性,并且假设部署一旦开始就会成功完成。
步骤 1 -测试的首次部署
查看从3.4.0
构建中获得的新项目仪表板,我们可以很好地了解与这个项目相关联的所有租户以及一个发布过滤器。只有可以部署到该版本的通道(在本例中为默认通道)的租户才会显示在这个过滤视图中。
创建我们的版本0.0.1
我们发现,按照对生命周期的标准理解,我们最初只能部署到第一个环境Testing
。到目前为止,一切都没有改变。由于我们的租户都没有连接到该环境,我们只能执行未租赁到Testing
环境的部署。假设不涉及租户,这意味着对变量模板的任何引用都将解析为模板默认值或适当范围的项目变量。
步骤 2 -测试完成,第一个租户部署
随着我们测试环境的成功,当前的生命周期阶段已经转移到了Staging
。现在,我们有两个客户可以部署到这个环境中,Customer-1
和Customer-2
。然而,由于Customer-3
没有连接到Staging
,他们仍然不能部署这个版本。请注意,未租赁部署的性质意味着,尽管现在有可用的租户,也可以执行到Staging
。
让我们假设我们已经通过部署Customer-1
到Staging
继续。
第三步-分期?生产?在哪里?谁啊。
根据我们所指的租户,生命周期的当前阶段现在稍微复杂一些。因为Customer-1
已经到了Staging
,所以我们现在可以为那个租户升级到Production
的部署。
只能部署到Production
的 Customer-3
现在也可以直接执行到Production
的首次部署。这是因为将任何租户部署到一个环境都是对该版本的未租用生命周期的一种提升。这解释了为什么Customer-3
部署和未租赁部署现在可以执行到Production
,尽管两者都没有执行Staging
部署。
另一方面,我们将Customer-2
定义为除了Production
之外还连接到Staging
环境。这个配置建议我们要求 Customer-2
在提升到Production
之前先部署到Staging
。让我们停下来想一想这意味着什么。虽然根据Customer-1
和Customer-3
我们现在可以执行Production
部署,但是对于Customer-2
的任何部署仍然必须首先发生在Staging
上。因此,就好像我们现在有一个并行的生命周期,这个版本仍然处于专门针对Customer-2
的Staging
阶段,而其他租户可以继续下一阶段。将租户链接到项目的环境需要在阶段进展之前将他们部署到该环境。此功能允许您在未完成每个配置阶段的情况下,阻止您的租户部署进入后续环境,而不会影响其他租户继续部署。
租户必需的项目
您可能已经注意到这种开箱即用的多租户行为的一个结果是,您总是能够对任何租户可以部署到的任何环境执行未租赁部署。然而,在您的特殊情况下,您可能会发现对Production
执行非租赁部署没有意义。也许某些必需的变量会丢失,因为它们只能在变量模板中的租户上定义。也许您希望防止未租用的部署意外地部署到生产基础架构中。为了支持您希望禁止未租赁部署的这些场景,我们在项目设置页面上包括了以下选项。
通过选择Require a tenant for all deployments
选项,该项目将无法再进行未租赁的部署。我们预计这将是一些团队在开始转向完全租用的架构时将采用的高级配置。在这种情况下,您还可以设置一个特殊的内部测试租户,它与开发或测试环境相关联,在这些环境中,您的客户特定的部署没有多大意义。也许您还将为您的每个测试人员创建一个测试租户,允许他们接收您的发布到不同机器的单独部署,以便进行并行测试。(请记住,在禁用未租赁部署时,您需要确保至少有一个租户能够部署到您的每个生命周期环境中,否则不会有太大进展!).
我们认为,即使您的项目实际上不需要为多个客户部署同一个版本,您的部署也可以通过多种不同的方式受益于多租户特性。让我们知道你可能如何利用租户,而不仅仅是明显的租户=客户设计!
触手。NET 版本更改- Octopus 部署
与。NET Framework 4.5.2 将于 2022 年 4 月 26 日结束支持。NET Core 3.1 将于 2022 年 12 月 13 日结束支持,我们将把触手迁移到。仅适用于 Windows installer 的. NET Framework 4.8,以及。NET 6 来实现其他功能,包括 Windows Docker 映像。
在这篇文章中,我解释了我们的决定,并回答了一些关于这些版本变化的问题。
为什么我们现在要移动触手?
触手已经展开。NET Framework 4.5.2 和。网芯 3.1 一段时间了。我们转移这些版本是因为。NET Framework 4.5.2 于 2022 年 4 月 26 日结束支持。NET Core 3.1 将于 2022 年 12 月 13 日结束支持。在此之后,我们将错过安全补丁和库更新。
为了继续为我们的客户提供安全的软件,我们必须与我们的底层开发框架的支持时间表保持一致。
触手前进的计划是什么?
从版本 6.3 开始,触手将需要。用于 Windows installer 的. NET Framework 4.8。NET 6 用于其他一切。仍有许多部署目标使用不同版本的 Windows 7 和 8(包括 Windows 7 SP1 版和 Windows Server 2008 SP2 版),它们需要一个受支持的。短期到中期的网络版本。
在不久的将来,我们将停止对的支持。NET 框架,让一切都在上面运行。NET 6。
你可能有的问题
我需要做些什么吗?网络改版?
操作系统(Operating System) | 行动 |
---|---|
Windows(Windows Server 2022 之前) | 安装。NET Framework 4.8 运行时 |
Windows (Windows Server 2022 或更高版本) | 没有任何东西 |
Linux/MacOs | 确保操作系统与兼容。网络 6 |
当触手 6.3 可用时,您可以使用我们支持的任何方法升级您现有的触手。
了解有关兼容的更多信息。请访问以下 Microsoft 文档页面:
我的部署会受到影响吗?
只要您的触手操作系统满足运行时需求,您的部署就不会受到这种移动的影响。通过包含。NET 框架 4.8,我们仍然保持向后兼容,早在 Windows 7 SP1。
我应该升级 Windows 7 SP1 版和 Windows Server 2008 SP2 版上的部署目标吗?
是的,我们鼓励您升级仍在 Windows 7 SP1 版或 Windows Server 2008 SP2 版上的产品。
我们建议升级到至少支持的版本。NET Framework 4.8,以及理想情况下与。NET 6 及以上。
如果我不能升级我的部署目标怎么办?
如果您无法升级到受支持的。NET 版本,你需要锁定你的触手版本,避免它自动升级。锁定确保你的触须保持功能。本次升级前的触手最新版本是 6.2.277。
要了解更多关于锁定你的触手版本,请阅读我们关于触手版本的帖子。
Windows Installer 何时会升级到。NET 6?
我们计划将触手移动到。在 2023 年初至 2023 年中期,Windows installer 将使用. NET 6。这使您有时间将部署目标升级到支持的版本。NET 6。
结论
我们希望这篇文章能解释为什么我们要把触手移动到。NET Framework 4.8 和。NET 6。
如果您有任何问题或顾虑,请联系我们的支持团队。
愉快的部署!
手臂上的章鱼触手/手臂 64 -章鱼展开
我们很高兴地分享我们的触手代理现在支持 ARM 和 ARM64 硬件。这一更新使您可以将应用和服务部署到 Raspberry Pi 3 & 4、AWS A1 EC2 实例或任何可以运行的 ARM 硬件。网芯 3.0 或更高版本。
在这篇文章中,我将解释为什么在 ARM 服务器上运行触手是有价值的,以及如何开始。
为什么触手在 ARM/ARM64 上?
早在 2019 年末,我们就推出了对基于 ARM 的部署目标和工作人员的支持,作为 SSH 连接选项*。 Linux 触手已经出现了一段时间,但是它只支持基于x86_64/amd64
的机器,使得 ARM 采用者只能使用 SSH。
通过 SSH 连接是可以的,但是它并不适用于每个人,比如不允许使用端口 22 的高度安全的环境。它还需要在 Octopus 服务器和 SSH 服务器之间建立直接连接,可能需要通过防火墙。
通过在 ARM 设备上安装轮询触手,可以避免为 SSH 连接打开防火墙端口,如果您使用的是 Octopus 云服务,这一点尤为重要。
请随意查看我们的前一篇博文,这篇博文更详细地介绍了使用 Linux 触手进行部署而不是 SSH 的好处。
在 ARM 硬件上运行您的工作负载有一些好处:
- 降低运行成本
- 对于 Raspberry Pi 等小型制造商而言,是低成本可更换单元。
- 与 x86 服务器相比,计算速度更快。
- 从技术上来说,它比这个时间更早,但你必须跨越一些尴尬的障碍。
入门指南
在 Linux 上配置触手的所有说明都可以在我们的文档中找到,但是我将提供一个步骤的演示。在演练的最后,我们将在私有网络中有一个 Raspberry Pi,它连接到一个 Octopus Cloud 实例。
对于这个例子,我在树莓派 3B+ 上运行 Fedora 33 服务器,并注册了章鱼云实例。
您还可以在装有 Ubuntu 18.04 或更高版本的 Raspberry Pi 或 Raspbian Buster 上运行 Linux 触手代理。
在我们安装触手应用程序之前,您需要一个用于认证的 Octopus 服务器 URL 和一个 API 密钥。如果您使用本地 Octopus 服务器实例,您可以使用用户名/密码进行认证。
装置
安装触手包很简单:
sudo wget https://rpm.octopus.com/tentacle.repo -O /etc/yum.repos.d/tentacle.repo
sudo yum install tentacle
安装结束时,您将看到以下消息:
To set up a Tentacle instance, run the following script:
/opt/octopus/tentacle/configure-tentacle.sh
如果您需要进行自动化的、可重复的安装,可以在 Linux 触手文档中找到示例脚本,但是现在,我们将运行配置脚本,并且大部分情况下只接受默认值。
对于这个例子,为触手的种类选择轮询 (2)是很重要的。轮询触手将连接到 Octopus 服务器,因此我们不需要在防火墙中打开任何额外的端口:
[user@fedora ~]# sudo /opt/octopus/tentacle/configure-tentacle.sh
Name of Tentacle instance (default Tentacle):
Invalid characters will be ignored, the instance name will be: 'Tentacle'
What kind of Tentacle would you like to configure: 1) Listening or 2) Polling (default 1): 2
Where would you like Tentacle to store log files? (/etc/octopus):
Where would you like Tentacle to install applications to? (/home/Octopus/Applications):
Octopus Server URL (eg. https://octopus-server): https://***.octopus.app
Select auth method: 1) API-Key or 2) Username and Password (default 1): 1
API-Key: ...
Select type of Tentacle do you want to setup: 1) Deployment Target or 2) Worker (default 1): 1
What Space would you like to register this Tentacle in? (Default):
What name would you like to register this Tentacle with? (fedora): fedorapi
Enter the environments for this Tentacle (comma seperated): test
Enter the roles for this Tentacle (comma seperated): pi
The following configuration commands will be run to configure Tentacle:
sudo /opt/octopus/tentacle/Tentacle create-instance --instance "Tentacle" --config "/etc/octopus/Tentacle/tentacle-Tentacle.config"
sudo /opt/octopus/tentacle/Tentacle new-certificate --instance "Tentacle" --if-blank
sudo /opt/octopus/tentacle/Tentacle configure --instance "Tentacle" --app "/home/Octopus/Applications" --noListen "True" --reset-trust
sudo /opt/octopus/tentacle/Tentacle register-with --instance "Tentacle" --server "https://***.octopus.app" --name "fedorapi" --comms-style "TentacleActive" --server-comms-port "10943" --apiKey "API-XXXXXXXXXXXXXXXXXXXXXXXXXX" --space "Default" --environment "test" --role "pi"
sudo /opt/octopus/tentacle/Tentacle service --install --start --instance "Tentacle"
Press enter to continue...
Creating empty configuration file: /etc/octopus/Tentacle/tentacle-Tentacle.config
Saving instance: Tentacle
Setting home directory to: /etc/octopus/Tentacle
A new certificate has been generated and installed. Thumbprint:
9B691824225B6A77AB68...
These changes require a restart of the Tentacle.
Removing all trusted Octopus Servers...
Application directory set to: /home/Octopus/Applications
Tentacle will not listen on a port
These changes require a restart of the Tentacle.
Checking connectivity on the server communications port 10943...
Connected successfully
Registering the tentacle with the server at https://***.octopus.app/
Detected automation environment: NoneOrUnknown
Machine registered successfully
These changes require a restart of the Tentacle.
Service installed: Tentacle
Service started: Tentacle
Tentacle instance 'Tentacle' is now installed
在脚本完成配置触手之后,您将能够在实例的 Deployment Targets 页面中看到 Linux 触手。
触手在行动
接下来,我们将对我们的新触须运行一些东西。
对于这一步,我将建立一个新项目并配置一个 runbook 来安装最新的软件包更新。
在您的实例中,创建一个新项目,我将我的项目称为 Pi🥧
然后我创建了一个名为的新 runbook 升级它!添加了一个脚本步骤,包含:
sudo yum upgrade -y
该步骤被配置为针对角色 pi 运行 bash 脚本,该角色是我在前面的配置脚本中指定的角色。
【T2
针对一个角色运行,意味着您可能有许多具有相同角色的目标,执行将针对每个目标运行。添加一个调度触发器,您就有了一个真正的 DevOps 流程。
这是一个简单的例子,这也可能是一个在 Raspberry Pi 上更新 PiHole ( pi-hole -up
)或清理 Docker 图像(docker images prune --force
)的操作手册,或者是一个将最新版本的内部 Python 应用程序部署到远程物联网监控设备的部署项目。
结论
使用 ARM 硬件的原因有很多,从成本节约到性能或远程物联网设备。能够通过触手将它们连接到 Octopus 实例允许您向它们部署应用程序更新,或者使用我们的 Runbooks 功能来集中管理操作系统和应用程序。轮询触手的额外好处是避免复杂的防火墙配置,并在公共互联网上暴露 SSH 端口。
介绍用于高度安全的 Linux 服务器的 Linux 触手- Octopus Deploy
Octopus 正在通过引入原生 Linux 触手来扩展我们支持的部署目标列表(您可以部署到的服务器和云服务)。触须一直是我们最受欢迎的部署目标。它们可以被配置为与 Octopus 服务器通信的方式提供了最大的灵活性,但是缺点是它们是 Windows 独有的。
我们一直在努力为 Linux 部署目标带来一流的支持,我们很高兴地宣布 提前获得 Linux 触手 。如果你想等到正式发布,你可以在我们的路线图页面上关注这个特性和其他特性,你甚至可以注册更新。
部署到 Linux 服务器的现有方法是在 Octopus 中将它们配置为 SSH 目标,虽然这种方法对大多数应用程序都很有效,但它要求目标机器有一个开放的 SSH 连接。不幸的是,一些公司在高度安全的环境中运营,在那里不可能在生产服务器上打开端口 22。
那么一个原生的 Linux 触手如何解决这个问题呢?Tentacle 支持轮询模式,通过这种配置,Tentacle 轮询 Octopus 服务器,以定期检查是否有任何任务需要它执行。最大的优点是目标服务器不需要任何防火墙的改变,它只需要 Octopus 服务器上的一个开放端口。这个功能消除了在 Linux 机器上运行 SSH 服务器的需要,这解决了许多团队的安全问题。
示例 1: Octopus 云部署到 Linux 服务器
假设你的团队正在使用 Octopus Cloud 将几个微服务部署到多个 Linux 服务器上。对于当前版本的 Octopus,您的 Linux 目标需要运行一个可以通过互联网公开访问的 SSH 服务器。虽然 SSH 被认为是更安全的远程访问方法之一,但一些公司在高度安全的环境中运营,在这种环境中不可能在生产服务器上打开端口 22。
在这种情况下,将您的 Linux 目标配置为 SSH 目标是唯一的选择,但是出于安全考虑,这可能是不可能的。
解决方案:Linux 触手!我们现在可以设置 Linux 目标来运行触手服务,并配置它以轮询模式与 Octopus 服务器通信。使用这种方法,我们不需要打开任何入站端口,Linux 机器只需要在出站端口(默认为 10943)和端口 80 或 443 上通信,就可以访问 Octopus 服务器 web 门户。
示例 2:只有 HTTPS 的 DMZ 中的服务器
假设您的团队正在尝试部署在 DMZ 中运行的高度安全的 Linux 服务器,除了 HTTPS 之外,不允许任何传入连接用于 web 流量。我们行业中的安全性和合规性限制使得不可能授予 SSH 访问权限。
解决方案:这是触手轮询模式真正的亮点,它允许你的触手主动轮询 Octopus 服务器,而 Octopus 服务器不需要知道任何关于 Linux 目标的 IP 地址。
Linux 触手早期访问
我们目前正在为 Linux 触手提供早期访问,以获得反馈并验证其设计。我们的文档涵盖了如何开始的所有细节。我们正在构建 DEB (Ubuntu/Debian)和 RPM (CentOS/Fedora)包,以及用于手动安装的. tar.gz 归档文件。触手是用。NET Core 2.x,所以如果你的 Linux 版本支持的话你应该可以运行触手。我们喜欢反馈,所以请在#linux-tentacle
频道中加入我们的社区 slack 的讨论。
包扎
Linux Tentacle 为在高度安全的环境中部署到 Linux 的团队提供了更大的灵活性,并补充了我们现有的 SSH 部署目标支持。我们的目标是为 Linux 生态系统带来我们已经为 Windows 提供的所有相同的特性。不要忘记在我们的公共路线图页面上注册更新。
触手版本和何时更新- Octopus 部署
版本化项目
...升级或者安装最新版本的章鱼服务器就可以了,不一定非要用最新版本的触手。
我们 Octopus 喜欢使用 GitVersion 来版本化我们的构建,因为这提供了一种简单的机制来基于 Git 库的历史语义版本化我们的产品。当多个开发人员向同一个项目添加修复、分支和补丁时,这是非常有用的,允许系统在构建时确定正确的“下一个”版本,而不必弄清楚谁刚刚添加了什么,以及我们何时停止发布。
当然,另一方面,同一个存储库中的所有项目都将被版本化,不管我们是否对那个特定的项目做了任何更改。这是因为版本化上下文基于存储库状态,而不是其中的代码。由于一系列的依赖(老实说,主要是历史原因),我们主要的 Visual Studio 解决方案包含 Octopus 服务器,目前还包含 Octopus 触手。这意味着每次我们创建一个新的服务器安装程序(我们最近已经做了很多次!),我们得到了一个新版本的触手安装程序作为构建工件。
我们的一些眼尖的用户已经注意到了这种版本控制的同步,并正确地问我们,当他们的服务器升级时,是否应该升级触须。由于触手代码很少改变的事实,你可以放心的知道升级或者安装最新版本的章鱼服务器是没问题的,不一定要用最新版本的触手。现在,在你直接进入下面的评论部分询问我们为什么不做“X”,或者“Y”会是一个更好的方法之前,让我向你保证这是我们计划很快解决的事情,可能通过将 Octopus 触手项目分解到它自己的存储库中。我们考虑过不发布没有变化的触手版本,但是不喜欢这样的想法,那看起来触手好像神秘地跳过了版本号。通过我们打算的方法,我们打破了由版本锁步引起的混乱,但必须确保兼容性是清楚的,因为双方将有不同的版本号。刚刚说了你“不需要更新触手”,还有几点需要考虑。
触手 3.1+要求。NET 4.5
去年年底,我们将触手从 3.1 版本升级到了使用。NET 4.5,以便为 TLS 1.2 提供支持。虽然触手 3.1+仍然支持 TLS 1.0,但这仍然需要。NET 4.5,因此从 3.1 开始的触角将无法与无法升级的服务器一起工作。NET 4.0 。
自从。在 Windows Server 2003 或 Windows 2008 SP1 版上不支持NET 4.5,那么这就意味着对于运行这些版本 Windows 的机器来说,你将无法升级到 Octopus 触手的更新版本。不过不要担心,我们会为触手 3.0.x 提供安全或可靠性补丁。
锁定触手
如果你在一个被。NET 4.0 或者只是不想在你的环境仪表板上看到烦人的消息,我们在 3.2.17 中提供了一个特性,让你可以将你的触手版本“锁定”到当前安装的版本。这将意味着它不会被包括在任何更新,如果你点击“升级所有触角”按钮,也不会提醒你当它是过时的升级后,你的章鱼服务器。这是减少环境页面上一些噪音的好方法,但是你需要关注我们的发布说明,以确保你没有错过任何有趣的特性或修复。值得注意的是,以这种方式锁定您的触手只会对您启用它的单个目标产生影响,因此您需要为您希望其触手安装保持在当前版本的所有目标启用它。
或者,你可以使用同一个 3.2.17 版本中提供的另一个特性来忽略触手更新消息。在更新 Octopus 服务器时,你可能会发现环境页面被黄色覆盖,上面有升级通知,表明有新的触手版本可用。由于实际上可能不需要升级,您可以单击“忽略此更新”按钮来忽略此消息。请注意,这种解除只是使用 cookies 在本地进行处理,并将在您下次更新时再次显示。一旦我们停止让触手版本与每个章鱼服务器版本冲突,这个特性就没那么必要了。同时,如果我们频繁的服务器发布扰乱了您的环境页面,那么这个按钮就是为您准备的!
在八字形窗帘后面
希望这篇短文能让你了解 Octopus Deploy 的一些幕后想法,以及为什么触手下载会有这样的版本。所有软件都需要改进,这是我们希望改进的一个方面。除非我们在发行说明中明确指出触手相关的变化,否则你现在可以少关注下载界面上的章鱼触手版本。虽然目前这可能不太理想,但我们确实计划在新的一年对这一流程做出一些改变。
基础设施作为 Azure 中的代码,具有 Terraform 和 Octopus Deploy - Octopus Deploy
原文:https://octopus.com/blog/terraform-and-octopus-deploy-in-azure
基础设施开发人员编写代码来自动化配置云和内部基础设施的过程,在这篇文章中,我将向您展示如何使用 Terraform 和 Octopus Deploy 将服务部署到 Azure。
先决条件
要跟进这篇博文,您需要以下内容:
- 一个 Octopus 部署的服务器,或者是本地或者是云实例
- Azure 订阅。如果你还没有,你可以注册一个 30 天的免费试用。
- Azure 服务主体(应用程序注册)有权在您的 Azure 订阅中创建资源。
- 初级到中级水平的地形知识。
同时使用章鱼和地球形态
Terraform 是由 Hashicorp 创建的代码为平台的开源基础设施,在 Octopus Deploy 中默认支持。
您可以将 Terraform 资源部署到:
- 蔚蓝的
- 自动警报系统
- 内部部署(硬件和虚拟化环境)
在持续交付和部署工具中使用 Terraform 的主要好处之一是,您可以专注于编写代码,而不是手动部署。将 Octopus 和 Terraform 结合起来,可以让整个生命周期自动化。
地形代码
要在 Azure 中创建资源或服务,您需要编写 HCL 代码。在这一节中,我将向您展示使用 Terraform 在 Azure 中创建资源组的 HCL 代码。
Azure Terraform 提供商
每当您与 Terraform 提供者交互时,您都需要在代码块中指定一些输入和身份验证。用于与 Azure 交互的提供者是 azurerm
提供者。
向azurerm
Terraform 提供商认证有四种方式:
出于这篇博文的目的,我们使用 Azure 服务主体。
提供商需要以下信息:
- Azure 订阅 ID
- 客户端 ID
- 客户机密
- Tenant ID
还有一个features
参数,但是可以留空。
提供者配置块看起来像下面的代码:
provider "azurerm" {
subscription_id = "#{subscriptionID}"
client_id = "#{clientID}"
client_secret = "#{clientSecret}"
tenant_id = "#{tenantID}"
features = {}
}
注意,订阅 ID、客户机 ID、客户机机密和租户 ID 的值都有变量。我将在后面的小节中介绍这些变量的配置。
创建 Azure 资源
资源创建操作将调用azurerm_resource_group
资源类型。资源类型在配置块中包含两个参数:
- 名称:您正在创建的资源组的名称。
- 位置:资源组将要驻留的位置,例如
eastus
。
resource "azurerm_resource_group" "resourceGroup" {
name = "#{resourceGroupName}"
location = "#{location}"
}
有了提供者和资源代码后,它应该类似于下面的代码片段:
provider "azurerm" {
subscription_id = "#{subscriptionID}"
client_id = "#{clientID}"
client_secret = "#{clientSecret}"
tenant_id = "#{tenantID}"
features = {}
}
resource "azurerm_resource_group" "myterraformgroup" {
name = "#{resourceGroupName}"
location = "#{location}"
}
从 Octopus 部署到 Azure 的身份验证
接下来,您需要一种从 Octopus Deploy 到 Azure 的身份验证方法。Octopus Deploy 提供了一种创建帐户的方法,用于对云和内部环境进行身份验证。
创建 Azure 帐户
- 登录 Octopus Deploy 门户网站,进入 基础设施➜账户
- 点击添加账户并选择 Azure 订阅选项。
- 为您用来在 Azure 门户中创建资源的 Azure 服务主体添加相关信息。
- 为了确认 Azure 服务主体工作,点击保存并测试。
在 Octopus Deploy 中创建新项目
从 Octopus Deploy 到 Azure 的身份验证完成后,您可以开始考虑您希望 Terraform runbook 如何存在以及存在于何处。为了确保 runbook 在它自己的项目中,您可以在 Octopus Web 门户中创建项目。
在 Octopus Deploy 中创建项目
- 登录 Azure 门户,进入项目。
- 选择您想要存储项目的项目组,然后单击添加项目。
- 创建一个新项目,并将其命名为 TerraformAzure 。
创建项目后,就该创建操作手册了。
创建章鱼变量
导航到项目的变量部分,添加项目变量:
AzureAuth = AzureAuth Account
clientID = guid_client_id
clientSecret = client_secret
location = eastus
resourceGroupName = your_resource_group_name
subscriptionID = your_subscription_id
tenantID = guid_tenant_id
这些值会因您使用的环境而异。变量的Name
应该与下面的例子相匹配,但是对于您的环境,值会有所不同。
配置操作手册
因为你是在 Azure 中部署服务,而不是为应用程序编码,所以使用 runbook 是最有效的方法。操作手册将为您提供使用 Terraform 步骤模板和创建资源组的能力。
创建一本操作手册
- 导航到项目,选择 操作➜操作手册 。
- 点击添加 RUNBOOK 。
- 创建一个 runbook 并将其命名为 ResourceGroup 。
向操作手册添加步骤
- 导航到操作手册,选择流程,点击添加步骤。
- 点击地形类别。
- 选择应用地形模板步骤。
配置 Terraform 步骤
根据您运行的环境,这些步骤可能会有所不同。例如,您可以使用不同于默认的工作池。以下是 Terraform 要包括的关键步骤:
- 在托管账户下,选择 Azure 账户并将您在身份验证中创建的 Azure 账户添加到从 Azure 部分的 Octopus Deploy。
- 在模板下,选择模板源并使用源代码选项。然后粘贴以下代码:
provider "azurerm" {
subscription_id = "#{subscriptionID}"
client_id = "#{clientID}"
client_secret = "#{clientSecret}"
tenant_id = "#{tenantID}"
features = {}
}
resource "azurerm_resource_group" "myterraformgroup" {
name = "#{resourceGroupName}"
location = "#{location}"
}
如你所见,这使用了你在创建 Octopus 变量一节中创建的变量。
执行操作手册
项目、身份验证、步骤和代码的配置都已完成。现在,是时候看看实际运行的代码了。
- 在运行手册下,你会看到资源组运行手册。点击运行。
- 选择您想要运行 runbook 的环境,然后单击运行。
runbook 执行后,任务摘要将显示您已经使用 Octopus Deploy 和 Terraform 在 Azure 中成功创建了一个资源组。
结论
将持续部署和基础设施作为代码结合起来是任何自动化环境的关键。它不仅为您提供了自动化,还为其他团队成员提供了一个协作、查看发生了什么以及理解流程的地方,而不是自己手动完成。
在本地测试 Kubernetes-Octopus 部署
Kubernetes 是一个复杂的平台,通常部署在许多服务器上以实现高可用性。然而,创建多节点集群对于执行本地测试的单个 DevOps 工程师来说通常是不切实际的。
幸运的是,在一台机器上安装开发 Kubernetes 集群有很多选择。这允许 DevOps 工程师在部署到共享集群之前验证他们的代码和部署过程的许多方面。
在本文中,我介绍了 DevOps 工程师在运行开发 Kubernetes 集群时可以使用的一些选项。
迷你库贝
minikube 是一个跨平台工具,用于创建单节点 Kubernetes 集群。它还为提供了许多例子,展示了如何使用持续集成(CI)服务器来配置它,允许作为自动化测试的一部分来创建和销毁短暂的测试集群。
在在 Windows 上安装 minikube这篇文章中学习如何在 Windows 上安装 minikube 并将其连接到 Octopus。
种类
kind ,在 Docker 中代表 Kubernetes,是一个跨平台工具,支持创建多节点 Kubernetes 集群,全部作为 Docker 容器托管。可能需要一段时间来理解 Docker 运行 Kubernetes 来编排 Docker 容器的概念,但该工具工作良好,并且比其他选项更有优势,因为它不需要 Windows 和 macOS 中的专用虚拟机。
你可以在文章Kubernetes testing with kind中找到更多关于集成 kind 集群和 Octopus 的信息。
MicroK8s
MicroK8s 是 Windows、Linux 和 macOS 上本地开发的一个选项,也是带有企业支持和安全维护选项的生产环境的一个选项。MicroK8s 支持插件,提供了一种方便的方式来安装常见的功能,如图像注册表、仪表盘、入口控制器等等。
K3s
K3s 是由 Rancher 创建的一个轻量级的、生产就绪的Kubernetes Linux发行版。K3s 可以用 k3d 在单台机器上创建多节点集群,为创建开发集群提供了便捷的解决方案。
了解如何整合牧场主和章鱼在岗位部署到牧场主与章鱼部署。
k0s
k0s 是另一个轻量级的、生产就绪的Kubernetes Linux发行版,带有对 Windows server 的实验性支持。它内置支持在 Docker 之上创建集群,用于 Windows、Linux 和 macOS 上的本地开发。
Docker 桌面
Docker 桌面包括一个独立的 Kubernetes 服务器和客户端,适用于 Windows、Linux 和 macOS。与这篇文章中列出的其他选项不同,Docker Desktop 是商业软件,在某些情况下需要付费。
鉴于 Docker Desktop 是支持在 Windows 中启用 Docker 引擎的方法,许多开发人员可能已经拥有启动开发 Kubernetes 集群所需的工具,这使其成为一个方便的选择。
结论
由于开源社区对 Kubernetes 的热切采用,DevOps 工程师有了广泛的选择,可以在他们的本地机器上快速启动开发集群,并在 CI 测试中创建临时集群。虽然 Linux 用户有最多的选择,但对于 Windows 和 macOS 用户来说,仍然有大量活跃且维护良好的项目。因此,通常只需一两个命令和几分钟时间,DevOps 工程师就可以启动一个本地集群进行测试。
了解更多信息
如果你想在 AWS 平台上构建和部署容器化的应用程序,比如 EKS 和 ECS,请查看一下 Octopus Workflow Builder 。构建器使用 GitHub Actions 工作流构建的示例应用程序填充 GitHub 存储库,并使用示例部署项目配置托管的 Octopus 实例,这些项目展示了最佳实践,如漏洞扫描和基础架构代码(IaC)。
愉快的部署!
使用 perste-Octopus Deploy 测试 PowerShell 代码
原文:https://octopus.com/blog/testing-powershell-code-with-pester
当你使用任何代码时,无论是自动化代码还是软件代码,都应该以同样的方式对待。毕竟函数还是函数,变量还是变量。与构建应用程序代码相比,在自动化过程中,有很多事情不经常出现,其中之一就是测试。
单元测试和模拟测试在脚本和自动化中非常重要。
想一想这个场景:我在本地测试了 PowerShell 功能,它工作了!它做了我需要它做的事情,现在我要把它存储在 GitHub 中,以便以后使用。
它是在本地测试的,这很好,但是您运行的测试现在已经是过去的事情了,可能会发生一些事情:
- 可以创建新版本的代码。
- 修改代码可能会引入错误。
- 可以添加新功能。
- 代码用于不同的系统或操作系统。
- 您正在使用的 PowerShell 模块被更新或更改。
- PowerShell 模块正在进行的 API 调用发生了变化。
鉴于以上几点,您在本地运行的测试不再有效。
在这篇博客文章中,您将学习如何使用 PowerShell 最流行的测试框架 Pester 。
先决条件
要跟进这篇博文,您需要具备以下条件:
安装纠缠
如果这是你第一次运行 Pester 框架,根据你的操作系统,你很可能必须安装它。安装过程是通过Install-Module
cmdlet 完成的,它是所有操作系统的 PowerShell 自带的。
若要安装 Pester,请运行以下 cmdlet:
Install-Module -Name Pester
运行 cmdlet 后,您应该会看到与下面的屏幕截图类似的输出。接受所有模块,将安装 Pester。若要确认安装,请运行以下 cmdlet:
Get-InstalledModule -Name Pester
看一看要测试的 PowerShell 代码
在运行任何类型的纠缠测试之前,您需要让纠缠测试使用的代码。首先来看一下“纠缠”,这个测试并不复杂。它可以是由几行代码组成的简单 PowerShell 函数。在这一节中,您将会看到用于测试的代码。
打开 VS 代码,为 PowerShell 函数创建一个新文件。
下面的 PowerShell 函数执行以下操作:
- 创建名为
Create-Dir
的新函数 - 设置一个
cmdletbinding()[]
将功能转换为高级功能。高级功能使您能够使用 Verbose、ErrorAction 等。 - param 块设置了两个参数,即创建新目录的路径和新目录名。
- 用于创建路径的 cmdlet 是
New-Item
。
如果您运行下面的代码并指定一个目录名和路径,您将看到一个新目录已经创建:
function Create-Dir {
[cmdletbinding()]
param(
[string]$path,
[string]$dirName
)
New-Item -Name $dirName -Path $path
}
下面的截图显示了在/Users/michaelevan/目录中创建一个名为 TestPath 的目录的示例:
编写第一个测试
现在您已经有了代码,您可以弄清楚您希望测试是什么样子的了。纠缠测试不仅基于长度,还基于功能。在这一节中,您将看一看一个基本的 Pester 测试来开始。
有一种软件开发实践叫做测试驱动开发(TTD) ,你首先定义测试,然后基于这些测试编写代码。这篇博客文章并没有遵循这种方法,但它确实是一个有趣的话题。
- 在 VS 代码中,打开一个新文件,命名为
Tests.ps1
。Tests
关键字让 PowerShell 知道您正在运行测试,VS 代码将提供一些额外的功能,比如能够从 IDE 中运行测试。 - 在
Tests.ps1
文件中,粘贴以下代码,这是测试本身:
Describe "Directory Creation" {
Context 'Path' {
It 'should contain: path_that_you_used_for_the_function'
$path = 'path_that_you_used_for_the_function'
$path | Should -Exist
}
Context "New Directory" {
It 'Should create a new directory called TestDir'
$dir = 'TestDir'
$dir | Should -Be 'TestDir'
}
}
在您运行测试之前,让我们先检查一遍。
- 你从
Describe
块开始。Describe 块定义了一组 PowerShell 测试。所有的纠缠文件必须包含至少一个描述块。 - 你会看到的第二个方块是
Context
。上下文块在描述块中定义了子组测试。上下文很方便,因为它允许您编写多个测试块。 - 在上下文块中,您会看到一个名为
It
的关键字。It
用于定义单个测试用例。It
真的很棒,因为你可以让代码听起来像自然语言的句子。例如,一个It
测试可以说它“应该包含:/Users/Michael levan”。 - 在您定义测试的地方,您会看到
Should
。Should
命令用于定义断言,也就是您希望测试用您给它的信息做什么。
一旦测试在Tests.ps1
文件中,您将看到一些运行测试的选项,如下面的截图所示。
运行单元测试
在前面的部分中,您确切地定义了测试应该是什么样子。测试应该有两个单独的测试;一个测试路径,另一个测试新目录是否叫做 TestDir 。
在这一节中,您将学习如何运行测试以及输出应该是什么样子。
在Tests.ps1
文件中,点击运行测试按钮运行测试。运行测试后,您应该会看到类似下面截图的输出。
如您所见,测试找到了目录,并确认正在创建的新目录是 TestDir 。
恭喜你。您已经使用 PowerShell 测试框架 Pester 正式创建并运行了一个测试。
结论
当您编写任何类型的代码时,您都知道它看起来像什么,如何工作,但是一天、一周、一个月或一年后,代码可能会发生变化,如果发生这种情况,就有可能出现功能差异,甚至引入错误。当你将测试融入到任何代码中时,这些事情发生的风险就会降低。
在这篇博客文章中,你学到了什么是纠缠,为什么你应该使用它,以及如何使用它。如果你想深入研究《纠缠》,我推荐亚当·伯特伦的这本书:纠缠书。
如果你想找到这篇博文中使用的代码,请查看 GitHub repo 。
使用触手章鱼部署测试 PowerShell 脚本
Octopus 允许您将 PowerShell 脚本嵌入到您的包中,这些脚本会在部署过程中自动执行。这是一个很好的挂钩,可以让您:
在 Octopus portal 中定义的变量会自动传递到您的脚本中,因为变量的作用域可以是环境或机器,所以这提供了一个很好的方法来为不同的环境参数化您的脚本。
触手代理在进程中托管 PowerShell,这意味着脚本在普通 PowerShell 中的运行方式与在 Octopus 中的运行方式有时会有所不同。测试脚本总是很困难——你必须将它们打包,创建一个发行版,然后部署这个发行版,只是为了看看对脚本的一个小的改变是否有效。
如果您在运行 PowerShell 脚本时遇到问题,您现在可以使用触手可执行文件直接测试脚本,这意味着它们在与部署完全相同的上下文中运行。您可以从命令行使用以下命令来完成此操作:
Tentacle.exe run-script -v Message="Hello world!" -v Repeat=5 -f MyScript.ps1
-v
参数允许您传递变量(就像它们是部署中使用的变量一样)。-f
参数指定要运行的脚本文件。你也可以使用-w
来设置一个自定义的工作目录。
假设我的脚本是这样的:
foreach ($i in (1..$Repeat))
{
write-host $Message
}
输出如下所示:
这个特性在 build 1.0.16.1276 中提供。希望它能让在触手环境中测试 PowerShell 脚本变得更容易!
了解更多信息
测试地形代码- Octopus 部署
测试代码是开发人员能做的最重要的事情之一。事实上,有一种叫做测试驱动开发( TDD )的编码实践是基于先写测试,然后基于测试写代码。即使你不遵循测试驱动开发,考虑在代码中实现单元测试和模拟测试仍然是极其重要的。没有测试,您实际上是在部署代码,并希望它会按照您认为应该的方式工作。即使代码部署得很好,如果代码被修改了,你也不知道会有什么结果。
在这篇文章中,你将关注用一个叫做 Terratest 的框架来测试 Terraform 代码。Terratest 是最受欢迎的 Terraform 测试框架之一,具有许多不同的工具集,功能丰富。
先决条件
要跟进这篇文章,您应该具备以下条件:
- 对编程的理解。虽然你不会深入 Golang 的基础知识,但如果你过去编程过,你应该会看到熟悉的概念(方法、函数等。).
- 关于地形的中级知识。
- Azure 订阅。如果你没有,你可以注册一个 30 天的免费试用。
- 文本编辑器。出于本文的目的,您将使用 Visual Studio 代码( VS 代码)。
- Golang 已安装。根据操作系统的不同,您可以找到安装说明。
单元测试和模拟测试
在进入测试部分之前,让我们回顾一下测试到底是什么。您通常会使用的两个主要测试是:
单元测试
单元测试是一种用于测试特定代码单元的软件测试方法。例如,假设您有一个名为Car
的类和该类中一个名为Ford
的方法。Ford
方法返回几个值;福特是什么颜色,福特是什么型号,福特是哪一年的。您可以运行该代码来查看结果是否正确,但是如果您不想创建任何特定的东西,而只想查看代码是如何构建的呢?这就是单元测试的用武之地。单元测试可以测试值应该是什么。
假设结果应该是以下值:
- 年份= 2020 年
- 型号= F150 套索
- 颜色=铂。
当单元测试运行时,如果这些值返回 true,则单元测试通过。如果结果返回为 false,则单元测试失败,它会让您知道失败在哪里。
模拟测试
模拟测试是另一种软件测试方法,它可以伪造资源的创建,也可以实际创建资源。假设您有一些 C#代码,您想为一个前端 web 应用程序进行测试。单元测试可以很好地确认方法在 C#代码中实际工作,但是如果您想测试实际的部署呢?您可以实现一个模拟测试,而不是一次又一次地手工部署测试,或者创建一个连续的交付管道来测试代码。模拟测试将部署代码,确认代码工作正常,或者保持部署不变,或者删除部署。在典型的模拟测试中,资源在模拟成功运行后立即被删除。
编写要测试的 Terraform 代码
在上一节中,您学习了什么是单元测试和模拟测试。在本节中,您将创建一个 Terraform 模块,该模块将在 Azure 中部署一个虚拟网络。您编写的代码将是用于测试目的的代码。
让我们检查一下 Terraform 代码的每个部分,以确认正在部署什么。要了解这一部分,您应该打开一个文本编辑器,如 Visual Studio 代码。
main.tf 配置
第一部分将是azurerm
提供者。azurerm
提供者对 Azure 进行 API 调用来创建资源:
provider "azurerm" {
version = "2.0.0"
subscription_id = var.subscriptionID
}
第二部分是第一个资源。第一个资源在 Azure 中创建了一个网络安全组(NSG)。NSG 非常类似于防火墙:
resource "azurerm_network_security_group" "OctopusSG" {
name = "OctopusSG"
location = "eastus"
resource_group_name = var.resourceGroupName
}
第三部分将创建第一个网络安全规则。可以把网络安全规则想象成防火墙上的端口规则。第一个端口规则是端口 80 对世界开放:
resource "azurerm_network_security_rule" "Port80" {
name = "Allow80"
priority = 102
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "80"
source_address_prefix = "*"
destination_address_prefix = "*"
resource_group_name = azurerm_network_security_group.OctopusSG.resource_group_name
network_security_group_name = azurerm_network_security_group.OctopusSG.name
}
第二个网络安全规则针对端口 22,它将允许安全外壳(SSH)连接到网络安全组中的任何虚拟机:
resource "azurerm_network_security_rule" "Port22" {
name = "Allow22"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "443"
source_address_prefix = "*"
destination_address_prefix = "*"
resource_group_name = azurerm_network_security_group.OctopusSG.resource_group_name
network_security_group_name = azurerm_network_security_group.OctopusSG.name
}
正在创建的第四个资源是 Azure 虚拟网络本身。虚拟网络将有一个地址空间(CIDR 范围)为10.0.0.0/16
和两个 DNS 服务器8.8.8.8
和8.8.4.4
:
resource "azurerm_virtual_network" "octopus-vnet" {
name = "octopus-vnet"
location = var.location
resource_group_name = var.resourceGroupName
address_space = ["10.0.0.0/16"]
dns_servers = ["8.8.8.8", "8.8.4.4"]
}
第五个也是最后一个资源是为虚拟网络内部的子网创建的。子网将有一个地址前缀(子网范围)为10.0.1.0/24
:
resource "azurerm_subnet" "octopus-sub" {
name = "testsubnet"
resource_group_name = azurerm_network_security_group.OctopusSG.resource_group_name
virtual_network_name = azurerm_virtual_network.octopus-vnet.name
address_prefix = "10.0.1.0/24"
}
可变配置
变量将由三个值组成:
- 订阅 ID
- 资源组名
- 位置
下面是没有任何默认值的变量配置。这使得代码可重用,并且在大多数环境中更容易实现:
variable "subscriptionID" {
type = string
description = "Variable for our resource group"
}
variable "resourceGroupName" {
type = string
description = "name of resource group"
}
variable "location" {
type = string
description = "location of your resource group"
}
Tfvars 配置
为了在运行时传递变量,您将使用一个terraform.tfvars
配置来尽可能地保持代码的可重用性。
下面是将要使用的tfvars
配置:
subscriptionID = value_here
resourceGroupName = "OctopusRG"
location = value_here
有了main.tf
、variables.tf
和terraform.tfvars
,您就可以开始查看 Terratest 来测试您编写的代码了。
首先看 TerraTest
在上一节中,您编写了将用 Terratest 测试的代码。在本节中,您将了解 Terratest 如何与 Terraform 一起使用。
Terratest 框架可以在 GitHub 找到。
Terratest 是一个 Go 库,帮助你实现基础设施即代码测试。Terratest 由 Gruntwork 创建和维护,这是一个提供 DevOps 即服务的平台。你可以了解更多关于 Gruntwork 。
在 Terratest GitHub 上的 examples 目录下,可以看到几个不同的例子。让我们来看看docker-hello-world-example:
正如您从下面的截图中看到的,可以通过运行build
和run
命令以标准方式构建 Docker 映像。
当 Docker 容器运行时,您可以使用 Golang 命令行来运行测试。您使用的命令行工具是go test
:
测试运行后,您将看到一个输出,告诉您测试是否成功完成。
在 Terratest 中编写测试
既然您已经了解了将要测试的代码和 Terratest 框架,那么是时候使用 Golang 创建一个新的测试了。
对于本节,您应该打开 Visual Studio 代码,并在与 Terraform 代码相同的目录中创建新文件。新文件可以随意命名,例如terraform-test.go
。
让我们首先设置package
并将其称为测试:
package test
}
接下来,您需要指定几个不同的 Golang 库。将使用的两个 Golang 库是:
package test
import (
"github.com/gruntwork-io/terratest/modules/terraform"
"testing"
)
当库在导入块中之后,设置新的函数。这个函数可以被命名为任何你喜欢的名字,但是在这篇文章中,我们称它为vnet_test
。通过在新创建的函数中调用testing
库来使用它:
package test
import (
"github.com/gruntwork-io/terratest/modules/terraform"
"testing"
)
func vnet_test(t *testing.T) {
}
}
最后,您需要添加测试本身。首先,将创建一个名为terraformOptions
的变量,它利用了terraform.Options()
。然后,代码使用一个名为defer
的语句,在虚拟网络被测试和创建后立即销毁它。在 defer 语句之后,您将看到用 Terraform 初始化和创建虚拟网络的.InitAndApply
方法:
package test
import (
"github.com/gruntwork-io/terratest/modules/terraform"
"testing"
)
func vnet_test(t *testing.T) {
terraformOptions := &terraform.Options{
}
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
}
当您有了terraform-test.go
文件中的代码后,在命令行上运行以下命令:
go test -v terraform-test.go
恭喜你!您已经使用 Terratest 成功创建了一个 Terraform 测试。
结论
在这篇文章中,您了解了测试的关键概念,以及为什么在任何代码中实现测试如此重要。无论是后端代码,前端代码,还是基础设施代码。您首先了解了两种关键的测试类型,单元测试和模拟测试。然后,您编写了一些 Terraform 代码,开始测试创建 Azure 虚拟网络。之后,您先看了一下 Terratest 框架及其提供的功能。最后,您编写了一个 Terratest 来测试 Azure 虚拟网络的创建,并在创建成功后销毁它。
对于你的下一个挑战,尽管在这篇文章中没有解释,看看厨房平台。Kitchen-terraform 是另一个即将推出并越来越受欢迎的测试框架。
使用 Kind - Octopus Deploy 创建测试 Kubernetes 集群
开始使用 Kubernetes 可能有点让人不知所措。像 Minikube 、 K3s 、 Docker Desktop 、 MicroK8s 和 Kind 这样的工具如此之多,即使知道使用哪个测试发行版也不是一个容易的选择。
为了本地发展,我发现自己在使用 Kind。它可以快速启动,并与 WSL2 很好地集成,允许我在 Windows 和 Linux 开发之间快速切换。
在这篇博文和相关的截屏中,我将向您展示如何使用 Kind 和 Octopus 的托管实例快速启动并运行本地开发的 Kubernetes 集群。
截屏
下面的视频演示了将 web 应用程序部署到 Kind 创建的开发 Kubernetes 集群的过程。文章的其余部分提供了其他资源的链接和演示中使用的脚本副本:
https://www.youtube.com/embed/sMt2-enODC0
VIDEO
启用 WSL2 Docker 集成
要从 WSL2 实例中使用 Kind,Docker Desktop 需要启用 WSL2 集成。这可以通过选择使用基于 WSL2 的引擎选项来实现:
Docker 随后在目标 WSL2 实例中公开:
安装种类
Kind 是一个自包含的 Linux 可执行文件,下载后放在 PATH 中以便于访问。 Kind 快速入门文档提供了关于安装 Kind 的说明,在我的 WSL2 Ubuntu 实例中是通过运行:
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.8.1/kind-linux-amd64
chmod +x ./kind
sudo mv ./kind /usr/local/bin
Kind 文档有到最新版本的链接,可能已经从上面的 URL 更新了。
创建集群
使用以下命令创建新的开发集群:
kind create cluster
然后,可以使用存储在~/.kube/config
文件中的配置来访问这个集群,该文件是在创建集群时由 Kind 创建的。该文件的示例如下所示:
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJd01Ea3hOREF4TXpBd04xb1hEVE13TURreE1qQXhNekF3TjFvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBS0xPCmV3Y3BBbThwNzB1UnBLSDBJV0Zod043MFJTNTZDTTVXd2xGR0d4YmZaZ0s4endiQmY4aWRzUS9hK1lHK1RWR3gKazBQZDdma0NIVG9yU1I5ajlhSEZLQVlpN3VDbkJoVGVmNjgxVHBJWFBtU3lqUFVpbkxrSG4yRXVNNitESWRTVwpMd2ExaUNwVVVqc0pTOTZ6UnViM2dOdHdvUndCZEo0d3J3SitYUm95VFpIREhtalZkZFJ5Qk1YRGN3dzNNS1BRCmFaRzA0dUtwRlRNeEgyakNrQm9sMW9zNTByRWJrdXY2TVhTVGdvbEpzMEVsSTZXckVpNk00cXdGRWFDWmpBcisKcmtyZUZvdDdXeVpPc3N1Rk91azk4ZS9sb0tvMmtOMVhwcVZKSk55c3FRbmNqRHIzV044VHowZTJOWjZndm9ZWgpNc1RsbTJCTFFqUnFRTllqU3FrQ0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFFSkNzUTg4Rk5MdjJPc0h5Zk96elFPSzdzRVUKVU5jVDhLa3EvbWE2SjkrSWsxY0ZpOHdhdnFZdi93SkNMS2xPc1FML3FUZ2tQSldlb05NV0YwYitMYU5INnUxTgp4NDF2aGNzYjI5Nks0d3orUi9uQzBZWkd1VStSMzFCaHRaS3p2ckI2L1dCSGZLSkYxVlQxOExxcTFvMkRKM1paCno0a1d2UGdEeEc3UjU1eGVTcWkxc2pZWDJmSnRpajNBREhBSGRwRmN4TldUVmNJVm4zMzJNczlCcEtBK1kxbkcKb1pXeXd5Rm8xektDdmNZeEltU2FabXYzTmpiQytlU0VrU1RrdjFCTmt6Z0lMVWtrbUNFOFdWMXMvWDVmN3V3eApNR1d4YVVMWDRvRFJ5Z1FCaitvL09Eb0lTQU5HZDRUWXFxcHVBS29IdC9jZ1VPSWl6NkNzdGp1dElWVT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
server: https://127.0.0.1:39293
name: kind-kind
contexts:
- context:
cluster: kind-kind
user: kind-kind
name: kind-kind
current-context: kind-kind
kind: Config
preferences: {}
users:
- name: kind-kind
user:
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM4akNDQWRxZ0F3SUJBZ0lJUzViczdNb0pEbmt3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TURBNU1UUXdNVE13TURkYUZ3MHlNVEE1TVRRd01UTXdNVEZhTURReApGekFWQmdOVkJBb1REbk41YzNSbGJUcHRZWE4wWlhKek1Sa3dGd1lEVlFRREV4QnJkV0psY201bGRHVnpMV0ZrCmJXbHVNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQW5xM0VDSFYwc0RmOXJIQnUKS09jelQ4L2pmcVkrbzU5Rm5MZWJZNUFRQVBLeWhCVm1vR3ZTaUVQSzhYUm5EMjA1dWFiTlBteFVLQmhBVFBjcApobXpFa2pVNjAwQkVCcnN6ZDJ6KzlmalUxQlFrUU9vL0ljSkM5YnBwdXhvbnNxVjhvWmY3L0R2OUpGamVIOTU3CkdDR1FsWXBwM2trb1kzc2VVaG9wOFY5SzJrYzJGa2srZjVpRXpIUFdiZkNsYTRpK01scVVxU21iakVYUGlvWnQKYUprMWxQbExLMVRZWER3QjI2N2ZYWnhsZU9Vcm5uem51Y0hJQTBLYjlnRFE5K25STFBWYm5yOU1wM2IzVG5RdwpKbno3bGpvclE2Y1htQmJ5b293dXZMTzZqMkpNcjhYOFZSUlgzMCtFOWNkVXpCSXRySUkyK0krekF6a3FsTWl3CllkUlVMd0lEQVFBQm95Y3dKVEFPQmdOVkhROEJBZjhFQkFNQ0JhQXdFd1lEVlIwbEJBd3dDZ1lJS3dZQkJRVUgKQXdJd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFGRGI1Q0thV0ZEc2RlV0xBS2plT0MyL0xlRnhYbUNzL2JuYgoxcGxIc0Z1Vmc0U3FlVmFVTGlidkR2bkd3Q3pEbUJuWFovNkxqUDhrWW9Wa3pobkYvS0paLys1dzJTelhEYzVECnFRaHhhWWlaclRQUlBsVmxpWWxTRy9XS3dlMTRxTEMvY01Ed1AxYU9aRXVPQ1huVnFZN1IwVlRydVJYTFREbW8KRjRVWHBicmw0R0t5V04zUWo3eXBKWkVqenFYeVJJWHJwNVpyQjF1WDQ0RzVqeUlEYXpIZXNRdk1mQnlPYTBHSgpMbUMvWDR1dFRaUEk1VTZRQWdjNzdaVGNoNkVUSXVjLzNZT3N5c1JHZWZXNUhpVFBESjFhNWQ3TkViT09HQ1N5CkN0UStiL2ljbGtHTHBDbmltb0RUdkp4QzNrSjNTZjJJKzlGQ1F2V0NzWU0vYnpZOWxHYz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBbnEzRUNIVjBzRGY5ckhCdUtPY3pUOC9qZnFZK281OUZuTGViWTVBUUFQS3loQlZtCm9HdlNpRVBLOFhSbkQyMDV1YWJOUG14VUtCaEFUUGNwaG16RWtqVTYwMEJFQnJzemQyeis5ZmpVMUJRa1FPby8KSWNKQzlicHB1eG9uc3FWOG9aZjcvRHY5SkZqZUg5NTdHQ0dRbFlwcDNra29ZM3NlVWhvcDhWOUsya2MyRmtrKwpmNWlFekhQV2JmQ2xhNGkrTWxxVXFTbWJqRVhQaW9adGFKazFsUGxMSzFUWVhEd0IyNjdmWFp4bGVPVXJubnpuCnVjSElBMEtiOWdEUTkrblJMUFZibnI5TXAzYjNUblF3Sm56N2xqb3JRNmNYbUJieW9vd3V2TE82ajJKTXI4WDgKVlJSWDMwK0U5Y2RVekJJdHJJSTIrSSt6QXprcWxNaXdZZFJVTHdJREFRQUJBb0lCQUFlQ0YxMkRHVU5oVXRwKwo4MmR5RVNaOG9yb1NhYkphVGZQdGFDZmM0RFQ3UnVFakZoa1BJUVlibHhXM3VVeXNrV2VzY2RlN1Rud2JNYWV5CnBqOWJGQzRLNEw2d01zZlN3Y3VyMTZDUjVwZ21YOVRHZ0xnN05lbmtxUzRXUGJ5aFFmVnZlSmZseXNPV2hPUWoKSmRYdGVLYnF4cm1pNG90YWZ3UEpneVNOcXNBTFBYblFDRzZMTnRBdWJrTXA0Qm94dE82MVBCZkN1QzlUZ3BpMQovVkIzTTdTQnJWcW4wUTkySzVzNWpvRnlSZ2dUMlRaUHZXT0NqWnVRZDNLWWIwT1NaZHhBbjY2ZVRJbks4bVlECnVqMFE2eE9KZGllZHl5a2dpc2pwZ0hMZUY3c3ZZTWRrZXI5SGhvOXBtRkNzY0VRMmF0TFZyZjRacjRHNXgwVDkKOExzSldKa0NnWUVBdzNjQ1BndDA1T1NSK202RlFlK3dsaURxWnhKTWFxa3lLcGJuNHVjMTRQMk1uN25BckVZNApaMllNUVZGVGpYai9hYXdpSzNPUSs2eCszZDliVkN6amxzZ05wcXlBeTczOUpDN2xObG5vL3hpdDBWTE5mSE9SClNGalRxdzNlWFRaYVZKcVJQaDgvaEs1ZlJvZDhCOThsM0hFZ3BsVjU1M0hnQXMwVDdUa2FTR3NDZ1lFQXo5SkEKOUlTdDM0VzBsVWhOcGErMFBDMVQ0T1dpMUd3NVlMNW4rSU1wWFFrOEk3S0k0c0ZMYTNRaXczSFlIamxkYjkveQo2RUU5LzBQYWhzV3JzTTB5MnFrT0JHMEE1OFJKV1lZK3k0RGM4OGpnSzBxWEt4K1g2cm1qN2J2L1ZjLzdnYU1ECk12WW80TkZUL2RuM0JJM3Q5eVVNeTlpd2M0Zi9BYjNkWnQyWXBFMENnWUVBbTlZVUNaZGt1T0NxcWJqWHNUd0IKMDQrbWtrcDZka2N5NGRXeVJxc0R2NzhtRUdvdC9LdDNhS2hwZU9IMzlVRFVrVkZWWk1NY2dpcUNjeTRTU0VnSgpvenNYOXh4dEN3TU1BWDhKNjQwL1A3SlRVaUhzQmg2MVk3SzkveEJ0aW04OUVWcXlGWThnT3c0eWs2Nk02bEcwCmc4NEZzOWROKzRKRWtMY2ovZXVhMHNVQ2dZQk1xRE9KZmo5Y2ljYzRvWGp5dXNMeXg0MS9FWFZrZ1o4UWptdHYKZ1lJS2JWT2ZuMFZhenczd3p0L2IwK3h5Q1pycm4ySE1SZlNHYWhMN1Q0S3JMcVdwZmw1TFI2SGoyOFZxbmxnZgpYS01qMFY3TzJTNjFtMnZBQzBYcWRVUVQ5U25DZ2N5MlNaSitpdmcrVk40RzhndHE5R0dwOTMzdXY2VlNrU1JQCncwR0FxUUtCZ0FYcDBKaXZEMmVvcmtTVzdtZFdpcHB4Q25sOGRyMGJ6WUg5Vm1HUEJoR2dnWTdPeUd4WXBJWEoKUzFicS9Bd3kxdlVUVTVpcGJha3dXYzlkbEdLWnczRXUvVDZwWEFQdklIeUNNc2xIWDdxVjFFSXQxekI4SFd1MgpyeTVpRWVhTDc2VU1Dc1NDTFdpYkk0RHJBUjJwQ2NMOUFxYU1DMTQ1YllFQS9oWVdTSDlCCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==
下面的脚本将client-certificate-data
和client-key-data
字段提取到一个 PFX 文件中,将certificate-authority-data
字段提取到一个纯文本文件中。
请注意,您需要安装jq
和openssl
应用程序来运行这个脚本。
#!/bin/bash
CONTEXT="kind-kind"
CERTIFICATE=$(kubectl config view --raw -o json | jq -r '.users[] | select(.name == "'${CONTEXT}'") | .user."client-certificate-data"')
KEY=$(kubectl config view --raw -o json | jq -r '.users[] | select(.name == "'${CONTEXT}'") | .user."client-key-data"')
CLUSTER_CA=$(kubectl config view --raw -o json | jq -r '.clusters[] | select(.name == "'${CONTEXT}'") | .cluster."certificate-authority-data"')
echo ${CERTIFICATE} | base64 -d > client.crt
echo ${KEY} | base64 -d > client.key
openssl pkcs12 -export -in client.crt -inkey client.key -out client.pfx -passout pass:
rm client.crt
rm client.key
echo ${CLUSTER_CA} | base64 -d > cluster.crt
PowerShell 中有一个类似的脚本:
param($username="kind-kind")
kubectl config view --raw -o json |
ConvertFrom-JSON |
Select-Object -ExpandProperty users |
? {$_.name -eq $username} |
% {
[System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String($_.user.'client-certificate-data')) | Out-File -Encoding "ASCII" client.crt
[System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String($_.user.'client-key-data')) | Out-File -Encoding "ASCII" client.key
& "C:\Program Files\OpenSSL-Win64\bin\openssl" pkcs12 -export -in client.crt -inkey client.key -out client.pfx -passout pass:
rm client.crt
rm client.key
}
kubectl config view --raw -o json |
ConvertFrom-JSON |
Select-Object -ExpandProperty clusters |
? {$_.name -eq $username} |
% {
[System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String($_.cluster.'certificate-authority-data')) | Out-File -Encoding "ASCII" cluster.crt
}
安装 Octopus worker
要将本地开发机器连接到 Octopus 实例,需要安装一个轮询工作器。当目标机器没有静态 IP 地址时,轮询工作器会联系 Octopus 服务器并允许通信。
触手是使用章鱼网站上的这些说明安装的。对于我的基于 Ubuntu 的 WSL2 实例,安装是通过以下命令完成的:
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
然后,使用以下命令配置触手实例:
sudo /opt/octopus/tentacle/configure-tentacle.sh
因为 WSL2 不支持 systemd,我们需要使用以下命令手动运行触手:
sudo /opt/octopus/tentacle/Tentacle run --instance Tentacle
上传证书
从 Kubernetes 配置文件创建的两个证书文件被上传到 Octopus:
创建目标
上传证书后,创建一个新的 Kubernetes 目标,使用种类用户证书进行身份验证,使用种类 CA 证书信任 HTTPS API 端点:
创建提要
正在部署的 Docker 映像托管在 DockerHub 上,必须将其配置为外部提要:
创建部署
然后我们用部署 Kubernetes 容器步骤部署nginx
容器。
该部署配置有暴露端口 80 的单个容器:
部署创建的 pod 通过在端口 80 上访问的服务公开:
访问服务
Kind 只公开 Kubernetes API 端点,因此在集群外部无法访问该服务。要从我们的浏览器打开 NGINX,我们需要使用kubectl
将流量从本地端口转发到集群。这是通过以下命令完成的:
kubectl port-forward svc/myservice 8081:80
NGINX 可以在 URL http://localhost:8081 上找到。下面是默认 NGINX 容器显示的欢迎页面:
结论
Kind 和 WSL2 的结合提供了一种创建本地开发 Kubernetes 集群的便捷方式,该集群可以通过轮询工作器向 Octopus 公开。
在这篇文章和截屏中,我们看到了如何在 WSL2 中配置一个开发 Kubernetes 集群,提取 HTTPS 端点和用户身份验证使用的证书,通过轮询触手连接到集群,并创建一个可用于执行部署的 Kubernetes 目标。
愉快的部署!
自动部署和事件源的发展——Octopus 部署
原文:https://octopus.com/blog/the-evolution-of-auto-deployments-and-event-sourcing
这篇文章是我们 Octopus 3.4 博客系列的一部分。在我们的博客或我们的推特上关注它。
Octopus Deploy 3.4 已经发货!阅读博文和今天就下载!
作为一个新特性,我们希望自动部署是我们有信心并自豪地发布的东西;目前,它的易用性和直观性与 Octopus 相当。基本上,它必须工作并让你微笑着点头。我们坚持这一愿景,尽管有几次情况并不明朗。这篇文章回顾了导致我们建立的弹性和瞬态环境的一些决策。
我们开始试图通过在一个巨大的清单中列出我们想要的所有功能来解决这个问题,并在 Balsamiq 中绘制一些模型(因为,谁不喜欢 Balsamiq 呢?).办公室里有演示会议,每个人都集思广益,提出想法和关注点。这最初将是一个小得多的功能集,我们将在更大的多租户版本中包含一些很酷的弹性功能。然而,随着这两个功能的开发,它们很快变得比我们预期的复杂得多,我们一直在计划的任何内部时间表都开始悄悄溜走(到了我们完全停止指定截止日期,只专注于工作的地步)。这是一场自我诚实的比赛,只有我们知道我们何时真正到达了终点:)
我们的功能清单大致如下:
- 当新机器上线时,它将自动部署
- 它应该知道在给定的机器上应该是哪个版本
** 它应该知道在给定的机器上哪个版本是** 它会自动清理旧环境 等等。*
*听起来很简单。不会很难吧?
我们疯狂地开始在现有的环境和部署目标屏幕上添加复选框和单选按钮,渴望开始给用户提供选择加入这些新的弹性和瞬态行为的选项。
一些早期的模型显示了这个特性的简单开端:
随着时间的推移,我们在改进 UI 决策的同时制作了更多的模型,并开发了一个与模型相匹配的版本。然后我注意到一些有趣的事情开始发生...
人们终于可以真实地玩玩 Octopus 里面的功能了。他们不开心。我也没有。有些事情感觉不对劲(即使功能是与每个人都认为是一个好方向的实体模型 1:1 开发的)。至少对我来说,这感觉就像我们突然将现有的环境和部署目标屏幕复杂化了,我们喜欢使用它们的简单性。还有一种挥之不去的感觉是,如果不进一步增加现有屏幕的体积,将来很难扩展这些功能。我们迷失了方向,但决定同时继续开发后端功能,同时给自己时间来解决我们的 UI 问题。
输入机器策略
没过多久,保罗就来帮我们了,他提醒我们一个被称为机器政策的老主意。突然有了这个想法,你可以把一个策略附加到一台机器上。它从环境和部署目标屏幕中抽象和隔离了弹性和瞬态逻辑和,从编程的角度来看,这也使得使用数据模型更加容易。这正是我们所需要的。
一些新的样机+一些新的希望!我们在路上。
我们花了几周时间研究这个想法,基本上是在做我们需要的事情。然而,当我们越来越接近终点线时,仍然有一种挥之不去的感觉,有些事情不太对劲。我们都知道,我们都在几次会议上说过,但有一段时间我们无法确定。你能通过查看那些机器策略模型来发现它吗?
这是模型的“部署缺失版本”部分。我们犯了一个错误,盲目地接受机器策略作为我们所有弹性环境需求的通用解决方案。机器策略的部署部分就像试图将圆钉放入方孔中。虽然我通常完全支持方孔中的圆钉,但在这种情况下,它就是不起作用,因为与部署相关的条件属于项目,而不是机器。保罗已经找到了缺失的部分,并用简单的潦草笔迹描述了这一景象:
这个小涂鸦给了多少微笑是难以形容的。它清除了所有的噪音(你脑中那个一直在说有些不对劲的声音),我们突然知道了终点线在哪里。
输入项目触发器
这是事情变得令人兴奋的时候...
项目触发器给了我们同样的灵活性和分离性,这是机器策略在几周前所促成的,并且只关注自动部署。它还扩展了自动部署的可能性。我们可以将自动部署限制到某些标准,比如角色或环境。我们可以有多个促进不同目的的触发器(一个用于您的开发环境,一个用于您的阶段环境等)。这太棒了。
所以现在我们有了愿景。然而,实现仍然不确定。
“如何”
我们研究了几种实现自动部署的方法。其中一些在实现了 90%之后就被彻底抛弃了(不夸张)。这就像是道路上的最后一个分支。只有一条分支通向实际的终点线,但是所有的分支看起来都像是从你站的地方通向终点线。
个人注释:对我来说,这是一次有趣的全新经历。在 Octopus 之前,我已经习惯于按照严格的时间表和预算工作,所以你只能选择一条路(如果它最终没有变得伟大、艰难,你现在就只能坚持下去)。但是八达通鼓励我们探索所有的可能性,这样我们就能为我们的客户获得最好的结果,这就是其中的一次。
我们首先探索了一个通知系统,我们称之为域事件(就像那些像我一样的 iOS 爱好者的 ns 通知一样)。我们的想法是可以在 Octopus 中注册事件(比如MachineEnabled
、MachineDisabled
、MachineAdded
等)。)我们会监听这些事件,并在必要时通过自动部署做出响应。
比如伪代码。if (event == MachineAdded) then ForwardThisMachineToAutoDeployForProcessing()
自动部署引擎将按计划运行(每 15 秒一次),并决定部署到哪些机器上(基于我们在各种表上的一些标志)。经过几天几夜的集中思考,基本概念已经建立并运行了。不幸的是,这种架构在高可用性 (HA)设置中失败了,我们需要做出决定:
- 继续推进这个域事件架构,并修补它以在 HA 下工作,或者
- 尝试不同的方法。
事件源的概念是作为一个想法提出来的,以避免我们遇到的架构问题。我们已经在 Octopus 中使用了一个名为Event
的表,以便在系统中发生重大事件时创建审计历史。因此,扩展这个Event
表使我们能够非常容易地探索事件源架构。
结合所有的努力
我们不想完全抛弃我们努力工作(实际上是几周的工作和测试)的领域事件架构。我们也喜欢领域事件在将来对 Octopus 的其他领域仍然有用的想法。因此,我们选择以一种不暴露域事件的高可用性问题的方式来重用它们。我们现在使用它们来填充弹性和瞬态环境所需的事件:)
例如,当添加一台机器时,我们为MachineAdded
发布一个域事件。然后,域事件监听器代表我们填充Event
表。这很好,因为这意味着我们有一个单独的类(域事件监听器)来控制哪些事件将被自动部署。
然后,我们的自动部署引擎被简化为一个纯粹的事件源架构,这使得它更加独立,更易于测试(并且在 HA 下工作)。它每 15 秒运行一次,并扫描Event
表,查看自上次通过以来发生了什么事件。然后,它根据正面事件(机器被添加、上线、被启用)和负面事件(机器被删除、离线、被禁用)来分析和平衡这些事件,然后决定自动部署哪些版本。
目前处于测试阶段
说了这么多,我想对整个团队,特别是社区表示我个人的感谢,感谢他们伸出援手,分享他们的想法和反馈。这是一项巨大的努力,并且这一切导致了我们今天所拥有的,目前在 3.4.0-beta0001 中:在机器策略和项目触发器之间有明显区别的弹性和瞬态环境。
在接下来的几周里,我们将通过一些简短的博客帖子来探索这些新功能。我们期待您的反馈,并希望这能让您对这些功能的形成有所了解:)*
Selenium 系列:第一个 WebDriver 测试——Octopus Deploy
原文:https://octopus.com/blog/selenium/3-the-first-test/the-first-test
这篇文章是关于创建 Selenium WebDriver 测试框架的系列文章的一部分。
现在我们已经在 IntelliJ 中配置并导入了 Maven 项目,我们可以开始添加一些测试了。
测试类将在目录src/test/java/com/octopus
中创建。Maven 有一个标准的目录结构,将测试类放在src/test/java
目录下,而测试类本身将在com.octopus
包中,该包映射到com/octopus
目录结构。
要创建新目录,右击顶层项目文件夹,选择新建➜目录。
输入src/test/java/com/octopus
作为目录名,并点击OK
按钮。
将创建新的目录结构。然而,IntelliJ 还不能将新目录识别为可以找到 Java 源文件的位置。要刷新 IntelliJ 项目,这将导致这些新目录被识别,单击Maven Projects
工具窗口中的Reimport All Maven Projects
按钮。
请注意,java
文件夹有一个绿色图标。这表明 IntelliJ 将该文件夹识别为包含 Java 源文件的文件夹。
在octopus
目录中,我们将创建一个名为InitialTest
的类。为此,右击octopus
目录并选择新➜ Java 类。
输入InitialTest
作为类名,并点击OK
按钮。
用以下内容替换默认类别代码:
package com.octopus;
import org.junit.Test;
import org.openqa.selenium.chrome.ChromeDriver;
public class InitialTest {
@Test
public void openURL() {
final ChromeDriver chromeDriver = new ChromeDriver();
chromeDriver.get("https://octopus.com/");
chromeDriver.quit();
}
}
让我们来分解这个代码。
第一步是获得一个驱动类的实例,它与我们控制下的浏览器相匹配。在这种情况下,我们要控制的浏览器是 Google Chrome,它对应的驱动类是ChromeDriver
。这个类来自我们在上一篇文章中添加的org.seleniumhq.selenium:selenium-java
依赖项:
final ChromeDriver chromeDriver = new ChromeDriver();
接下来,我们使用get()
方法打开一个 URL。这相当于在地址栏中输入 URL 并按回车键:
chromeDriver.get("https://octopus.com/");
最后,我们调用quit()
方法关闭浏览器并关闭驱动程序:
chromeDriver.quit();
要在 IntelliJ 中运行测试,单击openURL
方法旁边的绿色图标,然后单击Run openURL()
。
运行该测试会产生以下错误:
java.lang.IllegalStateException: The path to the driver executable must be set by the webdriver.chrome.driver system property; for more information, see
https://github.com/SeleniumHQ/selenium/wiki/ChromeDriver. The latest version can be downloaded from
http://chromedriver.storage.googleapis.com/index.html
当我们试图运行测试时抛出了IllegalStateException
异常,因为找不到驱动程序可执行文件。有帮助的是,这个错误把我们指向了http://chromedriver.storage.googleapis.com/index.html,在那里可以下载驱动程序。
打开此链接会显示许多对应于驱动程序可执行文件版本的目录。您几乎总是希望获得最新版本,尽管应用于列表的排序不会使最新版本变得明显。
在下面的屏幕截图中,您可以看到目录是使用字符串比较进行排序的,这导致 2.4 版出现在 2.37 版之后。然而,从这个列表中(当你读到这篇文章时,这些版本已经改变了),你实际上想要下载 2.37 版本,因为这是可用的最新版本。
或者,您可以访问网站https://sites . Google . com/a/chromium . org/chrome driver/downloads,该网站提供了最新版本的直接链接。
在这个目录中,您会发现许多与您运行测试的平台相对应的 zip 文件。
这些 zip 文件中是驱动程序可执行文件。对于 Linux 和 Mac 用户,该可执行文件被称为chromedriver
,而对于 Windows 用户,它被称为chromedriver.exe
。
异常消息告诉我们,我们需要将webdriver.chrome.driver
系统属性设置为从 zip 文件中提取的可执行文件的位置。
这是通过配置pom.xml
文件中的maven-surefire-plugin
来完成的。下面是显示新插件配置的片段:
<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">
<!-- ... -->
<build>
<plugins>
<!-- ... -->
<!--
This is the configuration that has been added to define the webdriver.chrome.driver system property during a test.
-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.21.0</version>
<configuration>
<systemPropertyVariables>
<!--
This element defines the webdriver.chrome.driver
system property.
-->
<webdriver.chrome.driver>/Users/Shared/tools/chromedriver</webdriver.chrome.driver>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
</project>
在这个例子中,我已经将驱动程序可执行文件提取到/Users/Shared/tools/chromedriver
。
定义webdriver.chrome.driver
系统属性的替代方法是将驱动程序可执行文件放在PATH
环境变量中的目录下。当在PATH
上找到驱动程序可执行文件时,您不需要像上面那样配置<systemPropertyVariables>
元素,因为文件会被自动找到。
在 MacOS 系统上,您可以通过将目录添加到文件/etc/paths
来将新目录添加到PATH
环境变量中。
在下面的例子中,您可以从cat
命令的输出(将文件的内容打印到屏幕上)中看到目录/Users/Shared/tools
已经被添加到了/etc/paths
文件中:
$ cat /etc/paths
/usr/local/bin
/usr/bin
/bin
/usr/sbin
/sbin
/Users/Shared/tools
您可能需要注销并重新登录才能使更改生效,一旦生效,您就可以通过运行echo $PATH
来确认新目录在PATH
环境变量中:
$ echo $PATH
/opt/local/bin:/opt/local/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/Shared/tools
在像 Ubuntu 这样的 Linux 发行版中,额外的定制软件通常安装在/opt
目录中。例如,您可以将 Chrome 驱动程序可执行文件保存到/opt/tools/chromedriver
。
要将/opt/tools
目录添加到PATH
中,将其添加到/etc/environment
中的PATH
变量中。在下面的截图中,你可以看到/opt/tools
已经被添加到已经分配给PATH
环境变量的目录列表的末尾。编辑完/etc/environment
文件后,您可能需要注销并重新登录以使更改生效。
在 Windows 中,您可能希望将驱动程序保存到类似C:\tools\chromedriver.exe
的路径。要将该目录添加到PATH
,需要编辑系统属性。
要查看系统属性,单击 Windows 键+ R 打开Run
对话框,并输入control sysdm.cpl,,3
作为要运行的命令。点击OK
按钮。
这将打开System Properties
对话框。点击Environment Variables
按钮。
环境变量分为两部分,一部分位于顶部,特定于当前用户,另一部分位于底部,由所有用户共享。两个列表都有一个Path
变量。我们将编辑System variables
以确保驱动程序可执行文件对所有用户可用,所以双击System variables
列表中的Path
项。
点击New
按钮,为环境变量添加一个新路径。
将C:\tools
添加到列表中。然后点击所有打开对话框上的OK
按钮保存更改。
给定将驱动程序可执行文件添加到路径或设置webdriver.chrome.driver
系统属性的选项,我通常更喜欢将驱动程序可执行文件提取到PATH
环境变量中的一个目录中。将每个浏览器的驱动程序可执行文件保存在一个公共位置,可以更容易地在浏览器之间切换,而不必记住为每个浏览器定义的特定系统属性,或者根据运行测试的操作系统修改不同的文件名。
因此,在这一点上,我们可以运行测试,没有错误。你会注意到 Chrome 浏览器启动,打开 https://octopus.com/的,然后再次关闭。我们现在已经成功地运行了我们的第一个 WebDriver 测试。
您还会注意到,浏览器窗口打开和关闭的速度非常快。事实上,你可能根本没有看到浏览器窗口。
有时,在测试运行后让浏览器保持打开状态是很有用的。尤其是在调试测试时,在测试失败后能够直接与浏览器交互以便确定失败测试的原因是很方便的。
让浏览器保持打开状态就像不调用驱动程序对象上的quit()
方法一样简单。因为是对quit()
方法的调用关闭了浏览器并关闭了驱动程序,所以不进行这个调用将会在测试完成后保持浏览器打开。
在下面的代码中,我注释掉了对chromeDriver.quit()
的调用,当这个测试运行时,它启动的 Chrome 浏览器将在屏幕上保持打开:
package com.octopus;
import org.junit.Test;
import org.openqa.selenium.chrome.ChromeDriver;
public class InitialTest {
@Test
public void openURL() {
final ChromeDriver chromeDriver = new ChromeDriver();
chromeDriver.get("https://octopus.com/");
//chromeDriver.quit();
}
}
不过,给你个警告。不调用quit()
方法将会留下驱动程序可执行的运行实例,由您来手动结束这些进程。
在下面的截图中,你可以看到许多chromedriver
实例一直在运行,因为驱动程序的quit()
方法没有被调用。必须手动停止这些实例,否则它们会随着您运行每个测试而不断累积,每个实例都会消耗额外的系统内存。
这个截图显示了 MacOS 活动监视器,在其中我们看到测试完成后,chromedriver
的实例一直在运行。需要手动关闭它们来回收它们消耗的资源。
当 Chrome 被 WebDriver 控制时,它会显示一条警告消息,称 Chrome 正被自动化测试软件控制。这是 Chrome 的一项安全功能,让用户知道他们的浏览器何时被用 WebDriver API 编写的软件控制。虽然可以手动关闭,但无法使用 WebDriver 关闭或阻止此警告。
这样,我们就有了一个简单但功能齐全的 WebDriver 测试来控制 Chrome web 浏览器,为我们开始构建一些更高级的测试奠定了基础。
Firefox 测试
对于我们启动 Firefox 的测试,需要安装它。火狐可以从 https://firefox.com 下载。
然后需要将geckodriver
可执行文件放在从https://github.com/mozilla/geckodriver/releases获得的某个平台特定下载的路径上。
MacOS 和 Linux 的可执行文件名称是geckodriver
,Windows 的可执行文件名称是geckodriver.exe
。
最后,创建了一个FirefoxDriver
类的实例。这个类和ChromeDriver
类有相同的get()
方法,因为它们都继承自RemoteWebDriver
。
您可以看到在新的openURLFirefox()
方法中创建了FirefoxDriver
类,如下所示:
package com.octopus;
import org.junit.Test;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
public class InitialTest {
@Test
public void openURL() {
final ChromeDriver chromeDriver = new ChromeDriver();
chromeDriver.get("https://octopus.com/");
chromeDriver.quit();
}
@Test
public void openURLFirefox() {
final FirefoxDriver firefoxDriver = new FirefoxDriver();
firefoxDriver.get("https://octopus.com/");
firefoxDriver.quit();
}
}
运行openURLFirefox()
单元测试将打开 Firefox 浏览器,在https://octopus.com/打开页面,然后再次关闭浏览器。
这篇文章是关于创建 Selenium WebDriver 测试框架的系列文章的一部分。
自动化部署的五大好处- Octopus Deploy
原文:https://octopus.com/blog/the-five-big-benefits-of-automated-deployment
每个软件开发团队都应该有一个完全自动化的部署过程
我在会议和活动中遇到的几乎每个人都这么认为。甚至都不算辩论。这是一个宣言。
事实上,只有很少一部分软件开发团队采用“一次点击”,完全不干涉的方法。例如,在 Redgate 的 2020 年数据库开发运维状况报告中,只有 16%的受访者表示他们拥有全自动化流程。对于其他人来说,要么是部分手动的部署过程,要么是完全手动的过程。
你为什么不做?
如果自动化软件部署过程是这样一个“不需要动脑筋”的事情,为什么没有更多的人去做呢?我的理论是,开发团队没有这样做,因为他们认为创建、设置、配置和维护自动化部署机制的开销不值得潜在的短期利益…
“设置自动化部署将花费很长时间!它会占用我的团队开发实际软件的时间!”
“我们的部署流程有如此多的移动部分,我该如何开始自动化它呢?”
“这只会耗尽我们开发人员的精力,他们更愿意解决业务问题,而不是编写脚本通过网络复制文件。”
你可能已经听同事(或你自己)说过类似的话。这种感觉上的开销可能会成为一个相当险恶的障碍。它看起来是如此的庞大和不可逾越,以至于开发团队不知道从哪里开始解决它。
克服这一点的一个方法是非常清楚这样做的好处是什么。所以,废话不多说,下面是我们团队自流程自动化以来看到的部署自动化的 5 大好处。
5 大好处
#1:部署变得更不容易出错,可重复性更高
手动部署容易出错。这是因为它涉及到人类做事情,所以受墨菲定律支配:如果事情会出错,它就会出错。发布过程中的重要步骤可能会被意外遗漏,发布过程中出现的错误可能不会被发现,不正确的软件版本可能会被发送出去,而损坏的软件最终会投入使用。如果你幸运的话,你可以很快恢复过来。如果你运气不好,嗯…这是相当尴尬的。
自动化部署不会受到可变性的影响。一旦配置完成,流程就设置好了,并且每次启动发布时都是一样的。如果你第一次把你的软件部署到一个给定的环境中,它就能工作,那么它会工作第一百次。
#2:团队中的任何人都可以部署软件
通过自动化部署过程,如何发布软件的知识是在系统中获取的,而不是在个人的大脑中。
执行手动或部分自动化的部署通常是组织中一小部分人的责任。事实上,在给定的项目团队中,这种责任落到一个人身上的情况并不少见。如果那个人生病了或者在短时间内不在,释放可能会变成一场噩梦。现在,任何有权访问“部署”按钮的人都可以发起发布。
#3:工程师花时间开发软件
执行和验证手动部署过程通常是一项耗时费力的任务。这项工作可以落在开发团队中的开发人员和测试人员身上,否则他们将花费时间来开发软件的下一组令人惊叹的特性和改进。
启动全自动部署所需的时间以秒计算。这些部署的验证是在幕后进行的,如果实际上出现了问题,团队成员可能只需要在部署上花费更多的时间。因此,团队可以花更多的时间去做他们喜欢做的事情,以及他们被组织起来要做的事情;创造伟大的软件。
#4:部署到新的地方并不令人头痛
自动化部署不仅是可重复的,而且是可配置的。尽管底层的发布过程是永久的,但是目标环境和机器可以很容易地改变。
这意味着,如果需要将软件部署到一个新的测试环境中,或者需要创建一个新的客户端安装,那么部署到额外目标的开销可以忽略不计。这只是一个简单的配置您现有的设置,并依靠您尝试和测试的发布自动化来完成工作的例子。
#5:你可以更频繁地发布
由自动化部署机制执行的单次部署开销很低。低开销的发布过程是一个可以频繁重复的过程。由于许多原因,频繁的发布是可取的,但是问题的关键是频繁的发布促进了真正敏捷的软件开发。
频繁发布的团队可以更频繁地向他们的用户交付有价值的特性,并以增量的方式进行。通过这样做,他们可以从这些用户那里收集关于他们正在创建的软件的持续反馈,并最终调整他们的方法。这种反馈可能是产品取悦目标受众或完全失去目标受众的区别。
揭穿架空神话
这些好处听起来很不错,但是还记得阻碍许多团队自动化部署的险恶的设置开销吗?开销真的只是时间。更准确地说,它是您对创建自动化部署机制所需时间的估计。
好消息是这种开销没有你担心的那么大。有许多很好的自动化软件部署工具可以很快地满足您的需求——例如,如果您从未尝试过 Octopus Deploy,我会尝试一下,因为它非常棒。Octopus Deploy 和其他版本管理工具也与 Redgate SQL Change Automation 集成,帮助您自动将数据库部署到预生产和生产环境中。这些工具可以集成到您现有的基础架构中,并将设置自动化部署流程的开销减少到一两个小时。
留给您的团队一个“一键操作”、完全无需干预、完全自动化的部署流程,这显然是一个显而易见的过程。
这是 Redgate 产品交付主管克里斯·史密斯的客座博文。Redgate 通过加速软件交付和帮助遵守数据保护法规的解决方案,帮助您满足业务和 IT 的需求。了解更多。
Selenium 系列:Maven POM 文件- Octopus 部署
原文:https://octopus.com/blog/selenium/2-the-maven-pom-file/the-maven-pom-file
这篇文章是关于创建 Selenium WebDriver 测试框架的系列文章的一部分。
建立我们的 Java 项目的第一步是创建一个 Maven 项目对象模型(POM)文件。这是一个 XML 文档,它定义了我们的代码将如何构建,它可以访问哪些附加的依赖项,以及测试如何运行。
我们从下面显示的 POM 文件开始:
<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>webdrivertraining</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<selenium.version>3.14.0</selenium.version>
<junit.version>4.12</junit.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>${selenium.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
让我们分解这个 POM 文件来理解各个组件。
项目元素
顶层是<project>
元素:
<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>
<!-- ... -->
</project>
所有 Maven POM 文件都有一个<project>
元素作为根元素,属性定义了 XML 名称空间和 Maven XML 模式的细节。
元素定义了 POM 版本。该元素唯一支持的值是4.0.0
。
https://maven.apache.org/pom.html#Quick_Overview详细介绍了组成 POM 文件的元素。
组、工件和版本
<groupId>
、<artifactId>
和<version>
元素定义了这个项目产生的 Maven 工件的身份。这些值有时被组合起来,缩写为 GAV。
groupId
通常采用反向域名的形式,尽管这只是惯例,并不是严格的要求。我们使用了值com.octopus
,它是 URL【https://octopus.com/】的域名的反义词。
groupId
和artifactId
的组合必须是唯一的,因为许多项目可能共享groupId
,所以用artifactId
来描述这个项目。
如您所料,version
元素定义了这个库的版本。1.0-SNAPSHOT
的值意味着该代码正在向 1.0 版本努力,但是还没有达到稳定:
<groupId>com.octopus</groupId>
<artifactId>webdrivertraining</artifactId>
<version>1.0-SNAPSHOT</version>
包装
元素定义了 Maven 将产生的工件的类型。在我们的例子中,我们正在生成一个 JAR 文件:
<packaging>jar</packaging>
性能
<properties>
元素为属性定义了值,这些值可以被 Maven 直接识别,也可以作为共享公共值的一种方式在 POM 文件中被引用。
这里我们已经设置了project.build.sourceEncoding
属性,这是一个由 Maven 识别的设置,它定义了我们的代码所依赖的依赖项的版本,并在java.version
属性中定义了我们的代码将要编译的 Java 版本:
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<selenium.version>3.12.0</selenium.version>
<junit.version>4.12</junit.version>
</properties>
属性project.build.sourceEncoding
被设置为UTF-8
以指定跨操作系统的 Java 源文件的一致编码。如果未设置该值,不同的操作系统会认为 Java 文件具有不同的编码,这可能会在代码由不同的人或服务编译时导致问题。在后面的文章中,我们将配置一个基于 Linux 的外部服务来为我们构建代码,设置这个属性意味着我们的构建将按预期工作,不管我们在本地使用什么操作系统开发代码。
插件
接下来的设置配置 Maven 构建过程的某些方面。Maven 项目有一个众所周知的默认生命周期,它包含以下默认阶段,如下图所示。
- clean -删除上一次构建生成的所有文件。
- 验证-验证项目是正确的,并且所有必要的信息都是可用的。
- 编译-编译项目的源代码。
- 使用合适的单元测试框架测试编译后的源代码。这些测试不需要打包或部署代码。
- 打包——将编译好的代码打包成可分发的格式,比如 JAR。
- 验证-对集成测试的结果进行检查,以确保符合质量标准。
- install——将包安装到本地存储库中,作为本地部署的其他项目的依赖项。
- 站点-生成项目的站点文档。
- 部署——在集成或发布环境中完成,将最终的包复制到远程存储库中,以便与其他开发人员和项目共享。
请参见https://maven . Apache . org/guides/introduction/introduction-to-the-life cycle . html # life cycle _ Reference了解 Maven 生命周期阶段的完整列表。
每个阶段都由一个插件公开的目标来执行,每个插件都可以在嵌套在<build><plugins>
元素下的<plugin>
元素中配置。
对于我们的项目,我们需要配置maven-compiler-plugin
,它用于在编译阶段编译我们的 Java 代码,以指示它构建与 Java 1.8 兼容的工件。
Java 版本的配置是通过<source>
和<target>
元素完成的。这些元素的值都被设置为${java.version}
,这就是我们引用前面在<properties>
元素中定义的名为java.version
的自定义属性的值的方式:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
属国
最后,我们有了<dependencies>
元素。这个元素定义了我们的应用程序所依赖的附加库。
依赖关系的细节通常可以通过许多 Maven 知识库搜索引擎中的一个找到,比如https://search.maven.org/。下面的屏幕截图显示了对 JUnit 库的搜索:
结果包括具有许多不同 GroupIDs 的工件,但是我们想要的工件具有GroupID
和junit
的ArtifactID
。结果还告诉我们最新的版本是什么。
点击版本号(上面例子中的 4.12 链接)会把你带到下面的页面,这个页面提供了可以添加到一个pom.xml
文件中的 XML,以包含这个依赖项。
JUnit 是一个流行的单元测试库,我们将在编写 WebDriver 测试时广泛使用它。此外,我们添加了对selenium-java
库的依赖,它提供了 WebDriver API 的实现。
注意<scope>
元素被设置为test
用于junit
依赖关系。这表明这种依赖性只在 Maven 生命周期的测试阶段需要,在构建发布工件时不会被打包。
我们在这里使用的是 JUnit 4,而不是更新的 JUnit 5,因为我们将在后面的课程中使用的一些库还没有更新到可以与 JUnit 5 一起使用。
<dependencies>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>${selenium.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
将pom.xml
文件保存到本地 PC 上的某个位置。一旦文件被保存,我们可以将它导入 IntelliJ,从这里我们将继续编写 Java 代码。
首次打开 IntelliJ 时,可以选择从初始对话框中导入项目。单击导入项目链接。
然后选择pom.xml
文件。
IntelliJ 识别出 pom.xml 文件是一个 Maven 项目,并开始从 Maven 导入项目。
这个对话框中的默认值是好的,所以点击Next
按钮。
自动检测com.octopus:webdrivertraining:1.0-SNAPSHOT
(这是组、工件和版本,或者 GAV,工件的表示)项目。
点击Next
按钮。
此对话框允许您选择用于构建项目的 JDK。如果您是第一次安装 IntelliJ,此列表将为空。
如果您没有看到 Java 1.8 SDK 选项,请单击加号图标并从Add New SDK
菜单中选择 JDK。
选择 Java 1.8 JDK 的安装目录,点击Open
或OK
按钮。
在 MacOS 系统上,Oracle JDK 通常位于类似/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home
的目录下。
在 Windows 系统中,你通常会在类似于C:\Program Files\Java\jdk1.8.0_144
的目录下找到 JDK。
在像 Ubuntu 这样的 Linux 系统上,JDK 可以在像/usr/lib/jvm/java-8-openjdk-amd64
这样的目录下找到。
这将把 JDK 添加到列表中。点击Next
按钮。
IntelliJ 项目的默认名称与 Maven ArtifactID 相同。这是默认设置,所以点击Finish
按钮。
然后 IntelliJ 创建一个链接到 Maven 项目的空项目。
在 ide 的右边你会看到一个叫做Maven Projects
的按钮。如果看不到这个按钮,可以通过点击查看➜工具窗口➜ Maven 项目打开窗口。
使用View
菜单或按钮打开该菜单,将显示Maven Projects
窗口。在Lifecycle
菜单项下,您可以看到构成默认生命周期的 Maven 阶段。
双击阶段用 Maven 执行它们。这里我们已经执行了package
阶段。这在目前不会取得很大的成就,因为我们没有要编译或打包的代码,但是操作成功的事实(我们可以从输出中的BUILD SUCCESS
消息中看到)意味着我们有一个有效的pom.xml
文件。
在我们结束这篇文章之前,我想演示一下设置project.build.sourceEncoding
属性的效果。从pom.xml
文件中注释掉这一行并保存修改。
IntelliJ 将检测到文件已被修改,并为您提供自动导入任何未来更改的选项。这通常是个好主意,所以单击通知弹出窗口中的Enable Auto-Import
链接。
从Maven Projects
工具窗口再次双击封装阶段。
请注意,当没有设置project.build.sourceEncoding
属性时,在构建项目时,Maven 会显示以下警告:
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
恢复变更并再次打包项目,您会注意到警告消失了。
这样我们就有了一个导入 IntelliJ 的最小 Maven 项目。在下一篇文章中,我们将开始编写一些 Java 来实现我们的第一个 WebDriver 测试。
这篇文章是关于创建 Selenium WebDriver 测试框架的系列文章的一部分。
Octopus Deploy 3.0 节省时间:delta 压缩- Octopus Deploy
原文:https://octopus.com/blog/the-octopus-deploy-3.0-time-saver-delta-compression
Octopus Deploy 3.0 中一个很酷的新特性是 delta 压缩。Octopus Deploy 中的一个典型场景是频繁部署对包的小更改。例如,您可能推送一些代码更新,但是您的所有库都没有改变。在过去,Octopus Deploy 会将整个包上传到每个触手上,不管变化有多小。随着 delta 压缩的引入,只有你的包的改变会被上传到每个触手。这在理论上听起来很棒,但我很好奇 delta 压缩在实践中会产生什么样的好处。
对于这个实验,我设置了 2.6 和 3.0 服务器,每个服务器有 20 个触角。触角位于世界的另一端,因此网络条件会减缓包的获取,使其成为部署的重要部分。我执行了 50 次 110MB 包的部署。每个包包含 10MB 的更改。
结果是:
2.6 完成一次部署平均需要 7 分钟,总运行时间为 6 小时 7 分钟。
3.0 用了 6 分 23 秒来执行第一次部署,然后平均用 1 分 30 秒来计算增量并将它们推送到触角,总运行时间为 1 小时 26 分。
这是一个相当惊人的结果。有了 Octopus Deploy 3.0,您等待部署的时间将会减少。增量压缩也节省了您的带宽,并释放您的章鱼服务器和触手资源,为更多的部署!
Selenium 系列:页面对象模型设计模式- Octopus Deploy
原文:https://octopus.com/blog/selenium/23-the-page-object-model-pattern/the-page-object-model-pattern
这篇文章是关于创建 Selenium WebDriver 测试框架的系列文章的一部分。
虽然我们之前的测试成功地验证了在 TicketMonster 中购买活动门票的过程,但是这种测试方式(我们按照顺序定义与页面的每个交互)有一些局限性。
第一个限制是每个交互都不是特别描述性的。对被测应用程序了解有限的人会被一行代码弄糊涂,这是可以理解的:
automatedBrowser.populateElement("tickets-1", "2", WAIT_TIME);
这种情况下的tickets-1
是什么?这个标识符没有很好地描述它所定位的元素。您需要非常熟悉被测试的应用程序,才能知道这行代码是做什么的。
第二个,也许是最重要的,限制是用来构建这个测试的代码是不可重用的。想象一下,除了这个购买机票的测试之外,我们想要编写第二个测试来验证每个可用部分的机票价格。您可以编写一个这样的测试来确保价格变化不会导致不合理的高或低票价。
为了编写第二个测试,与应用程序的每个交互都需要复制并粘贴到第二个测试方法中。然而,最好避免复制和粘贴,因为这会使代码更难维护,因为公共功能现在存在于多个方法中,并且都必须单独更新。
解决这两个限制的一个方法是使用页面对象模型(POM)设计模式。POM 设计模式封装了一个类中与单个页面的交互。这意味着与页面的交互现在暴露在具有友好名称的方法后面,并且这些类可以在测试之间重用。
让我们看看如何使用 POM 设计模式重写测试。
我们创建的每个 POM 类都需要访问一个AutomatedBrowser
实例。此外,我们将为与元素交互时使用的显式等待定义一个公共等待时间。公开这些共享属性是通过一个名为BasePage
的类来完成的。
请注意,实例变量、静态变量和构造函数都有受保护的范围。这意味着它们只对扩展了BasePage
的类可用,意味着BasePage
不是我们可以直接实例化的东西:
package com.octopus.pages;
import com.octopus.AutomatedBrowser;
public class BasePage {
protected static final int WAIT_TIME = 30;
protected final AutomatedBrowser automatedBrowser;
protected BasePage(AutomatedBrowser automatedBrowser) {
this.automatedBrowser = automatedBrowser;
}
}
我们在 TicketMonster 应用程序的主页上开始所有的测试。为了表示这个主页,我们创建了类MainPage
:
package com.octopus.pages.ticketmonster;
import com.octopus.AutomatedBrowser;
import com.octopus.pages.BasePage;
public class MainPage extends BasePage {
private static final String URL =
"https://ticket-monster.herokuapp.com";
private static final String BUY_TICKETS_NOW = "Buy tickets now";
public MainPage(final AutomatedBrowser automatedBrowser) {
super(automatedBrowser);
}
public MainPage openPage() {
automatedBrowser.goTo(URL);
return this;
}
public EventsPage buyTickets() {
automatedBrowser.clickElement(BUY_TICKETS_NOW, WAIT_TIME);
return new EventsPage(automatedBrowser);
}
}
让我们来分解这个代码。
我们所有的 POM 类都将扩展BasePage
。扩展BasePage
使这些类可以访问AutomatedBrowser
的实例,并使用共享的默认等待时间:
public class MainPage extends BasePage {
为了使 URL 和元素标识符更易维护,我们将字符串赋给常量。使用常量变量意味着我们可以给这些字符串一些有意义的上下文,这在以后处理一些更不直观的元素定位符时很重要:
private static final String URL =
"https://ticket-monster.herokuapp.com";
private static final String BUY_TICKETS_NOW = "Buy tickets now";
构造函数获取AutomatedBrowser
的一个实例,并将其传递给BasePage
构造函数:
public MainPage(final AutomatedBrowser automatedBrowser) {
super(automatedBrowser);
}
我们定义的第一个方法将打开应用程序主页的 URL。为了完成这个动作,我们创建了一个名为openPage()
的方法。
在像openPage()
这样的方法中,要打开的 URL 或要交互的元素的具体细节被封装。这个方法的调用者不需要知道正在打开的 URL,如果 URL 改变了,只需要在一个位置改变,使得维护更加容易。
为了允许链接调用,我们返回一个实例this
作为最终语句。POM 类中的每个方法都将返回 POM 类,可以在其上执行额外的交互。这样,POM 类的消费者可以很容易地理解应用程序的流程,我们将在编写测试方法时看到这一点:
public MainPage openPage() {
automatedBrowser.goTo(URL);
return this;
}
我们在主页上唯一感兴趣的动作是点击Buy tickets now
链接,我们用一个叫做buyTickets()
的方法来做。
如果您还记得上一篇文章,我们如何与像这个链接这样的元素交互并不像看起来那么简单,因为这些元素可以是样式化的链接(<a>
元素),也可以是表单按钮(<input>
元素)。根据使用的元素类型,我们的第一个测试必须使用不同的定位器。链接可以通过它们的文本来识别,而表单按钮必须通过一个id
或name
属性来识别。
元素之间的区别不再是编写这些测试的人所关心的,而是被封装在这个 POM 类中。调用buyTickets()
方法将导致所需的元素被点击,不管该元素是如何定位的。
因为单击此链接会将浏览器定向到事件页面,所以我们返回了一个EventsPage
类的实例。buyTickets()
的调用者可以理解,这个返回值表明页面导航已经发生,现在必须使用EventsPage
类来执行与页面的进一步交互:
public EventsPage buyTickets() {
automatedBrowser.clickElement(BUY_TICKETS_NOW, WAIT_TIME);
return new EventsPage(automatedBrowser);
}
让我们来看看EventsPage
类:
package com.octopus.pages.ticketmonster;
import com.octopus.AutomatedBrowser;
import com.octopus.pages.BasePage;
public class EventsPage extends BasePage {
public EventsPage(final AutomatedBrowser automatedBrowser) {
super(automatedBrowser);
}
public VenuePage selectEvent(final String category, final String event)
{
automatedBrowser.clickElement(category, WAIT_TIME);
automatedBrowser.clickElement(event, WAIT_TIME);
return new VenuePage(automatedBrowser);
}
}
与MainPage
类一样,EventsPage
扩展了BasePage
类,并将AutomatedBrowser
的实例从其构造函数传递给BaseClass
构造函数:
public class EventsPage extends BasePage {
public EventsPage(final AutomatedBrowser automatedBrowser) {
super(automatedBrowser);
}
在活动页面上,我们唯一想做的事情是选择我们要购买门票的活动。这包括单击左侧可折叠菜单中的链接。
为了允许该方法选择菜单中的任何选项,我们将菜单类别和事件名称作为参数传入。
选择一个事件将触发应用程序加载 venue 页面。我们通过返回一个VenuePage
类的实例来表示这种导航:
public VenuePage selectEvent(final String category, final String event)
{
automatedBrowser.clickElement(category, WAIT_TIME);
automatedBrowser.clickElement(event, WAIT_TIME);
return new VenuePage(automatedBrowser);
}
让我们来看看VenuePage
类:
package com.octopus.pages.ticketmonster;
import com.octopus.AutomatedBrowser;
import com.octopus.pages.BasePage;
public class VenuePage extends BasePage {
private static final String VENUE_DROP_DOWN_LIST = "venueSelector";
private static final String BOOK_BUTTON = "bookButton";
public VenuePage(final AutomatedBrowser automatedBrowser) {
super(automatedBrowser);
}
public VenuePage selectVenue(final String venue) {
automatedBrowser.selectOptionByTextFromSelect(venue, VENUE_DROP_DOWN_LIST, WAIT_TIME);
return this;
}
public CheckoutPage book() {
automatedBrowser.clickElement(BOOK_BUTTON, WAIT_TIME);
return new CheckoutPage(automatedBrowser);
}
}
这个类扩展了BasePage
,将AutomatedBrowser
传递给BasePage
构造函数,并为 venue 下拉列表和 book 按钮的定位器定义了一些常量:
public class VenuePage extends BasePage {
private static final String VENUE_DROP_DOWN_LIST = "venueSelector";
private static final String BOOK_BUTTON = "bookButton";
public VenuePage(final AutomatedBrowser automatedBrowser) {
super(automatedBrowser);
}
通过selectVenue()
方法选择场地,场地名称作为参数传入。
选择一个地点不会触发任何页面导航,因此我们返回this
以指示未来的交互仍将在同一页面上执行:
public VenuePage selectVenue(final String venue) {
automatedBrowser.selectOptionByTextFromSelect(venue,
VENUE_DROP_DOWN_LIST, WAIT_TIME);
return this;
}
通过book()
方法进入预订页面。
单击 book 按钮会导致应用程序导航到 checkout 页面,我们通过返回一个CheckoutPage
类的实例来表示它:
public CheckoutPage book() {
automatedBrowser.clickElement(BOOK_BUTTON, WAIT_TIME);
return new CheckoutPage(automatedBrowser);
}
让我们来看看CheckoutPage
类:
package com.octopus.pages.ticketmonster;
import com.octopus.AutomatedBrowser;
import com.octopus.pages.BasePage;
public class CheckoutPage extends BasePage {
private static final String SECTION_DROP_DOWN_LIST = "sectionSelect";
private static final String ADULT_TICKET_COUNT = "tickets-1";
private static final String ADD_TICKETS_BUTTON = "add";
private static final String EMAIL_ADDRESS = "email";
private static final String CHECKOUT_BUTTON = "submit";
public CheckoutPage(final AutomatedBrowser automatedBrowser) {
super(automatedBrowser);
}
public CheckoutPage buySectionTickets(final String section, final
Integer adultCount) {
automatedBrowser.selectOptionByTextFromSelect(section, SECTION_DROP_DOWN_LIST, WAIT_TIME);
automatedBrowser.populateElement(ADULT_TICKET_COUNT, adultCount.toString(), WAIT_TIME);
automatedBrowser.clickElement(ADD_TICKETS_BUTTON, WAIT_TIME);
return this;
}
public ConfirmationPage checkout(final String email) {
automatedBrowser.populateElement(EMAIL_ADDRESS, email, WAIT_TIME);
automatedBrowser.clickElement(CHECKOUT_BUTTON, WAIT_TIME);
return new ConfirmationPage(automatedBrowser);
}
}
这个类扩展了BasePage
并将AutomatedBrowser
传递给BasePage
构造函数。
这里的常量变量很好地说明了为什么应该使用变量来为定位器字符串提供上下文。特别是,定位器tickets-1
和submit
与它们识别的元素没有任何明显的联系。然而,我们可以通过这些定位器的变量名ADULT_TICKET_COUNT
和CHECKOUT_BUTTON
为它们提供一些有意义的上下文:
public class CheckoutPage extends BasePage {
private static final String SECTION_DROP_DOWN_LIST =
"sectionSelect";
private static final String ADULT_TICKET_COUNT = "tickets-1";
private static final String ADD_TICKETS_BUTTON = "add";
private static final String EMAIL_ADDRESS = "email";
private static final String CHECKOUT_BUTTON = "submit";
public CheckoutPage(final AutomatedBrowser automatedBrowser) {
super(automatedBrowser);
}
要购买特定区域的门票,我们使用buySectionTickets()
方法。该方法从下拉列表中选择想要的部分,添加要购买的票的数量,并点击Add
按钮。
这个动作不会导致任何页面导航,所以我们返回this
:
public CheckoutPage buySectionTickets(final String section, final Integer adultCount) {
automatedBrowser.selectOptionByTextFromSelect(section,
SECTION_DROP_DOWN_LIST, WAIT_TIME);
automatedBrowser.populateElement(ADULT_TICKET_COUNT,
adultCount.toString(), WAIT_TIME);
automatedBrowser.clickElement(ADD_TICKETS_BUTTON, WAIT_TIME);
return this;
}
我们使用checkout()
方法购买门票。该方法接受与购买相关联的电子邮件地址,将该电子邮件地址输入适当的字段,并单击Checkout
按钮。
单击Checkout
按钮将我们导航到确认页面,因此我们返回一个ConfirmationPage
类的实例:
public ConfirmationPage checkout(final String email) {
automatedBrowser.populateElement(EMAIL_ADDRESS, email, WAIT_TIME);
automatedBrowser.clickElement(CHECKOUT_BUTTON, WAIT_TIME);
return new ConfirmationPage(automatedBrowser);
}
我们来看看the ConfirmationPage
类:
package com.octopus.pages.ticketmonster;
import com.octopus.AutomatedBrowser;
import com.octopus.pages.BasePage;
public class ConfirmationPage extends BasePage {
private static final String EMAIL_ADDRESS = "div.col-md-6:nth-child(1) > div:nth-child(1) > p:nth-child(2)";
private static final String EVENT_NAME = "div.col-md-6:nth-child(1) > div:nth-child(1) > p:nth-child(3)";
private static final String VENUE_NAME = "div.col-md-6:nth-child(1) > div:nth-child(1) > p:nth-child(4)";
public ConfirmationPage(final AutomatedBrowser automatedBrowser) {
super(automatedBrowser);
}
public String getEmail() {
return automatedBrowser.getTextFromElement(EMAIL_ADDRESS, WAIT_TIME);
}
public String getEvent() {
return automatedBrowser.getTextFromElement(EVENT_NAME, WAIT_TIME);
}
public String getVenue() {
return automatedBrowser.getTextFromElement(VENUE_NAME, WAIT_TIME);
}
}
和其他 POM 类一样,这个类扩展了BasePage
并将AutomatedBrowser
传递给BasePage
构造函数。
我们希望在这个页面上与之交互的元素没有可以用来识别它们的属性,迫使我们使用 CSS 选择器。这里常量变量的使用在给这些定位器一些上下文时特别重要,因为像"div.col-md-6:nth-child(1) > div:nth-child(1) > p:nth-child(2)"
这样的字符串很难破译:
public class ConfirmationPage extends BasePage {
private static final String EMAIL_ADDRESS = "div.col-md-6:nth-child(1) > div:nth-child(1) > p:nth-child(2)";
private static final String EVENT_NAME = "div.col-md-6:nth-child(1) > div:nth-child(1) > p:nth-child(3)";
private static final String VENUE_NAME = "div.col-md-6:nth-child(1) > div:nth-child(1) > p:nth-child(4)";
public ConfirmationPage(final AutomatedBrowser automatedBrowser) {
super(automatedBrowser);
}
与其他 POM 类不同,我们没有在这个页面上单击、选择或填充任何元素。然而,我们对从页面中获取一些文本感兴趣,然后我们可以使用这些文本来验证我们购买的门票是否具有正确的值。
这里的 getter 函数返回 3 个段落(或<p>
)元素的文本内容:
public String getEmail() {
return automatedBrowser.getTextFromElement(EMAIL_ADDRESS, WAIT_TIME);
}
public String getEvent() {
return automatedBrowser.getTextFromElement(EVENT_NAME, WAIT_TIME);
}
public String getVenue() {
return automatedBrowser.getTextFromElement(VENUE_NAME, WAIT_TIME);
}
现在让我们来看看使用 POM 类的测试方法:
@Test
public void purchaseTicketsPageObjectModel() {
final AutomatedBrowser automatedBrowser =
AUTOMATED_BROWSER_FACTORY.getAutomatedBrowser("ChromeNoImplicitWait");
try {
automatedBrowser.init();
final EventsPage eventsPage = new MainPage(automatedBrowser)
.openPage()
.buyTickets();
final VenuePage venuePage = eventsPage
.selectEvent("Concert", "Rock concert of the decade");
final CheckoutPage checkoutPage = venuePage
.selectVenue("Toronto : Roy Thomson Hall")
.book();
final ConfirmationPage confirmationPage = checkoutPage
.buySectionTickets("A - Premier platinum reserve", 2)
.checkout("email@example.org");
Assert.assertTrue(confirmationPage.getEmail().contains("email@example.org"));
Assert.assertTrue(confirmationPage.getEvent().contains("Rock concert of the decade"));
Assert.assertTrue(confirmationPage.getVenue().contains("Roy Thomson Hall"));
} finally {
automatedBrowser.destroy();
}
}
初始化AutomatedBrowser
实例的代码与我们之前的测试相同:
@Test
public void purchaseTicketsPageObjectModel() {
final AutomatedBrowser automatedBrowser =
AUTOMATED_BROWSER_FACTORY.getAutomatedBrowser("ChromeNoImplicitWait");
try {
automatedBrowser.init();
我们的测试从主页面开始,现在由MainPage
类表示。我们首先创建一个MainPage
类的新实例,然后链接对openPage()
和buyTickets()
方法的调用。
EventsPage
类的一个实例由buyTickets()
方法返回。我们将这个值保存在一个名为eventsPage
的变量中。
请注意,在这段代码中,我们没有引用用于打开页面的 URL,或者用于点击Buy Tickets Now
链接的定位器。这些细节现在由 POM 类处理,将测试代码从 web 应用程序如何工作的任何特定知识中解放出来:
final EventsPage eventsPage = new MainPage(automatedBrowser)
.openPage()
.buyTickets();
浏览场地、结帐和确认页面遵循相同的模式。测试中唯一需要定义的值是音乐会的名称、地点、区域、电子邮件地址和要购买的门票数量。我们没有定义定位符,也没有区分链接和表单按钮:
final VenuePage venuePage = eventsPage
.selectEvent("Concert", "Rock concert of the decade");
final CheckoutPage checkoutPage = venuePage
.selectVenue("Toronto : Roy Thomson Hall")
.book();
final ConfirmationPage confirmationPage = checkoutPage
.buySectionTickets("A - Premier platinum reserve", 2)
.checkout("email@example.org");
验证所购门票的详细信息现在也更加简化了。ConfirmationPage
类通过 getter 方法公开了我们感兴趣的值,测试代码不再需要处理笨拙的 CSS 选择器来查找包含这些信息的段落元素:
Assert.assertTrue(confirmationPage.getEmail().contains("email@example.org"));
Assert.assertTrue(confirmationPage.getEvent().contains("Rock concert of the decade"));
Assert.assertTrue(confirmationPage.getVenue().contains("Roy Thomson Hall"));
一旦测试完成,我们就清理finally
块中的资源:
} finally {
automatedBrowser.destroy();
}
通过使用 POM 设计模式,我们使我们的测试更具可读性,并抽象出了许多与页面(如 URL 或定位器)交互所需的细节,允许针对描述性和流畅的 API 编写测试。
这篇文章是关于创建 Selenium WebDriver 测试框架的系列文章的一部分。
通过示例探索 Istio——Octopus 部署
这是探索 Istio 的系列文章的第一部分,Istio 是 Kubernetes 可用的流行服务网格。在本文中,我将介绍一个样例应用程序,我们将使用它来探索 Istio 的主要特性。
Istio 是当今 Kubernetes 最受欢迎和最强大的服务网格之一。为了理解它提供的特性,有一个非常简单的示例应用程序来发出网络请求是很有用的,我们可以通过 Istio 来操作和配置它。
Istio 项目提供的典型例子是 Bookinfo 。Bookinfo 是一个小型多语言微服务应用程序,其输出可以通过修改网络策略来调整。
但是,我发现 Bookinfo 的层次太高,无法真正理解 Istio 的特性,因此,我将展示一个非常简单的 Kubernetes 部署,其中 Node.js 应用程序代理来自集群内部和外部的各种 web 服务器的请求。通过使事情变得非常简单,当网络请求发出时,很容易看到 Istio 正在做什么。
代理应用程序
我们示例中面向公众的应用程序是一个非常简单的 Node.js web 服务器。这个应用程序向它所代理的服务发出第二个网络请求,包装响应并返回它以及发出请求所用的时间。
我们将使用这个代理前端来观察网络请求如何在网络中路由,显示任何失败的网络请求,并测量请求花费的时间。
该应用程序的代码可以在 GitHub 上找到:
// content of index.js
const http = require('http');
const url = require('url');
const requestService = require('request');
const port = 3030;
const proxyUrl = process.env.PROXYURL || "https://raw.githubusercontent.com/mcasperson/NodejsProxy/master/helloworld.txt";
const requestHandler = (request, response) => {
const path = url.parse(request.url).pathname;
const start = new Date().getTime();
requestService(proxyUrl + (path == "/" ? "" : path), (err, res, body) => {
const duration = new Date().getTime() - start;
if (err) {
response.end(err.toString() + " - Took " + duration + " milliseconds");
} else {
response.end("Proxying value: " + body + " - Took " + duration + " milliseconds");
}
});
}
const server = http.createServer(requestHandler);
server.listen(port, (err) => {
if (err) {
return console.log('something bad happened', err);
}
console.log(`server is listening on ${port}`);
});
外部服务
在同一个存储库中,我们有两个文本文件在这里和在这里。由于 GitHub 允许您查看托管存储库中文件的原始内容,这些将作为模拟外部服务供我们的代理使用。这意味着我们不必麻烦地部署一个公共服务来返回静态内容。
内部网络服务器
对于内部 web 服务器,我们将运行第二个 Node.js 应用程序,它返回静态文本,在我们的例子中是Webserver V1
和Webserver V2
,以及运行图像的容器的主机名。我们将为每个版本启动 3 个实例,这意味着我们将有 6 个实例运行服务器的 2 个版本。
网络服务器的不同版本将标有version: v1
或version: v2
。当我们开始研究 Istio 的 VirtualService 和 DestinationRule 资源时,这种配置将为我们提供以有趣的方式路由和管理网络流量的机会。
该应用的源代码可以在 GitHub 上找到:
const http = require('http');
var url = require("url");
const os = require('os');
const port = 3030;
const content = process.env.SERVERCONTNET || "Hello world";
const requestHandler = (request, response) => {
const path = url.parse(request.url).pathname;
if (path == "/failsometimes") {
if (Math.floor((Math.random() * 3)) == 0) {
response.statusCode = 500;
}
}
response.end(content + " requested from " + url.parse(request.url).pathname + " on " + os.hostname() + " with code " + response.statusCode);
};
const server = http.createServer(requestHandler);
server.listen(port, (err) => {
if (err) {
return console.log('something bad happened', err);
}
console.log(`server is listening on ${port}`);
});
建筑图
下面是使用 Kubernetes 部署语言 (KDL)的示例应用程序的顶级概述。
位于https://github . com/mcasperson/NodejsProxy/blob/master/kubernetes/example . YAML的 YAML 是该示例项目的一个可部署示例。
我们有一个负载均衡器服务资源,它将流量定向到由proxy
部署资源创建的 Pod 资源,后者又从由部署资源webserverv1
和webserverv2
创建的 Pod 资源请求内容。代理的内容然后被返回给浏览器。
同时,有两个额外的集群 IP 服务资源称为webserverv1
和webserverv2
,它们当前没有被访问。这些是为 Istio 策略做准备而创建的,与我们在最初的实现中建立的相比,Istio 策略将以更细粒度的方式引导流量。
示例应用程序 Kubernetes 架构。
当我们打开应用程序时,我们将看到代理包装了来自webserverv1
或webserverv2
的响应,因为服务资源指向所有 web 服务器 Pod 资源,因此将针对任何给定的请求联系它们中的任何一个。我们还可以看到检索代理值所花费的时间。
代理应用程序从 web 服务器返回值。
结论
这里显示的示例应用程序很简单,并不试图复制真实的场景。但是,它非常适合作为我们使用 Istio 添加新网络功能的起点。
在的下一篇文章中,我们将介绍 Istio VirtualService 资源。
硒系列:TicketMonster 测试-章鱼部署
原文:https://octopus.com/blog/selenium/22-the-ticketmonster-test/the-ticketmonster-test
这篇文章是关于创建 Selenium WebDriver 测试框架的系列文章的一部分。
TicketMonster 是由 RedHat 创建的一个示例应用程序,用于演示许多 Java web 技术。TicketMonster 的好处(无论如何,从 WebDriver 教程的角度来看)是它没有针对自动化测试进行优化,这意味着要成功测试应用程序的典型旅程,我们不能依赖一致的网络请求或所有具有方便的id
属性的元素,我们可以使用这些属性来定位它们。
但是为了给 TicketMonster 编写测试,我们需要把它部署在某个地方。TicketMonster 应用程序的源代码是免费提供的,您可以在https://developers . red hat . com/ticket-monster/whatsiticketmonster/找到如何在本地运行 TicketMonster 的详细说明。
我们将要测试的场景是购买一张活动门票。我们先来贯穿一下手动买票的流程。
从主页开始,我们单击Buy tickets now
按钮。
这为我们提供了一个事件列表。在这里,我们单击Concert
链接展开菜单,然后单击Rock concert of the decade
链接。
我们选择Toronto : Roy Thomson Hall
作为场地,并保留默认时间。然后点击Order tickets
按钮。
选择A - Premier platinum reserve
部分,并输入2
作为票的数量。在Order Summary
部分输入电子邮件地址,并点击Add tickets
按钮确认这些选择。
完成机票选择后,点击Checkout
按钮。
交易完成了,我们买了一场虚拟演出的门票。
尽管这个购买门票的场景并不复杂,但是用 WebDriver 测试它需要大量的技术,到目前为止我们已经在库中实现了这些技术。我们单击链接和按钮等元素,填充文本框,从下拉列表中选择项目,并与动态添加到页面的元素进行交互。
下面是使用 WebDriver 完成购票场景的测试:
package com.octopus;
import org.junit.Assert;
import org.junit.Test;
public class TicketMonsterTest {
private static final AutomatedBrowserFactory AUTOMATED_BROWSER_FACTORY =
new AutomatedBrowserFactory();
private static final int WAIT_TIME = 30;
@Test
public void purchaseTickets() {
final AutomatedBrowser automatedBrowser =
AUTOMATED_BROWSER_FACTORY.getAutomatedBrowser("ChromeNoImplicitWait");
try {
automatedBrowser.init();
automatedBrowser.goTo("https://ticket-monster.herokuapp.com");
automatedBrowser.clickElement("Buy tickets now", WAIT_TIME);
automatedBrowser.clickElement("Concert", WAIT_TIME);
automatedBrowser.clickElement("Rock concert of the decade", WAIT_TIME);
automatedBrowser.selectOptionByTextFromSelect("Toronto : Roy Thomson Hall", "venueSelector", WAIT_TIME);
automatedBrowser.clickElement("bookButton", WAIT_TIME);
automatedBrowser.selectOptionByTextFromSelect("A - Premier platinum reserve", "sectionSelect", WAIT_TIME);
automatedBrowser.populateElement("tickets-1", "2", WAIT_TIME);
automatedBrowser.clickElement("add", WAIT_TIME);
automatedBrowser.populateElement("email", "email@example.org", WAIT_TIME);
automatedBrowser.clickElement("submit", WAIT_TIME);
final String email = automatedBrowser.getTextFromElement("div.col-md-6:nth-child(1) > div:nth-child(1) > p:nth-child(2)", WAIT_TIME);
Assert.assertTrue(email.contains("email@example.org"));
final String event = automatedBrowser.getTextFromElement("div.col-md-6:nth-child(1) > div:nth-child(1) > p:nth-child(3)", WAIT_TIME);
Assert.assertTrue(event.contains("Rock concert of the decade"));
final String venue = automatedBrowser.getTextFromElement("div.col-md-6:nth-child(1) > div:nth-child(1) > p:nth-child(4)", WAIT_TIME);
Assert.assertTrue(venue.contains("Roy Thomson Hall"));
} finally {
automatedBrowser.destroy();
}
}
}
让我们一行一行地分解这段代码。
我们有一个AutomatedBrowserFactory
类的静态实例,我们将使用它来生成AutomatedBrowser
类的实例:
private static final AutomatedBrowserFactory AUTOMATED_BROWSER_FACTORY
= new AutomatedBrowserFactory();
因为我们在与元素交互时将利用显式等待,所以我们需要有一个等待元素可用的持续时间。常量WAIT_TIME
将用作显式等待时间的默认持续时间。
我们在这里有相当长的等待时间,因为应用程序已经被部署到一个相当小的 Heroku 实例,有时页面可能需要一些时间来加载:
private static final int WAIT_TIME = 30;
对于这个测试,我们将使用显式等待,并利用简单的元素选择方法。为了让这两者按预期工作,我们需要一个不实现隐式等待的AutomatedBrowser
实例。通过将ChromeNoImplicitWait
选项传递给AutomatedBrowserFactory
实例,我们将收到一个不实现隐式等待的AutomatedBrowser
实例:
@Test
public void purchaseTickets() {
final AutomatedBrowser automatedBrowser =
AUTOMATED_BROWSER_FACTORY.getAutomatedBrowser("ChromeNoImplicitWait");
然后我们初始化AutomatedBrowser
实例,并打开 TicketMonster URL:
try {
automatedBrowser.init();
automatedBrowser.goTo("https://ticket-monster.herokuapp.com");
在主页上,我们点击Buy tickets now
链接。尽管这个元素看起来像一个按钮,但它实际上是一个链接,或者说是一个<a>
元素。
这意味着我们可以通过它的文本来识别这个元素。如果您还记得我们实现SimpleBy
类时,我们用来标识元素的方法之一是By.ByLinkText
类:
final By[] byInstances = new By[] {
By.id(locator),
By.xpath(locator),
By.cssSelector(locator),
By.className(locator),
By.linkText(locator),
By.name(locator)
};
By.linkText()
方法意味着我们可以使用组成链接的文本,即Buy tickets now
:
automatedBrowser.clickElement("Buy tickets now", WAIT_TIME);
Concert and Rock concert of the decade
元素也是链接,因此我们通过它们的文本来识别它们:
automatedBrowser.clickElement("Concert", WAIT_TIME);
automatedBrowser.clickElement("Rock concert of the decade", WAIT_TIME);
venue selection 下拉列表的 ID 是venueSelector
,所以我们用它来标识它。从该列表中,我们选择Toronto : Roy Thomson Hall
选项:
automatedBrowser.selectOptionByTextFromSelect("Toronto : Roy Thomson Hall", "venueSelector", WAIT_TIME);
在我们选择了一个地点后,会显示一个新的面板,提供一些默认的日期和时间。我们接受这些默认值,不需要与新的下拉列表交互。
我们确实需要点击Order tickets
按钮。与我们点击的其他按钮类元素不同,这个元素是一个实际的表单按钮。这意味着我们不能使用元素中的文本来识别它。这个元素的名字是bookButton
,由于By.name()
方法是SimpleBy
标识元素的方法之一,我们可以使用这个属性来标识按钮。
这一步是显式等待有价值的一个很好的例子,因为我们正在交互的元素是动态显示的,我们不能假设元素可以立即被点击。因为我们使用了显式等待,所以我们可以确信,只有当元素处于期望的状态时,测试才会继续,在这种情况下,这意味着它们可以被单击:
automatedBrowser.clickElement("bookButton", WAIT_TIME);
从 ID 为sectionSelect
的下拉列表中选择该部分:
automatedBrowser.selectOptionByTextFromSelect("A - Premier platinum reserve", "sectionSelect", WAIT_TIME);
定义要购买的门票数量的文本框有一个tickets-1
的name
。我们用它来标识元素,并用文本2
填充它:
automatedBrowser.populateElement("tickets-1", "2", WAIT_TIME);
Add tickets
按钮是表单按钮的另一个例子。我们用它的add
中的name
来识别并点击它:
automatedBrowser.clickElement("add", WAIT_TIME);
电子邮件文本框有一个 IDemail
,我们用它来标识它,并用虚拟电子邮件地址email@example.org
填充它:
automatedBrowser.populateElement("email", "email@example.org", WAIT_TIME);
Checkout
按钮是另一个表单按钮。它有一个submit
的name
:
automatedBrowser.clickElement("submit", WAIT_TIME);
在最后一个屏幕上,我们不与任何元素交互,而是捕捉文本来验证它是否符合我们的预期。
我们感兴趣的文本可以在许多段落或<p>
元素中找到。我们可以从 developer tools 窗口中看到,这些元素没有任何我们可以用来识别它们的有用属性。其实他们根本没有任何属性。
在这些情况下,您必须使用 XPath 或 CSS 选择器来标识元素。因为 CSS 选择器对 web 开发人员来说更熟悉,所以这是我们将使用的样式:
右击元素并选择复制➜复制选择器让 Chrome 生成一个 CSS 选择器来唯一标识元素:
在这种情况下,保存电子邮件地址的元素可以用 CSS 选择器div.col-md-6:nth-child(1) > div:nth-child(1) > p:nth-child(2)
找到。我们用它来识别元素并获取其文本内容,然后对其进行检查以确保它包含我们之前输入的电子邮件地址:
final String email = automatedBrowser.getTextFromElement("div.col-md-6:nth-child(1) > div:nth-child(1) > p:nth-child(2)", WAIT_TIME);
Assert.assertTrue(email.contains("email@example.org"));
我们遵循相同的流程来识别包含地点和事件的段落,并验证这些元素包含的文本:
final String event = automatedBrowser.getTextFromElement("div.col-md-6:nth-child(1) > div:nth-child(1) > p:nth-child(3)", WAIT_TIME);
Assert.assertTrue(event.contains("Rock concert of the decade"));
final String venue = automatedBrowser.getTextFromElement("div.col-md-6:nth-child(1) > div:nth-child(1) > p:nth-child(4)", WAIT_TIME);
Assert.assertTrue(venue.contains("Roy Thomson Hall"));
然后测试结束,在finally
块中清理资源:
} finally {
automatedBrowser.destroy();
}
}
在测试像 TicketMonster 这样的真实应用程序时,我们可以观察到编写 WebDriver 测试的三个重要方面。
首先,在今天的 web 应用程序中,动态元素无处不在。无论这些是我们在加载新页面后需要与之交互的元素,还是由 JavaScript 操纵的元素,测试现代 web 应用程序都意味着处理不总是立即可用的元素。
其次,我们已经在这个测试中看到,我们想要与之交互的元素拥有惟一的 id 是多么的罕见。我们经常不得不依赖于name
属性,我们甚至不得不使用 CSS 选择器来为我们的最终验证识别一些段落元素。
第三,我们看到,仅仅因为两个元素在屏幕上看起来一样,它们就可以基于完全不同的 HTML 元素。在 TicketMonster 中,使用<a>
元素的链接和使用<input>
元素的表单按钮在视觉上是相同的。但是这两个元素对我们如何编写测试有影响。也就是说,链接可以通过它们的文本内容来识别,而表单按钮不能。
要在本地运行测试,单击测试旁边的绿色图标,并选择Run purchaseTickets()
选项:
你会看到 Chrome 打开,完成购买,然后测试通过并结束:
这是将代码更改推送到 GitHub 的好机会。右键单击根项目目录并选择 Git ➜提交目录...;
输入提交消息,点击Commit and Push
:
然后点击Push
按钮更新 GitHub 库:
几分钟后,Travis CI 将检测到变化并运行测试。在日志中,您将看到正在执行的新测试:
编写代码、添加测试、签入变更以及让中央服务器构建代码、运行测试并记录结果的循环对于持续集成的理念至关重要。如果您是团队的一员,代码库的当前状态可以通过 Travis CI 构建是否通过来快速确定,并且因为我们在每次签入时都运行测试,所以我们可以高度确信通过的构建代表了有效并且可以部署的代码库。
如果你有敏锐的眼光,你可能已经注意到一些图片在购票时没有正确加载。如果承载占位符图像的网站遇到性能问题,就会发生这种情况。正如您在下面的截图中看到的,事件和位置图像没有正确加载。
像这样的问题应该被认为是一个 bug,我们可以在测试中检测出来。让我们更新测试以捕获一个 HAR 文件,这是我们在添加对 BrowserMob 代理的支持时实现的:
@Test
public void purchaseTickets() {
final AutomatedBrowser automatedBrowser =
AUTOMATED_BROWSER_FACTORY.getAutomatedBrowser("ChromeNoImplicitWait");
try {
automatedBrowser.init();
automatedBrowser.captureHarFile();
automatedBrowser.goTo("https://ticket-monster.herokuapp.com");
automatedBrowser.clickElement("Buy tickets now", WAIT_TIME);
automatedBrowser.clickElement("Concert", WAIT_TIME);
automatedBrowser.clickElement("Rock concert of the decade", WAIT_TIME);
automatedBrowser.selectOptionByTextFromSelect("Toronto : Roy Thomson Hall", "venueSelector", WAIT_TIME);
automatedBrowser.clickElement("bookButton", WAIT_TIME);
automatedBrowser.selectOptionByTextFromSelect("A - Premier platinum reserve", "sectionSelect", WAIT_TIME);
automatedBrowser.populateElement("tickets-1", "2", WAIT_TIME);
automatedBrowser.clickElement("add", WAIT_TIME);
automatedBrowser.populateElement("email", "email@example.org", WAIT_TIME);
automatedBrowser.clickElement("submit", WAIT_TIME);
final String email = automatedBrowser.getTextFromElement("div.col-md-6:nth-child(1) > div:nth-child(1) > p:nth-child(2)", WAIT_TIME);
Assert.assertTrue(email.contains("email@example.org"));
final String event = automatedBrowser.getTextFromElement("div.col-md-6:nth-child(1) > div:nth-child(1) > p:nth-child(3)", WAIT_TIME);
Assert.assertTrue(event.contains("Rock concert of the decade"));
final String venue = automatedBrowser.getTextFromElement("div.col-md-6:nth-child(1) > div:nth-child(1) > p:nth-child(4)", WAIT_TIME);
Assert.assertTrue(venue.contains("Roy Thomson Hall"));
} finally {
try {
automatedBrowser.saveHarFile("ticketmonster.har");
} finally {
automatedBrowser.destroy();
}
}
}
然后,我们可以将生成的 HAR 文件加载到 HAR 分析器中,并通过将 HTTP 响应代码过滤为 0、4xx 和 5xx 来查找网络错误。在这些范围内的响应表明有错误。
果然,我们看到一些图像请求有一个响应代码0
,这意味着它们没有成功完成。因此,即使我们的测试成功完成了购票过程,HAR 文件也可以用来识别可能影响用户体验的其他问题:
TicketMonster 的这个测试代表了一个真实的例子,展示了如何使用 WebDriver 编写端到端的测试。我们创建的库使得与 web 应用程序的交互变得非常容易;然而,拥有一个直接列出每一次点击、选择和填充操作的测试是非常低级的。在下一篇文章中,我们将探讨一种设计模式,它将与 web 应用程序的交互抽象化,以生成更具可重用性和可维护性的代码。
这篇文章是关于创建 Selenium WebDriver 测试框架的系列文章的一部分。
为什么,更重要的是如何自动部署数据库——Octopus 部署
原文:https://octopus.com/blog/the-why-and-more-importantly-the-how-of-automated-database-deployment
可靠性、可追溯性、速度:这是自动化数据库变更部署的三大动力。尤其是当涉及到生产时,在审查的水平上没有妥协,这可能只是为了防止生产中发生灾难性的事情。
这种对质量的需求有一种奇怪的效果。我们最近与处于自动化数据库部署的不同阶段的 30 个团队进行了交谈,我们感到惊讶的是,对于许多团队来说,对高质量部署的需求阻止了他们开始时的自动化。
毕竟,如果某件事必须是正确的,你就要亲自去做。你注意每一步,你慢慢来,你充分利用你的经验,所有人类带给工作的好东西,对吗?此外,生产变更是 DBA 角色中非常明显的一部分;人们很容易认为自动化会向组织的其他成员表明它并不重要。
然后,突然,一些事情发生了,同样的人变了。有时是一系列失败的部署,对人类局限性的认识,或者可能是一个新人加入了以更好的方式工作的团队。同样的质量要求现在产生了相反的行为,团队认识到一个简单的事实:如果你喜欢某样东西,就自动化它。
这并不是说,“开发人员应该为数据库而疯狂”。有句老话是这么说的:如果你爱某样东西,就应该让它自由;但这里的情况正好相反。(毕竟,自由放养的数据库有办法变得野性十足。)一个高质量的过程约束了变更的流程,使进一步的开发变得容易,而不是抑制它。但是你从哪里开始呢?
在我们的调查电话中,流程变革以三种不同的方式展开:
自上而下
有些队伍是(un?幸运的是,我有一个自上而下的计划,可以让一大群任性的数据库保持一致。在这种情况下,可以预先设计一个新系统,所有组件都已就位。当速度和效率与商业价值直接相关时,这种方法更受青睐。以一个面向消费者的保险网站为例:在激烈的竞争压力下,新功能和漏洞修复正在交付。快速交付到产品中是必不可少的,所以他们努力工作,将数据库引入到他们的连续交付过程中。
如果开发人员和数据库管理员之间的沟通不畅,这可能是唯一能让双方合作做出改变的方法。
从“左边”
对于逐步引入变更的团队来说,自动化解决方案非常适合分阶段推出。首先,开发人员开始跟踪源代码控制中的数据库变更,解决可见性和冲突方面的问题。一旦他们沐浴在可追溯性的温暖光线中,团队就开始通过他们的 CI 系统运行变更,这样每个数据库变更都会触发基本的健全性检查。与应用程序开发 CI 一样,这为开发人员提供了关于他们的变更的快速反馈。
对于每个“绿色构建”,一些团队将变更直接推向集成或测试环境(持续部署),或者只是提供一个包(持续交付)给其他人。
无论是推还是拉的方法,都可以扩展到下游环境,如 UAT、试运行、生产,但在大多数情况下,诸如 Octopus Deploy 之类的发布管理工具正式确定了环境之间的关系,并提供了对什么版本在哪里的可见性。
这听起来像是这种方法总是由开发人员驱动的,但是我们已经看到了数量惊人的 DBA 推动了这种变化。或者这并不令人惊讶。重复排练将会暴露测试中的大多数问题,而不是生产中的问题,从而增加整体可靠性。
由外向内
我见过的另一种模式是通过钳形运动来实现自动化,但老实说,我不确定这是否能带来好的结果。在这种情况下,自动化来自开发生命周期的两端:开发和生产。这样做的动机是用工具或脚本缓解最尖锐的痛点,然后让自动化随着每个人对系统的信任而增长。
我对此不太确定的原因是,从我交谈过的团队来看,在这一点上很容易陷入困境。当然,它们首先解决最大的问题——通常是一个好方法。不利的一面是混乱被压缩到集成和测试中,众所周知,这两个领域很难预算修复所需的努力。
结论
对于我们交谈过的团队来说,使环境和流程成形比自动化本身更具挑战性,但即使如此,我们也找不到任何人会接受从部署自动化后退一步。即使是跳槽,他们也会坚持将他们的自动化知识应用到新的环境中。将工作放在部署过程中,而不是单独的部署,这意味着所有的爱和关心都得到了数百倍的回报。
这是来自 Redgate 的伊丽莎白·艾耶尔的客座博文。Redgate 是为在微软数据平台上工作的专业人员提供软件的领先供应商。他们帮助全球各种类型组织中的 800,000 多人,从中小型企业到财富 100 强中 91%的公司。他们的 SQL 工具带帮助用户为其数据库实施 DevOps 最佳实践,轻松监控数据库性能,并提高团队生产力。
我的用户体验之旅——Octopus Deploy 引发的思考
原文:https://octopus.com/blog/thoughts-from-my-recent-ux-journey
“我感觉很好,我感觉很棒,我感觉很棒”
[继续查看几个没有垂直对齐、字体大小不一致、缺少一些动画的表格]
“我感觉很好,我感觉很棒,我感觉很棒”
[看到缺少帮助选项,过多的信息使屏幕非常分散注意力,以及更多的样式问题]
(现在大声抽泣)“我感觉很好,我感觉很棒,我感觉很棒!”
你好。我叫马克,我是鲍勃(或者我是治疗师?有时候我也不确定)。你以前听过我的消息。我是 Octopus 的 UX 人之一,目前负责我们的入职工作。我们希望新客户取得成功,所以我们希望尽可能地避开他们,给他们一些简单的、不受干扰的东西。今天,我想分享一下我们上个季度一直在做的一些事情,以及为什么我们认为这些事情会让您的生活变得更加轻松。
从客户的角度接近 UX 是我们非常重视的事情,随着你的应用功能的增加,不时后退一步,想想第一次体验你的界面的新用户是很重要的。
这些概念有意义吗?有没有什么分散他们注意力的事情?他们能把点点滴滴联系起来,理解事物,然后教别人吗?
尽量减少分心
一致性很重要(在我们的代码、设计和实现中同样如此),因为作为消费者,它减少了我们大脑的认知负荷,使我们更容易接受新信息。
以这个代码示例为例:
var body: some View {
VerticalStack(alignment: .leading) {
Text(self.categoryName)
.font(.headline)
.padding(.leading, 15)
.padding(.top, 5)
ScrollView(showsHorizontalIndicator: false) {
HorizontalStack(alignment: .top, spacing: 0) {
ForEach(self.items) { landmark in
CategoryItem(landmark: landmark)
}
}
}
.frame(height: 185)
}
}
只要浏览一下这段代码,您就可以很容易地推断出发生了什么,因为一切都是正确一致的。如果这段代码中有一个 bug,我相信任何工程师都可以很快很容易地找到它。
现在,让我们拿同一个代码样本,但是这一次,用让我们眼睛抽搐的特殊方式编写它:
var body: some View {
VerticalStack(alignment: .leading) {
Text(self.categoryName).font(.headline).padding(.leading, 15)
.padding(.top, 5)
ScrollView(showsHorizontalIndicator: false) {
HorizontalStack(alignment: .top, spacing: 0) {
ForEach(self.items) { landmark in
CategoryItem(landmark: landmark)
}
}
}
.frame(height: 185)
}
}
我觉得大多数工程师像第一个例子一样编写代码,但是有时(通常只是以小的方式)我们像第二个例子一样实现并向客户介绍我们的设计。
为什么?
因为代码是我们大部分时间看到的——当我们工作时,它就在我们面前。我们关心代码,这很好!毕竟我们是工程师。
另一方面,顾客关心用户体验,因为这是他们互动的内容;这就是激励他们的东西;那就是牵着他们的手,带他们踏上这段神奇旅程的东西。
【耶一巫师,哈利】
客户的旅程只有通过底层工程才有可能实现。但是,如果它不是以直观的方式呈现,没有噪音和干扰,那么他们就不会与工程的美联系起来,我们的交付就会失败。
这样想吧
如果代码具有高质量,那么它允许伟大的 UX 具有潜力,但这并不能保证。进入工程和设计的同样仔细的想法需要进入 UX,以使你的软件的全部潜力被你的客户理解和欣赏。
这让我想到了入职团队上个季度一直关注的事情:减少噪音。
降低噪音
以前,当需要在 Octopus 中引入新功能时,会有功能屏幕本身,加上一个文档页面,再加上应用程序中的一些支持文本,为您提供要点,包括文档的链接。
作为一个新用户,这种格式最初可能非常有用,但是在您使用了该功能并理解了它之后,您真的不需要再看到支持文本了。它现在永久地占据了你屏幕上的垂直空间,你的大脑不得不在每次你加载那个屏幕的时候浏览它,这给你的大脑制造了一点额外的认知负荷,让你的大脑在它要去的地方说“嗯,跳过那个吧”。
随着时间的推移,这些小噪音开始累积。它们变成了不必要的干扰,并从对用户真正有价值的 UI 区域夺走了权力。
在 Octopus,我们希望让新客户能够轻松地将这些点连接起来并找到他们的路,这通常需要支持文本来解释正在发生的事情。但对于使用八达通多年的更高级用户,我们不想给他们制造不必要的噪音。
考虑到这一点,我们最近增加了一个小功能来帮助我们实现这一目标...
我们希望新用户获得成功,作为这一目标的一部分,我们研究了如何让新用户更容易上手,并探索章鱼概念。
我们决定在边栏上提供有用的信息,这些信息既容易获得,也容易被忽略。这个侧边栏提供了与您正在查看的页面相关的概念的快速摘要,以及到我们的文档和其他有用资源的链接,这些资源对开始使用 Octopus 的人来说很有帮助。
将支持文本包含到侧边栏也意味着我们可以将它从许多屏幕的顶部移除,从而释放宝贵的屏幕空间。
如果您需要的话,这个特性可以为您提供一点额外的帮助,但是也不会妨碍您。这通常是我们在任何屏幕上寻找的平衡。
因此,在这些小小的发现之旅中,我们不断从不同的方向、不同的组合和不同的角度探索事物,直到我们感觉达到了某种平衡。
这通常会让您的团队感到沮丧,因为您会发现自己会说这样的话:
“很好,但还是有些不对劲,我不知道是什么,但还没准备好”
当谈到 UX 时,你必须相信你的感觉,因为当它是对的时候,你会知道的。你肩上所有的重量会突然消失,你会微笑。
UX 团队的结束语
以我的经验来看,这个过程需要团队内部的高度信任。如果你真的想成长,你必须准备好给予和获得强硬的反馈,这意味着让你的团队为这种尊重但开放的反馈风格做好准备。你必须考虑到在某件事情上做了多少工作,但也要做出艰难的决定,一个设计是否朝着正确的方向前进。
提前谈论这一点有助于让人们轻松进入这个过程,并避免以后一些更困难的对话。我通常是这样处理的:
“我们要求并鼓励每个人公开设计给他们带来的感受,并相信他们的直觉。你不能太依赖任何一项工作,因为你可能不得不放弃几天的进展,转向不同的方向。这可能会很痛,但要知道这绝不是退步。我们总是在前进,因为我们从所有这些迭代中学习,并最终一起到达某个伟大的地方。”
有些时候你不得不放弃一些你非常努力的设计/代码,因为有些东西感觉不太对劲,或者你已经展示给人们看了,但他们没有以你希望的方式做出反应或与之联系。
回想起来,正是这些时刻教会了你很多关于你自己(以及和你一起工作的人)的事情。
好消息是,你可以深切地关心你的代码/设计,而不必把自己与它联系在一起。诀窍似乎是:
- 保持团队沟通畅通(表达感受)
- 保持定期评审和反馈(不要让作品达到沸点)
- 保持会议简短扼要(有议程,进来,出去,完成任务)
- 经常从客户的角度看问题(不是字面上的,只是奇怪)
开始做这些事情,我相信你会朝着正确的方向前进。要知道,UX 的小变化可以给人们的生活带来很大的变化,会有机会改善客户的体验,好事也会发生。
在我们忙乱的小世界里发生的一切,总是试图保持观点。也就是说,你希望顾客体验的主要价值是什么?其他一切可能只是噪音😃
保持微笑和愉快的部署。
构建定制执行容器的技巧——Octopus Deploy
原文:https://octopus.com/blog/tips-building-custom-execution-containers
执行容器是 Octopus Deploy 中隐藏的瑰宝。我最近完成了对它们的深入研究,以帮助一位客户。这包括创建一个定制的 Docker 映像,用于执行容器特性。我学到了一些提示和技巧,我想分享给你,帮助你写你的自定义图片。
执行容器的优势
当 2018 年 workers 发布时,我对从 Octopus 服务器卸载部署任务的能力感到兴奋。我开始创建工人池。我在配置工作人员时遇到的一个问题是需要预安装软件。在某些情况下,某些软件(如 Terraform)版本之间不兼容。并排运行在技术上是可能的,但它可能会导致问题,而且我无法在不修复一堆部署的情况下升级到最新版本。
我最终得到了这样的工人池:
- Terraform 11 工人池
- Terraform 12 工人池
- 等等。
执行容器解决了这个问题。我可以用我需要预装的软件创建 Docker 映像。现在,在我的部署过程中,我指定下载并运行一个 Docker 映像。
版本是部署过程的一部分。将我的部署过程升级到最新的工具需要更改分配给 Docker 映像的版本号和运行该过程的任何步骤。这并不奇怪,因为所需工具的版本是部署过程的一部分。
技巧 1:用执行容器引用包
数据库部署是工作人员的常见用例。数据库部署至少涉及一个包含所需状态或迁移脚本的包,但是包引用如何与执行容器一起工作呢?
首先,在执行容器上运行的步骤通常来自于运行脚本步骤。运行脚本步骤提供了引用包的能力。这将把必要的目录作为卷安装到 Docker 容器上。
您可以轻松地将包的内容传递给执行容器,这应该会使它从直接在服务器上运行无缝地过渡到执行容器。这适用于 Octopus Deploy 提供的图像或您的自定义图像。
技巧 2:映像需求
创建自定义执行容器映像时,有一些要求:
- Linux 镜像应该是基于 Ubuntu 的,并且安装了
wget unzip apt-utils curl software-properties-common
。这是 Calamari 正确运行所需的软件。 - Dockerfile 文件中不能定义入口点。Octopus 拥有容器的生命周期;入口点可能会过早结束容器。
- Linux 容器运行在 Linux 主机上,Windows 容器运行在 Windows 主机上。Windows 主机上没有 Linux 容器。
- 假设您使用多种体系结构标记映像(下面将详细介绍),工作池可以由 Windows 主机或 Linux 主机组成。
技巧 3:了解工作目录并配置可执行路径
docker run
命令还利用 workdir 或-w
参数来设置容器中的工作目录,以匹配主机中的工作目录。
通常,容器中的工作目录并不重要。如果脚本运行成功,它是从根目录还是从随机文件夹运行都没关系,除非您需要在 Docker 映像上安装定制软件。它不使用包管理器,如 Windows 的 Chocolatey,Linux 发行版的 APT 或 YUM。
我最近在为 Flyway 构建执行容器时遇到了这个问题。包管理器非常有用,因为它为环境变量添加了必要的路径。我可以使用flyway info
而不是C:\Flyway\flyway.exe info
来运行 info 命令。在撰写本文时,只有一个 Maven repo,您可以使用它下载一个. tar 或。zip 文件。
对于基于 Linux 的发行版,命令如下:
ARG FLYWAY_VERSION=7.7.1
# Change the workdir to Flyway to install flyway binaries
WORKDIR /flyway
# Install flyway
RUN curl -L https://repo1.maven.org/maven2/org/flywaydb/flyway-commandline/${FLYWAY_VERSION}/flyway-commandline-${FLYWAY_VERSION}.tar.gz -o flyway-commandline-${FLYWAY_VERSION}.tar.gz \
&& tar -xzf flyway-commandline-${FLYWAY_VERSION}.tar.gz --strip-components=1 \
&& rm flyway-commandline-${FLYWAY_VERSION}.tar.gz \
# Make the /flyway/flyway command an executable
&& chmod +x /flyway/flyway \
# Make it so any user can call /flyway/flyway
&& ln -s /flyway/flyway /usr/local/bin/flyway
# Octopus expects to start in the default directory
WORKDIR /
这会将 Flyway 包的内容提取到/flyway
目录,使/flyway/flyway
成为可执行文件,然后将其添加到适当的路径。我在脚本中需要做的就是调用/flyway/flyway info
来运行飞行路线信息命令。
对于 Windows 容器,这方面的命令是:
SHELL ["powershell", "-Command"]
ARG FLYWAY_VERSION=7.7.1
# # Install Flyway
RUN Invoke-WebRequest "https://repo1.maven.org/maven2/org/flywaydb/flyway-commandline/${env:FLYWAY_VERSION}/flyway-commandline-${env:FLYWAY_VERSION}-windows-x64.zip" -OutFile flyway-cli.zip; `
& '.\Program Files\7-Zip\7z.exe' x .\flyway-cli.zip; `
rm .\flyway-cli.zip;
RUN $old = (Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name path).path; `
Write-Host $old; `
$flywayPath = ';C:\flyway-' + ${env:FLYWAY_VERSION}; `
$new = $old + $flywayPath; `
Write-Host $new; `
Set-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name path -Value $new; `
refreshenv
该脚本将 Flyway 可执行文件下载并提取到C:\flyway-[version number]
,然后将该目录添加到环境路径中。我在脚本中需要做的就是调用flyway info
来运行飞行路线信息命令。
技巧 4:创建基本图像
我建议创建包含任何子映像都可以利用的基础映像。我为 Flyway 执行容器映像创建了基础映像:
- Windows 2019 映像
- PowerShell 核心
- 巧克力
- 计算机编程语言
- 7Zip
- Ubuntu 图像
- 。NET 核心必备软件(Calamari 是一个自带的。网芯 app)
- 卷曲
- 拉开…的拉链
- PowerShell 核心
- Python3
这样,子映像只需提取基础映像并安装必要的软件。基本映像可以减少子映像的构建时间,因为核心映像很少改变。
在转移到基本映像之前,我的 Linux 和 Windows 版本大约需要 13 分钟。移动到基本图像减少到 5 分钟。
技巧 5:对跨平台图像使用 Docker 清单
要求一个人选择 Windows 或 Linux 映像是一个额外的认知负荷,特别是如果工具是跨平台的。它迫使用户思考容器将在哪里运行。
这可以通过使用码头货单来完成。
Docker 提供了 buildx 作为 manifest 命令的替代。在撰写本文时,buildx 不容易支持 Windows 容器和 Linux 容器。
为了利用docker manifest
,你必须打开 Docker 中的实验特性。有很多例子说明如何为 Linux 做这件事。如果您使用的是 Docker Desktop for Windows,请以管理员身份运行此 PowerShell 命令,然后重新启动 Docker 引擎:
[Environment]::SetEnvironmentVariable("DOCKER_CLI_EXPERIMENTAL", "enabled")
清单的工作原理:
- 为 Windows 构建映像:
docker build ./windows-2019 --tag octopuslabs/flyway-workertools:$FLYWAY_VERSION_TO_BUILD-windows.2019 --tag octopuslabs/flyway-workertools:latest-windows.2019
。 - 为 Windows:
docker push octopuslabs/flyway-workertools:$FLYWAY_VERSION_TO_BUILD-windows.2019
的每个标签推送图片。 - 转到 Linux 容器。
- 为 Linux 构建映像:
docker build ./ubuntu-1804 --tag octopuslabs/flyway-workertools:$FLYWAY_VERSION_TO_BUILD-ubuntu.1804 --tag octopuslabs/flyway-workertools:latest-ubuntu.1804
。 - 为 Linux 的每个标签推送图片:
docker push octopuslabs/flyway-workertools:$FLYWAY_VERSION_TO_BUILD-ubuntu.1804
。 - 构建清单:
docker manifest create octopuslabs/flyway-workertools:latest octopuslabs/flyway-workertools:latest-windows.2019 octopuslabs/flyway-workertools:latest-ubuntu.1804
。 - 推送清单:
docker manifest push octopuslabs/flyway-workertools:latest
。
您将为每个架构(Windows 和 Linux)构建并标记一个映像。这些标签对于该架构是唯一的。当您为latest
标签构建清单时,您可以“将最新的 windows.2019 和最新的 ubuntu.1804 合并到一个标签中”。完成后,您的标签将在 Docker Hub 上显示如下。
必须在构建清单之前推送图像。否则,您会收到一个关于找不到图像的错误,因为清单没有查看本地计算机。
我已经让 GitHub 动作来构建公共的 Flyway 执行容器,所以你可以看到它的运行。
技巧 6:利用 Docker 构建参数
您会注意到,在我的 Dockerfile 文件示例中,我使用了ARG
命令:
ARG FLYWAY_VERSION=7.7.1
当一个新版本的 Flyway 准备好安装时,假设其他一切都是一致的,我只需在我的docker build
命令中传递一个--build-arg
开关。
docker build ./ubuntu-1804 --tag octopuslabs/flyway-workertools:$FLYWAY_VERSION_TO_BUILD-ubuntu.1804 --tag octopuslabs/flyway-workertools:latest-ubuntu.1804 --build-arg FLYWAY_VERSION=$FLYWAY_VERSION_TO_BUILD
为了创建 Docker 映像的新版本,我只更改了管道中的一个点,而不是多个地方。
技巧 7:根据关键软件对 Docker 镜像进行命名和版本化
我一直使用 Flyway 作为我的示例容器。这是容器的关键软件。其他软件,如 Java Runtime Environment (JRE ),需要它才能正常工作。图像的名称为flyway-workertools
,版本基于 Flyway 版本。
目标是减少图像消费者的模糊性。任何运行flyway-workertools
图像的7.7.2
的人都知道他们将得到 Flyway 的7.7.2
。
技巧 8:安排构建来检查新版本
最后一个技巧与日常维护有关。执行容器很棒,但是保持所有工具的最新版本是非常耗时的。Flyway 发布了一个清单文件,我可以让我构建检查。我的脚本将:
- 检查 Flyway 清单文件中的最新版本。
- 从 Docker Hub 中调出所有标签,搜索最新版本。
- 如果发现最新版本是一个标签,那么停止构建。
- 如果发现最新版本是而不是作为标签,那么构建一个新版本。
下面的脚本来自用于构建 Flyway worker 工具映像的 GitHub 动作。因为这是一个 GitHub 动作,所以它设置输出变量,供后面的动作和步骤使用。
$manifestDataRaw = Invoke-WebRequest "https://repo1.maven.org/maven2/org/flywaydb/flyway-commandline/maven-metadata.xml"
$manifestData = [Xml]$manifestDataRaw
$latestFlywayVersion = $manifestData.metadata.versioning.latest
$workerToolsTags = Invoke-RestMethod "https://registry.hub.docker.com/v1/repositories/octopuslabs/flyway-workertools/tags"
$matchingTag = $workerToolsTags | Where-Object { $_.name -eq $latestFlywayVersion }
echo "::set-output name=CURRENT_FLYWAY_VERSION::$latestFlywayVersion"
if ($null -ne $matchingTag)
{
Write-Host "Docker container already has latest version of flyway"
echo "::set-output name=Continue::No"
}
else
{
Write-Host "We need to upgrade the flyway container to $latestFlywayVersion"
echo "::set-output name=Continue::Yes"
}
有了这个脚本,我可以安排一个每日构建来检查新版本,并在发现新版本时构建一个新容器。只要消费者引用一个特定的版本号(例如,7.7.2
,他们就不会注意到新的图像。
包管理器的逻辑是不同的,并且依赖于包管理器本身。
结论
Octopus Deploy 中的执行容器使得管理依赖关系更加容易。Octopus 提供图像来帮助尽可能多的人开始使用执行容器。它们应该适用于大多数情况,但是图像可能包含您不需要的额外软件,导致膨胀和更长的下载时间。
我希望所提供的技巧能帮助你制作出符合你特定需求的图片。
愉快的部署!
避免重复部署步骤的提示- Octopus 部署
原文:https://octopus.com/blog/tips-to-avoid-duplicating-deployment-steps
在 Octopus 中,我们强烈认为部署应该是可重复的。当您准备使用 Octopus 部署一个项目时,您需要花一些时间定义您的部署过程。在 Octopus 中,这由您在步骤选项卡上定义的一组部署步骤来表示。
我注意到人们在定义步骤时会犯一些常见的错误。为了有所帮助,您可以做两件事来减少重复部署步骤的需要。
技巧 1:在所有环境中使用相同的角色
创建包或脚本步骤时,可以指定该步骤部署到的角色:
人们有时会认为,当我选择上面的web-server
角色时,该步骤将针对该角色中的所有机器运行,而不管我部署到哪个环境。事实并非如此。当我部署到我的开发环境中时,这个步骤将只在开发环境中的web-server
机器上运行。当我部署到生产环境时,这个步骤将只在生产环境中的web-server
角色的机器上运行。
如果您的角色名称以“prod”或“uat”或“dev”结尾,很可能您没有正确使用角色。
有一种理论认为,当谈到软件时,如果用户认为它必须以某种方式工作,而事实并非如此,那么是软件而不是用户有问题。我认为这在很大程度上是正确的。在这种情况下,我怀疑这是因为从 UX 的角度来看,我们没有做好明确部署期间会发生什么的工作。如果你对我们如何使这变得更清楚有任何建议,请在下面留下评论。
技巧 2:使用变量来说明环境之间的差异
定义步骤时,许多字段允许您引用变量。您可以通过查找字段旁边的哈希按钮来判断字段是否支持它。
假设我需要将一个 Azure 云服务包部署到 Azure 中的两个不同的云服务上。我可以创建两个不同的步骤,并为每个步骤选择不同的环境。然而,更好的方法是定义适用于每个环境的变量。
然后,我可以在定义步骤时引用该变量:
希望这两个技巧能够帮助您减少需要定义的部署步骤的数量。
章鱼 TL;灾难恢复-性能改进- Octopus 部署
每周三早上,我们会召开一个简短的全公司会议,我们称之为“TL;“博士。这是一个公开邀请,任何团队成员都可以展示他们认为相关或有趣的东西,以及他们希望公司其他人知道的东西。它们涵盖了各种各样的主题,从特性设计到从支持事件中吸取的经验教训,再到开发人员在构建软件时可能会用到的模式。
作为一项实验,我们将把这些演示文稿每周公之于众——至少是那些不太机密的部分😉我希望它能让你对 Octopus 的内部有一点“幕后”的了解。
今天的 TL;DR 展示了两个关于性能的演示。Octopus 3.12 具有一些非常显著的性能改进,这要感谢一些报告问题的客户,他们能够提供跟踪和其他信息来帮助我们重现问题。
首先,Rob Erez 分享了一些即将到来的与浏览器缓存相关的改进,并减少了我们的 web 请求总数。然后,迈克尔·诺南分享了他最近使用 JetBrains dotMemory 来诊断一些内存问题的过程。我希望你喜欢!
https://www.youtube.com/embed/eByv1uuum88
VIDEO
我很想知道这是否是你感兴趣的事情,以及是否有你想看到我们讨论的话题。请在下面的评论中留言,别忘了订阅 YouTube 频道!
使用 YAML 和 XML 配置文件替换将 Java 部署到 Tomcat
原文:https://octopus.com/blog/tomcat-deployments-with-variable-replacements
将应用程序部署到不同的环境时,一个常见的挑战是确保应用程序针对特定的环境进行了正确的配置。典型的例子是用每个环境所需的凭证配置数据库连接字符串。这些类型的敏感凭证不应该存储在源代码控制中,因此必须做一些工作来获取由在本地开发环境中工作的开发人员创建的应用程序包,以创建可以部署到共享但也受限制的环境中的包。
Octopus 多年来一直有能力将值注入到 JSON 文件中,但是 Java 应用程序通常没有将 JSON 作为一种配置格式。旧的 Java 应用程序严重依赖 XML,而像 Spring 这样的新库已经采用了 YAML 和属性文件作为它们的配置。
在本文和截屏中,我将向您展示如何将 Spring 应用程序部署到 Tomcat,利用 Octopus 2020.4 中的新功能将值注入 XML、YAML 和属性文件。
截屏
https://www.youtube.com/embed/x1u2iAr_BQ4
VIDEO
准备 Linux web 服务器
对于这个例子,我使用的是 Ubuntu 20.04。为了安装 Tomcat 和管理器应用程序,我们运行:
sudo apt-get install tomcat9 tomcat9-admin
然后,我们需要通过修改/var/lib/tomcat9/conf/tomcat-users.xml
文件来创建一个可以访问管理器应用程序的用户。该文件的示例如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<tomcat-users
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
version="1.0">
<role rolename="manager-script"/>
<role rolename="manager-gui"/>
<user username="admin" password="Password01!" roles="manager-gui,manager-script"/>
</tomcat-users>
如果您在访问管理器应用程序时遇到问题,可能是因为安全设置只限制从本地主机 IP 地址进行访问。该限制在文件/usr/share/tomcat9-admin/manager/META-INF/context.xml
中定义。以下是实施过滤的阀被注释掉的例子:
<?xml version="1.0" encoding="UTF-8"?>
<Context antiResourceLocking="false" privileged="true" >
<!-- Comment out the Valve below to remove the address filtering -->
<!--<Valve className="org.apache.catalina.valves.RemoteAddrValve"
allow="127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1" />-->
<Manager sessionAttributeValueClassNameFilter="java\.lang\.(?:Boolean|Integer|Long|Number|String)|org\.apache\.catalina\.filters\.CsrfPreventionFilter\$LruCache(?:\$1)?|java\.util\.(?:Linked)?HashMap"/>
</Context>
生成自签名证书
要生成自签名证书,请运行以下命令:
openssl genrsa 2048 > private.pem
openssl req -x509 -new -key private.pem -out public.pem
openssl pkcs12 -export -in public.pem -inkey private.pem -out mycert.pfx
由此产生的 PFX 文件可以上传到八达通证书商店。
示例应用程序
我们正在部署的应用程序名为 Random Quotes,源代码可从 GitHub 获得。这个应用程序有两个我们想要在部署期间修改的配置文件: application.yml 和 web.xml 。这些文件混合了 Spring 使用的较新的 YAML 配置风格和 servlet 应用程序使用的较旧的 XML 风格。
具体来说,我们将在 YAML 文件中设置活动的 Spring 概要文件,并在 XML 文件中设置应用程序显示名称。
要构建应用程序,请运行命令:
mvn package
然后将target
目录下的 WAR 文件上传到 Octopus 内置的 feed 中。
使用文件副本部署应用程序
使用 Octopus 可以以两种不同的方式部署 Java 应用程序。
一般的解决方案是将 WAR 文件复制到目标机器上的一个目录中。大多数应用程序服务器都有一个目录,用于监控新文件,然后自动部署应用程序。对于 Tomcat,该目录是/var/lib/tomcat9/webapps
。
要将 Java 档案部署到目录,请使用部署 Java 档案步骤。
通过必须手动启用的结构化配置变量功能将值注入配置文件:
然后,该步骤被配置为将 Java WAR 文件部署到/var/lib/tomcat9/webapps
目录:
然后将application.yml
和web.xml
文件定义为属性替换的目标:
通过管理器部署应用程序
将应用程序部署到 Tomcat 的第二种方式是通过管理器步骤将部署到 Tomcat。这一步利用了管理器应用程序公开的 API。
为了配置这个步骤,我们需要将它指向位于 http://localhost:8080/manager 的管理器 API,并定义我们添加到tomcat-users.xml
文件中的凭证:
定义变量
我们需要定义两个变量来匹配我们想要替换的 YAML 和 XML 文件中的属性。
对于 YAML 文件,语法是一个冒号分隔的属性层次结构,产生一个变量名 spring:profiles:active 。对于 XML 文件,XPath 用作语法,这导致变量名为 //*:display-name :
配置 HTTPS
配置 HTTPS 证书是通过将证书部署到 Tomcat 步骤完成的。
它要求定义 CATALINA_HOME 和 CATALINA_BASE 目录。这些在系统服务文件中定义在/lib/systemd/system/tomcat9.service
,在我们的例子中定义为/usr/share/tomcat9
和/var/lib/tomcat9
:
然后,我们通过端口 8443 暴露 HTTPS 访问:
结论
在这个例子中,我们使用支持本地开发的通用配置文件部署了一个 web 应用程序,并在部署期间注入了在 Octopus 中定义的变量,以便为特定环境配置最终的应用程序。这演示了如何在将应用程序归档部署到共享和受限环境时,使用没有特定环境设置的应用程序归档并对其进行定制。
我们还部署了一个证书来配置 Tomcat 服务器上的 HTTPS 访问。
最终结果是一个为共享环境定制的 Spring Boot 应用程序,并安全地在 HTTPS 上公开。
愉快的部署!
将证书部署到 Tomcat - Octopus 部署
在之前的一篇文章中,我向您展示了如何将由 Octopus Deploy 管理的证书导出到 Java 密钥库中,然后配置 WildFly 来利用密钥库提供对 web 应用程序和管理端口的 HTTPS 访问。
在这篇文章中,我将向你展示如何在 Windows 中为 Tomcat 做同样的事情。
先决条件
要运行这篇博文中的命令和脚本,您需要安装一些工具。
第一个是 OpenSSL 客户机。我使用了来自 Shining Light Productions 的 Windows OpenSSL 二进制文件。
第二个是 Groovy SDK。你可以从 Groovy 下载页面下载安装程序。
这些步骤已经用 Tomcat 8 测试过了,但是应该适用于 Tomcat 6 以上的所有版本。
最后,你还需要安装 Java 8 JDK。
创建和分发证书
因为 WildFly 和 Tomcat 都使用 Java 密钥库,所以 WildFly 博文中的创建证书库和导出证书库下的指令可以完全相同的方式应用于 Tomcat。
如果您遵循这些步骤,您的 Tomcat 实例将有一个名为C:\keystore.jks
的文件。
在 Tomcat 中配置 HTTPS
为 Tomcat 配置 HTTPS 支持实际上非常简单。我们需要做两件事:
- 向引用密钥库的
server.xml
文件添加一个<Connector>
元素,如下所示:
<Connector SSLEnabled="true"
keystoreFile="C:\keystore.jks"
keystorePass="Password01"
port="8443"
protocol="org.apache.coyote.http11.Http11NioProtocol"
scheme="https"
secure="true"
sslProtocol="TLS"/>
- 确保现有的 HTTP 连接器
redirectPort
属性指向 HTTPS 端口,如下所示:
<Connector
connectionTimeout="20000"
port="8080"
protocol="HTTP/1.1"
redirectPort="8443"/>
为了促进这些改变,我们写了一个 Groovy 脚本,它将使用必要的改变来更新server.xml
文件。
运行脚本
要用 HTTPS 支持更新 Tomcat,运行如下脚本:
groovy tomcat-deploy-certificate.groovy --tomcat-dir C:\apache-tomcat-8.5.15 --https-port 8443 --http-port 8080 --keystore-file C:\keystore.jks --keystore-password Password01
就是这样!重新启动 Tomcat,您将能够通过安全连接访问它。
后续步骤
这些 Groovy 脚本正在被开发,作为最终将被移植到 Octopus Deploy 中直接提供的步骤中的概念验证。
如果你对剧本有任何问题,请留下评论。如果有一些 Java 特性你希望 Octopus 在未来部署支持,请加入 Java RFC 帖子的讨论。
通过管理器部署 Tomcat 应用程序
在上一篇文章中,我向您展示了如何通过将一个 WAR 文件提取到 Tomcat webapps
文件夹中,从 Octopus Deploy 部署到 Tomcat。除了文件副本,Tomcat 还支持通过与 Tomcat 发行版捆绑在一起的管理器应用程序部署 WAR 文件。
在本文中,我将向您展示如何通过 Tomcat 管理器在 Tomcat 中部署 WAR 文件。
配置 Tomcat
在开始上传 WAR 文件之前,我们需要做一些初始配置。
配置用户
我们需要定义与管理器交互的凭证。经理区分两种类型的用户,一种是通过浏览器使用 web 界面的用户,另一种是将使用 API 的用户。
为了定义这些用户,我们需要编辑conf/tomcat-users.xml
文件。
基于浏览器的用户被分配到manager-gui
角色,而 API 用户被分配到manager-script
角色:
<user username="manager" password="s3cret" roles="manager-gui"/>
<user username="deployer" password="s3cret" roles="manager-script"/>
定义最大文件上传大小
默认情况下,您只能通过管理器上传大约 50MB 的文件。这对于大的包来说通常不够大,所以您可以通过编辑webapps/manager/WEB-INF/web.xml
文件来设置一个更高的限制。在该文件中,您会发现一个<multipart-config>
元素,它的子元素定义了最大文件上传大小。
在这里,我将最大文件上传大小设置为 250MB:
<multipart-config>
<max-file-size>262144000</max-file-size>
<max-request-size>262144000</max-request-size>
<file-size-threshold>0</file-size-threshold>
</multipart-config>
上传战争文件
要将 WAR 文件放入 Octopus Deploy,请按照前一篇博文中的打包 WAR 文件和推送包中的说明进行操作。
然后,您可以使用中的指令将 WAR 文件提取到目标服务器上,创建一个 Octopus 部署项目。
但是这次有一个重要的不同。我们将把 WAR 文件提取到一个临时位置,而不是直接提取到webapps
文件夹。在这种情况下,我将文件提取到了C:\staging
文件夹:
为了部署文件,我们需要对http://localhost:8080/manager/text/deploy
URL 进行 HTTP PUT,在请求体中提供要部署的 WAR 文件。path
查询参数定义了将分配给应用程序的上下文路径。请注意,在路径名中有前导斜杠是很重要的。
下面的 PowerShell 命令使用 curl 来发出 HTTP 请求:
& "C:\curl.exe" -u deployer:s3cret -X PUT -F "file=@C:\staging\demo.war" http://localhost:8080/manager/text/deploy?path=/demo
确认部署
打开 http://localhost:8080/manager,并为我们之前创建的管理员用户提供凭据。您将看到应用程序部署在demo
路径下:
后续步骤
正在开发这些过程,作为最终将迁移到 Octopus Deploy 中直接提供的步骤中的概念验证。
如果您对此流程有任何疑问,请留下评论。如果有一些 Java 特性你希望 Octopus 在未来部署支持,请加入 Java RFC 帖子的讨论。
使用 Octopus Deploy - Octopus Deploy 在 Tomcat 中启用并行部署
在 Octopus,我们正在努力构建对 Java 和 Tomcat 等服务器的支持。虽然这项工作仍在进行中,但我想分享 Tomcat 的一个简洁的特性,当这项 Java 工作发布时,您将能够通过 Octopus Deploy 使用它。
博文中的截图来自 Octopus Deploy 的开发版本,在最终版本中会有变化。
并行部署
考虑这个相当典型的场景。您有一个为 web 应用程序提供服务的 Tomcat 实例,并且您想要部署一个新版本。目前使用该应用程序的用户会发生什么情况?通常,部署新版本的应用程序会使为旧版本的应用程序建立的会话失效。这对用户体验意味着什么在很大程度上取决于所涉及的应用程序,但成为那些会话刚刚终止的客户之一几乎不会有什么乐趣。
Tomcat 附带了一个很好的特性,叫做并行部署,这使得这些升级对于管理员来说更加容易,对于客户来说更加愉快。并行部署允许应用程序的两个或更多版本并行运行,允许现有会话自然完成,同时将新流量导向新部署的应用程序版本。
并行部署不能解决数据库等共享资源的变更问题。如果应用程序的新版本改变了数据库模式,旧版本的应用程序可能不再按预期工作。在决定实现并行部署时,请记住这些限制。
并行部署很容易配置,所以让我们看一个简单的例子。
通过 Octopus Deploy 部署 Tomcat
这里是我们正在构建到 Octopus Deploy 中的工作进展步骤的截图。
任何通过 Octopus Deploy 将应用程序部署到 IIS 的人都应该对此很熟悉。您选择要部署的包,并定义您要部署到的服务器的一些细节。
为了利用并行部署,您只需要为Deployment Version
字段定义一个值。唯一的要求是更新的部署要有更高的版本。在这个例子中,我们使用 Octopus 版本号作为版本,这满足了每次部署都增加版本号的要求。
部署第一个版本
一旦部署完成,您将在 Tomcat 管理器中看到类似这样的内容。在本例中,演示应用程序已经部署了版本0.0.50
。另请注意,它还没有会话。这是因为没有人开始使用该应用程序。
这是刚刚部署的演示应用程序的屏幕截图。请注意屏幕底部的版本号。这是构建应用程序时生成的时间戳。
应用程序中的时间戳和它在 Tomcat 中被分配的版本不需要相同。使这些版本相同是有益的,但是对于这个例子,我们只使用这个时间戳来区分并行部署的应用程序的两个版本。
既然已经使用了这个应用程序,Tomcat 将显示它有一个与之相关联的会话。你可以在Sessions
栏下看到这个。
部署第二个版本
现在我们有一个新版本的应用程序要部署。部署由 Octopus Deploy 处理,这一次给了我们同一个应用程序的第二个版本,现在是版本0.0.51
。
在这里,我在一个隐姓埋名的窗口中打开了应用程序。这样做意味着我没有共享在常规 Chrome 窗口中开始的会话,这意味着 Tomcat 将这个请求视为一个新的会话。从截图中可以看到,应用程序显示的时间戳是不同的,因为这是后来构建的不同的 WAR 文件。
现在,Tomcat 管理器向我们展示了演示应用程序的两个版本:0.0.50
和0.0.51
。两者都有活动会话,并且都将独立运行。现有用户继续使用版本0.0.50
,而新流量被导向版本0.0.51
。
删除旧部署
在某个时刻,针对旧版本应用程序的所有会话都将结束。当这种情况发生时,我们可以利用 Tomcat 中的一个特性,该特性会在没有用户的情况下自动取消部署旧的应用程序版本。
要启用这个特性,您需要编辑server.xml
文件,并在<Host>
元素中设置undeployOldVersions="true"
。
<Host
name="localhost"
appBase="webapps"
unpackWARs="true"
autoDeploy="true"
undeployOldVersions="true">
通常会话会在大约 30 分钟后过期,但是您可以通过使用commands
列中的Expire sessions
按钮终止空闲了一分钟的会话,并将空闲时间减少到一分钟,从而加快进程。
随着会话手动过期,应用程序版本0.0.50
不再有任何活动用户。
几秒钟后,这个旧版本将自动取消部署。
结论
如果您正在寻找一种简单的方法来管理 Java 应用程序的滚动部署,Tomcat 中的并行部署特性是一种非常方便的方法,可以将用户定向到新版本,同时在不再使用旧版本时最终清理旧版本。
八大集装箱登记处-部署八达通
容器注册中心经常与它们的存储库副本混淆,尽管它们服务于不同的目的。
容器存储库是容器化应用程序映像的存储。如今,大多数图像存储库都专注于“OCI”格式,基于 Docker 普及并向所有人开放的容器格式。事实上,“OCI 形象”和“码头形象”在注册服务商的营销中经常互换使用。
OCI 主张开放集装箱倡议。该计划是一个容器结构,旨在充当行业标准格式。技术、开发和云服务领域的大多数主要参与者都支持这一倡议,并支持 OCI 格式。在开放容器倡议网站上了解更多信息。
然后,容器注册中心既作为容器存储库的集合,又作为管理和部署映像的可搜索目录。
市场上有许多容器注册选项,为不同类型、大小和需求的客户提供服务。让我们看看我们的 8 强,以及他们为什么会有吸引力。
坞站集线器
鉴于 Docker 发明了用于容器交付的标准 OCI 格式,并被所有主要操作系统采用,因此 Docker Hub 也是图像管理的标准注册中心是有道理的。如果你正在开发,很可能你已经使用过 Docker Hub,尤其是如果你曾经关注过我们的技术指南或博客。
虽然这个列表中的所有注册表服务都可以帮助你管理 Docker 的格式,但 Docker Hub 作为一个注册表仍然值得在这里占有一席之地。不仅仅是因为它提供了一个巨大的公共注册表,你可以用它来部署来自大大小小的供应商的应用,也可以交付你自己的应用。
亚马逊 ECR
由于其集成和团队管理选项,如果您已经在使用亚马逊网络服务(AWS)来托管应用程序,亚马逊的弹性容器注册中心(ECR) 会很有用。
Amazon 的 ECR 融入了他们所有的容器托管服务,因此您可以轻松地管理和推送您的图像到:
- 弹性集装箱服务
- 弹性立方结构服务(EKS)
- 哦,舔舔
(当然,前提是你能理解所有这些相似的缩写。)
像 Docker Hub 一样,他们的公共注册中心和市场也是物有所值的,允许部署许多产品、自由软件和开源项目。您永远不会缺少可以构建的现有环境。
海港
Harbor 是一个开源注册表,你几乎可以安装在任何地方,但特别适合 Kubernetes。
在某些服务将它们的注册中心与它们自己的服务紧密结合的情况下,Harbor 的自由使它成为一个多用途的选择。它与大多数云服务以及持续集成和持续交付(CI/CD)平台兼容,也是一个很好的内部解决方案。
Azure 容器注册表
在向 AWS 提供类似服务的同时,微软的Azure Container Registry(ACR)也支持 Docker 和 OCI 图像,以及 Helm charts。
然而,Azure 最大的卖点是它的注册地理复制。这确保了每个人都能以他们习惯的速度访问图像,无论他们身在何处。
GitHub 容器注册表
鉴于 GitHub 的影响力,并且它已经对所有用户可用, GitHub 的容器注册表(称为 GitHub 包的更大功能的一部分)是最容易接近的选项之一。
考虑将 GitHub 包用于容器管理的好处包括:
- 简化的用户管理-您的大多数用户已经拥有帐户
- GitHub Actions 集成推送、发布和部署图像
- 相对于其他服务的成本
谷歌容器注册
当其他服务将他们的承诺集中在对潜在客户子集重要的某些功能上时,谷歌云的容器注册(GCR) 是一个可靠的多面手。它做了你想从容器注册表中得到的一切,而且做得非常好。
就像云服务的其他大牌一样,你可能会被锁定在 GCR,这取决于你使用的谷歌云产品。例如,Google Cloud Run 将只使用存储在 GCR 的图像,所以在选择注册服务时要记住这一点。
它不像 AWS 或 Azure 那样吹嘘自己的功能,但谷歌云的 GCR 是云提供商“三巨头”之一的一个有价值的产品。
JFrog 容器注册表
基于另一个 JFrog 产品 Artifactory, JFrog 容器注册表支持 Docker 图像和舵图。JFrog Container Registry 还提供了存储任何包类型的选项,这要归功于它的通用存储库。
JFrog 既有云和自托管两种选择(或者混合,如果你愿意的话),并承诺很好的可伸缩性。
红帽码头
与其他选择不同,红帽码头只提供私人集装箱注册。这使得它特别适合企业级客户。
Quay 不受云提供商限制,可轻松连接到 DevOps 管道两端的系统。像 Azure 一样,Quay 也包括地理位置选项,有趣的是,它支持 BitTorrent 进行集装箱配送。
Red Hat 还在其 Kubernetes 平台 OpenShift 中包含了一个精简的注册表解决方案。然而,他们确实建议大型团队和组织使用 Quay。
下一步是什么
在我们最近的一些帖子中,我们更多地讨论了容器化和云流程编排,包括:
愉快的部署!
跟踪 CI/CD 渠道中的吉拉问题- Octopus Deploy
原文:https://octopus.com/blog/track-jira-issues-across-your-ci-cd-pipeline
随着人们认识到 DevOps 提供的好处,近年来 devo PS 的采用大幅增加。许多解决方案提供集成在一起的持续集成和持续交付(CI/CD ),我们之前已经讨论过 CI 和 CD 之间的差异,但是这些解决方案很少充分利用在 DevOps 中至关重要的持续反馈循环。在这篇文章中,我将向您展示如何集成 Jenkins、Octopus Deploy 和吉拉来提供一个解决方案,使您可以轻松地跟踪 CI/CD 渠道中的问题。
安装詹金斯章鱼部署插件
Octopus Deploy 为 Jenkins 提供了一个插件,实现了其他构建平台上可用的相同功能。
安装 Octopus Deploy 插件与为 Jenkins 安装任何其他插件是一样的。当你打开 Jenkins,从登陆页面点击管理 Jenkins ,然后管理插件:
点击可用的选项卡,按Octo
过滤。勾选 Octopus Deploy 旁边的方框,选择安装而不重启或立即下载并重启后安装。
安装插件后,您将可以访问以下构建任务:
- Octopus 部署:打包应用程序
- Octopus 部署:推送包
- Octopus 部署:推送构建信息
除了构建任务之外,您还将有以下构建后任务:
- Octopus 部署:创建发布
- 章鱼部署:部署释放
Jenkins 插件与 Azure DevOps、TeamCity 和 Bamboo 的不同之处在于,创建版本和部署版本只能作为构建后操作使用。Jenkins 只允许一个 post build 动作类型,这意味着每个构建定义不能有一个以上的 create release 动作。
配置 Octopus 服务器连接
许多 Octopus 部署步骤需要连接到 Octopus 服务器。要配置连接,点击管理 Jenkins ,然后配置系统,然后向下滚动到 Octopus Deploy 插件,点击添加 Octopus Deploy 服务器:
添加您的 Octopus 服务器详情并点击保存。
Octopus 部署 CLI
Octopus Deploy 插件包含了执行这些操作所需的所有命令,但是它仍然依赖于构建代理上存在的 Octopus Deploy CLI 。下载 Octopus CLI 并将其解压缩到一个文件夹中,然后配置 Jenkins 知道它在那里。
点击管理 Jenkins ,然后点击全局工具配置。滚动到 Octopus Deploy CLI 部分,点击添加 Octopus 工具。添加工具的名称和 Octopus CLI 的路径,例如c:\octopuscli\octo.exe
。
示例构建
在这篇文章中,我构建了 PetClinic 应用程序,这是一个使用 MySQL 作为后端的 Java 应用程序。
构建设置
首先,从 Jenkins 菜单中选择一个新项目:
给你的项目命名,选择 Maven 项目。单击 OK ,您将看到构建定义的配置屏幕。
我将我的构建配置为基于下面定义的参数创建一个惟一的版本号。这个版本号将被印在构建的工件上,这些工件随后将被推送到 Octopus Deploy。我安装了几个 Jenkins 插件来实现这个功能:
- 构建名称和描述设置器
- 日期参数插件
在通用选项卡下,勾选框,该项目被参数化。
我们构建所需的参数是(所有String
参数):
- 数据库名称:
#{Project.MySql.Database.Name}
- 数据库服务器名称:
#{MySql.Database.Server.Name}
- 数据库用户名:
#{Project.MySql.Database.User.Name}
- 数据库用户密码:
#{Project.MySql.Database.User.Password}
我添加了一些可选参数来构造版本号,格式如下:1.0.2098.101603
。
可选参数:
- 专业(字符串):1
- 次要(字符串):0
- 年份(日期):
- 日期格式:yy
- 默认值:local date . now();
- 年月日:
- 日期格式:D
- 默认值:local date . now();
- 时间(日期):
- 日期格式:HHmmss
- local date . now();
定义好参数后,让我们将构建与源代码控制挂钩。我在这个构建中使用 PetClinic 公共 bit bucket repo:https://twerthi@bitbucket.org/octopussamples/petclinic.git
。点击源代码管理,选择 Git,输入 repo 的 URL。
如果您使用了上面的可选参数,单击构建环境选项卡,选中复选框设置构建名称,输入${MAJOR}.${MINOR}.${YEAR}${DAYOFYEAR}.${TIME}
作为构建名称,将构建名称设置为我们之前配置的版本号。
构建步骤
由于我们选择了 Maven 构建,Jenkins 为我们创建了构建步骤。我们需要做的就是导航到 build 选项卡,并为目标和选项输入以下内容:
clean package -Dproject.versionNumber=${BUILD_DISPLAY_NAME} -DdatabaseServerName=${DatabaseServerName} -DdatabaseName=${DatabaseName} -DskipTests -DdatabaseUserName=${DatabaseUserName} -DdatabaseUserPassword=${DatabaseUserPassword}
命令的分解:
- 清理:清理项目并移除由之前的构建生成的所有文件。
- 打包:将编译好的源代码打包成可分发的格式(jar,war,…)。
- -D:传递到构建中的参数。
这一步构建一个名为petclinic.web.Version.war
的. war 文件。在这种情况下,包 ID 是petclinic.web
。
发布步骤
我们剩下的步骤在构建定义的 post 步骤部分。这是我们为 MySQL 数据库后端打包 Flyway 项目的地方,将包和构建信息推送到 Octopus Deploy,然后创建我们的版本。
在后期步骤页签中,点击添加后期构建步骤,选择Octopus:package application,输入任务详情:
- 包装标识:
petclinic.mysql.flyway
- 版本号:
${BUILD_DISPLAY_NAME}
这是我们通过参数配置的版本号,通过上面的设置构建名选项设置。 - 包格式:
zip|nuget
- 包基础文件夹:
${WORKSPACE}\flyway
。忽略警告,它工作正常。 - 包包含路径:这里没有这个项目。
- 包输出文件夹:
${WORKSPACE}
接下来,我们定义推送步骤。点击 Post 步骤选项卡,点击 Add post-build step 下拉菜单,选择Octopus Deploy:push packages:
选择我们之前配置的 Octopus Deploy 服务器连接,然后选择您想要推送的空间(如果没有指定空间,将使用默认空间)。最后,添加要推送的包的路径。这一步接受通配符格式。路径的起始文件夹是${WORKSPACE}
,所以没有理由指定它(事实上,如果您这样做,它将失败)。
在上面我们为 Flyway 定义的Octopus Deploy:package application步骤中,我们告诉该步骤将包放在${WORKSPACE}
文件夹中。Maven build 放置 build。war 文件放在/target/
文件夹中,所以我们的包路径文件夹值是:
/*.nupkg
/target/*.war
负责推动包裹。接下来,我们将推送一些构建信息。点击添加后期构建步骤并选择Octopus Deploy:Push build information。这一步是吉拉发布说明的地方。
填写以下详细信息:
- Octopus 服务器:前面定义的服务器连接。
- 空间:你推动包裹的空间。
- 包 id:
petclinic.web
petclinic.mysql.flyway
- 版本号:
${BUILD_DISPLAY_NAME}
构建定义完成
在这个构建定义中,我们将 Jenkins 与 Octopus Deploy 集成在一起,并将 Jenkins 构建配置为从 Bitbucket 中检索发行说明,因此它们出现在 Octopus Deploy 中。让我们前往吉拉,进行集成配置。
部署吉拉与八达通集成
Octopus Deploy 已经开发了与吉拉软件的集成,以便在部署发生时,提交消息所引用的任何问题都可以回调到吉拉,并提供关于问题修复程序已部署在管道中的哪个位置的更新。
在吉拉添加八达通部署应用程序
Octopus Deploy 在吉拉市场创建了一个应用程序,便于集成。在吉拉软件登陆页面,点击吉拉设置、应用,点击查找新应用,按Octo
过滤,选择八达通部署吉拉:
点击获取 app ,点击立即获取。
看到应用程序已成功安装的消息后,点击开始。
配置吉拉和 Octopus 部署集成
在本节中,我们需要在 Octopus Deploy 和吉拉之间切换。
在继续之前,让我们打开 Octopus Deploy 并进入右侧屏幕来完成这一集成。在 Octopus Deploy 中,点击配置,然后点击设置,再点击吉拉问题跟踪器。
复制 Octopus 安装 ID 并将该值粘贴到吉拉的 Octopus 安装 ID 中(但不要单击保存):
复制吉拉基本 URL 并将该值粘贴到 Octopus Deploy 中的吉拉基本 Url 字段和吉拉问题跟踪器屏幕上。
现在,回到吉拉,复制吉拉连接应用程序密码,并将其粘贴到 Octopus Deploy 中的吉拉连接应用程序密码字段。
注意在 Octopus Deploy 中的测试按钮不会起作用直到你先点击保存在吉拉:
现在,让我们回到 Octopus Deploy,点击测试按钮测试连接工作。如果连接正常,您将看到以下屏幕:
至此,我们已经完成了吉拉的工作,但是在 Octopus Deploy 中我们还有一些事情要做。首先,让我们通过单击已启用复选框来启用 Octopus Deploy 中的集成。
要配置发行说明,请向下滚动并输入吉拉用户名和密码。
Jira Username
是你的电子邮件地址,Jira Password
是你的 API 密匙。
确保单击“测试”以确保凭据有效。
环境测绘
作为与吉拉集成的一部分,您需要将 Octopus 部署环境映射到吉拉环境类型。这是必要的,这样吉拉可以了解八达通的环境和跟踪问题的进展。请注意,吉拉环境类型是不能编辑的固定列表。为此,单击基础架构选项卡,然后单击环境,并单击该环境的省略号和编辑:
使用吉拉环境类型部分中的下拉菜单将 Octopus 部署环境与吉拉环境类型相关联。
对要映射的任何其他环境重复此过程。
Octopus 部署项目自动发布说明创建
要配置自动发行说明创建,从您的 Octopus 项目中,单击设置,并为发行说明模板输入以下内容:
#{each package in Octopus.Release.Package}
- #{package.PackageId} #{package.Version}
#{each workItem in package.WorkItems}
- [#{workItem.Id}](#{workItem.LinkUrl}) - #{workItem.Description}
#{/each}
#{/each}
(可选)您可以为部署更改模板输入以下内容:
#{each release in Octopus.Deployment.Changes}
**Release #{release.Version}**
#{release.ReleaseNotes}
#{/each}
反馈回路
随着我们的集成完成,是时候看到所有这些一起工作了。
在吉拉软件中创建一个问题
让我们在吉拉创建一个问题,点击左侧的 + :
填写问题表格,点击创建。
记下为该问题创建的 ID,因为您稍后会需要它。对于这个帖子,是PET-3
。
承诺回购
提交在 Octopus Deploy 中显示为发行说明,因此您可以看到正在部署什么。此外,如果您在提交消息中引用吉拉问题,提交将与吉拉的问题相关联。当部署发生时,八达通将更新吉拉的状态。
向您的回购协议添加一些提交;在这篇文章中,我添加了以下内容:
- 更新 pom.xml 以使用 SSL 版本的https://repo.spring.io/milestone回购
- PET-3 -更新了数据源 bean 属性,以防止数据库连接超时
- 将 bin 文件夹添加到 Flyway 项目中,以包含 JRE 的内置版本
构建项目
提交完成后,我们就可以构建项目了。构建定义中的推送构建信息步骤将包含我们的提交消息。让我们在詹金斯排队建设。
在 Jenkins 中,点击用参数构建,点击构建。
查看 Octopus Deploy 中的构建信息
构建完成后,应该可以在 Octopus Deploy 中获得这些信息。导航到库标签,点击构建信息,点击最高版本链接查看提交和工作项。
这里我们看到构建来自 Jenkins 构建服务器,并且包括我们对相关工作项(PET-3)进行的三次提交。点击 PET-3 链接将我们带到吉拉问题。
部署版本
到目前为止,这种整合看起来相当不错!部署一个版本会用状态更新吉拉。让我们开始开发部署。本文假设您已经知道如何创建项目,所以我们将跳过项目创建和部署过程的步骤。
在您的 Octopus Deploy 项目中,单击 CREATE RELEASE ,在下一个屏幕中单击 Save 之后,您将看到发布的详细信息。在这个屏幕上,我们可以看到我们的发行说明和相关的构建信息:
当该版本开始部署时,它将向吉拉发送信息。在吉拉,我们可以看到这个问题目前正在发展中。
结论
詹金斯,吉拉和章鱼部署都是强大的 DevOps 工具。当您将这三者集成在一起时,您将获得一个强大的 DevOps 解决方案,它向开发人员、操作人员和业务团队等提供持续的反馈。
观看我们最近的网络研讨会,了解如何将 Atlassian 云管道与 Octopus Deploy 集成。我们涵盖了这篇文章中的许多概念,所以请查看:
https://www.youtube.com/embed/yPjooXDJUA0
VIDEO
为什么您应该在部署后跟踪漏洞- Octopus Deploy
原文:https://octopus.com/blog/track-vulnerabilities-after-deployment
处理漏洞是软件开发中不幸但重要的一部分。有一些引人注目的例子强调了主动风险管理的重要性,证明了漏洞的责任不仅限于部署。
在这篇文章中,我探索了:
- 为什么您应该在部署软件后跟踪漏洞
- 跟踪漏洞的方法以及如何保护您的用户和企业的安全
在部署后发现漏洞
一个简单的事实是,软件提供商是在部署之后发现漏洞的,而不是之前。
这有两个语义原因:
- 如果在部署之前测试发现了潜在的漏洞,那么代码永远不会成为环境中的漏洞
- 一个未被检测到的、尚未部署的漏洞并不是真正的漏洞,除非它遇到一个有人可以利用它的环境
撇开语义不谈,知道测试不能捕捉到代码中的每个问题是至关重要的。不管你的内部测试有多严格,不管是自动化的还是其他的,问题都会悄悄出现,因为你并不总是知道要寻找什么。
因此,您不应该仅仅依赖内部部署前测试。如果你没有在部署后跟踪漏洞,那么你将会错过一些。
你想成为发现弱点的人
如果你发布了一个漏洞,最好在心怀不轨的人之前找到它。黑客在不受监控的应用程序和基础设施上茁壮成长,不检查漏洞让他们的生活更轻松。
未能检查、发现和解决漏洞会导致:
- 停工期
- 公司、员工和客户数据面临的风险
- 对贵公司声誉和收益的损害
你有责任保护你的用户的利益。部署后不跟踪漏洞会对您的用户造成伤害,使他们和您自己的商业利益面临风险。
你需要担心的不仅仅是那些心怀不轨的人。通常,安全研究人员和库作者会在代码依赖关系首次部署后很久才发现其中的漏洞。在发现之前经过了很长时间,这意味着其他人很有可能将漏洞包含在他们自己的软件中,并可能对其进行迭代。
这很好地把我们带到了下一点。
你不想意外地重复一个问题
DevOps 过程的短冲刺有助于限制迭代未发现问题的可能性。短距离冲刺还可以更容易地查明引入问题的更新。
如果您不跟踪部署后发生的事情,您会再次引入漏洞迭代的风险。这可能会使漏洞更难发现、故障排除和修复,以免为时过晚。
部署后检查漏洞可以节省您自己(和他人)的时间,并减轻未来的压力。
管理漏洞
现在你明白了为什么你应该在部署后跟踪漏洞,让我们看看如何。
这里有一些你可以做的积极的事情来帮助保护你的利益。
虽然您对自己产品的安全性负责,但是您所使用的工具和基础设施的提供商也是容易犯错的。即使是最大的软件和服务提供商,如微软,也需要修复漏洞。
作为另一个例子,亚马逊使用了共享责任模式。该模型概述了他们提供的 AWS 服务和他们的客户之间的期望。简单来说,AWS 负责云的安全,客户负责云中的安全。这意味着您要对在 AWS 基础设施上运行的操作系统和软件的安全性负责。
但是,不管您的提供商是谁,您都必须确保用于交付软件的产品是:
- 在贵公司政策允许的情况下尽可能保持最新
- 对存在紧急漏洞的地方进行修补和热修复
- 仍然遵守您对客户的承诺
您应该定期查看服务提供商的支持和安全页面,以获取保护您产品的更新信息。
跟踪行业安全新闻
在查看供应商的支持页面时,您还应该关注最新的网络安全新闻。
以下网站报告了新的漏洞和其他网络安全问题,以及安全管理建议:
使用漏洞扫描器
您还可以使用漏洞扫描器,它可以主动检查代码和工具中的已知漏洞。
代码漏洞扫描器示例:
基础设施漏洞扫描器示例:
结论
在这篇文章中,我探讨了为什么您应该在部署后检查漏洞,并研究了一些有助于管理漏洞的策略。
愉快的部署!
传统运行手册与 Octopus 运行手册- Octopus 部署
原文:https://octopus.com/blog/traditional-runbooks-vs-octopus-runbooks
我们谈论了很多关于 Octopus 如何能够完成您的持续集成和持续部署(CI/CD)管道,但是您知道 Octopus Deploy 的 Runbooks 功能也可以帮助您完成运营任务吗?
在这篇文章中,我们探索了作为 DevOps 一部分的 Octopus Runbooks 的好处,它们解决的问题,以及如何设置它们。
什么是 runbook?
简而言之,操作手册是完成常规或紧急计算机程序的一步一步的指南。
在过去的 IT 运营中,团队将这些指南整理成实体书籍或文件夹,仅在需要时才从书架上取下。如今,它们通常存在于共享网络驱动器、维基或知识库的文档中。
例如,假设一个应用服务器有一个由偶尔挂起的 Windows 服务引起的已知问题。支持团队可能有一本操作手册(或知识文章)来指导支持成员重新启动该服务。其他典型的操作手册包括:
- 数据库备份和恢复
- 服务器维护,如升级、修补或文件整理
- 服务器、服务或 web 应用程序重新启动
- 决策树的故障诊断步骤
无论是物理的还是其他的,手动操作手册都有一些问题,因为它们可能是:
- 耗时——手动完成指南和分支步骤需要时间,对于那些不熟悉系统的人来说更是如此。
- 容易出现人为错误——即使是最简单的指令,我们中最优秀的人也会犯错误。毕竟 pobody 的 nerfect!
- 过时——在繁忙的团队中,内部文档的维护经常会降低优先级。这使得操作手册过时或缺乏一致性。
- 难以管理的访问——您的运营团队可能需要访问无数的系统。风险自然会随着能接触到某样东西的人数的增加而增加,或者一个人接触到的东西越多。但是,您也不希望支持成员在真正需要时发现他们没有访问权限。
如果使用 Octopus Deploy,这些都是可以避免的问题。
八达通手册的好处
让我们来看看 Octopus 如何帮助解决传统 runbooks 的问题,以及其他一些好处。
运行手册自动化
作为一个产品,我们开发 Octopus 是因为我们相信可重复的部署意味着健壮的部署。章鱼手册遵循同样的信念。
这降低了人为错误的风险,并加快了流程。只需设置您的步骤一次,并触发一个点击操作手册。
章鱼手册已经了解你的环境
如果您是客户,您的 Octopus 实例已经连接到您部署的基础设施。您在 Octopus 中创建的任何 runbooks 也使用这些集成,因此没有什么新的操作任务需要设置。
Octopus 操作手册也不依赖于您环境的部署生命周期。您可以在需要时针对任何环境或部署目标运行它们。
Octopus Runbooks 有助于安全和节省网络管理
在我们之前的示例中,当支持人员需要在 Windows 服务器上重新启动服务时,您需要通过以下方式之一授予他们访问服务器的权限:
- 权限组
- 共享网络帐户
- 本地管理员权限
如果同一个支持团队负责许多系统(在运营中就是这种情况),那么弄清楚他们拥有或需要访问什么可能会令人困惑。
鉴于 Octopus 已经连接到您的部署目标,我们的操作手册意味着您不需要为操作任务分配对基础架构的直接访问。因此,如果随叫随到的支持人员需要重启某些东西,他们只需要访问 Octopus 和操作手册。章鱼管理其余的。
八达通有详细的审计跟踪
Octopus 还提供每个触发的运行手册的完整审计日志,因此您可以随时看到:
- 谁做了什么,什么时候,为什么
- 操作手册的成功和失败——知道什么时候以及为什么是时候更新你的操作手册了
- 事故响应报告所需的信息
如果您可以创建一个部署流程,那么您可以创建一个操作手册
创建部署和操作手册的过程是相似的。通过这两种方式,您可以使用预定义操作的组合或您喜欢的任何脚本语言来设置步骤。
让我们通过一个简单的操作手册来完成创建过程。
用 Octopus 创建一个简单的操作手册
这个例子向您展示了如何创建一个基本的 runbook,它不会影响您的任何项目或环境。如果你是现有用户,你可以跟进,或者通过注册免费试用。
如果您不想跟随,但仍然想看到最终结果,我们用这个 runbook 设置了一个示例实例,可供来宾访问。
本指南假设您已经:
本操作手册将:
- 运行“Hello World”脚本
- 当 runbook 成功完成时,通知松弛通道
请遵循以下步骤:
- 打开并登录 Octopus。
- 点击项目,从列表中选择一个已有的项目。
- 从左侧菜单中点击操作。
- 点击进入运行手册。
- 点击添加运行手册。
- 给你的新 runbook 起一个合适的名字和描述,然后点击保存。
- 点击定义您的 RUNBOOK 流程。
- 点击添加步骤。
- 选择脚本,悬停在上,从结果中运行脚本,点击添加。
- 更改以下设置,保留其他所有设置为默认设置,并点击保存:
* 步骤名称 -给步骤起一个描述性的名称。
* 执行位置——根据你的 Octopus 设置,选择在 Octopus 服务器上运行或者在 worker 上运行一次。
* 内联源代码——选择 PowerShell 单选按钮,并在代码框中输入以下内容:Write-Host 'Hello, World!'
- 现在,您可以添加在 runbook 成功完成时向 Slack 发送消息的步骤。再次点击添加步骤。
- 搜索
slack
,悬停在 Slack -从结果中发送简单通知并点击添加。如果提示将步骤保存到实例的模板中,单击保存。 - 完成以下设置,保留其他所有设置为默认,并点击保存:
* 步骤名称 -给步骤起一个描述性的名称。
* 执行位置 -根据您的 Octopus 设置,选择在 Octopus 服务器上运行或在 worker 上运行一次。
* Hook URL -复制你的 Slack Webhook URL。如果其他人可以看到你的 Octopus 实例或者你正在创建一个真正的 runbook,考虑把你的 webhook 作为一个项目变量添加进来。然后,您可以使用下面的语法#{variable-name}
安全地调用 webhook。
* 频道句柄 -输入您想要发布消息的确切空闲频道。
* 消息 -输入你希望 Octopus 发送给 Slack 的成功消息。 - 点击运行...测试运行手册。根据您的 Octopus 设置,您可能需要选择环境。如果是,选择任何环境(这并不重要,因为这个 runbook 不会改变任何东西)并再次点击运行。
- 等待运行手册完成,并检查您之前设置的消息的剩余时间。
当创建一本真正的 runbook 时,你必须点击发布以使它对其他团队成员和 Octopus 触发事件可用。
下一步是什么?
这只是一个尝试者,向你展示 Octopus Runbooks 的好处以及设置它们是多么容易。
在即将发布的系列文章中,我们将带您浏览一些真实的用例,包括如何使用 Octopus Runbooks:
- 应对漏洞
- 创建标准化的支持电子邮件
- 对您的基础设施进行冒烟测试
- 通过实例调度管理云成本
- 管理影子 IT 资源
- 计算 DORA 指标
- 用 Bash 脚本设置 Linux 服务器
同时,参见我们的 Octopus Runbook 文档获取更多的例子。
阅读我们的 Runbooks 系列的其余部分。
愉快的部署!
硒系列:特拉维斯 CI -章鱼部署
这篇文章是关于创建 Selenium WebDriver 测试框架的系列文章的一部分。
既然我们已经在一个公共的 GitHub 存储库中有了我们的代码,我们可以将它与 Travis CI 链接起来,以允许签入触发我们代码的构建和测试。
首先打开https://travis-ci.com/并点击Sign in with GitHub
按钮。
GitHub 会要求你授权 Travis CI。点击Authorize travis-pro
按钮。
你需要重新输入你的 GitHub 密码,然后点击Confirm password
按钮。
几秒钟后,您将被带到一个屏幕,在这里您可以激活 GitHub 集成。点击Active
按钮。
选择All repositories
选项,点击Approve & Install
按钮。
几秒钟后,您将看到之前创建的公共 GitHub 存储库。
单击存储库,进入构建列表。这个列表将是空的,因为我们还没有将所需的配置文件添加到存储库中,以允许 Travis CI 构建它。然而,我们现在已经成功地将 Travis CI 和 GitHub 链接在一起,这意味着 Travis CI 将监控 GitHub 存储库的变化。这是创建持续集成管道的第一步。
Travis CI 和 GitHub 现在链接在一起了,Travis CI 正在监控保存我们的 Java 应用程序的存储库的任何签入。签入将触发 Travis CI 构建我们的代码并运行我们的测试,但是为了让 Travis CI 知道如何构建我们的项目,我们需要向我们的存储库添加一个名为.travis.yml
的特殊文件。
.travis.yml
文件是 Travis CI 在其监控的任何存储库中查找的配置文件。该文件包含 Travis CI 构建代码和运行测试所需的配置。
Travis CI 在 Linux 或 MacOS 实例上执行构建。我们将使用 Linux 来进行构建,因为 Linux 有许多有用的工具,我们可以在测试中加以利用。
让我们来看看完整的.travis.yml
档案:
sudo: required
dist: trusty
language: java
jdk:
- oraclejdk8
addons:
firefox: "60.0"
before_install:
- sudo apt-get update
- sudo apt-get install dbus-x11
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
- export CHROME_BIN=/usr/bin/google-chrome
- sudo apt-get install -y libappindicator1 fonts-liberation
- wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
- sudo dpkg -i google-chrome*.deb
- wget https://chromedriver.storage.googleapis.com/2.38/chromedriver_linux64.zip
- unzip chromedriver_linux64.zip
- sudo cp chromedriver /usr/bin
- wget https://github.com/mozilla/geckodriver/releases/download/v0.20.1/geckodriver-v0.20.1-linux64.tar.gz
- tar -xzf geckodriver-v0.20.1-linux64.tar.gz
- sudo cp geckodriver /usr/bin
现在让我们来分解这个文件。
sudo
选项用于指示构建是否应该在可以运行sudo
命令的环境中进行。通过将该选项设置为required
,我们已经表明我们需要能够运行sudo
命令,这意味着 Travis CI 将在虚拟机内部运行该构建。如果我们将这个选项设置为false
,Travis CI 就会创建一个容器来运行构建。
容器比虚拟机快,但是因为我们需要在构建环境中安装一些额外的软件来支持运行 WebDriver 测试,所以我们必须使用虚拟机选项:
sudo: required
dist
选项配置我们的构建将运行的 Ubuntu 版本。Ubuntu 版本有一些押韵的名字,比如“精确的穿山甲”和“可靠的塔尔”。dist
选项接受这些版本的简写,这里我们已经表明我们希望使用可信的 Tahr 版本的 Ubuntu(也称为版本 14.04)。
dist: trusty
language
选项定义了存储库中代码的编程语言。我们用 Java 编写代码,所以我们将这个选项设置为java
:
language: java
jdk
选项配置用于构建代码的 JDK。您可以选择使用 OpenJDK,它是 Java 的开源实现,或者使用 Oracle JDK,它是 Oracle 提供的 Java 发行版。对于我们的代码来说,这两种选择都可以,但是我们将使用 Oracle JDK。
jdk:
- oraclejdk8
Travis CI 提供了许多常见的应用程序,这些应用程序可以通过addons
选项包含在构建环境中,Firefox 就是提供的应用程序之一。在这里,我们已经配置了要安装的 Firefox 60:
addons:
firefox: "60.0"
before_install
选项为我们提供了运行原始脚本命令的能力,以便在构建代码之前进一步定制我们的构建环境。该选项下的每个项目都作为单独的命令运行,很像脚本文件:
before_install:
apt-get
命令是在 Ubuntu 中安装包的方式。大多数 Linux 发行版都有庞大的软件库,可以用包管理器安装,Ubuntu 也不例外。像这样用一个命令就能下载、安装和更新软件的能力是 Linux 如此受开发人员欢迎的原因之一。
在我们安装任何额外的软件包之前,我们使用update
命令来刷新可用软件包的列表。这可确保我们在稍后调用apt-get
时安装任何应用程序的最新版本:
- sudo apt-get update
当从 Travis CI 环境中运行 Firefox 时,许多类似于(firefox:9067): GConf-WARNING **: Client failed to connect to the D-BUS daemon
的警告被添加到日志文件中。这些可以忽略,但是很烦。问题https://github.com/travis-ci/travis-ci/issues/8520,中指出的解决方案是安装dbus-x11
包:
- sudo apt-get install dbus-x11
接下来的两个命令配置并启动 Xvfb。
在以前的帖子中,我们讨论了一些系统是如何无头的,这仅仅意味着它们没有连接监视器。Travis CI 使用的构建环境就是一个无头环境的例子。
然而,在某些情况下,比如在 web 浏览器上运行自动化测试,拥有一个即使没有监视器也能运行桌面应用程序的环境是很有用的。Xvfb 是 X 虚拟帧缓冲区的缩写,它允许这样的桌面应用程序在无头环境中运行。Xvfb 在内存中创建一个虚拟监视器,桌面应用程序将自己“画”到这个虚拟监视器上。
Xvfb 中的 X 来自名称 X Window System,这是可以在 Travis CI 中运行的 Linux 版本所使用的窗口系统。
通过使用 Xvfb,我们可以测试没有在 headless 环境中运行的本机支持的浏览器,或者运行像 Chrome 和 Firefox 这样的老版本浏览器,这些浏览器最近才获得本机 headless 支持。
导出DISPLAY
环境变量将应用程序配置为将自己绘制到屏幕99
,这是 Xvfb 默认提供的屏幕:
- export DISPLAY=:99.0
然后我们手动启动xvbf
服务:
- sh -e /etc/init.d/xvfb start
导出CHROME_BIN
环境变量可以确保 Chrome 二进制驱动程序可以在测试中定位并启动 Chrome:
- export CHROME_BIN=/usr/bin/google-chrome
这两个命令安装 Chrome 所需的一些依赖项:
- sudo apt-get install -y libappindicator1 fonts-liberation
与 Firefox 不同,Chrome 在 Travis CI 中不作为插件提供,所以我们必须自己手动安装。这里我们用 wget(Linux 下下载文件的工具)下载 Ubuntu 的 Chrome 包,用dpkg
安装:
- wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
- sudo dpkg -i google-chrome*.deb
正如我们在本地将 Chrome 二进制驱动程序安装到PATH
上的一个目录中一样,我们为 Travis CI 构建环境做了同样的事情。在这里,我们下载 Chrome 二进制驱动程序,将其解压缩,并将可执行文件复制到/usr/bin
目录。/usr/bin
目录已经在PATH
上了,这意味着任何复制到那里的可执行文件都可供我们的代码运行:
- wget https://chromedriver.storage.googleapis.com/2.38/chromedriver_linux64.zip
- unzip chromedriver_linux64.zip
- sudo cp chromedriver /usr/bin
我们对 Firefox 二进制驱动程序做同样的事情:
- wget https://github.com/mozilla/geckodriver/releases/download/v0.20.1/geckodriver-v0.20.1-linux64.tar.gz
- tar -xzf geckodriver-v0.20.1-linux64.tar.gz
- sudo cp geckodriver /usr/bin
要创建.travis.yml
文件,右击项目根文件夹并选择新➜文件。
输入文件名并点击OK
按钮。
填充.travis.yml
文件并保存更改。
我们需要将变更推送到或者签入到远程存储库中。为此,右击项目根目录并选择 Git ➜提交目录。
输入提交消息,点击Commit
按钮旁边的下拉箭头,然后点击Commit and Push
。
点击Push
按钮,将变更登记到远程存储库中。
推送完成后,新文件将显示在 GitHub 存储库中。
更重要的是,Travis CI 已经检测到了对 GitHub 存储库的推送,并使用了.travis.yml
文件中的配置来构建项目。
Travis CI 认识到我们的项目是使用 Maven 构建的,因为 pom.xml 文件的存在。然后,它将通过运行以下命令自动安装 Maven 依赖项:
mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V
然后通过运行以下命令来运行测试:
mvn test -B
这一切都是自动发生的,无需任何额外的配置。这意味着当我们的代码被签入 GitHub 时,Travis CI 将获得代码的副本,并运行我们编写的所有测试。
然而有一个问题。如果我们查看日志文件的末尾,我们会看到一些测试失败了:
Results :
Tests in error:
browserStackAndroidTest(academy.learnprogramming.FormTest): Invalid username or password (WARNING: The server did not provide any stacktrace information)(..)
browserStackEdgeTest(academy.learnprogramming.FormTest): Invalid username or password (WARNING: The server did not provide any stacktrace information)(..)
Tests run: 19, Failures: 0, Errors: 2, Skipped: 1
这些测试失败了,因为 BrowserStack 测试要求将用户名和密码存储在环境变量中。幸运的是,Travis CI 提供了一种为构建定义环境变量的简单方法。
点击More Options
菜单,选择Settings
选项。
在环境变量下添加BROWSERSTACK_USERNAME
和BROWSERSTACK_KEY
的值。您可以禁用构建日志中的Display value
,这意味着这些值将从 Travis CI 生成的日志中隐藏。这是防止秘密值泄露到日志文件中的一个有用的方法。
单击左侧菜单中的 build,然后单击Restart build
。这将重新构建代码,但是这次使用新的环境变量。
这一次,构建和相关的测试成功完成。
您可能会看到如下日志消息:
GLib-GObject-CRITICAL **: g_object_ref: assertion 'object->ref_count > 0' failed
这些可以忽略,因为它们不影响测试的结果。
我们现在已经成功地将代码签入到 GitHub 中托管的中央 Git 存储库中,Travis CI 已经检测到新代码,并自动构建它并运行所有测试。这是持续集成的核心思想,这意味着每次新代码被检入时,它都会被我们的测试自动验证。
您可以在此查看同一项目的 Travis CI 构建。
这篇文章是关于创建 Selenium WebDriver 测试框架的系列文章的一部分。
尝试生章鱼-章鱼部署
卡拉马里&为你的 SSH 目标部署 Mono-free。
原始脚本
如果你已经阅读了一些我们很棒的文档,你可能会遇到下面这个简单的架构图。
这描述了一个标准的 Windows 安装,其中 Octopus 服务器使用 Calamari 通过安装的触手执行部署,触手提供通信通道。有了 SSH 部署,SSH 连接消除了对触手的需求,因此 Octopus 可以直接向远程目标上的 Calamari 发出命令
对于某些场景,这似乎还不够远。甚至运行 Calamari(及其核心依赖 mono )的需求有时也是用户没有或不想要的奢侈品。也许你正试图在无法安装 mono 的硬件负载平衡器或 IOT 设备上简单地执行一些脚本。也许 mono 在 80 年代被分回来的那个不知名的 Unix 发行版上不被支持,bash shell 也还没有被移植。出于这个原因,我们已经发布了一个隐藏的特性,叫做原始脚本,它通过一个特殊的项目变量,允许你的脚本步骤通过打开的 SSH 连接直接执行,而不需要 Octopus 进行任何额外的包装或引导。结合 3.7.12 中新的传输包步骤,您现在可以将您的应用程序部署到您的 Linux 服务器上,甚至不知道什么是 mono!
我们真的只是打开一个 SSH 连接,按原样传递您的脚本。
转移包裹
对于某些部署,您可能只是希望将包传输到远程目标,而不是实际解压缩。也许它需要通过脚本进一步处理或上传到另一个服务器。新的Transfer A Package
步骤的工作方式与普通的Deploy A Package
步骤非常相似,除了没有真正提取包内容,也没有任何额外的脚本可以作为该步骤的一部分运行。在正常部署(非原始)中使用时,获取阶段可以利用增量压缩和保留策略,然后将包从其暂存位置复制到您提供的目标路径。如果在原始脚本编写期间发生包传输,则上述采集优化都不会发生(因为它们是由 Calamari 执行的),而是将包从上传暂存位置移动到目的地。
设置它
为原始脚本设置您的项目只是配置一个标准的 SSH 目标,然后添加项目级变量OctopusUseRawScript
。这个标志只对项目中的包传输和脚本步骤有影响。
我们在文档中包含了一个指南,概述了部署和运行一个简单打包脚本的简短步骤,而无需在目标上做任何额外的工作。
关于原始脚本,有一件事怎么强调都不为过,那就是我们实际上只是打开了一个 SSH 连接,并按原样传递您的脚本。这意味着您需要了解与典型 SSH 部署的一些行为差异:
- 标准的 SSH 健康检查测试各种依赖项的存在,比如 Mono 和 Calamari。如果打算在原始脚本中专门使用端点,您可能希望配置它们的机器策略来仅执行连接性测试,以避免它们的健康检查失败。该设置现在从版本
3.10.1
开始可用。
-
如果您的默认 shell 不是 bash,那么您的脚本将不会在 bash 中运行。确保您相应地配置了您的登录 shell 或编写了您的脚本。
-
get_octopusvariable
函数在你的脚本中将不再可用,因为它是由一个 Calamari 引导程序脚本提供的。相反,您可以编写脚本来利用变量替换语法 (#{MyVariable}
),该语法将在传递给远程目标并被远程目标调用之前被替换。这显然排除了只能在远程目标上解析的环境变量。 -
set_outputvariable
和new_artifact
功能也不可用。因为 Octopus Deploy 仍然会监听和响应这些服务消息,所以您可以自己编写这些服务消息来解决这个问题。查看 Calamari 源代码中的引导脚本以获取详细信息。
生吃
最终,这个特性为那些无法安装 Octopus 在 SSH 端点上运行 Calamari 所需的元素的用户提供了一种机制。虽然我们可以为 Windows 机器上的原始脚本提供额外的支持(默认情况下在 PowerShell 会话中运行,sans-Calamari ),但我们认为这是一个不太可能需要的场景,因为存在安装和运行整个触手服务的不可避免的需求。对于那些有这种需求的人(或者只是想在 SSH 上获得更简洁、更快速的部署体验),我们鼓励您尝试一下,并让我们知道您认为哪里有改进的空间。
浏览器 UI 测试的注意事项- Octopus Deploy
通过网络浏览器模拟用户交互的端到端测试已经变得越来越普遍。WebDriver 现在是一个开放的 W3 标准,所有主流的 web 浏览器都提供了 WebDriver 接口。最近的项目如 Cypress ,已经实现了他们自己的与浏览器交互的方法,专门用于编写测试。有了这样一系列得到良好支持的成熟测试平台,编写基于浏览器的测试变得前所未有的简单。
但是,在自动化 CI/CD 环境中运行基于浏览器的测试时,有一些注意事项需要考虑。在这篇博文中,我们将探讨如何运行这些测试以及测试环境的局限性。
Windows 交互式服务
在我们开始使用 Windows 进行交互式测试之前,有必要了解一下交互式服务的历史。
如果您曾经配置过 Windows 服务,您可能已经注意到了Allow service to interact with the desktop
选项:
回到 Windows XP,该选项允许 Windows 服务在第一个登录用户的桌面上绘图,该用户被分配了会话 0。从安全角度来看,这并不理想,导致了像粉碎攻击这样的攻击。
为了解决此漏洞,Windows Vista 引入了一项更改,即在会话 0 中运行服务,在会话 1 及更高版本中运行用户。这意味着用户不再暴露于服务所绘制的任何界面,这为期望用户与对话框提示交互的服务带来了向后兼容性问题。
创建 Interactive Services 检测服务是为了方便与这些提示进行交互,显示如下消息:
【T2
也可以用命令rundll32 winsta.dll,WinStationSwitchToServicesSession
切换到会话 0。
然而,最新版本的 Windows 10 已经移除了互动服务检测服务并禁止任何对会话 0 的访问。这意味着用户现在完全脱离了交互式服务,没有第三方解决方案。
此外,允许服务与桌面交互的选项在默认情况下通过NoInteractiveServices
注册表键被禁用。
底线是,虽然微软从未正式表示运行交互式服务不受支持或不被重视,但所有影响交互式服务的 Windows 更改都是为了限制或禁用它们的使用。即使您今天设法实现了一个交互式服务,假设它在将来会继续工作也是不明智的。
拯救无头浏览器
PhantomJS 推广了无头浏览器的概念。这使得自动化测试可以在非交互式环境中运行。
此后,Chrome/Chromium 和 Firefox 增加了对无头模式的支持。随着主流浏览器中的无头支持,PhantomJS 变得多余,并且不再被维护。
尽管如此,仍有许多浏览器,如 Internet Explorer 和 Edge,不支持(也可能永远不会支持)无头模式。headless 模式也没有提供测试桌面应用程序用户界面或捕获屏幕记录的解决方案。那么,当你需要一个真正的桌面来进行测试时,有什么选择呢?
Windows 交互式代理
对于 Windows,我们可以依靠 Azure DevOps 交互代理来指导微软如何支持在真实桌面上运行测试。微软的解决方案是给 Windows 机器配置自动登录,然后在启动时运行代理启动 UI 测试。这样,被测试的应用程序出现在普通用户的桌面上。
但是这个解决方案并非没有风险:
当您启用自动登录或禁用屏幕保护程序时,会有安全风险,因为您允许其他用户走近计算机并使用自动登录的帐户。如果将代理配置为以这种方式运行,则必须确保计算机受到物理保护;例如,位于安全设施中。
Octopus 的一个类似解决方案是在启动时用下面的命令运行触手(用本地触手的名称替换instance
名称):
"C:\Program Files\Octopus Deploy\Tentacle\Tentacle.exe" run --instance="Tentacle"
Linux 无头桌面
使用 xvfb ,Linux 用户可以更加灵活地在无头环境中运行应用程序。Xvfb 使用虚拟内存模拟一个哑帧缓冲区,这意味着所有应用程序都被渲染到内存中,而不需要任何显示硬件。
我们已经使用 xvfb 通过配置一个完整的 Linux 桌面环境来创建 Octopus Guides ,浏览器在这个环境中启动。这个 webdriver 脚本展示了 xvfb 是如何启动的,并且定义了DISPLAY
环境变量来实现这一点。
场外测试
有许多像 Browserstack 或 Sause Labs 这样的服务提供自动化测试服务。通过控制远程浏览器,您不再负责配置底层操作系统,大多数浏览器将提供视频录制等功能。
结论
在远程系统上运行自动化测试时,无头浏览器应该是您的首选。Linux 用户可以利用 xvfb 在无头桌面上运行应用程序。但是,如果你需要一个真正的桌面来测试 Windows 中的应用程序,启用自动登录并在启动时运行代理或触角是最好的解决方案。
使用交互式服务你可能会有一些运气,但这种选择是脆弱的,因为微软在每次更新 Windows 时都会限制和禁用交互式服务。
Kubernetes 微服务部署终极指南- Octopus Deploy
原文:https://octopus.com/blog/ultimate-guide-to-k8s-microservice-deployments
对于希望快速可靠地发布复杂系统的团队来说,微服务已经成为一种流行的开发实践。Kubernetes 是微服务的天然平台,因为它可以处理部署许多单个微服务的许多实例所需的编排。此外,服务网格技术将常见的网络问题从应用层提升到基础设施层,使路由、保护、记录和测试网络流量变得更加容易。
使用微服务、Kubernetes 和服务网格技术创建持续集成和持续交付(CI/CD)管道需要一些工作,因为健壮的 CI/CD 管道必须解决许多问题:
- 高可用性(HA)
- 多重环境
- 零停机部署
- HTTPS 和证书管理
- 功能分支部署
- 烟雾测试
- 回滚策略
在本文中,我将介绍如何通过将 Google 创建的名为 Online Boutique 的示例微服务应用程序部署到亚马逊 EKS Kubernetes 集群,配置 Istio 服务网格以处理网络路由,并深入 HTTP 和 gRPC 网络以通过集群路由租用的网络流量来测试功能分支,从而创建 CI/CD 管道的持续交付(或部署)部分。
创建 EKS 集群
即使我将微服务应用程序部署到亚马逊 EKS 托管的 Kubernetes 集群,我也不依赖 EKS 提供的任何特殊功能,因此任何 Kubernetes 集群都可以用于遵循该流程。
开始使用 EKS 最简单的方法是使用 ekscli 工具。这个 CLI 工具抽象出了与创建和管理 EKS 集群相关的大部分细节,并提供了合理的默认值来帮助您快速入门。
创建 AWS 帐户
Octopus 对通过 AWS 帐户向 EKS 集群进行身份验证提供了本机支持。该账户在基础设施➜账户下定义。
AWS 账户
创建 Kubernetes 目标
通过导航到基础设施➜部署目标,在 Octopus 中创建 Kubernetes 目标。在部署目标屏幕上,选择身份验证部分中的 AWS 帐户选项,并添加 EKS 集群的名称。
Kubernetes 目标根据 AWS 帐户进行身份验证。
在 Kubernetes Details 部分,添加 EKS 集群的 URL,并选择集群证书或选中 Skip TLS 验证选项。
该目标运行的默认名称空间在 Kubernetes 名称空间字段中定义。
部署过程中的每一步都可以覆盖名称空间,因此可以将该字段留空,并在多个名称空间中重用一个目标。但是,当在单个集群中共享多个环境时,最好设置默认的名称空间。
EKS 星团详情。
执行这些步骤的 Octopus 服务器或工作者必须在路径上有可用的kubectl
和 AWS aws-iam-authenticator
可执行文件。更多细节见文件。
安装 Istio
Istio 提供了许多安装选项,但我发现istioctl
工具是最简单的。
从 GitHub 下载一份istioctl
的副本。文件名将类似于istioctl-1.5.2-win.zip
,我们将其重命名为istioctl.1.5.2.zip
,然后上传到 Octopus。将可执行文件放在 Octopus 内置提要中,可以在脚本步骤中使用它。
向 runbook 添加一个运行 kubectl CLI 脚本步骤,并引用istioctl
包:
参考 Istio CLI 工具的附加包。
在脚本体中,执行如下所示的istioctl
,将 Istio 安装到 EKS 集群中。您可以从 Istio 文档中找到关于这些命令的更多信息。然后将istio-injection
标签添加到包含我们的应用程序的名称空间中,以启用自动 Istio sidecar 注入:
istioctl/istioctl manifest apply --skip-confirmation
kubectl label namespace dev istio-injection=enabled
您必须使用--skip-confirmation
参数来防止istioctl
永远等待工具通过 Octopus 运行时无法提供的输入。
安装 Istio 并在名称空间中启用自动边车注入的脚本。
创建 Docker 提要
构成我们的微服务应用程序的 Docker 映像将托管在 Docker Hub 中。Google 确实提供了来自他们自己的 Google Container Registry 的图像,但是这些图像没有 SemVer 兼容标签,Octopus 需要这些标签在发布创建期间对图像进行排序。我们还将创建一些特性分支映像来部署到集群,因此需要一个我们可以发布到的公共存储库。在下面的截图中,您可以看到在 Library ➜外部提要下创建的一个新的 Docker 提要:
Docker Hub Docker feed。
部署微服务
在线精品示例应用程序提供了一个 Kubernetes YAML 文件,该文件定义了运行应用程序所需的所有部署和服务。
每项服务都将作为单独的项目部署在 Octopus 中。微服务的优势之一是每个服务都有独立的生命周期,允许独立于任何其他服务进行测试和部署。为每个微服务创建单独的 Octopus 项目允许我们为该服务创建和部署版本。
YAML 文件中提到的第一个微服务叫做emailservice
。我们将在一个名为01\. Online Boutique - Email service
的项目中部署emailservice
。这个微服务有两个 Kubernetes 资源:一个部署和一个服务。这些资源的 YAML 如下所示:
apiVersion: apps/v1
kind: Deployment
metadata:
name: emailservice
spec:
selector:
matchLabels:
app: emailservice
template:
metadata:
labels:
app: emailservice
spec:
terminationGracePeriodSeconds: 5
containers:
- name: server
image: gcr.io/google-samples/microservices-demo/emailservice:v0.2.0
ports:
- containerPort: 8080
env:
- name: PORT
value: "8080"
# - name: DISABLE_TRACING
# value: "1"
- name: DISABLE_PROFILER
value: "1"
readinessProbe:
periodSeconds: 5
exec:
command: ["/bin/grpc_health_probe", "-addr=:8080"]
livenessProbe:
periodSeconds: 5
exec:
command: ["/bin/grpc_health_probe", "-addr=:8080"]
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
---
apiVersion: v1
kind: Service
metadata:
name: emailservice
spec:
type: ClusterIP
selector:
app: emailservice
ports:
- name: grpc
port: 5000
targetPort: 8080
部署和服务资源的这种配对是我们将在 YAML 文件中看到的重复模式。部署用于部署和管理实现微服务的容器。服务资源向其他微服务和前端应用程序公开这些容器,前端应用程序向最终用户公开微服务。
通过部署 Kubernetes 容器步骤,在 Octopus 中公开了组合公共 Kubernetes 资源的模式。这个固执己见的步骤为 Kubernetes 部署、服务、入口、秘密和配置映射提供了丰富的用户界面,使这个步骤成为部署我们在线精品微服务的自然选择。
从历史上看,使用部署 Kubernetes 容器步骤的一个缺点是将现有 YAML 文件中的属性翻译到用户界面中所花费的时间。每个设置都必须手动复制,这是一项艰巨的任务。
Octopus 2020.2.4 中最近添加的一个功能允许您直接编辑由该步骤生成的 YAML。单击每个 Kubernetes 资源的编辑 YAML 按钮,在一个操作中将 YAML 复制到步骤中:
编辑 YAML 按钮导入和导出 YAML。
在下面的截图中,您可以看到我粘贴了组成emailservice
部署资源的 YAML:
从微服务项目导入 YAML。
导入所提供的 YAML 中与表单公开的字段匹配的任何属性。在下面的屏幕截图中,您可以看到server
容器已经导入,包括环境设置、健康检查、资源限制和端口:
从导入的 YAML 得到的容器定义。
并不是每个可能的部署属性都被Deploy Kubernetes containers步骤所识别,未被识别的属性在导入过程中会被忽略。Deploy raw Kubernetes YAML
步骤提供了一种将通用 YAML 部署到 Kubernetes 集群的方法。然而,组成在线精品示例应用程序的微服务所使用的所有属性都是由部署 Kubernetes 容器步骤公开的。
接下来,我将服务 YAML 导入到步骤的服务部分:
服务区编辑 YAML 按钮。
导入 YAML 定义的服务资源。
服务将流量定向到与在selector
属性下定义的标签相匹配的 pod。Deploy Kubernetes containers步骤忽略了导入的服务 YAML 中的selector
属性,取而代之的是,假设部署中的 pod 都将由服务公开。以这种方式耦合部署和服务是由部署 Kubernetes 容器步骤实施的观点之一。
我们的微服务不会部署入口、秘密或配置映射资源,因此我们可以通过单击配置功能按钮从步骤中删除这些功能,并删除对未使用功能的检查:
删除未使用的配置特征。
最后一步是引用我们构建并上传到 Docker Hub 的容器。导入过程引用了部署 YAML 中定义的容器microservices-demo/emailservice
。我们需要将其更改为octopussamples/microservicedemo-emailservice
,以引用由octopus samplesDocker Hub 用户上传的容器:
更新 Docker 图像。
据此,我们创建了一个部署emailservice
的 Octopus 项目。emailservice
是组成在线精品示例应用程序的 11 个微服务之一,其他微服务称为:
checkoutservice
recommendationservice
frontend
paymentservice
productcatalogservice
cartservice
loadgenerator
currencyservice
shippingservice
redis-cart
adservice
这些微服务中的大多数都部署了与我们在emailservice
中看到的相同的部署和服务对。例外情况是loadgenerator
,它没有服务,以及frontend
,它包括一个额外的负载平衡器服务,将微服务暴露给公共流量。
额外的负载平衡器服务可以通过部署 Kubernetes 服务资源步骤进行部署。这个独立的步骤具有与部署 Kubernetes 容器步骤中相同的编辑 YAML 按钮,因此可以直接导入frontend-external
服务 YAML:
导入独立服务 YAML 定义。
与部署 Kubernetes 容器步骤不同,独立的部署 Kubernetes 服务资源步骤与部署无关,因此它导入并公开标签选择器来定义流量被发送到的 pod。
高可用性
由于 Kubernetes 负责集群中的配置单元,并内置了对单元和节点健康状况的跟踪支持,我们获得了开箱即用的合理程度的高可用性。此外,我们通常可以依靠云提供商来监控节点运行状况,重新创建故障节点,并跨可用性区域对节点进行物理配置,以降低停机的影响。
然而,我们导入的部署的默认设置需要一些调整,以使它们更有弹性。
首先,我们需要增加部署副本数量,这决定了一个部署将创建多少个单元。默认值为 1,表示任何一个 pod 出现故障都会导致微服务不可用。增加这个值意味着我们的应用程序可以承受单个 pod 的丢失。在下面的屏幕截图中,您可以看到我已经将广告服务的副本数量增加到了 2:
增加 pod 副本数量。
拥有两个 pod 是一个好的开始,但是如果这两个 pod 都是在单个节点上创建的,我们仍然会有单点故障。为了解决这个问题,我们在 Kubernetes 中使用了一个名为 pod anti-affinity 的功能。这允许我们指示 Kubernetes 更喜欢将某些 pod 部署在单独的节点上。
在下面的截图中,您可以看到我创建了一个首选的反相似性规则,该规则指示 Kubernetes 尝试将带有标签app
和值adservice
(这是该部署分配给 pod 的一个标签)的 pod 放置在不同的节点上。
拓扑关键字是指定给节点的标签名称,用于定义节点所属的拓扑组。在更复杂的部署中,拓扑关键字将用于指示细节,如节点所在的物理区域或网络注意事项。然而,在这个例子中,我们选择了一个标签来唯一地标识每个节点,这个标签叫做alpha.eksctl.io/instance-id
,有效地创建了只包含一个节点的拓扑。
最终结果是,Kubernetes 将尝试将属于同一部署的 pod 放在不同的节点上,这意味着我们的集群更有可能在失去单个节点的情况下不受影响:
定义 pod 反亲和性。
零停机部署
Kubernetes 中的部署资源为部署更新提供了两种内置策略。
首先是再造策略。该策略首先删除任何现有的 pod,然后再部署新的 pod。recreate 策略消除了两个 pod 版本共存的需要,这在诸如不兼容的数据库更改被合并到新版本中的情况下可能很重要。然而,当旧的吊舱被关闭时,它确实引入了停机时间,该停机时间持续到新的吊舱完全可操作为止。
第二种也是默认的策略是滚动更新策略。这种策略以增量方式用新的 pods 替换旧的 pods,并且可以以这样一种方式进行配置,以确保在更新期间总是有健康的 pods 可用于服务流量。滚动更新策略意味着新旧 pod 在短时间内并行运行,因此您必须特别注意确保客户端和数据存储能够支持两种 pod 版本。这种方法的好处是没有停机时间,因为一些 pod 仍然可以用来处理任何请求。
Octopus 引入了第三种部署策略,称为蓝/绿。蓝/绿策略是通过创建一个不同的新部署资源来实现的,也就是说,每个部署都有一个具有唯一名称的新部署资源。如果在Deploy Kubernetes containers步骤中定义了一个 configmap 或 secret,那么也会创建这些资源的不同的新实例。在新部署成功且所有运行状况检查都通过后,服务将更新,以将流量从旧部署切换到新部署。这允许在没有停机时间的情况下进行完全切换,并确保流量仅发送到旧的 pod 或新的 pod。
选择滚动或蓝/绿部署策略意味着我们可以零停机部署微服务:
【T2
启用滚动更新。
真正的零停机部署需要一些额外的工作,才能在更新过程中不丢失任何请求。博客文章用 Kubernetes 实现零停机滚动更新提供了一些在更新期间最小化网络中断的技巧。
功能分支部署
对于单一应用程序,功能分支部署通常是直接的;整个应用程序被构建、捆绑和部署为单个工件,并且可能由特定的数据库实例提供支持。
微服务呈现了一个非常不同的场景。当一个微服务的所有上游和下游依赖项都可以用来处理请求时,这个微服务才可能以一种有意义的方式运行。
博客文章为什么我们在优步的微服务架构中利用多租户讨论了在微服务架构中执行集成测试的两种方法:
- 平行测试
- 生产中的测试
这篇博文详细介绍了这两种策略的实施,但总结起来:
- 并行测试包括在一个共享的临时环境中测试微服务,该环境的配置类似于生产环境,但与生产环境相隔离。
- 生产中的测试包括将测试中的微服务部署到生产中,使用安全策略将其隔离,将所有静态数据分类为测试或生产数据,并将不同的流量子集定向到测试微服务。
这篇博客文章继续提倡在生产中进行测试,列举了并行测试的这些局限性:
- 额外硬件成本
- 同步问题(或试运行和生产环境之间的偏差)
- 不可靠的测试
- 不准确的容量测试
很少有开发团队会像优步那样接受微服务,所以我怀疑对于大多数团队来说,部署微服务功能分支将涉及到介于优步描述的并行测试和生产测试之间的解决方案。具体来说,在这里我们将了解如何在暂存环境中将微服务功能分支与现有版本一起部署,并将一部分流量定向到该分支。
通过利用硬件和网络隔离,将功能分支部署到临时环境中消除了干扰生产服务的风险,从而消除了在生产环境中实施这种隔离的需要。它还消除了划分或识别测试和生产数据的需要,因为在试运行环境中创建的所有数据都是测试数据。
在开始之前,我们需要简要回顾一下什么是服务网格,以及我们如何利用 Istio 服务网格来独立于微服务引导流量。
什么是服务网格?
在服务网格出现之前,网络功能很像老式的电话交换机。应用程序类似于个人打电话;他们知道他们需要与谁通信,像 NGINX 这样的反向代理将作为总机操作员来连接双方。只要交易中的所有各方都是众所周知的,并且他们交流的方式是相对静态和非专业化的,这种基础设施就可以工作。
微服务类似于手机的兴起。有更多的设备需要连接在一起,以不可预测的方式在网络中漫游,每个设备通常都需要自己独特的配置。
服务网格旨在适应大量服务相互通信的日益复杂和动态的需求。在服务网格中,每个服务负责定义它将如何接受请求;它需要哪些常见的网络功能,如重试、断路、重定向和重写。这避免了所有流量都必须通过的中央交换机,并且在大多数情况下,单个应用程序不需要知道它们的网络请求是如何被处理的。
服务网格是提供大量功能的丰富平台,但为了部署微服务功能分支,我们最感兴趣的是检查和路由网络流量的能力。
我们在路由什么流量?
下面是架构图,显示了组成在线精品的各种微服务,以及它们如何通信:
微服务应用架构。
请注意,在此图中,来自互联网的公共流量通过前端进入应用程序。这个流量是普通的 HTTP。
微服务之间的通信然后通过 gRPC 执行,它是:
一个高性能、开源的通用 RPC 框架
重要的是,gRPC 使用 HTTP2。因此,要将流量路由到微服务功能分支部署,我们需要检查和路由 HTTP 流量。
路由 HTTP 流量
Istio HTTPMatchRequest 定义了可用于匹配 HTTP 流量的请求的属性。这些属性相当全面,包括 URI、方案、方法、头、查询参数、端口等等。
为了将流量子集路由到功能分支部署,我们需要能够以不干扰请求中包含的数据的方式将附加信息作为 HTTP 请求的一部分进行传播。方案(即 HTTP 或 HTTPS)、方法(GET、PUT、POST 等)。)、端口、查询参数(问号后面的 URI 部分)和 URI 本身都包含特定于所做请求的信息,不能修改这些信息。这样就剩下头了,头是键值对,经常被修改以支持请求跟踪、代理和其他元数据。
查看浏览器在与在线精品前端交互时提交的网络流量,我们可以看到,Cookie
报头可能包含一个有用的值,我们可以检查该值以做出路由决策。该应用程序保存了一个 cookie,它带有一个标识浏览器会话的 GUID,事实证明,这就是该示例应用程序用来标识用户的方法。显然,在现实世界的例子中,身份验证服务用于识别用户,但是对于我们的目的,随机生成的 GUID 就足够了。
从浏览器捕获网络流量。
有了 HTTP 头,我们可以检查和路由。下一步是部署特性分支。
创建特征分支 Docker 图像
在线精品已经用多种语言编写,前端组件用 Go 编写。我们将对标题模板做一个小小的修改,以包含文本 MyFeature ,使其清楚地表示我们的特性分支。
我们将从这个分支构建一个 Docker 映像,并将其发布为octopussamples/microservicedemo-frontend:0.1.4-myfeature
。注意,0.1.4-myfeature
的标签是一个 SemVer 字符串,它允许这个图像被用作 Octopus 部署的一部分。
部署第一个功能分支
我们在部署前端应用程序的 Octopus 项目中定义了两个通道。
默认通道有一个版本规则,要求 SemVer 预发布标签为空,正则表达式为^$
。这个规则确保这个频道只匹配版本(或者在我们的例子中是 Docker 标签),比如0.1.4
。
特征分支通道有一个版本规则,要求 SemVer 预发布标签不为空,正则表达式为.+
。这个频道会匹配0.1.4-myfeature
这样的版本。
然后,我们向部署添加一个名为FeatureBranch
的变量,其值为#{Octopus.Action.Package[server].PackageVersion | Replace "^([0-9\.]+)((?:-[A-Za-z0-9]+)?)(.*)$" "$2"}
。该变量获取名为server
的 Docker 图像版本,在正则表达式中捕获预发布和前导破折号作为 group 2,然后只打印 group 2。如果没有预发布,变量将解析为空字符串。
用于提取 SemVer 预发布的变量。
然后,将该变量附加到部署名称、部署标签和服务名称之后。更改部署和服务的名称可以确保特性分支部署在名为frontend
的现有资源旁边创建名为frontend-myfeature
的新资源:
摘要文本显示部署的名称和标签
摘要文本显示服务的名称
通过 Istio 暴露前端
到目前为止,我们还没有部署任何 Istio 资源。包含我们的应用程序的名称空间上的istio-injection
标签意味着由我们的部署创建的 pod 包括 Istio sidecar,准备拦截和路由流量,但正是普通的旧 Kubernetes 服务将我们的 pod 相互公开。
为了开始使用 Istio 路由我们的内部流量,我们需要创建一个虚拟服务:
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: frontend
spec:
gateways:
- istio-system/ingressgateway
hosts:
- '*'
http:
- match:
- headers:
Cookie:
exact: shop_session-id=4f9e715d-fe56-4a1e-964b-b00b607e7695
route:
- destination:
host: frontend-myfeature
- route:
- destination:
host: frontend
这项虚拟服务有几个重要部分:
gateway
被设置为istio-system/ingressgateway
,这是安装 Istio 时创建的网关。该网关依次接受来自负载平衡器服务的流量,该服务也是在istio-system
名称空间中创建的,这意味着要访问我们的应用程序并通过该虚拟服务路由流量,我们需要通过 Istio 负载平衡器服务的公共主机名来访问应用程序。http
属性下的第一项指定其Cookie
报头与指定值匹配的传入流量将被定向到frontend-myfeature
服务。- 任何其他流量都被发送到
frontend
服务。
有了这些规则,我们可以重新打开应用程序,我们的请求被重定向到 feature 分支,如标题所示:
Istio 检查了 Cookie 头并将请求定向到特性分支。
然而,这种重定向只是挑战的一半。我们已经成功检查了应用程序已经添加的 HTTP 头,并将 web 流量定向到面向公众的前端应用程序的功能分支。但是重定向内部 gRPC 调用呢?
gRPC 路由
就像普通的 HTTP 请求一样,gRPC 请求也可以公开用于路由的 HTTP 头。任何与 gRPC 请求相关联的元数据都被公开为 HTTP 头,然后可以被 Istio 检查。
前端应用程序向许多其他微服务发出 gRPC 请求,包括广告服务。为了启用 ad 服务的特性分支部署,我们需要将用户 ID (实际上只是会话 ID,但是我们将这两个值视为同一事物)与 gRPC 请求一起从前端传播到 ad 服务。
为此,我们向getAd()
方法添加一个名为userID
的属性,创建一个名为metactx
的新上下文,通过userid
元数据属性公开用户 ID,并使用上下文metactx
发出 gRPC 请求:
func (fe *frontendServer) getAd(ctx context.Context, userID string, ctxKeys []string) ([]*pb.Ad, error) {
ctx, cancel := context.WithTimeout(ctx, time.Millisecond*100)
defer cancel()
// This is where we add the metadata, which in turn is exposed as HTTP headers
metactx := metadata.AppendToOutgoingContext(ctx, "userid", userID)
resp, err := pb.NewAdServiceClient(fe.adSvcConn).GetAds(metactx, &pb.AdRequest{
ContextKeys: ctxKeys,
})
return resp.GetAds(), errors.Wrap(err, "failed to get ads")
}
元数据包导入时带有:
metadata "google.golang.org/grpc/metadata"
注意,调用getAd()
函数的函数链也必须更新以传递新的参数,在顶层,通过调用sessionID(r)
找到用户 ID,其中r
是 HTTP 请求对象。
我们现在可以创建一个虚拟服务,根据userid
HTTP 头将前端应用程序发出的请求路由到广告服务,这就是 gRPC 元数据键值对的公开方式:
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: adservice
spec:
hosts:
- adservice
http:
- match:
- headers:
userid:
exact: 4f9e715d-fe56-4a1e-964b-b00b607e7695
route:
- destination:
host: adservice-myfeature
- route:
- destination:
host: adservice
手动编辑虚拟服务可能很繁琐,随着更多分支的部署或更多测试用户的配置,我们将需要一个更加自动化的解决方案来编辑虚拟服务。
下面是一个 PowerShell 脚本,它读取当前虚拟服务,为给定用户添加或替换重定向规则,并将更改保存回 Kubernetes。该代码可以保存为操作手册(因为流量切换是一个独立于部署的过程),并且变量SessionId
和Service
可以在每次运行之前被提示:
# Get the current virtual service
$virtService = kubectl get virtualservice adservice -o json | ConvertFrom-JSON
# Clean up the generated metadata properties, as we do not want to send these back
$virtService.metadata = $virtService.metadata |
Select-Object * -ExcludeProperty uid, selfLink, resourceVersion, generation, creationTimestamp, annotations
# Get the routes not associated with the new session id
$otherMatch = $virtService.spec.http |
? {$_.match.headers.userid.exact -ne "#{SessionId}"}
# Create a new route for the session
$thisMatch = @(
@{
match = @(
@{
headers = @{
userid = @{
exact = "#{SessionId}"
}
}
}
)
route = @(
@{
destination = @{
host = "#{Service}"
}
}
)
}
)
# Append the other routes
$thisMatch += $otherMatch
# Apply the new route collection
$virtService.spec.http = $thisMatch
# Save the virtual service to disk
$virtService | ConvertTo-JSON -Depth 100 | Set-Content -Path vs.json
# Print the contents of the file
Get-Content vs.json
# Send the new virtual service back to Kubernetes
kubectl apply -f vs.json
一个更健壮的解决方案可能包括编写一个定制的 Kubernetes 操作符来保持虚拟服务资源与定义测试流量的外部数据源同步。这篇文章不会深入讨论操作符的细节,但是你可以从文章中找到更多的信息,用 Kotlin 创建一个 Kubernetes 操作符。
部署内部功能分支
就像我们对前端应用程序所做的一样,广告服务的一个分支已经被创建并作为octopussamples/microservicedemo-adservice:0.1.4-myfeature
被推送。Octopus 中的广告服务项目获得了设置为#{Octopus.Action.Package[server].PackageVersion | Replace "^([0-9\.]+)((?:-[A-Za-z0-9]+)?)(.*)$" "$2"}
的新的FeatureBranch
变量,部署和服务的名称也更改为adservice#{FeatureBranch}
。
特性分支本身被更新为将字符串MyFeature
附加到由服务提供的广告上,以允许我们看到特性分支部署何时被调用。部署虚拟服务后,我们再次打开 web 应用程序,发现我们收到的广告确实包含字符串MyFeature
:
Istio 根据 userid 报头将内部 gRPC 请求路由到广告服务功能分支。
摘要
为了在微服务环境中部署用于集成测试的功能分支,在不干扰其他流量的情况下测试特定请求是有用的。通过在 HTTP 头和 gRPC 元数据中公开路由信息(进而公开为 HTTP 头),Istio 可以将特定流量路由到功能分支部署,而所有其他流量则通过常规主线微服务部署。
这可能足以在测试环境中部署微服务功能分支。如果您的特性分支发生故障,并在测试数据库中保存无效数据,这是不方便的,但不会导致生产中断。同样,将无效消息放在消息队列中可能会破坏测试环境,但是您的生产环境是隔离的。
优步的博客文章提供了一个诱人的视角,展示了如何将部署特性分支的想法扩展到生产环境中进行测试。但是,请注意,这篇文章明确指出,租用信息必须随所有请求一起传播,与所有静态数据一起保存,并且可选地将测试数据隔离在单独的数据库和消息队列中。此外,安全策略需要到位,以确保测试微服务不会失控,不会与它们不应该交互的服务进行交互。这篇博文不会涉及这些额外的需求,但是部署和与微服务特性分支交互的能力是一个很好的基础。
HTTPS 和证书管理
为了允许通过 HTTPS 安全地访问我们的应用程序,我们将配置秘密发现服务。
第一步是部署入口网关代理,这是通过使用istioctl
工具生成 YAML 文档并应用它来实现的。正如我们在安装 Istio 本身时所做的那样,istioctl
包引用被添加到作为 runbook 的一部分运行 kubectl CLI 脚本的运行步骤中。部署代理的脚本如下所示:
istioctl\istioctl manifest generate `
--set values.gateways.istio-egressgateway.enabled=false `
--set values.gateways.istio-ingressgateway.sds.enabled=true > `
istio-ingressgateway.yaml
kubectl apply -f istio-ingressgateway.yaml
使用 istioctl 启用代理。
下一步是将 HTTPS 证书和私钥的内容保存为秘密。在这篇文章中,我使用了由 Let's Encrypt 通过我的 DNS 提供商 dnsimple 生成的证书,并下载了 PFX 证书包。该软件包在将证书部署到 IIS 的说明下提供,但是 PFX 文件本身是通用的。我们使用 PFX 文件,因为它是独立的,很容易上传到 Octopus。
让我们加密由 DNS 提供商生成的证书。
PFX 文件作为新证书上传到库➜证书下:
把咱们的加密证书上传到八达通。
要将证书导入 Kubernetes,我们需要创建一个秘密。我们从引用名为Certificate
的变量中的证书开始:
作为变量引用的证书。
然后,证书的内容被保存到两个文件中,一个保存证书,另一个保存私钥。证书变量在 Octopus 中很特殊,因为它们公开了许多由生成的属性,包括PrivateKeyPem
和CertificatePem
。
这两个文件的内容依次保存到一个名为octopus.tech
的秘密中:
Set-Content -path octopus.tech.key -Value "#{Certificate.PrivateKeyPem}"
Set-Content -path octopus.tech.crt -Value "#{Certificate.CertificatePem}"
kubectl delete -n istio-system secret octopus.tech
kubectl create -n istio-system secret generic octopus.tech `
--from-file=key=octopus.tech.key `
--from-file=cert=octopus.tech.crt
kubectl get -n istio-system secret octopus.tech -o yaml
然后ingressgateway
网关被更新以暴露端口 443 上的 HTTPS 流量。credentialName
属性与我们上面创建的秘密的名称相匹配,并且mode
被设置为SIMPLE
以启用标准 HTTPS(与MUTUAL
相反,它配置相互 TLS,这通常对公共网站没有用):
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: ingressgateway
namespace: istio-system
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 443
name: https
protocol: HTTPS
tls:
mode: SIMPLE
credentialName: octopus.tech
hosts:
- "*"
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*"
做出这一更改后,我们可以通过 HTTPS 和 HTTP:
通过 HTTPS 访问网站。
烟雾测试
Kubernetes 提供了内置支持,用于在 pod 被标记为健康并投入使用之前检查其状态。就绪探测器确保在首次创建 pod 时,该 pod 处于健康状态并准备好接收流量,而活动探测器在其生命周期内不断验证 pod 的健康状态。
我们从示例应用程序中部署的微服务包括这些检查。下面显示的 YAML 是应用于内部 gRPC 微服务的就绪性和活性检查的一个片段(略有变化):
readinessProbe:
periodSeconds: 5
exec:
command: ["/bin/grpc_health_probe", "-addr=:8080"]
livenessProbe:
periodSeconds: 5
exec:
command: ["/bin/grpc_health_probe", "-addr=:8080"]
grpc_health_probe
是一个可执行文件,专门用于验证公开 gRPC 服务的应用程序的健康状况。这个项目可以在 GitHub 上找到。
因为前端是通过 HTTP 公开的,所以它使用不同的检查来利用 Kubernetes 的能力来验证带有特殊格式的 HTTP 请求的 pod。有趣的是,这些检查使用会话 ID cookie 值来识别测试请求,就像我们将流量路由到功能分支一样:
readinessProbe:
initialDelaySeconds: 10
httpGet:
path: "/_healthz"
port: 8080
httpHeaders:
- name: "Cookie"
value: "shop_session-id=x-readiness-probe"
livenessProbe:
initialDelaySeconds: 10
httpGet:
path: "/_healthz"
port: 8080
httpHeaders:
- name: "Cookie"
value: "shop_session-id=x-liveness-probe"
如果就绪性探测在部署过程中失败,部署将认为自己不健康。如果就绪检查失败,我们可以通过选择等待部署成功选项来使 Octopus 部署失败,这将在成功完成以下步骤之前等待 Kubernetes 部署成功:
【T2
等待部署成功可确保在成功完成该步骤之前通过所有准备情况检查。
回滚策略
Kubernetes 支持对部署资源的本地回滚,命令如下:
kubectl rollout undo deployment.v1.apps/yourdeployment
但是,这种回滚过程有一些限制:
- 它仅回滚部署,而不考虑部署所依赖的任何资源,如机密或配置映射。将特定于环境的配置存储在应用程序之外是十二因素应用程序所鼓励的实践之一,这意味着代码和配置通常会并排部署。
- 它不适用于蓝/绿部署,因为此过程创建了全新的部署资源,没有可回滚的配置历史。
- 八达通仪表板不会准确反映系统的状态。
Octopus 中的部署流程旨在捕获在给定版本中部署应用程序所需的所有步骤。通过重新部署旧的版本,我们可以确保代表可部署版本的所有资源和配置都被考虑在内。
请注意,必须特别注意持久存储数据的微服务,因为回滚到以前的版本并不能确保任何持久存储的数据都与以前的代码兼容。如果您使用滚动部署,情况也是如此,因为这种策略实现了增量升级,导致应用程序的旧版本和新版本在短时间内并行运行。
多重环境
CNCF 2019 年调查强调了在 Kubernetes 中分离团队的两种主要方法:分离名称空间和分离集群:
【T2
图表显示了 2019 年 CNCF 调查的团队分离策略。
Kubernetes 提供了对资源限制(CPU、内存和临时磁盘空间)、通过网络策略的防火墙隔离和 RBAC 授权的现成支持,可以根据名称空间限制对 Kubernetes 资源的访问。
Istio 可以用来实现网络速率限制,尽管它不像那么容易。
然而,名称空间并不是完全相互隔离的。例如,自定义资源定义不能限定在名称空间的范围内。
这意味着 Kubernetes 支持软多租户,其中名称空间大部分(但不是完全)相互隔离。硬多租户,其中名称空间可以用来隔离不受信任的邻居,是一个积极讨论的主题但目前还不可用。
这可能意味着您将拥有多个共享一个 Kubernetes 集群的测试环境,以及一个单独的生产集群。
好消息是,无论您的环境是通过名称空间还是集群实现的,都被 Octopus Kubernetes 目标抽象掉了。
正如我们在本文开头看到的,Octopus 中的 Kubernetes 目标捕获了默认名称空间、用户帐户和集群 API URL。这三个字段的组合代表了执行部署的安全边界。
Kubernetes 目标的范围是环境,部署过程的范围是目标角色,将部署过程从底层集群或名称空间中分离出来。在下面的截图中,您可以看到第二个 Kubernetes 目标,其作用域为Test
环境,默认为test
名称空间:
默认为测试名称空间的目标,范围为测试环境。
为了更好地分隔目标,我们可以为每个目标创建服务帐户,其范围为名称空间。下面的 YAML 显示了一个服务帐户、角色和角色绑定的示例,该示例仅授予对dev
名称空间的访问权限:
apiVersion: v1
kind: ServiceAccount
metadata:
name: dev-deployer
namespace: dev
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
namespace: dev
name: dev-deployer-role
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: dev-deployer-binding
namespace: dev
subjects:
- kind: ServiceAccount
name: dev-deployer
apiGroup: ""
roleRef:
kind: Role
name: dev-deployer-role
apiGroup: ""
创建服务帐户会导致创建密码。这个秘密包含一个 base64 编码的令牌,我们可以使用以下命令来访问它:
kubectl get secret $(kubectl get serviceaccount dev-deployer -o jsonpath="{.secrets[0].name}" --namespace=dev) -o jsonpath="{.data.token}" --namespace=dev
该值随后被解码并保存为 Octopus 中的令牌帐户:
【T2
八达通中保存的服务账户令牌。
Kubernetes 目标可以使用令牌帐户:
Kubernetes 目标使用的令牌。
这个目标现在可以用来将资源部署到dev
名称空间,并且禁止修改其他名称空间中的资源,从而有效地通过名称空间对 Kubernetes 集群进行分区。
结论
如果你已经做到了这一步,祝贺你!配置具有高可用性、多环境、零停机部署、HTTPS 访问、功能分支部署、冒烟测试和回滚的微服务部署并非易事,但有了本指南,您将拥有在 Kubernetes 中构建世界一流部署的坚实基础。