Go back

New RelicでFour Keysを計測する基盤をGitHub Actionsで作った

Posted on:

New RelicでDORAのFour Keys(デプロイ頻度・変更のリードタイム・変更失敗率・平均修復時間)を計測するサンプルリポジトリを作った記録。リポジトリはredamoon-org/four-keysに置いた。

New Relicには元々GitHubインテグレーションがあるが、個人アカウントのリポジトリでは連携できないという壁にぶつかった。この記事は、その回避策としてGitHub Actions + New Relic Event APIでカスタムイベントを送る構成にした過程と、途中でハマった箇所をまとめる。

New RelicのGitHub連携はOrganizationが必要

最初はNew RelicのCI/CD可観測性機能(GitHub連携)をそのまま使うつもりだった。ところが設定画面まで進んでわかったのは、この連携がGitHub Organizationのリポジトリを前提にしているということだった。redamoon-orgは今回のために作ったOrganizationだが、個人アカウント直下のリポジトリでは同じ導線に乗らない。

Organization化してAppを入れ直す選択肢もあったが、今回は学習目的のサンプルリポジトリなので、New RelicのEvent APIにGitHub Actionsから直接カスタムイベントを送る構成に切り替えた。公式インテグレーションが使えなくても、Event APIさえ叩ければ同じNRQLの土俵に乗る。

GitHub Cloud integrationの許可反映に時間がかかった

redamoon-org側でも公式のGitHub Cloud integrationを試したが、設定画面の意味がすぐには理解できず、最初は反映がうまくいっていないように見えた。手順はNew Relic側でGitHub Appのインストールを進め、Organization側の画面で対象リポジトリへのアクセスを許可する形なのだが、許可した直後にNew Relicの画面を開いても対象リポジトリが一覧に出てこない。設定を間違えたのかと何度か見直したが、少し時間を置いてから開き直すと、許可したリポジトリがきちんと表示されていた。権限の反映に多少のタイムラグがあるらしい。

構成

GitHubイベント → GitHub Actions workflow → New Relic Event API → NRQL / Dashboard

3種類のworkflowで3種類のカスタムイベントを送る。

workflow トリガー イベント 対応指標
deployment-tracking.yml mainへのpush / Release公開 FourKeysDeployment デプロイ頻度
change-lead-time.yml PRマージ FourKeysChange 変更のリードタイム
incident-tracking.yml incidentラベル付きIssueのopen/close FourKeysIncident 変更失敗率 / MTTR

Event APIへの送信部分は.github/actions/send-nr-eventという composite action に切り出して3つのworkflowから共通で使っている。

# .github/actions/send-nr-event/action.yml
runs:
  using: "composite"
  steps:
    - name: POST event to New Relic
      shell: bash
      run: |
        set -euo pipefail

        if [ "${{ inputs.region }}" = "eu" ]; then
          ENDPOINT="https://insights-collector.eu01.nr-data.net/v1/accounts/${{ inputs.account-id }}/events"
        else
          ENDPOINT="https://insights-collector.newrelic.com/v1/accounts/${{ inputs.account-id }}/events"
        fi

        HTTP_STATUS=$(curl -sS -o /tmp/nr-event-response.json -w '%{http_code}' \
          -X POST "$ENDPOINT" \
          -H "Api-Key: ${{ inputs.license-key }}" \
          -H "Content-Type: application/json" \
          -d '${{ inputs.payload }}')

        if [ "$HTTP_STATUS" -ge 300 ]; then
          echo "::error::Failed to send event to New Relic (HTTP $HTTP_STATUS)"
          exit 1
        fi

呼び出し側はjqactions/github-scriptでJSONペイロードを組み立てて、このactionにcompact JSONの文字列を渡すだけ。例えばデプロイ頻度はmainへのpushとRelease公開をそのまま「デプロイ」とみなしている。

- name: Build payload
  id: payload
  run: |
    TIMESTAMP_MS=$(($(date +%s%N) / 1000000))
    PAYLOAD=$(jq -nc \
      --arg eventType "FourKeysDeployment" \
      --arg repository "${{ github.repository }}" \
      --arg sha "${{ github.sha }}" \
      --argjson deployedAt "$TIMESTAMP_MS" \
      '{eventType:$eventType, repository:$repository, sha:$sha, deployedAt:$deployedAt}')
    echo "payload=$PAYLOAD" >> "$GITHUB_OUTPUT"

New Relic側が「pushされた」ことを能動的に検知する手段はないので、デプロイ頻度を測るにはこの手のGitHub Actions側のトリガーが必須になる。公式インテグレーションを使う場合でも、結局GitHub Appのwebhookが同じ役割を担っているはずで、仕組み自体はそう変わらない。

workflowスコープ不足でpushが弾かれた

.github/workflows/配下のファイルを含む状態で最初のpushをしたところ、これで弾かれた。

! [remote rejected] main -> main (refusing to allow an OAuth App to create or update workflow
`.github/workflows/change-lead-time.yml` without `workflow` scope)

gh auth statusで確認すると、認証トークンのスコープがgist, read:org, repoworkflowが抜けていた。GitHubはOAuthアプリ経由の認証でworkflowファイルを含むpushをする場合、workflowスコープを明示的に要求する仕様になっている。

gh auth refresh -h github.com -s workflow

これでブラウザ経由の再認可を挟めばスコープが追加される。gh auth statusのトークンスコープにworkflowが増えていることを確認してからgit pushをやり直せば通った。GitHub Actionsのworkflowファイルを新規に追加するリポジトリでは最初に踏みやすい落とし穴だと思う。

Issueベースの計測が一番シンプルだった

3つのイベントを実装してみて、一番実装も運用もシンプルだったのはincident-tracking.ymlだった。

on:
  issues:
    types: [opened, labeled, closed]

jobs:
  send-incident-event:
    if: contains(github.event.issue.labels.*.name, 'incident')

incidentラベルを付けたIssueをopen/closeするだけで、障害発生と復旧が記録される。デプロイ頻度やリードタイムはpushやPRマージという「本来の開発フロー」に計測を割り込ませる必要があるのに対し、障害管理は元々Issueで完結する運用が多いので、ラベルを1つ足すだけで済んだ。

復旧時間の計算もシンプルで、issue.created_atissue.closed_atの差分を秒数にしているだけ。

if (action === "closed" && issue.closed_at) {
  const openedAt = new Date(issue.created_at);
  const closedAt = new Date(issue.closed_at);
  payload.restoreTimeSeconds = Math.max(
    0,
    Math.round((closedAt - openedAt) / 1000)
  );
}

変更失敗率もこのFourKeysIncidentのopen件数とFourKeysDeploymentの件数を突き合わせるだけで出せる。NRQLは複数イベント種別をまたぐJOINができないので、2つのウィジェットを並べて見比べる形にした。

FROM FourKeysIncident SELECT count(*) WHERE action = 'opened' SINCE 4 weeks ago
FROM FourKeysDeployment SELECT count(*) SINCE 4 weeks ago

複数リポジトリで運用する場合、Issueをどこに集約するかも一度考えた。結論としては、Issueは各リポジトリに残したまま、GitHub Projects(Organizationレベルのプロジェクトボード)で横断的に見る形が良さそうだと思っている。incident-tracking.ymlはペイロードにrepository: ${{ context.repo.owner }}/${{ context.repo.repo }}を積んでいるので、New Relic側はFACET repositoryでリポジトリ別の内訳も、facetを外せば全リポジトリ合算の値も自由に出せる。集約はNew Relicのクエリ側に任せれば十分で、GitHub Projectsは各リポジトリのIssueをそのまま参照してボードに束ねるだけなので、この仕組みを壊さない。

逆に1つの「incidents」リポジトリにIssueそのものを集約してしまうと、どのリポジトリの障害かをラベルや本文で手動管理し直す必要が出て、workflow側にも「どのリポジトリのイベントか」を判定するロジックが要る。それをやってもNRQLのFACET repository以上のものは得られないので、複数リポジトリでもIssueは各リポジトリに残す方が手間が少ない。

ダッシュボードは手動インポート止まり

New Relicのダッシュボードはdocs/dashboard.jsonとしてリポジトリに置いていて、{{ACCOUNT_ID}}を実際のアカウントIDに置換した上でDashboards → Import dashboardからJSONを貼り付ける運用にした。ここは今のところ手作業。

New RelicはNerdGraph経由でdashboardCreate / dashboardUpdateのmutationを公開しているので、docs/dashboard.jsonをworkflowから読み込んでNerdGraph APIに投げるstepを足せば、ダッシュボードの作成・更新もGitHub Actionsに寄せられるはずだ。今回はサンプルリポジトリの範囲を超えるので見送ったが、次に触るときはここを自動化したい。

実際にインポートして、試しに1回分のデータを計測させてみたダッシュボードがこちら。継続運用したわけではなく、あくまで動作確認としての1回分。

New Relicに取り込んだFour Keysダッシュボード。デプロイ頻度・デプロイ回数/週・中央値リードタイム・インシデント件数・平均復旧時間・MTTR推移・デプロイ件数の7枚のウィジェットが並ぶ

deployment-tracking.ymlが実際にmainへのpushで発火し、デプロイ頻度のグラフに1本線が立っている。デプロイ回数/週は0.25(4週間で1回)。一方でPRマージとincidentラベルのIssueはまだ発生させていないので、リードタイムとインシデント件数は0、MTTRはNo valueのまま。データが1件も無いウィジェットは平均を取りようがないのでaverage()の結果がそのまま空になる、という挙動もここで確認できた。

AIツールのコストとFour Keysを掛け合わせたい

Four Keysで開発の速度と安定性を見えるようにした次は、そこにAIツールのコストを重ねられないかと考えている。まだ実装はしておらず、検討段階のメモ。

New Relic Preflight(Claude CodeやCursorの利用状況を計測するOSS。以前Zennに導入記事を書いた)には、すでにこの方向に近いMCPツールが用意されている。nr_observe_get_cost_per_outcomenr_observe_get_platform_comparisonの2つを試しに叩いてみた。

// nr_observe_get_cost_per_outcome の結果(抜粋、自分の手元データ)
{
  "outcome_distribution": {
    "documentation": { "count": 3, "totalCost": 1.1004, "avgCost": 0.3668 },
    "feature": { "count": 2, "totalCost": 0.1333, "avgCost": 0.0666 },
    "investigation": { "count": 1, "totalCost": 0.0403, "avgCost": 0.0403 }
  },
  "total_cost": 1.274,
  "roi_estimate": {
    "totalAiCost": 1.274,
    "estimatedHoursSaved": 11.5,
    "estimatedValueUsd": 862.5,
    "roi": 861.23
  }
}

タスクの性質(documentation / feature / investigation)ごとにAIコストを分けた上で、削減できたと見積もった時間と金額からROIまで出してくれる。ちょうど自分がやりたかった「コストに対して価値提供の速度感がわかる」比較そのものだった。

nr_observe_get_platform_comparisonmetric: "cost"を渡すと、ツール間の比較も出る。

{
  "platforms": {
    "claude-code": {
      "session_count": 8,
      "average": 3.58,
      "visibility_level": "full-hooks"
    },
    "cursor": {
      "session_count": 2,
      "average": 0,
      "visibility_level": "full-hooks"
    },
    "generic-mcp": {
      "session_count": 3,
      "average": 1.6,
      "visibility_level": "self-reported"
    }
  }
}

ただし、この数字をそのまま鵜呑みにはできない。まずvisibility_levelがツールごとに違う。CursorのaverageがClaude Codeよりかなり低く出ているのは、実際にコストが低いというより、計測できているデータの粒度が違うせいの可能性が高い(過去のPreflight記事で書いた、Cursor側のイベントがgeneric-mcp扱いに落ちやすいという判定の癖の影響もありそう)。レスポンス自体にも「visibility_levelが複数レベルにまたがる場合は比較に注意」という但し書きが入っている。

nr_observe_get_cost_per_toolの方はattributionRateが0.3程度で、全体コストのうち3割ほどしかツール単位に帰属できていないこともわかった。ROI推定のhoursSaved / valueUsdも実測ではなく見積もりロジックによる概算で、outcomeの分類自体もサンプル数が数件しかない。参考値として見る分にはいいが、そのまま経営指標として出すには心もとない数字だった。

Four Keysと組み合わせる方向としては、今のところ次のように考えている。

  • 同じNew RelicアカウントにFourKeysDeploymentとPreflightのコストイベントが両方入っているので、まずは時間軸(週次・月次)で並べて相関を見るところから始める
  • PR単位の厳密な紐付け(このPRはAIツールをどれだけ使ったか)は、今のPreflightのセッション/ターンとGitHub側のPR/コミットを結びつける仕組みが無いのでまだ無理
  • 次にやるなら、Preflightのoutcome分類(feature / bug fix / refactorなど)とGitHub側のPRラベルを揃えて、粒度だけでも寄せる工夫が要りそう

実装するかどうかはまだ決めていないが、Preflightのこのツール群を見て、ゼロから作らなくても土台は既にある、という感触は持てた。

まとめ

  • New RelicのGitHub連携はOrganization前提。個人アカウントで使うにはEvent APIへの直接送信に切り替える必要があった
  • デプロイ頻度の計測にはGitHub Actions側のトリガーが必須。New Relicが受動的に検知する手段はない
  • workflowスコープ不足によるpush失敗はgh auth refresh -h github.com -s workflowで解決できる
  • 4つの指標のうち、変更失敗率とMTTRのIssueベース計測が一番実装コストが低かった
  • ダッシュボードのインポートはNerdGraphで自動化できる余地がまだ残っている
  • AIツールのコストとFour Keysの掛け合わせは、New Relic PreflightのROI試算ツール群が土台になりそうだが、精度面はまだ検証が必要

サンプルリポジトリ全体はredamoon-org/four-keysにある。