3. 资源配额概念:Linux CGroup v2 与网络资源隔离

在 eBPF 网络观测与配额校准场景中,理解 Linux CGroup v2 的资源隔离机制是性能调优与故障排查的基础。CGroup v2 提供了统一的层级结构,通过 io、memory、cpu 以及 net 控制器实现精细化的资源配额管理。本章聚焦于网络资源隔离,重点解析带宽、连接数、数据包速率等核心配额指标及其在 eBPF 观测中的校准方法。

3.1 CGroup v2 网络控制器架构

CGroup v2 将网络资源隔离抽象为两个主要维度:带宽控制(通过 net_prio 和 net_cls 的演进)与 连接/包速率控制(通过 eBPF 辅助的 cgroup 钩子)。与 CGroup v1 不同,v2 采用单一层级树,所有进程归属于唯一的 cgroup 节点,避免了 v1 中多层级冲突的问题。

关键变化: CGroup v2 移除了 net_prio 和 net_cls 控制器,转而推荐使用 eBPF 程序挂载到 cgroup 套接字钩子(BPF_CGROUP_INET_INGRESS / BPF_CGROUP_INET_EGRESS)实现精细化的网络流量控制与统计。
CGroup v2 网络资源隔离架构 CGroup v2 网络资源隔离架构 CGroup v2 根节点 子 cgroup A (app1) 子 cgroup B (app2) 子 cgroup C (system) net 控制器 带宽/连接数 io 控制器 磁盘 IOPS/带宽 memory 控制器 内存上限/swap cpu 控制器 CPU 时间/权重 eBPF cgroup 套接字钩子 ingress/egress 过滤 eBPF 连接跟踪 连接数/状态统计 说明:CGroup v2 通过单一层级树管理进程,网络资源隔离依赖 eBPF 程序在 cgroup 套接字钩子处执行配额校准与观测。

3.2 核心网络配额指标

网络资源配额通常从三个维度进行度量:带宽(吞吐量)、连接数(并发与新建速率)、数据包速率(PPS)。每个指标对应不同的性能瓶颈与校准策略。

配额指标 单位 典型限制场景 eBPF 观测方法 校准目标
带宽 bps / Bps 容器出向带宽上限、跨 AZ 流量限速 BPF_CGROUP_INET_EGRESS 统计字节数 避免拥塞丢包,保障 QoS
连接数 个(并发)/ 个/s(新建) 四层负载均衡连接表溢出、短连接风暴 BPF_CGROUP_INET_SOCK_CREATE 跟踪新建 防止连接耗尽,控制内存开销
数据包速率 pps DDoS 小包攻击、高频心跳包限速 BPF_CGROUP_INET_INGRESS 统计包数 保护 CPU 免于软中断过载
校准要点: 在 eBPF 程序中,带宽校准通常使用 令牌桶算法( Bucket),连接数校准使用 计数器 + 时间窗口,数据包速率校准使用 漏桶算法(Leaky Bucket)。三种算法在 eBPF map 中维护状态,避免频繁系统调用。

3.3 带宽配额:令牌桶实现原理

带宽配额的核心是控制单位时间内通过的数据量。CGroup v2 本身不直接提供带宽限制接口,但 eBPF 程序可以在 cgroup 套接字钩子中实现令牌桶逻辑。以下是一个简化的 eBPF 带宽配额伪代码:

// 伪代码:eBPF 带宽令牌桶
struct _bucket {
    __u64 rate;          // 字节/秒
    __u64 burst;         // 突发大小(字节)
    __u64 tokens;        // 当前令牌数
    __u64 last_time;     // 上次更新时间(ns)
};

// 在 BPF_CGROUP_INET_EGRESS 钩子中调用
static inline bool consume_(struct _bucket *tb, __u32 bytes) {
    __u64 now = bpf_ktime_get_ns();
    // 计算新增令牌
    __u64 elapsed = now - tb->last_time;
    __u64 new_tokens = (elapsed * tb->rate) / NSEC_PER_SEC;
    tb->tokens = min(tb->tokens + new_tokens, tb->burst);
    tb->last_time = now;
    // 检查是否足够
    if (tb->tokens >= bytes) {
        tb->tokens -= bytes;
        return true;  // 允许通过
    }
    return false;     // 限速丢包或延迟
}
性能陷阱: 令牌桶算法中的 bpf_ktime_get_ns() 调用开销较高(约 50-100ns/次)。在高 PPS 场景下,建议使用 bpf_jiffies64 或批量更新时间戳,避免每次包都触发高精度时间读取。

3.4 连接数配额:并发与新建速率

连接数配额分为两个子指标:并发连接数(当前活跃连接总数)和新建连接速率(每秒新建连接数,CPS)。eBPF 通过挂载到 BPF_CGROUP_INET_SOCK_CREATE 钩子来拦截新建连接请求,并在 BPF_CGROUP_INET_SOCK_DESTROY 钩子中释放计数。

连接数配额状态机 连接数配额状态机 空闲(无连接) 配额充足 连接建立 并发计数 +1 配额耗尽 拒绝新建 连接关闭 并发计数 -1 说明:eBPF 在 BPF_CGROUP_INET_SOCK_CREATE 钩子中检查并发计数是否超过上限,超过则返回 -EPERM 拒绝连接。 新建速率通过滑动时间窗口(如 1 秒)内的新建连接数计算。

3.5 数据包速率配额:漏桶与 PPS 限制

数据包速率(PPS)限制通常用于防御小包攻击或控制 CPU 软中断负载。漏桶算法以恒定速率处理数据包,超出部分被丢弃或排队。eBPF 实现时,在 BPF_CGROUP_INET_INGRESS 钩子中维护一个包计数器和时间窗口。

// 伪代码:eBPF 数据包速率漏桶
struct leaky_bucket {
    __u64 pps_limit;     // 每秒允许的包数
    __u64 last_sec;      // 上一秒的时间戳(秒级)
    __u64 pkt_count;     // 当前秒内的包计数
};

static inline bool check_pps(struct leaky_bucket *lb) {
    __u64 now_sec = bpf_ktime_get_sec();  // 秒级时间
    if (now_sec != lb->last_sec) {
        // 新的一秒,重置计数
        lb->last_sec = now_sec;
        lb->pkt_count = 0;
    }
    if (lb->pkt_count >= lb->pps_limit) {
        return false;  // 超过 PPS 限制,丢包
    }
    __sync_fetch_and_add(&lb->pkt_count, 1);
    return true;
}
校准实践: 在 Kubernetes 环境中,通常将网络配额与 Pod 的 requests 和 limits 对齐。例如,设置 bandwidth: 100Mbps 对应令牌桶的 rate = 12.5MB/s,burst = 12.5MB(允许 1 秒突发)。连接数配额建议设置为 Pod 预期最大并发连接数的 1.5 倍,避免误杀正常流量。

3.6 配额校准的常见故障模式

当网络配额设置不合理或 eBPF 程序存在缺陷时,会出现以下典型故障:

  • 带宽配额过小:导致 TCP 吞吐量下降,重传率升高,应用层超时。eBPF 观测指标:tcpi_delivery_rate 远低于链路容量。
  • 连接数配额耗尽:新建连接被 -EPERM 拒绝,客户端出现 connection refused 错误。eBPF 观测指标:BPF_CGROUP_INET_SOCK_CREATE 返回码非零。
  • PPS 限速过于激进:导致正常小包业务(如 DNS、健康检查)被丢包,服务出现间歇性不可用。eBPF 观测指标:ingress_packets_dropped 计数器持续增长。
  • 时间戳精度不足:使用 bpf_jiffies64 或秒级时间戳时,在高速率场景下出现配额计算偏差,导致限速不准确。
故障现象 可能原因 eBPF 校准方法 修复建议
TCP 吞吐量低 带宽令牌桶 rate 设置过小 观测 tokens 是否频繁耗尽 增大 rate 或 burst 值
连接被拒绝 并发连接数达到上限 检查 concurrent_count map 值 提高连接数上限或优化连接复用
小包丢包率高 PPS 漏桶限速过严 统计 pkt_count 与 pps_limit 比值 增大 pps_limit 或启用 burst 模式
配额波动大 时间戳更新粒度太粗 对比纳秒与毫秒级时间戳的精度 改用 bpf_ktime_get_ns 并缓存
重要提示: 在 CGroup v2 环境中,eBPF 程序必须挂载到正确的 cgroup 层级。如果挂载到根 cgroup,配额将影响整个系统;如果挂载到子 cgroup,则仅影响该 cgroup 内的进程。使用 bpftool cgroup list 可以查看当前挂载的 eBPF 程序及其关联的 cgroup 路径。