Amazon Managed GrafanaからRDSの時系列データをリアルタイムに表示してみる

# はじめに

Amazon Managed Grafanaを使った時系列データのクエリの実装例が少なかったので記事を書いてみました。

- 以前、Grafanaは少しだけ記事にしました。
    - https://qiita.com/jnit/items/49f4954f8a7667e4a440#%E6%89%80%E6%84%9F

- 最近OpenSearchがニューラル検索に対応し、「リアルタイム可視化」という点でGrafanaとOpenSearchの棲み分けの方向性が明確になってきたように思います。
    - https://aws.amazon.com/jp/about-aws/whats-new/2023/11/amazon-opensearch-neural-search/
    - 個人的には単純なリアルタイム可視化であればGrafanaがよいのではと思います。

## 構成

各サービスのデータ連携は、すべてVPC内でセキュアに通信するようにしてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/23b97873-3028-3296-6ae9-09e0a5a5e3ba.png)

| Services | Version | note |
|:-:|:-:|:-:|
| Managed Grafana  | 9.4  | in VPC  |
| RDS(MySQL)  | 8.0.33  | in VPC |
| Lambda  | Python 3.10  | in VPC  |

# 構築

## RDS(MySQL)

- 詳細は省きます。
- MySQL を RDSで構築し、VPCに足を出しています。
- また、自宅からつなげるためパブリックアクセス可能とし、サブネットのルートテーブルにもインターネットゲートウェイが設定されています。
- データベース名は「mydb」としています。マスターユーザー名は「admin」です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/41822b37-94b6-24e8-22f8-b6f701849cd7.png)


- SecurityGroupは、同一VPCからのインバウンドをすべて許容しています。
- また、SQLクライアントソフトからDDL文を叩くため、自宅のグローバルIPも許可しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/f44c2e9d-86d2-00d4-e7b3-7686f2188247.png)


- 起動後、適当なテーブルを作成しておきます。
- idのほかに、int型のカラム(author_id)と、timestamp型のカラム(time)を作っておきます。
    - ※mysqlの場合、timestamp型（「2038-01-19」まで）よりもdatetime型（「9999-12-31」まで）のほうがよいです。今回はあくまでテストなのでtimestamp型を使っています。

```sql
CREATE TABLE `posts` (
  `id` int NOT NULL,
  `author_id` int NOT NULL,
  `time` timestamp NOT NULL,
  PRIMARY KEY (`id`)
);
```

## Lambda

まずPython3.10での関数を作成します。

IAMロールは、今回は一時的なテストなのでAdministratorAccessをつけちゃってますが、基本的にはAWSLambdaVPCAccessExecutionRole、AWSLambdaBasicExecutionRole があればよいと思います。

VPCは、先程RDSを展開したのと同じサブネットに足を出しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/560b7373-06e2-4095-473b-e29213e9ddc5.png)


MySQLのコネクタをLambdaレイヤー化するのが面倒だったので、素晴らしい有志が作成した公開レイヤーを利用します。

https://github.com/keithrozario/Klayers

上記サイトから「deployments」を選択し、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/ce396f8e-81b4-30fb-95a5-834a3b88d9dc.png)

現在Lambdaで使える3.10系を選択し、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/74f20f73-8c57-4fd2-4fa5-c8ec8e1e4a15.png)

東京リージョンのhtmlを選択し、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/412be0ab-7655-d212-9a6f-e4640d5b929d.png)

mysql-connector-pythonのARN欄をコピーします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/7b9552e7-29ad-5495-5dc4-c75f20c1dd8a.png)

Lambdaのレイヤーを追加し、先程のARNを入力します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/83595ceb-f923-249e-f860-fc58cddfb78f.png)

追加できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/f7a82451-7c3c-c269-92f9-0c33755fbbcb.png)

- Lambdaコードはこんな感じで雑に作ります。
    - RDSのパスワード等は直書きしていますので、本格的に使う場合はLambda環境変数なり、SecretsManagerなりに保存してください。

```python
import json
import mysql.connector as mydb
import time
import datetime
import random


def lambda_handler(event, context):

# コネクションの作成
    conn = mydb.connect(
        host='database-1.**********.ap-northeast-1.rds.amazonaws.com',
        port='3306',
        user='admin',
        password='**********',
        database='mydb'
    )
    print(conn.is_connected())

# カーソルの準備
    cur = conn.cursor()

# レコードの生成とインサート

    # idは重複しない値として、適当に現在のUNIXタイムを入れておきます。
    id = int(time.time())
    # author_idは、1～500までの疑似乱数を入れておきます。
    author_id = random.randint(1, 500)
    # timstamp型に入れる値として、現在のdatetimeを入れておきます。
    dt_now = datetime.datetime.now()

    # DBにインサートします。
    cur.execute("INSERT INTO posts VALUES (%s,%s,%s)",(id,author_id,dt_now))

# デバッグ用コード
    # Lambda上のデバッグ用に、selectの結果を標準出力しておきます。
    cur.execute("select * from posts where id = '%s'",(id, ))
    rows = cur.fetchall()
    for row in rows:
        print(row)

# コミット
    conn.commit()

# 後始末
    cur.close()
    conn.close()
    
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

```

最後にEventBridgeで1分毎のトリガーを設定しておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/faace82f-a38d-1ddb-3de5-bd365f3ec8f8.png)


## Managed Grafana

初期設定方法は私の以下の記事などを参考にしてください。
リンク先の「データソースと通知チャネル」は今回は設定不要です。
https://qiita.com/jnit/items/49f4954f8a7667e4a440#%E5%88%9D%E6%9C%9F%E8%A8%AD%E5%AE%9A

### ワークスペースの設定

作成済みのワークスペースを選択し、VPCに足を出しておきます。
「ネットワークアクセスコントロール」で設定が可能です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/68e3e2c0-e16d-d713-623c-8187dbeca976.png)

SecurityGroupは、適当に同一VPCからすべてのトラフィックを許可するSGを設定しました。

### データソースの設定

まず、Grafanaのユーザが管理者権限であるか確認します。データソースの追加は管理者権限である必要があります。

マネジメントコンソールの Managed Grafanaから「認証」「ユーザーとユーザーグループの設定」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/1c507612-fc3c-9c12-0270-d41b9e0f9331.png)

自身が使うユーザが「管理者」となっているか確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/79c42728-9faf-d2a0-10e5-2ed89a45d692.png)

AWS SSOなどからManaged Grafanaにログインします。
「Home」から「Data sources」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/92d62e93-16a3-18db-6fa9-929889efa26a.png)
※なお、「Administration」メニューは、Grafana上のユーザが管理者権限を持っている場合に表示されます。

「Add new data source」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/226a85fe-bac6-9f67-263b-3f7e2b026586.png)

MySQLを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/d65e5247-ed21-5992-6a7d-698d8484bdc2.png)

RDSのドメイン名とポート名をコロンで繋げて入力し、ご自身のデータベース名やユーザ名などを入力します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/05e175c5-42d2-58bd-26a1-7da7a322dbb7.png)

「Save & test」で接続が成功すればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/7bee9b07-8ff9-f7e6-7fd3-28d01200108d.png)

### ダッシュボードの作成

「Home」から「Dashboards」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/0683467d-17b3-26b1-3ffb-c122705b3ed9.png)

「New」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/d1d3ff39-f603-ff97-373c-1e4ef8f40e39.png)

「Add a new panel」を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/98a750e7-fcc6-e4b5-bc6f-7014fe32cb39.png)

最初はGrafanaのサンプルが表示されていますので、「Data source」から先程作成したMySQLデータソースを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/ac68cb1c-7940-e86e-d8f8-dd914354dba3.png)

プルダウンでスキーマやテーブル、カラムなども選べますが、やりやすいように「Code」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/55438a7f-1794-c098-80db-d92ac892d8b2.png)

SQLクエリ入力欄に以下のように入力します。

```sql
SELECT
  time,
  author_id
FROM
  mydb.posts
order by time asc;
```

- このSQL文はあくまで例です。
    - SELECTしたカラムがグラフに反映されます。
    - 時間を持つカラムは order byしておく必要があります。
    - 時系列グラフにする場合は、SELECTするカラムに以下のいずれかを含めてください。
        - datetime(or timestamp)型
        -  Unix エポックを秒単位で表す数値データ型
    - 実際には、頻繁に上記のSQLクエリが発行されるので、WHERE句で直近1ヶ月分に絞ったり、適切にインデックスを設定しておくなど、DBへの負荷を減らすための考慮が必要になります。
    - その他クエリの例などは以下を参考にしてください。
        - https://grafana.com/docs/grafana/v9.4/datasources/mysql/#time-series-queries

クエリ入力後、「Run query」を押します。するとGrafanaが自動的に時系列グラフ「Time series」として認識し、上部にグラフが表示されます。問題なければ「Apply」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/317971d2-5869-b304-65b1-046485e2a612.png)

するとダッシュボードに先程のパネルが追加されます。表示範囲については、お好みの範囲に変更してください。
また、タイムゾーンがお住まいの地域に合っているか確認してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/27176327-6f3c-4539-e4ed-5c81d7911aec.png)

Grafanaの画面の更新間隔をお好みに応じて変更してください。この例では5秒に設定しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/e08a6b86-f777-e2e2-e3e1-31a18fcc09bc.png)

最後にダッシュボードを保存します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/aed45a99-093e-1628-dc50-8893896c5b05.png)

パネルを編集する場合は、パネルの三点リーダから「Edit」を選びます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/bcd5004d-e29c-c9e9-897b-93b65842b75f.png)

これで、簡易的ですがリアルタイムデータの可視化ができました。

# おわりに

- ダッシュボードの作り方について
    - Grafanaは、やはりダッシュボードを作成する際のインターフェースが少しとっつきにくく、本例のようにSQL文で直接書いたほうが（私にとっては）分かりやすいです。
        - 細かい使い方は[AWS側の公式サイト](https://docs.aws.amazon.com/ja_jp/grafana/latest/userguide/v9-dashboards.html)にはあまり載っていないので、[grafana本家のサイト](https://grafana.com/docs/grafana/v9.4/dashboards/)を見る必要があります。

- サポートについて
    - AWSが提供する Managed Grafanaには、以下の2つのレイヤーがあります。
        - （１）. Amazon Managed Grafana(AMG) サービスとしての動作や AMG 独自の仕様など、AMG としての観点
        - （２）. Grafana エンジンにおける仕様や設定など、オープンソース Grafana としての観点
    - AWSサポートが担保するのは基本的に上記の（１）の範囲になり、（２）についてはベストエフォートになります。
    - （２）の技術的な点で手厚いサポートが必要な場合は、Enterprise版の契約が必要になります。
        - https://docs.aws.amazon.com/ja_jp/grafana/latest/userguide/upgrade-to-Grafana-Enterprise.html
        - https://aws.amazon.com/marketplace/pp/prodview-lj4ozgtqla6y4
            - ベース料金が $3,500/月 となり、なかなか高額であるため、可能な限りgrafana本家のページと検証で凌ぐのがよいと思います。


## その他参考
- grafana本家
    - ドキュメントのバージョンの変更
        - 今回ManagedGrafanaで利用している9.4にあわせます。
        - https://grafana.com/docs/versions/?project=/docs/grafana/
    - MySQLデータソースにおける時系列データの扱い方
        - https://grafana.com/docs/grafana/v9.4/datasources/mysql/#time-series-queries
- AWS公式
    - デフォルトで利用できるデータソース
        - https://docs.aws.amazon.com/ja_jp/grafana/latest/userguide/AMG-data-sources-builtin.html
    - Enterpriseで利用できるデータソース
        - https://docs.aws.amazon.com/ja_jp/grafana/latest/userguide/AMG-data-sources-enterprise.html

以上です。