mcpField
K
kubesense-sql

kubesense-sql

github.com/kubesense-ai数据与数据库366 次安装3 天前更新
代码仓库

关于 kubesense-sql

kubesense-sql 是用于通过 KubeSense SQL 工具查询日志和追踪数据的技能,适合需要连接、窗口函数等复杂结果形式的场景。它涵盖字段发现、SQL 验证与执行,并说明索引日志搜索和查询限制。

主要功能

  1. 01发现日志和追踪字段
  2. 02执行前验证只读 SQL
  3. 03运行 SQL 并返回 TSV 结果
  4. 04使用时间与集群过滤宏
  5. 05提供索引日志文本搜索指南

适用场景

  1. 01连接日志或追踪结果集
  2. 02在单个查询中比较时间范围
  3. 03用 CTE 或窗口函数推导结果
  4. 04使用索引文本谓词查询日志

MCP 相关

需要 KubeSense MCP 服务,并介绍如何使用其 SQL 工具发现字段、验证查询及执行针对日志或追踪数据的 SQL。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/kubesense-ai/kubesense-mcp-skills --skill kubesense-sql

安装后在 Claude Code 中输入/kubesense-sql确认已加载。

附带文件references/recipes.md

详细介绍

KubeSense SQL

需要 KubeSense MCP 服务;连接和身份验证请参阅 kubesense-mcp。这里的工具会针对 logs 或 traces 模块检查 RBAC,具体取决于你传入的 signal。

工具

工具使用时机返回内容
get-trace-or-log-fields始终首先使用当前时间范围的字段目录
validate-sql运行任何较复杂的查询之前valid=true/false + 将要运行的 SQL
execute-sql查询确认无误后TSV 格式的结果行

这两个 SQL 工具接受相同的参数:signal("logs" 或 "traces")、query、from_time、to_time,以及可选的 clusters。

仅在 analyze-* 无法满足需求时使用 SQL

analyze-logs / analyze-traces 可完成筛选 → 分组 → 聚合,能满足大多数问题。它们成本更低、对参数的检查更严格,且输出更小。只有在结果形状确实不适用时才使用 SQL:

问题类型工具
分组统计 count / rate / p95 / top-Nanalyze-logs、analyze-traces
两个指标相除(错误率 %)使用公式的 analyze-telemetry
读取单独的行search-logs、search-traces
连接两个结果集SQL
CTE 链 / 多步推导SQL
窗口函数(row_number、lag、运行总计)SQL
单次查询中对两个聚合结果进行运算SQL
UNION、DISTINCT ON、argMax、quantileIfSQL
在同一个结果中比较两个时间范围SQL

如果你发现自己在写 SELECT field, count(*) ... GROUP BY field,请停下——这应使用带有 value_operation: row_count 的 analyze-*。

筛选条件绝不是使用 SQL 的理由

search-* / analyze-* 的 where 参数使用的语言,与 Logs 和 Traces 页面上的 Advanced 查询选项卡相同。如果条件能在那里编写,就应该在那里编写——服务端会将 body 搜索改写为文本索引形式,并将仅针对列的筛选路由到预聚合的汇总表。SQL 两者都不会做:它始终读取原始表,并按输入内容运行谓词。

请按以下顺序判断:

  1. 能否将条件写成 where 字符串——= != < > LIKE ILIKE SUBSTR_ILIKE IN、AND / OR / NOT、@attr 筛选、body ILIKE "%…%"?→ 使用 search-* / analyze-*。这几乎涵盖了所有“查找 / 统计符合……条件的行”的需求。
  2. 是否只有结果形状不适用(连接、CTE、窗口函数、对聚合结果进行运算)?→ 使用 SQL,保留相同的条件;对于 body 中的内容,请参照下方的 搜索日志文本。
  3. 条件本身是否需要 where 语言不支持的功能——正则表达式、两列之间的比较、列函数?→ 使用 SQL,并同时添加索引谓词以先缩小扫描范围(例如 workload / namespace 相等条件,或针对正则表达式必须包含的词使用 hasToken(lower(body), …))。

同一字段有多个值时,应在 where 字符串中使用 IN 或 OR,这不是使用 SQL 的理由。

表

写法读取内容
FROM logslogs 表
FROM tracestraces 表

除此之外的内容均无法访问。带数据库限定符的名称(kube_logs.logs_combined)会被直接拒绝——从其他 SQL 工具粘贴查询时,这通常是最先出错的地方。signal 必须与所选的 FROM 表相匹配。

字段名称

与所有其他 KubeSense 工具使用相同的目录标签——即 get-trace-or-log-fields 返回的字段集合。不接受存储列名:

使用不要使用
typelevel
instancepod_name
containercontainer_name
domaincluster(见下文)、customer_identifier
serviceapp_service
rolekind
status_codereturn_code
methodsubtype
resourceclustered_resource
reasonissue_reason
request_typeis_external
timestampstart_timestamp
duration_msduration

SELECT * 会展开为目录列,而不是原始表列。

SQL 接受 4 个发现功能未列出的字段

get-trace-or-log-fields 会隐藏标记为不可筛选的字段,但 SQL 仍然接受这些字段。 不要因为发现结果中没有列出某个名称,就认定它无效:

字段信号说明
timestamp两者发现结果中未列出,但每个查询都需要它来使用 $__timeFilter(timestamp)
duration_mstraces毫秒。发现结果会显示 duration(纳秒)——SQL 不接受该名称
primary_namespace、primary_workloadtraces透视列
body_lengthlogs计算方式为 length(body)

还可以查询但不属于目录字段的有:cluster(与 domain 是同一列,也是 $__clusters 展开的内容),以及原始属性容器——logs 中的 string_attributes / float_attributes,traces 中的 attribute_names / attribute_values。

警告

duration 与 duration_ms 的区别最容易让人踩坑。 analyze-traces 使用纳秒作为 duration 的单位。SQL 使用毫秒作为 duration_ms 的单位,并且完全不接受 duration。沿用 analyze 调用中的阈值会导致数值相差 10⁶ 倍。

宏

在查询运行前于服务端展开。

宏展开为
$__timeFilter(timestamp)from_time / to_time 时间范围
$__fromTime / $__toTime作为标量的时间边界,可用于表达式
$__clusters集群筛选条件;当 clusters 为空时为 1=1
SELECT workload, count(*) AS cnt
FROM logs
WHERE $__timeFilter(timestamp) AND $__clusters AND type = 'ERROR'
GROUP BY workload ORDER BY cnt DESC

$__timeFilter 在两种信号中都使用列名 timestamp。也可以使用普通 SQL 时间条件 (WHERE timestamp >= now() - INTERVAL 1 HOUR),但宏可以确保查询与传入的时间范围一致。

说明

SQL 中的字符串值使用单引号(type = 'ERROR'),而 analyze/search 工具的 where 参数使用不带引号的词或双引号。枚举大小写保持不变:logs 中为大写 ERROR,traces 中的 status 为小写 error。

自定义属性

与其他地方一样,使用 @ 前缀。例如 @http.status_code,或 @"key.with spaces"。

SELECT @http.status_code, count(*) AS cnt
FROM logs
WHERE $__timeFilter(timestamp) AND @http.status_code != ''
GROUP BY 1 ORDER BY cnt DESC

属性以字符串形式存储——进行数值比较前请先转换类型:

WHERE toFloat64OrNull(@latency_ms) > 500

警告

任何工具在任何阶段都不会验证属性名称。 @ 表达式会在查询解析之前编译为映射查找,因此键名拼写错误不会报错:logs 中会返回 NULL,traces 中会返回 ''。validate-sql 会报告 valid=true。如果某列的返回值几乎全部为空,通常意味着属性键名错误,而不是数据缺失。请先使用 get-trace-or-log-fields 确认每个键名。

搜索日志文本(body)

注意

在 SQL 中,对裸列 body 的谓词会扫描时间范围内的每一行日志。 body 索引基于 lower(body) 构建。body LIKE、body ILIKE、position(body, …)、match(body, …) 和 position(lower(body), …) 都不会命中索引,因此查询会解压整个消息列。在生产集群上,以这种方式进行多日搜索曾读取数 TB 数据,并导致所有人的数据摄取停滞。

search-logs 和 analyze-logs 会将 body ILIKE "…" 重写为索引形式。SQL 不会——它会按原样执行谓词。因此,查找“包含 X 的行”时,优先使用这些工具;如果必须在 SQL 中搜索,请使用以下任一形式:

查找内容写法索引
一个单词或 IDhasToken(lower(body), 'abc123')文本索引——最佳
多个词中的任意一个hasAnyTokens(lower(body), ['abc123', 'def456'])文本索引
包含多个词中的全部词hasAllTokens(lower(body), ['connection', 'refused'])文本索引
精确子串,包含标点符号lower(body) LIKE '%connection refused%'n-gram 过滤器
  • 将搜索词转换为小写。 索引中存储的是小写文本;hasToken(lower(body), 'OOM') 不会匹配任何内容。
  • 一个 token 是由字母和数字组成的连续字符。hasToken 只能接受一个 token——'node-agent' 或 'a b' 会报错。请按标点符号拆分,并使用 hasAllTokens,或者改用 lower(body) LIKE。
  • hasAllTokens 表示“包含所有这些词”,而非“包含这个词组”。只有在词语相邻顺序很重要时,才同时添加 lower(body) LIKE '%…%'。
  • token 必须完整匹配:hasToken(lower(body), 'timeo') 不会匹配 timeout。要匹配部分单词,请使用 lower(body) LIKE '%timeo%'。
SELECT toDate(timestamp) AS d, workload, count(*) AS n
FROM logs
WHERE $__timeFilter(timestamp) AND $__clusters
  AND hasAnyTokens(lower(body), ['6abffe8am1sw6', '636ce0fe6khta'])
GROUP BY d, workload ORDER BY d, n DESC

在生产集群的一小时数据上测得(按时间裁剪后有 9,640 个 granule):裸 body LIKE '%…%' 读取了全部 9,640 个;lower(body) LIKE 读取了 213 个;hasToken 读取了 202 个。

先缩小范围,再扩大搜索。 确定条件后,添加 workload / namespace / type,并从一小时开始,而不是几天。每次只运行一个 body 搜索——不要将同一个搜索拆分到多个时间片并行执行;每个搜索都会与数据摄取争用同一块磁盘。validate-sql 无法对此发出警告:未使用索引的 body 扫描是完全有效的 SQL。

validate-sql

对于包含 CTE、联接或你以前未用过的字段的查询,请在调用 execute-sql 之前调用它。它不会扫描数据,因此可以放心重复调用。

它会检查两项内容:

  1. 重写结果——解析、单条语句、仅限 SELECT、表访问、受阻止的函数和列名。列检查仅涵盖顶层 SELECT 列表中的裸列引用;查询包含 WITH 子句时,则完全跳过此检查。
  2. ClickHouse 自身对重写后查询的分析,用于解析每个标识符。这会捕获 WHERE、GROUP BY 或 CTE 内的错误列名。

成功时,它会在 ## rewritten_sql 下返回实际将要运行的 SQL。请阅读它。 它会显示每个标签对应的存储列(level AS type、pod_name AS instance),这是确认你引用了目标字段的唯一方法,也是最快发现名称悄然解析为其他字段的方法。

读取输出

execute-sql 返回 TSV:一行 # signal=… rows=N,接着是你自己的 SELECT 列,按你指定的顺序排列的标题行,然后每个结果占一行。

# signal=logs rows=2
workload	cnt
checkout	412
payments	87

空结果只有标题行。这表示查询已运行,但没有匹配项——这不是失败,通常也不应该通过重写查询来应对。请先检查时间范围和集群筛选条件。

限制

  • 除非查询本身包含 LIMIT,否则最多返回 1000 行。应在 SQL 中聚合,而不是拉取数据行来计数。
  • 仅支持只读执行。INSERT / ALTER / DROP、多条语句,以及访问表外部资源的函数(sleep、url、s3、file、remote、mysql)都会被拒绝。
  • 集群筛选会在表扫描时应用,无法通过查询绕过。

警告

受范围限制的角色无法使用这两个工具。 如果 API 密钥或角色仅限于特定集群、命名空间或工作负载,则调用会被拒绝——SQL 层只应用集群筛选,无法强制执行命名空间或工作负载规则。请改用 search-* / analyze-*,它们会执行这些规则。拒绝消息会说明原因;不要重试,也不要尝试在 SQL 中自行指定范围。

示例

在一个结果中比较本小时与昨天同一时段

SELECT service,
       countIf(timestamp >= $__toTime - INTERVAL 1 HOUR) AS now_errors,
       countIf(timestamp <  $__fromTime + INTERVAL 1 HOUR) AS then_errors
FROM traces
WHERE $__timeFilter(timestamp) AND $__clusters AND status = 'error'
GROUP BY service HAVING now_errors > then_errors ORDER BY now_errors DESC

按服务查找最慢的端点(窗口函数)

SELECT service, resource, p95_ms FROM (
  SELECT service, resource, quantile(0.95)(duration_ms) AS p95_ms,
         row_number() OVER (PARTITION BY service ORDER BY quantile(0.95)(duration_ms) DESC) AS rn
  FROM traces
  WHERE $__timeFilter(timestamp) AND $__clusters AND role = 'server'
  GROUP BY service, resource
) WHERE rn = 1 ORDER BY p95_ms DESC

记录了错误但没有产生失败 span 的工作负载(CTE 联接)

WITH bad_logs AS (
  SELECT DISTINCT workload FROM logs
  WHERE $__timeFilter(timestamp) AND $__clusters AND type = 'ERROR'
)
SELECT workload FROM bad_logs

然后再查询一次 traces——这两种信号位于不同的表中,无法在一条语句中联接。请自行关联结果,或使用 analyze-telemetry,它会在一次调用中组合这些信号。

更多模式:references/recipes.md。

规则

  1. 优先尝试 analyze-*。仅在需要跨聚合进行联接、CTE、窗口函数或算术运算时使用 SQL。如果条件可以写成 where 字符串(高级查询语言),就不应使用 SQL。
  2. 编写查询前,针对相应时间窗口调用 get-trace-or-log-fields。
  3. 对任何非简单查询,先调用 validate-sql,再调用 execute-sql,并且阅读它返回的 rewritten_sql。
  4. 仅使用 FROM logs / FROM traces,并与 signal 相匹配。绝不能使用带数据库限定符的名称。
  5. 使用目录中的标签,而非存储列:用 type 而不是 level,用 instance 而不是 pod_name,用 service 而不是 app_service,用 domain 而不是 customer_identifier。
  6. traces 上的 duration_ms(毫秒)。duration 会被拒绝——不要将 analyze-traces 中的纳秒阈值沿用到这里。
  7. timestamp 在两种信号上都有效,尽管发现结果中不会列出它。
  8. 字符串值使用单引号——这是 SQL,不是 where 小型语言。
  9. 属性键不会经过验证。列值全为空表示键错误,而不是缺少数据。
  10. 空结果集也是一种答案。改写查询前先检查时间窗口。
  11. 在 SQL 中进行聚合;绝不要通过分页拉取数据行来计算总数。最多 1000 行。
  12. Logs 和 traces 无法在一条语句中联接——它们是不同的表。
  13. 如果受范围限制的角色调用遭拒,请改用 search-* / analyze-*。
  14. 绝不要在 SQL 中直接筛选 body。请在 lower(body) 上使用 hasToken / hasAnyTokens / hasAllTokens,并传入小写的搜索词;或者使用 lower(body) LIKE '%…%'——其他做法都会完整扫描消息列。
  15. 每次只进行一次 body 搜索,并使用能回答问题的最窄时间窗口和筛选条件。绝不要并行运行这些搜索。

评论

支持 Markdown