
Volcano Agent Cgroup V2 适配设计深度解析双版本检测、统一管理与事件处理器改造【免费下载链接】volcanoA Cloud Native Batch System (Project under CNCF)项目地址: https://gitcode.com/GitHub_Trending/vol/volcano导读本文围绕 Volcano Agent 的 cgroup v2 适配设计见 docs/design/agent-cgroup-v2-adaptation.md系统讲解 Agent 如何在 cgroup v1 与 v2 两代内核接口之间实现无缝共存包括基于statfs魔数的版本探测、多来源 cgroup 驱动识别、版本无关的统一管理接口以及 Resources / CPUQoS / MemoryQoS / CPUBurst 等事件处理器的逐项改造。读完本文你将掌握 Volcano Agent 中 cgroup 路径的构造规律、v1/v2 控制文件的映射关系如cpu.shares→cpu.weight、memory.limit_in_bytes→memory.max以及仓库源码中对应的实现位置与测试验证方式。背景与动机容器运行时向 cgroup v2 迁移的必然性主流的 Kubernetes 发行版与容器运行时正在将 cgroup v2 作为默认配置。相比 cgroup v1 将各子系统分散挂载的方式cgroup v2 提供了**统一层级unified hierarchy**与更强的资源管理能力。作为运行在节点侧、负责按扩展资源与 QoS 级别配置 cgroup 的组件Volcano Agent 必须同时兼容两代接口。该适配工作的核心挑战可归纳为四点层级结构不同cgroup v1 使用分离的子系统挂载/sys/fs/cgroup/cpu、/sys/fs/cgroup/memory而 cgroup v2 使用统一的挂载点/sys/fs/cgroup不再按子系统拆分目录控制文件改名两代版本的控制文件名称与格式不同例如cpu.shares对应 v2 的cpu.weightmemory.limit_in_bytes对应 v2 的memory.max资源表示方式不同CPU 与内存的限额在 v2 中采用新的表达形式cpu.max的 quota period 格式、max 表示不设限驱动兼容性systemd 与 cgroupfs 两种 cgroup 驱动都要能同时工作在两代 cgroup 版本之上。设计目标包括自动探测 cgroup 版本与驱动配置、提供 v1/v2 一致的统一 API、保持对既有 cgroup v1 部署的完全向后兼容、更新全部相关事件处理器以适配 v2 文件格式并为两代版本提供完整的单元测试覆盖。而迁移工具、混合 v1/v2 环境、性能优化明确不在本次设计范围内。分层架构事件处理器与底层细节解耦适配方案采用分层架构将版本差异隔离在底层使上层事件处理器无需关心 v1/v2 的路径与文件差异┌─────────────────────────────────────────────────────────┐ │ Event Handlers Layer │ │ (Resources, CPUQoS, MemoryQoS, CPUBurst) │ └─────────────────────────────────────────────────────────┘ │ ┌─────────────────────────────────────────────────────────┐ │ CgroupManager Interface │ │ (Version-agnostic operations) │ │ - GetRootCgroupPath() │ │ - GetQoSCgroupPath() │ │ - GetPodCgroupPath() │ │ - GetCgroupVersion() │ └─────────────────────────────────────────────────────────┘ │ ┌─────────────────────────────────────────────────────────┐ │ CgroupManagerImpl │ │ Unified implementation supporting both v1 v2 │ │ • cgroupDriver: systemd/cgroupfs │ │ • cgroupVersion: v1/v2 (auto-detected) │ │ • buildCgroupPath(): version-aware path builder │ │ ┌──────────────┴──────────────┐ │ │ ┌─────────────────┐ ┌─────────────────┐ │ │ │ v1 Behavior │ │ v2 Behavior │ │ │ │ Separate paths │ │ Unified path │ │ │ │ per subsystem │ │ hierarchy │ │ │ └─────────────────┘ └─────────────────┘ │ └─────────────────────────────────────────────────────────┘ │ ┌─────────────────────────────────────────────────────────┐ │ Detection Driver Layer │ │ • IsCgroupsV2(): filesystem magic number check │ │ • GetCgroupDriver(): multi-source driver detection │ │ • DetectCgroupVersion(): version detection with env │ │ override for testing │ └─────────────────────────────────────────────────────────┘从源码结构看这一分层对应仓库中的 pkg/agent/utils/cgroup/cgroup.go顶层是各事件处理器pkg/agent/events/handlers 下的resources、cpuqos、memoryqos、memoryqosv2、cpuburst等目录中间是CgroupManager接口与CgroupManagerImpl实现底层是IsCgroupsV2、GetCgroupDriver、DetectCgroupVersion等探测函数。Cgroup 版本探测魔数校验与测试覆盖1. 文件系统魔数探测生产环境版本探测的核心是判断挂载点文件系统类型是否为 cgroup v2func IsCgroupsV2(unifiedMountpoint string) bool { var st unix.Statfs_t err : unix.Statfs(unifiedMountpoint, st) if err ! nil { // Error handling } return st.Type unix.CGROUP2_SUPER_MAGIC }通过statfs系统调用读取挂载点文件系统类型CGROUP2_SUPER_MAGIC0x63677270用于标识 cgroup v2 文件系统该方法参考了 runc 等成熟容器运行时项目的实现思路见 cgroup.go。值得注意的细节是源码在statfs失败时做了场景区分如果挂载点不存在且进程运行在 user namespace 中userns.RunningInUserNS()则忽略 not found 错误并假定为 v1其余错误则记录日志后同样回退为 v1。此外isUnified通过sync.Once保证只探测一次避免重复系统调用。2. 测试环境覆盖单元测试注入func DetectCgroupVersion(cgroupRoot string) (string, error) { // Support test environment override if testVersion : os.Getenv(VOLCANO_TEST_CGROUP_VERSION); testVersion ! { // Handle test cases } // Production detection logic }环境变量VOLCANO_TEST_CGROUP_VERSION专用于单元测试支持取值v1、v2、1、2非法值会记录警告并回退到文件系统探测生产环境则走文件系统探测逻辑。在源码实现 DetectCgroupVersion 中探测结果会写入包级变量CgroupVersion供后续构造管理器使用。Cgroup 驱动探测五级优先级回退cgroup 驱动systemd 与 cgroupfs决定了 cgroup 路径的命名方式——systemd 驱动下目录使用xxx.slice切片命名cgroupfs 驱动下则使用传统层级目录。GetCgroupDriver()见 cgroup.go按照以下优先级依次探测环境变量CGROUP_DRIVER若取值为systemd或cgroupfs则直接返回Kubelet 配置文件依次解析/var/lib/kubelet/config.yaml、/etc/kubernetes/kubelet.conf、/var/lib/kubelet/kubeadm-flags.env从中提取 YAML 形式的cgroupDriver:字段或--cgroup-driver命令行参数Kubelet 进程参数若配置文件不存在或未包含驱动信息则扫描/proc目录找到名为kubelet的进程读取/proc/pid/cmdline解析--cgroup-driver参数同时兼容--cgroup-drivervalue与--cgroup-driver value两种写法若进程带--config参数还会回读对应配置文件见 cgroup.go系统探测通过文件系统特征判断——存在/sys/fs/cgroup/system.slice或/sys/fs/cgroup/systemd判定为 systemd存在/sys/fs/cgroup/cpu判定为 cgroupfscgroup v2 挂载下若cgroup.controllers存在且无system.slice则判定为 cgroupfs还处理了 hybrid 模式下/sys/fs/cgroup/unified的探测见 DetectCgroupDriver默认回退以上均失败时返回cgroupfs。这种多来源 回退的设计保证了在不同 Kubernetes 部署形态kubeadm、二进制、云厂商托管等下都能尽可能准确地识别驱动。统一管理接口CgroupManager 与路径构造接口与实现结构CgroupManager接口向事件处理器提供版本无关的操作见 cgroup.gotype CgroupManager interface { GetRootCgroupPath(cgroupSubsystem CgroupSubsystem) (string, error) GetQoSCgroupPath(qos corev1.PodQOSClass, cgroupSubsystem CgroupSubsystem) (string, error) GetPodCgroupPath(qos corev1.PodQOSClass, cgroupSubsystem CgroupSubsystem, podUID types.UID) (string, error) GetCgroupVersion() string BuildContainerCgroupName(containerID string) string Memory() MemorySubsystem }CgroupManagerImpl的字段结构见 cgroup.gotype CgroupManagerImpl struct { cgroupDriver string // systemd or cgroupfs cgroupRoot string // mount point kubeCgroupRoot string // kubelet cgroup root cgroupVersion string // v1 or v2 memory MemorySubsystem }其中kubeCgroupRoot与 kubelet 配置中的cgroup-root保持一致非空时会作为路径前缀拼接到kubepods之前。管理器通过NewCgroupManager(cgroupDriver, cgroupRoot, kubeCgroupRoot)构造内部先调用DetectCgroupVersion探测版本若传入的驱动为空则自动调用GetCgroupDriver()见 cgroup.go。版本感知的路径构造buildCgroupPath()是路径差异的核心收敛点见 cgroup.gofunc (c *CgroupManagerImpl) buildCgroupPath(cgroupSubsystem CgroupSubsystem, cgroupPath string) string { switch c.cgroupVersion { case CgroupV1: // v1: each controller has its own hierarchy return filepath.Join(c.cgroupRoot, string(cgroupSubsystem), cgroupPath) case CgroupV2: // v2: unified hierarchy, no subsystem in path return filepath.Join(c.cgroupRoot, cgroupPath) default: // Fallback to v1 behavior return filepath.Join(c.cgroupRoot, string(cgroupSubsystem), cgroupPath) } }两代版本的实际路径差异如下Cgroup v1 示例按子系统分离CPU 路径为/sys/fs/cgroup/cpu/kubepods/burstable/poduid内存路径为/sys/fs/cgroup/memory/kubepods/burstable/poduidCgroup v2 示例统一层级所有子系统共用/sys/fs/cgroup/kubepods/burstable/poduid。同时Pod 级与 QoS 级路径的构造逻辑GetRootCgroupPath/GetQoSCgroupPath/GetPodCgroupPath会在kubepods之后追加burstable/besteffort等 QoS 目录Guaranteed 类 Pod 不追加 QoS 子目录Pod 级路径末尾追加poduid后缀。驱动相关的路径命名CgroupNameToCgroupPath根据驱动选择命名方案见 cgroup.gocgroupfs 驱动ToCgroupfs()返回/ 各段用/拼接的简单路径systemd 驱动ToSystemd()将各段以-连接并追加.slice后缀再调用cgroupsystemd.ExpandSlice展开为完整切片路径名称中的-会被转义为_escapeSystemdCgroupName。容器级 cgroup 目录名由BuildContainerCgroupName生成见 cgroup.go输入为runtime://id格式的容器 ID。cgroupfs 驱动下直接返回纯 IDsystemd 驱动下按运行时类型生成 scope 名称例如 containerd 对应cri-containerd-id.scope、docker 对应docker-id.scope、cri-o 对应crio-id.scope。这一行为有专门的单元测试覆盖见 cgroup_test.go。控制文件映射v1 与 v2 的桥接表系统在 v1 与 v2 控制文件之间建立了明确的映射关系功能Cgroup v1Cgroup v2格式变化CPU Shares/Weightcpu.sharescpu.weightshares(1024 基准) → weight(100 基准)CPU Quotacpu.cfs_quota_uscpu.max200000→200000 100000CPU Burstcpu.cfs_burst_uscpu.max.burst格式相同Memory Limitmemory.limit_in_bytesmemory.max字节数 → max 表示不设限Memory High无memory.highv2 新增Memory Low无memory.lowv2 新增Memory Min无memory.minv2 新增这些文件名的常量定义集中在 cgroup.gov1 侧有cpu.qos_level、cpuacct.usage、cpu.cfs_burst_us、cpu.cfs_quota_us、net_cls.classid、cpu.sharesv2 侧有cpu.weight、cpu.stat、cpu.max.burst、cpu.max、cpu.idle等。CPU 权重与限额的换算规则在 pkg/agent/utils/pod/resources.go 中可以看到两代版本对应的换算函数v1 sharesmilliCPUToShares将 milliCPU 转换为 CFS shares基准sharesPerCPU 1024并夹在minShares 2与maxShares 262144之间v2 weightmilliCPUToWeight采用weight milliCPU / 10夹在 1 与 10000 之间对应内核 v2 权重范围v2 quotamilliCPUToMax生成quota period格式字符串其中quotaPeriod 100000等价 100msquota 最小值为minQuotaPeriod 10001msmilliCPU 为 0 时返回max 100000表示不设限v2 memorymemoryLimitToMax在内存字节数为 0 时返回max否则返回十进制字节数。内存子系统接口抽象[v2 独有的memory.high/memory.low/memory.min能力通过MemorySubsystem接口暴露见 memory.goNewMemorySubsystem(cgroupVersion)按版本构建接口映射v1 仅提供Max对应memory.limit_in_bytesv2 提供Max/High/Low/Min四个接口分别对应memory.max/memory.high/memory.low/memory.min每个MemoryInterface封装Get/Set操作并以字节为单位读写不设限的表示方式因版本而异v1 用-1valueUnlimitedV1v2 用字符串maxvalueUnlimitedV2读取时统一归一化为MemoryUnlimited -1常量见 memory.go。事件处理器适配四大处理器的 v2 改造每个事件处理器在注册时通过cgroupMgr与底层交互注册逻辑见 pkg/agent/events/handlers/registry.go。Resources Handler资源限额用途管理容器的 CPU 与内存资源限额。其实现位于 pkg/agent/events/handlers/resources/resources.go会在 Pod 事件到来时根据cgroupMgr.GetCgroupVersion()分流v1 调用CalculateExtendResources(pod)v2 调用CalculateExtendResourcesV2(pod)见 resources.gov2 关键变化使用cpu.weight替代cpu.sharescpu.max需要写入quota period格式——当值为 -1不设限时写入max 100000正值时写入%d 100000quota 后跟固定 period见 resources.go内存不设限时向memory.max写入max路径统一通过GetPodCgroupPathBuildContainerCgroupName计算Pod 级配置直接写 Pod 目录容器级配置写容器子目录。CPU Burst HandlerCPU 突发能力用途管理 CPU burst 能力通过 Pod 注解启用。其实现位于 pkg/agent/events/handlers/cpuburst/cpu_burst_linux.gov2 变化使用cpu.max.burst替代cpu.cfs_burst_us从cpu.max读取配额配额解析函数readCPUQuotaV2会解析cpu.max的两种格式max period返回 -1 表示不设限与quota period返回 quota 数值见 cpu_burst_linux.gowalkFunc会递归遍历 Pod cgroup 目录下所有容器目录对每个容器计算并写入 burst 值最后再汇总设置 Pod 级 cgroup若 burst 时间大于 quota 总时间则截断为 quota 值quota 为 -1不设限时跳过见 cpu_burst_linux.go。CPU QoS Handler 与 Memory QoS HandlerCPU QoScpuqos_linux.go设置 CPU QoS 等级目前沿用厂商特有的cpu.qos_level文件v2 下暂无变化Memory QoSmemoryqos_linux.go设置内存 QoS 等级同样沿用厂商特有的memory.qos_level文件。MemoryQoS V2 Handlerv2 独有能力针对 cgroup v2 专门新增了 memoryqosv2_linux.go 处理器利用 v2 独有的memory.high/memory.low/memory.min实现精细的内存 QoS 控制从 Pod 注解ColocationConfigKey解析混部配置中的MemoryQosHighRatio/LowRatio/MinRatio缺省时使用默认值HighRatio100, LowRatio0, MinRatio0见 memoryqosv2_linux.gomemory.high依据容器 Limits 按HighRatio百分比计算memory.low与memory.min依据容器 Requests 按对应比例计算比率达到 100% 或数量为 0 时写MemoryUnlimited即 v2 的 max通过cgroupMgr.Memory()获取的MemorySubsystem判断各接口是否supported保证在 v1 环境下该处理器自然失效。测试策略双版本覆盖仓库为 cgroup 适配提供了较为完整的测试支撑驱动与容器命名TestBuildContainerCgroupName覆盖 cgroupfs / systemd 两种驱动 × containerd / docker / cri-o / 纯 ID 四种输入的组合见 cgroup_test.go版本注入DetectCgroupVersion支持VOLCANO_TEST_CGROUP_VERSION环境变量强制指定版本使测试无需真实 cgroup 挂载即可覆盖 v1/v2 两条代码路径事件处理器测试resources、cpuqos、cpuburst、cputhrottle、memoryqos、memoryqosv2、networkqos各处理器目录下均配有*_test.go测试中通过cgroup.NewCgroupManager(cgroupfs, tmpDir, )在临时目录构造管理器例如 cpu_burst_linux_test.go结合测试注解验证 cgroup 文件的实际写入结果。向后兼容与未来展望向后兼容未显式配置驱动时GetCgroupDriver()默认回退到cgroupfs与既有部署习惯保持一致buildCgroupPath对未知版本回退到 v1 行为保证异常情况下的稳健性未请求任何扩展资源的 Pod 会被跳过不产生任何 cgroup 写入不影响普通工作负载Pod 级限额只有在所有容器都声明了对应限额时才写入避免 v1 语义下的误写见 resources.go。未来工作本设计不包含v1 到 v2 的迁移工具也不支持单集群内混合 v1/v2 部署CPU QoS 与 Memory QoS 目前仍使用厂商特有的cpu.qos_level/memory.qos_level文件后续可探索迁移到 v2 标准接口本次工作聚焦兼容性而非性能优化未来可在双版本兼容的基础上针对 v2 统一层级的优势做进一步性能调优。总结Volcano Agent 的 cgroup v2 适配通过底层探测 统一接口 版本感知路径构造 处理器分流的分层设计让 Agent 在两代 cgroup 之上都能正确工作底层以statfs魔数完成版本探测、以多来源回退完成驱动识别中间层以CgroupManager/MemorySubsystem抽象出版本无关的操作原语上层各事件处理器只需根据GetCgroupVersion()选择对应的资源换算与文件写入格式即可。这一设计既保证了既有 v1 集群的平滑兼容也为容器运行时全面转向 cgroup v2 做好了准备。相关实现与测试可直接在 pkg/agent/utils/cgroup 与 pkg/agent/events/handlers 目录中查阅验证。【免费下载链接】volcanoA Cloud Native Batch System (Project under CNCF)项目地址: https://gitcode.com/GitHub_Trending/vol/volcano创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考