SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor

FreeBSD jail 与 Docker 容器有什么区别?

FreeBSD jail 运行完整用户空间,Docker 使用 registry 中的分层镜像。本文比较两者在软件、状态管理、网络隔离和资源限制上的实际差异,并说明何时选择其中一种。

FreeBSD jail 与 Docker 容器:一段话说明

FreeBSD jail 和 Docker 容器以两种不同形式解决同一个问题。二者都在同一个共享内核上运行相互隔离的用户空间,因此都不是虚拟机。区别在于其中包含的内容。Docker 容器从 registry 中拉取的分层镜像运行一个进程。jail 运行完整的 FreeBSD 用户空间:拥有自己的 /etc、自己的 rc 启动脚本、自己的 pkg 数据库,以及任意数量的进程。本页中的几乎所有其他差异都源于这一点。

SSD Nodes 不提供 FreeBSD 镜像。您无法在此平台上租用 FreeBSD 服务器,下面的内容也不是针对您可在此购买的机器编写的安装指南。这是对两种隔离模型的比较,旨在帮助您判断某个工作负载实际需要哪一种模型,并帮助您阅读 FreeBSD 团队的配置,而无需自行猜测。

实际的 jail 是什么

Jail 于 2000 年 3 月随 FreeBSD 4.0 引入,因此比 cgroups 更早,大约比 Docker 早 10 年。其机制由一次内核调用完成。jail(8) 接收一个目录树,并在其中启动带有 jail ID 的进程;随后,内核会拒绝任何携带该 ID 的进程执行一组固定操作。Jail 中的进程无法查看 jail 外部的进程,无法挂载或卸载文件系统,无法加载内核模块,也无法绑定到未分配给该 jail 的网络地址。不需要学习单独的命名空间类型,也不需要按功能单独启用限制:这些限制作为一个整体生效,并通过 jail 配置中的参数进行调整。

在主机上,jls 列出正在运行的 jail,jexec web sh 让您进入名为 web 的 jail 中的 shell。

构建 jail 的方法是将 FreeBSD 用户空间放入一个目录。基础系统可以自动完成这一步:

sudo bsdinstall jail /usr/local/jails/containers/web

该命令会获取当前发行版对应的基础发行版集合,并执行大多数常规安装后步骤。因此,您可以像在新服务器上一样设置 root 密码、选择服务和添加用户。它不会询问时区:安装程序会将主机上的 /etc/localtime 复制到 jail 中,因此 jail 会继承主机的时区。最终结果是一个位于目录中的 FreeBSD 安装。然后在 /etc/jail.conf 中描述它:

web {
  host.hostname = "web.example.internal";
  path = "/usr/local/jails/containers/web";
  ip4.addr = "10.0.0.10";
  exec.start = "/bin/sh /etc/rc";
  exec.stop = "/bin/sh /etc/rc.shutdown";
  mount.devfs;
}

启动它,然后检查状态:

sudo service jail start web
jls

此时,jls 应列出带有 JID、主机名和 IP 地址的 web。如果 jail 未出现,请直接运行 sudo jail -c web。该命令会在前台使用相同的配置,并输出无法接受的参数,而不是将错误留在服务输出中。

值得仔细阅读两遍的是 exec.start = "/bin/sh /etc/rc"。启动 jail 时,FreeBSD 会在其中运行正常的启动脚本,因此 jail 会启动其自身 /etc/rc.conf 中启用的所有服务。Docker 容器没有对应步骤,因为它运行镜像的 entrypoint 进程,并在该进程停止后退出。

软件如何进入系统:镜像和注册表 vs 自行填充的用户空间

这是您第一天就能明显感受到的区别。

使用 Docker 时,您指定软件并直接获取它。docker pull nginx 会获取由他人构建和测试的分层、基于内容寻址的镜像,docker compose up -d 会在挂载卷并连接网络后启动它。注册表就是产品本身。Docker 工作流的大部分价值在于,成千上万个项目都会发布可用镜像。这使得在 VPS 上运行 Docker成为一项短期任务,而不是一个项目。

FreeBSD 不提供默认的公共 jail 镜像注册表。您需要创建一个空的用户空间,再像配置裸服务器一样在其中安装软件。这需要输入更多命令。但过程也更透明,因为 jail 中运行的内容就是 pkg 放入其中的内容,并且来自主机使用的同一套软件包。

工具可以简化这一过程。BastilleBSD 是常用的 jail 管理器,并且可以作为软件包安装:

sudo pkg install bastille
sudo sysrc bastille_enable=YES
sudo bastille setup
sudo bastille bootstrap 15.1-RELEASE

bastille setup 会为您配置网络、存储和防火墙。bastille bootstrap 会下载一次发行版,之后创建的每个 jail 都会复用该发行版。FreeBSD 15.1 是当前的生产版本,于 June 2026 发布;请替换为您实际运行的版本。

创建 jail 只需一条命令,填充其中的软件再执行一条命令:

sudo bastille create web 15.1-RELEASE 10.17.89.10/24
sudo bastille pkg web install nginx
sudo bastille service web nginx start
sudo bastille console web

bastille console web 会在 jail 内为您提供登录 shell,bastille list 则会显示主机上存在的内容。要重复构建过程,可以将步骤写入 Bastille 模板文件,再将模板应用到 jail。这是该生态中最接近 Dockerfile 的机制。模板会在每个 jail 上重新执行。不会有预构建内容自动进入其中。

因此,结论很简单。Docker 将他人构建的软件交给您。jail 让您自行安装软件。如果您备选的软件只发布为容器镜像,而没有其他形式,那么在其他因素开始比较之前,答案已经确定。

状态与升级:ZFS 改变的部分

Docker 会有意将状态分离。容器文件系统是临时的,您的数据存放在命名卷或绑定挂载中,升级流程是 docker compose pull,然后执行 docker compose up -d。容器会被替换,未放入卷中的内容都会丢失。遵循这条规则时,这是一个特性;忘记时,则会造成数据丢失。因此,在 Compose 堆栈中,绑定挂载和命名卷之间的选择非常重要。

jail 不会分离状态,而 ZFS 正是这一方式可行的原因。整个 jail 是一个数据集:

sudo zfs snapshot zroot/jails/containers/web@pre-upgrade
sudo pkg -j web upgrade
sudo zfs rollback zroot/jails/containers/web@pre-upgrade

运行此命令前,请使用 zfs list 检查实际的数据集名称;上面的路径是手册采用的布局。创建快照大约需要 1 秒。在 jail 内容发生变化前,它几乎不占用额外空间。如果升级导致服务故障,回滚会将整个用户态环境恢复到之前的状态,包括软件包数据库和您在凌晨 2 点手动编辑的配置文件。Docker 没有内置的等效功能,因为它的模型假定您从未需要这种功能。

zfs clone 是另一部分。快照的克隆是一个新的可写 jail,它与父 jail 共享未更改的数据块。因此,创建一个 3 GB jail 的暂存副本几乎不占用额外磁盘空间,直到您开始修改它。这就是 FreeBSD 管理员创建“与生产环境相同”的 jail 以演练升级的方式。

基本系统升级与软件包升级是分开的。对于包含自身用户态副本的 jail:

sudo freebsd-update -b /usr/local/jails/containers/web fetch install

轻量 jail 可避免重复这项工作。它们通过 nullfs 挂载一个共享的只读基础系统,并为每个 jail 提供一个独立的小型可写层。因此,您只需修补一次基础系统,所有 jail 都能看到更新结果。Bastille 默认创建轻量 jail。

网络:发布端口与地址模型的选择

Docker 会替您决定网络方式,并要求您发布例外端口。容器连接到 bridge 网络后,可以通过用户定义网络中的服务名相互访问,而 -p 8080:80 会将其中一个容器暴露给主机。Docker 会写入自己的数据包过滤规则来实现这一点,这也是 已发布的容器端口可以直接绕过 ufw 的原因。

使用 jail 时,您必须预先选择网络模型,共有两种。

共享 IP。 ip4.addr = "10.0.0.10" 将 jail 限制为该地址。它本身不会修改主机的网络接口,因此该地址必须已经配置在其中一个接口上。要让 jail(8) 在创建 jail 时将该地址添加为别名,请指定接口:在 jail 配置块中设置 interface = "em0";,或者在地址前加上 ip4.addr = "em0|10.0.0.10";。jail 没有自己的网络协议栈,因此不能运行自己的防火墙。它也无法真正绑定到所有地址:jail 中的套接字请求 0.0.0.0 时,内核会将其改写为 jail 自身的地址。两个 jail 不能同时监听同一地址的 80 端口,因此您需要为每个 jail 分配一个地址,或在前面放置反向代理。

VNET。 在 jail 中添加 vnet; 后,它会获得完整的网络协议栈,包括自己的网络接口、路由表和防火墙规则。您需要通过 epair 将它连接到主机;这是两端分别连接主机和 jail 的虚拟网线,并将主机端接入 bridge。这最接近 Docker 提供的网络方式,也是 Bastille 的 -V 和 -B jail 类型所使用的模式。

将主机端口转发到 jail 需要使用 pf 重定向规则。Bastille 对其进行了封装:

sudo bastille rdr web tcp 80 80

这里没有 EXPOSE,也不会自动发布端口。除非 jail 的地址或重定向规则允许,否则任何流量都无法到达 jail。启动配置更繁琐,但防火墙会安静得多。

资源限制:cgroups 与 rctl

Docker 使用 cgroups 限制容器,限制配置在容器定义所在的位置:可以在命令行中使用 --memory=1g --cpus=1.5,也可以在 Compose 文件中设置对应的键。如果您已经将服务栈保存在 VPS 上的 Docker Compose 文件中,限制就位于对应服务旁边,并会随服务一起纳入 git 管理。

FreeBSD 使用 rctl,这是一个必须手动启用的子系统。GENERIC 内核会编译资源统计功能,但默认通过 RACCT_DEFAULT_TO_DISABLED 内核选项将其关闭。将该 tunable 添加到 /boot/loader.conf,然后重启:

kern.racct.enable=1

然后设置规则并查看其状态:

sudo rctl -a jail:web:vmemoryuse:deny=1g
rctl -hu jail:web

rctl -hu jail:web 会以人类可读的单位显示 jail 当前的资源使用量,因此您可以在出现故障前了解它距离限制还有多远。deny 操作会使超出限制的分配在 jail 内失败,因此您看到的是应用自身的分配错误,而不是主机上的 kill 消息。

使用 rctl -a 添加的规则会在下次重启时消失。FreeBSD 的 rctl 服务会从 /etc/rctl.conf 重新加载这些规则,因此请将规则写入该文件并启用服务:

sudo sysrc rctl_enable=YES

这是 Docker 明显更方便的方面。Compose 文件中的限制会与受其约束的服务一起接受审查。rctl 规则则是单独文件中的一行,并且其中指定的 jail 可能定义在其他位置。

当答案是虚拟机时:bhyve

Jail 共享主机内核,因此有些功能永久无法使用。它不能运行其他版本的内核,不能加载内核模块,也不能像 Linux 容器那样运行 Linux 二进制文件。FreeBSD 提供了 Linux 兼容层 linuxulator,但它只实现了部分 Linux 系统调用,无法通用地运行任意 Linux 镜像。

bhyve 是 FreeBSD 的 hypervisor。当您需要真正的机器隔离边界时,应使用它:例如运行其他操作系统、其他内核,或不希望与租户共享内核。代价是需要预留而不是共享的内存,以及需要额外维护和修补一个内核。这与您在 Linux 上决定使用容器还是完整虚拟机时的选择相同,也决定了您是否需要底层支持嵌套虚拟化的 VPS。

生态系统,这是大多数团队使用 Docker 的真实原因

以上内容均围绕模型展开。决定大多数团队选择的因素是各自生态圈的规模。

Docker 带来了 Docker Hub 和 GHCR、docker compose、在单机无法满足需求时使用的 Kubernetes、内置容器支持的 CI runner,以及几乎每个项目 README 中都提供的一键快速启动命令。Jails 带来了 FreeBSD ports 树,它规模庞大且维护严谨,但现成的即用型应用包数量要少得多。当某个项目仅发布容器镜像时,FreeBSD 的处理方式是阅读其文档并自行组装组件。

Jails 在这种权衡中占据一席之地。如果您已经在运行 ZFS 并重视对整个服务进行快照和回滚,或者当您的服务是 FreeBSD 原生应用,或者您希望每个租户拥有完整的用户空间而非单一进程,又或者您希望内核、包过滤器、文件系统和文档作为一个统一的系统进行维护时,您可能会选择 Jails。最后一点正是人们称 FreeBSD 为“连贯系统”的原因,关于这一点,在 Linux 与 FreeBSD 作为服务器平台的更广泛比较 以及 FreeBSD 15 在服务器使用方面的变更 中有更多讨论。

最后总结。如果您的团队已经熟悉 Docker,迁移成本是客观存在的,因此收益必须非常明确。不要为了隔离质量而切换;这两种模型的隔离水平非常接近,您的配置方式影响更大。切换的原因应当是您需要基于 ZFS 的全服务回滚,或者您本身就在使用 FreeBSD。

FAQ

我可以在 FreeBSD 上运行 Docker 镜像吗?

不能运行 Linux 镜像,这也不是受支持的方案。FreeBSD 确实支持 OCI 容器:sudo pkg install -y podman-suite 可安装 Podman,Podman 通过 ocijail 运行容器;该运行时会在底层创建真正的 jail。容器监控需要将 fdescfs 挂载到 /dev/fd,容器 NAT(网络地址转换)需要 pf。OCI 原生 FreeBSD 镜像的运行效果最好。Linux 镜像还需要 Linux 兼容层;截至 October 2026,FreeBSD Podman port 自带的安装消息仍将其描述为实验性功能,仅用于评估和测试。如果部署由一组 Linux 镜像组成,请在 Linux 上运行。在 RHEL 系列系统上,这意味着在 Rocky Linux 或 AlmaLinux 上安装 Docker;此时 Podman 会再次出现,因为在安装任何内容之前,该软件包已经提供了 docker 命令。

FreeBSD jail 比 Docker 容器更安全吗?

两者共享同一个主机内核,因此内核漏洞会同时威胁两者;对于真正不受信任的代码,两者都不是应选择的隔离边界。区别在于默认起点。jail 默认拒绝一组广泛的操作,之后再逐项通过参数重新启用。Docker 容器默认以 root 身份运行在一组 namespace 中,并删除部分 capability;其他加固措施则需要显式启用。实际上,配置的影响大于模型本身:启用了 allow.mount 和 allow.raw_sockets 的 jail,并不比经过谨慎配置的容器更安全。

如何备份 jail?

为数据集创建快照,然后发送快照。sudo zfs snapshot zroot/jails/containers/web@backup,随后将该快照 zfs send 到另一个存储池,或发送到一个可以复制到主机外部的文件中。由于 jail 将完整的用户空间保存在一个数据集中,快照会捕获同一时间点的已安装软件包和数据,以及您手动编辑过的所有配置文件。这与 Docker 的常见做法相反:备份命名卷和 Compose 文件,其余内容从镜像重新构建。

我需要 BastilleBSD,还是基础系统就够了?

基础系统已经足够,也是更适合的起点。jail.conf、jls、jexec 和 service jail start 覆盖了整个模型。掌握这些命令后,您无需先学习某台 FreeBSD 主机专用的工具,就能阅读任何 FreeBSD 主机的配置。Bastille 是其上的便利层:它会引导安装 release、创建 thin jail、应用模板,并代您写入 pf 重定向规则。先学习基础命令;当 jail 数量增加到手动输入变得繁琐时,再添加 Bastille。