ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

XDP部署报错“No bpffs found”?从挂载到namespace的完整排查修复指南

XDP部署报错“No bpffs found”?从挂载到namespace的完整排查修复指南

最近在一台新装机器上部署XDP数据面时,脚本跑到一半就停了,终端只有一行:libxdp: No bpffs found at /sys/fs/bpf。我的第一反应是"bpffs没挂载",可是执行mount | grep bpf又明明能看到/sys/fs/bpf。反复确认了几次我才意识到,这个报错的意思比字面上要宽得多:它说的是 libxdp 在"当前进程视角"下没有找到可用的 bpffs,而不一定是文件系统真的不存在。这篇笔记就把我完整排查和修复的过程写下来,包括 libxdp 对 bpffs 的依赖逻辑、mount namespace 的坑、systemd 和容器环境下的差异,以及一套能直接抄的检查脚本,希望能给同样卡在这个报错上的人省点时间。

1. 先搞清 libxdp 和 bpffs 之间到底是什么关系

1.1 XDP程序为什么要"钉"在文件系统上

XDP 程序运行在内核里,但它本质上是 BPF 对象。BPF 对象是由用户态进程通过系统调用创建的,寿命和引用计数挂钩:如果创建它的进程退出、文件描述符关闭,又没有其他引用,这个对象就会被内核销毁。对 XDP 场景来说,一个数据面程序往往需要先加载 BPF program,再创建一堆 BPF map,随后 AF_XDP 套接字或用户态控制面还要持续访问这些 map。如果进程一退出对象就没了,整个数据面就没法管理。

解决办法就是"pin"(钉住)。内核允许把 BPF 对象固定到 BPF 文件系统(bpffs)上,只要 pin 文件还在,对象就活着。XDP 加载器通常会把 program 和 map 都 pin 到/sys/fs/bpf下面的某个子目录,比如/sys/fs/bpf/xdp/...。这样第一个加载程序退出后,后续的管理进程、监控进程仍然能通过这些路径找到对象。可以理解成:bpffs 是内核 BPF 对象之间互相传递的"公共白板"。

1.2 libxdp 初始化时藏在最前面的路径检查

libxdp 是 xdp-tools 的核心库,常配套的xdp-loader只是它的命令行外壳。加载器启动后不是立刻发 BPF 系统调用,而是要先把运行环境准备好:检查 bpffs 是否可用、创建自己的目录、清理上次残留的 pin 文件等。在这个准备阶段,libxdp 第一步就是去检测约定路径/sys/fs/bpf。

正常情况下,bpffs 挂载在这个路径后,加载器会往里写东西;如果检测失败,它就认为"没有可用的 bpffs",于是直接抛出libxdp: No bpffs found at /sys/fs/bpf,然后终止。也就是说,这条报错本质是一个"环境前提检查"失败,而不是 BPF 程序本身编译或加载出错。想解决它,得从环境入手。

1.3 同一个报错,三种完全不同的潜台词

我在排查过程中发现,这条信息背后至少有三个梯队的原因,而且它们表现很像:

原因梯队具体表现典型场景
bpffs 真的没挂载/proc/mounts里找不到 bpf 类型挂载新装系统、初始化脚本没做系统准备、内核模块加载顺序问题
挂载了但当前进程看不见宿主机有/sys/fs/bpf,进程所在 namespace 里没有systemd 服务开了 PrivateMounts、容器未 bind mount
挂载点可见但不可访问路径存在,但 ls/touch 报 Permission denied 或 Read-only file system普通用户无权限、LSM 策略拦截、挂载选项为 ro

这个分类帮我快速缩小了范围。后面所有排查步骤,基本都是在判断当前环境到底属于哪一类。

2. 我的排查链路:从常规检查一路挖到 namespace

2.1 先做一个"挂载状态快照"

排查的第一步永远是收集事实,别猜。我一般同时执行下面几条命令:

mount | grep bpf cat /proc/mounts | grep bpf ls -ld /sys/fs/bpf stat -f -c '%T' /sys/fs/bpf
  • 如果mount和cat /proc/mounts完全没有 bpf 相关行,说明当前 namespace 根本没挂载。
  • 如果ls -ld /sys/fs/bpf能输出目录信息,同时stat -f -c '%T'返回bpf,说明这个路径确实是 bpffs 挂载点。
  • 如果ls报Permission denied,问题就偏向权限而非挂载缺失。

注意一点:mount命令读的是/proc/mounts,它显示的是"当前挂载命名空间"里的挂载表。如果你是用 SSH 登到宿主机上执行的,那只是宿主机视角,不代表故障进程也看得到。这也是很多人第一步被误导的原因。

2.2 宿主机和故障进程的视角对比

发现宿主机有挂载但依然报错后,下一步就是切换到故障进程的视角去看。最直接的办法是用nsenter进入目标进程的挂载命名空间:

# 宿主机视角 grep bpf /proc/mounts # 目标进程视角,PID 换成实际值 sudo nsenter -t 1234 -m grep bpf /proc/mounts

如果宿主机有、目标进程没有,结论已经出来了:不是 bpffs 不存在,而是当前进程的 mount namespace 里根本没有这个挂载。我还可以再看两个 namespace 的编号,进一步确认是不是同一命名空间:

readlink /proc/self/ns/mnt readlink /proc/1234/ns/mnt

如果两个链接指向的 inode 号不同,说明它们确实生活在不同的挂载命名空间里。后面所有排查重点都放到"这个进程为什么看不到宿主挂载"上,而不是急着去 mount。

2.3 权限和挂载属性也不能跳过

即使 bpffs 挂载可见,如果当前用户访问不了,结果依然可能表现为"找不到"。我一般会随手用普通用户和 sudo 各执行一次:

ls -ld /sys/fs/bpf sudo ls -ld /sys/fs/bpf touch /sys/fs/bpf/.write_test sudo touch /sys/fs/bpf/.write_test

很多发行版里/sys/fs/bpf的默认权限是drwx------ root root,普通用户无法写。如果程序本身是为了给普通用户运行的,这个权限问题迟早会暴露。另外,挂载选项里如果有ro,即使 root 也没法写文件,touch会直接报Read-only file system。

顺带提一句:SELinux、AppArmor 这类 LSM 也可能拦截对 bpffs 的访问,但那个通常会报Permission denied或Operation not permitted,而不是这条No bpffs found。所以它一般不是主因,但我也见过策略导致 libxdp 在探测路径时拿不到完整信息的情况,因此不该完全忽略。

2.4 顺手确认内核 BPF 配置

如果挂载命令本身都失败了,那要去看内核配置。BPF 文件系统不是凭空就能 mount 的,内核必须开了CONFIG_BPF_SYSCALL。常见的检查方式:

zcat /proc/config.gz 2>/dev/null | grep -E 'CONFIG_BPF' # 如果 /proc/config.gz 不存在,就看 /boot/config-$(uname -r) grep -E 'CONFIG_BPF' /boot/config-$(uname -r)

重点关注几个选项:CONFIG_BPF_SYSCALL、CONFIG_BPF_JIT、CONFIG_DEBUG_INFO_BTF。如果CONFIG_BPF_SYSCALL=n,那真的没法用 bpffs,问题已经不是挂载能解决的了。不过我实际遇到的绝大多数机器都是开启的,所以这一步通常排在后面,避免一上来就在内核上浪费半天。

3. 根因定位:为什么"明明挂在那里"却找不到

3.1 挂载从来不是"全局"的,它是进程视角的

很多刚接触 Linux 的人会默认"挂了文件系统,所有进程都能看到",其实不对。挂载是挂载命名空间里的属性。每个进程都有一份自己的挂载表,它可能来自父进程的继承,也可能带着一堆私有的增删。用unshare(mount)或者某些容器运行时创建新命名空间后,新空间默认会复制父空间当时的挂载状态,但之后父空间新挂载的东西不会自动同步进去,除非双方设置了挂载传播。

回到这个报错:如果宿主机的 systemd 或管理员后来把 bpffs 挂到了/sys/fs/bpf,而你的程序是在挂载之前就进入了一个私有 mount namespace,那么在它眼里这个挂载点就是不存在的。不是系统坏了,是"相机的镜头"不对。

3.2 systemd 服务里最容易踩的 PrivateMounts 陷阱

systemd 默认会给一部分服务设置独立的挂载命名空间,尤其是当你写了这类配置时:

[Service] PrivateMounts=yes

或者某些安全加固选项组合起来,systemd 会让服务进程带上MountFlags=slave、PrivateMounts=yes之类的属性。服务进程启动后,宿主机的 bpffs 挂载不一定还是可见的。如果 xdp-loader 是被 systemd 拉起来的,我用 systemd-run 临时复现过:

systemd-run --wait --property=PrivateMounts=yes sh -c 'mount | grep bpf || echo NO_BPFFS'

如果输出NO_BPFFS,那基本实锤了。解决办法也很简单:把服务单元里多余的私有挂载选项去掉,或者在服务启动前确保 bpffs 已经挂载好;更稳妥的做法是让服务依赖一个专门的sys-fs-bpf.mount单元,保证启动顺序正确。

3.3 容器场景:不 bind mount 就别想看见

容器和 systemd 服务的逻辑是一样的,只不过更明显。容器默认不会继承宿主机后来新增的挂载点,就算挂载命名空间是新建的、初始状态复制自宿主机,也只在创建容器那一刻复制。如果容器启动命令里没有把/sys/fs/bpfbind 进去,容器内就是看不到。

正确的容器用法是在启动时显式挂载:

container run --rm -it \ --mount type=bind,src=/sys/fs/bpf,dst=/sys/fs/bpf \ your-image bash

这里多说一句:不要指望只给--privileged就能解决。特权模式只是给了容器更多 capability,不代表它自动继承宿主所有的挂载点。要访问 bpffs,最干净的方式仍然是显式 bind mount。

3.4 libxdp 找的是"约定路径",不是任意一个 bpffs

还有一个经常被忽略的点:libxdp 的探测逻辑通常是直奔/sys/fs/bpf,不会像grep /proc/mounts那样去扫描系统里所有的 bpf 挂载。如果你图方便把 bpffs 挂到了/mnt/bpf或者/run/bpffs,然后继续用 xdp-loader,它照样会报No bpffs found at /sys/fs/bpf。听起来很蠢,但很多脚本为了不污染系统根目录,确实会这么干。

如果已经挂到别的路径,可以把标准路径变成 bind 挂载:

sudo mkdir -p /sys/fs/bpf sudo mount --bind /run/bpffs /sys/fs/bpf

这样就既保留了原来的挂载,又让 libxdp 能在它期望的路径上找到文件系统。当然,如果上层工具支持通过参数指定 bpffs 路径,优先用参数,比 bind 挂载更清爽。

4. 修复方案:从临时救火到持久化配置

4.1 临时救急:把缺失的挂载补上

如果排查下来确实是当前 namespace 里没有挂载,最简单的修复就是手动挂载:

sudo mkdir -p /sys/fs/bpf sudo mount -t bpf bpf /sys/fs/bpf sudo mountpoint /sys/fs/bpf

这个命令里-t bpf指定文件系统类型,bpf作为设备名只是一个占位符,写none也行。如果你已经看到挂载点存在但里面是空的,说明它已经被挂载过了,这步可以跳过。要是 mount 报already mounted,说明有重复挂载,先检查再处理,别硬挂。

临时挂载重启后会失效,只适合用来做验证和救急。我实际部署时从来不会把这一步写进一个裸脚本里当长期方案,它只能证明"问题确实在挂载上"。

4.2 开机自动挂载:fstab 是最不容忘的方案

要让 bpffs 在系统启动后自动出现在/sys/fs/bpf,最省心的方式是写进/etc/fstab:

none /sys/fs/bpf bpf rw,relatime 0 0

写入后先执行sudo systemctl daemon-reload && sudo mount -a,确认没有语法问题,再重启验证一次。千万别以为 fstab 只对普通磁盘有效,特殊文件系统也支持;只要内核有对应驱动,mount 阶段会自动处理。

如果你更习惯 systemd 的方式,也可以创建一个/etc/systemd/system/sys-fs-bpf.mount单元,原理是一样的,只是多一个可管理状态。对这个需求来说,fstab 已经足够简单可靠,我一般不推荐过度设计。

4.3 容器和初始化脚本场景的推荐做法

容器场景前面已经说过 bind mount。如果你是在初始化脚本/CI 环境里遇到这个问题,我建议把检查挂载的动作放到所有 XDP 操作之前,而不是在每个包里各自 mount。一个简单的自检片段:

if ! grep -qs " /sys/fs/bpf " /proc/mounts; then sudo mkdir -p /sys/fs/bpf sudo mount -t bpf bpf /sys/fs/bpf fi

这段可以放在脚本最开始。注意脚本运行时的 mount namespace 就是后续所有子进程的起点,所以在这里挂载一次,后续命令都能看到。

4.4 权限层面:给指定用户可写权限

排查到最后,如果挂载没问题,但普通用户运行 xdp-loader 依然失败,可以看看是不是目录权限。bpffs 的默认 owner 是 root,权限通常是 700。如果要让某个系统账号管理 XDP 程序,可以:

sudo chown root:bpftools /sys/fs/bpf sudo chmod g+rwx /sys/fs/bpf

把bpftools换成实际用户组。这种改法在生产环境要谨慎,毕竟 bpffs 里 pin 着内核对象,不应该让无关用户随便改。更好的办法是让服务通过 systemd 以指定用户运行,同时只把/sys/fs/bpf/你的子目录授权给那个用户,别把整个挂载点放开。

5. 验证与自检:确认 libxdp 这回是真的找到了

5.1 三条命令快速确认环境 OK

修完之后不要直接跑业务,先用几条命令验证一下:

mountpoint /sys/fs/bpf cat /proc/self/mountinfo | grep bpf bpftool map show

mountpoint输出挂载点状态,mountinfo里有bpf类型挂载,bpftool map show能列出哪怕一个 map,都说明当前 namespace 环境是通的。如果bpftool没装,用xdp-loader status替代也可以。

5.2 真正让 libxdp 初始化一次

环境就绪后,直接跑一次 xdp-loader 的状态查询:

sudo xdp-loader status

如果输出的是接口和插槽信息,而不是No bpffs found,说明 libxdp 已经成功找到 bpffs。如果想更进一步,找个最小的 XDP 程序实际加载一个试试:

sudo xdp-loader load eth0 xdp_prog.o sudo xdp-loader unload eth0 --all

如果加载阶段又出现别的报错,那就可能是程序本身、BTF 或者驱动不支持的问题,已经和 bpffs 无关了。这时候去看dmesg,通常能找到更底层的信息。

5.3 我见过的几个常见误判

第一,把 debugfs 当成 bpffs。/sys/kernel/debug是 debugfs,不是 BPF 文件系统,bpffs 要单独挂载。第二,以为重启后挂载还在。临时 mount 重启就丢,必须写 fstab 或 systemd unit。第三,以为在宿主机 mount 完,容器里就一定可见。实际上容器创建时如果不带 bind mount,仍然看不到。第四,以为报错就是内核不支持 XDP。先看清楚报错前缀,No bpffs found和BPF syscall failed是两码事。

5.4 沉淀成一套自检脚本

我把这套检查写成了一个参考脚本,放在所有 XDP 部署流程的最前面:

#!/usr/bin/env bash set -euo pipefail if ! grep -qs " /sys/fs/bpf " /proc/mounts; then echo "bpffs not mounted, trying to mount..." sudo mkdir -p /sys/fs/bpf sudo mount -t bpf bpf /sys/fs/bpf fi if [ ! -r /sys/fs/bpf ] || [ ! -w /sys/fs/bpf ]; then echo "bpffs not accessible by current user" exit 1 fi sudo -n xdp-loader status >/dev/null 2>&1 if [ $? -ne 0 ]; then echo "xdp-loader cannot initialize libxdp" exit 1 fi echo "bpffs and libxdp are ready"

这段脚本不是万能的,但它能在一开始就把"挂载缺失"和"命名空间不匹配"这类最常见的坑挡在门外。实际跑的时候,如果脚本输出bpffs not accessible,大概率就是权限或 SELinux 的问题,再去针对性处理即可。

最后再分享一个我自己的习惯:现在遇到libxdp: No bpffs found at /sys/fs/bpf,我第一句话会问对方"你是在宿主机上跑,还是在容器/systemd 服务里跑?"这个问题基本能定位一半问题。如果是在容器里,就直接看有没有 bind/sys/fs/bpf;如果是在 systemd 服务里,就看 PrivateMounts 之类是不是开了;如果是宿主机裸跑,才去检查 fstab 和内核配置。把排查顺序理顺之后,这个报错就不再神秘了。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进