Octopus 博客中文翻译(二十一)

原文:Octopus Blog

协议:CC BY-NC-SA 4.0

在您的 CI/CD 渠道中快速跟踪代码推广- Octopus 部署

原文:https://octopus.com/blog/fast-tracking-code-promotion-in-your-ci-cd-pipeline

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

软件开发团队在 2020 年面临的最大挑战之一是需要更快地将代码交付给生产。为了无缝地做到这一点,我们需要一个完全自动化的部署过程,并且它必须可以在多个环境中重复。此外,为了放心地部署到生产环境而不中断服务,我们必须在测试金字塔的每一层都成功通过测试。

定义这个过程可能具有挑战性,而可视化它可能更加困难;谢天谢地,Octopus Deploy 让我们两者都变得容易了!在这篇文章中,我定义了一个预先批准的生产就绪的部署管道,并讨论了所涉及的每个步骤的细节。

自动化测试金字塔

在深入我们的场景之前,让我们从定义我们的测试金字塔开始。一个完整的测试金字塔有四个模块。较低的块具有较高的测试数量,而较高的块具有较高的测试质量。完整的测试金字塔为我们的部署成功率提供了高度的信心:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

来源:https://blog . octo . com/WP-content/uploads/2018/10/integration-tests-1024 x634 . png

如图所示,四层测试是:

  • 单元测试:这些测试需要在我们管道的持续集成阶段取得成功。
  • 组件测试:这些测试需要在我们管道的持续集成阶段取得成功。
  • 集成测试:这些测试需要在我们管道的连续交付阶段取得成功。
  • 端到端测试:在我们管道的连续交付阶段,这些测试必须成功。

方案

我们有一个正在生产中运行的/hello/world微服务,它当前返回一个 JSON 字段message,值为hello world!,我们希望在它成功返回时添加另一个名为status,值为200的响应字段:

{
    "message":"Hello World!",
    "status":200
} 

环境

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

组织策略规定,任何提升到生产环境的代码都必须部署在以下环境中:

  • 发展
  • 试验
  • 质量保证
  • 预生产
  • 生产

这些环境中的每一个都是静态集成环境,这意味着我们的应用程序的所有组件都存在于这些环境中。我们希望确保不会出现配置偏差。在后面的博文中,我将讨论如何让静态集成环境和短暂的动态环境共存于我们的 CI/CD 管道中。现在,我们将保持简单。

持续集成阶段

我们任何代码升级的目标都是构建一次,部署到任何地方。构建可部署对象显然是我们开始考虑部署它之前的第一步。在构建时,我们必须经历几个阶段:

  1. 预构建:代码林挺/格式化
  2. 预构建:单元测试
  3. 预构建:组件测试
  4. 预构建:静态代码分析
  5. 预构建:第三方库安全性分析
  6. 构建:二进制构建和打包
  7. 后期构建:将二进制文件推送到工件存储库

这些是在我们预先批准的部署流程中必须发生的最小可定义单元或阶段。每个阶段都必须根据由我们的工具管理员、信息安全和软件架构师配置的预定义规则成功通过。在连续集成阶段成功通过之后,我们就为连续交付阶段做好了准备。

连续交货

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

首先,我们来定义一下连续交付连续部署的区别。来自易消化的 DevOps:7 个 devo PS 实践:

交付:“一旦自动化测试验证了源代码的每一处变更,就为产品发布做好准备的实践。这包括自动构建、测试和部署。”

部署:“持续部署是努力实现端到端自动化生产部署的实践。”

对于连续交付,我们希望将可部署的二进制文件放到最低的集成环境中,以确保二进制文件:

  1. 根据需要执行。
  2. 运行我们最低要求的第三和第四阶段自动化测试,他们成功通过。
  3. 代码升级(或自动升级)的阶段。

通常,我们管道的持续交付方面作为持续集成管道的第二阶段来执行。对于开发人员来说,这是最容易混淆和最难解决的问题,因为这是传统的操作人员介入的地方。与管道的持续集成部分不同,我们在这里引入了许多新的故障点,包括:

  • 网络连接。
  • 名称空间冲突。
  • 事件驱动/事件触发的步骤未完成。
  • 混合部署问题和自动化测试问题。
  • 集成多种工具并对这些集成进行故障排除。

Octopus Deploy 的一个优点是它有一个内置的工件存储库,因此我们不必通过将工件存储在第三方位置或工具中来引入另一个潜在的故障点。这意味着我们可以确信,事实上,新的工件事件确实触发了我们管道中的连续交付步骤。我们应该清楚地定义我们的连续交付自动化和我们的集成/端到端测试阶段之间的区别,以便弄清楚在我们的管道中什么地方可能出现了故障。这些步骤可以定义为:

  • CD 二进制部署。
  • CD 二进制验证。
  • 集成测试光盘。
  • 端到端测试时的 CD。

有一个 CD 二进制文件验证阶段是很重要的,它可以确保我们不仅得到了需要的二进制文件,而且它确实按照预期执行了。在这种情况下,如果我们有一个坏的二进制文件(由于损坏或错误的代码逻辑等。),我们可以通过尽早失败并快速将反馈反馈给开发人员来节省大量时间。

什么时候测试?

代码升级的一个冲突方面是试图确保代码以相同的方式部署在每个环境中,同时只运行必要的测试以确保新的部署按预期工作。这方面的一个例子是在我们的 QA 环境中运行负载测试,而不是在其他环境中。在每个环境中运行负载测试将会非常耗时,并且会延长部署时间(这与我们试图实现的目标相反)。此外,我们可能不会在生产环境中运行负载测试,因为我们不想为实时服务的服务可靠性冒不必要的风险。

这就引出了一个问题,哪些测试需要运行,它们需要在什么时候运行,如何区分运行的测试,以及它们应该在什么环境中运行?

这些都是很好的问题,可以分为两类:

  • 预先批准的 CI/CD 管道。
  • 广泛的 CI/CD 渠道。

哪些变更可以通过预先批准的渠道进行?

并不是每一个代码变更都需要经过数小时的回归测试和负载测试。事实上,如果您遵循精益/敏捷开发方法,大多数代码更改不应该需要。如果变更很简单,并且没有从数据库中提取新数据或进行任何高级计算,那么该变更可能会被部署到生产环境中,而不需要进行全套测试,因为风险低且可信度高。

所有管道事件都应该从源代码控制中触发。为了确保更改将通过预先批准的 CI/CD 管道运行,我将创建一个新的 Git 分支,其命名约定如下:

feature-pa-eado-4287-add_status_response

  • feature:这意味着新的分支正在向代码库添加新的功能。
  • pa:这是一项预先批准的变更,将通过快速通道进行。
  • 这是我的 JIRA 项目。
  • <number>:这是我项目的一张 JIRA 门票。
  • add_status_response:这是对变更的快速描述。

默认情况下,所有更改将通过完整的 CI/CD 管道运行。这确保了开发人员对他们的变更投入足够的思考,以确定它是否可以被快速跟踪以部署到生产中。

在以后的博文中,我们将讨论非预先批准的变更将如何贯穿我们完整的 CI/CD 管道。

完整的预批准 CI/CD 渠道

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 部署前步骤(GitHub/吉拉):
    1. 一名开发人员被分配到 JIRA 机票 EADO-4287。根据需求,确定这是一个小的变化,并创建一个特征分支feature-pa-eado-4287-add_status_response
    2. 编写满足验收标准的单元测试。
    3. 编写满足单元测试的代码。
    4. 验证测试在本地成功通过,git push到源代码控制。
  2. 持续集成(Jenkins):
    1. 预构建:代码林挺/格式化。
    2. 预构建:单元测试。
    3. 预构建:组件测试。
    4. 预构建:静态代码分析。
    5. 预构建:第三方库安全分析。
    6. 预构建:请求已打开,暂停等待批准。
    7. 预构建:请求批准、合并、标记主分支(rc-<version>pa)。
    8. 构建:二进制构建并打包。
    9. 后期构建:将二进制文件推送到工件存储库(Octopus Deploy)。
  3. 开发环境(Octopus 部署):
    1. CD 二进制部署:将包复制并解压缩到 web 服务器。
    2. CD 二进制验证:验证 web 服务器返回 200 响应。
    3. 集成测试中的 CD:验证来自负载平衡 URI 的 200 个响应。
    4. 端到端测试时的 CD:验证发出 API 调用的前端是否返回成功响应。
    5. CD post git 标签:dev_success
  4. 测试环境:
    1. Git 标签dev_success触发 CD 二进制部署:将包复制并解压缩到 web 服务器。
    2. CD 二进制验证:验证 web 服务器返回 200 响应。
    3. 集成测试中的 CD:验证来自负载平衡 URI 的 200 个响应。
    4. 端到端测试时的 CD:验证发出 API 调用的前端是否返回成功响应。
    5. CD post git 标签:test_success
  5. 质量保证环境:
    1. Git 标签test_success触发 CD 二进制部署。将包复制并解压缩到 web 服务器。
    2. CD 二进制验证:验证 web 服务器返回 200 响应。
    3. 集成测试中的 CD:验证来自负载平衡 URI 的 200 个响应。
    4. 端到端测试时的 CD:验证发出 API 调用的前端是否返回成功响应。
    5. CD post git 标签:qa_success
  6. 生产前环境:
    1. Git 标签qa_success触发 CD 二进制部署:将包复制并解压缩到 web 服务器。
    2. CD 二进制验证:验证 web 服务器返回 200 响应。
    3. 集成测试中的 CD:验证来自负载平衡 URI 的 200 个响应。
    4. 端到端测试时的 CD:验证发出 API 调用的前端是否返回成功响应。
    5. CD post git 标签:preprod_success
  7. 生产环境:
    1. Git 标签preprod_success触发 CD 二进制部署:将包复制并解压缩到 web 服务器。
    2. CD 二进制验证:验证 web 服务器返回 200 响应。
    3. 集成测试中的 CD:验证来自负载平衡 URI 的 200 个响应。
    4. 端到端测试时的 CD:验证发出 API 调用的前端是否返回成功响应。
    5. CD post git 标签:v<version>
    6. CD post git 删除标签:
      1. rc-<version>
      2. dev_success
      3. test_success
      4. qa_success
      5. preprod_success

结论

将代码快速部署到生产环境中是开发团队面临的最大挑战。代码推广和部署都是关于自信的。为了获得这种信心,我们的 CI/CD 渠道必须包含测试金字塔的四层。确定何时在哪个部署环境中执行测试金字塔的每一层成为下一个挑战,但是为小的、低风险的更改定义预先批准的部署管道有助于平衡速度和质量,并且使用 Git 分支和标签来触发您预先批准的管道有助于将部署责任交给最了解代码的人,即开发人员。

浏览 DevOps 工程师手册以了解更多关于 DevOps、CI/CD 以及软件测试在持续交付中的作用。

通过执行 API - Octopus Deploy 加快部署

原文:https://octopus.com/blog/faster-deployments-with-the-executions-api

Executions API 是一组新的端点,可以显著提高部署、版本创建和 runbook 执行等操作的性能。在对这些操作进行审查后,我们设计并添加了这些端点,作为 Octopus 2022 Q3 版本的一部分。结果是这些操作的性能显著提高,部署时的可伸缩性更好。

在这篇文章中,我将深入探讨我们为什么创建 Executions API,以及它如何增强性能和开发人员体验。

使用 Octopus CLI 测量网络性能

今年早些时候,少数客户让我们知道了一个可以通过他们的部署来改善的问题。他们注意到构建环境和 Octopus REST API 之间有大量的网络流量。我们希望通过准确了解正在发生的事情来提高性能。

我们的调查从测量 Octopus CLI 生成的网络流量开始,以建立基线。Octopus CLI 广泛用于自动化场景,包括 Azure Pipelines、Buildkite、GitHub Actions、Jenkins 和 TeamCity。Octopus CLI 被用来创建和部署发行版,推送构建信息和软件包,以及运行操作手册。这些操作以 Octopus REST API 为目标来执行工作。

我们运行了一系列测试,涉及 3 个 Octopus CLI 操作:

  • create-release
  • deploy-release
  • run-runbook

我们跟踪了 Octopus CLI 和 Octopus REST API 之间发送的网络流量,并获得了它们的网络配置文件摘要:

操作申请数量持续时间请求(正文)回应(正文)
create-release171060 毫秒8.1 kB96.4 kB
deploy-release181275 毫秒8.7 kB105.6 kB
run-runbook141452 毫秒6.9 kB90 kB

17 HTTP 请求支持像create-release这样的命令确实是一个非常闲聊的话题!对 Octopus CLI 中的create-release命令的检查显示,针对 Octopus REST API 发出了以下 HTTP 请求:

 1  200  CONNECT  /   
 2  200  GET      /api
 3  200  GET      /api
 4  200  GET      /api
 5  200  GET      /api/spaces
 6  200  GET      /api
 7  200  GET      /api/users/me
 8  200  GET      /api/users/{id}/spaces
 9  200  GET      /api/{id}
10  200  GET      /api/users/me
11  200  GET      /api/{id}/projects/projects?name={name}
12  200  GET      /api/{id}/projects/projects/{id}/channels
13  200  GET      /api/{id}/deploymentprocesses/{id}
14  200  GET      /api/{id}/projects/{id}/deploymentprocesses/template?channel={id}
15  200  GET      /api/{id}/projects/{id}/deploymentsettings
16  201  POST     /api/{id}/releases?{ignore-channel-rules}
17  200  GET      /api/{id}/releases/{id} 

第 16 行的 HTTP POST 是负责创建发布的 HTTP 请求。之前的大多数 HTTP 请求都内置在 Octopus CLI 中,以限定资源标识符(例如,项目的 ID 是什么,“octopes”?).这也代表了一条快乐的道路;如果遇到异常和/或错误并需要解决,则可能涉及更多网络流量。更糟糕的是,这些 HTTP 请求是按顺序发出的:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这些操作在 Octopus REST API 中强制执行数据检查和决策树。如果一个发布涉及不同的包版本,这种情况会变得更糟,因为这可能会执行额外的服务操作。所有这些网络流量导致了更多的负载,损害了我们的可伸缩性。

我们需要一个更好的解决方案。我们在 2022 年第二季度开始工作,建立一组新的服务端点,统称为执行 API。这些 API 极大地减少了 Octopus REST API 和诸如 Octopus CLI 之类的 API 客户端之间发送的网络流量。

执行 API 简介

执行 API 由以下操作和路线组成:

操作途径
create-release/api/{space-id}/releases/create/v1
run-runbook/api/{space-id}/runbook-runs/create/v1
tenanted-deployment/api/{space-id}/deployments/create/tenanted/v1
untenanted-deployment/api/{space-id}/deployments/create/untenanted/v1

这些 API 包含了 Octopus CLI 执行的所有“繁重”工作。这不仅减少了网络流量,还使这项工作更接近数据。结果是执行 API 比通过 Octopus REST API 表示的集合调用快 3 倍:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

更好的开发人员执行 API 体验

除了解决性能问题之外,我们还希望 Executions API 能够提供出色的开发人员体验。我们通过以下方式实现了这一目标:

  1. 将版本控制合并到所有路线中
  2. 将版本控制合并到所有消息中
  3. 将 camel case 应用于消息模式
  4. 将资源名称(尽可能地)应用于消息模式

将版本化策略合并到执行 API 中具有重要的战略意义。我们知道 API 可以存在很长时间,需要进化。将版本应用于路由和消息为我们提供了一条途径,将更改合并到我们的 API 客户端库和现有集成中。

消息模式中键的骆驼大小写提供了更好的开发人员体验,因为它符合客户的期望:

{
  "channelName": "Default",
  "gitRef": "refs/heads/main",
  "projectName": "OctoPetShop",
  "releaseVersion": "1.2.3",
  "spaceId": "Spaces-2006",
  "spaceIdOrName": "Pattern - Tenants"
} 

使用 Executions API,您可以在消息有效负载中为资源指定名称而不是 id(只要有可能)。这可以节省您执行查找的时间,从而获得更好的整体开发体验。

下一步是什么?

Executions API 极大地提高了部署、版本创建和 runbook 执行等操作的性能,并且可供运行 Octopus Deploy 2022.3 的客户使用。

我们的下一步是将它们整合到我们现有的集成和工具中。已经开始将执行 API 与新的 Octopus CLI 结合起来。将新的 Octopus CLI 与 Executions API 结合使用时,您应该会注意到部署时间显著缩短。

愉快的部署!

特性分支 web 应用程序- Octopus 部署

原文:https://octopus.com/blog/feature-branch-web-apps

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Octopus 非常擅长管理开发、测试和生产环境中的变更进度。它还通过使用通道很好地处理了分支策略,如 hotfixes,允许您绕过某些环境,在紧急情况下将具有匹配版本规则的包(如在版本发布字段中有单词hotfix)直接推向生产。

但是特征分支呢?在这篇博客文章中,我详细分析了什么是特性分支,以及如何在 Octopus 中管理它们。

什么是特征分支?

在我们能够在 Octopus 中建模特征分支之前,我们需要理解什么是特征分支。

马丁·福勒提供了很好的描述:

特性分支是一种源代码分支模式,当开发人员开始处理一个新特性时,她会打开一个分支。她在这个分支上完成特性的所有工作,并在特性完成时将变更与团队的其他成员集成在一起。

我怀疑大多数开发人员都熟悉在一个特性分支中工作,但是我们感兴趣的是与部署相关的细节,包含在短语在这个分支上做所有的特性工作。

具体来说,我们希望为开发人员提供一种部署他们正在开发的代码的方式:

  • 在不容易在本地复制的环境中进行测试,例如平台即服务(PaaS)产品。
  • 轻松地与团队的其他成员分享他们工作的当前状态。
  • 为额外的测试提供稳定的目标,如端到端、性能或安全性测试。

不像固定的环境,比如测试或者生产,特性分支是短暂的。它们在一个特性被开发时存在,但是一旦合并到一个主线分支中,这个特性分支就应该被删除。

此外,功能分支并不打算部署到生产中。与热修复不同,热修复是快速解决关键问题的紧急生产部署,功能分支仅用于测试。

通过限制某个功能分支的受众,您还可以潜在地节省成本。这是因为您可以在晚上删除部署及其底层基础设施,然后在早上重新部署特性分支。这样做意味着你不再花钱去托管那些一夜之间没人会用到的应用程序。

特性分支通常由 CI 系统处理,这是一种确保测试通过并产生可部署工件的便捷方式。有一个令人信服的论点是,CI 系统应该产生一个可部署的工件(如果代码编译的话),而不管测试结果如何,假设像测试驱动开发(TDD)这样的过程鼓励失败的测试作为开发工作流的正常部分。

版本控制功能分支工件

像 GitVersion 这样的工具提供了一些例子,展示了包含在 SemVer 预发布字段中的特性分支名称,产生了类似于1.3.0-myfeature的版本。大多数包管理工具都有公开组件以适应特性分支名称的版本控制策略。

在包管理器没有版本控制指南的情况下,比如 Docker 存储库,采用 SemVer 这样的版本控制方案是一个不错的选择。

Maven 版本控制方案有一个怪癖,带有限定符的版本,如1.0.0-myfeature,被认为是比未限定的版本,如1.0.0更晚的版本。但是,使用通道版本规则意味着代表功能分支的合格版本不适合部署到生产环境,因此合格和不合格版本的排序在 Octopus 中不存在问题。

功能分支部署的质量

考虑到以上所有因素,我们可以将功能分支部署定义为具有以下品质:

  • 它们仅用于测试,不会在生产环境中公开。
  • 它们是短暂的,仅在版本控制系统中存在相应的分支时才存在。
  • 在工作时间之外可能不需要它们,如果部署可以在一夜之间停止,则可以节省成本。
  • 它们不会在任何地方得到提升,因此它们的生命周期包括在测试环境中的一次部署。
  • 他们的应用程序工件被版本化以识别源特性分支。

下一步是在 Octopus 中对上述规则进行建模。

Octopus 包含了有限的元步骤概念,我们将使用这个术语对修改 Octopus 本身的步骤进行分类。

Deploy a release 步骤就是一个例子,顾名思义,它可以用于部署为另一个项目创建的发布。脚本步骤还可以动态生成一些章鱼资源

元步骤功能有些特别和有限。我们需要一个全面的解决方案来创建和销毁 Octopus 中的资源,以反映功能分支的短暂性质。

Octopus CLI 提供了一些额外的功能,能够创建许多 Octopus 资源,如版本、频道和环境。不幸的是,它不包括删除这些资源的所有相应选项,所以它不是一个完整的解决方案。

另一种选择是使用 REST API,它公开了 web UI 可以执行的每个操作。然而,如果可能的话,我宁愿避免编写第二个 CLI 工具来管理 Octopus 资源的整个生命周期。

幸运的是,章鱼平台供应商正好提供了我们需要的东西。将这个提供者与 Octopus 中现有的 Terraform 部署步骤结合起来,我们可以创建自己的元步骤。这反过来意味着我们可以管理表示特征分支所需的短暂 Octopus 资源。

管理功能分支的操作手册

我们将利用 runbooks 来支持创建和删除短暂的 Octopus 资源。这允许我们将底层基础设施的管理与应用程序的部署分开。

我们将创建六本操作手册。三个 runbooks 将为单个功能分支创建、删除和暂停资源。根据 Git 中是否存在分支,这些将与另外三个 runbooks 配对执行:

  • 创建分支机构基础设施,这将创建部署单个分支机构所需的资源。
  • 恢复分支,这将基于 Git 存储库中的分支基础设施(重新)创建分支基础设施。
  • 破坏分支基础设施,这将破坏特征分支资源。
  • 销毁分支,这将删除 Git 中删除的分支资源的任何特性分支资源。
  • 暂停分支基础设施,这将在不使用时关闭或破坏昂贵的功能分支资源。
  • 挂起分支,这将挂起所有分支。

将章鱼资源映射到特色分支

Octopus 有几个资源来促进功能分支的部署,但是对于这些资源如何与功能分支相关联,并没有硬性的规则。我们将在这里提供一些意见,指导我们如何构建上面列出的操作手册。

环境

我们将使用环境来封装用于托管特性分支的资源。环境提供了一个很好的范围和安全边界,并且清楚地将特性分支与静态环境(如生产环境)分开。

生活过程

为了防止特性分支被提升到生产环境,或者根本不被提升,我们将为每个特性分支创建一个生命周期,包括上面的单个环境。

频道

为了确保适当的特性分支工件被部署到正确的环境中,我们将使用一个通道。通道规则将确保特性分支工件版本被选择并导向正确的生命周期,这反过来确保正确的环境接收特性分支部署。

在这三种资源中,我们将通道视为特性分支的表示。您在部署期间选择通道,它是 Octopus 项目的唯一本地资源(生命周期和环境的范围是一个空间)。这使得它成为在项目中表现一个特性分支的自然选择。

租户也可以用来表示一个功能分支。我发现环境、渠道和生命周期是更自然的匹配。

我们部署到的 web 应用程序将由目标来表示。这些目标将被限定在它们相关的功能分支环境中。

Web 应用程序有插槽的概念,插槽也可以用来部署特性分支。然而,插槽是 Azure 服务计划上的有限资源,这意味着您将受限于任何时候可以部署的功能分支的数量。因此,本博客为每个功能分支使用了新的网络应用。

最初的 Azure 帐户

为了在 Azure 中部署功能分支 web 应用,我们需要一个初始的“种子”Azure 帐户来管理云资源。这是运行 Terraform 脚本的帐户:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们的元步骤将像任何其他 Terraform 部署一样执行。这意味着即使 Octopus 在管理自己,我们也需要创建变量来允许 Terraform 部署返回到 Octopus 服务器。

  1. 创建一个新的 Octopus 项目,并定义两个名为 ApiKeyServerUrl 的变量。API 密钥将是一个保存有 Octopus API 密钥的秘密变量,服务器 URL 将是 Octopus 服务器的 URL。在这个例子中,我使用了一个 URL 为https://mattc.octopus.app云实例
  2. 此外,创建一个名为 FeatureBranch 的提示变量。这将用于将功能分支的名称传递到 runbooks 中。
  3. 最后,创建一个名为 Azure 的变量,该变量指向上一节中创建的 Azure 帐户:

【T2 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

部署项目

我们的部署项目将利用部署 Azure 应用服务步骤从octopussamples/randomquotes JavaDocker 存储库中部署 Docker 映像。存储库包含使用 GitHub 上的代码构建的图像,带有正确的标记规则以识别特征分支。

称该步骤为Deploy Web App。我们将在下一节创建通道时引用这个步骤名。

《创建分支机构基础设施操作手册》

我们将从创建构建所有资源的 runbook 开始,包括 Octopus 和 Azure,以部署一个特性分支。

这个 Terraform 模板创建了 Azure web 应用基础设施。我们将它保存在一个单独的模板中,这样我们可以单独销毁这些资源,而不会破坏 Octopus 资源。该模板将被部署在名为 Feature Branch Web App 的步骤中(我们稍后将使用步骤名称来引用输出变量):

provider "azurerm" {
version = "=2.0.0"
features {}
}

variable "apiKey" {
    type = string
}

variable "space" {
    type = string
}

variable "serverURL" {
    type = string
}

terraform {
  backend "azurerm" {
    resource_group_name  = "mattc-test"
    storage_account_name = "storageaccountmattc8e9b"
    container_name       = "terraformstate"
    key                  = "#{FeatureBranch}.azure.terraform.tfstate"
  }
}

resource "azurerm_resource_group" "resourcegroup" {
  name     = "mattc-test-webapp-#{FeatureBranch}"
  location = "West Europe"
  tags = {
    OwnerContact = "@matthew.casperson"
    Environment = "Dev"
    Lifetime = "15/5/2021"
  }
}

resource "azurerm_app_service_plan" "serviceplan" {
  name                = "#{FeatureBranch}"
  location            = azurerm_resource_group.resourcegroup.location
  resource_group_name = azurerm_resource_group.resourcegroup.name
  kind                = "Linux"
  reserved            = true

  sku {
    tier = "Standard"
    size = "S1"
  }
}

resource "azurerm_app_service" "webapp" {
  name                = "mattctestapp-#{FeatureBranch}"
  location            = azurerm_resource_group.resourcegroup.location
  resource_group_name = azurerm_resource_group.resourcegroup.name
  app_service_plan_id = azurerm_app_service_plan.serviceplan.id
}

output "ResourceGroupName" {
  value = "${azurerm_resource_group.resourcegroup.name}"
}

output "WebAppName" {
  value = "${azurerm_app_service.webapp.name}"
} 

下一个 Terraform 模板创建了一个 Octopus 环境,将其置于生命周期中,并在通道中对其进行配置。它还创建了一个 web 应用程序目标:

variable "apiKey" {
    type = string
}

variable "space" {
    type = string
}

variable "serverURL" {
    type = string
}

terraform {
  required_providers {
    octopusdeploy = {
      source  = "OctopusDeployLabs/octopusdeploy"
    }
  }

  backend "azurerm" {
    resource_group_name  = "mattc-test"
    storage_account_name = "storageaccountmattc8e9b"
    container_name       = "terraformstate"
    key                  = "#{FeatureBranch}.terraform.tfstate"
  }
}

provider "octopusdeploy" {
  address  = var.serverURL
  api_key   = var.apiKey
  space_id = var.space
}

resource "octopusdeploy_environment" "environment" {
  allow_dynamic_infrastructure = true
  description                  = "Feature branch environment for #{FeatureBranch}"
  name                         = "#{FeatureBranch}"
  use_guided_failure           = false
}

resource "octopusdeploy_lifecycle" "lifecycle" {
  description = "The lifecycle holding the feature branch #{FeatureBranch}"
  name        = "#{FeatureBranch}"

  release_retention_policy {
    quantity_to_keep    = 1
    should_keep_forever = true
    unit                = "Days"
  }

  tentacle_retention_policy {
    quantity_to_keep    = 30
    should_keep_forever = false
    unit                = "Items"
  }

  phase {
    name                        = "#{FeatureBranch}"
    is_optional_phase           = false
    optional_deployment_targets = ["${octopusdeploy_environment.environment.id}"]

    release_retention_policy {
      quantity_to_keep    = 1
      should_keep_forever = true
      unit                = "Days"
    }

    tentacle_retention_policy {
      quantity_to_keep    = 30
      should_keep_forever = false
      unit                = "Items"
    }
  }
}

resource "octopusdeploy_channel" "channel" {
  name       = "Feature branch #{FeatureBranch}"
  project_id = "#{Octopus.Project.Id}"
  lifecycle_id = "${octopusdeploy_lifecycle.lifecycle.id}"
  description = "Repo: https://Github.com/OctopusSamples/RandomQuotes-Java Branch: #{FeatureBranch}"
  rule {
    id = "#{FeatureBranch}"
    tag = "^#{FeatureBranch}.*$"
    action_package {
      deployment_action = "Deploy Web App"
    }
  }
}

resource "octopusdeploy_azure_service_principal" "azure_service_principal_account" {
  application_id  = "#{Azure.Client}"
  name            = "Azure account for #{FeatureBranch}"
  password        = "#{Azure.Password}"
  subscription_id = "#{Azure.SubscriptionNumber}"
  tenant_id       = "#{Azure.TenantId}"
}

resource "octopusdeploy_azure_web_app_deployment_target" "webapp" {
  account_id                        = "${octopusdeploy_azure_service_principal.azure_service_principal_account.id}"
  name                              = "Azure Web app #{FeatureBranch}"
  resource_group_name               = "#{Octopus.Action[Feature Branch Web App].Output.TerraformValueOutputs[ResourceGroupName]}"
  roles                             = ["Web Application", "Web Application #{FeatureBranch}"]
  tenanted_deployment_participation = "Untenanted"
  web_app_name                      = "#{Octopus.Action[Feature Branch Web App].Output.TerraformValueOutputs[WebAppName]}"
  environments                      = ["${octopusdeploy_environment.environment.id}"]
  endpoint {
    default_worker_pool_id          = "WorkerPools-762"
    communication_style             = "None"
  }
} 

在这个模板中有一些重要的东西需要强调。

Octopus provider 是一个官方插件,可以通过 Terraform 自动下载:

 required_providers {
    octopusdeploy = {
      source  = "OctopusDeployLabs/octopusdeploy"
    }
  } 

我们已经利用 Octopus 模板语法(后跟花括号的散列符号)构建了 Terraform 模板的各个部分。后端块不能使用 Terraform 变量,但是因为 Octopus 在将文件传递给 Terraform 之前对其进行了处理,所以我们可以绕过这一限制,将变量注入到键名中:

 backend "azurerm" {
    resource_group_name  = "mattc-test"
    storage_account_name = "storageaccountmattc8e9b"
    container_name       = "terraformstate"
    key                  = "#{FeatureBranch}.terraform.tfstate"
  } 

对通道的描述指出了通道所代表的 Git 存储库和分支。因为我们决定通道是项目中一个特性分支的表示,所以将它链接回它所来自的代码库所需的细节将在这里被捕获。

还要注意,我们引用前面创建的步骤Deploy Web App来将通道规则应用于:

resource "octopusdeploy_channel" "channel" {
  name       = "Feature branch #{FeatureBranch}"
  project_id = "#{Octopus.Project.Id}"
  lifecycle_id = "${octopusdeploy_lifecycle.lifecycle.id}"
  description = "Repo: https://Github.com/OctopusSamples/RandomQuotes-Java Branch: #{FeatureBranch}"
  rule {
    id = "#{FeatureBranch}"
    tag = "^#{FeatureBranch}.*$"
    action_package {
      deployment_action = "Deploy Web App"
    }
  } 

为了创建 Azure 帐户,我们创建了一个新帐户,其详细信息与在 Terraform 步骤中定义的 Azure 帐户相同:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在一个更安全的环境中,这些细节将被一个仅限于必要资源的帐户所取代。在不太安全的环境中,您可以完全跳过创建功能分支特定帐户,而只需共享现有帐户:

resource "octopusdeploy_azure_service_principal" "azure_service_principal_account" {
  application_id  = "#{Azure.Client}"
  name            = "Azure account for #{FeatureBranch}"
  password        = "#{Azure.Password}"
  subscription_id = "#{Azure.SubscriptionNumber}"
  tenant_id       = "#{Azure.TenantId}"
} 

当创建目标时,我们利用由第一个 Terraform 脚本创建的输出变量。资源组名输出为#{Octopus.Action[Feature Branch Web App].Output.TerraformValueOutputs[ResourceGroupName]},web app 名输出为#{Octopus.Action[Feature Branch Web App].Output.TerraformValueOutputs[WebAppName]}

这个目标还专门为健康检查定义了一个 Windows worker 池(在我的例子中,ID 为WorkerPools-762)。这是因为 Azure web 应用目标需要 Windows 工作线程来运行运行状况检查:

resource "octopusdeploy_azure_web_app_deployment_target" "webapp" {
  account_id                        = "${octopusdeploy_azure_service_principal.azure_service_principal_account.id}"
  name                              = "Azure Web app #{FeatureBranch}"
  resource_group_name               = "#{Octopus.Action[Feature Branch Web App].Output.TerraformValueOutputs[ResourceGroupName]}"
  roles                             = ["Web Application", "Web Application #{FeatureBranch}"]
  tenanted_deployment_participation = "Untenanted"
  web_app_name                      = "#{Octopus.Action[Feature Branch Web App].Output.TerraformValueOutputs[WebAppName]}"
  environments                      = ["${octopusdeploy_environment.environment.id}"]
  endpoint {
    default_worker_pool_id          = "WorkerPools-762"
    communication_style             = "None"
  } 

本操作手册的最后一步是调用 Octopus CLI 来创建一个版本并将其部署到新环境中。通过运行部署,我们可以确信,当我们在早上重新创建特性分支时,它们将部署最新的应用程序代码。

如果已经创建了一个分支,但是没有可用于部署的工件,我们允许这个调用失败:

octo create-release \
    --project="Random Quotes" \
    --channel="Feature branch #{FeatureBranch}" \
    --deployTo="#{FeatureBranch}" \
    --space="#{Octopus.Space.Name}" \
    --server="#{ServerUrl}" \
    --apiKey="#{ApiKey}"

# allow failure if the a package is not available
exit 0 

通过运行本操作手册:

  • Octopus 将填充所有的功能分支资源。
  • Azure 中创建了一个 web 应用程序。
  • 部署最新版本的功能分支应用程序(如果存在)。

因为 Terraform 是等幂的,所以我们可以多次重新运行这个 runbook,任何丢失的资源都将被重新创建。我们将利用这一点,在一夜之间销毁网络应用程序,以节省成本。

简历分支运行手册

Create Branch infra structure run book 可以为分支创建资源,但是它不知道 Git 中存在哪些分支。 Resume Branches runbook 扫描 Git repo 以查找分支,并执行Create Branch infra structurerun book 以在 Octopus 中反映这些分支。

下面的 PowerShell 解析对ls-remote --heads https://repo的调用结果,并使用结果列表来调用创建分支基础结构 runbook:

$octopusURL = "https://mattc.octopus.app"
$octopusAPIKey = "#{ApiKey}"
$spaceName = "#{Octopus.Space.Name}"
$projectName = "#{Octopus.Project.Name}"
$environmentName = "#{Octopus.Environment.Name}"
$repos = @("https://Github.com/OctopusSamples/RandomQuotes-Java.Git")
$ignoredBranches = @("master", "main")

# Get all the branches for the repos listed in the channel descriptions    
$branches = $repos |
    % {PortableGit\bin\Git ls-remote --heads $_} |
    % {[regex]::Match($_, "\S+\s+(\S+)").captures.groups[1].Value} |
    % {$_.Replace("refs/heads/", "")} |
    ? {-not $ignoredBranches.Contains($_)}

# Clean up the old branch infrastructure
$branches |
    % {Write-Host "Creating branch $_";
        octo run-runbook `
          --apiKey $octopusAPIKey `
          --server $octopusURL `
          --project $projectName `
          --runbook "Create Branch Infrastructure" `
          -v "FeatureBranch=$_" `
          --environment $environmentName `
          --space $spaceName} 

销毁分支机构基础设施操作手册

销毁分支基础设施创建分支基础设施相反,它删除所有资源,而不是创建它们。这是通过使用与在创建分支基础设施操作手册中定义的相同的 Terraform 模板来实现的,但是使用销毁 Terraform 资源步骤而不是应用 Terraform 模板步骤来执行它们。

摧毁树枝操作手册

破坏分支运行手册与恢复分支运行手册相反。它向 Octopus 查询所有通道,向 Git 查询所有分支,并且在通道没有匹配分支的情况下,调用销毁分支基础结构 runbook:

Install-Module -Force PowershellOctopusClient
Import-Module PowershellOctopusClient

$channelDescriptionRegex = "Repo:\s*(\S+)\s*Branch:\s*(\S+)"

# Octopus variables
$octopusURL = "#{OctopusUrl}"
$octopusAPIKey = "#{ApiKey}"
$spaceName = "#{Octopus.Space.Name}"
$projectName = "#{Octopus.Project.Name}"

$endpoint = New-Object Octopus.Client.OctopusServerEndpoint $octopusURL, $octopusAPIKey
$repository = New-Object Octopus.Client.OctopusRepository $endpoint
$client = New-Object Octopus.Client.OctopusClient $endpoint

$space = $repository.Spaces.FindByName($spaceName)
$repositoryForSpace = $client.ForSpace($space)

$project = $repositoryForSpace.Projects.FindByName($projectName)
$channels = $repositoryForSpace.Channels.GetAll() | ? {$_.ProjectId -eq $project.Id}

# Get all the project channels whose description is in the format "Repo: http://blah Branch: blah"
$repos = $channels |
    % {[regex]::Match($_.Description, $channelDescriptionRegex)} |
    ? {$_.Success} |
    % {$_.captures.groups[1]} |
    Get-Unique

# Get all the branches for the repos listed in the channel descriptions    
$branches = $repos |
    % {@{Repo = $_; Branch = $(PortableGit\bin\Git ls-remote --heads $_)}} |
    % {$repo = $_.Repo; [regex]::Match($_.Branch, "\S+\s+(\S+)").captures.groups[1].Value.Replace("refs/heads/", "") | %{"Repo: $repo Branch: $_"}}

# Get all the branches that have a channel but have been removed from the repo
$oldChannels = $channels |
    ? {[regex]::Match($_.Description, $channelDescriptionRegex).Success} |
    ? {-not $branches.Contains($_.Description)} |
    % {[regex]::Match($_.Description, $channelDescriptionRegex).captures.groups[2]}

# Clean up the old branch infrastructure
$oldChannels |
    % {Write-Host "Deleting branch $_";
        octo run-runbook `
          --apiKey $octopusAPIKey `
          --server $octopusURL `
          --project $projectName `
          --runbook "Destroy Branch Infrastructure" `
          -v "FeatureBranch=$_" `
          --environment $_ `
          --space $spaceName} 

暂停分支机构基础设施运行手册

为了一夜之间节省资金,我们将销毁 Azure 中的功能分支 web 应用。这是微软提出的暂停应用服务计划的解决方案。

删除 Azure 资源也只是运行销毁 Terraform 资源步骤的一个例子,模板在创建分支基础设施操作手册中的功能分支 Web 应用步骤中。因为我们只破坏 Azure 资源,所以任何现有的 Octopus 版本、渠道、环境、目标和帐户都将保留。

由于我们在删除和创建这些 Azure 资源时使用相同的后端状态文件,Terraform 知道任何给定 Azure 资源的状态。这意味着我们可以根据需要调用暂停分支基础设施创建分支基础设施销毁分支基础设施run book,Terraform 将让 Azure 处于所需的状态。

暂停分支运行手册

暂停分支运行手册扫描 Octopus 寻找通道,并调用暂停分支基础设施运行手册。这意味着任何与渠道相关的 Azure 资源,以及 Octopus 中的功能分支都将被销毁,并且不再需要花钱:

Install-Module -Force PowershellOctopusClient
Import-Module PowershellOctopusClient

$channelDescriptionRegex = "Repo:\s*(\S+)\s*Branch:\s*(\S+)"

# Octopus variables
$octopusURL = "#{ServerUrl}"
$octopusAPIKey = "#{ApiKey}"
$spaceName = "#{Octopus.Space.Name}"
$projectName = "#{Octopus.Project.Name}"
$environment = "#{Octopus.Environment.Name}"

$endpoint = New-Object Octopus.Client.OctopusServerEndpoint $octopusURL, $octopusAPIKey
$repository = New-Object Octopus.Client.OctopusRepository $endpoint
$client = New-Object Octopus.Client.OctopusClient $endpoint

$space = $repository.Spaces.FindByName($spaceName)
$repositoryForSpace = $client.ForSpace($space)

$project = $repositoryForSpace.Projects.FindByName($projectName)
$channels = $repositoryForSpace.Channels.GetAll() | ? {$_.ProjectId -eq $project.Id}

# Get all the project channels whose description is in the format "Repo: http://blah Branch: blah"
$branches = $channels |
    % {[regex]::Match($_.Description, $channelDescriptionRegex)} |
    ? {$_.Success} |
    % {$_.captures.groups[2].Value} |
    Get-Unique

# Clean up the old branch infrastructure
$branches |
    % {Write-Host "Deleting branch $_";
        octo run-runbook `
          --apiKey $octopusAPIKey `
          --server $octopusURL `
          --project $projectName `
          --runbook "Suspend Branch Infrastructure" `
          -v "FeatureBranch=$_" `
          --environment $environment `
          --space $spaceName} 

同步 Octopus 和 Git

我们现在有了管理功能分支资源所需的所有操作手册。为了保持 Octopus 和 Git 同步,下一步是调度触发器来删除旧的分支、关闭资源和创建新的分支。

我们首先在一天中的不同时间点触发销毁分支操作手册。该 runbook 将破坏功能分支资源,其中底层功能分支已从 Git 中删除。

清理这些分支不应该干扰任何人,因为我们假设删除底层分支意味着该特性已经被合并或放弃。所以我们创建一个触发器,每小时执行一次销毁分支运行手册。

同样,我们希望捕捉全天创建的任何新分支,因此我们安排 Create Branches runbook 在早上 6 点到下午 6 点之间定期运行,以覆盖平均工作日。

为了降低成本,我们希望在一天结束时删除托管我们的功能分支的云资源。为此,我们安排暂停分支机构运行手册在晚上 7 点运行。这确保它在最终的创建分支 runbook 触发器之后运行。

最终结果是:

  • 通过销毁分支,全天清理被删除的分支。
  • 在工作日开始时,任何新的分支都会被检测到,所有 Azure 资源都会通过创建分支重新创建。
  • 晚上所有 Azure 资源都被暂停分支破坏:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果使用触发器的响应能力不足以捕捉新分支的创建和销毁,也可以直接从 Git 本身执行octo run-runbook。GitHub 等流行的托管服务提供工具,允许在创建和销毁分支时运行脚本,这使得 Octopus 中特性分支的创建和清理几乎是即时的。

看章鱼的特征分支

有了这些操作手册并适当触发后,我们现在可以回顾在 Octopus 中创建的资源来表示我们的特性分支。

以下是渠道:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

以下是环境:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

以下是生命周期:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

以下是目标:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这些是账目:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这里是发布创建,我们选择一个要部署到的通道/特性分支,并让 Octopus 匹配通道规则来为我们选择正确的特性分支工件:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

结论

Terraform 应用和销毁步骤,与 Octopus Terraform 提供程序一起,为我们提供了创建元步骤以在 Octopus 中实现短期特性分支所需的工具。由于 Terraform 的幂等性质,我们有一组可靠地管理我们短暂的 Azure 和 Octopus 资源的健壮步骤。

通过一些自定义脚本来同步 Octopus 与 Git 分支和调度触发器或来自 GitHub 等托管平台的直接触发器,我们可以确保 Octopus 反映我们代码库中正在开发的功能分支。

观看网络研讨会

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

VIDEO

我们定期举办网络研讨会。请参见网络研讨会第页,了解过去的网络研讨会以及即将举办的网络研讨会的详细信息。

愉快的部署!

特征优先化:投票胜过一切吗?-章鱼部署

原文:https://octopus.com/blog/feature-prioritization

在我们的用户之声网站上,功能需求和建议通常介于两个极端之间:

  1. 使我能够做一些目前不可能做的事情
  2. 让目前可能做的事情变得稍微容易一点

作为一个软件发行商,我们的资源是有限的,我们不能对每一个建议都采取行动。对要采取行动的建议进行优先排序是很重要的,但这通常看起来是随机的,尤其是当投票数很少的项目在投票数很多的项目之前实施时。区分特性的优先级应该完全由投票决定吗?或者这应该是一个判断电话?

在决定采纳哪些建议时,我认为有许多因素需要考虑:

  • 有多少人想要这个功能,他们想要多少(投票)?
  • 有可接受的变通方法吗?
  • 变通方法有多困难/理想?
  • 该特性实现起来有多困难?
  • 支持该功能的持续成本是多少?
  • 我们能从这个特性中获益多久?

三个例子

举例来说,这里有三个关于 Octopus Deploy UserVoice 网站的建议:

  1. 安排发布 (18 票)
  2. 选择多个环境部署到 (40 票)
  3. 恢复对 ie 8 的支持 (3 票)

当您在 Octopus 中部署一个版本时,部署当前会立即执行。第一个建议意味着用户可以指定部署开始的时间。它可能看起来像这样:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

当前的解决方法是要么在凌晨 2 点进入办公室,要么在凌晨 2 点安排一个可能运行也可能不运行的任务,该任务使用我们的 API 或命令行工具来触发部署。

第二个建议是,我们可以选择多个环境,而不是选择一个环境进行部署:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

解决方法是简单地部署到一个环境,回到这个屏幕,选择下一个环境,然后再次部署。或者使用我们的 API 或命令行工具并指定要部署到的多个环境。

第三个建议不言自明——恢复对 IE8 的支持。目前我们只支持 IE9 或以上版本。

我们应该先做哪一个?

前两个建议可能有相同的难度——它们都可以很快完成。它们也是非常孤立的特性,不会影响产品中的许多其他特性。一旦我们构建了它们,并添加了一些测试来确保它们能够工作,这些就是我们可以忘记的特性。我喜欢这类功能。

“多种环境”比“预定部署”的票数多。但是“预定部署”的解决方法远不如“多环境”的解决方法理想。所以总的来说,我们可能会首先实现“预定部署”,尽管它有一半的票数。

IE 8 支持怎么样?

这个很有意思。第一,很难;我们使用的一些库也不能很好地支持 IE8,所以我们必须解决这个问题。然后,在我们说我们“支持它”之前,我们必须测试应用程序的所有/大部分都是可用的。

但像这样的功能伴随着持续的成本:每当我们实现一个新功能,我们也必须用 IE 8 测试它。不像其他两个,我们永远不能“忘记”这个特性。与自动化浏览器验收测试相比,编写一个集成测试来确保预定的部署在正确的时间发生要容易得多。

IE8 的变通办法也很有趣:变通办法是存在的(升级 IE,换一个不同的浏览器)。但由于成本(或政治)的原因,这些变通办法虽然简单,但并不是所有客户都能接受的。老实说,我发现当一个解决方案存在,但由于这些原因被拒绝时,我比解决方案很难(例如,凌晨 2 点上班)时更少同情。

还有一个事实是,IE8 的市场份额正在萎缩;我们可以花一周的时间来增加对它的支持,再加上额外的时间来测试我们增加的每个功能/回应关于 IE8 问题的错误报告,然后在 6 个月内,无论如何没有人会需要它。

为此,我决定将 IE8 建议标记为拒绝。不仅仅是比其他两个特性优先级低;由于持续的支持成本和最终的淘汰,这实际上是一个负面特性。我可以让它开放,以避免伤害建议者的感情,但现实是,即使它有 50 票,我们只是永远不会这样做。

我很想知道:你同意这个推理吗?确定功能优先级时,还应该考虑哪些因素?建议的受欢迎程度应该是关键驱动因素吗?

特色步骤模板:HTTP -测试 URL - Octopus Deploy

原文:https://octopus.com/blog/featured-step-template-http-test-url

无论是预热 web 服务器还是简单地测试一个新的部署,点击一个 URL 并验证结果是 web 应用程序部署过程中常见的事情。

虽然总是有可能用 Octopus Deploy 为这个编写一个 PowerShell 步骤, Sporting Solutions 已经足够友好地将这个打包到一个易于重用的模板中,用于 Octopus Deploy 库

因为这是我们第一次在 Octopus Deploy 博客上展示贡献的 step 模板,所以让我们来看看如何将它引入到您的 Octopus 项目中。

导入模板

首先要做的是访问库并找到模板。你可以在这里找到 HTTP - Test URL 模板。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

该库本身提供了模板的快速概述,以便您可以确定它是否符合您的要求;不出所料,这个页面上最重要的是绿色的**“复制到剪贴板”**按钮。

现在按它来检索描述模板的 JSON 文档。

接下来,登录你的 Octopus Deploy 服务器,访问库>步骤模板。在页面的右上角有一个导入链接——点击它,然后粘贴你刚刚从网站上复制的 JSON。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

一旦您点击了 Import ,就会显示模板——如果您愿意,现在可以调整参数或脚本,但除此之外,模板已经准备好用于您的部署过程。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用模板

假设您有一个安装 web app 的部署流程的项目,第一步是打开项目>流程选项卡,并选择添加步骤按钮。在显示的列表底部,您会看到刚刚导入的模板的名称。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

当您单击模板名称来创建新步骤时,您将看到每个模板参数的输入字段。在 HTTP - Test URL 的情况下,这是要测试的 URL、预期的状态代码和超时值。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

本例中的 URL 等步骤模板参数的工作方式就像普通的 Octopus 步骤参数一样——它们可以使用#{Variable}替换来绑定,每个参数本身作为一个变量公开,可以在其他地方使用,例如在 PowerShell 脚本中。

结果!

最后——这是最激动人心的部分:)—当您创建并部署项目的新版本时,该步骤将会运行,结果将会打印到部署日志中。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们真的很喜欢 HTTP - Test URL 模板,因为尽管它非常简单,但它是使用 step 模板和 Octopus Deploy 库可以做什么的一个很好的例子。如果您还没有浏览过这个库,您可能会惊讶地发现有多少模板已经可用了。

如果你已经在编写 step 模板并想与 Octopus 社区分享,现在参加我们的库竞赛还不算太晚;如果你提交一个模板,并在 6 月底前被接受,你就可以自豪地拥有一个 Octopus Deploy 马克杯!

愉快的部署!

二月社区综合报道-八达通部署

原文:https://octopus.com/blog/february-community-roundup

部署像 Octopus 这样的产品的一个真正酷的事情是当客户开始写他们自己的关于它的博客帖子。我们将开始整理这些帖子的列表,以便您可以看到世界各地的其他人在做什么。

这是二月份发生的事情的综述(我知道晚了几天)。

IANA 任务

首先,当 IANA 正式承认我们的触手通信端口(10933)时,我们有了一个“哇,太酷了”的时刻。

在社区里

最近不是只有我们在忙。我们的一些优秀客户已经写了关于他们成功使用 Octopus Deploy 的博客文章。

我们必须对卡罗林·克莱弗大呼小叫,她写了一系列关于向她的组织和所有客户介绍 Octopus Deploy 的文章。听起来像一次伟大的旅行!同样是关于文化变革的话题, Martyn Frank 在博客上讲述了他在引入 Octopus 和自动化后对团队文化的体验。

我们的朋友伊恩·波林继续他关于 Octopus 变化的帖子,并对 3.0 中发生的事情做了很好的总结。

Gregor Suttie 也已经开始给 Octopus Deploy 试驾,我们期待着那里更多的帖子。

最后,大卫·罗伯兹发布了名为 OctoPygmy 的谷歌 Chrome 扩展来定制界面,并对环境和仪表盘进行过滤和分组。看起来很酷!

这是二月份的综述,请关注几周后的下一篇!

节日科技日历-八达通黑客马拉松-八达通部署

原文:https://octopus.com/blog/festive-tech-calendar-hackathon

Octopus Deploy 是今年节日科技日历的一部分,举办黑客马拉松比赛。

黑客马拉松的目标是使用 Octopus Deploy 部署 Azure Web 应用程序。

什么是节日科技日历?

Festive Tech Calendar 是一个为期一个月的免费社区科技活动。邀请所有技术社区的人提交演讲、内容和竞赛,并参与活动。

章鱼黑客马拉松

什么:使用 Octopus Deploy 部署一个 Azure Web 应用,然后填写我们的黑客马拉松提交表单

何时:在 2021 年 12 月 1 日星期三至 12 月 15 日星期三之间完成练习

奖品:三个八爪鱼布包之一

黑客马拉松指南

我们的指导方针很宽松:

  • 我们不介意你如何配置你的部署或者最终的 web 应用是什么,只要你使用 Octopus Deploy 部署 Azure
  • 无论您的应用程序和流程是简单还是复杂
  • 您可以构建一个新的 web 应用程序或使用 Octopus 示例之一:
  • 你可以使用对你来说新的或者你熟悉的技术,从 ARM 模板、PowerShell、Terraform 到 GitHub 动作等等

首先,我们只是想让你玩得开心,学点东西!

如何进入

  • 使用现有的 Azure 帐户或注册一个 Azure 免费帐户
    • 如果你注册了 Azure 免费帐户,你将自动获得 30 天 200 美元的 Azure 点数和 12 个月有限数量的额外免费服务
  • 使用现有的 Octopus Deploy 实例或注册一个 Octopus Cloud 免费试用版
  • 使用 Octopus Deploy 部署 Azure Web 应用程序
  • 完成黑客马拉松后,请在 2021 年 12 月 15 日星期三格林威治时间下午 5 点之前填写我们的黑客马拉松提交表格中的所有必填字段

优胜者

我们将从提交的作品中随机选出三名获胜者,他们将获得我们的一个礼品包:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们将在 2021 年 12 月 20 日星期一格林威治时间下午 2 点左右宣布获胜者。

随意写一篇博客,创建一个视频,或者发一条关于你的黑客马拉松实现的微博,标记@ octopus deploy;我们希望收到您的来信。

这些可选的活动不会给你额外的学分,但会传播一些社区精神。

细则
  • 购买或付款不是必要的,也不会增加你中奖的机会。
  • 我们将只考虑每人一份参赛作品。
  • 黑客马拉松不向 Octopus Deploy 的员工、代表或代理人开放,包括他们的所有直系亲属和家庭成员。
  • 黑客马拉松不向参与设计、制作、推广、执行或分发黑客马拉松的任何其他个人开放,包括他们的所有直系亲属和家庭成员。
  • 黑客马拉松提交期:格林威治时间 2021 年 12 月 1 日上午 8 点–格林威治时间 2021 年 12 月 15 日下午 5 点。
  • 获胜者将于 2021 年 12 月 20 日(星期一)格林威治时间下午 2 点左右宣布。

Octopus Deploy 入门

如果您以前没有使用过 Octopus Deploy,请查看以下资源:

愉快的部署!

Selenium 系列:通过 XPaths 和 CSS 选择器查找元素

原文:https://octopus.com/blog/selenium/6-finding-elements-by-xpaths-and-css-selectors/finding-elements-by-xpaths-and-css-selectors

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

在上一节中,我们通过 ID 属性搜索了想要与之交互的元素。使用 ID 属性非常方便,但是在现实世界的场景中,并不是每个想要与之交互的元素都有 ID,或者 ID 是由模板语言生成的,而不是静态的。

与 ID 属性不同,网页中的每个元素都有唯一的 XPath。XPath (XML Path Language)是一种查询语言,用于从类似 XML 的文档中选择节点,比如我们的例子中的 HTML。

XPath 查询可能会变得非常复杂。独特的 XPaths 看起来像//*[@id="request-summary"]/div/div[2]/div/form/div[2]/input并不罕见。

幸运的是,Chrome 和其他浏览器提供了一种为元素生成唯一 XPaths 的简单方法。右键单击Elements选项卡中的元素并选择复制➜复制 Xpath 会将标识该元素的最简洁的唯一 Xpath 放入剪贴板。这个功能意味着您不需要理解 XPath 的本质细节就可以使用它们,因为您可以让浏览器为您生成 XPath。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

对于我们的测试网页,Chrome 生成的 XPath 利用了这样一个事实,即元素都有唯一的 ID 属性,这导致了相对简洁的 XPath,如//*[@id="button_element"]

为了使用 XPaths 而不是 id 来定位页面中的元素,我们需要向AutomatedBrowser接口添加四个新方法:

void clickElementWithXPath(String xpath);

void selectOptionByTextFromSelectWithXPath(String optionText, String xpath);

void populateElementWithXPath(String xpath, String text);

String getTextFromElementWithXPath(String xpath); 

反过来,我们将这四个方法的实现添加到AutomatedBrowserBase类中:

@Override
public void clickElementWithXPath(final String xpath) {
  if (getAutomatedBrowser() != null) {
      getAutomatedBrowser().clickElementWithXPath(xpath);
  }
}

@Override
public void selectOptionByTextFromSelectWithXPath(final String optionText, final String xpath) {
  if (getAutomatedBrowser() != null) {
    getAutomatedBrowser().selectOptionByTextFromSelectWithXPath(optionText, xpath);
  }
}

@Override
public void populateElementWithXPath(final String xpath, final String text) {
  if (getAutomatedBrowser() != null) {
    getAutomatedBrowser().populateElementWithXPath(xpath, text);
  }
}

@Override
public String getTextFromElementWithXPath(final String xpath) {
  if (getAutomatedBrowser() != null) {
    return getAutomatedBrowser().getTextFromElementWithXPath(xpath);
  }
  return null;
} 

然后我们在WebDriverDecorator类中定义这些方法的实现。

注意,我们在这些方法中调用By.xpath()而不是By.id()。这是我们根据元素的 XPath 搜索元素的方法:

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

@Override
public void selectOptionByTextFromSelectWithXPath(final String optionText, final String xpath) {
  new Select(webDriver.findElement(By.xpath(xpath))).selectByVisibleText(optionText);
}

@Override
public void populateElementWithXPath(final String xpath, final String text) {
  webDriver.findElement(By.xpath(xpath)).sendKeys(text);
}

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

最后,我们创建了一个新的测试方法,它与我们的示例 web 页面进行交互,但是这次利用了新的基于 XPath 的方法:

@Test
public void formTestByXPath() throws URISyntaxException {

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

  try {
    automatedBrowser.init();

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

    automatedBrowser.clickElementWithXPath("//*[@id=\"button_element\"]");
    assertEquals("Button Clicked", automatedBrowser.getTextFromElementWithId("message"));

    automatedBrowser.populateElementWithXPath("//*[@id=\"text_element\"]", "test text");
    assertEquals("Text Input Changed", automatedBrowser.getTextFromElementWithId("message"));

    automatedBrowser.populateElementWithXPath("//*[@id=\"textarea_element\"]", "test text");
    assertEquals("Text Area Changed", automatedBrowser.getTextFromElementWithId("message"));

    automatedBrowser.selectOptionByTextFromSelectWithXPath("Option 2.1", "//*[@id=\"select_element\"]");
    assertEquals("Select Changed", automatedBrowser.getTextFromElementWithId("message"));

    automatedBrowser.clickElementWithXPath("//*[@id=\"radio3_element\"]");
    assertEquals("Radio Button Changed", automatedBrowser.getTextFromElementWithId("message"));

    automatedBrowser.clickElementWithXPath("//*[@id=\"checkbox2_element\"]");
    assertEquals("Checkbox Changed", automatedBrowser.getTextFromElementWithId("message"));

    automatedBrowser.clickElementWithXPath("//*[@id=\"image_element\"]");
    assertEquals("Image Clicked", automatedBrowser.getTextFromElementWithId("message"));

    automatedBrowser.clickElementWithXPath("//*[@id=\"div_element\"]");
    assertEquals("Div Clicked", automatedBrowser.getTextFromElementWithId("message"));
  } finally {
    automatedBrowser.destroy();
  }
} 

像 XPaths 一样,HTML 文档中的所有元素都有一个唯一的 CSS 选择器来标识它们。

CSS 选择器与 CSS 规则集中使用的标识符相同。如果你做过任何网页开发,那么你很可能熟悉 CSS 选择器。但即使你不是,Chrome 和其他浏览器也提供了一种为 HTML 元素生成 CSS 选择器的方法。在 Chrome 中,右击Elements标签中的元素,选择复制➜复制选择器。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在我们的例子中,因为我们正在交互的元素都有 ID 属性,这个菜单选项将把一个 CSS 选择器如#button_element放入剪贴板。

使用 CSS 选择器的过程与我们支持 XPaths 的过程非常相似。

我们在AutomatedBrowser接口中定义新方法:

void clickElementWithCSSSelector(final String cssSelector);

void selectOptionByTextFromSelectWithCSSSelector(final String optionText, final String cssSelector);

void populateElementWithCSSSelector(final String cssSelector, final String text);

String getTextFromElementWithCSSSelector(final String cssSelector); 

然后,我们在AutomatedBrowserBase类中提供默认实现:

@Override
public void clickElementWithCSSSelector(final String cssSelector) {
  if (getAutomatedBrowser() != null) {
    getAutomatedBrowser().clickElementWithCSSSelector(cssSelector);
  }
}

@Override
public void selectOptionByTextFromSelectWithCSSSelector(final String optionText, final String cssSelector) {
  if (getAutomatedBrowser() != null) {
    getAutomatedBrowser().selectOptionByTextFromSelectWithCSSSelector(optionText, cssSelector);
  }
}

@Override
public void populateElementWithCSSSelector(final String cssSelector, final String text) {
  if (getAutomatedBrowser() != null) {
    getAutomatedBrowser().populateElementWithCSSSelector(cssSelector, text);
  }
}

@Override
public String getTextFromElementWithCSSSelector(final String cssSelector) {
  if (getAutomatedBrowser() != null) {
    return getAutomatedBrowser().getTextFromElementWithCSSSelector(cssSelector);
  }
  return null;
} 

用新方法更新了WebDriverDecorator类。我们使用By.cssSelector()方法通过元素的 CSS 选择器来搜索元素:

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

@Override
public void selectOptionByTextFromSelectWithCSSSelector(final String optionText, final String cssSelector) {
  new Select(webDriver.findElement(By.cssSelector(cssSelector))).selectByVisibleText(optionText);
}

@Override
public void populateElementWithCSSSelector(final String cssSelector, final String text) {
  webDriver.findElement(By.cssSelector(cssSelector)).sendKeys(text);
}

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

最后,我们将所有新方法与使用 CSS 选择器定位元素的新测试结合在一起:

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

  try {
    automatedBrowser.init();

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

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

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

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

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

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

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

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

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

我们已经看到了三种不同的方法来识别 HTML 页面中的元素:使用 ID、使用 XPaths 和使用 CSS 选择器。但是哪种方式是最好的呢?

在有效的 HTML 中,id属性必须是唯一的。在一个设计良好的页面中,id属性还为定义它的元素提供了一些有意义的上下文。当开发人员更新应用程序时,元素在页面中移动时,通常也会保留相同的id属性。这使得通过 ID 查找元素成为在页面中定位元素的最简洁可靠的方式。

不幸的是,因为id属性必须手动分配给一个元素,所以您不能依赖于您希望与之交互的具有id属性的元素。事实上,以我的经验来看,在针对现实世界的应用程序编写测试时,很少有id属性可用。

id属性不同,所有元素都可以使用 XPaths 和 CSS 选择器来定位。因此,在没有可用 ID 的情况下,您将不得不求助于这些定位器中的一个。

CSS 选择器往往比 XPaths 更熟悉,因为 CSS 选择器被 web 开发人员用来设计 CSS 规则集。出于这个原因,我推荐 CSS 选择器而不是 XPaths。

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

Selenium 系列:Firefox 调试技巧- Octopus 部署

原文:https://octopus.com/blog/selenium/12-firefox-debugging-tips/firefox-debugging-tips

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

我在使用代理时多次遇到的一个常见问题是 Firefox 中一个微妙的错误配置,它会导致抛出错误。

为了模拟这个错误,让我们尝试在BrowserMobDecorator类中配置 SOCKS 代理。

SOCKS 代理用于代理 TCP 数据包,这意味着它们可以用于 HTTP、HTTPS、FTP 和一系列其他高级协议。我们不会使用 BrowserMob 作为 SOCKS 代理,但是在这里配置它是演示错误配置错误的一个有用的方法。

我们通过调用seleniumProxy.setSocksProxy(proxyStr)来配置 SOCKS 代理:

@Override
public DesiredCapabilities getDesiredCapabilities() {

  proxy = new BrowserMobProxyServer();

  proxy.start(0);

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

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

  desiredCapabilities.setCapability(CapabilityType.PROXY, seleniumProxy);

  return desiredCapabilities;
} 

在 Firefox 中以这种配置运行测试将导致抛出下面的异常:

org.openqa.selenium.SessionNotCreatedException: InvalidArgumentError: Expected [object Undefined] undefined to be an integer
Build info: version: '3.12.0', revision: '7c6e0b3', time: '2018-05-08T14:04:26.12Z'
System info: host: 'Christinas-MBP', ip: '192.168.1.84', os.name:
'Mac OS X', os.arch: 'x86_64', os.version: '10.13.5', java.version: '1.8.0_144'
Driver info: driver.version: FirefoxDriver
remote stacktrace: WebDriverError@chrome://marionette/content/error.js:178:5
InvalidArgumentError@chrome://marionette/content/error.js:305:5
assert.that/<@chrome://marionette/content/assert.js:405:13
assert.integer@chrome://marionette/content/assert.js:256:10
assert.positiveInteger@chrome://marionette/content/assert.js:274:3
fromJSON@chrome://marionette/content/session.js:291:28
match_@chrome://marionette/content/session.js:458:23
fromJSON@chrome://marionette/content/session.js:427:12
GeckoDriver.prototype.newSession@chrome://marionette/content/driver.js:693:25
despatch@chrome://marionette/content/server.js:293:20
execute@chrome://marionette/content/server.js:267:11
onPacket/<@chrome://marionette/content/server.js:242:15
onPacket@chrome://marionette/content/server.js:241:8
_onJSONObjectReady/<@chrome://marionette/content/transport.js:500:9 

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

从异常中的消息来看,似乎某些远程 JavaScript 代码导致了一个错误。但是这段 JavaScript 代码在哪里,我们如何调试错误呢?

查看 JavaScript 堆栈跟踪,错误的来源似乎在这两行代码中。一个名为fromJSON()的方法断言一个整数是正的,这个断言失败了:

assert.positiveInteger@chrome://marionette/content/assert.js:274:3
fromJSON@chrome://marionette/content/session.js:291:28 

调试这个错误的关键是要理解文件chrome://marionette/content/session.js是和 Firefox 捆绑在一起的。如果你在 Firefox 中输入这个网址,你可以看到文件的来源。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在这种情况下,有问题的代码行是:

p.socksVersion = assert.positiveInteger(json.socksVersion); 

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

从这段代码中,我们可以推断出我们需要定义 SOCKS 代理的版本。

理论上,您可以用下面的代码在BrowserMobDecorator类中定义 SOCKS 代理版本:

@Override
public DesiredCapabilities getDesiredCapabilities() {
  // ...
  seleniumProxy.setSocksVersion(5);
  // ...
} 

实际上,WebDriver 库中的错误仍然会导致代码失败。然而,从这篇文章中得到的重要信息是,当您看到 Firefox stack traces 带有以chrome://marionette/开头的 URL 时,您可以通过直接在 Firefox 中输入 URL 来访问这些文件的源代码,以便调试根本原因。

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

詹金斯 X - Octopus 部署初探

原文:https://octopus.com/blog/first-look-at-jenkins-x

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作为一个免费的开源构建服务器,Jenkins 被数百万人使用,所以大多数开发者都使用过或者至少听说过 Jenkins。像大多数构建服务器一样,Jenkins 通常安装在服务器上,以使用源代码,在构建代理上执行构建过程,并部署或发布生成的工件。

从概念上讲,这个构建服务器的模型对我来说很容易理解,它适用于当今大多数流行的解决方案,如 Team City、Azure DevOps、Bamboo 等。所以第一次和 Jenkins X 合作的时候,很自然的,我尝试用同样的概念模型去理解。这被证明是一个错误。

我真的很难理解 Jenkins X 是什么,但是在一些尝试和错误之后,我回去重读了关于第页的Jenkins X。深藏在文本中的两句话对于理解詹金斯 X 至关重要:

詹金斯 X 固执己见。

要注意的关键是,你需要从你可能已经有的任何詹金斯经验中清除你的头脑。

内化这两个陈述对于理解詹金斯 X 是什么是至关重要的。考虑到这一点,让我们从高层次上来看看詹金斯 x。

它始于库伯内特星团

从字面上看,Kubernetes 是一个容器编排器。你描述你想要运行的容器,它们如何相互通信,它们需要什么资源,Kubernetes 做执行一切的艰苦工作。通常,Kubernetes 托管长期运行的应用程序,如不断等待请求的 web 服务器,Kubernetes 的一个重要特性是它将监控这些长期运行的进程,并在它们失败时重新启动它们。

这种对 Kubernetes 的传统看法是你首先要忘记的。要理解 Jenkins X,可以把 Kubernetes 更像一个云操作系统。

你使用apt-get在 Linux 中安装一个应用程序,同样,Kubernetes 可以使用 Helm 安装应用程序。

就像您可以使用docker CLI 运行短期命令来构建您的应用程序和 Docker 映像一样,Kubernetes 可以使用ska fold构建软件和 Docker 映像。

由于您将托管 Docker 图像或其他图像工件库,如 Nexus 作为随操作系统启动的服务,因此您可以将这些相同的应用程序部署到 Kubernetes。

Jenkins X 将您的 Kubernetes 集群视为一个运行构建的环境,一种托管工件存储库的方式,一个 Helm charts 的安装目标,最后,一个部署和运行您构建的应用程序的地方。

通过安装 Jenkins X,您将拥有一个自包含的 Kubernetes 集群,其中包含精选和定制配置的服务,可以开始构建和部署应用程序。

这扩展到了您的本地开发环境

我习惯于我的本地开发环境与中央构建服务器完全分离。为了让我的代码进入构建服务器,我通常将它推到一个中央 GIT 存储库,转到 CI 服务器,将一个项目指向 GIT repo,配置构建,然后启动它。

如果我的 CI 服务器提供了 CLI 工具,它专门用于管理 CI 服务器。这样的工具对我正在处理的代码没有任何概念。

同样,忘记您从传统 CI 服务器中学到的一切。Jenkins X 对如何构建和部署您的代码有自己的看法,并不羞于为您配置一切。

要让 Jenkins X 构建您的现有代码,您可以运行jx import。运行该命令将向您的项目添加几个文件,包括jenkins-x.yml(这是 Jenkins X 管道)、Dockerfile(这构建了 Docker 映像)、skaffold.yaml(这是ska fold 项目配置)和一个charts目录(这提供了掌舵图模板)。

Jenkins X 然后将所有代码放入 GIT 存储库中,推送它,并开始构建。

正如您从下面的输出中看到的,Jenkins X 将许多不同的工具和服务结合在一起,以创建一个可重复的构建过程:

PS C:\Users\Matthew\Downloads\ThymeleafSpring> jx import
WARNING: No username defined for the current Git server!
? Do you wish to use mcasperson as the Git user name: Yes
The directory C:\Users\Matthew\Downloads\ThymeleafSpring is not yet using git
? Would you like to initialise git now? Yes
? Commit message: Initial import

Git repository created
selected pack: C:\Users\Matthew\.jx\draft\packs\github.com\jenkins-x-buildpacks\jenkins-x-kubernetes\packs\maven
? Which organisation do you want to use? mcasperson
replacing placeholders in directory C:\Users\Matthew\Downloads\ThymeleafSpring
app name: thymeleafspring, git server: github.com, org: mcasperson, Docker registry org: kubernetes-demo-198002
skipping directory "C:\\Users\\Matthew\\Downloads\\ThymeleafSpring\\.git"
Using Git provider GitHub at https://github.com
? Using organisation: mcasperson
? Enter the new repository name: ThymeleafSpring
Creating repository mcasperson/ThymeleafSpring
Pushed Git repository to https://github.com/mcasperson/ThymeleafSpring
Creating GitHub webhook for mcasperson/ThymeleafSpring for url http://hook.jx.35.194.232.107.nip.io/hook

Watch pipeline activity via:  jx get activity -f ThymeleafSpring -w
Browse the pipeline log via:  jx get build logs mcasperson/ThymeleafSpring/master
You can list the pipelines via: jx get pipelines
When the pipeline is complete: jx get applications

For more help on available commands see: https://jenkins-x.io/developing/browsing/ 

构建和部署将它们联系在一起

如果说传统 CI 服务器有一个好处的话,那就是因为您手动设置了一切,所以您对代码如何从源代码库流向最终部署有一个相当好的想法。

因为 Jenkins X 为我们做了这么多工作,所以很难理解构建过程中实际发生了什么。所以让我们来看看幕后的一些工作。

作为 Kubernetes 集群初始化的一部分,Jenkins X 安装了 Nexus 存储库管理器。构建完成后,我们可以看到我们的应用程序所依赖的 Java 依赖项现在被缓存在本地。这种缓存意味着任何后续构建都将更快地完成。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Jenkins X 安装的另一个服务是 ChartMuseum,这是一个 Helm 存储库。通过下载index.yaml文件,我们可以看到 Jenkins X 已经创建了一个舵图并发布到这个内部存储库中:

curl http://chartmuseum.jx.35.194.232.107.nip.io/index.yaml
apiVersion: v1
entries:
  jenkinx-spring-demo:
  - apiVersion: v1
    appVersion: 0.0.1
    created: "2019-08-28T19:25:22.445582847Z"
    description: A Helm chart for Kubernetes
    digest: 9e048b78247da2a28562771742454484dbaf35b10d14a0c0a668c3ba826d02c4
    icon: https://raw.githubusercontent.com/jenkins-x/jenkins-x-platform/master/images/java.png
    name: jenkinx-spring-demo
    urls:
    - charts/jenkinx-spring-demo-0.0.1.tgz
    version: 0.0.1
generated: "2019-08-28T19:32:00Z" 

Jenkins X 还负责将 Docker 映像发布到本地容器注册中心。因为我们在 Google Cloud Kubernetes 集群中运行 Jenkins X,所以 Jenkins X 默认使用 Google 容器注册表。然而,如果该服务不可用,Jenkins X 就会在 Kubernetes 集群中安装 Docker 注册中心。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

最后,我们可以看到 Helm 图表已经部署在集群中,并且入口规则已经由 exposecontroller 创建,这导致我们的应用程序公开了一个自定义主机名。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

结论

当你第一次遇到 Jenkins X 时,可能很难理解它到底是什么。如果从这篇文章中有什么收获的话,那就是忘记你所知道的关于 CI 服务器的一切,并欣赏 Jenkin X 本身。

Jenkins X 不仅仅是一种运行构建的方式,它提供了一个完整的构建生态系统和自以为是的工作流,从准备本地代码结构开始,到部署 Kubernetes 应用程序结束。

欣赏 Jenkins X 需要从大多数开发人员与开发管道交互的方式上进行思维转变,但好处是一个结构良好的工作流和紧密配置的工具链。

我在 Octopus - Octopus Deploy 工作的第一年

原文:https://octopus.com/blog/first-year-working-at-octopus

本周,我迎来了在 Octopus 工作的一周年纪念日,我想分享一下在这里工作的感受并谈谈我们的文化会很棒。我之前在我的个人博客上写了我的第一印象,但是我想更深入地挖掘一下。

船舶

首先也是最重要的,章鱼有航运文化!我加入 Octopus 的时候,团队正准备推出 Octopus 3.0。这是一个巨大的里程碑,看着最后一个 bug 被击败并发布出去是令人兴奋的。也就是说,我实际上已经开始着手改进Octopus.com的功能,包括增强我们的销售和许可渠道,以及构建超级升级页面,让客户更容易升级和更新他们现有的许可证。

不久之后,我开始在 Octopus Deploy 上工作,进行错误修复和增强,并为更大的功能发布做出贡献。很高兴看到像ChannelsElastic EnvironmentsMulti-tenancy这样的新功能成形并进入客户手中。我喜欢我们为新功能发布 RFC(征求意见),社区提供反馈来帮助塑造产品。我喜欢的另一件事是,我们定期发布新版本,只要它们准备好了(有时每周几次),而不是更长的发布周期。这是尽早向我们的客户提供最新功能和修复的好方法。

我们甚至自动化了我们的发布过程,这样任何人都可以在任何时间轻松完成。这大大减少了运输的摩擦!有趣的是,我们确实用章鱼装运章鱼!

我们即将发布 Octopus 3.4,并开始一些非常酷的新工作。这是一个非常令人兴奋的时刻在八达通部署!

信任

我们定期发货,并努力发送高质量的版本!信任是实现这一目标的关键。无论我们做什么,整个团队都相互信任和支持。从我们的创始人到最新的团队成员,每个人都愿意随时分享或帮助。这是非常重要的,因为八达通是一个远程友好的公司,我们没有一个深层次的管理。除了需要合作的时候,团队的大部分成员都在家工作。Octopus 的总部设在澳大利亚的布里斯班,我们在中央商务区或市中心有一个办公室,在那里我们可以工作和协作。这是专注的远程工作和面对面交流的完美平衡。

我们是章鱼!

基于信任,八达通建立了一个伟大的团队,他们关心自己所做的事情。每个人都有长处和短处,但我们都愿意分享和帮助他人。周围都是很棒的人,感觉很棒。我的同事达米安·布雷迪指出,我们称章鱼为we,而不是说itthe company。我们都乐于谈论章鱼并代表它。我们都是被信任的。我们是一个团队。我们是章鱼!

当章鱼开始参加会议并且我们开始与人们面对面交谈时,这一点得到了加强。我们代表公司帮助人们理解我们解决的问题和我们提供的价值。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

远程工作

向远程工作的转变既有趣又有益。我绝对不怀念每天上下班的时间,这让我有更多时间在家陪妻子和两个年幼的儿子。家庭干扰有时会很有挑战性,但好处远远大于代价。我经常在推特上谈论我的工作生活平衡,我从来没有这么好过。

上次我写了以下内容,它仍然是非常正确的。

我可以更经常地见到我的妻子和孩子,我们一起吃午饭,如果我们需要为家庭跑腿,我也会在身边。这极大地改善了我与妻子和孩子的关系,我不认为我会回到过去。

成长

我以第 12 名员工的身份加入了 Octopus,我们的团队页面现在有 19 人,不久将有 2 人加入。我们已经超出了我们的办公室,因此团队已经从每周三与办公室的每个人一起进行 sprint 审查转变为在不同的日子进行协作。我们也开始做每周一次的远程友好 TL;每周三进行简短的演示,其他时间进行更深入的演示。总的来说,变化是积极的,但我们仍在学习和适应。

我们成长过程中最大的变化是,我们正在过渡到一种开放的分配管理风格,在这种风格下,所有团队成员在日常工作中都有很大的自由度。 Paul 帮助设定总体方向,但我们可以自由地推出新功能,并选择我们认为可以做出最大贡献的团队/任务组。

章鱼团队的新成员与一个伙伴配对,他们在办公室一起工作一周,或者直到他们适应在家工作。我是几个新团队成员的好朋友,这是一个了解新团队成员并让他们快速开始工作的好方法。我们还举办了为期两天的迷你章鱼大学训练营,以帮助我们的一些技术水平较低的团队成员了解软件开发的挑战和手动部署的痛苦。这很有趣,每个人都学到了很多东西!

包裹

我喜欢在八达通的第一年,我真的期待更多!再说一次,可以肯定地说,我是一只快乐的章鱼,未来非常光明。

灵活工作-杰森的故事-章鱼部署

原文:https://octopus.com/blog/flexible-work-week-jason

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

弹性工作在很多方面都可以改变游戏规则。我是 Jason,Octopus Deploy 的云架构师,我将解释为什么它不仅改变了我的游戏规则,而且改变了我的生活。

当我在 2017 年 6 月加入 Octopus Deploy 时,我已经在悉尼生活了近 19 年,老实说,我已经准备好改变了。

在现代悉尼,你有三种生活方式。你要么为住在 CBD 附近支付过高的租金,要么应对非同寻常的通勤,要么试图在两者之间取得平衡。

在我 15 公里的自行车通勤路上与危险的司机发生了太多的冲突后,我放弃了寻求平衡的努力,将我工资的一大部分交给了 Rozelle 的一个仓库,那里距离我以前的工作单位只有 3 公里。当然,我没有一天花三四个小时在火车上,我也不太可能被骑自行车的穿白色衣服的人撞倒,但我仍然压力很大,经常不开心。

Octopus Deploy 是一个远程优先的工作场所。我的许多同事在布里斯班,其他人在墨尔本、阿德莱德、美国,甚至阿根廷。加入 Octopus 后不久,我几乎准备好收拾行李搬到布里斯班加入我的新团队。

当然,结果是章鱼云团队大部分时间都在墨尔本,很适应远程工作,所以我留在了原地。但是悉尼机器仍然在磨我,我骑自行车更少,我现在没有明显的理由支付过高的租金。

老实说,我很不开心。更重要的是,我意识到多年来我一直不快乐。

我需要改变。

于是一个想法开始萌发。

几年前,当我开始认真地骑自行车时,我去了趟维多利亚东北部的布莱特镇。第一次拜访之后,我会一次又一次地回去。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

布莱特坐落在群山之中,是严肃的公路自行车手和山地车手所熟知的。它被纵横交错的森林包围,有适合全天探险的极好的砾石小径,在很短的车程内有五六个山地自行车公园,并且是澳大利亚一些最好的公路骑行的基地。因为它是一个旅游城市,所以它的基础设施也很好,靠近两个滑雪场,有足够的咖啡馆、酒吧和娱乐场所,让养尊处优的城市居民在周末也能开心。

我开始怀疑。很久以来,我一直想在布莱特买一个度假基地。我能不能…住在那里?

所以我问老板,如果我搬到维多利亚,他会不会介意。

答案是一个响亮的“是的,当然”。我仍然不太确定是否有人知道我要去维多利亚的哪里,但是我有一个肯定的答案,所以一切都在进行中。我在布莱特郊外的 Porepunkah 找到了一套有四间卧室和一间书房的房子,价格远低于我在悉尼的一间卧室。我租了辆卡车就走了。

【T2 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

最初有一些困难,比如安装有线互联网(第一个月我在 4G 上工作),但一旦我安顿下来,我发现我更放松,更专注,总体上更快乐。生活更简单了,我的作息更有规律了,而且我每天都可以看山!

这花了一段时间,但最终我意识到在一个城市里,噪音主宰了你的生活。我花了一段时间才改掉到处戴降噪耳机的习惯——这个习惯我还没有完全改掉。出去喝杯咖啡或遛狗而不被交通噪音打扰,这是一种简单而不被重视的快乐。

我搬到布莱特已经九个月了,野马也不能把我拖出去。当然,在城市里你不会遇到一些小问题,但是每天看到山和树的价值意味着我可以等几天来修理我的冰箱。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们不要自欺欺人了,在乡下修理你的冰箱可能需要几天时间。这不仅仅是老生常谈,这里的事情确实进展得更慢。我的镇上甚至没有送货上门的邮件。幸运的是,Octopus 的灵活性意味着我可以在一天中抽出时间来做一些需要做的小工作,并在以后弥补时间。有时候,我甚至可以挤出一个下午骑一两个小时的自行车,我发现这非常有用,因为山区冬天冰冷的早晨和黑暗的夜晚开始刺痛。

如果你是一个非常“随叫随到”的人,你重视城市生活提供的无限选择,你可能不会成功。但是,如果你能学会珍惜某种简单的生活,这可能正是你想要的。

使用 Flyway 和 Octopus 执行容器的数据库部署——Octopus Deploy

原文:https://octopus.com/blog/flyway-deployments-with-execution-containers

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我最近使用了 Flyway 和 Octopus Deploy 来部署数据库更改。Flyway 给我留下了深刻的印象,除了我必须将工具和 Java 运行时引擎(JRE)捆绑在一起,或者将它们预安装在一个 worker 上。

在 Octopus Deploy 的 2020.2 版本中,我们引入了执行容器特性。执行容器使用 Docker 映像来管理依赖性。

这篇文章介绍了如何使用执行容器和 Flyway 在 Octopus Deploy 中配置数据库部署。

执行容器基础

我不喜欢在我的包中包含所有运行 Flyway 的二进制文件,因为这会导致包膨胀。在我的例子中,差别是 10 KB 对 90 MB。

作为一名开发人员,我还负责升级二进制文件,并将它们包含在我的 Git repo 中。我还避免在员工身上预装工具,因为这意味着每个人都在同一版本上,升级可能会影响每个人。

执行容器通过使用 Docker 映像来管理依赖性,从而解决了这两个问题。Docker 映像拥有所有必要的工具(JRE、Flyway、PowerShell 等。)已安装。您可以指定部署过程中使用的 Docker 映像和标记。当部署使用执行容器运行时,Calamari 执行 Docker run 命令。此外,Calamari 会自动将文件夹挂载到容器中。

任务日志显示类似于以下内容的命令:

docker run --rm  --env TentacleHome=/home/Octopus  -w /home/Octopus/Work/20210329204922-325128-24   -v /home/Octopus/Work/20210329204922-325128-24:/home/Octopus/Work/20210329204922-325128-24  -v /home/Octopus:/home/Octopus  index.docker.io/octopuslabs/flyway-workertools:latest 

您拥有的任何包都会自动提取到/home/Octopus/Work/[DATETIME]文件夹中。这发生在幕后。要从直接在 worker 上运行改为在执行容器上运行,只需单击一个单选按钮并提供包名。其他都一样。

飞行路线执行容器

Octopus Deploy 提供了你可以使用的官方 Docker 图片。不幸的是,这些图像不能在这个例子中使用,原因有二:

  1. 它们包括几十种需要从 Docker Hub 下载千兆字节以上数据的工具。
  2. 没有一张图片包含飞行路线。

为了克服这一点,我创建了一个 Docker 图像,你可以在这个例子中使用。我还创建了一个 GitHub 动作,它将每天运行一次,并在检测到新版本时构建一个新映像。

这个 Docker 映像的基础映像包括我们支持的最流行的脚本语言:PowerShell 和 Python。基于 Ubuntu 的镜像也支持 Bash。

脚手架

需要完成两个脚手架步骤:

  1. 安装 Flyway 数据库迁移步骤模板
  2. 添加一个 Docker 容器注册表外部提要,并将其指向https://index.docker.io

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Flyway 数据库迁移步骤模板

如果您在我们的社区步骤模板库中搜索Flyway,您会注意到许多 Flyway 步骤模板。 Flyway 数据库迁移步骤模板旨在取代旧的步骤模板。

这个新步骤模板的主要区别在于:

  1. 您可以选择任何命令(migrate、info、validate 等。)飞行路线支持。
  2. 我和 Redgate 的 Flyway 团队一起寻找流行的命令行开关。
  3. 支持免费和付费版本的 Flyway,使您能够进行模拟迁移,并能够使用 undo 命令。
  4. 它既可以在 Linux 上运行,也可以在 Windows 上运行。
  5. 它试图找到 Flyway 可执行文件,使得将 Flyway 包含在包中(如果您喜欢的话)或在执行容器中运行它变得容易。
  6. SQL 和 JAR 迁移都受支持。

我遵循了类似的模式,在最近的其他步骤模板中包含了更多的参数,例如:

我这样做是为了确保任何以-开头的参数都是 Flyway 命令行工具中的命令行开关

打包迁移脚本

我们的文档指导你构建你的包。然而,如果您只有 SQL 文件,就没有什么可构建的了。你只需要把文件夹打包到你的构建服务器上,然后推给 Octopus。

考虑这个例子: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

您需要在构建服务器上的db/src文件夹中运行 Octo Pack 命令。该包将包含这些文件夹和内容。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在构建包之后,您需要将它发布到 Octopus Deploy。为了验证概念,您不需要构建服务器。可以使用 7-Zip 之类的工具将文件夹压缩成Flyway.Test.1.0.0.zip,手动上传包。这就是我为这篇文章所做的。

然而,在概念验证之后,如果集成一个构建服务器是有意义的,我们有文档和博客文章来帮助你。

构建服务器的示例:

配置项目

现在我们可以配置项目了,因为我们已经上传了步骤模板、Docker 提要和包。

首先,创建一个项目。在这个例子中,我将使用名称 Flyway - Azure SQL 执行容器。参见我们的 samples 实例中的 Flyway 示例,了解如何使用执行容器。

变量

创建项目后,导航至变量并添加必要的变量。

我推荐命名空间变量,例如项目变量使用Project.[Component].[VariableName],库变量集变量使用[VariableSetName].[Component].[VariableName]。这将使在流程中插入变量时更容易找到它们。

  • Project.Database.ConnectionString:我要部署到的数据库的连接字符串。**请注意:**这是我的例子使用 SQL Server 和将其改为 Oracle、MySQL、PostgreSQL、Maria、Snowflake 等之间唯一的真正的区别。
  • Project.Database.Name:要部署到的数据库的名称。
  • Project.Database.Password:进行部署的数据库用户的密码。
  • Project.Database.UserName:执行部署的数据库用户的用户名。
  • Project.Database.Server.Name:数据库所在服务器的名称。
  • Project.Flyway.LicenseKey:fly way 许可证密钥需要利用诸如模拟运行部署和撤消等功能。**请注意:**如果没有提供许可证密钥,Flyway 将恢复为社区版。
  • Project.Worker.Pool:工人池,工作将在这里完成。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

部署流程

我们的文档中,我们建议从以下数据库部署流程开始:

  1. 生成一个 delta 脚本,并将其作为一个工件进行附加。
  2. 通知数据库管理员等待批准(仅在生产中)。
  3. 数据库管理员通过人工干预批准增量脚本(仅在生产中)。
  4. 部署数据库更改。
  5. 通知团队成功或失败。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

通知步骤可以是电子邮件、Slack、微软团队或您选择的任何工具。手动干预是不言自明的,我们在文档中包含了这些信息。

使用 Flyway 数据库迁移步骤模板

生成增量报告和部署数据库更改是使用 Flyway 数据库迁移步骤模板完成的。当您将其添加到流程中时,请进行以下更改:

  1. 更新步骤的名称。
  2. 把它改成在一个工人身上运行。
  3. 选择一个工作池。
  4. 将容器图像更改为runs inside a container, on a worker
  5. 输入octopuslabs/flyway-workertools:latest作为 Docker 图像;Docker 会根据主机运行的内容自动下载正确的架构(Ubuntu 或 Windows)。

工人必须安装 Docker 才能工作。章鱼云提供了已经在运行 Docker 的托管工人。如果你想自己托管你的工人,Linux 工人必须运行 Linux 容器,而 Windows 工人只能运行 Windows 容器。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

步骤模板将在哪里运行现在已经配置好了。接下来,我们配置参数:

  • 选择包含您希望 Flyway 运行的脚本的包。
  • 可选:输入飞行路线所在的路径。

如果你在octopuslabs/flyway-workertools执行容器上运行这个,你不需要输入任何东西。步骤模板将自动找到要运行的可执行文件。

  • 选择希望该步骤运行的命令。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Octopus 中最常用的命令是:

  • info:这将生成一个列表,列出所有找到的脚本以及它们相对于正在部署的数据库的状态。当使用 community edition 时,info 命令是理想的,您需要列出将在数据库上运行的脚本。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • migrate dry run:这将生成一个包含所有待定 SQL 脚本的文件,它将被保存为一个工件,DBA 可以下载和查看。这优于info,但仅当您提供一个 Flyway 许可密钥时才受支持。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • migrate:这将获取包中的所有 SQL 脚本和 JAR 文件,并在目标数据库上运行它们。这是执行实际工作的命令。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

接下来是许可参数,接下来是连接参数,最后是 Flyway 支持的各种命令行开关。

  • 因为我有许可证密钥,所以把版本改成了Teams
  • 下一个选项是传递许可证密钥。
  • 然后,使用连接字符串格式提供数据库的 URL。
  • 之后是可选的参数,供 Flyway 运行。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

step 模板包括每个参数的详细帮助文本以及相应文档的链接。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

并非所有命令都支持所有命令行参数。步骤模板足够智能,可以排除命令不支持的参数。

生成增量脚本步骤

生成供 DBA 批准的增量脚本的步骤设置了以下参数。我有一个许可证密钥,所以我正在使用migrate dry run命令。如果我没有许可证密钥,我会选择info作为命令。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

部署数据库更改步骤

从包中部署数据库变更的步骤实际上与生成增量脚本步骤相同。唯一的区别是使用命令migrate而不是migrate dry run

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

结论

正如本文所展示的,更新您的流程以在执行容器中运行并不复杂,尤其是如果您使用的是 Octopus Cloud。添加 Docker 容器注册表外部提要后,只需为应该在执行容器上运行的每个步骤单击一个单选按钮。

这是一个很小的变化,但是它使部署过程和管道更加健壮。您可以利用运行您需要的 Flyway 的精确版本的工具,而无需所有维护开销。

愉快的部署!

Java CI/CD:从发布管理到运营——Octopus 部署

原文:https://octopus.com/blog/java-ci-cd-co/from-cd-to-co

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

本文是展示 Jenkins、Docker 和 Octopus 示例部署管道系列的一部分:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在之前的博文中,我们集成了 Jenkins 和 Octopus,在 Docker 映像被推送到 Docker Hub 后触发了对 Kubernetes 的部署。我们还在 Octopus 中添加了额外的环境来表示规范的开发➜测试➜生产进程。这给我们留下了一个在环境之间自动(如果不一定是自动的)发布管理的部署管道。

虽然传统的部署渠道以生产部署结束,但 Octopus 通过 runbooks 为 DevOps 生命周期的运营阶段提供了解决方案。通过使用 runbooks 自动执行数据库备份、日志收集和服务重启等常见任务,Jenkins 和 Octopus 的结合提供了一个完整的部署和运营管道,涵盖了应用程序的整个生命周期。

添加数据库

在为数据库备份创建操作手册之前,我们需要一个数据库。

您通常会在生产环境中使用 RDS 之类的托管服务。RDS 提供了现成的高可用性、备份、维护窗口、安全性等,所有这些都需要花费大量精力来使用本地数据库进行复制。然而,出于本博客的演示目的,我们将把 MySQL 部署到 EKS,并将我们的 PetClinic 应用程序指向它。然后,我们可以针对数据库编写常见管理任务的脚本,以演示保持生产部署运行的连续操作。

我们将使用官方的 MySQL Docker 映像,但是我们还需要一些额外的工具来将备份转移到第二个位置。因为我们使用 AWS 来托管我们的 Kubernetes 集群,所以我们将把数据库备份到 S3。这意味着我们需要 MySQL Docker 映像中包含的 AWS CLI 来传输数据库备份。

向图像添加新工具很容易。我们获取基本的 mysql 映像,并运行安装 AWS CLI 所需的命令。我们的 Dockerfile 看起来像这样:

FROM mysql
RUN apt-get update; apt-get install python python-pip -y
RUN pip install awscli 

然后,我们构建这个映像,将其推送到 Docker Hub,并使用下面的 Jenkinsfile 在 Octopus 中创建和部署一个版本。您会注意到,这个Jenkinsfile几乎是前一个的一个精确副本,对 Docker 映像和部署的 Octopus 项目的名称进行了更改:

pipeline {
    agent {
        label 'docker'
    }
    //  parameters here provide the shared values used with each of the Octopus pipeline steps.
    parameters {
        // The space ID that we will be working with. The default space is typically Spaces-1.
        string(defaultValue: 'Spaces-1', description: '', name: 'SpaceId', trim: true)
        // The Octopus project we will be deploying.
        string(defaultValue: 'MySQL', description: '', name: 'ProjectName', trim: true)
        // The environment we will be deploying to.
        string(defaultValue: 'Dev', description: '', name: 'EnvironmentName', trim: true)
        // The name of the Octopus instance in Jenkins that we will be working with. This is set in:
        // Manage Jenkins -> Configure System -> Octopus Deploy Plugin
        string(defaultValue: 'Octopus', description: '', name: 'ServerId', trim: true)
    }
    stages {
        stage ('Add tools') {
            steps {
                tool('OctoCLI')
            }
        }
        stage('Building our image') {
            steps {
                script {
                    dockerImage = docker.build "mcasperson/mysqlwithawscli:$BUILD_NUMBER"
                }
            }
        }
        stage('Deploy our image') {
            steps {
                script {
                    // Assume the Docker Hub registry by passing an empty string as the first parameter
                    docker.withRegistry('' , 'dockerhub') {
                        dockerImage.push()
                    }
                }
            }
        }
        stage('deploy') {
            steps {                                
                octopusCreateRelease deployThisRelease: true, environment: "${EnvironmentName}", project: "${ProjectName}", releaseVersion: "1.0.${BUILD_NUMBER}", serverId: "${ServerId}", spaceId: "${SpaceId}", toolId: 'Default', waitForDeployment: true                
            }
        }
    }
} 

MySQL Kubernetes 部署 YAML 也与我们的之前的示例非常相似,使用了新的映像名称,并添加了两个环境变量来配置数据库凭证和创建初始数据库:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql
spec:
  replicas: 1
  strategy:
    type: Recreate
  template:
    spec:
      containers:
        - name: mysql
          image: mcasperson/mysqlwithawscli
          ports:
            - name: sql
              containerPort: 3306
          env:
            - name: MYSQL_ROOT_PASSWORD
              value: Password01!
            - name: MYSQL_DATABASE
              value: petclinic 

因为我们不需要公开访问数据库,所以我们使用集群 IP 服务公开 MySQL 实例,这允许其他 pods 访问数据库,但不会创建公共负载平衡器:

apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  type: ClusterIP
  ports:
    - name: sql
      port: 3306
      protocol: TCP 

部署由上面的 YAML 创建的资源会导致可以使用主机名mysql从集群中的其他 pods 访问 MySQL 实例。

为了配置 PetClinic 使用 MySQL 数据库,我们需要定义四个环境变量。这些变量用于配置应用程序-mysql.properties 配置文件中的设置:

  • MYSQL_URL,这是 MySQL 数据库的 JDBC URL。
  • MYSQL_USER,作为 MySQL 用户连接,设置为root
  • MYSQL_PASS,这是 MySQL 密码,设置为我们在 MySQL pod 上的MYSQL_ROOT_PASSWORD环境变量中定义的密码。
  • SPRING_PROFILES_ACTIVE,它定义了 Spring 将用来配置应用程序的概要文件,设置为 mysql 来加载应用程序-mysql.properties 配置文件。

我们新 PetClinic 部署的 YAML 如下所示:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: petclinic
spec:
  replicas: 1
  template:
    spec:
      containers:
        - name: petclinic
          image: mcasperson/petclinic
          ports:
            - name: web
              containerPort: 8080
          env:
            - name: MYSQL_URL
              value: 'jdbc:mysql://mysql/petclinic'
            - name: SPRING_PROFILES_ACTIVE
              value: mysql
            - name: MYSQL_USER
              value: root
            - name: MYSQL_PASS
              value: Password01! 

我们现在有了一个 MySQL 数据库,并配置了 PetClinic 将其用作数据存储。

备份数据库

在开发运维生命周期的持续运营阶段,最明显的任务之一可能就是备份数据库。

MySQL Docker image 文档提供了一个使用mysqldump在活动容器内运行docker exe来备份数据库的示例命令。我们将以那个例子为例,将其重写为对kubectl exe的调用,以在一个正在运行的 pod 上执行备份。

下面的 PowerShell 脚本找到了 MySQL pod 的名称(由于该 pod 是作为部署的一部分创建的,所以它的名称是随机的),调用mysqldump创建数据库的备份,然后调用aws s3 cp将备份上传到 S3:

# Get the list of pods in JSON format
kubectl get pods -o json |
# Convert the output to an object
ConvertFrom-Json |
# Get the items property
Select -ExpandProperty items |
# Limit the items to those with the name starting with "mysql"
? {$_.metadata.name -like "mysql*"} |
# We only expect to find 1 such pod
Select -First 1 |
# Execute mysqldump on the pod to perform a backup
% {
    Write-Host "Performing backup on pod $($_.metadata.name)"
    kubectl exec $_.metadata.name -- /bin/sh -c 'mysqldump -u root -p#{MySQL Password} petclinic > /tmp/dump.sql 2> /dev/null'
    kubectl exec $_.metadata.name -- /bin/sh -c 'AWS_DEFAULT_REGION=us-east-1 AWS_ACCESS_KEY_ID=#{AWS.AccessKey} AWS_SECRET_ACCESS_KEY=#{AWS.SecretKey} aws s3 cp /tmp/dump.sql s3://mattc-deployment-backup/dump.sql'    
} 

该脚本在添加到 runbook 的运行 kubectl CLI 脚本步骤中执行:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 执行数据库备份的 kubectl 脚本。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 执行数据库备份的结果。

我们不想手动备份数据库,所以 Octopus 允许安排 runbooks。这里我们有一个执行每日备份的触发器:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 定时备份。

虽然找到执行备份的 pod 的名称需要一些处理,但是这个脚本并不特别复杂,经验丰富的系统管理员无疑见过比这复杂得多的管理脚本。按计划运行脚本的能力也不是什么突破性的东西。

当您考虑到在应用程序的生命周期中需要与这个基础设施进行交互的不同团队时,这种方法的真正优势就变得很明显了。

因为 Octopus 已经部署到我们的基础设施中,所以我们不需要复制凭证或其他设置(如 URL)来管理基础设施。都已经在章鱼里了。

操作手册消除了对额外工具和配置设置的需要,否则这些工具和配置设置可能需要在专门的支持笔记本电脑上维护,这意味着电话支持人员只需点击一个按钮,就可以从网络浏览器(如有必要,在他们的电话上)执行这些操作手册。因为这些操作手册的执行是在审计日志中捕获的,并且这些步骤的输出是在操作手册运行的历史中捕获的,所以在发现问题的根本原因时,您不会遇到同样的困难,如果运营团队必须从他们自己的工作站运行临时脚本,您就会遇到同样的困难。

一个额外的好处是,run book 知道我们的多种环境,所以正如我们的应用程序代码必须通过多种环境才能被认为已准备好用于生产发布,我们的 run book 也可以在非生产环境中进行测试和验证,以确保它们在生产中可以被信任。

所有这一切意味着支持生产系统所需的业务知识现在可以在可测试和可重复的操作手册中获取,使得支持移交更加容易,因为所有团队都共享相同的工具箱。

当保存在操作手册中时,这十几行 PowerShell 代表了一个共享的、可验证的、可审计的、易于访问的集中式业务知识单元,旨在保持您的系统以最佳状态运行。

重启 pod

让我们看另一个例子,这次是重新启动 PetClinic 应用程序。

下面的脚本找到名称以 petclinic 开头的 pod 并删除它们。因为这些 pod 是由 Kubernetes 部署创建的,所以它们将被自动重新创建,实质上是执行 pod 重启:

# Get the list of pods in JSON format
kubectl get pods -o json |
# Convert the output to an object
ConvertFrom-Json |
# Get the items property
Select -ExpandProperty items |
# Limit the items to those with the name starting with "mysql"
? {$_.metadata.name -like "petclinic*"} |
# Delete the pod to have the deployment recreate it
% { kubectl delete pod $_.metadata.name} 

如果你不熟悉 Kubernetes,像kubectl delete这样的命令可能会让人望而生畏。碰巧的是,由于我们的应用程序的部署方式,该操作将重启 pod,而不是永久删除它们。但是 DevOps 团队的新成员如何知道这个命令是安全的呢?

通过向运行手册添加描述,我们可以提供运行手册何时何地可以运行的指导。在下面的截图中,您可以看到对 Restart PetClinic runbook 的描述,它清楚地表明这是可以在生产中运行的东西:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 带有描述的运行手册,帮助运营团队了解何时何地运行它们。

更进一步,我们可以使用 Octopus 中的权限来限制对运行手册的访问,这可能需要更深入地了解基础架构才能安全运行,或者在采取任何行动之前使用手动干预来获得批准。

同样,这是一个将业务知识封装在操作手册中以减轻基础设施支持负担的例子。

结论

传统的部署管道以部署结束,但实际上,部署之后发生的事情与部署本身一样重要。这就是连续作战思想的由来。Runbooks 为您的团队提供了从第一次代码提交到产品部署后数周、数月或数年支持应用程序所需的工具。因为 Octopus 已经了解了您的基础架构以及如何部署到基础架构,所以 runbooks 可以轻松利用现有的凭证、目标和环境来实现 DevOps 生命周期的运营阶段。

基本上,操作手册将保持部署运行的脚本和工作流本身视为一种有价值的产品。从持续交付中获取最佳实践,并将其扩展到运营任务中,可确保整个应用生命周期由您的开发运维团队以连贯的方式进行管理。

通过这篇博文,我们结束了从本地构建的遗留 Java 应用程序到集成了 Jenkins、Octopus、Docker 和 AWS EKS 的完整部署管道的旅程。我希望示例管道为在您的组织中实现持续集成(CI)、发布管理和持续操作提供了一个基础。

浏览 DevOps 工程师手册了解更多关于 DevOps 和持续交付的信息。

愉快的部署!

Java CI/CD:从持续集成到发布管理- Octopus Deploy

原文:https://octopus.com/blog/java-ci-cd-co/from-ci-to-cd

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

本文是展示 Jenkins、Docker 和 Octopus 示例部署管道系列的一部分:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在之前的博文中,我们使用 EKS 在 AWS 中使用 Octopus 构建了一个 Kubernetes 集群,然后将 Jenkins 创建的 Docker 映像部署为 Kubernetes 部署和服务。

然而,我们仍然没有一个完整的部署管道解决方案,因为 Jenkins 没有与 Octopus 集成,让我们手动协调构建和部署。

在这篇博文中,我们将扩展我们的 Jenkins 构建来调用 Octopus,并在 Docker 映像被推送到 Docker Hub 时启动部署。我们还将创建额外的环境,并管理从本地开发环境到最终生产环境的发布。

安装 Jenkins 插件

Octopus 为 Jenkins 提供了一个插件,该插件公开了自由式项目和管道脚本中的集成步骤。通过导航到 管理詹金斯➜管理插件 安装此插件。从这里,你可以搜索“章鱼”并安装插件。

Octopus 插件使用 Octopus CLI 与 Octopus 服务器集成。我们可以在代理上手动安装 CLI,但在本例中,我们将使用定制工具插件下载 Octopus CLI 并将其推送到代理:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 安装自定义工具插件。

我们添加 Octopus 服务器,我们的管道将连接到,通过导航到 管理詹金斯➜配置系统 :

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 定义八达通服务器。

然后我们需要在 管理詹金斯➜全球工具配置 下定义一个定制工具。自定义工具的名称为Octopus CLI,因为在我的例子中代理运行在 Windows 上,Octopus CLI 将从https://download . Octopus deploy . com/Octopus-tools/7 . 4 . 1/Octopus tools . 7 . 4 . 1 . win-x64 . zip下载。对于最新版本的 CLI,以及支持其他操作系统的二进制文件,请参见 Octopus 下载页面:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 定义 Octopus CLI 自定义工具。

全局工具配置页面上,我们定义了 Octopus CLI 的路径。自定义工具插件将 Octopus CLI 安装到目录<jenkins home>/tools/com.cloudbees.jenkins.plugins.customtools.CustomTool/OctoCLI,其中<jenkins home>是 Jenkins 服务器或执行构建的代理的主目录。在我的例子中,代理主目录是C:\JenkinsAgent,所以从C:\JenkinsAgent\tools\com.cloudbees.jenkins.plugins.customtools.CustomTool\OctoCLI\octo开始就可以使用 Octopus CLI。刀具名称保留为默认:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 定义 Octopus CLI 路径。

通过配置这些工具,我们可以在 Docker 映像被推送到 Docker Hub 之后,更新管道脚本来启动 Octopus 中的部署。

更新詹金斯管道

我们现有的管道被配置为构建 Docker 映像并将其推送到 Docker Hub。我们将保留这些步骤,并添加额外的步骤来安装作为定制工具的 Octopus CLI,然后在 Docker 映像被推送后在 Octopus 中创建和部署一个版本。让我们看看完整的管道:

pipeline {
    agent {
        label 'docker'
    }
    parameters {
        string(defaultValue: 'Spaces-1', description: '', name: 'SpaceId', trim: true)
        string(defaultValue: 'Petclinic', description: '', name: 'ProjectName', trim: true)
        string(defaultValue: 'Dev', description: '', name: 'EnvironmentName', trim: true)
        string(defaultValue: 'Octopus', description: '', name: 'ServerId', trim: true)
    }
    stages {
        stage ('Add tools') {
            steps {
                tool('OctoCLI')
            }
        }
        stage('Building our image') {
            steps {
                script {
                    dockerImage = docker.build "mcasperson/petclinic:$BUILD_NUMBER"
                }
            }
        }
        stage('Deploy our image') {
            steps {
                script {
                    // Assume the Docker Hub registry by passing an empty string as the first parameter
                    docker.withRegistry('' , 'dockerhub') {
                        dockerImage.push()
                    }
                }
            }
        }
        stage('deploy') {
            steps {                                
                octopusCreateRelease deployThisRelease: true, environment: "${EnvironmentName}", project: "${ProjectName}", releaseVersion: "1.0.${BUILD_NUMBER}", serverId: "${ServerId}", spaceId: "${SpaceId}", toolId: 'Default', waitForDeployment: true                
            }
        }
    }
} 

这个管道有一些新的设置来支持与 Octopus 的集成。

我们从定义公共参数开始。当我们在 Octopus 中创建和部署一个版本时,将会引用这些参数,它们提供了一种很好的方法来将 Octopus 细节从任何特定的实例中分离出来,同时还提供了合理的默认值:

 parameters {
        string(defaultValue: 'Spaces-1', description: '', name: 'SpaceId', trim: true)
        string(defaultValue: 'Petclinic', description: '', name: 'ProjectName', trim: true)
        string(defaultValue: 'Dev', description: '', name: 'EnvironmentName', trim: true)
        string(defaultValue: 'Octopus', description: '', name: 'ServerId', trim: true)
    } 

为了让自定义工具插件提取代理主目录中的 Octopus CLI,我们需要调用tool('OctoCLI'):

 stage ('Add tools') {
            steps {
                tool('OctoCLI')
            }
        } 

最后一个阶段调用octopusCreateRelease来创建一个发布并将其部署到 Octopus 中的第一个环境。默认情况下,Octopus 将使用部署步骤中引用的最新版本的包来创建部署,这意味着我们将部署 Jenkins 在前一阶段上传到 Docker Hub 的 Docker 映像:

 stage('deploy') {
            steps {                                
                octopusCreateRelease deployThisRelease: true, environment: "${EnvironmentName}", project: "${ProjectName}", releaseVersion: "1.0.${BUILD_NUMBER}", serverId: "${ServerId}", spaceId: "${SpaceId}", toolId: 'Default', waitForDeployment: true                
            }
        } 

通过对管道的这些更改,我们在 Jenkins 中重新运行了该项目,从控制台日志中,我们可以看到 Jenkins 已经成功地触发了 Octopus 中的部署:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 * Jenkins 项目构建日志显示了 Octopus 部署输出。*

以下是 Octopus 中相应的部署:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 章鱼部署。

持续部署与持续交付

多年来,首字母缩略词 CI/CD 的 CD 半已经确定了两个定义:

  • 连续部署,这意味着一个完全自动的部署管道,假设所有测试和其他自动化需求都得到满足,那么每次提交都会进入生产环境。
  • 连续交付,这意味着每个提交可以通过自动化的,但不一定是自动的,部署管道进入生产。通过环境提升(或不通过环境提升)的决定仍然是由人做出的。

虽然连续部署,就其定义而言,消除了部署过程中的所有摩擦,但是有许多有效的理由来实现连续交付。例如,您可能需要与其他团队协调部署,产品负责人可能需要签署新功能,法规要求可能要求开发人员在没有一些审查过程的情况下不得修改生产基础结构,或者您可能只想保留在发布进入生产之前手动测试和验证发布的能力。

如果您阅读了关于 CI/CD 最佳实践的博客文章,您可能会留下这样的印象,即持续部署是您必须努力实现的事情。虽然允许真正的连续部署管道的实践将会有价值,但是我们交谈过的大多数开发团队都报告说连续交付对他们有效。

对于这个博客,我们将创建一个连续的交付管道,通过 Octopus 仪表板管理向多个环境的发布。

添加环境

我们在 Octopus 中只有一个环境叫做 Dev 。但是,典型的工作流会在生产过程中通过多个环境促进部署。为了实现这一点,我们需要在 Octopus 中创建更多的环境,我们称之为测试生产:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 添加测试和生产环境。

我们需要确保我们的 Kubernetes 目标也在这些新环境中:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 将 Kubernetes 目标添加到新环境中。

我们现在能够通过 Octopus 仪表板将版本从开发环境升级到测试环境:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 显示下一个部署环境的 Octopus 仪表盘。

将发布升级到测试环境,我们可以看到我们的 Kubernetes 资源正在 petclinic-test 名称空间中创建。如果您还记得上一篇博文,我们配置了 Kubernetes 步骤来部署到一个名为pet clinic-# { Octopus }的名称空间。Environment.Name | ToLower} ,这就是为什么新环境的部署被放置在新的名称空间中:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 一个部署到测试环境中。

为了证明这一点,我们可以在测试环境中重新运行 runbook Get 服务。我们可以看到,已经为新的服务资源创建了一个新的负载平衡器主机名:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 在测试环境中创建的负载平衡器服务的详细信息。

这样,我们就有了完整的部署渠道。

结论

在这篇文章中,在 Jenkins 完成 Docker 映像的构建和推送之后,我们在 Octopus 中触发了一个部署。这意味着我们已经实现了与 Jenkins 的持续集成,测试、构建和发布 Docker 映像,以及与 Octopus 的持续交付,提供了到开发环境的自动部署,以及准备在其他环境中手动触发的自动化流程。

现在,我们只需点击几个简单的按钮,就可以将应用程序源代码转化为产品。那些负责发布管理的人除了 web 浏览器之外不需要任何特殊的工具。每个构建和部署都在 Jenkins 和 Octopus 仪表板中被跟踪、审计和总结。

但是那些看到他们的代码放在客户手中的人知道,虽然没有什么比生产部署的前 10 分钟更能激发信心,但接下来的几个小时和几天是艰难的。需要管理数据库备份,需要安排操作系统更新,需要收集日志来诊断支持问题,并且需要执行一些好的、老式的开关操作。

在下一篇博文中,我们将展示在运行手册中实现的这些维护过程的例子,以完成我们管道的最后阶段:运营。

浏览 DevOps 工程师手册了解有关 DevOps 和 CI/CD 的更多信息。

Java CI/CD:从持续集成到 Kubernetes 部署——Octopus 部署

原文:https://octopus.com/blog/java-ci-cd-co/from-ci-to-cloud

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

本文是展示 Jenkins、Docker 和 Octopus 示例部署管道系列的一部分:

在之前的博文中,我们用 Jenkins 配置了一个 CI 服务器,它提供了一个中心位置来构建和发布我们的 Docker 映像。该映像现在可以从 Docker Hub 公开获得,下一步是创建可以托管我们的 Docker 容器的基础设施。

在本文中,我们将从 Octopus 的 AWS 中创建一个弹性 Kubernetes 服务(EKS)实例。

获取 Octopus 云实例

我们将使用 Octopus 编写 EKS 星团的创建脚本。获得 Octopus 的最简单方法是注册一个免费的 Octopus Cloud 实例。启动并运行一个实例只需要几分钟时间。

第一步是在 Octopus 中创建一个 AWS 帐户,该帐户将用于创建并连接到 EKS 集群。AWS 帐户由帐户密钥和密钥组成:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 一个 AWS 账户的例子。

名为**eks CTL-Create Cluster(bash)**的社区步骤可以添加到 runbook 中,以便在 Octopus 中快速创建 EKS 集群和相关的 Kubernetes 目标。该脚本执行 EKS CLI 工具来创建 EKS 集群:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 社区一步创建一个 EKS 集群。

为了方便使用 ekscli 工具,Octopus 支持基于图像在 Docker 容器内运行一个步骤,并且为图像提供了广泛的常用云工具选择,包括 ekscli

要使用 Octopus Cloud 中的 Docker 映像,我们需要选择 Ubuntu 动态工作器:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

为了使用工人工具图像,我们在容器图像部分选择它:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

用要创建的 EKS 集群的详细信息填充该步骤。对于本例,下面的 eksctl 配置 YAML 创建了一个包含两个 t3a.small 节点的集群:

apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: k8s-cluster
  region: us-east-1

nodeGroups:
  - name: ng-1
    instanceType: t3a.small
    desiredCapacity: 2
    volumeSize: 80 

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 人口密集的一步。

如果在us-east-1中出现UnsupportedAvailabilityZoneException错误,请尝试另一个可用区域。eksctl 文档指出us-east-1容易出现这种错误。

在执行 runbook 之前,我们需要允许将动态创建的目标放置在目标环境中,在本例中称为 Dev 。启用此设置允许脚本步骤(如我们刚刚配置的社区步骤模板)创建 Octopus 目标:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 在开发环境中启用动态基础设施。

现在执行操作手册。当 runbook 完成时,一个新的 Kubernetes 目标被创建,我们可以将我们的应用程序部署到:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 章鱼目标。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传EKS 集群。

创建 Docker 提要

为了使用 Docker 图像,我们需要在 Octopus 中创建一个 Docker 提要。这个提要指向 https://index.docker.io ,这是 Docker Hub 注册表的 URL:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 将 Docker 集线器馈入章鱼。

然后我们可以测试提要,以确保 Octopus 可以找到我们的 Petclinic 图像:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 测试对接器进给。

将映像部署到 Kubernetes

现在,我们已经完成了部署到 Kubernetes 集群的所有配置。我们将使用部署 Kubernetes 容器步骤来配置 Kubernetes 部署资源,并通过负载平衡器服务公开它:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 部署 Kubernetes 容器的步骤。

您可以通过两种方式与此步骤进行交互。

第一种方法是使用 UI 来构建 Kubernetes 部署。当您不太熟悉 Kubernetes YAML 酒店时,这很方便,因为您可以通过专用的表单字段构建资源。

使用该步骤的第二种方法是通过点击编辑 YAML 按钮时显示的 YAML 表示法来编辑值:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 编辑 YAML 章节。

然后,您可以直接以 YAML 的身份编辑部署资源,这很方便,因为复制和粘贴现有的 YAML 只需一个操作就可以填充该步骤。通过将下面的 YAML 粘贴到文本框中,我们创建了一个引用 Docker 映像的部署:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: petclinic
spec:
  selector:
    matchLabels:
      octopusexport: OctopusExport
  replicas: 1
  strategy:
    type: RollingUpdate
    rollingUpdate: {}
  template:
    metadata:
      labels:
        octopusexport: OctopusExport
    spec:
      containers:
        - name: petclinic
          image: mcasperson/petclinic
          ports:
            - name: web
              containerPort: 8080 

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 通过 YAML 编辑步骤。

我们希望将我们的部署放在每个环境的单独名称空间中。这是通过将名称空间字段设置为 petclinic-#{Octopus 来实现的。Environment.Name | ToLower} :

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

以与配置部署相同的方式,我们可以用下面的 YAML 填充服务的详细信息。这个 YAML 创建一个负载平衡器服务,这将导致创建一个弹性负载平衡器(ELB)来公开部署。这个 ELB 有一个公共主机名,我们可以从我们的 web 浏览器访问:

apiVersion: v1
kind: Service
metadata:
  name: petclinic
spec:
  type: LoadBalancer
  ports:
    - name: web
      port: 80
      targetPort: 8080
      protocol: TCP 

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 为 YAML 服务。

有了这些设置,我们就可以部署到 EKS 集群了。日志显示 Kubernetes 部署和服务资源已成功创建:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 * PetClinic 已成功部署。*

所以现在唯一的问题是我们如何访问应用程序?

查询群集

我们将经常需要查询集群,以找到我们需要的信息,或者调试一个问题。通常,设置部署的人会在本地配置 kubectl,并使用特定命令快速查询集群的状态。

虽然这是可行的,并且有时确实是必要的,但是执行这样的特别命令忽略了这样一个事实,即如果这些命令是成功完成初始部署所必需的,那么它们也可能是解决未来部署问题所必需的。

查找我们刚刚创建的负载平衡器的主机名就是一个很好的例子。我们可以通过多种方式获取这些信息,要么从 AWS 控制台获取,要么调用 kubectl。然而,为了在我们完成后维护这个集群的人的利益,我们将通过另一个操作手册找到这个信息。

为了获得服务信息,使用名为Kubernetes-Inspect Resources的社区步骤模板创建一个 runbook:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传Kubernetes-Inspect 资源社区步骤模板。

配置从 petclinic-#{Octopus 获取服务资源的步骤。Environment.Name | ToLower} 命名空间:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 获取服务详情。

运行 runbook 将代表我们用 kubectl 查询集群,在响应中显示负载平衡器的主机名:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 服务详情。

这个过程比直接跳到控制台并运行 kubectl 稍不方便,但好处是我们已经启动了一个 runbook 库,其中包含了我们知道对使用我们的集群有用的步骤。这个库将非常有价值,因为我们希望将对这个基础设施的支持交给另一个团队。当您考虑到下一个团队只需要对 Octopus 的适当访问,而不需要 kubectl 或任何凭证时,这一点尤其正确,当您的寻呼机在凌晨 3 点响起时,这是受欢迎的。

现在我们知道了 ELB 的主机名,我们可以访问我们公开托管的应用程序:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 * PetClinic live 和 public。*

结论

在本文中,我们使用 EKS 服务在 AWS 中创建了一个 Kubernetes 集群,并通过 Octopus 将我们的 PetClinic 应用程序部署到其中。我们还致力于通过 runbooks 调试集群,这提供了一个小但重要的基础,我们可以将它传递给在我们继续前进后最终负责该集群的团队。

我们还没有实现连续部署,因为 Jenkins 和 Octopus 之间没有集成。在下一篇文章的中,我们将连接我们的管道,以实现一个完整的 CI/CD 管道。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值