火焰图是一种把调用栈抽样结果可视化的方式,横轴表示样本占比,纵轴表示栈深度。容器环境中的进程与宿主机共享同一个内核,理论上perf等工具只要获得足够权限并正确映射符号,就能在容器内外完成采集;但在实际落地时,权限不足、容器内缺少调试符号、内核栈展开失败这三个问题经常导致火焰图断裂或空白。本文围绕容器化应用的火焰图生成,给出可操作步骤与踩坑记录。

一、容器环境与宿主机共用内核带来的便利与限制
容器不是虚拟机,它通过cgroup和namespace在同一个Linux内核上隔离资源与视图。这个特性对性能分析是有利的:宿主机上的perf events、tracepoint、kprobe等内核机制可以直接观测容器内进程,因为所有进程都出现在同一个进程表里。例如在宿主机执行ps -ef能看到容器内进程的PID,只是它们的命名空间不同。正因为这个设计,火焰图采集工具不必进入容器内部,也能对容器进程做栈采样。
但便利常常被安全限制抵消。默认情况下,Docker容器缺少CAP_SYS_ADMIN等特权能力,调用perf_event_open系统调用时可能被拒绝。宿主机上/proc/sys/kernel/perf_event_paranoid值如果大于1,未授权用户也不能查看内核地址和部分硬件事件。许多发行版默认设置为2或3,导致容器里执行perf record出现Permission denied。另一个限制是符号表:容器镜像通常为精简体积剥离了调试符号,火焰图只能显示十六进制地址,无法还原函数名。即使宿主机采集成功,也要把容器内的可执行文件、动态库映射到正确路径,否则栈展开会丢失用户态符号。
理解这两点之后,生成容器化火焰图的思路就清晰了:先解决权限,让采集工具能打开perf事件;再解决符号,让样本地址能对应到函数名;最后把数据接入FlameGraph脚本生成SVG。Docker和Kubernetes场景只是这些步骤的封装方式不同。
二、用perf在Docker容器中采集调用栈
perf是最常用的Linux性能分析工具,跟随内核版本发布。推荐在宿主机和容器内使用相同版本或接近版本的perf,避免record与script之间格式不兼容。最简单的方式是让容器继承宿主机的perf能力:启动容器时加上--cap-add SYS_ADMIN,并尽量降低perf_event_paranoid。也可以使用--privileged,但生产环境不建议因为权限过大。
先确认内核参数。可以在宿主机执行:
cat /proc/sys/kernel/perf_event_paranoid
如果输出为3,表示很多事件被禁用;将值临时调整为1或-1会放开限制。修改方式:
sudo sysctl kernel.perf_event_paranoid=1
然后以特权能力运行容器:
docker run --rm -it --cap-add SYS_ADMIN --pid=host --name flame-app your-image:latest bash
--pid=host让容器共享宿主机的PID命名空间,这样perf看到的进程号和宿主机一致,便于指定目标进程。接着在容器内安装perf。基于Debian的镜像可以执行:
apt-get update && apt-get install -y linux-perf
基于Alpine则需要执行apk add perf。注意包名可能随内核版本变化,如果安装失败,可以挂载宿主机的perf二进制:
docker run --rm -it --cap-add SYS_ADMIN -v /usr/bin/perf:/usr/bin/perf:ro your-image:latest bash
启动目标应用后,使用perf record对进程做99Hz采样,持续30秒:
perf record -F 99 -p 12345 -g -- sleep 30
这里-p指定进程PID,-g表示记录调用栈。若容器内出现No permission to enable cycles event,说明perf_event_paranoid仍然过高,或者缺少CAP_PERFMON。较新的内核支持更细粒度的CAP_PERFMON能力,可以只授予该能力而不给完整SYS_ADMIN:
docker run --rm -it --cap-add CAP_PERFMON --cap-add CAP_SYS_PTRACE your-image:latest bash
采样结束后生成perf.data,执行perf script得到折叠前文本:
perf script > out.perf
再配合FlameGraph仓库脚本生成火焰图:
git clone https://github.com/brendangregg/FlameGraph.git cd FlameGraph ./stackcollapse-perf.pl ../out.perf > out.folded ./flamegraph.pl out.folded > flame.svg
如果所得火焰图出现大量unknown或地址,重点检查符号表。容器内应用如果是静态编译,可以把带符号的二进制文件放入容器或宿主机对应路径;如果是动态链接,可以设置PERF_SEARCH_PATH或手动将build-id目录映射到采样机器。命令如下:
export PERF_SEARCH_PATH=/path/to/container-rootfs/usr/lib/debug
还可以在宿主机采集时使用--symfs参数指定容器根文件系统路径,这样perf report能自动寻找符号。
三、语言专用工具在容器中的火焰图生成
对于Java、Python、Node.js等运行时应用,perf只能看到JIT或解释器的内部符号,栈可能不完整。此时使用语言自带或专用的采样工具会更准确。Java应用推荐async-profiler,它基于HotSpot内部API和Linux perf事件,能同时展开Java栈与内核栈,并直接输出火焰图HTML。async-profiler在容器中运行时,同样需要perf_event_paranoid和CAP_PERFMON支持,但不必修改应用启动参数,通过attach方式即可。