mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
1983 字
6 分钟
沙箱运行时:gVisor/Kata
2026-05-22

runc 容器共享宿主内核——这是它最大的安全弱点。一旦攻击者利用内核漏洞突破 Namespace 隔离,就能控制整个宿主机。CVE-2019-5736(runc 容器逃逸)、CVE-2022-0185(内核 Namespace 逃逸)、CVE-2024-1086(内核 netfilter UAF)——这些漏洞提醒我们,共享内核的隔离模型存在根本性风险。

沙箱运行时通过不共享内核来解决这一问题。gVisor 实现了一个用户态内核,拦截并处理容器的所有系统调用;Kata Containers 用轻量级虚拟机为每个容器/Pod 提供独立的内核。两种方案都兼容 OCI Runtime Spec,可以无缝替换 runc。

沙箱运行时的兴起直接源于 runc 容器逃逸漏洞的频发。2019 年 2 月,CVE-2019-5736 震惊容器社区——攻击者可以通过容器内的 /proc/self/exe 覆盖宿主机的 runc 二进制文件,实现容器逃逸。这个漏洞的根本原因是 runc 容器与宿主机共享内核,任何内核漏洞都可能成为逃逸的跳板。Google 的 gVisor(2018 年开源)选择”拦截”路线——用用户态内核 Sentry 拦截所有系统调用,容器进程永远不直接接触宿主内核。OpenStack 的 Kata Containers(2017 年合并自 Clear Containers 和 runV)选择”隔离”路线——用轻量级虚拟机为每个 Pod 提供独立内核,硬件级隔离杜绝内核共享。两种哲学各有取舍:gVisor 兼容性更好但系统调用拦截有性能开销,Kata 隔离更强但 VM 启动有额外延迟。理解这两种路线的设计动机,是选择沙箱方案的关键。

前置知识#

  • Ch10 容器安全:seccomp/AppArmor/Capabilities:沙箱运行时是容器安全的”最后一层”——在 Namespace/Capabilities/seccomp/AppArmor 之上提供更强的隔离
  • Ch06 runc 源码分析:沙箱运行时替代 runc 成为容器的低层运行时,理解 runc 有助于理解沙箱的改进
  • 虚拟化基础:KVM、QEMU、VM 的基本概念(Kata Containers 相关)
Note

沙箱运行时并非”更好的 runc”,而是”不同安全需求下的选择”。对于可信工作负载,runc + 安全加固已经足够;对于不可信代码(如 CI/CD 中的用户提交代码、多租户平台),沙箱运行时是必要的安全保障。

一、为什么需要沙箱运行时#

1.1 runc 的安全边界#

graph TB subgraph runc容器["runc 容器(共享内核)"] APP1["容器进程 A"] APP2["容器进程 B"] KERNEL["宿主内核<br/>(共享)"] end subgraph 攻击路径["攻击路径"] VULN["内核漏洞<br/>CVE-2024-1086"] ESCAPE["Namespace 逃逸"] HOST["宿主机控制"] end APP1 --> KERNEL APP2 --> KERNEL VULN --> ESCAPE --> HOST style runc容器 fill:#ffcdd2,stroke:#c62828 style 攻击路径 fill:#ffcdd2,stroke:#c62828

1.2 沙箱运行时的安全模型#

graph TB subgraph gVisor["gVisor(用户态内核)"] APP3["容器进程"] SENTRY["Sentry<br/>(用户态内核)"] KERNEL3["宿主内核<br/>(最小攻击面)"] end subgraph Kata["Kata Containers(VM 隔离)"] APP4["容器进程"] GUEST["Guest 内核<br/>(独立内核)"] VMM["VMM (QEMU/Cloud-Hypervisor)"] KERNEL4["宿主内核"] end APP3 -->|"系统调用"| SENTRY SENTRY -->|"少量系统调用"| KERNEL3 APP4 -->|"系统调用"| GUEST GUEST -->|"虚拟硬件"| VMM VMM -->|"少量系统调用"| KERNEL4 style gVisor fill:#c8e6c9,stroke:#2e7d32 style Kata fill:#bbdefb,stroke:#1565c0

1.3 三种运行时对比#

维度runcgVisorKata Containers
隔离级别Namespace用户态内核硬件虚拟化
内核共享共享宿主内核不共享(Sentry 代理)不共享(独立 Guest 内核)
攻击面全部系统调用Sentry 实现的系统调用虚拟硬件接口
性能损耗基准5-20%10-30%
启动时间50-200ms100-500ms1-3s
兼容性完全兼容部分兼容完全兼容
内存开销极低中等(Sentry 进程)较高(VM 内存)

二、gVisor#

2.1 gVisor 架构#

gVisor 由三个核心组件组成:

组件功能运行位置
Sentry用户态内核,实现 Linux 系统调用用户态进程
Gofer文件系统代理,处理容器文件 IO用户态进程
runscOCI 运行时,替代 runc容器运行时
graph TB subgraph 容器沙箱["gVisor 容器沙箱"] APP["容器进程<br/>(在 Sentry 的用户态内核上运行)"] SENTRY["Sentry<br/>(用户态内核)<br/>实现 ~200 个系统调用"] GOFER["Gofer<br/>(文件系统代理)"] end subgraph 宿主机["宿主机"] KERNEL["Linux 内核"] FS["宿主文件系统"] NET["宿主网络"] end APP -->|"系统调用"| SENTRY SENTRY -->|"文件 IO(9P 协议)"| GOFER GOFER -->|"系统调用"| FS SENTRY -->|"网络 IO(少量系统调用)"| KERNEL KERNEL --> NET style 容器沙箱 fill:#e0f2f1,stroke:#00695c style 宿主机 fill:#e8eaf6,stroke:#283593

2.2 Sentry 的工作原理#

Sentry 实现了约 200 个 Linux 系统调用,容器进程的每个系统调用都由 Sentry 处理:

// Sentry 的系统调用处理(简化)
func (k *Kernel) Syscall(sysno uintptr, args ...uintptr) (uintptr, error) {
switch sysno {
case linux.SYS_READ:
return k.Read(args[0], args[1], args[2])
case linux.SYS_WRITE:
return k.Write(args[0], args[1], args[2])
case linux.SYS_OPENAT:
return k.OpenAt(args[0], args[1], args[2], args[3])
case linux.SYS_MMAP:
return k.MMap(args[0], args[1], args[2], args[3], args[4], args[5])
case linux.SYS_SOCKET:
return k.Socket(args[0], args[1], args[2])
case linux.SYS_CLONE:
return k.Clone(args[0], args[1], args[2], args[3], args[4])
case linux.SYS_EPOLL_WAIT:
return k.EpollWait(args[0], args[1], args[2], args[3])
case linux.SYS_FUTEX:
return k.Futex(args[0], args[1], args[2], args[3], args[4], args[5])
case linux.SYS_SCHED_GETAFFINITY:
return k.SchedGetAffinity(args[0], args[1], args[2])
// ... ~200 个系统调用
default:
// 未实现的系统调用返回 ENOSYS 错误
// Sentry 不会将未知系统调用传递给宿主内核
description: " // 这与 seccomp 的"允许+过滤"模型不同——Sentry 是"实现+拒绝"
return 0, syscall.ENOSYS
}
}

2.3 gVisor 的文件系统#

gVisor 通过 Gofer 进程代理容器的文件 IO,使用 9P 协议通信:

# gVisor 的文件系统架构
# 容器进程 → Sentry → 9P 协议 → Gofer → 宿主文件系统
# Gofer 进程负责:
# 1. 打开/读取/写入文件
# 2. 目录遍历
# 3. 文件元数据操作
# 4. 设备文件处理
# 这种架构确保 Sentry 不直接访问宿主文件系统

2.4 runsc:gVisor 的 OCI 运行时#

# 安装 gVisor
sudo apt install runsc
# 配置 Docker 使用 gVisor
sudo dockerd --add-runtime=runsc=/usr/bin/runsc
# 使用 gVisor 运行容器
docker run --runtime=runsc -d nginx
# 在 Kubernetes 中使用
# Pod 注解:runtimeClassName: gvisor
# Kubernetes RuntimeClass 配置
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: gvisor
handler: runsc
---
apiVersion: v1
kind: Pod
spec:
runtimeClassName: gvisor
containers:
- name: nginx
image: nginx

2.5 gVisor 的兼容性限制#

类别兼容不兼容
语言运行时Go, Python, Java, Node.js
Web 服务器nginx, Apache, Caddy
数据库Redis, MemcachedMySQL (ioctl 限制)
系统调用~200 个常用系统调用ptrace, perf_event_open 等
文件系统常规文件操作inotify (有限), fanotify
网络TCP/UDP/Unix socketraw socket, packet socket

三、Kata Containers#

3.1 Kata Containers 架构#

Kata Containers 为每个容器/Pod 创建一个轻量级虚拟机:

graph TB subgraph 宿主机["宿主机"] CTNRD["containerd"] SHIM["containerd-shim-kata-v2"] VMM["VMM<br/>QEMU / Cloud-Hypervisor"] end subgraph 虚拟机["轻量级 VM"] GUEST_KERNEL["Guest 内核<br/>(独立内核)"] INIT["kata-agent<br/>(容器 init)"] APP["容器进程"] end CTNRD --> SHIM SHIM --> VMM VMM --> GUEST_KERNEL GUEST_KERNEL --> INIT INIT --> APP SHIM -.->|"gRPC (vsock)"| INIT style 宿主机 fill:#e8eaf6,stroke:#283593 style 虚拟机 fill:#c8e6c9,stroke:#2e7d32

3.2 Kata 的核心组件#

组件功能
containerd-shim-kata-v2Kata 的 shim,替代 containerd-shim
VMM虚拟机监控器(QEMU 或 Cloud-Hypervisor)
Guest 内核独立的 Linux 内核,专为容器优化
kata-agentVM 内的代理,接收 shim 的 gRPC 指令
kata-runtimeOCI 运行时,替代 runc

3.3 Kata 的启动流程#

sequenceDiagram participant CTNRD as containerd participant SHIM as shim-kata-v2 participant VMM as VMM (QEMU) participant GUEST as Guest 内核 participant AGENT as kata-agent participant APP as 容器进程 CTNRD->>SHIM: Create container SHIM->>VMM: 启动 VM VMM->>GUEST: 引导 Guest 内核 GUEST->>AGENT: 启动 kata-agent SHIM->>AGENT: gRPC: CreateSandbox AGENT->>AGENT: 创建沙箱环境 SHIM->>AGENT: gRPC: CreateContainer AGENT->>AGENT: 设置 Namespace/Cgroup AGENT->>APP: 执行容器进程 SHIM->>AGENT: gRPC: StartContainer AGENT->>APP: 启动容器进程

3.4 Kata 的资源优化#

/etc/kata-containers/configuration.toml
# Kata Containers 配置(简化)
[hypervisor.qemu]
# VM 内存配置
default_memory = 2048 # 默认 2GB
default_vcpus = 1 # 默认 1 vCPU
# 优化选项
enable_mem_prealloc = false
enable_hugepages = false
file_backed_mem_dir = "/dev/shm"
# 安全选项
disable_seccomp = false
kernel_params = "agent.log=debug"
[agent]
# kata-agent 配置
enable_tracing = false

3.5 Kata vs gVisor 性能对比#

指标runcgVisor (runsc)Kata Containers
启动时间~100ms~500ms~2s(需启动 VM)
内存开销~10MB~50MB(Sentry)~200MB(VM + Guest 内核)
文件 I/O原生速度慢 2-5x(Sentry 代理)接近原生(VirtioFS)
网络 I/O原生速度慢 1.5-3x(Sentry 代理)接近原生(Virtio 网络)
安全边界Namespace+Cgroup用户态内核隔离硬件虚拟化隔离
适用场景可信工作负载不可信代码、多租户强隔离需求、合规要求
Tip

在 Kubernetes 中可以混合使用多种运行时:默认 Pod 用 runc,不可信工作负载用 gVisor(通过 RuntimeClass 切换),合规场景用 Kata。既保证安全,又不牺牲可信工作负载的性能。

指标runcgVisorKata
启动时间50ms200ms2s
内存开销0MB30MB128MB
CPU 开销0%5-20%10-30%
网络吞吐10 Gbps5-8 Gbps3-7 Gbps
文件 IO基准70-90%80-95%
兼容性100%~90%~99%

四、沙箱运行时的选型#

4.1 选型决策树#

flowchart TB START["需要更强隔离?"] --> Q1{"运行不可信代码?"} Q1 -->|否| RUNC["runc<br/>(默认运行时)"] Q1 -->|是| Q2{"需要完整 Linux 兼容性?"} Q2 -->|否| Q3{"对启动延迟敏感?"} Q3 -->|是| GVISOR["gVisor<br/>(用户态内核)"] Q3 -->|否| Q4{"需要内核级隔离?"} Q4 -->|是| KATA["Kata Containers<br/>(VM 隔离)"] Q4 -->|否| GVISOR Q2 -->|是| KATA style RUNC fill:#bbdefb,stroke:#1565c0 style GVISOR fill:#c8e6c9,stroke:#2e7d32 style KATA fill:#fff3e0,stroke:#e65100

4.2 典型场景推荐#

场景推荐运行时原因
内部微服务runc信任环境,性能优先
CI/CD 运行用户代码gVisor不可信代码,快速启动
多租户 SaaSKata强隔离,完整兼容性
金融/合规Kata硬件级隔离,审计友好
FaaS/ServerlessgVisor快速启动,低开销
AI/ML 工作负载runc + GPU需要 GPU 直通

五、动手实践#

5.1 安装和测试 gVisor#

#!/bin/bash
# 安装和测试 gVisor
# 1. 安装 runsc
curl -fsSL https://gvisor.dev/archive.key | sudo gpg --dearmor -o /usr/share/keyrings/gvisor-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/gvisor-archive-keyring.gpg] https://storage.googleapis.com/gvisor/releases release main" | sudo tee /etc/apt/sources.list.d/gvisor.list
sudo apt update && sudo apt install runsc
# 2. 配置 Docker
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json << 'EOF'
{
"runtimes": {
"runsc": {
"path": "/usr/bin/runsc"
}
}
}
EOF
sudo systemctl restart docker
# 3. 测试 gVisor 容器
docker run --runtime=runsc -it alpine uname -a
# Linux 4.4.0 ... # 这是 Sentry 报告的内核版本
# 4. 对比 runc 容器
docker run --runtime=runc -it alpine uname -a
# Linux 5.15.0 ... # 这是宿主机内核版本
# 5. 测试系统调用限制
docker run --runtime=runsc -it alpine sh -c "strace ls 2>&1 | head -5"
# gVisor 会限制某些系统调用

5.2 安装和测试 Kata Containers#

#!/bin/bash
# 安装和测试 Kata Containers
# 1. 安装 Kata
sudo apt install kata-containers
# 2. 配置 containerd
sudo tee -a /etc/containerd/config.toml << 'EOF'
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata]
runtime_type = "io.containerd.kata.v2"
pod_annotations = ["io.kata-containers.*"]
EOF
sudo systemctl restart containerd
# 3. 测试 Kata 容器
sudo ctr run --runtime io.containerd.kata.v2 -t docker.io/library/alpine:latest kata-test /bin/sh
# 4. 验证 VM 隔离
sudo ctr task exec --exec-id test kata-test cat /proc/cpuinfo
# 输出显示 VM 的虚拟 CPU 信息

六、gVisor 与 Kata 的内部机制对比#

6.1 系统调用处理路径#

flowchart TB subgraph gVisor路径["gViso 系统调用路径"] G1["容器进程<br/>syscall"] --> G2["Sentry<br/>用户态内核处理"] G2 --> G3{"Sentry 能处理?"} G3 -->|是| G4["返回结果<br/>(不进入内核)"] G3 -->|否| G5["通过 host syscall<br/>进入内核"] end subgraph Kata路径["Kata 系统调用路径"] K1["容器进程<br/>syscall"] --> K2["Guest 内核<br/>VM 内核处理"] K2 --> K3{"需要宿主资源?"} K3 -->|否| K4["返回结果<br/>(在 VM 内完成)"] K3 -->|是| K5["VMM 退出<br/>→ 宿主内核处理"] end style gVisor路径 fill:#c8e6c9,stroke:#2e7d32 style Kata路径 fill:#bbdefb,stroke:#1565c0

6.2 gVisor vs Kata vs 原生性能实测#

不同工作负载下三种运行时的性能差异显著,以下为典型基准测试数据:

工作负载runc(原生)gVisorKatagVisor 损耗Kata 损耗
Redis GET QPS110,00085,00072,000-23%-35%
nginx RPS42,00035,00028,000-17%-33%
文件写入吞吐520 MB/s380 MB/s460 MB/s-27%-12%
Python 启动45ms120ms2,100ms+167%+4,567%
网络延迟 (p99)0.3ms0.8ms1.2ms+167%+300%

6.3 内存开销分析#

组件gVisor 内存Kata 内存
运行时进程Sentry: ~30MBVMM: ~50MB
内核无(Sentry 代理)Guest 内核: ~80MB
容器进程与 runc 相同与 runc 相同
总计额外~30MB~130MB

6.4 Kubernetes 集成配置#

# Kubernetes RuntimeClass 配置示例
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: gvisor
handler: runsc
overhead:
podFixed:
memory: "30Mi"
cpu: "100m"
---
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: kata
handler: kata
overhead:
podFixed:
memory: "130Mi"
cpu: "200m"

七、本章小结#

上一章从全景视角介绍了容器安全机制。

运行时隔离方式性能兼容性适用场景
runcNamespace+Cgroup信任环境
gVisor用户态内核不可信代码、快速启动
Kata轻量级 VM多租户、合规
Warning

沙箱运行时无法防止所有攻击——如果攻击者控制了宿主内核(如通过硬件漏洞),gVisor 和 Kata 都无法阻止。沙箱运行时增加的是攻击成本,而非绝对安全。

Note

沙箱运行时不是银弹——它们在安全性和性能之间做了不同的权衡。选择哪种运行时取决于你的威胁模型:如果信任容器内的代码,runc 就够了;如果运行不可信代码,gVisor 提供了好的平衡;如果需要硬件级隔离,Kata 是最佳选择。

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

沙箱运行时:gVisor/Kata
https://blog.souloss.com/posts/container-runtime/sandbox-runtime/
作者
Souloss
发布于
2026-05-22
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

相关文章 智能推荐
1
综合实战:构建一个迷你容器运行时
容器运行时 综合实战——用 Go 从零构建一个迷你容器运行时——实现 Namespace 隔离(PID/Mount/UTS/IPC/Network)、Cgroup 资源限制(CPU/内存)、OverlayFS 分层文件系统、OCI Bundle 解析,最终实现一个能运行容器的 minirunc。将前 15 章的知识融会贯通,从「理解原理」到「动手实现」。
2
系列导读
容器运行时 本系列从 Linux 内核的 Namespace、Cgroup、OverlayFS 出发,深入 OCI 规范、runc 源码、containerd 架构,再到容器安全、沙箱运行时、网络、存储、镜像构建、Wasm 容器,最后综合实战构建一个迷你容器运行时——从「会用 Docker」到「理解容器运行时的每一行代码」,每章配有可运行的代码示例与架构图,让你从容器用户进阶到容器运行时工程师。
3
容器在 Kubernetes 中
容器运行时 深入容器在 Kubernetes 中的运行机制——CRI 接口、Pod Sandbox 创建、多容器模式、Init Container、Sidecar 注入,以及 kubelet 如何通过 CRI 与容器运行时交互。
4
Wasm 容器:WasmEdge/WASI
容器运行时 WebAssembly(Wasm)正在从浏览器走向服务器——WASI(WebAssembly System Interface)定义了 Wasm 访问操作系统的标准接口,WasmEdge/runwasi 等运行时让 Wasm 模块可以作为容器运行。详细解读 Wasm 容器的架构、WASI 接口、与 OCI 的集成、与 Linux 容器的对比——从「Wasm 只能在浏览器运行」到「Wasm 是下一代容器运行时」。
5
容器安全:seccomp/AppArmor/Capabilities
容器运行时 容器的安全边界在哪里?Namespace 提供视图隔离,但不阻止特权操作。从零讲透 Linux 安全模块在容器中的应用——Capabilities(权限细分)、seccomp(系统调用过滤)、AppArmor(文件访问控制),以及 rootless 容器的实现,让你理解容器的安全边界和加固方法。