关于 kubesense-sql
kubesense-sql 是用于通过 KubeSense SQL 工具查询日志和追踪数据的技能,适合需要连接、窗口函数等复杂结果形式的场景。它涵盖字段发现、SQL 验证与执行,并说明索引日志搜索和查询限制。
主要功能
- 01发现日志和追踪字段
- 02执行前验证只读 SQL
- 03运行 SQL 并返回 TSV 结果
- 04使用时间与集群过滤宏
- 05提供索引日志文本搜索指南
适用场景
- 01连接日志或追踪结果集
- 02在单个查询中比较时间范围
- 03用 CTE 或窗口函数推导结果
- 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-N | analyze-logs、analyze-traces |
| 两个指标相除(错误率 %) | 使用公式的 analyze-telemetry |
| 读取单独的行 | search-logs、search-traces |
| 连接两个结果集 | SQL |
| CTE 链 / 多步推导 | SQL |
窗口函数(row_number、lag、运行总计) | SQL |
| 单次查询中对两个聚合结果进行运算 | SQL |
UNION、DISTINCT ON、argMax、quantileIf | SQL |
| 在同一个结果中比较两个时间范围 | SQL |
如果你发现自己在写 SELECT field, count(*) ... GROUP BY field,请停下——这应使用带有 value_operation: row_count 的 analyze-*。
筛选条件绝不是使用 SQL 的理由
search-* / analyze-* 的 where 参数使用的语言,与 Logs 和 Traces 页面上的 Advanced 查询选项卡相同。如果条件能在那里编写,就应该在那里编写——服务端会将 body 搜索改写为文本索引形式,并将仅针对列的筛选路由到预聚合的汇总表。SQL 两者都不会做:它始终读取原始表,并按输入内容运行谓词。
请按以下顺序判断:
- 能否将条件写成
where字符串——=!=<>LIKEILIKESUBSTR_ILIKEIN、AND/OR/NOT、@attr筛选、body ILIKE "%…%"?→ 使用search-*/analyze-*。这几乎涵盖了所有“查找 / 统计符合……条件的行”的需求。 - 是否只有结果形状不适用(连接、CTE、窗口函数、对聚合结果进行运算)?→ 使用 SQL,保留相同的条件;对于
body中的内容,请参照下方的 搜索日志文本。 - 条件本身是否需要
where语言不支持的功能——正则表达式、两列之间的比较、列函数?→ 使用 SQL,并同时添加索引谓词以先缩小扫描范围(例如workload/namespace相等条件,或针对正则表达式必须包含的词使用hasToken(lower(body), …))。
同一字段有多个值时,应在 where 字符串中使用 IN 或 OR,这不是使用 SQL 的理由。
表
| 写法 | 读取内容 |
|---|---|
FROM logs | logs 表 |
FROM traces | traces 表 |
除此之外的内容均无法访问。带数据库限定符的名称(kube_logs.logs_combined)会被直接拒绝——从其他 SQL 工具粘贴查询时,这通常是最先出错的地方。signal 必须与所选的 FROM 表相匹配。
字段名称
与所有其他 KubeSense 工具使用相同的目录标签——即
get-trace-or-log-fields 返回的字段集合。不接受存储列名:
| 使用 | 不要使用 |
|---|---|
type | level |
instance | pod_name |
container | container_name |
domain | cluster(见下文)、customer_identifier |
service | app_service |
role | kind |
status_code | return_code |
method | subtype |
resource | clustered_resource |
reason | issue_reason |
request_type | is_external |
timestamp | start_timestamp |
duration_ms | duration |
SELECT * 会展开为目录列,而不是原始表列。
SQL 接受 4 个发现功能未列出的字段
get-trace-or-log-fields 会隐藏标记为不可筛选的字段,但 SQL 仍然接受这些字段。
不要因为发现结果中没有列出某个名称,就认定它无效:
| 字段 | 信号 | 说明 |
|---|---|---|
timestamp | 两者 | 发现结果中未列出,但每个查询都需要它来使用 $__timeFilter(timestamp) |
duration_ms | traces | 毫秒。发现结果会显示 duration(纳秒)——SQL 不接受该名称 |
primary_namespace、primary_workload | traces | 透视列 |
body_length | logs | 计算方式为 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 中搜索,请使用以下任一形式:
| 查找内容 | 写法 | 索引 |
|---|---|---|
| 一个单词或 ID | hasToken(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 之前调用它。它不会扫描数据,因此可以放心重复调用。
它会检查两项内容:
- 重写结果——解析、单条语句、仅限
SELECT、表访问、受阻止的函数和列名。列检查仅涵盖顶层 SELECT 列表中的裸列引用;查询包含WITH子句时,则完全跳过此检查。 - 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。
规则
- 优先尝试
analyze-*。仅在需要跨聚合进行联接、CTE、窗口函数或算术运算时使用 SQL。如果条件可以写成where字符串(高级查询语言),就不应使用 SQL。 - 编写查询前,针对相应时间窗口调用
get-trace-or-log-fields。 - 对任何非简单查询,先调用
validate-sql,再调用execute-sql,并且阅读它返回的rewritten_sql。 - 仅使用
FROM logs/FROM traces,并与signal相匹配。绝不能使用带数据库限定符的名称。 - 使用目录中的标签,而非存储列:用
type而不是level,用instance而不是pod_name,用service而不是app_service,用domain而不是customer_identifier。 - traces 上的
duration_ms(毫秒)。duration会被拒绝——不要将analyze-traces中的纳秒阈值沿用到这里。 timestamp在两种信号上都有效,尽管发现结果中不会列出它。- 字符串值使用单引号——这是 SQL,不是
where小型语言。 - 属性键不会经过验证。列值全为空表示键错误,而不是缺少数据。
- 空结果集也是一种答案。改写查询前先检查时间窗口。
- 在 SQL 中进行聚合;绝不要通过分页拉取数据行来计算总数。最多 1000 行。
- Logs 和 traces 无法在一条语句中联接——它们是不同的表。
- 如果受范围限制的角色调用遭拒,请改用
search-*/analyze-*。 - 绝不要在 SQL 中直接筛选
body。请在lower(body)上使用hasToken/hasAnyTokens/hasAllTokens,并传入小写的搜索词;或者使用lower(body) LIKE '%…%'——其他做法都会完整扫描消息列。 - 每次只进行一次 body 搜索,并使用能回答问题的最窄时间窗口和筛选条件。绝不要并行运行这些搜索。