# 17.5.瘦 jail

虽然瘦 jail 使用与厚 jail 相同的技术，但创建过程是不同的。瘦 jail 可以通过 OpenZFS 快照或使用模板和 NullFS 创建。使用 OpenZFS 快照和模板结合 NullFS 相比经典的 jail 有一些优势，例如可以更快速地从快照中创建，或者能够通过 NullFS 更新多个 jail。

## 17.5.1. 使用 OpenZFS 快照创建瘦 jail

由于 FreeBSD 和 OpenZFS 的良好集成，通过 OpenZFS 快照创建新的瘦 jail 非常容易。

要通过 OpenZFS 快照创建瘦 jail，第一步是创建 jail 目录树，按照[设置 Jail 目录树](https://docs.freebsd.org/en/books/handbook/jails/#host-configuration-directories)中的说明进行操作。

接下来，创建一个模板。模板仅用于创建新的 jail。因此，它们以“只读”模式创建，以便从不变的基础创建 jail。

要为模板创建数据集，执行以下命令：

```sh
# zfs create -p zroot/jails/templates/14.2-RELEASE
```

然后执行以下命令下载用户空间：

```sh
# fetch https://download.freebsd.org/ftp/releases/amd64/amd64/14.2-RELEASE/base.txz -o /usr/local/jails/media/14.2-RELEASE-base.txz
```

下载完成后，需要通过以下命令将内容解压到模板目录中：

```sh
# tar -xf /usr/local/jails/media/14.2-RELEASE-base.txz -C /usr/local/jails/templates/14.2-RELEASE --unlink
```

在模板目录中提取用户空间后，需要通过以下命令将时区和 DNS 服务器文件复制到模板目录中：

```sh
# cp /etc/resolv.conf /usr/local/jails/templates/14.2-RELEASE/etc/resolv.conf
# cp /etc/localtime /usr/local/jails/templates/14.2-RELEASE/etc/localtime
```

接下来的步骤是通过执行以下命令更新到最新的补丁级别：

```sh
# freebsd-update -b /usr/local/jails/templates/14.2-RELEASE/ fetch install
```

更新完成后，模板就准备好了。

要从模板创建 OpenZFS 快照，执行以下命令：

```sh
# zfs snapshot zroot/jails/templates/14.2-RELEASE@base
```

待创建了 OpenZFS 快照，就可以使用 OpenZFS 克隆功能创建无限多个 jail。

要创建一个名为 `thinjail` 的瘦 jail，执行以下命令：

```sh
# zfs clone zroot/jails/templates/14.2-RELEASE@base zroot/jails/containers/thinjail
```

最后一步是配置 jail。需要在 **/etc/jail.conf** 或 **jail.conf.d** 配置文件中添加该 jail 的条目。

例如，配置可能如下所示：

```ini
thinjail {
  # 启动/日志记录
  exec.start = "/bin/sh /etc/rc";
  exec.stop = "/bin/sh /etc/rc.shutdown";
  exec.consolelog = "/var/log/jail_console_${name}.log";

  # 权限
  allow.raw_sockets;
  exec.clean;
  mount.devfs;

  # 主机名/路径
  host.hostname = "${name}";
  path = "/usr/local/jails/containers/${name}";

  # 网络
  ip4 = inherit;
  interface = em0;
}
```

执行以下命令启动 jail：

```sh
# service jail start thinjail
```

有关如何管理 jail 的更多信息，请参阅[Jail 管理](https://docs.freebsd.org/en/books/handbook/jails/#jail-management)章节。

## 17.5.2. 使用 NullFS 创建瘦 jail

通过使用瘦 jail 技术并利用 NullFS 有选择性地共享主机系统中的特定目录，可以减少系统文件的重复，从而创建一个瘦 jail。

第一步是创建用于保存模板的数据集。如果使用 OpenZFS，请执行以下命令：

```sh
# zfs create -p zroot/jails/templates/14.2-RELEASE-base
```

如果使用 UFS，请执行以下命令：

```sh
# mkdir /usr/local/jails/templates/14.2-RELEASE-base
```

然后执行以下命令下载用户空间：

```sh
# fetch https://download.freebsd.org/ftp/releases/amd64/amd64/14.2-RELEASE/base.txz -o /usr/local/jails/media/14.2-RELEASE-base.txz
```

下载完成后，需要通过以下命令将内容解压到模板目录中：

```sh
# tar -xf /usr/local/jails/media/14.2-RELEASE-base.txz -C /usr/local/jails/templates/14.2-RELEASE-base --unlink
```

待用户空间提取到模板目录中，就需要通过以下命令将时区和 DNS 服务器文件复制到模板目录中：

```sh
# cp /etc/resolv.conf /usr/local/jails/templates/14.2-RELEASE-base/etc/resolv.conf
# cp /etc/localtime /usr/local/jails/templates/14.2-RELEASE-base/etc/localtime
```

将文件移动到模板中后，接下来的步骤是通过以下命令更新到最新的补丁级别：

```sh
# freebsd-update -b /usr/local/jails/templates/14.2-RELEASE-base/ fetch install
```

除了基础模板外，还需要创建一个目录来存放 `skeleton`。一些目录将从模板复制到 `skeleton` 中。

如果使用 OpenZFS，请执行以下命令来创建 `skeleton` 的数据集：

```sh
# zfs create -p zroot/jails/templates/14.2-RELEASE-skeleton
```

如果使用 UFS，请执行以下命令：

```sh
# mkdir /usr/local/jails/templates/14.2-RELEASE-skeleton
```

然后创建 `skeleton` 目录。`skeleton` 目录将存放 jail 的本地目录。

执行以下命令创建这些目录：

```sh
# mkdir -p /usr/local/jails/templates/14.2-RELEASE-skeleton/home
# mkdir -p /usr/local/jails/templates/14.2-RELEASE-skeleton/usr
# mv /usr/local/jails/templates/14.2-RELEASE-base/etc /usr/local/jails/templates/14.2-RELEASE-skeleton/etc
# mv /usr/local/jails/templates/14.2-RELEASE-base/usr/local /usr/local/jails/templates/14.2-RELEASE-skeleton/usr/local
# mv /usr/local/jails/templates/14.2-RELEASE-base/tmp /usr/local/jails/templates/14.2-RELEASE-skeleton/tmp
# mv /usr/local/jails/templates/14.2-RELEASE-base/var /usr/local/jails/templates/14.2-RELEASE-skeleton/var
# mv /usr/local/jails/templates/14.2-RELEASE-base/root /usr/local/jails/templates/14.2-RELEASE-skeleton/root
```

接下来的步骤是通过执行以下命令创建指向 `skeleton` 的符号链接：

```sh
# cd /usr/local/jails/templates/14.2-RELEASE-base/
# mkdir skeleton
# ln -s skeleton/etc etc
# ln -s skeleton/home home
# ln -s skeleton/root root
# ln -s ../skeleton/usr/local usr/local
# ln -s skeleton/tmp tmp
# ln -s skeleton/var var
```

`skeleton` 准备好后，需要将数据复制到 jail 目录中。

如果使用 OpenZFS，可以使用 OpenZFS 快照轻松创建所需数量的 jail，执行以下命令：

```sh
# zfs snapshot zroot/jails/templates/14.2-RELEASE-skeleton@base
# zfs clone zroot/jails/templates/14.2-RELEASE-skeleton@base zroot/jails/containers/thinjail
```

如果使用 UFS，可以使用 [cp(1)](https://man.freebsd.org/cgi/man.cgi?query=cp\&sektion=1\&format=html) 程序，执行以下命令：

```sh
# cp -R /usr/local/jails/templates/14.2-RELEASE-skeleton /usr/local/jails/containers/thinjail
```

然后创建一个目录，用于挂载基础模板和 skeleton：

```sh
# mkdir -p /usr/local/jails/thinjail-nullfs-base
```

在 **/etc/jail.conf** 或 **jail.conf.d** 文件中添加 jail 条目，如下所示：

```ini
thinjail {
  # 启动/日志记录
  exec.start = "/bin/sh /etc/rc";
  exec.stop = "/bin/sh /etc/rc.shutdown";
  exec.consolelog = "/var/log/jail_console_${name}.log";

  # 权限
  allow.raw_sockets;
  exec.clean;
  mount.devfs;

  # 主机名/路径
  host.hostname = "${name}";
  path = "/usr/local/jails/${name}-nullfs-base";

  # 网络
  ip4.addr = 192.168.1.153;
  interface = em0;

  # 挂载
  mount.fstab = "/usr/local/jails/${name}-nullfs-base.fstab";
}
```

然后创建 **/usr/local/jails/thinjail-nullfs-base.fstab** 文件，内容如下：

```sh
/usr/local/jails/templates/14.2-RELEASE-base  /usr/local/jails/thinjail-nullfs-base/ nullfs   ro          0 0
/usr/local/jails/containers/thinjail     /usr/local/jails/thinjail-nullfs-base/skeleton nullfs  rw  0 0
```

执行以下命令启动 jail：

```sh
# service jail start thinjail
```

## 17.5.3. 创建 VNET Jail

FreeBSD VNET Jail 拥有独立的网络栈，包括接口、IP 地址、路由表和防火墙规则。

创建 VNET jail 的第一步是创建 [bridge(4)](https://man.freebsd.org/cgi/man.cgi?query=bridge\&sektion=4\&format=html)，执行以下命令：

```sh
# ifconfig bridge create
```

输出应该类似于以下内容：

```sh
bridge0
```

创建好 `bridge` 后，接下来需要将其附加到 `em0` 接口，并启动它们，执行以下命令：

```sh
# ifconfig bridge0 addm em0 up
# ifconfig em0 up
```

为了使此设置在重启后生效，将以下行添加到 **/etc/rc.conf** 中：

```sh
defaultrouter="192.168.1.1"
cloned_interfaces="bridge0"
ifconfig_bridge0="inet 192.168.1.150/24 addm em0 up"
ifconfig_em0="up"
```

有关桥接的更多信息，请参阅 [Network Bridging](https://docs.freebsd.org/en/books/handbook/advanced-networking/#network-bridging)。

接下来的步骤是根据上述内容创建 jail。

可以使用 [经典 Jail（厚 Jail）](https://docs.freebsd.org/en/books/handbook/jails/#classic-jail) 或 [瘦 Jail](https://docs.freebsd.org/en/books/handbook/jails/#thin-jail) 的方法。唯一不同的是 **/etc/jail.conf** 文件中的配置。

在这个示例中，将使用路径 **/usr/local/jails/containers/vnet** 来表示创建的 jail。

以下是 VNET jail 的示例配置：

```ini
vnet {
  # 启动/日志记录
  exec.consolelog = "/var/log/jail_console_${name}.log";

  # 权限
  allow.raw_sockets;
  exec.clean;
  mount.devfs;
  devfs_ruleset = 5;

  # 路径/主机名
  path = "/usr/local/jails/containers/${name}";
  host.hostname = "${name}";

  # VNET/VIMAGE
  vnet;
  vnet.interface = "${epair}b";

  # 网络/接口
  $id = "154"; ①
  $ip = "192.168.1.${id}/24";
  $gateway = "192.168.1.1";
  $bridge = "bridge0"; ②
  $epair = "epair${id}";

  # 添加到 bridge 接口
  exec.prestart  = "/sbin/ifconfig ${epair} create up";
  exec.prestart += "/sbin/ifconfig ${epair}a up descr jail:${name}";
  exec.prestart += "/sbin/ifconfig ${bridge} addm ${epair}a up";
  exec.start    += "/sbin/ifconfig ${epair}b ${ip} up";
  exec.start    += "/sbin/route add default ${gateway}";
  exec.start	+= "/bin/sh /etc/rc";
  exec.stop	= "/bin/sh /etc/rc.shutdown";
  exec.poststop = "/sbin/ifconfig ${bridge} deletem ${epair}a";
  exec.poststop += "/sbin/ifconfig ${epair}a destroy";
}
```

* ① 表示 Jail 的 IP，必须是​**唯一**​的。
* ② 指代先前创建的 bridge。

## 17.5.4. 创建 Linux Jail

FreeBSD 可以在 jail 内运行 Linux，利用 [Linux 二进制兼容性](https://docs.freebsd.org/en/books/handbook/linuxemu/#linuxemu) 和 [debootstrap(8)](https://man.freebsd.org/cgi/man.cgi?query=debootstrap\&sektion=8\&format=html)。jail 无内核，它们运行在宿主机的内核上。因此，需要在宿主系统中启用 Linux 二进制兼容性。

要在启动时启用 Linux ABI，执行以下命令：

```sh
# sysrc linux_enable="YES"
```

启用后，可以通过执行以下命令在不重启的情况下启动它：

```sh
# service linux start
```

接下来的步骤是按照上述方法创建 jail，例如在 [使用 OpenZFS 快照创建瘦 Jail](https://docs.freebsd.org/en/books/handbook/jails/#creating-thin-jail-openzfs-snapshots) 中所示，但 **不进行** 配置。FreeBSD Linux jails 需要特定的配置，接下来将详细说明。

待按照上述说明创建了 jail，执行以下命令进行所需配置并启动它：

```sh
# jail -cm \
    name=ubuntu \
    host.hostname="ubuntu.example.com" \
    path="/usr/local/jails/ubuntu" \
    interface="em0" \
    ip4.addr="192.168.1.150" \
    exec.start="/bin/sh /etc/rc" \
    exec.stop="/bin/sh /etc/rc.shutdown" \
    mount.devfs \
    devfs_ruleset=4 \
    allow.mount \
    allow.mount.devfs \
    allow.mount.fdescfs \
    allow.mount.procfs \
    allow.mount.linprocfs \
    allow.mount.linsysfs \
    allow.mount.tmpfs \
    enforce_statfs=1
```

要访问 jail，首先需要安装 [sysutils/debootstrap](https://cgit.freebsd.org/ports/tree/sysutils/debootstrap/)。

执行以下命令访问 FreeBSD Linux jail：

```sh
# jexec -u root ubuntu
```

在 jail 内，执行以下命令安装 [sysutils/debootstrap](https://cgit.freebsd.org/ports/tree/sysutils/debootstrap/) 并准备 Ubuntu 环境：

```sh
# pkg install debootstrap
# debootstrap jammy /compat/ubuntu
```

当进程完成并在控制台上显示 `Base system installed successfully` 信息时，需要从宿主系统停止 jail，执行以下命令：

```sh
# service jail onestop ubuntu
```

然后，在 **/etc/jail.conf** 中添加 Linux jail 的条目：

```ini
ubuntu {
  # 启动/日志记录
  exec.start = "/bin/sh /etc/rc";
  exec.stop = "/bin/sh /etc/rc.shutdown";
  exec.consolelog = "/var/log/jail_console_${name}.log";

  # 权限
  allow.raw_sockets;
  exec.clean;
  mount.devfs;
  devfs_ruleset = 4;

  # 主机名/路径
  host.hostname = "${name}";
  path = "/usr/local/jails/containers/${name}";

  # 网络
  ip4.addr = 192.168.1.155;
  interface = em0;

  # 挂载
  mount += "devfs     $path/compat/ubuntu/dev     devfs     rw  0 0";
  mount += "tmpfs     $path/compat/ubuntu/dev/shm tmpfs     rw,size=1g,mode=1777  0 0";
  mount += "fdescfs   $path/compat/ubuntu/dev/fd  fdescfs   rw,linrdlnk 0 0";
  mount += "linprocfs $path/compat/ubuntu/proc    linprocfs rw  0 0";
  mount += "linsysfs  $path/compat/ubuntu/sys     linsysfs  rw  0 0";
  mount += "/tmp      $path/compat/ubuntu/tmp     nullfs    rw  0 0";
  mount += "/home     $path/compat/ubuntu/home    nullfs    rw  0 0";
}
```

然后，使用以下命令像往常一样启动 jail：

```sh
# service jail start ubuntu
```

可以使用以下命令访问 Ubuntu 环境：

```sh
# jexec ubuntu chroot /compat/ubuntu /bin/bash
```

更多信息，请参阅 [Linux 二进制兼容性章节](https://docs.freebsd.org/en/books/handbook/linuxemu/#linuxemu)。

## 17.5.5. 配置服务 Jail

服务 jail 完全通过 **/etc/rc.conf** 或 [sysrc(8)](https://man.freebsd.org/cgi/man.cgi?query=sysrc\&sektion=8\&format=html) 配置。基本系统服务已经准备好作为服务 jail。它们包含一行配置，启用网络连接或解除其他 jail 限制。不适合在 jail 中运行的基本系统服务，即使在 **/etc/rc.conf** 中启用，也被配置为不作为服务 jail 启动。例如，某些服务会在启动或停止方法中挂载或卸载某些内容，或者仅配置诸如路由、防火墙等内容。

第三方服务可能已经准备好作为服务 jail，或者可能没有。要检查某个服务是否已经准备好作为服务 jail，可以使用以下命令：

```sh
# grep _svcj_options /path/to/rc.d/servicename
```

如果没有输出，表示该服务没有准备好作为服务 jail，或者不需要任何额外的权限（例如，网络访问）。

如果服务没有准备好作为服务 jail，并且需要网络访问，可以通过向 **/etc/rc.conf** 添加必要的配置来使其准备好：

```sh
# sysrc servicename_svcj_options=net_basic
```

有关所有可能的 `_svcj_options`，请参阅 [rc.conf(5)](https://man.freebsd.org/cgi/man.cgi?query=rc.conf\&sektion=5\&format=html) 手册页。

要为某个服务启用服务 jail，需要先停止该服务，然后将 `servicename_svcj` 变量设置为 YES。以将 [syslogd(8)](https://man.freebsd.org/cgi/man.cgi?query=syslogd\&sektion=8\&format=html) 放入服务 jail 为例，执行以下命令：

```sh
# service syslogd stop
# sysrc syslogd_svcj=YES
# service syslogd start
```

如果更改了 `servicename_svcj` 变量，必须在更改之前停止该服务。如果服务没有停止，rc 框架将无法检测到服务的正确状态，并且无法执行所请求的操作。

服务 jails 仅通过 [rc.conf(5)](https://man.freebsd.org/cgi/man.cgi?query=rc.conf\&sektion=5\&format=html)/[sysrc(8)](https://man.freebsd.org/cgi/man.cgi?query=sysrc\&sektion=8\&format=html) 和 [service(8)](https://man.freebsd.org/cgi/man.cgi?query=service\&sektion=8\&format=html) 命令进行管理。可以使用 jail 工具（如 [jls(8)](https://man.freebsd.org/cgi/man.cgi?query=jls\&sektion=8\&format=html)，在 [Jail 管理](https://docs.freebsd.org/en/books/handbook/jails/#jail-management) 中有描述）来检查服务的运行情况，但不建议使用 [jail(8)](https://man.freebsd.org/cgi/man.cgi?query=jail\&sektion=8\&format=html) 命令来管理它们。
