.. _linux_jail:
=====================
FreeBSD Linux Jail
=====================
FreeBSD 可以使用 :ref:`linuxulator` 和 ``debootstrap`` 在jail中运行Linux。由于jail没有内核,所以物理主机上需要启用Linux二进制兼容:
.. note::
由于我的笔记本 :ref:`freebsd_wifi_bcm43602` 需要运行Linux :ref:`bhyve` 虚拟机,所以已经启用了 :ref:`linuxulator` 支持
- 在启动时启用Linux ABI:
.. literalinclude:: linux_jail/linux_enable
:caption: 启动时启用Linux ABI
- 一旦配置了启动时启用Linux ABI,就可以立即用命令启用linux兼容,无需重启操作系统
.. literalinclude:: linux_jail/linux_start
:caption: 启动Linux兼容
准备 :ref:`thin_jail`
==================================
为方便调整,我设置了环境变量来方便后续操作
.. literalinclude:: vnet_thin_jail/env
:caption: 设置 jail目录和release版本环境变量
:ref:`thin_jail` 初始化
----------------------------------
需要先构建一个FreeBSD常规Jail,例如 :ref:`thin_jail` ,但是不能直接执行配置,而是在配置前还有一个构建Linux兼容层的步骤。
- 现在先完成一个常规的 :ref:`thin_jail` (部分步骤已经在 :ref:`thin_jail` 做过,所以跳过):
(已完成,跳过)为模板创建发行版,这个是只读的:
.. literalinclude:: thin_jail/zfs
:caption: 在ZFS中创建模板数据集
(已完成,跳过)和 :ref:`thick_jail` 一样下载用户空间:
.. literalinclude:: vnet_thin_jail/fetch
:caption: 下载用户空间
(已完成,跳过)将下载内容解压缩到模板目录:
.. literalinclude:: thin_jail/template
:caption: 内容解压缩到模板目录
(已完成,跳过)将时区和DNS配置复制到模板目录:
.. literalinclude:: thin_jail/template_conf
:caption: 时区和DNS配置复制到模板目录
(已完成,跳过)更新模板补丁:
.. literalinclude:: thin_jail/template_update
:caption: 更新补丁
(已完成,跳过)从模板创建 :ref:`zfs` 快照:
.. literalinclude:: thin_jail/template_snapshot
:caption: 模板创建 :ref:`zfs` 快照
.. note::
以上步骤因为已经在实践 :ref:`thin_jail` 时做过,只需要做一次,所以在这里创建 Linux jail 的时候仅记录,但跳过不支持。
**下面的步骤才是实际创建名为 d2l 的thin jail**
- ``需要执行`` 使用 OpenZFS 克隆功能创建名为 ``d2l`` 的thin jail,为后续构建 Linux jail 做准备:
.. literalinclude:: linux_jail/d2l
:caption: 使用 OpenZFS 克隆功能创建名为 ``d2l`` 的thin jail
第一次启动 :ref:`thin_jail` d2l ( ``我跳过这步`` )
--------------------------------------------------------------
.. warning::
我仔细看了FreeBSD Handbook,感觉手册中说第一次命令行启动 ``d2l`` 这样的jail,并在jail中安装 ``debootstrap`` ,然后执行 ``debootstrap`` 似乎有点折腾。
:ref:`debootstrap` 是可以直接把 :ref:`debian` 系的操作系统直接复制到指定目录的,那么我为何不直接在物理主机上完成?
.. warning::
我最终采用了跳过这步启动,而采用host主机上执行 :ref:`debootstrap`
.. note::
现在我们启动一个常规的 :ref:`thin_jail` 名为 ``d2l`` ,但是还没有Linux兼容层的内容,需要启动以后通过 :ref:`debootstrap` 获取
- 执行以下命令 ``flying`` 方式启动 ``d2l`` :ref:`thin_jail` :
.. literalinclude:: linux_jail/start_j2l_first
:caption: 首次命令 ``flying`` 方式启动 ``d2l`` :ref:`thin_jail`
这里采用命令方式而不是配置文件方式临时启动jail,以便能够进一步通过 :ref:`debootstrap` 来获得兼容
现在使用 ``jls`` 可以看到已经运行起来一个 ``d2l`` 的容器:
.. literalinclude:: linux_jail/start_j2l_first_jls
:caption: 使用 ``jls`` 可以看到已经运行起 ``d2l`` jail
进入jail通过 :ref:`debootstrap` 获取Linux( ``我跳过这步`` )
---------------------------------------------------------------
.. warning::
我最终采用了跳过这步启动,而采用host主机上执行 :ref:`debootstrap`
- 通过 ``jexec`` 进入 ``d2l`` jail:
.. literalinclude:: linux_jail/exec_d2l
:caption: 通过 ``jexec`` 进入 ``d2l`` jail
- 完成后在host主机上停止 ``d2l`` :
.. literalinclude:: linux_jail/stop_d2l
:caption: 停止 ``d2l`` jail
( ``我的步骤`` )直接Host执行 :ref:`debootstrap` 获取Linux
-------------------------------------------------------------
.. literalinclude:: linux_jail/host_debootstrap
:caption: 在host上完成 :ref:`debootstrap`
完成以后,在 ``/usr/local/jails/containers/d2l/compat/debian`` 目录下就是一个剥离出来独立的debian系统
- 检查对比 ``/etc/jail.conf`` 配置,将 Linux Jail 差异部分都写入 ``/etc/jail.conf.d/d2l.conf`` 中:
.. literalinclude:: linux_jail/d2l.conf
:caption: Linux Jail差异部分配置在 ``/etc/jail.conf.d/d2l.conf``
.. note::
在 ``d2l.conf`` 配置中,最后两行挂载文件系统采用了 ``nullfs`` ,这是一种回环文件系统,用于在host主机和jail之间共享文件: 多个jail可以挂载相同的host主机目录
启动Linux Jail
================
.. literalinclude:: linux_jail/start_d2l
:caption: 启动 ``d2l`` Linux Jail
完成启动 ``d2l`` Linux Jail之后,在Host物理主机上可以看到容器挂载了Linux对应的设备文件系统以及procfs系统,所以此时在Host物理主机上执行 ``df -h`` 会看到:
.. literalinclude:: linux_jail/host_df
:caption: 在Host主机上 ``df -h`` 可以看到Linux的设备兼容层已经挂载
注意,对于Linux Jail的使用其实分两部分:
- 直接使用 ``jexec d2l`` 进入的是常规 FreeBSD Jail
- 执行以下 ``jexec`` 结合 ``chroot`` 将访问 :ref:`debian` 系统Linux二进制兼容:
.. literalinclude:: linux_jail/jexec_chroot
:caption: ``jexec`` 结合 ``chroot`` 将访问 :ref:`debian` 系统Linux二进制兼容
此时虽然 ``df -h`` 看不出差别:
.. literalinclude:: linux_jail/df
:caption: ``df -h`` 看到似乎和之前普通Jail一样挂载
但是执行 ``ls /etc/`` 就可以看到该目录下都是 ``debian`` 相关配置文件,例如 ``cat /etc/debian_version`` 可以看到版本是 ``12.8`` 。接下来的操作就好像是在 :ref:`debian` 中进行,例如可以执行 ``apt update`` 更新debian系统
此时检查Linux环境:
.. literalinclude:: linux_jail/uname
:caption: 使用 ``uname`` 检查Linux环境
输出类似:
.. literalinclude:: linux_jail/uname_output
:caption: 使用 ``uname`` 检查Linux环境输出案例
異常排查
=============
普通用户身份无法使用网络
--------------------------
我发现一个问题,我在 :ref:`linux_jail_init` 中为jail中创建了一个 ``admin`` 帐号(并设置了 ``sudo`` ):
.. literalinclude:: linux_jail_init/admin
:caption: Linux jail中创建用户组和用户admin
但是这个 ``admin`` 用户默认无法使用网络,例如 ``ping www.baidu.com`` 提示错误:
.. literalinclude:: linux_jail_init/admin_sock_error
:caption: Linux jail中普通用户无法使用网络错误
只有通过 ``sudo ping www.baidu.com`` 才行。
这个问题仅在 ``chroot /compat/debian /bin/bash`` 之后才存在,如果没有进入Linux环境,仅仅是FreeBSD :ref:`thin_jail` 环境普通用户是完全正常使用网络的。
:strike:`也就是说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 `_ 我尝试:
.. literalinclude:: linux_jail_init/setcap
:caption: 通过 ``setcap`` 为ping程序手工设置能力
但是设置失败:
.. literalinclude:: linux_jail_init/setcap_error
:caption: 通过 ``setcap`` 为ping程序手工设置能力,但是失败
- ``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`` 则直接返回无输出
.. note::
这个问题还在排查,目前仅发现是普通用户 ``admin`` 身份使用网络异常,其他执行脚本则正常(例如 :ref:`install_conda` 运行下载后的脚本安装正常)
`No internet access from inside jail! `_ 提到设置 ``allow.raw_sockets;`` :
.. literalinclude:: linux_jail/d2l_raw_sockets.conf
:caption: 在Linux Jail ``/etc/jail.conf.d/d2l.conf`` 中添加允许 ``raw_sockets``
:emphasize-lines: 2
但是启动 ``d2l`` 容器之后,Linux的普通用户依然无法使用 ``ping`` 和 ``dig`` 这样的UDP程序。
在没有 ``chroot /compat/debian /bin/bash`` 之前的FreeBSD Jail中可以检查Jail是否允许 ``raw_sockets``
.. literalinclude:: linux_jail/sysctl
:caption: 在FreeBSD Jail中(还没有chroot) ``sysctl`` 检查 ``allow_raw_sockets``
输出显示:
.. literalinclude:: linux_jail/sysctl_output
:caption: 在FreeBSD Jail中(还没有chroot) ``sysctl`` 检查 ``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`` )
.. literalinclude:: linux_jail/ifconfig
:caption: Linux Jail无法使用 ``ifconfig``
:emphasize-lines: 16,19-21,33
同样,由于没有socks支持,运行 :ref:`jupyter` :
.. literalinclude:: linux_jail/juypter_socket_error
:caption: 在Linux Jail中运行 :ref:`jupyter` 出现socket报错
简单小结
~~~~~~~~~~
**简单规律就是在Linux Jail中** :
- TCP协议栈是较为完整的
- 不支持UDP协议栈: DNS查询需要DNS服务器支持TCP查询协议(UDP查询会失败 ``connection refused`` )
- ``root`` 用户可以使用ICMP(ping),但是普通用户不能
- Linux Jail没有socket支持,所以部分需要绑定sockets的服务,如 ``juypter`` 无法启动
- 可能的解决方案: 采用 :ref:`vnet_jail` 来实现独立完整的网络堆栈
``/home/admin`` 属主是root(错误)
------------------------------------
当 ``admin`` 用户通过ssh登录到Jail中(尚未 ``chroot`` 进入debian linux),在尝试向 ``/compat/debian/home/admin`` 目录内复制文件,发现没有权限。检查发现 ``/compat/debian/home/admin`` 目录的属主是 ``root`` ,所以手工修订:
.. literalinclude:: linux_jail/fix_home
:caption: 修订 ``/compat/debian/home/admin`` 目录属主
这里可能有一个问题,Linux系统 :ref:`debootstrap` 没有设置 ``/compat/debian/home/admin`` 内任何内容,这是因为当时目录存在,所以 ``useradd`` 命令没有使用 ``-m`` 参数,也就没有复制任何初始profile相关文件。这在后续 :ref:`install_conda` 时就没有为用户设置好环境。
所以可能需要在一开始就修复好目录,或者直接删除掉目录,创建 ``admin`` 帐号时通过 ``useradd`` 构建
改进的思路
=============
我期望在多个 Linux Jail 之间使用共享的 :ref:`debootstrap` 数据,思路是使用 :ref:`share_folder_between_jails`
参考
======
- `(Solved)How to have network access inside a Linux jail? `_ 这个讨论非常详细,是解决Linux Jail普通用户网络问题的线索
- `(Solved)No internet access from inside jail! `_
- `ping as non-root fails due to missing capabilities #143 `_
- `Setting up a (Debian) Linux jail on FreeBSD `_ 一篇非常详细的Linux Jail实践