FreeBSD Linux Jail(归档)
FreeBSD 可以使用 Linuxulator 和 debootstrap 在jail中运行Linux。由于jail没有内核,所以物理主机上需要启用Linux二进制兼容:
备注
由于我的笔记本 FreeBSD无线网络BCM43602(通过wifibox) 需要运行Linux bhyve(BSD hypervisor) 虚拟机,所以已经启用了 Linuxulator 支持
在启动时启用Linux ABI:
sysrc linux_enable="YES"
一旦配置了启动时启用Linux ABI,就可以立即用命令启用linux兼容,无需重启操作系统
service linux start
准备 FreeBSD Thin(薄) Jail
为方便调整,我设置了环境变量来方便后续操作
export jail_dir="zdata/jails"
export bsd_ver="14.3"
# 在FreeBSD中root用户的shell默认是sh,所以调整 ~/.shrc
echo 'export jail_dir="zdata/jails"' >> ~/.shrc
echo 'export bsd_ver="14.3"' >> ~/.shrc
FreeBSD Thin(薄) Jail 初始化
需要先构建一个FreeBSD常规Jail,例如 FreeBSD Thin(薄) Jail ,但是不能直接执行配置,而是在配置前还有一个构建Linux兼容层的步骤。
现在先完成一个常规的 FreeBSD Thin(薄) Jail (部分步骤已经在 FreeBSD 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 快照:
备注
以上步骤因为已经在实践 FreeBSD Thin(薄) Jail 时做过,只需要做一次,所以在这里创建 Linux jail 的时候仅记录,但跳过不支持。
下面的步骤才是实际创建名为 d2l 的thin jail
需要执行使用 OpenZFS 克隆功能创建名为d2l的thin jail,为后续构建 Linux jail 做准备:
d2l 的thin jailzfs clone zroot/jails/templates/14.2-RELEASE@base zroot/jails/containers/d2l
第一次启动 FreeBSD Thin(薄) Jail d2l ( 我跳过这步 )
警告
我仔细看了FreeBSD Handbook,感觉手册中说第一次命令行启动 d2l 这样的jail,并在jail中安装 debootstrap ,然后执行 debootstrap 似乎有点折腾。
debootstrap 是可以直接把 Debian 系的操作系统直接复制到指定目录的,那么我为何不直接在物理主机上完成?
警告
我最终采用了跳过这步启动,而采用host主机上执行 debootstrap
备注
现在我们启动一个常规的 FreeBSD Thin(薄) Jail 名为 d2l ,但是还没有Linux兼容层的内容,需要启动以后通过 debootstrap 获取
执行以下命令
flying方式启动d2lFreeBSD Thin(薄) Jail :
flying 方式启动 d2l FreeBSD Thin(薄) Jail# 以下4个变量,这里假设SHELL是 sh 或 bash
NAME=d2l
HOSTNAME=d2l.cloud-atlas.io
IFACE=wifibox0
IP="10.0.0.9/24"
jail -cm \
name="$NAME" \
host.hostname="$HOSTNAME" \
path="/usr/local/jails/containers/$NAME" \
interface="$IFACE" \
ip4.addr="$IP" \
exec.start="/bin/sh /etc/rc" \
exec.stop="/bin/sh /etc/rc.shutdown" \
mount.devfs \
devfs_ruleset=4 \
allow.mount \
allow.mount.devfs \
allow.mount.fdescfs \
allow.mount.procfs \
allow.mount.linprocfs \
allow.mount.linsysfs \
allow.mount.tmpfs \
enforce_statfs=1
这里采用命令方式而不是配置文件方式临时启动jail,以便能够进一步通过 debootstrap 来获得兼容
现在使用 jls 可以看到已经运行起来一个 d2l 的容器:
jls 可以看到已经运行起 d2l jail JID IP Address Hostname Path
1 10.0.0.9 d2l.cloud-atlas.io /usr/local/jails/containers/d2l
进入jail通过 debootstrap 获取Linux( 我跳过这步 )
警告
我最终采用了跳过这步启动,而采用host主机上执行 debootstrap
通过
jexec进入d2ljail:
jexec 进入 d2l jailjexec -u root d2l
# 进入jail以后执行以下2条命令
pkg install debootstrap
# 注意,我使用的是 debian stable 版本
# 如果要安装Ubuntu,则可以使用 jammy 或 noble
debootstrap stable /compat/debian
完成后在host主机上停止
d2l:
停止d2ljailservice jail onestop d2l
( 我的步骤 )直接Host执行 debootstrap 获取Linux
NAME=d2l
JAILPATH="/usr/local/jails/containers/$NAME"
LINUXPATH="$JAILPATH/compat/debian"
debootstrap stable $LINUXPATH
完成以后,在 /usr/local/jails/containers/d2l/compat/debian 目录下就是一个剥离出来独立的debian系统
检查对比
/etc/jail.conf配置,将 Linux Jail 差异部分都写入/etc/jail.conf.d/d2l.conf中:
/etc/jail.conf.d/d2l.confd2l {
devfs_ruleset=4;
ip4.addr="10.0.0.9/24";
# MOUNT
mount += "devfs $path/compat/debian/dev devfs rw 0 0";
mount += "tmpfs $path/compat/debian/dev/shm tmpfs rw,size=1g,mode=1777 0 0";
mount += "fdescfs $path/compat/debian/dev/fd fdescfs rw,linrdlnk 0 0";
mount += "linprocfs $path/compat/debian/proc linprocfs rw 0 0";
mount += "linsysfs $path/compat/debian/sys linsysfs rw 0 0";
mount += "/tmp $path/compat/debian/tmp nullfs rw 0 0";
mount += "/home $path/compat/debian/home nullfs rw 0 0";
}
备注
在 d2l.conf 配置中,最后两行挂载文件系统采用了 nullfs ,这是一种回环文件系统,用于在host主机和jail之间共享文件: 多个jail可以挂载相同的host主机目录
启动Linux Jail
d2l Linux Jailservice jail start d2l
完成启动 d2l Linux Jail之后,在Host物理主机上可以看到容器挂载了Linux对应的设备文件系统以及procfs系统,所以此时在Host物理主机上执行 df -h 会看到:
df -h 可以看到Linux的设备兼容层已经挂载Filesystem Size Used Avail Capacity Mounted on
...
zroot/jails/containers/d2l 903G 750M 902G 0% /usr/local/jails/containers/d2l
devfs 1.0K 0B 1.0K 0% /usr/local/jails/containers/d2l/compat/debian/dev
tmpfs 1.0G 4.0K 1.0G 0% /usr/local/jails/containers/d2l/compat/debian/dev/shm
fdescfs 1.0K 0B 1.0K 0% /usr/local/jails/containers/d2l/compat/debian/dev/fd
linprocfs 8.0K 0B 8.0K 0% /usr/local/jails/containers/d2l/compat/debian/proc
linsysfs 8.0K 0B 8.0K 0% /usr/local/jails/containers/d2l/compat/debian/sys
/tmp 902G 7.8M 902G 0% /usr/local/jails/containers/d2l/compat/debian/tmp
/home 902G 96K 902G 0% /usr/local/jails/containers/d2l/compat/debian/home
devfs 1.0K 0B 1.0K 0% /usr/local/jails/containers/d2l/dev
注意,对于Linux Jail的使用其实分两部分:
直接使用
jexec d2l进入的是常规 FreeBSD Jail执行以下
jexec结合chroot将访问 Debian 系统Linux二进制兼容:
此时虽然 df -h 看不出差别:
df -h 看到似乎和之前普通Jail一样挂载Filesystem Size Used Avail Use% Mounted on
zroot/jails/containers/d2l 903G 693M 903G 1% /
但是执行 ls /etc/ 就可以看到该目录下都是 debian 相关配置文件,例如 cat /etc/debian_version 可以看到版本是 12.8 。接下来的操作就好像是在 Debian 中进行,例如可以执行 apt update 更新debian系统
此时检查Linux环境:
uname 检查Linux环境uname -s -r -m
输出类似:
uname 检查Linux环境输出案例Linux 5.15.0 x86_64
異常排查
普通用户身份无法使用网络
我发现一个问题,我在 Linux Jail初始化(Ubuntu) 中为jail中创建了一个 admin 帐号(并设置了 sudo ):
# 创建uid为501的admin组
groupadd -g 501 admin
# 创建admin用户 (/home/admin目录已经存在)
useradd -g 501 -u 501 -d /home/admin -s /bin/bash admin
# 安装sudo
apt install -y sudo curl
# 设置admin组用户(也就是admin)无需密码sudo
echo "%admin ALL=(ALL:ALL) NOPASSWD: ALL" >> /etc/sudoers.d/admin
# set TIMEZONE to Shanghai
unlink /etc/localtime
ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
但是这个 admin 用户默认无法使用网络,例如 ping www.baidu.com 提示错误:
ping: socktype: SOCK_RAW
ping: socket: Protocol not supported
ping: => missing cap_net_raw+p capability or setuid?
只有通过 sudo ping www.baidu.com 才行。
这个问题仅在 chroot /compat/debian /bin/bash 之后才存在,如果没有进入Linux环境,仅仅是FreeBSD FreeBSD Thin(薄) Jail 环境普通用户是完全正常使用网络的。
也就是说Linux Jail中普通用户无法使用网络?
通过 getcap 命令可以获得程序能力设置属性( getcap: command not found ),不过,在linux jail环境中, getcap 执行没有效果(无返回内容)
Pinging from a base user wsl tumbleweed 5.15.90.1-microsoft-standard-WSL2 看起来Windows的WSL环境中普通用户也不能执行 ping 。
参考 ping as non-root fails due to missing capabilities #143 我尝试:
setcap 为ping程序手工设置能力sudo setcap cap_net_raw+ep /usr/bin/ping
但是设置失败:
setcap 为ping程序手工设置能力,但是失败unable to set CAP_SETFCAP effective capability: Operation not permitted
admin用户使用ssh程序正常,可以远程访问其他系统似乎和UDP协议相关会失败,而TCP协议似乎正常:
尝试
dig www.baidu.com提示communications error to 192.168.0.1#53: connection refused,但是nslookup www.baidu.com能常常解析尝试
nc -v 192.168.0.1 53显示TCP的53端口正常打开Connection to 192.168.0.1 53 port [tcp/domain] succeeded!,但是指定UDP协议nc -uv 192.168.0.1 53则直接返回无输出
备注
这个问题还在排查,目前仅发现是普通用户 admin 身份使用网络异常,其他执行脚本则正常(例如 安装Conda 运行下载后的脚本安装正常)
No internet access from inside jail! 提到设置 allow.raw_sockets; :
/etc/jail.conf.d/d2l.conf 中添加允许 raw_socketsd2l {
allow.raw_sockets;
devfs_ruleset=4;
ip4.addr="10.0.0.9/24";
# MOUNT
mount += "devfs $path/compat/debian/dev devfs rw 0 0";
mount += "tmpfs $path/compat/debian/dev/shm tmpfs rw,size=1g,mode=1777 0 0";
mount += "fdescfs $path/compat/debian/dev/fd fdescfs rw,linrdlnk 0 0";
mount += "linprocfs $path/compat/debian/proc linprocfs rw 0 0";
mount += "linsysfs $path/compat/debian/sys linsysfs rw 0 0";
mount += "/tmp $path/compat/debian/tmp nullfs rw 0 0";
mount += "/home $path/compat/debian/home nullfs rw 0 0";
}
但是启动 d2l 容器之后,Linux的普通用户依然无法使用 ping 和 dig 这样的UDP程序。
在没有 chroot /compat/debian /bin/bash 之前的FreeBSD Jail中可以检查Jail是否允许 raw_sockets
sysctl 检查 allow_raw_sockets# 在Jail中检查allow_raw_sockets
# 注意,是FreeBSD Jail,还没有chroot进入Linux层之前检查
sysctl security.jail.allow_raw_sockets
输出显示:
sysctl 检查 allow_raw_sockets ,输出显示1表示允许security.jail.allow_raw_sockets: 1
注意,默认情况下,在Host主机执行 sysctl security.jail.allow_raw_sockets 可以看到 security.jail.allow_raw_sockets: 0 ,也就是默认不允许 raw_sockets 。以上在FreeBS Jail中输出显示 1 是因为我在 d2l.conf 配置中添加了 allow_raw_sockets;
确实发现,在Linux Jail中,无法正常使用 ifconfig 和 ip 命令(在 How to have network access inside a Linux jail? 讨论中提到了在Linux Jail中使用 ip 命令总是错误 Cannot open netlink socket: Address family not supported by protocol )
ifconfigroot@d2l:/# ifconfig
eth0: flags=4419<UP,BROADCAST,RUNNING,PROMISC,MULTICAST> mtu 1500
ether 58:9c:fc:10:ff:b4 (Ethernet)
RX packets 6035264 bytes 8399983590 (7.8 GiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 3804835 bytes 322805861 (307.8 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=4169<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384
unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 (UNSPEC)
RX packets 10473696 bytes 4994159902 (4.6 GiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 10473696 bytes 4994159902 (4.6 GiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
wifibo: error fetching interface information: Invalid argument
root@d2l:/# ifconfig eth0 10.1.1.10 netmask 255.255.255.0
SIOCSIFADDR: Invalid argument
SIOCSIFFLAGS: Invalid argument
SIOCSIFNETMASK: Invalid argument
root@d2l:/# ip addr
1: lo: <LOOPBACK,MULTICAST,UP> mtu 16384 qdisc noqueue state UP qlen 1000
link/ieee1394
2: wifibox0: <BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state UP qlen 1000
link/[209] 58:9c:fc:10:60:55 brd ff:ff:ff:ff:ff:ff
inet 10.0.0.9/24 brd 10.0.0.255 scope global dynamic wifibox0
3: eth0: <BROADCAST,MULTICAST,PROMISC,UP> mtu 1500 qdisc noqueue state UP qlen 1000
link/ether 58:9c:fc:10:ff:b4 brd ff:ff:ff:ff:ff:ff
root@d2l:/# ip addr add 10.1.1.10/24 dev eth0
RTNETLINK answers: Operation not permitted
同样,由于没有socks支持,运行 Jupyter - 数据科学开发平台 :
[I 2025-01-07 14:08:32.945 ServerApp] notebook | extension was successfully loaded.
Traceback (most recent call last):
File "/home/admin/conda/envs/d2l-zh/bin/jupyter-notebook", line 8, in <module>
sys.exit(main())
^^^^^^
File "/home/admin/conda/envs/d2l-zh/lib/python3.11/site-packages/jupyter_server/extension/application.py", line 616, in launch_instance
serverapp = cls.initialize_server(argv=args)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/admin/conda/envs/d2l-zh/lib/python3.11/site-packages/jupyter_server/extension/application.py", line 586, in initialize_server
serverapp.initialize(
File "/home/admin/conda/envs/d2l-zh/lib/python3.11/site-packages/traitlets/config/application.py", line 118, in inner
return method(app, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/admin/conda/envs/d2l-zh/lib/python3.11/site-packages/jupyter_server/serverapp.py", line 2822, in initialize
self.init_httpserver()
File "/home/admin/conda/envs/d2l-zh/lib/python3.11/site-packages/jupyter_server/serverapp.py", line 2620, in init_httpserver
self._find_http_port()
File "/home/admin/conda/envs/d2l-zh/lib/python3.11/site-packages/jupyter_server/serverapp.py", line 2667, in _find_http_port
sockets = bind_sockets(port, self.ip)
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/admin/conda/envs/d2l-zh/lib/python3.11/site-packages/tornado/netutil.py", line 128, in bind_sockets
sock = socket.socket(af, socktype, proto)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/admin/conda/envs/d2l-zh/lib/python3.11/socket.py", line 232, in __init__
_socket.socket.__init__(self, family, type, proto, fileno)
OSError: [Errno 93] Protocol not supported
简单小结
简单规律就是在Linux Jail中 :
TCP协议栈是较为完整的
不支持UDP协议栈: DNS查询需要DNS服务器支持TCP查询协议(UDP查询会失败
connection refused)root用户可以使用ICMP(ping),但是普通用户不能Linux Jail没有socket支持,所以部分需要绑定sockets的服务,如
juypter无法启动可能的解决方案: 采用 FreeBSD VNET Jail 来实现独立完整的网络堆栈
/home/admin 属主是root(错误)
当 admin 用户通过ssh登录到Jail中(尚未 chroot 进入debian linux),在尝试向 /compat/debian/home/admin 目录内复制文件,发现没有权限。检查发现 /compat/debian/home/admin 目录的属主是 root ,所以手工修订:
/compat/debian/home/admin 目录属主sudo chown admin:admin /compat/debian/home/admin
这里可能有一个问题,Linux系统 debootstrap 没有设置 /compat/debian/home/admin 内任何内容,这是因为当时目录存在,所以 useradd 命令没有使用 -m 参数,也就没有复制任何初始profile相关文件。这在后续 安装Conda 时就没有为用户设置好环境。
所以可能需要在一开始就修复好目录,或者直接删除掉目录,创建 admin 帐号时通过 useradd 构建
改进的思路
我期望在多个 Linux Jail 之间使用共享的 debootstrap 数据,思路是使用 在多个Jail间共享文件目录
参考
(Solved)How to have network access inside a Linux jail? 这个讨论非常详细,是解决Linux Jail普通用户网络问题的线索
Setting up a (Debian) Linux jail on FreeBSD 一篇非常详细的Linux Jail实践