# 19.3 Qjail

## Jail 管理工具对比

Qjail 是用于部署 Jail 环境的封装工具，提供了安全性和性能增强功能。

常见的 Jail 管理工具包括 ezjail、Qjail 和 iocage，各有侧重。

| 特性        | ezjail       | Qjail        | iocage          |
| --------- | ------------ | ------------ | --------------- |
| 开发语言      | `sh`         | `sh`         | Python          |
| 当前维护状态    | 基本停止维护       | 持续可用         | 持续维护            |
| 文件系统要求    | UFS / ZFS 均可 | UFS / ZFS 均可 | 必须 ZFS          |
| 是否依赖 ZFS  | 否            | 否            | 是               |
| 是否支持 UFS  | 支持           | 支持           | 不支持             |
| 是否支持 VNET | 不支持          | 支持           | 支持              |
| 网络隔离能力    | 较弱           | 较强           | 较强              |
| 配置方式      | Shell 脚本     | Shell 脚本     | Python CLI      |
| 学习成本      | 低            | 中            | 较高              |
| 快照/克隆能力   | 基本无          | 有限           | 强（依赖 ZFS）       |
| 资源占用      | 很低           | 很低           | 较高              |
| 适合新部署     | 不推荐          | 推荐           | 推荐（仅限 ZFS）      |
| 典型适用场景    | 老旧系统维护       | 通用 Jail 管理   | 大规模 ZFS Jail 环境 |
| 主要限制      | 无 VNET、停止维护  | 功能相对朴素       | 强依赖 ZFS         |

本节部署的 Jail 的概念结构如下图所示：

![Jail 结构](/files/UyQ7aolzGshTUuEMBZBN)

## 预留 Jail 的 IP 地址：网络接口配置

在 **/etc/rc.conf** 文件中添加如下配置。克隆接口 lo1 可将 Jail 网络和宿主机网络配置分开，增强隔离性。

```ini
cloned_interfaces="lo1"  # 克隆出 lo1，应和宿主机网络配置分离 ①
ifconfig_lo1_alias0="inet 192.168.1.1/28" # 创建独立网段，共 14 个 IP 可供分配。需要根据实际网络环境调整地址范围
```

> **警告**
>
> ① 如要生成多个接口，应在同一行中以空格分隔描述，而不是另外创建多行，应如 `cloned_interfaces="lo1 lo2"`。分行书写时，只有第一行会生效。

重启所有网络接口以应用配置：

```sh
# service netif restart
```

lo1 将获得 14 个 IP 地址，均可用于分配给各个 Jail。

## 安装 Qjail 工具

使用 pkg 安装 Qjail：

```sh
# pkg install qjail
```

或者使用 Ports 安装：

```sh
# cd /usr/ports/sysutils/qjail/
# make install clean
```

设置 `qjail` 服务开机自启动：

```sh
# sysrc qjail_enable=YES
```

## 部署 Qjail 使用的目录结构

使用 Qjail 前，需部署 Qjail 使用的目录结构，有以下两种方式：

### 方法一：从官方镜像站自动下载

```sh
# qjail install
```

此时 Qjail 会从 FreeBSD 官网下载 base.txz 文件，示例输出如下：

```sh
# qjail install
resolving server address: ftp.freebsd.org:80
requesting http://ftp.freebsd.org/pub/FreeBSD/releases/amd64/amd64/15.0-RELEASE/base.txz
remote size / mtime: 195363380 / 1652346155
...
```

### 方法二：从境内镜像站下载

境内网络访问受限时，可使用镜像站手动下载，以中国科学技术大学开源镜像站为例。使用 `freebsd-version` 命令可确认宿主机的 FreeBSD 版本，Qjail 要求文件版本与宿主机一致。以下示例为 FreeBSD amd64 15.0：

从镜像服务器下载 FreeBSD 基本系统文件：

```sh
# fetch https://mirrors.ustc.edu.cn/freebsd/releases/amd64/15.0-RELEASE/base.txz
```

使用 qjail 安装基本系统到指定 Jail：

```sh
# qjail install base.txz
```

部署好 Qjail 的目录结构后，**/usr/jails** 目录下会自动生成 `sharedfs`、`template`、`archive`、`flavors` 四个目录，这四个目录构成了 Qjail 的核心文件系统架构：

* **sharedfs** 包含一份只读的操作系统可执行文件库，通过 nullfs 在各 Jail 之间共享以节省存储空间。nullfs 是 FreeBSD 提供的特殊文件系统，可在同一主机的不同位置重复挂载同一文件系统。
* **template** 包含操作系统的配置文件模板，将被复制到每个 Jail 的基本文件系统中，作为新 Jail 的初始配置。
* **archive** 保存 Jail archive 命令产生的存档文件，用于 Jail 的备份与恢复。
* **flavors** 包含系统风格（flavors）和用户创建的自定义风格，本质上是预定义的配置文件集合，用于快速定制新 Jail 的配置。

文件结构：

```sh
/usr/jails/
├── sharedfs/         # 只读操作系统库（各 Jail 共享）
├── template/         # 配置文件模板
├── archive/          # Jail 存档文件
├── flavors/          # 系统风格配置
│   └── default/
│       └── usr/
│           └── local/
│               └── etc/
│                   └── pkg/
│                       └── repos/
│                           └── FreeBSD.conf  # 自定义 pkg 镜像配置
├── jail1/            # jail1 的根目录
└── jail2/            # jail2 的根目录
    └── usr/
        └── local/
            └── etc/
                └── pkg/
                    └── repos/
                        └── FreeBSD.conf  # 自动复制的配置
```

## 部署 Jail

创建 Jail jail1，指定网络接口和 IPv4 地址：

```sh
# qjail create -n lo1 -4 192.168.1.1 jail1
```

* `-n` 指定使用 lo1 作为网络接口
* `-4` 指定 IPv4 地址

生成 jail1 后，**/usr/jails/** 目录下会创建 `jail1` 目录（**/usr/jails/jail1/**）用于保存对应文件。

可在前述的 `flavors` 目录中创建自定义配置文件，以便在部署新的 Jail 时自动复制。例如，新建 **/usr/jails/flavors/default/usr/local/etc/pkg/repos/FreeBSD.conf** 文件，则之后创建的 Jail 会自动复制该文件：

```sh
# qjail create -n lo1 -4 192.168.1.2 jail2
```

建立 jail2 后，自动生成 **/usr/jails/jail2/usr/local/etc/pkg/repos/FreeBSD.conf** 文件，即修改了此后所有 Jail 的默认 `pkg` 镜像。但由于生成 jail1 时尚未在 flavors 目录中写入相应文件，jail1 未生成该文件。

## Qjail 基本用法

Qjail 管理的 Jail 本质上仍是标准的 FreeBSD Jail，因此 `jls`、`jexec` 等系统命令同样可用于查看和操作这些 Jail。以下为 Qjail 专属管理命令。

列出 Qjail 管理的 Jail：

```sh
# qjail list
```

启用 Jail：

```sh
# qjail start # 启动所有 Jail
# qjail start jail1 # 启动 jail1
```

停止 Jail：

```sh
# qjail stop # 停止所有 Jail
# qjail stop jail1 # 停止 jail1
```

重启 Jail：

```sh
# qjail restart # 重启所有 Jail
# qjail restart jail1 # 重启 jail1
```

进入 Jail 控制台：

```sh
# qjail console jail1
```

进入 Jail 控制台后，将以 Jail 中的 root 账户身份操作（无需输入密码）。由于 Jail 可能开启对外服务，为安全起见，建议设置 root 账户密码。

备份 Jail：

```sh
# qjail archive -A  # 备份所有 Jail
# qjail archive jail1  # 备份 jail1
```

从备份中恢复 Jail：

```sh
# qjail restore jail1
```

删除 Jail：

```sh
# qjail delete jail1  # 删除 jail1
# qjail delete -A     # 删除所有 Jail
```

## 更新 Jail

Jail 的部分更新并非针对单个 Jail，而是针对所有 Jail，这些文件通过 nullfs 共享一份。

### 更新 Jail 中的基本系统

更新 sharedfs 中的文件：

```sh
# qjail update -b
```

### 更新 ports

选项 `-P`（大写）使用宿主机的 Ports 更新 Jail 的 Ports 树。

```sh
# qjail update -P
```

### 更新系统源代码

```sh
# qjail update -S # S 大写
```

### 更新过程

完整的更新流程如下：

获取并安装 FreeBSD 系统更新：

```sh
# freebsd-update fetch
# freebsd-update install
```

停止所有 qjail Jail：

```sh
# qjail stop
```

更新 qjail 基本系统：

```sh
# qjail update -b
```

更新 qjail 源代码：

```sh
# qjail update -S
```

更新 qjail Ports：

```sh
# qjail update -P
```

启动所有 qjail Jail：

```sh
# qjail start
```

## Jail 设置

Qjail 提供 `qjail config` 命令，用于定制每个 Jail 的配置。Jail 运行时核心配置参数无法安全修改，运行该命令前须先停止目标 Jail。

`qjail config` 命令提供丰富的配置选项，以下列出几个常用参数。

### `-h`

为 jail1 快速配置 SSH 服务：

```sh
# qjail config -h jail1
```

该命令会快速启用 jail1 的 SSH 服务，新建一个 wheel 组用户，用户名和密码均与 jail1 名称相同，首次使用该用户登录时，系统会要求修改密码。也可在登录 jail1 控制台后，自行配置 `sshd` 服务。

### `-m`、`-M`

设置 jail1 为手动启动状态：

```sh
# qjail config -m jail1
```

将 jail1 设为手动启动状态（manual 状态）。`qjail_enable="YES"` 写入 **/etc/rc.conf** 文件后，系统启动时将自动启动各个 Jail；设为手动启动后，系统启动时不会自动启动相应 Jail，须以 `qjail start jailname` 手动启动。

对应小写的 `-m` 选项，有大写的 `-M` 选项，作用为关闭手动启动状态，即清除 manual 状态，可在系统启动时自动启用 Jail。Qjail 中有大量类似的选项，小写字母的选项启用某个功能，大写字母的选项关闭对应功能。下文中同时出现小写和大写的选项时不再说明。

### `-r`、`-R`

将 jail1 设为不允许启动（norun 状态）：

```sh
# qjail config -r jail1
```

该配置相当于禁用 jail1。

### `-y`、`-Y`

启用 jail1 的 System V IPC（进程间通信）机制：

```sh
# qjail config -y jail1
```

System V IPC 是 UNIX 的经典进程间通信机制，包括共享内存、信号量和消息队列，在 jail1 中部署 PostgreSQL 等依赖共享内存和信号量的应用时，必须启用该选项。

## 网络设定

部分教程中提到使用 `qjail config -k jailname` 启用 `raw_sockets` 功能以实现外网访问，这纯属误解。`raw_sockets` 仅为 `ping` 等工具所需，并非网络访问的必要条件。在 Jail 中启用 `raw_sockets` 存在安全风险，这是 Jail 环境默认的安全设计。除非确实需要在 Jail 中使用 `ping` 等工具，不建议启用 `raw_sockets` 功能。

Jail 尚无法连接网络，它绑定在 `lo1` 网络接口上，`lo1` 并不能直接访问外网，需借助 PF 配置网络，其中 `em0` 为外网接口。使用 `ifconfig` 命令可查找系统中的外网接口名称。

在 **/etc/pf.conf** 文件中写入配置。网络地址转换（NAT）可使 Jail 访问外部网络，端口重定向可使外部网络访问指定 Jail 的服务：

```ini
rdr pass on em0 inet proto tcp from any to em0 port 22 -> 192.168.1.1 port 22 # 端口重定向：将 em0 上 22 端口的 TCP 连接转发到 192.168.1.1（jail1）的 22 端口
nat pass on em0 inet from lo1 to any -> em0
```

启动防火墙服务：

```sh
# service pf enable
# service pf start
```

此时，绑定在 lo1 上的 jail 可以访问外部网络，外部网络可以通过宿主机的 22 号端口连接 jail1 的 22 号端口。

## 示例：部署 PostgreSQL Jail

已按前述步骤预留 Jail IP 并成功运行 `qjail install` 命令后，以 PostgreSQL 15 为例进行部署，其他版本也适用。

### 宿主机中操作

创建并配置 PostgreSQL Jail：

创建 jail postgres，绑定到 lo1 接口，IPv4 地址为 **192.168.1.3**：

```sh
# qjail create -n lo1 -4 192.168.1.3 postgres
```

配置 postgres jail，启用 SysV IPC：

```sh
# qjail config -y postgres
```

启动 postgres jail：

```sh
# qjail start postgres
```

编辑 **/etc/pf.conf** 文件：

```ini
nat pass on em0 inet from lo1 to any -> em0
rdr pass on em0 inet proto tcp from any to em0 port 5432 -> 192.168.1.3 port 5432
```

> **注意**
>
> 直接向外提供 PostgreSQL 连接存在安全风险，应根据实际需要谨慎开启端口转发。

启动防火墙服务 PF：

```sh
# service pf start
```

进入 jail postgres 的控制台：

```sh
# qjail console postgres
```

### Jail 控制台中的操作

以下命令均在 Jail 控制台下运行，`pkg` 安装时可根据需要使用镜像。如果使用镜像，可在 Jail 控制台中以和宿主机相同的方式进行设置，详见相关章节。

#### 配置 PostgreSQL 数据集

安装 PostgreSQL 的过程略去，详见本书其他章节。

设置 PostgreSQL 服务开机自启动：

```sh
# service postgresql enable
```

创建 PostgreSQL 数据目录（注意版本号）：

```sh
# mkdir -p -m 0700 /var/db/postgres/data15
```

设置数据目录属主为 postgres 用户：

```sh
# chown postgres:postgres /var/db/postgres/data15
```

切换到 postgres 用户：

```sh
# su postgres
```

初始化 PostgreSQL 数据库：

```sh
$ initdb -A scram-sha-256 -E UTF8 -W -D /var/db/postgres/data15
```

此处使用 `initdb` 而非安装时提示的 **/usr/local/etc/rc.d/postgresql initdb**，目的是避免在设置数据库密码时反复修改 `pg_hba.conf` 文件。对各选项简要说明如下：

* `-A` 为本地用户指定在 `pg_hba.conf` 中使用的默认认证方法
* `-E` 选择模板数据库的编码
* `-W` 使 initdb 提示为数据库超级用户设置口令
* `-D` 指定数据库集簇应该存放的目录

回到 jail root 用户：

```sh
$ exit
```

立即启动 PostgreSQL 服务：

```sh
# service postgresql start
```

如在上述过程中未使用 `qjail config -y postgres` 命令开启 SysV IPC，可能会出现如下错误：

#### 初始化数据库集簇时出错

![初始化错误](/files/05RvQaT1gOEbVAmH1daE)

启动 PostgreSQL 时出现的错误。

![启动错误](/files/mtEeuaZkvkXJeYb8uBMU)

此时在宿主机控制台下执行 `qjail config -y postgres` 即可修正错误，具体如下：

停止 postgres jail：

```sh
# qjail stop postgres
```

配置 postgres jail，启用 SysV IPC：

```sh
# qjail config -y postgres
```

启动 postgres jail：

```sh
# qjail start postgres
```

再次进入 Jail 的控制台后，即可正常初始化数据库集簇并运行 PostgreSQL 服务。

## 参考文献

* FreeBSD Project. qjail(8) - Utility for deployment of jail environments\[EB/OL]. \[2026-04-17]. <https://man.freebsd.org/cgi/man.cgi?query=qjail&sektion=8>. Qjail 官方手册页，涵盖所有命令参数与配置选项。
* Qjail Project. Qjail on SourceForge\[EB/OL]. \[2026-04-17]. <https://qjail.sourceforge.net/>. Qjail 项目主页，介绍其作为第四代 Jail 管理工具的设计理念。
* FreeBSD Project. iocage(8)\[EB/OL]. \[2026-04-17]. <https://man.freebsd.org/cgi/man.cgi?query=iocage&sektion=8>. iocage 手册页，该工具依赖 ZFS 提供 Jail 管理功能。


---

# 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://book.bsdcn.org/di-19-zhang-jail-rong-qi-guan-li/di-19.3-jie-qjail-guan-li-gong-ju.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.
