PLAY DEVELOPERS BLOG

HuluやTVerなどの日本最大級の動画配信を支える株式会社PLAYが運営するテックブログです。

HuluやTVerなどの日本最大級の動画配信を支える株式会社PLAYが運営するテックブログです。

AWS DevOps Agentを活用したインシデント自動分析基盤の構築

こんにちは、PLAY CLOUD本部 技術推進グループの市川です。

PLAY CLOUDでは現在、システムの安定運用と運用負荷の低減を目指し、インシデント対応の自動化に取り組んでいます。 その一環として、AWS DevOps Agent を活用した、エラー通知から調査・Issue起票(PR作成)までを一気通貫で自動化する基盤を構築しました。
本番環境でエラーが発生すると、Slackにアラートが飛び、エンジニアがログを確認し、コードを調査し、GitHub Issueを起票し修正をする——この一連の作業を毎回手動で行うのは大きな負担です。 特にエラーの発生頻度が高い環境では、同じようなエラーに対して繰り返し調査コストがかかるという課題があります。

AWS DevOps Agent自体は高い精度でインシデントを分析してくれる優秀なサービスです。ただ、実際に運用に組み込もうとすると 痒いところに手が届かない点 がいくつかありました。

  • Slack連携が特定チャンネルへの新規メッセージとして投稿される: デフォルトのSlack連携では、調査結果は事前に設定したチャンネルにスレッドとは無関係な単発メッセージとして投稿されてしまいます。実運用では、問題が起きた元のスレッドに調査結果を返してほしいと思い、ここがそのままだと使いづらい。
  • 調査時間による従量課金で、呼びすぎると一気に高くなる: DevOps Agentは1回の起動あたりの分析時間に応じて課金されるため、同じ原因のエラーが連続発火するたびに毎回Agentを起動すると、コストが想定外に膨らみます。

そこで、AWS DevOps Agentをコアに据えつつ、その前段にSlack/CloudWatchの受け口と類似エラーの重複排除を、後段にSlack/Issueへの戻し先制御をそれぞれ自前で組み込んだ基盤を構築しました。Slackのエラー通知やCloudWatch Alarmをトリガーに、AI エージェントが自動でコードを調査し、GitHub/Backlog への Issue 起票や PR の自動作成、元の Slack スレッドに結果を返してくれます。さらに、類似エラーの重複排除により不要なAgent起動を抑制し、コスト最適化も図っています。

加えて、この基盤は1つのサービス専用の作り込みではなく、全社の複数プロダクトを横断する共通基盤として展開できるように設計しており、 すでに社内のいくつかのプロダクトで運用が始まっています。詳細は後述します。

本記事では、このシステムの全体像と、構築する上で工夫した技術的なポイントを解説していきたいと思います。

システム全体像

まずは構築したシステムの全体像を見ていきます。

全体構成

処理フロー

  1. Slackのエラー通知またはCloudWatch Alarmがトリガーとなる
  2. Lambdaがイベントを受信し、メッセージを正規化する
  3. DynamoDBで類似メッセージの重複チェックを行う(後述のSimHashを利用)
    • 類似ありかつ過去のIssueが紐づいている場合 → Slackスレッドに「関連Issue: #XX」を自動返信し、DevOps Agentは起動しない
    • 類似なしの場合 → 次のステップへ
  4. AWS DevOps AgentにWebhookでインシデント情報を送信する
  5. DevOps Agent が CloudWatch Logs や 自作 MCP サーバーNew Relic MCP サーバーGitHub MCP サーバー などを使ってコード/テレメトリ情報を収集し、根本原因を分析する
  6. Slack に調査の進捗を投稿しながら、分析結果から GitHub や Backlog に Issue/PR を作成し、作成した Issue 番号を DynamoDB に登録する

このフローにより、初回のエラーはDevOps Agentが調査・Issue作成を行い、2回目以降の類似エラーではAgentを起動せずに過去のIssueを参照できる仕組みになっています。 DevOps Agentは処理時間により課金されるため何度も同じものを調査せず、アラートが連続で発生したりする場合に効果的です。

それでは、このシステムを実現する上での技術的なポイントを順に見ていきましょう。

類似メッセージの重複排除

このシステムにおいて重要な要素の一つが、「類似エラーの重複排除」 です。同じ原因のエラーに対して毎回DevOps Agentを起動するのは無駄が多いため、過去のエラーと似た内容のものはAgentを起動せず、過去のIssueに紐づける仕組みを入れています。

ここで課題になるのが「どうやってエラーメッセージの類似性を判定するか」です。エラーメッセージには発生のたびに変わる要素(タイムスタンプ、PID、リクエストIDなど)が含まれるため、SHA-256のような暗号学的ハッシュで完全一致を見るだけでは「同じエラー」をうまく拾えません。

そこで、類似したテキストに近いハッシュ値を生成する SimHash (Locality-Sensitive Hashingの一種)を採用しています。タイムスタンプ等を正規化したテキストからSimHashを計算し、ハミング距離 が閾値以下のものを「類似」と判定する形です。

DynamoDB上で効率的に近傍検索するために、64bitのSimHashを4つの16bitブロックに分割し、各ブロックをGSIのパーティションキーにしています(ハミング距離が3以下のペアは少なくとも1つのブロックが完全一致するため、4つのGSIに並列クエリを投げて候補を絞り込めます)。

コストも、1日1,000件のアラートで月額約$0.23程度と、サーバーレス構成と相性のよい類似検索を実現できています。

AWS DevOps Agentとの連携

類似判定の結果、新規のエラーであると判断された場合は、AWS DevOps Agentを呼び出してインシデント分析を依頼します。

Webhook呼び出し

DevOps AgentへのWebhook送信はAWSの仕様に準拠する形で実装しています。

  • ペイロード形式: eventType, incidentId, action, priority, title, description, data
  • 認証: HMAC-SHA256署名(Base64(HMAC-SHA256(secret, timestamp:payload))
  • ヘッダー: x-amzn-event-signature, x-amzn-event-timestamp
  • リトライ: 5xxとタイムアウトのみ、指数バックオフで最大2回

特に重要なのがdescriptionフィールドへのメタデータの含め方です。DevOps Agentに data.metadata のような構造化データを正しく認識させるには、descriptionに明示的に記載するのが確実でした。後述するMCPサーバーの呼び出し時にもこのメタデータを利用するため、ここの設計はトレース全体の動作に大きく影響します。

ルーティング設定

Slackのチャンネルや、CloudWatchのアラームごとに、呼び出すDevOps Agentを切り替えられるようにしています。設定はSSM Parameter Store(SecureString)で管理し、Lambdaの再デプロイなしで変更可能にしました。新しいサービスを連携対象に加える場合も、このルーティング設定に1つエントリを追加するだけで済むため、後述する全社展開がスムーズに進められる構造になっています。

{
  "routes": [
    {
      "source": "slack",
      "channel": "C0123456789",
      "webhookUrl": "https://agent-a.example.com/webhook",
      "webhookSecret": "hmac-secret-for-agent-a",
      "knowledgeBaseId": "kb-xxxxxxxx",
      "slackNotifyChannel": "C0123456789"
    },
    {
      "source": "cloudwatch",
      "channel": "HighErrorRate",
      "webhookUrl": "https://agent-b.example.com/webhook",
      "webhookSecret": "hmac-secret-for-agent-b",
      "slackNotifyChannel": "C9876543210"
    }
  ]
}

MCPサーバーによるDevOps Agentのツール拡張

AWS DevOps Agentを実際に動かしてみると、すぐに以下の壁にぶつかりました。

DevOps Agentはセキュリティ上の理由から、スキルから直接HTTPリクエストを発行したりLambdaを呼び出したりできない。

しかし今回のシステムでは、以下のことを実現したいというニーズがありました。

  • Slackスレッドへの進捗投稿: 「調査開始」「原因候補特定」「調査完了」の3段階で通知したい
  • Issue番号のコールバック: 作成したIssue番号をDynamoDBに登録し、次回以降の類似エラーと紐づけたい
  • 社内ナレッジベース検索: Bedrock Knowledge Baseから過去のインシデント報告情報などを参照し類似のインシデントも考慮して分析させたい
  • Backlogへの課題起票・更新: GitHub Issueだけでなく、サービスによってはBacklogでチケット管理しているため、こちらにも対応したい

この外部連携を実現する手段として用意されているのが、MCP(Model Context Protocol)サーバー です。

MCPサーバーの構成

別のLambda Function URLとしてMCPサーバーをデプロイし、DevOps Agentのコンソール画面でエンドポイントを登録することで利用できるようにしました。

DevOps Agent → MCP Server Lambda (Function URL)
                ├─ slack_post_thread     → Slack API
                ├─ issue_callback        → DynamoDB
                ├─ knowledge_base_query  → Bedrock KB
                └─ Backlog 系ツール       → Backlog REST API
                   (add_issue / update_issue / get_issues / ...)

ツール定義は@modelcontextprotocol/sdkを用いて以下のように記述します。

// MCPツール定義の例
server.tool(
  'slack_post_thread',
  'Post a message to a Slack channel or thread.',
  {
    channel: z.string().describe('Slack channel ID'),
    thread_ts: z.string().optional().describe('Thread timestamp for reply'),
    text: z.string().describe('Message text'),
  },
  async ({ channel, thread_ts, text }) => {
    const config = await getConfigProvider().getConfig();
    const result = await slackPostThread(config.slackBotToken, channel, thread_ts, text);
    // ...
  },
);

DevOps AgentからMCPサーバーへの接続にはいくつかの認証方式が用意されているため、運用環境に合わせて適切に設定してください。

公式MCPサーバーとの併用

自前のMCPサーバーだけでなく、GitHub公式のMCPサーバーNew Relic公式のMCPサーバー もDevOps Agentに登録しています。これにより、DevOps Agentは調査の過程で:

  • GitHubから該当リポジトリのソースコードや変更履歴、関連PR を直接参照する
  • New Relicからエラー発生時の各種メトリクス、トレース、Errors Inbox の情報を確認する

といった、より深いコンテキストを踏まえた分析ができるようになっています。サービス横断で「これは入れておきたい」ツールは公式のものをそのまま使い、サービス固有のドメイン要件 (Slack スレッド返信、Backlog 起票、社内KB など) だけ自前のMCPサーバーで提供する、という棲み分けです。

セキュリティ上の制約からMCPサーバーを介した間接的な構成にせざるを得ない側面はありますが、ツールをallowlistで管理できるというメリットもあり、公式・自前のMCPサーバーを組み合わせる形で結果として運用しやすい形に落ち着きました。

DevOps Agentのスキル設計

DevOps Agentの動作は、Claude Codeなどと同様にマークダウン形式のスキルファイルで定義します。

実際に運用してみて分かったのは、スキルファイルの書き方そのものが調査の精度とスピードに直結するということです。Agent自体は強力でも、スキルが曖昧だと毎回ゼロから調査範囲を探索することになり、結果として「分析に時間がかかる (= 課金が増える)」「見るべき場所を取りこぼす」といった問題が起きます。逆にスキルが具体的だと、Agentは迷わず必要なリソースに直行できます。

検証を重ねていく中で、特に効くと感じたのは 「このアラート/エラーパターンが来たら、どこを最初に見に行くべきか」を具体的に書く ことです。たとえば次のよう観点です。

  • CloudWatchロググループ: 「xxx というエラーメッセージが来たら、まず /aws/lambda/foo-handler のログを ${時刻} - 5分 の範囲で確認する」のように、エラー文字列とロググループの対応を具体的に列挙する
  • GitHubリポジトリとパス: 「このアラートの対象はモノレポの services/foo 配下なので、コード調査は <owner>/<repo>services/foo に絞る」など、どのリポジトリ・どのディレクトリを見にいくかまで明示する
  • メトリクス: New RelicやCloudWatchで該当時刻のメトリクスを確認するなら、ダッシュボードURLや関連クエリも併記する

こうした「入力 (アラートの種類) → 見に行くべきリソース」のマッピングをスキルに書き込んでおくことで、Agentが調査開始時にあちこち探索せず、最短距離で根本原因に辿り着けるようになります。逆にここを書かないと、汎用的な調査手順を毎回最初からなぞる挙動になり、時間も課金も嵩みやすくなります。

スキルを書く時の指針としては、抽象的な手順より具体的なStep + リソースの紐づけ が圧倒的に効きます。今回構築したシステムでは、調査から起票・通知までを次の9ステップに分割し、それぞれでMCPツールを呼び出す設計にしました。

Step 内容 使用するMCPツール
1 エラー情報のパース + 「調査開始」Slack通知 slack_post_thread
2 対象リポジトリの特定 -
3 ナレッジベース検索 knowledge_base_query
4 デプロイとの相関確認 -
5 コードの調査 + 「原因候補特定」Slack通知 slack_post_thread
6 GitHub Issue / Backlog 課題の作成 add_issue 等
7 「調査完了」Slack通知 slack_post_thread
8 Issue番号のコールバック issue_callback
9 結果の報告 -

スキルファイルの中で、「data.metadata から channelthread_tssimhashcallbackToken を取得して使用する」と明示的に記載することで、DevOps Agentがメタデータを正しく活用できるようにしています。スキルの設計は試行錯誤の連続でしたが、抽象的に書くよりも、Step単位で具体的に記述する方が安定することがわかりました。

また自作のMCPを利用するスキルに関しては展開できるように独立したマークダウンファイルとしています。 以下が Slack の MCP を利用するスキルの例です (Issue トラッカーには GitHub Issue を例に記載していますが、Backlog の場合もほぼ同じ構成で書けます)。

slack-notification.md(クリックで展開)

---
name: slack-notification
description: slack_post_thread MCPツールを使って調査の進捗と結果をSlackスレッドに投稿するルール。
---

# Slack通知ルール

## 概要

入力コンテキストにSlack関連の情報(チャンネルID、スレッドタイムスタンプなど)が含まれている場合、`slack_post_thread` MCPツールを使って調査の進捗と結果を元のSlackスレッドに投稿する。

## 前提条件

- `slack_post_thread` MCPツールが利用可能であること(カスタムMCPサーバー経由で登録済み)

## 投稿タイミング

以下のタイミングでSlackスレッドに投稿する:

### 1. 調査開始時

エラーの調査を開始した直後に投稿する。

**メッセージフォーマット:**

```
:mag: 調査を開始しました

エラー: `<エラークラス>: <エラーメッセージの要約>`
対象リポジトリ: `<リポジトリ名>`
```
### 2. 原因候補の特定時

根本原因の候補を特定した後(Issue作成前)に投稿する。

**メッセージフォーマット:**

```
:flashlight: 原因の候補を特定しました

*対象リポジトリ:* `<リポジトリ名>`
*ファイル:* `<ファイルパス>:<行番号>`
*原因候補:* <根本原因の簡潔な説明>

Issueを作成しています...
```
### 3. 調査完了時

GitHub Issueの作成後に投稿する。調査結果の全体像を含めること。

**メッセージフォーマット:**

```
:white_check_mark: 調査が完了しました

*エラー:* `<エラークラス>: <エラーメッセージ>`
*重要度:* <critical/high/medium/low>
*対象リポジトリ:* `<リポジトリ名>`

*影響のあるコード:*
• ファイル: `<ファイルパス>`
• 行番号: `<行番号>`
• メソッド: `<メソッド名>`

*根本原因:*
<根本原因の詳細な説明。エラーを引き起こすコードパスと条件を具体的に記載する>

*最近の関連コミット:*
• `<コミットハッシュ>` <著者> - <コミットメッセージ>
• ...

*修正の方針:*
<具体的な修正の方向性。変更が必要なコードを具体的に参照する>

*Issue:* <GitHub Issue URL>
```

根本原因を特定できなかった場合:

```
:warning: 調査が完了しました(原因特定に至らず)

*エラー:* `<エラークラス>: <エラーメッセージ>`
*重要度:* <critical/high/medium/low>
*対象リポジトリ:* `<リポジトリ名>`

*調査内容:*
<調査した内容 — 確認したファイル、参照したログ、確認したメトリクスなど>

*判明した事項:*
<根本原因が不明でも、判明した事実を記載する>

*修正の方針:*
<次のステップや追加で調査すべき領域>

*Issue:* <GitHub Issue URL>
```
## 投稿方法

**`slack_post_thread` MCPツール**を使ってメッセージを投稿する:

```
slack_post_thread({
  channel: "<data.metadata.slack_channel>",
  thread_ts: "<data.metadata.thread_ts>",
  text: "<メッセージ本文>"
})
```
## Slackコンテキストの検出

Webhookから受け取るインシデント入力の `data.metadata` にSlackコンテキストが含まれる。以下を探す:

- `data.metadata.slack_channel` — 投稿先SlackチャンネルID(例: `C01ABCDEF23`)。Slackソースの場合は元のチャンネル、CloudWatchソースの場合は設定された通知先チャンネル。
- `data.metadata.thread_ts` — スレッドタイムスタンプ(例: `1234567890.123456`)。Slackソースの場合のみ。

### slack_channel と thread_ts がある場合

`slack_post_thread``thread_ts` 付きで呼び出し、元のスレッドに返信する。

### slack_channel のみある場合(thread_ts なし)

`slack_post_thread``channel` のみで呼び出し(`thread_ts` 省略)、新規メッセージとして投稿する。CloudWatch Alarmソースのインシデントではこのケースが一般的。

### slack_channel がない場合

Slack通知をスキップする。

## ルール

- 入力メタデータに `slack_channel``thread_ts` がある場合は、必ず元のスレッドに返信する。
- `slack_channel` のみあり `thread_ts` がない場合は、そのチャンネルに新規メッセージとして投稿する。
- メタデータに `slack_channel` がない場合はSlack通知をスキップする。
- 新規Issue作成・既存Issueへのコメント追加に関わらず、常にSlackに投稿する。
- メッセージは簡潔にする。詳細な情報はGitHub Issueに記載し、Slackメッセージには含めない。
- `slack_post_thread` ツールがエラーを返した場合はエラーをログに記録し、調査ワークフローを続行する。

実際の動作例

実際に動いているSlackでの分析の例を添付します。 New Relicのアラートを元に、上記の仕組みが動作し、ある外部のシステムが異常にAPIを呼んでいるということを分析してくれた例になります。

  • アラートが飛び自動で調査開始

調査の進捗報告 (クリックで展開)

  • 調査結果の報告

  • 作成されたGitHubのissue

実際にSlackのスレッドに調査の進捗、結果を報告し、GitHubにissueが作成され内容がまとめられていることがわかります。

全社共通基盤としての展開

ここまではシステム単体の技術的な工夫について見てきましたが、実はこの基盤の最大の価値はもう一段上のレイヤー、つまり「全社で 1 つの仕組みとして共有できる」というところにあると考えています。

役割分担: 共通基盤チーム × サービスチーム

DevOps Agentで本当に難しいのは、各サービス特有の調査スキル(マークダウンで記述する手順書)を書く部分です。どのリポジトリを見るのか、どのCloudWatchロググループを参照するのか、どのGitHub Organizationにissueを起票するのか——これらはサービスの中身を一番よく知っている担当者にしか書けません

逆に、Slackイベントの受信、類似エラーの重複排除、MCPサーバーによるツール拡張、Slackスレッドへの戻し制御——このあたりは横展開できる共通の仕組みにできる部分です。

そこで本基盤では、両者を以下のように分業しています。

担当 やること
共通基盤側 (我々) Slack/CloudWatch受信、ルーティング、SimHash重複排除、MCPサーバーによるツール提供、Slackスレッド返信
各サービスチーム 自分たちのAWSアカウントにDevOps AgentのAgent Spaceを作成、サービス固有の調査スキル(マークダウン)を記述、Webhookを共通基盤に向ける

つまり、サービスチームは自分たちが詳しい部分(スキル)の記述に集中でき、共通基盤側のインフラ運用は気にする必要がありません。逆に共通基盤側は、サービス内部のドメイン知識を持たなくても運用が成り立ちます。

アカウント分離による自然なコスト按分

各サービスのDevOps AgentがそれぞれのAWSアカウントで動くことで、Agent起動の課金もアカウントごとに按分される仕組みになっています。

DevOps Agentは調査時間による従量課金なので、コストの責任分界点が曖昧だとサービス間で利害がぶつかりやすいのですが、この設計だと使った分だけそのサービスのアカウントに請求が立つため、組織運用上もシンプルです。

すでに社内のいくつかのプロダクトに本基盤を導入済みで、現在も対応プロダクトを段階的に増やしています。新しいプロダクトを追加するときの手順も、サービス側でAgent Spaceとスキルを準備さえできれば、共通基盤側はルーティング設定にWebhook URLを1行足すだけで連携が完了します。

おわりに

本記事では、AWS DevOps Agent を中核に据えたインシデント自動分析基盤について、設計のポイントを一通り紹介してきました。

要点を改めて整理すると、次の 3 つに集約できます。

  • 痒いところに手が届かない部分を「前段」と「後段」で補う: Slack の元スレッド返信や類似エラーの重複排除といった、DevOps Agent 単体では難しい部分を、自前の Lambda と MCP サーバーで補完する形にしたことで、Agent 本来の強み (高精度な調査) を実運用に乗せやすくしました。
  • 公式 MCP と自前 MCP の使い分け: GitHub や New Relic などサービス横断で必要なツールは公式の MCP サーバーをそのまま登録し、Slack スレッド返信や Backlog 起票、社内ナレッジ参照など組織固有のものだけ自前で実装する、という棲み分けによって、構築・保守コストを最小限に抑えられました。
  • 「基盤の仕組み」と「ドメイン知識」を分けた共通基盤化: 共通基盤側が Slack/CloudWatch の受け口・ルーティング・重複排除・MCP ツール提供を担い、サービスチーム側は AWS アカウントと Agent Space、調査スキルの記述に集中する、という役割分担にしたことで、すでに複数プロダクトへの導入が進んでいます。

AI エージェントを業務に組み込むときの最大の難しさは、エージェント単体の精度よりも、「既存の運用フローやチーム構造にどう乗せるか」 の部分にあると感じています。DevOps Agent そのものは非常に強力ですが、本記事で触れたような前後の作り込みがあって初めて、組織として継続運用できると感じています。 同じように AI エージェントを運用に組み込もうとしている方にとって、本記事が一つの設計サンプルとして参考になれば嬉しいです。