ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

gVisor 资源模型深度解析:Sentry 如何按需弹性管理 CPU、内存、线程与文件资源

gVisor 资源模型深度解析:Sentry 如何按需弹性管理 CPU、内存、线程与文件资源 gVisor 资源模型深度解析Sentry 如何按需弹性管理 CPU、内存、线程与文件资源【免费下载链接】gvisorApplication Kernel for Containers项目地址: https://gitcode.com/GitHub_Trending/gv/gvisor导读本文基于 gVisor 架构指南中的 Resource Model资源模型 章节系统讲解 gVisor 沙箱在进程、网络、文件、线程、时间、内存与资源限制七个维度上的资源管理设计。gVisor 的核心思想是不预先固定 vCPU 数量或物理内存大小而是让沙箱的资源形状紧密跟随被沙箱化进程的形状——忙碌时向上扩展占用大量核心与内存空闲时把资源交还宿主机。读完本文你将理解 gVisor 为何能做到接近零 CPU 的空闲开销、应用内存为何在 cgroup 中显示为 shmem 而非 anon以及如何用runsc usage从沙箱内部获取准确的内存明细。资源模型总览沙箱形状跟随工作负载形状gVisor 的资源模型并不假设存在固定数量的执行线程vCPU或固定大小的物理内存。凡是涉及底层物理资源的决策只要可能都会委托给拥有全局信息的宿主机系统去优化。这种委托机制使沙箱在资源使用上高度动态化繁忙时横跨大量 CPU 核与大量内存空闲时则把资源释放回宿主机。换言之沙箱的形状应当紧密跟踪被沙箱化进程的形状。下图直观展示了不同形状不同资源需求形态的工作负载如何在 gVisor 沙箱之上运行、并最终共享宿主机资源gVisor 资源模型示意图不同形状的工作负载运行在 gVisor 沙箱之上共享宿主机资源进程沙箱在宿主机上是一个不透明进程与虚拟机类似一个 gVisor 沙箱在系统上表现为一个不透明的进程。沙箱内部的进程不会以宿主机进程的形式显现——在宿主机上运行top(1)是看不到它们的。沙箱内进程层面的交互需要进入沙箱才能进行例如通过docker exec进入容器执行命令。这一点与 gVisor 的 Sentry 实现直接对应Sentry 维护自己的 PID 表来表示沙箱内的进程见 pkg/sentry/kernel 中的进程管理实现这些并非真实的宿主机进程沙箱内进程调用getpid(2)时Sentry 直接用自己的 PID 表回答不触发任何宿主机系统调用。网络自有协议栈资源全部限定在沙箱内沙箱会向系统挂接一个网络端点endpoint但网络协议栈是 gVisor 自己实现的位于 pkg/tcpip一个 Go 语言从零实现的网络栈。除宿主机上在途in flight的数据包之外所有网络资源只存在于沙箱内部并受相应资源限制约束。你可以像操作普通容器一样与沙箱暴露的网络端点交互但网络内部的探查introspection同样需要进入沙箱才能进行。文件Gofer 通过 SCM_RIGHTS 传递文件描述符沙箱内的文件可能由不同的后端实现支撑。对于宿主机原生文件已有可用文件描述符的情况Gofer一个略高权限的伴生进程负责代表 Sentry 访问宿主机文件系统可以通过SCM_RIGHTS将文件描述符传递给 Sentry。这一机制在源码中有清晰体现pkg/sentry/socket/control/control.go 中实现了SCMRights类型与NewSCMRights、PackRights等函数专门处理通过 Unix socket 控制消息传递文件描述符的逻辑pkg/sentry/control/fs.go 中的FilePayload注释也明确指出其包含通过SCM_RIGHTS发送的文件描述符例如传给 Gofer 的 socket FD。脚注补充除非启用了宿主网络host networkingSentry 自身无法创建或打开宿主机文件描述符它只能以这种方式从 Gofer 接收文件描述符。这些文件可以通过标准系统调用读写也可以映射进关联应用程序的地址空间。这意味着同一份宿主内存可以被多个沙箱共享例如多个沙箱映射同一文件页。需要说明的是这种机制并不排除侧信道攻击的可能相关讨论见 Security Model安全模型。另外有些文件系统只存在于沙箱上下文中。例如在很多情况下tmpfs挂载会出现在/tmp或/dev/shm它直接从沙箱内存文件见下文内存章节的 memfd中分配内存。这类内存最终会以与宿主机原生文件类似的方式计入相关限制。线程goroutine 即绿色线程宿主线程按需创建Sentry 用goroutine来为每一个任务线程task thread建模。因此每个任务线程都是一个轻量级绿色线程green thread不一定对应底层的一个宿主线程但是应用程序的执行被建模为对 Sentry 的一次阻塞式系统调用。这意味着宿主机可能会创建额外的线程——取决于活跃应用线程的数量。在实际运行中一个繁忙的应用会趋近于活跃线程数 宿主线程数的状态此时宿主机能够对所有应用线程做出合理的调度决策。这种设计让宿主机调度器得以介入沙箱内的线程调度实现了沙箱线程随应用负载伸缩的动态性。时间Sentry 自持时钟空闲时进入无 tick模式沙箱内的时间由 Sentry 通过自己的 vDSO 与时间保持time-keeping实现来提供这与宿主机时间截然不同且不与宿主机共享任何状态尽管初始时会用宿主机时钟校准。与运行在硬件上的内核类似Sentry 会运行定时器来感知时间流逝——只不过这里是软件定时器。这些定时器负责更新 vDSO、提供系统调用返回的时间、记录用于用量/限额跟踪的时间例如RLIMIT_CPU。当所有应用线程都空闲时Sentry 会禁用定时器直到某个事件唤醒 Sentry 或某个应用线程为止——这与无 tick 内核tickless kernel类似。这使得空闲应用可以达到接近零的 CPU 占用。gVisor 的 vDSO 实现位于 vdso 目录如 vdso_time.cc、cycle_clock.h通过共享映射向应用提供无系统调用的时间读取路径。内存单一 memfd 支撑全部应用内存Sentry 实现了自己的内存管理包括按需分页demand-paging针对无法原生使用的文件维护Sentry 内部页缓存。关键设计是一个单独的 memfd 支撑全部应用内存。memfd_create(2)的封装位于 pkg/memutil/memfd_linux_unsafe.go其CreateMemFD函数在实现中明确要求 Linux 3.17 及以上版本否则报错 memfd_create(2) is not implemented。Sentry 内核启动时会创建该 memfd见 pkg/sentry/kernel/kernel.go 中的相关调用所有应用内存页都挂在这一个文件上。地址空间平台相关必要时创建 stub 进程地址空间的创建是平台相关的例如 Systrap 平台与 KVM 平台的行为不同。对某些平台宿主机上可能会创建额外的stub 进程来支持额外的地址空间。这些 stub 进程同样受沙箱级别的各种限制约束例如 PID 限制。物理内存交给宿主机管理Sentry 启发式批量映射宿主机可以使用常规手段管理物理内存例如跟踪工作集、在压力下回收与交换。Sentry惰性地为应用填充宿主映射并允许宿主机对这些区域按需分页——这对上述机制的正常运转至关重要。为避免过高开销Sentry不会逐页按需分页而是基于启发式算法选择合适的内存区域批量处理。这带来一个权衡Sentry 无法轻易判断哪些页是活跃的、哪些不是即使逐页触发 fault宿主机也可能在 Sentry 不知情的情况下回收或交换某些页。因此沙箱内的内存使用统计例如通过 proc 查看只是近似值。Sentry 内部维护了内存使用的细分账目可以收集准确信息但只能通过一个相对昂贵的 API 调用实现而且从安全角度讲把宿主机管理内存的精确信息暴露给沙箱也被认为是不明智的。madvise释放内存归还宿主当应用标记某段内存不再需要时例如调用madviseSentry 会立即把这块内存释放回宿主机。这存在性能代价——在很多情况下保留内存并用它满足其他请求反而更便宜。但立即释放让宿主机能更有效地复用资源、执行全局性的高效策略这与资源决策交给宿主机的整体模型一脉相承。资源限制与 cgroup为什么 memory.stat 里 anon 很低所有 Sentry 线程和 Sentry 内存都受容器 cgroup约束。但这里有一个关键点应用的内存使用不会表现为匿名内存anon用量而是被计入那个memfd。也就是说所有匿名内存anon对应的是Sentry 自身的用量应用内存以memfd形式存在宿主机对容器收取的内存费用按常规方式工作即计为 file-backed/shmem。这带来一个容易困惑的后果读取 gVisor 沙箱 cgroup 的memory.stat或memory.current时由于应用内存由 memfd 支撑内核会将其计为shmem而不是anon。因此即使容器运行着内存饥渴的应用memory.stat中的anon字段也会保持很低而shmem或file视内核版本而定则反映应用真实内存用量的绝大部分。如果你需要沙箱内部应用内存用量的明细有两个途径查看memory.current获取总量使用Sentry 自身的记账runsc usage container id。runsc usage 命令实操runsc usage是 runsc 提供的专门命令其实现位于 runsc/cmd/usage.go。基本用法# 打印内存用量按类别以字节为单位 runsc usage container id # 枚举所有类别的完整用量明细 runsc usage --full container id # 通过既有的 usage FD 获取用量的子集 runsc usage --fd container id从源码看其输出通过 JSON 编码器美化后打印usage子命令的 Synopsis 为 Usage shows application memory usage across various categories in bytes.--full标志用于枚举所有类别的用量--fd则通过容器沙箱的 usage FDcont.Sandbox.UsageFD()快速抓取 Mapped/Unknown/Total 三个数值。cgroup 可以按常规方式监控标准信号压力指示pressure indicators、阈值通知threshold notifiers等也可以动态调整。值得注意的实现细节是Sentry 自身可能会监听所在 cgroup 的压力信号以便清理内部缓存即内存压力驱动的缓存回收机制从源码结构看这一行为与 pkg/sentry/kernel 中的内存管理逻辑相关。延伸阅读资源模型背后的安全设计动机参见 Security Model安全模型 与 Introduction to gVisor security系统调用与缺页拦截的平台实现Systrap / KVM参见 Platforms平台gVisor 的网络栈沙箱自有协议栈源码位于 pkg/tcpipGofer 与文件系统后端相关逻辑可查阅 pkg/sentry/fsimpl 与 runsc/fsgoferrunsc usage等命令的完整命令集可参考 runsc/cmd 目录与 runsc 用户指南。【免费下载链接】gvisorApplication Kernel for Containers项目地址: https://gitcode.com/GitHub_Trending/gv/gvisor创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表