关于 observability-sre-triage
observability-sre-triage 是面向 SRE 的服务故障排查技能,依据 SLO、告警、黄金信号、依赖和日志逐层判断服务状态与故障位置。它使用 elastic CLI 查询 Elasticsearch,并要求结论严格依据现有遥测证据。
主要功能
- 01按信号层级判断服务状态
- 02分析 SLO、错误预算与告警
- 03用延迟、错误率和吞吐量定位症状
- 04检查依赖与基础设施信号
- 05通过日志筛选补充故障解释
- 06区分遥测缺失与服务健康
适用场景
- 01判断服务健康、降级或不健康
- 02定位变慢或错误率升高的环节
- 03分析 SLO 燃烧与告警触发原因
- 04筛选解释故障的日志和请求子群
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ elastic/ agent-skills --skill observability-sre-triage安装后在 Claude Code 中输入/observability-sre-triage确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/apm-signals.mdlog-investigation.mdslo-and-alerts.md详细介绍
SRE 服务排查
判断服务处于健康、降级还是不健康状态,并说明原因。排查是分层的,而不是核对清单:SLO 和告警用于判断服务是否违反了自身承诺,基于追踪数据得出的黄金信号描述了故障的表现,依赖项和基础设施解释了故障的来源,日志则提供了可写入事件频道的说明。按层级逐步排查,直到证据足以支持判断,然后停止。
编写和调整 SLO 定义、错误率消耗规则及告警阈值时,请使用 observability-service-reliability skill。此 skill 仅读取这些状态。对于 Kubernetes 工作负载、节点或控制平面的诊断——例如重启循环、确认 OOM kill、节点压力、准入拒绝、发布卡住——请交由 observability-k8s-investigation skill 处理。此 skill 用于检查 Kubernetes 托管的服务是否已饱和;它不会诊断其背后的 Pod 或节点为何发生故障。
环境配置
此 skill 通过 elastic CLI 执行 Elasticsearch 操作。如果未安装
elastic CLI,请告知用户需要安装它的原因。不要猜测凭据、直接调用 HTTP API,或尝试其他变通方法。
此 skill 以 HTTP 简写形式引用操作(例如,GET /、GET /_cat/indices、GET /{index}/_mapping、
GET /{index}/_settings/index.mode、POST /_query)。本文末尾的 Operations 表格将每个简写映射到相应的 elastic CLI 命令——始终使用 CLI,而不是直接调用 HTTP API。
无法访问集群时的分析
上述 CLI 检查只限制_查询集群_,不限制分析。当用户已在问题中提供证据(指标值、计数、状态原因、日志行、告警负载、配置)时,应基于这些证据进行推理并给出结论。
如果确实需要用户未提供的数据,仍要说明你会检查什么以及如何检查——指出能够解决问题的具体查询、索引和字段,然后请求配置 CLI。指出要检查的内容,即使无法访问集群也有帮助;只请求配置则不然。
本文所有操作均使用 ES|QL(POST /_query)或 Kibana Observability API。不要使用 Query DSL,也不要使用 ES|QL KQL 搜索函数——应使用原生方式表达谓词(WHERE service.name == "checkout")。
待完成的工作
- 判断“服务 X 是否健康?”,给出结论及其依据
- 判断“服务 X 为什么变慢/报错/没有活动?”,将变化定位到服务、依赖项或其基础设施
- 在事件期间查看 SLO 状态、错误率消耗和剩余错误预算
- 确定当前适用于某个服务的告警规则,包括适用于所有服务的规则
- 从嘈杂的日志流中筛选出能够解释服务降级的故障
- 找出能够区分故障或缓慢子群体的属性(版本、主机、Pod、区域、路由)
- 区分健康的服务和没有遥测数据的服务
输出规范
适用于此技能下生成的每条回复。
- 坚持最有证据支持的结论。 当证据指向一个方向时,就明确说明。不要为了显得谨慎而降低确信程度——面对明确无误的证据仍含糊其辞,是缺陷,不是谦逊。
- 给出判定:健康、降级或不健康,并说明原因。未明确指出这三种状态之一的故障排查回答,就没有完成任务。
- 只在结论中说明一次确信程度。 不要在每条要点中重复。
- 不要超出证据范围进行猜测。 如果遥测数据没有显示原因,就不要在回答中提及。指出未知之处,然后到此为止。绝不要提出任何未经信号测量的机制(例如“可能是 GC 暂停”“可能是邻居干扰”)。
- 如实报告缺失情况。 零行意味着数据缺失或未采集;绝不意味着底层状况健康。“没有依赖项指标”不等于“依赖项没问题”。
- 不要凑字数。 不要复述问题,不要叙述运行了哪些查询(除非结果与结论相关),也不要总结摘要。
- 以发现结论收尾。 不要在末尾提出“要我继续深入调查吗?”之类的建议。可执行的后续事项应列在建议清单中,并以建议的形式表述,而不是提问。
信号层级
信号之间经常相互矛盾。按以下顺序决定以哪个信号为准。
| 排名 | 信号 | 权威性 |
|---|---|---|
| 1 | SLO 状态和消耗速率 | 存在 SLO 时具有权威性。它们体现了针对该服务约定的“良好”定义 |
| 2 | 活跃告警规则 | 没有 SLO 覆盖该症状时具有权威性。数据来源为 Alerting API |
| 3 | 错误率、延迟、吞吐量 | 描述降级情况。仅当不存在上述更高优先级信号时才具有决定性 |
| 4 | 依赖项健康状况 | 定位上游或下游原因;本身不决定判定结果 |
| 5 | ML 异常 | 与学习到的基线相比的偏差,而非与目标相比的偏差。用于佐证并限定时间范围 |
| 6 | 基础设施(CPU、内存、OOM) | 解释机制。黄金信号正常时,饱和的 Pod 是风险,不代表服务中断 |
| 7 | 日志 | 用于解释,绝不用于判定。日志量不代表健康状况 |
冲突规则:
- SLO 健康,延迟升高 → 已降级,但仍处于错误预算范围内。判定以 SLO 为准;将趋势作为风险报告,并附上消耗速率。
- SLO 违规,但当前时间窗口内的指标看起来正常 → 相信 SLO,并检查其时间窗口。SLO 按小时或天评估;即使 15 分钟的 ES|QL 窗口看起来正常,预算也可能已经耗尽。
- 告警触发,但未定义 SLO → 以告警作为判定结果。将每条规则的
params解析出来,确认其实际针对该服务后再归因。 - 日志噪声大,黄金信号平稳 → 未降级。日志量大但错误率或延迟没有变化,这是日志配置问题,不是健康状况问题。
- 吞吐量骤降,错误率平稳 → 调用方停止了调用。应检查上游,再判断是否归咎于此服务。
- 任何查询返回零行 → 数据缺失。指出哪个信号不可用,并相应缩小判定范围;绝不要把无数据解释为健康。
路由:症状到首个信号
| 当前症状 | 首先获取 | 参考 |
|---|---|---|
| “X 是否正常?”/ 不明确 | SLO 状态,然后是活跃规则,最后是黄金信号 | slo-and-alerts.md |
| “X 很慢” | 与上一时段相比的延迟百分位数,然后是依赖项延迟 | apm-signals.md |
| “X 出错了”/ 5xx | 按路由统计的错误率,然后是失败事务关联 | apm-signals.md |
| “X 已宕机”/ 没有流量 | 吞吐量,然后确认服务是否仍在接收任何数据 | apm-signals.md |
| “只有部分请求有问题” | 根据候选属性对问题子群进行关联 | apm-signals.md |
| “触发了警报”/“SLO 正在消耗预算” | 规则 params 和 SLO 消耗速率,然后是规则监控的指标 | slo-and-alerts.md |
| “日志里有什么?”/ 日志嘈杂 | 日志漏斗——通过 NOT 排除条件迭代筛选 | log-investigation.md |
| 怀疑 OOM、节流、重启 | 容器 CPU 和内存限制利用率 | apm-signals.md |
| 在非 K8s 主机上“是否已饱和?” | 从 hostmetrics 接收器获取主机 CPU、内存和平均负载 | apm-signals.md |
| “哪个下游服务拖累了 X?” | 按目标统计调用量、延迟和故障率 | apm-signals.md |
数据源
已根据 Elasticsearch 9.6.0 验证的 OTel 原生数据流:
| 数据 | 索引模式 |
|---|---|
| 跟踪(跨度、事务) | traces-*.otel-*;经典 Elastic APM Agent 摄取的数据也会写入 traces*apm* |
| 日志 | logs-*.otel-* |
| 原始指标 | metrics-*.otel-*;经典 APM Agent 摄取的数据也会写入 metrics*apm* |
| 服务清单(1 分钟汇总) | metrics-service_summary.1m.otel-* |
| 事务汇总(1 分钟) | metrics-service_transaction.1m.otel-*、metrics-transaction.1m.otel-* |
| 依赖项汇总(1 分钟) | metrics-service_destination.1m.otel-* |
| Kubernetes | metrics-kubeletstatsreceiver.otel-*、metrics-k8sclusterreceiver.otel-*、logs-k8seventsreceiver.otel-* |
| 主机(VM、裸机) | metrics-hostmetricsreceiver.otel-*;Elastic Agent 系统集成的数据会写入 metrics-system.* |
跟踪、指标和日志都会填充 service.name,因此它是三者之间的关联键。在 ES|QL 中使用扁平的 OTel 字段路径(k8s.pod.name,而不是 resource.attributes.k8s.pod.name)。分析 OTel 应用指标时,ES|QL 的 TS(时间序列)命令可以更高效地查询指标。它在 Serverless 上已正式发布;在 Stack 上,9.2 中为预览版,9.4 中正式发布,因此低于 9.4 的版本请改用 FROM 和 BUCKET。TS 也不接受 COUNT(*)——请改为统计某个字段。
此技能及其参考资料中的操作示例均针对上述 OTel 原生数据流编写。使用经典 Elastic APM Agent 进行插桩的服务会将数据写入 traces-apm* 和 metrics-apm*,且字段名称不同(transaction.duration.us、event.outcome),因此这些操作示例不会返回该服务的数据。如果某项服务显然仍在正常运行,但结果为空,这说明查询范围存在边界,并不能证明服务发生了故障:请检查该服务实际写入哪个索引系列(GET /_cat/indices),并报告数据摄取路径,而不要根据无结果就下结论。
ES|QL 功能可用性
此技能使用的三个功能均比其 8.11 基础版本更新。依赖这些功能前,请检查 GET /:
build_flavor: "serverless" 表示三者均可用;否则请根据 Stack 列对比 version.number。
如果实际情况是查询未运行,绝不要报告“无数据”——应说明哪个功能不可用,并使用
备用方案。
| 功能 | Serverless | Stack | 许可证 | 用于 | 备用方案 |
|---|---|---|---|---|---|
FORK | GA | preview 9.1-9.3, GA 9.4+ | 任意 | 日志漏斗和子群体比较 | 将每个分支作为单独查询运行,并自行合并结果 |
CATEGORIZE | GA | preview 9.0, GA 9.1 | Platinum | 日志漏斗中的消息分类 | 按截断后的消息前缀分组,或基于结构化错误字段构建漏斗 |
TS | GA | preview 9.2, GA 9.4 | 任意 | OTel 应用指标查询 | 对同一数据流使用 FROM 和 BUCKET |
CATEGORIZE 所需的 Platinum 许可证不是版本检查。许可证为 Basic 或 Gold 的 9.6 Stack 集群会像 8.11 集群无法使用
FORK 一样,无法使用该功能;错误信息会指出许可证问题,而非语法问题。在 Serverless 上,
该函数为 GA,且没有单独的许可证限制。
流程:排查服务降级问题
-
确定服务和时间范围。 根据请求解析服务名称和时间范围。使用用户指定的时间 范围——如果问题发生在过去,不要默默地假定范围是最近一小时。如果未指定范围,则使用最近一小时,并说明这一点。通过
POST /_query在traces-*.otel-*上执行COUNT(*) BY service.name,确认遥测数据中确实存在该服务; 如果没有出现该名称,请先消除歧义,再继续查询。决策:后续所有查询所限定的服务和时间范围。数据:范围内不同的
service.name值。 -
查看 SLO 状态和消耗速率。 使用
GET kbn:/api/observability/slos列出 SLO,并使用GET kbn:/api/observability/slos/{id}获取绑定到该服务的 SLO。查看状态、当前 SLI、消耗速率和剩余错误预算。决策:是否存在双方认可的服务目标,以及该目标是否遭到违反?如果是,结论已经确定,后续步骤仅用于解释原因。如果该服务没有 SLO 覆盖,请说明一次,然后继续执行步骤 3。
-
确定哪些告警规则适用于此服务,以及其中哪些正在触发。 调用
GET kbn:/api/alerting/rules/_find,并传入per_page=100&filter=alert.attributes.enabled:true;如果total超过已收到的数量,则使用page分页。然后在客户端筛选响应。不要查询.alerts*索引来 确定活动状态——告警 API 响应才是事实来源。需要规则的params时,使用GET kbn:/api/alerting/rule/{id}获取其完整定义。不要在服务端缩小此调用的范围。
_find的filter参数是针对已保存对象 attributes 的 KQL, 而params不属于这些属性——在确实存在相关规则的集群上,filter=alert.attributes.params.serviceName:<name>仍会返回零条规则。 使用search=apm&search_fields=tags、alertTypeId或consumer进行范围缩小则更糟:这会依据命名约定或规则类型允许列表丢弃规则,而被丢弃的规则中尤其常见的是适用于所有服务的规则。 请参阅 references/slo-and-alerts.md,了解实测失败情况。 在获取的集合中,同时评估params.serviceName与该服务匹配的规则,以及未设置params.serviceName的规则,因为后者是同样适用于该服务的全服务规则。读取每条规则的execution_status.status:active表示规则上次运行产生了告警,ok表示规则已运行但未产生告警,error表示规则完全未在评估——这是盲区,而不是通过。判断:有哪些规则覆盖了该服务,其中是否有规则当前正在触发?数据:规则
params.serviceName、规则类型和执行状态。 -
检查 ML 异常(如有作业)。 使用
GET /_ml/anomaly_detectors列出作业,并使用GET /_ml/anomaly_detectors/_stats确认它们正在运行——已停止的作业不会产生异常,这与没有异常并不是一回事。使用GET /_ml/anomaly_detectors/{id}/results/records获取评分记录。判断:延迟、吞吐量或错误率是否偏离了其学习到的基线,何时发生?使用异常时间窗口缩小步骤 5 和 6 的范围。
-
衡量黄金信号。 对
traces-*.otel-*运行 ES|QL,统计吞吐量、延迟(平均值、p95、p99)和错误率,按时间窗口分桶,并与紧邻其前、时长相同的窗口进行比较。请参阅 references/apm-signals.md。判断:与服务自身相比,服务是否确实发生了变化,变化体现在哪个维度?数据:当前和先前时间窗口的请求数、延迟百分位数和失败率。
-
定位问题:先检查依赖,再检查子群体,最后检查基础设施。
- 依赖 — 按
span.destination.service.resource聚合metrics-service_destination.1m.otel-*,统计调用量、平均延迟和失败率。如果此查询未返回该服务的任何行,则表示该服务的依赖未进行 APM 检测;请报告依赖数据不足,不要声称上游服务运行正常。 - 子群体 — 如果只有部分流量存在问题,请按候选属性比较失败率或慢请求率与总体比率,找出占比异常高的属性。请参阅 references/apm-signals.md。
- 基础设施 — 首先读取服务 span 上的资源属性(
k8s.pod.name、container.id、host.name),再根据属性内容选择后续检查。如果存在 Pod 和命名空间属性,则表示服务运行在 Kubernetes 上:检查metrics-kubeletstatsreceiver.otel-*中的k8s.container.cpu_limit_utilization和k8s.container.memory_limit_utilization。如果存在host.name但没有 Pod 属性,则表示服务运行在 VM 或裸机上,此时所有k8s.*字段都为空:改为检查metrics-hostmetricsreceiver.otel-*中的system.cpu.utilization、system.memory.utilization和system.cpu.load_average.1m。OOM 终止、CPU 限流和主机饱和都会直接降低 APM 健康状况。请参阅 references/apm-signals.md。 - 近期变更 — 部署是导致阶跃变化的最常见原因。使用
GET kbn:/api/apm/services/{serviceName}/annotation/search,在事件时间窗口内搜索该服务的部署注释,并在子群体查询中按service.version比较失败率或延迟率。若注释位于问题开始的时间窗口内,则存在较强的相关性;在归因之前,请确认它能够合理解释该症状。
判断:原因是在此服务内部、在它调用的某项服务中、在一部分实例中、在其下层基础设施中,还是由某项已上线的变更引起?
如果 Kubernetes 分支检查发现饱和、重启或 OOM 终止,则已确定故障机制;剩余诊断——Pod 为何被终止、节点是否承受压力、发布是否卡住——属于 observability-k8s-investigation 技能。请移交处理,不要继续在此处诊断。
- 依赖 — 按
-
结合日志说明。 按
service.name限定日志范围;如果已知具体的故障 Trace,则按trace.id限定范围,然后运行漏斗分析,直到剩余结果集小到可以逐条阅读。请参阅 references/log-investigation.md。日志用于确认并阐明原因;不能推翻步骤 2 和 3 的结论。 -
给出结论。 判断状态为健康、降级或不健康,并说明原因和置信度,随后给出建议。注明任何不可用的信号。
示例
“结账服务健康吗?” — 确定时间窗口,读取其 SLO,然后读取活动规则(包括适用于所有服务的规则),再对照前一时间窗口,查询 traces-*.otel-* 中的吞吐量、延迟百分位数和错误率。如果可用性 SLO 为 99.2%,目标为 99.5%,且消耗速率高于 1,则结论为不健康,原因是违反了 SLO;黄金信号用于解释原因,而非决定结论。
“前端为什么这么慢?” — 比较当前时间窗口与等长的上一时间窗口的 p95 和 p99。如果服务级延迟上升,而 metrics-service_destination.1m.otel-* 中按目标服务划分的延迟持平,则增加的耗时发生在服务内部;如果某个目标服务的平均响应时间也同步上升,则该依赖服务就是原因,前端则是受影响方。
“只有部分结账请求失败” — 执行子群体对比:按 service.version、k8s.pod.name、host.name 和 cloud.region 分组查看失败率,并与总体失败率对比。如果某个属性值的失败率达到总体的数倍,且请求量足以构成影响,那么该属性值就是相关因素。在实时数据中,按路由对前端服务器 Span 分组后发现,POST 请求的慢请求率为 3.8%,总体为 0.9%——高出 4 倍,因此可以将问题定位到写入路径。
“购物车服务的日志看起来很糟” — 在 logs-*.otel-* 上运行漏斗分析,并限定 service.name == "cart":通过一次 FORK 获取趋势、总量、样本和消息分类,然后针对每种主要模式添加 NOT ... LIKE 排除条件,并使用累积的完整过滤条件重新运行,直到剩余模式少于 20 种。日志量大本身并不能作为服务健康状况的结论——在判定服务降级前,先检查黄金信号。
“支付服务的上游健康吗?” — 查询该服务的 metrics-service_destination.1m.otel-*。如果没有结果,说明该服务不会发出依赖指标。请报告该服务的依赖数据不可用,并根据现有信号给出结论;不要报告上游健康。
“api-gateway 触发了警报” — 不使用服务端筛选,获取已启用的规则,然后在内存中匹配 params.serviceName == "api-gateway" 以及没有 params.serviceName 的规则,并读取 execution_status.status,查看哪些规则正在触发。通过 GET kbn:/api/alerting/rule/{id} 读取触发规则的阈值,然后在 ES|QL 中对相同时间窗口查询相同指标,确认该规则描述的是真实变化,而不是阈值设置得过于严格。
指南
- 按顺序检查信号层级,并在证据足以支持结论时停止。不要对每个请求都运行本文档中的所有查询。
- 如果存在 SLO,以 SLO 状态和消耗速率为依据。如果不存在,则依次参考告警、ML 异常、吞吐量、延迟、错误率、依赖项、基础设施和日志,并说明该服务没有 SLO 覆盖。
- 使用告警 API 获取活跃告警状态。绝不为此查询
.alerts*索引。始终同时评估服务范围的规则,以及没有params.serviceName的规则。 - 获取告警规则时不要缩小范围,并在客户端进行筛选。
_find无法按params筛选,标签搜索会遗漏不遵循命名约定的规则,而executionStatus.status:active只会返回当前正在触发的规则——这些方式都会悄然隐藏上一条所要求的适用于所有服务的规则。 - 始终使用用户指定的时间范围。将每项指标与紧邻其前、时长相同的时间窗口进行比较——没有基线的绝对数值不足以支持结论。
- 零行表示缺少数据。指出哪个信号不可用,不要把没有数据当作通过。
- 每个查询都要按
service.name和有界的@timestamp范围限定,并使用LIMIT限制输出。只需查看趋势时,优先使用较粗的时间桶。 - 统计失败的跨度时,优先使用
event.outcome == "failure";在 OTel 跟踪数据中,status.code == "Error"与之等效,但成功记录中的该字段为 null,因此无法直接计数。 - 衡量服务自身的吞吐量和延迟时,使用
kind == "Server"筛选服务端流量,避免重复计算客户端跨度。 - 将
log.level和severity_text仅视为提示,绝不能依赖它们进行筛选。在真实的 OTel 数据中,大多数日志记录完全没有级别;即使有,大小写和词汇也不一致(INFO、Information、SEVERE、Normal)。尤其绝不要写log.level == "error"——ECS 的小写词汇并非 OTel SDK 发出的内容,因此即使服务正在记录错误,也会返回零行且不报错,进而误判服务健康。如果确实需要严重性谓词,请使用标准化的数值severity_number >= 17。 - 日志用于解释,不用于决策。绝不能仅凭日志内容得出结论。
- 不要臆造字段名称。如果某个字段可能在此部署中不存在,请先使用
GET /_resolve/index/{pattern}确认数据流存在,再基于该字段构建查询。 - 检查饱和度之前,先确定服务的运行位置。Kubernetes 与主机遥测数据使用的字段名称完全不同,因此对 VM 托管的服务执行 Kubernetes 查询会返回零行,但这并不能说明服务是否达到饱和。
- 当
POST /_query的结果中大多数列为空时,传入--drop-null-columns。基础设施指标天然稀疏——没有声明限制的地方就不会有利用率数据。此标志会在all_columns下列出被抑制的列名,从而压缩无用信息,同时确保没有内容被隐藏。
操作
| HTTP API(简写) | elastic CLI 命令 |
|---|---|
GET / | elastic es info |
POST /_query | elastic es esql query --format tsv --query '<esql>' |
GET /_resolve/index/{pattern} | elastic es indices resolve-index --name '<pattern>' |
GET /_ml/anomaly_detectors | elastic es ml get-jobs |
GET /_ml/anomaly_detectors/_stats | elastic es ml get-job-stats |
GET /_ml/anomaly_detectors/{id}/results/records | elastic es ml get-records --job-id '<id>' |
GET kbn:/api/observability/slos | elastic kb slo find-slos-op --space-id '<space>' --kql-query '<kql>' |
GET kbn:/api/observability/slos/{id} | elastic kb slo get-slo-op --space-id '<space>' --slo-id '<id>' |
GET kbn:/api/alerting/rules/_find | elastic kb alerting get-alerting-rules-find --filter '<filter>' |
GET kbn:/api/alerting/rule/{id} | elastic kb alerting get-alerting-rule-id --id '<id>' |
GET kbn:/api/apm/services/{serviceName}/annotation/search | elastic kb apm-annotations get-annotation --service-name '<service>' --environment '<env>' --start '<iso8601>' --end '<iso8601>' |
SLO 查找命令接受 KQL 查询字符串,这是因为 API 约定如此;这并不违反数据查询使用 ES|QL 的规则。
注释搜索路由会拒绝缺少 environment 的请求,因此在服务的环境未知时,请传入 ENVIRONMENT_ALL。此处仅涉及搜索操作:该技能是只读的,因此不会绑定配套的创建注释操作。