nspawn容器的使用

systemd-container是systemd的容器组件。它和LXC对标,比chroot更强大。它虚拟化了文件系统、进程树以及客户系统中的进程间通信。

systemd-container容器有五个限制:

  1. 仅允许以只读方式访问例如/sys/proc/sys/sys/fs/selinux这样的内核文件系统。
  2. 禁止修改主机的网络接口以及系统时钟。
  3. 禁止创建设备节点。
  4. 禁止重启主机操作系统。
  5. 禁止加载内核模块。

它的吸引力在于由systemd-nspawn运行的容器将会与systemd组件一同运行在宿主系统上。举例来说,一个容器的日志可以输出到宿主系统的日志中。它的核心控制命令为systemd-nspawn

快速开始

在进入容器之前我们都需要一个最基本的根文件系统,对于systemd-nspawn来说,这个文件系统可以是一个raw格式的镜像文件,也可以是一个目录,下面主要以一个目录作为容器的根节点来说明systemd-nspawn的使用方法。

systemd-nspawn可以像chroot一样以一个目录作为根来启动一个容器,并且不需要手动挂载/proc、/run、/dev等目录,退出容器也会自动清理资源,不容易把主机系统弄崩溃。

chroot进入容器目录:systemd-nspawn -D /path-to-rootfs

systemd-nspawn不仅能像chroot一样以一个目录作为一个新系统的根,并且还能以一个完整的系统启动流程来启动一个容器,就像LXC一样。

启动一个完整的系统:systemd-nspawn -bD /path-to-rootfs

以Ubuntu-base为例,在 http://cdimage.ubuntu.com/ubuntu-base/releases/ 下载Ubuntu base版tar包,解包并配置一些初始化文件(例如hosts,hostname,resolv.conf)后,使用systemd-nspawn -D /path-to-ubuntu命令进入容器,此时可以使用aptuseradd等命令,关键是查看当前挂载分区并写入/etc/fstab中,否则稍后会影响启动。

exit退出容器后,使用systemd-nspawn -bD /path-to-ubuntu启动进入容器系统,此时可以和真实系统一样进行操作。除了正常退出容器,还可以使用ctrl+三下]操作强制退出。

安装

1
2
3
4
5
# Debian
apt install systemd-container

# RHEL
yum install systemd-container

创建容器

systemd-nspawn可以直接启动容器,就像chroot一样,而不对容器进行统一化管理。

如果用户希望对容器进行统一的服务化管理,则需要使用machinectl辅助进行管理,将容器镜像导入machinectl,它会调用systemd-nspawn启动容器并对容器进行监管。

需要注意的是,对于systemd-nspawn来说,一个镜像(根文件系统)就对应一个可引导的容器,它们之间不是分开管理的。

自行生成镜像

用户可以使用任意的可引导镜像启动一个nspawn容器,举例来说,用户可以使用debootstrap工具,执行debootstrap --include=systemd stable /var/lib/machines/debian创建一个基本Debian根文件系统。

然后,用户只需要执行machinectl import-fs 目录 [容器名]命令将容器的根文件系统目录导入为可用镜像即可。

手动下载镜像

和LXC一样,systemd-nspawn有自己官方的镜像发布站点,用户可以在 Images (nspawn.org)Index of /storage (nspawn.org) 寻找镜像。

在下载镜像Tar包后,用户可以执行machinectl import-tar|import-raw 文件名 容器名 [--read-only]进行导入。

当然,用户也可以将镜像解包,然后通过machinectl import-fs 目录 [容器名]命令进行导入。

直接拉取镜像

在拉取镜像之前,需要先创建密钥,具体方法为先创建本地密钥:

1
2
3
sudo gpg --no-default-keyring \
--keyring=/etc/systemd/import-pubring.gpg \
--fingerprint

然后拉取远程密钥:

1
2
3
4
sudo gpg --no-default-keyring \
--keyserver=keys.openpgp.org \
--keyring=/etc/systemd/import-pubring.gpg \
--search 9E31BD4963FC2D19815FA7180E2A1E4B25A425F6

然后,用户就可以执行machinectl pull-[tar|raw] https://hub.nspawn.org/storage/发行版/版本/架构/文件名.xz [容器名]拉取镜像了。

默认会在拉取后或导入后以隐藏镜像保存在/var/lib/machines/,并创建一个名为容器名的可写快照,如果不需要快照,可以把容器名设为-

也有一种简单的方法,就是使用 nspawn/nspawn 的封装脚本,它会自动导入密钥并进行拉取,使用方法为:

1
2
-i|--init  拉取镜像
-l|--list 列出可用镜像

具体可拉取的镜像可以看 https://hub.nspawn.org/storage/list.txt

初始化容器

使用systemd-nspawn命令临时启动一个容器,它的选项影响了容器的初始化配置,一些选项如下:

  • -M|--machine=:设置容器名称。此名称还会被设为容器的默认主机名。
  • -D|--directory=:指定容器根文件系统目录。
  • -b|--boot:启动容器中的init进程。
  • --background:后台启动容器。
  • -U:启用非特权容器,这和LXC的概念一样。
  • -j:尝试将容器内的日志收集到宿主机上。
  • -E NAME=VALUE|--setenv=NAME=VALUE:在启动容器时,设置一些环境变量。
  • --read-only:只读启动。
  • -x|--ephemeral:临时模式,在退出容器时抹除任何操作。

执行这条命令设置容器密码:
systemd-nspawn -D 根文件系统目录 passwd

然后使用这条命令启动容器即可:
systemd-nspawn -D 根文件系统目录 -M 容器名 -b [-U]

统一管理

machinectl是镜像与容器统一管理命令,它的操作有以下几种:

镜像管理

列出可用镜像
machinectl list-images

查看镜像情况
machinectl show-image|image-status 镜像名

克隆一个镜像
machinectl clone 镜像名 新镜像名

重命名一个镜像
machinectl rename 镜像名 新镜像名

把镜像只读化
machinectl read-only 镜像名 yes|no

删除镜像
machinectl remove 镜像名

一键清除旧版镜像
machinectl clean

容器管理

服务化启动容器
machinectl start 镜像名
这会启动容器并创建一个systemd-nspawn@容器名.service服务。

列出容器
machinectl list

查看容器状态
machinectl show|status 容器名

关机|重启|终止|杀死容器
machinectl poweroff|reboot|terminate|kill 容器名
-s|--signal=:发送的信号。
这等价于systemctl stop systemd-nspawn@容器名

登入容器
machinectl login 容器名
登入时总是使用/dev/console
和LXC一样,登入后会占用一个终端。

在容器中执行命令
machinectl shell [用户名@]容器名 [命令]
若没有命令,默认在容器中以指定用户身份启动Shell,这相当于machinectl login,但是不会进行登录(这意味着没有任何用户变量),而且总是会产生新会话,并且在退出后杀死。
-E|--setenv=VAR[=VALUE]:设置一个环境变量。

容器开机启动
启用:
machinectl enable 容器名
这等价于systemctl enable systemd-nspawn@容器名
禁用:
machinectl disable 容器名
这等价于systemctl disable systemd-nspawn@容器名

从主机上拷贝文件
machinectl copy-to 容器名 主机路径 [容器路径]

从容器中拷贝文件
machinectl copy-from 容器名 容器路径 [主机路径]

临时配置目录映射
machinectl bind 容器名 主机路径 [容器路径]
--read-only:只读映射。
--mkdir:映射时,如果目录不存在则创建。

配置

网络配置

和LXC不同,systemd-nspawn容器可以使用主机的网络命名空间,也就是说,systemd-nspawn容器可以使用主机网络或者私有网络两种模式。

主机网络

systemd-nspawn默认采用这一模式。即容器直接使用主机的网络命名空间,容器共享主机的网络接口,容器将能够访问主机上的所有网络服务,来自容器的数据包将在外部网络中显示为来自主机(即共享同一IP地址)。

仅本地模式

容器仅仅只有本地回环网络,不存在其他任何网络接口。

没什么意义的网络模式,在使用systemd-nspawn启动容器时,添加选项--private-network即可。

如果使用machinectl,创建容器配置/etc/systemd/nspawn/容器名.nspawn,添加以下内容:

1
2
[Network]
Private=yes

Veth模式

给容器创建一个Veth设备,用于和主机之间通信,容器对外部网络的通信通过NAT实现。这种模式很类似于LXC的默认模式。

在使用systemd-nspawn启动容器时,添加选项--network-veth即可。

如果使用machinectl,创建容器配置/etc/systemd/nspawn/容器名.nspawn,添加以下内容:

1
2
3
[Network]
Private=yes
VirtualEthernet=yes

如果使用systemd-networkd,那么主机上的DHCP服务端和客户机上的DHCP客户端的会分别通过/usr/lib/systemd/network/80-container-ve.network/usr/lib/systemd/network/80-container-host0.network文件建立起来,以此实现容器自动分配IP地址。

如果使用其他网络管理工具,可以在容器中配置静态IP,或者手动配置主机和容器上的DHCP服务端和客户端。

如果使用systemd-networkd,NAT将通过/usr/lib/systemd/network/80-container-ve.network中的IPMasquerade=yes选项自动完成。

如果使用其他网络管理工具,若想让主机连接外网,需要配置类似iptables -t nat -A POSTROUTING -s 192.168.163.192/28 -j MASQUERADE这样的NAT规则。

桥接模式

将容器对应的主机端Veth直接桥接到指定的网桥上,网桥应当是一个二层的静态网桥。

在使用systemd-nspawn启动容器时,添加选项--network-bridge=网桥即可。

如果使用machinectl,创建容器配置/etc/systemd/nspawn/容器名.nspawn,添加以下内容:

1
2
3
[Network]
Private=yes
Bridge=网桥

端口映射

如果为容器开启了私有网络,那么可以在使用systemd-nspawn启动容器时,添加选项-p|--port=[协议:]宿主端口[:容器端口]进行端口映射。

如果使用machinectl,创建容器配置/etc/systemd/nspawn/容器名.nspawn,添加以下内容:

1
2
[Network]
Port=[协议:]宿主端口[:容器端口]

专用网卡

直接将主机网卡分配给容器,直到容器停止前,主机都不能使用该网卡。

在使用systemd-nspawn启动容器时,添加选项--network-interface=设备名即可。

如果使用machinectl,创建容器配置/etc/systemd/nspawn/容器名.nspawn,添加以下内容:

1
2
[Network]
Interface=

目录映射

在使用systemd-nspawn启动容器时,添加选项:

  • --bind=源路径:目标路径[:挂载选项]:添加目录映射。
  • --bind-ro=源路径:目标路径[:挂载选项]:添加只读目录映射。
  • --tmpfs=挂载路径[:挂载选项]:在容器内挂载一个tmpfs内存文件系统。
  • --overlay=|--overlay-ro=:映射OverlayFS,接受一系列冒号分隔的目录路径列表,最后一个路径表示容器内映射路径。

如果使用machinectl,创建容器配置/etc/systemd/nspawn/容器名.nspawn,添加以下内容:

1
2
3
4
5
[Files]
Bind=源路径:目标路径[:挂载选项]
BindReadOnly=源路径:目标路径[:挂载选项]
TemporaryFileSystem=挂载路径[:挂载选项]
Overlay=|OverlayReadOnly=

资源限制

使用systemctl set-property systemd-nspawn@容器名 限制项=值命令对容器使用的资源进行限制。
常用的限制项有:
MemoryMax=:限制内存使用。
AllowedCPUs=:允许使用的CPU核心。
CPUQuota=:最大CPU占用率。