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. 03SQLを実行してTSV行を取得
  4. 04時刻とクラスターのマクロ利用
  5. 05インデックス付きログ検索の案内

活用シーン

  1. 01ログまたはトレース結果の結合
  2. 02単一クエリでの期間比較
  3. 03CTEやウィンドウ関数による導出
  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 を参照してください。ここにあるツールは、指定した signal に応じて、logs モジュールまたは traces モジュールに対して RBAC を解決します。

ツール

ツール使用するタイミング戻り値
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 を使用してください。

質問の形ツール
グループ化したカウント / レート / p95 / 上位N件analyze-logs、analyze-traces
2つのメトリクスを割る(エラー率%)数式を指定した analyze-telemetry
個々の行を読み取るsearch-logs、search-traces
2つの結果セットを結合するSQL
CTE の連鎖 / 複数段階の導出SQL
ウィンドウ関数(row_number、lag、累積合計)SQL
1回の処理で2つの集計値間の演算を行うSQL
UNION、DISTINCT ON、argMax、quantileIfSQL
1つの結果で2つの時間範囲を比較するSQL

SELECT field, count(*) ... GROUP BY field を書き始めたら、そこで止めてください。これは value_operation: row_count を指定した analyze-* で実行できます。

フィルター条件は、SQL を使う理由にはなりません

search-* / analyze-* の where 引数は、Logs ページと Traces ページの Advanced クエリタブと同じ言語です。条件をそこに記述できるなら、そこで記述してください。サーバーは body 検索をテキストインデックス形式に書き換え、列だけを使うフィルターを事前集計済みのロールアップテーブルにルーティングします。SQL ではどちらも行われません。SQL は常に未加工のテーブルを読み取り、入力されたとおりの述語を実行します。

次の順に判断してください。

  1. 条件を where 文字列で記述できますか。= != < > LIKE ILIKE SUBSTR_ILIKE IN、AND / OR / NOT、@attr フィルター、body ILIKE "%…%" などが使えます。→ search-* / analyze-* を使用してください。これで、「…に該当する行を検索 / 件数を数える」というケースのほとんどに対応できます。
  2. 結果の形だけが合わないのでしょうか(結合、CTE、ウィンドウ関数、集計値間の演算など)。→ SQL を使用し、同じ条件を使ってください。body に関するものには、以下の ログテキストの検索 の手順に従ってください。
  3. 条件自体に、where 言語では使えないもの(正規表現、2つの列間の比較、列に対する関数)が必要ですか。→ SQL を使用し、まずスキャン範囲を絞るため、インデックス付きの述語も併用してください(workload / namespace の等価条件や、正規表現に含まれる必要がある語に対する hasToken(lower(body), …) など)。

1つのフィールドに複数の値を指定する場合は、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_msトレースミリ秒単位です。ディスカバリーには duration(ナノ秒)と表示されますが、SQL ではその名前は拒否されます
primary_namespace, primary_workloadトレース視点を示す列です
body_lengthログlength(body) として計算されます

カタログフィールドではありませんが、クエリ可能な項目もあります。cluster(domain と同じ列で、$__clusters が展開される先です)と、生の属性コンテナー(ログでは string_attributes/float_attributes、トレースでは 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')を使います。これは、分析/検索ツールの where 引数で、引用符なしの単語またはダブルクォートを使うのとは異なります。列挙値の大文字・小文字は変わりません。ログでは ERROR(大文字)、トレースの 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

警告

属性名は、どのツールでも、どの段階でも検証されません。 @ 式はクエリが解析される前にマップ検索へコンパイルされるため、キーのスペルを間違えてもエラーにはなりません。ログでは NULL、トレースでは '' が返されます。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), …) はどのインデックスにも一致しないため、クエリはメッセージ列全体を展開します。本番クラスターでこの方法を使って数日分を検索すると、テラバイト単位のデータを読み込み、全ユーザーの取り込み処理を停滞させることがあります。

search-logs と analyze-logs は、body ILIKE "…" をインデックス付きの形式に書き換えます。SQL では書き換えられません。記述したとおりに条件が実行されます。そのため、「X を含む行を検索する」にはこれらのツールを使い、SQL 内で検索する必要がある場合は、次のいずれかを記述してください。

検索対象記述する内容インデックス
1 つの単語または 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') は何も一致しません。
  • トークンは、英字と数字が連続した文字列です。hasToken が受け取れるのは 1 つだけです。'node-agent' や 'a b' はエラーになります。句読点で分割して hasAllTokens を使うか、lower(body) LIKE に切り替えてください。
  • hasAllTokens は「これらの単語をすべて含む」という意味であり、「このフレーズを含む」という意味ではありません。単語の並び順も一致させる必要がある場合に限り、lower(body) LIKE '%…%' も追加してください。
  • トークン全体が一致する必要があります。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

本番クラスターの 1 時間分で測定(時間による絞り込み後、9,640 個のグラニュール)したところ、body LIKE '%…%' は 9,640 個すべてを読み込み、lower(body) LIKE は 213 個、hasToken は 202 個を読み込みました。

対象を絞ってから広げてください。 分かっている場合は workload / namespace / type を追加し、数日分ではなく 1 時間から始めてください。body の検索は一度に 1 回 実行してください。同じ検索を時間帯ごとに分けて並列実行しないでください。それぞれが同じディスクを取り込み処理と奪い合います。validate-sql では警告できません。インデックスを使わない body のスキャンも、SQL としては完全に有効だからです。

validate-sql

CTE、結合、またはこれまで使ったことのないフィールドを含むクエリでは、execute-sql の前に呼び出してください。データをスキャンしないため、繰り返し呼び出してもコストは低く抑えられます。

次の 2 つを検査します。

  1. 書き換えの検証 — 構文解析、単一ステートメントであること、SELECT のみであること、テーブルアクセス、ブロック対象の関数、列名を確認します。列名の確認対象は、トップレベルの SELECT リストにある裸の列参照のみです。また、クエリに WITH 句がある場合は、列名の確認を完全にスキップします。
  2. 書き換え後のクエリに対するClickHouse 独自の解析。すべての識別子を解決します。これにより、WHERE、GROUP BY、または CTE 内にある不正な列を検出できます。

成功すると、実際に実行される SQL が ## rewritten_sql の下に返されます。必ず確認してください。 各ラベルがどのストレージ列に解決されたか(level AS type、pod_name AS instance)が示されます。これが、意図したフィールドを参照していることを確認する唯一の方法であり、名前がいつの間にか別のフィールドに解決されていた場合に、それをすばやく見つける方法です。

出力の読み方

execute-sql は TSV を返します。# signal=… rows=N の行に続き、ユーザーが指定した SELECT 列が指定した順序で並ぶヘッダー行、さらに結果ごとに 1 行が返されます。

# 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 でスコープを指定しようとしたりしないでください。

使用例

この 1 時間と昨日の同じ 1 時間を、1 つの結果で比較する

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

エラーをログに記録したものの、失敗したスパンを提供していないワークロード(CTE の結合)

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

次に 2 つ目の traces クエリを実行します。2 つのシグナルは別々のテーブルにあり、1 つのステートメントでは結合できません。結果は自分で関連付けるか、1 回の呼び出しでシグナルを組み合わせる analyze-telemetry を使用してください。

その他のパターン:references/recipes.md。

ルール

  1. まず analyze-* を試してください。SQL は、結合、CTE、ウィンドウ関数、または集計間の算術演算にのみ使用してください。条件を where 文字列(Advanced query language)で記述できる場合、SQL には含めないでください。
  2. クエリを作成する前に、対象期間について get-trace-or-log-fields を呼び出してください。
  3. 複雑なクエリは、execute-sql の前に validate-sql を呼び出し、返された rewritten_sql を必ず確認してください。
  4. FROM logs/FROM traces のみを使用し、signal と一致させてください。データベース名で修飾した名前は使用しないでください。
  5. ストレージ列ではなく、カタログのラベルを使用してください。level ではなく type、pod_name ではなく instance、app_service ではなく service、customer_identifier ではなく domain を使用します。
  6. トレースでは duration_ms(ミリ秒)を使用してください。duration は拒否されます。analyze-traces のナノ秒単位のしきい値を引き継がないでください。
  7. timestamp は、フィールド検出で一覧に表示されなくても、両方のシグナルで有効です。
  8. 文字列値には、一重引用符を使用してください。SQL であり、where のミニ言語ではありません。
  9. 属性キーの検証は行われません。列がすべて空の場合は、データが欠落しているのではなく、キーが間違っています。
  10. 結果セットが空でも、それが答えです。クエリを書き換える前に対象期間を確認してください。
  11. SQL で集計してください。合計を計算するために行をページ分割して取得してはいけません。上限は 1000 行です。
  12. ログとトレースは別々のテーブルにあるため、1 つのステートメントでは結合できません。
  13. スコープ制限付きのロールで呼び出しが拒否された場合は、search-*/analyze-* に切り替えてください。
  14. SQL で body をそのままフィルターに使用しないでください。小文字にした検索語を使って lower(body) に hasToken/hasAnyTokens/hasAllTokens を適用するか、lower(body) LIKE '%…%' を使用してください。それ以外の方法では、メッセージ列全体がスキャンされます。
  15. body の検索は、一度に 1 つだけ、質問に答える最も狭い期間とフィルターで実行してください。並列実行はしないでください。

コメント