
本文提供的 Elasticsearch ES|QL 查询快速定位 Kubernetes 集群中的崩溃循环、OOM 前夕的内存压力、节点资源饱和以及命名空间级别的错误尖峰。所有查询均基于 Elastic 发行的 OpenTelemetry CollectorEDOT采集的数据可 Discover 中运行也可保存为仪表板面板或告警规则。前置条件Elasticsearch 9.2 或更高版本ES|QL 在 8.11 中引入但 9.2 后的 TS 命令更完善。EDOT Collector 已在集群中运行并启用了以下接收器receiverskubeletstats采集 Pod、容器、节点的资源指标CPU、内存、网络等。k8s_cluster采集 Kubernetes 对象状态如 Pod 相位、容器重启次数。filelog配合k8sattributes处理器采集容器日志并添加上下文属性命名空间、Pod 名等。EDOT 会将这些数据通过 OpenTelemetry 协议OTLP发送至 Elasticsearch指标存储在metrics-*索引日志存储在logs-*索引。ES|QL 之 Kubernetes 监控ES|QL 是一种管道式查询语言它的核心思想是从一个数据源开始每一行追加一个操作——过滤、计算、聚合、排序——逐步精炼你的查询。这种结构天然适合故障排查因为你可以像剥洋葱一样层层缩小问题范围。例如一个典型的 ES|QL 查询流程如下FROM metrics-* // 数据源 | WHERE timestamp NOW() - 1h // 时间过滤 | STATS count BY namespace // 聚合统计 | SORT count DESC // 排序每行只做一件事逻辑清晰易于调试和复用。TS 命令与时间序列Kubernetes 指标绝大多数是计数器Counter或仪表盘Gauge它们随时间变化采样而来。使用TSTime Series命令可以正确处理时间序列的语义对于Gauge如当前内存使用率你关心的是最新值或峰值而非平均值。对于Counter如累计重启次数你关心的是增量或最大值而非对时间窗口内的所有样本求和那样会得到一个无意义的巨大数字。TS命令为每个时间序列由维度如pod、container唯一标识单独处理聚合函数并支持TBUCKET对时间进行分桶。普通FROM则适用于计数去重、简单取最大值等不涉及时间窗口内序列间计算的场景。EDOT 采集器与依赖字段EDOT 采集的数据遵循OpenTelemetry 语义约定但具体的指标名称由接收器定义而非语义规范本身。因此不同接收器输出的字段名可能不同。接收器典型指标/字段说明kubeletstatsk8s.container.memory_limit_utilizationk8s.pod.cpu.node.utilizationk8s.node.cpu.usagek8s.container.restarts资源利用率指标来自 kubelet 的 APIk8s_clusterk8s.pod.phasePod 相位编码为数字filelogk8sattributesbody.text日志内容severity_text日志级别k8s.pod.name、k8s.namespace.name日志消息与上下文注意如果你启用的接收器不同或自定义了采集配置某些字段可能缺失。请将下面的查询视为模板根据实际索引映射调整字段名称。集群概览从全局开始在深入具体问题之前先把握集群的整体面貌。以下查询统计过去一小时内每个命名空间有多少个不同的 Pod 上报了指标FROM metrics-* | WHERE timestamp NOW() - 1 hour | STATS pod_count COUNT_DISTINCT(k8s.pod.name) BY k8s.namespace.name | SORT pod_count DESC原理解释COUNT_DISTINCT会去重同一个 Pod 在时间窗口内产生的多个采样点因此每个 Pod 只被计数一次。结果是一个“命名空间 — Pod 数量”的排行榜快速告诉你哪些命名空间负载最重也能帮你发现预期运行的 Pod 是否“消失”了如果某个命名空间计数为 0说明它可能已缩容或没有被采集到。定位 Pod 重启与崩溃循环重启是问题的第一信号。k8s.container.restarts是一个 Gauge 类型报告每个容器的当前累计重启次数该值会持续增长直到容器被重新创建。以下查询列出过去 24 小时内重启次数最多的容器FROM metrics-* | WHERE timestamp NOW() - 24 hours AND k8s.container.restarts IS NOT NULL | STATS restarts MAX(k8s.container.restarts) BY k8s.namespace.name, k8s.pod.name, k8s.container.name | WHERE restarts 0 | SORT restarts DESC | LIMIT 20原理解释MAX(k8s.container.restarts)取得该时间窗口内每个容器重启计数的最新值因为 Gauge 的值会随时间更新最大值即当前最新值。如果一个容器的重启次数很高且在持续上升这几乎是CrashLoopBackOff的教科书式信号。拿到 Pod 名称后你可以直接跳转到后面的日志查询查看它崩溃前输出了什么。发现非 Running 状态的 Pod重启次数告诉你“过去发生过故障”而Pod 相位Phase告诉你“现在是否健康”。k8s.pod.phase是一个编码为数字的 Gauge数值相位1Pending2Running3Succeeded4Failed5Unknown以下查询使用TS读取每个 Pod 的最新相位并筛选出非 Running的 PodTS metrics-* | WHERE TRANGE(15m) | STATS phase MAX(LAST_OVER_TIME(k8s.pod.phase)) BY k8s.namespace.name, k8s.pod.name | WHERE phase ! 2 | EVAL phase_name CASE( phase 1, Pending, phase 3, Succeeded, phase 4, Failed, phase 5, Unknown, Other) | SORT phase_name原理解释TRANGE(15m)限定时间范围为最近 15 分钟。LAST_OVER_TIME从每个 Pod 的时间序列中取出最新样本确保我们比较的是当前状态而非平均值。MAX在这里只是辅助因为每个 Pod 在最后一个时间点只有一个值语法要求必须有聚合函数。结果中Pending的 Pod 通常表明调度问题资源不足Failed或Unknown则需要立即关注。在 OOM 发生前捕捉内存压力OOMOut-Of-MemoryKill是 Kubernetes 中最常见的故障之一而且预防远胜于事后调试。当你在kubeletstats接收器中启用了 limit 元数据默认开启EDOT 会上报k8s.container.memory_limit_utilization它表示容器内存使用量占其limit的比例取值范围 0~1。以下查询找出过去一小时内容器内存使用峰值超过 85% limit的容器TS metrics-* | WHERE TRANGE(1h) | STATS peak_mem_pct MAX(MAX_OVER_TIME(k8s.container.memory_limit_utilization)) BY k8s.namespace.name, k8s.pod.name, k8s.container.name | EVAL peak_mem_pct ROUND(peak_mem_pct * 100, 1) | WHERE peak_mem_pct 85 | SORT peak_mem_pct DESC原理解释MAX_OVER_TIME在每个容器的时间序列内部找出峰值因为 Utilization 是 Gauge随时间波动。外层MAX确保每个容器只输出一个最终峰值由于TS可能产生多个时间分片此处用MAX取最大值。如果一个容器的峰值反复超过 95%那它极有可能成为下一个 OOM 牺牲品。结合重启查询如果一个容器同时满足“重启次数高”和“内存峰值高”几乎可以断定它因 OOM 被杀后重启了。追踪 CPU 使用率与节点压力CPU 问题通常表现为节流Throttling和响应变慢而非崩溃。k8s.pod.cpu.node.utilization指标报告每个 Pod 的 CPU 使用量占整个节点容量的比例因此所有 Pod 的该值之和不应超过 1。5 分钟粒度下最繁忙的 PodTS metrics-* | WHERE TRANGE(1h) | STATS avg_cpu AVG(AVG_OVER_TIME(k8s.pod.cpu.node.utilization)) BY k8s.pod.name, TBUCKET(5m) | SORT avg_cpu DESCTBUCKET(5m)将时间划分为 5 分钟桶每个桶内AVG_OVER_TIME对每个 Pod 的时间序列求平均外层AVG再合并同名 Pod因为一个 Pod 可能包含多个容器但其 Pod 级指标只有一个时间序列。结果可渲染为时间序列图直观展示 CPU 使用高峰。节点级别的资源饱和检查若要判断节点本身是否过载直接查询节点指标TS metrics-* | WHERE TRANGE(1h) | STATS cpu AVG(AVG_OVER_TIME(k8s.node.cpu.usage)), mem AVG(LAST_OVER_TIME(k8s.node.memory.usage)) BY k8s.node.name, TBUCKET(5m) | SORT cpu DESCk8s.node.cpu.usage是节点 CPU 使用率百分比通常 0~1k8s.node.memory.usage是内存使用率。如果某个节点的 CPU 长期接近 100%那它上面所有 Pod 都会受到节流影响这很容易被误认为是应用程序自身的问题。深入容器日志排查当指标将你指向某个 Pod 后日志会告诉你它当时在做什么。EDOT 将日志消息存储在body.text字段日志级别存储在severity_text同时附带了k8s.*上下文字段与指标一致。按错误量对 Pod 排名FROM logs-* | WHERE timestamp NOW() - 1 hour AND severity_text IN (ERROR, FATAL) | STATS errors COUNT(*) BY k8s.namespace.name, k8s.pod.name | SORT errors DESC | LIMIT 20这个查询帮你快速识别错误集中的 Pod。如果错误分布均匀可能是集群级问题如果集中在某个 Pod则是该工作负载的个例。查看特定 Pod 的日志带关键词过滤FROM logs-* | WHERE timestamp NOW() - 1 hour AND k8s.pod.name checkout-your-hash AND body.text LIKE *timeout* | KEEP timestamp, severity_text, body.text | SORT timestamp DESC | LIMIT 50LIKE *timeout*执行通配符匹配。若想进行全文相关性搜索可替换为MATCH(body.text, timeout)。KEEP只保留你关心的字段使结果更清晰。组合查询一次 Kubernetes 事故的完整调查链实际运维中我们通常串联多个查询而非孤立运行。典型的调查循环如下是否按错误量排名 Pod检查目标 Pod 的重启次数和内存峰值是否因 OOM 重启查看该 Pod 的日志搜索 OOM 相关关键词查看日志搜索异常堆栈或超时确认问题根因并采取措施因为所有查询都使用相同的k8s.namespace.name和k8s.pod.name字段你可以将 Pod 名称从一个查询直接复制到下一个。这种一致性也方便构建联动仪表板——点击某个命名空间或 Pod所有面板同步过滤。将查询升级为告警与仪表板本文中任何生成聚合值的查询都可以转化为告警规则而不仅仅是临时排查工具。以重启查询为例若要监控“15 分钟内重启超过 5 次”的情况只需稍作调整FROM metrics-* | WHERE timestamp NOW() - 15 minutes AND k8s.container.restarts IS NOT NULL | STATS restarts MAX(k8s.container.restarts) BY k8s.namespace.name, k8s.pod.name, k8s.container.name | WHERE restarts 5将此查询配置为Elasticsearch 查询规则Elastic 的告警框架当结果非空时触发告警。你就能在用户发现之前第一时间收到通知。同样的模式可应用于内存利用率 90%节点 CPU 95%错误日志数 阈值Pod 相位非 Running总结构建你的 ES|QL 监控工具包ES|QL 用一种统一的语法覆盖了 Kubernetes 所有的可观测信号对象状态Pod 相位、重启次数资源指标CPU、内存、网络容器日志错误、堆栈、事件查询类型关键函数适用场景集群概览COUNT_DISTINCT查看命名空间负载重启排行MAXGauge发现崩溃循环非运行 PodLAST_OVER_TIME检查当前健康状态内存压力MAX_OVER_TIME预测 OOM 风险CPU 使用AVG_OVER_TIMETBUCKET追踪资源热点错误日志排名COUNT(*)定位故障 Pod日志详情LIKE/MATCH深入排查根因开始你的监控之旅吧先用概览查询熟悉集群面貌然后将重启、相位、内存、CPU 和日志查询保存为常用工具。当事故发生时按调查链串联使用。再进一步将高频查询制作成仪表板将关键阈值升级为告警。小贴士根据你实际启用的接收器某些指标字段名可能有所不同。请查阅 EDOT 文档调整查询中的字段名称。但核心逻辑和聚合函数是通用的足以应对大多数场景。现在你已经拥有了一套。祝排查愉快