# 11.2 Jail 系统更新

## 多 Jail 管理与资源优化策略：分层架构设计

在大规模部署场景中，如何高效管理多个 Jail 实例是一个关键的系统工程问题。共享只读模板与隔离可写层的分层架构，是解决该问题的经典技术方案，该方案通过文件系统的分层挂载机制实现存储空间的高效利用与配置的集中管理。

如果需要同时管理多个 Jail，在更新 Jail 时就需要对每个 Jail 分别执行一次更新操作，这会导致存储空间的大量冗余浪费和管理复杂度的显著增加。通过建立统一的模板，可以使所有 Jail 共用同一个基础环境，同时各自拥有独立的可写空间，互不干扰，从而在保持隔离性的前提下实现资源共享。

本文将建立如下目录结构（系统设计）：

* `/jail/mroot` 是模板目录，是所有 Jail 共用的只读部分，在本例中将被挂载到 `/jail/www`。
* `/jail/skel` 是框架目录，用于方便创建 Jail，本身不直接供任何 Jail 使用。
* `/jail/www` 是 Jail `www` 运行的根目录，也是只读模板的挂载点，本身是个空目录。
* `/jail/www/s` 是 Jail `www` 的可写部分的挂载点，也是个空目录。
* `/jail/files/www` 是 Jail `www` 的可写部分的实际存放位置，将被挂载到 `/jail/www/s`。

相关文件结构图：

```sh
/jail/
├── mroot/           # 只读模板目录（所有 jail 共用）
│   ├── usr/
│   │   ├── ports/   # Ports 树
│   │   └── src/     # 系统源代码
│   └── s/           # 符号链接目标目录
├── skel/            # 框架目录（创建 jail 的模板）
│   ├── home/
│   ├── usr-X11R6/
│   ├── distfiles/
│   ├── portbuild/
│   ├── etc/
│   ├── usr-local/
│   ├── tmp/
│   ├── var/
│   └── root/
├── www/             # jail www 的根目录（空目录，挂载点）
│   └── s/           # jail www 的可写部分挂载点（空目录）
├── files/
│   └── www/         # jail www 的可写部分实际存放位置
└── www.fstab        # jail www 的 fstab 配置文件
```

如果需要创建多个 Jail，则只需重复创建数据目录和项目目录，这样所有 Jail 都可以共用 `/jail/mroot`。

## 安装 cpdup 工具

cpdup 是 FreeBSD 系统中用于高效复制目录结构的专用工具，其设计目标是精确复制文件系统的所有属性，包括权限、时间戳和特殊文件，在 Jail 模板管理场景中具有显著优势。

使用 pkg 安装 cpdup：

```sh
# pkg install cpdup
```

或者使用 Ports 安装：

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

安装完成后，建议使用 `cpdup --version` 验证工具是否正确安装。

## 创建框架目录结构

创建所需 Jail 路径并放入基本目录，同时将 Ports 树和系统源代码放入模板。注意源码对应的版本要与 `/jail/mroot` 的版本相同。获取 FreeBSD 源码可参照相关章节：

```sh
# mkdir -p /jail/mroot    # 创建所需 Jail 路径
# git clone --filter=tree:0 https://mirrors.ustc.edu.cn/freebsd-ports/ports.git /jail/mroot/usr/ports    # 拉取 freebsd-ports Git 
# cpdup /usr/src /jail/mroot/usr/src # 需要提前获取源码，且要注意源码对应的版本要与 /jail/mroot 的版本相同
```

建立符号链接映射，将可写部分链接到可写目录位置：

```sh
# cd /jail/mroot  # 进入模板目录
# mkdir s  # 创建用于符号链接的目标目录
# 注意：在创建符号链接之前，确保相关目录已被移动到骨架目录，或使用 -f 选项强制创建
# ln -sf s/etc etc  # 创建或替换 /etc 的符号链接
# ln -sf s/home home  # 创建或替换 /home 的符号链接
# ln -sf s/root root  # 创建或替换 /root 的符号链接
# ln -sf ../s/usr-local usr/local  # 创建或替换 /usr/local 的符号链接
# ln -sf ../s/usr-X11R6 usr/X11R6  # 创建或替换 /usr/X11R6 的符号链接
# ln -sf ../../s/distfiles usr/ports/distfiles  # 创建或替换 /usr/ports/distfiles 的符号链接
# ln -sf s/tmp tmp  # 创建或替换 /tmp 的符号链接
# ln -sf s/var var  # 创建或替换 /var 的符号链接
```

## 创建框架目录

创建骨架目录及其子目录，然后将可写目录移动到骨架目录：

```sh
# mkdir -p /jail/skel  # 创建骨架目录
# mkdir -p /jail/skel/home /jail/skel/usr-X11R6 /jail/skel/distfiles /jail/skel/portbuild  # 创建子目录
# 移动可写目录到骨架目录
# mv /jail/mroot/etc /jail/skel  # 移动 /etc
# mv /jail/mroot/usr/local /jail/skel/usr-local  # 移动 /usr/local
# mv /jail/mroot/tmp /jail/skel  # 移动 /tmp
# mv /jail/mroot/var /jail/skel  # 移动 /var
# mv /jail/mroot/root /jail/skel  # 移动 /root
```

使用 etcupdate 同步系统配置文件：etcupdate 是 FreeBSD 系统用于管理系统配置文件更新的专用工具，能够在系统升级过程中智能地合并本地修改与系统默认配置，避免配置文件的丢失或冲突。

```sh
# etcupdate -s /jail/mroot/usr/src -d /jail/skel/var/db/etcupdate -D /jail/skel
```

设置 Ports 构建工作目录前缀：通过设置 WRKDIRPREFIX 变量，可将 Ports 构建过程中的临时工作目录统一指向指定路径，便于进行集中管理和空间回收。

```sh
# echo "WRKDIRPREFIX?=/s/portbuild" >> /jail/skel/etc/make.conf
```

## 创建数据目录

复制一份框架目录作为数据目录：

```sh
# cpdup /jail/skel /jail/files/www
```

## 创建项目目录

创建项目目录及其子目录：

```sh
# mkdir /jail/www /jail/www/s
```

## 创建 fstab 文件

编辑 `/jail/www.fstab` 文件。nullfs 是 FreeBSD 提供的一种特殊文件系统，用于在同一主机的不同位置挂载同一文件系统，支持 ro（只读）和 rw（读写）等挂载选项：

```fstab
# 将公共的只读系统挂载到项目目录
/jail/mroot /jail/www nullfs ro 0 0
# 将项目数据目录挂载到项目目录
/jail/files/www /jail/www/s nullfs rw 0 0
```

## `jail.conf` 文件配置示例

以下为 jail.conf 配置示例。使用 `ifconfig` 命令可查找系统中的网络接口名称，请将“网卡名称”替换为实际的网络接口：

```ini
# 全局配置部分
exec.start = "/bin/sh /etc/rc";  # 启动命令
exec.stop = "/bin/sh /etc/rc.shutdown";  # 停止命令
exec.clean;  # 清理执行环境
mount.devfs;  # 挂载 devfs
allow.raw_sockets = 1;  # 允许原始套接字
allow.sysvipc = 1;  # 允许系统 V IPC
interface = "网卡名称";  # 指定网卡，请读者修改为自己的网络接口

# 主机名可使用变量替代
hostname = "$name.domain.local";

# jail 的路径，可使用变量替代
path = "/jail/$name";

# IPv4 地址
ip4.addr = 192.168.1.$ip;

# fstab 文件位置
mount.fstab = "/jail/$name.fstab";

www {    # Jail 名称为 www
    $ip = 2; # 占位变量，此处是指定 IP 为 192.168.1.2
    # 如果不使用 fstab，可覆盖全局配置
    # mount.fstab = "";
}
```

## 课后习题

1. 构建一个完整的分层 Jail 架构，创建模板和三个独立 Jail，共享只读基础系统并验证隔离性。
2. 修改模板中的符号链接映射策略，观察哪些目录变为可写。
3. 尝试不使用 cpdup 而手动创建可写层的符号链接。
