第十章:极端Jails

我如此喜欢jail的一个原因是,它们可以让你应对奇怪的问题。我们都遇到过那些古老而重要的系统,没有人敢碰,更不用说升级了,但硬件太弱了,你知道它们随时都会崩溃。我们的客户和应用程序需要运行自己的jail,但不需要整个主机。或者你甚至可能想在jail里运行Linux安装。

我们将从可以管理jail的jail开始。

第十章:极端Jails分级 Jails分级 Jail 网络创建父母 Jail创建子 Jails管理子 Jails旧 FreeBSD版本识别你的 Jail掠夺旧服务器复制旧服务器旧版本兼容性在 Jail 里面运行 Linux哪个Linux?创建Devuan Jail帮助 Linux Jails其他 Jail 技巧

分级 Jails

jail可以在host管理员的许可下经营自己的jail。这些分层jail允许您建立更多的访问控制层。分级jail让你从主人那里下放jail管理权。运行jail的jail被称为父母jail,而父母jail内的jail是儿童jail。儿童jail有可能运行自己的儿童jail,使他们既是父母又是孩子。家长jail可以重新启动其孩子,这赋予了用户权力。标准jail和iocage都可以创建一个顶级的家长jail,但你还不能在jail内使用iocage。

children.max 参数(iocage中的 children_max )设置jail可以运行的最大jail数量。这默认为零,阻止jail滋生jail。家长jail可以将一些被允许的孩子委托给儿童jail,让孩子们有自己的孩子。

如果jail是一组经过更改的命名空间的集合,那么分层jail会提供这些经过更改的名称空间的子jail子集。例如,子jail的进程名称空间是父jail进程名称空间的子集。父jail可以看到其命名空间中的所有进程及其子进程,而子进程只能看到自己的进程。父jail的进程名称空间是主机进程名称空间的子集,因此主机可以看到所有进程。父jail仅限于文件系统的一部分,因此子jail仅限于该部分的一个子集。如果父jail只能部分访问内核函数,则子jail可以获得其中的一个子集。

这意味着父母jail不能授予父母没有的儿童jail访问权限。如果不允许父jail挂载文件系统,则子jail也不能。jail不能为其子女分配对其没有的设备节点的访问权限。

分级 Jail 网络

在配置父jail之前,请考虑网络配置。当父母jail试图圈养孩子时,从主人那里管理jail时,一些看似微不足道的限制变得非常不方便。

一个常见的愿望是为每个父节点分配几个IP地址,这样它就可以将这些地址分配给它的子节点。不过,每个IP只能分配给一个jail。父母jail就是jail。如果父jail有自己的vnet,则可以将父jail的委托地址分配给其子女。记住,IP地址属于vnet。如果父jail控制vnet,则此限制不适用。

没有vnet的父jail无法控制其IP地址堆栈。它可以告诉它的孩子继承它的IP设置,这很好,但限制了jail的实用性。您可以配置主机防火墙,将传入连接重定向到绑定到环回接口的特定地址和端口。然而,这一切都显得丑陋而笨拙。

我建议始终在自己的vnet中配置父jail。

创建父母 Jail

创建父jail需要将 children.max设置为该父jail应具有的最大子jail数量。父jail必须能够为其子jail挂载文件系统——如果没有别的,每个子jail都需要一个设备文件系统。在我们的示例中,我们将使用父jail dba1dba2,每个jail都有自己的vnet。jail dba1 用于MariaDB测试,而 dba2 用于Postgres。

这是 jail.conf 中的dba1。它可以支持五个儿童jail,并获得一个连接到网桥的虚拟接口,如第9章所述。我从 jail.conf 顶部的默认值继承了pathhost.hostname 等参数:

用iocage创建一个家长jail与创建任何其他jail非常相似。它的地址条目将比大多数地址条目长得多,您必须设置 children_max 参数。在这里,我创建了jail dba2,IP地址为198.51.100.230 ,运行FreeBSD 12.0,并让它最多启动五个子jail:

虽然你可以在创建jail时设置所有必要的参数,但命令行已经太长了,让我感到不舒服。在这里,我允许 dba2 在其子jail中挂载 /dev

你必须使用标准jails来管理孩子。

创建子 Jails

将设置儿童jail,就像我在主机上设置jail一样,所有配置都在 /etc/jail.conf 中,所有儿童jail数据文件都在jail的 /jail 中。以下是我如何在dba1上为dba1上的一大堆MariaDB jail配置 /etc/jail.conf

以上所有内容都直接来自主机的 jail.conf

jail必须有一个 devfs 。不过,jail无法访问主机的 devd ,也无法创建自己的设备文件系统。他们的 /dev 是父jail的副本。不过,尝试应用规则集是一个错误,因此通过使用 devfs_ruleset 指定规则集 0 ,我们告诉 jail(8) 不要费心尝试应用规则集中。

通过从DNS中提取儿童jail IP地址,我们的jail定义变成了一组都使用默认设置的名称。

dba2上的jail.conf看起来完全像这样,除了全局搜索并将 mariadb 替换为 pg

父母jail现在可以运行 service jail start ,开始并生育他们的孩子。

管理子 Jails

jail可以看到自己的所有孩子,以及所有孩子的孩子。主机可以查看和管理系统上的每个jail。如果我在dba2上运行 jls ,我会看到它的儿童jail。默认的jls视图在这里并不是很有启发性,所以我将专门研究jail名称、jailID和父母。

正如你所料,每个jail的名字都是按照这个父jail的配置命名的。如果你刚刚开始你的jail,你可能会期望你的jail ID以1开头,但这些更高的数字是可以的。jail的父母应该出示父母jail的JID。0 的父级表示“此系统”。

主机上的完全相同的命令显示了其他内容:

前两个jail是 ioc-dba2 ,即“通过iocage运行的 jaildba2 ” 和标准 jaildba1 。他们的父母是0 ,所以他们在主机上运行。

儿童jail的名字以父母、一个句点和儿童jail的名称开头。jaildba1.mariadb1 是在 jaildba1 上运行的 jailmariadb1jailioc-dba2.pg3jailioc-dba2 上运行的 jailpg3 。如果儿童jail有自己的孩子,这些名字可能会很长。

“parent”栏显示了每个儿童jail父母的JID。

host对所有jail拥有最终控制权。作为主机上的root,我可以运行 top -j ioc-dba2.pg4 ,看看这个jail里发生了什么。我可以杀死任意进程。如果一个儿童jail失控,我可以严厉地关闭它。一种方法是在母jail上使用 jexec 并关闭jail,但如果真的有问题,我会发出原始jail命令并把孩子吹走。 -r 标志可以随意删除jail,而无需运行任何关机命令。

问题儿童已移除。有了分级jail,问题用户现在可以拥有他们jail的任何问题。

旧 FreeBSD版本

jail的一个优点是可以避免升级旧系统。每个企业都有足够旧的主机,硬件随时都会消亡,但操作系统和应用程序太古老了,没有一个当前的系统管理员敢碰它。你几乎可以拿起任何FreeBSD 4.0或更高版本的系统,并将其关押在当前的FreeBSD主机上。有些人成功地监禁了FreeBSD 3和更早的版本,但这需要一个支持 a.out 的自定义内核。

我们将通过监禁一个古老的FreeBSD服务器。我的首要建议是不要这样做。重新安装现代操作系统版本和当前应用程序几乎总是更好。软件包集合包括FreeBSD 4的兼容性库。在将旧主机转换为jail之前,试着让你的应用程序与这些主机一起工作。有时,安装新的操作系统版本、兼容性库并将旧服务器的 /usr/local 复制到新主机就足以让这些旧应用程序运行。

然而,有时情况并非如此。我监禁的最后一台古代服务器运行的是FreeBSD 4,PHP 5和MySQL 4。不幸的是,它运行的是一个关键任务应用程序,该应用程序是在内部编写的,并在多年后不断修改。仍然在那里工作的人对应用程序的内部一无所知,甚至没有人愿意尝试重写它,商业替代品花费了数十万美元,底层硬件从废弃的台式机开始。作为系统的负责人,我决定宁愿把它关进jail,也不愿在不可避免的冒烟死亡后试图复活硬件。

这样一个项目的目标不是在jail里完美地复制旧的FreeBSD安装。目标是通过任何必要的手段恢复重要的应用程序。

识别你的 Jail

在尝试将旧服务器转换为jail之前,请调查现有系统。你至少希望你的jail一直撒谎。

许多人使用 uname(1) 的某种变体来获取操作系统版本。这是不正确的做法,但大多数时候都没关系。我在一个不熟悉的系统上做的第一件事就是运行 uname -a ,看看我真正在处理什么。问题是, uname 查询内核。主机内核必须等于或高于jail的操作系统版本。如果uname声明你运行的是FreeBSD 15,你唯一能确定的是你没有运行FreeBSD 16或更高版本。在一个假想的FreeBSD 4 jail中,这是非常误导的。

内核提供了两个sysctls,uname使用它们来提供此信息。 kern.osreldate sysctl 提供 uname -K 的内核发布日期,而 kern.osrelease 提供 uname -r 返回的FreeBSD版本。osreldate 参数允许您设置jail的 kern.osredidate ,而 osrelease 允许您设置kern.oslease 。这些的问题是,你需要知道你正在监禁的FreeBSD版本的这些值。如果主机运行,您可以检查 sysctls 。如果没有,花点时间在搜索引擎上,挖掘出合理的价值。

从FreeBSD 10开始,使用 freebsd-version(1) 打印您正在运行的版本。

掠夺旧服务器

将注定要jail的服务器置于单用户模式。如果它足够旧,可以安装 /proc ,请卸载它。安装某种可移动存储设备,如USB驱动器,甚至NFS共享。对服务器上的每个文件(包括临时目录)进行标记。如果存在 /usr/obj/usr/src/usr/ports 等目录,请获取它们。它们不会占用现代硬盘上的太多空间,并且稍后使用用于构建主机二进制文件的确切源代码可能至关重要。

将原始服务器放在手边,直到你完全确定不再需要它。

复制旧服务器

在jail主机上的新家中提取旧服务器的文件。请确保使用 -p 标志来保留权限。

研究jail的 /etc/fstab 。现在,其中一大堆都无关紧要了——你不需要为 /var/tmp 单独挂载,但这个jail可能需要一个特殊的NFS挂载或类似的挂载。这是一种方便,还是应用程序真的需要它?你可能需要从主机而不是jail提供这样的坐骑。如果应用程序不需要/proc,就去掉它。

jail的 /etc/rc.conf 是事情变得非常有趣的地方。主机处理所有网络功能,因此所有这些设置都可以消失。消除任何提供主机现在处理的服务的守护进程。您被监禁的主机只需要直接支持应用程序的功能。对于我的jail来说,这意味着MySQL和Apache。

抵制住改进应用程序的诱惑。如果web服务器在每次点击时都编译PHP脚本,而不是使用PHP-FPM,那就别管它了。过早的优化将使这个项目失败。

原始服务器是否早于 devfs ?如果是这样,请删除 /dev 的内容。您不能在现代系统上使用旧设备节点。让jail安装一个 devfs

使用主机来保护新jail。古老的数据包过滤器和SSH守护进程可能容易受到入侵者的攻击,所以不要使用它们。使用主机的SSH守护进程提供用户访问权限,然后 jexec 进入jail。使用数据包过滤器将jail与所有不需要访问的人隔离开来。

理论上,jail现在应该运行。您必须执行通常的系统管理员调试,以找到促使您尝试此迁移的所有边缘情况。

旧版本兼容性

FreeBSD与旧版本的二进制兼容性通常很好,但直接访问内核的程序存在问题。像 netstat(1)route(1) 这样的程序希望读取内核内存结构,并在这些结构与预期不一致时阻塞。我在FreeBSD 13主机上运行FreeBSD 12 sockstat(1) 时遇到问题。FreeBSD 4 /bin/ps 无法从现代FreeBSD内核读取进程信息。

一种可能的解决方案是将这些程序的静态二进制文件复制到jail中,覆盖原始程序。/recue 目录包含一大堆程序,这些程序硬链接到一个经过处理的二进制文件 /recue/rescue 。它旨在修复严重损坏的系统。里面有很多有用的东西,从gzip到路由。您可以将主机的 /rescue/ps 复制到jail的 /bin/ps 上并恢复该功能,只要您的自定义应用程序不涉及解析 ps(1) 输出。如果你需要替换多个程序,请使用硬链接,而不是再次复制压缩的二进制文件。

Jetpack容器工具包(https://gitlab.com/jetpack-containers/)包含 jexec-static ,这是一个在jail内的主机上运行静态二进制文件的程序。你可能会发现这在拼凑解决方案时很有用。

如果FreeBSD没有附带可以复制到jail中的程序的静态版本,你可能不得不自己构建该二进制文件。弄清楚如何做到这一点比等待一个有20年历史的硬盘崩溃并拖垮整个公司要容易得多。这并不是说你在做一些真正无法形容的事情,比如在jail里运行Linux。

在 Jail 里面运行 Linux

虽然你可以在jail里运行Linux,但我必须坦率地说:我不建议这样做。这里有野生袋熊、翻新的虎坦克和索伦。在jail里运行Linux比在jail中运行有20年历史的FreeBSD更复杂,需要道德妥协和繁琐的调试。但人们成功地在Linux jail中运行Linux软件。人们在Linux jail中开发了工作设备驱动程序。有时,在jail里运行Linux是解决问题最不可怕的方法。

在jail里运行Linux之前,你必须了解如何使用FreeBSD的Linux模式。如果你从未使用过Linux模式,试图运行Linux jail可能会让你发疯。我将在这里介绍一些基本知识,以提醒那些已经有一段时间没有使用过它的人。

FreeBSD的Linux兼容系统并不是一个真正的兼容系统。FreeBSD内核提供了一个与Linux兼容的应用程序二进制接口(Application Binary Interface——ABI)。ABI是程序从内核请求服务的方式。就软件而言,ABI是核心。通过提供Linux ABI,FreeBSD可以应答来自Linux程序的系统调用。你仍然需要提供用户空间库和程序,但你可以从任何数量的下载网站上获取这些库和程序。FreeBSD的标准Linux模拟器使用 /compat/linux 中的Linux用户区,并运行其中的大多数程序,但我们想更进一步,在jail中运行一个纯Linux用户区。

永远记住Linux模式是不完美的。ABI提供了Linux内核的大部分功能。一些系统调用丢失,并且将一直丢失,直到有人足够恼火地添加它们。sysctl compat.linux.osrelease 提供了一个我们声称支持的Linux内核版本,但实际上这只是FreeBSD告诉的一个谎言,因为当该字符串不可用时,某些linux程序会感到不安。

并非所有程序都有效;您将不会使用Linux工具格式化文件系统。您必须将解决特定问题所需的软件和配置拼凑在一起。在开始这样的项目之前,一定要复习Linux兼容性的基础知识;您必须了解如何安装软件包以及如何使用 trussDTrace 等工具进行调试。现代FreeBSD应该为您处理二进制品牌;如果你需要 brandelf(1) ,这是一个bug。

启动前,在主机上启用Linux模式。初始安装过程还需要Linux合成文件系统 linysfslinprocfs ,以及 fdescfstmpfs 。启动前将所有内容加载到内核中。

哪个Linux?

快速的互联网搜索显示,人们已经成功地在jail中安装了不同的发行版。你应该选哪一个?我建议选择最简单、最接近BSD的发行版,以满足您的需求。如果可能的话,避免基于 systemd 的发行版——同样,这是可以做到的,但 systemd 是侵入性的,这种依赖性使整个工作更加复杂。

对于这个例子,我使用的是Devuan(https://www.devuan.org),一个无系统的Debian变体。大多数Linux管理员都熟悉用户空间工具,并且它拥有相当多的用户群。

如果你需要另一个特定的Linux发行版,或者你试图以监禁旧FreeBSD的方式监禁旧Linux,环顾四周,你可能会找到一个已经这样做的人的帖子。从他们的错误中吸取教训。

创建Devuan Jail

从一个空jail开始。在这里,我用iocage创建了jail devuan

Linux使用FreeBSD的 /etc/rc 以外的启动和关闭脚本。找出你选择的Linux使用的脚本,并在jail的 exec.startexec.stop 中设置它们。在这里,我告诉iocage使用Devuan脚本。如果您使用的是标准jail,请在 jail.conf 中设置这些参数。

jail还需要一个 linprocfslinysfstmpfs

现在把一些文件放进jail。使用 debootstrap 将Devuan下载到jail中。debootstrap 程序是一个Debian管理工具,用于将Debian安装到目录中,但它可以作为FreeBSD软件包使用。我们只能使用 debootstrap 的部分功能,但这足以在磁盘上获取包。

我们的 debootstrap 命令具有以下格式:

--foreign 标志告诉 debootstrap 它正在目标平台之外的其他平台上运行,并且它不应该尝试运行Debian特定的命令,因为它们不存在。它将下载和解压缩程序,但在制作设备节点和其他Linuxy内容之前会停止。

arch 标志是您运行的硬件架构。这可能是 amd64 ,但如果您使用的是32位硬件,请使用 i386

Devuan和Debian一样,同时有几个版本。您不能使用Devuan版本名,但可以使用stableunstabletesting 标签。第一次尝试时,请使用 stable

directory 是这些文件的目标目录——在本例中为 /iocage/jails/devan/root/

最后, site 是下载文件的互联网位置。我们将使用Devuan主站,http://deb.devuan.org/merged/.

这给了我们最后的命令:

你会收到一条警告,指出debootstrap无法验证包签名,因为你的FreeBSD机器没有安装Devuan密钥,然后你会看到一堆包被下载。

下载包后,进入jail的根目录并临时挂载合成文件系统。

理论上,你现在可以运行一个简单的 chroot ,进入Linux jail。如果你遇到涉及二进制类型的错误,要么你没有加载所有的Linux内核模块,要么你需要修复jail的 /bin/sbin 中所有二进制文件的品牌。

该提示包括标准的Debian未配置主机投诉,并且还拖动了我的jail服务器的主机名。我不知道还有什么更好的迹象表明这个jail一团糟,但这是进步。

我们只做了最初的Debian引导。包数据库处于不一致的状态,在提取包时,它们并没有真正配置。在 chroot 内部,告诉 dpkg 配置所有内容并设置包数据库。

它会生成警告,因为您并没有真正在Linux主机上运行,但您最终会得到配置好的软件包。根据您的Devuan版本,系统会提示您选择几个选项。我接受默认设置,因为我要摧毁这个系统,没有理由造成额外的损害。不过,其中一些错误使包处于“待配置(to be configured)”状态,所以让我们对其进行配置。

此时,所有东西都应该安装完毕。不过,您可以检查包数据库是否存在问题。任何标有“ii”的包都有问题。

如果一个软件包出现问题,请尝试重新安装。在 /var/cache/apt/archives/ 中找到该软件包的原始下载,然后在文件上运行 dpkg --force-all -i

一旦用户区看起来尽可能好,就开始运行jail:

恭喜,怪物还活着,正蹒跚地朝村子走去。为你成功的可憎之物干杯,并找到一种方法来净化你的灵魂。

此时,你只能靠自己了。你的Linux专用程序会在jail里运行吗?唯一的办法就是安装它并解决不可避免的错误。

帮助 Linux Jails

虽然你现在只能靠自己,但我有一些技巧可以帮助你管理Linuxjail。

你不能在Linux jail中使用Linux ifconfigroute 。如果你想把jail放在自己的jail里,那就有问题了。您需要使用FreeBSD工具来配置FreeBSD网络。/recue 目录包括许多标准命令,包括网络命令。不过,不要只是复制整个目录;该目录中只有一个二进制文件, /resce/rescue 。这是一个压缩的二进制文件,所有其他命令都是指向该二进制文件的链接。如果你复制目录,你会有无数个相同的压缩二进制文件的副本。将主机的 /rescue 用打包,并将其提取到jail的 /rescue 中,以保留这些链接。也许用FreeBSD版本替换Linux ifconfig和route命令会简化你的生活,或者可能会毁掉一切。

我发现了标准jail和jail之间的奇怪区别。有时Linux jail在一种或另一种情况下运行良好。我用自己的vnet运行Linux jail,只要我使用外部脚本创建和连接接口,而不是依赖于内置的jail配置,它就可以正常工作。

运行Linux jail需要耐心和毅力。祝你好运。

其他 Jail 技巧

如果你能想象它发生在jail里,有人尝试过。人们在jail里使用Xnest 运行X服务器。人们在jail里开发设备驱动程序。人们在jail里养牛——不,等等。不是牛。但jail几乎可以容纳任何东西,人们成功地监禁了我想象不到会受到这种限制的软件。

做一些研究。看看人们成功地监禁了什么,在哪里失败了。注意日期;jail工具链不断改进,几年前不可能的事情在今天可能非常可行。

现在,让我们确保你的实验不会垄断你的host。