FreeBSD Thin(薄) Jail
FreeBSD Thin Jail是基于 ZFS (OpenZFS) 快照 或模板 和 NullFS 来创建的 瘦 Jail
设置环境变量
为方便调整,我设置了环境变量来方便后续操作
export jail_dir="zdata/jails"
export bsd_ver="14.3"
# 在FreeBSD中root用户的shell默认是sh,所以调整 ~/.shrc
echo 'jail_dir="zdata/jails"' >> ~/.shrc
echo 'bsd_ver="14.3"' >> ~/.shrc
OpenZFS快照Thin Jail
为模板创建发行版,这个是只读的:
zfs create -p zroot/jails/templates/14.2-RELEASE
和 FreeBSD Thick(厚) Jail 一样下载用户空间:
fetch https://download.freebsd.org/ftp/releases/amd64/amd64/$bsd_ver-RELEASE/base.txz -o /$jail_dir/media/$bsd_ver-RELEASE-base.txz
将下载内容解压缩到模板目录:
tar -xf /usr/local/jails/media/14.2-RELEASE-base.txz -C /usr/local/jails/templates/14.2-RELEASE --unlink
将时区和DNS配置复制到模板目录:
cp /etc/resolv.conf /usr/local/jails/templates/14.2-RELEASE/etc/resolv.conf
cp /etc/localtime /usr/local/jails/templates/14.2-RELEASE/etc/localtime
更新模板补丁:
freebsd-update -b /usr/local/jails/templates/14.2-RELEASE/ fetch install
从模板创建 ZFS 快照:
一旦创建了 OpenZFS 快照,就可以使用 OpenZFS 克隆功能创建无限个 jail
zfs clone zroot/jails/templates/14.2-RELEASE@base zroot/jails/containers/dev-1
zfs clone zroot/jails/templates/14.2-RELEASE@base zroot/jails/containers/dev-2
# 再增加一个dev
zfs clone zroot/jails/templates/14.2-RELEASE@base zroot/jails/containers/dev
准备
dev-1
Thin Jail配置/etc/jail.conf.d/dev-1.conf
(dev-2.conf
类似,只有主机名和IP不同):
dev-1.conf
dev-1 {
# 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;
# HOSTNAME/PATH
host.hostname = "${name}";
path = "/usr/local/jails/containers/${name}";
# NETWORK
# ip4 = inherit;
ip4.addr = 10.0.0.10/24;
interface = wifibox0;
}
启动Thin Jail容器:
service jail start dev-1
service jail start dev-2
# 再启动一个dev
service jail start dev
警告
OpenZFS快照Thin Jail实际上有一个 致命限制 : ZFS snapshot是不可更改的
虽然 snapshot => clone
极大地节约了磁盘空间,但是如果需要更新OpenZFS snapshot Thin Jails,需要:
destroy 所有的 clone,也就是销毁Thin Jails
更新ZFS卷,也就是上文案例中的
zroot/jails/templates/14.3-RELEASE
,当然可以是再建立一个卷,例如zroot/jails/templates/14.3-RELEASE
重新走一遍 ZFS snapshot => ZFS clone ,来构建Thin Jails
也就是说,这是一个重建的过程,和 Docker 的镜像发布非常相似: 不存在更新snapshot从而达到所有clone自动更新的效果
好处是和 Docker 容器镜像发布是一个原理,如果你熟悉容器管理,其实就是一样的技术。在大规模部署时,假设类似 Kubernetes ,可以采用滚动销毁旧Jail,然后从新的snapshot上clone出新的Jail来实现升级。
相关讨论可以参考 Upgrade thin jail with ZFS snapshot
NullFS thin Jail
备注
目前我主要使用 NullFS thin Jail
,在 VNET + Thin Jail 中就是采用这种方式。此外,我在 FreeBSD桌面 环境也采用 NullFS thin Jail
,但没有结合 FreeBSD VNET Jail (因为桌面没有多个IP地址可以分配),具体案例见 桌面环境 Thin Jail
通过结合 Thin Jail 和 NullFS
技术也可以创建节约系统文件存储开销(类似于 ZFS snapshot
clone出来的卷完全不消耗空间),并且能够将Host主机的我呢见目录共享给 多个 Jail。
备注
实际上直接使用 ZFS snapshot 创建 thin jail 和 使用 NullFS 创建 thin jail 的区别在于:
ZFS snapshot 是只读的,不可更改的
,这意味着 没有办法简单通过更新共享的ZFS snapshot来实现Thin Jail操作系统NullFS直接使用ZFS dataset是可读写的
,也就是 直接更新NullFS底座共享的ZFS dataset可以瞬间更新所有Thin Jail
所以,两者区别仅在于创建Thin Jail的开始步骤:
将FreeBSD Release base存放在 只读 的
14.3-RELEASE@base
快照 - OpenZFS snapshot Thin Jail将FreeBSD Relaase base存放在 读写 的
14.3-RELEASE-base
数据集 - NullFS Thin Jail
备注
这里创建的 Thin Jail Using NullFS
是基于 ZFS 完成的实践。实际上,基于NullFS的Thin Jail也可以使用传统的UFS构成。由于我在阿里云租用的VM默认文件系统是UFS,所以我独立再记录到 在UFS文件系统上构建Thin Jail 中。
创建 读写模式 的
14.3-RELEASE-base
(注意,大家约定俗成@base
表示只读快照,-base
表示可读写数据集)
14.3-RELEASE-base
数据集zfs create -p $jail_dir/templates/$bsd_ver-RELEASE-base
和 FreeBSD Thick(厚) Jail 一样下载用户空间:
fetch https://download.freebsd.org/ftp/releases/amd64/amd64/$bsd_ver-RELEASE/base.txz -o /$jail_dir/media/$bsd_ver-RELEASE-base.txz
将下载内容解压缩到模板目录:
14.3-RELEASE-base
后续不需要创建快照,直接使用)tar -xf /$jail_dir/media/$bsd_ver-RELEASE-base.txz -C /$jail_dir/templates/$bsd_ver-RELEASE-base --unlink
将时区和DNS配置复制到模板目录:
cp /etc/resolv.conf /$jail_dir/templates/$bsd_ver-RELEASE-base/etc/resolv.conf
cp /etc/localtime /$jail_dir/templates/$bsd_ver-RELEASE-base/etc/localtime
更新模板补丁:
freebsd-update -b /$jail_dir/templates/$bsd_ver-RELEASE-base/ fetch install
关键部分来了,以下是NullFS特别部分
创建一个特定数据集
skeleton
(骨骼) ,这个 "骨骼"skeleton
命名非常形象,用意就是构建特殊的支持大量thin jial的框架底座
skeleton
(骨骼)zfs create -p $jail_dir/templates/$bsd_ver-RELEASE-skeleton
执行以下命令,将特定目录移入
skeleton
数据集,并构建base
和skeleton
必要目录的软连接关系(注意:刚开始步骤有报错,我修改了方法)
mkdir -p /usr/local/jails/templates/14.2-RELEASE-skeleton/home
mkdir -p /usr/local/jails/templates/14.2-RELEASE-skeleton/usr
# etc目录包含发行版提供的配置文件
mv /usr/local/jails/templates/14.2-RELEASE-base/etc /usr/local/jails/templates/14.2-RELEASE-skeleton/etc
# local目录是空的
mv /usr/local/jails/templates/14.2-RELEASE-base/usr/local /usr/local/jails/templates/14.2-RELEASE-skeleton/usr/local
# tmp 目录是空的
mv /usr/local/jails/templates/14.2-RELEASE-base/tmp /usr/local/jails/templates/14.2-RELEASE-skeleton/tmp
# var 目录有很多预存目录,其中移动时有报错显示 var/empty 目录没有权限
mv /usr/local/jails/templates/14.2-RELEASE-base/var /usr/local/jails/templates/14.2-RELEASE-skeleton/var
# root 目录是管理员目录,有基本profile文件
mv /usr/local/jails/templates/14.2-RELEASE-base/root /usr/local/jails/templates/14.2-RELEASE-skeleton/root
这里有一个报错,在 mv var
到 skeleton
ZFS数据集下时会报错,显示其中 var/empty
目录没有权限删除: mv var/empty: Operation not permitted
,我后来是通过将上一级目录重命名来解决的 mv var var.bak
,具体方法按照 VNET + Thin Jail :
skeleton
数据集mkdir -p /$jail_dir/templates/$bsd_ver-RELEASE-skeleton/home
mkdir -p /$jail_dir/templates/$bsd_ver-RELEASE-skeleton/usr
# 我单独创建一个docs目录用于后续将host主机的docs映射到jail中
mkdir -p /$jail_dir/templates/$bsd_ver-RELEASE-skeleton/docs
# etc目录包含发行版提供的配置文件
mv /$jail_dir/templates/$bsd_ver-RELEASE-base/etc /$jail_dir/templates/$bsd_ver-RELEASE-skeleton/etc
# local目录是空的
mv /$jail_dir/templates/$bsd_ver-RELEASE-base/usr/local /$jail_dir/templates/$bsd_ver-RELEASE-skeleton/usr/local
# tmp 目录是空的
mv /$jail_dir/templates/$bsd_ver-RELEASE-base/tmp /$jail_dir/templates/$bsd_ver-RELEASE-skeleton/tmp
# var 目录有很多预存目录,但是直接mv移动时有报错显示 var/empty 目录没有权限,这会导致目标目录破坏,所以改为rsync同步
rsync -avz /$jail_dir/templates/$bsd_ver-RELEASE-base/var /$jail_dir/templates/$bsd_ver-RELEASE-skeleton/var
mv /$jail_dir/templates/$bsd_ver-RELEASE-base/var /$jail_dir/templates/$bsd_ver-RELEASE-base/var.bak
# root 目录是管理员目录,有基本profile文件
mv /$jail_dir/templates/$bsd_ver-RELEASE-base/root /$jail_dir/templates/$bsd_ver-RELEASE-skeleton/root
执行以下命令创建软连接
skeleton
软连接cd /$jail_dir/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
# 这里增加一个docs目录用于存放数据(从Host主机映射到jail中)
ln -s skeleton/docs docs
补充步骤 (实践发现需要修正certs软连接,见 VNET + Thin Jail ) 修复
/etc/ssl/certs
目录下证书文件软链接
#cd /$jail_dir/templates/$bsd_ver-RELEASE-base/etc/ssl/certs
cd /$jail_dir/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则非常方便使用快照:
skeleton
创建快照,也就是用于jailzfs snapshot $jail_dir/templates/$bsd_ver-RELEASE-skeleton@base
# 假设这里创建名为dev的jail
jail_name=dev
zfs clone $jail_dir/templates/$bsd_ver-RELEASE-skeleton@base $jail_dir/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.3-RELEASE-base 6.1T 446M 6.1T 0% /zdata/jails/templates/14.3-RELEASE-base
zdata/jails/templates/14.3-RELEASE-skeleton 6.1T 4.4M 6.1T 0% /zdata/jails/templates/14.3-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_dir/$jail_name-nullfs-base
之前构建
d2l
ZFS snapshot Thin Jail时,共用/etc/jail.conf
:
/etc/jail.conf
# 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;
# HOSTNAME/PATH
host.hostname = "${name}";
# NETWORK
#ip4 = inherit;
interface = wifibox0;
.include "/etc/jail.conf.d/*.conf";
以及 /etc/jail.conf.d/dev.conf
(其中网络部分是 FreeBSD VNET Jail 配置,可忽略):
/etc/jail.conf.d/dev.conf
配置dev {
# 这里 devfs_ruleset 和Linux Jail的4不同
devfs_ruleset=5;
# 去除了 ip4.addr 配置
# HOSTNAME/PATH
path = "/usr/local/jails/${name}-nullfs-base";
# VNET/VIMAGE
vnet;
vnet.interface = "${epair}b";
# NETWORKS/INTERFACES
$id = "10";
$ip = "10.0.0.${id}/24";
$gateway = "10.0.0.1";
$bridge = "wifibox0";
$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 = "/usr/local/jails/${name}-nullfs-base.fstab";
}
注意,这里配置引用了一个针对nullfs的fstab配置,所以还需要创建一个
/usr/local/jails/dev-nullfs-base.fstab
/usr/local/jails/dev-nullfs-base.fstab
配置 dev
Jail的nullfs挂载/usr/local/jails/templates/14.2-RELEASE-base /usr/local/jails/dev-nullfs-base/ nullfs ro 0 0
/usr/local/jails/containers/dev /usr/local/jails/dev-nullfs-base/skeleton nullfs rw 0 0
启动
dev
:
dev
jailservice jail start dev
然后通过 rexec dev
进入jail进行观察,就会明白 NullFS
构建的 FreeBSD Thin(薄) Jail 巧妙之处:
dev
检查# df -h
Filesystem Size Used Avail Capacity Mounted on
/usr/local/jails/templates/14.2-RELEASE-base 893G 446M 893G 0% /
# ls -lh /
total 159
-rw-r--r-- 1 root wheel 1.0K Nov 29 17:58 .cshrc
-rw-r--r-- 1 root wheel 495B Nov 29 17:58 .profile
-r--r--r-- 1 root wheel 6.0K Nov 29 18:42 COPYRIGHT
drwxr-xr-x 2 root wheel 49B Nov 29 17:58 bin
drwxr-xr-x 14 root wheel 68B Nov 29 18:42 boot
dr-xr-xr-x 11 root wheel 512B Jan 9 15:02 dev
lrwxr-xr-x 1 root wheel 12B Jan 9 13:07 etc -> skeleton/etc
lrwxr-xr-x 1 root wheel 13B Jan 9 13:08 home -> skeleton/home
drwxr-xr-x 4 root wheel 78B Nov 29 18:20 lib
drwxr-xr-x 3 root wheel 5B Nov 29 17:57 libexec
drwxr-xr-x 2 root wheel 2B Nov 29 17:54 media
drwxr-xr-x 2 root wheel 2B Nov 29 17:54 mnt
drwxr-xr-x 2 root wheel 2B Nov 29 17:54 net
dr-xr-xr-x 2 root wheel 2B Nov 29 17:54 proc
drwxr-xr-x 2 root wheel 150B Nov 29 18:15 rescue
lrwxr-xr-x 1 root wheel 13B Jan 9 13:08 root -> skeleton/root
drwxr-xr-x 2 root wheel 150B Nov 29 18:23 sbin
drwxr-xr-x 8 root wheel 8B Jan 9 13:00 skeleton
lrwxr-xr-x 1 root wheel 11B Nov 29 17:54 sys -> usr/src/sys
lrwxr-xr-x 1 root wheel 12B Jan 9 13:18 tmp -> skeleton/tmp
drwxr-xr-x 13 root wheel 14B Jan 9 13:18 usr
lrwxr-xr-x 1 root wheel 12B Jan 9 14:29 var -> skeleton/var
NullFS简析
在 NullFS
Jail中,只有特定的从数据集 14.3-RELEASE-base
移出到 14.3-RELEASE-skeleton
并被快照clone成 skeleton
的数据才是可读写的。这部分通过软连接和 只读 挂载的 14.3-RELEASE-base
关联。
通过数据集 14.3-RELEASE-base 只读挂载确保Jail不会修改基础软件,同时由于是数据集,所以可以在Host进行滚动更新
这里有一个非常巧妙的构思: 13.2-RELEASE-skeleton@base
将部分需要根据每个jail变化的部分单独摘除出来( /etc
/ /usr/local
/ root
/ /tmp
/ /var
),这样后续clone出来的这部分每个jail都可以各自读写没有障碍。
如果需要为所有Jail部署相同的软件和配置,例如 sudo
和 ssh
,则可以在 chroot
14.3-RELEASE-base
目录下进行安装更新,完成后只要对 13.2-RELEASE-skeleton
做依次快照 @base
,那么这部分增加的软件配置会被所有NullFS Jail使用。唯一的缺点是,这部分非 base
的软件包安装是使用ZFS snapshot,是不能自动为每个Jail更新的,这部分定制需要在每个Jail中对 clone
内容进行更新( snapshot
是死的,更新需要重建所有Jail,这也是没有办法的办法,相当于底座替换)