MySQL上のデータをGrafanaで表示する

# 要約

- MySQLコンテナとGrafanaコンテナを連携させて、MySQL内にあるサンプルデータをGrafana Dashboard上に表示する
- https://github.com/rakiraki-lucky/docker-grafana
    - アプリ実行方法はリポジトリのreadmeを参照

# アプリ構成

ざっくり構成。
grafana-mysqlは内部コンテナからしか参照されないのでlocalhostへのポートマッピングは無し。
![component.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614279/43fdb720-12e4-83c1-710d-cd3c18a3f23d.png)



# 環境

- Docker version: 20.10.22

# きっかけ

grafanaで遊びたかった。datasourceにMySQLを選んだのは使い慣れていたから。  
（本来ならPrometheusやInfluxDBなんかの時系列データ向けのDBと合わせるのがいいらしい）

# 実装内容

DBログイン時のユーザ情報などはコンテナの環境変数として管理する。
各変数はアプリ起動時に引数として`.env.dev`を渡すことで実体化させる。
```yaml:docker-compose.yaml
version: "1.1"

services:
  grafana-mysql:
    image: grafana-mysql
    build:
      context: ./grafana-mysql
      dockerfile: Dockerfile
    container_name: grafana-mysql
    hostname: ${DB_HOSTNAME}
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
      MYSQL_DATABASE: ${DB_NAME}
      MYSQL_USER: ${DB_USER}
      MYSQL_PASSWORD: ${DB_PASSWORD}
      TZ: Asia/Tokyo
      host_cache_size: 0
    init: true
    volumes:
      - grafana-mysql:/var/lib/mysql

  grafana-app:
    image: grafana-app
    build:
      context: ./grafana-app
      dockerfile: Dockerfile
    container_name: grafana-app
    user: grafana
    environment:
      GF_SECURITY_ADMIN_USER: ${GF_USER}
      GF_SECURITY_ADMIN_PASSWORD: ${GF_PASSWORD}
      GF_DATASOURCE_DB: ${GF_DATASOURCE_DB}
      DB_HOSTNAME: ${DB_HOSTNAME}
      DB_PORT: 3306
      DB_USER: ${DB_USER}
      DB_PASSWORD: ${DB_PASSWORD}
      DB_NAME: ${DB_NAME}
    init: true
    ports:
      - ${GF_PORT}:3000
    volumes:
      - grafana-app:/var/lib/grafana
    depends_on:
      - grafana-mysql

volumes:
  grafana-app:
    name: grafana-app
  grafana-mysql:
    name: grafana-mysql
```


以下環境ファイルをアプリ起動時に引数として渡す。
```bash:.env.dev
DB_ROOT_PASSWORD=root
DB_HOSTNAME=grafana-mysql
DB_NAME=grafana-db
DB_USER=db-user
DB_PASSWORD=password
GF_DATASOURCE_DB=mysql
GF_USER=grafana-user
GF_PASSWORD=password
GF_PORT=8085
```

## grafana-mysql

grafana-appで表示するデータを格納したMySQLコンテナ。

`docker-entrypoint-initdb.d`直下にsqlファイルを配置することで、コンテナ起動時に記述クエリを自動実行してくれる。
テストデータの自動作成ができて便利。
```dockerfile:Dockerfile
FROM mysql:8.0

COPY ./my.cnf /etc/mysql/conf.d/
COPY ./testdata.sql /docker-entrypoint-initdb.d/testdata.sql
```
テストデータには`time_table`と`time_table2`の2つのテーブルを用意した。
それぞれアプリ起動時の時刻を元に時系列データを自動生成する。
```sql:testdata.sql
USE grafana-db

CREATE TABLE time_table (
    id int(10) AUTO_INCREMENT PRIMARY KEY,
    data_time DATETIME,
    value int
);

INSERT INTO time_table(data_time, value) VALUES (DATE_SUB(NOW(), INTERVAL 1 HOUR), 10);
INSERT INTO time_table(data_time, value) VALUES (DATE_SUB(NOW(), INTERVAL 2 HOUR), 20);
INSERT INTO time_table(data_time, value) VALUES (DATE_SUB(NOW(), INTERVAL 3 HOUR), 30);
INSERT INTO time_table(data_time, value) VALUES (DATE_SUB(NOW(), INTERVAL 4 HOUR), 40);
INSERT INTO time_table(data_time, value) VALUES (DATE_SUB(NOW(), INTERVAL 5 HOUR), 30);
INSERT INTO time_table(data_time, value) VALUES (DATE_SUB(NOW(), INTERVAL 6 HOUR), 20);    

CREATE TABLE time_table2 (
    id int(10) AUTO_INCREMENT PRIMARY KEY,
    data_time DATETIME,
    value int
);

INSERT INTO time_table2(data_time, value) VALUES (DATE_SUB(NOW(), INTERVAL 10  MINUTE), 11);
INSERT INTO time_table2(data_time, value) VALUES (DATE_SUB(NOW(), INTERVAL 20  MINUTE), 12);
INSERT INTO time_table2(data_time, value) VALUES (DATE_SUB(NOW(), INTERVAL 30  MINUTE), 13);
INSERT INTO time_table2(data_time, value) VALUES (DATE_SUB(NOW(), INTERVAL 40  MINUTE), 14);
INSERT INTO time_table2(data_time, value) VALUES (DATE_SUB(NOW(), INTERVAL 50  MINUTE), 15);
INSERT INTO time_table2(data_time, value) VALUES (DATE_SUB(NOW(), INTERVAL 60  MINUTE), 16);    
```

## grafana-app
データ表示用のダッシュボードコンテナ。

grafanaコンテナの`/etc/grafana/provisioning/`配下の`dashboards`および`datasources`ディレクトリにそれぞれの定義ファイルを配置することで、コンテナ起動時に読み取って実行してくれる。

```dockerfile:Dockerfile
FROM grafana/grafana:7.1.1

# sample dashboard settings
COPY ./sample-dashboard-models.json /etc/grafana/provisioning/dashboards/
COPY ./dashboards.yaml /etc/grafana/provisioning/dashboards/

# sample datasource settings
COPY ./datasources.yaml /etc/grafana/provisioning/datasources/datasources.yml
```


ダッシュボードについてはパネル構成などを定義したjsonファイルを別途用意し、options.pathプロパティでその保管場所を渡すことで、コンテナ起動に合わせて構築済みダッシュボードが自動作成される。
```yaml:dashboards.yaml
apiVersion: 1

providers:
  - name: sample-dashboard
    orgId: 1
    folder: ""
    folderUid: ""
    type: file
    disableDeletion: false
    updateIntervalSeconds: 10
    allowUiUpdates: false
    options:
      path: /etc/grafana/provisioning/dashboards/
      foldersFromFilesStructure: true
```

DBへの接続情報はコンテナの環境変数として保持する構成なので、datasources.yamlもそれを読み取る形で記述した。

```yaml:datasources.yaml
apiVersion: 1

datasources:
  - name: sample-datasource
    type: ${GF_DATASOURCE_DB}
    access: proxy
    orgId: 1
    url: ${DB_HOSTNAME}:${DB_PORT}
    user: ${DB_USER}
    secureJsonData:
      password: ${DB_PASSWORD}
    database: ${DB_NAME}
    isDefault: true
    editable: false
    version: "1"

```

# 実行結果
アプリを起動してgrafana dashboardを表示すると、以下のようなグラフが表示される。
今回はダッシュボードでのデータ表示が目的だったのでグラフは適当。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614279/eaa36771-a850-f864-25dc-e19dcd186115.png)


# 最後に

コンテナ間通信を行うDockerアプリを初めて組んだ。
疎通もそうだが環境変数などでの制御をどうするかに結構試行錯誤した。。。

無料公開されているAPIからデータをとってきて可視化してみると面白そう。
時系列データ向けDBは今回触らなかったので、次作るときはinfluxDBなどで作ってみたいな。

# 参考記事

- [GrafanaでMySQLのデータを可視化する in Docker (in WSL2)](https://qiita.com/iwatake2222/items/9c98aa9a4a7491331acf)
- [【Docker】【MySQL】コンテナ初回起動時にスクリプトを実行させる](https://zenn.dev/re24_1986/articles/978801ae092498)
- https://doc.sitecore.com/xp/ja/developers/101/managed-cloud/import-and-export-your-grafana-dashboards.html
- https://grafana.com/docs/grafana/latest/dashboards/build-dashboards/view-dashboard-json-model/
- https://dev.mysql.com/doc/refman/5.6/ja/date-and-time-functions.html#function_date-add