资讯动态

Cilium 数据路径配置机制详解:用 DECLARE_CONFIG 与 BPF Static Data 替代 define

发布时间:2026/9/13 4:11:37 来源:尧图企业网站定制
Cilium 数据路径配置机制详解用 DECLARE_CONFIG 与 BPF Static Data 替代 #define【免费下载链接】ciliumeBPF-based Networking, Security, and Observability项目地址: https://gitcode.com/GitHub_Trending/ci/cilium导读Cilium 的数据路径datapathBPF 程序在加载时需要获取特性开关、地址、超时、Security ID 等大量运行时配置。本文基于 Cilium 官方开发文档 Configuring the Datapath完整讲解这套基于 Linux 5.2 static dataread-only map的配置机制如何用DECLARE_CONFIG/NODE_CONFIG/ASSIGN_CONFIG宏声明与使用配置变量、Go 侧脚手架代码如何由dpgen自动生成、agent 如何把值“接线”进 BPF 程序以及测试中覆盖配置值的正确姿势。读完后你可以在 Cilium 数据路径中安全地新增、移植和测试任何配置项。1. 背景从#define/#ifdef到结构化静态数据早期 Cilium 将 agent 配置以运行时生成的#define语句的形式“喂”进数据路径可选代码段用#ifdef等手段围起来。这套方式撑了很多年但随着 agent 与数据路径复杂度的上升官方认为需要一种更结构化、更易维护的配置方式。新机制依赖 Linux 内核 5.2 起支持的只读 mapstatic data配置数据存放在内核验证程序之后不可更改的只读区域当这些值被用于分支判断时verifier 可以做死代码消除DCE把不可达分支裁掉减少后续验证步骤的工作量让最终 BPF 程序映像尽可能精简不再需要按配置条件编译conditional compile数据路径代码——BPF 代码在编译期构建并嵌入 agent因此 agent 容器不再需要携带 LLVM所谓 “clang-free”镜像更小、启动更快、CPU 占用更低配置变更时 endpoint 重生成也更快。2. 快速上手一个配置变量的完整生命周期下面用一个贯穿全文的例子来自bpf/include/bpf/config/lxc.h被bpf_lxc.c引入展示声明、生成、接线、读取四个阶段。2.1 阶段一声明 C 变量在bpf/include/bpf/config/lxc.h中声明DECLARE_CONFIG(__u16, endpoint_id, The endpoints security ID)该宏声明了一个名为endpoint_id的 16 位无符号整型配置值第二个参数是描述文本——这个描述后面会直接出现在生成的 Go 代码注释里。仓库中该文件实际内容见 bpf/include/bpf/config/lxc.hDECLARE_CONFIG(__u16, endpoint_id, The endpoints security ID) #define LXC_ID CONFIG(endpoint_id) /* Backwards compatibility */声明宏的底层实现在 bpf/lib/static_data.h可以看到几个关键设计#define __CONFIG_SECTION .rodata.config #define DECLARE_CONFIG(type, name, description) \ DECLARE_CONFIG_KIND(object, type, name, description) #define DECLARE_CONFIG_KIND(kind, type, name, description) \ __section(__CONFIG_SECTION) \ __attribute__((btf_decl_tag(kind: kind))) \ __attribute__((btf_decl_tag(description))) \ volatile const type __config_##name;要点变量被放到.rodata.config段编译器会为其中的所有变量生成 BTF Datasec方便工具遍历并生成 Go 配置脚手架ebpf-go 会将其暴露在CollectionSpec.Variables中两个 BTF decl tagkind区分 object 级配置与 node 级配置决定 dpgen 生成到哪个 Go structdescription把 C 里的描述带入生成的 Go 文档注释变量本身是volatile constvolatile防止编译器消除所有访问从而把变量从 ELF 中丢掉。声明完成后重新构建数据路径并运行dpgen生成 Go 代码make -C bpf -j$(nproc)生成逻辑位于 tools/dpgen输出会落到pkg/datapath/config包的 Go 配置脚手架中。2.2 阶段二自动生成的 Go 结构体make之后pkg/datapath/config包中会出现对应结构体字段。实际的生成文件 pkg/datapath/config/lxc_config.go 开头即标注// Code generated by dpgen. DO NOT EDIT.// BPFLXC is a configuration struct for a Cilium datapath object. // // Warning: do not instantiate directly! Always use [NewBPFLXC] to ensure the // default values configured in the ELF are honored. type BPFLXC struct { ... // MTU of the device the bpf program is attached to. DeviceMTU uint16 config:device_mtu ... // The endpoints security ID. EndpointID uint16 config:endpoint_id ... }结构体字段上的注释正是我们在 C 侧DECLARE_CONFIG里写的描述字段通过config:endpoint_id标签与 C 变量名绑定。注意结构体注释中的警告不要直接实例化必须经由NewBPFLXC等构造函数创建以确保 ELF 中已配置好的默认值被正确保留这正是第 5 节ASSIGN_CONFIG默认值机制的配套要求。2.3 阶段三把 Go 值“接线”到 agent生成字段只是脚手架还需要 agent 在 BPF 加载时填入真实值。具体填在哪里取决于对象类型以及该值是“节点级配置”还是“对象专属配置”。以bpf_lxc.c的 endpoint 配置为例赋值发生在pkg/datapath/config包的 endpoint 配置逻辑中pkg/datapath/config/endpoint.go 中可以看到cfg.EndpointID uint16(ep.GetID())警告这个接线工作每个需要访问该变量的对象都要做一遍如果你在bpf_lxc.c与bpf_host.c共同引用的头文件中声明了一个变量就必须保证 agent 同时向两个对象对应的 struct 供值。文档说明撰写时填充 Go 配置脚手架的工作仍大多散落在pkg/datapath/loader等少数几处目标是为每个配置对象建立 StateDB 表由 Hive Cell 管理并在任一值变化时自动触发相应 BPF 程序重载。文档会随该演进持续更新。若本文档与代码库不一致可 grep 各 struct 及其字段的用法沿既有代码模式扩展。2.4 阶段四在 BPF C 代码中读取统一通过CONFIG()宏访问变量。该宏展开为对特殊变量名的引用实现见 bpf/lib/static_data.h宏存在的意义是未来如果底层表示方式变化可以避免大规模交叉改动代码。CONFIG(endpoint_id)用法与平常的变量一致——直接取值__u16 endpoint_id CONFIG(endpoint_id);或在分支中判断if (CONFIG(endpoint_id) ! 0) { ... }CONFIG()宏内部值得注意的细节它用asm volatile在每次访问时重新构造 rodata 指针阻止编译器在相邻多次访问间复用同一指针。从源码注释看这样能让基于配置变量的 load/deref/branch 指令彼此靠近、不被跨基础块复用从而更利于 verifier 通过回溯做出可预测的分支推断。注意配置变量不是编译期常量因此不能用来控制 BPF map 大小也不能在编译期初始化其他全局const变量。3. 节点级配置Node Configuration警告历史上 agent 的绝大多数配置都以“节点配置”node_config.h的形式提供给数据路径。该模式今后不推荐继续使用未来可能被移除新代码应优先使用DECLARE_CONFIG()见第 4 节约定。为让从#define风格配置向新机制迁移更平滑项目保留了节点配置这一概念只是值改为运行时提供而非#ifdef。节点配置声明在bpf/include/bpf/config/node.h中bpf/include/bpf/config/node.h 中有大量真实例子NODE_CONFIG(__u32, cilium_net_ifindex, Interface index of the cilium_net device) NODE_CONFIG(union macaddr, cilium_net_mac, MAC address of the cilium_net device) NODE_CONFIG(__u32, trace_payload_len, Length of payload to capture when tracing native packets.) #define TRACE_PAYLOAD_LEN CONFIG(trace_payload_len) /* Backwards compatibility */ NODE_CONFIG(__u32, cluster_id, Cluster ID)NODE_CONFIG与DECLARE_CONFIG的区别仅在于 BTF decl tag 中的kind字段为node见 bpf/lib/static_data.hdpgen 据此把它们生成到独立的 Node 结构体可嵌入所有对象级配置 struct。在 Go 脚手架中的形态为type Node struct { // The foo value. Foo uint64 config:foo }agent 侧通过pkg/datapath/config.NodeConfig()填充例如func NodeConfig(lnc *config.Config) Node { ... node.Foo 42 ... }在 BPF C 代码中节点配置的读取方式与 object 配置完全一致同样是CONFIG(name)。4. 约定与推荐Guidelines and Recommendations)官方文档给出了几条指导原则决定了配置变量应声明在哪里避免“死代码”——即声明了却从未被 agent 赋值的变量。例如只有bpf_lxc.c使用某变量时不要把它放进多个 BPF 对象共用的头文件如需与其他对象共享类型请把类型放进单独的头文件。在使用点附近声明变量例如放在实现该特性的头文件里。避免条件式#include。判断配置声明位置的决策流程新特性在实现该特性的头文件中使用DECLARE_CONFIG()并只在该特性实际被使用的 BPF 对象中引入该头文件既有特性的新配置在尽可能靠近消费该配置代码处使用DECLARE_CONFIG()从node_config.hWriteNodeConfig移植节点配置先尽量缩小该配置的用途范围看能否改为在使用它的少量 BPF 对象所引入的头文件中DECLARE_CONFIG()。这种重构是值得的——它能避免在不使用该节点配置的对象中产生死代码以上都不适用时才使用NODE_CONFIG()。bpf/include/bpf/config/目录按对象拆分的头文件lxc.h、node.h、host.h、sock.h、xdp.h、overlay.h、endpoint.h、global.h即这一组织方式的落地体现。5. 默认值ASSIGN_CONFIG如果希望某个配置变量的默认值不是 0并直接在 C 侧给出默认值可在声明之后使用ASSIGN_CONFIG()宏。这在 agent 不提供值时自动生效一个合理默认值很有用。agent 对设备 MTU 就是这么做的见 bpf/lib/nodeport.hDECLARE_CONFIG(__u16, device_mtu, MTU of the device the bpf program is attached to) ASSIGN_CONFIG(__u16, device_mtu, MTU)而ASSIGN_CONFIG的宏实现bpf/lib/static_data.h还包含一个“先引用再赋值”的防护#define ASSIGN_CONFIG(type, name, ...) \ void __check_##name(void) \ { CONFIG(name); /* Error: variable was assigned before declaring. */ }; \ volatile const type __config_##name __VA_ARGS__;从源码结构看这段先对CONFIG(name)取址的桩函数保证“必须先声明后赋值”否则编译会直接报错。警告ASSIGN_CONFIG()每个变量在每个编译单元中只能使用一次。这意味着该变量无法在测试中被覆盖除非使用第 6 节的 workaround因此应谨慎使用。6. 测试中覆盖配置值编写数据路径测试时往往需要覆盖配置值以走到不同代码路径。做法与第 5 节相同在导入被测主对象如bpf_lxc.c之后在测试文件中用ASSIGN_CONFIG()覆盖。最及时的示例请直接参考测试套件本身例如 bpf/tests/bpf_nat_tests.cASSIGN_CONFIG(__u16, device_mtu, 1500);需注意的限制传给ASSIGN_CONFIG()的字面量必须是编译期常量不能是其他变量的名字。偶尔你可能需要覆盖一个已经用ASSIGN_CONFIG()设置了默认值的配置此时需要一个 workaround#ifndef OVERRIDABLE_CONFIG DECLARE_CONFIG(__u8, overridable, Config with a default and an override from tests) ASSIGN_CONFIG(__u8, overridable, 42) #define OVERRIDABLE_CONFIG CONFIG(overridable) #endif然后在测试文件中#include被测对象之前先#define OVERRIDABLE_CONFIG使覆盖值生效#define OVERRIDABLE_CONFIG 1337 #include bpf_lxc.c文档强调这种写法“有点反直觉”应少用并优先考虑重构代码以规避这种需求。7. 小结一次声明三端联动整套机制的核心链路是环节位置作用声明bpf/include/bpf/config/*.hDECLARE_CONFIG/NODE_CONFIG生成.rodata.config段变量 BTF tag生成tools/dpgen →pkg/datapath/config/*_config.go从 BTF 生成带config:name标签的 Go struct接线pkg/datapath/config、pkg/datapath/loaderagent 在 BPF 加载时为每个对象填充字段读取BPF C 代码统一经CONFIG()宏访问verifier 可做死代码消除默认/覆盖ASSIGN_CONFIGC 侧编译期默认值测试中按规则覆盖理解这条链路后你就能在 Cilium 数据路径中正确新增配置项选对DECLARE_CONFIG还是NODE_CONFIG、为每个消费该变量的 BPF 对象完成 agent 侧接线、并借助ASSIGN_CONFIG为测试与默认值兜底。【免费下载链接】ciliumeBPF-based Networking, Security, and Observability项目地址: https://gitcode.com/GitHub_Trending/ci/cilium创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价