← 返回文章选择

可观测性笔记:从埋点到能处理的告警

先分清图上的箭头:三种信号、Trace 与 Span、选型条件,以及告警响了之后做什么。

监测平台那页把埋点、数据库、看板和告警都画在了一起。回看时最先要理清的是箭头:有的表示数据流,有的表示包含关系,还有的只是工具名的联想。照着画成一条链,读者会以为 Metrics 先变成日志,再变成 Trace。

三种信号描述同一次运行,粒度不同

信号 记录什么
指标 一段时间内的数值变化
日志 事件记录
Trace 跟着一次请求走,记录请求经过的操作

三者可以关联着看同一个系统的运行,只是粒度不同:指标是一段时间的数值,和单次请求不是一一对应。Trace 里的 Span 表示操作片段,同一条 Trace 的 Span 带着同一个标识,靠上下文建立关系。术语可以对照 OpenTelemetry 的信号说明。

官方那页其实列了四种信号,多一个 Baggage,定义是“Contextual information that is passed between signals”,在信号之间传递的上下文,本身不描述运行;Events 和 Profiles 还在开发或提案阶段。所以这里只记三种,是取主干。

“指标和单次请求不是一一对应”在 Metrics 页里有硬依据:导出的是一个时间窗内聚合后的统计量,官方原话是“intended to provide statistical information in aggregate”。指标的基数是属性组合的个数,SDK 默认每个指标流最多跟踪 2000 个组合,超出的测量会并进一个 overflow 数据点。Prometheus 的埋点建议更保守:每组 label 都是一条独立的时间序列,要吃内存、CPU、磁盘和网络,基数一般压在 10 以下,过了 100 就该换思路。往指标上加 user ID 这种维度,等于给每个用户开一条序列。所以指标粒度粗,是为了控制成本。

采集链路是另一种画法:不同信号并列进入处理链,Collector 后面的存储只是功能角色,不指定某个部署方案。

三种信号并列汇入 Collector,再供查询与告警使用

查看 Mermaid 源码
flowchart TD
app["应用埋点与 Instrumentation"] --> metrics["Metrics:指标"]
app --> logs["Logs:事件记录"]
app --> traces["Traces:调用轨迹"]
metrics --> collector["Collector:接收、处理与导出"]
logs --> collector
traces --> collector
collector --> backend["后端存储与查询"]
backend --> dashboard["看板与排查"]
backend --> alert["告警判断"]

这是一种经过 Collector 的示意链路;采用直接导出等方式时,要按实际路径调整。

Collector 本来就是可选的:开发或小规模环境直接发往后端也能有不错的效果,官方建议生产环境在服务旁边跑一个,让服务把数据快速卸下来,重试、批处理、加密交给它。配置时有个坑,组件配了但没写进 service.pipelines 就不会启用(见 Configuration 页)。图上画了箭头,配置里没接上,数据一样不走。

Trace 与 Span 单独画

一条调用链里的操作共享 Trace 上下文,Span 之间还能记录父子关系。下面是一个假设请求:请求 Span 是起点,接口处理 Span 在它下面,数据库查询和外部调用都挂在接口处理之下。四个 Span 各记一步操作,上下层级表示嵌套关系。

这个层级在数据里真实存在。按 Traces 页的说法,Span 里存着 parent span ID,子 Span 就是子操作;没有 parent 的那个是根 Span,标出整个操作的起止。跨进程时,同一个 Trace 标识靠上下文传播带过去,具体是 W3C Trace Context 的 traceparent 头,四个字段:version、trace-id(16 字节)、parent-id(8 字节,也就是上游的 span-id)、trace-flags。标识能跨语言、跨厂商对上,靠的就是这个标准头。

四个 Span 共享 Trace T,并形成父子关系

查看 Mermaid 源码
flowchart TD
request["请求 Span:Trace T"] --> api["接口处理 Span:Trace T"]
api --> db["数据库查询 Span:Trace T"]
api --> remote["外部调用 Span:Trace T"]

日志带上对应的 Trace 标识,排查时就能把事件和调用过程连起来。用 OpenTelemetry 的话这一步不用手写:日志记录本来就有 TraceId、SpanId、TraceFlags 三个顶层字段,接上 SDK 或自动埋点后会自动关联。官方也推荐生产环境用结构化日志,排查时按字段过滤比在自由文本里搜靠谱。指标则先帮你找到异常时段,再到相关日志和轨迹里看细节。

还有一点容易忽略:采样决定了最终留下哪些轨迹,留下的能查到,没留下的在数据里根本不会出现。没找到某条 Trace,不能证明那次请求没发生,它可能真没发生,也可能只是没被保留。

采样页把“未采样”直接定义为不处理、不导出,数据里就是不存在。头采样在链路开始前决定,常见做法是按 trace ID 和期望百分比算,看不到整条链路,所以官方明说“you cannot ensure that all traces with an error within them are sampled with head sampling alone”。尾采样能按整条链路判断,代价是得有地方先把 Span 攒住。再加一层:traceparent 里的 sampled 位只是调用方的建议,下游不一定照办。Tempo 文档甚至说“By definition, traces are never complete”,Span 可能隔几天才到。

工具名字背后是部署条件

这次记下的选型线索有 Jaeger、Tempo、Loki 和 Elasticsearch。比较之前,先回答四个问题:

  • 数据量多少?
  • 保留多久?
  • 主要查完整调用,还是查文本?
  • 是否已有相关的运维能力?

四个工具的官方文档里,能直接对上这几个问题的部署条件如下:

工具 存什么、怎么查 文档里写明的条件
Jaeger v2 Trace;它现在是 OpenTelemetry Collector 的一个定制发行版 内存后端重启即丢数据,不建议上生产;all-in-one 加 Badger 只撑“modest data volumes”;agent 角色建议直接换成标准 Collector
Tempo Trace 全放对象存储,Parquet 列式块 读请求分两类:按 trace ID 精确取,或用 TraceQL 跨 Trace 搜
Loki 日志;只把元数据索引成 label,不索引内容 label 要选得低基数,否则查询慢
Elasticsearch(作 Jaeger 后端) 全文索引 默认按天建索引,官方自己说“might not optimally utilize resources”;空索引也占分片,分片和副本建索引时就定了

Jaeger FAQ 在存储上明确推荐 Elasticsearch 或 OpenSearch,而不是 Cassandra。对照四个问题:“查完整调用还是查文本”基本决定了是 Tempo、Jaeger 这一边还是 Loki、Elasticsearch 那一边;“保留多久”在 Elasticsearch 上就落到索引滚动和 ILM 策略。

这份笔记没有规模、配置和测量记录,排不出成本高低。光凭“低成本”三个字,判断不了一套方案迁进现有系统后要付出多少存储和维护开销。

告警之后要有处理办法

SLI、SLO、错误预算、Runbook 和 Auto-Recover 可以接在告警之后理解。先把三件事分开记:观察什么数值,什么情况需要注意,出问题后由谁采取什么动作。

这三件事正好对得上 SRE 书第 4 章的定义。SLI 是对服务水平某个方面的定量度量;SLO 是用 SLI 衡量的目标值或范围;SLA 是和用户之间的契约,达不达标有后果。错误预算是“允许 SLO 被错过的速率”,剩下多少可以拿来决定什么时候发新版本。

“什么情况需要注意”,SRE Workbook 的 SLO 告警一章给的判据是 burn rate,也就是相对 SLO 消耗错误预算的速度,而不是数值一越线就报。它举了个例子:按小偏差告警,极端情况下一天能收到 144 条告警,一条都不处理,SLO 照样达标。目标是只在错误预算面临可行动的、具体的威胁时才叫醒值班的人。

Runbook 要能帮着处理一个具体告警,写明检查入口和可执行步骤。自动恢复要知道动作有没有生效、失败后由谁接手。这样告警出现后,维护者能顺着已有信息继续排查,不用重新猜整张架构图。

Runbook 管的是已知情况。事故响应一章的顺序是先执行 runbook 里的缓解步骤,runbook 解决不了,再有条理地尝试新的恢复手段;一线响应者把缓解放在第一位,根因分析排在后面。未知情况靠角色和升级路径接力:Incident Commander 统一指挥,Communications Lead 和 Ops Lead 向他汇报,On-Call 一章也把清晰的升级路径列为必备。

自动恢复“有没有生效”,可以拿 Kubernetes 的 liveness 探针当例子:探针失败,kubelet 杀掉容器重启,Pod 事件里会写一行“will be restarted”,容器恢复运行时 RESTARTS 计数加一。如果我来设告警,会盯这个计数的增长速度:自愈一直在重启却没好,就该转给人了。

留下几句话 读者来信

读到这里,有什么想法?欢迎留下你的看法。