AWS CloudWatch Logsと連携するMCPをロール（プロファイル）ごとに切り替えて使ってみた




![スクリーンショット 2025-05-19 12.00.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/591019/29c87178-b8aa-4b36-9151-03d2f96ccf1a.png)


## 🌟 はじめに - 「ログ解析をもっと効率的に」

自社で月間PV数1500～2000万以上のメディアサイトを運営しており、私はそのメインエンジニアを担当させてもらっています。日々、保守運用から新規開発のリードまでいろいろとやっていますが、なにをするにも**可用性**と**パフォーマンス**が重要です。その対策のためにも、ログ調査自体の効率化、最適化も大事だよなーーと思いつつ、毎日試行錯誤しています。

下記記事にて書いたのがその一つ。

https://qiita.com/tamanoyu/items/8e67a2c60433ef391d42

↓
そんな中で見つけたのが、**MCP（Multimodal Conversational Prototype）を使ったCloudWatch Logs解析**です。これはAIの力を借りてログ解析を行うという素晴らしいアプローチで、すごく期待が持てました！

### Thanks for the reference!

https://qiita.com/moritalous/items/2209f44e443e329b3693

上記の素晴らしい記事を参考にさせていただいたのですが、弊社の現状にはそのままではマッチしませんでした。

**マッチしなかった点**：
弊社では複数のメディアサイトを運営しており、それらごとに**AWSのロール**で分かれています。そのため、そのまま設定するだけでは、ロールの指定ができないという壁にぶつかりました。今回、その壁を超えたので、ナレッジとして残します。

## 📋 この記事でわかること

- MCPを使ってAWS CloudWatch Logsを解析する方法
- 複数のAWSプロファイル（ロール）を切り替えて使う設定方法
- AIを活用したログ解析の実践例

## 🛠 必要なツール・想定環境

- Python 3.9以上
- AWS CLI（設定済み、複数プロファイルあり）
- Claude Desktop, CursorなどのMCPサポートのあるAIエージェントツール
- MCP（Log-Analyzer-with-MCP）

## :family_wwbb: 想定読者

- 複数のAWSアカウント/ロールを管理している開発者
- CloudWatch Logsの解析を効率化したい方
- AIを活用して業務効率を向上させたい方

この方法を使うことで得られるメリット：

1. 複数のAWSアカウント/ロール間でシームレスに切り替え可能
2. 各メディアサイトや環境ごとのログ解析を効率化
3. AIの力を借りたログ解析による問題解決の迅速化

## 🔄 AWS プロファイルを切り替える方法

参考記事では基本的なMCPの設定方法が紹介されていますが、**複数のAWSプロファイル（ロール）を扱う場合**の設定が足りませんでした。

### 解決策：env > AWS_PROFILE の追加

結論としては、設定ファイルに **AWS_PROFILE** 環境変数を追加するだけで解決します！

```json
"Log-Analyzer-with-MCP": {
      "command": "/Users/[ユーザー名]/Log-Analyzer-with-MCP/.venv/bin/python",
      "args": [
        "/Users/[ユーザー名]/Log-Analyzer-with-MCP/src/cw-mcp-server/server.py"
      ],
      "env": {
        "AWS_PROFILE": "site1-role"
      }
    },
```

:::note info
**ポイント**: ここで指定するプロファイル名は、自分の `~/.aws/config`に設定されているプロファイル名と一致させる必要があります
:::

```
[default]
region = ap-northeast-1
output = json

[profile site1-role]
role_arn = arn:aws:iam::[number]:role/site1-role
region = ap-northeast-1
source_profile = default

[profile site2-role]
role_arn = arn:aws:iam::[number]:role/site2-role
region = ap-northeast-1
source_profile = default
```


### 複数のプロファイルを切り替えるテクニック

複数のプロファイルを使い分けたい場合は、MCPの設定をプロファイルごとに用意するのがおすすめです。

例えば：

```json
{
  "tools": {
    "Log-Analyzer-with-MCP-Site1": {
      "command": "/Users/[ユーザー名]/Log-Analyzer-with-MCP/.venv/bin/python",
      "args": [
        "/Users/[ユーザー名]/Log-Analyzer-with-MCP/src/cw-mcp-server/server.py"
      ],
      "env": {
        "AWS_PROFILE": "site1-role"
      }
    },
    "Log-Analyzer-with-MCP-Site2": {
      "command": "/Users/[ユーザー名]/Log-Analyzer-with-MCP/.venv/bin/python",
      "args": [
        "/Users/[ユーザー名]/Log-Analyzer-with-MCP/src/cw-mcp-server/server.py"
      ],
      "env": {
        "AWS_PROFILE": "site2-role" 
      }
    }
  }
}
```

これにより、必要に応じて異なるプロファイルでログ解析を実行できます。

:::note warn
**注意点**: AWS認証情報は機密情報です。設定ファイル内のプロファイル名やその他の認証情報が漏洩しないよう、適切に管理してください。
:::

## 📊 設定の流れ

MCPとAWS CloudWatch Logsを連携させる全体の流れを図解してみました：

```mermaid
graph TD
    A[AWS CLI設定] -->|複数プロファイル設定| B[~/.aws/config]
    B --> C[MCP設定ファイル作成]
    C -->|AWS_PROFILE追加| D[プロファイルごとのMCP設定]
    D --> E[Claude Desktopで利用]
    E --> F[CloudWatch Logsの解析]
    
    style A fill:#f9d5e5,stroke:#333,stroke-width:2px
    style B fill:#eeeeee,stroke:#333,stroke-width:1px
    style C fill:#f9d5e5,stroke:#333,stroke-width:2px
    style D fill:#d3f8e2,stroke:#333,stroke-width:2px
    style E fill:#e3f2fd,stroke:#333,stroke-width:2px
    style F fill:#fff9c4,stroke:#333,stroke-width:2px
```

## 🔍 実際の活用例

### 1. 過去１週間の本番環境のスロークエリログにて、上位３つを調査・分析し、解決策を出してもらう

![スクリーンショット 2025-05-19 12.02.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/591019/445ab6d1-f015-4dc6-bb4d-13467d99e505.png)


↓

<details><summary>分析結果</summary>

※具体箇所はマスキングしています。スキーマやテーブル名、ロジックはダミーです。

```

# メディアサイトデータベースの上位3つのスロークエリ分析

過去1週間のCloudWatchログから、STG環境を除外して分析した結果、PRD環境で最も実行時間が長いスロークエリの上位3つは以下の通りです。

## 1. 統計データ挿入クエリ

**クエリパターン:**

    INSERT INTO content_statistics (date, content_id, page, page_views, sessions)
    SELECT StatsTemp.date AS `StatsTemp__date`, 
           StatsTemp.content_id AS `StatsTemp__content_id`, 
           StatsTemp.page AS `StatsTemp__page`, 
           sum(page_views) AS `page_views`, 
           sum(sessions) AS `sessions` 
    FROM content_statistics_temp StatsTemp 
    WHERE (is_enable = 1 AND content_id <> 0) 
    GROUP BY date, content_id, page;

**特徴:**
- 実行時間: 平均13〜16秒
- 検査行数: 40,000〜51,000行
- 発生頻度: 毎日19:00頃に定期実行
- GROUP BY句を使用した集計処理と大量データの挿入を一括で実行
- 毎日同じ時間に実行されるバッチ処理

**対策:**
1. インデックス最適化: `content_statistics_temp`テーブルの`date`、`content_id`、`page`列に複合インデックスを作成
2. バッチ処理の分割: 大量データを小さなバッチに分けて挿入（例：日付ごとに分割実行）
3. パーティショニング: `date`列でテーブルをパーティショニングし、挿入パフォーマンスを向上
4. 実行時間の調整: アクセスが少ない時間帯に実行するようスケジュール変更
5. メンテナンス対策: 処理前後でテーブル統計情報の更新やVACUUM処理の実行

## 2. 統計データ削除クエリ

**クエリパターン:**

    DELETE FROM content_statistics WHERE (date <= '2025-XX-XX' AND date >= '2025-XX-XX');

**特徴:**
- 実行時間: 平均11〜16秒
- 検査行数: 17,000〜20,000行
- 発生頻度: 毎日00:00と14:30頃に定期実行
- 単一日付のデータを一括削除
- 毎回異なる日付だが同一パターンのクエリ

**対策:**
1. インデックス最適化: `content_statistics`テーブルの`date`列に専用インデックス作成
2. クエリ書き換え: 等値比較（`date = 'YYYY-MM-DD'`）を使用
3. パーティショニング: `date`列でテーブルをパーティショニングし、`DROP PARTITION`による高速削除
4. バッチ処理: 一度に削除する行数を制限したバッチ処理に変更
5. アーカイブ戦略: 削除の代わりにアーカイブテーブルへの移動を検討

## 3. メールアドレス取得クエリ

**クエリパターン:**

    SELECT user.id AS `id`, user.email AS `email` 
    FROM ((SELECT User.id AS `id`, User.email AS `email` 
           FROM user User 
           INNER JOIN user_subscription UserSubscription 
           ON UserSubscription.user_id = (User.id) 
           WHERE (User.is_enabled = 1 AND UserSubscription.is_enabled = 1 AND UserSubscription.type=1))
          UNION ALL 
          (SELECT LegacyUsers.id AS `id`, LegacyUsers.email AS `email` 
           FROM legacy_users LegacyUsers 
           WHERE (LegacyUsers.role_id = 400 AND LegacyUsers.banned = 0 AND LegacyUsers.is_migration = 0 
                  AND LegacyUsers.subscription_status = 1))) AS user;

**特徴:**
- 実行時間: 平均11〜13秒
- 検査行数: 約260,000行
- 発生頻度: 不定期だが主に深夜〜早朝に実行
- UNIONを使った2つのテーブルからのデータ結合と大量データの取得
- ニュースレター購読者のメールアドレス抽出用途と推測

**対策:**
1. インデックス最適化:
   - `user`テーブルの`is_enabled`列にインデックス
   - `user_subscription`テーブルの`user_id`、`is_enabled`、`type`列に複合インデックス
   - `legacy_users`テーブルのWHERE句条件に複合インデックス

2. マテリアライズドビュー: 頻繁に利用される場合は、結果をマテリアライズドビューとして保存
3. クエリ最適化: サブクエリ内でLIMITを使用してメモリ消費を制限
4. テーブル統合: 可能であれば、`user`テーブルと`legacy_users`テーブルの統合を検討
5. データ分析: 実際の用途に基づいて、必要な列のみを選択するよう最適化

これらの対策を実装することで、メディアサイトDBクラスターのパフォーマンスは大幅に向上し、スロークエリによるリソース消費が削減されるでしょう。これらの変更を本番環境に適用する前に、テスト環境でのパフォーマンス検証を強くお勧めします。
```

</details>

↓
なるほど。これはけっこう強力ですね。調査の手間が省けそう。

### 2. 本番環境のDBのエラーログにて、特筆すべきエラーをリストアップしてもらう

![スクリーンショット 2025-05-04 18.17.10.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/591019/9bdf0851-1535-4236-96c4-5f3dcc25386a.jpeg)

↓

<details><summary>分析結果</summary>

※具体箇所はマスキングしています。スキーマやテーブル名、ロジックはダミーです。

```

# 過去1週間のメディアサイトデータベースの特筆すべきエラー分析

AWS RDSデータベースクラスター「media-dbcluster-XX」のエラーログを過去1週間分調査した結果、以下の特筆すべきエラーパターンが確認されました。

## 1. 接続中断エラー

**発生時期**: 2025年5月1日 12:48頃

**エラーメッセージ**:

    Aborted connection [ID] to db: '[DB名]' user: '[ユーザー名]' host: '[IPアドレス]' (Got an error reading communication packets).

**特徴**:
- 複数のデータベース（media_prd, media_stg, media_dev1）にわたって同時に発生
- 同一ホスト（10.x.x.x）からの接続が多数中断
- 短時間に集中して6件の接続中断が発生

**考えられる原因**:
- ネットワーク接続の一時的な問題
- クライアント側のアプリケーションの異常終了
- コネクションプールの設定不備
- タイムアウト設定の不一致

**対策**:
- ネットワーク接続の安定性確認
- クライアントアプリケーションのエラーハンドリングの改善
- コネクションプールの設定最適化
- wait_timeout、interactive_timeout、net_read_timeout設定の調整

## 総合評価と注意点

エラーログ全体を分析した結果、以下のことが言えます：

1. **深刻なエラーは少ない**: デッドロック、メモリ不足、ディスク容量不足などの深刻なエラーは検出されませんでした。

2. **認証方式の時代遅れ**: 現在使用している認証方式は非推奨となっており、将来のMySQLバージョンでは削除される可能性があります。

3. **一時的な接続問題**: 5月1日に見られた接続問題は一時的な現象であり、その後同様の問題は発生していません。

短期的には認証方式の変更が重要な対応と考えられます。長期的には、接続の安定性を向上させるネットワーク設定やアプリケーション設定の最適化が推奨されます。
```

</details>

↓
”深刻なエラーは少ない”ということがわかるだけでもありがたい。定期的にチェックできるとよいですね。


## 📝 まとめ

複数のAWSアカウントやロールを管理している環境でも、**AWS_PROFILE**環境変数を活用することで、MCPを使ったCloudWatch Logs解析が可能になります。とりあえず使えてよかった。