Infinite BusyBox with systemd

带有 PID 1 的轻量级虚拟容器。

在本文中,我演示了一种使用 systemd 管理工具套件中的最新实用程序在一个 Linux 系统内构建另一个 Linux 系统的方法。Guest OS 容器设计侧重于 BusyBox 和 Dropbear 用于用户空间系统实用程序,但我也研究了运行更通用应用程序软件的方法,以便容器真正有用。

本教程在 Oracle Linux 7 上开发,它很可能在其常见的同类产品(Red Hat、CentOS、Scientific Linux)上运行不变,从现在开始,我将此平台简称为 V7。在其他 systemd 平台(如 SUSE、Debian 或 Ubuntu)上可能需要进行细微更改。Oracle 的 V7 仅在 x86_64 平台上运行,因此这是本文的主要重点。

所需实用程序

Red Hat 认为应该从其 V7 发行版中删除长期包含的 BusyBox 二进制文件,但这很容易通过直接从该项目的网站下载最新的二进制文件来补救。由于安装 V7 时 /home 文件系统默认获得大量空间,让我们暂时将其放在那里。以 root 身份运行以下命令,直到另有说明


cd /home
wget http://busybox.net/downloads/binaries/latest/busybox-x86_64

您也可以从该位置获取 Dropbear SSH 服务器和客户端的二进制副本


wget http://landley.net/aboriginal/downloads/
↪binaries/extras/dropbearmulti-x86_64

在本文中,我使用了以下版本

  • BusyBox v1.21.1。

  • Dropbear SSH 多用途 v2014.63。

这些是静态二进制文件,不链接到共享对象——运行它们不需要其他任何东西,它们是快速构建新的类似 UNIX 环境的理想选择。

构建 chroot

chroot 系统调用和相关的 shell 实用程序允许将系统上某处的任意子目录声明为所有子进程的根目录。以下命令填充“chroot 监狱”,然后将您锁定在其中。请注意,对 chroot 的调用需要您对下面 SHELL 环境变量的更改,因为您在监狱内没有 bash(并且它可能是 $SHELL 的默认值)


export SHELL=/bin/sh
mkdir /home/nifty
mkdir /home/nifty/bin
cd /home/nifty/bin
cp /home/busybox-x86_64 /home/dropbearmulti-x86_64 .
chmod 755 busybox-x86_64 dropbearmulti-x86_64
./busybox-x86_64 --list | awk '{print "ln -s 
 ↪busybox-x86_64 " $0}' | sh
chroot /home/nifty
export PATH=/bin
ls -l
###(try some commands)
exit

在退出之前,花一些时间探索您在上面启动 chroot 后的 shell 环境。请注意,您有一个 /bin 目录,该目录由解析为 BusyBox 二进制文件的软链接填充。BusyBox 根据其调用方式更改其行为——它将整个实用程序程序系统捆绑到一个方便的软件包中。

尝试一些您可能知道的其他 UNIX 命令。一些可以工作的命令是 viunameuptime 和(当然)您正在其中工作的 shell。无法工作的命令包括 pstopnetstat。它们失败是因为它们需要 /proc 目录(该目录由 Linux 内核动态提供)——它尚未在监狱内挂载。

请注意,如果不移动许多依赖库(对象),则很少有本机实用程序会在 chroot 中运行。您可能会尝试将 bash 或 gawk 复制到监狱中,但您将无法运行它们(目前还不能)。在这方面,BusyBox 是理想的,因为它不依赖于任何东西。

构建最小 UNIX 系统并启动它

systemd 套件包括作为 Linux 上 PID 1 运行的同名程序。在许多其他实用程序中,它还包括 nspawn 程序,该程序用于启动容器。nspawn 创建的容器修复了 chroot 监狱的大多数问题。它们提供 /proc、/dev、/run,并以其他方式为子环境配备了更强大的运行时。

接下来,您将配置一个 getty 以在容器的控制台上运行,您可以使用它登录。确保您已从上一步退出 chroot,以 root 身份运行以下命令


mkdir /home/nifty/etc
mkdir /home/nifty/root
echo 'NAME="nifty busybox container"' > 
 ↪/home/nifty/etc/os-release
cd /home/nifty
ln -s bin sbin
ln -s bin usr/bin
echo 'root::0:0:root:/root:/bin/sh' > 
 ↪/home/nifty/etc/passwd
echo 'console::respawn:/bin/getty 38400 /dev/console' >
 ↪/home/nifty/etc/inittab
tar cf - /usr/share/zoneinfo | (cd /home/nifty; tar xvpf -)
systemd-nspawn -bD /home/nifty

在您执行上述 nspawn 后,您将看到一个“nifty login”提示符。以 root 身份登录(目前没有密码),并尝试更多命令。您会立即注意到 pstop 工作正常,并且现在有一个 /proc。

您还会注意到,子容器中出现的进程也会出现在主机系统上,但父进程和子进程之间将分配不同的 PID。

请注意,您还会收到消息:“已知内核审计子系统与容器不兼容。请确保在使用 systemd-nspawn 之前,使用内核命令行上的 'audit=0' 关闭审计。休眠 5 秒...” 审计设置似乎不会影响 BusyBox 容器登录,但您可以在 grub 配置中调整内核命令行(至少可以消除警告并停止延迟)。

在您的容器中运行 Dropbear SSH

最好配置系统的非 root 用户并禁止网络 root 登录。当我讨论容器安全时,原因将变得清晰。

在容器内以 root 身份运行所有这些命令


cd /bin
ln -s dropbearmulti-x86_64 dropbear
ln -s dropbearmulti-x86_64 ssh
ln -s dropbearmulti-x86_64 scp
ln -s dropbearmulti-x86_64 dropbearkey
ln -s dropbearmulti-x86_64 dropbearconvert

上面,您已经建立了调用 Dropbear 所需的名称,包括主客户端和服务器,以及各种密钥生成和管理实用程序。

然后,您生成此容器将使用的主机密钥,并将它们放置在新的目录 /home/nifty/etc/dropbear 中(由主机查看)


mkdir /etc/dropbear
dropbearkey -t rsa -f /etc/dropbear/dropbear_rsa_host_key
dropbearkey -t dss -f /etc/dropbear/dropbear_dss_host_key
dropbearkey -t ecdsa -f /etc/dropbear/dropbear_ecdsa_host_key

然后创建您很快需要的各种目录


mkdir -p /var/log/lastlog
mkdir /home
mkdir /var/run
mkdir /tmp
mkdir /var/tmp
chmod 01777 /tmp /var/tmp

然后,您创建 inittab,它将在启动时启动 syslogd 和 Dropbear(除了现有的 getty,它会在每次死机时重新生成)


echo ::sysinit:/bin/syslogd >> /etc/inittab
echo '::sysinit:/bin/dropbear -w -p 2200' >> /etc/inittab

接下来,您添加一个 shadow 文件并为 root 创建一个密码


echo root:::::::: > /etc/shadow
chmod 600 /etc/shadow
echo root:x:0: > /etc/group
passwd -a x root

请注意,此处使用的 BusyBox passwd 调用生成了 MD5 哈希——root 的 /etc/shadow 的第二个字段中有一个 $1$ 前缀。此版本的 passwd 实用程序提供了其他哈希算法(选项 -a s 将生成 $5$ SHA256 哈希,-a sha512 将生成 $6$ 哈希)。但是,Dropbear 目前似乎只能使用 $1$ 哈希。

最后,向系统添加一个新用户,然后停止容器


adduser -h /home/luser -D luser
passwd -a x luser

halt

您应该会看到类似于系统停止的容器关闭消息。

当您下次启动容器时,它将监听套接字 2200 以进行连接。如果您希望远程主机能够从网络上的任何位置连接到您的容器,请在主机上以 root 身份运行此命令以打开防火墙端口


iptables -I INPUT -p tcp --dport 2200 --syn -j ACCEPT

该端口将仅在您重新启动之前打开。如果您希望打开的端口在重新启动后仍然存在,请在 X Window 系统中使用 firewall-config 命令(在 GUI 中的第二个选项卡上设置端口)。

在任何情况下,使用之前的 nspawn 语法运行容器,然后尝试从父主机操作系统中的另一个 shell 使用以下命令连接


ssh -l luser -p 2200 localhost

您应该能够以 BusyBox shell 登录到 luser 帐户。

执行具有运行时依赖项的程序

如果您将各种系统程序从 /bin 或 /usr/bin 复制到您的容器中,您会立即注意到它们不起作用。它们缺少运行所需的共享对象。

如果您之前从主机复制了 gawk 二进制文件


cp /bin/gawk /home/nifty/bin/

您会发现尝试执行它会失败,并显示“gawk: 未找到”错误(在主机上,通常会有关于缺少共享对象的明确抱怨,这在容器中看不到)。

您可以使用 nspawn 的参数轻松地使大多数 64 位库可用,该参数建立绑定挂载


systemd-nspawn -bD /home/nifty --bind-ro=/usr/lib64

然后,从容器内运行


cd /
ln -s usr/lib64 lib64

然后,您会发现您从主机复制进来的许多 64 位二进制文件都可以运行(运行 /bin/gawk -V 返回 “GNU Awk 4.0.2”——已确认整个 Oracle 12c 实例以这种方式运行)。只读库绑定挂载还具有在安全补丁出现在主机上时立即接收安全补丁的好处。

但是,这存在一个重大的安全问题。容器中的 root 用户有权 mount -o remount,rw /usr/lib64,从而获得对主机库目录的写入权限。一般来说,您不能将 root 权限授予您不认识和不信任的容器用户——在其他问题中,这些挂载可能会被滥用。

您也可能想以相同的方式挂载 /usr/lib 目录。您会发现的困难是 systemd 二进制文件将在该目录树下找到,nspawn 将尝试优先执行它而不是 BusyBox init。启用 32 位运行时支持可能需要比 /usr/lib64 更多的目录和挂载技巧。

现在,我要离题了。

systemd 服务文件

您需要直接调用主机 PID 1 (systemd) 以自动方式(可能在启动时)启动您的容器。为此,您需要创建一个服务文件。

由于缺乏关于将 inittab 和服务功能转移到 systemd 的清晰讨论,我将在为容器创建服务文件之前涵盖所有基本用途。

首先配置 telnet 服务器。telnet 协议不安全,因为它以明文形式传输密码。不要在生产服务器上或使用敏感信息或帐户练习这些示例。

经典的 telnetd 由 inetd 超级服务器启动,两者都由 BusyBox 实现。让我们为端口 12323 上的 telnet 配置 inetd。以 root 身份在主机上运行以下命令


echo '12323 stream tcp nowait root 
 ↪/home/nifty/bin/telnetd telnetd -i -l
/home/nifty/bin/login' >> /etc/inetd.conf

在完成上述配置后,如果您手动启动 BusyBox 中包含的 inetd,您将能够 telnet 到端口 12323。请注意,V7 平台默认不包含 telnet 客户端,因此您可以 使用 yum 安装它,或使用 BusyBox 客户端(下面的示例将这样做)。除非您在防火墙上打开端口 12323,否则您必须 telnet 到 localhost。

在继续创建 inetd 服务文件之前,请确保您启动的任何 inetd 都已关闭


echo '[Unit]
Description=busybox inetd
#After=network-online.target
Wants=network-online.target

[Service]
#ExecStartPre=
#ExecStopPost=
#Environment=GZIP=-9

#OPTION 1
ExecStart=/home/nifty/bin/inetd -f
Type=simple
KillMode=process

#OPTION 2
#ExecStart=/home/nifty/bin/inetd
#Type=forking

#Restart=always
#User=root
#Group=root

[Install]
WantedBy=multi-user.target' > 
 ↪/etc/systemd/system/inetd.service

systemctl start inetd.service

在启动上述 inet 服务后,您可以检查守护程序的状态


[root@localhost ~]# systemctl status inetd.service
inetd.service - busybox inetd
   Loaded: loaded (/etc/systemd/system/inetd.service; disabled)
   Active: active (running) since Sun 2014-11-16 12:21:29 CST; 
            ↪28s ago
 Main PID: 3375 (inetd)
   CGroup: /system.slice/inetd.service
            ↪3375 /home/nifty/bin/inetd -f

Nov 16 12:21:29 localhost.localdomain systemd[1]: Started 
 ↪busybox inetd.
Try opening a telnet session from a different console:

/home/nifty/bin/telnet localhost 12323

您应该看到登录提示符


Entering character mode
Escape character is '^]'.

S
Kernel 3.10.0-123.9.3.el7.x86_64 on an x86_64
localhost.localdomain login: jdoe
Password:

再次检查状态,您会看到有关连接和会话活动的信息


[root@localhost ~]# systemctl status inetd.service
inetd.service - busybox inetd
   Loaded: loaded (/etc/systemd/system/inetd.service; disabled)
   Active: active (running) since Sun 2014-11-16 12:34:04 CST; 
            ↪7min ago
 Main PID: 3927 (inetd)
   CGroup: /system.slice/inetd.service
            ↪3927 /home/nifty/bin/inetd -f
            ↪4076 telnetd -i -l /home/nifty/bin/login
            ↪4077 -bash

您可以使用 man 5 systemd.service 命令了解有关 systemd 服务文件的更多信息。

这里有一个重要的点需要说明——您已经使用 “-f 在前台运行” 选项启动了 inetd。这不是 inetd 的正常启动方式——此选项通常用于调试活动。但是,如果您要使用经典的 inittab 条目启动 inetd,则 -f 将与 “respawn” 结合使用很有用。如果没有 -f,inetd 将立即 fork 到后台;尝试为 fork 守护程序重新生成将重复启动它们。使用 -f,您可以配置 init 以在 inetd 死机时重新启动它。

另一个重要的点是停止服务。对于前台守护程序和服务文件中的 KillMode=process 设置,当服务停止时,子 telnetd 服务不会被杀死。这不是 systemd 服务的正常默认行为,在默认行为中,所有子进程都将被杀死。

要查看这种批量杀死行为,请注释掉服务文件 (/etc/systemd/system/inetd.service) 中的 OPTION 1 设置,并启用 OPTION 2 中的默认设置。然后执行


systemctl stop inetd.service
systemctl daemon-reload
systemctl start inetd.service

启动另一个 telnet 会话,然后停止服务。当您这样做时,您的 telnet 会话将全部被 “Connection closed by foreign host.” 切断。简而言之,systemd 的默认行为是在父进程死机时杀死服务的所有子进程。

KillMode=process 设置可以与 fork 版本的 inetd 一起使用,但第一个选项中的 “-f 在前台运行” 更具体,因此更安全。

您可以使用 man 5 systemd.kill 命令了解有关 KillMode 选项的更多信息。

另请注意,systemctl status 输出包括单词 “disabled”。这表明该服务不会在启动时启动。将 enable 关键字传递给 systemctl 以使服务设置为在启动时启动(disable 关键字将撤消此操作)。

请注意上面注释掉的选项。您可以为您的服务设置环境变量(此处建议压缩质量),指定非 root 用户/组以及在服务启动之前或停止之后要执行的命令。这些功能超出了经典 inittab 提供的直接功能。

当然,systemd 能够直接生成 telnet 服务器,允许您完全省去 inetd。以 root 身份在主机上运行以下命令,以配置 systemd 以用于 BusyBox telnetd


systemctl stop inetd.service

echo '[Unit]
Description=mytelnet

[Socket]
ListenStream=12323
Accept=yes

[Install]
WantedBy=sockets.target' > 
 ↪/etc/systemd/system/mytelnet.socket

echo '[Unit]
Description=mytelnet

[Service]
ExecStart=-/home/nifty/bin/telnetd telnetd -i -l 
 ↪/home/nifty/bin/login
StandardInput=socket' > 
 ↪/etc/systemd/system/mytelnet@.service

systemctl start mytelnet.socket

关于 inetd 风格服务的一些注意事项

  • 启动 inetd 服务时,启动的是套接字,而不是服务。同样,启用它们以设置为在启动时启动。

  • 服务文件中的 @ 字符表示这是一个 “实例化” 服务。当使用单个服务文件启动多个类似服务时使用它们(getty 是主要示例——它们也适用于 Oracle 数据库实例)。

  • 上面 telnet 服务器路径中的 - 前缀表示 systemd 不应关注来自进程的任何状态返回代码。

  • 在客户端 telnet 会话中,命令 cat /proc/self/cgroup 将返回有关所涉及 IP 地址的详细连接信息。

此时,我已经从我长篇大论的题外话中返回,所以现在让我们为容器构建一个服务文件。以 root 身份在主机上运行以下命令


echo '[Unit]
Description=nifty container

[Service]
ExecStart=/usr/bin/systemd-nspawn -bD /home/nifty
KillMode=process' > /etc/systemd/system/nifty.service

确保您已关闭 nifty 容器的任何其他实例。您可以选择通过注释/删除 /home/nifty/etc/inittab 的第一行来禁用控制台 getty。然后使用 PID 1 直接启动您的容器


systemctl start nifty.service

如果您检查服务的状态,您将看到与之前在控制台上看到的相同级别的信息


[root@localhost ~]# systemctl status nifty.service
nifty.service - nifty container
   Loaded: loaded (/etc/systemd/system/nifty.service; static)
   Active: active (running) since Sun 2014-11-16 14:06:21 CST; 
            ↪31s ago
 Main PID: 5881 (systemd-nspawn)
   CGroup: /system.slice/nifty.service
            ↪5881 /usr/bin/systemd-nspawn -bD /home/nifty

Nov 16 14:06:21 localhost.localdomain systemd[1]: Starting 
 ↪nifty container...
Nov 16 14:06:21 localhost.localdomain systemd[1]: Started 
 ↪nifty container.
Nov 16 14:06:26 localhost.localdomain systemd-nspawn[5881]: 
 ↪Spawning namespace container on /home/nifty 
 ↪(console is /dev/pts/4).
Nov 16 14:06:26 localhost.localdomain systemd-nspawn[5881]: 
 ↪Init process in the container running as PID 5883.
内存和磁盘消耗

BusyBox 是一个大型程序,如果您运行多个容器,每个容器都有自己的副本,您将浪费内存和磁盘空间。

可以在所有正在运行的程序之间共享 BusyBox 内存使用情况的 “text” 段,但前提是它们在同一 inode 上运行,来自同一文件系统。“text” 段是程序的只读编译代码,您可以像这样查看大小


[root@localhost ~]# size /home/busybox-x86_64 
   text	   data	    bss	    dec	    hex	filename
 942326	  29772	  19440	 991538	  f2132	/home/busybox-x86_64

如果您想节省 BusyBox 使用的内存,一种方法是创建一个通用的 /cbin,您将其作为只读绑定挂载附加到所有容器(就像您之前对 lib64 所做的那样),并将 /bin 中的所有链接重置到新位置。root 用户可以这样做


systemctl stop nifty.service

mkdir /home/cbin
mv /home/nifty/bin/busybox-x86_64 /home/cbin
mv /home/nifty/bin/dropbearmulti-x86_64 /home/cbin
cd /
ln -s home/cbin cbin
cd /home/nifty/bin
for x in *; do if [ -h "$x" ]; then rm -f "$x"; fi; done
/cbin/busybox-x86_64 --list | awk '{print "ln -s 
 ↪/cbin/busybox-x86_64 " $0}' | sh
ln -s /cbin/dropbearmulti-x86_64 dropbear
ln -s /cbin/dropbearmulti-x86_64 ssh
ln -s /cbin/dropbearmulti-x86_64 scp
ln -s /cbin/dropbearmulti-x86_64 dropbearkey
ln -s /cbin/dropbearmulti-x86_64 dropbearconvert

您还可以安排绑定挂载 zoneinfo 目录,从而节省容器中更多的磁盘空间(并为容器提供时区数据补丁)


cd /home/nifty/usr/share
rm -rf zoneinfo

然后修改服务文件以绑定 /cbin 和 /usr/share/zoneinfo(请注意下面共享 /cbin 时语法的更改,当主机和容器之间的路径不同时)


echo '[Unit]
Description=nifty container

[Service]
ExecStart=/usr/bin/systemd-nspawn -bD /home/nifty
--bind-ro=/home/cbin:/cbin --bind-ro=/usr/share/zoneinfo
KillMode=process' > /etc/systemd/system/nifty.service

systemctl daemon-reload

systemctl start nifty.service

现在,任何使用 /cbin 中的 BusyBox 二进制文件的容器都将共享相同的 inode。在这些容器中运行的所有版本的 BusyBox 实用程序都将共享内存中的同一 text 段。

无限 BusyBox

一次启动数十个、数百个甚至数千个容器可能很有趣。您可以通过复制 /home/nifty 目录来启动克隆,然后调整 systemd 服务文件。为了简化,您将把新容器放在 /home/nifty1、/home/nifty2、/home/nifty3 ... 中,在目录上使用整数后缀来区分它们。

请确保您已禁用内核审计,以消除启动容器时的五秒延迟。至少,在启动时按 grub 菜单中的 e,并将 audit=0 添加到您的内核命令行以进行一次性启动。

我将回到 systemd “实例化服务” 的主题,我在替换 inetd 的 telnetd 服务文件中提到了该主题。此技术将允许您使用一个服务文件来启动所有容器。此类服务的文件名中有一个 @ 字符,用于引用服务的特定、区分的实例,并且它允许在服务文件中使用 %i 占位符进行变量扩展。在主机上以 root 身份运行以下命令,以放置实例化容器的服务文件


echo '[Unit]
Description=nifty container # %i

[Service]
ExecStart=/usr/bin/systemd-nspawn -bD /home/nifty%i
 ↪--bind-ro=/home/cbin:/cbin --bind-ro=/usr/share/zoneinfo
KillMode=process' > /etc/systemd/system/nifty@.service

上面的 %i 首先调整描述,然后调整 nspawn 的启动目录。将替换 %i 的内容在 systemctl 命令行上指定。

为了测试这一点,创建一个名为 /home/niftyslick 的目录。服务文件不限制您使用数字后缀。您将在复制后调整 SSH 端口。在主机上以 root 身份运行此命令


cd /home
mkdir niftyslick
(cd nifty; tar cf - .) | (cd niftyslick; tar xpf -)
sed "s/2200/2100/" < nifty/etc/inittab > niftyslick/etc/inittab

systemctl start nifty@slick.service

考虑到这种模式,让我们创建一个脚本来大量生成这些容器。让我们制作一千个容器


cd /home
for x in $(seq 1 999)
do
  mkdir "nifty${x}"
  (cd nifty; tar cf - .) | (cd "nifty${x}"; tar xpf -)
  sed "s/2200/$((x+2200))/" < nifty/etc/inittab > 
   ↪nifty${x}/etc/inittab
  systemctl start nifty@${x}.service
done

正如您在下面看到的,此测试启动所有容器


$ ssh -l luser -p 3199 localhost
The authenticity of host '[localhost]:3199 ([::1]:3199)' 
 ↪can't be established.
ECDSA key fingerprint is 07:26:15:75:7d:15:56:d2:ab:9e:
↪14:8a:ac:1b:32:8c.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '[localhost]:3199' (ECDSA) 
 ↪to the list of known hosts.
luser@localhost's password: 
~ $ sh --help
BusyBox v1.21.1 (2013-07-08 11:34:59 CDT) multi-call binary.

Usage: sh [-/+OPTIONS] [-/+o OPT]... [-c 'SCRIPT' 
 ↪[ARG0 [ARGS]] / FILE [ARGS]]

Unix shell interpreter

~ $ cat /proc/self/cgroup
10:hugetlb:/
9:perf_event:/
8:blkio:/
7:net_cls:/
6:freezer:/
5:devices:/
4:memory:/
3:cpuacct,cpu:/
2:cpuset:/
1:name=systemd:/machine.slice/machine-nifty999.scope

systemctl 的输出将列出您的每个容器


# systemctl
...
machine-nifty1.scope    loaded active running   Container nifty1
machine-nifty10.scope   loaded active running   Container nifty10
machine-nifty100.scope  loaded active running   Container nifty100
machine-nifty101.scope  loaded active running   Container nifty101
machine-nifty102.scope  loaded active running   Container nifty102
...

使用 systemctl status 可以获得更多详细信息


machine-nifty10.scope - Container nifty10
   Loaded: loaded (/run/systemd/system/machine-nifty10.scope; 
                    ↪static)
  Drop-In: /run/systemd/system/machine-nifty10.scope.d
            ↪90-Description.conf, 90-Slice.conf,
            ↪90-TimeoutStopUSec.conf
   Active: active (running) since Tue 2014-11-18 23:01:21 CST; 
            ↪11min ago
   CGroup: /machine.slice/machine-nifty10.scope
            ↪2871 init      
            ↪2880 /bin/syslogd
            ↪2882 /bin/dropbear -w -p 2210

Nov 18 23:01:21 localhost.localdomain systemd[1]: 
 ↪Starting Container nifty10.
Nov 18 23:01:21 localhost.localdomain systemd[1]: 
 ↪Started Container nifty10.

使用此方法可以启动的容器的原始数量更多地受到内核限制的影响,而不是一般的磁盘和内存资源。在具有 2GB RAM 的小型系统上启动上述容器未使用任何交换空间。

在您调查了一些容器及其监听端口之后,关闭所有容器的最简单和最干净的方法可能是重新启动。

容器安全

这些功能引发了许多担忧

1) 由于 BusyBox 和 Dropbear 不是使用 RPM 主机软件包工具安装的,因此必须手动加载对它们的更新。定期检查是否有新版本可用以及是否已发现任何安全漏洞非常重要。如果需要加载新版本,则应将二进制文件复制到所有可能使用的容器,然后重新启动这些容器(尤其是在涉及安全问题的情况下)。

2) 容器中 root 用户的控制权不能传递给您不信任的个人。对于一个特定的示例,如果使用上面的 lib64/cbin/zoneinfo 绑定挂载,容器 root 用户可以发出命令


mount -o remount,rw /usr/lib64

此时,容器 root 将对您的 64 位库、容器 bin 或 zoneinfo 具有完全写入权限。systemd-nspawn 手册页甚至进一步警告说

请注意,即使采取了这些安全预防措施,systemd-nspawn 也不适用于安全的容器设置。许多安全功能可能会被规避,因此主要用于避免容器意外更改主机系统。此程序的预期用途是调试和测试以及构建与启动和系统管理相关的软件包、发行版和软件。

关键在于,不受信任的用户不能拥有容器 root,就像您不会给他们完整的系统 root 权限一样。容器 root 将具有 CAP_SYS_ADMIN 权限,该权限允许完全控制系统。如果您想进一步隔离非 root 用户,容器环境确实限制了非 root 用户对主机活动的可见性,因为他们看不到完整的进程表。

3) 请注意,BusyBox su 和 passwd 实用程序以上述方式安装时不起作用。它们缺少适当的文件系统权限。要解决此问题,可以执行 chmod u+s busybox-x86_64,但这从安全角度来看也是令人厌恶的。在应用 setuid 权限之前,删除链接并将 BusyBox 二进制文件复制到 su 和 passwd 可能会更好,但也只是略微好一点。最好是 su 不可用,并找到另一种更改密码的机制。

4) 上面 Dropbear SSH 服务器的 -w 参数阻止了来自网络的 root 登录。从安全角度来看,放宽此限制有点令人厌恶。净效果是,当强制使用 -w 时,root 被锁定在容器中的活动使用之外,并且 su/passwd 没有 setuid。如果完全有可能忍受您的容器的这种安排,请尝试这样做,因为安全性会大大提高。

systemd 争议

Linux 用户对 systemd 持有高度敌意。这种敌意分为两个主要抱怨

  • 来自 UNIX System V 的经典 inittab 不应更改,因为它已被充分理解。

  • 越来越多的功能被捆绑到 systemd 中,这给关键系统进程带来了危险的复杂性。

对于第一点,对遗留系统的怀旧并非总是被误导,但不应允许它不合理地阻碍进步。经典的 System V init 无法 nspawn,并且对系统上运行的进程的控制远不如 systemd。systemd 提供的功能无疑证明了在许多情况下更改带来的不便。

对于第二点,来自不同组织的熟练设计师对 systemd 架构的采用进行了深思熟虑。那些对新环境最挑剔的人应该承认 systemd 的技术成功,因为它已被大多数 Linux 社区采用。

无论如何,未来十年将看到流行的 Linux 服务器发行版配备 systemd,并且有能力的管理员将无法选择忽略它。不幸的是,systemd 的引入没有为用户社区提供更多指导,但新功能引人注目,不应被忽视。

Charles Fisher 拥有爱荷华大学的电气工程学位,并在一家财富 500 强矿业和制造公司担任系统和数据库管理员。

加载 Disqus 评论