运行时后端:Docker 与 Kubernetes、池化、快照、暂停恢复
30 秒导读: 02 控制面 讲清了「一次 create 请求怎么走到后端」;本章接着讲后端自己怎么把容器造出来、管起来。OpenSandbox 把「造容器」抽象成一个契约,背后挂两个可插拔实现:Docker(单机,直接调 daemon)和 Kubernetes(集群,提交 CRD 交给 operator)。同一份 create 请求,在 Docker 上变成一次
create_container,在 K8s 上变成一个BatchSandbox对象。之后三个高级能力——预热池、快照、暂停/恢复——两个后端语义一致,但实现天差地别。
1. 这章在整幅拼图里的位置(先对齐坐标)
先用一句话锚定边界,避免和相邻章重叠:
- 01 协议与生命周期 定义了「沙箱」这个抽象对象、它的状态机(Creating→Running→Paused→…)、以及对外协议。
- 02 控制面 讲一次
POST /sandboxes如何被鉴权、校验、路由,最后分派到某个运行时后端。 - 本章(03) 只讲后端如何真正造/管容器:镜像、端口、卷、网络命名空间、Pod 模板、池、快照、暂停。
- 04 execd 数据面 讲容器跑起来之后,里面那个 execd 守护进程怎么执行命令、传文件、开 PTY。
- 05 嵌套隔离 讲容器内再用 bubblewrap 套一层。
- 06 网络与保险箱 讲入站路由、出站策略、凭证注入的网络组件本身。
一句话:本章是「造容器的车间」,不是「容器里干活的工人」,也不是「车间外的管网」。
2. 顶层全景:一套契约,两个后端
2.1 为什么要「可插拔后端」
OpenSandbox 想让同一个 SDK / 同一套 API,既能在开发者笔记本上(一个 Docker daemon)跑,也能在生产集群里(成百上千节点的 K8s)跑。做法是经典的策略模式:控制面只依赖一个抽象接口,具体用哪个后端由配置 runtime.type 决定。
两个后端的根本差异,一句话概括:
| 维度 | Docker 后端 | Kubernetes 后端 |
|---|---|---|
| 造容器的方式 | 命令式:直接调 daemon 的 create_container / start | 声明式:向 API Server 提交一个 CRD 对象,operator 异步把它变成 Pod |
| 谁真正干活 | 服务进程自己(同步) | 集群里的 operator + kube-scheduler(异步) |
| 状态从哪读 | docker inspect 容器 | 读 CRD 的 .status,必要时再看 Pod |
| 天然支持 | 单机、快 | 多节点调度、池、签名路由、secureAccess |
| 快照实现 | docker commit 内联 | 派发一个 Job 跑 nerdctl commit |
| 暂停实现 | 冻结进程(freezer) | 快照 rootfs + 删 Pod + 重建 |
2.2 一张图看清分派
下面这张图从上往下读:控制面拿到规范化后的请求,按 runtime.type 选一条路;两条路最终都产出「一个跑着 execd 的容器/Pod」。
┌───────────────────────────────┐
│ 控制面(见 02):鉴权→校验→ │
│ 快照恢复→选后端(runtime.type)│
└───────────────┬───────────────┘
runtime.type=docker │ runtime.type=kubernetes
┌─────────────────────┴─────────────────────┐
▼ ▼
┌────────────────────────┐ ┌──────────────────────────────┐
│ DockerSandboxService │ │ KubernetesSandboxService │
│ (一堆 Mixin 拼成) │ │ → WorkloadProvider(工厂选) │
├────────────────────────┤ ├──────────────────────────────┤
│ ① 拉镜像/校验平台 │ │ ① 组装 initContainer(装execd)│
│ ② 造 host_config(安全/ │ │ ② 组装 main container │
│ 资源/GPU/runtime) │ │ ③ 合并 YAML 模板 │
│ ③ create_container │ │ ④ create BatchSandbox CRD │
│ ④ 注入 execd(put_archive)│ └───────────────┬──────────────┘
│ ⑤ container.start() │ │ 声明式,交给集群
└───────────┬─────────────┘ ▼
│ 命令式,立刻生效 ┌────────────────────────────────────┐
▼ │ operator(Go):Reconcile │
┌────────────────────────┐ │ · batchsandbox_controller 造 Pod │
│ Docker 容器 │ │ · pool_controller 管预热池 │
│ 跑着 execd + 用户进程 │ │ · sandboxsnapshot_controller 派 Job │
└────────────────────────┘ │ · kube-scheduler 落到某节点 │
└──────────────────┬─────────────────┘
▼
┌────────────────────────┐
│ Pod:initContainer 装 │
│ execd → main 容器跑起来 │
└────────────────────────┘
部件一句话职责:
| 部件 | 干什么 | 在哪 |
|---|---|---|
DockerSandboxService | Docker 后端总入口,由若干 Mixin 组合而成 | server/opensandbox_server/services/docker/docker_service.py |
WorkloadProvider | K8s「造 CRD」的抽象接口 | server/opensandbox_server/services/k8s/workload_provider.py:27 |
BatchSandboxProvider | 默认 K8s provider,支持池/暂停/快照 | services/k8s/batchsandbox_provider.py:64 |
AgentSandboxProvider | 对接上游 agents.x-k8s.io Sandbox CRD | services/k8s/agent_sandbox_provider.py:74 |
| operator | 集群侧 Go 控制器,把 CRD 变成 Pod | kubernetes/internal/controller/ |
3. 后端契约:两个后端到底承诺了什么
在钻进任一后端前,先看它们共同实现的契约。K8s 侧这个契约写得最清楚——WorkloadProvider 抽象基类:
# services/k8s/workload_provider.py:27 WorkloadProvider(ABC)
class WorkloadProvider(ABC):
@abstractmethod
def create_workload(self, sandbox_id, namespace, image_spec, entrypoint,
env, resource_limits, labels, expires_at, execd_image,
...) -> Dict[str, Any]: ... # 造工作负载
@abstractmethod
def get_workload(self, sandbox_id, namespace): ... # 查
@abstractmethod
def delete_workload(self, sandbox_id, namespace): ... # 删
@abstractmethod
def get_status(self, workload) -> Dict[str, Any]: ... # 状态机映射
# 默认抛 NotImplementedError,只有支持的 provider 覆盖:
def pause_sandbox(self, sandbox_id, namespace):
raise NotImplementedError("Pause is not supported by this provider")
关键设计:pause_sandbox / resume_sandbox 默认抛 NotImplementedError(workload_provider.py:188-208)。这样「支不支持暂停」变成一个后端能力开关——agent-sandbox 不实现就自动 501,batchsandbox 覆盖了才可用。上层 kubernetes_service.py:1056 把这个 NotImplementedError 翻译成 HTTP 400「Pause is not supported for this sandbox type」。
Docker 侧没有显式抽象基类,而是用 Mixin 组合:DockerContainerOpsMixin(造容器)、DockerNetworkingMixin(网络)、DockerVolumesMixin(卷)、DockerRuntimeMixin(装 execd)、OSSFSMixin(OSS 挂载)拼成一个 DockerSandboxService。每个 Mixin 管一块关注点——这是本章 §4 的骨架。
4. Docker 后端:怎么造、怎么管一个容器
这一节走一遍 Docker 后端从「拿到规范化请求」到「容器跑起来」的真实路径,再补上卷、端口、OSSFS、Windows 四个横切细节。