mcpField
R
root-cause-analysis

root-cause-analysis

github.com/kopai-appクラウド・DevOps1044 回インストール1 日前に更新
リポジトリ

root-cause-analysis について

root-cause-analysisは、OpenTelemetryのトレース、ログ、メトリクスを使って本番環境の問題を調べるKopai向けスキルです。TypeScript SDKのクエリでエラーや証拠を関連付け、影響を測定して原因候補を報告します。

主な機能

  1. 01型付きTypeScript SDKクエリ
  2. 02トレース、ログ、メトリクスの分析
  3. 03エラー率とスループットによる順位付け
  4. 04時系列による影響分析
  5. 05トレースとログの相関
  6. 06SQLiteクエリの制約説明

活用シーン

  1. 01インシデント対応でのサービス順位付け
  2. 02関連ログをたどったエラー調査
  3. 03問題の発生時期と影響範囲の測定
  4. 04レイテンシやメトリクス変化の調査

MCP 対応

Kopai SDKとCLIを使うスキルであり、MCPサーバーやクライアントではありません。READMEにMCP連携の説明はありません。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/kopai-app/kopai-mono --skill root-cause-analysis

インストール後、Claude Code で/root-cause-analysisを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

tile.json
references/
log-filters.md
metric-filters.md
trace-filters.md
rules/
_sections.md
pattern-distributed.md
pattern-http-errors.md
pattern-log-driven.md
pattern-slow-requests.md
workflow-check-metrics.md
workflow-correlate-logs.md
workflow-find-errors.md
workflow-get-context.md
workflow-identify-cause.md

詳細

Kopai による根本原因分析

テレメトリ(トレース、ログ、メトリクス)から本番環境の問題をデバッグします。Kopai の分析機能であるエラー率、スループット、レイテンシー、グループ化、時系列のバケット化は、SDK のクエリ APIに含まれており、CLI では利用できません。そのため、調査は必ず短い TypeScript スクリプトから始め、CLI は簡単な参照用の代替手段として使います。

前提条件

  • サービスから Kopai に OpenTelemetry データが送信されていること(otel-instrumentation スキルを参照)。
  • プロジェクトに @kopai/sdk がインストールされていること:npm i @kopai/sdk(クエリ API と .kopairc リーダーが含まれています)。

コードモード(推奨)

接続 — clientFromConfig() は CLI と同じ方法で .kopairc を読み込みます:

import { clientFromConfig } from "@kopai/sdk/node";
// Reads ./.kopairc then ~/.kopairc; defaults to http://localhost:8000.
// Override any field: clientFromConfig({ url, token, configPath, timeout }).
const client = clientFromConfig();

実行 — npx tsx rca.mts を実行します。.mts 拡張子を使ってください。これは常に ES モジュールとして扱われるため、ホストプロジェクトの package.json の "type" に依存せず、トップレベルの await が動作します。(tsx がない場合:node --experimental-strip-types rca.mts。)

kq でクエリを作成し、client.query(q) で実行します。 client.query(q) は、作成したあらゆるクエリを実行するシンプルな方法で、型付けが完全に行われています。行の入力補完が利用でき、タイプミスはコンパイルエラーになります。集計クエリは { data } を返し、生クエリは { data, nextCursor } を返します。結果の行は完全に型付けされているため、any/any[] にキャストせず、data を直接反復処理してください。 集計値は number 型なので(row.error_rate * 100 がそのまま動作し、String(...) やキャストは不要です)、グループ化されたディメンションも利用でき(row["service.name"])、.timeSeries() の行には bucket_start が追加されます:

const { data } = await client.query(q); // typed rows — do NOT write `data as any[]`
for (const row of data) {
  console.log(
    row["service.name"],
    `${(row.error_rate * 100).toFixed(1)}%`,
    row.rps
  );
}

出力 — 標準出力が結果です。最後に console.log(JSON.stringify(data, null, 2)) を出力し、.build() とクエリの呼び出しを try/catch で囲んでください:

import { kq, KopaiQueryBuildError } from "@kopai/sdk";
try {
  const q = kq.traces
    .aggregate() /* … */
    .build();
  const { data } = await client.query(q);
  console.log(JSON.stringify(data, null, 2));
} catch (e) {
  if (e instanceof KopaiQueryBuildError)
    console.error(e.issues); // {path,message}[]
  else throw e;
}

バックエンドの注意点と落とし穴(クエリを実行する前にお読みください)

  • すべてのクエリに時間範囲が必要です — .timeRelative("1h") または .timeAbsolute(startISO, endISO) を指定します。ルックバック期間/粒度は ^[1-9]\d*[smhdw]$("30s"、"15m"、"2h"、"7d")に一致します。
  • StatusCode は正確に "Unset" | "Ok" | "Error" です(タイトルケース)。成功したスパンは通常 "Ok" ではなく "Unset" です。この列挙型は型チェックされるため、"ERROR" のような誤った値はコンパイルエラーになり、大文字・小文字の間違いは自動的に検出されます。いずれにしても、値を直接フィルタリングするより、サーバー側でエラー数を数える errorRate メジャーを優先してください。
  • ServiceName ではなく、service.name(ドット区切り)を使用してください。 ServiceName はクエリ可能な列ではないため、これでフィルタリングしても何も一致しません(エラーも発生しません)。結果行では、グループ化したキー名で値が返されます。例:row["service.name"]。
  • 負荷の高いシステムで結果が空の場合、実際にデータがないのではなく、列名または値が間違っている可能性があります。 「エラーなし/データなし」と判断する前に、列名と値の大文字・小文字を再確認してください。空の結果がエラーなく返ることが、誤って「すべて正常」と判断する最大の原因です。
  • パーセンタイル(p50~p999)は ClickHouse 専用です。 SQLite ではクエリ実行時に失敗します(KopaiError: Percentile measures … not yet supported on the sqlite backend)。まずは Duration の avg/max でレイテンシーを確認し、パーセンタイルは ClickHouse へのアップグレードとして try/catch で扱ってください。
  • Duration フィルターでは、単位 s/m/h/d/w の期間文字列を指定できます — f.gt("Duration", "1s")、f.lte("Duration", "2h")(gte/lt も同様)。1 秒未満の単位は使えません。1 秒未満のしきい値には、ナノ秒単位の数値を渡してください。(結果行の Duration は引き続きナノ秒で返されます。Duration の avg/max も ns 単位で返されます。1ms = 1e6、1s = 1e9。)
  • メトリクスクエリでは、最初に型を選択します。 kq.metrics("Gauge")…、kq.metrics("Sum")…(型 ∈ "Gauge" | "Sum" | "Histogram" | "ExponentialHistogram" | "Summary")。ビルダーの引数によって MetricType が自動的に固定されるため、.where(f => f.eq("MetricType", …)) を手動で指定する必要はありません。値の列は型ごとに異なります。Gauge/Sum → "Value"、Histogram/ExponentialHistogram → "Count" | "Sum" | "Min" | "Max"、Summary → "Count" | "Sum"。
  • searchTraces/searchLogs/searchMetrics は非同期イテレーターです(自動ページネーション)。for await (const row of client.searchLogs({ … })) { … } で読み取ってください。配列として await してはいけません。単一ページを取得するには searchTracesPage/searchLogsPage/searchMetricsPage を使います。結果は { data, nextCursor } です。上限:kq の .limit() は 10000 件、search* のフィルターは 1000 件までです。

RCA ワークフロー

  1. 失敗している処理を見つける — エラー率とスループットでサービスを順位付けします。errorRate はサーバー側で StatusCode を処理するため、値を推測する必要はありません:

    const q = kq.traces
      .aggregate()
      .measure((m) => m.errorRate("error_rate"))
      .measure((m) => m.throughput("rps"))
      .measure((m) => m.count("spans"))
      .dimension("service.name")
      .timeRelative("1h")
      .summary()
      .orderByMeasure("error_rate", "desc")
      .build();
    const { data } = await client.query(q);
    

    ログを起点にトリアージする場合は、SeverityNumber >= 17 でエラーレベルのログを取得します(テキストの大文字・小文字に関係なく ERROR/FATAL を検出します): client.query(kq.logs.raw().where(f => f.gte("SeverityNumber", 17)).timeRelative("1h").limit(50).build())。 workflow-find-errors を参照してください。

  2. トレースの完全なコンテキストを取得する — const spans = await client.getTrace(traceId)。Duration(ボトルネック)、ParentSpanId(呼び出しチェーン)、StatusMessage、SpanKind を調べます。 workflow-get-context を参照してください。

  3. ログをトレースに関連付ける — client.query(kq.logs.raw().where(f => f.eq("TraceId", traceId)).timeRelative("1h").build()) (または client.searchLogs({ traceId }) を反復します)。最も早いエラーとその スタックトレースを探します。workflow-correlate-logs を参照してください。

  4. 影響を定量化し、発生時点を特定する — CLI ではできないアップグレードです。メトリクスを時間でバケット化し、いつ始まったのか、影響範囲がどれほど広いのかを確認します。保持期間の下限に注意してください。最も早いバケットですでに値が上昇している場合、実際の発生時点はその時点またはそれ以前です。下限を「段階的な変化」と呼ばず、その旨を伝えてください:

    const q = kq.traces
      .aggregate()
      .measure((m) => m.errorRate("error_rate"))
      .measure((m) => m.avg("Duration", "avg_ns"))
      .measure((m) => m.max("Duration", "max_ns"))
      .dimension("service.name")
      .timeRelative("3h")
      .timeSeries("5m")
      .orderByMeasure("error_rate", "desc")
      .build();
    const { data } = await client.query(q);
    

    信号がトレースではなくメトリクスの場合は、最初に種類を選択して MetricType を自動設定します。たとえば、Gauge を時間でバケット化します:

    const q = kq
      .metrics("Gauge")
      .aggregate()
      .measure((m) => m.avg("Value", "avg_value"))
      .where((f) => f.eq("MetricName", "process.cpu.utilization"))
      .timeRelative("3h")
      .timeSeries("5m")
      .build();
    const { data } = await client.query(q);
    

    workflow-check-metrics を参照してください。

  5. 調査結果を提示する — 証拠(具体的な TraceId、ログエントリー、メトリクスの差分)を添えた根本原因、影響範囲、推奨される修正を示します。インシデントダッシュボードの作成を提案してください(create-dashboard スキルを参照)。workflow-identify-cause を参照してください。

簡単な例

// rca-triage.mts — run: npx tsx rca-triage.mts
import { kq, KopaiQueryBuildError } from "@kopai/sdk";
import { clientFromConfig } from "@kopai/sdk/node";
const client = clientFromConfig();

try {
  const q = kq.traces
    .aggregate()
    .measure((m) => m.errorRate("error_rate"))
    .measure((m) => m.throughput("rps"))
    .measure((m) => m.avg("Duration", "avg_ns"))
    .measure((m) => m.max("Duration", "max_ns"))
    .measure((m) => m.count("spans"))
    .dimension("service.name")
    .timeRelative("1h")
    .summary()
    .orderByMeasure("error_rate", "desc")
    .build();

  const { data } = await client.query(q); // typed rows
  console.log(JSON.stringify(data, null, 2));
} catch (e) {
  if (e instanceof KopaiQueryBuildError) console.error(e.issues);
  else throw e;
}

ルール

1. ワークフロー(最重要)
  • workflow-find-errors - エラートレースとエラーレベルのログを見つける
  • workflow-get-context - トレースの完全なコンテキストを取得する
  • workflow-correlate-logs - ログをトレースに関連付ける
  • workflow-check-metrics - 集計クエリ/時系列クエリで影響を定量化する
  • workflow-identify-cause - 根本原因を特定し、調査結果を提示する
2. パターン(高優先度)
  • pattern-http-errors - HTTP エラーのデバッグ
  • pattern-slow-requests - 遅いリクエストの分析
  • pattern-distributed - 分散障害のトレース
  • pattern-log-driven - ログを起点とする調査

詳細については rules/<rule-name>.md をお読みください。

ヒント

  1. 集計値(サービスごとのエラー率/レイテンシ)から始めて、1 つのトレースを詳しく調べます。
  2. StatusCode でフィルターするよりも、errorRate メジャーを優先してください(サーバー側でカウントされます)。
  3. 構築したクエリは client.query(q) で実行します。完全に型付けされており(行の補完が利用でき、タイプミスはコンパイルエラーになります)。
  4. Duration は文字列でフィルターします(f.gt("Duration", "1s"))。結果の行では、引き続き Duration がナノ秒で報告されます。トレースチェーンで最も早いエラーが、通常は根本原因に最も近いものです。
  5. .timeSeries(granularity) を使用して、回帰が いつ 始まったかを特定します。保持期間の下限を発生時点と取り違えないでください。
  6. メトリクスでは、最初に種類を選択します — kq.metrics("Gauge")…(MetricType を自動設定し、値の列を型付けします)。
  7. SQLite では、レイテンシは Duration の avg/max です。ClickHouse では p95/p99 に切り替えてください。

CLI による代替手段(単発のクエリ向け)

CLI は単一の検索には適していますが、クエリ/集計コマンドはありません。 StatusCode の値はリテラルの "Error" であることに注意してください。

npx @kopai/cli traces search --status-code Error --limit 20 --json
npx @kopai/cli traces get <traceId> --json
npx @kopai/cli logs search --trace-id <traceId> --severity-min 17 --json
npx @kopai/cli metrics discover --json

参考資料

コメント