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 目录树中的说明进行操作。

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

要为模板创建数据集,执行以下命令:

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

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

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

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

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

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

# 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

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

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

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

要从模板创建 OpenZFS 快照,执行以下命令:

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

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

要创建一个名为 thinjail 的瘦 jail,执行以下命令:

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

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

例如,配置可能如下所示:

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:

# service jail start thinjail

有关如何管理 jail 的更多信息,请参阅Jail 管理章节。

17.5.2. 使用 NullFS 创建瘦 jail

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

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

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

如果使用 UFS,请执行以下命令:

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

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

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

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

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

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

# 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

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

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

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

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

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

如果使用 UFS,请执行以下命令:

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

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

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

# 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 的符号链接:

# 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,执行以下命令:

# 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) 程序,执行以下命令:

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

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

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

/etc/jail.confjail.conf.d 文件中添加 jail 条目,如下所示:

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 文件,内容如下:

/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:

# service jail start thinjail

17.5.3. 创建 VNET Jail

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

创建 VNET jail 的第一步是创建 bridge(4),执行以下命令:

# ifconfig bridge create

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

bridge0

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

# ifconfig bridge0 addm em0 up
# ifconfig em0 up

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

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

有关桥接的更多信息,请参阅 Network Bridging

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

可以使用 经典 Jail(厚 Jail)瘦 Jail 的方法。唯一不同的是 /etc/jail.conf 文件中的配置。

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

以下是 VNET jail 的示例配置:

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 二进制兼容性debootstrap(8)。jail 无内核,它们运行在宿主机的内核上。因此,需要在宿主系统中启用 Linux 二进制兼容性。

要在启动时启用 Linux ABI,执行以下命令:

# sysrc linux_enable="YES"

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

# service linux start

接下来的步骤是按照上述方法创建 jail,例如在 使用 OpenZFS 快照创建瘦 Jail 中所示,但 不进行 配置。FreeBSD Linux jails 需要特定的配置,接下来将详细说明。

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

# 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

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

# jexec -u root ubuntu

在 jail 内,执行以下命令安装 sysutils/debootstrap 并准备 Ubuntu 环境:

# pkg install debootstrap
# debootstrap jammy /compat/ubuntu

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

# service jail onestop ubuntu

然后,在 /etc/jail.conf 中添加 Linux jail 的条目:

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:

# service jail start ubuntu

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

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

更多信息,请参阅 Linux 二进制兼容性章节

17.5.5. 配置服务 Jail

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

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

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

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

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

# sysrc servicename_svcj_options=net_basic

有关所有可能的 _svcj_options,请参阅 rc.conf(5) 手册页。

要为某个服务启用服务 jail,需要先停止该服务,然后将 servicename_svcj 变量设置为 YES。以将 syslogd(8) 放入服务 jail 为例,执行以下命令:

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

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

服务 jails 仅通过 rc.conf(5)/sysrc(8)service(8) 命令进行管理。可以使用 jail 工具(如 jls(8),在 Jail 管理 中有描述)来检查服务的运行情况,但不建议使用 jail(8) 命令来管理它们。

最后更新于