在容器中运行 GNOME
容器化 GUI 可以将您的工作和娱乐分离开来。
虚拟化一直是有钱人的游戏,而更节俭的爱好者们——无力负担昂贵的服务器级组件——常常难以跟上。Linux 提供了免费的高质量虚拟机监控程序,但是当您开始在主机上运行实际工作负载时,其资源会迅速饱和。再多的备用 RAM 塞进旧的戴尔台式机也无法解决这个问题。如果配置完善的主机遥不可及,您不妨考虑使用容器。
容器不是虚拟化整台计算机,而是允许将 Linux 内核的部分划分为几个部分。这发生在不模拟硬件或运行多个相同内核的开销的情况下。完整的 GUI 环境(例如 GNOME Shell)可以在容器内启动,只需稍加努力。
您可以通过命名空间来实现这一点,命名空间是 Linux 内核内置的一项功能。深入研究此功能超出了本文的范围,但一个简短的示例说明了这些功能如何创建容器。每种命名空间都隔离了内核的不同部分。例如,PID 命名空间阻止命名空间内的进程看到内核中运行的其他进程。因此,这些进程认为它们是计算机上唯一运行的进程。每个命名空间对内核的其他区域也执行相同的操作。挂载命名空间隔离了其中进程的文件系统。网络命名空间为其中运行的进程提供了唯一的网络堆栈。IPC、用户、UTS 和 cgroup 命名空间也对内核的这些区域执行相同的操作。当七个命名空间组合在一起时,结果就是一个容器:一个隔离程度足以使其相信自己是一个独立的 Linux 系统的环境。
容器框架将从用户那里抽象出配置命名空间的细节,但每个框架都有不同的重点。Docker 是最流行的,旨在大规模运行多个相同的容器副本。LXC/LXD 旨在轻松创建模仿特定 Linux 发行版的容器。事实上,早期版本的 LXC 包含一组脚本,用于创建流行发行版的文件系统。第三种选择是 libvirt 的 lxc 驱动程序。与听起来相反,libvirt-lxc 根本不使用 LXC/LXD。相反,libvirt-lxc 驱动程序直接操作内核命名空间。libvirt-lxc 还集成了 libvirt 套件中的其他工具,因此 libvirt-lxc 容器的配置类似于在其他 libvirt 驱动程序中运行的虚拟机,而不是原生的 LXC/LXD 容器。因此,即使品牌令人困惑,它也很容易学习。
我选择 libvirt-lxc 用于本教程有几个原因。首先,Docker 和 LXC/LXD 已经发布了在容器内运行 GNOME Shell 的指南。我无法找到 libvirt-lxc 的类似文档。其次,libvirt 是与传统虚拟机一起运行容器的理想框架,因为它们都通过同一组工具进行管理。第三,在 libvirt-lxc 中配置容器可以很好地了解容器化中涉及的权衡。
要做的最大决定是运行特权容器还是非特权容器。特权容器使用用户命名空间,并且容器内部和外部都具有相同的 UID。因此,如果安全漏洞允许具有管理权限的用户运行的容器化应用程序突破容器,则可能会造成重大损害。鉴于此,运行非特权容器似乎是一个明显的选择。但是,非特权容器将无法访问 GPU 的加速功能。根据容器的用途——例如照片编辑——这可能没有用。有人认为应该只在容器中运行您信任的软件,而将不受信任的软件留给适当虚拟机的更重隔离。尽管我认为 GNOME 桌面是值得信赖的,但我在此演示创建非特权容器,以便在需要时可以应用该过程。
接下来要决定的是使用远程显示协议(如 spice 或 VNC),还是让容器将其内容渲染到主机的一个虚拟终端中。使用显示协议允许从任何地方访问容器并增加其隔离性。另一方面,容器访问主机硬件的风险可能不高于两个不同的进程在命名空间外运行的风险。同样,如果您运行的软件不可信,请使用完整的虚拟机。在本文中,我使用了后一种选择,即 libvirt-lxc 访问主机硬件。
最后一个考虑因素稍微小一些。首先,libvirt-lxc 不会将 /run/udev/data 共享到容器中,这会阻止 libinput 在其中运行(可以挂载 /run,但这会引起其他问题)。因此,您需要编写一个简短的 xorg.conf 来使用输入设备。如果主机 /dev/input 目录下的节点排列发生变化,则需要相应地调整容器配置和 xorg.conf 文件。一切就绪,让我们开始吧。
准备容器主机Fedora 29 Workstation 的基本安装包含 libvirt,但还需要一些额外的组件。libvirt-lxc 驱动程序本身需要安装。让我们使用 virt-manager 和 virt-bootstrap 工具来加速容器的创建。还有一些您稍后需要的辅助实用程序。它们不是必需的,但它们将帮助您监控容器的资源利用率。请参阅您的软件包管理器的文档,但我运行了以下命令
sudo dnf install libvirt-daemon-driver-lxc virt-manager
↪virt-bootstrap virt-top evtest iotop
注意:libvirt-lxc 在 Red Hat Enterprise Linux 7.1 版本中已被弃用作为其容器框架。它仍在上游开发中,并且可以安装在 RHEL/Fedora 系列发行版中。
不过,在创建容器之前,您还需要修改 /etc/systemd/logind.conf 以确保 getty 不会在您想要传递给容器的虚拟终端上启动。取消注释 NautoVTs
行并将其设置为 3,以便它只在前三个终端上启动 tty。将 ReserveVT
设置为 3,以便它将保留第三个 vt 而不是第六个。修改此文件后,您需要重新启动计算机。重新启动后,检查 getty 是否仅在 tty 1 到 3 上处于活动状态。根据您的设置要求更改这些参数。我的 logind.conf 文件的修改行如下所示
AutoVTs=3
ReserveVT=3
准备容器文件系统
您可以直接通过 virt-manager 创建容器的文件系统,但无论如何都需要在命令行上进行一些调整,因此让我们也在那里运行 virt-bootstrap。virt-bootstrap 是一个很棒的 libvirt 工具,可以从 Docker 下载基本镜像。这为您提供了您想要在容器中运行的发行版的维护良好的文件系统。我发现在 Fedora 29 上,我必须关闭 SELinux 才能使 virt-bootstrap 正常运行。需要将其他软件包添加到 Docker 基本镜像(例如 x.org 和 gnome-shell),并且需要取消屏蔽一些 systemd 服务
sudo setenforce 0
mkdir container
virt-bootstrap docker://fedora /path/to/container
sudo dnf --installroot /path/to/container install xorg-x11-server-Xorg
xorg-x11-drv-evdev xorg-x11-drv-fbdev gnome-session-xsession xterm
net-tools iputils dhcp-client passwd sudo
sudo chroot /path/to/container
passwd root
#unmask the getty and logind services
cd /etc/systemd/service
rm getty.target
rm systemd-logind.service
rm console-getty.service
exit
# make sure all of the files in the container are accessible
sudo chown -R user:user /path/to/container
sudo setenforce 1
注意:有许多替代方法可以创建操作系统文件系统。许多软件包管理器都有允许将软件包安装到本地目录的选项。在 dnf
中,这是 installroot
选项。在 apt-get
中,它是 -o Root=
选项。还有一个与 virt-bootstrap 类似的可选工具,称为 distrobuilder。
当您打开 virt-manager 时,您会看到 lxc 虚拟机监控程序丢失了。您可以通过从菜单中选择“文件”和“添加连接”来添加它。从下拉菜单中选择“LXC (Linux 容器)”,然后单击“连接”。接下来,返回“文件”菜单并单击“新建虚拟机”。

图 1. 将 libvirt-lxc 驱动程序添加到 virt-manager。
在 virt-manager 中创建新的虚拟机/容器的第一步是选择它将在其下运行的虚拟机监控程序。选择“LXC”和操作系统容器的选项。单击“下一步”。

图 2. 确保您选择“操作系统容器”。
virt-bootstrap 已经运行,因此请向 virt-manager 提供容器文件系统的位置。单击“下一步”。
为容器提供适合其使用的 CPU 和内存量。对于此容器,只需保留默认值。单击“下一步”。
在最后一步,单击“安装前自定义配置”,然后单击“完成”。
将打开一个窗口,允许您自定义容器的配置。在选中“概述”选项的情况下,展开标有“用户命名空间”的区域。单击“启用用户命名空间”,并在“用户 ID”和“组 ID”的“计数”字段中键入 65336。单击“应用”,然后单击“开始安装”。virt-manager 将启动容器。不过,您还没有完全准备好,因此请关闭容器,然后退出 libvirt。

图 3. 启用用户命名空间允许容器以非特权方式运行。
您需要修改容器的配置才能共享主机的设备。具体来说,目标 tty (tty6)、环回 tty (tty0)、鼠标、键盘和帧缓冲区 (/dev/fb0) 需要在配置中创建条目。通过运行 sudo evtest
并在枚举设备后按 Ctrl-c,快速识别 /dev/input 下的哪些项是鼠标和键盘。从输出中,我可以看到我的鼠标在 /dev/input/event3,我的键盘在 /dev/input/event6。

图 4. 我的工作站上的输入设备列表
您不能仅使用 sudo
命令访问 /etc/libvirt 文件夹。通过运行 sudo bash
进入 root bash 会话,并将目录更改为 /etc/libvirt/lxc。打开容器的配置并向下滚动到设备部分。您需要为您刚刚标识的每个设备添加 hostdev
标签。使用以下布局
<hostdev mode='capabilities' type='misc'>
<source>
<char>/dev/mydevice</char>
</source>
</hostdev>
对于我的容器,我添加了以下标签
<hostdev mode='capabilities' type='misc'>
<source>
<char>/dev/tty0</char>
</source>
</hostdev>
<hostdev mode='capabilities' type='misc'>
<source>
<char>/dev/tty6</char>
</source>
</hostdev>
<hostdev mode='capabilities' type='misc'>
<source>
<char>/dev/input/event3</char>
</source>
</hostdev>
<hostdev mode='capabilities' type='misc'>
<source>
<char>/dev/input/event6</char>
</source>
</hostdev>
<hostdev mode='capabilities' type='misc'>
<source>
<char>/dev/fb0</char>
</source>
</hostdev>
运行容器
是时候启动容器了!在 virt-manager 中打开它,然后单击“启动”按钮。一旦容器可以选择使用主机的 tty,它通常只在该 tty 上显示登录提示符。因此,请按 Ctrl-Alt-F6 切换到 tty6 并登录到容器。正如我上面提到的,您需要编写一个带有输入部分的 xorg.conf。为了您的参考,这是我编写的一个
Section "ServerFlags"
Option "AutoAddDevices" "False"
EndSection
Section "InputDevice"
Identifier "event3"
Option "Device" "/dev/input/event3"
Option "AutoServerLayout" "true"
Driver "evdev"
EndSection
Section "InputDevice"
Identifier "event6"
Option "Device" "/dev/input/event6"
Option "AutoServerLayout" "true"
Driver "evdev"
EndSection
不要忽略对容器执行新的 Linux 系统所需的通常的维护操作。您采取的步骤将取决于您在容器内运行的发行版,但至少要确保您创建一个单独的用户并将其添加到 wheel 组,并配置容器的网络接口。完成这些操作后,运行 startx
以启动 GNOME Shell。

图 5. 在容器中运行的 GNOME Shell
现在 GNOME 正在运行,检查容器对系统资源的使用情况。像 top
这样的工具没有容器感知能力。为了真正了解容器的内存使用情况,请改用 virt-top
。通过在容器外部运行 virt-top -c lxc:///
将 virt-top
连接到 libvirt-lxc 驱动程序。接下来,运行 machinectl
以获取容器的内部名称
[adam@localhost ~]$ machinectl
MACHINE CLASS SERVICE OS VERSION ADDRESSES
containername container libvirt-lxc - - -
运行 machinectl status -l 容器名称
以打印容器的进程树。在命令输出的最开始,请注意根进程的 PID 被列为领导者。要查看容器总共消耗了多少内存,您可以通过运行 top -p 领导者PID
将领导者 PID 传递给 top
[adam@localhost ~]$ top -p leaderpid
lxc-5016-fedora(c198368a58c54ab5990df62d6cbcffed)
Since: Mon 2018-12-17 22:03:24 EST; 19min ago
Leader: 5017 (systemd)
Service: libvirt-lxc; class container
Unit: machine-lxc\x2d5016\x2dfedora.scope
[adam@localhost ~]$ top -p 5017
top - 22:43:11 up 1:11, 1 user, load average: 1.57, 1.26, 0.95
Tasks: 1 total, 0 running, 1 sleeping, 0 stopped, 0 zombie
%Cpu(s): 1.4 us, 0.3 sy, 0.0 ni, 98.2 id, 0.0 wa, 0.1 hi,
↪0.0 si, 0.0 st
MiB Mem : 15853.3 total, 11622.5 free, 2363.5 used, 1867.4
↪buff/cache
MiB Swap: 7992.0 total, 7992.0 free, 0.0 used. 12906.4 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
5017 root 20 0 163.9m 10.5m 8.5m S 0.0 0.1 0:00.22 systemd
该容器总共使用了 163MB 的虚拟内存——与完整虚拟机使用的资源相比,非常精简!您可以通过运行 sudo iotop -p 领导者PID
以类似的方式监控 I/O。您可以使用 du -h /容器路径
计算容器的磁盘大小。我完全配置的容器重达 1.4GB。
随着为容器提供额外的软件和工作负载,这些数字显然会增加。我喜欢拥有一个独立的环境来安装构建依赖项,而我最常见的容器用途是运行 gnome-builder。我偶尔也会设置一个特权容器来运行 darktable 进行照片编辑。我编辑照片的频率很低,以至于将 darktable 安装在容器外部没有意义,而且我发现如果我想在另一台计算机上重新创建容器文件系统,那么我可以将其 tar 起来的想法令人放心。如果您发现自己资金紧张并且需要最大限度地利用您的主机,请考虑使用容器而不是虚拟机。
资源