使用 ftrace 跟踪内核函数调用栈

搞虚拟化的,经常会遇到很多和内核相关的线上问题。比如最近我们遇到一个 k8s 的 pod 在删除的时候 pvc 卸载不掉的情况。

umount 的时候提示挂载点已经 not mounted 了,但是在 /proc/mounts 里仍然存在。statvfs 看了一下,这个挂载点的device id和父目录的device id 是一样的,说明已经不是挂载点了(因为看 /proc/mounts,这个挂载点的src是在另外一个设备上)

特别注意:如果 /proc/mounts 里,src 和 dst 是同一个设备,那就是 –bind 的方式,这种情况下就很难判断挂载点是否已经真正的卸载掉了

针对这个问题,社区已经有了一个比较 hack 的解决方案:https://github.com/kubernetes/kubernetes/issues/114546,就是忽略 /proc/mounts 残留,允许 pod 删除流程继续往下走

但是根源还是内核的存在脏数据(大概率猜测可能是引用计数泄露导致),还是得从内核层面定位这种问题,而且这种问题,很多时候我们得知道内核代码的执行路径是什么样的。这个时候用 ftrace 来定位就很方便了,比 eBPF 好使,ftrace 能够直接跟踪一个内核函数的调用栈

用法很简单:

  1. 打开 function_graph:echo function_graph > /sys/kernel/debug/tracing/current_tracer
  2. 指定要跟踪的函数:echo do_umount > /sys/kernel/debug/tracing/set_graph_function
    1. 确保 /sys/kernel/debug/tracing/set_ftrace_filter 是空的,不然看不到完整的栈,只能输出这里指定的函数
  3. 开始跟踪:echo 1 > /sys/kernel/debug/tracing/tracing_on,注意如果定位完了,一定要 echo 0 关闭掉
  4. 获取输出:cat /sys/kernel/debug/tracing/trace

最后得到的 trace 结果是这样的:

有了这个调用栈,内核函数的执行路径就一目了然了,因为内核走不到的分支这里不会打印出来(注意内核有很多 inline 函数也不会展示,编译优化掉了)

除此之外,如果想跟踪特定的进程的操作,可以使用 /sys/kernel/debug/tracing/set_ftrace_pid 接口指定 pid