SSD Nodes Learn 🎉 VPS $5.50/月起
指南 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 密码和时区。最终结果是一个位于目录中的 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 进程,并在该进程停止后退出。

软件获取方式:镜像与仓库,对比自行填充的用户态环境

这是您在第一天就能感受到的差异。

使用 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,它与父数据集共享未发生变化的数据块。因此,创建一个 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 没有独立的网络协议栈,因此无法运行自己的防火墙。它也无法真正绑定到所有地址:jail 中的套接字请求 0.0.0.0 时,内核会将其改写为 jail 自身的地址。两个 jail 不能同时监听同一地址的 80 端口,因此您需要为每个 jail 分配一个地址,或者在前面放置反向代理。

VNET。vnet; 添加到 jail 后,它会获得完整的网络协议栈,包括独立的网络接口、路由表和防火墙规则。您可以通过 epair 将它连接到主机;这是一个虚拟网线,两端分别连接两侧,并将主机端接入 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,这是一个必须手动启用的子系统。资源记账默认关闭,因为每次分配都会产生少量开销。将该 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 内失败,这样您看到的是应用程序自身的分配错误,而不是主机上的终止消息。

使用 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 的虚拟机管理程序。当您需要真正的机器隔离边界时,应使用它:例如运行其他操作系统、其他内核,或不希望与租户共享内核。代价是需要预留而不是共享的内存,以及需要维护第二个内核。这与您在 Linux 上决定使用容器还是完整虚拟机时面临的选择相同,也决定了您是否需要在底层使用支持嵌套虚拟化的 VPS

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

上文讨论的都是模型。对大多数团队而言,真正决定选择的是每个模型背后的生态规模。

Docker 提供 Docker Hub 和 GHCR、docker compose、在一台主机不够用时使用的 Kubernetes、已经配置好容器支持的 CI runner,以及几乎每个项目 README 中都有的一条命令快速启动方式。Jail 则提供规模庞大且维护细致的 FreeBSD ports tree,以及数量少得多的开箱即用应用包。如果项目只发布容器镜像而没有其他内容,那么使用 FreeBSD 就需要阅读项目文档并自行组装各个组件。

Jail 在权衡的另一侧具有优势。以下情况适合使用 Jail:您已经运行 ZFS,并且重视对整个服务执行快照和回滚;您的服务原生运行在 FreeBSD 上;您希望每个租户拥有完整的 userland,而不是只运行单个进程;或者您希望内核、packet filter、文件系统和文档作为一个系统统一维护。人们称 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。原生 FreeBSD OCI 镜像的兼容性最好。Linux 镜像还需要 Linux 兼容层;截至 2026 年 8 月,FreeBSD 的 Podman 移植版本仍被描述为实验性版本。如果您的部署由一组 Linux 镜像组成,请在 Linux 上运行。

FreeBSD jail 比 Docker 容器更安全吗?

两者共用主机内核,因此内核漏洞会同时影响两者;对于真正不受信任的代码,两者都不是应选择的隔离边界。区别在于默认起点。jail 默认拒绝一组较广泛的操作,您需要逐个参数重新启用这些操作。Docker 容器默认以 root 身份运行在一组 namespace 中,并删除部分 capability;进一步加固则需要主动配置。实际情况中,配置的影响大于模型本身:启用了 allow.mountallow.raw_sockets 的 jail,并不比经过仔细配置的容器更安全。

如何备份 jail?

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

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

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