gVisor 是用户态内核,通过 runsc 拦截并模拟系统调用,与 runc 等直接调用宿主机内核的 Runtime 有本质区别:它提供更强隔离但牺牲 syscall 兼容性、性能及调试能力。

gVisor 是什么,和普通 ContainerRuntime 有什么根本区别
gVisor 不是 Docker 或 containerd 那种传统 Runtime,它是个用户态内核(user-space kernel),运行在容器进程和宿主机之间,拦截并重实现系统调用。普通 Runtime(如 runc)直接让容器进程调用宿主机内核,而 gVisor 把 read、open、clone 这类调用全接过来,在自己的 runsc 进程里模拟执行——相当于给每个容器套了个轻量“内核沙箱”。
- 宿主机内核完全不暴露给容器进程,逃逸风险显著降低
- 但所有系统调用都要经过用户态转发,I/O 密集型场景(比如大量小文件读写、高并发 socket)延迟明显上升
- 不支持全部 Linux syscall,比如
epoll_wait在早期版本有兼容问题,perf_event_open、bpf等调试/观测类调用基本不可用
什么时候该选 gVisor,而不是 runc 或 Kata Containers
选 gVisor 的核心前提是:你愿意为更强的隔离性,接受明确的性能折损和功能限制。
- 适合场景:
多租户函数即服务(FaaS)、不可信代码沙箱(如在线编译/执行平台)、CI 中运行第三方构建脚本 - 不适合场景:
数据库容器、实时音视频处理、需要 /dev/kvm 或 GPU 支持的应用 - 和 Kata 对比:Kata 用轻量虚拟机,启动慢、内存开销大但 syscall 兼容性接近原生;gVisor 启动快、内存占用低,但 syscall 模拟不全,遇到
ENOSYS错误很常见
部署 gVisor 常见报错和绕不过去的坑
最常卡在三件事上:集成方式、syscall 白名单、rootfs 权限模型。
-
runsc必须和containerd配合使用,不能直接塞进 Docker daemon;Docker 用户得先换到containerd+cri-containerd - 默认禁用很多 syscall,比如
settimeofday、modify_ldt,容器里跑 JVM 可能因clock_gettime行为差异抛java.lang.UnsatisfiedLinkError -
runsc默认以非 root 用户启动 sandbox 进程,如果容器镜像里用了USER 0且依赖/proc/sys写入,会静默失败——得显式加--platform=ptrace或调整spec.json中的linux.security_context - 日志藏得深:
runsc --debug --log-level=debug输出全在/tmp/runsc.*.log,不是标准容器日志流
怎么验证 gVisor 确实生效了,而不是 fallback 到 runc
别信配置文件,看进程树和 syscall 拦截日志。
- 运行容器后执行
ps auxf | grep runsc,看到runsc-gofer和runsc-sandbox进程才对;只有runc init就说明没切过去 - 在容器里执行
cat /proc/1/status | grep Tgid,如果 PID 1 的Tgid和宿主机上runsc-sandbox进程 PID 一致,说明被接管了 - 最硬核验证:在容器里跑
strace -e trace=openat,read,write ls /,输出里全是--- SIGSYS {si_signo=SIGSYS, ...} ---,代表 syscall 被 gVisor 拦截并模拟,不是直通内核
gVisor 的安全收益是真实的,但它的“沙箱”边界不是靠黑科技,而是靠主动放弃一部分兼容性换来的。一旦你的应用碰到了没被模拟的 syscall,或者依赖内核模块行为(比如某些 eBPF 程序),它不会警告你,只会静默失败或返回错误码。上线前必须用真实 workload 做 syscall trace 验证,不能只靠 hello world 测试。










