Octopus-博客中文翻译-七-

Octopus 博客中文翻译(七)

原文:Octopus Blog

协议:CC BY-NC-SA 4.0

滚动部署的终极指南- Octopus Deploy

原文:https://octopus.com/blog/ultimate-guide-to-rolling-deployments

Rolling Deployments

当部署现代软件的新版本时,比如 web 应用程序和服务,一些团队在进行部署时关闭整个网站的情况仍然很常见。

如果您的大多数客户只在工作时间使用您的应用程序,那么这种大爆炸式的方法可能是可以接受的,但是如果您的客户全天候使用您的应用程序会怎么样呢?

如今,用户希望应用程序始终可用,有一些部署模式可以用来实现零停机。在这篇文章中,我将更深入地讨论其中一种模式;滚动部署。我还将为您提供一些如何使用不同工具实现滚动部署的实际例子。

在这篇文章中

什么是滚动部署?

滚动部署是一种部署模式(也称为增量部署、批量部署或斜坡部署),在这种模式下,通常一次向一个或多个部署目标交付新软件,直到所有目标都部署了软件的更新版本。

典型的流程如下所示:

  1. 在应用程序的两个节点运行v1.0的情况下,清空要更新的第一个节点,将其从负载平衡器池中取出,并让剩余的节点保持在线以服务流量:

Rolling Deployment: Draining nodes

  1. 停止在耗尽的节点上运行v1.0应用程序,然后部署新的v1.1版本。可选地,通过在新部署的应用程序上运行测试来验证部署是否成功。同时,维护至少一个运行应用程序v1.0的节点:

Rolling Deployment: Update nodes with new versions

  1. 在第一个节点成功更新后,继续清空仍在运行应用程序v1.0的剩余节点,同时您的新v1.1版本现在在线提供流量:

Rolling Deployment: Drain remaining nodes in pool

  1. 停止剩余节点上的v1.0应用程序的运行,部署新的v1.1版本。同样,也可以验证部署是否成功:

Rolling Deployment: Update remaining nodes in pool

  1. 最后,在应用程序的v1.1成功部署到所有节点之后,您的滚动部署就完成了!

Rolling Deployment: Update remaining nodes in pool

如果您想要加速您的滚动部署,并同时向多个节点交付一个新版本,比如说两个节点,那么它看起来应该是这样的:

Rolling Deployment: Update multiple nodes in pool

这种增量方法通常在位于负载平衡器之后的 web 应用程序中更受青睐,因为大多数负载平衡器支持一种称为连接排出的概念。这允许到服务的连接自然完成,并防止建立新的连接。

通过执行该动作,被选择更新的实例可以在它们完成它们的工作之后从可用池中移除,而其他实例保持在线服务流量。

尽管上面的场景描述了 web 应用程序滚动部署,但是也可以实现其他类型应用程序的滚动部署,只要它们是以支持安全地结束其过程的方式构建的。

例如,Octopus Deploy 的高可用性配置也有一个消耗选项,它可以阻止任何新任务的执行,并完成它当前正在执行的任何任务,直到空闲。排水等功能允许安全终止进程,然后可以更新并重新联机。

它们为什么有用?

那么,为什么要使用滚动部署而不是其他模式,比如 canary 或 blue/green?滚动部署具有以下优势:

增量更新

应用程序的新版本是逐步推出的。这使您可以验证它的工作,例如,在进行下一批更新之前运行健康检查或测试。

如果您需要启动回滚,您也可以用一种安全且可控的方式来完成。

保持灯亮着

当您着手更新少量的应用程序实例时,其余的继续为请求服务。这意味着您的应用程序不会停机,并且在整个部署过程中您的用户都可以使用它。

平行

您通常可以控制一次部署到的并发实例的数量。在之前的部署完成之前,不会开始进一步的部署。

您可以在 Octopus 滚动部署中使用窗口大小选项来控制一次可以部署多少个部署目标。

实践中的滚动部署模式

为了演示滚动部署的不同方法,我们有一个非常简单的。将显示网页的 NET Core 3.1 应用程序。

我感兴趣的部分的 HTML 如下所示:

<div class="text-center">
    <h1 class="display-4">Welcome</h1>
    <p>If you are seeing this, then <strong>Congratulations!</strong> <br/> You've got the example application running. </p>

    @if(Settings.Value.AppVersion == "0.0.2") {
        <p>v0.0.2 of the application comes with this text </p>
    }
    @if(Settings.Value.AppVersion == "0.0.3") {
        <p>But don't miss out on v0.0.3 of the application which comes with this text! </p>
    }
</div> 

该应用程序的代码可在 GitHub 上获得,并有一个与三个不同的AppVersion值相对应的标签。一张 Docker 图片也已经发布为octopus deploy/rolling-deploy-web-example

我想看看使用一些流行的技术和工具来执行此应用程序的滚动部署有多简单,因此我将使用以下工具来演示滚动部署:

Docker 滚动应用程序更新

在过去几年中,Docker 已经成为事实上的容器技术。因此,它以 Docker 服务的概念支持滚动部署也就不足为奇了。通常,服务是更大的架构图中的一小部分,在微服务中很受欢迎。

服务支持许多不同的选项,包括滚动更新策略和回滚能力。

码头集装箱应用

我在一个 Ubuntu 服务器上运行 Docker,并使用我们预先构建的容器映像。在 Ubuntu 上安装 Docker 有几种方法:

  1. 通过运行sudo apt-get install docker.io来使用 Ubuntu 存储库。
  2. 使用官方的对接指南

我选择了 Ubuntu 库,因为它似乎更快更容易,但你的里程可能会有所不同。无论您选择哪种方法,都有必要确保您满足安装的先决条件。

为了简单起见,我将在我的 Linux 机器的 SSH 终端会话中与 Docker 交互。有一些生产就绪的设置可以实现这一点,这些设置的特点是在一个 Docker Compose 文件中定义您的服务,包括控制自动更新和回滚设置的部分。

权限要求:
本演示中的大多数命令都使用了 sudo 。默认情况下,Docker 守护程序作为根用户运行,需要提升权限才能执行命令。如果你不想在执行命令时使用sudo,请确保遵循 Docker 安装后说明。

首先,为了查看这个独立运行的 Docker 映像,我们将使用以下命令运行它:

markh@ubuntu01:~$ sudo docker run -d -p 5001:5001 octopusdeploy/rolling-deploy-web-example:0.0.1 

不出所料,运行这个 Docker 图像会显示网页:

在这篇文章中,我不解释如何建立一个容器图像。如果您是 Docker 的新手,我的同事 Shawn 已经写了一个很好的系列文章,介绍如何将一个真实世界的应用程序容器化,而不是另一个“Hello World”的例子。

集装箱清理

接下来需要快速整理。要删除我们使用上面的run命令创建的容器,我们需要停止它,然后使用rm命令删除它。我们可以用简洁的一行程序来实现这一点:

sudo docker rm $(sudo docker stop $(sudo docker ps -a -q --filter ancestor=octopusdeploy/rolling-deploy-web-example:0.0.1 --format="{{.ID}}")) 

这通过图像名octopusdeploy/rolling-deploy-web-example:0.0.1定位我们的容器,并将它传递给stop命令,最后将它传递给rm命令。

为了部署容器的多个实例,我们需要创建 Docker 服务。这使用码头工人群作为其幕后指挥者。

Docker Kubernetes orchestrator
Docker 在使用 Docker stack 命令部署容器时也支持 Kubernetes 作为 orchestrator,但是在使用service create时无法指定 orchestrator。

让我们看看创建服务的命令是什么样子的:

markh@ubuntu01:~$ sudo docker service create --name rolling-deploy-svc --replicas 3 --publish published=5001,target=5001 --update-delay 10s --update-parallelism 1 octopusdeploy/rolling-deploy-web-example:0.0.1 

这个命令中有很多内容,所以让我们来分析一下我们对 Docker 的要求:

  • --name不言自明。
  • --replicas标志控制我们想要的容器数量(3)。
  • --publish published=5001,target=5001使用 Swarm 的路由网格指定要在端口 5001 上访问的服务,该路由网格本质上就像一个软件负载平衡器。
  • --update-delay配置服务任务更新之间的时间延迟(10 秒)。
  • --update-parallelism控制 Docker 将同时调度的最大任务数(1)。
  • 最后,我们指定要使用的图像:octopusdeploy/rolling-deploy-web-example:0.0.1

提示:
第一次运行service create时,可能会收到警告,就像我一样:This node is not a swarm manager。要解决此问题,请运行以下命令之一:

  • 这将把你当前的节点初始化为群组管理器。
  • sudo docker swarm join:这将把你的本地节点连接到 swarm。

执行此操作将导致我们的服务被部署到 Docker Swarm,其中包含三个实例:

wxi1w4m7crknaz1f800kr9ztt
overall progress: 3 out of 3 tasks
1/3: running   [==================================================>]
2/3: running   [==================================================>]
3/3: running   [==================================================>]
verify: Service converged 

我们还可以通过运行service inspect命令来检查我们的服务是否有正确的更新配置:

markh@ubuntu01:~$ sudo docker service inspect rolling-deploy-svc --pretty

ID:             bh03s0yjzkevzkkwvu8q2h0jj
Name:           rolling-deploy-svc
Service Mode:   Replicated
 Replicas:      3
Placement:
UpdateConfig:
 Parallelism:   1
 Delay:         10s
 On failure:    pause
 Monitoring Period: 5s
 Max failure ratio: 0
 Update order:      stop-first
RollbackConfig:
 Parallelism:   1
 On failure:    pause
 Monitoring Period: 5s
 Max failure ratio: 0
 Rollback order:    stop-first
ContainerSpec:
 Image:         octopusdeploy/rolling-deploy-web-example:0.0.1@sha256:4da10d630025bf268b855b0b4afafa7334769ab6d0b3e75e11a3f11949708552
 Init:          false
Resources:
Endpoint Mode:  vip
Ports:
 PublishedPort = 5001
  Protocol = tcp
  TargetPort = 5001
  PublishMode = ingress 

这个结果表明我们有了我们想要的UpdateConfig,它将一次更新一个任务。

Docker 服务更新

现在我们可以通过运行service update命令将octopusdeploy/rolling-deploy-web-example的容器图像更新为v0.0.2:

markh@ubuntu01:~$ sudo docker service update rolling-deploy-svc --image octopusdeploy/rolling-deploy-web-example:0.0.2 

Docker 对每个容器运行更新,一次一个任务,就像我们配置的那样:

overall progress: 0 out of 3 tasks
1/3: running   [=============================================>     ]
2/3:
3/3: 

第一个任务完成后,它进入第二个任务:

overall progress: 1 out of 3 tasks
1/3: starting  [==================================================>]
2/3: ready     [=====================================>             ]
3/3: 

直到更新容器到v0.0.2的所有任务完成:

overall progress: 3 out of 3 tasks
1/3: running   [==================================================>]
2/3: running   [==================================================>]
3/3: running   [==================================================>]
verify: Service converged 

现在浏览网站显示申请v0.0.2的文本:

Docker 服务回滚

正如直接推出一样,也可以在 Docker 中用一个简单的命令手动回滚。

首先,我们将通过运行以下命令来更新应用程序的最终版本v0.0.3:

markh@ubuntu01:~$ sudo docker service update rolling-deploy-svc --image octopusdeploy/rolling-deploy-web-example:0.0.3 

我们通过运行service inspect命令来验证新的v0.0.3版本,只为我们希望看到的输出传入一个--format参数:

markh@ubuntu01:~$ sudo docker service inspect --format='{.Spec.TaskTemplate.ContainerSpec.Image}}' rolling-deploy-svc

octopusdeploy/rolling-deploy-web-example:0.0.3@sha256:151a8f2aaed0192bf9f22eaeff487d546e6ff8fec4d0691e6697dede743b187c 

因为 Docker Swarm 知道我们部署的版本,所以我们可以使用rollback命令恢复到之前的版本(v0.0.2):

markh@ubuntu01:~$ sudo docker service rollback rolling-deploy-svc

rolling-deploy-svc
rollback: manually requested rollback
overall progress: rolling back update: 3 out of 3 tasks
1/3: running   [>                                                  ]
2/3: running   [>                                                  ]
3/3: running   [>                                                  ]
verify: Service converged 

一旦成功回滚,它就确认服务正在运行。

提示:
由于我没有给rollback命令指定任何参数,Docker 将默认一次回滚一个任务,每个任务之间没有延迟。您可以通过将以下内容传递给命令来指定不同的值:

  • --rollback-parallelism
  • --rollback-delay

Docker 文档中有您可以使用的参数的完整列表。

我们可以使用与之前相同的命令来检查服务,以验证回滚是否成功:

markh@ubuntu01:~$ sudo docker service inspect --format='{.Spec.TaskTemplate.ContainerSpec.Image}}' rolling-deploy-svc

octopusdeploy/rolling-deploy-web-example:0.0.2@sha256:4843a91ba84ace97cb11a6e3f68827a8c28a628d509159910c868f9ad02c3053 

这导致输出中出现预期的v0.0.2版本。

数据库回滚
当 Docker 中的服务利用数据库进行存储时,有一个适当的策略来处理服务回滚是很重要的,特别是当数据库在一个容器中时。Docker 不会自动回滚数据库更改,这可能会使您的数据库和应用程序处于不兼容状态。Docker 存储文档提供了一些关于在容器中存储不同选项的指导。

码头服务清理

最后,要删除 Docker 服务,我们只需运行rm命令:

markh@ubuntu01:~$ sudo docker service rm rolling-deploy-svc 

Docker 摘要

如您所见,在 Docker 中进行滚动部署并不需要太多的设置。加上它对回滚的支持,使它成为一个值得考虑的有吸引力的选择。

Kubernetes 滚动更新

Kubernetes 中的滚动部署被称为滚动更新

pod 的实例将使用新实例进行增量更新。它支持在更新期间不可用的 pod 的最大数量或百分比,以及可以创建的新 pod 的最大数量。除此之外,Kubernetes 还有一个方便的内置特性,允许更新恢复到以前的版本。

为了找到更多关于 Kubernetes 的信息,我的同事 Shawn 继续他的容器系列,主要关注 Kubernetes。

Kubernetes 关于更新的教程包括一个展示其工作原理的漂亮图表:

Kubernetes 集群设置

和以前一样,我将在这个演示中重用我们预先构建的容器映像,这次使用的是 MicroK8s 。我还将主要在 SSH 终端会话中与它进行交互。

作者 Canonical 将 MicroK8s 描述为:

适用于 42 种 Linux 的 k8s 软件包。专为开发人员打造,非常适合 edge、IoT 和设备。

对于像我这样想尝试 Kubernetes 或者用它做一些开发的人来说,这很有用。

MicroK8s 的一个好处是它不像其他 Kubernetes 选择的那样需要任何类型的虚拟机(比如 Minikube )。

在安装 MicroK8s 之前,值得注意的是有一些先决条件:

  • Ubuntu 18.04 LTS 或 16.04 LTS(或其他支持snapd的 OS)。
  • 至少 20G 可用磁盘空间。
  • 4GB 内存。
  • 互联网连接。

为了安装 MicroK8s,我们运行snap命令:

markh@ubuntu01:~$ sudo snap install microk8s --classic

microk8s v1.17.0 from Canonical✓ installed 

完整安装输出
如果您想查看来自snap的完整安装输出,运行snap changes命令:

markh@ubuntu01:~$ snap changes

ID   Status  Spawn               Ready               Summary
1    Done    today at 10:17 UTC  today at 10:17 UTC  Initialize system state
2    Done    today at 10:17 UTC  today at 10:17 UTC  Initialize device
3    Done    today at 14:38 UTC  today at 14:38 UTC  Install "microk8s" snap 

由此,您可以运行命令snap change 3,其中3是上面ID列中用于安装 MicroK8s 的值。这将为您提供安装步骤的明细行。

Kubernetes 部署

现在我们已经安装并运行了 MicroK8s,让我们继续使用我们现有的映像rolling-deploy-web-example创建一个 Kubernetes 部署,并将其设置为在端口 5001 上侦听。

Google 将 Kubernetes 部署描述为:

表示一组没有唯一标识的多个相同的 pod。部署运行应用程序的多个副本,并自动替换任何失败或无响应的实例。通过这种方式,部署有助于确保应用程序的一个或多个实例可用于服务用户请求。部署由 Kubernetes 部署控制器管理。

这听起来非常适合滚动部署。

Kubernetes 容器化应用程序设置

为了设置我们的应用程序的部署,我们将使用与 MicroK8s 打包在一起的 kubectl 二进制文件。这是用于管理 Kubernetes 的命令行界面(CLI)。它特别添加了前缀microk8s.,以避免与您可能运行的任何其他 Kubernetes 实例发生命名冲突。如果我们运行create deployment命令:

markh@ubuntu01:~$ sudo microk8s.kubectl create deployment rollingdeploy-microk8s --image=octopusdeploy/rolling-deploy-web-example:0.0.1

deployment.apps/rollingdeploy-microk8s created 

MicroK8s 将创建我们的部署,并确认它已经成功创建。

接下来,我们将设置应用程序窗格监听端口5001。为此,我们运行 expose 命令:

markh@ubuntu01:~$ sudo microk8s.kubectl expose deployment rollingdeploy-microk8s --type=NodePort --port=5001

service/rollingdeploy-microk8s exposed 
Kubernetes 仪表板

虽然已经创建了rollingdeploy-microk8s pod,但它可能不会立即可用。我们可以通过使用 Kubernetes 仪表板查看我们的服务来检查它的状态,该仪表板作为插件包含在 MicroK8s 中。

试图远程访问仪表板需要您经历一些困难。启用附加组件后,我发现最简单的方法是通过运行kubectl proxy命令创建一个从我的机器到服务器的代理:

markh@ubuntu01:~$ sudo microk8s.kubectl proxy --accept-hosts=.* --address=0.0.0.0

Starting to serve on [::]:8001 

从那里你可以访问端口8001上的仪表板,但是你需要一个Kubeconfig文件或者Token来登录。更多详情请参见 MicroK8s 仪表板附件

注意:您可以通过为仪表板容器设置--enable-skip-login参数来跳过登录,但是不建议这样做,因为这违反了安全性最佳实践。

一旦打开,您就可以使用仪表板部署容器化的应用程序,管理集群资源并与之交互。

为了执行滚动更新,首先我们需要应用程序的多个副本。我们可以通过单击部署部分右侧的三个省略号,直接从仪表板扩展我们的部署:

对于我们的 Kubernetes 部署,我将所需副本更新为 3,这样我就可以执行滚动更新,然后点击缩放:

等价的 kubectl 命令
您可能已经注意到仪表板为我们的操作提供了运行的等价命令。来扩展我们的资源,那就是:

sudo microk8s.kubectl scale -n default deployment rollingdeploy-microk8s --replicas=3 

配置完 pod 后,我们可以通过运行get pod命令(名称可能不同)直接查询 pod 的状态来确认这一点:

markh@ubuntu01:~$ sudo microk8s.kubectl get pod

NAME                                      READY   STATUS    RESTARTS   AGE
rollingdeploy-microk8s-794bdc64c4-fv7zt   1/1     Running   0          76s
rollingdeploy-microk8s-794bdc64c4-t6mh5   1/1     Running   0          76s
rollingdeploy-microk8s-794bdc64c4-trr6f   1/1     Running   0          76s 

为了验证我们的应用程序正在工作,我们需要通过运行get service找到 Kubernetes 向我们在开始时创建的部署公开的端口:

markh@ubuntu01:~$ sudo microk8s.kubectl get service rollingdeploy-microk8s

NAME                     TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
rollingdeploy-microk8s   NodePort   10.152.183.39   <none>        5001:32334/TCP   1m 

在我的例子中,端口是32334,所以我访问服务的 URL 是:

http://ubuntu01.octopusdemos.com:32334 

注意:
在您自己的机器上运行时,端口可能会有所不同。Kubernetes 将在 30000-32767(默认)范围内随机分配一个端口,因为我们在前面运行expose命令时选择了NodePort类型。

在浏览器中打开 URL,我们可以看到在 Microk8s 中运行的应用程序有v0.0.1:

Kubernetes 滚动更新

让我们继续,通过运行set image命令,指示 Kubernetes 用我们的图像octopusdeploy/rolling-deploy-web-examplev0.0.2更新我们的三个 pod:

markh@ubuntu01:~$ sudo microk8s.kubectl set image deployment/rollingdeploy-microk8s rolling-deploy-web-example=octopusdeploy/rolling-deploy-web-example:0.0.2 --record

deployment.apps/rollingdeploy-microk8s image updated 

接下来,我们可以通过运行rollout status命令来观察我们的首次展示的实时进度,直到它完成:

markh@ubuntu01:~$ sudo microk8s.kubectl rollout status deployment.v1.apps/rollingdeploy-microk8s

Waiting for deployment "rollingdeploy-microk8s" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "rollingdeploy-microk8s" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "rollingdeploy-microk8s" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "rollingdeploy-microk8s" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "rollingdeploy-microk8s" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "rollingdeploy-microk8s" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "rollingdeploy-microk8s" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "rollingdeploy-microk8s" rollout to finish: 1 old replicas are pending termination...
deployment "rollingdeploy-microk8s" successfully rolled out 

您可以看到它表示一次更新一个 Pod。

Kubernetes 部署确保只有一定数量的 pod 在更新时关闭。它通过创建一个新的 pod 并在完成后销毁旧的 pod 来实现这一点。

默认 pod 更新控制

默认情况下,Kubernetes 确保至少有 75%的期望数量的 pod 可用。此外,另一个默认情况是创建不超过 25%的总体期望数量的 pod。

在浏览器中打开 URL,我们可以看到我们的应用程序的v0.0.2在 Microk8s 中运行:

这里触发了部署的首次展示,因为set image导致了对底层部署 pod 的模板的更新。模板是描述复制控制器创建实际 pod 的方式的规范文档。

通过运行edit,我们可以看到我们的应用程序的模板是什么样子的:

markh@ubuntu01:~$ sudo microk8s.kubectl edit deployment.v1.apps/rollingdeploy-microk8s 

这将在文本编辑器中打开模板文件。对我来说,那是在终端本身。您可以交互编辑该文件。更改部署窗格的模板(.spec.template中的部分)将导致触发部署的首次展示:

提示:
对部署的其他更新,如我们之前所做的缩放,不会导致部署被触发。

Kubernetes 部署回滚

成功的滚动部署显然是我们所有人都希望的,但是不可避免的是,在某个时候,您需要启动回滚,要么是在部署过程的一部分,要么是在一段时间之后。

使用 Kubernetes,默认情况下,所有部署的部署历史都保存在系统中。这意味着,您可以随时回滚。

注意:
可以更改为部署的首次部署存储的历史量(通过修改修订历史限制),但通常不建议这样做,因为这会限制您回滚部署的能力。

为了查看我们部署的首次展示历史,我们运行rollout history命令:

markh@ubuntu01:~$ sudo microk8s.kubectl rollout history deployment.v1.apps/rollingdeploy-microk8s

deployment.apps/rollingdeploy-microk8s
REVISION  CHANGE-CAUSE
1         <none>
2         kubectl set image deployment/rollingdeploy-microk8s rolling-deploy-web-example=octopusdeploy/rolling-deploy-web-example:0.0.2 --kubeconfig=/var/snap/microk8s/1107/credentials/client.config --record=true 

我们可以选择通过运行rollout undo恢复到之前部署的v0.0.1版本:

markh@ubuntu01:~$ sudo microk8s.kubectl rollout undo deployment.v1.apps/rollingdeploy-microk8s

deployment.apps/rollingdeploy-microk8s rolled back 

提示:

markh@ubuntu01:~$ sudo microk8s.kubectl rollout undo deployment.v1.apps/rollingdeploy-microk8s --to-revision=1 

其中--to-revision参数包含您希望返回的版本。

Kubernetes 文档中有您可以使用的参数的完整列表。

我们可以确认我们已经回滚,或者通过查看仪表板,在浏览器中查看应用程序,或者通过运行describe命令:

markh@ubuntu01:~$ sudo microk8s.kubectl describe deployment

Name:                   rollingdeploy-microk8s
Namespace:              default
CreationTimestamp:      Wed, 22 Jan 2020 13:19:30 +0000
Labels:                 app=rollingdeploy-microk8s
Annotations:            deployment.kubernetes.io/revision: 3
Selector:               app=rollingdeploy-microk8s
Replicas:               3 desired | 3 updated | 3 total | 3 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod Template:
  Labels:  app=rollingdeploy-microk8s
  Containers:
   rolling-deploy-web-example:
    Image:        octopusdeploy/rolling-deploy-web-example:0.0.1
    Port:         <none>
    Host Port:    <none>
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    NewReplicaSetAvailable
OldReplicaSets:  <none>
NewReplicaSet:   rollingdeploy-microk8s-794bdc64c4 (3/3 replicas created)
Events: 

这表明Image被设置为octopusdeploy/rolling-deploy-web-example:0.0.1,正如我们所预期的。

数据库回滚
与 Docker 一样,重要的是要了解当您启动 Kubernetes 回滚时,您拥有的任何数据库将会发生什么。谷歌有一篇很棒的文章更深入地讨论了在 Kubernetes 上运行数据库。

Kubernetes 部署清理

为了删除与我们的 Kubernetes 部署相关的所有资源,我们使用delete命令:

markh@ubuntu01:~$ sudo microk8s.kubectl delete services,deployment rollingdeploy-microk8s -n default

service "rollingdeploy-microk8s" deleted
deployment.apps "rollingdeploy-microk8s" deleted 

Kubernetes 摘要

与 Docker 相比,Kubernetes 的滚动部署设置似乎要多一点,特别是在访问仪表板方面,但是在完成所有配置之后,它运行得非常好。

使用 Octopus 进行滚动部署

Octopus 从 Octopus 2.0 开始就支持滚动部署的概念。

通过使用子步骤,我们可以为 Octopus 中的rolling-deploy-web-example应用程序设置部署流程。

创建新项目后,我们通过三个步骤配置滚动部署:

  • 从负载平衡器中删除节点的脚本。
  • web 应用程序的部署。
  • 将节点添加回负载平衡器的脚本。

为了在 Octopus 中实现增量发布,我们需要使我们的窗口大小低于部署目标的总数。在我的例子中,我将其设置为1,如下所示:

我有两个配置了目标角色 : rolling-deploy-webapp的部署目标。

当我将这个版本部署到Test环境时,Octopus 一次部署到一个部署目标,正如我在前面的部署过程中配置的那样:

这就是全部了!查看我们的文档以获得关于 Octopus 中滚动部署的完整参考。

样本 Octopus 项目
您可以在我们的样本实例中查看这个 Octopus 项目设置。

关于数据库的一句话

通常滚动部署的主要症结之一是数据库。执行涉及某种持久存储的滚动部署可能很棘手,但并非不可能。魔鬼总是在细节中。

如果您希望执行包含数据库更改的滚动部署,我建议首先部署数据库。您还希望确保对数据库所做的任何更改都与您已部署的代码的以前版本向后兼容。

最后,经常测试你的回滚策略绝对是个好主意

*## 结论

无论您使用哪种工具,滚动部署只是优化软件部署的一种模式。但是使用增量方法,它允许您保持应用程序在线,同时以受控的方式推出软件的新版本,通常具有对回滚的本机支持,这使它成为我最喜欢的最小化中断的方法。

CI/CD 管道指南
如果您需要任何其他帮助来配置您的 CI/CD 管道,我们已经创建了 Octopus 指南,其中包含为各种技术堆栈(包括 Docker 和 Kubernetes)设置 CI/CD 管道的分步说明。

欢迎发表评论,让我们知道您对滚动部署的想法!*

Tomcat 部署的最终指南- Octopus Deploy

原文:https://octopus.com/blog/ultimate-guide-to-tomcat-deployments

The ultimate guide to Tomcat deployments

持续集成和交付(CI/CD)是 DevOps 团队降低成本和增加软件团队敏捷性的共同目标。但是 CI/CD 管道不仅仅是测试、编译和部署应用程序。强大的 CI/CD 渠道解决了许多问题,例如:

  • 高可用性(HA)
  • 多重环境
  • 零停机部署
  • 数据库迁移
  • 负载平衡器
  • HTTPS 和证书管理
  • 功能分支部署
  • 烟雾测试
  • 回滚策略

如何实现这些目标取决于所部署的软件类型。在本文中,我将介绍如何通过将 Java 应用程序部署到 Tomcat 来创建 CI/CD 管道的持续交付(或部署)部分。然后,我构建了一个支持性的基础设施栈,其中包括用于负载平衡的 Apache web 服务器、用于数据库的 PostgreSQL、用于高可用性负载平衡器的 Keepalived 和用于协调部署的 Octopus。

关于 PostgreSQL 服务器的说明

本文假设 PostgreSQL 数据库已经部署在高可用性配置中。有关如何部署 PostgreSQL 的更多信息,请参考文档

本文中的说明可用于单个 PostgreSQL 实例,理解数据库代表单点故障。

如何在 Tomcat 中实现 HA

当谈到 HA 时,重要的是要准确理解需要管理平台的哪些组件来解决单个 Tomcat 实例的不可用性,例如,当一个实例由于日常维护或硬件故障而不可用时。

出于本文的目的,我创建了一个基础设施,允许传统的有状态 Java servlet 应用程序在单个 Tomcat 服务器不再可用时继续运行。实际上,这意味着当最初托管会话的服务器不再可用时,应用程序会话状态将持续存在并被分发到集群中的其他 Tomcat 实例。

简单回顾一下,Java servlet 应用程序可以根据一个HttpSession实例保存数据,然后可以跨请求使用。在下面的(天真的,因为它不处理竞争条件)例子中,我们有一个简单的计数器变量,它随着对页面的每个请求而递增。这演示了如何在 web 浏览器发出的单个请求中保存信息:

@RequestMapping("/pageCount")
public String index(final HttpSession httpSession) {
    httpSession.setAttribute("pageCount", ObjectUtils.defaultIfNull((Integer)httpSession.getAttribute("pageCount"), 0) + 1);
    return (Integer)httpSession.getAttribute("pageCount");
} 

会话状态保存在单个服务器的内存中。默认情况下,如果该服务器不再可用,会话数据也会丢失。对于像页面计数这样的小例子来说,这并不重要,但是对于更关键的功能来说,依赖于会话状态的情况并不少见。例如,购物车可能在会话状态中保存了要购买的商品列表,丢失该信息可能会导致销售失败。

为了保持高可用性,需要复制会话状态,以便在服务器脱机时可以共享。

Tomcat 提供了三种实现会话复制的解决方案:

  1. 使用会话持久性并将会话保存到共享文件系统(PersistenceManager + FileStore)。
  2. 使用会话持久性并将会话保存到共享数据库(PersistenceManager + JDBCStore)。
  3. 使用内存复制,并使用 Tomcat 附带的 SimpleTcpCluster(lib/catalina-tribes.jar+lib/catalina-ha.jar)。

因为我们的基础设施堆栈已经假设了一个高度可用的数据库,所以我将实现选项二。对我们来说,这可能是最简单的解决方案,因为我们不需要实现任何特殊的网络,并且我们可以重用现有的数据库。但是,这种解决方案确实会在会话状态被修改和被保存到数据库之间引入延迟。这种延迟会产生一个窗口,在此期间,如果硬件或网络出现故障,数据可能会丢失。尽管支持计划的维护任务,因为当 Tomcat 关闭时,任何会话数据都将被写入数据库,这允许我们安全地修补操作系统或更新 Tomcat 本身。

我们注意到上面的示例代码很简单,因为它没有处理会话缓存不是线程安全的事实。即使是这个简单的例子也会受到可能导致页面计数不正确的竞争条件的影响。这个问题的解决方案是使用 Java 中可用的传统线程锁和同步特性,但是这些特性只在单个 JVM 中有效。这意味着我们必须确保客户端请求总是指向单个 Tomcat 实例,这又意味着只有一个 Tomcat 实例包含会话状态的单个权威副本,这可以通过线程锁和同步来确保一致性。这是通过粘性会话实现的。

粘性会话提供了一种方法,让负载平衡器检查客户端请求,然后将其定向到集群中的一个 web 服务器。默认情况下,在 Java servlet 应用程序中,客户端由 web 浏览器发送的JSESSIONID cookie 来标识,负载平衡器检查该 cookie 以标识持有会话的 Tomcat 实例,然后 Tomcat 服务器将请求与现有会话相关联。

总之,我们的 HA Tomcat 解决方案:

  • 将会话状态保存到共享数据库中。
  • 依靠粘性会话将客户端请求定向到单个 Tomcat 实例。
  • 当 Tomcat 关闭时,通过保持会话状态来支持日常维护。
  • 有一个小窗口,在此期间,硬件或网络故障可能会导致数据丢失。

为 HA 负载平衡器保持活动状态

为了确保网络请求分布在多个 Tomcat 实例中,而不是指向一个脱机实例,我们需要实现一个负载平衡解决方案。这些负载平衡器位于 Tomcat 实例的前面,将网络请求导向那些可用的实例。

有许多负载平衡解决方案可以完成这个任务,但是在这篇文章中,我们使用带有 mod_jk 插件的 Apache web 服务器。Apache 将提供网络功能,而 mod_jk 将把流量分配给多个 Tomcat 实例,实现粘性会话,为每个请求将客户机定向到同一个后端服务器。

为了保持高可用性,我们至少需要两个负载平衡器。但是,我们如何以一种可靠的方式在两个负载平衡器之间分割一个单一的传入网络连接呢?这就是 Keepalived 的用武之地。

Keepalived 是一个跨多个实例运行的 Linux 服务,它从健康实例池中挑选一个主实例。在确定主实例做什么时,Keepalived 非常灵活,但是在我们的场景中,我们将使用 Keepalived 为承担主角色的实例分配一个虚拟的浮动 IP 地址。这意味着我们的传入网络流量将被发送到一个分配给健康负载平衡器的浮动 IP 地址,然后负载平衡器将流量转发给 Tomcat 实例。如果其中一个负载平衡器脱机,Keepalived 将确保为剩余的负载平衡器分配浮动 IP 地址。

总之,我们的高可用性负载平衡解决方案:

  • 使用 mod_jk 插件实现 Apache,将流量定向到 Tomcat 实例。
  • 实施 Keepalived 以确保一个负载平衡器分配有虚拟 IP 地址。

网络图

这是我们将要创建的网络图:

零停机部署和回滚

持续交付的一个目标是始终处于可以部署的状态(即使您选择不部署)。这意味着放弃要求人们在午夜醒来,在客户睡觉时执行部署的计划。

零停机部署要求达到可以随时无中断完成部署的程度。在我们的示例基础架构中,为了实现零停机部署,我们需要考虑两点:

  • 客户能够使用现有版本的应用程序来完成他们的会话,即使在部署了新版本的应用程序之后。
  • 任何数据库更改的向前和向后兼容性。

在推出新的应用程序版本时,确保数据库更改向后和向前兼容需要一些设计工作和规则。幸运的是,有可用的工具,包括 FlywayLiquidbase ,它们提供了一种通过应用程序本身推出数据库更改的方法,负责对更改进行版本控制,并在所需的事务中包装任何迁移。我们将在本文后面的示例应用程序中看到 Flyway 的实现。

只要共享数据库在应用程序的当前版本和新版本之间保持兼容,Tomcat 就会提供一个名为并行部署的特性,允许客户端继续访问应用程序的先前版本,直到它们的会话到期,同时针对应用程序的新版本创建新的会话。并行部署允许在不中断任何现有客户端的情况下部署新版本的应用程序。

Tomcat 能够自动清理不再有任何会话的旧版本。但是我们不会启用这个特性,因为它可能会阻止旧版本的会话迁移到另一个 Tomcat 实例。

确保数据库更改在应用程序的当前版本和新版本之间兼容,意味着我们可以轻松回滚应用程序部署。如果新版本引入了任何错误,重新部署应用程序的先前版本可以提供快速的后备。

总之,我们的零停机部署解决方案:

  • 依赖于向前和向后兼容的数据库更改(至少在应用程序的新版本和当前版本之间)。
  • 使用并行部署来允许现有会话不间断地完成。
  • 通过恢复到以前安装的应用程序版本来提供应用程序回滚。

建设基础设施

此处显示的示例基础架构部署在 Ubuntu 18.04 虚拟机上。尽管一些包名和文件位置可能会改变,但大多数指令都与发行版无关。

配置 Tomcat 实例

安装软件包

我们从安装 Tomcat 和管理器应用程序开始:

apt-get install tomcat tomcat-admin 

添加 AJP 连接器

Apache web 服务器和 Tomcat 之间的通信是通过 AJP 连接器执行的。AJP 是一个优化的二进制 HTTP 协议,Apache 和 Tomcat 的 mod_jk 插件都理解它。连接器被添加到/etc/tomcat9/server.xml文件中的Service元素:

<Server>
  <!-- ... -->
  <Service name="Catalina">
    <!-- ... -->
    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443"></Connector>
  </Service>
</Server> 

定义 Tomcat 实例名

每个 Tomcat 实例都需要在/etc/tomcat9/server.xml文件的Engine元素中添加一个惟一的名称。默认的Engine元素如下所示:

<Engine name="Catalina" defaultHost="localhost"> 

Tomcat 实例的名称在jvmRoute属性中定义。我将调用第一个 Tomcat 实例worker1:

<Engine defaultHost="localhost" name="Catalina" jvmRoute="worker1"> 

第二个 Tomcat 实例被称为worker2:

<Engine defaultHost="localhost" name="Catalina" jvmRoute="worker2"> 

添加经理用户

Octopus 通过管理器应用程序向 Tomcat 执行部署。这是我们之前用tomcat-admin包安装的。

为了对管理应用程序进行认证,需要在/etc/tomcat9/tomcat-users.xml文件中定义一个新用户。我将用密码Password01!调用这个用户tomcat,它将属于manager-scriptmanager-gui角色。

manager-script角色授予对管理器 API 的访问权限,而manager-gui角色授予对管理器 web 控制台的访问权限。

这里是用户定义的tomcat文件的副本:

<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-gui"/>
  <role rolename="manager-script"/>
  <user username="tomcat" password="Password01!" roles="manager-script,manager-gui"/>
</tomcat-users> 

添加 PostgreSQL JDBC 驱动程序 jar

每个 Tomcat 实例将与 PostgreSQL 数据库通信,以持久化会话数据。为了让 Tomcat 与 PostgreSQL 数据库通信,我们需要安装 PostgreSQL JDBC 驱动程序 JAR 文件。这是通过将文件https://jdbc.postgresql.org/download/postgresql-42.2.11.jar保存为/var/lib/tomcat9/lib/postgresql-42.2.11.jar来完成的。

启用会话复制

为了支持数据库的会话持久性,我们在文件/etc/tomcat9/context.xml中添加了一个新的Manager定义。这个管理器使用org.apache.catalina.session.PersistentManager将会话细节保存到嵌套的Store元素中定义的数据库中。

元素反过来定义了会话信息将被保存到的数据库。

我们还需要添加一个装载org.apache.catalina.ha.session.JvmRouteBinderValve类的Valve。当客户机从一个 Tomcat 实例重定向到集群中的另一个 Tomcat 实例时,这个阀门非常重要。在部署我们的示例应用程序后,我们将看到这个阀门的运行。

这里是定义了ManagerStoreValve元素的/etc/tomcat9/context.xml文件的副本:

<Context>
  <Manager
    className="org.apache.catalina.session.PersistentManager"
    processExpiresFrequency="3"
    maxIdleBackup="1" >
      <Store
        className="org.apache.catalina.session.JDBCStore"
        driverName="org.postgresql.Driver"
        connectionURL="jdbc:postgresql://postgresserver:5432/tomcat?currentSchema=session"
        connectionName="postgres"
        connectionPassword="passwordgoeshere"
        sessionAppCol="app_name"
        sessionDataCol="session_data"
        sessionIdCol="session_id"
        sessionLastAccessedCol="last_access"
        sessionMaxInactiveCol="max_inactive"
        sessionTable="session.tomcat_sessions"
        sessionValidCol="valid_session" />
  </Manager>
  <Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>
  <WatchedResource>WEB-INF/web.xml</WatchedResource>
  <WatchedResource>WEB-INF/tomcat-web.xml</WatchedResource>
  <WatchedResource>${catalina.base}/conf/web.xml</WatchedResource>
</Context> 

配置 PostgreSQL 数据库

安装软件包

我们需要用新的数据库、模式和表来初始化 PostgreSQL。为此,我们使用psql命令行工具,该工具随以下软件一起安装:

apt-get install postgresql-client 

添加数据库、模式和表

如果您查看上面定义的/etc/tomcat9/context.xml文件中的connectionURL属性,您会看到我们正在将会话信息保存到:

  • 这个数据库叫做tomcat
  • 这个模式叫做session
  • 一个叫tomcat_sessions的桌子。

为了在 PostgreSQL 服务器中创建这些资源,我们运行了许多 SQL 命令。

首先,将以下文本保存到一个名为createdb.sql的文件中。如果数据库不存在,该命令将创建数据库(关于语法的更多信息,请参见 this StackOverflow 帖子):

SELECT 'CREATE DATABASE tomcat' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'tomcat')\gexec 

然后用下面的命令执行 SQL,用 PostgreSQL 服务器的主机名替换postgresserver:

cat createdb.sql | /usr/bin/psql -a -U postgres -h postgresserver 

接下来,我们创建模式和表。将以下文本保存到名为createschema.sql的文件中。注意tomcat_sessions表的列匹配/etc/tomcat9/context.xml文件中Store元素的属性:

CREATE SCHEMA IF NOT EXISTS session;

CREATE TABLE IF NOT EXISTS session.tomcat_sessions
(
  session_id character varying(100) NOT NULL,
  valid_session character(1) NOT NULL,
  max_inactive integer NOT NULL,
  last_access bigint NOT NULL,
  app_name character varying(255),
  session_data bytea,
  CONSTRAINT tomcat_sessions_pkey PRIMARY KEY (session_id)
);

CREATE INDEX IF NOT EXISTS app_name_index
  ON session.tomcat_sessions
  USING btree
  (app_name); 

然后使用以下命令执行 SQL,用 PostgreSQL 服务器的主机名替换postgresserver:

psql -a -d tomcat -U postgres -h postgresserver -f /root/createschema.sql 

我们现在在 PostgreSQL 中有了一个表,可以保存 Tomcat 会话。

配置负载平衡器

安装软件包

我们需要安装 Apache web 服务器、mod_jk 插件和 Keepalived 服务:

apt-get install apache2 libapache2-mod-jk keepalived 

配置负载平衡器

mod_jk 插件通过文件/etc/libapache2-mod-jk/workers.properties进行配置。在这个文件中,我们定义了流量可以被定向到的工作线程的数量。该文件中的字段记录在这里

我们首先定义一个名为loadbalancer的工作者,它将接收所有的流量:

worker.list=loadbalancer 

然后,我们定义前面创建的两个 Tomcat 实例。确保用匹配的 Tomcat 实例的 IP 地址替换worker1_ipworker2_ip

注意,这里定义为worker1worker2的工人的名字与/etc/tomcat9/server.xml文件中Engine元素的jvmRoute属性值相匹配。这些名称必须匹配,因为 mod_jk 使用它们来实现粘性会话:

worker.worker1.type=ajp13
worker.worker1.host=worker1_ip
worker.worker1.port=8009

worker.worker2.type=ajp13
worker.worker2.host=worker2_ip
worker.worker2.port=8009 

最后,我们将loadbalancer工作线程定义为负载平衡器,它将流量导向启用了粘性会话的worker1worker2工作线程:

worker.loadbalancer.type=lb
worker.loadbalancer.balance_workers=worker1,worker2
worker.loadbalancer.sticky_session=1 

以下是/etc/libapache2-mod-jk/workers.properties文件的完整副本:

# All traffic is directed to the load balancer
worker.list=loadbalancer

# Set properties for workers (ajp13)
worker.worker1.type=ajp13
worker.worker1.host=worker1_ip
worker.worker1.port=8009

worker.worker2.type=ajp13
worker.worker2.host=worker2_ip
worker.worker2.port=8009

# Load-balancing behaviour
worker.loadbalancer.type=lb
worker.loadbalancer.balance_workers=worker1,worker2
worker.loadbalancer.sticky_session=1 

添加 Apache 虚拟主机

为了让 Apache 接受流量,我们需要定义一个VirtualHost,我们在文件/etc/apache2/sites-enabled/000-default.conf中创建它。这个虚拟主机将接受端口 80 上的 HTTP 流量,定义一些日志文件,并使用JkMount指令将流量转发给名为loadbalancer的工作器:

<VirtualHost *:80>
  ErrorLog ${APACHE_LOG_DIR}/error.log
  CustomLog ${APACHE_LOG_DIR}/access.log combined
  JkMount /* loadbalancer
</VirtualHost> 

配置保持活动状态

我们有两个负载平衡器,以确保在任何给定时间都可以让其中一个脱机进行维护。Keepalived 是我们用来将虚拟 IP 地址分配给一个负载平衡器服务的服务,Keepalived 称为主服务。

Keepalived 是通过/etc/keepalived/keepalived.conf文件配置的。

我们从命名负载平衡器实例开始。第一个负载均衡器叫做loadbalancer1:

vrrp_instance loadbalancer1 { 

state主机指定活动服务器:

state MASTER 

interface参数将物理接口名称分配给这个特定的虚拟 IP 实例:

运行ifconfig可以找到接口名。

interface ens5 

virtual_router_id是虚拟路由器实例的数字标识符。它必须在参与此虚拟路由器的所有 LVS 路由器系统上相同。它用于区分在同一网络接口上运行的多个 Keepalived 实例:

virtual_router_id 101 

priority指定分配的接口在故障转移中接管的顺序;数字越大,优先级越高。该优先级值必须在 0 到 255 的范围内,并且配置为状态MASTER的负载均衡服务器的优先级值应该设置为比配置为状态BACKUP的服务器的优先级值更高的数字:

priority 101 

advert_int定义发送 VRRP 广告的频率:

advert_int 1 

authentication块指定用于验证服务器故障转移同步的验证类型(auth_type和密码(auth_pass)。PASS指定密码验证:

authentication {
    auth_type PASS
    auth_pass passwordgoeshere
} 

unicast_src_ip是此负载平衡器的 IP 地址:

unicast_src_ip 10.0.0.20 

unicast_peer列出其他负载平衡器的 IP 地址。因为我们总共有两个负载平衡器,所以这里只列出了另一个负载平衡器:

unicast_peer {
  10.0.0.21
} 

virtual_ipaddress定义 Keepalived 分配给主节点的虚拟或浮动 IP 地址:

virtual_ipaddress {
    10.0.0.30
} 

这里是第一个负载平衡器的/etc/keepalived/keepalived.conf文件的完整副本:

vrrp_instance loadbalancer1 {
    state MASTER
    interface ens5
    virtual_router_id 101
    priority 101
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass passwordgoeshere
    }
    # Replace unicast_src_ip and unicast_peer with your load balancer IP addresses
    unicast_src_ip 10.0.0.20
    unicast_peer {
      10.0.0.21
    }
    virtual_ipaddress {
        10.0.0.30
    }
} 

这里是第二个负载平衡器的/etc/keepalived/keepalived.conf文件的完整副本。

注意,名称已被设置为loadbalancer2,state已被设置为BACKUP,priority100低,并且unicast_src_ipunicast_peer IP 地址已被翻转:

vrrp_instance loadbalancer2 {
    state BACKUP
    interface ens5
    virtual_router_id 101
    priority 100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass passwordgoeshere
    }
    # Replace unicast_src_ip and unicast_peer with your load balancer IP addresses
    unicast_src_ip 10.0.0.21
    unicast_peer {
      10.0.0.20
    }
    virtual_ipaddress {
        10.0.0.30
    }
} 

使用以下命令在两个负载平衡器上重新启动keepalived服务:

systemctl restart keepalived 

在第一个负载平衡器上,运行命令ip addr。这将显示分配给接口的虚拟 IP 地址,Keepalived 被配置为使用输出inet 10.0.0.30/32 scope global ens5进行管理:

$ ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: ens5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP group default qlen 1000
    link/ether 0e:2b:f9:2a:fa:a7 brd ff:ff:ff:ff:ff:ff
    inet 10.0.0.21/24 brd 10.0.0.255 scope global dynamic ens5
       valid_lft 3238sec preferred_lft 3238sec
    inet 10.0.0.30/32 scope global ens5
       valid_lft forever preferred_lft forever
    inet6 fe80::c2b:f9ff:fe2a:faa7/64 scope link
       valid_lft forever preferred_lft forever 

如果第一个负载平衡器关闭,第二个负载平衡器将采用虚拟 IP 地址,第二个 Apache web 服务器将充当负载平衡器。

构建部署管道

我们的部署管道将包括部署随机报价示例应用程序。这是一个简单的有状态 Spring Boot 应用程序,利用 Flyway 来管理数据库迁移。

当您单击刷新按钮时,一个新的报价从数据库中加载,一个计数器在会话中递增,并且计数器在页面上显示为报价计数字段。应用程序版本显示在版本字段中。

我们可以使用引用计数版本信息来验证在执行新部署或 Tomcat 实例离线时,现有会话是否被保留。

获取一个 Octopus 实例

如果你还没有安装 Octopus,获得 Octopus 实例最简单的方法是注册一个云账户。这些实例对多达 10 个目标是免费的。

创造环境

我们将为这个例子创建两个环境: DevProd 。这意味着我们将总共配置八个目标:四个负载平衡器和四个 Tomcat 实例。

展开触手

我们将在我们的每个虚拟机上安装一个触手,以允许我们执行部署、更新和系统维护任务。安装触手软件的说明可以在 Octopus 下载页面上找到。由于本例使用 Ubuntu 作为基本操作系统,我们用以下命令安装了触手:

apt-key adv --fetch-keys https://apt.octopus.com/public.key
add-apt-repository "deb https://apt.octopus.com/ stretch main"
apt-get update
apt-get install tentacle 

在安装了触手之后,我们用以下命令配置一个实例:

/opt/octopus/tentacle/configure-tentacle.sh 

该安装让你在轮询或监听触角之间做出选择。您选择哪个选项通常取决于您的网络限制。轮询触角要求托管触角的虚拟机能够到达 Octopus 服务器,而监听触角要求 Octopus 服务器能够到达虚拟机。通信方式的选择取决于 Octopus 服务器或 VMs 是否有固定的 IP 地址,以及防火墙中是否打开了正确的端口。任一选项都是有效的选择,不会影响部署过程。

这里是一个 Dev 环境的截图,带有 Tomcat 和负载平衡器实例的触角。Tomcat 实例的角色是 tomcat ,负载平衡器实例的角色是负载平衡器:

创建外部提要

随机报价示例应用程序已经作为 WAR 文件推送到Maven Central。这意味着我们可以直接从 Maven 提要部署应用程序。

创建一个指向https://repo.maven.apache.org/maven2/的新 Maven 提要。此提要的屏幕截图如下所示:

通过搜索com . octopus:random quotes来测试提要。在这里,我们可以看到我们的应用程序位于存储库中:

创建部署流程

生成时间戳

为了支持零停机部署,我们希望利用 Tomcat 中的并行部署特性。通过在部署每个应用程序时对其进行版本控制,可以实现并行部署。

此版本号使用字符串比较来确定最新版本。典型的版本化方案,比如 SemVer,使用一种 major.minor.patch 格式,比如 1.23.4 ,来标识版本。在很多情况下,这些传统的版本化方案可以作为字符串来比较,以确定它们的顺序。

但是,填充会带来一些问题。比如版本 1.23.4001.23.41 低,但是直接字符串比较会返回相反的结果。

出于这个原因,我们使用部署时间作为 Tomcat 版本。因为版本需要跨目标保持一致,所以我们从一个脚本步骤开始,该步骤生成一个时间戳,并将其保存到一个输出变量中,代码如下:

$timestamp = Get-Date -Format "yyMMddHHmmss"
Set-OctopusVariable -name "TimeStamp" -value $timestamp 

Tomcat 部署

我们的部署流程从使用Deploy to Tomcat via Manager步骤将应用程序部署到每个 Tomcat 实例开始。我们将这个步骤称为随机报价,并在 tomcat 目标上运行它:

我们从之前设置的 Maven 提要中部署com . octopus:random quotes包:

因为触手位于托管 Tomcat 的 VM 上,所以管理器 API 的位置是http://localhost:8080/Manager。然后我们提供管理器凭证,这是我们配置 Tomcat 时输入到tomcat-users.xml文件中的详细信息:

上下文路径构成了 URL 中可访问已部署应用程序的路径。这里我们公开了路径 /randomquotes 上的应用程序:

T31

通过引用输出变量 #{Octopus,部署版本被设置为上一步生成的时间戳。动作[获取时间戳].输出.时间戳} :

对部署进行冒烟测试

为了验证我们的部署是否成功,我们发出一个 HTTP 请求并检查响应代码。为此,我们使用一个名为 HTTP - Test URL (Bash) 的社区步骤模板。

和以前一样,这个步骤将在 Tomcat 实例上运行:

该步骤将尝试从新部署的应用程序中打开index.html页面,预期 HTTP 响应代码为 200 :

执行初始部署

让我们继续执行初始部署。对于这个部署,我们将特别选择以前版本的随机报价应用程序。版本 0.1.6.1 在这种情况下,是我们的倒数第二个工件版本:

Octopus 然后从 Maven 存储库中下载 WAR 文件,将其推送到 Tomcat 实例,并通过管理器部署到 Tomcat。完成后,运行冒烟测试以确保应用程序可以成功打开:

通过堆栈检查请求

部署完成后,我们可以通过负载平衡器访问它。

在前面的配置示例中,我们有一个浮动 IP 地址 10.0.0.30,但是对于这些截图,我使用 Keepalived 为负载平衡器分配一个公共 IP 地址。

下面是用 Chrome 开发工具打开的应用程序的屏幕截图:

这张截图有三点需要注意:

  1. 用一个随机的会话 ID 和响应请求的 Tomcat 实例的名称来设置 JSESSIONID cookie。在这个例子中,其 jvmRoute 被设置为 worker1 的 Tomcat 实例响应了请求。
  2. 我们已经打开了应用程序的 0.1.6.1 版本。
  3. 报价计数设置为 1,但当我们点击刷新按钮时,报价计数将会增加。

让我们通过单击刷新按钮来增加报价计数。该值保存在 Tomcat 服务器上与 JSESSIONID cookie 相关联的会话中:

现在让我们关闭 worker1 Tomcat 实例。关机后,我们再次点击刷新按钮:

这张截图有三点需要注意:

  1. JSESSIONID cookie 上的后缀从 worker1 更改为 worker2
  2. JSESSIONID cookie 会话 ID 保持不变。
  3. 报价计数增加到 6。

当 Tomcat 正常关闭时,它会将所有会话的内容写到数据库中。然后因为 worker1 Tomcat 实例不再可用,请求被定向到 worker2 Tomcat 实例。 worker2 Tomcat 实例从数据库加载会话信息,并增加计数器。jvmroutebendervalve 阀重写了会话 cookie,将当前的 Tomcat 实例名附加到末尾,并且将响应返回给浏览器。

我们现在可以看到,负载平衡器/etc/libapache2-mod-jk/workers.properties文件中的工作者名称与分配给/etc/tomcat9/server.xml文件中的 jvmRoute 的名称相匹配是很重要的,因为匹配这些名称可以实现粘性会话。

因为引用计数没有重置回 1,所以我们知道会话被保存到数据库中,并被复制到集群中的其他 Tomcat 实例。我们还知道该请求是由另一个 Tomcat 实例处理的,因为 JSESSIONID cookie 显示了一个新的 worker 名称。

即使我们让 worker1 重新联机,这个浏览器会话也将继续由 worker2 处理,因为负载平衡器通过检查 JSESSIONID cookie 来实现粘性会话。这也意味着负载平衡器不需要共享状态,因为它们只需要 cookie 值来引导流量。

我们现在已经展示了 Tomcat 实例支持会话复制和故障转移,使它们高度可用。

为了演示负载平衡器的故障转移,我们只需要重新启动 Keepalived 指定为主服务器的实例。然后,我们可以使用以下命令查看备份实例上的事件:

journalctl -u keepalived -f 

很快,我们将看到这些消息出现在备份上,因为它承担了主角色:

Apr 01 03:15:00 ip-10-0-0-21 Keepalived_vrrp[32485]: VRRP_Instance(loadbalancer2) Transition to MASTER STATE
Apr 01 03:15:01 ip-10-0-0-21 Keepalived_vrrp[32485]: VRRP_Instance(loadbalancer2) Entering MASTER STATE 

承担主服务器角色后,负载平衡器将被分配虚拟 IP 地址,并像前一个主服务器一样分配流量。

在之前的主实例重新启动后,它将重新承担主角色,因为它配置了更高的优先级,并且虚拟 IP 地址将被分配回去。

整个过程是无缝的,上游客户端永远不需要知道发生了故障转移和故障恢复。因此,我们已经演示了负载平衡器可以进行故障转移,从而使它们高度可用。

总而言之:

  • JSESSIONID cookie 包含会话 ID 和处理请求的 Tomcat 实例的名称。
  • 负载平衡器基于附加到 JSESSIONID cookie 的工作者名称来实现粘性会话。
  • 当一个 Tomcat 实例接收到它原本不负责的会话的流量时,JvmRouteBinderValve valve 重写 JSESSIONID cookie。
  • 如果主负载平衡器脱机,Keepalived 会将一个虚拟 IP 分配给备用负载平衡器。
  • 当主负载平衡器重新联机时,它会重新承担虚拟 IP。
  • 基础设施栈可以在失去一个 Tomcat 实例和一个负载平衡器的情况下生存,并且仍然保持可用性。

零停机部署

我们现在已经成功地将 web 应用程序的版本 0.1.6.1 部署到 Tomcat。这个版本的应用程序使用一个非常简单的表结构来保存引用者的姓名,将名字和姓氏放在一个名为AUTHOR的列中。

该表结构最初是由 Flyway 数据库脚本使用以下 SQL 创建的:

create table AUTHOR
(
    ID INT auto_increment,
    AUTHOR VARCHAR(64) not null
); 

我们应用程序的下一个版本将把名字分成FIRSTNAMELASTNAME列。我们使用包含以下 SQL 的新 Flyway 脚本添加这些列:

ALTER TABLE AUTHOR
ADD FIRSTNAME varchar(64);

ALTER TABLE AUTHOR
ADD LASTNAME varchar(64); 

此时,我们必须考虑如何以向后兼容的方式进行这些更改。零停机部署策略的基石要求共享数据库同时支持应用程序的当前版本和新版本。不幸的是,没有提供这种兼容性的灵丹妙药,作为开发人员,我们有责任确保我们的更改不会破坏任何现有的会话。

在这个场景中,我们必须保留旧的AUTHOR列,并将它保存的数据复制到新的FIRSTNAMELASTNAME列中:

UPDATE AUTHOR SET FIRSTNAME = 'Rob', LASTNAME = 'Siltanen' WHERE ID = 1;
UPDATE AUTHOR SET FIRSTNAME = 'Albert', LASTNAME = 'Einstein' WHERE ID = 2;
UPDATE AUTHOR SET FIRSTNAME = 'Charles', LASTNAME = 'Eames' WHERE ID = 3;
UPDATE AUTHOR SET FIRSTNAME = 'Henry', LASTNAME = 'Ford' WHERE ID = 4;
UPDATE AUTHOR SET FIRSTNAME = 'Antoine', LASTNAME = 'de Saint-Exupery' WHERE ID = 5;
UPDATE AUTHOR SET FIRSTNAME = 'Salvador', LASTNAME = 'Dali' WHERE ID = 6;
UPDATE AUTHOR SET FIRSTNAME = 'M.C.', LASTNAME = 'Escher' WHERE ID = 7;
UPDATE AUTHOR SET FIRSTNAME = 'Paul', LASTNAME = 'Rand' WHERE ID = 8;
UPDATE AUTHOR SET FIRSTNAME = 'Elon', LASTNAME = 'Musk' WHERE ID = 9;
UPDATE AUTHOR SET FIRSTNAME = 'Jessica', LASTNAME = 'Hische' WHERE ID = 10;
UPDATE AUTHOR SET FIRSTNAME = 'Paul', LASTNAME = 'Rand' WHERE ID = 11;
UPDATE AUTHOR SET FIRSTNAME = 'Mark', LASTNAME = 'Weiser' WHERE ID = 12;
UPDATE AUTHOR SET FIRSTNAME = 'Pablo', LASTNAME = 'Picasso' WHERE ID = 13;
UPDATE AUTHOR SET FIRSTNAME = 'Charles', LASTNAME = 'Mingus' WHERE ID = 14; 

此外,新的 JPA 实体类需要忽略旧的AUTHOR列(通过@Transient注释)。然后,getAuthor()方法返回getFirstName()getLastName()方法的组合值:

@Entity
public class Author {
    @Id
    @GeneratedValue(strategy= GenerationType.AUTO)
    private Integer id;
    @Column(name = "FIRSTNAME")
    private String firstName;
    @Column(name = "LASTNAME")
    private String lastName;

    @OneToMany(
            mappedBy = "author",
            cascade = CascadeType.ALL,
            orphanRemoval = true
    )
    private List<Quote> quotes = new ArrayList<>();

    protected Author() {

    }

    public Integer getId() {
        return id;
    }

    @Transient
    public String getAuthor() {
        return getFirstName() + " " + getLastName();
    }

    public List<Quote> getQuotes() {
        return quotes;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }
} 

虽然这是一个简单的例子,但由于AUTHOR表是只读的,所以很容易实现,它演示了如何以向后兼容的方式实现数据库更改。有可能就维护向后兼容性的策略写一整本书,但是出于本文的目的,我们将把这个讨论留在这里。

在我们执行下一次部署之前,请重新打开现有应用程序并刷新一些报价。这将针对现有的 0.1.6.1 版本创建一个会话,我们将使用它来测试我们的零停机部署策略。

通过以向后兼容的方式编写迁移脚本,我们可以部署应用程序的新版本。为了方便起见,这个新版本已经作为版本 0.1.7 推送到 Maven Central:

部署完成后,在http://tomcatip:8080/manager/html打开管理器应用程序。注意,虽然您可以通过负载平衡器访问管理器,但是您不能选择要管理哪个 Tomcat 实例,因为负载平衡器会为您选择一个 Tomcat 实例。这意味着最好直接连接到 Tomcat 实例,绕过负载平衡器:

这张截图有两点需要注意:

  1. 我们在路径/randomquotes下有两个应用程序,每个都有一个唯一的版本。
  2. 早期版本的应用程序有一个与之关联的会话。这是我们在部署版本 0.1.7 之前通过访问版本 0.1.6.1 创建的会话。

如果我们回到打开应用程序版本 0.1.6.1 的浏览器,我们可以继续刷新报价。计数器如我们预期的那样增加,页脚中显示的版本号仍然是版本 0.1.6.1

如果我们在私人浏览窗口中重新打开应用程序,我们可以保证不会重用旧的会话 cookie,并且我们会被定向到应用程序的版本 0.1.7 :

因此,我们展示了零停机部署。因为我们的数据库迁移被设计成向后兼容的,所以应用程序的版本 0.1.6.1 和版本 0.1.7 可以使用 Tomcat 并行部署并行运行。最重要的是,旧部署的会话仍然可以在 Tomcat 实例之间转移,因此我们可以在并行部署的同时保持高可用性。

回滚策略

只要应用程序的最新版本和当前版本(在本例中是版本0.1.6.10.1.7)之间保持了数据库兼容性,回滚就像用应用程序的先前版本创建一个新部署一样简单。

因为 Tomcat 版本有一个在部署时计算的时间戳,部署应用程序的版本0.1.6.1再次导致它处理任何新的流量,因为它有一个更高的版本。

注意,由于 Tomcat 的并行部署,版本0.1.7的任何现有会话都将自然终止。如果这个版本必须离线(例如,如果有一个关键问题,它不能继续使用),我们可以使用 Tomcat 步骤中的开始/停止应用程序来停止一个已部署的应用程序。

我们将创建一个运行手册来运行这一步骤,因为这是一个维护任务,可能需要应用于任何环境来推出一个坏版本。

我们首先添加一个提示变量,该变量将填充与我们想要关闭的部署相对应的 Tomcat 版本时间戳:

然后,使用 Tomcat 步骤中的启动/停止应用程序配置运行手册。部署版本设置为提示变量的值:

运行 runbook 时,系统会提示我们输入要停止的应用程序的时间戳版本:

运行手册完成后,我们可以通过打开管理器控制台来验证应用程序是否已停止。在下面的截图中,你可以看到版本 200401140129 没有运行。此版本不再响应请求,所有将来的请求将被定向到应用程序的最新版本:

功能分支部署

一个常见的开发实践是在一个单独的 SCM 分支中完成一个特性,称为特性分支。

CI 服务器通常会观察特性分支的存在,并根据提交的代码创建一个可部署的工件。

这些特性分支工件然后被版本化,以指示它们是从哪个分支构建的。GitVersion 是一个流行的工具,用于生成版本以匹配 Git 中的提交和分支,他们提供了这个示例,展示了作为 GitHub 流的一部分而创建的版本:

从上图可以看出,对一个名为 myfeature 的特性分支的提交生成了一个类似于 1.2.1-myfeature.1+1 的版本。这又会产生一个文件名类似于myapp.1.2.1-myfeature.1+1.zip的工件。

尽管像 GitVersion 这样的工具会生成 SemVer 版本字符串,但同样的格式也可以用于 Maven 工件。然而,有一个问题。

SemVer 将订购一个功能分支低于没有任何预发布组件的版本。例如, 1.2.1 被认为是比 1.2.1-myfeature 更高的版本号。这是预期的顺序,因为功能分支最终将合并回主分支。

当一个特性分支被附加到 Maven 版本时,它被认为是一个限定符。Maven 允许任何限定符,但也识别一些特殊的限定符,如快照最终ga 等。完整的列表可以在博文 Maven 版本解释中找到。带有无法识别的限定符的 Maven 版本(特性分支名称是无法识别的限定符)被视为比未限定版本更高的版本。

这意味着 Maven 认为版本 1.2.1-myfeature 将比 1.2.1 更晚发布,而这显然不是特性分支的意图。您可以在一个托管在 GitHub 上的项目中通过下面的测试来验证这种行为。

然而,由于 Octopus 中的通道功能,我们可以确保 SemVer 预发布和 Maven 限定符都按照预期的方式进行过滤。

这是我们应用程序部署的默认通道。注意,预发布标签字段的正则表达式 ^$ 。这个正则表达式只匹配空字符串,这意味着默认通道将只部署没有预发布或限定符字符串的工件:

T31

接下来,我们有特征分支通道,它定义了一个正则表达式。+ 用于预发布标签字段。这个正则表达式只匹配非空字符串,这意味着功能分支通道将只部署带有预发布或限定符字符串的工件:

以下是 Octopus 允许在默认频道中发布的版本列表。注意,显示的唯一版本没有限定符,这意味着它是主版本:

以下是 Octopus 允许使用功能分支通道创建的版本列表。所有这些版本都有一个嵌入特性分支名称的限定符:

这些渠道的最终结果是,像 1.2.1-myfeature 这样的版本将永远不会与像 1.2.1 这样的版本进行比较,这消除了功能分支版本号被认为是后续版本的模糊性。

最后一步是将这些特性分支包部署到唯一的上下文中,这样就可以在单个 Tomcat 实例上并排访问它们。为此,我们将上下文路径修改为:

/randomquotes#{Octopus.Action.Package.PackageVersion | Replace "^([0-9\.]+)((?:-[A-Za-z0-9]+)?)(.*)$" "$2"} 

在版本1.2.1-myfeature.1+1上使用上面的正则表达式将执行以下操作:

  • ^([0-9\.]+)将版本开头的所有数字和句点分组为第 1 组,与1.2.1匹配。
  • ((?:-[A-Za-z0-9]+)?)将前导破折号和任何后续字母数字字符(如有)分组为第 2 组,匹配-myfeature
  • (.*)$将任何后续字符(如果有)分组为组 3,它匹配.1+1

这个变量过滤器将导致完整的预发布或限定符字符串被正则表达式中的第二组替换。这会产生以下上下文路径:

  • 版本1.2.1-myfeature.1+1生成/randomquotes-myfeature的上下文路径。
  • 版本1.2.1生成/randomquotes的上下文路径。

下面是应用了新上下文路径的 Tomcat 部署步骤的屏幕截图:

SemVer 项目提供了一个更加健壮的正则表达式来可靠地捕获 SemVer 版本中的组。

带有命名捕获组的正则表达式为:

^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$ 

没有命名组的正则表达式是:

^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$ 

公共证书管理

为了完成基础设施的配置,我们将通过负载平衡器启用 HTTPS 访问。我们可以编辑 Apache web 服务器虚拟主机配置来启用 SSL,并指向我们为我们的域获得的密钥和证书。

获得证书

在这篇文章中,我获得了一个由我们的 DNS 提供商生成的加密证书。生成 HTTPS 证书的确切过程不是我们在这里要讨论的,但是您可以参考您的 DNS 或证书提供商以获得具体的指导。

在下面的截图中,您可以看到下载证书的各种选项。注意,虽然为 Apache 提供了说明和下载,但我们下载的是 IIS 说明下提供的 PFX 文件。PFX 文件在一个文件中包含公钥、私钥和证书链。我们需要这个文件将证书上传到 Octopus。在这里,我们下载了octopus.tech域名的 PFX 文件:

在 Octopus 中创建证书

部署证书是一项持续的操作。特别是,Let's Encrypt 提供的证书每三个月就会过期,因此需要经常刷新。

这使得部署证书成为 runbooks 的一个很好的用例。与常规部署不同,runbooks 不需要在环境中进行,您也不需要创建部署。我们将创建一个 runbook 来将证书部署到 Apache 负载平衡器。

首先,我们需要上传由 DNS 提供商生成的 PFX 证书。在下面的截图中,您可以看到上传到八达通证书商店的加密证书:

部署证书

在 Octopus 中创建一个新项目,并添加我们刚刚上传的证书作为变量:

接下来,创建一个包含单个运行脚本步骤的运行手册。

脚本的第一步是使用命令启用 mod_ssl :

a2enmod ssl 

然后创建一些目录来保存证书、证书链和私钥:

[ ! -d "/etc/apache2/ssl" ] && mkdir /etc/apache2/ssl
[ ! -d "/etc/apache2/ssl/private" ] && mkdir /etc/apache2/ssl/private 

然后,证书变量的内容作为文件保存到上述目录中。证书是一种特殊的变量,它通过扩展属性公开组成证书的各个组件。我们需要访问三个属性:

  • Certificate.CertificatePem,也就是公证。
  • Certificate.PrivateKeyPem,也就是私钥。
  • Certificate.ChainPem,也就是证书链。

我们将这些变量的内容打印到三个文件中:

get_octopusvariable "Certificate.CertificatePem" > /etc/apache2/ssl/octopus_tech.crt
get_octopusvariable "Certificate.PrivateKeyPem" > /etc/apache2/ssl/private/octopus_tech.key
get_octopusvariable "Certificate.ChainPem" > /etc/apache2/ssl/octopus_tech_bundle.pem 

如果你还记得,在这篇文章的前面,我们用以下内容创建了文件/etc/apache2/sites-enabled/000-default.conf:

<VirtualHost *:80>
  ErrorLog ${APACHE_LOG_DIR}/error.log
  CustomLog ${APACHE_LOG_DIR}/access.log combined
  JkMount /* loadbalancer
</VirtualHost> 

我们想修改这个文件,就像这样:

<VirtualHost *:443>
  ErrorLog ${APACHE_LOG_DIR}/error.log
  CustomLog ${APACHE_LOG_DIR}/access.log combined
  JkMount /* loadbalancer
  SSLEngine on
  SSLCertificateFile /etc/apache2/ssl/octopus_tech.crt
  SSLCertificateKeyFile /etc/apache2/ssl/private/octopus_tech.key
  SSLCertificateChainFile /etc/apache2/ssl/octopus_tech_bundle.pem
</VirtualHost> 

这是通过将所需的文本回显到文件/etc/apache2/sites-enabled/000-default.conf中来实现的:

{
cat << EOF
<VirtualHost *:443>
  ErrorLog ${APACHE_LOG_DIR}/error.log
  CustomLog ${APACHE_LOG_DIR}/access.log combined
  JkMount /* loadbalancer
  SSLEngine on
  SSLCertificateFile /etc/apache2/ssl/octopus_tech.crt
  SSLCertificateKeyFile /etc/apache2/ssl/private/octopus_tech.key
  SSLCertificateChainFile /etc/apache2/ssl/octopus_tech_bundle.pem
</VirtualHost>
EOF
} > /etc/apache2/sites-enabled/000-default.conf 

最后一步是重启apache2服务来加载变更:

systemctl restart apache2 

以下是完整的脚本供参考:

a2enmod ssl

[ ! -d "/etc/apache2/ssl" ] && mkdir /etc/apache2/ssl
[ ! -d "/etc/apache2/ssl/private" ] && mkdir /etc/apache2/ssl/private
get_octopusvariable "Certificate.CertificatePem" > /etc/apache2/ssl/octopus_tech.crt
get_octopusvariable "Certificate.PrivateKeyPem" > /etc/apache2/ssl/private/octopus_tech.key
get_octopusvariable "Certificate.ChainPem" > /etc/apache2/ssl/octopus_tech_bundle.pem

{
cat << EOF
<VirtualHost *:443>
  ErrorLog ${APACHE_LOG_DIR}/error.log
  CustomLog ${APACHE_LOG_DIR}/access.log combined
  JkMount /* loadbalancer
  SSLEngine on
  SSLCertificateFile /etc/apache2/ssl/octopus_tech.crt
  SSLCertificateKeyFile /etc/apache2/ssl/private/octopus_tech.key
  SSLCertificateChainFile /etc/apache2/ssl/octopus_tech_bundle.pem
</VirtualHost>
EOF
} > /etc/apache2/sites-enabled/000-default.conf
systemctl restart apache2 

运行手册完成后,我们可以验证应用是否通过 HTTPS 公开:

内部证书管理

正如我们所看到的,在使用管理器控制台时,直接连接到 Tomcat 实例是非常有用的。此连接传输凭据,应该通过安全连接完成。为了支持这一点,我们为 Tomcat 配置了自签名证书。

创建自签名证书

因为我们的 Tomcat 实例不是通过主机名公开的,所以我们没有为它们获取证书的选项。要启用 HTTPS,我们需要创建自签名证书,这可以通过 OpenSSL 来实现:

openssl genrsa 2048 > private.pem
openssl req -x509 -new -key private.pem -out public.pem -days 3650
openssl pkcs12 -export -in public.pem -inkey private.pem -out mycert.pfx 

将证书添加到 Tomcat

使用Deploy a certificate to Tomcat步骤在 Tomcat 中配置证书。

Tomcat CATALINA_HOME 路径被设置为/usr/share/tomcat9,而 Tomcat CATALINA_BASE 路径被设置为/var/lib/tomcat9

我们为选择证书变量字段引用一个证书变量。 Catalina 的缺省值对于 Tomcat 服务名是合适的。

对于 Tomcat 如何处理证书,我们有几个选择。一般来说,阻塞 IO非阻塞 IO非阻塞 IO 2Apache Portable Runtime 选项的性能越来越高。Apache Portable Runtime 是 Tomcat 可以利用的一个额外的库,它是由我们和apt-get一起安装的 Tomcat 包提供的,所以使用这个选项是有意义的。

T32

为了允许 Tomcat 使用新的配置,我们需要使用以下命令通过脚本步骤重新启动服务:

systemctl restart tomcat9 

我们现在可以从https://tomcatip:8443/manager/html加载管理器控制台。

纵向扩展到多个环境

到目前为止,我们创建的基础设施现在可以用作其他测试或生产环境的模板。我们在此展示的内容都不是特定于环境的,这意味着所有流程和基础架构都可以根据需要扩展到任意多个环境。

通过将分配给新 Tomcat 和负载平衡器实例的触角与 Octopus 中的其他环境相关联,我们获得了将部署推向生产的能力:

结论

如果你已经到了这一步,恭喜你!建立一个零停机部署、分支、回滚和 HTTPS 的高可用性 Tomcat 集群并不适合胆小的人。最终用户仍然需要结合多种技术来实现这一结果,但是我希望这篇博客文章中的说明能够揭示出现实世界中 Java 部署的一些神奇之处。

总而言之,在这篇文章中,我们:

  • 配置了带有 PostgreSQL 数据库的 Tomcat 会话复制和带有JvmRouteBinderValve阀的会话 cookie 重写。
  • 使用 mod_jk 插件配置 Apache web 服务器作为负载平衡器。
  • 通过 Keepalived 在负载平衡器之间实现高可用性。
  • 利用 Tomcat 的并行部署特性和 Flyway 执行向后兼容的数据库迁移,执行零停机部署。
  • Smoke 用 Octopus 中的社区步骤测试了部署。
  • 实现了特性分支部署,考虑到了使用 Octopus 通道的 Maven 版本控制策略的局限性。
  • 了解了如何回滚应用程序或使其退出服务。
  • 向 Apache 添加了 HTTPS 证书。
  • 对多种环境重复该过程。

3.4 中的未租赁部署是什么?-章鱼部署

原文:https://octopus.com/blog/un-tenanted-deployment

这篇文章是我们 Octopus 3.4 博客系列的一部分。在我们的博客或我们的推特上关注它。


在 Octopus 3.4.0中启用多租户部署,您可能已经在几个不同的地方注意到了术语“未租赁部署”。那么交易是什么?我以为我开启了多租户部署功能!请放心,非租用部署是您已经在做的事情。

Un-tenanted deployment

最小惊奇路径

支持多租户部署为部署等式增加了另一个维度。一个环境中的同一个版本现在可以为不同的租户部署,将他们自己的变量、机器和权限的整个上下文带入流程。如果您想要单独管理您的 web 应用程序在您自己的 100 多个客户中的生产部署,这比以往任何时候都更容易。

在提供这一新功能时,我们努力确保它不是一种全有或全无的方法。有 1000 多个项目和更多环境需要启动,我们希望尽可能无缝地过渡到使用租户。当您最初启用多租户部署时,您可以继续部署您的现有项目,而无需任何进一步的更改。在您开始添加租户或标签之前,您可能根本不会注意到太多的变化。别担心,这是设计好的!

人工添加剂

多租户部署是 Octopus Deploy 的附加特性。当您开始创建租户标记集时,您会看到它们开始出现在整个系统的机器、步骤、变量甚至(最终)权限上。您现有的项目将和以前一样工作,您可以继续管理、创建和部署发布,而不必学习任何新概念。当您在没有任何租户的情况下使用传统流程进行部署时,这被认为是一个未租用的版本。

出于安全原因,您可能会决定将您的一些机器限定在特定的租户范围内,因为它们只被批准用于特定的租户。也许这些机器可以通过不同的标签集进行分组,以便在一个环境中对它们进行资源管理。类似地,您可以开始添加电子邮件步骤以仅触发 VIP 租户,或者在项目中添加包部署步骤以仅触发接收特定模块的租户。只有当您启用并开始使用这些新特性时,您的项目才会变得更加灵活。

所有这一切的要点是,只有当您需要并开始使用租户时,我们才会向您展示允许您真正创建符合您需求的高级部署流程的开关和拨号盘。对于其他不需要使用管理租户带来的额外复杂性的人来说,应该完全隐藏起来,一切照常。

这不应该强迫你学习新的东西去做你已经在做的事情。

好吧好吧...但是为什么不创建一个默认租户呢?

在设计阶段,有人建议只创建一个默认租户,该租户可以链接到部署,并用于没有连接显式租户的环境。对于那些已经熟悉频道特性的人来说,你可能已经注意到在从3.1.x升级到3.2.0的过程中,作为引入频道概念的一部分,我们自动为你的所有项目添加了一个默认频道。今后,所有新项目也有自己的默认通道,最初连接到默认的生命周期。这里的区别是通道是由拥有的,而只影响它们所连接的特定项目。

然而,多租户部署在整个 Octopus 部署体验的基础上增加了一个新的层,其结果会影响部署过程的各个部分,例如涉及哪些机器或帐户,或者全局变量如何被包括在整个部署中。

应该在什么时候创建默认租户,是在创建项目时还是仅在连接另一个租户时?自动创建默认租户,即使它是在幕后抽象出来的,当行为不能被抽象出来时,也会导致混乱。在最坏的情况下,它可能潜在地冒着阻塞用户现有项目发布的风险,因为在继续之前需要理解(甚至纠正)新的概念。默认租户是按项目创建还是全局创建?我们推断,更好的解决方案是将不包含租户的部署视为未租赁的部署。

在数据库中,我们用一个null tenantId 来记录这个部署。

Null TenantId

章鱼部署和奶酪

我们在提供任何新功能时的口号是:

升级应该避免移动你的奶酪(即使我们试图用一个借口来包装“奶酪移动”,比如我们已经将你的普通品牌奶酪片升级为一些你不会发音的花哨的欧洲风格的奶酪。)

最少惊喜的途径意味着,对于那些不需要立即使用多租户部署功能的人来说,他们可以继续部署,而不必学习任何新东西。对于那些多租户部署解决了他们一直面临的部署架构问题的人来说,他们可以放心地慢慢升级到完全租用的部署。最终,我们预计许多使用多租户部署的团队可能会将他们的所有项目切换到需求租赁版本,以便对部署生命周期进行更细粒度的控制。

RFC: Azure 和 FTP 步骤- Octopus 部署

原文:https://octopus.com/blog/upcoming-features-azure-and-ftp

我目前正在为 Octopus Deploy 构建两个新的步骤类型:

  1. 通过 FTP
    部署在这一步中,我们将在 Octopus 服务器上提取一个 NuGet 包,然后将其“同步”到一个 FTP/FTPS 服务器上。这也使得部署 Azure 网站成为可能,因为它们支持 FTP。
  2. 部署 Azure 云服务包
    在这一步中,我们将在 Octopus 服务上提取一个 NuGet 包,它应该包含一个 Azure 云服务包(。cspkg)(我们将扩展 Octo.exe 或 OctoPack,以便更容易地将. cspkg 重新打包为. nupkg)。然后,我们将 cspkg 上传到 blob 存储,然后将其部署到 Azure。

通常,我们在触手代理上运行包步骤。然而,在这种情况下,这两种步骤类型都将直接在 Octopus 服务器上运行,而不是在 Tentacles 上运行。这意味着如果你只是使用 Octopus 通过 FTP 或 Azure 进行部署,你不需要设置任何触手代理。

这两个步骤都将支持我们目前所做的常规的 XML 更新和转换,加上执行常规的 PowerShell 脚本(将在 Octopus 服务器上运行)。因此,举例来说,你可以在 FTP 之前或上传到 Azure 之前使用 PowerShell 更新文件。

配置 FTP 步骤时,您将设置主机、用户名、密码和其他常用设置。您也可以指定 FTP 根目录。所有这些都可以引用 Octopus 变量,例如,您可以为您的暂存/生产部署使用不同的主机/根目录/用户名。

对于 Azure 云服务包,我们还将提供两个选项来管理实例数量:

  • 使用 CSCFG 中的实例计数设置
  • 更新时保留当前部署的服务中的实例计数设置

例如,如果您最初使用两个实例部署到生产环境,然后使用 Azure 管理门户将其扩展到 4,我们可以使用 2(在 cscfg 中指定)或 4(我们将从管理 API 获取),这取决于您选择的选项。

我们还将支持 Azure cscfg 文件中的变量替换。例如:

<Role name="WorkerRole1">
  <Instances count="1" />
  <ConfigurationSettings>
    <Setting name="MyConnectionString" value="blah blah" />
  </ConfigurationSettings>
</Role> 

如果您有一个名为WorkerRole1/MyConnectionString的 Octopus 变量,我们将替换该设置的“value”属性。请注意,该设置以角色名称为前缀,以允许不同角色的不同设置。

在选择要使用的云服务配置文件时,我们将寻找(按此顺序):

  • 服务配置。环境。cscfg
  • 服务配置。Cloud.cscfg

由于连接 Azure 管理 API 需要 X509 证书,我们将自动使用 Octopus 服务器证书。要允许 Octopus 部署到您的 Azure 订阅,您只需从 Octopus 下载.cer文件并将其上传到 Azure 门户。我们将在页面上提供到您的.cer和安装说明的链接,用于编辑 Azure 步骤。

你怎么想呢?正如我所说的,我目前正在构建这些功能,所以如果有其他与 Azure 或 FTP 相关的场景,我很乐意听到它们,这样我们就可以在下一个版本中包含它们。

从 Octopus 3.x 升级比你想象的要容易——Octopus Deploy

原文:https://octopus.com/blog/upgrading-octopus-3-easier-than-you-think

“如果它没坏,就不要修理它”这句话在发展的世界里并不总是有效的。Octopus 也不例外,这就是为什么我们的目标是通过我们自己的主要版本来改进您发布版本的方式。如果您没有更新 Octopus,您可能会错过一些使部署更容易、更高效并帮助您的团队进行协调的功能。

也就是说,我们理解为什么有些人更喜欢老版本的 Octopus。您可能觉得您当前的版本可以满足您的需求,或者公司政策可能会要求您保留几个版本以避免风险。也许你落后于更新,担心时间和资源来获得最新的。

特别是对于最后一类人,让我们看看我们是否能减轻你的恐惧。有了规划和适合您业务的正确方法,您可以对从我们最古老的“现代”版本升级充满信心。

在这篇文章中,我浏览了:

  • 您因不升级而错过的功能
  • 升级前要备份什么,以及其他注意事项
  • 规划升级时避免风险的方法
  • 从 3.x 就地升级到我们最新版本的示例
  • 如果出现问题,如何回滚
  • 如果您有任何疑问或问题,我们如何帮助您

自 Octopus 3.1 以来我们引入的功能

如果还在 3.x 版本上,你会错过 Octopus 的发展以及现代部署标准,还有一堆新特性。

以下是我们在 Octopus 3.1 和 2021.2 之间添加的内容:

  • 改进的用户界面——新的流程编辑器、黑暗模式和全局搜索
  • 对配置文件的改进,包括对 JSON、XML、YAML 和属性文件的内置支持
  • 空间(Spaces)-不同团队只能看到和使用他们需要的东西
  • 操作手册 -允许您自动执行日常维护和紧急操作任务
  • 租户 -为软件即服务(SaaS)或多区域部署创建客户特定的渠道
  • 项目导出和导入 -导出一个或多个项目以导入到其他空间或实例中
  • 代码为的配置 Octopus 项目的版本控制(这是早期访问预览)

我们还增加了新的平台集成,包括支持:

  • 主要云提供商——微软 Azure、亚马逊网络服务(AWS)和谷歌云平台(GCP)
  • Kubernetes 和 AWS 弹性集装箱服务(ECS)
  • Linux 上的自托管 Octopus
  • Linux、ARM 和 ARM64 的触角
  • Chocolatey、Homebrew、APT 和 YUM 的 Octopus 命令行(CLI)
  • 插件用于:

要备份什么以及如何备份

万能钥匙

主密钥是一个独特的安全字符串,用于保护 Octopus Deploy 数据库中的敏感数据。没有它,您无法在干净的操作系统上恢复备份。

除了数据库,这是升级前最重要的备份。我们无法找回丢失的万能钥匙。

要备份主密钥:

  1. 在你的服务器上打开 Octopus Manager
  2. 点击存储标题下的查看主密钥
  3. 在这里,您可以单击:
    • 保存将主密钥保存在文本文件中
    • 复制到剪贴板粘贴到某个安全的地方,比如密码管理器

The master key screen in Octopus Manager

许可密钥

您还需要许可证密钥来恢复实例。

要备份许可证密钥,请执行以下操作:

  1. 打开 Octopus 门户网站,点击顶部菜单中的配置
  2. 从左侧菜单中选择许可证。从 XML 框中复制所有文本并粘贴到安全的地方。

如果您找不到或不知道您的许可证密钥,请发送电子邮件给我们的客户成功团队,我们可以帮助您找回它。

数据库ˌ资料库

在升级 Octopus 之前,您应该备份您的数据库。大多数数据库管理工具都有向导来帮助您。

例如,在微软的 SQL Server Management Studio (SSMS)中备份数据库:

  1. 在数据库服务器上展开数据库文件夹
  2. 右击数据库,选择任务,点击备份…
  3. 使用目的地部分设置您想要存储备份的位置,并点击确定

The backup and restore options in SQL Server Management Studio

对于那些喜欢使用 T-SQL 命令的人,您可以使用以下命令将备份保存到 NAS 或文件共享:

BACKUP DATABASE [OctopusDeploy]
          TO DISK = '\\SomeServer\SomeDrive\OctopusDeploy.bak'
             WITH FORMAT; 

如果不确定,请咨询您的数据库管理员。

数据文件夹

复制并存储下列文件夹及其所有数据:

  • c:\章鱼\神器
  • c:\八达通\套餐
  • c:\章鱼\任务日志

下载你已经在使用的相同版本的 Octopus

你需要现有的八达通版本:

要检查您的版本,请打开 Octopus 门户网站,然后单击 Octopus 右上角的问号。版本号位于下拉列表的顶部。

然后前往我们的下载档案库重新下载该版本。

【T2 Where to check the Octopus version in old and new versions

升级前需要考虑的其他事项

八达通牌照

您可以在 3 个不同的实例上使用您的 Octopus 许可证(由它连接的数据库决定)。如果您认为您将超出您的 Octopus 实例限制,发送电子邮件给我们的客户成功团队询问您的选择。

不要一次做太多

虽然利用 Octopus 升级的停机时间来完成其他任务(比如更改 Octopus 服务器的操作系统或转移到高可用性实例)很有诱惑力,但我们建议只关注升级。

这样做将:

  • 确保升级尽可能平稳运行
  • 防止其他任务使升级复杂化
  • 减少对八达通用户的影响
  • 如果您已经使用了我们的最新版本,那么让其他任务变得简单一点

规避风险的策略

升级 Octopus 时,有两种推荐的方法可以避免风险。两者都很简单,但是你应该在开始之前考虑哪一个最适合你的生意。

我们来分解一下选项。

方法 1:克隆您的实例并升级

克隆实例并升级克隆是我们推荐的方法,因为它允许您:

  • 测试时保持主实例可用
  • 在承诺升级之前,对升级有充分的信心
  • 不用担心出错时会回滚

该方法的主要步骤是:

  1. 将实例置于维护模式
  2. 备份数据库
  3. 使实例退出维护
  4. 将备份作为新数据库恢复到所需的 SQL Server 上
  5. 下载与主实例相同版本的 Octopus,并将其安装在新的服务器上
  6. 配置新实例以指向数据库的新副本
  7. 从主实例的备份文件夹中复制所有文件
  8. 可选:禁用所有部署目标
  9. 升级克隆的实例
  10. 测试克隆的实例,然后检查所有 API 脚本、CI 集成和部署工作
  11. 决定是迁移到新实例,还是执行新备份并升级主实例

阅读我们的升级文档,更详细地了解这个方法的步骤。

此方法的注意事项

虽然这是平稳过渡的最佳选择,但这种方法非常耗时,而且在迁移过程中需要停机。它的成本也很高,因为您必须克隆整个环境。这意味着硬件、许可或云服务的成本翻倍。

当团队在测试期间继续使用主实例时,您也有“漂移”的风险。如果你认为你需要一周以上的时间来理清事情,你可以:

  • 请选择方法 2
  • 找出克隆实例上的任何问题,然后在合适的时候在新实例上重启

方法 2:创建一个新的实例来测试流程,然后放心地更新

一些客户可能没有时间或资源来完成第一种方法。相反,您可以用几个项目创建一个小的测试实例,以便在主实例上升级之前获得信心。

这种方法:

  • 节省时间和资源
  • 停机时间更短
  • 消除漂移的风险
  • 如果出现问题,很容易回滚

该方法的主要步骤是:

  1. 下载与主实例相同版本的 Octopus,并将其安装在新的虚拟机上
  2. 从主实例中导出一些项目,并将它们导入到测试实例中
  3. 下载最新版本的八达通
  4. 备份测试实例数据库
  5. 将测试实例升级到最新的 Octopus 版本
  6. 测试并验证测试实例
  7. 将主实例置于维护模式
  8. 备份主实例上的数据库
  9. 备份主实例上的所有文件夹
  10. 对主实例进行就地升级
  11. 测试升级后的主实例
  12. 将主实例退出维护模式

阅读我们的升级文档,更详细地了解该方法的步骤。

此方法的注意事项

对于旧版本的 Octopus,您只能使用 Octopus Manager 导出所有数据,尽管您可以通过命令行获得部分导出。对您想要导出进行测试的每个项目运行以下命令:

Octopus.Migrator.exe partial-export --instance=OctopusServer --project=AcmeWebStore --password=5uper5ecret --directory=C:\Temp\AcmeWebStore --ignore-history --ignore-deployments --ignore-machines 

升级高可用性 Octopus 设置

升级 Octopus 的高可用性实例与普通升级没有什么不同,但是由于额外的节点,需要注意一些事情。

在升级其他节点之前,您必须只升级一个节点。Octopus 安装程序会更新数据库,因此一次升级所有节点会导致数据库问题。

高可用性升级的主要步骤是:

  1. 下载最新版本的八达通
  2. 启用维护模式
  3. 停止所有节点
  4. 备份数据库和 Octopus 文件夹
  5. 升级 1 个节点
  6. 升级剩余节点
  7. 启动所有停止的节点
  8. 测试升级的实例
  9. 禁用维护模式

由于高可用性,Octopus 数据文件夹也可能存储在网络驱动器上,而不是节点上。升级前,您需要知道此位置才能备份文件。请记住,您应该备份的文件夹是:

  • 史前古器物
  • 包装
  • 任务日志

从 3.1 就地升级的示例

无论是在克隆、测试还是生产实例上,Octopus 的就地升级都是一样的。

如果从 a 3 升级。x 版本,您应该升级到 3.17,然后再跳转到我们的最新版本。这提供了最可靠的升级途径,因为 3.1 版和 3.17 版之间有很大的差异。

谢天谢地,不管你运行的是哪个版本的 Octopus,升级过程都不会花很长时间。

开始之前

对于此升级示例,您应该:

步骤 1:启用维护模式

一旦所有东西都备份好了,您应该将实例置于维护模式。这意味着只有 Octopus 管理员可以部署。

要设置维护模式,点击顶部菜单中的配置,从左侧选择维护。在上将维护模式切换到,点击保存

第二步:安装八达通 3.17

  1. 关闭八达通管理器和八达通门户网站。
  2. 运行 Octopus 3.17 的安装程序,完成后点击完成
  3. 八达通将自动更新您的配置和 SQL 数据库,然后打开八达通管理器。
  4. 打开 Octopus 门户网站,点击右上角的问号,查看版本是否为 3.17。版本在菜单的顶部。
  5. 运行一个测试部署来检查一切是否正常。

第三步:安装最新版本的八达通

  1. 关闭八达通管理器和八达通门户网站。
  2. 运行 Octopus 最新版本的安装程序,完成后点击完成
  3. 八达通将自动更新您的配置和 SQL 数据库,然后打开八达通。
  4. 打开 Octopus 门户网站,点击右上角的用户名,检查版本是否是最新的。版本在菜单的顶部。
  5. 运行一个测试部署来检查一切是否正常。

第四步:让 Octopus 退出维护模式

要解除维护模式,点击顶部菜单中的配置并从左侧选择维护。点击禁用维护模式

出错时回滚

虽然我们对 Octopus 从现代版本的升级过程充满信心,但我们知道问题仍然会发生,特别是复杂的设置。

如果发生意外,您可以轻松回滚到旧版本的 Octopus。

第一步:卸载新的八达通版本

  1. 关闭八达通门户网站和八达通管理器
  2. 在 Windows 开始菜单中找到 Octopus Manager 或使用任务栏进行搜索,右键单击并选择卸载
  3. 从应用程序列表中选择 Octopus 并点击卸载

The uninstall option in Windows

步骤 2:恢复数据库

使用您的数据库管理工具将数据库恢复到原始状态(例如,使用微软的 SSMS):

  1. 在数据库服务器上展开数据库文件夹。
  2. 右击数据库,选择任务恢复,选择数据库
  3. 使用向导选择正确的备份或文件。在我的情况下,它默认为我的最后一次备份。准备好后点击确定
  4. 在弹出的数据库恢复成功上点击确定完成。

The Restore Database screen in SQL Server Management Studio

步骤 3:恢复 Octopus 文件夹

如果在原始服务器上回滚,我们建议您在恢复原始文件夹之前将升级的 Octopus 文件夹复制到新位置。一旦知道回滚成功,就可以删除它们。

要恢复原始版本:

  1. 删除以下文件夹的内容:
    • c:\章鱼\神器
    • c:\八达通\套餐
    • c:\章鱼\任务日志
  2. 将旧版本的文件复制回相同的位置

如果恢复到一个干净的服务器,你只需要复制文件夹到 C:\Octopus。

第四步:重新安装之前版本的 Octopus

你现在可以重新安装你的原始版本的八达通。

  1. 运行你原来的章鱼版本的安装程序,完成后点击完成。如果回滚到干净的操作系统,Octopus 安装向导将在安装前提示您输入许可证密钥和主密钥。
  2. Octopus Manager 应该会自动打开。您可能需要手动启动 Octopus 服务器-只需点击左侧的启动
  3. 打开八达通门户网站。重新安装后第一次打开 Web 门户时,可能会出现与缓存相关的错误。如果发生这种情况,刷新标签,八达通应该加载罚款。
  4. 运行部署以检查一切是否仍然正常。

下一步是什么?

升级完 Octopus 实例后,可以选择为将来的升级节省一些工作。

首先是自动升级八达通卡。这使得 Octopus 能够为自己部署新版本,执行所有必要的步骤并减少停机时间。

如果 Octopus 服务器的本地实例不是业务需求,您可以迁移到 Octopus Cloud 。使用 Octopus Cloud 意味着您永远不必担心升级,并且您可以在我们的所有新功能可用时立即访问它们。

我们可以帮忙

如果您有任何问题或需要帮助规划您的最佳升级方法,我们随时恭候。我们的客户成功团队可以针对规划或升级后的问题提供实例审查和支持——我们距离只有一封电子邮件的距离。

愉快的部署!

硒系列:上传网页到 S3 -八达通部署

原文:https://octopus.com/blog/selenium/16-uploading-to-s3/uploading-to-s3

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

到目前为止,我们测试了从本地磁盘加载的网页,并对公共网站进行了一些简单的测试。然而,当我们开始研究 WebDriver 的高级特性时,我们需要能够托管我们可以与之交互的真实公共网站的能力。

托管网站有一百万种方式,但是考虑到我们将在后面的文章中使用亚马逊网络服务(AWS ),使用 S3 服务托管网站对我们来说是一个合理的选择。

对于那些不熟悉它的人来说,AWS 是亚马逊提供的服务和平台的集合,允许你做诸如创建托管虚拟机(通过 EC2 服务),创建托管数据库服务器(通过 RDS 服务),存储文件(通过 S3 服务),运行功能(通过 Lambda 服务)等等。AWS 有 100 多种不同的服务可以利用,尽管我们在这个博客系列中只使用了一小部分。

AWS 的一个好处是它提供了一个相当慷慨的免费层,这意味着你可以不花钱就开始使用 AWS。我们将在 AWS 中托管的小网页将很容易位于免费层的限制内。

AWS S3 是一种文件托管服务,让我们可以在云端保存数据,并让公众可以访问。S3 通常用于托管典型网络驱动器上的内容,如日志文件和文档,但它也可用于托管公共网站。通过上传 HTML 文件和任何其他资源,如图像或 CSS 文件,并配置 S3 作为一个网站托管这些文件,我们可以快速轻松地创建一个公开可用的,高度可靠的网站。这正是我们将对我们的测试网站所做的。

要访问 S3 控制台,从 AWS 控制台点击服务➜ S3 。

首先,我们需要创建一个 bucket。存储桶大致类似于目录,但是有一个重要的警告,存储桶的名称必须是全局唯一的。这意味着您的存储桶的名称不能被任何其他 AWS 客户使用。

如果您看到 S3 存储桶名称似乎是随机生成的,这是因为所有常见的存储桶名称早已被其他 AWS 客户使用。

要创建新的存储桶,点击Create bucket按钮。

给你的 bucket 起一个名字(记住它必须是全局惟一的,我在这里使用的名字现在不可用),然后点击Next按钮。

我们不需要启用向导的这个部分中显示的任何附加属性,所以单击Next按钮。

默认权限没问题,所以点击Next按钮。

审查屏幕向我们显示了我们选择的摘要。点击Create bucket按钮完成。

然后显示我们新创建的存储桶。点击它打开桶。

桶当前是空的。点击Upload按钮。

单击添加文件按钮。这将显示一个文件选择对话框。

在这里,我们上传各种测试网页的文件,这些文件是我们在测试期间从本地磁盘上下载的。这些文件将位于src/test/resources目录下。

这些文件将在向导中列出。点击Next按钮继续。

我们现在接受默认权限,所以点击Next按钮。

默认属性没问题,所以点击Next按钮。

查看屏幕显示了选项的摘要。点击Upload按钮,将选中的文件上传到 S3 桶。

几秒钟后(文件相当小,应该上传很快),文件会显示在桶中。

要允许这些文件作为公共网页查看,我们需要更改权限以允许匿名公共访问它们。为此,选择所有文件,点击More按钮,然后选择Make public选项。

点击Make public按钮,公开曝光文件。

现在这些文件已经公开了,我们需要找到可以用来在浏览器中打开它们的 URL。点击form.html文件。

打开该文件的链接显示在 Overview 选项卡中,在这种情况下,URL 是https://S3 . amazonaws . com/web driver-testing-website/form . html。您可以单击此链接在浏览器中打开页面。

通过将文件上传到 S3 并公之于众,我们有效地利用了 S3 作为网络主机。这为我们提供了一个公共 URL,我们可以将其作为测试的一部分进行加载,这意味着我们可以在无法访问 Java 项目中的本地 HTML 文件的平台上开始编写测试。我们将在下一篇文章中利用这个网站托管,在 BrowserStack 上运行测试,这将允许我们跨多个浏览器和移动设备运行测试。

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

自动化数据库部署流程:案例研究- Octopus 部署

原文:https://octopus.com/blog/use-case-for-designing-db-deployment-process

Octopus reading an automated database deployment process case study

在加入 Octopus Deploy 之前,我是试点团队的首席开发人员,负责自动化数据库部署,将每次部署的时间从 2 到 4 小时缩短到 10 分钟。当我开始的时候,我认为我们应该自动化现有流程中的步骤,并且我开始实现工具来实现这一点。我一点也不知道,整个过程将会改变。

本文是上一篇文章的延续:如何设计自动化的数据库部署过程,如果您愿意,您可以跳到下一篇文章,在那里我为提供了使用 Octopus Deploy 实现数据库部署过程的分步指南。

我们所有的数据库部署文章都可以在这里找到。

在这篇文章中

设计数据库部署流程的快速概述

如果你跳过了上一篇文章,这里有一篇 TL;DR;

  • 创建一个小团队或工作组来定义流程。包括来自每个部署阶段的代表(开发人员、数据库管理员等。).工作组不应超过 4 至 6 人。
  • 确定要包含在工作组中的试验团队或应用程序。
  • 召开为期 1 至 2 天的会议,启动工作组。
    • 写下现有的流程,确定关键人员、难点和需要改变的内容。
    • 起草理想的部署流程。
    • 研究工具。
    • 在启动结束时,试点团队应该知道需要实施什么以及使用什么工具。
  • 试点团队实施新流程。
    • 一直部署到生产。
    • 迭代流程。
    • 在这个过程成功一段时间后,看看是否有人愿意成为早期采用者。
    • 与早期采用者团队一起迭代流程。
  • 普遍采用。
    • 专注于建立对流程的信任。
    • 推广到多个团队。
    • 找到痛点就迭代。

组建工作组

该公司的一位 DBA 总结得很好,他们说:“我们的数据库部署过程是西部大开发。”我们每个月都有更多的开发人员加入公司,新的团队不断形成,每天都有更多的代码被部署,数据库部署的问题必须停止。

是时候成立一个工作组了。

DBA 和数据库架构师都喜欢使用 Redgate 的工具,所以他们向 Redgate 寻求帮助。雷德盖特同意帮忙,以换取一个案例研究。他们会派两个人飞过来和我们的工作组会面几天。当我写这篇文章时,我 99%确定他们不会一直这样做。我们本可以在正确的时间抓住他们。

数据库管理员确定了在工作组中代表他们的数据库管理员。最后决定的是试点团队。这是我参与的时候,因为我的团队被选为试点团队。

天真的我期望 Redgate 帮助我们在现有的流程中实现他们的工具,所以我开始着手做了。然而,工作组扔掉了我大约 75%的工作。这没什么大不了的。这是一次很好的学习经历,因为它让我对数据库部署工具如何工作有了更好的基本理解,这有助于我为启动会议做出贡献。

启动会议

我的团队中的数据库开发人员,一个 DBA,一个数据库架构师,两个 Redgaters,我见了两天。第一天的重点是设计我们的理想流程。最终目标是有一个流程部署到developmentteststaging,并一直部署到production

首先,我们浏览了现有的流程,如下所示:

  1. 一个开发者在test做了一个改变。所有开发者在test都有 sysadmin 权限。他们应该在development中进行更改,但是test有所有的数据来验证他们的更改,所以这是他们的代码指向的服务器。
  2. 开发人员在 SSMS 更改连接,并对development进行更改。在development中所有开发者都有 sysadmin 权限。
  3. 数据库开发人员或首席开发人员运行 Redgate SQL Compare 来生成teststaging之间的增量脚本。任何复杂的数据库更改(移动列、合并列等。)被删除并手动编写脚本。脚本保存在共享文件夹中。除了 DBA 之外的每个人都拥有对staging的只读权限。数据库管理员必须运行脚本。
  4. 通过电子邮件通知 DBA 运行staging上共享文件夹中的脚本。它们运行脚本并将输出发送给请求者。
  5. 在转到production之前,可以将多个变更推送到staging。因此,数据库开发人员或首席开发人员会在stagingproduction之间生成一个新的 Redgate SQL 比较增量脚本。就像以前一样,任何复杂的数据库更改(移动列、合并列等。)被删除并手动编写脚本。脚本保存在共享文件夹中。除了 DBA 之外,每个人都对production拥有只读权限。
  6. DBA 通过变更请求得到通知,在production中运行一组脚本。他们运行脚本,将结果保存到变更请求系统,该系统会向请求者发送电子邮件。

我们接下来解决了这些问题:

  1. 谁参与了这个过程?回答:开发人员、数据库开发人员、首席开发人员、DBA。
  2. 他们有什么权限?回答:开发人员、数据库开发人员、首席开发人员都有developmenttest的 sysadmin 权限。DBA 拥有developmentteststagingproduction的系统管理员权限。
  3. 他们为什么会参与进来?回答:开发人员、首席开发人员和数据库开发人员对developmenttest进行修改。数据库开发人员和首席开发人员使用 Redgate SQL Compare 创建增量脚本。DBA 将增量脚本部署到stagingproduction
  4. 哪些环境有不同的流程?:有三个流程,一个是部署到developmenttest的,而stagingproduction的流程相似但略有不同。Production涉及一个变更请求系统。
  5. 为什么会不一样?回答:权限和审核;stagingproduction定期刷新,用于暂存和最终验证,因此staging需要尽可能靠近production以帮助消除意外。对production的任何更改都需要一个更改请求,因为这是审计员检查的内容。
  6. 脚本运行失败会发生什么?回答:在developmenttest中,写剧本的人运行了剧本,他们做了必要的调整,重新运行。在stagingproduction中,DBA 通知请求者失败。请求者调试脚本并进行必要的调整。然后,他们要求 DBA 再次运行该脚本。
  7. 为什么脚本通常会失败?回答:失败的发生是因为每个环境都有不同的 delta 脚本。缺少架构更改或迁移脚本。
  8. 谁审查脚本,何时审查?:数据库开发人员或首席开发人员在进入staging之前审查变更。由于不同的 delta 脚本,DBA 在去stagingproduction之前审查脚本。
  9. 谁需要参与每次部署?:部署到developmenttest只涉及做出改变的人。到staging的部署涉及请求者、数据库开发人员或主要开发人员以及 DBA。部署到production需要每个人,因为每个环境都有一个独特的增量脚本,任何问题都需要立即修复。
  10. 什么不起作用,什么需要改变?回答:见下文。

需要改变什么

敏锐的读者会注意到这些答案中反复出现的主题。

  • 三个不同的过程。
  • 每个环境唯一的增量脚本。
  • 独特的 delta 脚本意味着很难或者几乎不可能测试。
  • 共享开发环境。
  • 直到该去staging的时候,评论才出现。
  • production部署期间“全体人员准备就绪”。
  • 没有谁做了什么更改、何时做的或为什么做的历史记录。
  • 通过文档手动跟踪更改。
  • developmentteststaging中没有审计。对production的一点点审核。

我的团队负责的应用程序有 700 个表。所有数据库访问都是通过存储过程完成的,包括 CRUD 操作。数据库大约有 5000 个对象(表、存储过程、函数等)。).所有的开发都是在同一个数据库上完成的。当我们发布时,一些改变被推进,而另一些改变被排除。我们记录了要在一张纸上包含哪些更改。这不是 20 年前。这发生在 2014 年。

这些问题导致了 2 到 4 个小时的production部署,我们不信任这一过程,这导致了密集的验证过程。实际的部署可以在 30 分钟内完成,但是剩下的 90 分钟要花在验证部署上。这包括 QA、业务所有者、业务分析师、开发人员、首席开发人员和运行各种场景的经理。即使付出了所有的努力,我们仍然错过了一些随机的事情,只有 0.5%的用户遇到过。

大约 60%的情况下,由于错过了模式更改,我们不得不在第二天进行紧急修复。

理想过程的草稿

起草理想的流程花了相当多的时间。这是由于缺乏工具所能提供的知识造成的。当时,我们知道源代码控制、构建服务器和 Redgate 工具,但是我们都不熟悉部署工具能提供什么。谢天谢地,雷德盖特在那里帮助教育我们。

首先,我们列出了各种工具及其提供的功能:

  • 源代码控制
    • 它存储所有的 SQL 脚本。
    • 它是变化的真相中心。
    • 包括为新特征创建分支的能力。
    • 它提供了在合并之前检查变更的能力。
  • 数据库工具
    • 它提供了一种在目标数据库上运行源代码管理中存储的脚本的方法。
    • 包括某种预览功能,可以生成一个文件供人们在部署期间查看。该文件可以是实际的增量脚本,也可以是总结更改的 HTML 文件。
  • 构建服务器
    • 从源代码管理中获取 SQL 脚本并将其打包。
    • 它将包推送到部署服务器。
    • 它可以监视多个分支,并为每个分支构建。
  • 部署工具
    • 调用数据库工具来部署数据库更改。
    • 使用数据库工具预览功能创建一个文件,以便在部署期间进行审查。
    • 它提供审计和批准。
    • 用于部署到所有环境。
    • 具有允许各种场景的安全特性,例如只允许数据库管理员部署到production

随着工具责任的排除,我们花了大量的时间讨论共享数据库模型和专用数据库模型。专用数据库模型意味着每个开发人员在他们自己的机器上运行数据库服务器。使用共享数据库模型,我们发现:

  • 进行了数据库更改,代码更改需要一个小时到几天才能使用新的数据库更改。有时会阻止其他开发人员和 QA 使用应用程序中的特定功能或区域。
  • 有两个真实中心,源代码控制和共享数据库,所有的更改都在这里进行,然后保存到源代码控制。如果源代码管理和共享数据库之间有冲突,哪一方会赢?
  • 我们无法有效利用分支机构。可以有 1 到 N 个分支,但只能有一个数据库。
  • 在审查之前对中央数据库进行了更改,但是审查应该在什么时候进行?
  • 每个人都使用相同的测试数据。一个人更改数据会影响团队中的多个人。

我们决定改用专用数据库。对我们来说,这很有意义。我们知道工具是如何工作的,以及哪里会有变化。

是时候勾勒出理想的流程了:

  1. 一名开发人员/数据库开发人员/首席开发人员创建了一个分支。
  2. 所有数据库更改和代码更改都在该分支上进行。
  3. 变更已完成并签入分支。
  4. 创建了一个合并请求,这启动了一个构建。构建验证更改是有效的 SQL。
  5. 数据库开发人员或首席开发人员审查合并请求中的数据库更改,并提供修复反馈。
  6. 分支机构被批准并合并。
  7. 构建服务器启动一个构建,验证更改是否是有效的 SQL,如果是,将它们打包并推送到部署服务器。构建服务器告诉部署服务器部署到development
  8. 部署服务器部署到development
  9. 开发人员/数据库开发人员/首席开发人员告诉部署服务器部署到test
  10. 部署服务器部署到test
  11. 变更在test进行验证。
  12. 一个开发人员/数据库开发人员/首席开发人员告诉部署服务器部署到staging。部署服务器使用数据库工具为 DBA 生成用于审批的审查文件。
  13. 部署服务器通知 DBA 对staging的部署请求。他们审查变更并提供修复反馈。
  14. DBA 批准对staging的更改。
  15. 部署服务器完成部署到staging
  16. 开发人员/数据库开发人员/首席开发人员在staging中验证变更。
  17. 变更请求被提交给 DBA,以将部署服务器中的特定包升级到production
  18. 几个小时后,DBA 告诉部署服务器部署到production。部署服务器使用数据库工具生成评审文件,供 DBA 评审。
  19. DBA 审查该文件作为最后的完整性检查。
  20. 部署服务器完成对production的部署。
  21. 负责应用程序的团队验证production中的变更。

当提出这个过程时,我们有意避免使用工具。直到我们有了流程草案之后,才讨论工具。首先是已经在使用的工具:

  • 构建服务器:团队城市正在作为 2012 年 TFS 奥运会的替代项目进行试点。
  • 源代码控制:团队城市的试点团队已经转移到 Git。
  • 数据库部署:Redgate 的 SQL 变更自动化(当时是这么叫的)。
  • 部署服务器:没有,虽然当时我认为构建服务器=部署服务器。

TFS 2012 就要过时了,所以继续使用它没有意义。我的团队是切换到 Git 和 TeamCity 的团队的一部分。

雷德盖特飞过来帮助我们。我们有一个使用他们工具的原型。我们讨论了 SSDT 和 RoundhousE,但他们失败的原因大致相同。95%的人在 SQL Server Management Studio 中更改数据库,太多人忘记将这些更改迁移到 SSDT 或 RoundhousE。我们的讨论以及我们设计的流程导致了对工具的以下关键要求:

  1. 可以从 SSMS 保存数据库更改。
  2. 自动检测数据库变化。
  3. 由该工具处理的大多数更改(添加列、删除列、更改存储过程)能够手动编写和存储复杂的更改(将列移动到新表)。

RoundhousE 满足了 3 个中的 1 个(可以保存来自 SSMS 的数据库更改),SSDT 满足了 3 个中的 1 个(由该工具处理的大多数复杂更改)。对我们来说,继续在试点项目中使用 Redgate 是有意义的。

我们没有部署服务器,但是在 Redgate 的人解释了 Octopus Deploy 的好处和特性之后,我们使用 TeamCity、Redgate 和 Octopus Deploy 做了一个快速的概念验证。这个概念验证花了大约一个小时才完成,并且显示出了很大的潜力,所以我们决定使用 Octopus Deploy 进行试点。

实施流程

使用工具将商定的流程落实到位。让它在developmentstaging运行只需要很少的时间。我们确实遇到了一些在动员会上没有预料到的问题:

  • 权限:自动化流程能做什么,不能做什么。我们的目标是阻止流程创建新用户并将他们添加到角色中。这样,某人就不能在production中给自己 db_owner 权限。
  • 解决所有环境之间的差异:在production中有模式变化,而在development中没有。我们第一次尝试在production中运行该流程时,我们几乎将它们消灭。我们很快将这一更改添加到源代码控制中,重新构建包,并通过环境将其推送到production

在解决了这些小问题之后,我们能够将流程部署到stagingproduction

加速部署

很快,由于缺少模式更改而导致的紧急修复的数量下降到了零。仅此一项就是巨大的胜利。

因此,我们花在验证上的时间开始减少。

  1. 30 分钟部署,90 分钟验证。
  2. 25 分钟部署,80 分钟验证。
  3. 20 分钟部署,70 分钟验证。
  4. 15 分钟部署,60 分钟验证。
  5. 10 分钟部署,50 分钟验证。
  6. 5-8 分钟部署,40 分钟验证。
  7. 5-8 分钟部署,30 分钟验证。

这反过来又让我们想要更频繁地部署。频繁的部署意味着较小的变化。较小的变化意味着较少的验证。更少的验证意味着更快的部署。更快的部署意味着我们希望更频繁地部署。这个循环一直持续到验证也花了 5 到 8 分钟。

早期采用者和迭代

其他几个团队(剩下的 9 个团队中的)看到了我们正在做的事情,他们作为早期采用者加入进来。让三个团队使用该流程,找出棘手问题和需要改进的地方:

  • 没有部署已批准的项目。我没有正确使用 Redgate 工具。我使用了一个命令来生成预览以供审批。然后我忽略了那个预览,直接使用不同的命令来部署这个包。每 30 个部署中就有 1 个最终发生了意外变化。通过一点研究,找到并实现了正确的命令。
  • 将预览文件保存到文件共享。不知道章鱼还有把文件存为神器的能力。审批者可以从 Octopus 下载工件。这比在文件共享中找到正确的文件要好。对脚本的一个小调整解决了这个问题。
  • 数据库管理员成为瓶颈。三个团队部署到staging时,数据库管理员经常跟不上。他们也厌倦了在部署期间必须在线才能批准对production的部署。我们改变了流程,在部署到staging时生成两个文件,一个用于staging,一个用于production。部署将在staging进行,DBA 在事后批准了一切。在production中,当部署失败时,我们添加了一个寻呼它们的步骤。
  • 开发人员没有使用他们的专用数据库。开发人员的数据库中没有测试数据,所以他们指向test来测试他们的更改。我们通过在每次部署后在test中创建数据库备份来解决这个问题。开发人员可以将该数据库恢复到他们的本地实例,并拥有他们需要的所有数据。

没有一个过程从一开始就是 100%完美的。预计迭代多次。

普遍采用

最终,普遍采用的时候到了。我对这种推后感到非常惊讶,特别是来自数据库开发人员的推后。他们花了太多的时间来生成部署脚本,他们相信这就是他们为团队带来价值的方式。人们担心这个过程会让他们失业。事实并非如此。该过程旨在消除所有浪费在生成和调整部署脚本上的时间,从而将他们解放出来,专注于重要的事情,如数据库结构、性能、审查更改以及处理复杂的更改。

事后看来,我应该:

  • 与数据库开发人员和团队中的首席开发人员安排一次会议,以完成整个过程。
  • 为他们两人安排了另一次会面,以将应用程序的数据库纳入流程。
  • 为他们安排了最后一次会议,教他们的团队如何使用该流程,我在后台回答问题。

但我没有那么做。我把所有这些合并成一个大型会议。活到老学到老。

结论

为了自动化数据库部署,我们不得不做一些改变。最终,这是值得的。部署到production变成了无事件。我在那家公司做的最后一次production部署涉及到我自己、企业所有者和我的经理。我们在线了 30 分钟。其中 25 分钟用来讲有趣的故事和糟糕的笑话。部署很顺利地完成了。

在本文中,我重点介绍了我们如何设计自动化数据库部署过程。我没有关注我们实际上做了什么,但是我将在下一篇文章中讨论这个问题。

下次再见,愉快的部署!

如果您喜欢这篇文章,好消息,我们有一个关于自动化数据库部署的完整系列。

用 UserVoice - Octopus Deploy 驯服积压

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

在测试期间,新功能的建议会被发布在招标上,我会立即将它们添加到特雷罗中进行跟踪。它在测试阶段工作得很好,因为没有那么多的建议,而且它们往往是由 V1 实现的显而易见的特性。

在过去的几周里,建议的数量有了很大的增长,这真是太棒了!然而,这意味着 Trello 董事会正在变成一个混合体:

  1. 肯定会很快实现的事情
  2. 可能在 2381 年的某个时候实现的东西,在博格人攻击之前
  3. 可能永远不会实现的东西,但需要跟踪,以防它变得流行或发生变化(招标没有投票支持)

为了避免对积压的工作感到不知所措,也为了帮助驯服它,我创建了一个 UserVoice 网站来收集建议。我已经把很多招标项目移到了那里,我鼓励你直接在那里张贴新的建议。

我的想法是:

  1. 标书将用于故障排除和一般讨论
  2. Trello 上的项目要么是 bug,要么是肯定要实现的特性
  3. UserVoice 上的项目可能会实现,这取决于它们有多受欢迎,以及它们与整个产品路线图的契合程度

如果某个东西是一个 bug,或者它非常明显,应该很快就会出现在产品中,它就会去找 Trello。否则,它很可能会进入用户之声,在那里其他人可以投票和扩展这个想法,直到它准备好。

使用 Alpine Docker 图像- Octopus 部署

原文:https://octopus.com/blog/using-alpine-docker-image

由于尺寸较小, Alpine Docker 图像经常被用作其他自定义图像的基础。在 Docker Hub 上有超过 10 亿的下载量,Alpine 也是最受欢迎的图片之一。(你也可以阅读我在发的关于 Ubuntu 的帖子,它目前是 Docker Hub 下载最多的图片。)

在这篇文章中,我向你展示了当你在 Alpine 上创建自己的图片时可以采用的最佳实践。

清理缓存

在工作站和服务器上安装软件时,缓存的软件包列表非常有用,因为它们提供了对软件包存储库中可用软件包的快速访问。然而,安装在 Docker 容器中的包很少在运行时更新;相反,Docker 映像本身被更新,容器被重新创建。这意味着将包缓存列表烘焙到 Docker 映像中是不必要且低效的。

删除缓存包列表的一种方法是用apk add --update-cache命令安装新的包,然后删除/var/cache/apk下的文件,作为单个RUN指令的一部分。这可确保创建软件包缓存,这是安装其他软件包所必需的,然后在不捕获中间映像层中的软件包缓存的情况下进行清理:

RUN apk add --update-cache \
    python \
    python-dev \
    py-pip \
    build-base \
  && pip install virtualenv \
  && rm -rf /var/cache/apk/* 

您也可以使用apk add --no-cache选项。这相当于前面的命令,但更简洁:

RUN apk add --no-cache nginx 

虚拟包

虚拟包提供了一种将包捆绑在一个通用名称下的方法,允许它们作为一个组被删除。 Alpine Docker 图像文档提供了安装 Python 开发库、下载 Python 应用程序的依赖项,然后移除 Python 开发库的示例:

FROM alpine

WORKDIR /myapp
COPY . /myapp

RUN apk add --no-cache python py-pip openssl ca-certificates
RUN apk add --no-cache --virtual build-dependencies python-dev build-base wget \
  && pip install -r requirements.txt \
  && python setup.py install \
  && apk del build-dependencies

CMD ["myapp", "start"] 

虽然这个例子可行,但是效率很低。由于 Docker 缓存的实现方式,对使用指令COPY . /myapp复制到映像中的文件的任何更改都会使缓存失效,迫使后续指令重新运行。实际上,这意味着上面的例子将在每次 Python 代码改变时下载、安装和删除 Python 开发库。

更好的解决方案是使用多阶段构建。下面显示了一个示例:

FROM alpine AS compile-image

RUN apk add --no-cache python3 py-pip openssl ca-certificates python3-dev build-base wget

WORKDIR /myapp

COPY requirements.txt /myapp/
RUN python3 -m venv /myapp
RUN /myapp/bin/pip install -r requirements.txt

FROM alpine AS runtime-image

RUN apk add --no-cache python3 openssl ca-certificates

WORKDIR /myapp
COPY . /myapp

COPY --from=compile-image /myapp/ ./

CMD ["/myapp/bin/python", "myapp.py", "start"] 

多阶段构建允许您使用构建应用程序源代码所需的开发库来创建映像。这个“编译映像”在两次构建之间保留了这些开发库,消除了每次下载它们的需要。

创建第二个映像来托管可执行的应用程序代码和运行时库,但具体来说不包括任何仅在编译时需要的库。这个“运行时映像”尽可能小,因为它复制由编译映像产生的文件,而不需要相关的编译时库。

musl vs glibc

在很大程度上,Alpine 可以作为任何其他基本 Docker 图像的替代物。然而,了解 Alpine 和其他常见的基础 Docker 映像(如 Ubuntu、Debian 或 Fedora)之间的架构差异非常重要。

Alpine 使用 musl C 标准库,而 Ubuntu、Debian、Fedora 使用 glibc 。下面是两个库的详细对比。

在某些情况下,第三方工具假设或需要 glibc。例如, Visual Studio 代码远程容器执行文档提供了以下警告:

当使用 Alpine Linux 容器时,由于扩展中本机代码的 glibc 依赖性,一些扩展可能无法工作。

基于 Alpine 的图像也不适合用作 Octopus 容器图像:

构建在 musl 上的 Linux 发行版,尤其是 Alpine,不支持 Calamari,也不能用作容器映像。这是因为 Calamari 目前只针对 glibc 编译,而不是 musl。

PythonSpeed 的博客文章使用 Alpine 可以让 Python Docker 构建速度慢 50 倍,详细介绍了在 Alpine 上构建 Python 应用时的一些性能问题:

大多数 Linux 发行版使用标准 C 库的 GNU 版本(glibc ),几乎每个 C 程序都需要它,包括 Python。但是 Alpine Linux 使用的是 musl,那些二进制轮子是针对 glibc 编译的,因此 Alpine 禁用了 Linux 轮子支持。

除了已知与 musl 不兼容的特定用例之外,我发现 Alpine 是一个可靠而实用的选择,可以作为我自己的 Docker 图像的基础。但是了解使用实现 musl 的发行版的含义是有好处的。

结论

Alpine 提供了一个轻量级和流行的 Docker 映像,与其他流行的映像(如 Ubuntu)相比,它可以改进您的映像构建和部署时间。

Alpine 使用 musl C 标准库,这在某些情况下可能会引入兼容性问题,但是您通常可以假设 Alpine 为您的定制 Docker 映像提供了所需的一切。虚拟包等高级功能也可以让您缩小映像大小,尽管多阶段构建可能是更好的选择。

了解如何使用其他流行的容器图像:

资源

了解更多信息

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

愉快的部署!

使用 AWS Secrets Manager 和 Octopus - Octopus Deploy

原文:https://octopus.com/blog/using-aws-secrets-manager-with-octopus

我已经编写了几个 step 模板,扩展了 Octopus 的功能,以与秘密管理器集成,最后一个是 Google Cloud Secret Manager 。在这篇文章中,我将介绍另一个主要的云提供商,Amazon Web Services (AWS)。

我浏览了AWS Secrets Manager-Retrieve Secrets步骤模板,该模板旨在从 AWS Secrets Manager 中检索机密,以便在您的部署或操作手册中使用。

入门指南

这篇文章假设读者对定制步骤模板和 Octopus 社区库比较熟悉。

此外,这篇文章没有详细介绍 AWS Secrets Manager 的概念或如何设置 Secrets Manager。你可以通过阅读亚马逊的用户指南了解更多信息。

本文中的步骤模板使用 AWS 命令行接口 (AWS CLI),从 AWS 秘密管理器中检索秘密,这是一个 AWS 命令行工具。在该步骤能够成功检索机密之前, AWS CLI 工具必须在部署目标或工作者上可用。step 模板已经在 Windows 和 Linux 上测试过(安装了 PowerShell 核心)。

向 AWS 认证

在从 AWS Secrets Manager 中检索机密之前,您必须通过 AWS 的身份验证。在他们的认证和访问控制指南中,亚马逊描述了他们如何使用 IAM 权限来控制对秘密的访问:

通过使用 IAM 权限策略,您可以控制哪些用户或服务可以访问您的机密。权限策略描述了谁可以对哪些资源执行哪些操作。

在 Octopus 中,您可以使用具有适当权限的 AWS 帐户进行 AWS 认证。

要了解有关使用 AWS Secrets Manager 将 IAM 权限策略附加到机密或身份的更多信息,请查看权限策略示例

找回秘密

AWS 机密管理器-检索机密步骤模板:

  • 从 AWS 机密管理器中检索一个或多个机密
  • 从每个秘密中提取一个或多个键/值对
  • 为检索到的每个键/值对创建敏感的输出变量

像大多数云提供商一样,AWS Secrets Manager 通过使用版本标识符和一个或多个分级标签来支持版本化的机密。这是有用的,因为它可以定期轮换你的秘密。

转移标签在循环过程中跟踪不同的版本。AWS 秘密总是有一个带有分级标签AWSCURRENT的版本,这是当前的秘密值。

AWS Secrets Manager 中的一个秘密可以存储多个值。Amazon 建议使用带有键/值对的 JSON 文本字符串,例如:

{
  "hostname"   : "test01.example-database.net",
  "hostport"   : "3458",
  "user"       : "octo_admin_user",
  "pwd"        : "M4eXT4a$uPeA$3cRetP@s5w0rd!"
} 

此步骤模板设计用于从一个或多个机密中检索多个键/值对。

检索单个机密及其键/值对需要:

  • 有权访问机密的 AWS 帐户
  • 默认 AWS 区域代码
  • 名称和可选地秘密的版本和存储在秘密中的特定键/值对的名称,以便为其创建变量

step 模板有一个高级特性,支持一次检索多个秘密。这需要在新的一行中输入每个秘密。

对于检索到的每个秘密和键/值对,创建一个敏感输出变量用于后续步骤。默认情况下,任务日志中只会显示已创建变量的数量。要查看任务日志中的变量名,将打印输出变量名参数更改为True

步骤模板参数

步骤模板使用以下参数:

  • AWS 账户:一个 AWS 账户,有权限从秘密管理器中访问秘密。

  • AWS 区域:指定默认区域。查看 AWS 区域和端点文档,获取可用区域代码的当前列表。

  • 要检索的机密名称:指定要从 AWS Secrets Manager 返回的机密名称,格式:SecretName SecretVersionId SecretVersionStage | KeyNames | OutputVariableName其中:

    • SecretName :要检索的秘密的名称。您可以指定机密的Amazon Resource Name (ARN)或友好名称。
    • SecretVersionId :您想要检索的秘密版本的唯一标识符。如果未指定该值,则检索当前版本。
    • SecretVersionStage :您想要通过附加到版本的 staging 标签检索的秘密版本。如果未指定该值,则使用默认的分段标签值AWSCURRENT注意:如果未指定 SecretVersionId ,该值将被忽略。
    • KeyNames :存储在您希望检索其值的秘密中的密钥的名称。可以检索由空格分隔的多个字段。或者,您可以使用特殊关键字all*指定所有字段。
    • OutputVariableName可选 Octopus 输出变量的名称,用于存储秘密值。如果指定了多个键/值对,则将键/值名称附加到该值。

    如果同时指定了 SecretVersionIdSecretVersionStage ,它们必须指向同一个秘密版本,否则 AWS 通常会返回一个NotFound错误。

    注:通过在新的一行中输入每个秘密,可以检索多个秘密。更多信息参见示例

  • 打印输出变量名:将章鱼输出变量名写入任务日志。默认:False

Parameters for the step

秘密检索示例

让我们以 AWS Secrets Manager 中存储的名为octo samples-user credentials的秘密为例,它有两个键/值对:

以下是检索机密及其键/值对的一些方法:

  1. OctoSamples-usercredentials | Username | octousername

    这将检索秘密并从名为Username的键/值中提取值,并将其保存到名为octousername的敏感输出变量中。

  2. OctoSamples-usercredentials | Username Password | octocreds

    这将检索秘密,并从名为UsernamePassword的键/值中提取值,并将它们保存到名为octocreds.Usernameoctocreds.Password的两个敏感输出变量中。

  3. OctoSamples-usercredentials | * | octocreds

    这将检索秘密,并从秘密中提取所有键/值,并将它们保存到前缀为的敏感输出变量中。

  4. OctoSamples-usercredentials | all

    这将检索秘密,并从秘密中提取所有键/值,并将它们保存到前缀为OctoSamples-usercredentials的敏感输出变量

使用步骤

AWS 机密管理器-检索机密步骤以与其他步骤相同的方式添加到部署和运行手册流程中。

将步骤添加到流程后,填写步骤中的参数:

AWS Secrets Manager - Retrieve Secrets step used in a process

填写参数后,您可以在操作手册或部署流程中执行该步骤。在成功执行时,在匹配机密中找到的键/值对中的任何值都被存储为敏感的输出变量。如果将步骤配置为打印变量名,它们会出现在任务日志中:

AWS Secrets Manager - Retrieve secrets step output variables task log

在后续步骤中,创建的输出变量可以在您的部署或 runbook 中使用。

提示:对于任何输出变量名,记得用您的步骤名替换AWS Secrets Manager - Retrieve Secrets

结论

AWS Secrets Manager-Retrieve Secrets 步骤模板演示了与 AWS Secrets Manager 集成并利用 Octopus 部署或 runbooks 中存储的秘密是很容易的。

愉快的部署!

使用 Azure Key Vault 和 Octopus - Octopus Deploy

原文:https://octopus.com/blog/using-azure-key-vault-with-octopus

Using Azure Key Vault with Octopus Deploy

我最近写了关于使用 step 模板扩展 Octopus 的功能以集成 HashiCorp Vault 的文章。后来,几个人问我是否打算创建 step 模板来与其他 secret managers 集成。

在这篇文章中,我介绍了一个新的步骤模板,Azure Key Vault-Retrieve Secrets,它旨在从 Azure Key Vault 中检索机密,以便在您的部署或操作手册中使用。

介绍

这篇文章假设读者对定制步骤模板和 Octopus 社区库比较熟悉。

此外,这篇文章没有详细介绍 Azure Key Vault 的概念或如何设置它。你可以通过阅读微软的 Azure Key Vault 基本概念指南来了解更多信息。

本文中的步骤模板使用 Az 从 Azure 密钥库中检索秘密。KeyVault PowerShell 模块。该模块必须下载并安装在部署目标或工作器上,然后该步骤才能成功检索机密。step 模板已经在 Windows 和 Linux 上测试过了(安装了PowerShell Core)。

证明

在您可以从 Azure Key Vault 检索机密之前,您必须向 Azure 进行身份验证。在他们的认证概念文档中,微软指出:

使用 Key Vault 的身份验证与 Azure Active Directory (Azure AD)协同工作,后者负责对任何给定安全主体的身份进行身份验证。

在 Octopus 中,Azure Key Vault 的身份验证可以通过一个 Azure 帐户使用服务主体来实现。

除了访问 Azure 中的资源,您的服务主体可能需要配置更多权限来访问和检索 Azure Key Vault 中存储的机密。要了解更多信息,请阅读关于如何使用 Azure 基于角色的访问控制提供对密钥、证书和秘密的访问的 Azure Key Vault RBAC 指南。

找回秘密

Azure Key Vault-Retrieve Secrets步骤模板从 Azure Key Vault 中检索一个或多个机密,并为每个检索到的机密创建敏感的输出变量。

对于每个秘密,您可以选择检索一个特定的版本,并提供一个定制的输出变量名。

检索一个秘密需要:

  • 有权限访问机密的 Azure 帐户。
  • 要从中检索机密的 Azure 密钥库的名称。
  • 要检索的机密的名称。

step 模板的一个高级特性支持一次检索多个机密。这需要在新的一行中输入每个秘密。

对于检索到的每个秘密,创建一个敏感输出变量用于后续步骤。默认情况下,任务日志中只会显示已创建变量的数量。要查看任务日志中的变量名,将打印输出变量名参数更改为True

步骤模板参数

步骤模板使用以下参数:

  • Azure Account:一个 Azure 账户,有权限从 Azure Key Vault 中检索机密。

  • Vault Name:要从中检索机密的 Azure Key Vault 的名称。

  • Vault Secrets to retrieve:指定从 Azure Key Vault 返回的秘密名称,格式:SecretName SecretVersion | OutputVariableName其中:

    • SecretName是要检索的秘密的名称。
    • SecretVersion可选的版本的秘密检索。如果未指定该值,将检索最新版本
    • OutputVariableName可选 Octopus 输出变量的名称,用于存储秘密值。如果未指定该值,将动态生成一个输出名称

    注意:通过在新的一行中输入每个字段,可以检索多个字段。

  • Print output variable names:将 Octopus 输出变量名称写入任务日志。默认:False

  • Az PowerShell Module version (optional):如果您希望使用特定版本的Az PowerShell 模块(而非默认版本),请在此输入版本号。如5.9.0

    注意:指定的版本必须存在于机器上。

  • Az PowerShell Install Location (optional):如果您希望为Az PowerShell 模块提供自定义路径(而不是默认路径),请在此输入值。

    注意:模块必须存在于机器上的指定位置。此步骤模板不会下载模块。

Parameters for the step

使用步骤

以与其他步骤相同的方式将Azure Key Vault-Retrieve Secrets步骤添加到部署和 runbook 流程中。

将步骤添加到流程后,填写步骤中的参数:

Azure Key Vault retrieve secrets step used in a process

填写完参数后,您可以在操作手册或部署流程中执行该步骤。成功执行后,任何匹配的机密都将被存储为敏感的输出变量。如果您已将步骤配置为打印变量名,它们将出现在任务日志中:

Azure Key Vault retrieve secrets step task log

在后续步骤中,从匹配密码创建的输出变量可以在您的部署或 runbook 中使用。

提示:对于任何输出变量名,记得用您的步骤名替换Azure Key Vault - Retrieve Secrets

结论

这篇文章中提到的步骤模板演示了与 Azure Key Vault 集成并利用 Octopus 部署或 runbooks 中存储的秘密是很容易的。

愉快的部署!

使用自定义 Terraform 插件的三种方法——Octopus Deploy

原文:https://octopus.com/blog/using-custom-tf-plugins

Custom Terraform Plugins

Hashicorp GitHub 仓库中正在进行关于支持 Terraform 定制插件仓库的讨论。尽管现在只有官方的 Terraform 插件可以按需下载,而定制插件需要手动分发。

在本帖中,我们将看看从 Terraform 模板中访问自定义插件的三种不同方式。

示例项目

为了演示如何加载自定义插件,我们将创建一个非常简单的 Terraform 模板,它使用了 Octopus 插件

为了添加对 Octopus provider 的依赖,我们在一个名为octopus.tf的文件中有以下代码:

provider "octopusdeploy" {
  address = "${var.address}"
  apikey  = "${var.apikey}"
  space   = "${var.space}"
} 

然后在名为variables.tf的文件中定义变量:

variable "address" {
  default = "http://myserver"
}
variable "apikey" {
  default = "API-YOURAPIKEYGOESHERE"
}
variable "space" {
  default = "Default"
} 

有了本地目录中的这两个文件,我们运行terraform init。结果是失败的,因为 Terraform 不知道如何获得 Octopus 插件。

没有可用的插件,init 命令失败。

使插件全球可用

解决该错误的第一个选项是将插件文件保存到 Windows 中的~\AppData\Roaming\terraform.d\plugins或 Linux 和 MacOS 中的~/.terraform.d/plugins

你必须小心文件名。对于 Windows,Octopus 插件将具有文件名terraform-provider-octopusdeploy_v0.5.0.exe,对于 Linux 和 MacOS,文件名将为terraform-provider-octopusdeploy_v0.5.0(用插件版本替换0.5.0)。任何其他文件名都将导致一个关于注册表服务不可访问的模糊错误。

一个意外的文件名会产生这个无用的错误。

但是当您有正确的文件名时,init命令将成功完成。

使用正确的文件名,init 命令成功。

将插件保存在模板旁边

下一个选项是将插件保存在模板文件旁边的.terraform/plugins/<arch>目录中。<arch>目录名与下表中的一个相匹配。同样,对于 Windows,插件的文件名必须是terraform-provider-octopusdeploy_v0.5.0.exe,对于 Linux 和 MacOS,插件的文件名必须是terraform-provider-octopusdeploy_v0.5.0

操作系统(Operating System) 拱门
Windows 32 位 windows_386
Windows 64 位 windows_amd64
Linux 32 位 linux_386
Linux 64 位 linux_amd64
MacOS 64 位 darwin_amd64

插件目录选项

最后一个选项是将插件可执行文件保存在您选择的文件夹中,并使用--plugin-dir参数引用它。

当您使用--plugin-dir参数时,对目录名没有特殊要求。这里我调用了terraform init --plugin-dir C:\Users\Matthew\Desktop\plugins\whatever来证明目录名没有意义。

plugin-dir 选项将获取任何包含正确插件的目录。

结论

在 Terraform 实现对定制插件库的支持之前,最终用户将不得不自己部署插件可执行文件。在这里,我们看到这些插件可以保存在:

  • ~\AppData\Roaming\terraform.d\plugins用于 Windows 或~/.terraform.d/plugins用于 Linux 和 MacOS。
  • .terraform/plugins/<arch>傍模板文件。
  • 任何被--plugin-dir选项引用的目录。

使用新的 ECS 部署步骤- Octopus 部署

原文:https://octopus.com/blog/using-ecs-deployment-step

我之前写过一篇文章,演示如何使用 Octopus Deploy 部署到 AWS ECS。我向您展示了如何让它工作,但是使用 AWS CLI 自己编写脚本并不是一种理想的体验。我们从社区中收集反馈,发现我们的客户想要更好的 ECS 支持。从 2021.3 版本开始,Octopus Deploy 包含了一个 ECS 部署步骤,取代了我最初帖子中的所有内容。

在这篇文章中,我将解释如何实现新的 ECS 部署步骤。

亚马逊 ECS 目标

首先,您需要创建一个要部署到的 ECS 集群。以下脚本使用 AWS CLI 创建集群:

# Get variables
$ecsClusterName = $OctopusParameters['AWS.ECS.Cluster.Name']

# Create cluster
aws ecs create-cluster --cluster-name $ecsClusterName 

创建集群后,需要将其作为部署目标添加到 Octopus Deploy 中。创建新目标时,选择 AWS 类别,然后选择新的亚马逊 ECS 集群目标。

这个帖子使用PetClinic-ECS作为分配的角色。

与其他目标类型一样,您也可以通过 API 或使用脚本命令来添加目标。

应用程序接口

有许多关于如何通过 API 注册目标的例子。然而,ECS 步骤使用了新的步骤 UI 框架。ECS 目标的 JSON 文档的Endpoint组件不同于其他目标类型。以下示例脚本向您展示了如何通过 API 注册 ECS 目标:

# Define parameters
$baseUrl = $OctopusParameters['Global.Base.Url']
$apiKey = $OctopusParameters['Global.Api.Key']
$spaceId = $OctopusParameters['Octopus.Space.Id']
$spaceName = $OctopusParameters['Octopus.Space.Name']
$environmentName = $OctopusParameters['Octopus.Environment.Name']
$environmentId = $OctopusParameters['Octopus.Environment.Id']
$awsAccount = $OctopusParameters['AWS.Account']
$awsECSClusterName = $OctopusParameters['AWS.ECS.Cluster.Name']
$awsRegion = $OctopusParameters['AWS.Region.Name']
$name = $OctopusParameters['AWS.ECS.Cluster.Name']

# Get default machine policy
$machinePolicy = (Invoke-RestMethod -Method Get -Uri "$baseUrl/api/$spaceId/machinepolicies/all" -Headers @{"X-Octopus-ApiKey"="$apiKey"}) | Where-Object {$_.Name -eq "Default Machine Policy"}
Write-Output "Retrieved $($machinePolicy.Name) ..."

# Build JSON payload
$jsonPayload = @{
    Id = $null
    MachinePolicyId = $machinePolicy.Id
    Name = $name
    IsDisabled = $false
    HealthStatus = "Unknown"
    HasLatestCalamari = $true
    StatusSummary = $null
    IsInProcess = $true
    EndPoint = @{
        DeploymentTargetType = "aws-ecs-target"
        DeploymentTargetTypeId = "aws-ecs-target"
        StepPackageId = "aws-ecs-target"
        StepPackageVersion = "1.0.0"
        Inputs = @{
            clusterName = $awsECSClusterName
            region = $awsRegion
            awsAccount = $awsAccount
        }
        RelatedDocumentIds = @($awsAccount)
        Id = $null
        CommunicationStyle = "StepPackage"
        Links = $null
        DefaultWorkerPoolId = ""
    }
    Links = $null
    TenantedDeploymentParticipation = "Untenanted"
    Roles = @(
        "PetClinic-ECS"
    )
    EnvironmentIds = @(
        $environmentId
    )
    TenantIds = @()
    TenantTags = @()
}

# Register the target to Octopus Deploy
Invoke-RestMethod -Method Post -Uri "$baseUrl/api/$spaceId/machines" -Headers @{"X-Octopus-ApiKey"="$apiKey"} -Body ($jsonPayload | ConvertTo-Json -Depth 10) 

脚本命令

使用 Step UI 框架开发的目标类型利用一个新命令来注册它们的目标类型。下面是如何使用命令通过 PowerShell 或 Bash 注册 ECS 目标的示例:

$inputs = @"
{
    "clusterName": "$($OctopusParameters["clusterName"])",
    "region": "$($OctopusParameters["region"])",
    "awsAccount": "$($OctopusParameters["awsAccount"])",
}
"@
New-OctopusTarget -Name $OctopusParameters["target_name"] -TargetId "aws-ecs-target" -Inputs $inputs -Roles $OctopusParameters["role"] 
read -r -d '' INPUTS <<EOT
{
    "clusterName": "$(get_octopusvariable "clusterName")",
    "name": "$(get_octopusvariable "target_name")",
    "awsAccount": "$(get_octopusvariable "awsAccount")",
}
EOT
new_octopustarget -n "$(get_octopusvariable "target_name")" -t "aws-ecs-target" --inputs "$INPUTS" --roles "$(get_octopusvariable "role")" 

部署 Amazon ECS 服务步骤

为了向您展示如何使用部署 Amazon ECS 服务步骤,这篇文章复制了来自上一篇文章的 Octo 宠物店应用程序的容器化版本的部署,使用了新的步骤。

添加部署 Amazon ECS 服务步骤

添加部署亚马逊 ECS 服务步骤,点击添加步骤,选择 AWS ,然后部署亚马逊 ECS 服务

Octopus dashboard showing process editor and Deploy Amazon ECS Service step

添加步骤后,使用脚本中的相同信息填充表单字段。没有列出的内容可以留空或使用默认值。

  • 名称:octopetshop-web
  • 期望计数:1
  • 任务内存(GB): 4 GB
  • 任务 CPU(单位):0.5 vCPU
  • 安全组 ID:您的 AWS 安全组 ID
  • 子事件 id:两个 AWS 子网 id
  • 自动分配公共 IP: Yes
  • 启用 ECS 管理的标签:No

点击添加按钮,将集装箱添加到 ECS 服务中。

Container Definitions section with ADD button highlighted

  • 容器定义:
    • 容器名称:octopetshop-web
      • 容器图像:这篇文章从 AWS ACR 中提取 octopetshop-web 图像
      • 容器端口映射:
      • 环境变量
        • 按键:ProductServiceBaseUrl, Value: http://localhost:5011
        • 关键词:ShoppingCartServiceBaseUrl, Value: http://localhost:5012
    • 容器名称:octopetshop-productservice
    • 容器图像:这篇文章从 AWS ACR 中提取 octopetshop-productservice 图像
    • 容器端口映射:
    • 环境变量:
      • 按键:OPSConnectionString, Value: Database connection string (ie: Data Source=localhost;Initial Catalog=OctoPetShop; User ID=#{Project.Database.User.Name}; Password=#{Project.Database.User.Password}
    • 容器名称:octopetshop-shoppingcartservice
      • 容器图像:这篇文章从 AWS ACR 中提取 octopetshop-web 图像
      • 容器端口映射:
    • 环境变量:
      • 关键:OPSConnectionString, Value: Database connection string (ie: Data Source=localhost;Initial Catalog=OctoPetShop; User ID=#{Project.Database.User.Name}; Password=#{Project.Database.User.Password})
    • 容器名称:sqlserver
      • 容器映像:这篇文章从微软容器注册表中提取 mssql/server 映像
      • 容器端口映射:
      • 环境变量:
        • 关键:ACCEPT_EULA, Value: Y
        • 关键:SA_PASSWORD, Value: (secure password)
    • 容器名称:octopetshop-database
      • 容器图像:这篇文章从 AWS ACR 中提取 octopetshop 数据库图像
      • 必备:False
    • 环境变量:
      • 关键:DbUpConnectionString, Value: Database connection string (ie: Data Source=localhost;Initial Catalog=OctoPetShop; User ID=#{Project.Database.User.Name}; Password=#{Project.Database.User.Password})

就是这样!这一步取代了前一篇文章中的自定义脚本。

部署如下所示:

Octopus task summary with green ticks

测试部署

部署完成后,AWS 控制台显示 Fargate 服务正在运行。

AWS console showing a Fargate service running

点击进入集群,您会看到您的容器正在运行,并且分配了一个公共 IP 地址。

Cluster showing containers and IP address

octopetshop-database执行数据库迁移,并被设计为在完成后停止。STOPPED状态正常。

octopetshop-web 容器被配置为侦听端口 5000 和 5001。导航到公共 IP 地址,端口 5000 将您重定向到 5001 上的 HTTPS。该证书是不受信任的. NET 核心开发证书,因此关于安全风险的对话是正常的。进入该站点后,您会看到 Octo 宠物店应用程序。

Octo Pet Shop welcome screen

结论

我的上一篇文章向您展示了如何使用脚本方法部署到 Amazon ECS。虽然它有效,但体验并不像内置的部署步骤那样简单。这篇文章演示了如何用新的内置 ECS 部署步骤替换脚本方法。

愉快的部署!

使用谷歌云秘密管理器与八达通-八达通部署

原文:https://octopus.com/blog/using-google-cloud-secret-manager-with-octopus

我以前写过关于使用 step 模板扩展 Octopus 的功能,以与 HashiCorp VaultAzure Key Vault 集成。我们致力于为我们的客户提供许多成功使用 Octopus 的方法,因此我们正在创建更多与其他 secret managers 集成的 step 模板。

在这篇文章中,我将介绍一个新的步骤模板, GCP 秘密管理器-获取秘密。此步骤模板从 Google 云平台(GCP)上的 Secret Manager 中检索机密,以便在您的部署或操作手册中使用。

入门指南

这篇文章假设读者对定制步骤模板和 Octopus 社区库比较熟悉。

此外,这篇文章没有详细介绍秘密管理器的概念或如何设置秘密管理器。你可以通过阅读谷歌的 Secret Manager 快速入门指南了解更多。

本文中的步骤模板使用 GCP 命令行工具 gcloud秘密管理器中检索秘密。必须在部署目标或工作者上安装 gcloud 工具版本 338.0.0 或更高版本,步骤才能成功检索机密。

这一步还需要 Octopus 2021.2 或更新版本,因为它利用了我们最近添加的对谷歌云平台的内置支持。step 模板已经在 Windows 和 Linux 上测试过(安装了 PowerShell 核心)。

证明

在您可以从 Secret Manager 中检索机密之前,您必须通过 Google 认证。在他们关于创建和访问机密的文档中,Google 描述了访问机密的角色:

访问机密版本需要机密、项目、文件夹或组织的机密管理员机密访问者角色(roles/secretmanager.secretAccessor)。不能在秘密版本上授予 IAM 角色。

要了解更多关于您可以在 Secret Manager 中使用的不同角色,请阅读关于如何提供访问敏感密码、证书和其他机密的权限的访问控制文档

在 Octopus 中,您可以使用我们在版本 2021.2 中添加的谷歌云账户与谷歌云平台进行认证

找回秘密

GCP 秘密管理器-检索秘密步骤模板从秘密管理器中检索一个或多个秘密,并为每个检索到的秘密创建敏感的输出变量。

对于每个秘密,您必须提供一个要检索的秘密版本,并且可以选择提供一个自定义输出变量名。

始终选择特定的机密版本
Google 建议,对于生产应用程序,您应该始终使用特定版本来检索机密,而不是使用最新的版本说明符。

检索一个秘密需要:

  • 一个允许访问机密的 Google Cloud 帐户,包括默认的项目区域和区域的详细信息
  • 要检索的机密的名称

step 模板的一个高级特性支持一次检索多个机密。这需要在新的一行中输入每个秘密。

对于检索到的每个秘密,创建一个敏感输出变量用于后续步骤。默认情况下,任务日志中只会显示已创建变量的数量。要查看任务日志中的变量名,将打印输出变量名参数更改为True

步骤模板参数

步骤模板使用以下参数:

  • Google Cloud Account :一个 Google Cloud account ,有权限访问 Secret Manager 的秘密。

  • Google Cloud 项目:指定默认项目。这将设置CLOUDSDK_CORE_PROJECT环境变量。

  • 谷歌云区域:指定默认区域。这将设置CLOUDSDK_COMPUTE_REGION环境变量。

  • Google Cloud Zone :指定默认区域。这将设置CLOUDSDK_COMPUTE_ZONE环境变量。

  • 要检索的秘密名称:指定要从 Google Cloud 中的 Secret Manager 返回的秘密名称,格式:SecretName SecretVersion | OutputVariableName其中:

    • SecretName是要检索的机密的名称。
    • SecretVersion是秘密检索的版本。如果未指定该值,将检索最新版本
    • OutputVariableName可选 Octopus 输出变量的名称,用于存储秘密值。如果未指定该值,将动态生成一个输出名称

    注意:通过在新的一行中输入每个字段,可以检索多个字段。

  • 打印输出变量名:将章鱼输出变量名写入任务日志。默认:False

Parameters for the step

使用步骤

GCP 机密管理器-检索机密步骤以与其他步骤相同的方式添加到部署和运行手册流程中。

将步骤添加到流程后,填写步骤中的参数:

GCP Secret Manager - Retrieve Secrets step used in a process

填写参数后,您可以在操作手册或部署流程中执行该步骤。成功执行后,任何匹配的机密都将被存储为敏感的输出变量。如果您将步骤配置为打印变量名,它们会出现在任务日志中:

GCP Secret Manager - Retrieve secrets step task log

在后续步骤中,从匹配密码创建的输出变量可以在您的部署或 runbook 中使用。

提示:对于任何输出变量名,记得用您的步骤名替换GCP Secret Manager - Retrieve Secrets

尽管不建议这样做,但还是有可能检索到秘密的最新版本。您可以从机密名称参数中省略版本,如下所示:

OctoSecrets-username | username-latest 

或者,您可以使用最新的版本说明符,如下所示:

OctoSecrets-password latest | password-latest 

在这两种情况下,步骤模板都会向任务日志发送警告:

GCP Secret Manager - Retrieve secret using latest version specifier warning

结论

GCP 秘密管理器-检索秘密步骤模板演示了与 Google Cloud Secret Manager 集成并利用 Octopus 部署或 runbooks 中存储的秘密是很容易的。

愉快的部署!

通过 Octopus - Octopus 部署使用 HashiCorp Vault

原文:https://octopus.com/blog/using-hashicorp-vault-with-octopus-deploy

在 Octopus Deploy 中存储敏感值解决了许多问题。但是,如果您的组织已经标准化了机密管理器,这可能意味着将敏感值存储两次,从而使机密管理更加复杂。

Octopus 2.0 以来,Octopus 一直支持敏感变量的概念,但客户经常询问对秘密管理器的支持。一个特别的例子是哈希公司的金库

在这篇文章中,我将介绍一些我们介绍过的 HashiCorp Vault 步骤模板,这些模板旨在从 Vault 中检索机密,用于您的部署或操作手册。

截至 2022 年 11 月,我们 HashiCorp Vault 的外部秘密存储模板由 HashiCorp 认证,使 Octopus 部署了一个认证的 HashiCorp 合作伙伴

在这篇文章中

介绍

这篇文章假设你知道自定义步骤模板和章鱼社区库。要了解更多,你可以阅读 Ryan Rousseau 的关于创建自己的 step 模板并将其发布到库的两部分系列文章。

此外,这篇文章没有详细介绍 Vault 服务器的概念或如何配置 Vault 服务器。

本文中介绍的步骤模板为版本 1 和 2 的键值(kv) 秘密引擎执行保险库认证和秘密检索。

所有的步骤模板都利用了 Vault HTTP API ,因此除了连接到您的 Vault 服务器之外,没有其他依赖项可以使用它们。它们都已经使用 Vault 版本 1.11.3 进行了测试,包括对名称空间(Vault Enterprise 的一项功能)的支持,并且可以在安装了Powershell Core的 Windows 和 Linux 上运行。

证明

在与 Vault 交互之前,您必须根据验证方法进行验证。Vault 提供了许多不同的身份验证选项。已创建以下步骤模板来支持 Vault 身份验证:

AppRole 方法是使用 Vault for servers 进行身份验证的推荐方法。

通过 Vault 验证后,会生成一个令牌,可用于与 Vault 的进一步交互。

LDAP 登录步骤

HashiCorp Vault - LDAP 登录步骤模板使用 LDAP 认证方法向 Vault 服务器进行认证。这允许 Vault 集成,而无需复制用户名或密码配置。

如果您已经有可用的 LDAP 服务器,您可以选择使用 LDAP 进行鉴定,并使用服务帐户来控制对敏感信息的访问。

认证后,来自保险库响应的client_token将作为名为LDAPAuthToken敏感输出变量在其他步骤中使用。

LDAP 登录参数

步骤模板具有以下参数:

  • Vault Server URL:您正在连接的 Vault 实例的 URL,包括端口(默认为8200)。
  • API version:从下拉列表中选择要使用的 API 版本。目前只有一个选项:v1
  • Namespace : 可选要使用的命名空间。可以提供嵌套的名称空间,例如ns1/ns2注意:名称空间仅在 Vault Enterprise 上受支持。
  • LDAP Auth Login path:LDAP 方法挂载到的路径。默认是/auth/ldap
  • Username:LDAP 用户名。
  • Password:LDAP 密码。

T31

使用 LDAP 登录步骤

LDAP 登录步骤以与其他步骤相同的方式添加到中的部署和 runbook 流程中。

将步骤添加到流程后,填写步骤中的参数:

Vault LDAP login step used in a process

然后,您可以在操作手册或部署流程中执行该步骤。成功执行后,包含该令牌的敏感输出变量名将显示在任务日志中:

Vault LDAP login step task log

在后续步骤中,输出变量#{Octopus.Action[HashiCorp Vault - LDAP Login].Output.LDAPAuthToken}可用于认证和检索秘密。

提示:对于任何输出变量名,记得用您的步骤名替换HashiCorp Vault - LDAP Login

JWT 登录步骤

哈希公司 Vault - JWT 登录步骤模板使用 JWT 认证方法向 Vault 服务器进行认证。这允许使用一个 JSON Web 令牌进行保险库集成。

如果您的组织中已经使用了 JWT 身份验证方法,您可以选择使用该方法进行身份验证。当通过 HTTP 发出请求时,这是一个很好的选择,因为它是一个自包含的令牌,可以在任何地方生成。

生成 JWT
Octopus 有两个现有的步骤模板来生成一个用私钥签名的 JWT,但它们不在本文讨论范围之内。有关更多信息,请参见步骤模板描述:

认证后,来自保险库响应的client_token作为一个名为JWTAuthToken敏感输出变量可用于其他步骤。

JWT 登录参数

步骤模板具有以下参数:

  • Vault Server URL:您正在连接的 Vault 实例的 URL,包括端口(默认为8200)。
  • API version:从下拉列表中选择要使用的 API 版本。目前只有一个选项:v1
  • Namespace : 可选要使用的命名空间。可以提供嵌套的名称空间,例如ns1/ns2注意:名称空间仅在 Vault Enterprise 上受支持。
  • JWT Auth Login path:在安装 JWT 方法的路径。默认为/auth/jwt
  • JWT role:跳马 JWT 角色
  • JWT Token:已签名的 JSON Web 令牌 (JWT)用于登录。

Parameters for the Vault JWT login step

使用 JWT 登录步骤

以与其他步骤相同的方式将 JWT 登录步骤添加到部署和运行手册流程中。

将步骤添加到流程后,填写步骤中的参数:

Vault JWT login step used in a process

然后,您可以在操作手册或部署流程中执行该步骤。成功执行后,包含该令牌的敏感输出变量名将显示在任务日志中:

Vault JWT login step task log

在后续步骤中,输出变量#{Octopus.Action[HashiCorp Vault - JWT Login].Output.JWTAuthToken}可用于认证和检索秘密。

提示:对于任何输出变量名,记得用您的步骤名替换HashiCorp Vault - JWT Login

适当的登录步骤

HashiCorp Vault - AppRole 登录步骤模板使用 AppRole 认证方法向 Vault 服务器进行认证。这非常适合与章鱼一起使用。HashiCorp 建议将其用于机器或应用程序:

这种身份验证方法面向自动化工作流(机器和服务),对人工操作员用处不大。

使用 AppRole,机器可以使用以下方式登录:

  • RoleID,将此视为认证对中的用户名。
  • SecretID,把这个当做认证对中的密码。

不要存储 SecretID:
将 RoleID 作为敏感变量存储在 Octopus 中是一种很好的方法,可以确保它在需要之前保持加密状态。

然而,同样是不推荐给 SecretID。

秘密就像密码一样,是为过期而设计的。存储 SecretID 还可以提供检索所有秘密的能力,因为 RoleID 和 SecretID 都是可用的。

我们建议您使用更安全的 Get wrapped SecretIDunwrapped SecretID 和 Login 步骤模板,因为它们使用了最佳实践响应包装之一。

如果您使用适当的登录步骤模板,我们建议您在执行时使用敏感的提示变量提供 SecretID。

经过认证后,来自保险库响应的client_token将作为一个名为AppRoleAuthToken敏感输出变量在其他步骤中使用。

合适的登录参数

步骤模板具有以下参数:

  • Vault Server URL:您正在连接的 Vault 实例的 URL,包括端口(默认为8200)。
  • API version:从下拉列表中选择要使用的 API 版本。目前只有一个选项:v1
  • Namespace : 可选要使用的命名空间。可以提供嵌套的名称空间,例如ns1/ns2注意:名称空间仅在 Vault Enterprise 上受支持。
  • App Role Path:安装 approle auth 方法的路径
  • Role ID:批准的角色
  • Secret ID:批准的secret

Parameters for the Vault AppRole login step

使用适当的登录步骤

批准登录步骤以与其他步骤相同的方式添加到部署和运行手册流程中。

将步骤添加到流程后,填写步骤中的参数:

Vault AppRole login step used in a process

然后,您可以在操作手册或部署流程中执行该步骤。成功执行后,包含该令牌的敏感输出变量名将显示在任务日志中:

Vault AppRole login step task log

在后续步骤中,输出变量#{Octopus.Action[HashiCorp Vault - AppRole Login].Output.AppRoleAuthToken}可用于认证和检索秘密。

提示:对于任何输出变量名,记得用您的步骤名替换HashiCorp Vault - AppRole Login

合适的最佳实践

AppRole 认证方法被认为是可信代理方法。这意味着信任的责任在于作为客户机(通常是 Octopus 部署目标)和 Vault 之间的认证中介的系统(即代理)。

一个重要的最佳实践是避免存储合适的机密。相反,使用响应包装来提供一个包装令牌,它将提供一个访问机制来在需要时检索 SecretID。这种获取 SecretID 的方法也称为拉模式,因为它需要从 AppRole 中获取 SecretID 或拉取

保险库文件包含使用适当认证时的推荐模式

以下是这些建议的摘要:

  • 使用安全的系统作为代理来检索包装的 SecretID。
  • 使用响应包装获得 SecretID。
  • 限制 SecretID 的使用次数和生存时间(TTL)值,以防止过度使用。
  • 避免反模式,比如让代理检索秘密。

保护代理:
由于信任依赖于代理,我们建议使用 Octopus 服务器的内置工作器,或者高度安全的外部工作器来充当代理。它将负责检索包装好的 SecretID,并将该值传递给通过 Vault 进行身份验证的机器(客户端)。

为了支持这些推荐的实践,创建了三个额外的AppRole步骤模板:

接近包装的秘密步骤

hashi corp Vault-AppRole Get Wrapped Secret ID步骤模板为 AppRole 认证方法生成一个响应包装的 SecretID。

step 模板使用令牌通过 Vault 服务器进行身份验证,以检索包装的 SecretID。响应包含一个包装令牌和其他元数据,比如令牌的创建路径。

该值可用于验证未发生不当行为。包装令牌随后可用于检索实际的 SecretID 值。

用于进行身份验证以检索包装的 SecretID 的令牌应该具有有限的范围,并且应该只允许检索包装的 SecretID。考虑创建一个长期存储令牌,因为这只会带来较小的风险。

从 Vault 服务器收到响应后,创建 2 个敏感输出变量用于其他步骤:

  • WrappedToken这是从响应中包装的token,用于检索实际的 SecretID 值。
  • WrappedTokenCreationPath这是令牌的创建路径。它允许您验证没有发生不当行为

AppRole Get Wrapped SecretID 参数

步骤模板使用以下参数:

  • Vault Server URL:您正在连接的 Vault 实例的 URL,包括端口(默认为8200)。
  • API version:从下拉列表中选择 API 版本。目前只有一个选项:v1
  • Namespace : 可选要使用的命名空间。可以提供嵌套的名称空间,例如ns1/ns2注意:名称空间仅在 Vault Enterprise 上受支持。
  • App Role Path:安装 AppRole auth 方法的路径
  • Role Name:审批的角色名称
  • Time-to-live (TTL):以秒为单位的响应包装令牌本身的 TTL。默认为:120s
  • Auth Token:用于向 Vault 认证的令牌,生成响应包装的 SecretID。

Parameters for the Vault Get Wrapped SecretID step

使用适当的获取包装机密步骤

以与其他步骤相同的方式将 Get Wrapped SecretID 步骤添加到部署和 runbook 流程中。

将步骤添加到流程后,填写步骤中的参数:

Vault Get Wrapped SecretID step used in a process

然后,您可以在操作手册或部署流程中执行该步骤。成功执行后,敏感的输出变量名会显示在任务日志中:

Vault Get Wrapped SecretID step task log

在后续步骤中,输出变量可用于验证和检索实际的 SecretID 值:

  • #{Octopus.Action[HashiCorp Vault - AppRole Get Wrapped Secret ID].Output.WrappedToken}
  • #{Octopus.Action[HashiCorp Vault - AppRole Get Wrapped Secret ID].Output.WrappedTokenCreationPath}

提示:对于任何输出变量名,记得用您的步骤名替换HashiCorp Vault - AppRole Get Wrapped Secret ID

近似展开秘密步骤

hashi corp Vault-AppRole Unwrap Secret ID步骤模板使用包装令牌检索并解开 AppRole 的 Secret ID。

来自保险库响应的secret_id将作为名为UnwrappedSecretID敏感输出变量用于其他步骤。

适当的展开保密参数

步骤模板使用以下参数:

  • Vault Server URL:您正在连接的 Vault 实例的 URL,包括端口(默认为8200)。
  • API version:从下拉列表中选择要使用的 API 版本。目前只有一个选项:v1
  • Namespace : 可选要使用的命名空间。可以提供嵌套的名称空间,例如ns1/ns2注意:名称空间仅在 Vault Enterprise 上受支持。
  • Wrapped Token:包装令牌,用于从金库中检索实际的秘密 ID。
  • Token Creation Path : 可选包装令牌的创建路径。如果提供了该值,步骤模板将执行回绕查找验证没有发生不当行为

Parameters for the Vault Unwrap SecretID step

使用展开秘密步骤

与其他步骤一样,在中将展开 SecretID 步骤添加到部署和 runbook 流程中。

将步骤添加到流程后,填写步骤中的参数:

Vault Unwrap SecretID step used in a process

然后,您可以在操作手册或部署流程中执行该步骤。成功执行后,敏感的输出变量名会显示在任务日志中:

Vault Unwrap SecretID step task log

在后续步骤中,输出变量#{Octopus.Action[HashiCorp Vault - AppRole Unwrap Secret ID].Output.UnwrappedSecretID}可用于向 Vault 进行身份验证,并接收一个令牌,该令牌可用于检索机密。

提示:对于任何输出变量名,记得用您的步骤名替换HashiCorp Vault - AppRole Unwrap Secret ID

适当的解密和登录步骤

hashi corp Vault-AppRole Unwrap Secret ID and log in步骤模板是将 Vault 使用的两个步骤模板合并为一个的便捷方法:

它是 Vault 两步工作流程的第二部分:

  1. 使用AppRole Get wrapped SecretID步骤模板获取包装的 SecretID。
  2. 提供从第一步到此步骤模板的敏感输出变量中存储的包装的 SecretID,以进行解包装和身份验证。

经过认证后,来自保险库响应的client_token就可以作为一个名为AppRoleAuthToken敏感输出变量用于其他步骤。

AppRole 展开 SecretID 和登录参数

步骤模板使用以下参数:

  • Vault Server URL:您正在连接的 Vault 实例的 URL,包括端口(默认为8200)。
  • API version:从下拉列表中选择要使用的 API 版本。目前只有一个选项:v1
  • Namespace : 可选要使用的命名空间。可以提供嵌套的名称空间,例如ns1/ns2注意:名称空间仅在 Vault Enterprise 上受支持。
  • App Role Path:安装 AppRole auth 方法的路径
  • Role ID:批准的角色 ID
  • Wrapped Token:用于从金库中检索实际秘密 ID 的包装令牌
  • Token Creation Path : 可选包装令牌的创建路径。如果提供了该值,步骤模板将执行回绕查找验证没有发生不当行为

Parameters for the Vault Unwrap SecretID and Login step

使用展开 SecretID 和登录步骤

解开 SecretID 并登录步骤以与其他步骤相同的方式添加到部署和 runbook 流程中。

将步骤添加到流程后,填写步骤中的参数:

Vault Unwrap SecretID and Login step used in a process

然后,您可以在操作手册或部署流程中执行该步骤。成功执行后,敏感的输出变量名会显示在任务日志中:

Vault Unwrap SecretID and Login step task log

在后续步骤中,输出变量#{Octopus.Action[HashiCorp Vault - AppRole Unwrap Secret ID and Login].Output.AppRoleAuthToken}可用于认证和检索秘密。

提示:对于任何输出变量名,记得用您的步骤名替换HashiCorp Vault - AppRole Unwrap Secret ID and Login

找回秘密

使用 Vault 进行身份验证后,您会收到一个可用于检索机密的身份验证令牌。Vault 中的机密存储在一个机密引擎中,该引擎有许多不同的类型。

为支持检索机密而创建的步骤模板关注于键值(kv) 机密引擎,因为它是一个通用的键值存储,用于存储任意机密:

检索 KV (v1)机密步骤

HashiCorp 保险库密钥值(v1)检索机密步骤模板检索存储在v1密钥值机密引擎中的一个或多个机密。

检索一个秘密需要:

  • 通往秘密的道路。
  • 有权限访问机密的身份验证令牌。
  • 可选的,要检索的字段名列表。

step 模板的一个高级特性支持一次检索多个机密。这需要将机密检索方法参数更改为Multiple vault keys

递归检索秘密也是可能的。当您想要检索给定路径的所有秘密时,这很有用。

对于检索到的每个秘密,创建一个敏感输出变量用于后续步骤。默认情况下,任务日志中只会显示已创建变量的数量。要查看任务日志中的变量名,将打印输出变量名参数更改为True

检索 KV (v1)秘密参数

步骤模板使用以下参数:

  • Vault Server URL:您正在连接的 Vault 实例的 URL,包括端口(默认为8200)。
  • API version:从下拉列表中选择要使用的 API 版本。目前只有一个选项:v1
  • Namespace : 可选要使用的命名空间。可以提供嵌套的名称空间,例如ns1/ns2注意:名称空间仅在 Vault Enterprise 上受支持。
  • Auth Token:用于认证检索秘密的令牌
  • Secrets Path:您要检索的机密的完整路径。该值应该包括安装机密引擎的路径以及机密本身的路径。
  • Secrets retrieval method:选择检索单个秘密或多个秘密。检索单个秘密相当于使用 Get 方法的vault kv get命令。检索多个秘密相当于组合使用列表方法的vault kv list命令和针对每个秘密的后续vault kv get命令。
  • Recursive retrieval:如果正在检索多个秘密,是否也应该枚举和检索任何子文件夹?默认为:False
  • Field names:从已识别的机密中选择要检索的特定字段。当您只想从一个或多个机密中检索特定字段时,这很有用。您可以选择包含输出变量的名称。
  • Print output variable names:将 Octopus 输出变量名写入任务日志。默认为:False

Parameters for the retrieve KV v1 secrets step

使用检索 KV (v1)机密步骤

以与其他步骤相同的方式将键值(v1)检索机密步骤添加到部署和 runbook 流程中。

将步骤添加到流程后,填写步骤中的参数:

Vault retrieve KV v1 secrets step used in a process

填写参数后,您可以在操作手册或部署流程中执行该步骤。在成功执行时,任何匹配的机密都被存储为敏感的输出变量。如果将步骤配置为打印变量名,它们会出现在任务日志中:

Vault retrieve KV v1 secrets step task log

在后续步骤中,从匹配密码创建的输出变量可以在您的部署或 runbook 中使用。

提示:对于任何输出变量名,记得用您的步骤名替换HashiCorp Vault - Key Value (v1) retrieve secrets

检索 KV (v2)机密步骤

hashi corp Vault-Key Value(v2)retrieve secrets步骤模板检索存储在v2 Key-Value secrets 引擎中的一个或多个机密。

v2键值机密引擎的关键优势之一是它支持版本化机密。如果您需要在意外丢失数据的情况下回滚机密,这将非常有用。

检索一个秘密需要:

  • 通往秘密的道路
  • 具有访问机密的权限的身份验证令牌
  • 可选的,要检索的字段名列表

此步骤模板提供了高级功能:

  1. 支持一次检索多个秘密。这需要将机密检索方法参数改为Multiple vault keys。递归检索秘密也是可能的。这对于检索给定路径的所有秘密非常有用。
  2. 支持检索特定版本的秘密。这仅在检索单个机密时受支持。

对于检索到的每个秘密,创建一个敏感输出变量用于后续步骤。默认情况下,任务日志中只会显示已创建变量的数量。要查看任务日志中的变量名,将打印输出变量名参数更改为True

检索 KV (v2)秘密参数

步骤模板使用以下参数:

  • Vault Server URL:您正在连接的 Vault 实例的 URL,包括端口(默认为8200)。
  • API version:从下拉列表中选择要使用的 API 版本。目前只有一个选择:v1
  • Namespace : 可选要使用的名称空间。可以提供嵌套的名称空间,例如ns1/ns2注意:名称空间仅在 Vault Enterprise 上受支持。
  • Auth Token:用于认证取回机密的令牌
  • Secrets Path:您想要检索的机密的完整路径。该值应该包括安装机密引擎的路径以及机密本身的路径。
  • Secrets retrieval method:选择检索单个秘密或多个秘密。检索一个秘密相当于使用 Get 方法的vault kv get命令。检索多个秘密相当于使用 List 方法的vault kv list命令和随后针对每个秘密的vault kv get命令的组合。
  • Recursive retrieval:如果正在检索多个机密,是否也应该枚举和检索任何子文件夹?默认为:False
  • Secret Version : 可选检索单个秘密时,选择要检索的秘密版本。例如,如果您希望所有字段值的版本 2 处于保密状态,请输入值2
  • Field names:选择要从已识别的机密中检索的特定字段。当您只想从一个或多个机密中检索特定字段时,这很有用。您可以选择包含输出变量的名称。
  • Print output variable names:将 Octopus 输出变量名写入任务日志。默认为:False

T32

使用检索 KV (v2)机密步骤

键值(v2)检索机密步骤以与其他步骤相同的方式添加到中的部署和 runbook 流程中。

将步骤添加到流程后,填写步骤中的参数:

Vault retrieve KV v2 secrets step used in a process

填写参数后,您可以在操作手册或部署流程中执行该步骤。在成功执行时,任何匹配的机密都被存储为敏感的输出变量。如果将步骤配置为打印变量名,它们会出现在任务日志中:

Vault retrieve KV v2 secrets step task log

在后续步骤中,从匹配密码创建的输出变量可以在您的部署或 runbook 中使用。

提示:对于任何输出变量名,记得用您的步骤名替换HashiCorp Vault - Key Value (v2) retrieve secrets

结论

本文中的模板展示了如何扩展 Octopus 的功能,从 Vault 或任何其他 secrets manager 中检索机密,并在您的部署或 runbooks 中使用它们。请务必在我们的示例实例的秘密管理空间中查看它们。

了解更多信息

有关更多信息,您可以阅读:

愉快的部署!

使用 HTTPd Docker 映像- Octopus 部署

原文:https://octopus.com/blog/using-httpd-docker-image

Apache HTTP 服务器是最流行的 web 服务器之一。据维基百科报道,2022 年 3 月,它要么是使用最多的,要么是第二多的网络服务器。一张官方 HTTPd Docker 图片可以在 Docker Hub 上找到,已经被下载了超过 10 亿次,使其成为最受欢迎的 Docker 图片之一。

在本文中,我将向您展示如何开始使用 HTTPd 映像来托管您自己的网站或构建嵌入 HTTPd 的自定义 Docker 映像。

入门指南

使用 HTTPd 映像最简单的方法是让它托管来自本地工作站的静态 web 内容。将以下 HTML 代码保存到名为index.html的文件中:

<html>
    <body>
        Hello from Octopus!
    </body>
</html> 

然后运行 HTTPd Docker 映像,将index.html文件挂载到容器中的/usr/local/apache2/htdocs/index.html下:

docker run -p 8080:80 -v "$PWD/index.html":/usr/local/apache2/htdocs/index.html httpd:2.4 

然后您可以打开http://localhost:8080/查看网页。

以这种方式安装文件需要将单个 web 资产打包并分发到任何新的服务器,这很不方便。更好的解决方案是构建一个嵌入静态 web 文件的定制 Docker 映像。

基于 HTTPd 创建自定义图像

要创建自己的 Docker 图像,请将以下文本保存到名为Dockerfile的文件中:

FROM httpd:2.4
COPY index.html /usr/local/apache2/htdocs/index.html 

Dockerfile包含构建自定义 Docker 映像的说明。这里您使用FROM命令将您的映像基于 HTTPd 映像,然后使用COPY命令将您的index.html文件复制到/usr/local/apache2/htdocs目录下的新映像中。

使用以下命令构建新映像:

docker build . -t myhttpd 

这构建了一个名为myhttpd的新图像。使用以下命令运行新映像:

docker run -p 8080:80 myhttpd 

注意,这次您没有挂载任何目录。然而,当你打开http://localhost:8080/index.html时,你的定制 HTML 页面会显示出来,因为它嵌入在你的定制图像中。

HTTPd 不仅仅能够托管静态网站。要释放 HTTPd 的全部潜力,您必须添加定制的配置文件。

高级 HTTPd 配置

使用以下命令提取 HTTPd 映像中嵌入的标准配置文件:

docker run --rm httpd:2.4 cat /usr/local/apache2/conf/httpd.conf > my-httpd.conf 

生成的文件很大,有 500 多行代码,所以我不会在这里列出。

要让 HTTPd 加载额外的配置文件,可以在该文件的末尾添加一条指令,指示 HTTPd 加载指定目录中的文件:

IncludeOptional conf/sites/*.conf 

用以下内容创建一个名为health-check.conf的文件。这个配置使 HTTPd 能够监听端口 90,并用 200 OK 响应来响应/health路径上的请求。此响应模拟 web 服务器的运行状况检查,客户端可以使用该检查来确定服务器是否启动并运行:

LoadModule rewrite_module modules/mod_rewrite.so
Listen 90

<VirtualHost *:90>
  <Location /health>
      ErrorDocument 200 "Healthy"
      RewriteEngine On
      RewriteRule .* - [R=200]
  </Location>
</VirtualHost> 

然后更新DockerFile以创建目录/usr/local/apache2/conf/sites/,将health-check.conf文件复制到该目录,并用包含IncludeOptional指令的副本覆盖原始配置文件:

FROM httpd:2.4
RUN mkdir -p /usr/local/apache2/conf/sites/
COPY health-check.conf /usr/local/apache2/conf/sites/health-check.conf
COPY my-httpd.conf /usr/local/apache2/conf/httpd.conf 

使用以下命令构建新映像:

docker build . -t myhttpd 

使用命令运行 Docker 映像。请注意,您公开了一个新端口来提供对运行状况检查端点的访问:

docker run -p 8080:80 -p 9090:90 myhttpd 

然后打开http://localhost:9090/health进入健康检查页面。

选择 HTTPd 变体

HTTPd 图像基于 Debian 或 Alpine。Alpine 经常被用作 Docker 图像的轻量级基础。要查看 Docker 图像的大小,必须首先将它们下载到您的本地工作站:

docker pull httpd:2.4
docker pull httpd:2.4-alpine 

然后,您可以使用以下命令查找图像大小:

docker image ls 

从这里你可以看到 Alpine 的变体在尺寸上要小得多,基于 Debian 的 imaged 重 145MB,Alpine image 重 55MB。

要使用 Alpine 变体,请将您的自定义图像基于httpd:2.4-alpine图像:

FROM httpd:2.4-alpine
RUN mkdir -p /usr/local/apache2/conf/sites/
COPY health-check.conf /usr/local/apache2/conf/sites/health-check.conf
COPY my-httpd.conf /usr/local/apache2/conf/httpd.conf 

结论

HTTPd 是一个流行的 web 服务器,官方的 HTTPd Docker 映像允许 DevOps 团队在 Docker 中托管定制的 web 应用程序。通过对 HTTPd 配置文件进行一些小的调整,还可以使用全系列的 HTTPd 模块,解锁许多高级功能。

在本文中,您了解了如何创建托管静态 web 应用程序的自定义 Docker 映像,添加了高级 HTTPd 配置文件来提供健康检查端点,并比较了 Debian 和 Alpine HTTPd 映像的大小。

您可能还想了解:

资源

了解更多信息

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

愉快的部署!

使用 Jenkins 管道和 Octopus - Octopus 部署

原文:https://octopus.com/blog/using-jenkins-pipelines

Using Jenkins Pipelines with Octopus

Octopus Jenkins 插件最近的更新增加了对管道的支持,允许创建包并将其推送到 Octopus,创建和部署发布,以及将提交消息等元数据与包相关联。

在本文中,我们将介绍创建一个简单的 Jenkins 管道来部署一个示例 Java 应用程序的过程。

安装和配置插件

Octopus 插件可通过 Jenkins 插件管理器获得:

这个插件通过调用 Octopus CLI 来工作。CLI 可以单独安装,但在本例中,我们将让 Jenkins 通过自定义工具插件下载并安装 CLI:

安装 Octopus 和自定义工具插件后,打开 Jenkins 全局工具配置页面,点击添加自定义工具按钮。

给新工具命名为Octopus CLI,并为二进制归档字段的下载 URL 提供 Octopus CLI 下载路径,它将是一个类似于https://Download . Octopus deploy . com/Octopus-tools/7 . 4 . 0/Octopus tools . 7 . 4 . 0 . Linux-x64 . tar . gz的 URL(最新版本请访问下载页面):

当我们在管道中引用一个定制工具时,Jenkins 会将工具的归档内容下载、提取并复制到一个类似于/var/lib/jenkins/tools/com.cloudbees.jenkins.plugins.customtools.CustomTool的目录中(根据 Jenkins 主目录,/var/lib/jenkins可能会因代理而异)。在本例中,Jenkins 将 Octopus CLI 可执行文件提取到/var/lib/jenkins/tools/com.cloudbees.jenkins.plugins.customtools.CustomTool/OctoCLI/octo

事先知道了这条路径,我们就可以在 Jenkins 中定义 Octopus CLI 工具了。在 Octopus Deploy CLI 部分,将默认工具的路径设置为/var/lib/jenkins/tools/com.cloudbees.jenkins.plugins.customtools.CustomTool/OctoCLI/octo。可以忽略该警告,因为当我们引用先前定义的定制工具运行管道时,该路径将是可用的:

要连接到 Octopus 服务器,我们需要创建一个 API 密钥:

然后在 Jenkins 配置系统屏幕中配置 Octopus URL 和 API 密钥:

我们现在已经配置了所需的设置来支持 Octopus 插件。然而,我们将构建的示例 Java 应用程序需要另外两个工具:Maven 和 JDK。这些在 Jenkins 全球工具配置页面中进行配置。

在过去,获得 JDK 最简单的方法是从 Oracle 下载。但是现在,许可已经改变,这意味着大多数开发人员将使用 OpenJDK 构建。许多公司都提供 OpenJDK 版本,对于这个例子,我们将使用由 AdoptOpenJDK 提供的版本。

我们将配置一个名为 Java 的 JDK 工具,并从https://github . com/adopt open JDK/open JDK 14-binaries/releases/download/JDK-14 . 0 . 1% 2b 7/open JDK 14 u-JDK _ x64 _ Linux _ hotspot _ 14 . 0 . 1 _ 7 . tar . gz下载归档文件。解压子目录 jdk-14.0.1+7 (这就是 AdoptOpenJDK 在归档中打包路径的方式):

然后创建了一个名为 Maven 3 的新 Maven 工具,它可以下载最新版本:

有了这些设置,我们就可以创建我们的第一个管道了。

一个示例流水线

Jenkins 有两种类型的管道:脚本式声明式。通常推荐使用声明性管道,这是我们将在示例管道中使用的格式。

我们将构建一个名为 Random Quotes 的示例应用程序,它可以在 GitHub 上找到。管道在名为 Jenkinsfile 的文件中定义。Jenkinsfile 的副本如下所示:

pipeline {
    //  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: 'RandomQuotes', 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)
    }
    /*
        These are the tools we need for this pipeline. They are defined in Manage Jenkins -> Global Tools Configuration.
    */
    tools {
        maven 'Maven 3'
        jdk 'Java'
    }
    agent any
    stages {
        /*
            The OctoCLI tool has been defined with the Custom Tools plugin: https://plugins.jenkins.io/custom-tools-plugin/
            This is a convenient way to have a tool placed on an agent, especially when using the Jenkins Docker image.
            This plugin will extract a .tar.gz file (for example https://download.octopusdeploy.com/octopus-tools/7.3.7/OctopusTools.7.3.7.linux-x64.tar.gz)
            to a directory like /var/jenkins_home/tools/com.cloudbees.jenkins.plugins.customtools.CustomTool/OctoCLI/Octo.
            This directory is then specified as the default location of the Octo CLI in Jenkins under
            Manage Jenkins -> Global Tools Configuration -> Octopus Deploy CLI.
        */
        stage ('Add tools') {
            steps {
                sh "echo \"OctoCLI: ${tool('OctoCLI')}\""
            }
        }
        stage('build') {
            steps {
                // Update the Maven project version to match the current build
                sh(script: "mvn versions:set -DnewVersion=1.0.${BUILD_NUMBER}", returnStdout: true)
                // Package the code
                sh(script: "mvn package", returnStdout: true)
            }
        }
        stage('deploy') {
            steps {                
                octopusPack additionalArgs: '', includePaths: "${env.WORKSPACE}/target/randomquotes.1.0.${BUILD_NUMBER}.jar", outputPath: "${env.WORKSPACE}", overwriteExisting: false, packageFormat: 'zip', packageId: 'randomquotes', packageVersion: "1.0.${BUILD_NUMBER}", sourcePath: '', toolId: 'Default', verboseLogging: false
                octopusPushPackage additionalArgs: '', overwriteMode: 'FailIfExists', packagePaths: "${env.WORKSPACE}/target/randomquotes.1.0.${BUILD_NUMBER}.jar", serverId: "${ServerId}", spaceId: "${SpaceId}", toolId: 'Default'
                /*
                    Note that the gitUrl param is passed manually from the environment variable populated when this Jenkinsfile is downloaded from Git.
                    This is from the Jenkins "Global Variable Reference" documentation:
                    SCM-specific variables such as GIT_COMMIT are not automatically defined as environment variables; rather you can use the return value of the checkout step.
                    This means if this pipeline checks out its own code, the checkout method is used to return the details of the commit. For example:
                    stage('Checkout') {
                        steps {
                            script {
                                def checkoutVars = checkout([$class: 'GitSCM', userRemoteConfigs: [[url: 'https://github.com/OctopusSamples/RandomQuotes-Java.git']]])
                                env.GIT_URL = checkoutVars.GIT_URL
                                env.GIT_COMMIT = checkoutVars.GIT_COMMIT
                            }
                            octopusPushBuildInformation additionalArgs: '', commentParser: 'GitHub', overwriteMode: 'FailIfExists', packageId: 'randomquotes', packageVersion: "1.0.${BUILD_NUMBER}", serverId: "${ServerId}", spaceId: "${SpaceId}", toolId: 'Default', verboseLogging: false, gitUrl: "${GIT_URL}", gitCommit: "${GIT_COMMIT}"
                        }
                    }
                */
                octopusPushBuildInformation additionalArgs: '', commentParser: 'GitHub', overwriteMode: 'FailIfExists', packageId: 'randomquotes', packageVersion: "1.0.${BUILD_NUMBER}", serverId: "${ServerId}", spaceId: "${SpaceId}", toolId: 'Default', verboseLogging: false, gitUrl: "${GIT_URL}", gitCommit: "${GIT_COMMIT}"
                octopusCreateRelease additionalArgs: '', cancelOnTimeout: false, channel: '', defaultPackageVersion: '', deployThisRelease: false, deploymentTimeout: '', environment: "${EnvironmentName}", jenkinsUrlLinkback: false, project: "${ProjectName}", releaseNotes: false, releaseNotesFile: '', releaseVersion: "1.0.${BUILD_NUMBER}", serverId: "${ServerId}", spaceId: "${SpaceId}", tenant: '', tenantTag: '', toolId: 'Default', verboseLogging: false, waitForDeployment: false
                octopusDeployRelease cancelOnTimeout: false, deploymentTimeout: '', environment: "${EnvironmentName}", project: "${ProjectName}", releaseVersion: "1.0.${BUILD_NUMBER}", serverId: "${ServerId}", spaceId: "${SpaceId}", tenant: '', tenantTag: '', toolId: 'Default', variables: '', verboseLogging: false, waitForDeployment: true
            }
        }
    }
} 

让我们把这个文件分解一下。

所有声明性管道都以pipeline开头:

pipeline { 

为了使我们的 Jenkinsfile 通用,我们将所有 Octopus 特定变量作为参数公开。Jenkins 第一次执行这个管道时,将使用默认值。然后运行管道将这些属性添加到 Jenkins 项目中,这意味着当第二次手动触发构建时,将通过 web UI 提示您输入这些属性:

 //  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: 'RandomQuotes', 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)
    } 

我们在前面部分配置的 Maven 和 Java 工具通过tools部分包含在这个管道中:

 tools {
        maven 'Maven 3'
        jdk 'Java'
    } 

这个构建将在任何代理上运行,这用agent设置来表示:

 agent any 

然后,我们定义管道将经过的stages:

 stages { 

第一阶段回显名为 OctoCLI 的自定义工具的位置。有趣的是,自定义工具插件并没有在tools部分定义。本期詹金斯刊有关于这一限制的细节。但是通过调用tool('OctoCLI'),定制工具作为管道的一部分被安装:

 stage ('Add tools') {
            steps {
                sh "echo \"OctoCLI: ${tool('OctoCLI')}\""
            }
        } 

构建阶段调用 Maven CLI(由于前面提到的 Maven 工具,它现在位于PATH上)来设置项目的版本并打包它:

 stage('build') {
            steps {
                // Update the Maven project version to match the current build
                sh(script: "mvn versions:set -DnewVersion=1.0.${BUILD_NUMBER}", returnStdout: true)
                // Package the code
                sh(script: "mvn package", returnStdout: true)
            }
        } 

最后一个阶段是用 Octopus 部署软件包:

 stage('deploy') {
            steps { 

我们首先将 JAR 文件打包成一个 ZIP 文件。我们不会使用生成的 ZIP 文件,因为 Java 部署通常直接使用 JAR 或 WAR 文件,将这些文件嵌套在第二个 ZIP 存档中是多余的。但是此处包含的步骤是作为使用octopusPack步骤的示例:

 octopusPack additionalArgs: '', includePaths: "${env.WORKSPACE}/target/randomquotes.1.0.${BUILD_NUMBER}.jar", outputPath: "${env.WORKSPACE}", overwriteExisting: false, packageFormat: 'zip', packageId: 'randomquotes', packageVersion: "1.0.${BUILD_NUMBER}", sourcePath: '', toolId: 'Default', verboseLogging: false 

octopusPushPackage步骤将 JAR 文件推送到 Octopus 内置提要。请注意我们是如何引用先前定义为${ServerId}${SpaceId}的参数的:

 octopusPushPackage additionalArgs: '', overwriteMode: 'FailIfExists', packagePaths: "${env.WORKSPACE}/target/randomquotes.1.0.${BUILD_NUMBER}.jar", serverId: "${ServerId}", spaceId: "${SpaceId}", toolId: 'Default' 

除了应用程序包,我们还将使用octopusPushBuildInformation步骤推送一个元数据包,其中包括 GIT 提交消息和链接。稍后我们将在 Octopus 中看到这些信息:

 octopusPushBuildInformation additionalArgs: '', commentParser: 'GitHub', overwriteMode: 'FailIfExists', packageId: 'randomquotes', packageVersion: "1.0.${BUILD_NUMBER}", serverId: "${ServerId}", spaceId: "${SpaceId}", toolId: 'Default', verboseLogging: false, gitUrl: "${GIT_URL}", gitCommit: "${GIT_COMMIT}" 

这里需要注意的一点是,GIT_URLGIT_COMMIT环境变量只有在 Jenkins 从 GIT 中提取包含 Jenkinsfile 的项目时才可用。如果管道直接进入 Jenkins 项目,您负责使用checkout步骤从 GIT 中检查代码,并根据返回对象中的属性创建GIT_URLGIT_COMMIT环境变量:

 script {
                    def checkoutVars = checkout([$class: 'GitSCM', userRemoteConfigs: [[url: 'https://github.com/OctopusSamples/RandomQuotes-Java.git']]])
                    env.GIT_URL = checkoutVars.GIT_URL
                    env.GIT_COMMIT = checkoutVars.GIT_COMMIT
                } 

通过octopusCreateRelease步骤在 Octopus 中创建一个发布。我们再次引用了类似${EnvironmentName}${ProjectName}${ServerId}${SpaceId}的参数:

 octopusCreateRelease additionalArgs: '', cancelOnTimeout: false, channel: '', defaultPackageVersion: '', deployThisRelease: true, deploymentTimeout: '', environment: "${EnvironmentName}", jenkinsUrlLinkback: false, project: "${ProjectName}", releaseNotes: false, releaseNotesFile: '', releaseVersion: "1.0.${BUILD_NUMBER}", serverId: "${ServerId}", spaceId: "${SpaceId}", tenant: '', tenantTag: '', toolId: 'Default', verboseLogging: false, waitForDeployment: false 

最后,我们用octopusDeployRelease步骤部署发布:

 octopusDeployRelease cancelOnTimeout: false, deploymentTimeout: '', environment: "${EnvironmentName}", project: "${ProjectName}", releaseVersion: "1.0.${BUILD_NUMBER}", serverId: "${ServerId}", spaceId: "${SpaceId}", tenant: '', tenantTag: '', toolId: 'Default', variables: '', verboseLogging: false, waitForDeployment: true
            }
        }
    }
} 

创建詹金斯项目

在 Jenkins 中,构建来源于 GIT 存储库的管道非常容易。新建一个管道项目,从 SCM 中选择管道脚本选项,将库 URL** 设置为https://github.com/OctopusSamples/RandomQuotes-Java.git😗*

T32

然后,您可以触发构建。注意第一次运行构建时,不会提示您输入任何参数。参数通过管道添加到项目中,因此管道需要先运行一次。第一次运行后,您可以选择使用参数构建,并显示管道参数提示:

查看构建信息

上面的管道推了两个包给章鱼。第一个是 Maven 创建的 JAR 文件。第二个是通过调用octopusPushBuildInformation推送的,创建并推送一个构建信息包。这个包包含关于包的元数据,包括返回到 Jenkins 构建和 GIT 提交信息的链接。

如果我们看一下被推送到内置提要的 JAR 包,我们会看到它有一个构建信息部分,指出这个包是由 Jenkins 构建的,还有一个链接指向 Jenkins 构建:

但是请注意,没有关于 GIT 提交的信息。您将只能看到关于两次构建之间提交的信息,因为在 Jenkins 构建之间 GIT 存储库中没有发生任何变化,所以没有提交被记录。为了演示与元数据打包在一起的提交历史,我们将提交并在 Jenkins 中重建项目。现在,构建信息包含了 Jenkins 构建之间提交的链接:

你可以在我们的文档中找到更多关于构建信息的信息。

蓝色海洋中的管道

蓝色海洋是詹金斯建造体验的再现。它作为插件安装:

蓝海界面通过直观的交互式用户界面可视化管道。虽然旧的自由式詹金斯项目可以在蓝海观看,但体验已针对管道进行了大量优化:

结论

Jenkins 管道是创建和共享可重复应用构建的强大工具。它们本身是有用的,当与像 Blue Ocean 这样的 Jenkins 插件结合时,提供了现代构建体验的基础。有了 Octopus Jenkins 插件中的新功能,部署可以通过 Jenkins 管道进行管理,步骤组合与自由式项目中相同。

如果你正在寻找一种快速测试 Jenkins 和 Octopus 的方法,可以看看项目 TestDrive ,该项目通过预装 Jenkins 和 Octopus 的 vagger 提供了多个虚拟机。

使用 NGINX Docker 映像- Octopus 部署

原文:https://octopus.com/blog/using-nginx-docker-image

Docker 是一个打包和运行 web 应用程序的引人注目的平台,尤其是当它与云平台提供的众多平台即服务(PaaS)产品之一结合使用时。NGINX 长期以来为 DevOps 团队提供了在 Linux 上托管 web 应用程序的能力,并且还提供了一个官方的 Docker 映像作为定制 web 应用程序的基础。

在这篇文章中,我解释了 DevOps 团队如何使用 NGINX Docker 映像在 Docker 上构建和运行 web 应用程序。

基础映像入门

NGINX 是一个多用途的工具,包括负载平衡器、反向代理和网络缓存。然而,当在 Docker 容器中运行 NGINX 时,这些高级功能中的大部分被委托给其他专门的平台或 NGINX 的其他实例。通常,当运行在 Docker 容器中时,NGINX 实现了 web 服务器的功能。

要使用默认网站创建 NGINX 容器,请运行以下命令:

docker run -p 8080:80 nginx 

该命令将下载nginx映像(如果尚未下载)并创建一个容器,将容器中的端口 80 暴露给主机上的端口 8080。然后可以打开http://localhost:8080/index.html查看默认的“欢迎使用 nginx!”网站。

为了允许 NGINX 容器公开定制的 web 资产,可以在 Docker 容器中挂载一个本地目录。

将以下 HTML 代码保存到名为index.html的文件中:

<html>
    <body>
        Hello from Octopus!
    </body>
</html> 

接下来,运行下面的命令,在 NGINX 容器中的/usr/share/nginx/html下挂载当前目录,并进行只读访问:

docker run -v $(pwd):/usr/share/nginx/html:ro -p 8080:80 nginx 

再次打开http://localhost:8080/index.html,您会看到显示的自定义 HTML 页面。

Docker 映像的一个好处是能够将所有相关文件捆绑到一个可分发的工件中。要实现这一优势,您必须基于 NGINX 映像创建一个新的 Docker 映像。

基于 NGINX 创建自定义图像

要创建自己的 Docker 图像,请将以下文本保存到名为Dockerfile的文件中:

FROM nginx
COPY index.html /usr/share/nginx/html/index.html 

Dockerfile包含构建自定义 Docker 映像的说明。在这里,您使用FROM命令将您的映像基于 NGINX one,然后使用COPY命令将您的index.html文件复制到/usr/share/nginx/html目录下的新映像中。

使用以下命令构建新映像:

docker build . -t mynginx 

这构建了一个名为mynginx的新图像。使用以下命令运行新映像:

docker run -p 8080:80 mynginx 

注意,这次您没有挂载任何目录。然而,当你打开http://localhost:8080/index.html时,你的定制 HTML 页面会显示出来,因为它嵌入在你的定制图像中。

NGINX 不仅仅能够托管静态文件。要解锁此功能,您必须使用自定义 NGINX 配置文件。

高级 NGINX 配置

NGINX 通过配置文件公开其功能。默认的 NGINX 映像附带了一个简单的默认配置文件,用于托管静态 web 内容。该文件位于默认图像中的/etc/nginx/nginx.conf处,其内容如下:

user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
} 

不需要详细理解这个配置文件,但是有一行有趣的内容指示 NGINX 从/etc/nginx/conf.d目录加载额外的配置文件:

include /etc/nginx/conf.d/*.conf; 

默认的/etc/nginx/conf.d文件将 NGINX 配置为 web 服务器。具体来说,location /阻止从/usr/share/nginx/html加载文件是你之前将 HTML 文件挂载到那个目录的原因:

server {
    listen       80;
    server_name  localhost;

    #access_log  /var/log/nginx/host.access.log  main;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    # proxy the PHP scripts to Apache listening on 127.0.0.1:80
    #
    #location ~ \.php$ {
    #    proxy_pass   http://127.0.0.1;
    #}

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    #location ~ \.php$ {
    #    root           html;
    #    fastcgi_pass   127.0.0.1:9000;
    #    fastcgi_index  index.php;
    #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
    #    include        fastcgi_params;
    #}

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #    deny  all;
    #}
} 

您可以利用该指令来加载/etc/nginx中的任何*.conf配置文件,以定制 NGINX。在本例中,您添加了一个健康检查,它通过一个自定义位置监听端口 90,用 HTTP 200 OK 响应对/nginx-health路径的请求。

将以下文本保存到名为health-check.conf的文件中:

server {
    listen       90;
    server_name  localhost;

    location /nginx-health {
        return 200 "healthy\n";
        add_header Content-Type text/plain;
    }
} 

修改Dockerfile将配置文件复制到/etc/nginx/conf.d:

FROM nginx
COPY index.html /usr/share/nginx/html/index.html
COPY health-check.conf /etc/nginx/conf.d/health-check.conf 

使用以下命令构建映像:

docker build . -t mynginx 

使用命令运行新映像。请注意 9090 上显示的新端口:

docker run -p 8080:80 -p 9090:90 mynginx 

现在打开http://localhost:9090/nginx-health。返回运行状况检查响应,表明 web 服务器已启动并正在运行。

以上示例基于默认的nginx图像定制您的图像。但是也有其他的变体,在不牺牲任何功能的情况下提供更小的图像尺寸。

选择 NGINX 变体

默认的nginx图像基于 Debian 。不过 NGINX 也提供基于阿尔卑斯的图片。

Alpine 经常被用作 Docker 图像的轻量级基础。要查看 Docker 图像的大小,必须首先将它们下载到您的本地工作站:

docker pull nginx
docker pull nginx:stable-alpine 

然后,您可以使用以下命令查找图像大小:

docker image ls 

从这里你可以看到 Debian 图像重约 140 MB,而 Alpine 图像重约 24 MB。这大大节省了图像尺寸。

为了让您的图像基于 Alpine 变体,您需要更新Dockerfile:

FROM nginx:stable-alpine
COPY index.html /usr/share/nginx/html/index.html
COPY health-check.conf /etc/nginx/conf.d/health-check.conf 

使用以下命令构建并运行映像:

docker build . -t mynginx
docker run -p 8080:80 -p 9090:90 mynginx 

再次打开http://localhost:9090/nginx-healthhttp://localhost:8080/index.html查看网页。一切都像以前一样继续工作,但是您的自定义映像现在要小得多。

结论

NGINX 是一个强大的 web 服务器,官方的 NGINX Docker 映像允许 DevOps 团队在 Docker 中托管定制的 web 应用程序。NGINX 还支持高级场景,因为它能够读取复制到自定义 Docker 映像中的配置文件。

在本文中,您了解了如何创建托管静态 web 应用程序的自定义 Docker 映像,添加了高级 NGINX 配置文件来提供健康检查端点,并比较了 Debian 和 Alpine NGINX 映像的大小。

了解如何使用其他流行的容器图像:

资源

了解更多信息

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

愉快的部署!

通过 TFS 团队构建和 Octopus Deploy - Octopus Deploy 实现自动化部署

原文:https://octopus.com/blog/using-octopus-and-tfs-builds

Article date 2012-07-16 Octopus Version 1.X supported for this article

Damian Karzon 有一个很好的演示,展示了如何使用来自 TFS 团队构建的 Octopus Deploy:

我最近一直在玩 Octopus Deploy,我已经设置了我们的 TFS 构建服务器来自动创建版本并部署到开发环境中。下面是如何设置 TFS 构建服务器来创建 NuGet 包,以便通过 Octopus 进行部署。

Dave Welling 也有几篇文章扩展了 TFS 的工作流程来发布一个包并将其部署到 Octopus :

为了让其他人不那么心痛,我会在下一篇博文中发布一些我自己的有用花絮。我创建了一个团队构建代码活动来启动 Octopus 版本的创建和部署。我已经创建了另一个,它将监控部署并把结果合并到您的构建中。

最后,Racingcow 有一个帖子分享了他的经历让 OctoPack 和 TFS 一起玩:

总的来说,章鱼看起来真的很有前途。虽然与 TFS 融合本可以更容易,但我不确定这是八达通本身的错。也许一点额外的关于如何与 TFS 集成的文档(对于 NuGet dummies)会加速我解决我遇到的一些问题。然而,除此之外,Octopus 绝对是我向主要使用微软技术的商店推荐的东西。在我看来,它比使用 TFS 构建和 PowerShell 脚本更易于管理,而且肯定比手动部署要好得多。

我喜欢这些帖子的一点是,每个帖子都展示了不同的整合方式;Damian 使用定制的 MSBuild 目标文件,Dave 使用修改过的工作流(听起来很吓人!)而 Racingcow 依赖于对 OctoPack 的一点修改(我将把它合并到源代码中)。如果你正在考虑章鱼和 TFS,这些帖子绝对值得一读!

在 Octopus Deploy - Octopus Deploy 中使用带有 Terraform 的远程后端

原文:https://octopus.com/blog/using-remote-backend-with-terraform

Terraform 允许你用代码定义基础设施。在这篇文章中,我演示了如何使用 GitHub 库作为 Terraform 模板的源,并确保它们是受版本控制的。我还解释了如何在 Terraform 自己的云产品中存储工作区状态。

设置地形版本

在本地和通过 Octopus 从同一工作空间运行 Terraform 命令时要小心,因为不同版本的 Terraform 具有不兼容的状态格式。

如果您使用 Terraform Cloud,您可以通过 设置➜常规 指定您想要使用的 Terraform 的确切版本。

您可以使用 tfenv 工具来管理多个版本的 Terraform。

您还可以通过指定required_version来限制可用于工作区的 CLI 版本。

terraform {
  required_version = "= 0.13.5"
} 

详见本期 GitHub

在 Terraform Cloud 中创建新的工作空间

首先,您需要在 app.terraform.io 创建一个工作空间,作为您项目的后端。

如果您还没有帐户,请创建一个帐户。

在工作区向导中:

Terraform Version Selector

创建外部源

接下来,您需要配置 Octopus Deploy 来使用您的 GitHub 存储库作为包提要

Octopus GitHub Feed

GitHub 知识库

在这篇文章中,我创建了一个样本库,你可以很容易地为你的项目创建这个样本库。

存储库中有四个文件。您可以用不同的方式构建这些文件;但是要确保您的项目和后端变量是作为单独的文件保存的。

记住,提要中只有带标签的提交才可用,所以要向您的存储库添加一个标签。

1。****main . TF——在这里指定全局 Terraform 选项,比如后端选项和想要使用的工作空间。

terraform {
  backend "remote" {
    organization = "octopus-deploy"
    workspaces {
      name = "egorp-test-workspace"
    }
  }
}

variable "aws_access_key" {
  type = string
}

variable "aws_secret_key" {
  type = string
}

provider "aws" {
  region     = "us-east-1"
  access_key = var.aws_access_key
  secret_key = var.aws_secret_key
} 

2。****buckets . TF——对于这个例子,创建一个空的 S3 bucket。

variable "bucket_name" {
  description = "the bucket name to use"
}

resource "aws_s3_bucket" "mybucket" {
  bucket = var.bucket_name
  force_destroy = true
  acl    = "private"
  tags = {
      Name        = var.bucket_name
      Environment = "Dev"
  }
  cors_rule {
    allowed_headers = ["*"]
    allowed_methods = ["GET","PUT","HEAD","DELETE","POST"]
    allowed_origins = ["*"]
    max_age_seconds = 3000
  }
} 

3。****terra form . auto . TF vars-在这里存储其他文件中使用的变量。Octopus 在使用文件之前会执行变量替换

注意,这个文件必须有扩展名.auto.tfvars,否则远程后端不会使用它。

bucket_name = "#{BucketName}"
aws_access_key = "#{AWS Account.AccessKey}"
aws_secret_key = "#{AWS Account.SecretKey}" 

4。 backend.tfvars -该文件被提供给terraform init命令的-backend-config参数。这允许您安全地存储敏感变量,而不是将这些值直接存储在存储库中。

token = "#{TerrraformCloudRemoteToken}" 

您的工作区现在应该如下所示:

Terraform Folder Structure

接下来,配置应用 Terraform 模板步骤,以使用我们的 GitHub 存储库并执行必要的变量替换,为您提供远程令牌和存储桶名称。

工作空间现在存储在 Terraform Cloud 中,因此您可以通过手动调用 Terraform CLI 来使用它,并将其作为 Octopus Deploy 中部署流程的一部分。

Terraform 将跟踪工作区状态,并确保您的本地状态与真实的基础设施相匹配。

添加 AWS 帐户

导航至 基础设施➜账户 ,添加您的 AWS 账户密钥和密码。

Octopus Infrastructure Account

添加变量

现在导航到左边的变量,添加您在.tfvars文件中定义的两个变量:

  • AWS 账户:选择字段,将类型更改为 AWS 账户,然后选择您已经添加到基础设施选项卡的账户。
  • BucketName :您的 S3 桶的期望名称(如果您点击打开编辑器并勾选提示输入值,该值将必须在每次运行 runbook 时手动输入)。
  • TerrraformCloudRemoteToken:将类型更改为 Sensitive ,并为您的 Terraform 云工作空间粘贴机密令牌。

Octopus Project Variables

创建操作手册

基础设施活动通常被定义为运行手册。这篇文章假设你熟悉在 Octopus 中创建项目,所以我将跳过这一部分。

  1. 为您的项目创建一个操作手册。我把我的叫做Terraform Apply
  2. 在您的流程中添加一个应用 Terraform 模板步骤。

这个步骤模板很大,所以我将介绍最少的组件来完成它。

托管账户

  • 启用 AWS 账户整合:选择
  • 使用 AWS 服务角色为 EC2 实例执行:选择
  • 选择账户变量:选择您在上一节创建的账户变量。
  • 区域:输入默认使用的 AWS 区域,在我的例子中是us-east-1

T50

模板部分

选择包中的文件作为模板源,填写如下:

  • 包提要:选择您在第一步中创建的 GitHub 提要。
  • 包 ID :输入你的仓库地址,在我这里是OctopusDeploy/TerraformBackendSample

Octopus Terraform Template Options

其余的可以保留默认值。

高级选项

展开地形选项并指定以下内容:

  • 自定义地形初始化参数 : -backend-config=backend.tfvars

Octopus Custom Terraform Init Parameters

点击运行在您选择的环境中运行操作手册。

Successful Runbook Run

您可以在 Terraform 步骤的日志中找到 Terraform 云运行的 URL。

Terraform Cloud URL

现在,您已经使用共享的远程后端完成了部署。您可以在同一个工作区进行协作,并允许 Terraform 自动管理基础设施的状态。

结论

在这篇文章中,我演示了如何在 Octopus Deploy 中为您的 Terraform 工作区使用 Terraform 云后端。

愉快的部署!

在 Kubernetes - Octopus Deploy 中使用模板文件

原文:https://octopus.com/blog/using-template-files-in-kubernetes

Using template files in Kubernetes

如果您以前在 Octopus 中部署过应用程序,那么您可能已经在文件中使用了变量替换,作为一种在部署期间获取通用应用程序包并注入特定于环境的配置的方式。这个过程很方便,因为您可以生成单个应用程序工件,然后每个环境负责对其进行定制,以匹配本地基础设施。

替换应用程序包(如 ZIPs 或 NUPKGs)中的文件是非常简单的,因为这些文件是标准的归档文件,在从工件存储库下载之后、部署到最终目标之前可以很容易地修改。

Docker 图像文件不太容易处理。首先,在构建层的方式上有一些魔法,以确保新的和删除的文件都受到尊重,这意味着解包 Docker 图像文件不像解包各个层那么简单。第二,Kubernetes 希望直接从存储库中下载图像,这样就可以将 Octopus 从分发定制工件的管道中移除。

好消息是,我们可以利用社区创建的一些开源工具来下载和解包 Docker 图像,然后使用 Kubernetes 中的本机功能将单个文件挂载到 Pods 中,以实现与部署修改后的包几乎相同的最终结果。

示例应用程序

为了演示模板文件处理,我们有一个非常简单的基于 HTTPD 的 Docker 图像,它将显示一个带有当前环境名称的 HTML 页面。这张图片的代码可以在 GitHub 上找到,并已作为图片mcasperson/docker file replacement发布。

Docker 应用程序显示的 HTML 文件如下所示。一旦使用 Octopus 完成部署,字符串#{Octopus.Environment.Name}将被替换为环境的名称:

<html>
<body>
<h1>#{Octopus.Environment.Name}</h1>
</body>
</html> 

为了查看这个 Docker 映像的运行情况,我们将使用以下 Docker run 命令在本地运行 Docker 映像:

docker run -p 8888:80 mcasperson/dockerfilereplacement:0.0.1 

如您所料,在本地运行这个 Docker 映像会以未处理的形式显示 web 页面。

当由 Docker 直接运行时,web 服务器暴露原始模板文件。

处理 Docker 图像,不使用 Docker

当我们将部署转移到 Octopus 时,第一步是下载并解压缩 Docker 映像。通常,与 Docker 映像和存储库的交互是通过docker CLI 工具完成的。尽管总是运行 Docker 守护进程并不十分有效,但是已经开发了额外的第三方工具来处理 Docker 守护进程之外的 Docker 映像。

第一个工具叫做 skopeo 。我们将使用skopeo下载一个 Docker 映像,并将其作为一个独立的文件保存在本地磁盘上。

第二个工具叫做 umoci 。我们将使用umoci来解包skopeo下载的文件,允许我们访问由 Docker 映像中所有单独层创建的最终目录结构。

虽然这两个工具都是开源的,但是获得二进制版本可能是一个挑战。在这个练习中,我使用 SUSE Linux VM(即 SUSE Linux Enterprise Server)作为一个 Octopus worker 。SUSE 创建了umoci,并从 GitHub 发布页面提供二进制下载,而标准的 SUSE 包存储库包含一个skopeo版本,这意味着我们不必经历尝试自己构建这些工具的痛苦。

下载并提取 Docker 图像

让我们看看由 Octopus Run a script步骤运行的 bash 脚本,它将从 Docker 映像下载、提取和保存文件内容:

read_file () {
  CONTENTS=""
  while read -r line || [ -n "$line" ]; do
    CONTENTS="${CONTENTS}${line}";
  done < ${1}
  printf -v "${2}" '%s' "${CONTENTS}"
}

skopeo copy docker://mcasperson/dockerfilereplacement:0.0.1 oci:image:latest
umoci unpack --image image --rootless bundle

cd bundle/rootfs/usr/local/apache2/htdocs
read_file template.html TemplateHtml
echo -e $TemplateHtml

set_octopusvariable "TemplateHtml" ${TemplateHtml} 

我们从一个 bash 函数开始,它逐行读取文件的内容,作为第一个参数提供。然后将结果字符串保存回一个全局变量中,该变量的名称作为第二个参数传入(因为 bash 函数只能返回整数退出代码),使用printf:

read_file () {
  CONTENTS=""
  while read -r line || [ -n "$line" ]; do
    CONTENTS="${CONTENTS}${line}";
  done < ${1}
  printf -v "${2}" '%s' "${CONTENTS}"
} 

Docker 映像由skopeo下载,并保存在名为image的开放容器倡议(OCI)包中:

skopeo copy docker://mcasperson/dockerfilereplacement:0.0.1 oci:image:latest 

然后用umoci解压这个文件:

umoci unpack --image image --rootless bundle 

此时,我们已经在本地提取了构成 Docker 映像的文件。我们感兴趣的模板文件是/usr/local/apache/htdocs/template.html。使用我们之前创建的 bash 函数,这个文件的内容被读入一个名为TemplateHtml的变量。我们还使用echo将该变量的内容转储到屏幕上,以确认我们得到了预期的内容:

cd bundle/rootfs/usr/local/apache2/htdocs
read_file template.html TemplateHtml
echo -e $TemplateHtml 

一旦我们有了文件的内容,我们将它保存为一个输出变量:

set_octopusvariable "TemplateHtml" ${TemplateHtml} 

创建 Kubernetes 配置图

将文件内容保存为 Octopus 变量后,下一步是创建一个 Kubernetes ConfigMap 来保存处理后的值。我们将通过 Octopus 中的Deploy raw Kubernetes YAML步骤来实现这一点。

以下是该步骤将部署以创建配置图的 Kubernetes YAML:

apiVersion: v1
kind: ConfigMap
metadata:
  name: templateconfigmap
data:
  template.html: "#{Octopus.Action[Extract File].Output.TemplateHtml}" 

template.html字段是这个配置图的重要部分。这个键定义了我们要替换的文件名,而"#{Octopus.Action[Extract File].Output.TemplateHtml}"的值将导致我们在上一步中提取的文件内容被处理,然后分配给这个键。重要的是,这意味着变量Octopus.Action[Extract File].Output.TemplateHtml中的任何嵌套变量引用都将被替换。

最终结果是一个 ConfigMap,它保存了template.html文件的原始内容,但是执行了任何变量替换。

安装 Kubernetes 配置图

最后一步是从 Kubernetes ConfigMap 获取值,并将其装载回 Kubernetes Pod,从而替换原始的、未处理的文件。我们将通过 Octopus 中的Deploy Kubernetes containers步骤来实现这一点。

这是通过定义一个引用上一步中创建的配置图的卷来实现的。

库本内特斯卷的概要。

库伯内特卷的细节。

然后将配置图安装到 Pod 中。这里的技巧是将Mount path设置为要被替换的单个文件的完整路径,并将Sub path设置为 ConfigMap 中包含该文件内容的条目。

使用此配置,我们将在 Pod 中装入一个包含配置图中的值的文件,替换原始的通用文件。

一个 Kubernetes 卷挂载,向 Kubernetes 容器添加一个文件。

为了完整起见,这是来自Deploy Kubernetes containers步骤的容器部分。您可以看到,我们正在部署映像mcasperson/dockerfilereplacement,公开端口 80,并将配置图挂载为一个卷。

Kubernetes 容器配置概要。

为了方便起见,这个 Pod 将由负载平衡器服务直接公开。这为我们提供了一个公共 IP 地址,我们可以使用它与 Pod 进行交互。

Pod 由负载平衡器服务公开。

该服务从 Pod 公开端口 80。

处理结果

一旦部署完成,我们将拥有一个公共 IP,可以用来访问 web 服务器。现在,当我们打开template.html页面时,我们得到的是替换了变量的 HTML 模板文件。这意味着我们现在可以在网页正文中看到环境的名称。

在开发环境中,HTTPD 展示了经过处理的 template.html 文件。

如果我们将这个部署推进到下一个环境,我们可以看到新创建的负载平衡器公开了一个将环境名Test放入template.html的 Pod。

相同的 template.html 文件在部署后被推送到测试环境中。

结论

文件变量替换是创建可部署到任何环境的通用包的一种便捷方式。尽管还需要一些额外的步骤,但是同样的工作流也可以应用到 Kubernetes 部署中。

通过利用skopeoumoci下载和提取 Docker 映像,然后在 Kubernetes 中使用 ConfigMaps 作为卷挂载,我们可以实现在部署期间替换模板化文件的效果,而不必发布特定于环境的 Docker 映像。

使用 WildFly CLI - Octopus 部署

原文:https://octopus.com/blog/using-the-wildfly-cli

Using the WildFly CLI

WildFly CLI 是一个强大的管理工具,它提供了交互式控制台和脚本功能。CLI 可用于查询和配置 WildFly 应用服务器的所有方面,在这篇文章中,我将从较高的层次来看如何使用 CLI。

登录

下载 WildFly 11,解压,运行bin/standalone.sh或者bin\standalone.bat。这将以默认配置开始,即将管理接口绑定到端口 9990 上的本地主机。

在另一个控制台中,使用--connect参数运行bin/jboss-cli.shbin\jboss-cli.bat。您将登录到 CLI:

$ ./jboss-cli.sh --connect
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.wildfly.security.manager.GetAccessibleDeclaredFieldAction (jar:file:/Users/matthewcasperson/Downloads/wildfly-11.0.0.Final/modules/system/layers/base/org/wildfly/security/elytron-private/main/wildfly-elytron-1.1.6.Final.jar!/) to field java.security.AccessControlContext.context
WARNING: Please consider reporting this to the maintainers of org.wildfly.security.manager.GetAccessibleDeclaredFieldAction
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
[standalone@localhost:9990 /] 

从上面的输出示例中,我们可以看到发生了三件重要的事情。

首先,Java 9 已经报告了一些非法反射访问警告。这听起来很可怕,但这是因为 Java 9 模块系统引入了一些有意的变化(被称为拼图)。我希望随着时间的推移,这些警告会得到解决,但现在可以忽略它们。

其次,我们已经使用默认协议(remote+http)连接到默认主机(localhost)和端口(9990)。这些可以通过--controller选项手动指定:

./jboss-cli.sh --connect --controller=remote+http://localhost:9990 

CLI 接受许多不同的协议。默认情况下,可以使用remote+httphttp-remoting。如果管理接口受 SSL 保护,则可以使用协议remote+httpshttps-remoting

WildFly 的旧版本默认在9999上公开了一个本地管理端口,这需要协议remoting。WildFly 11 默认不公开本机管理端口。

第三,我们设法在没有提供任何凭证的情况下登录。这得益于一项名为静默认证的功能。

静默认证依赖于对standalone/tmp/authdomain/tmp/auth目录的访问。这个想法是,如果一个用户有权访问这个目录,他们可能有权创建新的用户,所以静默认证给你访问权。

如果您拒绝对auth目录的写访问,静默认证将会失败,并且会提示您输入凭据。

环顾四周

CLI 的结构类似于文件系统,可以使用与 Bash 等 shell 中相同的命令进行导航。

ls命令将列出当前目录的内容:

[standalone@localhost:9990 /] ls
core-service                               subsystem                                  namespaces=[]                              release-version=3.0.8.Final                
deployment                                 system-property                            organization=undefined                     running-mode=NORMAL                        
deployment-overlay                         launch-type=STANDALONE                     process-type=Server                        runtime-configuration-state=ok             
extension                                  management-major-version=5                 product-name=WildFly Full                  schema-locations=[]                        
interface                                  management-micro-version=0                 product-version=11.0.0.Final               server-state=running                       
path                                       management-minor-version=0                 profile-name=undefined                     suspend-state=RUNNING                      
socket-binding-group                       name=matthews-mbp                          release-codename=Kenny                     uuid=ca421018-3df9-43e1-8b3f-ff843ebd38ee 

pwd命令显示当前工作目录:

[standalone@localhost:9990 /] pwd
/ 

cd命令将改变当前目录。通常目录会是一个类似于subsystem的类别和一个类似于undertow的实例,中间有一个等号:

[standalone@localhost:9990 /] cd subsystem=undertow
[standalone@localhost:9990 subsystem=undertow] ls
application-security-domain        server                             default-server=default-server      instance-id=${jboss.node.name}     
buffer-cache                       servlet-container                  default-servlet-container=default  statistics-enabled=false           
configuration                      default-security-domain=other      default-virtual-host=default-host 

help命令显示了可用命令的列表:

[standalone@localhost:9990 subsystem=undertow] help
Usage:

  jboss-cli.sh/jboss-cli.bat [--help] [--version]
                     [--bind=client_bind_address]
                     [--controller=(controller_alias | [protocol://][host][:port])]
                     [--connect] [--file=file_path]
                     [--commands=command_or_operation(,command_or_operation)*]
                     [--command=command_or_operation]
                     [--user=username --password=password]
                     [--properties=file_path]
                     [--no-local-auth]
                     [--error-on-interact]
                     [--timeout=timeout]
                     [--echo-command]
                     [--command-timeout=timeout]

 --help (-h)     - prints (this) basic description of the command line utility.
 ... 

quit命令将退出 CLI。

制表符结束

这些命令也有许多选项。查看这些选项的最简单方法是使用 tab complete。在这里,我输入了ls(末尾的空格很重要),然后按 tab 键查看还有哪些选项可用:

[standalone@localhost:9990 subsystem=undertow] ls
--headers                    --resolve-expressions        application-security-domain  configuration                servlet-container            
--help                       -l                           buffer-cache                 server 

执行操作

在任何给定的目录中,都有许多可以执行的操作。操作以:字符开始。使用制表符结束,我们可以看到可用操作的列表:

[standalone@localhost:9990 http-listener=default] :
add                         map-clear                   read-attribute              read-children-types         remove                      
list-add                    map-get                     read-attribute-group        read-operation-description  reset-statistics            
list-clear                  map-put                     read-attribute-group-names  read-operation-names        undefine-attribute          
list-get                    map-remove                  read-children-names         read-resource               whoami                      
list-remove                 query                       read-children-resources     read-resource-description   write-attribute 

:read-operation-names操作显示与制表完成相同的列表:

[standalone@localhost:9990 http-listener=default] :read-operation-names
{
    "outcome" => "success",
    "result" => [
        "add",
        "list-add",
        "list-clear",
        "list-get",
        "list-remove",
        "map-clear",
        "map-get",
        "map-put",
        "map-remove",
        "query",
        "read-attribute",
        "read-attribute-group",
        "read-attribute-group-names",
        "read-children-names",
        "read-children-resources",
        "read-children-types",
        "read-operation-description",
        "read-operation-names",
        "read-resource",
        "read-resource-description",
        "remove",
        "reset-statistics",
        "undefine-attribute",
        "whoami",
        "write-attribute"
    ]
} 

可以在当前目录中执行操作(就像我们在上面的例子中所做的那样),或者使用类似/:read-operation-names/subsystem=undertow:read-operation-names的命令在特定目录上执行操作。

:read-resource操作是列出当前目录详细信息的常用方法。

WildFly 使用动态模型表示(DMR)格式表示对象。

[standalone@localhost:9990 http-listener=default] :read-resource
{
    "outcome" => "success",
    "result" => {
        "allow-encoded-slash" => false,
        "allow-equals-in-cookie-value" => false,
        "always-set-keep-alive" => true,
        "buffer-pipelined-data" => false,
        "buffer-pool" => "default",
        "certificate-forwarding" => false,
        "decode-url" => true,
        "disallowed-methods" => ["TRACE"],
        "enable-http2" => true,
        "enabled" => true,
        "http2-enable-push" => true,
        "http2-header-table-size" => 4096,
        "http2-initial-window-size" => 65535,
        "http2-max-concurrent-streams" => undefined,
        "http2-max-frame-size" => 16384,
        "http2-max-header-list-size" => undefined,
        "max-buffered-request-size" => 16384,
        "max-connections" => undefined,
        "max-cookies" => 200,
        "max-header-size" => 1048576,
        "max-headers" => 200,
        "max-parameters" => 1000,
        "max-post-size" => 10485760L,
        "no-request-timeout" => 60000,
        "proxy-address-forwarding" => false,
        "read-timeout" => undefined,
        "receive-buffer" => undefined,
        "record-request-start-time" => false,
        "redirect-socket" => "https",
        "request-parse-timeout" => undefined,
        "require-host-http11" => false,
        "resolve-peer-address" => false,
        "rfc6265-cookie-validation" => false,
        "secure" => false,
        "send-buffer" => undefined,
        "socket-binding" => "http",
        "tcp-backlog" => 10000,
        "tcp-keep-alive" => undefined,
        "url-charset" => "UTF-8",
        "worker" => "default",
        "write-timeout" => undefined
    }
} 

使用:read-attribute操作可以读取单个属性:

[standalone@localhost:9990 http-listener=default] :read-attribute(name=enabled)
{
    "outcome" => "success",
    "result" => true
} 

可以通过:write-attribute操作写入属性:

[standalone@localhost:9990 http-listener=default] :write-attribute(name=enabled, value=false)
{"outcome" => "success"} 

使用:undefine-attribute操作可以不定义属性:

[standalone@localhost:9990 http-listener=default] :undefine-attribute(name=write-timeout)
{"outcome" => "success"} 

特殊字符

要定义带空格的值,请用引号将字符串括起来:

[standalone@localhost:9990 /] /system-property=test:write-attribute(name=value, value="value with space")
{"outcome" => "success"}
[standalone@localhost:9990 /] /system-property=test:read-attribute(name=value)
{
    "outcome" => "success",
    "result" => "value with space"
} 

要使用引号,请用反斜杠将其转义:

[standalone@localhost:9990 /] /system-property=test:write-attribute(name=value, value="\"quoted value with space\"")
{"outcome" => "success"}
[standalone@localhost:9990 /] /system-property=test:read-attribute(name=value)
{
    "outcome" => "success",
    "result" => "\"quoted value with space\""
} 

反斜杠本身用反斜杠转义:

[standalone@localhost:9990 /] /system-property=test:write-attribute(name=value, value="\"quoted value with space and a backslash \\\"")
{"outcome" => "success"}
[standalone@localhost:9990 /] /system-property=test:read-attribute(name=value)
{
    "outcome" => "success",
    "result" => "\"quoted value with space and a backslash \\\""
} 

重新加载服务器

更改某些设置需要重新加载服务器。您可以通过读取根目录中的server-state属性来检查服务器的状态。在本例中,我们有一些需要重新加载的设置:

[standalone@localhost:9990 /] :read-attribute(name=server-state)
{
    "outcome" => "success",
    "result" => "reload-required",
    "response-headers" => {"process-state" => "reload-required"}
} 

:reload操作将重新加载服务器:

[standalone@localhost:9990 /] :reload
{
    "outcome" => "success",
    "result" => undefined
} 

批处理操作

WildFly 中的一些操作需要作为一个原子单元运行,或者您可能希望所有命令作为一个整体成功或失败。batchrun-batch命令提供该功能。

在批处理模式下,一个#字符会出现在提示中。

[standalone@localhost:9990 /] batch
[standalone@localhost:9990 / #] /subsystem=undertow/server=default-server/http-listener=default:undefine-attribute(name=write-timeout)
[standalone@localhost:9990 / #] /subsystem=undertow/server=default-server/http-listener=default:write-attribute(name=enabled, value=false)
[standalone@localhost:9990 / #] run-batch
The batch executed successfully 

discard-batch命令将放弃所有批处理命令并退出批处理模式:

[standalone@localhost:9990 /] batch
[standalone@localhost:9990 / #] /subsystem=undertow/server=default-server/http-listener=default:write-attribute(name=enabled, value=false)
[standalone@localhost:9990 / #] discard-batch
[standalone@localhost:9990 /] 

list-batch命令将显示未决的批处理命令,而clear-batch命令将清除任何批处理命令,但使您处于批处理模式:

[standalone@localhost:9990 /] batch
[standalone@localhost:9990 / #] /subsystem=undertow/server=default-server/http-listener=default:write-attribute(name=enabled, value=false)
[standalone@localhost:9990 / #] list-batch
#1 /subsystem=undertow/server=default-server/http-listener=default:write-attribute(name=enabled, value=false)
[standalone@localhost:9990 / #] clear-batch
[standalone@localhost:9990 / #] list-batch
The batch is empty.
[standalone@localhost:9990 / #] discard-batch 

备份配置

您可能希望在进行任何更改之前备份当前配置。这可以通过:take-snapshot操作来实现。

此操作的结果会告诉您备份保存的位置:

[standalone@localhost:9990 /] :take-snapshot
{
    "outcome" => "success",
    "result" => "C:\\Users\\matth\\Downloads\\wildfly-11.0.0.Final\\wildfly-11.0.0.Final\\standalone\\configuration\\standalone_xml_history\\snapshot\\20171108-082107378standalone.xml"
} 

运行 CLI 脚本

CLI 命令可以添加到脚本文件中,并以非交互方式运行。

例如,将这个脚本保存到名为test.cli的文件中:

connect
batch
/subsystem=undertow/server=default-server/http-listener=default:undefine-attribute(name=write-timeout)
/subsystem=undertow/server=default-server/http-listener=default:write-attribute(name=enabled, value=false)
run-batch 

然后可以使用--file命令行选项运行它:

./jboss-cli.sh --file=test.cli 

在这个测试脚本中,我们使用connect命令从脚本内部连接到 WildFly 实例,而不是传递--connect命令行选项。

要在 Windows 中运行jboss-cli.bat文件时禁用Press any key to continue ...提示符,请将NOPAUSE环境变量设置为true:

PS C:\Users\matth\Downloads\wildfly-11.0.0.Final\bin> $env:NOPAUSE="true"
PS C:\Users\matth\Downloads\wildfly-11.0.0.Final\bin> .\jboss-cli.bat --connect
[standalone@localhost:9990 /] quit
PS C:\Users\matth\Downloads\wildfly-11.0.0.Final\bin> 

流量控制语句

CLI 支持 if/else 和 try/catch/finally 之类的流控制语句。

例如,您可以将以下代码添加到 CLI 脚本中,如果尚未定义,它会将系统属性test设置为true:

if (outcome != success) of /system-property=test:read-resource
    /system-property=test:add(value=true)
end-if 

您也可以在交互模式下运行相同的命令:

[standalone@localhost:9990 /] if (outcome != success) of /system-property=test:read-resource
[standalone@localhost:9990 /] /system-property=test:add(value=true)
[standalone@localhost:9990 /] end-if
{"outcome" => "success"} 

try/catch/finally 流控制的工作方式与 Java 中的非常相似。下面将尝试添加数据源,如果出现异常,将移除并添加数据源。最后,数据源被启用:

try
  /subsystem=datasources/data-source=myds:add(connection-url=xxx,jndi-name=java:/myds,driver-name=h2)
catch
  /subsystem=datasources/data-source=myds:remove
  /subsystem=datasources/data-source=myds:add(connection-url=xxx,jndi-name=java:/myds,driver-name=h2)
finally
  /subsystem=datasources/data-source=myds:enable
end-try 

多行命令

通过用一个\字符结束每一行,命令可以分成多行:

[standalone@localhost:9990 /] /subsystem=datasources/data-source=myds:add( \
>   connection-url=xxx, \
>   jndi-name=java:/myds, \
>   driver-name=h2)
{"outcome" => "success"} 

运行 CLI GUI

CLI 有一个 GUI 模式,该模式提供了一个类似文件浏览器的界面,用于在 WildFly 设置目录结构中导航:

./jboss-cli.sh --gui 

WildFly CLI GUI

结论

在这篇文章中,我们从较高的层次上了解了 CLI 的工作原理以及您可以用它做些什么。如果您对 Java 应用程序的自动化部署感兴趣,请尝试免费的开始版本的 Octopus Deploy ,并看看我们的文档

使用 Ubuntu Docker 镜像- Octopus 部署

原文:https://octopus.com/blog/using-ubuntu-docker-image

Ubuntu Docker 官方图片是 Docker Hub 下载最多的图片。超过 10 亿的下载量证明了 Ubuntu 是一个受欢迎和可靠的基础映像,你可以在它的基础上构建自己的定制 Docker 映像。

在这篇文章中,我将向你展示如何在构建你自己的 Docker 映像时充分利用基本的 Ubuntu 映像。

Dockerfile 文件示例

这是一个例子Dockerfile,包括在这篇文章中讨论的调整。我仔细检查了每个设置,以解释它们增加了什么价值:

FROM ubuntu:22.04
RUN echo 'APT::Install-Suggests "0";' >> /etc/apt/apt.conf.d/00-docker
RUN echo 'APT::Install-Recommends "0";' >> /etc/apt/apt.conf.d/00-docker
RUN DEBIAN_FRONTEND=noninteractive \
  apt-get update \
  && apt-get install -y python3 \
  && rm -rf /var/lib/apt/lists/*
RUN useradd -ms /bin/bash apprunner
USER apprunner 

使用以下命令构建映像:

docker build . -t myubuntu 

现在你已经看到了如何从 Ubuntu 基础映像构建一个自定义映像,让我们仔细检查每一个设置来理解为什么要添加它们。

选择基础图像

Docker 映像适用于所有版本的 Ubuntu,包括长期支持(LTS)版本,如 20.04 和 22.04,以及普通版本,如 19.04、19.10、21.04 和 21.10。

LTS 版本支持 5 年,在此期间相关的 Docker 映像也由 Canonical 维护,如 Ubuntu 发布周期页面所述:

这些映像也会随着安全更新映像的定期发布而保持更新,您应该自动使用最新的映像,以确保为您的用户提供一致的安全保护。

当创建托管生产软件的 Docker 映像时,以最新的 LTS 版本为基础是有意义的。这允许开发运维团队在最新的 LTS 基础映像的基础上重建他们的定制映像,该映像自动包括所有更新,但也不太可能包括主要操作系统版本之间可能引入的那种重大更改。

我使用了 Ubuntu 22.04 LTS Docker 图像作为这个图像的基础:

FROM ubuntu:22.04 

没有安装建议或推荐的依赖项

一些软件包有一个建议或推荐的依赖项列表,这些依赖项不是必需的,但在默认情况下是安装的。这些额外的依赖会不必要地增加最终 Docker 图像的大小,正如 Ubuntu 在他们关于减小 Docker 图像大小的博客文章中提到的

为了禁用对所有apt-get调用的这些可选依赖项的安装,在/etc/apt/apt.conf.d/00-docker处用以下设置创建配置文件:

RUN echo 'APT::Install-Suggests "0";' >> /etc/apt/apt.conf.d/00-docker
RUN echo 'APT::Install-Recommends "0";' >> /etc/apt/apt.conf.d/00-docker 

安装附加软件包

大多数基于 Ubuntu 的定制镜像需要你安装额外的包。例如,要运行用 Python、PHP、Java、Node.js 或 DotNET 编写的自定义应用程序,您的自定义映像必须安装与这些语言相关的包。

在典型的工作站或服务器上,使用简单的命令安装软件包,如:

apt-get install python3 

在 Docker 映像中安装新软件的过程是非交互式的,这意味着您没有机会响应提示。这意味着您必须添加-y参数,以便在提示继续安装软件包时自动回答“是”:

RUN apt-get install -y python3 

防止软件包安装过程中出现提示错误

一些软件包的安装试图打开额外的提示,以进一步自定义安装选项。在非交互环境中,例如在 Docker 映像的构建过程中,试图打开这些对话框会导致如下错误:

unable to initialize frontend: Dialog 

这些错误可以忽略,因为它们不会阻止软件包的安装。但是可以通过将DEBIAN_FRONTEND环境变量设置为noninteractive来防止这些错误:

RUN DEBIAN_FRONTEND=noninteractive apt-get install -y python3 

Docker 网站提供了关于 DEBIAN _ FRONTEND 环境变量使用的官方指导。他们认为这是一种装饰性的改变,并建议不要永久设置环境变量。上面的命令为单个apt-get命令的持续时间设置了环境变量,这意味着对apt-get的任何后续调用都不会定义DEBIAN_FRONTEND

清理包列表

在安装任何软件包之前,您需要通过调用以下命令来更新软件包列表:

RUN apt-get update 

但是,在安装了所需的软件包之后,软件包列表就没有什么价值了。最佳实践是从 Docker 映像中删除任何不必要的文件,以确保生成的映像尽可能小。安装完所需的软件包后,为了清理软件包列表,删除了/var/lib/apt/lists/下的文件。

在这里,您可以更新软件包列表,安装所需的软件包,并作为一个命令的一部分清理软件包列表,该命令分为多行,每行末尾有一个反斜杠:

RUN DEBIAN_FRONTEND=noninteractive \
  apt-get update \
  && apt-get install -y python3 \
  && rm -rf /var/lib/apt/lists/* 

以非超级用户身份运行

默认情况下,root 用户在 Docker 容器中运行。root 用户通常拥有比运行自定义应用程序所需更多的权限,因此创建没有 root 权限的新用户可以提供更好的安全性。

useradd命令提供了创建新用户的非交互方式。

这不要与adduser命令混淆,后者是比useradd更高级别的包装器

在编辑了所有配置文件并安装了软件包之后,您创建了一个名为apprunner的新用户:

RUN useradd -ms /bin/bash apprunner 

然后,该用户将被设置为任何进一步操作的默认用户:

USER apprunner 

结论

除了安装任何需要的额外的软件包之外,很少定制就可以使用基本的 Ubuntu Docker 镜像。但是,通过一些调整来限制安装可选软件包,在软件包安装后清理软件包列表,并创建具有有限权限的新用户来运行自定义应用程序,您可以为您的自定义应用程序创建更小、更安全的映像。

了解如何使用其他流行的容器图像:

资源

了解更多信息

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

愉快的部署!

变量编辑器:旅程-章鱼部署

原文:https://octopus.com/blog/variable-editor-improvements

Octo-Scientist improving variables

作为 Octopus 4.0 中 UI 改进的一部分,我们抓住机会重新设计了变量编辑器。这对我们的许多用户来说是一个痛点,也是一个强烈要求的改变。让我带您浏览一下我们对变量编辑器所做的更改!

旧的变量编辑器

变量编辑器过去看起来像这样:

Old Variable Editor

它是基于 SlickGrid 的,在重新设计的过程中,它有一些我们想要解决的问题:

  • 没有对变量进行分组或排序。
  • 通常需要两次点击来突出显示文本的一部分。
  • 过滤器不容易被发现或应用。
  • 有必要进入一个弹出窗口来编辑变量范围。

到目前为止,旧的变量编辑器最大的缺点是很难扩展。这是一个很少被关注的代码领域,任何改变都是有风险的。

新的变量编辑器

在 4.0 中,我们提供了新的变量编辑器。它看起来像这样:

Variable Editor - 4.0

它在以下方面对旧的变量编辑器进行了改进:

  • 一键聚焦和选择文本的能力。
  • 变量按名称自动分组。
  • 过滤器易于发现和使用。
  • 可以在没有弹出窗口的情况下编辑范围。
  • 向变量添加描述的能力。
  • 使用虚拟化支持大量变量。

然而,这种实现仍然存在一些重大问题:

  • 用户无法调整列的大小。
  • 滚动时渲染很慢,让体验感觉反应迟钝。

我们决定不推迟发布 4.0 来修复这些问题,而是专注于尽快修复它们。

可调整大小的列

在旧的变量编辑器中,用户可以调整表列的大小。当您想要调整列的大小时,这对于长变量名或值非常有用,以便您可以看到更多信息。对于我们的许多用户来说,忽略这个特性是有问题的。新的变量编辑器中更大的字体使问题变得更糟,留下更少的空间来显示关键信息。

在 4.0.11 版本中,我们提供了一个变量编辑器,您可以在其中调整列的大小。

Resizeable columns

这些可调整大小的列是在 react-dnd 库的帮助下实现的,其工作原理是在调整大小后对列应用一个百分比宽度。与固定宽度相比,使用百分比宽度的优势在于,当用户调整浏览器窗口大小时,它会自动响应。

滚动是变量编辑器最初版本的一个大问题,它不仅仅影响大型变量集。

Slow scrolling performance

问题是呈现一行是一个相对昂贵的操作。它昂贵的一个主要原因是因为我们在 Scope 列中使用的特定组件。需要测量这些芯片的宽度,以计算我们可以在可用空间中显示多少芯片。

该单元的每次渲染包括:

  1. 渲染所有的筹码。
  2. 测量所有芯片的(这涉及到昂贵的浏览器回流)。
  3. 仅重新渲染我们可以在“显示更多”按钮旁边放置的芯片。

当变量编辑器中的每一行滚动到视图中时,都会发生这种情况,因为我们正在使用虚拟化,这是渲染缓慢的主要原因。这种方法的一种替代方法是不进行虚拟化而提前渲染所有行,但是这使得初始渲染非常慢。对于一个大的变量集,这变得非常慢,甚至比滚动性能更糟糕。

为了解决这个问题,我们对渲染和测量这些组件的方式进行了大量优化。我们比我们需要的更频繁地测量了许多东西,并且我们能够最小化一些回流的成本。这些变化最终对性能产生了显著的影响。

我们还发现,随着用户滚动,我们不必要地重新呈现了比我们需要的更多的组件。我们通过使用不可变数据结构和在几个关键组件中实现 shouldComponentUpdate ,专注于在滚动时只呈现最少的内容。

这些变化使得 Chrome 和 FireFox 的滚动性能都可以接受。

Improved scrolling performance

IE11 和 Edge 似乎有不同的性能特征,我们仍然对这些浏览器的滚动性能不满意(尽管它现在的性能比以前明显好了)。到目前为止,我们还没有找出这些差异的根本原因,但这是一项正在进行的调查。目前,我们已经通过对较小的变量集禁用虚拟化来缓解这个问题,这些变量集可以在合理的时间内执行初始渲染。

延迟加载变量集

如果从项目中查看“库变量集”页面,您可以看到每个包含的库变量集中的变量集合,这些变量被分组到可扩展的部分中。

Library variable sets

加载此页面时,每个库变量集都是从单独的请求中从服务器加载的。如果您有大量的库变量集,那么这是有问题的,因为它可能会达到浏览器的请求限制,并且整个页面需要很长时间来加载。即使我们以更有效的方式加载库变量集,如果包含大量的库变量集,加载该页面的所有数据仍然需要很长时间。

我们决定更改在此页面上加载数据的方式,使其行为更像 Environments 页面,其中每个环境的内容仅在您展开该环境时加载。此外,我们通过向 API 添加一个新的端点来优化发出请求的方式,该端点允许您同时加载多个变量集。

所有变量的过滤都发生在客户端,所以我们仍然需要让用户能够搜索所有包含的变量集中的所有变量。幸运的是,我们在页面顶部有一个Expand All按钮,它有加载所有变量集的副作用。如果一个用户想要在所有的变量集中搜索一个特定的变量,那么他们可以首先搜索变量集的Expand All,然后应用他们的过滤器。

可变分组 UX

作为这一变化的一部分,我们希望改变用户对变量的思考方式。在旧的变量编辑器中,每个变量都有一个名称和值。如果您想在不同的范围(例如,不同的环境)中更改变量的值,那么您需要创建一个全新的变量,并确保它与另一个变量同名。

可变的 价值 范围
网站端口 1000 发展
网站端口 2000 试验
网站端口 3000 生产

我们向用户介绍的新模型是一个变量可以有多个值。用户可以声明他们希望在不同的范围内使用哪些值。因为所有的值都与同一个变量相关联,所以它们在执行时将使用同一个变量名。

可变的 价值观念
网站端口 (1000 用于开发),(2000 用于测试),(3000 用于生产)

我们从改变 UI 来反映这个新模型开始,这意味着我们可以保持 API 的向后兼容性。这意味着我们只是在 UI 中按名称对变量进行分组,这让许多用户起初感到困惑。

【T2 Original variable groups

一个常见的抱怨是某些变量的名称丢失了。很容易理解为什么用户会这样认为,因为乍一看,新的变量编辑器和旧的变量编辑器之间的主要功能差异似乎是变量名有时会丢失。

我们在这个设计上迭代了几次,得出了一个新的设计,它强调我们显示了变量的多个值和范围。现在,对于每个有多个值的变量,变量编辑器中都有一个单独的“标题”行。这也解决了我们的另一个问题;我们现在有了一个合适的位置来放置一个溢出菜单,该菜单包含特定于整个变量的操作,而不是单个值。

Improved variable groups

变量警告

新的变量编辑器有许多不同的警告和消息,为用户提供更多关于变量状态的信息,并指导他们设计可维护的变量。

缺少变量范围

最初,我们想添加一个警告,当用户缺少特定范围的变量值时显示出来。例如,如果您有一个具有开发和测试值的变量,但没有生产值,那么您可能会认为在生产过程中部署会失败,因为缺少变量值。

Missing scope warning

这个警告最终变得过于复杂而难以实现。考虑这样一种情况,您有一个带有通道的项目,并且这些通道使用不同的生命周期。我们需要检查每个通道中的每个环境都有一个值。

当您考虑到有多种方法来确定变量值的范围,从而在每个可能的部署范围内产生一个有效值时,事情就变得更加复杂了。实现这一点的一种常见方法是使用单个未划分范围的值,该值适用于没有明确限定范围的值的任何环境。然而,还有其他方法可以达到同样的效果。例如,您可以拥有一个使用该变量的特定部署步骤的值,而不是一个未划分范围的值。

我们最终在变量编辑器的初始版本发布之前移除了这个特性,因为它被证明在不产生大量误报的情况下很难实现。

空值

我们提供了新的变量编辑器,能够在变量值为空时显示警告。

Empty value warning

我们认为用户很少会真的想这么做。然而,比我们预期的更多的用户以有效的方式使用空值。对于这些用户来说,警告指示器最终是一个糟糕的视觉指示器。这让他们觉得自己做错了,也增加了很多视觉噪音。也没有办法抑制这个警告,这意味着他们将永远被一个无用的警告所困。我们仍然认为能够过滤没有值的变量值是有价值的,所以我们决定删除警告指示器。

Empty value without warning

新功能

作为我们在 2018.2 中对 AWS 支持的改进的一部分,我们添加了对 AWS 帐户的一级支持作为一种新类型的变量。使用新的变量编辑器,这是一个相对容易添加的扩展,其工作方式与证书相同。

AWS Account variables

变量统一

我们开始考虑变量编辑器下一步的发展方向,我们正在考虑的下一个主要项目已经被内部命名为“变量统一”项目。

围绕这个项目的一部分将会涉及到什么,有一些想法,但是我们想做的一件事是在定义租户变量时重用变量编辑器。目前,我们正在使用一个定制的租户变量 UI 组件,但是我们没有理由不使用变量编辑器来代替它。这将解决 UI 这一部分的许多突出的性能和可用性问题。

我们还想重温变量模板的工作原理,以及我们如何改善变量体验,让用户相信他们已经为部署指定了所有正确的变量。作为其中的一部分,我们可能会更改我们的变量 API 来反映新的变量模型,其中一个变量可以包含多个值。事实上,我们刚刚完成了客户端代码的重构,这样它就可以一致地使用这个新的变量和值模型。

下一步是什么?

变量编辑器在过去的六个月里已经走过了很长的路,这感觉就像是它旅程的开始。您希望在变量编辑器的下一个版本中看到什么?

变量编辑器重新设计- RFC - Octopus 部署

原文:https://octopus.com/blog/variable-editor-update-rfc

在 4.0 版本中,我们计划彻底检查变量编辑器。这是我们最重要的用户意见建议之一,感谢所有提供如何改进变量编辑器想法的用户。

根据收到的反馈,我们对变量编辑器第一版的目标是通过加入一些新功能,使表格编辑体验像预期的那样工作:

  • 为一个变量提供不同范围的多个值
  • 在弹出编辑器中为变量添加描述,将鼠标悬停在表格中的注释图标 Octopus variable editor - description icon 上进行查看
  • 添加变量时,能够在一个范围内输入多个值。

用户场景

在第一个版本中,我们着重于为四种用户场景提供解决方案。这些涵盖了 Octopus 团队的共同主题和我们用户的建议。

  • 用户正在输入一个或两个环境范围内的数百个单个文本变量。他们需要通过使用键盘来快速完成这项工作。

  • 用户需要添加大量代码作为变量,并且需要将其复制并粘贴到一个大的文本字段中。他们被要求给每个变量一个描述,并将其范围扩展到租户标签集。

  • 为用户提供了每个环境的用户名和密码列表。用户希望能够首先选择环境,然后向该环境中添加多个值,而不是一次添加一个值并分别确定它们的范围。

  • 用户希望查看特定范围配置的范围内有哪些值,以查看是否有任何重复。

注意还有一些很棒的附加功能建议。但是我们不能一下子把它们都包括进来,因为我们希望变量编辑器的第一个版本集中于提高基本的可用性,并为更高级的特性创建一个坚实的平台。

新的变量编辑器外观

变量编辑器将继承新的 4.0 UI,并保持高级概念,如在变量类型之间切换、过滤能力以及仅查看仅在库中可编辑的库变量集和公共模板。

引入了新的 UI 模式来帮助更好地过滤和显示信息:

  • 默认显示的高级过滤器,可以通过过滤器图标 Octopus variable editor - filter icon 隐藏

  • 能够通过带有警告的变量进行过滤

  • 展开面板,现在可以看到哪些变量和公共变量模板属于哪个变量集

  • 源图标有助于区分变量类型。

项目变量

Octopus variable editor - project variables

项目变量模板

Octopus variable editor - project variable templates

通用变量模板

Octopus variable editor - common variable templates

库变量集

Octopus variable editor - library variable sets

查看所有变量

Octopus variable editor - all variables

添加新变量

当前变量编辑器表的底部有一个新的变量行。许多用户可能有很多条目,这导致这个空行出现在屏幕之外。我们已经将空的 add 行移到了表的顶部,这样无论有多少个变量,添加变量都变得简单快捷。您可以单击名称单元格开始添加值,或者单击“添加新变量按钮”创建新行。下面的视频显示了添加新变量。

Octopus Deploy adding variables

向范围添加多个值

我们计划在环境、角色、目标、步骤或租户标签集的范围内添加多个值。这个新功能将使为新环境或新角色添加一组变量变得更加容易。

此操作将在“添加新变量”按钮上显示为下拉菜单“添加多个值”。添加体验将发生在弹出编辑器中,用户首先定义范围,然后添加多个值。

Octopus Deploy adding variables

表格编辑体验

我们对新变量编辑器的目标是让它成为一个无缝的表格编辑体验,让用户在最小范围内输入简单变量。我们希望确保用户可以仅通过键盘导航表格,并可以选择使用鼠标。

以下是我们计划使用的常规键盘控件和快捷键。

键盘控制

Tab

  • Moves the cell focus through the row from left to right, top to bottom - Moves the selector controls focus from top to bottom

Enter

  • Adds another variable

  • Performs the action of a selected button

  • Selects item in dropdown list

Arrow up and down

  • Moves the focus through a dropdown list

  • Moves the focus from the form field to the Open editor link in the edit dialog

  • Moves the focus of the rows in the variable table

esc

  • collapses dropdown and puts a cell in selected state

  • exist edit/add mode if a cell is in a selected state

  • Esc moves through the states until out of edit mode

Typing

Will activate any selectors/dropdowns

ctl+enter

selects multiple items in a drop down

快捷指令

ctl+e

Opens editor popup

ctl+o

Creates a new variable

Octopus Deploy adding variables

行操作

行操作将在悬停时显示为行末尾的溢出菜单。这将替换第一个单元格上的当前右键单击功能。

Octopus variable editor - row actions

弹出式编辑器用于显示高级选项和更大的文本字段,以便添加或编辑变量。编辑现有变量时,可以随时打开弹出编辑器。我们希望确保如果用户只使用键盘输入变量,弹出窗口仍然可以通过链接和键盘快捷键(ctl+o)访问。使用与表格编辑器相同的键盘命令在弹出编辑器中导航。

弹出编辑器中的高级设置包括:

  • 向变量添加描述的能力
  • 用于添加代码编辑器的较大值文本字段
  • 提示值
  • 租户标签集

Octopus variable popup editor

Octopus variable popup editor - define scope

性能和浏览器支持

作为 4.0 的一部分,我们希望充分利用自 2013 年我们上次重新设计以来网络取得的进步。这意味着 Octopus 4.0 将只支持 Internet Explorer (IE) 11 及以上版本。4.0 重写也将提高性能,允许快速加载和过滤大量数据。请阅读我们的 GitHub 问题,了解更多关于 Octopus 4.0 浏览器支持的信息。

反馈

我们认为我们上面概述的将改进我们添加变量的方式,并为我们向编辑器添加更多高级功能提供一个更好的平台。

我们希望听到您对变量编辑器发布计划的反馈。我们想问的是,如果你只是想提供一些快速反馈,请在下面的评论中提出。如果有更大的事情需要更详细的讨论,请在我们的 GitHub 规范报告变量编辑器重新设计规范报告上提出问题。

使用新的 Octostache 过滤器创建动态运行条件- Octopus Deploy

原文:https://octopus.com/blog/variable-run-conditions-with-octostache

我喜欢使用的 Octopus Deploy 的一个特性是可变运行条件。可变运行条件允许您根据业务逻辑跳过步骤。当与输出变量结合时,它们是一个强大的工具。

在我们的 2021.2 版本中,过滤器MatchContains 被添加到 Octostache 中。

在我们的发布公告中了解更多关于 Octopus 2021.2 (Q3)发布的信息。

在这篇文章中,我将带您了解如何将可变运行条件与输出变量和新的Contains过滤器结合起来。

场景

我一直在帮助一个客户使用 Octopus 部署了 Azure 虚拟机规模集。可以按计划或根据指标添加或删除虚拟机。基于指标的规则的一个示例是,如果 10 分钟内 CPU 使用率为 60%,则添加更多虚拟机。

对于我们客户的场景,需要注意的事项有:

  • 晚上,他们会缩减规模集中的虚拟机数量
  • 早上,他们向规模集添加了 10 多台虚拟机
  • 它们使用标准映像,但每个应用程序都要安装和配置额外的后端软件(IIS、MSMQ、。网等。)

客户使用部署目标触发器,在添加新的部署目标时触发部署。

要解决的问题

从远处看,一切看起来都很棒,并且正常工作,但是放大看,就有一个时间问题:

  • 虚拟机规模集无法同时完成所有虚拟机的设置。
  • Octopus Deploy 不知道虚拟机规模集;它获取一批新的部署目标并进行部署。
  • 每个应用程序需要 30 多分钟来完成后端软件的安装和配置。
  • 默认情况下,Azure 虚拟机规模集启用了过度配置。要求增加 10 台虚拟机,最初创建了 14 台。10 台虚拟机成功运行后,另外 4 台将被删除。但是这 4 个用 Octopus Deploy 注册了自己。

每天早上,扩展虚拟机数量都要花费两倍或三倍的时间。例如,如果他们将虚拟机数量从 5 台增加到 25 台,就会出现这种情况:

  1. Azure 创建了 27 个新的虚拟机(而不是 20 个),并开始供应过程。
  2. 虚拟机上线并以分散的方式向 Octopus Deploy 注册。
  3. Octopus Deploy 中的部署目标触发器选择 5 到 8 台虚拟机并开始部署。
  4. 部署开始安装附加软件。
  5. 90–180 秒后,剩余的虚拟机完成资源调配。
  6. Azure 移除了额外的 7 个虚拟机。
  7. 第一次部署变慢是因为这 7 个虚拟机中有一个在第一批中。Octopus 正在等待超时发生。
  8. 第一次部署在 30 分钟后完成。
  9. 部署目标触发器选择剩余的机器。
  10. 第二次部署在虚拟机添加到虚拟机规模集后一个多小时完成。

因此,客户在早上很早就开始横向扩展。他们需要确保在工作日开始时有足够的虚拟机。

解决方案要求

等待一个多小时是不可接受的。虽然可以调整时间表,但由于意外负载,仍然存在伸缩问题。这时,客户问:

在虚拟机规模集完成所有新虚拟机的配置时,配置 Octopus 以暂停部署有多难?

经过讨论,我们确定了这些要求:

  • Octopus 应该暂停并等待虚拟机规模集完成配置。
  • 应该同时配置和部署所有新的虚拟机。
  • 应该跳过所有现有的虚拟机。
  • 单个部署流程应该处理手动部署和由部署目标触发器创建的部署。

解决方案

我知道解决这个问题将使用新的检查 VMSS 供应状态步骤模板(我编写该模板是为了帮助解决这个客户的问题)、健康检查步骤可变运行条件

简化的部署流程是:

  1. 检查 VMSS 供应状态:仅在部署目标导致部署时运行
  2. 健康检查步骤:仅在部署目标导致部署时运行
  3. 配置网络服务器
  4. 部署应用程序

High-Level View of Deployment Process

此部署流程中的所有步骤都使用可变的运行条件。只有当部署目标触发器导致部署时,才需要运行步骤 1 和 2。我将在下一节介绍第 3 步和第 4 步。

#{unless Octopus.Deployment.Error}#{if Octopus.Deployment.Trigger.Name}True#{/if}#{/unless} 

检查 VMSS 设置状态

检查 VMSS 供应状态是一个新的步骤模板,具有以下功能:

  • 它会一直等待,直到 VMSS 完成对规模集中所有虚拟机的资源调配。
  • VMSS 完成资源调配后,它会使用 Octopus Deploy 来协调规模集中的虚拟机列表。任何其他部署目标注册都将被删除。
  • 设置包含计算机名、计算机 id 和一个布尔值的输出变量,该值指示是否添加了新的部署目标。

将该步骤添加到部署流程的顶部。

不要忘记将运行条件设置为之前的值。

【T2 Check VMSS Provision Status

健康检查步骤

健康检查步骤将所有新虚拟机添加到部署中。运行状况检查步骤是我们如何将添加到规模集的所有新虚拟机部署到,而不仅仅是那些导致部署目标触发器触发的虚拟机。

设置以下选项:

  • 关于角色中的目标:与部署目标相同的角色。
  • 运行状况检查类型:执行仅连接测试(由机器策略执行的初始运行状况检查已经完成了完整的运行状况检查,因此无需再次执行)。
  • 健康检查错误:跳过不可用的部署目标。
  • 新部署目标:在部署中包含新的部署目标。

Health Check Step

跳过现有虚拟机

运行状况检查步骤会将规模集中的所有虚拟机添加到部署中。这是一个问题,因为一个要求是在部署过程中应该跳过所有现有的虚拟机。简而言之,这种情况会发生:

  1. 虚拟机规模集中的虚拟机数量从 5 个增加到 25 个。
  2. 部署目标触发器将看到 7 个新的部署目标并开始部署。
  3. “检查 VMSS 预配状态”步骤将一直等到添加了剩余的 13 个部署目标。
  4. 运行状况检查步骤将添加新的 15 个部署目标和先前存在的 5 个部署目标。

健康检查步骤将触发器的部署目标数量从 7 个增加到 25 个,而不是 20 个(新虚拟机的数量)。我需要跳过其中的 5 个部署目标。

关于可变运行条件的一个鲜为人知的事实是,当一个步骤被配置为在一个角色上执行时,它们为每个部署目标运行。

step configured to execute on a role

这意味着当我有 25 个部署目标时,它运行变量 run 条件 25 次。每当变量 run condition 返回False,时,它就会跳过那个部署目标。

父/子步骤或滚动部署也是如此。为了简单起见,滚动部署不在本文讨论范围之内。

获取部署目标 ID 很简单;使用#{Octopus.Machine.Id}。我需要一份虚拟机的列表,以便与该 ID 进行比较。

这就是检查 VMSS 供应状态步骤模板的用武之地。如上所述,它创建了一个输出变量,其中包含添加到虚拟机规模集的所有部署目标 id 的列表。

parameter excluding pre-existing machines

在这个运行示例中,虚拟机vmss-ti000020已经存在,它从输出变量中排除了它。

virtual machine excluded because it is pre-existing

现在,我有了一个新创建的部署目标列表和要部署到的特定部署目标。最后一块拼图是可变运行条件。这就是新的Contains 可变滤波器的用武之地。

Contains过滤器检查一个字符串是否包含另一个字符串。Match以同样的方式工作,但是它使用正则表达式。对于我的用例,正则表达式是多余的。

以下是完整的运行条件:

#{unless Octopus.Deployment.Error}
    #{if Octopus.Deployment.Trigger.Name}
        #{Octopus.Action[Check VMSS Provision Status].Output.VMSSDeploymentTargetIds | Contains #{Octopus.Machine.Id} }
     #{else}
        True
     #{/if}
#{/unless} 

运行条件是:

  1. 如果有错误,那么不要运行这一步。
  2. 进行手动部署时,然后部署到所有部署目标。
  3. 当触发器导致部署时,检查当前部署目标是否在步骤模板的输出变量中。如果不是,则跳过该部署目标。

继续看前面的截图,我们可以看到vmss-ti000020被跳过了。

skipped deployment target because of variable run condition

手动部署将跳过步骤 1 和 2,并在所有部署目标上运行步骤 3 和 4。

manual deployment

最后一个问题

在我的一些实践部署中,部署目标触发器会在完成第一次部署后的一分钟内运行第二次部署。尽管之前的部署已经部署到所有机器上,但还是发生了这种情况。

Example of a duplicate run

那是有问题的;您不想重新部署并导致停机。谢天谢地检查 VMSS 供应状态处理。它通过计算当前部署的队列时间减去前一个部署的完成时间的差值来检测重复运行。如果差值小于 3 分钟,则重复运行。您可以配置步骤模板来取消当前部署或让其继续进行。

但是这不是必需的。我们在前面配置了该步骤来删除预先存在的机器。预先存在的部署目标是在触发器触发前 3 分钟就存在的目标。除非您的部署花费的时间少于 3 分钟,否则将排除第一次运行中的所有现有部署目标。

Duplicate run skipping pre-existing machines

我们预先存在的新变量过滤器与输出变量和运行条件的组合无需额外配置即可处理这种情况。对你来说,这可能是一个有争议的问题。当我在测试中向一个虚拟机规模集添加超过 10 个虚拟机时,往往会发生这种情况。

结论

当您将八进制、输出变量和运行条件结合起来时,您会得到一个健壮的部署过程。Octostache 中添加了ContainsMatch滤镜,使得这一组合更加强大。您可以使用更容易维护的Contains比较,而不是索引匹配或循环。

如果您对如何在您的部署流程中组合八进制、输出变量和运行条件有任何疑问,请联系位于customersuccess@octopus.com的客户成功团队。我们很乐意帮忙。

愉快的部署!

可变特异性和复杂性- Octopus 部署

原文:https://octopus.com/blog/variable-specificity-and-complexity

Octopus 允许变量作用于不同的环境、角色或机器。这很有用的一个简单例子是,根据您是部署到测试环境还是生产环境,数据库连接字符串有不同的值。

Variable scoping

变量的范围可以是多个值,因此在给定的场景中决定哪个变量是合理的是一个非常棘手的过程。我们称之为可变特异性,它的工作原理有点像 CSS 特异性

例如,假设我们有这组变量:

DatabaseName = DB01 (Environment: Staging, Production)
DatabaseName = DB02 (Environment: Staging)
DatabaseName = DB03 (Environment: Production) 

在这种情况下,如果我们部署到生产环境,要使用的正确值可能是DB01DB03,因为两者匹配。有时候变量作用域的结果是不确定的,我认为这很直观。

每个范围级别都有不同的优先级:

  • 作用域为机器的变量比作用域为角色的变量更具体
  • ...这比环境范围内的变量更具体
  • ...这比没有范围的变量更具体

这通常也有直观的意义——机器比环境更细粒度,因此范围应该优先。

在 Octopus 中计算变量的特异性如下:

public int Rank()
{
    var score = 0;
    score += Score(ScopeField.Private, 10000000);
    score += Score(ScopeField.User, 1000000);
    score += Score(ScopeField.Action, 100000);
    score += Score(ScopeField.Machine, 10000);
    score += Score(ScopeField.TargetRole, 1000);
    score += Score(ScopeField.Role, 100);
    score += Score(ScopeField.Environment, 10);
    score += Score(ScopeField.Project, 1);
    return score;
} 

如果多个变量与一个场景相匹配,我们只需取排名第一的变量。较高优先级的项目在较低优先级的项目之前提高等级。例如,在生产中,我使用一个数据库,但是我的报告服务应该总是使用不同的数据库:

DatabaseName = DB01 (no scope)
DatabaseName = DB02 (Environment: Staging)
DatabaseName = DB03 (Environment: Production)
DatabaseName = DB04 (Role: Reporting)
DatabaseName = DB05 (Role: Reporting; Environment: Production) 

这很有效——我的报告应用程序甚至在准备阶段也会得到DB04,因为角色比环境更具体。但是在生产中,我可以通过在两个层次上界定它来强制它。

容易混淆的一个地方是涉及角色的多个匹配。举第一个例子,但让我们考虑角色,而不是环境:

DatabaseName = DB01 (Role: WebServer, AppServer)
DatabaseName = DB02 (Role: WebServer)
DatabaseName = DB03 (Role: AppServer) 

现在,我不能一次部署到多个环境,所以很明显,在第一个例子中,这两个值都适用。但是我可以拥有一台属于多个角色的机器。在我的开发环境中,也许我的预算仅限于单机,所以它既是一个WebServer又是一个AppServer

在那个例子中,我的WebServer+AppServer机器应该得到哪个值?如果我调查 100 个人,我怀疑大多数人会期待DB01。换句话说,当多个值匹配时,他们期望应用一个关系,并且每个匹配都应该有助于排名。

但是在这种情况下会发生什么呢?

DatabaseName = DB01 (Role: WebServer, AppServer)
DatabaseName = DB02 (Role: WebServer)
DatabaseName = DB03 (Role: AppServer; Environment: Production) 

如果我在生产中部署到一台WebServer+AppServer机器上,我应该获得什么价值?如果我对 100 个人进行民意测验,我会说一半人会期待DB01,一半人会期待DB03。不像前面的例子,没有直观的方法来解释这些。如果你花了几个小时来思考这个问题,它可能对你有意义,但是对下一个必须维护它们的人有意义吗?

像这样的决定是棘手的。我的希望是,不要试图让特殊性规则太过聪明,这只会鼓励人们找到更好的方式来为部署建模(也许环境比角色组合更有意义),而不是依赖于很少有人一眼就能理解的复杂矩阵。

Octopus 2.3 - Octopus Deploy 文件中的变量替换

原文:https://octopus.com/blog/variable-substition-in-files

Octopus 对变量提供了丰富的支持,这些变量的作用范围可以是特定的机器、环境和角色。在部署时,通过将变量名与<appSetting><connectionString>元素的名称相匹配,这些也可以被替换到 XML App.configWeb.config 文件中。

在 Octopus 2.3 中,我们将它扩展到其他类型的文件,使用在整个应用程序中工作的相同的变量语法。

我们的示例应用程序是一个提要监控服务,它在一个 JSON 文件中保存了一个目标服务器列表,该文件是:

{
  "Servers": [{
    "Name": "localhost",
    "Port": 1567
  }]
}

部署应用程序时,我们希望根据目标部署环境配置列表。UAT 环境中的服务器被称为 FOREXUAT01FOREXUAT02

Variables

我们隐式地建立了一个包含两个条目的ServerEndpoints集合,UAT 环境中的每台服务器都有一个条目。

我们可以在部署时运行的模板文件中迭代这些。为了不妨碍开发,我们称它为 Config.json.template

{
  "Servers": [
    #{each server in ServerEndpoints}
      {
        "Name": "#{server.Name}",
        "Port": #{server.Port}
      }#{unless Octopus.Template.Each.Last},#{/unless}
    #{/each}
  ]
}

您可能熟悉简单形式的#{Variable}语法。这个例子使用了一些在 Octopus 2.0 中增加的新特性,包括#{each}#{unless}

随着 Config.json.template 包含在我们应用的 NuGet 包中,下一步是启用部署特性。

Enabling the feature

该特性易于配置,接受 Octopus 将在其中执行变量替换的文件列表。

Configuring substitutions

由于我们使用了与目标文件不同的模板名称,我们还将添加一个 PowerShell 片段,用模板文件的内容替换原来的 Config.json

PostDeploy

该模板现在将作为我们部署过程的一部分运行。

Execution

生成的 JSON 文件是(空白被稍微清除了一点):

{
  "Servers": [
    {
      "Name": "forexuat01.local",
      "Port": 1566
    },
    {
      "Name": "forexuat02.local",
      "Port": 1566
    }
  ]
}

愉快的部署!

Octopus 1.2.2 中的变量替换- Octopus 部署

原文:https://octopus.com/blog/variable-substitutions

Octopus 1.2.2 支持变量替换,允许一个变量的一部分引用另一个变量。

例如,在 Octopus 1.2.2 之前,我定义了这个变量:

Old variables

升级到 1.2.2 后,我现在可以使用引用预定义变量之一的单个变量。

New variables

解决

变量可以引用其他变量,其他变量可以引用其他变量,依此类推。在部署过程中,Octopus 服务器将选择“范围内”的变量(基于部署到的环境等)。)并把它们推到触手处。然后触手会设置一些额外的特殊变量(比如OctopusOriginalPackageDirectoryPath),然后它会通过应用任何替换来解析所有变量的值。在此过程中,它将检查循环依赖关系,如果检测到循环,将抛出异常。

自定义替换的工作方式

如果一个变量引用另一个未定义的变量,默认行为是抛出。您可以通过将特殊变量OctopusIgnoreMissingVariableTokens设置为true来进行定制,从而忽略该表达式。

如果您想完全退出该功能,请将OctopusNoVariableTokenReplacement设置为true

默认情况下,Octopus 期望变量采用#{Name}的形式。如果该语法不适合您,您可以通过在变量OctopusVariableTokenRegex中设置一个自定义正则表达式来定制它。默认使用的正则表达式是\#\{(?<variable>.+?)\}

如果您需要检查您的变量是否被正确替换,将两个特殊变量PrintVariablesPrintEvaluatedVariables设置为true,以打印解析前/后的值。

希望这个特性能让定义变量变得更加容易。

可变更新通知-八达通部署

原文:https://octopus.com/blog/variable-update-notification

沟通对一个成功的团队很重要。每天的单口相声、信息节目和电子邮件让每个人都了解事情的最新进展。不过,信息可能会在混乱中丢失,就像变量值被更新时一样。

Octopus Deploy 的订阅特性可以配置为在变量被电子邮件或 webhook 自动更新时通知用户。

在这篇文章中,您将学习如何使用订阅功能通过 Azure 函数向 Slack 发布消息。

创建 Azure 资源

在创建功能之前,首先需要创建一些 Azure 资源来准备部署。

这篇文章中提出的解决方案使用了以下 Azure 资源:

  • 资源组
  • 存储帐户
  • 存储帐户消息队列
  • 两个 Azure 函数:
    • 接受消息
    • 流程消息

这个解决方案的源代码可以在 GitHub 上的 OctoSubscriber repo 的 azure 文件夹中找到。

资源组

首先创建一个资源组来存放您将创建的所有其他资源。这使得您的工作易于清理,因为删除资源组将删除其中的所有资源。

您可以通过 Azure 门户或通过将 Azure CLI 脚本添加到运行手册来创建资源组,如下所示:

$resourceGroupName = "MyResourceGroup"
$resourceGroupLocation = "westus3"

if ((az group exists --name $resourceGroupName) -eq $false)
{
    Write-Output "Creating resource group $resourceGroupName in $resourceGroupLocation"
    az group create --location $resourceGroupLocation --name $resourceGroupName 
} 

存储帐户

要使用 Azure 的队列功能,首先创建一个存储帐户。

以下是创建存储帐户的 Azure CLI 命令:

# Get variables
$storageAccountName = "MyStorageAccount"
$resourceGroupName = "MyResourceGroup"

# Create Azure storage account
Write-Host "Creating storage account ..."
az storage account create --name $storageAccountName --resource-group $resourceGroupName 

存储帐户消息队列

邮件队列存在于存储帐户中。创建帐户后,使用以下命令创建队列:

# Get variables
$storageAccountName = "MyStorageAccount"
$queueName = "MyMessageQueue"
$resourceGroupName = "MyResourceGroup"

# Get account keys
$accountKeys = (az storage account keys list --account-name $storageAccountName --resource-group $resourceGroupName) | ConvertFrom-JSON

# Create Azure storage queue
Write-Host "Creating queue ..."
az storage queue create --name $queueName --account-name $storageAccountName --account-key $accountKeys[0].Value 

Azure 功能应用

这篇文章中的解决方案使用了两种不同的 Azure 函数:

  • 接受消息
  • 流程消息

这两个功能都需要在 Octopus Deploy 中注册为目标。

使用 Azure 功能最常见的计划是消费计划(sku Y1),然而,我无法使用az function app Plan createCLI 命令,因为Y1不是受支持的 sku。

接受消息

Azure CLI 也可以用来创建函数。接受消息函数是用 Node 编写的。JS,所以一定要为运行时指定node

# Get variables
$resourceGroupName = "MyResourceGroup"
$appServiceName = "Accept-Message"
$appServiceRuntime = "node"
$storageAccountName = "MyStorageAccount"
$osType = "Windows"
$functionsVersion = 3
$azureLocation = "westus3"

# Create App Service
Write-Host "Creating Accept function app service ..."
az functionapp create --name $appServiceName --consumption-plan-location $azureLocation --resource-group $resourceGroupName --runtime $appServiceRuntime --storage-account $storageAccountName --os-type $osType --functions-version $functionsVersion 

流程消息

Provisioning Process-Message 类似于 Accept-Message,只是它是用 C#编写的,并且您需要将dotnet指定为运行时版本。

# Get variables
$resourceGroupName = "MyResourceGroup"
$appServiceName = "Process-Message"
$appServiceRuntime = "dotnet"
$storageAccountName = "MyStorageAccount"
$osType = "Windows"
$functionsVersion = 3
$azureLocation = "westus3"

# Create App Service
Write-Host "Creating Process function app service ..."
az functionapp create --name $appServiceName --consumption-plan-location $azureLocation --resource-group $resourceGroupName --runtime "$appServiceRuntime" --storage-account $storageAccountName --os-type $osType --functions-version $functionsVersion 

对于这两个功能,我都收到了一条消息,提示我没有配置应用洞察。CLI 为您提供了配置它的参数,但是似乎没有办法告诉它您不需要洞察。

配置完所有资源后,资源组的内容应该如下所示:

Screenshot shows 1 to 7 records, with resources provisioned for both Test and Production environments

为测试和生产环境提供资源

Azure 函数

Accept-Message 函数接收提交的正文,并将其放在消息队列中。将消息放入队列后,将触发 Process-Message,解析消息并发布到 Slack。

接受消息

接受消息函数是用 Node 编写的。JS 并且相当基础。如果你从未使用 Node 创建过 Azure 函数。JS,入门这个教程来自微软

将以下内容添加到index.js:

const { QueueClient, QueueServiceClient } = require("@azure/storage-queue");
const connectionString = process.env.AZURE_STORAGE_CONNECTION_STRING;
const queueName = process.env.QUEUE_NAME;
const queueServiceClient = QueueServiceClient.fromConnectionString(connectionString);
const queueClient = queueServiceClient.getQueueClient(queueName);

module.exports = async function (context, req) {
    if (req.body) {
        // Base64 encode message
        let message = req.rawBody;
        let messageBuffer = new Buffer.from(message, 'utf-8');
        let encodedMessage = messageBuffer.toString('base64');

        //await queueClient.sendMessage(req.body.body);
        await queueClient.sendMessage(encodedMessage);
        context.res = {
            body: "Message added!",
            status: 200
        }
    } else {
        context.res = {
            status: 400,
            body: "Request contains no body!"
        };
    }
}; 

环境变量AZURE_STORAGE_CONNECTION_STRINGQUEUE_NAME是在你将函数部署到 Azure 时提供的。

本教程将下载创建 Azure 函数所需的所有node_modules,但是,它不会将它们添加到package.json文件中。您需要添加引用来使您的函数工作。

流程消息

Process-Message 函数由放入队列的消息触发。消息从 JSON 反序列化,解析,然后发送到 Slack。Process-Message 用 C#编写,并使用以下 NuGet 包引用:

  • 微软。Azure.WebJobs .扩展.存储
  • 微软。函数
  • 纽顿软件。Json
  • 懈怠。Webhooks

与接受消息类似,处理消息并不复杂:

using System;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Slack;

namespace process_message
{
    public static class process_message
    {
        [FunctionName("process_message")]
        [StorageAccount("AzureWebJobsStorage")]
        public static void Run([QueueTrigger("octopusdeploy", Connection = "")]string myQueueItem, ILogger log)
        {
            // Deserialize message into JSON object
            dynamic subscriptionEvent = JsonConvert.DeserializeObject(myQueueItem);
            string slackUrl = Environment.GetEnvironmentVariable("SlackUrl");
            string slackChannel = Environment.GetEnvironmentVariable("SlackChannel");

            // Create slack objects
            Slack.Webhooks.SlackClient slackClient = new Slack.Webhooks.SlackClient(slackUrl);
            Slack.Webhooks.SlackMessage slackMessage = new Slack.Webhooks.SlackMessage();

            // Get Octopus Event information
            string librarySetName = subscriptionEvent.Payload.Event.ChangeDetails.DocumentContext.Name;
            System.Text.StringBuilder messageText = new System.Text.StringBuilder();

            // Get message from event
            messageText.AppendLine(subscriptionEvent.Payload.Event.Message.ToString());

            if (subscriptionEvent.Payload.Event.ChangeDetails.Differences.Count > 0)
            {
                // Loop through variables collection
                foreach (var difference in subscriptionEvent.Payload.Event.ChangeDetails.Differences)
                {
                    // Split the path
                    string[] parsedDifference = difference.path.ToString().Split("/", StringSplitOptions.RemoveEmptyEntries);

                    if (parsedDifference[0] == "Variables")
                    {
                        // Get the variable index value
                        int variableIndex = int.Parse(parsedDifference[1]);

                        // Add to message
                        messageText.AppendLine(string.Format("Variable: {0}", subscriptionEvent.Payload.Event.ChangeDetails.DocumentContext.Variables[variableIndex].Name));
                        messageText.AppendLine(string.Format("Old value: {0} \r\n New value: {1}", subscriptionEvent.Payload.Event.ChangeDetails.DocumentContext.Variables[variableIndex].Value, difference.value));
                    }
                }
            }

            log.LogInformation($"Posting to Slack: {messageText.ToString()}");

            // Post message to channel
            slackMessage.Channel = slackChannel;
            slackMessage.Text = messageText.ToString();
            slackClient.Post(slackMessage);
        }
    }
} 

环境变量SlackUrlSlackChannel是在部署过程中提供的。

构建和打包功能

Process-Message 是两个函数中唯一需要编译的函数,但是,它们都需要打包。

GitHub repo 包含一个 GitHub 操作构建定义作为示例:

# This is a basic workflow to help you get started with Actions

name: Azure Function

on:
  push:
    paths:
      - 'azure/**'

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        dotnet-version: ['3.1.x']

    steps:
      - uses: actions/checkout@v2
      - name: Setup .NET Core SDK ${{ matrix.dotnet-version }}
        uses: actions/setup-dotnet@v1.7.2
        with:
          dotnet-version: ${{ matrix.dotnet-version }}

      - name: Create artifacts folder
        run: |
          mkdir "$GITHUB_WORKSPACE/artifacts"
          mkdir "$GITHUB_WORKSPACE/artifacts/process-message"
          mkdir "$GITHUB_WORKSPACE/azure/accept_message/node_modules"

      - name: Restore dependencies for process-message
        working-directory: azure/process_message/process_message
        run: dotnet restore

      - name: Build process-message
        working-directory: azure/process_message/process_message
        run: dotnet build --configuration Release --no-restore --output "$GITHUB_WORKSPACE/artifacts/process-message"

      - name: Install Octopus CLI
        uses: OctopusDeploy/install-octopus-cli-action@v1.1.1
        with:
          version: latest

      - name: Pack and Push
        working-directory: azure/accept_message
        env:
          OCTOPUS_URL: ${{ secrets.OCTOPUS_SERVER }}
          OCTOPUS_API_KEY: ${{ secrets.OCTOPUS_API_KEY }}  
        run: |
          npm ci --production
          octo pack --id=OctoSubscriber-AcceptMessage-Function --format=Zip --version=2021.1.1.$GITHUB_RUN_NUMBER --basePath="$GITHUB_WORKSPACE/azure/accept_message"
          octo pack --id=OctoSubscriber-ProcessMessage-Function --format=Zip --version=2021.1.1.$GITHUB_RUN_NUMBER --basePath="$GITHUB_WORKSPACE/artifacts/process-message"
          octo push --package=OctoSubscriber-AcceptMessage-Function.2021.1.1.$GITHUB_RUN_NUMBER.zip --server=$OCTOPUS_URL --apiKey=$OCTOPUS_API_KEY --space="Target - Serverless"
          octo push --package=OctoSubscriber-ProcessMessage-Function.2021.1.1.$GITHUB_RUN_NUMBER.zip --server=$OCTOPUS_URL --apiKey=$OCTOPUS_API_KEY --space="Target - Serverless" 

部署功能

这篇文章假设你知道如何在 Octopus Deploy 中创建一个项目,并且不会涉及这个主题。如果您不熟悉,请查看我们的入门指南。

变量

在定义我们的部署流程之前,这两个功能都需要定义一些变量:

  • 项目。Azure.Storage.ConnectionString
  • 项目。Azure .存储.队列.名称
  • 项目。Slack.Url
  • 项目。松弛。频道。名称

项目。Azure.Storage.ConnectionString

这些函数需要 Azure 存储帐户的连接字符串才能访问队列。这个值可以通过导航到 Azure 存储帐户,然后是访问密钥来找到。默认情况下,Azure 会创建两个密钥,任何一个都可以。点击显示键复制连接字符串属性的值。

Microsoft Azure dashboard open on Access keys page with Show keys and Connection string highlighted

项目。Azure .存储.队列.名称

您之前创建的消息队列的名称。

项目。Slack.Url

这是 Slack 的集成 webhook URL。建议您将此变量设为敏感值。

项目。松弛。频道。名称

这是您希望函数发布到的松弛通道的名称。

部署流程

部署流程的步骤如下:

  • 部署接受消息功能
  • 部署流程消息功能

Screenshot of Process in Octopus showing step 1. Deploy Accept-Message Function and step 2. Deploy Process-Message Function

部署接受消息功能

添加一个部署 Azure 应用服务步骤。

Octopus dashboard showing Deploy an Azure App Service step being selected

填写该步骤的表单字段:

  • 代表:您的 Azure Web 应用目标被分配的角色
  • :包含接受消息功能的包
  • 应用程序设置:使用以下:
[
   {
      "name": "AZURE_STORAGE_CONNECTION_STRING",
      "value": "#{Project.Azure.Queue.ConnectionString}",
      "slotSetting": false
   },
   {
      "name": "QUEUE_NAME",
      "value": "#{Project.Azure.Storage.Queue.Name}",
      "slotSetting": false
   }
] 

部署流程消息功能

这使用与部署接受消息功能相同的步骤模板。填写该步骤的表单字段:

  • 代表:你的 Azure Web App 目标被分配的角色
  • :包含接受消息功能的包
  • 应用程序设置:使用以下:
[
   {
      "name": "SlackUrl",
      "value": "#{Project.Slack.Url}",
      "slotSetting": false
   },
   {
      "name": "SlackChannel",
      "value": "#{Project.Slack.Channel}",
      "slotSetting": false
   },
   {
      "name": "AzureWebJobsStorage",
      "value": "#{Project.Azure.Queue.ConnectionString}",
      "slotSetting": false
   }
] 

部署

部署函数看起来像这样:

Octopus dashboard showing Task Summary with green ticks for every step

要调用这个函数,需要从 Azure 获取函数 URL。

打开 Azure 门户,导航到您的功能。点击功能然后链接到您的功能。

点击获取功能 Url ,然后点击复制图标。这个留着以后用。

测试通知

部署完函数后,您可以在 Octopus Deploy 中配置一个订阅,以便在变量发生变化时通知您。

要配置订阅,点击配置,然后订阅,然后添加订阅

填写以下字段

  • 名称:给订阅命名
  • 事件过滤器:从文档类型下拉列表中选择变量集
  • PayloadURL :从 Azure 粘贴函数 URL

点击保存

现在,当变量发生变化时,您就可以接收时差通知了。要对此进行测试,请更新一个变量。几秒钟后,Octopus 将处理订阅,调用 Azure 函数将 Octopus 有效负载放入队列。Process-Message 函数将触发并发布到 Slack。

结论

这篇文章演示了如何使用 Octopus Deploy 订阅来调用 Azure 函数,以便在变量发生变化时向 Slack 发布消息。

愉快的部署!

Octopus 部署中的变量使用- Octopus 部署

原文:https://octopus.com/blog/variable-use

许多客户要求能够看到变量在 Octopus Deploy 中的使用位置。

在这篇文章中,我探讨了寻找所有可能使用变量的地方的一些挑战。

为什么人们希望看到变量的使用

人们希望看到变量使用的原因有很多:

  • 确定变量的用途。从变量名来看可能不明显。
  • 更改变量值。确定变量的使用位置有助于确定值更改的影响以及更改是否安全。
  • 删除未使用的变量。识别所有潜在的变量用途将有助于确定变量是否未被使用并且可以被安全地删除。

这些用例涉及到 ifwhere 变量的值被实际使用。当你改变一个变量的值或者删除未使用的变量时,识别出所有的潜在用途是非常重要的。

人们希望看到变量使用的相关原因:

  • 识别重复变量(通过名称或值)。这允许将重复变量提取到库变量集中,并在使用它们的项目之间共享。
  • 识别冲突范围问题,例如,生命周期中环境的不确定性结果或缺失值。
  • 识别应该敏感的纯文本变量,例如,包含passwordpwdtokenapikey的变量名,或者包含看起来像 API 键的值的变量名,并建议将它们更改为 sensitive。

这些用例关心哪些变量存在以及它们的是什么。他们不关心它们到底用在哪里。虽然支持这些用例可能有价值,但我们不会在本文中考虑它们,因为它们与变量使用没有直接关系。

让我们看看如何找到变量的用法,以及在这个过程中遇到的一些挑战。

静态搜索

静态搜索将使用当前的 API 来收集关于变量使用位置的信息。可以搜索项目变量和库变量集。使用当前 API 可以确定以下用途:

  • 可变值
  • 项目部署流程或任何操作手册中的步骤参数
  • 在步骤脚本中使用

对于变量值,我们在试图找到所有的用法时会遇到问题。乍一看,在变量值中寻找变量的用途应该很容易——只需搜索#{variablename}。然而,变量值可以使用 Octostache 表达式来定义,这是 Octopus Deploy 的变量替换语法。表达式非常灵活,允许一些奇怪和奇妙的边界情况,特别是当使用扩展语法时。例如,考虑这些变量及其值:

可变的 价值
Greeting.English Hello
Greeting.Māori Kia ora
Language English
Greeting #{Greeting.#{Language}}

在这个例子中,没有简单的方法来确定在哪里使用了Greeting.English变量。Step 属性也支持 Octostache 表达式,因此也有同样的问题。

还有许多其他的八分音符表达式会使任何寻找变量用法的尝试出错。表达式允许灵活和创造性地使用变量,但是它们很难找到所有变量的用法。

步骤脚本中的变量使用(例如,运行脚本步骤的内联源代码属性)也会导致问题。变量名几乎可以是任何东西,并且可以动态访问。例如,我们有以下变量名:

  • *(_🤦_*(& KL" P{}$^[p]'!(这确实是一个有效的变量名)
  • FrankieSay"Relax"
  • PowerMax

这个 PowerShell 脚本:

# *(_🤦_*(& KL" P{}$^[p]'! is a silly variable name.
Write-Host $OctopusParameters["FrankieSay""Relax"""]
$powerLevel = 'Max'
Write-Host $OctopusParameters["Power" + $powerLevel] 

对变量名进行直接文本搜索会对*(_🤦_*(& KL" P{}$^[p]'!产生误报,因为它在注释中。它还遗漏了FrankieSay"Relax"PowerMax,因为它们被运行时生成的名称引用。也许可以使用更智能的搜索来处理上面的一些问题,但就像 Octostache 表达式一样,还有许多(也许是无穷无尽的)变体也会导致问题。想把他们都抓起来感觉是不可能的。

其他静态搜索选项

目前,如果一个项目使用 Config 作为代码进行版本控制,那么部署过程和变量是在文本文件中定义的。在任何文本编辑器中,通过简单的文本搜索都可以找到变量的用法(受上述限制)。我们计划将来将 run book 作为代码添加到 Config 中,这意味着文本搜索也可以在 run book 中找到类似的变量用法。

非版本控制项目的另一个选择是使用 PowerShell 脚本来查询当前的 API 并搜索变量用途。下面是两个示例脚本。它们只执行直接的文本搜索,所以它们受到上述所有限制的约束,并且不能保证找到所有变量的用法。

部署时搜索

这种类型的搜索会在部署过程中捕获变量使用。在所有部署步骤中,有 3 种功能可以使用变量来执行各种类型的替换:

  • 结构化配置变量
  • 。净配置变量
  • 模板中的替代变量

每次变量匹配和替换完成后,详细信息会被发送回 Octopus 服务器并存储起来。这种方法的一个优点是,它可以从 Octopus Deploy 之外的文件中捕获替换。将基础设施构建到 Octopus Deploy 中以支持这种类型的搜索并不简单。

考虑这种类型的搜索如何工作是很有趣的。例如,步骤可以是有条件的,并且可能只在生产部署期间执行。因此,可能需要多次部署来捕获所有可变用途。Octopus Deploy 应该尝试从多个部署中获取不同的用途,并以某种方式整理它们吗?应该在什么时候重置它们?用户如何确信已经捕获了所有可能部署类型的使用?

最终,无论这个特性是如何实现的,它都需要用户仔细设置和输入,以捕捉不同的用途。这不会像点击一下就能得到所有的用途那么简单。

结论

静态搜索和部署时搜索都会捕获不同类型的变量使用。两者都不能保证涵盖所有的用途,但它们在大多数情况下会涵盖大多数用途。遗漏的用途大多是变量被不寻常或创造性地使用的地方。

现在的问题是,我们应该建造它吗?一个特性是否有足够的价值让可能在大部分时间都能工作?为了确信一个变量真的没有被使用并且可以被安全地移除,所有的变量使用都需要被识别——这是不能保证的。即使这种限制在用户界面中表现得非常明显,也不可避免地会让一些用户感到困惑。

由于构建部署时搜索需要大量的工作,并且不容易使用,我们应该只构建静态搜索吗?这感觉像是一个解决方案的一半。当已经可以用 PowerShell 脚本实现相同的功能时,向 UI 添加静态搜索有多大价值?

你怎么想呢?我们希望在下面的评论中听到你的想法。

愉快的部署!

使用 JSON - Octopus Deploy 验证应用程序设置

原文:https://octopus.com/blog/verify-appsettings-json

Illustration showing Octopus variables being scanned

在我的上一篇文章中,我展示了如何验证 web.config 文件中的所有应用程序设置都有相应的 Octopus 项目变量,以及如何确保所有为变量替换而配置的文件不会留下任何占位符。这篇文章将重点验证存储在 JSON 配置文件中的应用程序设置。在这种情况下我们看到的是。NET Core appSettings.json 文件,但它可以扩展到任何 json 配置文件。

比较环境文件

像 appSettings . development . JSON 这样的. NET 核心应用程序的环境应用程序设置文件并不少见。Deveopment.json 文件,也需要将其添加到 appSettings.json 文件中。为了解决这个问题,我们可以创建一个带有递归调用的 PowerShell 函数来遍历 JSON 文件键,然后将条目与环境文件进行比较。与上一篇文章类似,我们可以使用$settingsToIgnore数组定义我们想要忽略的任何设置:

Function Get-AppSettings
{
    # Define parameters
    param($jsonObject,
        $parentNamespace,
        $settingsToIgnore)

    $namespace = ""
    $tempArray = @()

    if (![string]::IsNullOrEmpty($jsonObject) -and ($jsonObject.GetType().Name -eq "PSCustomObject"))
    {

        # Get number of properties
        $properties = $jsonObject | Get-Member -MemberType Properties | Select-Object -ExpandProperty Name

        # Check to see if it has properties
        if ($properties.Length -gt 0)
        {
            # Loop through returned properties
            foreach($property in $properties)
            {
                # Make sure we not supposed to ignore it
                if ($null -eq $parentNamespace)
                {
                    # Assign the property value
                    $namespace = $property
                }
                else
                {
                    $namespace = "$parentNamespace.$property"
                }

                # Make sure we're not supposed to ignore it
                if ($settingsToIgnore -notcontains $namespace)
                {
                    # Add the namespace to the array
                    $tempArray += $namespace

                    # Add the returned array from the recursive call
                    $tempArray += Get-AppSettings -jsonObject ($jsonObject.$property) -parentNamespace $namespace -settingsToIgnore $settingsToIgnore
                }
            }
        }
        else
        {
            # Add the value to the array
            if ($null -eq $parentNamespace)
            {
                $namespace = $property
            }
            else
            {
                $namespace = "$parentNamespace.$property"
            }

            $tempArray += $namespace
        }
    }

    return $tempArray
}

# Load the json file
$appSettingsFile = Get-Content -Path (Get-ChildItem -Path $OctopusParameters['Octopus.Action.Package.InstallationDirectoryPath'] | Where-Object {$_.Name -eq "appsettigs.config"}).FullName | ConvertFrom-Json

# Define working variable
$appSettings = @()
$settingsToIgnore = @("octofront.azure")

# Get all the json keys
$appSettings = Get-AppSettings -jsonObject $appSettingsFile -parentNamespace $null -settingsToIgnore $settingsToIgnore

# Get all files that are appsettings.something.json
$otherAppSettingsFiles = Get-ChildItem -Path  $OctopusParameters['Octopus.Action.Package.InstallationDirectoryPath']  | Where-Object {$_.Name -match "^appSettings.*..json"}

# Loop through other files
foreach ($otherFile in $otherAppSettingsFiles)
{
    # Convert the json to PowerShell objects
    $otherSettingsFile = Get-Content -Path $otherFile.FullName  | ConvertFrom-Json

    # Get the json keys
    $otherSettings = Get-AppSettings -jsonObject $otherSettingsFile -parentNamespace $null -settingsToIgnore $settingsToIgnore

    # Compare the properties to see if they are identical
    $results = Compare-Object -ReferenceObject $appSettings -DifferenceObject $otherSettings

    # Check to see if something was returned
    if ($null -ne $results)
    {
        # Extract results
        $resultsError = $results | Out-String

        throw "Differences found in $($otherFile.FullName)`n $resultsError"
    }
} 

现在,如果我们忘记向主 appsettings.json 文件添加设置,我们可以检测到它并阻止部署执行!

确保应用程序设置有 Octopus 参数

使用上面定义的函数Get-AppSettings$settingsToIgnore数组,我们可以使用下面的代码来确保 App Settings 中定义的设置具有相应的 Octopus Deploy 变量:

# Load the json file
$appSettingsFile = Get-Content -Path (Get-ChildItem -Path $OctopusParameters['Octopus.Action.Package.InstallationDirectoryPath'] | Where-Object {$_.Name -eq "appsettigs.config"}).FullName | ConvertFrom-Json

# Define working variable
$appSettings = @()
$settingsToIgnore = @("octofront.azure")

# Get all the json keys
$appSettings = Get-AppSettings -jsonObject $appSettingsFile -parentNamespace $null -settingsToIgnore $settingsToIgnore

foreach ($appSetting in $appSettings)
{
    # Check to see if key is present
    if (!$OctopusParameters.ContainsKey($appSetting.Replace(".", ":"))) # Variables have : delimeters for nested json keys
    {
        # Fail the deployment
        throw "Octopus Parameter collection does not contain a value for $($appSetting)"
    }    
} 

有了这些步骤,您可以帮助防止部署在。json 配置文件。当然,这个解决方案并没有涵盖所有的场景,但是希望它能让您了解可以采取的预防措施。

愉快的部署!

验证 AppSettings 或变量替换- Octopus 部署

原文:https://octopus.com/blog/verify-appsettings-or-variable-replacement

介绍

没有什么比部署过程中出现意外更糟糕的了,见鬼,这也是 Octopus Deploy 存在的原因之一!为了应对意外情况,Octopus Deploy 通过确保不同环境之间的部署过程始终相同来提供一致性和可靠性。然而,当部署到不同的环境时,有一件事是可以改变的,那就是变量。由于变量可以从一个环境改变到下一个环境,我们需要检查我们是否拼写了相同的变量,并相应地确定了它们的范围。这篇文章提供了一些如何在部署步骤中检测缺失或未改变的变量的例子。

。网。配置文件

. config 文件中的值通常需要根据软件部署的环境而有所不同,例如连接字符串或应用程序设置。这些。配置文件通常由可能无法访问 Octopus Deploy 的开发人员维护。在这种情况下,很容易错过变量的更新或添加,直到应用程序部署到生产环境中,问题才可能被注意到。

检查所有应用程序设置

任何提供预先部署脚本特性的步骤模板都可以与这个解决方案一起工作。对于本例,我们将使用部署到 IIS 步骤模板。将部署到 IIS 步骤添加到流程中后,单击配置功能按钮:

接下来,启用定制部署脚本特性:

现在,展开步骤模板的自定义部署脚本部分:

在“预先部署脚本”窗口中输入以下代码:

# Get the config file
$configFile = Get-ChildItem -Path $OctopusParameters['Octopus.Action.Package.InstallationDirectoryPath'] | Where-Object {$_.Name -eq "web.config"}

# Create FileMap object
$fileMap = New-Object System.Configuration.ExeConfigurationFileMap

# Set location of exe config file
$fileMap.ExeConfigFilename = $configFile.FullName

# Create Configuration Manager object
$configManager = [System.Configuration.ConfigurationManager]::OpenMappedExeConfiguration($fileMap, [System.Configuration.ConfigurationUserLevel]::None)

# Iterate through appSettings collection
foreach($appSetting in $configManager.AppSettings.Settings)
{
    # Check to see if key is present
    if (!$OctopusParameters.ContainsKey($appSetting.Key))
    {
        # Fail the deployment
        throw "Octopus Parameter collection does not contain a value for $($appSetting.Key)"
    }
} 

就是这样!现在,在部署步骤之前,我们检查以确保所有的应用程序设置键都存在于 Octopus 参数集合中,如果没有找到,则部署失败!

静态 App 设置呢?

奇妙的问题!只有当您将所有应用程序设置都定义为 Octopus Deploy 变量时,上面的例子才有效。在很多情况下,你有一个永远不会改变的应用程序设置的静态列表,让它们成为变量是没有意义的。为此,我们仍然可以使用相同的方法,但是要调整它以排除一个键值列表:

# Define array of app settings to ignore
$settingsToIgnore = @("somesetting", "someothersetting")

# Get the config file
$configFile = Get-ChildItem -Path $OctopusParameters['Octopus.Action.Package.InstallationDirectoryPath'] | Where-Object {$_.Name -eq "web.config"}

# Create FileMap object
$fileMap = New-Object System.Configuration.ExeConfigurationFileMap

# Set location of exe config file
$fileMap.ExeConfigFilename = $configFile.FullName

# Create Configuration Manager object
$configManager = [System.Configuration.ConfigurationManager]::OpenMappedExeConfiguration($fileMap, [System.Configuration.ConfigurationUserLevel]::None)

# Iterate through appSettings collection
foreach($appSetting in $configManager.AppSettings.Settings)
{
    # Check to see if key is present
    if ((!$OctopusParameters.ContainsKey($appSetting.Key)) -and ($settingsToIgnore -notcontains $appSetting.Key))
    {
        # Fail the deployment
        throw "Octopus Parameter collection does not contain a value for $($appSetting.Key)"
    }
} 

这个版本的代码将忽略在$settingsToIgnore数组中定义的任何设置。这样你就忽略了所有你不关心的东西,但是抓住了任何可能已经被添加的新东西。

替换文件中的变量

处理变量时的另一个常见问题是文件中的替代变量特性。当使用替换文件中的变量特性时,如果 Octopus Deploy 在集合中有匹配的变量,它将只替换文件中的变量占位符。如果文件中的占位符在集合中没有匹配的变量,Octopus Deploy 不会警告您。可以想象,这可能会很成问题。

变量替换发生在部署阶段,所以在这种情况下我们不能使用预先部署组件。好消息是,部署自定义脚本在预部署之后、步骤处理之前执行,因此在部署到 IIS 步骤交换到新创建的文件夹之前,我们仍然可以使部署失败(注意,如果您使用自定义安装目录,这可能无法按预期工作)。与之前一样,我们将展开部署到 IIS 步骤的自定义部署脚本部分,并将以下代码粘贴到部署脚本窗口中:

function CheckSubstitutions($file)
{
    Write-Output "Verifying file $file"

    # Check to make sure file exists
    if ((Test-Path -Path "$file" -PathType leaf) -eq $true)
    {       
        # Read file
        $stringData = Get-Content -Path "$file" -Raw

        # Find placeholders
        $placeholders = [regex]::Matches($stringData, "(#{.*?})")

        # Check for token
        if ($placeholders.Count -gt 0)
        {
            # Something wasn't transformed
            throw "$file still contains #{} syntax. $placeholders"
        }
    }
    else
    {
        # Display message
        Write-Output "Unable to find file $file."
    }
}

# Get list of files that were specified for substitution
$fileList = $OctopusParameters['Octopus.Action.SubstituteInFiles.TargetFiles'].Split([Environment]::NewLine)

# Get base install path
$basePath = $OctopusParameters['Octopus.Action.Package.InstallationDirectoryPath']

# Ensure basePath ends with a \
if (!$basePath.EndsWith("\"))
{
    # Add ending slash
    $basePath += "\"
}

# Loop through list of files that were marked for substitution
foreach ($file in $fileList)
{
    if ($file -Match "\*")
    {
        Write-Output "$file contains wildcard. Get files by mask $($basePath + $file)"
        $files = Get-ChildItem -Path "$($basePath + $file)"

        foreach ($childFile in $files)
        {
            CheckSubstitutions($childFile)
        }
    }
    else
    {
        CheckSubstitutions($($basePath + $file))
    }
} 

现在你知道了!现在,您可以主动验证文件的占位符是否被替换为值!

结论

范围和拼写错误总是困扰任何自动化部署系统的变量组件。这篇文章中提供的例子应该有助于缓解常见的问题。

愉快的部署!

使用运行手册验证备份- Octopus Deploy

原文:https://octopus.com/blog/verifying-backups-with-runbooks

如今,是否应该备份已经不再是一个问题。对于任何认识到数据价值的现代组织来说,定期备份程序都是不可或缺的。许多组织甚至将他们的备份介质转移到另一个站点,以确保一个位置的物理灾难不会破坏他们的数据。

但不太常见的是强大的备份验证流程。备份恢复过程的细微之处通常只有在灾难发生时才能解决。也正是在灾难发生时,组织发现自己怀疑是否以正确的格式备份了正确的数据。不用说,这些都不是 DevOps 人员在危机期间试图恢复运营时想要面对的问题。

Runbooks 为这些问题提供了一个便捷的解决方案。通过在一次性环境中自动执行并定期执行恢复备份的过程,DevOps 团队可以确信他们的备份是有效的,并且恢复系统的过程是经过充分演练的。

几年前,我们发布了一个博客系列,记录了为 Java 应用构建 CI/CD 和操作管道的过程。本系列的最后是一个旨在备份 MySQL 数据库的 runbook。虽然这是一个很有价值的建议,但这篇文章犯了同样的错误,认为创建一个备份并将其保存在异地就是故事的结尾。

在这篇文章中,您将学习如何通过在定制的 runbook 中使用真实的 MySQL 数据库来验证备份,从而完成备份周期。

查看运行手册

这篇文章中描述的 runbook 已经被部署到这个公共的 Octopus 实例中。点击我是客人查看操作手册和之前执行产生的输出。

将备份视为可部署的工件

上一篇文章中描述的数据库被部署到一个 Kubernetes 集群中。如前一篇文章所述,执行备份的过程包括在 MySQL 容器中运行mysqldump客户端,转储数据库表,并将结果文件上传到一个更永久的位置。那个地方是一个 S3 桶。

然而,验证备份文件意味着像对待任何其他可部署的工件一样对待它。用于部署您的应用程序的工件被版本化并保存在允许查询和比较版本的存储库中。从概念上讲,您的数据库备份应该只是另一个可部署和版本化的工件,随时可以被 runbook 查询和使用。

在实践中,备份工件需要上传到存储库,而不是简单的文件存储。这并不意味着不再使用像 S3 这样的服务,因为你可以很容易地将 S3 格式化为一个专家知识库。然而,出于本文的目的,您将备份文件直接上传到 Octopus 内置提要。

您可以使用 Octopus CLI 最轻松地将文件上传到 Octopus。下面的Dockerfile在 MySQL 服务器旁边安装 Octopus 和 AWS CLIs:

FROM mysql
RUN apt-get update; apt-get install python python-pip -y
RUN pip install awscli
RUN apt update && apt install --no-install-recommends gnupg curl ca-certificates apt-transport-https -y && \
curl -sSfL https://apt.octopus.com/public.key | apt-key add - && \
sh -c "echo deb https://apt.octopus.com/ stable main > /etc/apt/sources.list.d/octopus.com.list" && \
apt update && apt install octopuscli -y 

备份数据库

下面的 Bash 脚本定位名称以“mysql”开头的第一个 pod,执行mysqldump以执行备份,使用 Octopus CLI 将 sql 文件打包为工件,并将工件推送到 Octopus 服务器:

POD=$(kubectl get pods -o json | jq -r '[.items[]|select(.metadata.name | startswith("mysql"))][0].metadata.name')
VERSION=$(date +"%Y.%m.%d")
kubectl exec $POD -- /bin/sh -c 'cd /tmp; mysqldump -u root -p#{MySQL Password} petclinic > dump.sql 2> /dev/null'
kubectl exec $POD -- /bin/sh -c "cd /tmp; octo pack --overwrite --include dump.sql --id PetClinicDB --version ${VERSION} --format zip"
kubectl exec $POD -- /bin/sh -c "cd /tmp; octo push --package PetClinicDB.${VERSION}.zip --overwrite-mode OverwriteExisting --server https://tenpillars.octopus.app --apiKey #{Octopus API Key} --space #{Octopus.Space.Name}" 

这个脚本的最终结果是 Octopus 内置提要中的版本化备份工件:

SQL backup artifacts

验证备份

既然您的数据库备份已经版本化并上传到存储库,就像任何其他可部署的工件一样,在 runbooks 中使用它们变得更加容易。下一步是自动化在短暂(一次性)环境中验证备份的过程。

Docker 提供了一个完美的平台来启动和关闭一个测试数据库。这是因为 Docker 容器在设计上是独立和自包含的,允许您协调备份恢复并在事后清理一切。

下面的 Bash 脚本执行以下操作:

  • 创建一个 MySQL Docker 容器
  • 暴露主机上随机端口的内部端口 3306
  • 提取随机端口号
  • 等待 MySQL 服务器启动
  • 还原备份
  • 在数据库中查询已知要包括在备份中的记录
  • 移除 Docker 容器
  • 验证先前 SQL 查询的结果
# Run docker mapping port 3306 to a random port
docker run -p 3306 --name mysql-#{Octopus.RunbookRun.Id | ToLower} -e MYSQL_ROOT_PASSWORD=Password01! -d mysql

# Extract the random port Docker mapped to 3306
PORT=$(docker port mysql-#{Octopus.RunbookRun.Id | ToLower})
IFS=: read -r BINDING MYSQLPORT <<< "$PORT"
echo "mysql-#{Octopus.RunbookRun.Id | ToLower} listening on $MYSQLPORT"

echo "Waiting for MySQL to start"
while ! echo exit | nc localhost $MYSQLPORT >/dev/null 2>&1; do echo "sleeping..."; sleep 10; done
echo "Sleep a little longer to allow MySQL to finish booting"
sleep 20

echo "Restoring the database"
docker exec mysql-#{Octopus.RunbookRun.Id | ToLower} mysql -u root -p#{MySQL Password} -e "CREATE DATABASE petclinic;" 2>/dev/null
cat PetClinicDB/dump.sql | docker exec -i mysql-#{Octopus.RunbookRun.Id | ToLower} /usr/bin/mysql -u root -p#{MySQL Password} petclinic 2>/dev/null

echo "Query the database"
COUNT=$(docker exec mysql-#{Octopus.RunbookRun.Id | ToLower} mysql -u root -p#{MySQL Password} petclinic -s -e "select count(*) from owners;" 2>/dev/null)
echo "Table owners has $COUNT rows"

echo "Shutting the container down"
docker stop mysql-#{Octopus.RunbookRun.Id | ToLower}
docker rm mysql-#{Octopus.RunbookRun.Id | ToLower}

if [[ "$COUNT" -eq 0 ]]; then
    # If there were no rows returned, something went wrong and the backup is not valid
    exit 1
fi 

该脚本作为常规的运行脚本步骤在 runbook 中运行,并带有下载 SQL 备份工件的附加包引用:

Additional package reference

像引用任何其他可部署工件一样引用备份允许您在创建 runbook 运行时选择要验证的备份,或者默认接受最新版本:

Selecting the package

结论

备份的好坏取决于它们的恢复能力,但往往在危机期间才发现恢复备份的细微之处,这让 DevOps 团队怀疑备份是否包含正确格式的正确数据。

通过像对待任何其他可部署工件一样对待备份工件,并自动化恢复和验证备份数据的流程,DevOps 团队可以将他们的恢复流程作为常规备份生命周期的一部分进行优化,并确信他们可以从任何涉及数据丢失的场景中快速恢复。

在本文中,您看到了生成数据库备份、将它们上传到工件存储库、将备份作为可部署工件使用,以及验证将数据恢复到短暂环境的过程的示例操作手册。最终结果是,备份工作流将数据恢复视为另一项例行的自动化任务。

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

愉快的部署!

Octopus Deploy 2018 版本变更- Octopus Deploy

原文:https://octopus.com/blog/version-change-2018

几天前,Paul 发布了一个关于 Octopus Deploy 2017 年的回顾,随后是我们 2018 年的路线图。在这篇文章中,我将讨论为什么我们决定将 Octopus Deploy 的版本策略与每月发布的节奏保持一致。

但是与其花太多的时间来描述原因和好处,我想描述一下我们是如何走到这一步的——也许这对你也有帮助?

Roadmap for 2018

我们正在改变什么

不多,真的。下面是我们计划改变的简要总结:

  • 我们将在 2018 年发布第一版 Octopus 2018.1,而不是在几天后发布 Octopus 4.2
  • 尽管从4.x2018.x的跳跃可能看起来意义重大,但除了我们计划作为章鱼4.2发布的新功能之外,代码并没有从章鱼4.1发生重大变化
  • 定价和许可保持不变
  • 我们仍计划每月发货一次
  • 我们将在每个日历年的第一次发布时删除版本的主要部分(第一个数字)
  • 我们将在每月发布的节奏上增加版本的次要部分(第二个数字);除非我们因为某些原因跳过了一个月
  • 我们仍然会在这个月发布补丁,包括一些小的改进和错误修复

去了又回来

改变我们的版本策略的决定实际上是我们作为一个软件产品公司发展的副产品,我认为值得分享一些故事,以防对您的情况有所帮助。

我们喜欢用实验的方法来经营我们的公司。我们不会纯粹假设新的方式会比旧的方式更好,或者盲目地跟随他人的脚步来做出决定,而是花时间来反思我们过去所做的改变,并决定如何从那里进行有机的迭代。我们最好用某种可量化的指标来做这件事。找到最适合我们的可能需要更长的时间,但是最大的成功是:我们理解为什么我们以我们的方式做事。转述一下切斯特顿栅栏的负责人:

在你拆掉围墙之前,首先要明白为什么要把它放在那里。

如果 G. K .切斯特顿是 2018 年的开发者,他可能会说:

在你阅读 git 历史之前,不要拆掉围墙!

也许我们故事的一部分会对你自己的情况有所帮助?

版本化事物

决定如何对事物进行版本化证明是一个比我最初设想的更棘手的问题。一路走来,我们尝试了:

  • 0.1开始,从那里开始做当时觉得正确的事情
  • 对所有事情使用严格的 SemVer
  • 区分“产品版本化”(版本更多地与营销和沟通联系在一起)和“组件版本化”(遵循语义版本化)
  • 让版本反映每月和每年的节奏,中间有补丁

每种方法都有一些优点和一些缺点。

做当时觉得正确的事

从 Octopus 1.x2.x再到3.x的变化都是合理的:我们做了大的架构改变,增加了主要功能,有时还会破坏兼容性。

3.0之后,我们在3.3之前发布了几个特性版本,并最终致力于构建对多租户部署的一流支持。在某个时间点,我们承诺在 Octopus 3.4中发布多租户部署,并坚持使用该版本。回想起来,我认为这个版本应该是章鱼4.0。这是对产品及其功能的重大改变,从开始到结束花了六个月的时间。

在许多方面,这种方法是可行的,但它也让人感觉非常随意,没有一套清晰的指导方针来推动我们的决策。这导致了大版本变化时的一些内部紧张,并使我们的版本不可预测。

高度自治和永久自治

多租户部署是一种入侵性的变化,它就像一个代码坝,阻碍了其他功能,直到它完成,因为一切都开始依赖于这些代码变化。在那次经历的基础上,我们决定让团队在高度自治的情况下工作,一完成就把较小的功能独立地发布给彼此。

我们还想使用一种语义版本化的形式来帮助指导我们决定每个版本的版本应该是什么样子。我们为 Octopus Deploy 产品采用了这个策略,我们的每个库和其他组件(比如触手)都将使用语义版本控制。

同样,这种方法通常适用于版本控制,但是我们发现我们的团队过于独立。我们最终运送 Octopus 3.93.10仅相隔几天,一周后又运送 Octopus 3.11

Octopus 3.9, 3.10, and 3.11 merely days apart

就其本身而言,我们并不认为这是一个问题——代码很好,而且这些变化对我们和我们的客户都很重要...但是我们已经优化以减少内部摩擦,代价是外部的可预测性和我们讲述一个有凝聚力的产品故事的能力。

营销和沟通受到影响

我们是如此自主地工作,摩擦如此之少,以至于我们团队的一半人甚至没有意识到我们已经达到了3.11,并且我们已经为发布了管理和部署 X.509 证书的特性——可以说这是 Octopus 能为你做的最好的事情之一——而我们并没有真正充分利用这个机会来宣扬它!同时,我们在类型化变量上犯了一个小错误,两个团队在解决同一个问题时采用了稍微不同的方法。

这不是版本问题,这是一个计划问题!我们运送的东西真的很好,但我们并没有刻意保持凝聚力和一致性。

开始节奏

2017 年 4 月,我们承诺从 Octopus Deploy 3.12 开始,每月发布一个新的 Octopus Deploy“功能”版本。在那篇文章中:

这意味着我们将按照您可以信赖的可预测的时间表发布新版本。这些版本将包括新的特性,以及当月所有补丁的汇总。我们做这个实验的目的如下:

注意:我们仍然会继续在补丁中发布小的改进和错误修复,只要它们准备好了。我们希望尽快将这些改变交到你们手中,这样就不会改变。

自 2017 年 4 月以来,我们每月大约在同一时间发布一个新功能版本。我们认为它帮助我们更好地规划对产品的更小的、增量的和有价值的改变。

这种节奏有助于我们更好地计划,但它不是灵丹妙药。每个团队会自主地找到他们能创造的最高价值的东西,并着手去创造它们。在每个月初,我们会查看哪些产品即将发布,然后为该版本构建一个有凝聚力的故事。我们有几个月没有准备好发布,所以我们推迟了几周发布。

使用这种方法,我们发布了 Octopus 3.12 - 3.17所有有价值的功能,最终当我们在一个大版本中发布了新用户界面一大堆其他有价值的功能时,我们升级到了4.0

一个缺失的部分:故意排列

我故意用故意这个词。我们最终发布了一些非常好的版本,这些版本的内聚性更多的是一个快乐的意外,而不是刻意的设计。2017 年底,我们开始思考下一个有机步骤。我们真的希望 Octopus Deploy 在 2018 年向拥抱 DevOps 文化的人们讲述一个有凝聚力的相关故事。

我们的每个团队仍然在高度自治的情况下工作,但随着 2018 年的进展,我们已经迭代到每个团队都在努力讲述一个有凝聚力的故事的地方。

结论

没有太多变化。定价和许可保持不变。我们只是将我们的版本号与我们的月度和年度节奏保持一致。这意味着我们将不再使用我们的主版本号来传达推断的含义,就这样。现在,我们被迫对产品进行更小、更多的增量更改,这对每个人都有好处。

无聊的部分结束了,我认为这篇文章的真正结论是:持续交付是变革的高效代理。通过继续信奉我们公司自己的核心价值观之一,我们多次重新审视我们的版本战略,并从根本上改变了我们规划、设计、构建和交付我们自己的软件的方式。

向 GitHub 操作添加版本- Octopus Deploy

原文:https://octopus.com/blog/versioning-with-github-actions

Illustration showing GitHub CI processes with versions

GitHub Actions 正在以测试版的形式慢慢向用户推出。这个新特性让 GitHub 用户可以使用 GitHub 管理的基础设施直接从他们的代码中执行构建和部署。这为开发人员提供了很多机会,但是尽管动作非常强大和灵活,我还是立即遇到了版本控制的问题。

通常,CI 系统会包含某种递增计数器,可以用作任何构建的补丁发布。这意味着每个构建都会被自动分配一个新的编号,并且任何产生的工件都会继承一个有意义的版本。不幸的是,在 GitHub Actions 的当前测试版中,没有相当于构建号的内容。构建环境公开了信息,如 Git SHAs、存储库、用户和事件,但没有构建号。

这是不方便的,至少可以说,但有一个解决方案。

实现 GitVersion

GitVersion 是一个基于 Git 存储库中的标签生成 SemVer 版本号的工具。GitVersions 非常适合与 GitHub 操作一起使用,因为 Git 存储库本身就是版本控制的真实来源,除了 Git 客户端之外,不需要任何特殊工具来管理版本号。

GitVersion 还提供了一个 Docker 镜像,GitHub 动作可以直接使用。

这意味着,理论上,作为 GitHub Actions 工作流的一部分,我们拥有生成有意义的版本号所需的一切。然而在实践中,我们仍然有一些困难要克服。

GitHub 动作和共享变量

GitHub Actions 基于个人工作的理念。这些作业可以在底层虚拟机上运行,也可以在 Docker 容器中运行。以这种方式构建提供了很大的灵活性,但是不利的一面是,很难获取一项工作的输出并在另一项工作中使用它。CI 服务器通常通过用特殊标记捕获输出来解决这个问题。例如,TeamCity 可以观察格式为##teamcity[setParameter name='env.whatever' value='myvalue']的输出,并创建一个变量作为响应。不幸的是,GitHub Actions 的 beta 版不提供任何传递变量的支持。

我们在作业之间共享的是文件系统。直接在 VM 上运行的作业可以访问路径/home/runner/work/RepoName/RepoName/(其中RepoName被替换为 GitHub 库的名称)。对接作业具有安装到/github/workspace的相同路径。

我们可以使用这个共享文件系统来保存 GitVersion 生成的版本,然后在后面的步骤中消耗它。

示例构建

要了解这在实践中是如何工作的,请看下面的工作流定义。这是一个构建 Python AWS Lambda 应用程序的项目示例:

name: Python package

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v1
    - name: Get Git Version
      uses: docker://gittools/gitversion:5.0.2-beta1-27-linux-centos-7-netcoreapp2.2
      with:
        args: /github/workspace /nofetch /exec /bin/sh /execargs "-c \"echo $GitVersion_FullSemVer > /github/workspace/version.txt\""
    - name: Set up Python 3.7
      uses: actions/setup-python@v1
      with:
        python-version: 3.7
    - name: Package dependencies
      run: |
        python -m pip install --upgrade pip
        cd hello_world
        pip download -r requirements.txt
        unzip \*.whl
        rm *.whl
        ls -la
    - name: Extract Octopus Tools
      run: |
        mkdir /opt/octo
        cd /opt/octo
        wget -O /opt/octo/octopus.zip https://download.octopusdeploy.com/octopus-tools/6.12.0/OctopusTools.6.12.0.portable.zip
        unzip /opt/octo/octopus.zip
        chmod +x /opt/octo/Octo
    - name: Pack Application
      run: >-
        /opt/octo/Octo pack .
        --outFolder /home/runner/work/AWSSamExample/AWSSamExample
        --basePath /home/runner/work/AWSSamExample/AWSSamExample/hello_world
        --id AwsSamLambda
        --version $(cat /home/runner/work/AWSSamExample/AWSSamExample/version.txt)
        --format zip
    - name: Push to Octopus
      run: >-
        /opt/octo/Octo push
        --server ${{ secrets.MATTC_URL }}
        --apiKey ${{ secrets.MATTC_API_KEY }}
        --package /home/runner/work/AWSSamExample/AWSSamExample/AwsSamLambda.$(cat /home/runner/work/AWSSamExample/AWSSamExample/version.txt).zip
        --overwrite-mode IgnoreIfExists 

这个工作流的第一个有趣的部分是我们调用 GitVersion docker 映像的地方。

这里的技巧是调用 GitVersion,将生成的 SemVer 版本保存到一个文件中,而不是打印到控制台输出中。我们通过将/exec参数设置为/bin/sh,并将/execargs参数设置为"-c \"echo $GitVersion_FullSemVer > /github/workspace/version.txt\""来实现这一点。这些选项导致 GitVersion 执行 shell,shell 将环境变量GitVersion_FullSemVer(由 GitVersion 为我们定义)的值写入文件/github/workspace/version.txt

该作业的最终结果是一个名为/github/workspace/version.txt/home/runner/work/RepoName/RepoName/version.txt的文件,这取决于该作业是否在 Docker 容器中运行。

环境变量$GitVersion_FullSemVer只是 GitVersion 提供的众多变量之一。下面 JSON 中的任何字段都可以以GitVersion_为前缀,并作为环境变量读取。

{                                                           
  "Major":0,
  "Minor":1,
  "Patch":0,
  "PreReleaseTag":"",
  "PreReleaseTagWithDash":"",
  "PreReleaseLabel":"",
  "PreReleaseNumber":"",
  "WeightedPreReleaseNumber":"",
  "BuildMetaData":55,
  "BuildMetaDataPadded":"0055",
  "FullBuildMetaData":"55.Branch.master.Sha.3903750b2aa5d84fd6004b2244cdb491f45520d9",
  "MajorMinorPatch":"0.1.0",
  "SemVer":"0.1.0",
  "LegacySemVer":"0.1.0",
  "LegacySemVerPadded":"0.1.0",
  "AssemblySemVer":"0.1.0.0",
  "AssemblySemFileVer":"0.1.0.0",
  "FullSemVer":"0.1.0+55",
  "InformationalVersion":"0.1.0+55.Branch.master.Sha.3903750b2aa5d84fd6004b2244cdb491f45520d9",
  "BranchName":"master",
  "Sha":"3903750b2aa5d84fd6004b2244cdb491f45520d9",
  "ShortSha":3903750,
  "NuGetVersionV2":"0.1.0",
  "NuGetVersion":"0.1.0",
  "NuGetPreReleaseTagV2":"",
  "NuGetPreReleaseTag":"",
  "VersionSourceSha":"0f692a38449b853d8a04aa891ac48e63ebec1add",
  "CommitsSinceVersionSource":55,
  "CommitsSinceVersionSourcePadded":"0055",
  "CommitDate":"2019-08-21"
} 
- name: Get Git Version
  uses: docker://mcasperson/gitversion:5.0.2-linux-centos-7-netcoreapp2.2
  with:
    args: /github/workspace /nofetch /exec /bin/sh /execargs "-c \"echo $GitVersion_FullSemVer > /github/workspace/version.txt\"" 

为了使用该版本,我们将使用 Octopus CLI 工具。在以下作业中,我们下载并解压缩 CLI 包,以便在后续步骤中使用。

Octopus CLI 工具也可以作为 Docker image 使用,因此我们可以使用uses: docker://octopusdeploy/octo:6.12.0工作流中的这些工具。然而,直接调用 docker images 会使使用 shell 扩展变得困难,我们需要提取文件version.txt的内容,并将其作为命令行参数传递。这就是我们在本地提取工具的原因:

- name: Extract Octopus Tools
  run: |
    mkdir /opt/octo
    cd /opt/octo
    wget -O /opt/octo/octopus.zip https://download.octopusdeploy.com/octopus-tools/6.12.0/OctopusTools.6.12.0.portable.zip
    unzip /opt/octo/octopus.zip
    chmod +x /opt/octo/Octo 

提取 Octopus CLI 工具后,我们可以调用它们来打包应用程序。请注意我们如何使用带有参数--version $(cat /home/runner/work/AWSSamExample/AWSSamExample/version.txt)的 shell 扩展来读取 GitVersion 创建的version.txt文件的值(AWSSamExample是我的 GitHub repo 的名称)。这是我们在作业之间传递变量的方式:

- name: Pack Application
  run: >-
    /opt/octo/Octo pack .
    --outFolder /home/runner/work/AWSSamExample/AWSSamExample
    --basePath /home/runner/work/AWSSamExample/AWSSamExample/hello_world
    --id AwsSamLambda
    --version $(cat /home/runner/work/AWSSamExample/AWSSamExample/version.txt)
    --format zip 

我们以类似的方式使用 Octopus CLI 将生成的应用程序推送到 Octopus 服务器:

- name: Push to Octopus
  run: >-
    /opt/octo/Octo push
    --server ${{ secrets.MATTC_URL }}
    --apiKey ${{ secrets.MATTC_API_KEY }}
    --package /home/runner/work/AWSSamExample/AWSSamExample/AwsSamLambda.$(cat /home/runner/work/AWSSamExample/AWSSamExample/version.txt).zip
    --overwrite-mode IgnoreIfExists 

结论

GitHub 动作对开发者来说是一个强大的新功能,但是在测试版中有一些粗糙的边缘,你必须在生产场景中解决。GitVersion 为缺乏构建号提供了一个简洁的解决方案,并允许 GitHub actions 与 Octopus 等平台进行交互。

IIS 和 Windows 服务步骤,包括虚拟目录支持- Octopus 部署

原文:https://octopus.com/blog/virtual-dir-iis-windows-service-steps

在 Octopus Deploy 3.4.7 中,我们添加了:

IIS 和 Windows 服务部署步骤

部署 IIS 网站和 Windows 服务是我们的核心能力;我们的面包和黄油;我们存在的理由。
这是我们的工作。

然而,在 Octopus 中配置 IIS 或 Windows 服务部署步骤(直到现在)还有些模糊。
以前,必须创建一个部署包步骤,点击配置特性,并启用相关特性。

Enable IIS via features

我们意识到(尤其是对于那些第一次与 Octopus 互动的人来说)这种体验并没有想象中那么直观。

因此,我们添加了两种新的部署步骤类型:

Add Step dialog with new steps

现有的部署包步骤仍然可用。现有部署流程不会受到任何影响。

我们希望在添加部署步骤时,这些步骤能够更好地符合用户的目标。

支持部署到虚拟目录和 Web 应用程序

我们当前的任务之一是提供世界上最棒的 IIS 部署体验。

当我们考虑如何改进我们的 IIS 功能时,我们发现的第一个缺口(尽管这个缺口已经被我们的社区库很好地填补了)是对虚拟目录和 Web 应用程序的支持。

现在,当部署到 IIS 时,您可以选择:

  • IIS 网站
  • IIS 虚拟目录
  • IIS Web 应用程序

虚拟目录示例

Configuring Virtual Directory

Web 应用程序示例

Configuring Web Application

一如既往,欢迎所有反馈。请告诉我们你的想法。

Wanted:一种通用的应用程序打包格式。网络-章鱼部署

原文:https://octopus.com/blog/wanted-universal-packaging-format

最简单地说,任何自动化部署或连续交付解决方案的构建块都是:

Build and deployment processes

分解一下,我称之为“可构建的工件”通常指源代码,它应该存放在版本控制系统中。一个构建过程获取那些工件,并产生新的工件:准备好用一个部署过程部署的工件。这些工件通常是二进制文件,通常被打包成某种格式,并标有解释内容的元数据。

将构建过程与部署过程分离的想法非常重要。在他们的书连续交付中,Jez 和 David 认为你应该“只构建你的二进制文件一次”:

许多构建系统使用版本控制系统中保存的源代码作为许多步骤的规范源。代码将在不同的上下文中重复编译:在提交过程中,再次在验收测试时,再次为容量测试,并且通常为每个单独的部署目标编译一次。每次编译代码,你都有引入一些差异的风险。稍后阶段安装的编译器版本可能与您用于提交测试的版本不同。您可能会获得某个第三方库的不同版本,而这并不是您想要的。甚至编译器的配置也可能改变应用程序的行为。我们已经看到这些来源中的每一个都进入了生产阶段。

史前古器物

一旦我们构建了代码,我们就有了一组可以部署的二进制文件。如果我们要多次重新部署这些二进制文件,我们需要以一种我们可以轻松返回并找到它们的方式存储它们。毫无疑问,在您的旅途中,您会遇到一个带有文件夹的文件共享,看起来像这样:

  • WebApp 的副本
  • WebApp 的副本(2)
  • WebApp
  • WebApp1
  • WebApp-2012-06-19
  • web app-制作
  • WebApp-Mike(不要删除这是生产)

如果我们希望接近可重复的、自动化的、低风险的部署,适当地维护我们的可部署工件是必不可少的。我们需要考虑这些工件是如何构造的。

也许最常见的工件格式是将文件压缩到一个归档中,并用日期或版本号标记文件名。例如,这将是上述问题的一个更好的解决方案:

  • WebApp-1.0.0.zip
  • WebApp-1.0.1.zip
  • WebApp-1.0.2.zip
  • WebApp-1.0.3.zip

一个工件可能只包含部署所需的文件。然后,可以使用版本号或时间戳来找到关于内容的更多信息,例如,工件中包含的变更列表、构建者和时间、修复的问题等等。

在理想的情况下,这些“元数据”可以存储在工件内部,因此工件是自描述的。这就是 NuGet 之类的规格。nupkg 文件格式——它们不仅包含部署所需的原始二进制文件/文件,还包含描述它们的元数据。这就是为什么我们称它为打包格式而不是“只是另一个 zip 文件”。

Windows 平台上常见的自描述工件的例子有 NuGet 包和 Windows installer MSI 文件。

工件仓库

一旦我们有了这些自我描述的工件,下一个问题是,我们把它们放在哪里?在连续交付手册中,这个地方被称为一个工件库:

它是一个关键的资源,为您的每个候选版本存储二进制文件、报告和元数据。

有很多选择,从文件共享到 FTP 服务器到 NuGet 服务器到像 Nexus 这样的专用产品。

工件存储库的目的是:

  • 提供一个单独的地方来保存你的工件
  • 提供一种快速的方法来根据版本查找和检索工件

想象一个企业,每天都在构建数百个应用程序,生产数千个工件。找到和检索正确工件的能力对于部署的成功至关重要。

这如何应用于?净空间

当我设计 Octopus Deploy 时,我真的想坚持将构建和部署分开的目标;Octopus 是一个自动部署系统,而不是自动构建系统。所以我们消费人工制品的方式非常重要。

现在,想象一个每天都在构建大量项目的环境:

  • WPF 应用
  • ASP。将在内部网运行的. NET web 应用程序
  • Azure 云服务
  • Azure 网站
  • Windows 服务
  • Node.js 应用程序

哪种工件格式最适合打包所有这些应用程序类型?

ASP。通过右击并选择“打包”或“发布”,可以将. NET 应用程序打包到 Visual Studio 的 MSDeploy 包中。他们也可以通过使用一系列咒语和山羊献祭从命令行打包。不过,MSDeploy 包的元数据并不丰富(例如,包格式中没有版本号)。

Azure 云服务被打包成。cspkgs,另一种基于 ZIP 的文件格式,元数据有限。Windows 服务和桌面应用程序可以打包成 MSI。

不幸的是,所有这些格式都有问题。MSI 有版本号,但是对于许多应用程序类型来说并不理想。MSDeploy 包对于 ASP.NET 应用程序来说很棒,但是没有简单的方法为桌面应用程序创建它们,而且它们没有版本号。Azure 云服务包再次缺少版本号。

NuGet 是允许 Octopus 部署发生的突破,因为它提供了三样东西:

  • 一种自描述的包格式,具有非常丰富的元数据
  • 使它们创建起来相对快速和简单的工具(不像——我肯定有些人不同意——MSI 或 MSDeploy 包)
  • 一个标准的存储库接口,易于查询以查找工件

从概念上讲,任何应用程序都可以打包成一个 NuGet 包,并存储在一个 NuGet 存储库中。我看到 TeamCity 正在努力成为一个原生的 NuGet 库,并且我假设其他构建系统有一天也会这样做(除了 TFS,它总是最后一个)。

但是 NuGet 包也有自己的问题。这些约定主要是为了分发开源库,而不是需要运行的应用程序,所以打包应用程序可以打破这些约定。当涉及到较大的归档时,他们还会遇到性能问题。

为应用程序创建 NuGet 包也很难。虽然您可以右键单击并将 ASP.NET web 应用程序打包为 MSDeploy 包,但不能以这种方式创建 NuGet 包。像 TFS 这样的构建工具可以发布用于部署的 web 应用程序,但是它们很难自动打包。我们已经为 octo pack T1 工作了很长一段时间,试图让它变得更简单,但它还不够理想。

Azure 云服务可以从右键菜单或者通过 MSBuild 轻松打包,但是目前一个 Azure 项目无法使用 NuGet,所以使用 OctoPack 重新打包一个。cspkg 作为。nupkg 需要手动编辑项目文件。在 2013 年,在一个持续交付和开发的时代,这类任务应该会容易得多。

《我的愿望》

我希望看到一种通用的应用程序打包格式:

  • 适用于所有应用类型(ASP.NET 应用、Windows 服务、Azure 包、节点、Java)
  • 在微软堆栈上有非常好的工具支持(例如,不需要牺牲山羊就可以让它在 TFS 上的所有应用程序类型上工作),理想的情况是在其他堆栈上
  • 有一个定义良好的存储库接口,用于按版本查询和检索包

我不认为这种格式应该是特定于微软或 Octopus Deploy 的。我认为它应该类似于(甚至可以构建在)NuGet 之上,但是没有阻碍 NuGet 不仅仅用于打包库的约定。

实现这一目标需要什么?

网络研讨会:使用 VSTS 和 Octopus Deploy 实现自动化部署- Octopus Deploy

原文:https://octopus.com/blog/webinar-automating-deployments-vsts-octopus

Webinar: Automating deployments with VSTS and Octopus Deploy

更新:点击此处观看录制的网络研讨会!

如果您正在阅读这篇博客,您可能已经使用 Octopus Deploy 作为部署管道的一部分。在过去的几年里,我们付出了很大的努力,使其易于与您现有的构建引擎集成。

如果您正在使用 Visual Studio Team Services,我们希望向您展示这种集成变得多么容易!

集成的历史选项

长期以来,OctoPack 一直提供一种简单的方法来打包你的应用程序,并把它们推给 Octopus,为了以编程方式创建和部署版本,你可以使用 octo.exe 的 T2、API 和各种插件 T3。

TeamCity 用户已经有一个很棒的 Octopus 插件有一段时间了,但是直到最近,对于那些大量投资于全微软 ALM 堆栈的用户来说,这个选项还有点复杂。

但是事情变了!

Octopus 部署在 Visual Studio 市场中

微软已经在 Visual Studio Team Services 方面做了一些很好的工作,在此基础上,我们已经能够为该生态系统中的团队创建并发布一个真正有用的扩展。

在 VSTS(和 TFS)的新构建和发布系统意味着 Octopus 作为构建或发布管道的一部分的一流集成。VSTS 其他地方的扩展点意味着增加更多的机会!

简而言之,VSTS 和 Octopus Deploy 为端到端开发运维提供了一个引人注目的故事——从需求和工作项目一直到生产中运行的软件。


加入网络研讨会!

为了展示 VSTS 和 Octopus Deploy 的良好合作,我们将与 Brian A. Randell 和 Damian Brady 举办一次网络研讨会!

Watch the Webinar here

时间:3 月 21 日星期二,太平洋时间上午 9 点|美国东部时间下午 12 点(在我的时区,这是什么时间?)

我们将介绍的内容:

  • 安装延伸部分并连接到 Octopus
  • 如何使用扩展提供的每个构建任务
  • 设置内部版本的常用策略
  • 发行说明的提示和技巧
  • 最后:一个新的刚刚发布的扩展!

关于布莱恩:

Brian A. Randell 是一位著名的开发人员、演说家、作家和技术专家。他目前是咨询公司 MCW 技术有限责任公司的合伙人,也是 SaaS 教育平台公司 DuoMyth 的联合创始人。在超过 25 年的时间里,他一直在构建软件解决方案。他通过写作和培训向团队传授微软技术,既有面对面的也有按需提供的。他目前是 Visual Studio 和开发技术 MVP,专注于 DevOps、敏捷开发实践、虚拟化和 Microsoft Azure。不工作的时候,Brian 喜欢和他的妻子和两个孩子在一起,他们喜欢在 Xbox One 上让他看起来很糟糕(不管有没有 Kinect)。

确保你带着问题来,像往常一样,视频将在之后提供。

那里见!

我们 2020 年最受欢迎的网络研讨会- Octopus Deploy

原文:https://octopus.com/blog/webinar-of-the-year

Webinar of the year

网络研讨会是我们为客户和更广泛的 DevOps 社区分享和深入探讨特定主题的一种方式。在这篇文章中,我将看看我们如何举办网络研讨会,我们如何在 2020 年改进它们,以及如果你在假期工作并寻找一些公司,我们的主要建议。

您可以在 YouTube 网络研讨会播放列表上找到我们所有的 29 场网络研讨会。

2020 年顶级网络研讨会

在这一部分,我将列出一些我个人最喜欢的,以及章鱼投票选出的 2020 年最喜欢的。

将微服务容器部署到 Kubernetes

Kubernetes 是 DevOps 的热门话题。在本次网上技术交流讲座中,Shawn Sesna 向您介绍了 Kubernetes 的 101 项内容,以及如何使用 Octopus Deploy 来管理您的容器、pod、部署和变量管理。我们涵盖以下主题:

  • Kubernetes 的基本概念。
  • 使用 Octopus Deploy 将一个现有的应用程序部署为一个容器。
  • 了解 Octopus 如何在您从开发到测试再到生产环境的部署过程中自动更新您的 web 应用程序配置。

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

VIDEO

使用 AWS 和 runbooks 进行服务器配置

当从 AWS 上的 Cloudformation章鱼手册开始时,有很多东西让你摸不着头脑。CloudFormation 是一项强大的技术,它使得在 AWS 中按需提供和销毁基础设施变得很容易。我们涵盖以下主题:

  • 如何使用 Runbook Automation 供应和拆除 Linux 服务器
  • 如何通过 runbook 调度节省云计算成本
  • 如何将 CloudFormation 模板部署到 AWS
  • 如何在新的基础设施上将 Java web 应用程序部署到 Tomcat

https://www.youtube.com/embed/6cKhypLE11I

VIDEO

具有 runbook 自动化功能的实用自助开发工具

DevOps 是将团队聚集在一起进行协作。这在现实生活中说起来容易做起来难,因为开发和运营团队通常有不同的优先级。

开发团队希望快速交付代码并快速修复问题,而运营团队希望提供稳定可靠的 IT 服务。事实没有单一的来源,工具经常碍手碍脚,强化了信息孤岛。

Octopus 中的 Runbook Automation 允许开发和运营团队合作,在不牺牲生产控制的情况下实现日常维护和自助服务的自动化。我们讨论了以下主题:

  • 开发运维自动化如何打破开发和运维之间的孤岛
  • runbook 自动化和自助操作简介
  • 如何使用 Azure 资源管理器模板供应云基础架构
  • 如何使用 Octopus Runbooks 管理您的数据库维护

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

VIDEO

2020 年:章鱼年

2020 年是全球面临独特挑战的一年。虽然世界已经天翻地覆,但我们一直在努力改进 Octopus,增加了一些很棒的新功能,并根据客户反馈进行了一些小的改进。在本次网络研讨会中,我们讨论了以下主题:

  • 2020 年的独特技术挑战
  • 2020 年发布的主要功能
  • 运行手册
  • 更简单的 Java 支持
  • 增强的集成和跨平台支持
  • 更好的依赖性管理

https://www.youtube.com/embed/apxzK-rDHIc

VIDEO

塑造章鱼工程

章鱼在过去的几年里成长了很多。我们从 2015 年的 10 名澳大利亚人发展到 2020 年的近 100 人,团队成员分布在英国和美国。这种增长并非没有挑战。

随着我们工程团队的成长,我们期望我们的产品和新特性会随着团队一起增加。事实是,随着我们增加工程师和发布更复杂的功能,相反的事情发生了。

在 2019 年年中,在我们发布 Spaces 后不久,我们开始考虑并采用 Basecamp Shape-Up 模型,这有助于我们获得更多专注的开发时间,消除中断,让我们有更多时间提前了解功能。在本次网络研讨会中,我们讨论了以下主题:

  • 我们的工程团队在成长过程中面临的问题
  • 我们如何采用 Basecamp 的 Shape-up 开发方法来改进 Octopus 的开发和发布管理
  • 我们在尝试改进软件交付方式时吸取的经验教训

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

VIDEO

摘要

我们已经在 2020 年举办了一些世界级的网络研讨会,我对我们在 2021 年将要做的事情感到兴奋,因为我们将推出一些很棒的新功能。

我们 2020 年第一季度的渠道已经满了,如果你对网络研讨会有什么想法,或者我们可以对网络研讨会进行改进,请发电子邮件至 Advice@Octopus.com联系我们。

什么是 SBOMs?-章鱼部署

原文:https://octopus.com/blog/what-are-sboms

如果您是一名开发人员或管理一个企业软件应用程序,您可能会被问到应用程序中的组件。人们为什么想知道?客户希望信任您的应用程序,他们希望您的应用程序是安全的。企业供应商和政府机构想知道,因为他们关心使用您的软件的客户的安全问题。

软件应用程序由几个来源组成,开源的,内部的,或者两者的混合。随着依赖列表的增长,如果用于构建应用程序的单个组件都不知道,那么应用程序如何安全呢?

物料清单列出了可靠生产给定产量所需的库存。多年来,物料清单一直被用来提供制造过程中的透明度和可重复性。软件材料清单(SBOMs)应用了类似的概念。SBOMs 在一个列表中逐项列出了软件应用程序中的组件,开发人员可以跨团队共享这些组件。

关于改善国家网络安全的行政命令

2021 年 5 月 12 日,美国政府发布了一项关于改善国家网络安全的行政命令。

联邦政府必须动用其全部权力和资源来保护和保护其计算机系统,无论是基于云的、内部部署的还是混合的。保护和安全的范围必须包括处理数据的系统(信息技术(IT))和运行确保我们安全的重要机制的系统(操作技术(OT))。

该行政命令试图在人们获取软件时,将供应链中的网络安全风险降至最低。随着软件应用程序中未知组件数量的增加,风险也会增加。

该行政命令要求美国政府购买的所有软件生产 SBOM。这对美国内外的商业都有几个影响。出于安全目的,任何美国政府项目现在都必须生产 SBOMs。任何不能为其产品生产 SBOMs 的供应商都不会被批准参与政府项目。

该订单可能是世界各地需要 SBOMs 的许多类似订单的开始。随着对 sbom 认识的提高,企业可能会开始要求 sbom。如果你从事软件行业,未来很可能会有 SBOMs。

SBOMs 包含哪些内容?

国家电信和信息管理局(NTIA)提供了关于构建 SBOM 的指南。NTIA 在医疗保健领域进行了 SBOM 概念验证,验证了 SBOM 所需的基线要素。

基线要素包括:

  • 作者姓名-描述主要组件的 SBOM 文档的作者。作者可能与主要组件的供应商不同。
  • 供应商名称-组件的供应商。
  • 组件名称-组件的名称。
  • 版本字符串-组件的版本。
  • 组件散列-用于标识组件的二进制实例的加密散列。
  • 唯一标识符-组件的唯一标识符。一个元素可能存在多个标识符,因为不同的系统可能使用另一个标识符。
  • 关系-用于确定一个组件包含另一个组件。此外,Relationship 用于记录关于包含在另一个组件中的组件列表的完整性的知识。
  • 组件关系
  • 主要组件–由 SBOM 描述的组件。
  • 包含的组件–包含在另一个组件中的组件。

为什么 SBOMs 很重要?

对 SBOMs 的要求会对软件产生重大影响。软件是协作构建的,通常包含几个使用其他第三方库的第三方库。如果没有生成 SBOMs 的能力,软件就不会符合行政命令。

根据行政命令行事的政府机构和组织需要选择能够按需生成 SBOM 并且能够证明每个组件都不是网络安全风险的软件。SBOMs 的广泛使用将增加供应商和政府机构之间的信任。

软件供应商需要一种可靠的方法来检测他们部署的应用程序中的任何已知漏洞。SBOMs 让您能够主动应对风险,降低您的工具被黑客攻击的可能性。漏洞扫描还可以帮助您避免与那些自己扫描您的应用程序并报告漏洞的客户进行尴尬的交谈。

对 SBOMs 的需求可以被看作是生成工件的构建过程中的一个额外步骤,但是它确实提出了一些问题,例如:

  • 如何将一个 SBOM 与一个可部署的工件配对,以便一个可以与另一个匹配?
  • 您如何知道应用程序的生产版本,以便在应用程序部署后的几周或几个月内扫描相关的 SBOM?
  • 如何协调应用程序的部署并发布相关的 SBOM?
  • 您如何安排 SBOM 扫描来主动检测新发现的漏洞?
  • 如何扫描旧的 SBOM 版本来识别包含易受攻击组件的软件的早期版本?

在八达通,我们建立了一个免费的工具,为您回答所有这些问题,并满足您的 SBOM 要求。

Octopus Workflow Builder 如何帮助满足 SBOM 要求

Octopus Workflow Builder 帮助您生成 SBOMs 并将其构建到您的部署流程中。该工具演示了如何在构建过程中构造 SBOM 文件,然后演示了 Octopus Deploy 如何在部署过程中扫描 SBOM。

使用该构建器,您将获得一个示例项目,该项目展示了可部署的构件及其相关的 SBOM 在一个版本中是如何紧密耦合的,允许您编排和发布具有任何应用程序部署的 SBOM,安排 SBOM 扫描,或者访问旧的 SBOM 版本。

结论

2021 年,美国政府发布了一项行政命令,以改善国家的网络安全。该命令要求政府知晓软件组件,以最大限度地降低安全风险。如果您从事软件工作,这需要您公开您的应用程序的组件,否则会有被排除在政府 IT 相关项目之外的风险。

您可以通过 SBOMs 让您的软件组件为人所知。SBOMs 是软件应用程序中可共享的组件列表,在每个应用程序版本中自动生成。

为了帮助您满足这一需求, Octopus Workflow Builder 在构建过程中生成 SBOMs,并在部署过程中扫描它们。

愉快的部署!

什么是服务网格?-章鱼部署

原文:https://octopus.com/blog/what-is-a-service-mesh

Illustration showing a service mesh

如果你使用 Kubernetes 有一段时间,你会听到服务网格这个术语。几家大公司都在支持服务网格项目,比如谷歌和 Istio 以及云计算原生计算基金会和 T2 linker d T3。

那么什么是服务网格,它与 Kubernetes 本地的标准服务和入口资源有什么不同?

什么是服务网格?

有许多关于服务网格的描述,但是在视频什么是服务网格中,我们听到了构建 LinkerD 的浮力公司的威廉·摩根和从事 Istio 项目的路易斯·瑞安的观点。

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

VIDEO

威廉·摩根将服务网格描述为:

专用于管理微服务应用中发生的服务对服务通信的基础设施层。我所说的管理是指事情的操作方面,比如可靠性特性[...],有安全功能[...]以及可见性。

服务网格的目标是将这些东西作为基础设施的一部分提供给你,而不是必须在应用程序本身中完成。

路易斯·瑞安提出了这样的观点:

我们将其视为网络的抽象,因此应用程序必须减少对网络的考虑。希望运营商也能减少对网络结构的考虑,随着[...]部署规模增长。

那么,为什么要使用服务网格呢?

没有服务网的生活

为了理解我们为什么要使用服务网格,让我们首先看看网络功能是如何在单个应用程序中实现的。

处理微服务应用中的网络问题的标准解决方案是重试请求。假定 HTTP GETs 和 put 是等幂的,这些端点的消费者可以期望能够重试失败的网络调用。

有许多库可以让开发人员轻松实现这些重试。Spring retry 和 Polly 就是两个这样的例子,它们都超越了简单的重试循环,提供了像可配置补偿公式这样的特性。

下面是 Spring 服务中的一个函数示例,它将使用自定义的退避公式重试网络调用。

@Retryable(maxAttempts=10,value=RuntimeException.class,backoff = @Backoff(delay = 10000,multiplier=2))
public Double getCurrentRate() {
  // ...
} 

Polly 提供了类似的重试逻辑:

Policy
  .Handle<SomeExceptionType>()
  .WaitAndRetry(
    5,
    retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
    (exception, timeSpan, context) => {
      // do something
    }
  ); 

这两个例子并不特别复杂,但可能允许代码在与它们使用的任何服务的瞬时连接故障中存活。正因如此,像这样的重试逻辑是非常常见的。

将网络逻辑移至网络层

虽然这种代码可能很普遍,但它的配置并不标准。你不能期望使用 Spring retry 库的 Java 代码与。NET 代码使用 Polly 库。

微服务模式的好处之一是每个服务都是用最适合其需求的语言编写的。即使您能控制自己编写的代码,任何规模合适的 Kubernetes 集群都会包含至少一些用您无法控制的语言编写的第三方服务。

同时,网络问题在整个集群中共享,并作为一个整体进行理想的配置。不幸的是,当集群中的应用程序是用多个配置方式略有不同的库编写的时,标准化像重试逻辑这样简单的事情就变得很困难。

将这些网络问题从应用程序转移到基础设施层意味着可以统一管理标准网络功能,如请求重试。具体来说:

  • Kubernetes 将利用任何安全或审计策略来维护配置。
  • 标准的kubectl命令行工具现在用于查看或更新网络配置。
  • 标准的 Kubernetes 仪表板可用于查看和管理网络配置。
  • 运营商只需要了解少量服务网格的具体实施细节,而不是已经捆绑到部署的应用中的每一个网络库。

入口控制器和服务网格有什么区别?

Kubernetes 本机公开了入口资源,用于引导来自(通常是共享的)负载均衡器的流量。网络流量被定向到一个 Kubernetes 服务,该服务又将流量定向到一个 Kubernetes Pod。

一个入口控制器执行入口资源的实际网络处理,有多个入口控制器可以从中选择,如 Nginx、HAProxy、Traefik 等。像 Istio 这样的服务网格平台也扮演入口控制器的角色。

一些入口控制器由标准 Kubernetes 入口资源配置,一些通过它们自己的定制资源配置,一些由定制和 Kubernetes 入口资源配置。

例如,Nginx,一种更受欢迎的入口控制器,最近宣布支持两种新的定制资源,称为 VirtualServer 和 VirtualServerRoute 。这些定制资源是入口控制器超越基线规范的一个例子。

因此,服务网格可以是入口控制器,并且入口控制器可以实现超出基线入口资源所定义的功能。这些模糊的界限意味着这两个概念之间没有明显的区别。我怀疑入口控制器和服务网格之间的区别将继续变得更加随意,因为平台会继续添加新的功能来区分自己。

结论

服务网格为 Kubernetes 提供了一致的基础设施层,具有丰富的网络监控、可靠性和安全特性。服务网格消除了每个应用程序公开该网络功能的需要,并且可以利用现有的 Kubernetes 安全性、CLI 工具、仪表板和审计来维护基础架构层。

虽然术语服务网格还没有很好的定义,但是随着时间的推移,我们希望看到类似服务网格的功能进入到今天被用作入口控制器的许多项目中。

什么是云编排?-章鱼部署

原文:https://octopus.com/blog/what-is-cloud-orchestration

无论你在哪里工作,你可能已经注意到你使用的许多应用程序已经转移到了云端。从存储电子邮件或照片等数据,到在 Git 等云存储库中开发软件,云解决方案正在主导市场。Gartner 的一项研究表明,到 2025 年,云产品的收入将超过传统 IT 解决方案。

如果您使用云产品,您希望让它们更高效、更具成本效益。有两个过程可以帮助您做到这一点:

  • 云流程编排
  • 云自动化

这些概念经常互换使用,但它们在一些关键方面有所不同。

在这篇文章中,我将介绍云流程编排和云自动化之间的区别、各种“即服务”模型、云流程编排的好处、工具以及 Octopus Deploy 的适用范围。

Gartner Cloud Adoption

云流程编排和云自动化之间的区别

云协调是指公共云和私有云环境中工作负载、资源和基础架构的协调和自动化,以及整个云系统的自动化。每个部分都应该一起工作,以产生一个有效的系统。

云自动化是云流程编排的一个子集,专注于自动化云系统的各个组件。

云流程编排和自动化相互补充,形成自动化云系统。

“即服务”模式

开发人员通过 3 种主要模式访问云服务:

  • 软件即服务(SaaS)
  • 平台即服务(PaaS)
  • 基础设施即服务(IaaS)

SaaS 是一种软件许可和交付模式,软件解决方案由提供商按需提供和托管。SaaS 解决方案通常收取订阅费或使用免费增值定价模式。这种方法的好处是您不必安装和托管应用程序,并且可以访问您需要的内容。你可能已经在使用 SaaS 的解决方案,比如 Dropbox、Gmail 或网飞。

PaaS 平台为您提供完整的云开发和部署环境。您可以在虚拟机上加载操作系统和开发工具。PaaS 提供了一个封闭的环境来构建云应用程序,而无需管理许可或底层应用程序基础架构。想想用于构建 SaaS 应用程序的平台,如微软 Azure、谷歌云平台和亚马逊网络服务。

IaaS 提供按需服务来部署 IT 基础架构,如虚拟机、服务器、网络和存储。IaaS 是随用随付,因此您可以在需要时为所需的基础架构付费。将 IaaS 视为 PaaS 和 SaaS 系统背后的基础设施。IaaS 平台的例子包括数字海洋和 AWS EC2。

开发者在 IaaS 和 PaaS 平台上构建 SaaS 系统,开发者在 IaaS 平台上构建 PaaS 平台。总之,“即服务”系统允许您实现云流程编排和自动化。

下图显示了 SaaS、IaaS 和 PaaS 如何合作交付云解决方案:

As a service models

云流程编排的优势

云流程编排让您能够自动化云解决方案的每个部分,并实现:

  • 提高效率
  • 降低成本
  • 支持 DevOps
  • 增强安全性

您可以在云解决方案中自动执行流程,以检测何时出现高峰时间,并部署额外的服务来防止服务过载。云解决方案还可以关闭任何你不需要的闲置进程。通过优化资源配置,你提高了平台的效率,降低了成本。

云编排通过允许持续集成、监控和测试来支持 DevOps 框架。云流程编排解决方案管理所有服务,以便您获得更频繁的更新和更快的故障排除。您的应用程序也更加安全,因为您可以快速修补漏洞。

迈向完全云流程编排的旅程很难完成。为了使过渡更易于管理,您可以在整个过程中发现云自动化的好处。例如,您可以自动化数据库组件以加速手动数据处理,或者为您的 Kubernetes 工作负载安装一个智能调度器。即使很小的改进也能为你节省时间和金钱。

Terraform 是一个开源的基础设施代码(IaC)工具,也是部署基础设施解决方案的通用框架。您在配置文件中指定您的基础设施,以便在云上部署基础设施。IaC 可以在版本之间保存和恢复。

Kubernetes 是 Google 开发的容器编排工具。容器是轻量级的计算单元,构成了一个更大的应用程序。Kubernetes 与云提供商合作,在基础设施上管理和部署容器。资源可以根据需求增加或减少,从而节省资金并提高应用程序的可靠性。

许多 PaaS 云提供商拥有支持云流程编排的工具,例如:

  • 自动气象站云的形成
  • 微软 Azure 自动化
  • IBM 云协调器
  • 谷歌云作曲家

这些工具让您可以通过代码形式的基础架构、部署管理 GUI 以及与 PaaS 系统中其他云解决方案的集成来自动化您的云环境。

还有专用的云流程编排工具,例如:

  • 红帽 Ansible
  • 云化
  • 摩耳甫斯

这些专用工具提供云供应、配置管理和自动化。所有云编排工具都可以与 Terraform 和 Kubernetes 等技术配合使用。

您选择的工具将取决于您的:

  • IT 预算
  • 首选语言
  • 现有部署的位置
  • 其他特定应用要求

Octopus 部署适合哪里?

Octopus Deploy 是一款独立于云的部署工具,可帮助您通过内部解决方案或云提供商设置和管理应用程序的部署。

Octopus 将应用程序从代码到构建再到部署。部署完成后,像 Kubernetes 或 AWS Cloud Formation 这样的工具可以管理应用程序的基础设施和云资源的状态。如果您需要使用新版本部署应用程序,Octopus 会管理新版本,并将新版本部署到生产环境中。

结论

云流程编排和自动化为您提供了更高的效率、更低的成本、对开发运维的支持以及更高的安全性。

云服务通常通过软件即服务(SaaS)、平台即服务(PaaS)和基础设施即服务(IaaS)来访问。SaaS、PaaS 和 IaaS 提供按需服务,让您无需管理资源即可访问资源。您可以结合 SaaS、PaaS 和 IaaS 来实现云流程编排和自动化。

流行的云编排框架和工具包括 Terraform、Kubernetes、PaaS 编排工具和专用编排工具。

Octopus 是一个与云无关的部署工具,它与您的 DevOps 工具链一起工作,以实现云协调和更快、更可靠的部署。

愉快的部署!

什么是 CNAB?-章鱼部署

原文:https://octopus.com/blog/what-is-cnab

Docker Whale with a Cloud Native Application Bundle on its back

使用云基础架构可能是一项艰巨的任务。每个云提供商都有自己的 CLI 工具和首选部署策略,跨平台工具(如 Terraform、Ansible、Puppet 和 Chef)需要大量投资来学习,一旦您启动了基础架构,您可能会面临管理更多部署到 Docker 或 Kubernetes 等平台的任务。

可以肯定地说,如今云部署几乎总是需要多种工具和凭证,而云原生应用捆绑包(CNAB)规范是对部署和管理云基础架构日益增长的复杂性的回应。

在本帖中,我们将看看 CNAB 解决的问题,CNAB 工具生态系统,以及今天使用 CNAB 的利与弊。

CNAB 解决了什么问题?

为了理解 CNAB 解决的问题,我们可以看看我们在 Octopus 与 Terraform 供应商遇到的一个问题。

该提供者的目的是允许通过 Terraform 配置 Octopus 服务器,并且该功能作为一个定制的 Terraform 插件公开。但是,要使用该插件,您必须手动下载它,将其放在正确的目录中,并通过传递给 Terraform 的命令行参数引用它。虽然在 Terraform 社区中有很多关于分发插件的最佳方式的讨论,但是现在仍然是一个手动的过程。

因为这是一个手动过程,所以提供插件和记录其使用的负担就落在了我们身上。对于像 Terraform 这样的跨平台工具来说,这并不是一个微不足道的负担,因为这意味着这个过程在 Windows、MacOS 和 Linux 上得到了完美的记录和测试。如果我们可以将所有需要的工具和脚本捆绑到一个单独的、自包含的可部署工件中,这不是很好吗?

这正是 CNAB 被设计出来的场景。CNAB 包实质上是 Docker 映像的集合,包含针对远程资源执行安装所需的一切。在本例中,CNAB 包将包含 Terraform 可执行文件、插件、Terraform 模板和执行所有操作所需的脚本。最终的包消除了最终用户下载和配置单个工具的需要,而是为他们提供了一个自包含的安装程序。

通过提供独立的安装包,CNAB 解决了在自动化复杂部署时出现的许多常见问题,包括:

  • 凭证管理
  • 离线安装
  • 版本化安装程序
  • 签名和可验证的安装人员
  • 审计线索
  • 卸载过程
  • 指向并单击安装程序

你如何使用 CNAB?

CNAB 本身只是一个规范,实现它取决于提供者。Duffle 项目提供了 CNAB 规范的参考实现,我们将在博客的其余部分使用这个工具。

Duffle 可执行文件可以从项目的 GitHub releases 页面下载。为 Windows、Linux 和 MacOS 提供了预编译的二进制文件。

Duffle 用命令duffle create <bundle>创建了一个样例包项目。将创建一个目录,其中包含 Duffle 项目duffle.json文件、Dockerfile和将包含在 Docker 映像中的示例安装脚本。

为了将 Duffle 项目转换成 CNAB 包,我们执行duffle build。这将采用 Duffle 特定的duffle.json文件中的包配置,并构建 Docker 映像和 CNAB 包。

构建项目意味着现在可以安装这个包了。我们可以通过运行duffle bundle list看到这一点,它显示了可用的包。

要执行安装,运行命令duffle install <install name> <bundle>:<version>。这将从包中运行 install 命令,在示例应用程序中,它将hey I am installing things over here打印到屏幕上。

一旦安装完成,命令duffle list将列出安装的详细信息。

要卸载一个包,运行duffle uninstall <install name>。这将运行 bundle 卸载脚本,对于我们的示例,该脚本将hey I am uninstalling things now打印到屏幕上。

这些包也可以使用行李包项目打包成一个独立的安装程序。这为最终用户提供了与任何其他应用程序安装程序非常相似的体验。

CNAB/达芙蕾今天可以使用了吗?

如果您现在尝试使用 CNAB,有几个问题需要解决。

实现 CNAB 规范的项目中没有 1.0 版本。 DufflePorterDocker App 都还在开发他们最初的零发布版本,这意味着随着它们的发展,你可以期待一些突破性的变化。

安装 CNAB 工具本身是一个手动过程。Duffle 从他们的 GitHub 发布页面提供二进制文件,最终用户可以下载、重命名和安装它们。理想情况下,这些安装程序将通过主要的软件仓库或商店提供,以使安装和更新它们变得轻松,但最终用户今天必须自己管理这个过程。

CNAB 规范的目标之一是允许将包保存在中央存储库中。像 CNAB 到 OCI 这样的项目正在研究这个问题,但是像大多数 CNAB 工具一样,它还处于早期阶段,所以你的运气可能会有所不同。

最后,对于面向操作人员的工具来说,创建独立的安装程序需要安装大量未记录的开发工具。您至少需要 Node.js、Python 和 Visual Studio Tools 之类的 C++编译器,即使这样,您也可以在构建期间调试深奥的错误。CNAB 还没有达到堆栈溢出帖子的临界质量,以使解决这些错误变得容易。

即使作为一名开发者,这片红色的海洋也足以让你离开这个建筑。

尽管有这些挑战,我还是惊喜地发现 Visual Studio 代码插件是如此容易地与 Duffle 项目一起工作,在经历了一些最初的挫折后,我很高兴地构建并打包了我的 CNAB 包。我特别喜欢构建自包含和执行安装程序的能力,以及 CNAB 处理凭证的事实。

因此,尽管随着工具更新到 1.0 版本,开发过程中的粗糙边缘被消除,任何加入 CNAB 潮流的人都可以预期会有一些颠簸,但整体 CNAB 捆绑包是一种分发安装程序的便捷方式,它将合并多个工具的复杂部署自动化。

作为代码的一切是什么?-章鱼部署

原文:https://octopus.com/blog/what-is-everything-as-code

如果您在 DevOps 或 cloud 中工作,您可能使用过 GitHub Actions、Jenkins 或 Terraform 等工具来交付您的 DevOps 管道。您可能已经注意到,这些工具都将 DevOps 管道的一部分表示为代码,允许您在以后存储和重用管道的一部分。

DevOps 管道的代码表示是向一切即代码(EaC)转变的一部分。但是什么是 EaC,为什么它很重要?

“一切如代码”是一种软件开发方法,它使用代码来定义和管理 IT 资源。资源的代码表示使开发人员更容易:

  • 审计变更
  • 提高一致性
  • 扩展资源
  • 将设置从一个环境转移到另一个环境

从字面上看,EaC 是一种理想状态,软件生命周期的每一部分都是代码。

然而,如今 EaC 的实现远非理想,EaC 被用作一个总括术语,涵盖了“as code”框架的具体应用。基础设施即代码(IaC)和配置即代码(Config as Code)是广泛应用的 EaC 应用程序,其他应用程序涵盖了一系列 IT 领域。

在这篇文章中,我将讨论一切都是代码的一些应用、好处以及我对转向 EaC 的想法。

基础设施作为代码

基础设施即代码(IaC)是通过代码配置 IT 基础设施的过程。过去,开发人员手动调配 IT 基础架构,无法控制谁可以调配基础架构以及他们使用什么方法。

您可能遇到过这样的情况:一个人,即看门人,控制着您所有的 IT 基础架构。只有那个人知道东西在哪里,但即使是他们也不能确定所有的事情。

有了 IaC,组织中的每个人都可以看到所有基础结构的内容和位置。基础设施不再由少数人控制,而是在配置文件中受版本控制。好多了!

Terraform 是最流行的 IaC 框架之一。Terraform 提供了一种配置语言,开发人员使用这种语言来定义云和内部资源。许多云提供商使用 IaC,因此开发人员可以以可重复的方式按需供应基础架构。如果你能学会 Terraform 语言,你就可以使用它和任何云提供商或部署工具,比如 Octopus,来提供基础设施。

配置为代码

Config as Code 是在代码中捕获所有系统配置设置的过程。在 Octopus 中,配置设置指定了部署过程。我们在 2022 年 3 月发布了作为 Octopus 代码的 Config。

我们将 Config 设计为代码,以提供 Git 的能力(分支、拉请求和完整的变更审计日志)和 Octopus UI 的可用性。你可以在我们的文章 Shaping Config as Code 中读到一些我们的设计思想。

在 Octopus Deploy,Config as Code 中,用户可以选择使用 UI 或源代码控制的实现,而不会失去任何功能。您可以选择使用 UI 进行小的更改,或者使用源代码管理进行高级更改。

我们相信我们不折不扣的解决方案是最好的“代码”实现之一。

代码形式的其他例子

EaC 还有其他一些例子,有些比其他的更适合,例如:

  • 作为代码的环境:提供计算环境的工具,如 Docker 和 vagger。许多云提供商都有自己的计算环境作为代码产品,比如 Google 的 Compute Engine 和 Amazon 的 EC2。

  • 数据分析如代码:你可以将数据管道和机器学习过程表示为代码。数据管道作为代码允许数据科学家将数据分析组件从一个项目移植到另一个项目。

  • DevOps 管道作为代码:像 GitHub Actions 和 Jenkins 这样的工具将 DevOps 管道表示为代码。当开发人员推动代码变更时,会触发一个流程来构建存储库,并产生一个工件或部署一个版本。

  • 安全性即代码:如果你正在管理一个云环境,你会关心安全性。代码形式的安全性允许您在配置文件中表示安全数据,如角色和权限。如果敏感数据需要“作为代码”,您应该考虑加密技术。

EaC 适用于开发人员可以对流程和资源进行编码的任何场景,因此它适用于 it 的更多部分。您能想到您的企业中有任何 EaC 应用程序吗?

利益

“一切都是代码”让您可以将 IT 资源表达为代码。主要好处是:

  • 一致性:您可以在一个标准框架(如 Terraform)中捕获基础设施和配置设置。这个框架减少了人为错误,提高了可靠性,因为系统是受版本控制的。
  • 可伸缩性:如果您想要伸缩,您只需对配置文件做一点小小的更改,就可以将任何问题回滚到以前的版本。
  • 可移植性:您可以导出您的基础设施、配置或其他系统部件,并复制它们。
  • 可审计性:您可以更容易地审计您的系统,因为版本控制使变更可见。

EaC 对您和您的软件栈有一些明显的好处,但是将系统转向 EaC 并不总是有意义的。

虽然 Octopus Deploy 中的代码部署有很大的好处,但是将其他平台部分转换成 EaC 的回报却在减少。与组织可以在同一时间段内开发的其他功能相比,开发一个额外的 EaC 功能总是有机会成本的。在实践中,组织应该在有意义并且对用户有最大好处的地方应用 EaC。

结论

一切如代码(EaC)是一种软件开发方法,使用代码来定义和管理 IT 资源。EaC 已经在基础设施代码、配置代码和其他 IT 领域找到了许多应用。如果您在 DevOps 和云计算领域工作,您可能已经亲眼看到了 EaC 的好处。

尽管一切以代码的形式出现对组织来说是一种有希望的最终状态,但是将平台的一部分转换成 EaC 是有机会成本的,这将告诉你在哪里投资你的资源。毫无疑问,您的平台的某些部分可以从 EaC 方法中受益,关键是识别那些区域。

愉快的部署!

什么是 GitOps?-章鱼部署

原文:https://octopus.com/blog/what-is-gitops

GitOps 是我们行业中不断增长的“Ops”范例列表中的一个相对较新的成员。这一切都是从 DevOps 开始的,虽然 DevOps 这个术语已经存在了很多年,但似乎我们仍然无法就它是一个过程、思维模式、职位名称、一套工具还是它们的组合达成一致。在我们的DevOps 简介帖子中,我们捕捉到了关于 devo PS 的想法,在我们的 DevOps 工程师手册中,我们更深入地探讨了这些想法。

术语 GitOps 也有同样的歧义,所以在本帖中,我们来看看:

  • 吉托普的历史
  • GitOps 目标和理想
  • GitOps 的局限性
  • 支持 GitOps 的工具
  • 在您自己的组织中采用 GitOps 的实际意义

GitOps 的起源

GitOps 这个术语最初是由 WeaveWorks 在一篇名为GitOps-Pull Request的博客文章中提出的。这篇文章描述了 WeaveWorks 如何使用 git 作为真理的来源,带来了以下好处:

我们的 AWS 资源供应和 k8s 部署是声明性的

我们的整个系统状态都在版本控制之下,并在单个 Git 存储库中描述

运营变更由拉式请求(加上构建和发布管道)做出

Diff 工具检测任何差异,并通过松弛警报通知我们;同步工具支持融合

回滚和审计日志也通过 Git 提供”

自从那篇博文发表以来,像 GitOps 工作组这样的计划已经被组织起来:

清楚地定义 GitOps 的供应商中立的、原则主导的含义,这将为工具、一致性和认证之间的互操作性建立基础。"

该工作组最近发布了其原则的第一个版本,其中指出:

GitOps 受管系统的理想状态必须是:

声明性——由 GitOps 管理的系统必须声明性地表达其期望的状态。

版本化和不可变——期望的状态以一种强制不变性、版本化和保留完整版本历史的方式存储。

自动提取-软件代理自动从源代码中提取所需的状态声明。

持续协调-软件代理持续观察实际的系统状态,并尝试应用期望的状态。"

在大多数博客文章中发现的 GitOps 的低级实现和工作组描述的 GitOps 系统的高级理想之间的对比值得讨论,因为它们之间的差异是许多混乱的来源。

GitOps 并不意味着使用 Git

围绕 GitOps 的大多数讨论都集中在如何在 Git 上构建过程,从而产生许多归于 GitOps 范例的好处。Git 自然地提供了一个(几乎)不可变的变更历史,通过 pull 请求对变更进行了注释和批准,其中 Git 存储库的当前状态自然地代表了系统的期望状态,因此充当了事实的来源。Git 和 GitOps 之间的重叠是不可否认的。

但是,您可能已经注意到,工作组从未将 Git 作为 GitOps 的一个需求。因此,虽然 Git 是 GitOps 解决方案的一个方便的组件,但 GitOps 本身关注的是系统的功能需求,而不是将声明性模板签入 Git。

这种区别很重要,因为许多团队专注于 GitOps 的“Git”部分。GitOps 这个术语对于它试图表达的概念来说是一个不合适的名字,这导致许多人认为 Git 是 GitOps 的核心方面。但 GitOps 赢得了营销战,并在 IT 部门获得了思想份额。虽然它可能是一个限制性术语,用来描述与 Git 无关的功能需求,但是 GitOps 现在是描述实现一组高级关注点的过程的简写。

GitOps 并不意味着使用 Kubernetes

Kubernetes 是第一个广泛使用的平台,它将声明性状态和持续协调的思想与执行环境相结合,以实现协调并托管运行的应用程序。看着 Kubernetes 集群重新配置自己以匹配应用于系统的最新模板真是太神奇了。所以毫不奇怪,Kubernetes 是 Flux 和 Argo CD 等 GitOps 工具的基础,而像30+GitOps 工具列表这样的帖子提到 Kubernetes 20 次。

虽然持续的和解令人印象深刻,但这并不是真正的魔术。在幕后,Kubernetes 运行许多操作符,这些操作符会收到配置更改的通知,并执行定制逻辑以将集群恢复到期望的状态。

持续对账的主要要求是:

  • 对以声明方式表达所需状态的配置或模板的访问
  • 当配置改变时,执行能够协调系统的过程的能力
  • 流程可以运行的环境

Kubernetes 将这些需求烘焙到平台中,很容易实现持续的调和。但这些要求也可以通过一些简单的编排、基础设施即代码(IaC)工具来满足,如 Terraform、Ansible、Puppet、Chef、CloudFormation、Arm 模板以及 CI server 或 Octopus 等执行环境:

  • IaC 模板可以存储在 Git 中,Git 是像 S3 或 Azure Blob 存储这样的文件托管平台,具有不可变的审计历史。
  • CI/CD 系统可以轮询存储,通过 webhooks 获得更改通知,或者通过 GitHub Actions 等平台触发构建或部署。
  • 然后执行 IaC 工具,使系统符合期望的状态。

事实上,真实世界中的端到端 GitOps 系统不可避免地会包含 Kubernetes 之外的编排。例如,Kubernetes 不太可能管理您的 DNS 记录、集中式认证平台或 Slack 之类的消息系统。您可能还会发现,至少有一种托管服务比试图在 Kubernetes 集群中复制它们更有吸引力,比如数据库、消息队列、调度和报告。此外,任何已建立的 IT 部门都保证拥有非 Kubernetes 系统,这些系统将受益于 GitOps。

因此,虽然最初选择的专用 GitOps 工具倾向于紧密集成到 Kubernetes 中,但要在已建立的基础设施上实现 GitOps 的功能需求,将不可避免地需要编排一个或多个 IaC 工具。

持续的和解是成功的一半

如工作组所述,持续协调描述了对两种系统变化的反应。

第一个是您所期望的,对 Git 或其他版本化存储中的配置的故意更改被检测到并应用到系统。这是配置更改的逻辑流程,代表正确配置的 GitOps 工作流的正常操作。

第二种情况是代理检测到源配置中未描述的不良系统更改。在这种情况下,您的系统不再反映期望的状态,代理需要将系统恢复到 Git 中维护的配置。

这种解决第二种情况的能力是一种很好的技术能力,但是代表了一个不完整的业务流程。

想象一下,前台的保安报告说他们驱逐了一名入侵者。作为一次性事件,该报告可能会引起轻微的关注,但是安全团队完成了他们的工作并解决了问题。但是现在想象一下你每周都收到这些报告。显然,有一个更重要的问题迫使安全团队对每周的入侵做出响应。

同样,一个不断移除不良系统状态的系统对于一个更根本的问题来说是一个不完整的解决方案。真正的问题是谁在做出这些改变,为什么要做出改变,为什么不通过正确的过程做出改变?

您的系统能够对不良状态做出响应的事实证明了一个健壮的过程能够适应不可预测的事件,这种能力不应该被低估。团队应该执行他们的恢复过程,这是一个由来已久的最佳实践,因此在发生灾难时,团队能够运行一个排练良好的恢复过程。连续协调可以被视为一种自动恢复过程,允许该过程被轻松地测试和验证。

但是,如果你的系统必须对不良状态做出反应,这就是一个有缺陷的过程的证据,在这个过程中,人们可以访问他们不应该或者没有遵循既定的过程。过度依赖一个可以在不良改变发生后将其撤销的系统,可能会掩盖一个更重要的潜在问题。

GitOps 不是一个完整的解决方案

虽然 GitOps 描述了管理良好的基础设施和部署流程的许多可取特征,但它并不是一个完整的解决方案。除了 GitOps 描述的 4 项功能要求之外,健壮的系统还必须:

  • 可验证——基础设施和应用程序一旦部署,就必须是可测试的。
  • 可恢复——团队必须能够从不良状态中恢复过来。
  • 可见——基础设施和部署到基础设施上的应用程序的状态必须呈现在一个易于使用的摘要中。
  • 安全——必须存在关于谁可以对哪些系统进行什么更改的规则。
  • 可测量的——必须收集有意义的指标,并以易于使用的格式公开。
  • 标准化——必须以一致的方式描述应用程序和基础设施。
  • 可维护性——支持团队必须能够查询系统并与之交互,通常是以非声明的方式。
  • 协调——应用程序和基础设施的变更必须在团队之间进行协调。

对于在配置提交给 Git repo 或其他版本化且不可变的存储之前会发生什么,GitOps 几乎没有提供任何建议或见解,但它是“repo 的左边”,您的大部分工程流程将在这里定义。

如果您的 Git repo 是您系统的权威表示,那么任何可以编辑 repo 的人本质上都拥有管理权限。然而,Git 回购并没有为您在已建立的基础设施中发现的那种细微的责任分离提供天然的安全边界。这意味着您最终会为每个应用程序、每个环境、每个角色创建一个 repo。获得这些回购的可见性并确保它们拥有正确的权限并非易事。

您还会很快发现,仅仅因为您可以在 Git 中保存任何内容,并不意味着您应该这样做。不难想象这样一条规则:开发团队必须创建 Kubernetes 部署资源,而不是单独的 pods,使用响应非常具体的主机名的入口规则,并始终包括一个标准的安全策略。这种标准化很难通过拉请求来实现,所以一个更好的解决方案是给团队标准的资源模板,他们用自己特定的配置来填充这些模板。但这不是 Git 或 GitOps 固有的特性。

然后,我们有那些“集群的权利”的过程,其中定义了管理和支持任务。

报告 Git 提交的意图几乎是不可能的。如果您查看两次提交之间的差异,发现增加了一个部署映像标记,添加了新的秘密值,删除了一个配置映射,您将如何描述这一变化的意图?简单的答案是读取提交消息,但这对于报告工具来说不是一个可行的选择,这些工具必须将高级事件(如“部署了新的应用程序版本”或“bug 修复发布”)映射到两次提交之间的差异,如果你想根据标准指标(如 DORA 报告中提供的指标)来衡量自己,这是至关重要的。即使你能推测出一种理解 Git 提交意图的算法,Git repo 也绝不应该被用作时序数据库。

GitOps 也没有提供在系统处于理想状态后如何执行支持任务的指导。您会对 Git repo 承诺什么来删除行为不当的 pod,以便它们可以被其父部署重新创建?也许一个工作可以做到这一点,但你必须小心,Kubernetes 不会试图申请该工作资源两次。但是,您会向 repo 承诺什么来查看服务的 pod 日志,比如预装在您的集群上的入口控制器?一想到在 GitOps 模型中重建kubectl logs mypod需要实现的所有异步消息处理,我就感到困惑。

像这样的即席报告和管理任务在 GitOps 模型中没有自然的解决方案。

这并不是说 GitOps 有缺陷或不完整,而是说它解决了特定的问题,并且必须用其他流程和工具来补充,以满足基本的操作需求。

Git 是 GitOps 中最没意思的部分

我想向你们展示一个理论和一个思维实验,并将其应用于:

在任何足够复杂的 GitOps 流程中,您的 Git repo 只是另一个结构化数据库。

您使用 Git 和 Kubernetes 的常见组合开始您的 GitOps 之旅。所有更改都由 pull request 审查,提交到 Git repo,由 Argo CD 或 Flux 等工具使用,并部署到您的集群。您已经满足了 GitOps 的所有功能需求,并享受到了单一事实来源、不可变的变更历史和持续协调的好处。

但是,每次发布新的图像时,让一个人打开一个 pull 请求来碰撞部署资源中的图像属性会变得很乏味。因此,您指示您的构建服务器获取 Git repo,编辑部署资源 YAML 文件,并提交更改。您现在拥有 GitOps 和 CI/CD。

您现在需要衡量您的工程团队的表现。新版本部署到生产环境的频率如何?您很快意识到从 Git 提交中提取这些信息效率很低,而且 Kubernetes API 不是为频繁和复杂的查询设计的,所以您选择用部署事件填充一个更合适的数据库。

随着集群复杂性的增加,您发现需要实现关于可以部署哪种资源的标准。工程团队只能创建部署、机密和配置映射。部署资源必须包括资源限制、一组标准标签,并且不能授予 pod 特权。事实上,在构成部署到集群的资源的数百条 YAML 线路中,只有大约 10 条应该被定制。正如您对 image 标记更新所做的那样,您将资源的编辑从手动 Git 提交提升到了一个自动化的过程,在这个过程中,模板拥有一个严格控制的属性子集,这些属性随着每次部署而更新。

现在,您的 CI/CD 正在执行 Git 的大部分提交,您意识到您不再需要使用 Git repos 作为实施安全规则的手段。您将为代表单个应用程序和环境而创建的几十个回购整合到一个只有 CI/CD 系统每天与之交互的回购中。

您发现自己不得不回滚失败的部署,却发现恢复 Git 提交的概念过于简单。对您想要恢复的一个应用程序的更改已经与十几个其他部署混合在一起。这并不是说任何人都应该直接接触 Git 回购,因为合并冲突可能会带来灾难性的后果。但是您可以使用 CI/CD 服务器来重新部署旧版本的应用程序,因为 CI/CD 服务器具有组成单个应用程序的上下文,所以重新部署只更改与该应用程序相关的文件。

在这一点上,你承认你的 Git repo 是另一个结构化的数据库,反映了“真理之源”的子集:

  • 人类不能碰它。
  • 所有的改变都是由自动化工具完成的。
  • 自动化工具需要特定位置的特定格式的已知文件。
  • Git 历史显示了由机器人而不是人所做的更改列表。
  • Git 历史现在显示为“Deployment #X.Y.Z”,其他提交信息只在自动化工具的上下文中有意义。
  • 不再使用拉请求。
  • “事实的来源”现在可以在 Git repo(显示文件的变更)、CI/CD 平台的历史(显示发起变更的人,以及做出变更的脚本)和度量数据库中找到。
  • 您整合了您的 Git repos,这意味着即使您想隔离对人类的访问,您的能力也是有限的。

您还意识到,您的 GitOps 流程中增加独特业务价值的部分是“回购的遗留部分”,包括度量收集、标准化模板、发布编排、回滚和部署自动化;以及带有报告、控制面板和支持脚本的“集群右侧”。Git repo 和集群之间的过程现在是如此自动化和可靠,以至于您不需要考虑它。

结论

GitOps 已经封装了一个理想功能需求的子集,这些需求可能会为实现这些需求的任何团队提供大量的好处。虽然 Git 和 Kubernetes 都不是满足 GitOps 所必需的,但它们是开始 GitOps 之旅的逻辑平台,因为它们得到了当今更成熟的 GitOps 工具的良好支持。

但是 GitOps 工具倾向于重点关注 Git repo 的提交和 Kubernetes 集群之间发生的事情。尽管这无疑是任何部署渠道的一个关键组成部分,但要实施强大的 CI/CD 渠道和 DevOps 工作流,在“repo 的左边”和“集群的右边”还有许多工作要做。

GitOps 工具还倾向于假设,因为所有东西都在 Git 中,所以每个更改的意图都用提交消息进行了注释,与作者相关联,经过了审查过程,并且可供将来检查。然而,这过于简单了,因为任何足够先进的团队考虑实现 GitOps 都会通过自动化手动接触点立即开始迭代过程,通常是关于如何将配置添加到 Git repo 中。

当您规划 GitOps 工作流的自然发展时,您可能会得出这样的结论:如此多的自动化流程依赖于特定位置和格式的声明性配置,Git 提交必须以与数据库迁移非常相似的方式来处理。必须管理和协调 GitOps 流程的输入,并且必须测试、测量和维护输出。与此同时,Git repo 和集群之间的处理应该是自动化的,使我们今天谈论的许多 GitOps 只是专门的 CI/CD 管道或 DevOps 工作流中的一个中间步骤。

围绕 GitOps 的最大困惑可能是误解了它代表了一个端到端的解决方案,并且您实现了 GitOps 和以 GitOps 为中心的工具,而排除了替代流程和平台。

实际上,GitOps 封装了基础设施和部署管道中的一个步骤,必须用其他流程和平台来补充,以满足常见的业务需求。

愉快的部署!

影子是什么?-章鱼部署

原文:https://octopus.com/blog/what-is-shadow-it

在传统组织中,IT 部门监督和管理所有 IT 资源。然而,由于可以如此轻松地访问基于云的 it 资源,缺乏耐心和时间的员工通常会发现,自己构建 IT 基础架构比填写请求和等待 IT 部门更简单、更快速。当员工创建和使用他们自己的 IT 资源,而这些资源对 IT 部门来说是不可见的,这就是所谓的影子 IT。

2017 年,Gartner 预测,IT 部门将做出更少的技术决策,单个业务部门将开始为其团队选择技术,占技术购买的38%。2019 年,珠穆朗玛峰集团预测组织中超过 50%的技术支出是由于影子 IT 。云技术的兴起加剧了这一问题,使员工比以往任何时候都更容易使用未经批准的 it 资源。

影子 IT 给组织带来了新的问题。其中包括:

  • IT 部门应该如何应对影子 IT?
  • 跟踪 100%的所有 it 资源现实吗,甚至实际吗?
  • 是否应该有一个风险可接受的更可控的方法?
  • 哪些工具可以帮助管理影子 IT?

这篇文章探讨了这些问题。

企业成本

越来越多的团队正在利用影子 IT,这增加了安全违规的风险,因为资源不在 IT 部门的控制范围内。

EMC 的一项研究估计,由于影子 IT 安全漏洞,数据丢失和停机每年会造成1.7 万亿美元的损失。

在 IBM 的 2021 年数据泄露成本报告中,从 2020 年到 2021 年,数据泄露的平均成本从 386 万美元上升到 424 万美元。

Average total cost of a data breach - IBM Cost of a Data Breach Report 2021

来源:IBM

对于高度管制行业的企业来说,还存在合规性问题。通用数据保护条例(GDPR) 对从欧盟(EU)人民处收集数据的组织实施了严格的规定。违法者将被处以高达数千万欧元的高额罚款。随着影子 IT 的增加,确保只有经过授权的员工才能访问敏感数据变得越来越困难。

影子 IT 也会影响运营成本。当影子 IT 不受管理时,服务变得分散,因为每个业务部门都根据自己的需求采购服务。一个业务部门可能偏好一种产品,而另一个则偏好其竞争对手。这也可能导致云基础架构不可预测的运营成本。想想所有为单一目的而创建的未受监控的虚拟机,它们总是在运行,但从未停止运行。通过允许业务部门采购自己的 IT 基础架构,企业失去了购买力优势和降低 IT 基础架构成本的能力。

影子 IT 的真实成本归结于不断增长的未知资源,这些资源存在运营和安全风险。

员工为什么要用影子 IT?

使用 shadow IT 的主要动机是方便。

IT 政策可能非常严格。通常,员工自己获得 it 解决方案比通过 IT 部门更容易、更快。员工也可能更喜欢特定的解决方案,而不是规定的解决方案,这加剧了问题。员工可能会找到问题的另一种解决方案,再次引入影子 IT,而不是处理支持票证。

不幸的是,使用影子 IT 解决方案的人通常不会意识到后果。他们只想以一种高效的方式完成工作。

自助服务操作手册可以通过确保简化的治理体验来解决这一问题,使员工能够在不避开 IT 部门的情况下提升他们所需的基础架构。

风险缓解

影子 IT 的未知性质增加了组织的风险状况。影子 IT 正在广泛传播和增长,所以这是一个管理风险的问题。Gartner 建议了三种风险缓解策略来解决这一问题:

影子 IT 需要治理、发现和保护。解决方案必须精简,并最大限度地减少支持时间。

影子 IT 发现生命周期

这张图片来自微软的博客文章,展示了影子 IT 发现生命周期的各个阶段。这支持了任何影子 IT 的解决方案都应该有治理和法规遵从性。

T3Shadow IT lifecycleT5

来源:微软

在维护治理的同时减少合规障碍是管理影子 IT 的重要一步。Octopus Deploy 的 Runbooks 特性有助于实现整个组织的合规性和治理。

什么是 runbook?

runbook 是一种可重复使用的方法,用于执行经常重复的任务。runbooks 可以自动执行的任务类型包括最大限度地减少应用停机时间、简化日常维护以及提供自助操作。让我们看一下操作团队可能收到的请求,刷新测试数据库中的数据。

通常,当开发人员需要刷新测试数据库中的数据时,需要执行以下操作:

  1. 开发人员向支持团队创建一个请求来刷新他们数据库中的数据。
  2. 支持团队审查请求以了解需求。
  3. 如果支持团队需要额外的信息,他们会向开发人员请求。
  4. 当支持团队拥有了处理请求所需的一切时,他们就可以运行刷新数据库中数据的过程。

根据支持团队的工作量和周转时间,这个请求可能需要几分钟到几天的时间,并且开发人员通常不知道时间表。操作手册有助于避免这些陷阱。

刷新数据库中数据的步骤可以由 runbook 捕获并执行。runbook 还包括执行任务的所有权限,这意味着 run book 可以自助服务。这允许用户执行任务,而无需请求和等待支持团队成员。

任何可以自动化的任务都可以记录在操作手册中,允许团队成员完成以前需要专门团队完成的任务。

运行手册也引入了一致性。想象一下创建新 AWS 帐户的自助操作手册。用户需要设置访问级别、VPC 设置和其他 IAM 注意事项。如果 50 个不同的用户试图建立一个帐户,这可能导致 50 个不同类型的用户,这是另一个挑战。如果您将此应用于创建虚拟机、容器注册中心或其他 PaaS 基础设施,很容易发现 shadow IT 的问题。

使用操作手册可以限制这一过程,并使 IT 资源标准化。运营团队可以使用操作手册来监控和保护 IT 资源。

虽然 run book 不能解决影子 IT 的所有障碍,但 run book 可以改善 IT 资源管理并简化最终用户的使用。根据 MRC 关于管理影子 IT 风险的规定:

这一步的目标是受控的自助式解决方案。您提供的任何软件都必须满足两个重要标准:

  • 自助服务:用户必须在不打扰解决方案的情况下使用它。
  • 控制:它必须仍然能够控制数据和用户访问。

当您提供受控的自助服务选项时,您的企业将两全其美。用户可以快速获得他们需要的解决方案,而 IT 仍然可以保护数据和应用程序。"

操作手册让运营团队监控资源并提供安全保障。他们还允许员工在没有支持的情况下自助解决问题。

结论

影子 IT 是指不在组织控制范围内的任何 IT 资源。它给企业带来了多种风险和高成本的问题。

企业需要对 IT 资产进行更多的治理、发现和保护。员工希望流程更加简化,并且能够在没有太多支持的情况下解决问题。

Runbooks 可以通过提供运行常见任务的自助方式来解决这些问题。将这一概念应用于像设置云帐户这样的问题,为 IT 资产提供了标准化。

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

愉快的部署!

Selenium 系列:什么是 WebDriver 和 Selenium?-章鱼部署

原文:https://octopus.com/blog/selenium/1-what-is-webdriver-and-selenium/what-is-webdriver-and-selenium

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

谈到测试 web 应用程序,您可能听说过 WebDriver 和 Selenium。这两个术语通常可以互换使用,甚至可以组合使用,以指代开发人员可以编写代码与 web 浏览器进行交互的平台。然而,这两个术语所指的东西略有不同。

从技术上讲,WebDriver 是一个标准的、基于 HTTP 的 API,用于与 web 浏览器交互。该标准由 W3C 在 https://www.w3.org/TR/webdriver1/提供,任何人都可以免费实现。

大多数浏览器供应商通过浏览器本身附带的附加可执行文件来实现 WebDriver API。下面的列表显示了主要浏览器在哪里提供这个二进制驱动程序:

为了查看 WebDriver API 的运行情况,我们将下载并运行 Chrome 二进制驱动程序。打开https://sites . Google . com/a/chromium . org/chrome driver/downloads,点击最新发布的链接。

然后为您的本地操作系统下载驱动程序。

我用的是 Mac,所以我下载了chromedriver_mac64.zip。在这个归档文件中是名为chromedriver的驱动程序可执行文件。Windows 版本叫做chromedriver.exe

提取这个文件并运行它。您将获得公开 WebDriver API 的端口:

$ ./chromedriver

Starting ChromeDriver 2.40.565386
(45a059dc425e08165f9a10324bd1380cc13ca363) on port 9515

Only local connections are allowed. 

为了与 API 交互,我们需要一个允许我们发出 HTTP 请求的工具。我用过 Postman,它可以从https://www.getpostman.com/买到。

第一步是打开浏览器。这是通过创建新会话来完成的。

为了通过 WebDriver API 创建一个新的会话,我们向/session端点发出一个 HTTP POST 请求。此外,我们需要定义我们希望打开的浏览器的类型。该信息在 POST 主体中的 JSON 对象中发送。在本例中,我们将打开 Chrome:

{
    "desiredCapabilities": {
        "browserName": "chrome"
    }
} 

这个请求的响应包括一个sessionId。我们可以用它在刚刚打开的浏览器窗口上执行额外的操作。

下一个逻辑步骤是在浏览器中打开一个 URL。这是通过对/session/<session id>/url的 HTTP POST 请求来完成的,POST 主体包括将要打开的 URL:

{
    "url": "http://octopus.com"
} 

该调用导致在浏览器中打开所请求的 URL。

此时,你可能会感到有些不知所措。像 Postman 这样的工具使用 JSON body 对 REST API 进行 HTTP 调用……这是一个非常复杂的过程,打开一个浏览器并浏览到一个 URL 的过程并不简单。

但是不用担心。作为开发人员,我们从来不需要直接使用 WebDriver API,事实上这将是我们在这个博客系列中第一次也是最后一次直接查看 WebDriver API。

从这一点开始,我们编写的所有代码都将使用 Selenium API。Selenium 是由第三方提供的一个库,它包装了与浏览器交互时需要进行的所有 WebDriver API HTTP 调用。通过 Java 类和接口公开 WebDriver API 的功能,Selenium API 使得编写与浏览器交互的 Java 应用程序变得更加容易。

下图显示了浏览器、二进制驱动程序、Selenium API 和我们将要编写的 Java 代码之间的关系。

Selenium 与 WebDriver 同义,因为它是利用 WebDriver API 的最流行的方式。我个人从未在现实世界中见过直接针对 WebDriver API 编写代码的案例。因此,术语 WebDriver、Selenium 和 Selenium WebDriver 可以互换使用,指的是 Selenium API,而不是较低级别的 WebDriver API。

这篇博客的关键是:

  • WebDriver API 是一个基于开放标准的低级 HTTP 接口。
  • Selenium API 是调用 WebDriver API 的类和接口的集合。
  • 因为直接使用 WebDriver API 不切实际,所以大家都用 Selenium API。
  • 术语 WebDriver、Selenium 和 Selenium WebDriver 可以互换使用,通常指 Selenium API。

既然我们已经知道了 WebDriver 和 Selenium APIs 之间的区别,那么实际上我们可以将它们视为同一个东西。在讨论如何编写 WebDriver 测试时,我们从现在开始编写的所有代码都将使用 Selenium API。因此,尽管你现在知道 WebDriver 和 Selenium APIs 在技术上不是T4 的同一个东西,我们通常称“Selenium WebDriver”为我们与浏览器交互的平台。

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

Octopus 3.4 的新特性:弹性和瞬态环境——Octopus 部署

原文:https://octopus.com/blog/whats-new-elastic-transient-environments

这篇文章是我们 Octopus 3.4 博客系列的一部分。在我们的博客或我们的推特上关注它。

Octopus Deploy 3.4 已经发货!阅读博文今天就下载


Octopus 3.4 的新特性:弹性和瞬态环境

我想从我们的深入探讨和幕后帖子中退一步,给出弹性和瞬态环境特性集的概述。弹性和瞬态环境引入了一系列新特性,这些新特性使得能够更好地支持在机器可以来来去去的环境中工作。

特征

机器政策

机器策略是一组与机器相关的设置,使您能够定制运行状况检查执行的内容(以及执行的频率),控制触手和 Calamari 更新,以及当机器不可用时会发生什么。这些功能极大地改善了您在机器来来去去的环境中的工作方式。机器策略针对每台机器进行设置,并可在环境屏幕中进行配置。

机器策略允许您:

  • 定义健康检查的时间间隔
  • 定义运行状况检查期间在触手上运行的额外脚本
  • 定义运行状况检查期间离线计算机的行为
  • 定义乌贼和触手何时更新
  • 定义发现离线时是否清理机器

健康检查步骤

基于机器策略,我们还引入了运行状况检查步骤,使您能够触发运行状况检查,作为项目部署过程的一部分。这在定期添加或删除机器以及长期运行部署的环境中很有价值。是的,这意味着在 a 部署期间,您可以重新评估机器是否在线

项目触发器

触发器是一个很棒的新功能,它可以确保添加到环境中的新机器保持最新的适当版本。它们在响应事件时执行,并根据项目进行指定。它允许您创建一个触发器,以便在特定环境中为某些角色提供新的部署目标时自动进行部署。可以通过选择项目屏幕上的触发器菜单项来找到触发器。

项目机器连接设置

通过与其他新功能配合使用,project machine connectivity settings 允许您指定在部署期间计算机不可用时会发生什么情况。这有助于避免在部署时临时计算机不可用时部署失败。这意味着您不必单独选择在线机器作为部署目标!相反,我们将自动跳过离线机器。

包裹

我希望您喜欢探索构成弹性和瞬态环境的高度要求的特性。更多信息,请参见弹性和瞬态环境指南

Octopus 3.4 的新特性:多租户部署——Octopus 部署

原文:https://octopus.com/blog/whats-new-multi-tenant-deployments

这篇文章是我们 Octopus 3.4 博客系列的一部分。在我们的博客或我们的推特上关注它。

Octopus Deploy 3.4 已经发货!阅读博文今天就下载


Octopus 3.4 的新特性:多租户部署

继续上一篇博客文章的主题,我将后退一步,概述新的多租户部署特性集。多租户部署是一个高级特性,它为 Octopus 部署体验增加了一个全新的维度,但我们努力让它变得平易近人!请继续阅读,了解更多信息。😃

为什么我应该关注多租户部署?

如果您曾经想要将项目的多个实例部署到每个环境中,那么您应该考虑在 Octopus Deploy 中进行多租户部署。例如:

  • 您希望在您的测试/QA/UAT 环境中有多个独立的部署
  • 您想要为每个测试人员提供一个隔离的测试部署
  • 您希望为功能分支上的工作提供独立的部署
  • 您希望跨环境管理对单个目标的部署,例如管理一组嵌入式设备或一组笔记本电脑/工作站
  • 您想将您的项目部署给另一个客户
  • 你想把你的项目变成一个 SaaS 应用程序

保罗·斯托弗最近录制了一集《T2》。NET Rocks 谈论构建多租户应用。如果能更好地理解为什么要构建多租户应用程序,以及在设计和部署多租户应用程序时要考虑的因素,这将是一次很好的聆听。强烈推荐的播客!

特征

功能切换

多租户部署的一个最重要的特征是,它被有意设计成一个附加的东西。换句话说,如果你不需要它,你不应该注意到有任何变化。这由 Octopus 功能配置屏幕中的功能开关控制。

同样需要注意的是,如果您启用了多租户部署,您可以继续愉快地部署现有项目,直到您决定将租户连接到它们。如果您将一个项目连接到一个或多个租户,您仍然可以像现在一样通过环境部署它们。我们称之为非租赁部署。

最后,您可以完全控制项目如何支持多租户部署,从禁用它到要求租户进行部署。

头等房客

八达通的租户是一等公民。它们使您能够将项目部署到同一环境中的一个或多个租户。您可以在租户屏幕中创建和管理他们的详细信息,我们已经通过项目和环境更新了部署体验,以支持租户概念。

可变模板

变量模板允许您指定将项目成功部署到租户所需的变量。它们可以在一个项目或一个库变量集上定义,我们根据它们定义的位置以稍微不同的方式解释它们。

在项目中定义变量模板意味着对于租户所连接的每个环境,变量可以有不同的值。这是管理技术设置(如数据库连接字符串或其他特定于环境的细节)的好方法。

在库中定义变量模板意味着无论项目和环境如何,该租户的变量都将有一个常量值。特定于租户的库变量模板值在“通用变量”选项卡下的租户页面上进行管理。

租户标签集

租户标记可帮助您使用自定义标记对租户进行分类,从而为您的项目和环境定制租赁部署。租户标签是在标签集中定义的,用于将相似的标签组合在一起。项目步骤、通道、变量、机器、账户等等都可以被标记。这使您能够与多组租户一起工作,而不是单独工作。例如,您可以将最新版本部署到带有Tenant Important/VIP标签的所有租户。租户标签集可在库租户标签集屏幕中管理。

租户设计者/预览

一些团队可能只有几个租户,而其他团队可能有几千个租户。考虑到这一点,我们建立了一个租户选择设计器/预览工具,以便在您需要时通过 Octopus 轻松找到合适的租户。您可以按名称查找特定租户,按标记集/标记筛选租户,并进一步搜索结果租户。

租户部署

将所有这些功能整合在一起可以实现租户部署。通过增强核心 Octopus 部署体验,将一个版本部署到租户,以支持租户选择一个或多个租户,然后将它们部署到环境中。

Octopus 仪表板现在包括一个租户计数,向您显示一个环境中有多少租户拥有最新版本。这可以进一步定制,以过滤特定租户或标记/标记集值。

默认项目概述指示板让您一目了然地看到哪些版本已经部署到了哪些环境和租户。更好的是,现在可以对仪表板进行过滤和分组,以便只查看您需要的相关信息。

包裹

多租户部署是一项高级功能,但它也是我们最受欢迎的用户意见建议。我们很喜欢在我们构建它的时候从我们的社区收到的反馈,我们迫不及待地想要发布它。有关更多信息,请参见多租户部署指南

为什么要考虑数据库部署自动化?-章鱼部署

原文:https://octopus.com/blog/why-consider-database-deployment-automation

Why consider database deployment automation?

这是关于数据库部署自动化系列文章的第一篇。

对我来说,数据库是任何部署中最伤脑筋的部分。部署代码的压力要小得多。如果有什么不对劲,代码可以回滚。当它进入生产阶段时,如果它是在开发、QA 和预生产中测试过的相同代码,应该不会有任何意外。

数据库没有那么灵活。假设数据库脚本中有一个错误,所有用户的名字都被删除了。没有一个好的方法来回滚。备份可以恢复,但是备份是在什么时候进行的,自备份以来系统中是否有任何用户?如果恢复备份,哪些数据将会丢失?

手动数据库部署的问题

在 Octopus Deploy 工作之前,我是美国一家大型金融机构贷款发放系统的首席开发人员。在任何时候,都有数亿美元的贷款在外逃。这些都是大额贷款,需要许多个人来处理。每个人可能会在贷款上花费 15 分钟到 1 小时不等的时间。财务人员可能会花半个小时输入客户的财务状况,而承销商可能会花一个小时研究客户,并在贷款上添加注释和条件。不用说,恢复数据库备份是最后的手段。告诉人们他们必须重新输入信息,这种情况只会发生很多次,然后他们才会想要找到我。

我们有自动化的代码部署,但是数据库增量脚本是由数据库开发人员在部署之前手工创建的。对他们来说,花上一两个工作日的时间来整理剧本是很平常的事情。手动创建也意味着很少会发生测试。当脚本在生产中运行时,它很可能是第一次运行。我们还使用了 Redgate 的 SQL Compare 等工具,并在可能的情况下对恢复的备份进行了测试,但这只能做这么多。

由于存在风险,部署只能在下班后进行,这意味着晚上或周末,但这确实意味着我们可以为回滚进行备份。除了进行部署的开发团队之外,我们还有一名在线 DBA 和操作人员。操作人员必须等待 DBA 运行所有脚本,这可能需要一秒钟或二十分钟才能完成。

时间安排上的风险和困难意味着我们只能每个季度部署一次主要版本,在此期间进行小的 bug 修复。在一个主要版本之后,我们通常会发布几个错误修复版本,通常是因为错过了一些模式更改。每季度部署一次意味着一次要做很多改变。变更的验证需要很长时间,通常每个版本都需要两三个小时。

数据库模式更改被遗漏,因为数据库没有真实的来源。索引可能存在于生产前,但不存在于 QA 中,那么哪种环境是正确的呢?那个索引是什么时候添加的?谁加的?更糟糕的是,数据库中有将近 6,000 个对象(大部分是 CRUD 存储过程)。数据库开发人员不得不求助于手动跟踪所有的更改。80%的时间是数据库开发人员进行更改,另外 20%是开发人员进行更改。如果数据库开发人员那天不在,我们试图记住通过电子邮件将更改发送给他们,但是想想您在上一个季度对代码所做的所有更改。你记得他们所有人吗?

简而言之,除了应用程序中最关键的部分,我们已经实现了所有的自动化。完全相同的代码在环境中移动时被测试了多次。为每个环境手动创建了唯一的数据库增量脚本。数据库模式没有真实的来源,数据库管理员正在拔头发,试图让一切保持运行。

总结一下我们面临的挑战:

  • 环境都不一样。
  • 为每个环境创建了自定义脚本。
  • 数据库对象存在于一个环境中,而不存在于另一个环境中。
  • 部署耗时数小时。
  • Big bang 多个 bug 修复版本的季度发布。
  • 手动跟踪更改。

数据库变更控制是狂野的西部。

数据库部署自动化

有些东西必须放弃。数据库更改必须进入源代码控制,这些脚本需要打包并在部署期间自动运行。经过大量的讨论、研究和测试,我们找到了一个工具。工具本身并不重要。重要的是,我们自动化了数据库部署。

这种影响几乎立刻就能被察觉。

将数据库放在源代码控制中可以让我们看到什么时候有人做了更改。我们可以将它与故事联系起来,我们知道为什么要进行更改,我们设置它以便删除不在源代码控制中的更改。如果一个索引在 QA 中,但不在源代码控制中,我们就删除它。这有点苛刻,但它保证了数据库模式匹配源代码控制中的内容。

由于随机视图或存储过程丢失而导致的紧急修复几乎降到了零。我们在 Octopus Deploy 中设置了一个手动干预步骤,这允许我们在部署之前查看和批准数据库增量脚本。每个人真正喜欢手动干预步骤的地方是,如果 delta 脚本中出现意外的数据库更改,可以取消部署。这有助于每个人、开发人员、QA 和 DBA 信任这个过程。

对部署的信心开始增加。很快,我们每月进行一次部署。然后一周一次。一旦通过 QA 和业务所有者的验证,功能就可以交付给用户。可以报告一个 bug,一旦通过验证,修复程序就可以交给用户了。我们发布得如此频繁,以至于每次发布的更改数量都显著减少,部署时间从两到三个小时减少到五到二十分钟。

将这一切都放在 Octopus Deploy 中还有一个好处,那就是让数据库管理员和运营团队的夜晚和周末时光重新回来。现在,他们可以安排部署,并且只在出现问题时才需要上线。

博客系列

这篇文章是我带领您建立数据库生命周期管理(DLM)和数据库部署自动化的系列文章的第一篇。本系列的目标是为您提供一些使用各种数据库部署工具的真实示例。除此之外,我们将讨论一些你会遇到的常见陷阱。


数据库部署自动化系列文章:

为什么在 Kubernetes 部署中使用 Octopus?-章鱼部署

原文:https://octopus.com/blog/why-kubernetes-and-octopus-deploy

回到 Octopus 2018.9,我们引入了一系列功能,为 Octopus 中的 Kubernetes 提供了核心支持。

在本文中,我将介绍使用 Octopus 管理 Kubernetes 部署的一些好处。毕竟,Kubernetes 已经为它的所有资源提供了一个丰富的声明性模型,一个全功能的命令行工具,以及更多的仪表盘。

Octopus 2021 Q3 包括对 Kubernetes 部署的更新支持,以及针对 Google Cloud、AWS 和 Azure 用户的 runbooks。在我们的发布公告中了解更多信息。

建模您的开发环境

开发团队中的一个常见做法是在不同的环境中开发代码。虽然没有两个团队是相同的,但是我曾经合作过的每个团队都采用了一些 开发➜测试➜生产 环境生命周期的变体。

环境进展是 Octopus 的核心概念,这并非巧合。环境是 Octopus 中的核心实体,产品的每个部分都包含了对生产环境部署的管理。

从生命周期到频道再到仪表盘,Octopus 使你的团队如何工作的建模变得容易,并以一种可靠和可见的方式促进发布。

以一个 Octopus 项目的概述为例。该表清楚地显示了在什么时间向什么环境发布了什么版本。只需点击几下鼠标,您就可以获得更详细的信息,例如谁执行了部署以及包含了哪些版本的包。

Octopus Dashboard

另一方面,Kubernetes 仪表板关注于显示关于 Kubernetes 资源的信息,比如 pod、服务和部署。这是集群的低级视图,可用于调试和监控。

Kubernetes Dashboard

Octopus 允许您对您的团队已经在使用的环境进行建模,而 Kubernetes 仪表板为调试和其他管理任务提供了一个低级别的资源视图。结合 Octopus 和 Kubernetes,您可以从上到下查看您的基础设施。

管理应用程序变量

Helm 提供了一种表达性模板语言,允许从多个来源提供变量,包括变量 YAML 文件或命令行。这个功能允许定义复杂的和可定制的部署,使得 Helm 成为事实上的 Kubernetes 包管理器。

但是模板语言只是故事的一半。另一半是管理定义特定部署的变量。

Octopus 提供了一个具有全面变量管理功能的解决方案,包括秘密存储和范围规则。这些变量可以传递到 Helm 中,在 Kubernetes 步骤中使用,或者在自定义步骤中使用。

Octopus variables

通过利用 Octopus 管理变量,复杂的 Helm 和 Kubernetes 部署可以轻松地跨多个环境和集群进行协调。

版本化您的 Docker 容器

部署流程创建后,通常不会有太大变化。不同部署之间会发生变化的是容器版本。

Octopus 将构建部署的设计时过程与选择包版本的部署时过程分开。这意味着当您推出容器的新版本时,Octopus 会在部署期间选择这些版本,并将它们合并到生成的 YAML 文件中。

正如您在下面的截图中看到的,在设计 Kubernetes 部署时,您指定了容器 ID,但没有指定版本。

Adding an container

然后在部署过程中,您可以选择一个特定的容器版本,或者简单地让 Octopus 为您选择最新的版本。

Octopus deployment

Octopus 通过分离设计和部署时间问题,使管理可重复部署变得容易,这意味着您只需要在推出新部署时担心您希望部署哪个版本的容器。

单独迭代或集体进步

微服务是一种越来越受欢迎的开发策略,它允许专注的团队在更大的生态系统中快速交付小的变化。

然而,将一组已知版本的单个微服务升级到下一个环境并不是不常见的需求。虽然微服务架构不鼓励这种依赖性,但公司测试或外部法规可能要求您的环境在任何时间点都处于众所周知的状态。

Octopus 可以对这些开发策略进行建模,无论你的团队是通过环境独立推广单个微服务,还是多组微服务一起推广。

对于独立推广自己个人微服务的团队,可以使用个人 Octopus 部署项目。

Project dashboard

为了以可预测的顺序提升一组已知版本的微服务,项目可以使用部署发布步骤。通过将其他项目的部署视为可部署的资源,部署发布步骤允许团队在给定的时间点捕获环境的状态,并将该状态部署到下一个环境。

在下面的截图中,你可以看到一个 Octopus 项目的例子,其中包括一系列的部署发布步骤。这些步骤的顺序确保了微服务以固定的顺序部署,一组部署发布步骤代表了一个完整的微服务生态系统,它作为一个单元部署到新环境中。

The coordinated release of microservices

使用 Octopus 来管理 Kubernetes 部署可以让您在开发过程中自由地快速迭代,同时以可预测的方式在环境之间进行升级。

管理云和从内部迁移

Kubernetes 是您工具箱中的一个优秀工具,但它不太可能是您将使用的唯一工具。Kubernetes 真的是托管静态文件的最佳选择,还是 S3 或 Azure 存储更合适?您还需要使用本地数据库吗?RDS 是不是比容器化数据库更好的选择?

增量迁移、遗留系统和强大的 PaaS 产品通常意味着您的部署策略不会局限于您的 Kubernetes 集群。因为 Octopus 已经支持广泛的云和本地平台,所以您可以跨 Kubernetes 和现有服务无缝集成部署流程。

如果您的部署确实跨越多个技术堆栈,您可以放心,上述所有优势同样适用于您的内部部署和云部署,以创建一个统一的部署流程。

使用最佳实践对 Kubernetes 部署进行建模

互联网安全中心就如何保护您的 Kubernetes 基础设施提供指导。特别是,有两个建议与您的部署策略特别相关:

  • 1.6.1 确保仅在需要时使用群集管理角色。
  • 1.6.2 使用名称空间在资源之间创建管理边界。

Octopus 鼓励通过目标使用具有有限权限的凭证部署到单独的名称空间。Octopus 中的 Kubernetes 目标捕获集群 URL、帐户和名称空间,并且作用于角色和环境。

通常,Kubernetes 目标对于每个环境和角色都有一个惟一的名称空间。建议关联帐户仅拥有在该命名空间内部署所需的权限。结果是 Kubernetes 目标代表了集群中的权限边界。遵循这种模式可以确保您的部署不依赖于单个管理员帐户,并在名称空间内隔离资源。

【T2 Kubernetes targets

通过使用目标,Octopus 鼓励您以安全和可管理的方式对 Kubernetes 部署建模。

集中式审计 Kubernetes 集群管理

你曾经通过电子邮件或 Slack 发送过配置文件吗?您是否曾被要求报告一次生产中断,但却不知道发生了什么变化,因为从人们的桌面上进行了未记录的修改?

不幸的是,这些情况非常普遍。随着您的 Kubernetes 集群变得越来越复杂,管理凭证和了解变化的影响会变得越来越困难。

Octopus 通过脚本控制台提供解决方案。通过使用脚本控制台,开发人员和管理员可以与 Kubernetes 集群进行交互,而无需共享凭证。可以对一个或多个 Kubernetes 目标运行特殊命令,运行的命令、运行它的人以及结果都保存在审计日志中,以后可以查看。

The Script Console

The task log

通过脚本控制台,Octopus 让团队能够调试和管理他们的 Kubernetes 集群,而不需要分发凭证,并且有一个审计日志来记录每个更改。

结论

Octopus 在交付可重复且可靠的部署方面有着悠久的历史,并且已经发展到支持数千个开发团队中的部署模式。

通过使用 Octopus 来管理您的 Kubernetes 部署,您可以获得快速成为管理高密度基础架构的标准平台的所有好处,并将经过实战检验的部署策略融入 Octopus 中。

愉快的部署!

为什么章鱼不用。网络服务总线(或轮询)- Octopus 部署

原文:https://octopus.com/blog/why-octopus-doesnt-use-the-servicebus

Octopus 服务器将软件部署到许多部署代理(“触角”)。通信模型使用标准的客户机/服务器模型,触手监听 TCP 端口(默认为 10933 ), Octopus 与之连接。

Octopus Server -------> Tentacle 

一个常见的功能要求是反转这种通信,因此:

Octopus Server <------- Tentacle 

这方面的一些实现选项可能是:

  • 让触角“轮询”Octopus 服务器的部署命令
  • 使用像 web sockets/SignalR 这样的技术
  • 使用。网络服务总线

举个例子,考虑一家向银行销售软件的云创业公司。Octopus 服务器可能安装在 Amazon EC2 上,而触手代理将安装在银行的生产 web 服务器上,深埋在银行管理的数据中心中。

上面描述的特性——让银行的生产 web 服务器调用 Amazon 托管的 Octopus 服务器——听起来是一个很好的解决方案,可以避免银行打开防火墙规则。但是缺点是什么呢?

为什么章鱼是这样工作的

选择当前的通信架构有几个原因:

  1. “服务器”通常提供一些服务——有可能已经允许到服务器的入站连接,否则您不会向它部署任何东西。根据我的经验,让系统管理员批准一个出站连接和让他们批准一个入站连接一样困难,特别是当 Octopus 服务器与生产 web 服务器处于不同的安全区域时。
  2. 我发现大多数系统管理员实际上更喜欢这样,因为他们可以创建规则来调节入站连接。如果软件试图“绕过”防火墙规则,这可能会导致系统管理员不信任它。
  3. 负载/性能——让触手向外连接意味着某种主动连接——无论是使用套接字还是轮询。通过让触手监听,我们只有一个打开的监听套接字和一个等待 I/O 的阻塞线程。虽然这两种方式的开销都很小,但如果触手碰巧运行在生产 web 服务器上,一些人会觉得这种方法更舒服。
  4. 这与大多数操作工具的工作方式一致——WinRM、PSExec、SSH——都需要“目标”服务器进行监听
  5. 它更简单,越简单越好

逆转这种模式可能会损害信任

Who broke through my firewall?

在我看来,逆转这一模式的唯一原因是“绕过”防火墙规则。但是它实际上并不是关于绕过防火墙规则(从技术上讲,这些规则并不难改变);这是关于围绕控制防火墙规则工作。

如果我是上述银行的系统管理员,我想我更愿意知道在我的生产服务器上运行的软件需要打开一个端口,并且我有机会批准/拒绝它。如果该软件悄悄地“绕过”我的防火墙规则,我会非常不安。我宁愿有人花时间告诉我这个软件的目的(使应用程序部署更简单)和它的安全方面,这样我就可以做出明智的选择。如果八达通使我周围的工作变得容易,我就不太可能信任该产品。

现在,任何银行系统管理员都不太可能同意更改防火墙规则,以便亚马逊托管的 web 服务器可以将软件部署到银行的生产 web 服务器中。但是如果他们不同意,这难道不是足够的理由来知道试图绕过他们是一个坏主意吗?

有有效的场景吗?

虽然替代通信模型的唯一原因似乎是绕过防火墙(或者换句话说,绕过系统管理员),但是有没有这样的场景,它可能是一个好主意呢?

反转模型可能是一个好主意的一个场景是当使用 Octopus 部署桌面软件时,其中触手运行在每个客户机工作站上。虽然它有可能工作,但我认为将软件部署到客户机工作站与将软件部署到服务器是完全不同的问题领域(由于连接性、大量的机器、需要客户机单独回滚等等),Octopus 首先不适合这种情况。这就是为什么 Windows Update 不构建在 MS Deploy 之上的原因。

另一个例子可能是防火墙由外部提供商管理,虽然系统管理员很乐意更改规则,但及时更改规则可能很困难。同样,这感觉像是对人的问题的技术解决方案,但有时这就是它所需要的。

我很乐意听到您可能遇到过的任何使这种沟通模式成为必要的场景。现在,当我考虑实现它需要做的工作以及这种模型可能在产品中产生的不信任时,我觉得添加一个基于轮询/服务总线的通信模型并不是一个高优先级的特性。

为什么八达通使用自签名证书-八达通部署

原文:https://octopus.com/blog/why-self-signed-certificates

Octopus Deploy 服务器和 Tentacles 之间的通信使用 X.509 证书进行保护。安装 Octopus 后,会生成一个新的自签名证书。安装 Tentacles 时,它们还会生成一个新的自签名证书。在这篇文章中,我想讨论一下为什么我们使用自签名证书——毕竟,这不是很糟糕吗?

背景

当触手在 Octopus 中注册时,拇指纹(标识证书的公钥)被交换——也就是说,您手动将 Octopus 拇指纹粘贴到触手设置向导中,并在 Octopus UI 中手动验证触手的拇指纹。这张截图来自 Octopus 1.0,但在 2.6 中也有相同的概念:

Exchanging thumbprints

当 Octopus 连接到触手时,它会验证触手是否提供了带有它所期望的指纹的证书。同样,当触手处理一个连接时,它会验证这个连接是否来自它信任的 Octopus 服务器。如果对方不是他们认为的那个人,双方都会中止连接。

为什么自签名?

有时会出现一个关于我们为什么使用自签名证书的问题——这难道不是一种糟糕的做法吗?要回答这个问题,让我们反过来问:为什么要使用证书颁发机构?

假设你想做一些网上银行。你去你的银行网站,并被重定向到他们的 HTTPS 网站。

Bank of America certificate

你实际上不知道这是不是美国银行。但是你的浏览器信任赛门铁克(拥有 VeriSign),赛门铁克告诉你“当有人为 BankOfAmerica.com 订购证书时,我们给他们打了电话并做了一些其他检查,我们非常确定这确实是美国银行。你信任我们,因为我们是诺顿,对吗?”

这里有一个更安全的替代方案,可以替代依赖赛门铁克这样的证书颁发机构(CA ):当你在银行分支机构设立账户,与银行经理握手时,他们会给你一个指纹,作为他们网站证书的指纹。你把它写在一些纸上,当你回家时,你把它插在你的电脑上。这样你就不需要任何认证机构:你的计算机将只信任 BankOfAmerica.com,而不需要任何第三方。

为了方便起见,我们有认证机构,因为期望人们为他们计划访问的每个站点写下公钥太费力了。我们没有安全认证机构:它们实际上降低了安全性。CA 受到威胁:还记得 DigiNotar 吗?

如果章鱼用了 CA 的

如果 CA 和 Octopus 一起使用,我们有两种方法可以使用它们:

  1. 我们只信任由 CA 发布的任何内容。我们没有检查章鱼/触手是否可信,而是假设如果它们提供了由你的公司颁发的证书,那么就可以进行通信了。
  2. 无论如何,我们都要验证指纹,并用它们来建立信任。

方法#1 是危险的,因为如果 CA 受到威胁,那么网络上的每台机器实际上都受到威胁,因为任何拥有有效证书的人都可以伪装成其他人。无论 CA 是您组织内部的,还是受信任的第三方,这一点都适用。使用我们当前的方法,由于我们单独验证证书,机器 A 不可能在没有机器 B 的私钥的情况下伪装成机器 B。

方法#2 更好,只是我们实际上忽略了 CA。如果我们自己验证指纹,CA 在这种情况下提供了什么价值?

有人可能会说,Octopus 应该支持使用 CA 来颁发证书,只是因为“感觉良好”的因素,因为我们已经习惯于认为自签名证书在某种程度上本质上是不好的。你可以使用Tentacle.exe import-certificate让 Tentacles 使用 CA 颁发的证书(但是目前在 Octopus 服务器上没有办法做到这一点),但是我认为争论是由公司政策驱动的(“禁止自签名证书!”)而不是通过合理的推理。

从安全的角度来看,我们能想到的关于 CA 在 Octopus 中有意义的唯一理由是撤销列表。但是你总是可以告诉 Octopus/触手不再信任一个给定的证书,而不需要 CRL,所以我认为这个论点也站不住脚。

我们在 Octopus 中非常重视安全性,我们也尽力使其易于使用。我们认为从安全的角度来看,交换指纹和使用自签名证书是最好的解决方案,添加任何额外的 CA 支持层似乎会使软件变得比必要的更复杂,并且要么没有增加任何价值,要么根据其实现方式来创建攻击媒介。我希望这篇文章能对我们的推理有所启发。

感谢 OJ 来自 Beyond Binary 对本帖草稿的审阅

使用 NGINX 单元有什么好处?-章鱼部署

原文:https://octopus.com/blog/why-use-nginx-unit

NGINX Unit web server

NGINX 是当今最流行的 web 服务器和反向代理之一。它提供了高性能和几乎无限的可配置性,是 Kubernetes 等现代堆栈中常用的组件。现在,NGINX 团队有了一个名为 NGINX Unit 的新产品,旨在解决现代开发过程中的一些常见挑战。

如果你跳转到 NGINX 单元文档,你会发现一个精彩的来自 NGINX Conf 17 的现场演示。在同一个会议上还有一个概述会议。这些演示很好地回答了 NGINX 单元是做什么的,但仍然让我想知道为什么有人会选择使用 NGINX 单元。

在这篇博文中,我们将看看 NGINX 单元提供的一些优势。

标准化的 JSON 配置文件

传统的 NGINX 配置文件看起来像这样:

server {
  location / {
    fastcgi_pass localhost:9000;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param QUERY_STRING  $query_string;
  }

  location ~ \.(gif|jpg|png)$ {
    root /data/images;
  }
} 

虽然花引号语言的开发人员应该对这种格式相当熟悉,但 NGINX 配置文件语法不符合任何通用标准。如果您想定期更新这个文件,您可以编写复杂的sed命令来编辑原始文本。

但是使用正则表达式修改配置文件并不是一种愉快的体验。不可避免地,你会发现你的正则表达式匹配了一些你没有预料到的东西,没有考虑到行尾或者没有完全捕捉到一个值的所有变化。

NGINX 单元通过利用 JSON 进行配置来解决这个问题。对于如何构造配置数据不再有任何模糊之处,而且以编程方式更新配置值要容易得多。依靠公共数据格式使得 NGINX 单元更容易管理。

HTTP 配置 API

每一个现代计算平台都有一个由结构良好的 API 支持的丰富的 CLI 工具。很容易认为这个功能是理所当然的,直到您发现自己在对一个配置文件运行sed,然后重新启动一个服务。

虽然 NGINX 单元没有提供 CLI 工具(文档中的所有例子都使用了curl,但是它通过一个易于理解的 HTTP API 公开了所有的配置。这为您提供了很大的灵活性来选择如何公开 API(即,在本地主机上公开它或者安全地代理它以使它公开可用),这意味着您可以使用您选择的任何脚本工具来与之交互。

常见用例的声明性模型

鉴于 NGINX 无处不在,我发现自己不得不每年阅读几次随机的 NGINX 配置文件。每次我都必须查阅文档来回忆这些命令的作用,每次我都发现自己必须从命令序列中逆向工程配置的意图。

NGINX 单元摒弃了这种命令式配置模型,而是公开了一种声明式配置模型。诚然,NGINX 单元模型远不如传统的 NGINX 配置文件可配置,但它很好地展示了您将使用的常见路由和安全选项。

声明式配置模型使得 NGINX 单元对于那些有配置云服务和像 Kubernetes 这样的平台的经验的人来说更加容易接近。

一致的网络层

多语言开发越来越受欢迎。无论是因为您的团队找到了最适合他们需求的混合语言,还是因为您的基础架构包括用多种语言编写的第三方产品,拥有一个包含多种编程语言的应用程序堆栈并不罕见。

然而,网络层仍然需要以一种内聚的方式来处理,当每个应用程序都有一个以稍微不同的方式配置的网络组件时,这是一个挑战。

NGINX 单元是一种日益增长的趋势的一部分,它将网络问题从单个应用程序中分离出来,使之成为基础设施层的责任。NGINX 单元通过公开一个公共 API 和处理安全和路由等公共网络任务来整合网络问题。

无需重启的重新配置

NGINX 单元在它托管的应用程序进程和它置于顶层的网络层之间提供了一个清晰的分离。这使得无需重启托管应用程序就可以更改网络配置。这也意味着网络更改可以应用到正在运行的系统中,而无需停机。

将这种将更改应用到实时系统的能力与 NGINX 单元托管的所有应用程序都可以利用的一致网络配置相结合,您就拥有了一个可以随着部署数量和应用程序版本的增加而轻松扩展的基础架构。

采用门槛低

NGINX 单元只需几秒钟就能安装完毕,只需一个命令就能启动,并且只需对配置进行一次更新,就能托管您的第一个应用程序。它不需要代理、控制平面、数据库或专用集群来运行。

与普通云部署或第一次启动和运行 Kubernetes 集群的工作量相比,NGINX 是一股新鲜空气。

你所看到的就是你用 NGINX 单元得到的,使它易于部署、推理和诊断。

将应用程序作为服务进行管理

您目前如何管理 Node.js 应用程序的生产部署?Node.js 本身并没有真正提供一个解决方案,这已经产生了一系列的解决方案,如 PM2永远、nohup 或通过 systemd 的本地服务管理。

NGINX Unit 是托管传统上依赖第三方解决方案来引导、运行和重启进程的应用程序的有效替代方案,其额外的好处是它可以托管用多种语言编写的应用程序。

使用单位的缺点

使用 NGINX 单元时,有一些缺点,或者至少是需要注意的问题:

  • 没有 Windows 端口,这使得 NGINX 单元仅限于 Linux 和 MacOS 商店。
  • 必须修改 Go 和 Node.js 代码库才能与 NGINX 单元一起工作。
  • NGINX 单元仅限于证书处理和基本路由。例如,您不会发现认证、复杂的重写规则、故障注入、重试、加权流量分割等。
  • NGINX 单元不为它管理的进程提供健康检查。

结论

总的来说,我得到的印象是 NGINX Unit 就是 NGINX 如果今天写出来的样子。它提供了一个基于 JSON 的配置模型,通过 HTTP API 公开该配置,并提供了一个用于配置最常见的网络用例的声明性模型,所有这些都是轻量级的,并且易于运行。

这种回归基础的方法确实意味着 NGINX 目前支持的一些用例在 NGINX 单元中是不可能的,但随着每隔几个月就会有一个新版本,我预计 NGINX 单元的功能将会增长。

如果 NGINX 单元提供的功能满足您今天的需求,那么它是部署传统 NGINX 服务的自然选择。

通过 XML 模板或 CLI 脚本配置 wild fly-Octopus Deploy

原文:https://octopus.com/blog/wildfly-cli-vs-xml

当用像 Puppet、Chef、Ansible 等自动化工具构建 WildFly 服务器时,你将面临如何从库存下载到定制服务器的问题。定制的很大一部分是在构建过程中如何编辑配置文件(standalone.xmldomain.xmlhost.xmlhost-slave.xml)。

编辑这些 XML 文件时,您有两个主要选择:直接编辑文本,或者使用 WildFly CLI 进行更改。两者各有利弊,我们将在这篇博文中探讨。

直接编辑 XML

直接编辑 XML 配置文件可能是最自然的选择。由于 XML 文件只是纯文本,并且每个部署工具都有某种形式的模板支持,因此在基本模板中进行所需的更改并在部署期间复制定制版本是非常容易的。

这种方法有许多好处。

首先,您不需要 WildFly 的运行实例来进行配置更改。这消除了您在使用 CLI 工具时会遇到的一些麻烦,默认情况下,CLI 工具需要运行一个实例,并会强制您重新启动服务器以使一些更改生效。

第二,所有的更改本质上都是以“批处理”方式完成的,也就是说所有的更改都是一次性完成的。当您复制一个模板 XML 文件时,这听起来可能很明显,但是它确实删除了在通过 CLI 进行更改时必须考虑的一些内容。

但是,直接编辑 XML 配置文件有一些明显的缺点。

WildFly 使用的一些 XML 文件不是静态的。例如,WildFly 将使用当前部署的应用程序的详细信息更新domain.xmlstandalone.xml文件。任何部署脚本的目标都是幂等的,但是如果您在每次部署时盲目地将一个新的 XML 配置文件复制到服务器上,您会发现自己丢失了胡乱做出的运行时更改。此外,您可能会发现带有新 XML 配置文件的服务器没有部署,这可能会给生产带来巨大的冲击。

如果没有预先考虑,您可能还会发现自己处于这样一种情况,即很难看出对 XML 文件做了什么更改。这些配置文件相当长,如果没有 diff 工具,几乎不可能发现定制模板中的更改。

出于同样的原因,在 WildFly 的新版本中,将您的定制应用到配置文件也是一个挑战。鉴于 WildFly 大约每年都会发布一个主要版本(从 WildFly 12 开始,这个发布时间表将会加快),您真的希望能够轻松地将您的定制应用到下一个版本,即使只是为了利用 WildFly 后续版本中的安全补丁。

对于那些直接对 XML 配置文件应用更改的人,我的建议是为每个更改添加清晰的注释。例如,您可以使用如下代码更改配置以绑定到任何网络适配器:

<interfaces>
        <interface name="management">
            <!-- CHANGE: Bind to any IP address -->
            <any-address/>
        </interface>
        <interface name="public">
            <!-- CHANGE: Bind to any IP address -->
            <any-address/>
        </interface>
</interfaces> 

WildFly 在启动时会丢弃这些注释(WildFly 在运行时覆盖 XML 文件是 WildFly 运行时无法编辑这些文件的原因),但通过简单的搜索字符串,您可以找到您在模板中所做的所有更改。这使得将更改移植到新版本变得容易,或者简单地理解您的定制版本与股票下载有何不同。

使用 CLI 更新

使用 WildFly CLI 工具来应用定制比编辑 XML 文件更高级,但它确实有许多优点。

您可能会发现,您的 CLI 脚本将适用于 WildFly 的新版本,无需任何更改。例如,以下 CLI 命令将公共接口绑定到任何地址:

/interface=public/:undefine-attribute(name=inet-address)
/interface=public/:write-attribute(name=any-address,value=true) 

我希望这些命令能在 WildFly 的所有最新版本中工作,也希望它们能在即将发布的版本中工作。这使得升级你的 WildFly 版本更加容易。

这些 CLI 命令还提供了一种简洁的方式来理解对股票下载所做的更改。与模板 XML 文件不同,在模板 XML 文件中,更改分散在一个大文件中,CLI 命令易于查看和理解。

因为您正在对当前配置应用有针对性的更改(而不是覆盖整个 XML 文件),CLI 命令有可能是等幂的。通过使用 CLI 流控制语句,可以仅在当前设置不是所需状态时应用更改。

CLI 命令提供了一定程度的错误检查。公平地说,WildFly 中的 XML 验证非常严格,所以不太可能有无效的 XML 和可启动的服务器,但是当您试图做错什么时,CLI 会提供更直接的反馈。

对于复杂的环境,您可以利用 CLI Java 库,用任何 JVM 语言编写您的更改。Groovy 是一个不错的选择。使用 Grape 你可以删除任何需要的依赖项,你的脚本可以像任何其他可执行文件一样在 Linux 或 MacOS 环境中运行。

这个示例 groovy 脚本使用 WildFly CLI 库来执行所需的 CLI 命令。虽然这是一个非常简单的例子,但是可以修改它以适应更复杂的需求。

#!/usr/bin/env groovy

@Grab(group='org.wildfly.core', module='wildfly-embedded', version='2.2.1.Final')
@Grab(group='org.wildfly.security', module='wildfly-security-manager', version='1.1.2.Final')
@Grab(group='org.wildfly.core', module='wildfly-cli', version='3.0.0.Beta23')
import org.jboss.as.cli.scriptsupport.*

final DEFAULT_HOST = "localhost"
final DEFAULT_PORT = "9990"
final DEFAULT_PROTOCOL = "remote+http"

def cli = new CliBuilder()

cli.with {
    h longOpt: 'help', 'Show usage information'
    c longOpt: 'controller', args: 1, argName: 'controller', 'WildFly controller'
    d longOpt: 'port', args: 1, argName: 'port', type: Number.class, 'Wildfly management port'
    e longOpt: 'protocol', args: 1, argName: 'protocol', 'Wildfly management protocol i.e. remote+https'
    u longOpt: 'user', args: 1, argName: 'username', required: true, 'WildFly management username'
    p longOpt: 'password', args: 1, argName: 'password', required: true, 'WildFly management password'
}

def options = cli.parse(args)

if (!options) {
    return
}

if (options.h) {
    cli.usage()
    return
}

def jbossCli = CLI.newInstance()

jbossCli.connect(
                options.protocol ?: DEFAULT_PROTOCOL,
                options.controller ?: DEFAULT_HOST,
                Integer.parseInt(options.port ?: DEFAULT_PORT),
                options.user,
                options.password.toCharArray())

jbossCli.cmd("/:take-snapshot")
jbossCli.cmd("/interface=public/:undefine-attribute(name=inet-address)")
jbossCli.cmd("/interface=public/:write-attribute(name=any-address,value=true)")

System.exit(0) 

该脚本可以在以下情况下运行:

./script.groovy -u admin -p password01 

不过,运行 CLI 脚本也有缺点。

通常,您需要有一个正在运行的 WildFly 实例,以便运行您的 CLI 脚本。当服务器没有正确配置以便引导,从而可以对其运行 CLI 脚本时,这可能会很棘手。例如,接口绑定可能不正确,端口可能不正确,或者从属实例可能无法连接到域主节点。

这种情况可以通过运行嵌入式服务器来缓解,这是一种“离线”模式,让您无需运行服务器就可以访问 CLI。

您还需要知道哪些更改需要重新启动服务器,以及哪些更改需要作为批处理的一部分进行。这是在复制 XML 文件和启动服务器时不需要担心的两件事。

最后的想法

如果需要等幂,CLI 脚本是最佳选择。它们允许您只修改不处于所需状态的设置,并且是非破坏性的。CLI 脚本往往更容易移植到新的 WildFly 版本,并提供了您的实例如何不同于股票下载的简明定义。

对于不可变的环境,编辑 XML 文件的简单性是无法超越的。每个系统管理员都知道如何编辑 XML 文件(而对于新手来说,CLI 可能比较棘手),每个部署工具都支持某种类型的模板,可以处理 XML 文件。只要您小心翼翼地清楚识别对股票配置文件所做的更改,将这些更改移植到 WildFly 的新版本是非常容易的。

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

从 Octopus Deploy - Octopus Deploy 部署到 Wildfly

原文:https://octopus.com/blog/wildfly-deploy

这篇文章是为老版本的 Octopus 写的,在加入原生野生支持之前。

请访问章鱼指南获取关于部署到野外的最新说明。

在 Octopus,我们已经开始了一个项目,研究如何更好地支持 Java 开发人员。在之前的一篇博客文章中,我谈到了如何使用 Octopus Deploy 将 WAR 文件部署到 Tomcat。Tomcat 无疑是当今产品中最受欢迎的 Java 应用服务器(根据来自 PlumbrRebel Labs 的统计,Tomcat 占据了超过 50%的市场份额)。下一个最受欢迎的应用服务器是 JBoss/wildly,在这篇文章中,我将向您展示如何将 WAR 文件部署到 Wildfly 11(目前在 Alpha 中)。

像 Tomcat 一样,将 WAR 文件部署到 Wildfly 可以简单到只需将它复制到一个特殊的目录。在 Wildfly 的情况下,将 WAR 文件复制到standalone/deployments目录将触发 Wildfly 部署并运行应用程序。这是一个非常有效的部署方法,如果这对您有用,那么关于部署到 Tomcat 的博文同样适用于 Wildfly。

然而,有些情况下简单的文件拷贝在 Wildfly 中不起作用。特别是,部署到 Wildfly 域控制器通常是通过 JBoss CLI 工具完成的。

CLI 部署面临的挑战

当从 Octopus Deploy 部署 WAR 文件时,理想情况下,我们希望这些步骤是等幂的。幂等只是一种花哨的说法,表示部署可以运行任意次,最终结果都是一样的。

JBoss CLI 工具中的部署操作不是这样的。您不能执行 CLI 命令,如

deploy myApplication.war --all-server-groups 

多次,因为一旦文件myApplication.war在 Wildfly 内容存储库中,如果没有像--force这样的选项,它就不能被覆盖。这自然会让您尝试运行这样的命令

deploy myApplication.war --all-server-groups --force 

这是行不通的。您不能在一个命令中强制上传文件并将其部署到服务器组。

这意味着,如果您希望能够将应用程序部署和重新部署到域环境中,您需要做一些额外的工作来确保正在运行的命令能够正常工作,无论该应用程序是否存在于 Wildfly content repository 中,它是否被分配到服务器组,以及它是否被启用或禁用。

绝妙的解决方案

当我们谈到部署到一个 Wildfly 域时,我们希望完成两项任务:

  • 在 Wildfly 内容存储库中创建或更新 WAR 文件的内容。
  • 可以选择将 WAR 文件分配给处于启用或禁用状态的服务器组。

这个 Groovy 脚本通过几个命令行参数公开了这些任务。在幕后,做了大量的工作来允许部署根据需要重新运行多次,并利用 Spring retry 框架来确保部署确实到达了 Wildfly 服务器。

利用 Octopus Deploy 的力量

我们可以使用两种方法将文件放入 Wildfly 域控制器。

第一种方法是通过管理界面将文件直接上传到 Wildfly 服务器。在这种情况下,Wildfly 服务器将在公共 IP 地址上公开其管理端口,而 Octopus Deploy 服务器本身将运行脚本,将文件上传到 Wildfly 服务器。

这种解决方案有一些缺点。

  • 这些文件的上传没有经过优化,这意味着当您上传 100 MB 的 WAR 文件时,您必须在每次重新部署时发送该数据的每个字节。
  • 您的 Wildfly 服务器需要在公共 IP 地址上公开管理接口。
  • 运行脚本的负担放在 Octopus Deploy 服务器上

第二种方法是通过 Octopus deploy package 步骤将文件分发到 Wildfly 服务器,然后使用 CLI 工具的本地副本将文件上传到运行在同一实例上的 Wildfly 服务器。

这个解决方案解决了第一种情况的缺点。

  • 通过 Octopus Deploy 分发的文件可以利用增量复制。这意味着可能只复制包中已更改的部分,从而减少网络流量并缩短部署时间。
  • Wildfly 服务器只需要将其管理接口暴露给本地主机适配器,因为所有 CLI 命令都将从本地服务器运行。
  • Octopus Deploy 服务器只负责复制文件和脚本,而 Wildfly 服务器本身将在本地运行脚本。

鉴于第二种方法的明显优势,这就是我们将如何进行。

先决条件

Wildfly 域控制器服务器需要安装以下软件包:

  • Groovy,由将部署 WAR 文件的脚本使用。
  • 章鱼部署乌贼所用的单色。

使用 SDKMAN 安装 Groovy。它通常比您的 Linux 发行版附带的软件包做得更好。

您需要将一个 WAR 文件打包并推送到 Octopus Deploy 服务器。参见关于部署到 Tomcat 的博文,了解如何打包一个 WAR 文件并将其推送到 Octopus。

您还需要将 Wildfly 域控制器添加到 Octopus 环境中。正如我们在 Tomcat 示例中所做的,Wildfly 服务器很可能使用 SSH 连接。

最后,Wildfly 服务器需要将 Groovy 脚本和jboss-cli.xml文件复制到一个位置,该位置可以被章鱼用来连接 Wildfly 服务器的 SSH 用户访问。我把https://github.com/OctopusDeploy/JBossDeployment.git回购克隆到了/opt,所以所有的文件都在/opt/JBossDeployment下。

部署到 Wildfly 域控制器

将包部署到 Wildfly 将是一个两步的过程。

部署包

第一步将利用Deploy a package步骤将 WAR 文件放到 Wildfly 域控制器文件系统中。点击Configure features链接,勾选Custom installation directory选项。

Custom Installation Directory

然后在Install to字段中定义保存 WAR 文件的目录。我用过目录/var/deployments

Install to

确保 Octopus 连接到 Wildfly 服务器的 SSH 用户拥有将文件复制到该目录的权限,否则该步骤将失败。

运行脚本

第二步将利用Run a Script步骤从 Bash 启动 Groovy 脚本。

Run on选项设置为Deployment targets,选择Bash作为脚本类型,并输入以下内容作为脚本体:

cd /opt/JBossDeployment
./deploy-app.groovy --controller=localhost --user=user --password=password --application=/var/deployments/demo.war --enabled-server-group=main-server-group 

脚本第一次运行可能需要几分钟,因为它将下载 Maven 依赖项(由 Groovy 脚本中的@Grab注释引用)并将它们存储在本地缓存中。

如果您想查看下载进度,请像这样运行脚本:

cd /opt/JBossDeployment
groovy -Dgroovy.grape.report.downloads=true deploy-app.groovy --user=user --password=password --application=/var/deployments/demo.war --enabled-server-group=main-server-group 

一旦下载了所有的依赖项,部署脚本将把 WAR 文件上传到 Wildfly,将其添加到main-server-group服务器组,并启用它。

部署到独立的 Wildfly 实例

为独立 Wildfly 实例部署包的过程与部署到域控制器非常相似。唯一的区别是传递给 Groovy 脚本的参数。

因为独立服务器没有服务器组的概念,所以删除了参数enabled-server-group。否则所有步骤保持不变。

cd /opt/JBossDeployment
./deploy-app.groovy --controller=localhost --user=user --password=password --application=/var/deployments/demo.war 

后续步骤

我们将使用像这里的脚本这样的实验来指导 Octopus 将来对 Java 和 Wildfly 这样的应用服务器的支持。在接下来的几周里,我们将为计划中的 Java 支持准备一份 RFC,如果您对 Java 支持感兴趣,我们将非常感谢您的反馈。

将证书部署到 WildFly - Octopus 部署

原文:https://octopus.com/blog/wildfly-https

Octopus Deploy 最近增加的一个功能是能够在您的基础设施中管理和部署证书。在这篇博文中,我将向您展示如何将证书从 Octopus 导出到 Java 密钥库中,然后使用该密钥库来保护运行在 Windows 上的 WildFly 独立实例或域实例。

先决条件

要运行这篇博文中的命令和脚本,您需要安装一些工具。

第一个是 OpenSSL 客户机。我使用了来自 Shining Light Productions 的 Windows OpenSSL 二进制文件。

第二个是 Groovy SDK。你可以从 Groovy 下载页面下载安装程序。

这些步骤用 WildFly 11.0.0.Alpha1 进行了测试。

最后,你还需要安装 Java 8 JDK。

创建证书存储

首先,我们需要将证书和私钥对上传到 Octopus Deploy 中。最简单的方法是创建一个自签名证书和私钥,然后将它们合并到一个 PKCS12 密钥库中。

要创建私钥和证书,请运行以下命令:

openssl req -x509 -newkey rsa:2048 -keyout private.pem -out certificate.pem -days 356 

系统会提示您提供密码并填写一些个人信息,之后会创建两个文件:private.pemcertificate.pem。这些文件是私钥和证书。

然后,我们需要将这两个文件组合成一个 PKCS12 密钥库,我们可以使用以下命令来完成:

openssl pkcs12 -export -in certificate.pem -inkey private.pem -out combined.pfx -name octopus 

combined.pfx文件现在包含证书和私钥,可以上传到 Octopus Deploy。

Certificate

导出证书存储

既然证书是由 Octopus 管理的,我们需要创建一个带有自定义脚本步骤的项目,以便将证书放到我们的 WildFly 服务器上。

首先,我们需要通过变量引用证书。在 Octopus 项目的Variables部分,创建一个名为Certificate的新变量,该变量引用刚刚上传的证书。

Certificate

【T2 Certificate

创建这个变量使我们能够访问证书中保存的信息的种不同表示。

下面的脚本将证书和私钥作为 PEM 文件保存在目标服务器上,将它们合并到组合的 PKCS12 密钥库,然后将 PKCS12 密钥库导入到 Java 密钥库中。

作为我们 Java RFC 的一部分,将这个证书直接公开为 Java 密钥库是我们打算包含在 Octopus 中的一个特性。但是今天,我们必须使用一个手动脚本来提取证书细节并将其导入到 Java 密钥库中。

这个脚本的最终结果是一个名为c:\keystore.jks的文件,它是我们可以从 WildFly 引用的 Java 密钥库,以启用 HTTPS 支持。

if ([String]::IsNullOrWhiteSpace($OctopusParameters["Certificate.CertificatePem"])) {
    Write-Error "Certificate is empty"
}

if ([String]::IsNullOrWhiteSpace($OctopusParameters["Certificate.PrivateKeyPem"])) {
    Write-Error "Private key is empty"
}

$OctopusParameters["Certificate.CertificatePem"] | Out-File -Encoding ASCII "C:\certificate.pem"
$OctopusParameters["Certificate.PrivateKeyPem"] |  Out-File -Encoding ASCII "C:\private.pem"
# Fix the error
# WARNING: can't open config file: /usr/local/ssl/openssl.cnf
$env:OPENSSL_CONF="C:\OpenSSL-Win64\bin\openssl.cfg"
C:\OpenSSL-Win64\bin\openssl.exe pkcs12 -export -inkey C:\private.pem -in C:\certificate.pem -name octopus -out C:\keystore.pfx -password pass:Password01
if (Test-Path C:\keystore.jks) {
  rm C:\keystore.jks
}
C:\Java\x86\jdk1.8.0_131\bin\keytool -importkeystore -srckeystore C:\keystore.pfx -srcstoretype pkcs12 -destkeystore C:\keystore.jks -storepass Password01 -srcstorepass Password01 -noprompt 

在 WildFly 中配置 HTTPS 支持

在我们开始配置 WildFly 以利用 Java 密钥库之前,有必要回顾一下我们在 WildFly 中配置 HTTPS 支持的确切含义。

WildFly 有两个 web 界面:客户端用来查看已部署的 Java 应用程序的界面,以及管理界面。两者都可以用 HTTPS 保护,尽管两者的过程略有不同。

此外,WildFly 可以以独立模式部署,也可以作为域部署。同样,为独立服务器和域服务器配置 HTTPS 支持也有细微的区别。

虽然 WildFly CLI 工具功能强大,并公开了配置 HTTPS 支持所需的所有功能,但它的运行级别较低,不包括像configureHTTPS()这样的功能。CLI 工具的一个缺点是它不是等幂的,这意味着使用 CLI 工具配置 WildFly 实例通常需要不同的步骤序列,这取决于服务器的当前状态。

当您从像 Octopus 这样的平台进行部署时,这并不理想。您想要的是有一种方法来描述您希望达到的理想状态(比如“配置 HTTPS”),而不必知道服务器的当前状态。

为了方便起见,我们创建了一个 Groovy 脚本,它隐藏了需要通过 CLI 工具发出的低级命令,以便在 WildFly 中配置(或重新配置)HTTPS 支持。

独立模式下的 HTTPS 支持

要配置具有 HTTPS 支持的独立 WildFly 实例,请运行以下命令:

groovy deploy-certificate.groovy --controller localhost --port 9990 --user admin --password password --keystore-file C:\keystore.jks --keystore-password Password01 

用户名和密码需要与您已经用 adduser 脚本配置的相匹配。您可以通过打开 http://localhost:9990 并在提示登录时输入这些凭据来验证这些凭据是否有效。

在后台,脚本首先创建一个名为octopus-ssl-realm的安全领域,它引用密钥库:

<management>
   <security-realms>
      <security-realm name="octopus-ssl-realm">
         <server-identities>
            <ssl>
               <keystore path="C:\keystore.jks" keystore-password="Password01" alias="octopus" />
            </ssl>
         </server-identities>
      </security-realm>
      ...
   </security-realms>
</management> 

然后,它将配置一个引用安全领域的 https 侦听器:

<subsystem >
   <server name="default-server">
      <http-listener name="default" socket-binding="http" redirect-socket="https" enable-http2="true" />
      <https-listener name="https" socket-binding="https" security-realm="octopus-ssl-realm" enable-http2="true" />
      <host name="default-host" alias="localhost">
         <location name="/" handler="welcome-content" />
         <filter-ref name="server-header" />
         <filter-ref name="x-powered-by-header" />
         <http-invoker http-authentication-factory="application-http-authentication" />
      </host>
   </server>
   ...
</subsystem> 

最后,重新启动服务器,以便新设置生效。当你打开 https://localhost:8443/的时候,你会看到 WildFly 现在被你的自签名证书保护了。

Certificate

保护独立管理界面

保护管理接口可以简单地通过添加--management-interface参数来完成。

groovy deploy-certificate.groovy --controller localhost --port 9990 --user admin --password password --keystore-file C:\keystore.jks --keystore-password Password01 --management-interface 

虽然这只是对前一个命令的一个参数更改,但是实际完成的工作有点不同。

首先,从ManagementRealm中引用密钥库:

<security-realm name="ManagementRealm">
   <server-identities>
      <ssl>
         <keystore path="C:\keystore.jks" keystore-password="Password01" alias="octopus" />
      </ssl>
   </server-identities>
   ...
</security-realm> 

然后,管理界面被更新以包括 https 套接字绑定:

<management-interfaces>
   <http-interface security-realm="ManagementRealm">
      <http-upgrade enabled="true" />
      <socket-binding http="management-http" https="management-https" />
   </http-interface>
</management-interfaces> 

并再次重启 WildFly 实例以使更改生效。

现在,您可以打开 https://localhost:9993,通过 https 与管理控制台进行交互。

Certificate

域模式下的 HTTPS 支持

如果您作为域的一部分运行 WildFly,那么使用 HTTPS 保护 web 界面的命令实际上与您在独立模式下运行时是一样的。

但是,关于配置 WildFly 域的成员,有一些重要的事情需要记住。

首先,您运行脚本的控制器是域控制器。在后台,域控制器正在更新两个文件:

  • 位于从服务器文件系统上的host.xml文件(或者当 WildFly 从服务器启动时传递到--host-config选项中的任何文件)。
  • 位于域服务器文件系统上的domain.xml文件(或者当 WildFly 域控制器启动时传递给--domain-config选项的任何文件)。

第二件事是密钥库路径是相对于从属服务器的。因此,在运行这个命令之前,将keystore.jks文件复制到从属服务器是很重要的。

groovy deploy-certificate.groovy --controller domaincontroller --port 9990 --user admin --password password --keystore-file C:\keystore.jks --keystore-password Password01 

与独立服务器一样,在域中运行该命令将配置一个安全领域,但是这次是在当前域中所有从属服务器的host.xml文件中。

如果您只想更新特定的主机,请将它们传递给--hosts选项。例如,此命令将更新主机 slave1 和 slave2 的安全领域。

groovy deploy-certificate.groovy --controller domaincontroller --port 9990 --user admin --password password --keystore-file C:\keystore.jks --keystore-password Password01 --hosts slave1,slave2 

然后,它在域管理的每个配置文件中配置 https 侦听器。WildFly 自带 4 种配置文件:默认、高可用性、完全和完全高可用性。如果你只想更新特定的配置文件,将它们传递到--profiles选项中。例如,该命令将更新默认配置文件和 ha 配置文件中的 https-listener。

groovy deploy-certificate.groovy --controller domaincontroller --port 9990 --user admin --password password --keystore-file C:\keystore.jks --keystore-password Password01 --profiles ha,default 

保护域管理界面

保护域管理接口的命令几乎与保护独立管理接口的命令相同。惟一的变化是添加了--management-port参数,该参数定义了管理控制台将暴露的 https 端口。

groovy deploy-certificate.groovy --controller domaincontroller --port 9990 --user admin --password password --keystore-file C:\keystore.jks --keystore-password Password01 --management-interface --management-port 9993 

这将在套接字接口上设置secure-port属性。

<management>
   <management-interfaces>
      <native-interface security-realm="ManagementRealm">
         <socket interface="management" port="${jboss.management.native.port:19999}" />
      </native-interface>
      <http-interface security-realm="ManagementRealm">
         <http-upgrade enabled="true" />
         <socket interface="management" port="${jboss.management.http.port:9990}" secure-port="9993" />
      </http-interface>
   </management-interfaces>
   ...
</management> 

使用 HTTPS 管理界面

一旦使用 HTTPS 保护了管理接口,您就需要在运行脚本时指定remote+https协议,这是通过--protocol参数完成的。例如,此命令在具有 HTTPS 安全管理界面的域控制器上配置 ha 和默认配置文件。

groovy deploy-certificate.groovy --controller domaincontroller --port 9993 --protocol remote+https --user admin --password password --keystore-file C:\keystore.jks --keystore-password Password01 --profiles ha,default 

已知问题

似乎偶尔域控制器主机不会正常重新启动。更多详情见https://issues.jboss.org/browse/WFLY-8975

您可以使用--no-restart选项阻止重启。

groovy deploy-certificate.groovy --controller domaincontroller --port 9990 --user admin --password password --keystore-file C:\keystore.jks --keystore-password Password01 --management-interface --management-port 9993 --no-restart 

后续步骤

这些 Groovy 脚本正在被开发,作为最终将被移植到 Octopus Deploy 中直接提供的步骤中的概念验证。

如果你对剧本有任何问题,请留下评论。如果有一些 Java 特性你希望 Octopus 在未来部署支持,请加入 Java RFC 帖子的讨论。

创建一个 WildFly 集群- Octopus 部署

原文:https://octopus.com/blog/wildfly-jdbc-ping

之前的博客文章中,我们看到了在没有固定 IP 地址或网络广播的 AWS 环境中,如何通过将配置设置集中在 S3 桶中来配置 WildFly 域。

在这篇博文中,我们将通过在 AWS 中配置一个 WildFly 实例集群来进一步进行配置。

域与集群

在我们开始配置集群之前,有必要花点时间讨论一下域和集群之间的区别。

一个域由一个域控制器和一个或多个域从属设备组成。域控制器将设置和应用程序分发给域从属服务器,并提供启动、停止和重新启动从属服务器实例的方法。

域只是一个管理工具。除了简化的管理之外,您不会获得任何额外的功能,因为实例是域的一部分。事实上,您可以使用类似于JBoss Operations Network(JON)的工具来替换一个域。

术语集群可能有歧义,因此对于这篇博文来说,集群意味着共享 Servlet 会话状态的两个或更多 WildFly 实例。共享此状态意味着服务器端 web 应用程序可以由集群中的任何节点提供,如果其中一个节点关闭,请求将由拥有会话状态副本的另一个节点来完成。

尽管从理论上讲,集群允许任何节点响应有状态 web 应用程序的请求,但在实践中,您通常会实现粘性会话来将流量从一个会话定向到单个服务器(只要它可用)。Infinispan 文档谈到了使用粘性会话的性能优势。

域可用于将从属实例配置为集群的一部分,或者域可由不构成集群的多个独立节点组成。同样,WildFly 的多个独立实例可以配置成一个集群。

因此,虽然域和集群都是以某种方式协调的 WildFly 实例组,但这两个术语指的是不同的东西。

配置集群

虽然我们不需要域来配置集群,但域是将公共设置分发到多个 WildFly 从属服务器的一种便捷方式,因此我们将使用域来构建我们的集群。我们将在之前的博文中已经完成的工作的基础上构建一个野域名。

扩展域以支持集群需要许多步骤。

定义中央配置数据库

在构建域时,我们使用了一个共享的 S3 存储桶来帮助发现域控制器。以类似的方式,我们将使用共享数据库来促进集群节点的发现。

因为我们正在 AWS 中部署 WildFly 实例,所以我们将使用 Aurora 数据库作为集群配置的中心点。Aurora 与 MySQL 兼容,这意味着我们将配置 WildFly,就像它正在与 MySQL 数据库通信一样。

添加模式

使用 MySQL 客户端工具,登录 RDS 实例,并创建一个名为jgroups的模式。这是通过命令create database jgroups;完成的:

[ec2-user@ip-172-30-0-89 configuration]$ mysql -ujgroups -p -hyour-rds-hostname.cluster-c1ufrgizkeyf.us-east-1.rds.amazonaws.com
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 15
Server version: 5.6.10 MySQL Community Server (GPL)

Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> create database jgroups;

Query OK, 1 row affected (0.04 sec) 

添加 MySQL 驱动程序

在每个 WildFly 从属服务器上,我们需要添加一个包含 MySQL 驱动程序的模块。一个模块只是一个 JAR 文件和一些 WildFly 可以用来加载它们的元数据的集合。

首先创建一个名为modules/system/layers/base/com/mysql/driver/main的目录。在这个目录中,创建一个名为module.xml的文件,内容如下:

<module  name="com.mysql.driver">
    <resources>
        <resource-root path="mysql-connector-java-5.1.44.jar" />
    </resources>
    <dependencies>
        <module name="javax.api"/>
        <module name="javax.transaction.api"/>
    </dependencies>
</module> 

然后将 MySQL 驱动 JAR 文件保存到mysql-connector-java-5.1.44.jar目录下。

添加数据源

RDS 实例将通过数据源进行访问。这在ha轮廓下的domain/confguration/domain.xml文件中定义。<profile name="ha">元素的所有子元素组成了ha概要文件。

为了定义我们的数据源,我们需要一个<datasource>元素(定义数据库连接)和一个<driver>元素(定义 MySQL JDBC 驱动程序细节)。

在本例中,我们将数据源指向一个名为jgroups的模式,并连接一个名为jgroups的用户(尽管这两个值不需要相同):

<subsystem >
  <datasources>
      <datasource jndi-name="java:jboss/datasources/JGroups" pool-name="JGroupsDS">
          <connection-property name="url">
              jdbc:mysql://your-rds-hostname.cluster-c1ufrgizkeyf.us-east-1.rds.amazonaws.com/jgroups
          </connection-property>
          <driver>mysql</driver>
          <security>
              <user-name>jgroups</user-name>
              <password>yourpassword</password>
          </security>
      </datasource>
      <drivers>
          <driver name="mysql" module="com.mysql.driver">
              <xa-datasource-class>com.mysql.jdbc.jdbc2.optional.MysqlXADataSource</xa-datasource-class>
              <datasource-class>com.mysql.jdbc.jdbc2.optional.MysqlDataSource</datasource-class>
          </driver>
      </drivers>
  </datasources>
</subsystem> 

定义 JGroups 堆栈

JGroups 是 WildFly 用来连接集群成员的库。默认情况下,JGroups 被配置为使用 UDP 和多播,但是 Amazon 不支持 UDP 和多播。

相反,我们将配置 WildFly 使用 TCP,并利用中央数据库作为发现对等点的方式。

在 JGroups 子系统中,我们将通过更改<channel>元素上的stack属性来切换到tcp堆栈:

<channel name="ee" stack="tcp" cluster="ejb"/> 

然后我们需要用JDBC_PING替换MPING协议:

<protocol type="org.jgroups.protocols.JDBC_PING">
    <property name="datasource_jndi_name">
        java:jboss/datasources/JGroups
    </property>
</protocol> 

完整的堆栈现在是这样的:

<subsystem >
    <channels default="ee">
        <!-- We are now using the tcp stack -->
        <channel name="ee" stack="tcp" cluster="ejb"/>
    </channels>
    <stacks>
        <stack name="udp">
            <transport type="UDP" socket-binding="jgroups-udp"/>
            <protocol type="PING"/>
            <protocol type="MERGE3"/>
            <protocol type="FD_SOCK"/>
            <protocol type="FD_ALL"/>
            <protocol type="VERIFY_SUSPECT"/>
            <protocol type="pbcast.NAKACK2"/>
            <protocol type="UNICAST3"/>
            <protocol type="pbcast.STABLE"/>
            <protocol type="pbcast.GMS"/>
            <protocol type="UFC"/>
            <protocol type="MFC"/>
            <protocol type="FRAG2"/>
        </stack>
        <stack name="tcp">
            <transport type="TCP" socket-binding="jgroups-tcp"/>
            <!-- MPING has been replaced with JDBC_PING -->
            <protocol type="org.jgroups.protocols.JDBC_PING">
                <property name="datasource_jndi_name">
                    java:jboss/datasources/JGroups
                </property>
            </protocol>
            <protocol type="MERGE3"/>
            <protocol type="FD_SOCK"/>
            <protocol type="FD_ALL"/>
            <protocol type="VERIFY_SUSPECT"/>
            <protocol type="pbcast.NAKACK2"/>
            <protocol type="UNICAST3"/>
            <protocol type="pbcast.STABLE"/>
            <protocol type="pbcast.GMS"/>
            <protocol type="MFC"/>
            <protocol type="FRAG2"/>
        </stack>
    </stacks>
</subsystem> 

使用ha轮廓

为了利用我们对ha概要文件的更改,需要配置从属实例来使用它。这在<server-groups>元件中进行配置。

更改main-server-group以使用ha轮廓:

<server-group name="main-server-group" profile="ha"> 

还将服务器组更改为使用full-ha-scokets套接字绑定组:

<socket-binding-group ref="full-ha-sockets"/> 

完整的<server-groups>元素现在看起来像这样:

<server-groups>
    <!-- main-server-group uses the ha profile -->
     <server-group name="main-server-group" profile="ha">
         <jvm name="default">
             <heap size="64m" max-size="512m"/>
         </jvm>
         <!-- main-server-group uses the full-ha-sockets socket binding group -->
         <socket-binding-group ref="full-ha-sockets"/>
     </server-group>
     <server-group name="other-server-group" profile="full-ha">
         <jvm name="default">
             <heap size="64m" max-size="512m"/>
         </jvm>
         <socket-binding-group ref="full-ha-sockets"/>
     </server-group>
 </server-groups> 

打开防火墙

套接字绑定组为 JGroups 使用端口7600,所以这个端口需要在我们的防火墙上打开。

启动奴隶

为了在数据库中注册正确的地址,WildFly 从属实例需要将private接口绑定到外部 NIC 的 IP 地址。这是通过将-bprivate=<ip address>参数传递给domain.sh来完成的。

因为这些从属实例需要为外部世界的流量提供服务,我们还需要将public接口绑定到外部 NIC 的 IP 地址。这是通过将参数-b=<ip address>传递给domain.sh来完成的。

例如,这个命令在 IP 地址为172.30.0.88的 EC2 实例上启动一个从属服务器:

[ec2-user@ip-172-30-0-88 bin]$ ./domain.sh --host-config host-slave.xml -bprivate=172.30.0.88 -b=172.30.0.88 

我们不绑定到 EC2 实例的公共 IP 地址(如果它甚至有一个公共 IP 地址的话)。负载均衡器将获取公共互联网流量,并将其定向到 WildFly 绑定的本地子网 IP 地址。

创建负载平衡器

至此,我们已经有了一个配置集群的域。WildFly slave 实例将从域控制器获取它们的配置(通过共享的 S3 桶发现),该配置包括一个 JGroups 堆栈,允许形成一个集群(通过查询一个共享的数据库)。

为了真正利用集群,我们需要一个集中的负载均衡器来在从设备之间分配请求。这很重要,因为代表会话的 cookie 将被绑定到负载平衡器的地址,而不是任何单个节点的地址。这样,由单个 cookie 表示的单个会话在集群成员之间共享。

AWS 提供了一个负载平衡器来完成这项工作。

我们的负载平衡器将针对WildFly目标群体:

AWS Load Balancer Listener

该目标组有两个 WildFly 从属实例,并将流量定向到端口8080(wild fly 的默认 HTTP 端口):

AWS Target Targets

我们将使用默认的 WildFly 欢迎页面作为健康检查:

AWS Target Health Check

部署可分发的 web 应用程序

为了利用复制会话状态,我们的 Java web 应用程序需要标记为可分发的。这是在带有<distributable/>元素的web.xml文件中完成的。这个示例 web 应用程序已经被配置为可分发的。

这个示例应用程序在会话存储中维护一个页面计数,我们可以使用这个值来确保我们的会话数据实际上是跨集群复制的。

我们将构建这个应用程序,并在整个域中部署它:

Deployed App

打开应用程序

部署好应用程序和我们的负载平衡器后,我们就可以打开应用程序了。每次我们刷新页面,Page count就会增加。这是通过增加会话存储中的值来实现的,会话存储是我们的集群正在复制的存储:

Load Balanced Request

如果我们看一下JSESSIONID cookie,我们可以看到它被绑定到负载平衡器域。这很重要,因为这个 cookie 是我们跟踪会话的方式,而且由于浏览器的工作方式,这个 cookie 只会被发送到创建它的域。通过将 WildFly 服务器隐藏在负载平衡器之后,浏览器不知道哪个集群实例正在响应请求。

然而,如果我们仔细观察 cookie 的值,我们可以看到发起会话的从属实例的 IP 地址是172.30.0.88。该值仅代表启动会话的集群成员,如果不同的集群成员响应后续请求,该值不会改变:

JSESSIONID

我们可以使用这些信息来关闭托管我们会话的从属服务器,将流量强制转移到第二个从属服务器上。

通过定义host-slave.xml文件中<host>元素的name属性,可以避免从机 IP 地址泄露;

<host name="Slave Name" > 

启动会话的群集节点现在关闭,所有流量都转移到群集的另一个成员。但是页面计数将继续从其先前的值攀升,而不会重置为 1,因为复制的会话意味着最终用户可以像什么都没发生一样继续。

验证集群配置

部署了可分发的应用程序后,JGroups 将开始在共享数据库中注册信息。使用 MySQL 客户端,我们可以用show tables;命令登录回数据库并列出我们的jgroups模式中的表。

这里我们可以看到JGROUPSPING表已经创建:

[ec2-user@ip-172-30-0-88 log]$ mysql -u jgroups -p -hyour-rds-hostname.cluster-c1ufrgizkeyf.us-east-1.rds.amazonaws.com
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 22
Server version: 5.6.10 MySQL Community Server (GPL)

Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> use jgroups;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> show tables;
+-------------------+
| Tables_in_jgroups |
+-------------------+
| JGROUPSPING       |
+-------------------+
1 row in set (0.00 sec)

mysql> 

结论

在这篇文章中,我们看到了如何配置一个域来创建一个集群,以及如何允许该集群通过一个共享数据库来识别对等体。然后,该集群通过 AWS 负载平衡器公开。

然后,我们将一个可分发的 web 应用程序部署到域中,并通过关闭创建原始会话的实例,将流量转移到一个新的集群节点上。

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

了解更多信息

野火 S3 域名发现-章鱼部署

原文:https://octopus.com/blog/wildfly-s3-domain-discovery

在云环境中配置 WildFly 域时,通常不可能依靠固定的 IP 地址或网络广播来发现资源。为 AWS 用户发现域控制器提供的一个解决方案是使用 S3 桶作为中央配置点。在这篇博文中,我们将看看如何在 AWS 中配置一个简单的 WildFly 域,使用 S3 桶来发现域控制器。

AWS IAM 用户

首先,我们需要一个 IAM 用户,他有权限访问 S3 存储桶。WildFly 将使用该用户的密钥来用域控制器的详细信息更新 S3 桶。

在这个例子中,我们将使用一个名为wildfly-domain的桶。为了授予对这个 bucket 的访问权,WildFly 将使用其密钥的 IAM 用户具有以下安全策略。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Stmt1496702015000",
            "Effect": "Allow",
            "Action": [
                "s3:*"
            ],
            "Resource": [
                "arn:aws:s3:::wildfly-domain",
                "arn:aws:s3:::wildfly-domain/*"
            ]
        }
    ]
} 

有了这个策略,我们需要创建访问键。生成Access key IDSecret access key时记下它们,因为 AWS 不会再向您显示秘密访问密钥。

创建存储桶

S3 水桶本身没什么特别的。它不需要启用任何特殊特性,因此只需创建一个名为安全策略中引用的名称的 bucket(在我们的例子中为wildfly-domain)。

配置域控制器

要配置域控制器以允许从属实例通过 S3 存储桶发现它并进行连接,需要很多步骤。

添加从属用户

我们需要做的第一件事是在域控制器上创建一个用户,从属实例将使用它来连接到域控制器。这是通过bin/add-user.sh脚本完成的。用户:

  • 是一个Management User
  • 不属于任何团体
  • 将用于一个 AS 流程连接到另一个 AS 流程

创建名为slave的用户的输出如下所示。

[ec2-user@ip-172-30-0-89 bin]$ ./add-user.sh

What type of user do you wish to add?
 a) Management User (mgmt-users.properties)
 b) Application User (application-users.properties)
(a):

Enter the details of the new user to add.
Using realm 'ManagementRealm' as discovered from the existing property files.
Username : slave
Password recommendations are listed below. To modify these restrictions edit the add-user.properties configuration file.
 - The password should be different from the username
 - The password should not be one of the following restricted values {root, admin, administrator}
 - The password should contain at least 8 characters, 1 alphabetic character(s), 1 digit(s), 1 non-alphanumeric symbol(s)
Password :
Re-enter Password :
What groups do you want this user to belong to? (Please enter a comma separated list, or leave blank for none)[  ]:
About to add user 'slave' for realm 'ManagementRealm'
Is this correct yes/no? yes
Added user 'slave' to file '/home/ec2-user/wildfly-11.0.0.Final/standalone/configuration/mgmt-users.properties'
Added user 'slave' to file '/home/ec2-user/wildfly-11.0.0.Final/domain/configuration/mgmt-users.properties'
Added user 'slave' with groups  to file '/home/ec2-user/wildfly-11.0.0.Final/standalone/configuration/mgmt-groups.properties'
Added user 'slave' with groups  to file '/home/ec2-user/wildfly-11.0.0.Final/domain/configuration/mgmt-groups.properties'
Is this new user going to be used for one AS process to connect to another AS process?
e.g. for a slave host controller connecting to the master or for a Remoting connection for server to server EJB calls.
yes/no? yes
To represent the user add the following to the server-identities definition <secret value="UGFzc3dvcmQwMSE=" /> 

记下这条线

To represent the user add the following to the server-identities definition <secret value="UGFzc3dvcmQwMSE=" /> 

稍后我们将需要它来配置从属实例。

配置 S3 存储桶

domain/configuration/host-master.xml文件中,用下面的代码替换<domain-controller>元素的默认内容。该配置指示域控制器在启动时将其详细信息保存在wildfly-domain S3 桶中。

<domain-controller>
  <local>
      <discovery-options>
          <discovery-option name="s3-discovery" code="org.jboss.as.host.controller.discovery.S3Discovery" module="org.jboss.as.host-controller">
            <property name="access-key" value="AKIAINKG7EYTNEPL2TBA"/>
            <property name="secret-access-key" value="yoursecretkeygoeshere"/>
            <property name="location" value="wildfly-domain"/>
        </discovery-option>
    </discovery-options>
  </local>
</domain-controller> 

运行域控制器

默认情况下,管理接口绑定到 localhost,在这种情况下,域控制器将尽职尽责地将配置保存在 S3 存储桶中,指示它可以在 127.0.0.1 上访问,当从属服务器在另一个实例上运行时,这显然不是有用的信息。

要解决这个问题,域控制器必须将其管理端口绑定到外部接口。这里,我们用-bmanagement=<ip>参数启动了域控制器,它指示 WildFly 将管理端口绑定到外部 NIC 的 IP 地址。

我们还通过--host-config参数提供了主机配置文件的名称。

[ec2-user@ip-172-30-0-89 bin]$ ./domain.sh --host-config=host-master.xml -bmanagement=172.30.0.89 

验证 S3 配置

一旦域控制器已经启动,你会发现新的文件创建在 S3 桶。

s3 domain config

配置域从属服务器

要配置从机,我们需要编辑domain/configuration/host-slave.xml文件。

用创建slave用户时由add-users.sh脚本生成的<secret>替换<server-identities>元素的内容。

<server-identities>
  <!-- Replace this with either a base64 password of your own, or use a vault with a vault expression -->
  <secret value="UGFzc3dvcmQwMSE=" />
</server-identities> 

然后用下面的代码替换<domain-controller>元素的内容。

<domain-controller>
  <remote security-realm="ManagementRealm" username="slave">
    <discovery-options>
        <discovery-option name="s3-discovery" code="org.jboss.as.host.controller.discovery.S3Discovery" module="org.jboss.as.host-controller">
          <property name="access-key" value="AKIAINKG7EYTNEPL2TBA"/>
          <property name="secret-access-key" value="yoursecretkeygoeshere"/>
          <property name="location" value="wildfly-domain"/>
        </discovery-option>
    </discovery-options>
  </remote>
</domain-controller> 

然后,可以使用命令启动从属实例

./domain.sh --host-config host-slave.xml 

在管理控制台中验证域

当域控制器和从控制器都在运行时,我们可以使用管理控制台来查看域的详细信息。打开 http://domaincontrollerip:9990。系统将提示您输入凭据。通常我们会为管理控制台创建一个专用用户,但是为了方便起见,我们可以只使用我们之前创建的slave用户。

Runtime选项卡下,我们可以看到主主机和从主机确实按照预期连接。

wildfly domain management

结论

使用 S3 桶作为配置的中心点是在 AWS 中配置 WildFly 域的一种简单、方便和可靠的方式。它消除了对固定 IP 地址的需求,并解决了 AWS 阻止网络广播的限制。

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

将存储库部署到 WildFly - Octopus 部署

原文:https://octopus.com/blog/wildfly-vault

在处理密码等敏感信息时, Octopus Deploy 为您提供加密和保存这些值的能力以确保它们的安全。

如果这些密码是用于数据库服务器之类的外部系统,通常需要解密密码,并将其以纯文本形式存储在某个配置文件中,以便实际使用密码。但是以纯文本的形式保存敏感信息并不理想,因为这使得它容易受到许多简单的攻击,例如在编辑配置文件时监视他人,或者在聊天系统中共享、通过电子邮件发送或发布到帮助论坛,或者检查版本控制系统的历史时捕捉内容文件的内容。

WildFly 通过将敏感信息放入保险库中来缓解这些漏洞。

虽然保险库被认为是安全的,但将敏感信息放入保险库仍然是值得的,因为这意味着配置文件的普通观察者将无法提取密码。

作为为 Java 提供一流支持的举措的一部分,我们正计划提供将 Octopus 变量导出到 WildFly vault 的能力。在这篇博文中,我将向你介绍如何在今天实现这一目标。

导出变量

如果您曾经使用过 WildFly 附带的vault脚本,您会知道要存储在 vault 中的每个值都必须一次添加一个。这很乏味,而且很难维护。为了提供一种更快的方式将安全值放入保险库中,我们编写了一个 Groovy 脚本,它从一个 CSV 文件中获取值。

但是首先我们需要从 Octopus 中获取安全值并保存到一个 CSV 文件中。幸运的是,Powershell 附带了Export-Csv命令,这使得事情变得微不足道。下面的代码可以在 Octopus 的脚本步骤中定义,以将一组键/值对导出到 CSV 文件。然后它调用create-vault.groovy脚本将 CSV 文件转换成 WildFly vault。保险库密码保存在名为VaultPassword的输出变量中,CSV 文件被删除。

try {
    # Dump all available variables
    # $OctopusParameters.GetEnumerator() | sort-object Name  | Export-Csv -Path "C:\variables.csv"

    # Dump only a few variables
    $variables = @{"wildfly_slave_password" = $wildfly_slave_password}
    $variables.GetEnumerator() | Select Key,Value  | Export-Csv -Path "C:\variables.csv"

    cd C:\Apps\JBossDeployment
    $vaultPassword = &groovy create-vault.groovy `
      --keystore-file C:\wildfly_dc\wildfly-11.0.0.Alpha1\domain\configuration\keystore.vault `
      --keystore-password Password01 `
      --enc-dir C:\wildfly_dc\wildfly-11.0.0.Alpha1\domain\configuration\vault `
      --csv C:\variables.csv

    Set-OctopusVariable -name "VaultPassword" -value $vaultPassword
}
finally {
    rm "C:\variables.csv"
} 

一旦这个脚本运行,我们将有一个名为C:\wildfly_dc\wildfly-11.0.0.Alpha1\domain\configuration\keystore.vault的文件,一个名为C:\wildfly_dc\wildfly-11.0.0.Alpha1\domain\configuration\vault的目录,以及一个可以用来访问保险库的密码。

您需要首先在所有域节点或独立节点上运行此脚本,以便它们都有一个 vault 的本地副本,可以在下一步中进行配置。

配置存储库

创建了保险库之后,我们现在需要配置 WildFly 来使用它。这是通过将以下 XML 添加到 WildFly 主机配置文件中来完成的。

<vault>
    <vault-option name="KEYSTORE_URL" value="C:\wildfly_standalone\wildfly-11.0.0.Alpha1\standalone\configuration\keystore.vault"/>
    <vault-option name="KEYSTORE_PASSWORD" value="MASK-223/wEo1GLELe8EuQa5u20"/>
    <vault-option name="KEYSTORE_ALIAS" value="vault"/>
    <vault-option name="SALT" value="12345678"/>
    <vault-option name="ITERATION_COUNT" value="50"/>
    <vault-option name="ENC_FILE_DIR" value="C:\wildfly_standalone\wildfly-11.0.0.Alpha1\standalone\configuration\vault\/"/>
</vault> 

为了简化这个过程,有另一个 Groovy 脚本将为您添加这个配置。

下面的 Powershell 用于运行 Groovy 脚本并将 vault 配置添加到主机。

您只需要在域控制器上运行这个脚本,而不需要在域从属服务器上运行,因为域控制器会将 vault 配置推送到从属服务器。但是,域控制器不会推出实际的 vault 文件,这就是我们在添加此配置之前在所有主机上创建 vault 文件的原因。

如果您有独立的实例,那么这个脚本将在所有的实例上运行。

cd C:\Apps\JBossDeployment
&groovy deploy-vault.groovy `
  --controller localhost `
  --port 9990 `
  --user admin `
  --password password `
  --keystore-file C:\wildfly_dc\wildfly-11.0.0.Alpha1\domain\configuration\keystore.vault `
  --keystore-password $OctopusParameters["Octopus.Action[Create Vault].Output.VaultPassword"] `
  --enc-dir C:\wildfly_dc\wildfly-11.0.0.Alpha1\domain\configuration\vault 

访问保险库密码

通过 WildFly 配置文件中的一个特殊变量,可以访问存储库中包含的密码。这里,我们引用了我们的 vault 中别名vaultwildfly_slave_password变量。

<server-identities>
  <secret value="${VAULT::vault::wildfly_slave_password::1}"/>
</server-identities> 

正如您所看到的,实际的密码不再以纯文本的形式保存,即使这个配置文件被泄露,它也不会泄露一个泄漏的密码。

后续步骤

这些 Groovy 脚本正在被开发,作为最终将被移植到 Octopus Deploy 中直接提供的步骤中的概念验证。

如果你对剧本有任何问题,请留下评论。如果有一些 Java 特性你希望 Octopus 在未来部署支持,请加入 Java RFC 帖子的讨论。

部署 ASP.NET 核心应用程序-它会部署吗?第 1 集-章鱼部署

原文:https://octopus.com/blog/will-it-deploy-episode-01

今天,我们将发布它会部署吗?这是我们全新的视频系列,我们尝试使用 Octopus Deploy 自动部署不同的技术。

我们以一个有趣的视频开始这个系列,我们试图将 ASP.NET 核心网络应用部署到微软的 Azure 平台上。光是这一点就很简单,所以我们决定通过自动调配我们的云基础架构并确保零停机生产部署来让它变得更有趣一些。

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

VIDEO

问题

技术堆栈

我们的应用程序是一个报价生成器,名为随机报价。这相当简单,但它将让我们了解如何将 web 应用程序自动部署到 Microsoft Azure 平台。

向我们的营销经理安德鲁致敬,他一直在学习编码并开发了这款应用的第一部分。干得好!

部署目标

Microsoft Azure logo

解决办法

那么它会部署吗?是的,会的!我们的部署流程如下所示。

Octopus deployment process

第一步是添加一个 Octopus Azure 帐户,该帐户拥有使我能够安全地连接到 Azure 平台所需的所有详细信息。它用于在部署或执行脚本时向 Azure 进行身份验证。

Octopus Azure account

然后,我们添加以下步骤来成功部署我们的应用,包括云基础架构配置和零停机生产部署。

  • Octopus 部署 Azure 资源组步骤,通过 ARM 模板供应我们的云基础设施。
  • Octopus 运行 Azure Powershell 脚本步骤,以确保我们始终有一个新的应用服务部署位置。我们调用 Azure Powershell cmdlets 来删除和创建应用服务部署槽。
  • Octopus 部署 Azure Web App 步骤将我们的 Web 应用程序部署到我们的 App Service staging 部署槽。
  • Octopus 运行 Azure Powershell 脚本步骤来交换我们的应用服务阶段和生产(现场)部署槽。这仅在生产部署期间进行,以便我们实现零停机时间!

这个项目使用以下变量来存储我们的资源组名称、网站名称和应用程序设置。简单又好看!

Project variables

本集的 GitHub repo 包含了本视频中使用的所有资源和链接。

总结

我们希望您喜欢这个新系列,并且我们希望它可以帮助世界各地的人们学习如何自动部署他们的应用程序和服务。

不要忘记订阅我们的 YouTube 频道,因为我们会定期添加新视频。如果你想让我们探索某个框架或技术,请在评论中告诉我们。

愉快的部署!😃

了解更多信息

部署 Spring Boot web 应用程序-它会部署吗?第 2 集-章鱼部署

原文:https://octopus.com/blog/will-it-deploy-episode-02

欢迎来到另一个它会部署吗?我们尝试使用 Octopus Deploy 自动部署不同技术的那一集。在这一集中,我们将尝试通过云基础设施配置将 Spring Boot web 应用程序部署到亚马逊 web 服务平台,并确保零停机生产部署。

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

VIDEO

问题

技术堆栈

我们的应用程序是一个报价生成器,名为随机报价。这个应用程序非常简单,但是它让我们演示了如何将 Java web 应用程序部署到 Amazon Web Services 平台。

Spring Boot logo

向我们的营销经理安德鲁致敬,他一直在学习编码并开发了这款应用的第一部分。干得好!

部署目标

Amazon web services logo

解决办法

那么它会部署吗?是的,会的!我们的部署流程如下所示。

Octopus deployment process

第一步是添加一个 Octopus AWS 帐户,其中包括安全可靠地连接到 AWS 平台所需的所有详细信息。它用于在部署或执行脚本时向 AWS 进行身份验证。

AWS Account details

然后,我们添加以下步骤来成功部署我们的应用,包括云基础架构配置和零停机生产部署。

  • Octopus 传送包步骤将 Spring Boot jar 包传送到 Octopus 服务器。
  • Octopus 运行 AWS CLI 脚本步骤,将 web 应用程序包复制到 S3 桶中。
  • Octopus 部署 AWS CloudFormation 模板步骤来供应我们的云基础设施,包括创建我们的弹性 Beanstalk 应用程序和两个环境。
  • Octopus 运行 AWS CLI 脚本步骤,将我们的 web 应用程序部署到绿色或临时环境中。
  • Octopus 运行一个 AWS CLI 脚本步骤来交换 Elastic Beanstalk 应用程序环境 URL,以便我们的绿色(暂存)环境接收我们的蓝色(生产)URL。这仅在生产部署期间执行,以便我们实现零停机时间!

这个项目使用以下变量来存储我们的资源组名称、网站名称和应用程序设置。简单又好看!

Project variables

本集的 GitHub repo 包含了本视频使用的所有资源和链接。

总结

我们希望你喜欢这一集,因为我们有更多的作品!如果你想让我们探索某个框架或技术,请在评论中告诉我们。

不要忘记订阅我们的 YouTube 频道,因为我们会定期添加新视频。愉快的部署!😃

使用实体框架核心部署到 SQL Server 它会部署吗?第 3 集-章鱼部署

原文:https://octopus.com/blog/will-it-deploy-episode-03

欢迎来到另一个它会部署吗?我们尝试使用 Octopus Deploy 自动部署不同技术的那一集。在这一集中,我们尝试使用实体框架核心迁移将 Microsoft SQL Server 数据库部署到 Amazon Web Services (AWS)虚拟机(VM)。

https://www.youtube.com/embed/0XfVDc71OpU

VIDEO

问题

技术堆栈

我们的应用程序是一个报价生成器,名为随机报价。这个应用程序非常简单,但是它允许我们演示如何部署数据库更改和更新。

SQL Server logo

向我们的营销经理安德鲁致敬,他一直在学习编码并开发了这款应用的第一部分。干得好!

部署目标

Amazon web services logo

解决办法

那么它会部署吗?是的,会的!我们的部署流程如下所示。

Octopus deployment process

然后,我们添加以下步骤来成功部署我们的数据库更改和 web 应用程序。

  • Octopus 部署包步骤将我们的数据库脚本复制到我们的数据库部署目标
  • Octopus 社区贡献了步骤模板- SQL -执行脚本文件 来针对我们的 SQL Server 数据库执行我们的实体框架核心迁移脚本。
  • Octopus 部署到 IIS 部署我们的 ASP.NET 核心 web 应用程序的步骤

该项目使用以下变量来存储我们的应用程序设置、数据库连接详细信息和 web 应用程序配置。

Project variables

本集的 GitHub repo 包含了本视频中使用的所有资源和链接。

总结

我们希望你喜欢这一集,因为我们有更多的作品!如果你想让我们探索某个框架或技术,请在评论中告诉我们。

不要忘记订阅我们的 YouTube 频道,因为我们会定期添加新视频。愉快的部署!😃

使用 DACPAC 部署到 SQL Server 它会部署吗?第 4 集-章鱼部署

原文:https://octopus.com/blog/will-it-deploy-episode-04

欢迎来到另一个它会部署吗?我们尝试使用 Octopus Deploy 自动部署不同技术的那一集。在本集中,我们尝试使用数据层应用程序包(DACPAC)将 Microsoft SQL Server 数据库部署到 Amazon Web Services (AWS)虚拟机(VM)上。

https://www.youtube.com/embed/zGJthrY-qTQ

VIDEO

问题

技术堆栈

我们的应用程序是一个报价生成器,名为随机报价。这个应用程序非常简单,但是它允许我们演示如何部署数据库更改和更新。

SQL Server logo

向我们的营销经理安德鲁致敬,他一直在学习编码并开发了这款应用的第一部分。干得好!

部署目标

Amazon Web Services logo

解决办法

那么它会部署吗?是的,会的!我们的部署流程如下所示:

Octopus deployment process

然后,我们添加以下步骤来成功部署我们的应用程序。

  • Octopus 部署一个包步骤,将我们的数据库脚本复制到我们的数据库部署目标。
  • Octopus 社区贡献的步骤模板-SQL-Deploy DACPAC将我们的 DAC PAC 部署到我们的 SQL Server 数据库。
  • Octopus 部署到 IIS 部署我们的 ASP.NET 核心 web 应用程序的步骤。

这个项目使用以下变量来存储我们的应用程序设置、数据库连接细节和 web 应用程序配置。

Project variables

本集的 GitHub repo 包含了本视频中使用的所有资源和链接。

包裹

我们希望你喜欢这一集,因为我们有更多的作品!如果你想让我们探索某个框架或技术,请在评论中告诉我们。

不要忘记订阅我们的 YouTube 频道,因为我们会定期添加新视频。愉快的部署!😃

向多个客户部署多租户 Web 应用程序——它能部署吗?第 5 集-章鱼部署

原文:https://octopus.com/blog/will-it-deploy-episode-05

欢迎来到另一个它会部署吗?我们尝试使用 Octopus Deploy 自动部署不同技术的那一集。在这特别的两集节目中,我们试图为运行在亚马逊网络服务(AWS)虚拟机上的不同客户部署一个多租户 SaaS 网络应用。

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

VIDEO

问题

Multi-Tenant architecture

技术堆栈

我们的应用程序是一个报价生成器,名为随机报价。该应用程序相对简单,但它包含许多通用特性,使我们能够说明多租户部署的复杂性。

特点:

  • 为每位客户定制应用程序设置
  • 应用程序启动时的动态特征/模块加载
  • 为每位顾客定制颜色和款式

技术:

SQL Server logo

向我们的营销经理安德鲁致敬,他一直在学习编码并开发了这款应用的第一部分。干得好!

部署目标

Amazon web services logo

解决办法

那么它会部署吗?是的,会的!我们的部署流程如下所示。

Octopus Multi-Tenant Deployments

Octopus deployment process

然后,我们添加以下步骤来成功部署我们的应用程序。

  • Octopus 部署包步骤将我们的数据库脚本复制到我们的数据库部署目标
  • Octopus 社区贡献了 step template-SQL-Execute Script File来针对我们的 SQL Server 数据库执行我们的实体框架核心迁移脚本。
  • Octopus 部署到 IIS 部署多租户 ASP.NET 核心 web 应用程序的步骤
  • Octopus 部署一个包步骤,将管理模块复制到客户的网站,如果他们为此功能付费的话
  • Octopus 部署一个包步骤,将共享模块复制到客户的网站,如果他们为该功能付费的话

这个项目使用以下变量,变量模板和公共变量模板来存储我们的应用程序设置,数据库连接细节和 web 应用程序配置。

Project variables

Project variable templates

Common variable templates

本集的 GitHub repo 包含了本视频使用的所有资源和链接。

总结

我们希望你喜欢这一集,因为我们有更多的作品!如果你想让我们探索某个框架或技术,请在评论中告诉我们。

不要忘记订阅我们的 YouTube 频道,因为我们会定期添加新视频。愉快的部署!😃

使用 AppVeyor 和 Octopus 将 ASP.NET MVC web 应用程序部署到 Azure 它能部署吗?第 6 集-章鱼部署

原文:https://octopus.com/blog/will-it-deploy-episode-06

欢迎来到另一个它会部署吗?我们尝试使用 Octopus Deploy 自动部署不同技术的那一集。在这一集中,我们试图将 ASP.NET MVC 5 web 应用部署到微软 Azure 应用服务。我们还探索与 AppVeyorOctopus 建立基于云的交付管道。

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

VIDEO

注:章鱼云即将到来!注册您的兴趣,了解我们基于云的解决方案的最新动态。

问题

技术堆栈

我们的应用程序是一个名为随机报价的随机报价生成器 web 应用程序。这相当简单,但它将让我们了解如何将 ASP.NET MVC web 应用程序自动部署到微软的 Azure 平台上。

向我们的营销经理安德鲁致敬,他一直在学习编码并开发了这款应用的第一部分。干得好!

部署目标

Microsoft Azure logo

微软的 Azure 平台 - App 服务

解决办法

那么它会部署吗?是的,会的!

我们基于云的交付渠道如下所示:

GitHub, AppVeyor and Octopus delivery pipeline

我们将源代码提交给 GitHub ,用 AppVeyor 自动构建我们的应用,用 Octopus 部署到微软 Azure

把 AppVeyor 和 Octopus 集成起来又快又简单。我们只需选择“Package Web Applications for Octopus deploy”构建选项,并配置一个“Octopus Deploy”部署提供程序。

AppVeyor build settings

AppVeyor deployment provider settings

我们的部署过程如下所示:

Octopus deployment process

我们的部署流程中只有一个步骤,即 Octopus 部署 Azure Web App 步骤,将我们的 Web 应用程序部署到我们的应用程序服务。它的配置非常简单,我们大多使用默认设置。我们确实在部署期间打开了配置变量替换特性来更新我们的web.config文件。

该项目使用以下变量来存储我们的 Azure 资源组名称、网站名称以及一些其他应用程序设置,如横幅背景颜色、发布版本和环境名称。这很简单,但它说明了当我们的 web 应用程序在环境中推广时,我们可以如何更改我们的配置。

Project variables

本集的 GitHub repo 包含了本视频使用的所有资源和链接。

总结

我们希望你喜欢这一集,因为我们有更多的作品!如果你想让我们探索某个框架或技术,请在评论中告诉我们。

不要忘记订阅我们的 YouTube 频道,我们会定期添加新视频。愉快的部署!😃

了解更多信息

将 ASP.NET 核心网络应用程序部署到 Linux——它能部署吗?第 7 集-章鱼部署

原文:https://octopus.com/blog/will-it-deploy-episode-07

欢迎来到另一个它会部署吗?我们尝试使用 Octopus Deploy 自动部署不同技术的那一集。在这一集里,我们试图将 ASP.NET 核心 2.0 网络应用程序部署到亚马逊网络服务(AWS)的 Ubuntu Linux 虚拟机(VM)和 SQL Server for Linux 上。我们还探索与 AppVeyorOctopus 建立基于云的交付管道。

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

VIDEO

注:章鱼云即将到来!注册您的兴趣,了解我们基于云的解决方案的最新动态。

问题

技术堆栈

我们的应用程序是一个名为随机报价的随机报价生成器 web 应用程序。这相当简单,但它将让我们了解如何将 ASP.NET 核心 web 应用程序和数据库自动部署到运行在 AWS 中的 Ubuntu Linux VM 和 SQL Server for Linux 上。

向我们的营销经理安德鲁致敬,他一直在学习编码并开发了这款应用的第一部分。干得好!

部署目标

Amazon web services logo

解决办法

那么它会部署吗?是的,会的!

我们基于云的交付渠道如下所示:

GitHub, AppVeyor and Octopus delivery pipeline

我们将我们的源代码提交给 GitHub ,用 AppVeyor 自动构建我们的应用,并部署到一个 AWS Ubuntu VMOctopus

把 AppVeyor 和 Octopus 集成起来又快又简单。我们使用一个定制的构建脚本来构建和打包我们的应用程序,并配置“Octopus Deploy”部署提供程序来部署它。

AppVeyor build settings

AppVeyor deployment provider settings

我们的部署过程如下所示:

Octopus deployment process

  • Octopus 部署一个包步骤,在 Octopus 服务器上获取/解压缩我们的数据库脚本。
  • Octopus 社区提供了步骤模板- SQL -执行脚本文件 以针对我们的 SQL Server 数据库执行我们的实体框架核心迁移脚本。
  • Octopus 部署一个包步骤将我们的 ASP.NET 核心 web 应用程序部署到我们的 Ubuntu Linux 虚拟机上,并对其进行适当的配置。

这个项目使用以下变量来存储我们的应用程序设置和数据库连接细节。

Project variables

本集的 GitHub repo 包含了本视频中使用的所有资源和链接。

总结

我们希望你喜欢这一集,因为我们有更多的作品!如果你想让我们探索某个框架或技术,请在评论中告诉我们。

不要忘记订阅我们的 YouTube 频道,我们会定期添加新视频。愉快的部署!😃

了解更多信息

在 Windows Server 2008 上终止对 Octopus 服务器的支持。介绍 Linux 上的 Octopus 服务器!-章鱼部署

原文:https://octopus.com/blog/windows-server-2008-eol-hello-linux

章鱼服务器 2019.3 LTS 将是你可以在 Windows Server 2008 上托管的章鱼服务器的最终版本。

这个决定为真正的跨平台 Octopus 服务器铺平了道路。在不久的将来,你将能够在任何现代的 Windows 或 Linux 操作系统上托管 Octopus 服务器,或者在任一平台上托管一个容器。为此,我们需要将托管 Octopus 服务器的最低要求提高到 Windows Server 2008 R2

同样值得注意的是,微软将于 2020 年 1 月结束对 Windows Server 2008 和 2008 R2 的扩展支持,谢天谢地,你可以执行就地升级。如果 Octopus 服务器是您业务中的关键资源,您真的应该考虑将其托管在现代操作系统上,以提高安全性和性能。

这篇博文的其余部分应该回答最常见的问题。一如既往,如果您有任何问题或疑虑,请联系我们的支持团队!愉快的部署!

问题:我会受到影响吗?

只有在以下情况下,您才会受到影响:

  1. 您在 Windows Server 2008 上托管 Octopus 服务器,并且
  2. 你想升级 Octopus 服务器超过2019.3 LTS

别担心,八达通服务器安装程序会防止你意外升级。如果你想升级 Octopus 服务器,你需要升级你的主机操作系统。

问:我们还能在 Windows Server 2008 R2 上托管 Octopus 服务器吗?

是的,Windows Server 2008 R2 仍将是 Octopus 服务器支持的主机。

尽管微软将于 2020 年 1 月终止对 Windows Server 2008 R2 版的扩展支持,但我们现在没有排除 Windows Server 2008 R2 版的实际理由。

问:我的部署会受到影响吗?

不,您的部署不会因升级 Octopus 服务器超过2019.3 LTS而受到影响。这只影响八达通服务器本身的托管。我们仍然高度向后兼容您在 Octopus 中的部署目标,甚至可以追溯到Windows Server 2003

问:如何升级我的主机操作系统?

您可以将 Windows Server 2008 操作系统就地升级到更现代的操作系统。了解如何升级 Windows 服务器。

或者,你可以将你的八达通服务器转移到另一台已经运行更现代的操作系统的主机上。了解移动你的章鱼服务器

问题:你们会支持八达通服务器 2019.3 LTS 和 Windows Server 2008 多久?

我们将根据我们的长期支持计划 继续支持 Octopus 服务器2019.3 LTS,直到 2019 年 10 月。要升级 Octopus 服务器超过2019.3 LTS,您需要将您的主机操作系统升级到 Windows Server 2008 R2 或更新的

问:什么时候可以在 Linux 上托管 Octopus 服务器?

在不久的将来。我们内部已经在这么做了。Octopus Server 2019.5,我们目前在快车道上发布的版本,将开始引入跨平台功能。在此之后,我们将关注 Linux 上 Octopus 服务器的稳定性和性能,随后是运行在 Linux 上的 Octopus 服务器的完全支持版本。

章鱼亮相 WinOps 2018 伦敦-章鱼部署

原文:https://octopus.com/blog/winops-2018

Derek Campbell speaking at WinOps London 2018 details

技术是任何好的商业策略的关键。您的技术、应用和产品的自动化是关键,没有它,您的业务很快就会变得无关紧要。对你和你的企业来说,成功是什么样的?更频繁地释放?发布更靠谱?消除停机时间?减轻开发团队的压力?

为什么不是全部?

在我的会议中,我将讨论一些现实世界中的 DevOps 实现策略。我将根据自己的经验,展示一些真实的案例研究,包括英国一些最大的公司,以及他们是如何实现 CI/CD 渠道自动化的。

在 Octopus Deploy,我们热衷于自动化所有部署,无论是代码、数据库还是基础设施。这就是为什么我很兴奋地宣布,我, Derek Campbell 将在本月的 WinOp 伦敦活动上继续我们关于“Octopus Deploy 如何实现成功的 DevOps 之旅”的讨论。我将在 11 月 16 日发表演讲,我将展示一些我们客户成功案例的真实例子。

现在注册还来得及!您可以使用代码 Octopus10 注册,享受九折优惠。

如果你已经来了,那么一定要向我和我们的英国客户经理卡尔·麦克唐纳问好,并抢一些章鱼贴纸!

WinOps 2019 - Octopus 讲座和研讨会- Octopus 部署

原文:https://octopus.com/blog/winops-2019

WinOps London 将于 9 月 23-24 日举行,届时章鱼将会到场!我很高兴地分享我正在领导一个 DevOps 研讨会,并谈论如何使用 Octopus 加速 Azure DevOps 部署。

为您的 CI/CD 管道车间增压

您目前的 CI/CD 渠道是否已经无法满足您的需求?是太慢,手动,还是太多的设置需要调整太频繁?有没有不小心成为“DevOps”的负责人?

不用担心,在这个实践研讨会中,我将带您了解 CI/CD 核心概念和最佳实践,并回顾现实世界中的发布管理和自动化问题以及如何克服它们。

在 2019 年 9 月 23 日为期半天的研讨会期间,您将通过 OctoFX 亲自体验 TeamCity章鱼云,这是我们用来演示章鱼的一个示例应用程序。您将配置它的构建,运行自动化测试,设计您的部署过程,并最终在开发、测试和生产环境中推广它。

要求

  • 笔记本电脑
  • VS 代码或 Visual Studio 2017+
  • 现代网络浏览器

配备 Octopus Deploy talk 的涡轮增压 Azure DevOps

Derek Campbell speaking at WinOps London 2019 details

在 Octopus Deploy,我们对自动化充满热情,无论是代码、数据库还是基础设施,这就是为什么我们很高兴地宣布,我, Derek Campbell 将在 2019 年 9 月 24 日的 WinOp 伦敦活动上,在我们关于涡轮增压 Azure devo PS with Octopus Deploy的演讲中继续这一讨论。

在本节中,我将带您了解如何使用 Azure DevOps 扩展集成 Azure DevOps 和 Octopus Deploy。然后,我将演示一个 Azure 管道,最后将它传递给 Octopus Deploy,Octopus Deploy 将从单个构建部署到 Azure,并一直测试到生产。

如果你已经来了,那么一定要和团队打招呼,抢一些章鱼贴!

向导、对话框和上下文菜单,还是构建服务器和脚本?-章鱼部署

原文:https://octopus.com/blog/wizards-vs-build-servers

多年来,我一直热衷于使用持续集成工具。早期是有南特剧本的CruiseControl.NET天龙,后来是团队城。即使是个人项目,我也喜欢使用 CI 服务器,原因有很多,包括:

  • 它确保软件是在不是我的机器上构建的
  • 它记得标记版本号,使用正确的发布/调试设置,以及其他我可能会忘记的步骤,使构建更加可靠
  • 它确保了如果“构建人员”不在,构建仍然可以进行
  • 它们为每个人提供了一个中央仪表盘,让他们知道构建的健康状况

中的每个新的持续集成服务器。NET 空间使得实现自动化构建更加容易。但是在从事 Octopus Deploy 之前,我从未找到一个自动化部署的好解决方案(我认为我们还有很长的路要走,但是我希望我们正在朝着正确的方向前进)。

在 Visual Studio 中,已经在上下文菜单和对话框中投入了数以百计的工时,使得将代码放在开发人员的机器上并放到生产服务器上变得很容易。接受这个对话:

Publishing from VS

我理解为什么 Visual Studio 中需要这样的工具。而且这些选项可以通过命令行使用(如果你有足够多的山羊可以牺牲),所以自动化它们是可能的。但是,正如依赖开发人员的机器进行构建是错误的一样,从开发人员的机器进行部署也是错误的。

  • 如果通常负责“通过 front page Server Extensions from Visual Studio 右键单击并发布到产品”的人不在,会发生什么情况?
  • 如果您忘记更改为发布模式或更新连接字符串会发生什么?你记得运行单元测试吗?
  • 您如何轻松地回滚到以前的版本,或者找到最后一个好的部署版本?
  • 您团队中的其他人如何知道哪个版本处于试运行阶段,或者您正在部署到测试环境中?

这就是为什么对于 Octopus,我们致力于使打包应用程序作为自动化构建和部署的一部分变得容易,最好是在构建服务器上,而不是从您的开发机器上。代替向导、对话框和上下文菜单,我们投资了团队城市插件MSBuild 扩展。我们相信鼓励我们的客户遵循良好的实践,比如在构建服务器上自动构建,并让他们很容易做到这一点。

Creating Octopus packages from TeamCity

当然,好心没好报,是要付出代价的。你不能在没有至少一个 NuGet 服务器和某种自动化构建过程的情况下,就从 Visual Studio 中的代码到 Octopus 的生产部署。我觉得比较好,但是不像向导和上下文菜单,有个问题:演示的不是很好。仅仅是尝试该产品就可能需要大量的前期投资。

所以我对自己说:Octopus 需要一个向导来打包你在工作站上构建的代码,并部署它吗?我们应该把有限的时间投资在 Visual Studio 插件和发布向导上吗?或者,我们应该投资于更好地构建服务器集成吗?期望人们使用命令行和构建脚本来创建包是不是太过分了?

当您第一次尝试 Octopus 时,您必须完成的第一步是创建一个 NuGet 包。你是怎么做到的?很难吗?它是否几乎让你在第一次部署之前就放弃了这个产品?

我们可以做些什么来使自动化部署更容易开始?

员工友好的自定义步骤模板- Octopus 部署

原文:https://octopus.com/blog/worker-friendly-step-template

介绍

2018 年 9 月,Michael Richardson 写了一篇博文介绍了在脚本步骤中引用包的功能。“为什么?”中描述的第二个场景说明在此功能之前,您需要先将包推送到目标,然后脚本任务才能针对提取的内容执行。Michael 继续解释说,这种方法既复杂又不能在 Octopus 服务器或工人上运行。

自定义步骤模板

使用 Michael 的想法,就像运行脚本步骤一样,我们能够从自定义步骤模板中引用包

我们还能够通过将包 ID 分配给步骤模板的一个参数来使被引用的包动态化。

这允许我们引用提取的包文件并对它们做一些事情:

$OctopusParameters["Octopus.Action.Package[$myPackageId].ExtractedPath"] 

专门的软件呢?

有时候,仅仅让文件在一个工作人员上可用并不足以使它们与工作人员兼容,比如将 SSIS 包部署到 SQL server。工人将有。它可以使用 ispac 文件,但是它没有安装知道如何使用它的软件。解决这个问题的一个方法是在你所有的员工身上安装这个软件。这增加了复杂性,因为您的所有员工都需要维护,以确保安装和/或更新了正确的软件版本。另一种方法是利用 PowerShell Gallery 在部署时安装必要的 PowerShell 模块。对于 SSIS 的例子,PowerShell 图库中的 SqlServer 模块包含必要的。允许工作人员部署。ispac 到 SQL server。

以下脚本用于演示目的。

使用下面的代码,我们可以检查 worker 是否安装了必要的模块。如果模块不可用,请将指定的版本(如果未指定,则为最新版本)下载到临时文件夹中,并将其包含在内,以便 cmdlets 可用。

首先,在当前工作文件夹中创建临时文件夹:

# Define PowerShell Modules path
$LocalModules = (New-Item "$PSScriptRoot\Modules" -ItemType Directory -Force).FullName 

接下来,我们将此文件夹添加到此会话的 PowerShell 模块路径中:

# Add folder to the PowerShell Modules Path
$env:PSModulePath = "$LocalModules;$env:PSModulePath" 

现在,让我们定义一个函数来检查是否安装了模块:

function Get-ModuleInstalled
{
    # Define parameters
    param(
        $PowerShellModuleName
    )

    # Check to see if the module is installed
    if ($null -ne (Get-Module -ListAvailable -Name $PowerShellModuleName))
    {
        # It is installed
        return $true
    }
    else
    {
        # Module not installed
        return $false
    }
} 

然后一个函数会在它丢失时安装它:

function Install-PowerShellModule
{
    # Define parameters
    param(
        $PowerShellModuleName,
        $LocalModulesPath
    )

    # Save the module in the temporary location
    Save-Module -Name $PowerShellModuleName -Path $LocalModulesPath -Force

    # Display
    Write-Output "Importing module $PowerShellModuleName ..."

    # Import the module
    Import-Module -Name $PowerShellModuleName
} 

最后,我们将定义一个函数来加载。以便可以使用它们的命名空间:

如果将它用于 SqlServer 模块,接下来将向 Get-ChildItem 添加一个 Exclude:

Get-ChildItem -Path $ModulePath -Exclude msv*.dll

Function Load-Assemblies
{
    # Declare parameters
    param(
        $PowerShellModuleName
    )

    # Get the folder where the module ended up in
    $ModulePath = [System.IO.Path]::GetDirectoryName((Get-Module $PowerShellModuleName).Path)

    # Loop through the assemblies
    foreach($assemblyFile in (Get-ChildItem -Path $ModulePath | Where-Object {$_.Extension -eq ".dll"}))
    {
        # Load the assembly
        [Reflection.Assembly]::LoadFile($assemblyFile.FullName) | Out-Null
    }    
} 

一旦定义了这些,调用我们的函数并在必要时安装:

# Check to see if SqlServer module is installed
if ((Get-ModuleInstalled -PowerShellModuleName "SqlServer") -ne $true)
{
    # Display message
    Write-Output "PowerShell module SqlServer not present, downloading temporary copy ..."

    # Download and install temporary copy
    Install-PowerShellModule -PowerShellModuleName "SqlServer" -LocalModulesPath $LocalModules

    # Dependent assemblies
    Load-Assemblies -PowerShellModuleName "SqlServer"
}
else
{
    # Load the IntegrationServices Assembly
    [Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Management.IntegrationServices") | Out-Null # Out-Null supresses a message that would normally be displayed saying it loaded out of GAC
} 

对于我们的 SSIS 示例,我们的工作人员现在拥有了部署所需的组件。ispac 文件!

摘要

在这篇文章中,我们学习了如何从一个定制的步骤模板中引用一个包,通过使它成为一个步骤模板参数来使包 ID 引用成为动态的,以及动态地下载和安装 PowerShell 模块来制作工人友好的模板。

使用触手 Docker 图像创建工人- Octopus Deploy

原文:https://octopus.com/blog/workers-as-containers

Creating workers with the Tentacle Docker image

我最近需要在 Octopus Deploy 的样本云实例上创建一些额外的临时工人。由于我只需要短命的工人,集装箱似乎是完美的解决方案。我把它们旋转起来,它们做一些工作,然后我把它们拆下来。在这篇文章中,我演示了如何在 Azure 托管的容器中创建 workers,执行健康检查,以及安装其他软件组件。

动员工人

我们需要执行的第一步是创建一本操作手册。这篇文章假设你对我们的 runbooks 功能有些熟悉。

我的操作手册由以下步骤组成:

  • 创建 Azure 资源组
  • 运行 ARM 模板来创建 Octopus Deploy 触手容器
  • 运行健康检查
  • 安装附加软件

在这个例子中,我使用的是微软的 Azure 平台,但也可以使用任何其他云,包括 AWS 和谷歌云。我们的用 CloudFormation 在 AWS 中创建 EC2 实例的博文用一个详细的例子介绍了 AWS 中的类似场景。

创建 Azure 资源组

为了便于移除和整体整洁,第一步使用运行 Azure 脚本步骤创建一个 Azure 资源组:

$resourceGroupName = $OctopusParameters["Azure.Network.ResourceGroup.Name"]
$resourceGroupLocation = $OctopusParameters["Azure.Location.Abbr"]

if ((az group exists --name $resourceGroupName) -eq $false)
{
    Write-Output "Creating resource group $resourceGroupName in $resourceGroupLocation"
    az group create --location $resourceGroupLocation --name $resourceGroupName
} 

运行 ARM 模板

Azure 容器实例是一种快速简单的方法来提升工作容器。使用一个小的 Azure 资源管理器(ARM)模板,我们可以自动创建 Octopus 触手容器。

模板代码
{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "location": {
            "type": "string"
        },
        "containerName": {
            "type": "string"
        },
        "imageType": {
            "type": "string",
            "allowedValues": [
                "Public",
                "Private"
            ]
        },
        "imageName": {
            "type": "string"
        },
        "osType": {
            "type": "string",
            "allowedValues": [
                "Linux",
                "Windows"
            ]
        },
        "numberCpuCores": {
            "type": "string"
        },
        "memory": {
            "type": "string"
        },
        "restartPolicy": {
            "type": "string",
            "allowedValues": [
                "OnFailure",
                "Always",
                "Never"
            ]
        },
        "ipAddressType": {
            "type": "string"
        },
        "ports": {
            "type": "array"
        },
        "dnsNameLabel": {
            "type": "string"
        },
        "environmentVariables": {
            "type": "array"
        }
    },
    "resources": [
        {
            "location": "[parameters('location')]",
            "name": "[parameters('containerName')]",
            "type": "Microsoft.ContainerInstance/containerGroups",
            "apiVersion": "2018-10-01",
            "properties": {
                "containers": [
                    {
                        "name": "[parameters('containerName')]",
                        "properties": {
                            "image": "[parameters('imageName')]",
                            "resources": {
                                "requests": {
                                    "cpu": "[int(parameters('numberCpuCores'))]",
                                    "memoryInGB": "[float(parameters('memory'))]"
                                }
                            },
                            "ports": "[parameters('ports')]",
                            "environmentVariables": "[parameters('environmentVariables')]"
                        }
                    }
                ],
                "restartPolicy": "[parameters('restartPolicy')]",
                "osType": "[parameters('osType')]",
                "ipAddress": {
                    "type": "[parameters('ipAddressType')]",
                    "ports": "[parameters('ports')]",
                    "dnsNameLabel": "[parameters('dnsNameLabel')]"
                }
            },
            "tags": {}
        }
    ]
} 

模板需要输入一些参数:

  • 位置:Azure 中的位置代码,即 centralus
  • 容器名:容器的名称
  • imageType :公共|私有
  • 图像名 : octopusdeploy/tentacle
  • osType : Linux | Windows
  • numberCpuCores :要使用的内核数量
  • 内存:容器使用的数字(以 GB 为单位)
  • 重启策略:参见 Docker 文档了解选项
  • IP address type:Public | Private
  • 端口:要暴露的端口数组
  • dnsNameLabel : DNS 条目的 DNS 前缀,即【dnsNameLabel】。[azure region]. azure container . io
  • 环境变量:要传递给容器的环境变量数组

该模板将启动 Octopus Deploy 触手的 Azure 容器实例。

我选择了 Linux 变种,因为它比 Windows 容器小得多(254.39 MB 对 2.27 GB)。

运行健康检查

内置的健康检查模板仅适用于部署目标,但是,有一个专门为工作人员开发的社区步骤:工作人员健康检查。运行此步骤可确保我们的员工身体健康,并为下一步做好准备。

安装附加软件

工作人员将需要与 Azure 和 AWS 进行交互。触手映像只有运行所需的最少软件,所以我需要安装以下软件:

  • PowerShell 核心
  • Azure CLI
  • AWS CLI

使用部署目标,您可以针对具有特定角色的所有目标执行步骤。由于工人没有角色,我需要找到一个替代者。脚本控制台允许您对池中的所有工作线程执行相同的代码。因为 Octopus 是 API 优先编写的,所以我能够复制脚本控制台的功能来运行代码,以便通过 API 针对整个池安装附加软件:

# Define parameters
$baseUrl = $OctopusParameters['Global.Base.Url'] 
$apiKey = $OctopusParameters['Global.Api.Key'] 
$spaceId = $OctopusParameters['Octopus.Space.Id']
$spaceName = $OctopusParameters['Octopus.Space.Name']
$workerPoolName = $OctopusParameters['Project.WorkerPool.Name']

if ($baseUrl.EndsWith("/"))
{
    $baseUrl = $baseUrl.SubString(0, $baseUrl.LastIndexOf("/"))
}

# Get worker pool
$workerPool = ((Invoke-RestMethod -Method Get -Uri "$baseUrl/api/$spaceId/workerpools/all" -Headers @{"X-Octopus-ApiKey"="$apiKey"}) | Where-Object {$_.Name -eq $workerPoolName})

# Build payload
$jsonPayload = @{
    Name = "AdHocScript"
    Description = "Script run from management console"
    Arguments = @{
        MachineIds = @()
        TenantIds = @()
        TargetRoles = @()
        EnvironmentIds = @()
        WorkerIds = @()
        WorkerPoolIds = @($workerPool.Id)
        TargetType = "WorkerPools"
        Syntax = "Bash"
        ScriptBody = @"
# Update the list of products
sudo apt-get update

# 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

# Install az module
echo "Installing az module ..."
pwsh -Command "& {Install-Module az -Scope AllUsers -Force}" > /tmp/azModuleInstall.log

# Install Az cli
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash

# Install aws cli
sudo apt-get install awscli -y
"@
    }
    SpaceId = "$spaceId"
}

$scriptTask = (Invoke-RestMethod -Method Post -Uri "$baseUrl/api/tasks" -Body ($jsonPayload | ConvertTo-Json -Depth 10) -Headers @{"X-Octopus-ApiKey"="$apiKey"})

# Check to see if the health check is queued
while ($scriptTask.IsCompleted -eq $false)
{
    Write-Output "Waiting 5 seconds ..."
    Start-Sleep -Seconds 5
    $scriptTask = (Invoke-RestMethod -Method Get -Uri "$baseUrl/api/tasks/$($scriptTask.Id)" -Headers @{"X-Octopus-ApiKey"="$apiKey"})
}

Write-Output "Installation complete!" 

因为我需要创建多个工人,所以步骤 2 和 3 在我的流程中创建了多次

集装箱化工人

当配置为监听模式时,触手容器被编码为自动连接到 Octopus 服务器并向指定的工作池注册。运行手册完成后,我有三个健康的工人。

结论

在这篇文章中,我演示了如何创建作为容器运行的工人。这为您提供了创建工作线程的灵活性,而无需管理虚拟机!

工作人员解释-章鱼部署

原文:https://octopus.com/blog/workers-explained

在 Octopus Deploy 的早期,随着产品的发展,对安装它的服务器提出了越来越高的要求。任何没有直接在目标上执行的步骤都在服务器本身上执行。

为了处理越来越多的直接在服务器上执行的任务,我们创建了 Workers 的概念。

在这篇文章中,我将回答一些关于工人以及他们如何工作的常见问题。

工人到底是什么?

本质上,工人是一根触手。它运行与部署目标相同的触手软件,但是它是在工作池中的服务器上注册的。工作池是工作机的集合。

如果工人是触手,算不算许可对象?

尽管运行着触手软件,但工人被视为 Octopus 服务器的延伸,因此不被算作目标。

在当前的许可模式下,您可以拥有多少台工作机是没有限制的。

什么是内置工人?

所有 Octopus 实例都定义了一个默认的工人池。任何选择默认工人池的步骤都在内置工人上执行,这个工人就是 Octopus 服务器。然而,如果一个工作机被添加到默认工作机池,这个内置工作机就不再被使用了。

工人可以用来做什么?

Workers 可以用于不需要在目标上执行的步骤。最常见的使用案例是:

  • 数据库部署
  • API 或 Web 服务调用
  • 运行脚本
  • Kubernetes 部署

数据库部署

部署数据库更新只需要一个到数据库服务器和正在使用的数据库的连接字符串。工作人员可以帮助执行数据库部署,而不必在数据库服务器上安装额外的软件。

API 或 Web 服务调用

Workers 非常适合调用 API,或者不需要目标就能运行的 Web 服务。例子包括:

  • 时差通知
  • Microsoft 团队消息
  • 部署 SQL Server Reporting Services 报表

运行脚本

运行脚本是工作者的另一个用例。进程密集型操作可以被卸载到一个工人上运行,而不是减慢 Octopus 服务器。

Kubernetes 部署

Kubernetes (K8s)目标是唯一需要工人的目标类型。

K8s 目标的工作人员要求安装kubectl CLI。因此,您可以在 K8s 目标屏幕上选择在运行状况检查操作期间使用的工作池。

Kubernetes 部署与 API 交互,向 K8s 集群提供指令,而不是直接向其部署文件。这对工人来说是一个完美的用例。

使用工人的优势

工人提供两大优势:

  • 从 Octopus 服务器卸载进程
  • Ability to run customized software

从 Octopus 服务器卸载进程

长时间运行或密集的进程会影响 Octopus 服务器的性能。这些任务可以卸载到一个工作机,释放资源,让 Octopus 服务器以最佳状态运行。

定制软件

Octopus 附带的捆绑软件可能不包含流程所需的所有内容。有了工作人员,您可以安装定制软件包来帮助您的部署或 runbook 过程。

如果 Worker 安装了 Docker,它可以使用执行容器特性来定制容器,而不是直接在 Worker 上安装软件。

【T2 Octopus dashboard showing Container Image section with Runs inside a container, on worker selected.

如何指定使用工人的步骤?

当在 Runbook项目部署过程中定义一个步骤时,可以告诉 Octopus 这个步骤在一个 Worker 上运行并选择一个池。

Octopus dashboard Execution Location, Run once on a worker & Worker Pool, Runs on a worker from a specific worker pool selected

工人池变量

您可能已经注意到 Worker Pool 部分有第二个选择,运行在通过变量选择的池中的一个 Worker 上。我们创建了工人池变量,用于在不同的情况下需要不同的工人池,比如环境。

有些人进行了安全隔离,这样开发人员就不允许接触测试中的资源。使用 Worker Pool 变量,您可以将池的范围扩大到环境,甚至是表示特定 Azure 区域的租户标签。

Octopus dashboard showing Project.Worker.Pool variable with multiple values.

工人的执行与目标有何不同?

如果您试图对同一台目标机器执行两个部署,您可能会注意到部署似乎在任务之间来回切换,一次执行一个步骤。此行为旨在保护目标,防止多个部署试图同时更新同一资源,如 IIS 元数据库。另一方面,工作人员被配置为同时处理多项任务。

Acquire Packages这样的活动会导致一个工作线程被锁定,而使用同一个工作线程的任何其他部署/runbook 都处于等待状态。

如何从人才库中挑选员工?

工人是以循环赛的方式从人才库中挑选出来的。需要注意的是,在部署或运行手册运行的开始选择工人。我将在这篇文章的后面解释工人选择的注意事项。

假设每个步骤由池中不同的工作人员执行是最安全的。

考虑以下场景,其中工作池Setup由以下部分组成:

运行手册释放克拉肯调用运行手册Create AWS RDS进行环境开发、测试、准备和生产。

Octopus dashboard showing Runbooks process for Unleash the kraken.

流程中的所有步骤按顺序执行,但配置为不等待运行手册完成就进入下一步(详情见运行 Octopus 部署运行手册步骤)。工人选择如下:

Unleash the kraken
|
+-- worker1
|    |
     Create AWS RDS Development
     |
     +-- worker1
     |
     Create AWS RDS Test
     |
     +-- worker2
     |
     Create AWS RDS Staging
     |
     +-- worker3
     |
     Create AWS RDS Production
     |
     +-- worker1 

工人选择警告

有些情况会影响 Octopus 选择员工的方式:

  • 引用包的步骤
  • 包装参考订购
  • 人工干预

引用包的步骤

任何使用相同包的步骤都在相同的工作机器上执行。例如,run bookCreate Region Workers将相同的映像部署到不同 Azure 区域中的 Kubernetes 集群。因为步骤 2 到 6 使用相同的包(映像),所以它们都使用相同的 Worker。

Octopus dashboard open on Runbooks section then Create Region Workers showing steps 1 to 6 of process creating workers in specific Azure regions.

参考包装订购

薪资包参考排序也会影响员工选择。例如,如果有两个步骤以相同的顺序引用相同的包,那么 Octopus 会在同一个 Worker 上运行这两个步骤。

Octopus dashboard showing Referenced Packages

但是,如果包的顺序不同,八达通为每个步骤选择不同的工人。

Octopus dashboard showing Referenced Packages in opposite order

人工干预

当遇到手动干预步骤时,它将从任务队列中删除。在干预被执行之后,任务被添加回队列,这迫使工人选择再次发生。

我在用章鱼云,动态工作者是怎么工作的?

Octopus Deploy 维护了一组工作器(VM ),您可以按需将它们用作动态工作器。这些员工可在以下人才库中找到:

  • 默认工作池(Windows Server 2016)
  • 托管 Windows (Windows Server 2019 *)
  • 托管的 Ubuntu (Ubuntu 18.04 *)

*池可以使用执行容器特性。

每个云实例可以为每个池租赁一个工作线程,专门用于该云实例。租约到期后,工人被销毁(时间到期见这篇关于动态工人的文章。)销毁后,将供应一个新的工作线程,并将其添加到可供云实例租赁的可用工作线程池中。

结论

我希望这篇文章能澄清什么是工人,他们是如何被使用和选择的。写这篇文章让我学到了很多。

如果您需要帮助或想了解更多关于使用员工的信息,您可以通过customersuccess@octopus.com联系我们的客户成功团队。

愉快的部署!

Octopus 工作人员和绩效- Octopus 部署

原文:https://octopus.com/blog/workers-performance

Octopus Workers illustration

在上一篇中,我介绍了工人和一些可以用他们做的事情。我也答应给你更多的细节。从那以后,我们在 2018.7.0 版本中交付了工人,八达通客户已经拿起并开始使用它。现在我又有了几个帖子。

外部工作人员让你将一些部署工作从你的 Octopus 服务器转移到其他机器上。你可以简单地从安全的角度将工作转移给工人,例如,这样脚本就不会在你的 Octopus 服务器上运行。但是将工作从服务器上移走还有其他原因。其中之一就是性能。这一次,我将研究在部署期间使用 Workers 对您的 Octopus 服务器性能的影响。

现在,在这种情况下,我不得不从经典的警告开始,你的里程可能会有所不同。你的八达通服务器上有很多东西。根据您的设置,您可能将数据库放在同一台机器上,也可能放在其他地方,您可能将日志存储在本地或共享上,Octopus 运行系统任务以及您的部署,您的部署模式将极大地影响服务器正在进行的工作。在这篇文章中,我将研究部署期间服务器上的 CPU、磁盘和网络负载。

我们一直在努力提高 Octopus 的性能。有时这就是数据库性能。有时,它是寻找我们可以优化或缓存以提高 API 性能的部分。这篇文章不是关于这些。这是关于当你运行一个部署时轮子何时上路的问题。章鱼无法围绕这些位进行优化。这是部署的实际工作,所以必须完成。但是您可以分配工作量,以获得对您来说最好的结果。

您的 Octopus 服务器是您的部署基础设施的核心,过载的 Octopus 服务器会使部署变得缓慢,并使部署者沮丧。然而,使用 Workers,您可以有效地将 CPU、磁盘和网络负载转移到外部机器,让您的 Octopus 服务器休息一下,并促进您的部署。

在这篇文章中,我将把一些东西分开,这样你就可以理解工作的各个部分,以及将你的服务器的工作分配给工作机的权衡。为此,我将从您可以看到的角度来运行所有内容。因此,这不是关于我可以在内部测量什么,而是关于你可以在你的机器上观察到什么,以及它如何影响你的服务器和你的部署时间。

为了做到这一点,我不打算用数字或基准来谈论,而只是简单地展示资源监视器中的图表。没有花哨的工具,只是 AWS 中一个新的 Octopus 服务器,一个每个人都有的工具,以及对图形上下波动的解释。

下面的案例是玩具示例,旨在展示概念,而不是代表真实的部署模式。但是,如果我可以在这些简单的例子上演示负载削减,想象一下您可能在 Octopus 实例上获得的负载削减,以及随之而来的部署和可用性方面的改进,以及更健壮的 Octopus 服务器设置。

案例 1:一个简单的脚本

好了,让我们用一个非常简单的部署来热身。在部署时模拟任务的脚本。它休眠 10 秒钟,然后旋转到 10,000 次写入日志消息。

Script Step

因为这是一个紧密的循环,它会影响 CPU 因为它写日志,所以会撞到磁盘。我已经将脚本定位于默认的工作池。因此,如果池是空的,它将在 Octopus 服务器上的内置 Worker 上运行,如果池包含 Worker,它将在外部 Worker 机器上运行。

当缺省池为空时,该步骤在 Octopus 服务器上的内置 Worker 上运行,Octopus 服务器上的负载如下图所示。当我将一个 Worker 放入缺省池中时,该步骤在那个外部 Worker 上运行,Octopus 服务器上的负载看起来就像右下图所示。

Script Step performance

在我的设置中,数据库和日志存储都在 Octopus 服务器上,所以 CPU 上的第一个任务包含一些数据库工作以及规划部署。但是在我的例子中,数据库太小了,所以我忽略它的影响。

在左边,我们看到一个 CPU 命中结果是调用一个新的 Calamari (我们的开源的,基于约定的部署可执行文件)进程的成本。睡眠者将工作与第二个尖峰分开,第二个尖峰主要是for循环。在“磁盘”下,您可以看到用于设置日志等并将日志写入磁盘的尖峰。磁盘图表上的比例让您知道,这个简单的日志写操作并没有给我们带来太多负担。

在右边,当实际工作发生在外部工作机器上时,磁盘活动与以前基本相同:因为,在这个例子中,服务器与日志存储在同一个机器上,这些成本总是在这里被看到。然而,CPU 使用率消失了,这表明第一个图中的初始峰值是 Calamari 调用。有一些网络活动将日志从 Worker 传输回 Octopus 服务器,但是这里太小了,看不清楚;您将在后面的示例中看到网络点击。

我们可以用一些额外的基础设施来平衡这里的磁盘命中率:例如,将日志存储在其他地方会将这些成本转移到其他机器上(以网络流量为代价)。

关于成本需要记住的一点是,每个步骤的每个角色都会调用 Calamari。因此,如果我将脚本定位为代表包含十个部署目标的角色运行,我们将获得 10 倍的 Calamari 调用和 10 倍的脚本成本。在你的 Octopus 服务器上的内置工作器上,这加起来,但是如果我们把它卸载到外部工作器上,这几乎是不可察觉的。

Script Step on behalf performance

案例 2:S3 文件上传

最后一个例子显示了一个非常明显的例子,如果有计算工作可以转移到一个工人身上,那么这样做就转移了 Octopus 服务器的整个 CPU 开销。但这也暗示了需要做出其他的权衡,正如我们将在本例中看到的,有时服务器会产生计算工作。

在这个例子中,我正在 S3 上传一个 296MB 的文件。同样,我将这一步的目标放在了默认的工人池。当该池为空时,它在内置的 Worker 上运行,并在下面的左侧给出了图形,当该池包含外部 Worker 时,它在该机器上运行,对 Octopus 服务器的影响由右侧的图形给出。

S3 Upload

在左侧,您可以看到将包推送到 AWS S3 需要一些 CPU 成本,而通过网络获取包需要一些网络开销。在此示例中,包已经在服务器上。如果服务器必须首先获得包,我们会看到额外的网络和磁盘成本

在右边,整个包仍然必须通过网络出去,所以网络成本是相同的,但有趣的是,将包推送到 S3 比 Octopus 将它推送到一个工人那里花费更多的 CPU。

当我们构建该包的下一个版本并再次部署该项目时会怎么样呢?在这种情况下,在 Octopus 服务器上运行内置 Worker 的成本是一样的;然而,在外部工作器上,我们可以选择不推送整个包,而是计算包的差异并只发送差异。如果我们这样做,我们会得到一个如下图所示的图表。

S3 upload package options

对于近 300MB 的包,有一个新的 CPU 成本,这是差异的计算,在这种情况下,我们可以看到磁盘上的表现,因为 Octopus 必须访问包的两个版本来计算差异。在任务日志中,我看到

22:24:14   Verbose  |         Finding earlier packages that have been uploaded to this Tentacle.
22:24:15   Verbose  |         Found 1 earlier version of bigpackage on this Tentacle
22:24:15   Verbose  |         - 2018.7.6: C:\Octopus\TestInstance1\Files\bigpackage@S2018.7.6@103E651645EFF14EB4E34C8DA3AC2E6C.zip
22:24:15   Verbose  |         Process C:\Windows\system32\WindowsPowershell\v1.0\PowerShell.exe in C:\Octopus\TestInstance1\Work\20180806222409-891-383 exited with code 0
22:24:15   Info     |         Found matching version 2018.7.6: C:\Octopus\Packages\bigpackage\bigpackage.2018.7.6.zip
22:24:39   Info     |         Delta for package bigpackage v2018.7.7 successfully uploaded and applied.
                    |       
                    |         Success: Building delta for bigpackage v2018.7.7 (296.824 MB)
22:24:15   Info     |           Using package C:\Octopus\Packages\bigpackage\bigpackage.2018.7.6.zip with hash 68dc6388f3f467eeeec802c8ad6e8f207309c982 for creating delta.
22:24:15   Verbose  |           Building signature file: C:\Octopus\OctopusServer\PackageCache\bigpackage.2018.7.6.zip.octosig
22:24:15   Verbose  |           - Using nearest package: C:\Octopus\Packages\bigpackage\bigpackage.2018.7.6.zip
22:24:18   Verbose  |           Building delta file: C:\Octopus\OctopusServer\PackageCache\bigpackage.2018.7.6_68DC6388_to_2018.7.7_19AF67C0.octodelta
22:24:18   Verbose  |           - Using package: C:\Octopus\Packages\bigpackage\bigpackage.2018.7.7.zip.
22:24:18   Verbose  |           - Using signature: C:\Octopus\OctopusServer\PackageCache\bigpackage.2018.7.6.zip.octosig
22:24:32   Info     |           Original package was 296.824 MB, delta file is 109.195 MB (63.21% size reduction). 

因此,在这种情况下,我得到了 63.21%的不错的减少,但如果这两个包更相似,计算差异的成本回报会更好(有趣的是,这两个包是 Octopus Server 版本 2018.7.7 和 2018.7.8,所以我预计它们会更相似——这只是显示了我们在每个版本中塞进了多少工作)。

触手还可以选择直接获取包,而不是把包放在服务器上。我们正在添加新的特性来允许每步多个包,所以我们正在修改它在 UI 中的外观,但是对于这个测试,我设置了 Octopus 系统变量 Octopus.Action.Package.DownloadOnTentacle。在这种情况下,我们的 Octopus 服务器几乎不做任何事情(上图右侧),它只是询问工人是否完成并写入日志的成本。

案例 Azure Web 应用部署

Azure 部署都需要一个工人。所有的 Azure 步骤要么使用 WebDeploy,要么使用 Azure CmdLets,所以要么是内置的,要么是外部的。

为了这个测试,我从 Github 克隆了一个公共的 ASP.NET 核心项目,做了一些小的修改以允许 Octopus 变量替换,使用dotnet publishocto pack构建和打包,并使用我们的内置步骤部署为一个 Web 应用程序。最终的包非常小,只有 21MB 多一点。

同样,该步骤的目标是在默认的工作池中运行。因此,当池为空时,它在 Octopus 服务器上的内置 Worker 上运行,并在下面的左侧给出图形,当池包含 Worker 时,该步骤在该机器上运行,对 Octopus 服务器的影响显示在右侧的图形中。

【T2 Azure Web App deployment

左边,CPU 成本是开始部署,调用 Calamari,解包,做变量替换,和 Azure 协商需要上传什么文件。磁盘成本是出于同样的原因,网络成本是与 Azure 的协商和将数据推送到云。这是一个非常小的封装,因此所有这些成本都会随着封装尺寸的增加而增加。如果你正在把 100 兆以上的包推给 Azure,通过预部署和后部署脚本、配置转换、变量替换等等。,然后你会在这里看到一个更大的点击。

在右边,当该步骤在外部工作器上运行时,Octopus 服务器上唯一的开销是少量的网络流量和写入一个 1.1MB 的日志文件。注意,在这种情况下,磁盘图形上的刻度缩小了 10 倍。同样,通过选择将包直接下载到 Worker 和外部日志存储,可以进一步降低这些成本。

案例 4:全部在一起

我已经描述了三个漂亮的玩具部署示例,但是每一个都涉及一些必须完成的内在工作,以使部署成功。所以没有理由优化掉这些成本,但是这些成本是可以转移的。下图显示了所有三个项目同时运行时 Octopus 服务器的成本。左边是使用内置工作人员,右边是外部工作人员执行实际的部署工作。请注意磁盘图表上的比例——服务器左侧是右侧的 10 倍。

Three Deployments

现在,左边的图表并不是该服务器有史以来最艰难的一天,但是如果这还没有接近您的 Octopus 实例上的工作负载,那么您可能可以通过将 CPU 工作、磁盘命中和网络流量从您的 Octopus 服务器上移走来释放更多的资源。一旦工作负载变得更大,它也会开始对总部署时间产生影响,因为并行部署会争夺资源,因此转移到工作人员可能会加快您的部署,并将工作转移到服务器之外。

结论

在这篇文章中,通过三个简单的部署,我挑选了部署在你的 Octopus 服务器上的各种成本。这些例子并不大也不真实,所以如果我可以在这些玩具例子中减少负载,你应该可以在真实的工作负载中做更多的事情。我希望它们让您对部署中的一些活动部分有了更多的了解,并帮助您理解如何为使用 Workers 的步骤优化部署。有很多选择,例如,从从服务器部署,到只推送包差异,到将整个包处理转移到工作器——也许差异足够小,以至于在服务器上花费 CPU 将在网络流量上取得巨大胜利,或者也许你可以将你的包 feeds、工作器和 Azure 目标放在一起,以便证明是最好的网络选项。工作人员只是为您提供如何设置部署以及如何分配工作的选项。

我们也有性能文档来帮助你优化你的八达通服务器。

记住,工人没有什么特别的。它只是一台触须或 SSH 机器,因此您可以获得您拥有的任何备用计算资源——可能是工作不多的现有触须虚拟机,可能是本地机器,可能是具有备用周期的开发或测试箱——或者您可以仅为部署负载提供特殊的工作人员基础架构。

下一次,我将仔细观察每个目标都连接了默认工作人员池的 PaaS 目标,这样您就可以让您的工作人员靠近您的目标,甚至可以用防火墙隔离您的基础架构,以便工作人员从安全的网络轮询服务器,并且只有工作人员有权部署到目标。

愉快的部署!

在八达通工作-硅和钢的故事-八达通部署

原文:https://octopus.com/blog/working-at-octopus-silicon-and-steel

我在八达通的五周年纪念日很快就要到了。为了庆祝,我决定利用八达通政策,它提供了我每天最常用的工具:电脑。在 Octopus 工作的最大好处之一是计算机项目,这项政策让我每隔几年就更新一次硬件,并保留(或捐赠)我的旧硬件。

在成长过程中,我接触过一台 386 电脑,配有涡轮按钮和 2400bps 调制解调器。我认为这是世界上最好的东西,但当我的朋友开始得到 486 和奔腾时,我很快就变得嫉妒了。我的嫉妒在惊奇地看着《星际争霸》时达到了顶峰,难过的是它永远不会在我爸爸的笨重的 386 上运行。

当我开始在 Octopus 工作时,我第一次尝到了顶级电脑的滋味。我选择了微星 WS60 笔记本电脑,这是一个强大的功能和便携性的平衡。我记得我第一天到办公室。保罗·斯托弗(首席执行官)最近骑着他的摩托车从当地的一家电脑商店回来,我的笔记本电脑绑在他的背上。太壮观了。

随着 Octopus 的成长,我越来越多地参与性能调查,Octopus 变得更加以远程为中心。我的下一台笔记本电脑牺牲了便携性,换来了强大的功能和存储空间。Metabox 提供你能想象到的任何规格的笔记本电脑,所以我选择了一台更大的 Metabox Prime 笔记本电脑,它有很多内存和 2TB 的存储空间。我欣赏额外的力量,我的按摩师也欣赏额外的尺寸。

为了五周年纪念电脑,我着手制作终极代码编译工作站。它将被称为 Octobox-V,它的功能足够强大,可以在 10 分钟内加载 Visual Studio(zing ),但又足够小巧时尚,可以放在我的桌子上。我想摆脱笔记本电脑常见的风扇噪音和散热问题。我会造一个精简的,吝啬的,章鱼桌面机。

这个配置是我想出来的:

An amazing desktop computer

Ryzen 7 2700X, 32GB DDR4 3200, RTX 2070, 1TB PCIe, 2TB SATA

那么它的表现如何呢?与 Octobox-V 正在取代的极其强大的笔记本电脑相比,编译时间减少了 65%。它是完全无声的。对于性能调查中常见的进程转储和数据库备份有足够的空间(所以请将它们发送到 support@octopus.com)。对于不在家的工作,我有一台超极本,我用它通过 VPN 到 Octobox-V 来远程桌面,因此性能和便携性之间没有任何折衷。最棒的是,它玩了一个星际争霸的卑鄙游戏。

编写自己的 PowerShell 期望状态配置(DSC)模块- Octopus Deploy

原文:https://octopus.com/blog/write-your-own-powershell-dsc-module

Octopus learning how to write a custom PowerShell DSC Module

PowerShell DSC 是一项非常棒的技术,可以放在您管理基于 Windows 的服务器的工具箱中。这篇文章是一系列文章的一部分:

我们也有关于使用 PowerShell DSC 和 Octopus Deploy 的文章:


随着您对 PowerShell 期望状态配置(DSC)的了解越来越多,您可能会遇到可用模块不太适合您想要做的事情的情况。您可以编写自己的脚本资源,但是它们的伸缩性不好,传递参数很困难,并且它们不提供加密方法,以明文形式留下密码,但是,您可以编写自己的 DSC 模块。

在这个 PowerShell DSC 教程中,我将介绍如何编写您的第一个 PowerShell DSC 模块。

编写自己的 PowerShell DSC 模块并没有那么难。最困难的部分是将文件和文件夹放在正确的位置,因为 DSC 非常明确地规定了哪些文件放在哪里。然而,微软认识到这可能非常令人沮丧,并开发了 xDscResourceDesigner ,这是一个 PowerShell 模块,可以帮助您开始。使用这个模块,您可以很容易地定义您的资源需要什么属性,它将为您生成整个模块结构,包括 MOF 模式文件。如果你是第一次,我强烈推荐你使用这个模块,它可以帮你省去不少麻烦(相信我)。

安装 xDscResourceDesigner

安装模块与在系统上安装任何其他模块没有什么不同:

Install-Module -Name xDscResourceDesigner 

正如这篇微软文章所指出的,如果您的 PowerShell 版本早于第 5 版,您可能需要安装 PowerShellGet 模块以便安装工作。

使用 xDscResourceDesigner

使用 xDscResourceDesigner 实际上非常简单,只有两个函数:New-DscResourcePropertyNew-xDscResourceNew-DscResourceProperty是你用来定义你的 DSC 资源的属性。完成之后,将信息发送给New-xDscResource函数,它会生成实现资源所需的一切:

# Import the module for use
Import-Module -Name xDscResourceDesigner

# Define properties
$property1 = New-xDscResourceProperty -Name Property1 -Type String -Attribute Key
$property2 = New-xDscResourceProperty -Name Property2 -Type PSCredential -Attribute Write
$property3 = New-xDscResourceProperty -Name Property3 -Type String -Attribute Required -ValidateSet "Present", "Absent"

# Create my DSC Resource
New-xDscResource -Name DemoResource1 -Property $property1, $property2, $property3 -Path 'c:\Program Files\WindowsPowerShell\Modules' -ModuleName DemoModule 

这就是你自己的 DSC 模块,生成了所有的存根。

了解资源属性特性

对于 DSC 中资源属性的属性组件,有四个可能的值:

钥匙

使用您的资源的每个节点都必须有一个使该节点唯一的键。与数据库表类似,这个键不必是单个属性,但可以由几个属性组成,每个属性都带有 key 属性。在上面的例子中,Property1是我们的资源键。然而,也可以这样做:

# Define properties
$property1 = New-xDscResourceProperty -Name Property1 -Type String -Attribute Key
$property2 = New-xDscResourceProperty -Name Property2 -Type PSCredential -Attribute Write
$property3 = New-xDscResourceProperty -Name Property3 -Type String -Attribute Required -ValidateSet "Present", "Absent"
$property4 = New-xDscResourceProperty -Name Property4 -Type String -Attribute Key
$property5 = New-xDscResourceProperty -Name Property5 -Type String -Attribute Key 

在本例中,property1property4property5构成了节点的唯一值。键属性总是可写的,并且是必需的。

阅读

读取属性是只读的,不能为其赋值。

需要

必需属性是在声明配置时必须指定的可分配属性。使用上面的例子,当我们创建资源时,Property3属性被设置为 required。

写属性是可选属性,您可以在定义节点时为其指定值。在示例中,Property2被定义为写属性。

ValidateSet 开关

ValidateSet开关可以与 Key 或 Write 属性一起使用,指定给定属性的允许值。在我们的例子中,我们已经指定Property3只能是AbsentPresent。任何其他值都会导致错误。

DSC 模块文件和文件夹结构

无论您决定自己动手还是使用工具,文件夹和文件结构将如下所示:

$env:ProgramFiles\WindowsPowerShell\Modules (folder)
    |- DemoModule (folder)
        |- DSCResources (folder)
            |- DemoResource1 (folder)
                |- DemoResource1.psd1 (file, optional)
                |- DemoResource1.psm1 (file, required)
                |- DemoResource1.schema.mof (file, required) 

MOF 文件

MOF 代表托管对象格式,是用于描述公共信息模型(CIM)类的语言。使用来自工具的示例来帮助您编写模块部分,生成的 MOF 文件将如下所示:

[ClassVersion("1.0.0.0"), FriendlyName("DemoResource1")]
class DemoResource1 : OMI_BaseResource
{
    [Key] String Property1;
    [Write, EmbeddedInstance("MSFT_Credential")] String Property2;
    [Required, ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Property3;
}; 

MOF 文件将只包含我们将在模块中使用的属性,以及它们的属性和数据类型。除非我们添加或删除属性,否则这几乎是我们对 MOF 文件所做的全部工作。

psm1 文件

psm1 文件是我们大部分代码将要存放的地方。该文件将包含三个必需的函数:

  • Get-TargetResource
  • Test-TargetResource
  • Set-TargetResource

获取目标资源

Get-TargetResource函数返回资源所负责的当前值。我们使用xDscResourceDesigner得到的存根函数如下所示:

function Get-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [parameter(Mandatory = $true)]
        [System.String]
        $Property1,

        [parameter(Mandatory = $true)]
        [ValidateSet("Present","Absent")]
        [System.String]
        $Property3
    )

    #Write-Verbose "Use this cmdlet to deliver information about command processing."

    #Write-Debug "Use this cmdlet to write debug information while troubleshooting."

    <#
    $returnValue = @{
    Property1 = [System.String]
    Property2 = [System.Management.Automation.PSCredential]
    Property3 = [System.String]
    }

    $returnValue
    #>
} 

注意,该功能不需要可选参数Property2(写入属性)。

测试目标资源

Test-TargetResource函数返回一个布尔值,表明资源是否处于期望的状态。从我们生成的示例来看,该函数如下所示:

function Test-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [parameter(Mandatory = $true)]
        [System.String]
        $Property1,

        [System.Management.Automation.PSCredential]
        $Property2,

        [parameter(Mandatory = $true)]
        [ValidateSet("Present","Absent")]
        [System.String]
        $Property3
    )

    #Write-Verbose "Use this cmdlet to deliver information about command processing."

    #Write-Debug "Use this cmdlet to write debug information while troubleshooting."

    <#
    $result = [System.Boolean]

    $result
    #>
} 

设置-目标资源

Set-TargetResource功能用于将资源配置到指定的期望状态。我们生成的示例如下所示:

function Set-TargetResource
{
    [CmdletBinding()]
    param
    (
        [parameter(Mandatory = $true)]
        [System.String]
        $Property1,

        [System.Management.Automation.PSCredential]
        $Property2,

        [parameter(Mandatory = $true)]
        [ValidateSet("Present","Absent")]
        [System.String]
        $Property3
    )

    #Write-Verbose "Use this cmdlet to deliver information about command processing."

    #Write-Debug "Use this cmdlet to write debug information while troubleshooting."

    #Include this line if the resource requires a system reboot.
    #$global:DSCMachineStatus = 1
} 

摘要

无论简单还是复杂,创建自己的 PowerShell DSC 模块的步骤都是一样的。这篇文章旨在让你朝着正确的方向开始。从这里,您可以创建您的模块来适应您需要配置的任何资源,并保持在期望的状态。关于工作模块的完整示例,请查看我的 GitHub repo 上的 xCertificatePermission

Selenium 系列:编写小黄瓜特性——Octopus Deploy

原文:https://octopus.com/blog/selenium/27-writing-a-gherkin-feature/writing-a-gherkin-feature

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

现在我们知道了如何构造正则表达式来将方法映射到小黄瓜步骤,我们可以在AutomatedBrowserBase类中为所有适当的方法添加注释。

注意,我们没有为所有的方法添加注释。像getTextFromElementWithId()这样返回值的方法不能用在 Gherkin 步骤中,因为 Gherkin 没有变量的概念,所以返回值没有任何意义。我们也不公开像init()destroy()这样的方法,因为这些生命周期方法由openBrowser()closeBrowser()方法调用。有一些内部方法,像getWebDriver()getAutomatedBrowser()setAutomatedBrowser()getDesiredCapabilities()只被装饰者使用,作为小黄瓜步骤公开没有任何意义。

剩下的步骤应用了 Cucumber 注释,分配正则表达式遵循我们在上一篇文章中看到的相同逻辑:

package com.octopus.decoratorbase;

import com.octopus.AutomatedBrowser;
import com.octopus.AutomatedBrowserFactory;
import cucumber.api.java.en.And;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.DesiredCapabilities;

public class AutomatedBrowserBase implements AutomatedBrowser {

  // ...

  public AutomatedBrowserBase() {

  }

  public AutomatedBrowserBase(AutomatedBrowser automatedBrowser) {
    // ...
  }

  public AutomatedBrowser getAutomatedBrowser() {
    // ...
  }

  public void setAutomatedBrowser(AutomatedBrowser automatedBrowser) {
    // ...
  }

  @Given("^I open the browser \"([^\"]*)\"$")
  public void openBrowser(String browser) {
    // ...
  }

  @Given("^I close the browser$")
  public void closeBrowser() {
    // ...
  }

  @Override
  public WebDriver getWebDriver() {
    // ...
  }

  @Override
  public void setWebDriver(WebDriver webDriver) {
    // ...
  }

  @Override

  public DesiredCapabilities getDesiredCapabilities() {
    // ...
  }

  @Override
  public void init() {
    // ...
  }

  @Override
  public void destroy() {
    // ...
  }

  @And("^I open the URL \"([^\"]*)\"$")
  @Override
  public void goTo(String url) {
    // ...
  }

  @And("^I click the \\w+(?:\\s+\\w+)* with the id \"([^\"]*)\"$")
  @Override
  public void clickElementWithId(String id) {
    // ...
  }

  @And("^I click the \\w+(?:\\s+\\w+)* with the id \"([^\"]*)\" waiting up to \"(\\d+)\" seconds?$")
  @Override
  public void clickElementWithId(String id, int waitTime) {
    // ...
  }

  @And("^I select the option \"([^\"]*)\" from the \\w+(?:\\s+\\w+)* with the id \"([^\"]*)\"$")
  @Override
  public void selectOptionByTextFromSelectWithId(String optionText, String id) {
    // ...
  }

  @And("^I select the option \"([^\"]*)\" from the \\w+(?:\\s+\\w+)* with the id \"([^\"]*)\" waiting up to \"(\\d+)\" seconds?$")
  @Override
  public void selectOptionByTextFromSelectWithId(String optionText, String id, int waitTime) {
    // ...
  }

  @And("^I populate the \\w+(?:\\s+\\w+)* with the id \"([^\"]*)\" with the text \"([^\"]*)\"$")
  @Override
  public void populateElementWithId(String id, String text) {
    // ...
  }

  @And("^I populate the \\w+(?:\\s+\\w+)* with the id \"([^\"]*)\" with the text \"([^\"]*)\" waiting up to \"(\\d+)\" seconds?$")
  @Override
  public void populateElementWithId(String id, String text, int waitTime)
  {
    // ...
  }

  @Override
  public String getTextFromElementWithId(String id) {
    // ...
  }

  @Override
  public String getTextFromElementWithId(String id, int waitTime) {
    // ...
  }

  @And("^I click the \\w+(?:\\s+\\w+)* with the xpath \"([^\"]*)\"$")
  @Override
  public void clickElementWithXPath(String xpath) {
    // ...
  }

  @And("^I click the \\w+(?:\\s+\\w+)* with the xpath \"([^\"]*)\" waiting up to \"(\\d+)\" seconds?$")
  @Override
  public void clickElementWithXPath(String xpath, int waitTime) {
    // ...
  }

  @And("^I select the option \"([^\"]*)\" from the \\w+(?:\\s+\\w+)* with the xpath \"([^\"]*)\"$")
  @Override
  public void selectOptionByTextFromSelectWithXPath(String optionText, String xpath) {
    // ...
  }

  @And("^I select the option \"([^\"]*)\" from the \\w+(?:\\s+\\w+)* with the xpath \"([^\"]*)\" waiting up to \"(\\d+)\" seconds?$")
  @Override
  public void selectOptionByTextFromSelectWithXPath(String optionText, String xpath, int waitTime) {
    // ...
  }

  @And("^I populate the \\w+(?:\\s+\\w+)* with the xpath \"([^\"]*)\" with the text \"([^\"]*)\"$")
  @Override
  public void populateElementWithXPath(String xpath, String text) {
    // ...
  }

  @And("^I populate the \\w+(?:\\s+\\w+)* with the xpath \"([^\"]*)\" with the text \"([^\"]*)\" waiting up to \"(\\d+)\" seconds?$")
  @Override
  public void populateElementWithXPath(String xpath, String text, int waitTime) {
    // ...
  }

  @Override
  public String getTextFromElementWithXPath(String xpath) {
    // ...
  }

  @Override
  public String getTextFromElementWithXPath(String xpath, int waitTime) {
    // ...
  }

  @And("^I click the \\w+(?:\\s+\\w+)* with the css selector \"([^\"]*)\"$")
  @Override
  public void clickElementWithCSSSelector(String cssSelector) {
    // ...
  }

  @And("^I click the \\w+(?:\\s+\\w+)* with the css selector \"([^\"]*)\" waiting up to \"(\\d+)\" seconds?$")
  @Override
  public void clickElementWithCSSSelector(String cssSelector, int waitTime) {
    // ...
  }

  @And("^I select the option \"([^\"]*)\" from the \\w+(?:\\s+\\w+)* with the css selector \"([^\"]*)\"$")
  @Override
  public void selectOptionByTextFromSelectWithCSSSelector(String optionText, String cssSelector) {
    // ...
  }

  @And("^I select the option \"([^\"]*)\" from the \\w+(?:\\s+\\w+)* with the css selector \"([^\"]*)\" waiting up to \"(\\d+)\" seconds?$")
  @Override
  public void selectOptionByTextFromSelectWithCSSSelector(String optionText, String cssSelector, int waitTime) {
    // ...
  }

  @And("^I populate the \\w+(?:\\s+\\w+)* with the css selector
  \"([^\"]*)\" with the text \"([^\"]*)\"$")
  @Override
  public void populateElementWithCSSSelector(String cssSelector, String text) {
    // ...
  }

  @And("^I populate the \\w+(?:\\s+\\w+)* with the css selector \"([^\"]*)\" with the text \"([^\"]*)\" waiting up to \"(\\d+)\" seconds?$")
  @Override
  public void populateElementWithCSSSelector(String cssSelector, String text, int waitTime) {
    // ...
  }

  @Override
  public String getTextFromElementWithCSSSelector(String cssSelector) {
    // ...
  }

  @Override
  public String getTextFromElementWithCSSSelector(String cssSelector, int waitTime) {
    // ...
  }

  @And("^I click the \\w+(?:\\s+\\w+)* with the name \"([^\"]*)\"$")
  @Override
  public void clickElementWithName(String name) {
    // ...
  }

  @And("^I click the \\w+(?:\\s+\\w+)* with the name \"([^\"]*)\" waiting up to \"(\\d+)\" seconds?$")
  @Override
  public void clickElementWithName(String name, int waitTime) {
    // ...
  }

  @And("^I select the option \"([^\"]*)\" from the \\w+(?:\\s+\\w+)* with the name \"([^\"]*)\"$")
  @Override
  public void selectOptionByTextFromSelectWithName(String optionText, String name) {
    // ...
  }

  @And("^I select the option \"([^\"]*)\" from the \\w+(?:\\s+\\w+)* with the name \"([^\"]*)\" waiting up to \"(\\d+)\" seconds?$")
  @Override
  public void selectOptionByTextFromSelectWithName(String optionText, String name, int waitTime) {
    // ...
  }

  @And("^I populate the \\w+(?:\\s+\\w+)* with the name \"([^\"]*)\" with the text \"([^\"]*)\"$")
  @Override
  public void populateElementWithName(String name, String text) {
    // ...
  }

  @And("^I populate the \\w+(?:\\s+\\w+)* with the name \"([^\"]*)\" with the text \"([^\"]*)\" waiting up to \"(\\d+)\" seconds?$")
  @Override
  public void populateElementWithName(String name, String text, int waitTime) {
    // ...
  }

  @Override
  public String getTextFromElementWithName(String name) {
    // ...
  }

  @Override
  public String getTextFromElementWithName(String name, int waitTime) {
    // ...
  }

  @And("^I click the \"([^\"]*)\" \\w+(?:\\s+\\w+)*$")
  @Override
  public void clickElement(String locator) {
    // ...
  }

  @And("^I click the \"([^\"]*)\" \\w+(?:\\s+\\w+)* waiting up to \"(\\d+)\" seconds?$")
  @Override
  public void clickElement(String locator, int waitTime) {
    // ...
  }

  @And("^I select the option \"([^\"]*)\" from the \"([^\"]*)\" \\w+(?:\\s+\\w+)*$")
  @Override
  public void selectOptionByTextFromSelect(String optionText, String locator) {
    // ...
  }

  @And("^I select the option \"([^\"]*)\" from the \"([^\"]*)\" \\w+(?:\\s+\\w+)* waiting up to \"(\\d+)\" seconds?$")
  @Override
  public void selectOptionByTextFromSelect(String optionText, String locator, int waitTime) {
    // ...
  }

  @And("^I populate the \"([^\"]*)\" \\w+(?:\\s+\\w+)* with the text \"([^\"]*)\"$")
  @Override
  public void populateElement(String locator, String text) {
    // ...
  }

  @And("^I populate the \"([^\"]*)\" \\w+(?:\\s+\\w+)* with the text \"([^\"]*)\" waiting up to \"(\\d+)\" seconds?$")
  @Override
  public void populateElement(String locator, String text, int waitTime) {
    // ...
  }

  @Override
  public String getTextFromElement(String locator) {
    // ...
  }

  @Override
  public String getTextFromElement(String locator, int waitTime) {
    // ...
  }

  @And("^I capture the HAR file$")
  @Override
  public void captureHarFile() {
    // ...
  }

  @And("^I capture the complete HAR file$")
  @Override
  public void captureCompleteHarFile() {
    // ...
  }

  @And("^I save the HAR file to \"([^\"]*)\"$")
  @Override
  public void saveHarFile(String file) {
    // ...
  }

  @And("^I block the request to \"([^\"]*)\" returning the HTTP code \"\\d+\"$")
  @Override
  public void blockRequestTo(final String url, final int responseCode) {
    // ...
  }

  @And("^I alter the response fron \"([^\"]*)\" returning the
  HTTP code \"\\d+\" and the response body:$")
  @Override
  public void alterResponseFrom(String url, int responseCode, String responseBody) {
    // ...
  }

  @And("^I maximize the window$")
  @Override
  public void maximizeWindow() {
    // ...
  }

} 

有了这些注释,我们现在可以编写一个特性文件来完成从 TicketMonster 购买门票的测试。

将以下代码保存到文件src/test/resources/com/octopus/ticketmonster.feature:

Feature: Test TicketMonster
  Scenario: Purchase Tickets
    Given I open the browser "ChromeNoImplicitWait"
    When I open the URL "https://ticket-monster.herokuapp.com"
    And I click the "Buy tickets now" button waiting up to "30" seconds
    And I click the "Concert" link waiting up to "30" seconds
    And I click the "Rock concert of the decade" link waiting up to "30" seconds
    And I select the option "Toronto : Roy Thomson Hall" from the "venueSelector" drop-down list waiting up to "30" seconds
    And I click the "bookButton" button waiting up to "30" seconds
    And I select the option "A - Premier platinum reserve" from the "sectionSelect" drop-down list waiting up to "30" seconds
    And I populate the "tickets-1" text box with the text "2" waiting up to "30" seconds
    And I click the "add" button waiting up to "30" seconds
    And I populate the "email" text box with the text "email@example.org" waiting up to "30" seconds
    And I click the "submit" button waiting up to "30" seconds
    Then I close the browser 

现在要么从 IntelliJ 运行CucumberTest测试类,要么将代码提交给 GitHub,让 Travis CI 为您运行测试。我们刚刚通过 TicketMonster 应用程序成功地复制了这个旅程,这个应用程序是我们在以前的帖子中用 Java 编写的。

如果你大声读出这个测试,它听起来就像是你给同事的指示,如果你在指示他们完成购票的话。但是格式还是有点笨重。大多数步骤以短语waiting up to "30" seconds结束,一些定位器如tickets-1没有给出很多上下文。

让我们解决短语waiting up to "30" seconds的不必要的重复。

我们首先向AutomatedBrowser接口添加一个名为setDefaultExplicitWaitTime()的新方法。我们将使用这个方法设置一个默认时间,用于所有步骤的显式等待:

package com.octopus;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.DesiredCapabilities;

public interface AutomatedBrowser {
  // ...
  void setDefaultExplicitWaitTime(int waitTime);
  // ...
} 

然后这个方法在AutomatedBrowserBase类中实现,并作为一个小黄瓜步骤公开:

package com.octopus.decoratorbase;

import com.octopus.AutomatedBrowser;
import com.octopus.AutomatedBrowserFactory;
import cucumber.api.java.en.And;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.DesiredCapabilities;

public class AutomatedBrowserBase implements AutomatedBrowser {

  // ...

  @And("^I set the default explicit wait time to \"(\\d+)\" seconds?$")
  @Override
  public void setDefaultExplicitWaitTime(int waitTime) {
    if (getAutomatedBrowser() != null) {
      getAutomatedBrowser().setDefaultExplicitWaitTime(waitTime);
    }
  }

  // ...

} 

然后在WebDriverDecorator类中,我们捕获了setDefaultExplicitWaitTime()方法中的默认等待时间,如果默认等待时间大于 0,则对于之前不接受waitTime参数的任何方法使用默认等待时间:

package com.octopus.decorators;

import com.octopus.AutomatedBrowser;
import com.octopus.decoratorbase.AutomatedBrowserBase;
import com.octopus.utils.SimpleBy;
import com.octopus.utils.impl.SimpleByImpl;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.Select;
import org.openqa.selenium.support.ui.WebDriverWait;

public class WebDriverDecorator extends AutomatedBrowserBase {

  // ...

  private int defaultExplicitWaitTime;

  // ...

  @Override
  public void setDefaultExplicitWaitTime(final int waitTime) {
    defaultExplicitWaitTime = waitTime;
  }

  // ...

  @Override
  public void clickElementWithId(final String id) {
    if (defaultExplicitWaitTime <= 0) {
        webDriver.findElement(By.id(id)).click();
      } else {
        clickElementWithId(id, defaultExplicitWaitTime);
    }
  }

  // ...

  @Override
  public void clickElement(final String locator) {
    clickElement(locator, defaultExplicitWaitTime);
  }

  // ...

} 

setDefaultExplicitWaitTime()方法中,defaultExplicitWaitTime实例变量被设置为默认等待时间:

private int defaultExplicitWaitTime;

@Override
public void setDefaultExplicitWaitTime(final int waitTime) {
  defaultExplicitWaitTime = waitTime;
} 

然后,我们在任何与元素交互但不接受等待时间参数的方法中使用这个默认值。例如,下面的clickElementWithId()方法不接受等待时间参数,默认情况下,会尝试立即单击元素而不等待。

随着我们所做的改变,如果defaultExplicitWaitTime大于零,我们改为调用重载的clickElementWithId()方法,该方法接受等待时间参数,传入defaultExplicitWaitTime的值。这意味着,如果已经定义了defaultExplicitWaitTime,那么不接受等待时间参数的方法现在会遵从那些接受等待时间参数的方法的重载版本,并且将依次等待一段时间,以使正在交互的元素可用并处于正确的状态。

所有不接受等待时间参数的方法都用这个新的if语句重写了:

@Override
public void clickElementWithId(final String id) {
  if (defaultExplicitWaitTime <= 0) {
    webDriver.findElement(By.id(id)).click();
  } else {
    clickElementWithId(id, defaultExplicitWaitTime);
  }
} 

唯一不使用相同逻辑来检查defaultExplicitWaitTime是否大于 0 的方法是那些使用简化定位器字符串的方法。这些方法已经委托给它们的重载兄弟,等待时间为零,现在被替换为defaultExplicitWaitTime变量:

@Override
public void clickElement(final String locator) {
  clickElement(locator, defaultExplicitWaitTime);
} 

现在我们可以像这样编写小黄瓜特性。我们称该步骤为And I set the default explicit wait time to "30" seconds,并从所有其他步骤中删除短语waiting up to "30" seconds:

Feature: Test TicketMonster With Default Wait
  Scenario: Purchase Tickets with default wait time
    Given I open the browser "ChromeNoImplicitWait"
    And I set the default explicit wait time to "30" seconds
    When I open the URL "https://ticket-monster.herokuapp.com"
    And I click the "Buy tickets now" button
    And I click the "Concert" link
    And I click the "Rock concert of the decade" link
    And I select the option "Toronto : Roy Thomson Hall" from the "venueSelector" drop-down list
    And I click the "bookButton" button
    And I select the option "A - Premier platinum reserve" from the "sectionSelect" drop-down list
    And I populate the "tickets-1" text box with the text "2"
    And I click the "add" button
    And I populate the "email" text box with the text "email@example.org"
    And I click the "submit" button
    Then I close the browser 

我们现在非常接近有一个测试,可以用接近简单的英语来写和读。最后剩下的关卡就是像tickets-1这样的定位器,很难读懂。

Gherkin 没有任何固有的常量概念,这意味着我们需要引入一些我们称之为别名的东西。别名只不过是键值对,但是它们允许我们将像Adult Ticket Count这样有意义的键赋给值tickets-1。然后,我们可以在小黄瓜步骤中使用键Adult Ticket Count,使该步骤更具可读性。

为了存储这些键值对,我们创建了一个名为aliases的新实例变量和一个名为setAliases()的新方法来保存它们:

public class AutomatedBrowserBase implements AutomatedBrowser {
  // ...

  private Map<String, String> aliases = new HashMap<>();

  // ...

  @Given("^I set the following aliases:$")
  public void setAliases(Map<String, String> aliases) {
    this.aliases.putAll(aliases);
  }

  // ...

} 

然后,我们利用 Cucumber 中一个名为数据表的特性来填充别名映射。

注意,正则表达式^I set the following aliases:$没有捕获组。传统上,我们使用捕获组作为向方法参数传递值的一种方式。但是在这种情况下,数据表是在步骤之后提供的,并作为一个Map对象传递给方法:

@Given("^I set the following aliases:$")
public void setAliases(Map<String, String> aliases) {
  this.aliases.putAll(aliases);
} 

我们这样称呼这一步。步骤下面的表作为Map传递给方法,第一列是键,第二列是值:

And I set the following aliases:
  | Venue | venueSelector |
  | Book | bookButton |
  | Section | sectionSelect |
  | Adult Ticket Count | tickets-1 |
  | Add Tickets | add |
  | Checkout | submit | 

现在我们可以填充这个地图,我们需要一种方法来阅读它。

Java 8 有一个方便的方法叫做getOrDefault(),它允许我们从 map 中获取一个值,或者返回一个默认值。现在,在AutomatedBrowserBase类的每个方法中,我们使用字符串值作为别名映射的键,将字符串参数传递给子AuotomatedBrowser实例,或者如果别名映射不包含作为键的字符串,则按原样传递参数。

例如,不要调用:

getAutomatedBrowser().selectOptionByTextFromSelectWithId(optionText, id) 

将参数optionTextid直接传递给子AuotomatedBrowser实例,我们改为调用:

getAutomatedBrowser().selectOptionByTextFromSelectWithId(
  aliases.getOrDefault(optionText, optionText),
  aliases.getOrDefault(id, id)) 

代码aliases.getOrDefault(optionText, optionText)表示“从aliases映射中获取分配给键optionText的值,或者如果该键不存在,则返回optionText作为默认值。”

下面的代码显示了AutomatedBrowserBase类中的方法在第一次尝试在别名映射中查找别名值时的样子。每个方法都被更新以查找别名映射,下面的代码显示了selectOptionByTextFromSelectWithId()方法是如何更新的:

@And("^I select the option \"([^\"]*)\" from the \\w+(?:\\s+\\w+)* with the id \"([^\"]*)\"$")
@Override
public void selectOptionByTextFromSelectWithId(String optionText, String
id) {
  if (getAutomatedBrowser() != null) {
    getAutomatedBrowser().selectOptionByTextFromSelectWithId(
    aliases.getOrDefault(optionText, optionText),
    aliases.getOrDefault(id, id));
  }
} 

这些变化意味着我们现在可以像这样编写测试。别名地图现在给模糊的定位器如tickets-1一个可读的名字如Adult Ticket Count:

Feature: Test TicketMonster With Aliases
  Scenario: Purchase Tickets with default wait time and aliases
    Given I open the browser "ChromeNoImplicitWait"
    And I set the following aliases:
      | Venue | venueSelector |
      | Book | bookButton |
      | Section | sectionSelect |
      | Adult Ticket Count | tickets-1 |
      | Add Tickets | add |
      | Checkout | submit |
    And I set the default explicit wait time to "30" seconds
    When I open the URL "https://ticket-monster.herokuapp.com"
    And I click the "Buy tickets now" button
    And I click the "Concert" link
    And I click the "Rock concert of the decade" link
    And I select the option "Toronto : Roy Thomson Hall" from the "Venue" drop-down list
    And I click the "Book" button
    And I select the option "A - Premier platinum reserve" from the "Section" drop-down list
    And I populate the "Adult Ticket Count" text box with the text "2"
    And I click the "Add Tickets" button
    And I populate the "email" text box with the text "email@example.org"
    And I click the "Checkout" button
    Then I close the browser 

现在我们有了暴露元素 id 的别名和友好名称的名称,如Add TicketsCheckout,测试满足了提供执行测试所需的实现细节的要求,同时也易于阅读。任何熟悉 TicketMonster web 应用程序的人都可以按照这些指示购买音乐会的门票。这就是小黄瓜语言的美妙之处,也是黄瓜库的强大之处。

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

您的部署自动化障碍是什么?-章鱼部署

原文:https://octopus.com/blog/your-deployment-automation-roadblocks

这个月我们将尝试一些新的东西,并可能把它变成一个常规的事情。

您是否一直希望自动化您的部署,或者开始向 DevOps 或持续交付天堂交付,但似乎无法实现?

您是否下载了 Octopus Deploy 的试用版,或者正在研究它,但在您的组织中安装时遇到了问题?

我们想知道你的障碍是什么,我们愿意帮助你解决它们。

可能是应用程序设计、配置、如何获得良好的构建管道,或者可能是说服人们自动化您的部署的问题。

报名参加我们的网络研讨会,让我们知道你有什么问题

然后在网上研讨会中,我们将尝试解决常见问题,并进行问答和互动环节。

在他们的章鱼部署之旅中,我们和很多人交谈过,我很确定我们有很多建议和智慧可以传递。

因此,如果这听起来对你有所帮助,去报名参加下一次网络研讨会,让我们来帮助你。

关于时间,很难让世界上的每个人都知道。如果你真的很想参与,但抽不出时间,请注册并提出你的问题(也许还可以记下你在世界的哪个角落)。我们将尽力解决您的问题,并为您提供一段录音。

从零到章鱼英雄-了解章鱼部署-章鱼部署

原文:https://octopus.com/blog/zero-to-octopus-hero-part-1

我叫 Sarah Lean,最近加入 Octopus Deploy,担任社区团队的高级解决方案架构师。

我从零开始部署章鱼,但我打算在未来几周内从零到章鱼英雄。

这篇文章是我学习章鱼系列文章的第一部分。我们会在该系列的其他文章中添加链接。

八达通部署是什么?

在您的环境中部署软件和基础设施是所有软件工程师都需要做的事情,无论我们是在内部运行还是在云环境中运行。在流程中引入自动化有助于提高效率和一致性。

Octopus Deploy 是一个平台,可帮助客户加速跨云和内部基础架构的可重复、可靠和可跟踪的部署。

Octopus Deploy 处理软件和基础设施部署的能力使其成为开发人员、DevOps 工程师、DevOps 经理和系统管理员的绝佳工具。

了解 Octopus 部署

作为一名系统管理员,我总是渴望了解一个产品是如何运行的。Octopus Deploy 允许您以多种方式设置实例:

我一直在从头开始安装章鱼部署。

这是一次很好的学习经历,亲身体验了技术,构建了基础设施,并在此过程中积累了知识。

在单个服务器配置上安装 Octopus Deploy 并建立一个用于学习目的的实验室非常简单。然而,我想了解客户如何在现实世界中运行 Octopus。

我花时间在我的环境中运行部署在高度可用架构中的 Octopus。我看着:

  • 如何配置负载平衡器
  • 数据库高可用性
  • 共享存储配置

我有过几次错误的开始,因为我认为我知道该做什么,并且没有阅读文档。

幸运的是,在使用虚拟机时,当你在学习新产品或新概念时犯了错误,你可以删除它们并重新开始。我利用了这个功能!

我的失败经历教会了我主密钥对你的 Octopus 部署环境有多重要,以及如何在没有备份密钥的情况下恢复。

主密钥是在安装时创建的,与 AES-128 一起用于加密 Octopus 数据库中的某些敏感数据。确保你备份了它并把它放在安全的地方。

当你开始工作并看到你的工作成果时,这种感觉很棒。当我设置配置并可以通过负载均衡器访问我的 Octopus Deploy 实例时,我第一次抽了空气!

经验教训

构建和破坏我的实现有助于强化部署 Octopus 的概念。我通过像这样的练习来积累知识。我不是高可用性配置方面的专家,但是我现在理解这些概念和要点。

另一个让我困惑的领域是共享存储。这需要在安装 Octopus Deploy 之前进行配置和检查。

您可以在没有共享存储的情况下安装 Octopus Deploy,但是如果您这样做,将会出现一些奇怪的行为。在开始安装之前,请确保您配置了共享存储,并检查它是否可以运行。

后续步骤

我将继续积累知识,并为未来几周制定计划。我将学习 Octopus Deploy 使用的术语,设置我的第一个部署,并熟悉最佳实践。

我还想知道如何照看我的 Octopus Deploy 实现,如何处理复杂的部署,以及 Octopus 如何与其他产品集成。

一定要回来看看我进展如何。我们会在该系列的其他文章中添加链接。

如果你有任何问题,请在下面留言告诉我。

愉快的部署!

从零到章鱼英雄-发现 DevOps -章鱼部署

原文:https://octopus.com/blog/zero-to-octopus-hero-part-2

嘿,伙计们,欢迎来到我的零到英雄博客系列的第二部分。

我于 2021 年 10 月加入 Octopus Deploy,我正在学习该产品。在第一部分中,我介绍了 Octopus Deploy 的功能,并建立了基础设施。

在第 2 部分中,我将介绍 Octopus Deploy 如何帮助您的组织实现 DevOps。

DevOps 简介

“DevOps 是人员、流程和产品的结合,能够为我们的最终用户持续提供价值。”多诺万·布朗。

我已经在 IT 行业工作了很多年,在部署服务器或帮助向客户发布新版本的软件时,我用自己的方式完成了许多清单。

虽然清单很有帮助,但过程中的人为因素可能会有缺陷。问题包括:

  • 假设你完成了一项任务
  • 跳过任务
  • 使用过期版本的清单

这就是 DevOps 的帮助之处。如果你把你的清单构建成一个自动化序列,自动化工具不会做任何假设,也不会因为累了或者赶时间而跳过一个任务。

DevOps 引入了我们在 IT 环境中需要的一致性。在许多情况下,它完成任务的速度也比人快。

当我们谈论 DevOps 时,您经常会听到持续集成和持续部署(CI/CD)这两个术语。

持续集成(CI)是将代码签入版本控制系统的过程。一个流行的工具是 Git,代码库选择包括 GitHub、GitLab、Azure DevOps 和 BitBucket。

CI 将您的代码放入构建您的代码或编译您的应用程序的管道中。然后它完成自动化测试。然后将您的代码或应用程序打包到一个可部署的工件中,比如一个 Zip 文件、NuGet 包或 Docker 映像。然后可以使用连续部署(CD)管道来部署您的工件。

持续部署(CD)关注于您的位、代码或应用程序的部署、实际安装和分发。CD 帮助您在不同的环境中分发您的代码或应用程序,从开发、测试到生产。

部署了 Octopus 的 DevOps

Octopus Deploy 与 Azure DevOps、TeamCity、Bamboo、Jenkins 和 GitHub Actions 等持续集成(CI)服务器(也称为构建服务器)协同工作。

在几周的时间里,我在 Azure DevOps 中使用 CI 管道构建了一个 ASP.NET 应用程序,然后将它传递给 Octopus 以部署到我的云平台上。

步骤模板

Octopus Deploy 自动化和加速部署的方式令人印象深刻。您使用步骤模板配置您的 CD 管道。步骤模板的范围从能够运行脚本到将证书导入 Windows 服务器。

这些步骤模板通过提供代码来加速您的过程。您不必知道将证书导入服务器等任务的脚本语法。相反,您选择步骤模板,然后回答一些基本问题,剩下的就交给 Octopus Deploy 了。

【T2 An example of an Octopus Deploy Continuous Deployment pipeline

我不是一名开发人员,所以这使得开始使用 Octopus Deploy 和建立一个工作 CD 管道变得快速而容易。

运行手册

在我的部署管道中使用 Octopus Deploy 的run book已经改变了游戏规则。

我建立了一个流程,将我所有的基础设施组件、Azure 中的资源组、web 应用和 SQL 数据库部署为我的 CD 管道的一部分。

我还使用了一个 runbook 进程来拆除我所有的 Azure 资源,以避免在我不使用资源时产生不必要的成本。

部署策略

部署策略(或模式)对于任何软件部署或更新管理都很重要。部署策略有助于减少停机时间,并为推出新功能创建无缝流程。

我了解到,在开发和 DevOps 领域,部署策略有着令人兴奋的名字:

  • 滚动部署
  • 蓝色/绿色部署
  • 金丝雀部署
  • 多区域部署

了解每种策略之间的区别以及何时最好地使用它们是很有趣的。这些概念对我来说并不陌生,只是当我们从 DevOps 的角度处理软件部署或系统升级时的术语和好处。

我们有一篇有用的博客文章介绍了这些部署模式以及每种模式如何与 Octopus Deploy 一起使用。

使用 Octopus 进行滚动部署

采用我之前构建的基本管道并使其适应滚动部署是一个简单的过程。在步骤模板中有一个配置滚动部署的选项,然后 Octopus Deploy 将进一步设置您想要的步骤作为子步骤,直到您的滚动部署的逻辑完成。

它使复杂的过程变得简单。

An example of an Octopus Deploy Rolling Deployment

经验教训

这是对 Octopus 团队的证明,而不是我的技能,我可以如此轻松地使用 Octopus Deploy 进行部署。

我了解到你的逻辑不能有缺陷,你的部署才能成功。花时间规划您的部署过程。思考需要发生的步骤以及顺序。

在 Octopus 中配置步骤时,考虑以下问题,为自己的成功做好准备:

  • 我需要什么变量?
  • 我什么时候需要进行人工检查,什么可以完全自动化?

我担心我缺乏开发经验会成为学习 DevOps 的障碍,但事实并非如此。有新的术语,但我已经熟悉了这些概念。

我的建议是:不要害怕测试你的知识和学习新的东西,你可能会让自己大吃一惊。

后续步骤

接下来,我想了解 Octopus Deploy 的更多特性,并了解它如何与其他工具集成。

我很享受我的学习之旅,也很享受概念开始产生的方式。我对扩展我的知识和测试它感到兴奋。

一定要回来看看我进展如何。我们会在该系列的其他文章中添加链接。

愉快的部署!

从零到章鱼英雄-章鱼功能和集成-章鱼部署

原文:https://octopus.com/blog/zero-to-octopus-hero-part-3

嘿,伙计们,很高兴回到我的零到英雄博客系列的第 3 部分。

我于 2021 年 10 月加入 Octopus Deploy,我正在学习,我想与你分享。在第一部分中,我介绍了 Octopus Deploy 的功能。在《T2》第二部中,我发现了章鱼在 DevOps 中扮演的角色。

在本文中,我将探讨 Octopus Deploy 如何与组织使用的其他工具集成。我还深入了解了 Octopus Deploy 的更多特性。

配置为代码

版本控制是 DevOps 方法的核心组成部分。版本控制你的 Octopus 配置现在可以用代码为的 Config 来完成。

使用 Config as Code,您可以查看部署过程的变更历史,包括谁在何时执行了变更,以及 Octopus 项目的版本控制(Git)文本表示。如果您也将应用程序存储在 GitHub 中,那么您就有了一个真实的来源,应用程序代码、构建脚本和部署配置都在一个地方。

为了探索这个特性,安装用于 Visual Studio 代码插件的 Octopus Deploy。该插件有助于语法高亮显示。Octopus 在 Git 存储库中创建的 OCL 文件。

观看由我们的产品总监 Michael Richardson 主持的深度网络研讨会,了解更多关于 Config as Code 的信息。

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

VIDEO

导出和导入项目

Octopus 中有一个方便的功能,可以将项目导出,然后导入到另一个空间,让您可以更好地控制如何组织 Octopus 实例。您可以将一个空间中的项目的设置和配置带到另一个空间。

我花时间建立了我的第一个空间和项目,把一切都做对了。然后,我使用导出/导入功能,将一些工作带到了另一个空间。

我可以导入:

这是 Octopus 中的一个有用特性,可以在实例之间移动项目,或者将项目拆分到多个空间以获得更好的可见性。

不能进出口就不值钱;

认证系统集成

大多数组织都建立了现有的身份认证系统。活动目录是一个广泛使用的例子。Octopus Deploy 可以与多种身份验证系统集成,包括:

  • 活动目录身份验证
  • Azure 活动目录身份验证
  • GoogleApps 认证
  • Okta 认证
  • 开源代码库
  • LDAP 认证

查看认证提供者兼容性文档了解更多信息。

我们建议您将身份验证设置为向所有用户提供一致的体验。如果您要将 Octopus Deploy 引入到您的环境中,您希望避免为您的用户提供另一个用户名和密码组合来进行管理。

个性化您的空间

我做的一件有趣而有价值的事情,也是我推荐你做的,就是用描述和标识定制你的空间。

配置下,然后是空间,然后在每个章鱼空间里,可以添加描述和 logo。添加这些内容有助于您快速识别共享空间。这也让和你一起工作的其他人很容易区分每个空间。

Octopus Deploy Spaces Personalized

结论

我真的开始觉得使用 Octopus Deploy 很舒服了。总是有更多的东西需要学习,但是拼图正在拼起来。

如果你还想知道什么,请在下面留下评论。

愉快的部署!

posted @ 2024-11-01 16:33  绝不原创的飞龙  阅读(7)  评论(0编辑  收藏  举报