GNU/Linux systemd-nspawn 容器用法(替代 docker)

纯水,因为和 emacs 无关

是什么

systemd 管理的容器

man 1 systemd-nspawn

能做什么

  • 提供完全的隔离环境,和宿主机共用内核,但是具有自己隔离的进程、用户,网络,文件命名空间
  • 隔离级别可定制
  • 允许绑定挂载,overlay 挂载
  • 性能和 docker 持平

使用场景

  • 运行隔离环境,取代大多数 docker 使用场景
  • 运行无头桌面

特点

  • 默认就是容器内写入持久化模式,但也可修改为易失模式(重启后修改丢失)

不能做什么

  • 如果容器在宿主机上以非 root 身份运行,则容器内无法运行 flatpak 应用,原因在于对命名空间无操作权限

    以 root 方式启动容器,本人未测试

  • 如果容器在宿主机上以非 root 身份运行,则容器内对 /dev/uinput 无权限,因此 sunshine 中的鼠标控制完全失效

    以 root 方式启动容器,本人未测试

安装

apt-get install systemd-container

配置

内核变量

net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1

防火墙

防火墙配置仅在 容器连到了一个虚拟的网桥 的情况的情况下需要定制,否则不需要

本文为了简单起见,不引入任何虚拟网桥,让容器直接使用宿主机的网络(完全访问权限)

虚拟网桥

本文为了简单起见,不引入任何虚拟网桥,让容器直接使用宿主机的网络(完全访问权限)

创建

/etc/systemd/nspawn/container-test.nspawn

持久化容器配置,container-test 替换为容器名(容器根目录名)

[Exec]
PrivateUsers=pick

Timezone=bind
# 如果启用下面一行,那么不 bind 宿主机的 /etc/resolv.conf 到容器中
# ResolvConf=off
# 如果宿主机的 /etc/resolv.conf 内是一个互联网的 dns 服务器地址,那么可以 bind 进容器
ResolvConf=bind-host

[Files]
PrivateUsersOwnership=auto

# 绑定挂载:参考下述写法
# 如果不使用 owneridmap 这类选项,那么需要事先准备好 挂载源目录 的所有者和权限。选项解释见 man 5 systemd.nspawn
# BindReadOnly=/default/etc/resolv.conf:/etc/resolv.conf
# Bind=/path/to/postgres:/var/lib/postgresql:owneridmap

[Network]
Private=no

创建容器根目录

cd /var/lib/machines

# container-test 替换为你的容器名
debootstrap --include=apt-transport-https,ca-certificates,dbus,libpam-systemd,systemd --arch=amd64 stable https://mirrors.tuna.tsinghua.edu.cn/debian/

启动

sudo machinectl start container-test
# 无密码直接取得 root shell
sudo machinectl shell container-test

友情链接

https://linux.do/t/topic/691953

这个人是懂 nspawn 的,但如果想要在网络层面隔离容器和宿主机,或许不需要像上面链接里面说的配这么复杂

用 nspawn 运行 sway 桌面

1 个赞

NixOS Containers 用的就是 systemd-nspawn

1 个赞

继续水

用于 ai code 工具隔离

优势

  • 如果不希望 ai 进行任何修改,可让项目目录在文件系统级别表现为只读
  • 如果用于 ai 读写,可确保永远不会干扰/破坏容器外的环境,因为文件系统命名空间是隔离的

准备步骤

下文以一个完全可读写的容器为例:

nspawn 配置文件示例

[Exec]
PrivateUsers=pick

Timezone=bind
ResolvConf=off

[Files]
PrivateUsersOwnership=auto

# Volatile=overlay
# BindReadOnly=/default/etc/resolv.conf:/etc/resolv.conf
# Overlay=+/etc/systemd/network:/default/etc/systemd/network:/overlay/etc/systemd/network:/etc/systemd/network
# Bind=/path/to/postgres:/var/lib/postgresql:owneridmap
Bind=/path/on/host/emacs-config:/home/user/mount/project/emacs-config:owneridmap

[Network]
Private=no

然后

# 宿主机
# debootstrap 创建容器文件系统目录的命令,见上文
# 创建完了,别急着用 machinectl 启动,先创建一个容器内的普通用户。假设容器根目录在 /var/lib/machines, 那么先手动启动一次
sudo systemd-nspawn --settings=no --resolv-conf=bind-host --timezone=bind -U -D /var/lib/machines/container-test

# 此时会取得 root shell

# 容器内
# 容器内新建一个用户
useradd -d /home/user -m -s /usr/bin/bash user

# 退出容器 shell, 容器自动关闭

# 宿主机
# 提前创建容器内的挂载目标目录,因为我们使用了 owneridmap 选项,用 /path/on/host/emacs-config 的所有者身份执行即可
mkdir -p /var/lib/machines/container-test/home/user/mount/project

# 在宿主机上执行下述命令,以后台方式启动容器
sudo machinectl start container-test
# 取得容器 root shell
sudo machinectl shell container-test

# 容器内
# 此时宿主机的 /path/on/host/emacs-config 被以读写方式挂载到容器内的 /home/user/mount/project/emacs-config
su - user
# 安装 code cli
npm ....

提示

  • 容器内可运行 ssh 服务器,但在 Private=no 的 network 模式下,需要使用一个和宿主机 ssh 服务不重复的端口,避免冲突
  • 容器内可运行 tun 模式网卡,但我仅在 Private=yes 的 network 模式下测试过

AI 工具隔离还是用工具自带的sandbox或者 nono 比较好 (claude-code sandbox bug挺多; codex 还挺好的)

这个看起来还可以,不过可能我自己更加习惯样貌上隔离出来一个看似完整的操作环境

容器内运行游戏

时代确实变了,GNU/Linux 上现在甚至可以通过 proton 运行许多的 m$win 游戏了。m$win 世袭的独占的领域,我认为也离崩塌不远了

下面是翻译(原文是用英语瞎写的)


这个解决方案是我和 Gemini 之间对话的结果。它已经过测试,似乎是一个很好的方法。

我这样做是为了隔离游戏环境并提高安全性。

游戏在容器中运行,但在主机上显示。

步骤:

创建一个 systemd-nspawn 容器

请参考互联网上的指南。我建议您运行非特权容器(启用 PrivateUsers)并启用尽可能多的安全选项。

必须:

  • /dev/dri 绑定到容器中,并确保容器中的用户可以访问 dri 设备(可以通过 acl 来做到这一点)
  • 将另一个自定义路径绑定到容器中。我们需要从主机到容器共享 gamescope 文件,例如,在本主题中,我在主机和容器上都使用路径 /share-with-container/

提示:您可以在容器中运行无头桌面,例如:sway。

在容器中安装 proton 和游戏

你可以使用一些启动器来帮助你设置 proton 和游戏,我使用 Faugus。

运行

在主机上(请注意,我假设您已经在主机上运行桌面,其套接字为 /run/user/1000/wayland-1):

touch /share-with-container/tmp/.X{0..10}-lock

systemd-run --user --wait --pty -p "BindPaths=/share-with-container/tmp:/tmp" \
-E XDG_RUNTIME_DIR=/share-with-container/runtime \
-E WAYLAND_DISPLAY=/run/user/1000/wayland-1 \
gamescope \
--expose-wayland -W 2560 -H 1422 --borderless --force-windows-fullscreen -- sleep infinity

systemd-run --user --wait -p "BindPaths=/share-with-container/tmp:/tmp" \
-E DISPLAY=:11 \
xhost '+'

在容器中(将 … 部分替换为游戏启动命令):

systemd-run --user --wait -p "BindPaths=/share-with-container/tmp:/tmp" \
-E DISPLAY=:11 \
-E WAYLAND_DISPLAY=/share-with-container/runtime/gamescope-0 \
...

音频

我用的是 pipewire netjack2 来将容器内的声音转到宿主机播放

我测试过的游戏

  • 红色警戒3
  • 侠盗猎车手 SA
  • 刺客信条2