FreeBSD Linux Jail
FreeBSD 可以使用 Linuxulator: Linux执行程序兼容 和 debootstrap
在jail中运行Linux。由于jail没有内核,所以物理主机上需要启用Linux二进制兼容:
备注
由于我的笔记本 FreeBSD无线网络BCM43602(通过wifibox) 需要运行Linux bhyve(BSD hypervisor) 虚拟机,所以已经启用了 Linuxulator: Linux执行程序兼容 支持
在启动时启用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 'jail_dir="zdata/jails"' >> ~/.shrc
echo '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
方式启动d2l
FreeBSD 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
进入d2l
jail:
jexec
进入 d2l
jailjexec -u root d2l
# 进入jail以后执行以下2条命令
pkg install debootstrap
# 注意,我使用的是 debian stable 版本
# 如果要安装Ubuntu,则可以使用 jammy 或 noble
debootstrap stable /compat/debian
完成后在host主机上停止
d2l
:
停止d2l
jailservice 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.conf
d2l {
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初始化 中为jail中创建了一个 admin
帐号(并设置了 sudo
):
# 创建uid为1000的admin组
groupadd -g 1000 admin
# 创建admin用户 (/home/admin目录已经存在)
useradd -g 1000 -u 1000 -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_sockets
d2l {
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
)
ifconfig
root@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实践