我如此喜欢jail的一个原因是,它们可以让你应对奇怪的问题。我们都遇到过那些古老而重要的系统,没有人敢碰,更不用说升级了,但硬件太弱了,你知道它们随时都会崩溃。我们的客户和应用程序需要运行自己的jail,但不需要整个主机。或者你甚至可能想在jail里运行Linux安装。
我们将从可以管理jail的jail开始。
第十章:极端Jails分级 Jails分级 Jail 网络创建父母 Jail创建子 Jails管理子 Jails旧 FreeBSD版本识别你的 Jail掠夺旧服务器复制旧服务器旧版本兼容性在 Jail 里面运行 Linux哪个Linux?创建Devuan Jail帮助 Linux Jails其他 Jail 技巧
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时,一些看似微不足道的限制变得非常不方便。
一个常见的愿望是为每个父节点分配几个IP地址,这样它就可以将这些地址分配给它的子节点。不过,每个IP只能分配给一个jail。父母jail就是jail。如果父jail有自己的vnet,则可以将父jail的委托地址分配给其子女。记住,IP地址属于vnet。如果父jail控制vnet,则此限制不适用。
没有vnet的父jail无法控制其IP地址堆栈。它可以告诉它的孩子继承它的IP设置,这很好,但限制了jail的实用性。您可以配置主机防火墙,将传入连接重定向到绑定到环回接口的特定地址和端口。然而,这一切都显得丑陋而笨拙。
我建议始终在自己的vnet中配置父jail。
创建父jail需要将 children.max
设置为该父jail应具有的最大子jail数量。父jail必须能够为其子jail挂载文件系统——如果没有别的,每个子jail都需要一个设备文件系统。在我们的示例中,我们将使用父jail dba1 和 dba2,每个jail都有自己的vnet。jail dba1 用于MariaDB测试,而 dba2 用于Postgres。
这是 jail.conf 中的dba1。它可以支持五个儿童jail,并获得一个连接到网桥的虚拟接口,如第9章所述。我从 jail.conf 顶部的默认值继承了path
、 host.hostname
等参数:
xxxxxxxxxx
dba1 {
vnet;
vnet.interface=e0b_dba1;
exec.prestart+="/usr/local/scripts/jib addm dba1 jailether";
exec.poststop+="/usr/local/scripts/jib destroy dba1";
allow.mount;
allow.mount.devfs;
enforce_statfs=1;
children.max=5;
}
用iocage创建一个家长jail与创建任何其他jail非常相似。它的地址条目将比大多数地址条目长得多,您必须设置 children_max
参数。在这里,我创建了jail dba2,IP地址为198.51.100.230
,运行FreeBSD 12.0,并让它最多启动五个子jail:
xxxxxxxxxx
# iocage create -n dba2 -r 12.0-RELEASE vnet=on interfaces="vnet0:jailetherbridge" ip4_addr="vnet0|198.51.100.230/24" defaultrouter=198.51.100.1 children_max=5
虽然你可以在创建jail时设置所有必要的参数,但命令行已经太长了,让我感到不舒服。在这里,我允许 dba2 在其子jail中挂载 /dev 。
xxxxxxxxxx
# iocage set allow_mount=1 dba2
# iocage set allow_mount_devfs=1 dba2
# iocage set enforce_statfs=1 dba2
你必须使用标准jails来管理孩子。
将设置儿童jail,就像我在主机上设置jail一样,所有配置都在 /etc/jail.conf 中,所有儿童jail数据文件都在jail的 /jail 中。以下是我如何在dba1上为dba1上的一大堆MariaDB jail配置 /etc/jail.conf :
xxxxxxxxxx
$j="/jail";
path="$j/$name";
host.hostname="$name.mwl.io";
exec.clean;
exec.start="sh /etc/rc";
exec.stop="sh /etc/rc.shutdown";
以上所有内容都直接来自主机的 jail.conf 。
xxxxxxxxxx
mount.devfs;
devfs_ruleset="0";
jail必须有一个 devfs
。不过,jail无法访问主机的 devd
,也无法创建自己的设备文件系统。他们的 /dev 是父jail的副本。不过,尝试应用规则集是一个错误,因此通过使用 devfs_ruleset
指定规则集 0
,我们告诉 jail(8)
不要费心尝试应用规则集中。
xxxxxxxxxx
ip_hostname;
mariadb1 {}
mariadb2 {}
mariadb3 {}
mariadb4 {}
mariadb5 {}
通过从DNS中提取儿童jail IP地址,我们的jail定义变成了一组都使用默认设置的名称。
dba2上的jail.conf看起来完全像这样,除了全局搜索并将 mariadb
替换为 pg
。
父母jail现在可以运行 service jail start
,开始并生育他们的孩子。
jail可以看到自己的所有孩子,以及所有孩子的孩子。主机可以查看和管理系统上的每个jail。如果我在dba2上运行 jls
,我会看到它的儿童jail。默认的jls视图在这里并不是很有启发性,所以我将专门研究jail名称、jailID和父母。
xxxxxxxxxx
# jls -h name jid parent |column -t
name jid parent
pg1 28 0
pg2 29 0
pg3 30 0
pg4 31 0
…
正如你所料,每个jail的名字都是按照这个父jail的配置命名的。如果你刚刚开始你的jail,你可能会期望你的jail ID以1开头,但这些更高的数字是可以的。jail的父母应该出示父母jail的JID。0
的父级表示“此系统”。
主机上的完全相同的命令显示了其他内容:
xxxxxxxxxx
# jls -h name jid parent | column -t
name jid parent
ioc-dba2 21 0
dba1 22 0
dba1.mariadb1 23 22
dba1.mariadb2 24 22
…
ioc-dba2.pg1 28 21
ioc-dba2.pg2 29 21
ioc-dba2.pg3 30 21
…
前两个jail是 ioc-dba2 ,即“通过iocage运行的 jaildba2 ” 和标准 jaildba1 。他们的父母是0
,所以他们在主机上运行。
儿童jail的名字以父母、一个句点和儿童jail的名称开头。jaildba1.mariadb1 是在 jaildba1 上运行的 jailmariadb1 。jailioc-dba2.pg3 是 jailioc-dba2 上运行的 jailpg3 。如果儿童jail有自己的孩子,这些名字可能会很长。
“parent”栏显示了每个儿童jail父母的JID。
host对所有jail拥有最终控制权。作为主机上的root,我可以运行 top -j ioc-dba2.pg4
,看看这个jail里发生了什么。我可以杀死任意进程。如果一个儿童jail失控,我可以严厉地关闭它。一种方法是在母jail上使用 jexec
并关闭jail,但如果真的有问题,我会发出原始jail命令并把孩子吹走。 -r
标志可以随意删除jail,而无需运行任何关机命令。
xxxxxxxxxx
# jail -r dba1.mariadb3
dba1.mariadb3: removed
问题儿童已移除。有了分级jail,问题用户现在可以拥有他们jail的任何问题。
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一直撒谎。
许多人使用 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中运行有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兼容性的基础知识;您必须了解如何安装软件包以及如何使用 truss
或 DTrace
等工具进行调试。现代FreeBSD应该为您处理二进制品牌;如果你需要 brandelf(1)
,这是一个bug。
启动前,在主机上启用Linux模式。初始安装过程还需要Linux合成文件系统 linysfs
和 linprocfs
,以及 fdescfs
和 tmpfs
。启动前将所有内容加载到内核中。
快速的互联网搜索显示,人们已经成功地在jail中安装了不同的发行版。你应该选哪一个?我建议选择最简单、最接近BSD的发行版,以满足您的需求。如果可能的话,避免基于 systemd
的发行版——同样,这是可以做到的,但 systemd
是侵入性的,这种依赖性使整个工作更加复杂。
对于这个例子,我使用的是Devuan(https://www.devuan.org),一个无系统的Debian变体。大多数Linux管理员都熟悉用户空间工具,并且它拥有相当多的用户群。
如果你需要另一个特定的Linux发行版,或者你试图以监禁旧FreeBSD的方式监禁旧Linux,环顾四周,你可能会找到一个已经这样做的人的帖子。从他们的错误中吸取教训。
从一个空jail开始。在这里,我用iocage创建了jail devuan:
xxxxxxxxxx
# iocage create -e -n devuan ip4_addr="203.0.113.229"
Linux使用FreeBSD的 /etc/rc 以外的启动和关闭脚本。找出你选择的Linux使用的脚本,并在jail的 exec.start
和 exec.stop
中设置它们。在这里,我告诉iocage使用Devuan脚本。如果您使用的是标准jail,请在 jail.conf 中设置这些参数。
xxxxxxxxxx
# iocage set exec_start="/etc/init.d/rc 3" devuan
# iocage set exec_stop="/etc/init.d/rc 0" devuan
jail还需要一个 linprocfs 、 linysfs 和 tmpfs:
xxxxxxxxxx
# iocage fstab -a devuan tmpfs /tmp tmpfs rw,mode=1777 0 0
# iocage fstab -a devuan linprocfs /proc linprocfs rw 0 0
# iocage fstab -a devuan linsysfs /sys linsysfs rw 0 0
现在把一些文件放进jail。使用 debootstrap
将Devuan下载到jail中。debootstrap
程序是一个Debian管理工具,用于将Debian安装到目录中,但它可以作为FreeBSD软件包使用。我们只能使用 debootstrap
的部分功能,但这足以在磁盘上获取包。
我们的 debootstrap
命令具有以下格式:
xxxxxxxxxx
# debootstrap --foreign --arch=architecture devuan-version /directory site
--foreign
标志告诉 debootstrap
它正在目标平台之外的其他平台上运行,并且它不应该尝试运行Debian特定的命令,因为它们不存在。它将下载和解压缩程序,但在制作设备节点和其他Linuxy内容之前会停止。
arch
标志是您运行的硬件架构。这可能是 amd64
,但如果您使用的是32位硬件,请使用 i386
。
Devuan和Debian一样,同时有几个版本。您不能使用Devuan版本名,但可以使用stable
、unstable
和 testing
标签。第一次尝试时,请使用 stable
。
directory
是这些文件的目标目录——在本例中为 /iocage/jails/devan/root/。
最后, site
是下载文件的互联网位置。我们将使用Devuan主站,http://deb.devuan.org/merged/.
这给了我们最后的命令:
xxxxxxxxxx
# debootstrap --foreign --arch=amd64 stable /iocage/jails/devuan/root/ http://deb.devuan.org/merged/
你会收到一条警告,指出debootstrap无法验证包签名,因为你的FreeBSD机器没有安装Devuan密钥,然后你会看到一堆包被下载。
下载包后,进入jail的根目录并临时挂载合成文件系统。
xxxxxxxxxx
# mount -t linprocfs linprocfs proc
# mount -t linsysfs linsysfs sys
# mount -t tmpfs tmpfs tmp
理论上,你现在可以运行一个简单的 chroot
,进入Linux jail。如果你遇到涉及二进制类型的错误,要么你没有加载所有的Linux内核模块,要么你需要修复jail的 /bin 和 /sbin 中所有二进制文件的品牌。
xxxxxxxxxx
# chroot /iocage/jails/devuan/root/ /bin/bash
I have no name!@storm:/#
该提示包括标准的Debian未配置主机投诉,并且还拖动了我的jail服务器的主机名。我不知道还有什么更好的迹象表明这个jail一团糟,但这是进步。
我们只做了最初的Debian引导。包数据库处于不一致的状态,在提取包时,它们并没有真正配置。在 chroot
内部,告诉 dpkg
配置所有内容并设置包数据库。
xxxxxxxxxx
# dpkg --force-depends -i /var/cache/apt/archives/*.deb
它会生成警告,因为您并没有真正在Linux主机上运行,但您最终会得到配置好的软件包。根据您的Devuan版本,系统会提示您选择几个选项。我接受默认设置,因为我要摧毁这个系统,没有理由造成额外的损害。不过,其中一些错误使包处于“待配置(to be configured)”状态,所以让我们对其进行配置。
xxxxxxxxxx
# dpkg --configure --pending
此时,所有东西都应该安装完毕。不过,您可以检查包数据库是否存在问题。任何标有“ii”的包都有问题。
xxxxxxxxxx
# dpkg -l |grep -v ^ii
如果一个软件包出现问题,请尝试重新安装。在 /var/cache/apt/archives/ 中找到该软件包的原始下载,然后在文件上运行 dpkg --force-all -i
。
一旦用户区看起来尽可能好,就开始运行jail:
xxxxxxxxxx
# iocage start devuan
# iocage console devuan
Linux devuan 2.6.32 x86_64 GNU/Linux
The programs included with the Devuan GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Devuan GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
root@devuan:~#
恭喜,怪物还活着,正蹒跚地朝村子走去。为你成功的可憎之物干杯,并找到一种方法来净化你的灵魂。
此时,你只能靠自己了。你的Linux专用程序会在jail里运行吗?唯一的办法就是安装它并解决不可避免的错误。
虽然你现在只能靠自己,但我有一些技巧可以帮助你管理Linuxjail。
你不能在Linux jail中使用Linux ifconfig
或 route
。如果你想把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里使用Xnest
运行X服务器。人们在jail里开发设备驱动程序。人们在jail里养牛——不,等等。不是牛。但jail几乎可以容纳任何东西,人们成功地监禁了我想象不到会受到这种限制的软件。
做一些研究。看看人们成功地监禁了什么,在哪里失败了。注意日期;jail工具链不断改进,几年前不可能的事情在今天可能非常可行。
现在,让我们确保你的实验不会垄断你的host。