# CBSD：第一部分——生产环境

* 原文链接：[CBSD: Part 1-Production](https://freebsdfoundation.org/wp-content/uploads/2022/03/CBSD-Part-1-Production.pdf)
* 作者：**OLEG GINZBURG**

2012 年，我作为 Nevosoft 的 IT 系统管理员工作，这是一家小型游戏开发公司，所有的服务器基础设施都基于 FreeBSD 操作系统开发。当时，没人听说过 Kubernetes 和 Docker，但得益于 FreeBSD Jail，公司的服务器通过将所有组件分开，每个服务都使用独立的 Jail 容器，从而受益。如今，FreeBSD 拥有十几个（甚至更多）容器编排程序，尽管在 2012 年时，选择并不广泛。那时有 ezjail，但它是为独立服务器提供的解决方案。我们的安装环境有三十到四十台物理服务器，每台服务器上运行着十到二十个容器。除了基本的创建操作外，删除、启动和克隆容器，公司的系统管理员还需要更高级的功能，如远程服务器上的容器管理、容器从一台服务器迁移到另一台服务器的能力，以及将容器保存为可移植镜像的功能。这些结果是通过创建简单的 shell 脚本实现的。然而，2013 年，出于应用专有软件的需要，导致公司开始迁移到 Linux。因为到那时，所有创建的 shell 脚本在功能上已经与 ezjail 平起平坐，某些情况下还增加了独特的功能（例如，最初有 TUI—基于文本的用户界面），于是决定将这些脚本集合合并，并以相同的标题发布到 FreeBSD Ports Tree 中。这标志着 CBSD 项目的开始。

## 与其他管理系统的比较

如今，FreeBSD 至少支持三十种用于管理容器和虚拟机的工具。在 FreeBSD 平台上有 bhyve，从这里开始，列表逐渐变长。2022 年标志着 CBSD 项目的十周年——它持续更新并继续扩展。到目前为止，它是 FreeBSD 平台上最古老的虚拟环境管理系统之一。虚拟环境不仅意味着基于 Jail 的容器化，还支持基于 bhyve、XEN 和 QEMU / NVMM 超级虚拟机监控器的虚拟机。

该项目的开发基于以下概念和哲学：

* 既关注单一模式的安装，也关注由多个主机构成的环境；
* 有机会与其他解决方案集成；
* 对变化保持开放，并从大局出发思考：越大的构想，潜力越大；而较小的想法通常不容易扩展；
* 保持简洁和灵活的使用方式。

> “简单就是终极的复杂。”——列奥纳多·达芬奇

围绕 CBSD 已经出现了广泛的工具包：Web 界面、用于将任务传递给分布式 CBSD 节点的消息代理服务、API 服务以及用于与其交互的瘦客户端。

最后，CBSD 是一个原生的 FreeBSD 产品，而不是一个尝试移植 Linux 解决方案的项目。

了解更多关于项目目标的信息：<https://www.bsdstore.ru/en/cbsd_goals_ssi.html>

了解更多关于项目开发的信息：<https://www.bsdstore.ru/en/cbsd_history_ssi.html>

## 子项目

CBSD 不仅是面向最终用户的产品，也是参与构建复杂解决方案的元素之一，通过委派创建和管理 CBSD 虚拟环境的功能，能够节省大量工时。因此，存在多个独立的项目和发行版，用于展示和获取有关 CBSD 工作重用的见解：

* Reggae（由 Goran Mekic 开发）使用 CBSD 自动化 DevOps 任务；
* 发行包 <https://k8s-bhyve.convectix.com/>（k8s-bhyve）展示了能够快速（从几秒钟到 1 分钟）启动设置在 bhyve 超级虚拟机监控器上的 Kubernetes 集群的能力；
* 发行包 <https://clonos.convectix.com/> 具有为 CBSD 上的 bhyve 创建容器和虚拟机构建 Web/UI 界面的能力；
* 发行包 <https://myb.convectix.com/>（MyBee）使用户能够通过 CBSD 与云镜像进行交互，而无需使用 UI 和 CLI：可以通过向 CBSD API 发送 HTTP 请求（例如，使用 curl 工具）或使用 nubectl 瘦客户端（<https://github.com/bitcoin-software/nubectl>）来获取虚拟机。

## CBSD 和 Jail 容器：实际应用

让我们更好地了解可用的 CBSD jail 操作方法。网站上有关于初始 CBSD 安装和自定义过程的描述，假设你已经拥有运行时版本。设置容器的方式有多种：

**选项 1**：命令行对话格式。这种方法不需要学习各种可能的 jail 参数，因为脚本会自动回忆它们：

cbsd jconstruct

![](https://github.com/user-attachments/assets/b9e1bac1-1b99-4dd1-9a99-fbc4761f7a9b)

**选项 2**：通过 TUI 的对话格式。这种方式也适合初学者，因为与第一种选项一样，不需要了解可能的参数和命令：

cbsd jconstruct-tui

![](https://github.com/user-attachments/assets/3add189e-d5e8-4672-b4a9-e14cbece2fcf)

这两种对话框的输出是生成文本配置文件，其中包含 jcreate 脚本的参数集合，你可以直接从 TUI 界面或命令行调用该脚本。

选项 3：使用配置文件或命令行参数。

与之前的方法相比，这种方法较为复杂，因为它需要了解可配置参数的名称，但它适合于环境开发的自动化。你可以通过以下方式合成配置文件模板：

```sh
jname="myjail1"
ver=13.0
baserw=0
pkglist="misc/mc shells/bash"
astart=0
ip4_addr="DHCP"
...
```

启动：`cbsd jcreate jconf=<文件路径>`

此外，也可以直接通过命令行参数指定选项，而无需使用配置文件：

```sh
cbsd jcreate jname=myjail1 ver=13.0 baserw=0 pkglist="misc/mc
shells/bash" astart=0 ip4_addr="DHCP"
```

配置文件和命令行参数可以结合使用：

```sh
cbsd jcreate jconf=<文件路径>
```

**选项 4：类似** [**Vagrant**](https://www.vagrantup.com/) **的风格：CBSDfile**

这是一种描述 CBSD 虚拟环境的标记方式，允许同时描述容器设置并在创建时进行操作自定义（例如，将文件复制到容器文件系统并配置服务）。尽管在一个 CBSD 文件中可以描述无限数量的环境，并通过调用 `cbsd up` 和 `cbsd destroy` 来创建和删除它们，但这种标记方式对于某些目录结构来说更为用户友好，通常每个目录仅描述一个环境，例如：<https://github.com/cbsd/cbsdfile-recipes/tree/master/jail>

此外，通过 CBSD 文件格式创建的环境的独特能力与本地环境以及通过 [CBSD API](https://www.bsdstore.ru/en/cbsd_api_ssi.html) 的环境无关。

## Jail 模板

我们不会详细讨论典型的容器工作操作，因为这些内容已经在 [网站](https://www.bsdstore.ru/en/docs.html) 上描述。我们将介绍一些 CBSD 项目的创新，旨在简化在 jail 中引用，特别是与容器模板的工作。模板是容器描述和配置的方法。对于容器来说，它可以被导出为可移植的镜像。容器可以包含一个标准的运行时环境，但在大多数情况下，它们用于服务/应用程序的隔离和通过镜像进行分发。这种方式有其优缺点。容器方法的一些优点包括服务部署的速度、不会影响基本系统环境，以及能够提交依赖项的版本，使得应用程序完全正常运行。

谈到这种方法的缺点，最主要的问题是安全性，特别是当使用没有自包含构建脚本（模板）的容器或镜像时。这使得对容器进行操作性软件更新变得困难（例如，修复 [0-day](https://en.wikipedia.org/wiki/Zero-day_\(computing\)) 漏洞），并且容易出现后门，这些后门可能是图像收集者故意或无意留下的。考虑到安装某些软件的复杂性，通常会发现容器中配置的服务是手动放置的，或者丢失了组装说明。

另一个缺点是容器服务配置的复杂性。有些镜像可以通过环境变量配置有限数量的参数，尽管这并不总是足够的。一个例子是动态配置，当需要为 WEB 服务器添加和配置多个虚拟主机（vhost）并在数据库管理系统（DBMS）中设置多个数据库时。对于这样的任务，有一个专门的软件部分，称为配置管理。该类别中最知名的产品包括 [Ansible](https://www.ansible.com/)、[Chef](https://www.chef.io/)、[Puppet](https://puppet.com/)、[Rex](https://www.rexify.org/) 和 [SaltStack](https://saltproject.io/)。然而，它们通常是为了适应经典环境而优化的。CBSD 可以结合配置管理器的全部功能和基于容器的方法，从而获得带有管理服务的容器。CBSD 中使用的模板有两种类型：

* 静态（经典）模板，主要仅包含安装软件。例如，CBSD 的一个模板是 sambashare 容器。

类似的模板可以在其他项目中找到：例如，Fockerfile 示例中的 [nginx](https://github.com/sadaszewski/focker/tree/8be1fcaf601cca6afd703f96b1f90946a9831ff3/example/gateway/nginx-http) 模板（[focker](https://github.com/sadaszewski/focker) 项目）：

```sh
base: freebsd-latest
steps:
 - run:
 - ASSUME_ALWAYS_YES=yes IGNORE_OSVERSION=yes pkg install nginx
```

或者，这是 [BastilleBSD](https://bastillebsd.org/) 项目中 [RabbitMQ](https://github.com/BastilleBSD-Templates/rabbitmq/tree/e1a1275090c3d47b38402ab73941afb3c33cf3a6) 模板的示例，其中 CMD 文件的内容是：

```sh
service rabbitmq restart
```

**PKG** 文件内容：

```sh
rabbitmq
python27
```

这些模板的实用性有限，因为它们只是以另一种形式编写了两行 shell 命令：

```sh
pkg install -y rabbitmq python27
service rabbitmq restart
```

这些模板更为复杂，涵盖了服务配置文件的复制，并且在某些情况下，配置过程会伴随着通过复杂的 sed/awk 结构的处理，整个容器配置过程也会包括这些步骤。

作为一般原则：

a) 这是一项不可逆的操作，旨在一次性使用：无法回滚或更改配置，除非重新创建容器；

b) 这种模板对其维护要求很高：即使容器中进行了一次小的服务更新，源配置文件也可能会发生实质性变化；

c) 无法保证服务会正常运行，或者在配置错误的情况下无法回滚到以前的运行时版本；

d) 在 sed/awk 操作和切换到使用配置管理程序之间，难以找到合适的界限。

这是问题的解决方案，也是 CBSD 项目的主要信息：不要重新发明轮子，而是在可能的情况下重复使用他人的工作。许多开发者维护着配置管理模块，因此我们建议为了节省时间，使用他们的工作来委派配置管理，并使用原生的工具来处理配置。为了实现这一目标，CBSD 从 2016 年起引入了 forms 脚本。CBSD 的 [Forms](https://www.bsdstore.ru/en/13.0.x/wf_imghelper_ssi.html) 功能用于获取和保存用户参数，以适合配置模块的 [YAML](https://en.wikipedia.org/wiki/YAML) 格式——一种特殊的中间件。Puppet 被选为配置管理系统，但也可以使用其他任何框架。

## 为使用模板准备 CBSD

为了更好地理解，假设我们创建一个 jail1 容器，并将 [redis](https://redis.io/) 服务模板应用于它。

目前，预计 CBSD 已正确安装和配置：你可以启动一个容器，并且 `cbsd dhcpd` 命令会为客户端分配能够访问互联网的地址（例如通过 NAT）。这是 pkg 操作和包安装在容器中所必需的条件。

1. 必须安装 Git 来从公共仓库 <https://github.com/cbsd> 安装 CBSD 插件。如果尚未安装，安装 git（约 220 MB）或其轻量版 git-lite（约 40 MB）：

```sh
pkg install -y git-lite
```

2. 从 CBSD 基础分发版中包含的配置文件创建系统容器 cbsdpuppet1。这一步需要在每个新的 CBSD 主机上执行一次：

```sh
cbsd jcreate jname=cbsdpuppet1 jprofile=cbsdpuppet
```

cbsd puppet1 容器不应运行，因为它只执行一个功能——它包含了 CBSD 用于配置容器的 [puppet7](https://www.freshports.org/sysutils/puppet7/) 安装包。

3. 安装 CBSD puppet 模块——这是可选功能，不包含在基本分发版中。该模块包含了由 CBSD 项目检查的 puppet 模块。这一步需要在每个新的 CBSD 主机上执行一次：

```sh
cbsd module mode=install puppet
```

4. 安装 redis 服务配置模块。这一步需要在每个新的 CBSD 主机上执行一次：

```sh
cbsd module mode=install forms-redis
```

5. 创建一个容器，名称任意，在其中我们将获取 redis 服务，例如：jail1：

```sh
cbsd jcreate jname=jail1 runasap=1
```

（注意：参数 `runasap=1` 意味着容器将立即启动）

6. 最后一步：将 redis 服务模板应用到我们的 jail1 容器中：

```sh
cbsd forms module=redis jname=jail1
```

将出现熟悉的 TUI 界面，显示 Redis 模块的参数，你可以根据需要进行配置：

![](https://github.com/user-attachments/assets/6c449efc-a184-40fc-867e-de07e1498e82)

让我们保留默认参数，并通过 \[COMMIT] 操作选择它们。脚本运行完成可能需要一段时间（取决于互联网连接速度，因为模块从官方仓库 pkg.FreeBSD.org 安装 redis 服务器），因此你需要再次确认容器中的服务已安装并正在运行：

```sh
~ # cbsd jexec jname=jail1 sockstat -4l
USER COMMAND PID FD PROTO LOCAL ADDRESS FOREIGN ADDRESS
redis redis-serv 8910 7 tcp4 172.16.0.13:6379 *:*
```

通过重新使用 cbsd forms 调用，你可以随时重新配置服务。此外，forms 会保留以前的值，并且在初始化时始终输出当前数据。例如，让我们更改一组参数：

* 将端口设置为 7777；
* 设置连接到 redis 服务器的密码；
* 将 maxmemory 参数设置为 4g；
* 将 maxmemory\_policy 参数设置为 noeviction。

应用新参数后，检查状态：

```sh
~ # cbsd jexec jname=jail1 sockstat -4l
USER COMMAND PID FD PROTO LOCAL ADDRESS FOREIGN ADDRESS
redis redis-serv 12587 7 tcp4 172.16.0.13:7777 *:*
~ # cbsd jexec jname=jail1 grep ^maxmemory /usr/local/etc/redis.conf
maxmemory 4g
maxmemory-policy noeviction
```

大多数 Puppet 模块会承诺进行不正确的数据验证、配置文件验证和服务状态检查。因此，由于无效参数输入导致无法服务的服务的可能性，比静态模板要小得多。

现在我们已经了解了 TUI 界面，让我们转向自动化。cbsd forms 允许通过环境变量接受模板的参数，从而省略交互式对话。为了查看某个特定模板接受哪些变量，可以使用 `vars` 参数并指明所需模块：

```sh
~ # cbsd forms module=redis vars
H_BIND H_PORT H_REQUIREPASS H_MAXMEMORY H_MAXMEMORY_POLICY
H_TCP_KEEPALIVE H_LOG_LEVEL H_SYSLOG_ENABLED H_TIMEOUT H_SLAVE_PRIORITY
H_SLAVEOF
```

Forms 会在常规参数的名称前添加 `H_` 前缀，以减少与系统全局变量发生潜在冲突的可能性。让我们第三次重新配置 redis 端口（将其设置为 9999），但不使用交互模式（`inter=0`）：

```sh
env H_PORT=9999 cbsd forms module=redis jname=jail1 inter=0
```

请注意应用模板时输出的值：

![](https://github.com/user-attachments/assets/b7cf7bb8-6ae1-4d78-a816-354ddecc5e24)

这是模板工作结果导出的 CBSD 变量的输出信息。参数化格式可以通过配置文件 `forms_export_vars.conf` 在目录 `~cbsd/etc` 中设置。

查看其默认值：

<https://github.com/cbsd/cbsd/blob/v13.0.18/etc/defaults/forms_export_vars.conf>

这些值可以自动导出到文件或各种服务发现服务，如 [Consul](https://www.consul.io/)。在这种情况下，你的集群会自动获取这些值，可以用于构建 SOA（面向服务的架构），但这是另一个话题。

除了 redis，[这里](https://github.com/cbsd) 还有其他服务配置模板：命名为 `modules-forms-XXXX` 的仓库。例如，尝试使用以下模板：

• modules-forms-memcached

• modules-forms-mysql

• modules-forms-grafana

• modules-forms-rabbitmq

• modules-forms-postgresql

• modules-forms-elasticsearch

注意：尽管传统上 1 个服务对应 1 个容器，但对于 CBSD forms，你可以将多个模板应用于同一个环境，从而一次性获得所有服务。

## 它是如何工作的

Puppet 模块（或任何其他类似系统）包含了在 99% 的情况下采用值参数形式的参数，这些参数通常采用 YAML 格式进行参数化。为了方便用户，CBSD 提供了一个 TUI 界面来处理基本表单，在这个界面中，与 HTML 表单类似，以下元素可以存在：

• 单选按钮（布尔类型的值为 true/false、yes/no）；

• 复选框元素；

• 已知特定值的下拉菜单；

• 自定义输入框用于相对用户输入；

为了将参数保持在通用形式中，CBSD forms 使用 [SQLite3](https://www.sqlite.org/index.html) 数据库——一种描述参数输入的表单，存储输入的值。记录（写入）格式是通用的，既适用于 TUI 对话框的自动生成（CBSD forms），也适用于 WEB/HTML 表单（[ClonOS](https://clonos.convectix.com/)），如下例所示。让我们使用 CBSD forms 所需的格式创建一个 SQLite3 数据库：

![](https://github.com/user-attachments/assets/cbb13782-3e4a-49e7-b83a-f761e1a77700)

让我们详细谈谈这些行中的一些内容。对我们来说，最重要的表格列是：

* `param` – 直接的参数名称，表示我们希望获取的值；
* `desc` – 相关的描述（如果适用）；
* `def` – 默认值；
* `new` – 用户输入的新值；
* `type` – 字段类型：inputbox、delimiter、password、group\_add、group\_del、radio、checkbox。

接下来的两行可以添加两个参数，我们希望获取的值是：FreeBSD 和 DragonFlyBSD，每个都有自己的默认值，并且都使用 inputbox 字段格式，因此用户可以自由地输入任意字符串值。

在这个表单上运行 cbsd forms 脚本：

```sh
cbsd forms formfile=/tmp/myforms.sqlite
```

输出将是一个动态构建的表单，用于处理参数：

![](https://github.com/user-attachments/assets/446765ee-e5bd-41ef-88d0-319feb936317)

redis 服务的表单结构更为复杂，因为它包含布尔类型和下拉菜单类型的字段元素。让我们来看一下表格：

![](https://github.com/user-attachments/assets/2f2b6823-665a-4853-8d8d-61b8a08d794c)

我们以表格 [memory\_policy\_select](http://clonos.bsdstore.ru:81/phpliteadmin.php?database=%2Fusr%2Fjails%2Fvar%2Fdb%2Fredis.sqlite\&table=memory_policy_select\&fulltexts=0\&numRows=30\&action=row_view) 为例，这是 memory\_policy 参数的选择类型变体表：

![](https://github.com/user-attachments/assets/670a38c9-b562-4856-9387-b5e7d1931349)

在 WEB 界面中，自动生成的表单（截图来自 ClonOS）如下所示：

![](https://github.com/user-attachments/assets/ed585bb2-caf3-4324-bfb6-906b75830eda)

其中，maxmemory 策略参数位于特定的下拉菜单元素中。

![](https://github.com/user-attachments/assets/26c8f6f0-3743-4ebc-bd4f-c465ddaa6921)

TUI 界面提供了类似的选择：

![](https://github.com/user-attachments/assets/1c718aa8-d542-4031-9aac-652cd4ad54c1)

> **注意**
>
> 所有 CBSD TUI 对话框都可以通过类似的基于 SQL 的表单来描述，这些表单目前仅用于与模板的工作。

在获取并处理参数后，cbsd forms 之后，cbsd puppet 模块介入了操作。接下来发生的事情如下：

* 使用 nullfs 挂载 overlay 文件系统，将 cbsdpuppet1 系统容器中的 puppet7 包文件挂载到目标容器（jail1）的 `/tmp/XXX` 目录；
* 将包含模块参数的 YAML 文件呈现到目标容器（jail1）；
* 获取 puppet apply 指令，应用于可配置容器（jail1），以便从 `/tmp/XXX` 目录应用 puppet 清单。

例如，以下是应用参数时 nullfs 挂载点的样子：

![](https://github.com/user-attachments/assets/276a0979-fc93-4e61-81fb-f2c876349569)

服务完成并重新配置后，临时的 nullfs 文件系统将被卸载，因为它们已经不再需要。因此，如果我们查看上述示例中的 jail1 容器中已安装的软件，将不会看到 puppet7 包或它依赖的文件。

```sh
~ # cbsd jexec jname=jail1 pkg info
pkg-1.17.4 Package manager
redis-6.2.6 Persistent key-value database
```

换句话说，通过使用 cbsdpuppet1 容器，你无需在每个容器中安装 puppet7 和必要的依赖项——配置应用不依赖于有限容器中是否存在任何系统软件。

## 尾声

cbsd forms 是 CBSD 框架在处理基于 jail 的容器时最强大的功能之一，它在 CBSD 和配置管理器之间架起了桥梁。因此，CBSD 项目通过适当的 Puppet 模块为 FreeBSD 提供支持，而不是依赖 sed/awk 脚本支持和静态模板。如果某个模块能够在 FreeBSD 环境中运行，你可以通过 cbsd forms 透明地使用它。这对特定的 CBSD 用户和所有 FreeBSD 上的 Puppet 用户都有好处。如果你使用其他配置系统，可以通过使用 cbsd forms 脚本调用其他系统。

CBSD 项目支持基于现有模板的即用型镜像的振荡和分发，你可以通过 CBSD 仓库使用 cbsd repo 和 cbsd images。请参阅内联文档和示例：

```sh
~ # cbsd repo --help
~ # cbsd images --help
```

在系列文章的下一篇中，我们将探讨 CBSD 的虚拟机管理功能。

***

**OLEG GINZBURG** 生活在俄罗斯，担任 [X5 Retail group](https://x5.ru/en/) 的 DevOps 工程师。他自 ZX Spectrum（**译者注：英国公司 Sinclair Research 于 1982 年发布的个人电脑。**）以来便热爱计算机科学，是 Unix 爱好者和 FreeBSD 爱好者。他是 FreeBSD 的推广者，并参与多个项目：CBSD、[ClonOS](https://clonos.convectix.com/)、[MyB](https://myb.convectix.com/)、[K8Sbhyve](https://k8s-bhyve.convectix.com/)、以及 [AdvanceBSD group](https://www.reddit.com/r/AdvanceBSD/)。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://book.bsdcn.org/qi-kan/20220102-ruan-jian-yu-xi-tong-guan-li/cbsd.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
