目录

Linux-38 观测体系的设计与 eBPF:看见系统而不压垮系统

目录

上一篇讲 systemd 如何统一管理服务生命周期与日志。这一篇继续追问:服务已经运行,P99 突然升高,但 CPU 平均值正常、日志没有 error、重启后暂时恢复,怎样从“症状”走到“内核里真正发生了什么”?

可观测性不是安装一个 dashboard,也不是“把所有事件都记录下来”。系统每秒可能发生数百万次 syscall、调度、包处理和内存分配;无差别采集会改变被测对象、制造巨大成本,甚至成为新的故障。真正的设计目标是:用有界开销保留足够证据,在不知道故障答案之前仍能提出并验证假设。

先看五个问题:

  1. metric、log、trace、profile 分别压缩了什么信息,为什么任何一种都不能单独回答所有问题?
  2. /proc、sysstat、strace、perf、ftrace、tracepoint、kprobe 各自适合什么层级,观测会怎样扰动系统?
  3. eBPF 程序如何在内核 hook 上执行而不允许任意破坏内核?verifier 能证明什么、不能证明什么?
  4. map、per-CPU aggregate、ring buffer、BTF 与 CO-RE 如何让 BPF 程序跨事件保存状态并适应内核版本?
  5. 高基数、采样偏差、时间同步、丢事件和探针本身 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_BPFCAP_PERFMON 等,但具体 attach仍可能需要 CAP_NET_ADMINCAP_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 泄漏等症状放进完整事故时间线,用“现象—假设—证据—反证—修复—验证”的方式演示如何避免凭经验拍脑袋。