
containerd Sandbox API 深度解析从 pause 容器到一等公民的沙箱抽象【免费下载链接】containerdAn open and reliable container runtime项目地址: https://gitcode.com/GitHub_Trending/co/containerd导读本文以 containerd 官方文档 docs/sandbox-api.md 为主体深入剖析 containerd 引入的一等公民Sandbox API它如何基于 Runtime v2 shim 架构将一组共享资源、拥有共同生命周期的容器抽象为可插拔的沙箱原语从而取代 CRI 插件中硬编码的 pause 容器方案。阅读本文后你将掌握 Sandbox API 的Controller接口、SandboxServiceRPC 全貌、kubelet 创建 Pod 的完整调用链、分组 shim 的关闭语义以及运行时作者如何接入shim/podsandbox两种控制器实现。背景Runtime v2 模型与容器分组的困境Runtime v2每个容器一个 shim在 Runtime v2 模型中containerd 守护进程并不会直接启动容器。相反它为每个容器拉起一个 shim 进程shim 通过 ttrpc或 gRPCsocket 暴露 TaskServicecontainerd 通过该连接下发 create/start/stop/delete 等命令。具体流程见 core/runtime/v2/README.mdcontainerd 收到创建容器请求铺好容器文件系统与配置containerd 启动 shim 二进制如containerd-shim-runc-v2shim 启动 ttrpc server 并返回 socket 地址containerd 通过TaskService.CreateTaskRequest调度任务shim 再调用 runc 等 OCI runtime engine 真正创建/启动容器。这一模型对单个容器而言非常高效但在容器需要被分组进一个共享执行环境沙箱时就会暴露问题。Kubernetes Pod 与 pause 容器在 Kubernetes 中一个 Pod 是一组被共同调度、共享网络命名空间等资源的容器。为了实现这一点Kubernetes 使用了一个被称为pause 容器的最小容器它自身不做任何业务唯一的作用是作为父进程、维持共享命名空间存活应用容器启动时再join这些命名空间。[!NOTE] 本文档中pod sandbox与sandbox是两个不同的概念。pod sandbox是 CRI 插件与 Kubernetes gRPC API如RunPodSandbox中使用的 Kubernetes 特有概念传统上通过 pause 容器实现而sandbox是 Sandbox API 定义的通用抽象——pod sandbox 只是它的一种可能实现。旧模型的缺陷在 Sandbox API 出现之前containerd 没有一等公民的容器分组概念pause 容器的生命周期与沙箱元数据完全由 CRI 插件内部管理。文档 docs/sandbox-api.md 明确指出了这种方案的三个缺陷一刀切One-size-fits-all实现假设所有沙箱都是 pause 容器。对于管理自己沙箱的 VM 类运行时VMM没有接入的途径。没有扩展点No extension points沙箱生命周期存在于 CRI 插件内部运行时作者无法为自己的运行时定制行为。shim 生命周期与任务绑定shim 进程随任务的创建与销毁而生灭但沙箱需要的是一个在容器来来去去期间保持存活的 shim。Sandbox API 的设计目标与核心抽象Sandbox API 的核心思路是把沙箱建模为一个最先启动、最后结束的父环境——它先获取共享资源如网络命名空间、IP 地址随后子容器加入其中。它围绕沙箱实现提供一个抽象层运行时作者无需修改 containerd 或 CRI 插件即可提供自己的实现。官方文档给出的设计目标有两条围绕容器分组提供更好的抽象通过统一的 Controller 接口支撑非标准用例如 microVM 风格容器完整的 RPC 面见 SandboxService。让 containerd 的 CRI 插件不再背负实现细节、减少固执己见——pause 容器预期将成为 Sandbox API 的一种实现而非硬编码假设。Controller 接口沙箱生命周期管理契约core/sandbox/controller.go 定义了核心的Controller接口它是沙箱运行时的管理契约type Controller interface { // Create 用于初始化沙箱环境mounts、any Create(ctx context.Context, sandboxInfo Sandbox, opts ...CreateOpt) error // Start 启动先前创建的沙箱 Start(ctx context.Context, sandboxID string) (ControllerInstance, error) // Platform 返回沙箱将要运行容器的目标 OScontainerd 据此生成正确的 OCI spec Platform(_ctx context.Context, _sandboxID string) (imagespec.Platform, error) // Stop 停止沙箱实例 Stop(ctx context.Context, sandboxID string, opts ...StopOpt) error // Wait 阻塞直到沙箱进程退出 Wait(ctx context.Context, sandboxID string) (ExitStatus, error) // Status 查询沙箱进程状态比 Ping 更重用于获取状态、运行时长、资源使用等元数据 Status(ctx context.Context, sandboxID string, verbose bool) (ControllerStatus, error) // Shutdown 删除并清理所有任务与沙箱实例 Shutdown(ctx context.Context, sandboxID string) error // Metrics 查询沙箱指标 Metrics(ctx context.Context, sandboxID string) (*types.Metric, error) // Update 修改沙箱对象的一部分extensions/annotations/labels/spec // 控制器可能需要据此更新运行中的沙箱 Update(ctx context.Context, sandboxID string, sandbox Sandbox, fields ...string) error }接口的创建选项通过函数式选项functional options提供便于运行时作者组合使用CreateOpt说明WithRootFS(m []mount.Mount)以指定的 rootfs mount 创建沙箱WithOptions(options any)向 shim 传递任意选项CRI 用它传PodSandboxConfig注意不要与 shim 实例启动时的 Runtime options 混淆WithNetNSPath(netNSPath string)为沙箱指定网络命名空间路径WithAnnotations(annotations map[string]string)设置沙箱创建注解停止操作同样支持选项Stop(ctx, sandboxID, WithTimeout(timeout))可指定停止超时。方法返回值中ControllerInstance携带SandboxID、Pid、CreatedAt、Address、Version、Labels、Spec等实例信息ExitStatus携带ExitStatus与ExitedAtControllerStatus则在 Status 语义之上额外携带State、Info、Extra、Address、Version。Store沙箱元数据模型沙箱元数据存储在 core/sandbox/store.go 定义的Store接口中支持Create/Update/Get/List/Delete五种操作。元数据对象Sandbox的结构与 API 侧的 api/types/sandbox.proto 一一对应type Sandbox struct { ID string // 命名空间内唯一标识 Labels map[string]string // 元数据扩展 Runtime RuntimeOpts // 使用的 shim 运行时Name Options Spec typeurl.Any // 运行时规范类似 OCI spec写入 bundle 的 config.json Sandboxer string // 管理该沙箱的沙箱控制器名称 CreatedAt time.Time UpdatedAt time.Time Extensions map[string]typeurl.Any // 客户端指定的元数据 }Sandbox还提供AddExtension/AddLabel/GetExtension/GetLabel辅助方法方便 CRI 等调用方在沙箱上挂载自定义元数据如 Pod 级别的资源与开销信息。SandboxServiceshim 侧的完整 RPC 面api/runtime/sandbox/v1/sandbox.proto 定义了 shim 需要实现的Sandbox服务。其注释明确Sandbox 是 shim 可选的接口用于支持沙箱环境典型的沙箱例子是 microVM 或 pause 容器——一个分组容器并/或持有该组相关资源的实体。服务包含 10 个 RPCRPC说明CreateSandbox沙箱 shim 实例启动后立即调用适合初始化沙箱环境StartSandbox启动先前创建的沙箱Platform查询沙箱将运行容器的平台containerd 据此生成正确的 OCI specStopSandbox停止已存在的沙箱实例WaitSandbox阻塞直到沙箱退出SandboxStatus返回运行中沙箱实例的当前状态PingSandbox轻量级存活检查ShutdownSandbox关闭 shim 实例SandboxMetrics获取沙箱实例的指标UpdateSandbox将更新后的沙箱元数据对象应用到运行中的沙箱实例如 Pod 级资源 resize关键请求/响应消息的字段设计同样值得细读CreateSandboxRequestsandbox_id、bundle_path、rootfsrepeated containerd.types.Mount、optionsgoogle.protobuf.Any、netns_path、annotationsmap。StartSandboxResponse返回pid、created_at与specAny对应 Controller 的Start返回值。StopSandboxRequestsandbox_id与timeout_secs。SandboxStatusResponsesandbox_id、pid、state、infomap、created_at、exited_at、extraAny。UpdateSandboxRequestresources与annotations字段已标记deprecatedcontainerd 不再设置请使用sandbox字段sandbox是 containerd 存储的完整更新后元数据对象含 labels、spec、extensionsfields是发生变更的 fieldpath 列表空列表表示整个对象都应视为已更新。完整调用流程kubelet 创建 Pod 时的沙箱交互官方文档用一段 Mermaid 时序图完整刻画了 kubelet 创建一个带应用容器的 Pod 时使用shim沙箱控制器的 CRI 调用流程已省略快照、OCI spec、NRI hooks、退出监视器等容器细节聚焦 Sandbox API 交互流程要点解读RunPodSandbox 阶段containerd 先在元数据 store 中创建沙箱记录创建网络命名空间并完成 CNI 网络设置随后调用SandboxController.Create。shim控制器在此刻拉起 shim 二进制shim 返回 socket 地址containerd 通过SandboxService.CreateSandbox初始化沙箱环境再经SandboxController.Start→SandboxService.StartSandbox拿到沙箱 PID 与 endpoint将其存入元数据后向 kubelet 返回PodSandboxId。CreateContainer / StartContainer 阶段创建容器时containerd 先查沙箱元数据再调用SandboxStatus/Platform获取状态与平台信息以生成正确的 OCI spec随后创建关联到沙箱的容器元数据。启动容器时containerd 复用沙箱 shim 连接直接通过TaskService.Create / Start完成容器启动——容器最终运行在沙箱的命名空间内。StopPodSandbox / RemovePodSandbox 阶段停止时containerd 逐个对沙箱内每个容器执行TaskService.Kill / Delete与TaskService.Shutdown再经SandboxController.Stop→SandboxService.StopSandbox停止沙箱。移除时先确保沙箱已停止、清理容器元数据然后SandboxController.Shutdown→SandboxService.ShutdownSandbox关闭 shim最后删除沙箱元数据。分组 shim 的关闭语义官方文档特别强调了一个容易踩坑的语义containerd 会在删除每个任务后调用TaskService.Shutdown因此对于分组 shimShutdown可能被多次调用它并不意味着 shim 必须立即终止。shim 应当在仍有活动任务时直接返回而不终止只有在没有活动任务时收到TaskService.Shutdown才真正退出。与此同时SandboxService.ShutdownSandbox与TaskService.Shutdown相互独立前者负责关闭沙箱实例本身。二者的配合关系可对照 docs/runtime-v2.md 中关于 shim 命令与 ttrpc 协议的描述来理解。可选的沙箱更新UpdateSandboxSandboxController.Update会把更新后的沙箱元数据对象连同发生变更的 fieldpaths 一起通过SandboxService.UpdateSandbox转发给 shim。CRI 使用这一路径完成 Pod 级别的资源更新UpdatePodSandboxResources新的资源与开销以沙箱 extension 的形式随请求传递。实现UpdateSandbox是可选的。与任何其他不支持的 shim RPC 相同参见 Unsupported rpcs不支持更新的 shim必须返回github.com/containerd/errdefs.ErrNotImplemented错误该错误映射为 gRPCcodes.Unimplemented完全未注册该方法的 shimttrpc 也会返回同样的错误码调用方需要容忍该错误。这与TaskService层面shim 必须为不支持的 RPC 返回ErrNotImplemented的通用约定见 docs/runtime-v2.md一脉相承。Controller 实现shim 与 podsandbox目前仓库中存在两个Controller实现1. shim 控制器目标模型支持 Sandbox API 流程的 shim 二进制实现 SandboxService 的全部 RPC并原生管理沙箱生命周期。这是 Sandbox API 设计时瞄准的目标模型——运行时作者如 microVM 类运行时通过实现这些 RPC 即可接入 containerd无需修改 containerd 或 CRI 插件本体。相关集成代码可参考 plugins/sandbox/controller.go。2. podsandbox 控制器pause 容器实现pause 容器实现位于 CRI 的 internal/cri/server/podsandbox 包中。从 controller.go 的源码看它以插件形式注册ID为podsandbox依赖 Event、Lease、SandboxStore、Transfer、CRIService、Warning 等插件并通过containerd.New(..., containerd.WithInMemoryServices(ic))在进程内构建 containerd client同时维护自己的storeNewStore()与事件监视器eventMonitor。官方文档对其定位的描述是podsandbox控制器在技术上满足Controller接口但实践中它更像一个与 CRI 层紧密耦合的内存实现。它之所以留在 CRI 层是因为重构复杂度——自 containerd 1.7 首次引入 Sandbox API 以来干净地把它拆出去一直是一项持续推进的大工程并随每个版本不断改善。状态与演进Sandbox API 于containerd 1.7作为实验性 API 首次引入并在2.0中提升为稳定 API。它仍在持续演进相关进行中的工作可跟踪官方 issue #9431。对运行时作者而言这意味着沙箱抽象已是一等公民pause 容器只是其实现之一接入自定义沙箱不再需要改动 containerd 与 CRI 插件本体。【免费下载链接】containerdAn open and reliable container runtime项目地址: https://gitcode.com/GitHub_Trending/co/containerd创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考