> For the complete documentation index, see [llms.txt](https://book.bsdcn.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://book.bsdcn.org/handbook/di-17-zhang-jail-yu-rong-qi/17.3.-zhu-ji-pei-zhi-jail.md).

# 17.3.主机配置

在宿主系统上创建任何 jail 之前，必须做一些配置，并获取宿主系统的一些信息。

这包括配置 [jail(8)](https://man.freebsd.org/cgi/man.cgi?query=jail\&sektion=8\&format=html) 工具、创建用于配置和安装 jail 的必要目录、获取宿主系统的网络信息，以及检查宿主是否使用 OpenZFS 或 UFS 作为文件系统。

> **技巧**
>
> jail 中运行的 FreeBSD 版本不得高于宿主系统运行的版本。

## 17.3.1. jail 工具

[jail(8)](https://man.freebsd.org/cgi/man.cgi?query=jail\&sektion=8\&format=html) 工具用于管理 jail。

若要在系统启动时自动启动 jail，执行以下命令：

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

> **技巧**
>
> 启用 `jail_parallel_start` 后，所有已配置的 jail 将在后台同时启动。

默认情况下，配置文件中有条目的每个 jail 都会在系统启动时启动。如果只想启动选定的 jail，可在 `jail_list` 中列出它们的名称：

```sh
# sysrc jail_list="www db"
```

`jail_list` 中的名称以空格分隔，必须与 **/etc/jail.conf** 或 **/etc/jail.conf.d/** 中定义的 jail 名称匹配。只有列表中的 jail 会在启动时启动，并按给定顺序启动，因此未在 `jail_list` 中的 jail 必须手动启动。当 `jail_list` 为空时，配置文件中定义的所有 jail 都会启动。

要在关机时按 `jail_list` 的相反顺序停止 jail，可设置 `jail_reverse_stop`：

```sh
# sysrc jail_reverse_stop="YES"
```

> **注意**
>
> 启用 `jail_parallel_start` 后，jail 在后台启动，`jail_list` 的顺序不再得到保证。当某个 jail 必须在另一个 jail 启动之前运行时，请在 **/etc/jail.conf** 中使用 `depend` 参数来表达这种依赖关系，而不是依赖列表顺序。jail 的依赖会在其启动之前自动创建，并在其停止时以相反顺序停止，这与 `jail_list` 无关。`depend` 参数将在 [jail 配置文件](/handbook/di-17-zhang-jail-yu-rong-qi/17.3.-zhu-ji-pei-zhi-jail.md) 一节中介绍。

## 17.3.2. 网络

FreeBSD jail 的网络可通过几种不同的方式配置：

**宿主网络模式（IP 共享）** 在宿主网络模式中，jail 共享宿主的网络协议栈，而不是获得自己的协议栈。共享协议栈并不意味着共享地址：常见做法是为 jail 分配独立的 `ip4.addr`（和 `ip6.addr`），由 [jail(8)](https://man.freebsd.org/cgi/man.cgi?query=jail\&sektion=8\&format=html) 作为别名添加到宿主接口上，因此 jail 和宿主在同一协议栈上通过不同地址应答。只有配置为 `ip4 = inherit` 的 jail 才会完整共享宿主的地址，应答宿主自己的地址，并无限制地访问宿主持有的所有地址。

**虚拟网络（VNET）** 虚拟网络是 FreeBSD jail 提供的高级网络功能，比基础的宿主网络模式更灵活。VNET 能为每个 jail 创建独立的网络协议栈，为其分配独立的 IP 地址、路由表和网络接口。这提供了更高程度的网络隔离，使 jail 的运行行为更像是独立的虚拟机。

**netgraph 系统** [netgraph(4)](https://man.freebsd.org/cgi/man.cgi?query=netgraph\&sektion=4\&format=html) 是通用的内核框架，用于创建自定义网络配置。它可用于定义 jail 与宿主之间、以及多个 jail 之间网络流量的流向方式。

### 17.3.2.1. jail 的 IP 地址来源

共享协议栈的 jail 通过宿主接口上的地址访问网络。`interface` 参数指定该接口，对于 `ip4.addr` 和 `ip6.addr` 中的每个地址，[jail(8)](https://man.freebsd.org/cgi/man.cgi?query=jail\&sektion=8\&format=html) 都会自行管理别名：正如 jail(8) 所述，“在创建 jail 之前，会为每个地址在接口上添加一个别名，并在移除 jail 之后从接口上移除该别名”。宿主上无需预先配置该地址。

也可以按地址指定接口，从而用一行将地址与承载它的接口绑定：

```sh
ip4.addr = "em0|192.168.1.151/24";
```

当地址必须独立于 jail 持久存在时——因为宿主上的另一个服务依赖它，或者因为未使用 `interface` 参数——可在宿主上将其配置为 rc.conf 别名，不交给 jail(8) 管理：

```sh
# sysrc ifconfig_em0_aliases="inet 192.168.1.151/32"
```

较早的编号形式 `ifconfig_em0_alias0="inet 192.168.1.151/32"` 仍然可用，但对于新配置，推荐使用 `_aliases` 形式，因为它避免了编号别名必须形成连续 `alias0`、`alias1`、`alias2` 序列这种脆弱的要求。

### 17.3.2.2. 共享 IP jail 中的回环地址

共享协议栈的 jail 没有自己的回环接口。FreeBSD 内核对此做了补偿：在非 VNET jail 中，只要 `127.0.0.1` 或 `::1` 本身不是 jail 自己的地址之一，内核就会将回环地址上的 `bind()` 和 `connect()` 静默改写为 jail 的第一个 `ip4.addr` 或 `ip6.addr`。此行为未在 [jail(8)](https://man.freebsd.org/cgi/man.cgi?query=jail\&sektion=8\&format=html) 中记录，但它很重要，因为它带有安全隐患。

> **警告**
>
> 不要以为将服务绑定到 `localhost` 就能将其限制在 jail 内部。在共享 IP jail 中，被告知监听 `127.0.0.1` 的守护进程实际上绑定的是 jail 的第一个地址，这通常是面向局域网的地址，因此可从网络访问。

标准的缓解方法是给 jail 分配一个私有的回环地址并将其列在首位。在宿主上克隆一个专用的回环接口，并为其分配 `127.0.0.0/8` 范围内的地址（或私有 RFC 1918 地址）：

```sh
# sysrc cloned_interfaces="lo1"
# sysrc ifconfig_lo1="inet 127.0.1.1/32"
```

然后将该地址列在 jail 的 `ip4.addr` 中的首位，位于任何可路由地址之前，这样回环改写就会落到私有地址而非面向局域网的地址上。

### 17.3.2.3. 准备宿主服务

每个 jail 别名地址都共享宿主的端口。因此，绑定到通配符地址（在监听器列表中显示为 `*:port`）的宿主守护进程也会监听每个 jail 别名，并在 jail 自己的服务可以占用该端口之前就将其占用。在启动任何 jail 之前，先审计宿主已监听的内容：

```sh
# sockstat -4 -6 -l
```

限制每个绑定通配符地址的守护进程，使其仅使用宿主自己的地址，将 jail 地址留空：

* [syslogd(8)](https://man.freebsd.org/cgi/man.cgi?query=syslogd\&sektion=8\&format=html)：在 **/etc/rc.conf** 中设置 `syslogd_flags="-ss"`。单个 `-s` 已经使 syslogd 以安全模式运行，不监听远程日志消息；根据 syslogd(8)，如果指定两次 `-s`，“将完全不打开网络套接字”，这同时也会阻止 syslogd 向远程主机发送消息。
* [sshd(8)](https://man.freebsd.org/cgi/man.cgi?query=sshd\&sektion=8\&format=html)：在 **/etc/ssh/sshd\_config** 中将 `ListenAddress` 固定到宿主的地址，而不是保留默认的通配符。
* [ntpd(8)](https://man.freebsd.org/cgi/man.cgi?query=ntpd\&sektion=8\&format=html) 和 [sendmail(8)](https://man.freebsd.org/cgi/man.cgi?query=sendmail\&sektion=8\&format=html)：通过它们各自的配置将它们绑定到宿主地址。

关于哪些地址可由 jail 使用的防火墙级控制，请参见 [防火墙](/handbook/di-34-zhang-fang-huo-qiang/34.1.-gai-shu.md) 一章。

## 17.3.3. 设置 jail 目录结构

jail 的文件不需要放在特定路径。

有些管理员使用 **/jail**，有些使用 **/usr/jail**，也有些使用 **/usr/local/jails**。本章将使用 **/usr/local/jails**。

除了 **/usr/local/jails**，还将创建以下目录：

* **media**：用于存放下载的用户空间压缩包。
* **templates**：使用瘦 jail 时用于存放模板。
* **containers**：用于存放各个 jail。

如果使用 OpenZFS，执行以下命令为这些目录创建数据集：

```sh
# zfs create -o mountpoint=/usr/local/jails zroot/jails
# zfs create zroot/jails/media
# zfs create zroot/jails/templates
# zfs create zroot/jails/containers
```

> **技巧**
>
> 此处使用 `zroot` 作为父数据集，也可使用其他数据集。

为每个 jail 创建单独的子数据集，是实现每个 jail 独立快照、克隆、配额和数据集委托的基础。由于每个 jail 目录都是独立的数据集，OpenZFS 可以使用 `quota` 和 `reservation` 属性为其磁盘使用设置上限和保证；参见 [jail 资源限制](/handbook/di-17-zhang-jail-yu-rong-qi/17.13.-jail-zi-yuan-xian-zhi.md)。

如果使用 UFS，执行以下命令创建这些目录：

```sh
# mkdir /usr/local/jails/
# mkdir /usr/local/jails/media
# mkdir /usr/local/jails/templates
# mkdir /usr/local/jails/containers
```

## 17.3.4. jail 配置文件

配置 jail 有两种方式。

第一种是在 **/etc/jail.conf** 文件中为每个 jail 添加条目。另一种方式是在 **/etc/jail.conf.d/** 目录中为每个 jail 创建独立的配置文件。

如果宿主系统上只运行少量 jail，可直接将每个 jail 的配置写入 **/etc/jail.conf** 文件。若宿主系统上运行多个 jail，建议在 **/etc/jail.conf.d/** 目录下为每个 jail 分别创建配置文件。

**/etc/jail.conf.d/** 中的文件必须使用 `.conf` 作为扩展名，并且需要在 **/etc/jail.conf** 中通过以下语句引入：

```sh
.include "/etc/jail.conf.d/*.conf";
```

典型的 jail 配置条目如下：

```sh
jailname { ①
  # 启动与日志
  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.addr = 192.168.1.151; ⑩
  ip6.addr = "2001:db8::151"; ⑪
  interface = em0; ⑫
}
```

* ① `jailname` jail 的名称。
* ② `exec.start` 在 jail 创建时运行的命令。常见写法为 `/bin/sh /etc/rc`。
* ③ `exec.stop` 在移除 jail 前运行的命令。常见写法为 `/bin/sh /etc/rc.shutdown`。
* ④ `exec.consolelog` 用于记录命令输出（stdout 和 stderr）的日志文件路径。
* ⑤ `allow.raw_sockets` 能在 jail 中创建原始套接字。启用后，可在 jail 中使用如 [ping(8)](https://man.freebsd.org/cgi/man.cgi?query=ping\&sektion=8\&format=html) 与 [traceroute(8)](https://man.freebsd.org/cgi/man.cgi?query=traceroute\&sektion=8\&format=html) 等工具。
* ⑥ `exec.clean` 在干净的环境中运行命令。
* ⑦ `mount.devfs` 将 [devfs(5)](https://man.freebsd.org/cgi/man.cgi?query=devfs\&sektion=5\&format=html) 文件系统挂载到 chroot 后的 **/dev** 目录，以限制 jail 内部可见的设备。它应用 `devfs_ruleset` 参数指定的规则集，或者当该参数保留为默认值 `0` 时，使用 `mount.devfs` 的回退规则集 4，即 `devfsrules_jail`，该规则集隐藏了除最小化 jail 安全设备集之外的所有宿主设备（参见 [devfs 规则集](/handbook/di-17-zhang-jail-yu-rong-qi/17.3.-zhu-ji-pei-zhi-jail.md)）。
* ⑧ `host.hostname` jail 的主机名。
* ⑨ `path` jail 的根目录路径。通过 `jail` 或 [jexec(8)](https://man.freebsd.org/cgi/man.cgi?query=jexec\&sektion=8\&format=html) 执行的命令均在该目录下运行。
* ⑩ `ip4.addr` 分配给 jail 的一个 IPv4 地址，或以逗号分隔的地址列表。设置 `ip4.addr` 隐含了 `ip4 = new`；`ip4` 本身不会保留为独立的默认值。`ip4` 参数接受三个值：`new` 将 jail 限制为 `ip4.addr` 中列出的地址，`inherit` 让 jail 无限制地访问宿主的所有 IPv4 地址，`disable` 完全关闭 jail 中的 IPv4。未设置 `ip4` 或 `ip4.addr` 的 jail 行为类似于 `inherit`，此时 `interface` 和 `ip4.addr` 在 `inherit` 下无效。在有多个地址的 jail 中，`ip4.saddrsel` 让内核为未绑定套接字的出站连接选择源地址，而 `ip4.nosaddrsel` 强制使用列表中的第一个地址。
* ⑪ `ip6.addr` 分配给 jail 的一个 IPv6 地址，或以逗号分隔的地址列表。`ip6` 参数接受与 `ip4` 相同的 `new`、`inherit` 和 `disable` 值，`ip6.saddrsel` 和 `ip6.nosaddrsel` 控制源地址选择。对于仅 IPv4 的 jail，使用 `ip6 = disable`；对于仅 IPv6 的 jail，使用 `ip4 = disable`。
* ⑫ `interface` 用于添加 jail IP 地址的网络接口，通常为宿主系统的接口名。

> **注意**
>
> jail 没有虚拟控制台：[init(8)](https://man.freebsd.org/cgi/man.cgi?query=init\&sektion=8\&format=html) 和 [getty(8)](https://man.freebsd.org/cgi/man.cgi?query=getty\&sektion=8\&format=html) 不会在其中运行，因此没有可交互连接的对象。`exec.consolelog` 文件仅捕获 `exec.*` 命令的标准输出和标准错误，即 **/etc/rc** 启动脚本和 **/etc/rc.shutdown** 脚本的输出。它不是交互式控制台；如需交互，可使用 [jexec(8)](https://man.freebsd.org/cgi/man.cgi?query=jexec\&sektion=8\&format=html)（参见 [访问 jail](/handbook/di-17-zhang-jail-yu-rong-qi/17.10.-guan-li-jail.md)）或在 jail 内运行 [sshd(8)](https://man.freebsd.org/cgi/man.cgi?query=sshd\&sektion=8\&format=html)。jail 自己的守护进程会将后续输出发送到 jail 的 syslog。

> **技巧**
>
> `exec.consolelog` 文件会无限增长。可通过 [newsyslog(8)](https://man.freebsd.org/cgi/man.cgi?query=newsyslog\&sektion=8\&format=html) 添加单个 glob 条目来轮转，例如在 **/etc/newsyslog.conf.d/jail** 中：
>
> ```sh
> /var/log/jail_console_*.log            640  7     100  *     J
> ```

### 17.3.4.1. 全局设置、通配符和变量

在任何 jail 块之外设置的参数是通配符默认值：[jail.conf(5)](https://man.freebsd.org/cgi/man.cgi?query=jail.conf\&sektion=5\&format=html) 会将它们应用于每个 jail，就像写在 `*` 通配符节中一样。一次性设置共享参数可使每个 jail 块保持简短。

以美元符号开头、可选地用大括号括起的名称，会被替换为该参数或变量的值。内置的 `${name}` 会展开为 jail 自己的名称，自定义变量的定义方式与参数相同，但以美元符号开头。

以下 **/etc/jail.conf** 一次性定义了公共头信息，并仅覆盖每个 jail 不同的部分：

```sh
# 应用于每个 jail 的共享默认值
exec.start = "/bin/sh /etc/rc";
exec.stop = "/bin/sh /etc/rc.shutdown";
exec.consolelog = "/var/log/jail_console_${name}.log";
exec.clean;
mount.devfs;
host.hostname = "${name}";
path = "/usr/local/jails/containers/${name}";
interface = em0;

www {
    ip4.addr = 192.168.1.151;
}

db {
    host.hostname = "database.example.com";
    ip4.addr = 192.168.1.152;
}
```

此处 `www` 继承了头信息中的所有内容，仅设置了自己的地址，而 `db` 还覆盖了 `host.hostname`。

### 17.3.4.2. 命令钩子

[jail(8)](https://man.freebsd.org/cgi/man.cgi?query=jail\&sektion=8\&format=html) 通过一系列生命周期钩子来运行 jail，每个钩子是在宿主环境还是在 jail 内部运行都很重要。创建顺序为：

`exec.prestart` 在创建 jail 之前于宿主环境运行。 `exec.created` 在 jail 已存在但尚未启动任何进程之前于宿主环境运行。 `exec.start` 在 jail 内部运行，通常为 `/bin/sh /etc/rc` 以启动其服务。 `exec.poststart` 在 jail 启动之后于宿主环境运行。

关闭时运行的是镜像过程：

`exec.prestop` 在停止 jail 之前于宿主环境运行。 `exec.stop` 在 jail 内部运行，通常为 `/bin/sh /etc/rc.shutdown`。 `exec.poststop` 在 jail 停止之后于宿主环境运行，用于拆除 `exec.prestart` 设置的内容。

可通过 `+=` 为一个钩子分配多个命令：

```sh
exec.poststart += "logger jail ${name} started";
```

`exec.timeout` 限制了 [jail(8)](https://man.freebsd.org/cgi/man.cgi?query=jail\&sektion=8\&format=html) 等待命令完成的时间，`exec.consolelog` 捕获这些命令的输出。[VNET jail](/handbook/di-17-zhang-jail-yu-rong-qi/17.6.-vnet-jail.md) 一节使用 `exec.prestart` 和 `exec.poststop` 在宿主上构建和拆除每个 jail 的网络管道。

钩子在单个 jail 内部为命令排序，而 `depend` 参数则为整个 jail 排序。写入 `depend = otherjail;` 可告知 [jail(8)](https://man.freebsd.org/cgi/man.cgi?query=jail\&sektion=8\&format=html) 该 jail 需要 `otherjail`：依赖项会在此 jail 创建之前首先创建，直到其最后一个 `exec.poststart` 命令完成，并且这些 jail 会以相反顺序拆除。

### 17.3.4.3. devfs 规则集

每个设置 `mount.devfs` 的 jail 都会获得一个 devfs 规则集，用于决定其 **/dev** 下出现哪些设备节点，该规则集要么是 `devfs_ruleset` 参数指定的，要么在该参数未设置时使用 `mount.devfs` 的默认规则集 4。[devfs.rules(5)](https://man.freebsd.org/cgi/man.cgi?query=devfs.rules\&sektion=5\&format=html) 在 **/etc/defaults/devfs.rules** 中定义命名规则集。其中两个面向 jail：`devfsrules_jail`（规则集 4）是标准的 jail 最小设备集，`devfsrules_jail_vnet`（规则集 5）额外暴露 **/dev/pf**，使 VNET jail 可以运行自己的防火墙。VNET jail 示例设置 `devfs_ruleset = 5`，Linux jail 示例设置 `devfs_ruleset = 4`；传统 jail 和瘦 jail 示例仅设置 `mount.devfs`，因此使用其默认的规则集 4。

要暴露额外设备，可在 **/etc/devfs.rules** 中编写自定义规则集，包含一个标准规则集并取消隐藏所需设备。以下规则集从 `devfsrules_jail` 开始，并额外取消隐藏 [bpf(4)](https://man.freebsd.org/cgi/man.cgi?query=bpf\&sektion=4\&format=html) 设备：

```sh
[devfsrules_jail_bpf=100]
add include $devfsrules_jail
add path 'bpf*' unhide
```

重新加载规则，然后在 jail 配置中按编号引用新规则集：

```sh
# service devfs restart
```

```sh
devfs_ruleset = 100;
```

**/etc/devfs.rules** 中的规则集会覆盖 **/etc/defaults/devfs.rules** 中同号的规则集；否则这两个文件将有效合并。

关于配置变量的更多信息，可参考 [jail(8)](https://man.freebsd.org/cgi/man.cgi?query=jail\&sektion=8\&format=html) 与 [jail.conf(5)](https://man.freebsd.org/cgi/man.cgi?query=jail.conf\&sektion=5\&format=html)。


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## 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, and the optional `goal` query parameter:

```
GET https://book.bsdcn.org/handbook/di-17-zhang-jail-yu-rong-qi/17.3.-zhu-ji-pei-zhi-jail.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
