VNET + Thin Jail
构建 VNET + Thin Jails
主机激活 jail
- 执行以下命令配置在系统启动时启动
Jail
:
# 配置在系统启动时激活Jail功能
sysrc jail_enable="YES"
# 配置所有jails在后台启动
sysrc jail_parallel_start="YES"
Jail目录树
Jail文件的位置没有规定,在FreeBSD Handbook中采用的是 /usr/local/jails
目录。
我为了方便管理和数据存储最大化,使用了 stripe 模式 ZFS 存储池 zdata
下的 /zdata/jails
注意,我使用了 jail_zfs
环境变量来指定ZFS位置,对应目录就是 /$jail_zfs
- 创建jail目录结构
export jail_zfs="zdata/jails"
export bsd_ver="14.2"
# 在FreeBSD中root用户的shell默认是sh,所以调整 ~/.shrc
echo 'jail_zfs="zdata/jails"' >> ~/.shrc
echo 'bsd_ver="14.2"' >> ~/.shrc
zfs create $jail_zfs
zfs create $jail_zfs/media
zfs create $jail_zfs/templates
zfs create $jail_zfs/containers
完成后检查 df -h
可以看到磁盘如下:
Filesystem Size Used Avail Capacity Mounted on
...
zdata 6.1T 96K 6.1T 0% /zdata
zdata/jails 6.1T 104K 6.1T 0% /zdata/jails
zdata/jails/media 6.1T 96K 6.1T 0% /zdata/jails/media
zdata/jails/templates 6.1T 96K 6.1T 0% /zdata/jails/templates
zdata/jails/containers 6.1T 96K 6.1T 0% /zdata/jails/containers
media
将包含已下载用户空间的压缩文件templates
在使用 Thin Jails 时,该目录存储模板(共享核心系统)containers
将存储jail (也就是容器)
Thin(薄) Jail
快照(snapshot)
型 vs. 模板和NullFS
型
FreeBSD Thin Jail是基于 ZFS 快照(snapshot)
或 模板和NullFS
来创建的 瘦 Jail:
-
使用区别:
快照(snapshot)
型: 快照只读的,不可更改的 - 这意味着 没有办法简单通过更新共享的ZFS snapshot来实现Thin Jail操作系统更新模板和NullFS
型: 可以通过 直接更新NullFS底座共享的ZFS dataset可以瞬间更新所有Thin Jail
-
安装区别:
- ZFS snapshot Thin Jail: 将FreeBSD Release base存放在 只读 的
14.2-RELEASE@base
快照上 - NullFS Thin Jail: 将FreeBSD Release base存放在 读写 的
14.2-RELEASE-base
数据集上
- ZFS snapshot Thin Jail: 将FreeBSD Release base存放在 只读 的
我的选择: 模板和NullFS
通过结合Thin Jail 和 NullFS
技术可以创建节约文件系统存储开销(类似于 ZFS snapshot
clone出来的卷完全不消耗空间),并且能够将Host主机的目录共享给 多个 Jail。
- 创建 读写模式 的
14.2-RELEASE-base
(注意,大家约定俗成@base
表示只读快照,-base
表示可读写数据集)
zfs create -p $jail_zfs/templates/$bsd_ver-RELEASE-base
- 下载用户空间:
fetch https://download.freebsd.org/ftp/releases/amd64/amd64/$bsd_ver-RELEASE/base.txz -o /$jail_zfs/media/$bsd_ver-RELEASE-base.txz
- 将下载内容解压缩到模版目录: 内容解压缩到模板目录( 14.2-RELEASE-base 后续不需要创建快照,直接使用)
tar -xf /$jail_zfs/media/$bsd_ver-RELEASE-base.txz -C /$jail_zfs/templates/$bsd_ver-RELEASE-base --unlink
- 将时区和DNS配置复制到模板目录:
cp /etc/resolv.conf /$jail_zfs/templates/$bsd_ver-RELEASE-base/etc/resolv.conf
cp /etc/localtime /$jail_zfs/templates/$bsd_ver-RELEASE-base/etc/localtime
- 更新模板补丁:
freebsd-update -b /$jail_zfs/templates/$bsd_ver-RELEASE-base/ fetch install
关键步骤:以下是NullFS特别部分( skeleton
)
- 创建一个特定数据集
skeleton
(骨骼) ,这个 "骨骼"skeleton
命名非常形象,用意就是构建特殊的支持大量thin jial的框架底座
zfs create -p $jail_zfs/templates/$bsd_ver-RELEASE-skeleton
- 执行以下命令,将特定目录移入
skeleton
数据集,并构建base
和skeleton
必要目录的软连接关系
mkdir -p /$jail_zfs/templates/$bsd_ver-RELEASE-skeleton/home
mkdir -p /$jail_zfs/templates/$bsd_ver-RELEASE-skeleton/usr
# 我单独创建一个docs目录用于后续将host主机的docs映射到jail中
mkdir -p /$jail_zfs/templates/$bsd_ver-RELEASE-skeleton/docs
# etc目录包含发行版提供的配置文件
mv /$jail_zfs/templates/$bsd_ver-RELEASE-base/etc /$jail_zfs/templates/$bsd_ver-RELEASE-skeleton/etc
# local目录是空的
mv /$jail_zfs/templates/$bsd_ver-RELEASE-base/usr/local /$jail_zfs/templates/$bsd_ver-RELEASE-skeleton/usr/local
# tmp 目录是空的
mv /$jail_zfs/templates/$bsd_ver-RELEASE-base/tmp /$jail_zfs/templates/$bsd_ver-RELEASE-skeleton/tmp
# var 目录有很多预存目录,其中移动时有报错显示 var/empty 目录没有权限
mkdir /$jail_zfs/templates/$bsd_ver-RELEASE-skeleton/var
rsync -avz /$jail_zfs/templates/$bsd_ver-RELEASE-base/var/ /$jail_zfs/templates/$bsd_ver-RELEASE-skeleton/var
mv /$jail_zfs/templates/$bsd_ver-RELEASE-base/var /$jail_zfs/templates/$bsd_ver-RELEASE-base/var.bak
# root 目录是管理员目录,有基本profile文件
mv /$jail_zfs/templates/$bsd_ver-RELEASE-base/root /$jail_zfs/templates/$bsd_ver-RELEASE-skeleton/root
执行 mv /$jail_zfs/templates/$bsd_ver-RELEASE-base/var /$jail_zfs/templates/$bsd_ver-RELEASE-skeleton/var
有如下报错:
mv: /zdata/jails/templates/14.2-RELEASE-base/var/empty: Operation not permitted
mv: /zdata/jails/templates/14.2-RELEASE-base/var: Directory not empty
mv: /bin/rm /zdata/jails/templates/14.2-RELEASE-base/var: terminated with 1 (non-zero) status
这是因为 var/empty
目录没有权限删除: mv var/empty: Operation not permitted
警告⚠️,上述mv命令会导致源和目标var目录都残缺损坏,所以千万不要执行,而应该采用rsyc同步目录
所以 必须 先复制目录,也就是使用 rsync
,然后再移除原var目录
- 执行以下命令创建软连接:
cd /$jail_zfs/templates/$bsd_ver-RELEASE-base/
mkdir skeleton
ln -s skeleton/etc etc
ln -s skeleton/home home
ln -s skeleton/root root
ln -s ../skeleton/usr/local usr/local
ln -s skeleton/tmp tmp
ln -s skeleton/var var
我在实践NullFS的Thin Jails,发现移动到 skeleton
目录下的 /etc
目录有一个子目录 /etc/ssl/certs
。这个证书目录下的文件是软链接到 ../../../usr/share/certs/trusted/
目录下的证书文件。由于 /etc
目录移动后会导致这些相对链接失效,所以需要有一个修复软链接的步骤。
如果不执行这个证书软链接修复,则后续host主机上执行 pkg -j <jail_name> install <package_name>
会报错;而在jail中执行 pkg
命令会显示证书相关错误:
Certificate verification failed for /C=US/O=Let's Encrypt/CN=E6
- 修复
/etc/ssl/certs
目录下证书文件软链接:
cd /$jail_zfs/templates/$bsd_ver-RELEASE-skeleton/etc/ssl/certs
# 先保存一份原始列表记录
ls -lh > /tmp/fix_link.txt
# 生成unlink命令
ls | sed "s@^@unlink @" > /tmp/unlink.sh
# 生成fix命令
# 这里不能使用绝对路径链接,否则会报错 link: ffdd40f9.0: Cross-device link
# ls -lh | awk '{print $NF, $(NF-2)}' | cut -c 9- | tail -n +2 | sed "s@^@link @" > /tmp/fix_link.sh
ls -lh | awk '{print $NF, $(NF-2)}' | tail -n +2 | sed 's@^@ln -s ../@' > /tmp/fix_link.sh
# 检查一下 /tmp/fix_link.sh 是否满足要求
# 没有问题在执行以下2条命令
sh /tmp/unlink.sh
sh /tmp/fix_link.sh
- 在
skeleton
就绪之后,需要将数据复制到 jail 目录(如果是UFS文件系统),对于ZFS则非常方便使用快照:
zfs snapshot $jail_zfs/templates/$bsd_ver-RELEASE-skeleton@base
# 假设这里创建名为dev的jail
jail_name=dev
zfs clone $jail_zfs/templates/$bsd_ver-RELEASE-skeleton@base $jail_zfs/containers/$jail_name
- 现在可以看到相关ZFS数据集如下:
Filesystem Size Used Avail Capacity Mounted on
zroot/ROOT/default 192G 1.2G 191G 1% /
...
zdata/jails 6.1T 112K 6.1T 0% /zdata/jails
zdata/jails/media 6.1T 197M 6.1T 0% /zdata/jails/media
zdata/jails/templates 6.1T 104K 6.1T 0% /zdata/jails/templates
zdata/jails/containers 6.1T 96K 6.1T 0% /zdata/jails/containers
zdata/jails/templates/14.2-RELEASE-base 6.1T 446M 6.1T 0% /zdata/jails/templates/14.2-RELEASE-base
zdata/jails/templates/14.2-RELEASE-skeleton 6.1T 4.4M 6.1T 0% /zdata/jails/templates/14.2-RELEASE-skeleton
zdata/jails/containers/dev 6.1T 4.4M 6.1T 0% /zdata/jails/containers/dev
- 创建一个
base
template的目录,这个目录是skeleton
挂载所使用的根目录:
# 创建 dev 所使用的nullfs模板目录,这个目录就是jail的根PATH
# 和常规Jail仅命名 ${name} 不同,采用 ${name}-nullfs-base
mkdir -p /$jail_zfs/dev-nullfs-base
配置jail
- Jail的配置分为公共部分和特定部分,公共部分涵盖了所有jails共有的配置
- 尽可能提炼出Jails的公共部分,这样就可以简化针对每个jail的特定部分,方便编写较稳维护
- 创建所有jail使用的公共配置部分
/etc/jail.conf
(使用了 VNET 模式配置):
# 这里 devfs_ruleset 和Linux Jail的4不同
devfs_ruleset=5;
# STARTUP/LOGGING
exec.start = "/bin/sh /etc/rc";
exec.stop = "/bin/sh /etc/rc.shutdown";
exec.consolelog = "/var/log/jail_console_${name}.log";
# PERMISSIONS
allow.raw_sockets;
exec.clean;
mount.devfs;
allow.mount;
allow.mount.devfs;
enforce_statfs = 1;
# HOSTNAME/PATH - NullFS
host.hostname = "${name}";
path = "/zdata/jails/${name}-nullfs-base";
# NETWORK - VNET/VIMAGE
#ip4 = inherit;
interface = igc0bridge;
vnet;
vnet.interface = "${epair}b";
# common NETWORK config
$gateway = "192.168.7.101";
$bridge = "igc0bridge";
$epair = "epair${id}";
# ADD TO bridge INTERFACE
exec.prestart += "ifconfig ${epair} create up";
exec.prestart += "ifconfig ${epair}a up descr jail:${name}";
exec.prestart += "ifconfig ${bridge} addm ${epair}a up";
exec.start += "ifconfig ${epair}b ${ip} up";
exec.start += "route add default ${gateway}";
exec.poststop = "ifconfig ${bridge} deletem ${epair}a";
exec.poststop += "ifconfig ${epair}a destroy";
# MOUNT
mount.fstab = "/zdata/jails/${name}-nullfs-base.fstab";
.include "/etc/jail.conf.d/*.conf";
在 jail.conf
中一定要配置 allow.mount
相关的3条配置:
allow.mount;
allow.mount.devfs;
enforce_statfs = 1;
这样 dev
jail 中才能执行 df -h
看到
Filesystem Size Used Avail Capacity Mounted on
/zdata/jails/templates/14.3-RELEASE-base 6.1T 457M 6.1T 0% /
/zdata/jails/containers/dev 6.1T 860M 6.1T 0% /skeleton
devfs 1.0K 0B 1.0K 0% /dev
没有这个 allow.mount
在jail中会看到非常奇怪的只有 /
挂载
Filesystem Size Used Avail Capacity Mounted on
/zdata/jails/templates/14.3-RELEASE-base 6.1T 457M 6.1T 0% /
/etc/jail.conf.d/dev.conf
独立配置部分:
dev {
# NETWORKS/INTERFACES
$id = "253";
$ip = "192.168.7.${id}/24";
}
- 注意,这里配置引用了一个针对nullfs的fstab配置,所以还需要创建一个
/zdata/jails/dev-nullfs-base.fstab
:
/zdata/jails/templates/14.2-RELEASE-base /zdata/jails/dev-nullfs-base/ nullfs ro 0 0
/zdata/jails/containers/dev /zdata/jails/dev-nullfs-base/skeleton nullfs rw 0 0
- 最后启动
dev
:
service jail start dev
通过 rexec dev
进入jail
- 设置Jail
dev
在操作系统启动时启动,修改/etc/rc.conf
jail_enable="YES"
jail_parallel_start="YES"
jail_list="dev"
jail_reverse_stop="YES"
脚本辅助配置jail
- 写了一个简单的脚本帮助创建配置文件:
export jail_zfs="zdata/jails"
export bsd_ver="14.2"
jail_name=$1
id=$2
zfs clone $jail_zfs/templates/$bsd_ver-RELEASE-skeleton@base $jail_zfs/containers/$jail_name
mkdir -p /$jail_zfs/$jail_name-nullfs-base
cat << EOF > /$jail_zfs/$jail_name-nullfs-base.fstab
/$jail_zfs/templates/14.2-RELEASE-base /$jail_zfs/$jail_name-nullfs-base/ nullfs ro 0 0
/$jail_zfs/containers/$jail_name /$jail_zfs/$jail_name-nullfs-base/skeleton nullfs rw 0 0
EOF
cat << 'EOF' > /etc/jail.conf.d/$jail_name.conf
JAIL_NAME {
$id = "ID";
$ip = "192.168.7.${id}/24";
}
EOF
sed -i '' "s@JAIL_NAME@$jail_name@" /etc/jail.conf.d/$jail_name.conf
sed -i '' "s@ID@$id@" /etc/jail.conf.d/$jail_name.conf
通过执行
./jail_zfs.sh pg-1 111
就可以创建一个使用 192.168.7.111
为IP的名为 pg-1
的Jail