# 简单而轻松的 FreeBSD Jail

* 原文：[FreeBSD Jails are Simple and Easy](https://freebsdfoundation.org/blog/freebsd-jails-are-simple-and-easy/)
* 2025 年 8 月 25 日
* 作者：FreeBSD 基金会

在 FreeBSD 上运行常见服务很简单。但有时你想在同一个操作系统实例上运行多个服务，并且让每个服务都彼此安全地“隔离”。如果不提供互联网连接，web 服务就没多大意义，但这同时也可能带来了安全漏洞。很显然，我们不希望这样。如果某个服务有可能因为安全漏洞而被攻破，那么尽可能减少系统的暴露会更好，不是吗？

这就是 jail 的用武之地。Jail 是容器化的轻量化答案，早在 Docker 变得流行之前，[很久以前](https://en.wikipedia.org/wiki/FreeBSD_jail#History) 它就已存在。

## 设置 Jail

同科技行业中的所有事情一样，至少有一只手的方法能实现相同的结果。在 FreeBSD 上有许多工具可以用来创建和管理 jail，但我更倾向于 **保持简单**，所以选择使用默认安装系统中自带的工具。

手册中有 [关于 jail 的优秀文档](https://docs.freebsd.org/en/books/handbook/jails/)，我基本遵循它，仅做了一些细微的调整。

我从 [我们最近 YouTube 视频](https://youtu.be/CWuZLJkUBfw) 中创建的基础虚拟机开始，因为接下来的设置使用 ZFS。

这就是我设置 jail 的过程。如果你有五分钟，我们一起来看看。

## 配置宿主机

启用 jail：

```sh
sysrc jail_enable="YES" && sysrc jail_parallel_start="YES"
```

然后创建一些基本的 zfs 文件系统：

```sh
zfs create -o mountpoint=/jails zroot/jails
zfs create zroot/jails/media
zfs create zroot/jails/_base
```

手册中有其他挂载方式，你也可以选择使用。我更喜欢稍微简单一些的“base”和模板文件系统，然后通过快照来生成新的 jail。我们稍后会用到。

## 创建模板 jail

下载 FreeBSD 用户态文件用作我们的模板：

```sh
fetch https://download.freebsd.org/ftp/releases/$(uname -m)/$(freebsd-version)/base.txz -o /jails/media/$(freebsd-version)-base.txz
```

解压文件，修补模板，然后创建快照：

```sh
tar -xf /jails/media/$(freebsd-version)-base.txz -C /jails/_base/ --unlink
cp /etc/resolv.conf /jails/_base/etc/resolv.conf
cp /etc/localtime /jails/_base/etc/localtime
freebsd-update -b /jails/_base/ fetch install
pkg -c /jails/_base install -y pkg python zsh
zfs snapshot zroot/jails/_base@$(freebsd-version)-$(date +%d-%b-%y)
```

现在我们有了“模板”jail，可以用来创建新的 jail。我不会运行这个 jail，而且通常会在模板里添加常用包，这样每个后续克隆都会包含它们。把你喜欢的工具都放进去 🙂 值得注意的是：如果你打算用 Ansible 来管理 jail 中的包，你也需要在里面安装 pkg。之前为了保持简单，我没有这么做，而是直接在宿主机上用命令 `pkg -j JAILNAME install BLAH` 来安装。我想这算是个哲学问题——你是把 jail 看作是扩展的 chroot，还是微型的虚拟机？

差不多快完成了。我们需要 `/etc/jail.conf` ——我选择使用统一的“全局”文件，其中的选项适用于所有 jail，然后在 `/etc/jail.conf.d/` 中为每个 jail 单独创建配置文件：

`/etc/jail.conf` 模板：

```sh
# https://man.freebsd.org/jail.conf
# https://man.freebsd.org/jail
#
ip4 = inherit;
ip6 = inherit;

# 默认应用于所有 jail
exec.start = "/bin/sh /etc/rc";
exec.stop = "/bin/sh /etc/rc.shutdown jail";
exec.clean;

# 文件系统
mount.devfs;
devfs_ruleset=4;
enforce_statfs=1;
securelevel=2;
#allow.mount.zfs;
#allow.mount;

# 禁用所有花哨的东西
allow.mount.nodevfs;
allow.mount.nofdescfs;
allow.mount.nolinprocfs;
allow.mount.nonullfs;
allow.mount.noprocfs;
allow.mount.notmpfs;
allow.nochflags;

# 但这些总是有用的
allow.raw_sockets;
allow.reserved_ports;
allow.sysvipc=1;

# 杂项
allow.nomlock;
allow.noquotas;
allow.noread_msgbuf;
allow.noset_hostname;
allow.nosocket_af;
allow.nosysvipc;
sysvmsg=disable;
sysvsem=disable;
children.max=0;
.include "/etc/jail.conf.d/*.conf";
```

## 创建新的 jail

每当我需要新的 jail 时，只需 zfs 克隆模板，在 `/etc/jail.conf.d/` 中创建一个配置文件，并在 `/etc/rc.conf` 的 `jail_list` 中添加新 jail 名称。我写了个简单的 shell 脚本来实现这一点：

```sh
#!/bin/sh
#
set -eo pipefail

SNAP=${2:-$(zfs list -t snapshot -H -o name | grep "jails/_base" | cut -f3 -d/)}

if [ -z $1 ]; then
    echo "pass new jail name"
    exit 1
else
    new=$1
fi

zfs clone zroot/jails/${SNAP} zroot/jails/${new}

test -d /etc/jail.conf.d || mkdir -p /etc/jail.conf.d
cat > /etc/jail.conf.d/${new}.conf <<EOF
${new} {
    host.hostname = "${new}.jail";
    path = "/jails/${new}";
}
EOF

sysrc jail_list+=${new}
```

## 扩展 Jail

这只是有关关于如何快速启动 jail 的简单介绍。手册文档里有更多细节，包括如何 [管理资源限制](https://docs.freebsd.org/en/books/handbook/jails/#jail-resource-limits)（比如限制内存和 CPU）。如果你想进一步扩展，运行更多的 jail，你可能需要看看手册中列出的一些工具，或者参考 [社区的 Ansible 剧本](https://git.sr.ht/~dch/ansible-jails)。

正如我们博客文章中常做的那样，我们把本文用到的一些资源放在了 [我们的 GitHub 仓库](https://github.com/FreeBSDFoundation/blog/tree/main/easy-jail-setup)，并制作了视频：

<https://www.youtube.com/embed/vVpd34bWCJA>


---

# 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://translated-articles.bsdcn.org/2025-nian/jails.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.
