标签: docker

  • Runtime 后端:qwrap 与 Container 两种隔离模式详解

    在沙箱运行时中,”隔离”是核心诉求。qwrap(基于 bwrap user namespace)和 Container(podman/docker)是两种主流后端。它们解决的是同一个问题——让代码在受限环境中运行——但走的是完全不同的路。本文用大量类比帮你理解两者的异同。

    先建立一个直觉:两种”锁门”的方式

    想象你要把一个不太信任的人关在房间里干活:

    • qwrap 方式:你在现有的房子里,用隔板把一个角落围起来,只留一个小窗口递材料进去。墙还是原来的墙,地板还是原来的地板,但那个人只能看到隔板里面的东西。

    • Container 方式:你直接造了一个集装箱,里面有独立的电、水、通风系统。把人塞进去,关上门。他感觉自己在一个完整的小房子里,完全不知道外面长什么样。

    这就是两者最本质的区别:qwrap 是轻量级视图隔离,Container 是完整环境封装

    什么是 qwrap(bwrap user namespace)

    qwrap 底层使用 bubblewrap(bwrap),一个利用 Linux user namespace 做沙箱的工具。

    工作原理

    宿主机文件系统
    ├── /usr/bin/python3          ← 宿主机的 Python
    ├── /home/user/project/       ← 用户项目
    └── /tmp/secrets/             ← 敏感文件
    
    qwrap 沙箱视图(进程看到的)
    ├── /usr/bin/python3          ← bind-mount 进来的,只读
    ├── /workspace/               ← 只暴露了项目目录
    └── (/tmp/secrets/ 不存在)   ← 根本看不到
    

    关键机制:
    User Namespace:进程以为自己是 root,实际上映射到宿主机上的普通用户
    Mount Namespace:只 bind-mount 需要的目录进去,其余不可见
    没有镜像、没有层、没有网络命名空间(除非额外配置)

    类比:VPN 分流

    qwrap 就像手机上的 VPN 分流规则——你不是给整个手机套了一层 VPN,而是只让特定 App 走代理。系统还是那个系统,只是”视野”被限制了。

    什么是 Container(podman/docker)

    Container 是一个完整的隔离运行环境,底层用了 Linux 的多种 namespace(pid, net, mount, uts, ipc)加上 cgroups 做资源限制。

    工作原理

    宿主机
    └── 运行 podman/docker daemon(或 rootless 直接 fork)
    
    Container 内部
    ├── /usr/bin/python3          ← 镜像自带的,跟宿主机可能版本不同
    ├── /workspace/               ← volume mount 进来的
    ├── 独立的 PID 1              ← 进程树从 1 开始
    ├── 独立的网络栈              ← 有自己的 eth0、IP 地址
    └── 独立的 hostname           ← 不是宿主机名
    

    关键机制:
    OCI 镜像:环境完全打包,包括 OS 基础层、依赖库、工具链
    多维 Namespace:PID、网络、挂载、主机名全部隔离
    Cgroups:CPU、内存、IO 可以设上限
    分层文件系统:OverlayFS,写操作不影响底层镜像

    类比:虚拟机的”穷人版”

    Container 就像一台”轻量虚拟机”——没有虚拟化硬件的开销,但给进程的体验几乎等同于独占一台机器。

    核心差异对比

    启动速度

    • qwrap:毫秒级。本质就是 clone() + 设置几个 namespace + exec,跟启动一个普通进程差不多。
    • Container:百毫秒到秒级。需要准备 rootfs(解压层/挂载 overlay)、配置网络、启动 init 进程。

    举例:你有一个 AI Agent 要反复执行用户提交的 Python 片段,每次执行都需要隔离。如果用 Container,每次 docker rundocker rm,一秒调一次就吃不消了。qwrap 可以做到每秒启动几十个沙箱实例。

    隔离强度

    • qwrap:中等。进程仍然共享宿主机内核,网络默认不隔离(可以访问外网),只做了文件系统视图裁剪和权限降级。
    • Container:强。网络、PID、文件系统全面隔离。配合 seccomp profile 还能限制系统调用。

    举例:如果沙箱里的代码尝试 kill -9 1(杀 init 进程):
    – qwrap:由于在 user namespace 里没有 CAP_KILL 对宿主机进程的权限,操作被内核拒绝,但进程能”看到”宿主机的 PID(除非额外加了 PID namespace)。
    – Container:进程看到的 PID 1 是容器自己的 init,杀了也只是容器自己挂掉,宿主机毫发无伤。

    环境一致性

    • qwrap:依赖宿主机环境。如果宿主机上没装 numpy,沙箱里也没有(除非你把 virtualenv 目录 mount 进去)。
    • Container:自带环境。镜像里装了什么就有什么,跟宿主机装了什么无关。

    举例:你的 CI 跑在一台 Ubuntu 22.04 的机器上,但项目需要 Python 3.12 + CUDA 12。
    – qwrap 方案:你得先在宿主机上装好 Python 3.12 和 CUDA,然后 qwrap 只是限制可见范围。
    – Container 方案:直接 FROM nvidia/cuda:12.0-python3.12,镜像里全都有,宿主机哪怕是 CentOS 7 都无所谓。

    资源开销

    • qwrap:接近零开销。没有额外进程、没有 overlay 文件系统、没有虚拟网桥。沙箱就是一个”被限制了视野的进程”。
    • Container:轻量但有感知。每个容器有自己的 mount 栈、可能有 veth pair、有 cgroup 控制器在跟踪。跑几个没事,跑几百个时网络和存储开销开始累积。

    可移植性

    • qwrap:只能在 Linux 上用(依赖 user namespace),且要求内核版本 ≥ 3.8。不同发行版对 user namespace 的策略不同(Ubuntu 默认开启,Debian/RHEL 可能需要 sysctl 调整)。
    • Container:跨平台。macOS/Windows 通过 VM 垫层(Docker Desktop、Podman Machine)也能跑。镜像是标准 OCI 格式,随处可部署。

    什么时候选 qwrap

    • 需要极快的启动/销毁周期(Agent 每次工具调用都起一个沙箱)
    • 宿主机环境已经准备好了,只需要”限制可见性”
    • 不需要网络隔离(或者愿意手动用 iptables 管理)
    • 对资源敏感,不想为隔离付出额外内存/存储开销
    • 运行环境确定是 Linux,且内核支持 user namespace

    典型场景:代码执行沙箱。AI 编程助手让 LLM 生成的代码在 qwrap 里跑,跑完就丢。一秒可能跑几十次,每次只需要 Python 解释器 + 有限的文件访问。

    什么时候选 Container

    • 需要完整、可复现的运行环境(”在我机器上能跑”的问题直接消失)
    • 需要强隔离(不信任的代码、多租户场景)
    • 需要网络隔离(每个任务一个独立网络栈)
    • 需要跨平台部署
    • 生命周期较长(服务型进程、长时间运行的任务)

    典型场景:CI/CD Pipeline。每次构建在一个干净的容器里进行,确保环境一致性。或者多租户 SaaS,每个租户的自定义逻辑跑在独立容器里,资源和网络完全隔离。

    能不能结合使用?

    可以,而且这是很常见的模式:

    • 外层 Container + 内层 qwrap:Container 提供环境一致性和粗粒度隔离,qwrap 在容器内部做细粒度的进程级沙箱。比如一个容器内跑着 Agent 服务,Agent 每次调用工具时用 qwrap 起沙箱执行。

    • qwrap 做”轻量容器”的替代:在开发环境中不想装 Docker,但需要一定隔离性,qwrap 可以充当极简替代品。

    一张表总结

    • 启动延迟:qwrap 毫秒级 / Container 百毫秒~秒级
    • 隔离维度:qwrap 文件系统+用户权限 / Container 文件系统+网络+PID+资源
    • 环境依赖:qwrap 依赖宿主机 / Container 自包含镜像
    • 资源开销:qwrap 接近零 / Container 轻量但可感知
    • 可移植性:qwrap 仅 Linux / Container 跨平台
    • 适合场景:qwrap 高频短生命周期 / Container 长生命周期+强隔离

    总结

    选 qwrap 还是 Container,本质上是在”轻”和”全”之间做取舍:

    • 如果你要的是”快速给进程戴上眼罩”——选 qwrap
    • 如果你要的是”把进程关进一个独立的集装箱”——选 Container

    理解了这个区别,在设计沙箱系统时你就能做出合理的分层决策:用 Container 解决环境一致性问题,用 qwrap 解决高频隔离执行问题,两者搭配覆盖从 CI 到 Agent Runtime 的全部场景。