Overlord:让部署 Jail 像编程一样快

当我创建 AppJail 时——这是一款完全用 sh(1) 和 C 语言编写的、基于 BSD-3 许可证的开源框架,用于利用 FreeBSD jail 创建隔离、便携且易于部署的类似应用程序的环境——我的初衷是用它来测试 Ports,以避免破坏我的主环境。如今,AppJail 不仅仅是一个用于测试 Ports 的脚本,它变得高度灵活,具备许多非常实用的自动化功能。

当 AppJail 达到稳定阶段并被用于多种系统后,我意识到为每个想要的服务都部署一个 jail 并不可行,尤其是在需要部署越来越多服务的情况下。于是,Director 应运而生。

AppJail Director 是一款基于 AppJail 的多 jail 环境管理工具,使用简单的 YAML 配置文件定义如何配置组成应用程序的一个或多个 jail。有了 Director 文件,你只需一条命令 appjail-director up 即可创建并启动你的应用程序。

Director 是 AppJail "一切皆代码"理念的首次实践。它将 jail 组织成项目,让你以声明式方式创建含有一个或多个 jail 的项目;当你修改了该配置文件或相关文件(例如 Makejail 文件),Director 会检测到变更,并毫不犹豫地销毁并重新创建 jail。听起来有点激进,但用 The Ephemeral Concept(流变的概念)来解释最为恰当:

Director 将每个 jail 视为"流变"的。这并不意味着 jail 停止或系统重启后数据会丢失,而是意味着 Director 认为销毁 jail 是安全的,因为你已经明确区分了应持久保存的数据和被视为流变的数据。

更多细节见 appjail-ephemeral(7) 手册页,原则和上述相同。

值得注意的是,Director 本身不负责部署 jail,它依赖执行配置、安装包等操作的指令,因此大量利用了 AppJail 的 Makejails 功能——这是一种简易文本文件,自动化创建 jail 的步骤。Centralized Repository(集中仓库)中已有许多 Makejail 文件,但你也可以使用自己的仓库来托管。

AppJail 和 Director 极大简化了我的工作,但有一个问题二者都未能解决——即如何在多台服务器上协调管理 jail。对少量服务器,结合 SSH 使用 AppJail 和 Director 尚可,但随着服务器数量增多,这会变得非常痛苦。因此 Overlord 应运而生。

Overlord 是一个面向 GitOps 的快速分布式 FreeBSD jail 编排器。你只需定义一个文件说明集群上运行的服务,部署就能在几秒到几分钟内完成。

幸好 Overlord 诞生时,AppJail 和 Director 已经相当成熟,重用这两个经过充分测试的工具并与 Overlord 结合,是个明智的选择。继承 Director "一切皆代码"的哲学,使得 Overlord 易于使用。另一个设计决策是 Overlord 采用全异步架构。大型服务的部署可能耗时较长,但即使部署很快,声明式发送指令并让 Overlord 处理工作也是更优的体验。本文后续会详细介绍上述内容的诸多细节。

架构

Overlord 架构被描述为一种树形链式架构。每个运行 API 服务器的 Overlord 实例都可以配置为管理其他链(chains)的分组。每个成员链还可以进一步配置为管理更多的链,层层递进。虽然这种层级结构几乎可以无限扩展,但不加控制地扩展会引入延迟,因此了解你计划如何组织服务器是非常重要的。

选择这种架构的原因在于它非常简单且具备良好的可扩展性,通过将多个链连接起来形成集群,即可在多台服务器间共享资源,从而方便地进行项目部署。

该架构同时抽象了项目的部署方式。想要部署项目的用户无需了解每个链的具体终端节点,只需知道第一个链(也称根链,root chain)即可。这是因为每条链都会被标记一个任意的字符串标签,用户只需在部署文件中指定根链的终端地址、访问令牌和标签即可。虽然标签本质上是主观的,但它们可以表达需求。例如,我们可以用字符串 vm-only 标记那些具备部署虚拟机能力的服务器,用 db-only 标记数据库服务器,实际上标签是完全任意的。

Y__T6KJ`DC9R @T5A89{9ON

假设只有 charlie 和 delta 拥有 db-only 标签。要将项目部署到带有指定标签的 API 服务器,客户端必须向 main 发起 HTTP 请求,指定链为 alpha.charliealpha.charlie.delta。这个过程是透明进行的,无需用户干预。

FEG5 )NST){2T18L3(TJDNQ

如果某条链宕机,会发生什么情况?如果根链宕机,则无法进行任何操作,虽然可以指定多个根链(但本文档其余部分只使用一个)。然而,如果根链之后的某条链宕机,会出现一个有趣的处理机制。

当根链之后的某条链检测到错误时,它可以将该失败的链暂时加入黑名单。黑名单的作用是不显示失败的链,尽管并不禁止通过其他链尝试连接到被标记为失败的链,因为一旦成功连接到黑名单中的链,它会被自动重新启用。

每个被列入黑名单的链都会被分配一个保持黑名单状态的时间。过了一段时间后,该链会从黑名单中移除;但如果链依旧失败,它会被重新加入黑名单,且保持时间更长。这个时间延长机制有上限,避免链被永久禁用。

上述机制的好处是,HTTP 请求的完成时间可以缩短,因为不必去尝试连接那些已知失败的链。

项目部署

演示 Overlord 最好的方式是部署一个小项目。

需要注意的是,一个项目可以包含多个 jail,但在以下示例中只需一个 jail。

filebrowser.yml:

你可能已经注意到,我并未直接指定访问令牌和入口点,而是通过环境变量来传递,这些变量是通过 .env 文件加载的:

.env:

那么另一个问题是:访问令牌是如何生成的?这很简单,令牌是由运行你想要访问的 Overlord 实例的机器生成的,但只有拥有访问密钥权限的人才能生成令牌。这个密钥默认是伪随机生成的。

下一步就是简单地应用部署文件。

如果没有任何输出,说明一切正常,但这并不意味着项目已经部署完成。当应用一个部署文件且其中包含部署项目的规格(如上例中的 directorProject)时,项目会进入队列等待执行。由于当前没有其他项目正在运行,我们的项目会尽快被部署。

元数据

元数据用于创建小型文件(例如配置文件),这些文件可以在部署项目或虚拟机时使用。虽然像 GitLab、GitHub、Gitea 等 Git 托管服务与 Makejails 配合使用非常方便,但你也可以使用元数据来代替依赖 Git 托管,以进一步配置你正在部署的服务或虚拟机。

元数据的另一个优点是可以在不同部署之间共享。例如,通过部署共享相同 sshd_config(5)authorized_keys 文件的虚拟机,实现配置复用。

tor.yml:

metadata.yml:

.env:

从用户的角度来看,部署项目和部署元数据没有区别。然而,元数据不会进入队列,而是直接(异步地)写入磁盘。

部署 FreeBSD 虚拟机

Overlord 能够借助出色的 vm-bhyve 项目部署虚拟机。虚拟机可以隔离很多 jail 无法做到的部分,尽管这样会带来一定的开销,但根据你的使用场景,这种开销可能并不是问题。

这个部署过程如下:会创建一个 director 文件(由 Overlord 内部完成),该文件用于进一步创建一个 jail,代表一个必须安装了 vm-bhyve 的环境,且需要配置使用 FreeBSD 支持的防火墙,以及配置虚拟机使用的桥接网络。听起来很复杂,但有一个 Makejail 专门完成这些工作,可以查看该项目了解细节。上述 Makejail 会创建一个安装了 vm-bhyve-devel 的环境,配置 pf(4) 防火墙,并创建一个带有分配的 IPv4(192.168.8.1/24)的桥接,因此我们必须给虚拟机分配一个该网段内的 IPv4 地址。pf(4) 并未配置进一步隔离连接,因此虚拟机内的应用程序可以"逃逸"访问其他服务,这是否理想取决于应用的具体需求。

vm.yml:

metadata.yml:

.profile-vmtest.env:

与其每次部署虚拟机都复制粘贴部署文件,不如创建多个环境(或类似配置文件)来管理。

根据安装类型,安装过程可能需要一些时间。以上例中,我们选择从 FreeBSD 组件进行安装,因此如果服务器尚未拥有这些组件,或者已有组件但远程发生了变化(例如修改时间变动),Overlord 会自动下载最新组件。

由于我使用了 Tailscale,上述虚拟机即将被配置加入我的 tailnet,过一段时间后,它应该会出现在节点列表中:

服务发现

服务已经部署完成,然而根据你集群中服务器的数量,确定服务端点可能会变得复杂。你当然知道使用的端口和外部接口,IP 地址也相对容易获取,但更方便的做法是使用 DNS,这正是 DNS 的主要用途。服务虽然可以部署在不同服务器上,但它的访问端点始终保持一致。

SkyDNS 是一种较为老牌但功能强大的协议,结合 Etcd 使用时,可以实现便捷的服务发现。Overlord 可以配置使用 Etcd 和 SkyDNS。接下来,我们来部署 Etcd 集群。

etcd.yml:

收获:

部署完成后,就可以使用以下参数配置每个 Overlord 实例。

/usr/local/etc/overlord.yml:

请记得重启 Overlord 进程以使更改生效。

下一个需要部署的服务是 CoreDNS。多亏了它,我们可以通过 Etcd 插件 使用 SkyDNS。

coredns.yml:

metadata.yml:

正如你在 CoreDNS 配置文件中看到的,假设使用了 overlord.lan 域名区域,但默认情况下 Overlord 仅使用根域名 .,这对于当前场景没有意义,因此请根据这一点配置 Overlord 并部署 CoreDNS。

/usr/local/etc/overlord.yml:

注意:请记得重启 Overlord 进程,使配置更改生效。

我们的 Etcd 集群和 DNS 服务器均已启动运行。客户端应配置为通过这些 DNS 服务器解析主机名,因此请在它们的 resolv.conf(5) 或类似文件中进行配置。

示例 /etc/resolv.conf 配置如下:

我们的“弗兰肯斯坦”(译者注:弗兰肯斯坦是个制造怪物的科学家,参见小说《科学怪人》)活过来了!下一步是部署一个服务,并测试所有部分是否如预期般正常工作。

homebox.yml:

如此简单。Overlord 会拦截我们在 Director 文件中定义的标签,并基于这些标签创建 DNS 记录。

最后,我们的服务端点是 http://homebox.overlord.lan:8666/

负载均衡

关于 SkyDNS 的一个有趣特性是,多个域名可以被分组管理。因此,如果我们在多台服务器上部署同一个服务,且它们使用相同的分组,DNS 请求将返回三个 A 记录(对于 IPv4),也就是三个 IPv4 地址。

hello-http.yml:

这带来的一个额外好处是,服务会以轮询(round-robin)的方式进行负载均衡,尽管这完全取决于客户端,但大多数现代客户端都会这么做。

不过,我知道在大多数情况下,需要更复杂的配置。更糟糕的是,正如前面提到的,这还取决于客户端,因此可能符合,也可能不符合你的预期。

幸运的是,Overlord 提供了与 HAProxy 的集成,更准确地说,是与 Data Plane API 的集成,因此你的配置可以复杂到满足任何需求。

haproxy.yml:

metadata.yml:

我们即将在两台服务器上部署 HAProxy / Data Plane API。这样做的原因是为了避免出现 SPOF(单点故障)。至少在任意时刻,如果某个 HAProxy / Data Plane API 实例宕机,另一个实例仍能提供服务。

然而,Overlord 只能指向一个 Data Plane API 实例。因此,如果我们使用如下的两台服务器,就必须在每台机器上指定不同的 Data Plane API 实例。

如你在 Director 文件中所见,我们使用了 SkyDNS,这样客户端就可以使用 revproxy.overlord.lan 这个域名,而无需直接使用各自的 IP 地址。其优势在于,即使某个 HAProxy / Data Plane API 实例宕机,另一个仍可接手,客户端也能继续向其发送请求。

/usr/local/etc/overlord.yml (centralita) 总机

/usr/local/etc/overlord.yml (provider): 提供者

hello-http.yml:

收获!

横向自动扩展

即使在拥有数百台服务器的情况下,部署项目也变得非常容易。然而,这种方式的问题在于资源的浪费,因为客户端很可能只使用了集群资源的不到 5%;或者相反,你可能将项目部署在少数几台你认为"够用"的服务器上,直到某一时刻你发现资源根本不够用,更糟糕的是,这些服务器还可能因各种原因随时宕机。这正是 Overlord 的自动扩展机制能够解决的问题。

hello-http.yml:

正如你可能已经注意到的,我们指定了两种类型的标签。这体现了自动扩缩部署与非自动扩缩部署之间的细微差别。与非自动扩缩部署不同,deployIn.labels 中的标签用于匹配相应的服务器以进行自动扩缩和监控,换句话说,匹配这些标签(在本例中为 desktop)的服务器将负责部署、监控,以及在必要时重新部署。另一方面,匹配 autoScale.labels 中标签(在本例中为 servicesprovider)的服务器则用于以非自动扩缩部署的方式部署项目。我们已指定该项目至少包含三个副本。我们还可以指定其他内容,例如 rctl(8) 规则,但为了简洁起见,这样就足够了。

假设 provider 中的服务由于某种原因宕机。

在没有任何人工干预的情况下,让我们看看魔法是如何发生的。

服务已经恢复运行。

信息、指标及更多内容……

多亏了 AppJail,我们可以从 jail 中获取大量信息。Overlord 有一个特殊的部署方式叫做 readOnly,它可以与 get-info 命令完美结合。

info.yml:

后续工作

Overlord 能为你做的事情远不止本文件所展示的内容,更多示例请参见 Wiki

Overlord 是一个较新的项目,仍有很大的改进空间,未来也将加入更多功能以提升可用性。如果你愿意支持该项目,请考虑捐款


Jesús Daniel Colmenares Oviedo 是一位系统管理员、开发者及 Ports 提交者,同时也是 AppJail 及其相关项目(如 Director、Makejails、Reproduce、Overlord 等)的创建者。

最后更新于

这有帮助吗?