Linux-38 观测体系的设计与 eBPF:看见系统而不压垮系统
上一篇讲 systemd 如何统一管理服务生命周期与日志。这一篇继续追问:服务已经运行,P99 突然升高,但 CPU 平均值正常、日志没有 error、重启后暂时恢复,怎样从“症状”走到“内核里真正发生了什么”?
可观测性不是安装一个 dashboard,也不是“把所有事件都记录下来”。系统每秒可能发生数百万次 syscall、调度、包处理和内存分配;无差别采集会改变被测对象、制造巨大成本,甚至成为新的故障。真正的设计目标是:用有界开销保留足够证据,在不知道故障答案之前仍能提出并验证假设。
先看五个问题:
- metric、log、trace、profile 分别压缩了什么信息,为什么任何一种都不能单独回答所有问题?
/proc、sysstat、strace、perf、ftrace、tracepoint、kprobe 各自适合什么层级,观测会怎样扰动系统?- eBPF 程序如何在内核 hook 上执行而不允许任意破坏内核?verifier 能证明什么、不能证明什么?
- map、per-CPU aggregate、ring buffer、BTF 与 CO-RE 如何让 BPF 程序跨事件保存状态并适应内核版本?
- 高基数、采样偏差、时间同步、丢事件和探针本身 CPU 如何让一张“漂亮图”得出错误结论?
1. 可观测性不是监控的同义词
1.1 监控已知失败,可观测性探索未知状态
监控通常从已知风险出发:
CPU > 90%
disk usage > 85%
HTTP 5xx rate > 1%
replication lag > 30s
它适合告警和自动化动作。可观测性更强调:仅凭系统输出,能否推断内部状态、回答事先未写好 dashboard 的问题,例如“只有某 NUMA node 上、携带特定 payload 大小、恰逢 cgroup throttle 的请求为何慢”。
两者不是互斥。没有稳定监控,团队不知道何时调查;只有预设监控,未知故障会落在指标盲区。
1.2 telemetry 不是事实本身
真实系统行为
-> instrumentation / kernel counters
-> aggregation / sampling
-> queue / transport
-> storage / downsampling
-> query / visualization
-> 人的解释
每层都可能丢失、延迟或改变信息。看到“0 errors”可能是确实为零,也可能是 exporter 挂了、label 改名、采样没命中、日志被 rate limit。可观测系统本身也需要自监控:采集成功率、drop、queue lag、存储拒绝、查询延迟和成本。
1.3 信号必须能对应决策
收集前问:
谁会看?
用它判断什么?
判断后能采取什么动作?
需要多快、多细、多长保留?
“以后可能有用”会把系统变成昂贵数据坟场。面向告警的指标可能保留 13 个月趋势;面向秒级内核事件的原始 trace 也许只在内存保留 30 秒;审计日志则有另一套不可篡改与合规周期。
2. 四种主信号:Metrics、Logs、Traces、Profiles
2.1 Metrics:时间序列上的聚合
metric 通常是:
name + labels + timestamp -> value
例如:
http_requests_total{service="api",method="GET",code="200"} 1289012
http_request_duration_seconds_bucket{service="api",le="0.5"} 88230
process_resident_memory_bytes{service="api"} 734003200
优势:体积小、可长期保留、适合 rate/趋势/SLO/告警。代价:聚合后无法恢复单次事件;label 选择决定以后能切哪些维度。
2.2 Logs:离散事件及其上下文
{"time":"...","level":"error","service":"api","request_id":"r-123","msg":"commit failed","error":"context deadline exceeded"}
日志能承载错误链、状态变化和少量业务上下文,适合解释“这次发生了什么”。它不是免费字符串:格式化、锁、序列化、磁盘/网络、索引都消耗资源。请求成功全量 debug 日志可能比业务本身更贵。
日志应结构化且 schema 稳定;敏感 token、密码、完整请求体和个人数据不应因排障方便被写入。
2.3 Traces:一次请求跨组件的因果路径
分布式 trace 用 trace/span 表示操作树或图:
trace r-123
api HTTP server span 240ms
auth RPC 8ms
database query 210ms
connection pool wait 180ms
它回答“时间花在哪个服务/阶段”,需要上下文随 RPC 传播。trace 中的父子关系是应用因果,不自动等于内核调度因果;一个 span 的 200ms 可能是 CPU、锁、网络、quota或等待下游。
2.4 Profiles:资源消耗按调用栈聚合
CPU profile采样“CPU 正执行哪些栈”;heap profile聚合仍存活/累计分配;block/mutex profile记录同步等待事件的样本。典型输出是:
stack -> samples / bytes / delay
profile擅长回答“哪个代码路径消耗最多资源”,通常没有每次请求的完整时序。continuous profiling把低频采样长期保留,可与部署/指标时间窗对齐。
2.5 四者如何协作
metric:P99 从 80ms 升到 600ms,何时、范围多大?
trace:慢请求主要卡在连接池等待,哪些下游?
profile:等待期间 CPU/锁热点在哪个调用栈?
log:某次 pool reset 的错误和配置上下文是什么?
eBPF/kernel trace:线程为什么没运行、网络包/IO 在内核哪层等待?
不是所有事故都需要全部信号。先用低成本广覆盖信号缩小范围,再临时启用高分辨率工具。
3. Metric 类型与常见数学陷阱
3.1 Counter 只累计
requests_total
errors_total
bytes_sent_total
进程重启会归零,查询应计算区间 rate/increase并处理 reset。直接告警 requests_total > N 没有意义,因为它只会增长。
rate(http_requests_total[5m])
rate(http_requests_total{code=~"5.."}[5m])
/
rate(http_requests_total[5m])
低流量分母接近零时错误率剧烈波动,应同时设最小请求量或使用 burn-rate窗口。
3.2 Gauge 可升可降
queue_depth
active_connections
memory_bytes
temperature
gauge 适合当前状态,但 scrape之间的短峰可能完全看不到。每 30 秒采一次队列深度,峰值在 2 秒内形成又消失,dashboard仍显示正常。需要 histogram、high-water mark或事件采样保留短时信息。
3.3 Histogram 保留分布近似
classic histogram用累计 bucket:
le=0.1 800
le=0.5 980
le=1.0 995
+Inf 1000
可跨实例合并 bucket后估算 quantile。bucket边界决定误差:若 SLO 是 300ms,只有 100ms和1s边界,很难判断 SLO附近变化。
summary在客户端计算 quantile,通常不能把多个实例的 quantile再求平均。P99 的平均不是全局 P99。 选择取决于监控系统和聚合需求。
3.4 平均值隐藏双峰
99% requests = 10ms
1% requests = 10s
average ≈ 110ms
平均值看似还能接受,尾部用户已经严重失败。反过来 P99也隐藏最慢 1%内部规模;SLO应配合 error budget、直方图、最大/超时计数和请求量。
3.5 Little’s Law 连接并发与延迟
稳态下:
L = λ × W
平均在途请求 L = 到达率 λ × 平均停留时间 W。吞吐不变而在途数上升,通常意味着延迟上升;连接池 queue depth与请求 rate能交叉验证。但系统非稳态、丢请求或队列有界时,不能机械套公式。
4. 高基数:最容易把观测平台打垮的设计
4.1 每种 label 组合都是一条时间序列
假设:
service 100
instance 1000
endpoint 200
status 20
user_id 1,000,000
把 user_id 放 metric label 会产生近乎无界 series。内存索引、WAL、网络、查询 fan-out 和账单迅速膨胀。
适合 metric label 的是有界、稳定、可聚合维度:service、region、method、规范化 route、status class。request ID、原始 URL、用户、订单号应放 trace/log,并受采样与访问控制。
4.2 路由模板而不是原始路径
错误:
path="/users/184729/orders/99128"
正确维度:
route="/users/{user_id}/orders/{order_id}"
还要防 404 随机路径、User-Agent、异常 header把 series打爆。instrumentation应在 schema层白名单 label,而不是把任意 tag自动提升为 metric。
4.3 trace/log 也有高基数成本
日志索引 request_id、stacktrace全文或动态 JSON key,同样会扩大索引;trace attribute过多会增加 payload和查询成本。高基数不是“Prometheus 独有问题”,区别只是存储系统如何收费和退化。
4.4 cardinality budget
每个团队/metric应估算:
series ≈ product(label cardinalities)
samples/day = series × 86400 / scrape_interval
设 per-service series budget、拒绝未知 label、监控 churn与top metrics。上线前自动测试 instrumentation,在故障期间临时加维度也要有过期机制。
5. 从 RED 与 USE 建立排查入口
5.1 RED 面向请求服务
Rate:请求/消息速率
Errors:失败、超时、拒绝比例
Duration:延迟分布
按 service、规范化 operation、result分类。它回答用户体验和 SLO,适合微服务入口。
5.2 USE 面向资源
对每个资源检查:
Utilization:资源忙碌比例/使用量
Saturation:需求超过即时能力形成的队列/压力
Errors:硬件/内核/驱动错误
资源包括 CPU、内存、磁盘、NIC、连接池、线程池、quota。CPU 40%但 run queue/CPU PSI高,可能是 cpuset只给少数核或 cgroup throttle;利用率必须和 saturation一起看。
5.3 从症状向下钻
RED:哪类请求慢/错?
-> dependency trace:时间在哪个边?
-> USE:对应资源是否饱和/报错?
-> profile:哪个调用栈消耗/等待?
-> kernel tools:调度、锁、IO、网络哪层?
-> targeted logs:状态变化与具体错误?
这比“CPU、内存、磁盘 dashboard 从左到右扫一遍”更有因果方向。
5.4 告警应基于用户影响与预算
CPU 90%可能是高效满载,也可能没有用户影响;HTTP SLO快速 burn才是紧急。资源告警适合预测硬边界和解释根因,但 page应围绕可行动的服务影响。
多窗口 burn rate可同时发现快速大故障和慢性消耗,避免单一 5 分钟阈值抖动。每条告警应有 owner、runbook、去重和演练。
6. Linux 原生观测:先用已有计数器
6.1 /proc 和 /sys 是内核状态窗口
常见来源:
/proc/stat CPU、上下文切换、进程等累计
/proc/meminfo 内存分类
/proc/PID/status 进程状态、namespace、内存
/proc/PID/smaps_rollup 映射内存汇总
/proc/net/* 网络协议状态
/proc/pressure/* PSI
/sys/fs/cgroup/* cgroup resource/events
/sys/class/net/* 接口属性与统计
很多工具只是解析这些文件。优点是稳定、低成本、无需新探针;缺点是快照/累计聚合,通常没有具体调用栈或单次事件因果。
6.2 累计计数要做差分
grep -E 'ctxt|processes' /proc/stat
cat /proc/net/softnet_stat
启动以来数值大不表示当前异常。记录时间窗口增量,再除以时间或事件量;同时识别 counter wrap/reset、CPU hotplug和进程重启。
6.3 sysstat 工具建立时间线
vmstat 1
mpstat -P ALL 1
pidstat -u -w -d -p "$PID" 1
iostat -xz 1
sar -n DEV 1
它们适合快速确认 CPU、run queue、context switch、fault、块设备延迟、网络吞吐。采样间隔太长漏峰,太短本身增开销;%util=100 在现代多队列设备上也不能单独解释饱和,需结合 await、queue、IOPS和应用 latency。
6.4 基线比绝对“标准值”可靠
上下文切换 10 万/s 在一台高并发机器可能正常,在低流量服务可能异常。应保留同硬件/同版本/同流量的健康基线,比较部署前后与故障前后,而不是套互联网固定阈值。
7. strace/ptrace:看 syscall 的强工具与扰动
7.1 strace 回答用户态与内核边界
sudo strace -ff -ttT -p "$PID" \
-e trace=network,file,desc
可以看到:
- 调了什么 syscall、参数和返回 errno
- 阻塞多久
- 哪个线程反复 EAGAIN
- 文件/地址是否错误
- 进程是否陷入 open/retry loop
-f 跟踪 fork/clone,-T 显示 syscall耗时,-tt 增加时间戳,-c 汇总。
7.2 ptrace 会改变调度
strace常基于 ptrace/seccomp辅助等机制,syscall entry/exit需要 tracer参与,可显著增加延迟和上下文切换。高频服务全量跟踪可能把轻微故障变成严重故障,也让竞态消失或出现。
生产使用原则:
先 -c/过滤/短时
只跟目标 PID/线程/syscall
设置 timeout
避免打印大 buffer/string
记录观测开始结束与影响
7.3 syscall 时间不等于内核子系统时间
一次 read 100ms可能是线程睡眠等网络数据,不表示内核执行指令 100ms。strace显示 wall duration,不区分 runnable等待、IO、锁或信号。需结合 perf sched、off-CPU profile、网络/IO tracepoint。
7.4 权限与敏感数据
syscall参数可能包含文件名、网络地址、命令参数和 buffer片段。ptrace权限受 UID、Yama、capability、namespace、dumpable状态控制。生产采集要授权并保护输出,不应为方便永久放宽 ptrace_scope。
8. perf:硬件计数器、采样与调用栈
8.1 perf stat 先回答 CPU 在做什么
perf stat -p "$PID" -- sleep 10
常见指标:
cycles
instructions
branches / branch-misses
cache-references / cache-misses
context-switches
cpu-migrations
page-faults
IPC = instructions/cycle 可作线索:低 IPC可能是内存 stall、branch miss,也可能是 workload本身。虚拟化、频率、混合 CPU、counter multiplexing会影响解释。
8.2 perf record 用采样近似热点
sudo perf record -F 99 -g -p "$PID" -- sleep 30
sudo perf report
每秒约 99 次采样,记录当前 instruction pointer/调用栈;某函数占 30% samples,近似它占 30% on-CPU时间。采样避免每次函数事件都记录,开销可控,但短暂稀有事件可能没命中。
-g 的栈质量取决于 frame pointer、DWARF、ORC、JIT symbol和构建参数。Go 二进制通常有符号,但内联、runtime和版本会影响展示。
8.3 frequency 与 period
频率模式按目标 samples/s动态调整;period模式每 N 个事件采样。频率越高分辨率越好、开销越大。跨机器比较要记录配置、lost samples和counter可用性。
8.4 on-CPU 与 off-CPU
普通 CPU profile只采正在运行的线程,看不到“为什么睡了 900ms”。off-CPU分析在 schedule-out/in记录等待时长与阻塞栈,可发现锁、futex、IO、poll、sleep。
on-CPU profile:CPU 时间花在哪
off-CPU profile:线程未运行的 wall time 从哪里阻塞
两者相加仍需注意 runnable但没获得 CPU、cgroup throttle等状态分类。
8.5 perf 权限
kernel.perf_event_paranoid、kptr限制、capability和容器策略控制访问。硬件 PMU是共享资源,也曾涉及侧信道风险。应使用最小权限观测 agent,不要给业务容器通用特权。
9. ftrace、tracepoint、kprobe 与 uprobe
9.1 ftrace 最初为函数追踪而生
tracefs 常挂载于:
/sys/kernel/tracing
可配置 function/function_graph tracer、events、filters和trace buffer。它适合内核开发与短时现场调查,但错误地开启全内核函数图会产生巨大开销。
sudo ls /sys/kernel/tracing/events
sudo cat /sys/kernel/tracing/available_tracers
9.2 tracepoint 是相对稳定的显式 hook
内核子系统在重要事件定义 tracepoint:
sched:sched_switch
sched:sched_wakeup
syscalls:sys_enter_*
block:block_rq_issue
block:block_rq_complete
net/netif events
其格式可从 tracefs 查看。相比 kprobe探内部函数,tracepoint是内核维护的观测 ABI意图,字段仍可能演进但通常更稳定。
9.3 kprobe 动态挂内核函数
kprobe/kretprobe可在许多内核函数入口/返回附加处理,无需预先 tracepoint,覆盖广;但函数名、签名、内联和内部结构随内核版本变化。优化/安全补丁后脚本可能失效或更危险地读错字段。
优先级通常是:
已有高层 metric/counter
-> stable tracepoint
-> BTF-enabled typed tracing/fentry
-> kprobe as compatibility/fallback
9.4 uprobe 观察用户态二进制
uprobe/uretprobe可挂 ELF 文件某符号/偏移,观察库函数或应用函数。PIE、ASLR、内联、strip、不同构建 ID和 Go ABI会使定位复杂。按路径挂 probe还要确认实际进程映射的是哪个 inode/version。
应用已有 OpenTelemetry或语言 profiler时,优先使用语义稳定的 instrumentation;uprobe适合无法修改进程的临时调查。
9.5 tracepoint 事件也会淹没消费者
sched_switch、syscall等每秒可数百万。trace buffer有界,消费者慢会 overwrite/drop。必须按 PID/cgroup/CPU/event过滤、内核侧聚合或采样;“成功 attach”不表示数据完整。
10. eBPF 到底是什么
10.1 受限制程序在内核 hook 上运行
eBPF 是 Linux 内核中的指令集、验证器、运行时与多类 hook机制。用户态 loader 将程序加载,内核 verifier检查,通过后解释执行或 JIT为本机指令,再附加到事件点:
userspace loader
-> bpf(BPF_PROG_LOAD)
-> verifier proves allowed properties
-> interpreter/JIT
-> attach to tracepoint/kprobe/fentry/XDP/tc/cgroup/LSM/...
-> event occurs
-> BPF program runs in kernel context
它不仅用于观测,也用于网络、security policy、scheduler扩展等。本文聚焦只读/聚合观测路径。
10.2 eBPF 与内核模块不同
内核模块是原生内核代码,错误可任意破坏内核;BPF 程序受 program type、helper白名单、verifier和 attach权限限制,不能随意调用内核函数或访问任意内存。
这降低风险,不等于无风险:verifier/JIT/helper本身可能有漏洞;合法 BPF也能消耗 CPU/内存、泄露敏感数据或影响网络。许多环境限制 unprivileged BPF,只允许受信任 agent加载。
10.3 program type 决定上下文与能力
示例:
| 类型/attach | 上下文 | 常见用途 |
|---|---|---|
| tracepoint/raw tracepoint | 内核事件字段 | 调度、syscall、block trace |
| kprobe/fentry | 内核函数 | 深入内部路径 |
| uprobe | 用户函数 | 无侵入应用观察 |
| perf event | PMU/软件采样 | stack/profile |
| XDP | 驱动早期 RX | 高速过滤/统计 |
| tc | qdisc ingress/egress | 网络分类/改写/观测 |
| cgroup socket hooks | cgroup网络事件 | 容器网络策略/统计 |
| BPF LSM | security hooks | 安全审计/策略 |
同一 helper并非所有 type可用,因为执行上下文是否可睡眠、是否有 skb、是否允许修改数据不同。
10.4 helper 是受控系统调用面
BPF 程序通过 helper/kfunc执行 map lookup、取时间、读取受保护内存、输出事件、获取 PID/cgroup等。verifier根据参数类型追踪指针合法性。
BPF 程序不能直接使用 libc,也不能任意阻塞。执行事件热路径时必须短小有界。
11. Verifier:为什么能进内核,也为什么经常拒绝
11.1 verifier 做静态状态探索
它分析控制流和每条路径,追踪寄存器/栈类型与范围,证明:
- 无不可达或非法指令
- 所有路径在允许范围内终止
- 内存访问边界安全
- 指针来源和偏移合法
- helper参数和 program type匹配
- map value/null检查正确
- 栈和复杂度在限制内
通过后意味着相对于 verifier模型满足这些安全条件,不意味着业务逻辑正确或开销小。
11.2 典型 null check
u32 key = 0;
u64 *value = bpf_map_lookup_elem(&counts, &key);
if (!value)
return 0;
__sync_fetch_and_add(value, 1);
lookup可返回 null;若未经分支检查就解引用,verifier拒绝。它会沿分支细化 value 类型。
11.3 循环必须可证明有界
早期 BPF几乎禁止循环;现代内核允许 bounded loop、BPF loop helper等,但 verifier必须证明上界或使用受控机制。按用户输入长度无界扫描字符串会被拒绝或造成巨大状态空间。
#pragma unroll
for (int i = 0; i < 16; i++) {
// fixed upper bound
}
即使理论有界,复杂分支也可能触发 verifier complexity limit。应缩小状态、拆 tail call/subprogram、用 helper和固定边界。
11.4 verifier log 是编译错误的一部分
加载失败会返回 EINVAL/EACCES 等,真正原因在 verifier log:
invalid mem access 'scalar'
R1 type=scalar expected=ctx
math between map_value pointer and unbounded register
libbpf/bpftool可输出日志。不要用反复加权限掩盖 verifier错误;权限失败和程序证明失败是不同问题。
11.5 verifier 不能证明什么
它不保证:
- metric定义正确
- 事件不会漏
- map key基数有界
- 每事件程序足够快
- 用户态 consumer跟得上
- attach点跨内核语义稳定
- 没有隐私泄露
- 多 CPU 更新符合你想要的一致性
“verifier通过”是内核安全准入,不是生产质量认证。
12. Maps:BPF 程序的状态与用户态接口
12.1 为什么程序需要 map
BPF 程序栈很小、事件执行短暂。map保存跨事件状态并让用户态读写:
hook event
-> key = PID/cgroup/stack/device
-> map lookup/update aggregate
userspace periodically reads map
-> exports histogram/top stacks
常见类型:hash、array、per-CPU hash/array、LRU hash、stack trace、ring buffer、perf event array、queue/stack、map-in-map。
12.2 per-CPU map 避免热锁
全局 counter被所有 CPU原子更新会 cache line bouncing。per-CPU map为每 CPU保存 value:
CPU0 count 100
CPU1 count 120
CPU2 count 80
userspace sum = 300
更新快,但读取要聚合;CPU hotplug、内存占用和瞬时一致性要考虑。适合统计,不适合要求每次更新全局严格顺序的状态机。
12.3 hash map 的 cardinality 仍需限制
按 PID、IP、path或完整 stack建 key,条目可快速增长。普通 hash达到 max_entries后更新失败;LRU会淘汰旧项,却让计数变成近似且可能偏向热点。
程序要统计 update failure/eviction,并设计 key规范化、top-K、sketch或采样。BPF map内存会被锁定/记账并受资源限制,不能当无限内核数据库。
12.4 map pinning 延长生命周期
map/program可 pin 到 bpffs:
/sys/fs/bpf/...
这样 loader退出后对象仍有引用,多个进程共享状态或平滑升级。代价是 stale pinned对象、schema/version不匹配和权限管理。agent启动时应核对 map type/key/value/BTF schema,不要盲目复用同名路径。
12.5 并发与一致性
BPF map API提供各类型定义的原子性,不代表多个字段复合更新是事务。读者可能看到部分时间点数据;可使用 per-CPU、spin lock(允许的 map value/program type)、sequence/generation或双 buffer设计。
观测统计通常接受最终一致;不要为“精确”在热路径加全局锁,反而改变系统。
13. 从内核把事件送回用户态
13.1 每事件输出与内核聚合
两个方案:
A. 每个 syscall 输出一条事件
信息细,但高频时 CPU/带宽/丢失巨大
B. 内核 map 按 key累计 count/sum/histogram
信息压缩,开销小,但失去单事件细节
默认应聚合。只对错误、慢事件或采样事件输出详情。
13.2 perf buffer
传统 BPF perf event array按 CPU提供 buffer;程序输出 sample,用户态 poll各 CPU ring。它成熟广泛,但事件跨 CPU全局排序需用户态按 timestamp处理,buffer配置和CPU映射较繁琐。
13.3 BPF ring buffer
BPF ringbuf提供跨 CPU共享 ring,可保留 reservation提交顺序并改善内存利用:
struct event *e = bpf_ringbuf_reserve(&events, sizeof(*e), 0);
if (!e) {
// increment drop counter
return 0;
}
e->pid = ...;
e->ts = bpf_ktime_get_ns();
bpf_ringbuf_submit(e, 0);
reserve失败必须记 drop;用户态消费慢时不能阻塞内核热路径等待。ring大小吸收短 burst,无法解决持续生产大于消费。
13.4 变长事件与隐私
捕获命令行、路径、DNS、payload容易产生变长数据、截断和敏感信息。最好只采固定上限和必要字段,标记 truncated;网络 payload/SQL全文默认不采。内核侧过滤后数据一旦送出,权限和保留同样重要。
13.5 丢事件是数据的一部分
每个 pipeline都应暴露:
reserve/output failures
kernel buffer overruns
userspace decode errors
queue drops
export failures
sampling probability
attach/detach state
报告结果时写“在 drop rate 0.2%、1:100采样下观察到”,而不是假装完整事实。
14. BTF 与 CO-RE:跨内核版本的现实方案
14.1 内核内部结构会变
直接假定:
struct task_struct + fixed offset -> field
在另一个内核配置/版本可能读错。发行版 backport还让 release string相同也可能结构不同。过去 BCC常在目标机用 kernel headers即时编译,部署依赖重。
14.2 BTF 提供类型元数据
BTF(BPF Type Format)描述内核/程序类型、字段和函数信息,常由 /sys/kernel/btf/vmlinux 暴露。loader可据此做重定位、pretty print和typed attach。
ls -l /sys/kernel/btf/vmlinux
bpftool btf dump file /sys/kernel/btf/vmlinux format c | less
不是所有内核都启用完整 BTF,容器还需能访问宿主对应信息。
14.3 CO-RE 在加载时重定位
Compile Once – Run Everywhere:BPF object编译时保留字段访问重定位,libbpf在目标内核加载时用 BTF解析实际字段 offset/存在性:
compile against vmlinux.h
-> ELF + BTF + CO-RE relocations
load on target
-> compare target BTF
-> relocate field accesses
它解决结构布局差异,不保证 hook语义、字段含义或 helper/program type在所有内核都相同。程序仍需 feature detection与 fallback。
14.4 fentry/fexit
基于 BTF 的 fentry/fexit可直接附加到支持的内核函数入口/退出,参数类型明确,通常比 kprobe开销低、可靠性好。但函数是否存在、允许 tracing、发行版配置仍需探测。
14.5 feature probe 不要只看 uname
bpftool feature probe
bpftool prog help
bpftool map help
权限不足会让 feature probe看似“不支持”。agent应区分:内核未配置、权限策略禁止、BTF缺失、helper/type不支持、attach目标不存在,并选择 tracepoint/kprobe等fallback或明确禁用功能。
15. 一个观测程序的典型设计
目标:统计每个 cgroup 中超过 10ms 的 block IO,并按延迟 bucket聚合。
15.1 在 issue 时保存开始状态
key: request identity
value: timestamp, cgroup, device, bytes, op
在 block_rq_issue 写 map;在 block_rq_complete lookup并计算 delta,随后 delete。
15.2 在 complete 时聚合
delta = complete_ts - issue_ts
bucket = log2(delta_us)
key = cgroup_id + device + op + bucket
per-CPU counter++
只对 delta > 10ms 的少量事件可选输出 ring buffer,携带 stack/request metadata。
15.3 必须处理生命周期异常
- issue没看到,complete才 attach:lookup miss
- map满:issue insert失败
- request identity复用/merge/requeue:语义需按 tracepoint定义
- detach时仍有 in-flight entry
- cgroup删除/ID复用或路径解析延迟
- 用户态读 map期间数据继续更新
- buffer丢慢事件
观测工具若不暴露这些 uncertainty,会给出精确数字的错觉。
15.4 输出 schema
window_start/end
kernel boot ID
agent version / BPF object build ID
attach points
filter scope (cgroup/PID/device)
sampling config
map insert miss / ring drop
histogram counts
slow-event samples
可复现性与数据本身同样重要。
16. 调度观测:慢请求为何没获得 CPU
16.1 三种“不在 CPU 上”
sleeping:等待 IO/futex/timer,当前不可运行
runnable:已经可运行,但在 run queue等 CPU
throttled:cgroup quota耗尽,即使 CPU空闲也受限
只看 wall time或 goroutine blocked不能区分。
16.2 run queue latency
在 sched_wakeup 记录 task变为 runnable的时间,在 sched_switch 真正切入时计算:
runqlat = on_cpu_ts - wakeup_ts
按 PID/cgroup/CPU bucket聚合。高 runqlat可能来自 CPU饱和、affinity/cpuset、优先级、softirq或 quota;需结合 cpu.stat/PSI。
16.3 off-CPU stack
schedule-out时记录用户/内核栈和 timestamp,schedule-in时累计 duration:
futex wait stack -> 40% off-CPU
epoll wait stack -> 35% (possibly healthy idle)
disk read stack -> 20%
epoll_wait占比高对空闲事件循环是健康,不应“优化掉”。profile必须限定故障请求/繁忙窗口并理解栈语义。
16.4 waker/wakee 因果
仅知道线程等 100ms,还可追谁唤醒它:网络 softirq、锁释放者、timer或另一个 goroutine所在 M。完整因果追踪成本高,可只对长等待采样并关联 task ID、cgroup与 stack。
16.5 Go 调度与 Linux 调度是两层
Go G runnable -> 等 P/M(runtime run queue)
M runnable -> 等 Linux CPU(kernel run queue)
cgroup -> 可能 throttle整个进程
Go trace看到 G调度延迟,不自动知道 M为何没获得 CPU;eBPF/perf看到 OS thread,不自动知道其当前 G。通过 thread ID、runtime labels/uprobes和时间对齐可关联,但版本/开销复杂。通常先分别证明哪一层,再做深度关联。
17. 网络观测:包、socket 与请求三种层级
17.1 接口 counter
ip -s link
ethtool -S eth0
nstat -az
ss -s
低成本回答包/字节、driver drop、retrans/listen overflow等,适合持续监控。
17.2 包级 capture
tcpdump/AF_PACKET观察 hook位置的 packet:
sudo tcpdump -ni eth0 'host 203.0.113.20 and tcp port 443'
它能看握手、重传、window、RST,但 TLS payload不可见,GRO/TSO/checksum offload会改变主机观察形态。高流量全包 capture开销和隐私风险大。
17.3 socket级 eBPF
可在 TCP state、retransmit、accept/connect、send/recv相关 tracepoint/kprobe上按 cgroup/socket cookie聚合:
connect latency histogram
TCP RTT/retrans by destination prefix
accept queue drops
socket lifetime and bytes
zero-window duration
socket cookie比 fd稳定,因为 fd是进程局部且复用;cookie本身也有生命周期,关联用户 request仍需应用 context。
17.4 HTTP/L7 自动观测的边界
BPF可通过 uprobes、socket buffer或 TLS library hooks尝试重建 HTTP,但面临:
- TLS加密,内核只看密文
- HTTP/2多路复用,一条连接多 stream
- user-space networking/QUIC
- library版本/静态链接/Go ABI变化
- 分段与partial IO
- payload隐私和高成本
因此“零代码自动全链路 tracing”需要明确支持矩阵和fallback,不能承诺所有语言/协议透明准确。
17.5 XDP 统计的位置
XDP在驱动早期、通常 skb构造前运行,适合高 pps drop/redirect和轻量统计。它看到的是非常早期包:后续 firewall、routing、TCP socket和应用结果尚未知。XDP drop计数不能直接等同应用拒绝;不同层 metric需标注 hook位置。
18. IO 与文件系统观测
18.1 应用延迟与设备延迟不是一回事
read syscall
-> page cache hit:无设备 IO
-> page fault/readahead
-> filesystem
-> block queue
-> device
iostat await只覆盖进入设备的请求;应用可能卡 inode lock、page cache reclaim、journal、cgroup IO throttle。反过来设备忙,应用读若全命中 cache也未受影响。
18.2 block tracepoint
issue/complete可计算设备服务/队列相关延迟,按 device/op/bytes/cgroup聚合。merge会让应用 IO与block request不是一一对应;NVMe并行下“队列深”未必坏。
18.3 VFS/filesystem probes
可观测 open/read/write/fsync、writeback、reclaim、ext4/xfs tracepoint。但 kprobe内部 VFS函数受内核版本影响,先使用稳定 tracepoint和应用 syscall metric。
18.4 fsync 延迟要关联业务提交
数据库 commit latency可能包括 group commit等待、WAL lock、write、filesystem journal和device flush。只见 block flush 5ms不能解释事务 100ms;trace需要从应用 span到 syscall/block时间窗逐层对齐。
19. Go 服务的观测基线
19.1 Runtime metrics
Go 应持续导出有限集合:
goroutines
GOMAXPROCS
heap live / goal / released
allocation bytes/objects rate
GC cycles, pause, assist CPU
mutex/block profile samples(按需)
process CPU/RSS/fd/thread
runtime/metrics 的 key和含义随 Go版本演进,collector要标记 runtime version。不要把全部 runtime metric无筛选暴露为长期高成本 dashboard。
19.2 pprof 要保护
/debug/pprof/profile
/debug/pprof/heap
/debug/pprof/goroutine
/debug/pprof/trace
它们可能暴露函数名、路径、参数/内存线索并消耗 CPU;execution trace尤其可产生较大数据。应绑定管理网络/localhost、认证授权、限时采集,绝不无保护暴露公网。
curl -o cpu.pprof 'http://127.0.0.1:6060/debug/pprof/profile?seconds=30'
go tool pprof -http=:8081 cpu.pprof
19.3 Mutex/block profile 有采样率
开启:
runtime.SetMutexProfileFraction(10)
runtime.SetBlockProfileRate(1_000_000)
更高采样提高可见性也增加原子/栈采集开销。线上应按事故窗口动态开启、有自动关闭和记录配置。profile显示累计 contention,不同时间窗/采样率不能直接比较原始 count。
19.4 execution trace 分辨 Go 调度
Go trace可看 goroutine creation/runnable/running、GC、syscall、network block、processor状态,适合短时间精细分析:
curl -o trace.out 'http://127.0.0.1:6060/debug/pprof/trace?seconds=5'
go tool trace trace.out
5 秒在高并发程序也可能很大;trace本身有开销。结合 Linux cpu.stat、perf/eBPF调度数据区分 runtime与内核等待。
19.5 trace context 与 log关联
在请求入口生成/接收 trace context,把 trace_id/span_id 写入结构化 error log,不应放 metric label。profile可用时间、版本、instance、cgroup关联;若使用 runtime labels,要评估 profile cardinality和开销。
19.6 版本与部署维度
每个信号应至少能按 service、environment、region、version/build、instance/cgroup聚合。部署 marker是最便宜的因果线索之一:
P99 上升时间 = version rollout 30%
只有新 build 的 alloc profile增长
rollback 后恢复
但相关不等于因果,还要检查流量迁移、节点类型和缓存冷启动。
20. 采样:成本与偏差的明确交换
20.1 Head sampling
请求开始时随机决定是否采整条 trace:
sample probability = 1%
简单、一致、开销可控;但稀有错误如果总体很少可能完全漏掉。可按入口/租户/operation调整,仍须避免用户可操控采样制造成本。
20.2 Tail sampling
先暂存 span,trace结束后依据结果/延迟决定:
保留所有 error
保留 duration > 1s
正常请求随机 0.1%
更容易保留异常,但 collector需缓存完整 trace、处理迟到/缺失 span,成本和故障面更大。压力时 buffer drop可能恰好丢最慢 trace。
20.3 采样后不能直接数事件
若每个请求以概率 p独立采样,可用权重 1/p估计总量,但 adaptive/tail sampling让概率依赖结果;查询必须知道策略。错误 trace全部保留、正常只留0.1%,不能拿样本中错误占比当真实错误率。错误率应来自无偏 counter。
20.4 Coordinated omission
负载生成器若每个响应结束才发下一请求,系统卡住时也停止发压,最差排队延迟不被测到,称 coordinated omission。生产 histogram也可能只记录成功完成请求,超时/丢弃没有 duration。
应分别统计 timeout/reject、arrival与completion,并让压测按预定到达率发送或使用修正方法。
20.5 Observer effect
开启 debug logging、1000Hz profile、全 syscall trace或大 BPF event可能改变锁、缓存、调度和网络。每次采集记录:
tool/version/config
scope/filter
start/end
CPU/memory/output overhead
dropped/lost
先 staging压测开销,生产逐级升采样,必要时设 kill switch。
21. 时间:没有共同时间轴就没有因果
21.1 realtime 与 monotonic
- wall/realtime:用于跨机器日历时间,可被 NTP step/人工调整
- monotonic:从启动起单调递增,适合本机 duration
- boot time:可能包含 suspend语义差异
- TSC/硬件 cycle:高精度但换算/同步依平台
BPF常用 bpf_ktime_get_ns() 得到单调时间;应用日志使用 wall clock。关联时需要采集两者映射或由 agent统一转换,并记录 boot ID。
21.2 NTP 不保证完全相等
机器间几十毫秒偏差就可能让“响应早于请求”。分布式 trace的父子关系提供逻辑因果,duration应由本机 monotonic测量;跨主机 timestamp仍需时间同步监控。
21.3 时钟回拨与 histogram
用 wall time计算 duration可能得到负数/巨峰。Go time.Time 在同进程常携带 monotonic component,但序列化后通常只剩 wall time。RPC传递绝对 deadline要考虑时钟语义,常传 timeout budget并在每 hop递减。
21.4 boot ID 与 PID复用
内核 monotonic timestamp、PID、socket cookie等只在一定生命周期内唯一。事件 schema加入 machine ID/boot ID、PID namespace/cgroup、process start time,防止重启后误关联同 PID。
22. eBPF 安全、权限与供应链
22.1 加载 BPF 是高权限操作
现代内核把能力拆为 CAP_BPF、CAP_PERFMON 等,但具体 attach仍可能需要 CAP_NET_ADMIN、CAP_SYS_ADMIN或 LSM许可,取决于内核/程序类型。容器内加载还受 user namespace、seccomp、bpffs/BTF挂载影响。
不要给每个业务容器 privileged以便观测。常见架构是节点级受信任 agent,用 cgroup/container metadata限定范围,并通过受认证控制面提供结果。
22.2 unprivileged BPF
许多发行版禁用或限制非特权 BPF,因为 verifier/JIT攻击面和侧信道风险。检查:
sysctl kernel.unprivileged_bpf_disabled
不要为了运行一个工具在生产临时全局放开;使用受审计 agent、最小 capability或离线环境。
22.3 BPF object也是供应链代码
预编译 .o、container image、loader和脚本都能在高权限 agent中运行。需要:
- 固定来源、签名/哈希与版本
- review BPF与用户态代码
- 最小 map/program权限
- 记录 attach目标和变更
- 可回滚/自动 detach
- 与内核支持矩阵测试
“eBPF受 verifier保护”不能保证恶意合法程序不采集 secret或丢包。
22.4 bpffs 与 map权限
pinned map/program是内核对象入口。错误 filesystem permission可能让其他进程读取敏感事件、修改策略 map或替换 link。将 bpffs目录按 agent隔离,避免业务用户可写。
22.5 观测与执行分离
只读统计程序尽量不使用能 override return、redirect/drop packet或写内核状态的 attach/helper。平台应区分 observability profile与 enforcement profile,审批和 blast radius不同。
23. 常用 eBPF 工具与使用边界
23.1 bpftrace
一行脚本适合交互调查:
sudo bpftrace -e '
tracepoint:syscalls:sys_enter_openat
/comm == "myserver"/
{ @[comm] = count(); }
interval:s:10 { print(@); clear(@); }'
优点是快速;缺点是生产脚本版本、BTF/tracepoint兼容、map基数和开销容易失控。脚本也需要代码 review和 timeout。
23.2 BCC tools
BCC提供 execsnoop、opensnoop、biolatency、runqlat、offcputime、tcpconnect等经典工具,常在目标机编译 BPF C,依赖 headers/LLVM/Python环境。发行版打包能降低门槛;CO-RE工具更适合轻量部署。
23.3 libbpf/bpftool
libbpf负责 BPF ELF、CO-RE relocation、map/program/link生命周期;bpftool用于 feature、prog/map/link/net/btf查看:
sudo bpftool prog list
sudo bpftool map list
sudo bpftool link list
sudo bpftool feature probe
列表可能包含安全/网络策略程序,不要为“清理测试”随意 detach或删除 pinned对象。
23.4 先问现成工具能否回答
CPU hotspot -> perf/pprof
syscall错误 -> strace过滤
IO latency distribution -> biolatency/tracepoint
run queue latency -> runqlat
TCP connect/retrans -> tcpconnect/tcpretrans
文件打开 -> opensnoop
使用成熟工具比临时写 probe更不易犯语义/丢失错误。只有现成工具无法表达 filter/aggregation时再开发自定义程序。
24. 一次完整调查:P99 周期性跳到 100ms
24.1 症状
每 100ms 附近出现延迟台阶
平均 CPU 只有 2 cores
错误率低
数据库 trace正常
Go CPU profile无单一热点
24.2 从 metrics 建假设
- HTTP histogram在 80–110ms出现峰
- cgroup
nr_throttled/throttled_usec同期增长 - CPU PSI上升
- runnable goroutine增加
怀疑 CPU quota period throttling,而非数据库。
24.3 用 Go trace与调度观测验证
Go trace看到大量 G runnable却迟迟未运行;eBPF runqlat按 cgroup聚合显示 wakeup到on-CPU延迟;但普通 runqlat可能把 quota等待混在 run queue。读取 cpu.max/cpu.stat确认 8 个 M并行快速耗尽 quota。
cpu.max = 200000 100000
GOMAXPROCS = 32
traffic burst -> 8+ threads parallel
25ms左右耗尽200ms CPU budget
等待下个100ms period
24.4 修复与验证
可能动作:
- 按实际 quota调整 GOMAXPROCS/并行 worker
- 减少 burst分配/GC CPU
- 提高或移除不合理 quota
- 用 CPU weight而非硬 quota(若容量模型允许)
- 限制入口并发,平滑工作
对照实验观察 P99、throughput、throttle、runqlat、CPU成本,而不是只看到 throttled次数下降就宣布成功。
24.5 证据链的价值
metric发现周期与范围
trace排除主要下游
Go trace证明 G runnable
cgroup counter证明 throttle
BPF scheduler数据证明 OS执行延迟
配置解释100ms周期
实验验证因果
每个工具回答一个问题,没有一个“万能 eBPF dashboard”直接给根因。
25. 建设可观测平台的原则
25.1 默认低成本,按需提高分辨率
always on:RED/USE metrics、错误日志、低采样 trace/profile
incident:提高特定 service/route采样
focused:短时 BPF/perf/trace capture
postmortem:保留聚合、关键样本和配置,不保留无界原始流
动态控制必须有权限、审计、自动过期和资源上限。
25.2 统一资源身份
metric、log、trace、profile和BPF事件应共享稳定资源维度:service、namespace、pod/container、host、cgroup ID、version、region。PID和IP会复用/变化,不适合作为唯一长期身份。
25.3 Schema治理
- metric name/unit/type稳定
- label白名单与基数预算
- log字段类型和敏感分类
- trace semantic convention
- profile build ID/symbol保留
- BPF event version和drop字段
没有 schema治理,团队会出现 latency_ms 实际单位秒、同名 counter语义不同、dashboard悄悄合并错误数据。
25.4 保留原始证据的平衡
长期保存每个 event昂贵且有隐私风险。可保留:长期聚合趋势、中期错误/慢 trace、短期高分辨率滚动 buffer。事故触发时冻结前后窗口,类似 flight recorder。
25.5 观测系统也要降级
collector/storage故障时,业务不能因为同步发 telemetry而阻塞。客户端队列有界,满时 drop低优先级数据并计数;critical audit采用独立可靠通道。日志写磁盘也需容量限制,防止观测填满业务盘。
26. 线上排查命令
26.1 先建立一分钟系统快照
date -Ins
uptime
vmstat 1 10
mpstat -P ALL 1 10
pidstat -u -w -d -p "$PID" 1 10
iostat -xz 1 10
ss -s
cat /proc/pressure/cpu
cat /proc/pressure/memory
cat /proc/pressure/io
记录命令运行的 namespace、host、PID/cgroup和时间。容器内工具可能看的是 namespace视图或缺少权限;节点视图与容器视图都要标注。
26.2 CPU profile
sudo perf stat -p "$PID" -- sleep 10
sudo perf record -F 99 -g -p "$PID" -- sleep 30
sudo perf report
Go已有 pprof时优先语言 profile,内核/混合栈再用 perf。生产采样先评估 perf_event_paranoid和开销。
26.3 syscall调查
sudo timeout 10s strace -ff -c -p "$PID"
sudo timeout 10s strace -ff -ttT -p "$PID" \
-e trace=connect,accept4,read,write,fsync,futex
timeout 是安全边界,过滤输出;不要在超高 syscall rate下直接全量附加。
26.4 BPF对象状态
sudo bpftool feature probe
sudo bpftool prog list
sudo bpftool map list
sudo bpftool link list
mount | grep -E 'tracefs|bpf'
只读列表也可能需要权限。不要删除不认识的程序/map/link,它可能属于 CNI、防火墙或安全 agent。
26.5 Tracepoint存在性
sudo perf list 'sched:*'
sudo ls /sys/kernel/tracing/events/sched
sudo cat /sys/kernel/tracing/events/sched/sched_switch/format
字段以本机 format/BTF为准,脚本不要假定所有发行版相同。
27. 常用命令知识点扩展
| 命令/短参 | 英文全称 | 作用 |
|---|---|---|
perf stat |
statistics | 统计 PMU/软件事件 |
perf record |
record samples | 记录采样数据 |
perf -F |
frequency | 指定采样频率 |
perf -g |
call graph | 采集调用栈 |
strace -c |
summary | 汇总 syscall次数/时间 |
strace -f |
follow forks | 跟踪线程/子进程 |
strace -T |
syscall times | 显示 syscall耗时 |
tcpdump -n |
numeric | 不解析地址/端口名称 |
tcpdump -i |
interface | 选择接口 |
bpftool prog |
BPF programs | 查看/管理 BPF程序 |
bpftool map |
BPF maps | 查看/管理 map |
bpftool btf |
BPF Type Format | 检查 BTF类型信息 |
场景配方:
# 哪些进程在频繁 exec(工具存在且授权时)
sudo timeout 30s execsnoop
# 块 IO 延迟分布
sudo timeout 30s biolatency
# run queue 延迟分布
sudo timeout 30s runqlat
# 查看 Go 进程线程与调度
pidstat -u -w -t -p "$PID" 1
# 当前机器是否有 vmlinux BTF
stat /sys/kernel/btf/vmlinux
BCC工具参数随版本变化,执行前用 -h;工具名存在不表示当前 kernel hook/headers/BTF兼容。
28. 面试题
Q:Metrics、Logs、Traces、Profiles 分别最适合回答什么?
Metric用聚合时间序列回答何时、范围、趋势和SLO;log记录离散状态与错误上下文;trace串起一次请求跨组件的因果阶段;profile按调用栈聚合CPU、内存或等待。它们压缩维度不同,应由低成本信号定位范围,再用高分辨率信号验证假设。
Q:为什么不能对多个实例的 P99 求平均?
quantile不是可加统计量。每个实例流量和分布不同,平均P99既不等于合并全部请求后的P99,也可能掩盖大实例尾延迟。应合并可聚合histogram bucket后计算,或在支持的原生histogram上查询全局分位数。
Q:高基数为什么危险?
每个label组合会创建独立series/index key。把user ID、request ID、原始URL等放label会让内存、WAL、网络、索引和查询成本近乎无界。metric只用有界稳定维度;高基数上下文放受采样的trace/log并建立budget。
Q:RED 与 USE 方法有什么区别?
RED从请求服务看Rate、Errors、Duration,反映用户体验;USE对资源看Utilization、Saturation、Errors,定位容量/硬件层。通常先用RED发现哪类请求受影响,再沿依赖和USE检查CPU、内存、IO、网络、连接池等资源。
Q:strace 显示 read 用了 100ms,是否表示内核执行 read 代码用了 100ms?
不一定。它显示syscall入口到返回的wall time,线程可能在socket wait queue睡眠、等待IO/信号或调度。要区分on-CPU、sleeping、runnable和throttled,需结合perf/BPF off-CPU、sched trace、cgroup与资源指标。
Q:perf采样为何不是精确执行计数?
它按时间或硬件事件抽样instruction pointer,样本占比统计近似CPU时间;短函数/稀有事件可能漏采,stack unwinding、频率、PMU multiplex和符号影响结果。提高频率降低随机误差却增加开销,报告应包含配置和lost samples。
Q:tracepoint 与 kprobe 如何选择?
优先使用内核显式维护、语义相对稳定的tracepoint;没有合适事件再考虑BTF fentry/fexit或kprobe。kprobe可覆盖内部函数但函数名、签名、内联和结构随版本变化,需支持矩阵和fallback。
Q:eBPF verifier 保证什么?
它静态探索程序路径,验证终止性、内存边界、指针类型、helper参数、栈和复杂度等,阻止不符合执行模型的程序进入内核。它不保证统计语义正确、开销低、map基数有界、事件不丢或没有隐私泄露。
Q:为什么 eBPF 程序不能简单把每个事件都发到用户态?
syscall、调度、网络事件可达百万每秒,每事件复制会消耗CPU/内存带宽,ring满后丢失并扰动被测系统。应优先在per-CPU map聚合,只输出错误、慢事件或采样详情,并记录reserve/drop和sampling。
Q:per-CPU map 的优缺点是什么?
每CPU独立value避免跨核原子更新和cache line bouncing,适合高频counter/histogram;用户态要汇总所有CPU,内存按CPU倍增,读取不是全局瞬时事务,CPU hotplug也需处理。它优化统计,不适合全局严格顺序状态。
Q:BTF 与 CO-RE 解决什么?
BTF提供目标内核类型/字段元数据;CO-RE把BPF object中的字段访问在加载时按目标BTF重定位,使一次编译适配结构布局变化。它不保证函数/hook存在、helper可用或语义不变,仍需feature detection和fallback。
Q:BPF ring buffer 满了会怎样?
reserve失败,程序不能在内核热路径阻塞等消费者,只能丢事件或降级。工具必须累计drop并暴露给用户。增大ring只能吸收短burst,持续生产速率大于消费速率仍会满,应过滤、聚合或采样。
Q:如何用调度事件区分任务睡眠和等CPU?
sched_wakeup标记任务变runnable,sched_switch切入时计算run queue latency;schedule-out/in duration结合阻塞栈得到off-CPU等待。再查cgroup cpu.stat识别quota throttle。sleeping、runnable、throttled都不在CPU,但根因不同。
Q:为什么 Go trace 与 Linux调度trace都需要?
Go调度器先在G-P-M层决定goroutine何时绑定OS thread;Linux再决定该thread何时上CPU,cgroup还可能throttle。Go trace能看到G runnable/GC/syscall,内核trace能看到M的run queue/CPU状态。任一层单独都可能把另一层等待当空白。
Q:Head sampling 与 tail sampling 的取舍?
head在请求开始决定,简单、低成本、传播一致,但可能漏稀有慢/错;tail先缓存trace,结束后保留error/slow,异常覆盖好但需要大缓存、处理迟到span,过载时可能丢数据。两者都需记录有效采样策略,不能用有偏样本算真实错误率。
Q:什么是 observer effect?
观测本身消耗CPU、锁、缓存、IO和网络,从而改变调度、延迟甚至竞态。全量strace/debug log/高频BPF输出尤其明显。应先低频聚合、窄过滤、短时采集,记录开销与drop并设置自动停止。
Q:为什么时间同步对观测重要?
跨机日志/span若wall clock偏差,会出现响应早于请求和错误因果;wall time还可回拨。duration应优先用本机monotonic,跨机靠trace逻辑关系和受监控NTP;BPF monotonic事件与应用wall日志关联需转换并带boot ID。
Q:为什么加载 eBPF 不应给业务容器 privileged?
加载/attach程序能观察敏感内核事件,某些类型还能改写/丢包;verifier只保证内存执行安全,不保证意图。privileged扩大设备、capability和宿主攻击面。应由受信任节点agent最小授权加载,按cgroup过滤并审计程序、map和输出。
Q:可观测平台故障时为什么不能阻塞业务?
若同步日志/trace exporter反压请求,telemetry后端故障会演变成业务故障。客户端应使用有界异步队列,满时按优先级丢弃并计数;审计等必须可靠的数据另走专用通道。观测系统自身要监控drop、lag和成本。
小结
- 可观测性是从系统输出推断内部状态的能力;telemetry经过采样、聚合、传输和存储,每层都可能丢失,采集系统也必须自监控
- metrics适合趋势/SLO,logs适合事件上下文,traces适合跨组件因果,profiles适合调用栈资源归因;没有单一万能信号
- counter要算rate并处理reset,gauge会漏短峰,histogram bucket决定分位误差;P99不能跨实例求平均
- 高基数是平台容量风险,metric label只用有界稳定维度,request/user/raw path放受控trace/log
- RED从请求体验定位症状,USE从资源利用、饱和、错误定位根因;告警应围绕SLO和可执行动作
/proc、sysstat提供低成本基线,strace回答syscall边界但扰动大,perf用采样找on-CPU热点,ftrace/tracepoint提供内核事件- eBPF程序经verifier后在特定hook执行,通过helper和map保存/输出状态;verifier保证准入安全属性,不保证业务正确、低开销或数据完整
- per-CPU map适合热路径聚合,ring buffer只输出有价值样本;map满、ring drop、decode/export失败都必须成为结果的一部分
- BTF/CO-RE解决内核结构布局适配,fentry提高typed tracing可靠性,但hook语义和feature仍需运行时探测
- 调度慢要区分sleeping、runnable和cgroup throttled;Go G调度与Linux M调度是两层,需要分别验证
- eBPF在网络/IO中能填补内核因果盲区,但hook位置决定语义,TLS、多路复用、offload和cache会限制“自动观测”
- 采样必须说明概率与偏差,tail sampling不能用于直接计算真实错误率;时钟、boot ID和资源身份决定跨信号关联是否可信
- 观测有observer effect与安全风险,应默认低成本、按需升分辨率、窄过滤、自动过期、最小权限并保护敏感数据
下一篇讲 线上排查实战案例集 —— 把 CPU quota、内存 OOM、磁盘 fsync、网络重传、DNS、连接池、锁竞争、fd 泄漏等症状放进完整事故时间线,用“现象—假设—证据—反证—修复—验证”的方式演示如何避免凭经验拍脑袋。
xingliuhua