使用 systemd-nspawn 这个命令我们可以很方便的创建一个 Linux 容器,需要的只是一个使用 systemd 作为 init 的 Linux 发行版的根文件系统。通过创建容器,我们可以获得一个可以随便折腾而不用担心损坏的 Linux 环境。这里用 Ubuntu 16.04 和 CentOS 7 为例,整个过程可以说是非常简单(虽然比起 Docker 还是麻烦了点)

对于 Ubuntu,可以直接从源里下载到它的根文件系统。下载一份,并解压到 /var/lib/machines/ubuntu1604

sudo mkdir -p /var/lib/machines/ubuntu1604
wget http://mirrors.ustc.edu.cn/ubuntu-cdimage/ubuntu-base/releases/16.04.3/release/ubuntu-base-16.04.1-base-amd64.tar.gz -O /tmp/rootfs.tgz
sudo tar xpzf /tmp/rootfs.tgz -C /var/lib/machines/ubuntu1604

OK,到此为止我们就得到了一个可以被 systemd-nspawn 启动的 rootfs,不过我们还需要一些配置,例如修改 root 密码等等:

chroot /var/lib/machines/ubuntu1604 /usr/bin/passwd root
echo ubuntu > /var/lib/machines/ubuntu1604/etc/hostname

下面只需要用 systemd-nspawn 来“启动”这个容器:

systemd-nspawn -b -D /var/lib/machines/ubuntu1604 --bind=/lib/firmware

这样就完成了!相当简单吧~输出内容大概是这样:

Spawning container ubuntu1604 on /var/lib/machines/ubuntu1604.
Press ^] three times within 1s to kill container.
systemd 229 running in system mode. (+PAM +AUDIT +SELINUX +IMA +APPARMOR +SMACK +SYSVINIT +UTMP +LIBCRYPTSETUP +GCRYPT +GNUTLS +ACL +XZ -LZ4 +SECCOMP +BLKID +ELFUTILS +KMOD -IDN)
Detected virtualization systemd-nspawn.
Detected architecture x86-64.

Welcome to Ubuntu 16.04.1 LTS!

Set hostname to .
Failed to install release agent, ignoring: No such file or directory
[  OK  ] Listening on Journal Socket (/dev/log).
[  OK  ] Started Dispatch Password Requests to Console Directory Watch.
[  OK  ] Started Forward Password Requests to Wall Directory Watch.
[  OK  ] Reached target Paths.
[  OK  ] Reached target Remote File Systems (Pre).
[  OK  ] Reached target Remote File Systems.
[  OK  ] Listening on /dev/initctl Compatibility Named Pipe.
[  OK  ] Created slice System Slice.
[  OK  ] Reached target Slices.
[  OK  ] Created slice system-getty.slice.
[  OK  ] Reached target Swap.
[  OK  ] Reached target Encrypted Volumes.
[  OK  ] Listening on Journal Socket.
         Mounting Huge Pages File System...
[  OK  ] Reached target Sockets.
         Starting Remount Root and Kernel File Systems...
         Mounting POSIX Message Queue File System...
         Starting Journal Service...
         Mounting FUSE Control File System...
[  OK  ] Mounted POSIX Message Queue File System.
[  OK  ] Mounted Huge Pages File System.
[  OK  ] Mounted FUSE Control File System.
[  OK  ] Started Remount Root and Kernel File Systems.
[  OK  ] Reached target Local File Systems (Pre).
[  OK  ] Reached target Local File Systems.
         Starting Load/Save Random Seed...
[  OK  ] Started Load/Save Random Seed.
[  OK  ] Started Journal Service.
         Starting Flush Journal to Persistent Storage...
[  OK  ] Started Flush Journal to Persistent Storage.
         Starting Create Volatile Files and Directories...
[  OK  ] Started Create Volatile Files and Directories.
         Starting Update UTMP about System Boot/Shutdown...
[  OK  ] Reached target System Time Synchronized.
[  OK  ] Started Update UTMP about System Boot/Shutdown.
[  OK  ] Reached target System Initialization.
[  OK  ] Started Daily Cleanup of Temporary Directories.
[  OK  ] Reached target Basic System.
         Starting Permit User Sessions...
         Starting LSB: Set the CPU Frequency Scaling governor to "ondemand"...
         Starting /etc/rc.local Compatibility...
[  OK  ] Started Daily apt activities.
[  OK  ] Reached target Timers.
[  OK  ] Started Permit User Sessions.
[  OK  ] Started /etc/rc.local Compatibility.
[  OK  ] Started Console Getty.
[  OK  ] Reached target Login Prompts.
[  OK  ] Started LSB: Set the CPU Frequency Scaling governor to "ondemand".
[  OK  ] Reached target Multi-User System.
[  OK  ] Reached target Graphical Interface.
         Starting Update UTMP about System Runlevel Changes...
[  OK  ] Started Update UTMP about System Runlevel Changes.

Ubuntu 16.04.1 LTS ubuntu console

ubuntu login: 

值得注意的是,这个容器和虽然看起来很像那么一回事儿,但是它的内核和网络等仍然是使用宿主机的。

如果我们想让这个容器拥有独立的网络呢?其实也很简单,只要给它一个桥接的接口就行了。首先我们需要准备一个桥接接口,此处以 netctl 为例,准备好配置文件:

Description="A bridge connection"
Interface=br0
Connection=bridge
BindsToInterfaces=(enp1s0 enp2s0 enp3s0 enp4s0)
IP=dhcp

这样就是将 enp1s0 ~ enp4s0 这四张网卡桥接,形成 br0 接口,并通过 DHCP 获得 IP 地址。

然后微调启动命令,增加一个命令 --network-bridge=br0 即可,例如:

systemd-nspawn -b -D /var/lib/machines/ubuntu1604 --bind=/lib/firmware --network-bridge=br0

启动完成后,通过 ip link 命令就能看到接口了,我这里看到的是 host0@if9。之后再按照对应的发行版修改一下网络配置,就完成了。

如果不想每次都通过这么长的命令来启动容器,我们可以使用 systemd 提供的 machinectl 命令来完成容器的开启与关闭,比如:

machinectl start ubuntu1604
machinectl login ubuntu1604
machinectl stop ubuntu1604

如果需要添加额外的参数,可以前往 /etc/systemd/nspawn 目录创建一个同名文件,例如 ubuntu1604.nspawn,在其中写上这个容器的配置,例如:

[Exec]
Boot=true

[Files]
Bind=/lib/firmware:/lib/firmware

[Network]
VirtualEthernet=yes
Private=yes
Bridge=br0

以上就是比较完整的容器创建和使用过程啦~

CentOS 稍微复杂一点,因为他没有直接提供最小的 rootfs,我们要自己从 ISO 中解压安装,整个过程如下:

# 切换到超级用户
sudo -s

# 建立一些文件夹
mkdir -p /var/lib/machines/centos7
mkdir -p /tmp/iso
mkdir -p /tmp/squashfs
mkdir -p /tmp/rootfs

# 挂载 CentOS 7 的系统盘
mount /mnt/Disk2/OS/Linux/CentOS-7-x86_64-Minimal-1708.iso /tmp/iso
mount /tmp/iso/LiveOS/squashfs.img /tmp/squashfs
mount /tmp/squashfs/LiveOS/rootfs.img /tmp/rootfs

# 复制系统文件,速度看你的硬盘,可能会比较慢_(:з」∠)_
cp -pr /tmp/rootfs/* /var/lib/machines/centos7

# 卸载一些不会再用到的镜像
umount /tmp/{rootfs,squashfs}

# 安装一下 yum
mkdir -p /var/lib/machines/centos7/mnt/iso
mount --bind /tmp/iso /var/lib/machines/centos7/mnt/iso

chroot /var/lib/machines/centos7 /usr/bin/rpm -ivh --nodeps /mnt/iso/Packages/rpm-4.11.3-25.el7.x86_64.rpm
chroot /var/lib/machines/centos7 /usr/bin/rpm -ivh --nodeps /mnt/iso/Packages/yum-3.4.3-154.el7.centos.noarch.rpm

# 配置一下基本系统,执行最小安装
echo "[cdrom]
name=Install CD-ROM 
baseurl=file:///mnt/iso
enabled=0
gpgcheck=1
gpgkey=file:///mnt/iso/RPM-GPG-KEY-CentOS-7" > /var/lib/machines/centos7/etc/yum.repos.d/cdrom.repo

chroot /var/lib/machines/centos7 /usr/bin/yum --disablerepo=\* --enablerepo=cdrom -y reinstall yum
chroot /var/lib/machines/centos7 /usr/bin/yum --disablerepo=\* --enablerepo=cdrom -y groupinstall "Minimal Install"

# 删掉 ISO 源
rm /var/lib/machines/centos7/etc/yum.repos.d/cdrom.repo

# 卸载 ISO
umount /var/lib/machines/centos7/mnt/iso /tmp/iso

# 设置一下 root 密码之类的
chroot /var/lib/machines/centos7 /usr/bin/passwd root

# 进入虚拟环境,执行这段脚本
systemd-nspawn -D /var/lib/machines/centos7 --bind=/lib/firmware << _END_POSTINSTALL_
# 换源,先备份
mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup
curl https://lug.ustc.edu.cn/wiki/_export/code/mirrors/help/centos\?codeblock=3 > /etc/yum.repos.d/CentOS-Base.repo

# 更新
yum makecache
# 安装一个缺失的依赖,为什么会缺我也不知道…
yum install lvm2-libs -y

# 这些服务是作为一个容器不需要的,把他们关掉
systemctl disable auditd.service
systemctl disable kdump.service
systemctl disable multipathd.service
systemctl disable network.service
systemctl disable smartd.service
systemctl disable lvm2-monitor.service
systemctl disable sshd.service

# 设置 locale,如果需要中文就用 zh_CN.UTF-8
echo LANG=en_US.UTF-8 > /etc/locale.conf

# 设置主机名
echo CentOS-7 > /etc/hostname

_END_POSTINSTALL_

# 然后就可以开机了(((
systemd-nspawn -b -D /var/lib/machines/centos7 --bind=/lib/firmware

参考资料: