MQTTで受け取ったデータを可視化する (emqx、Telegraf、influxdb、grafana)

# やっていること
さまざまなメトリック（データの指標や測定値）をMQTTプロトコルを使って送信し、それをリアルタイムでグラフ化するシステムを構築しています。MQTTは、IoTデバイスなど軽量なネットワーク環境でも効率的に通信できるプロトコルで、多くのメトリックを効率的に扱うのに最適です。
このシステムを持ち運び可能にするため、環境全体を仮想マシン上で構築しました。USBメモリさえあれば、どこでも同じ環境でメトリックの収集や可視化ができるようになっています。

※2024年10月26日 誤字を修正しました。
※2024年11月09日 gpasswdコマンドでログインしているユーザ名を指定するよう修正しました。

# 環境
- OS : Windows 11 Pro
- MQTTX 1.10.1
- QEMU 20240815版

仮想環境内のツール類は以下です。
- 仮想OS : Ubuntu 24.04
- Docker 25.0.4
- emqx 5.7.2
- telegraf 1.31.3
- InfluxDB 2.7.10
- Grafana 11.1.4

:::note info
LinuxやWLS上のDockerで構築する場合は、QEMUの箇所は読み飛ばして問題ないです。
:::

# ツールの説明
各ツールがどのような役割を行っているか説明します。
私の理解なので、間違っていたら生暖く見守ってください。
- MQTTX
  MQTT Publisherとして動作する
  要は、MQTTクライアント
  MQTT Brokerへメッセージを送信する
- EMQX
  MQTT Brokerとして動作する
  MQTT Publisherからメッセージを受取り、MQTT Subscriberへメッセージを送信する
- telegraf
  MQTT Subscriberとして動作する
  MQTT Publisherからメッセージを受取り、InfluxDBへデータを送信する
- InfluxDB
  時系列データに特化したデータベース
- Grafana
  InfluxDBのデータを読み取り、可視化する

# 接続図

telegrafは、emqxへ接続してデータを取得しています。
Grafanaは、influxdbへ接続してデータを取得しています。
そのため矢印が上向きになっていますが、データは上から下へ流れています。

<img width="70%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236088/fb3f2e6b-fc41-d64b-d0b7-d535511d11d0.png">


  
# これからやること
1. QEMUをインストールして仮想環境を用意する
1. 仮想環境にUbuntuをインストールする
1. Ubuntu環境にDockerを構築する
1. Dockerでemqx、telegraf、InfluxDB、Grafanaを起動する
1. MQTTXでデータを送る
1. Grafanaで可視化、ワーイ

# QEMUのインストール
下記URLからQEMUのインストールファイルをダウンロードします。

[QEMU](https://www.qemu.org/)
アクセス後、「Download」をクリックし、「Windows」をクリックします。
「64-bit」のリンクをクリックします。
「2024」をクリックします。
今回は「qemu-w64-setup-20240815.exe」をダウンロードしました。

ダウンロードしたファイルを実行してインストールします。
インストールパスは、「F:\qemu」などを指定します。

※USBメモリを指定すると、USB上に環境を作成して、持ち運べるようになります。

## Ubuntuのインストール
下記URLからUbuntuのISOファイルをダウンロードします。
[Ubuntu](https://jp.ubuntu.com/)
今回は「ubuntu-24.04-live-server-amd64.iso」をダウンロードしています。

QEMUを起動します。
その際にUbuntuのISOファイル指定とインストール用の10GBのディスク領域作成しています。
ダウンロードしたUbuntuのイメージをマウントして仮想マシンを起動しています。

コマンドプロンプトで下記のコマンドを実行します。

```bash
> f:
> cd qemu
> qemu-img create -f qcow2 ubuntu24.img 10G
> qemu-system-x86_64.exe -m 1G -smp 2 -boot order=dc -hda ubuntu24.img -cdrom "f:\ubuntu-24.04-live-server-amd64.iso"
```

QEMUが起動すると、Windowが表示され、Ubuntuのイメージがブートします。
そのままUbuntuをインストールします。

:::note info
QEMUのWindowからカーソルを放したい場合は、「Ctrl]+[Alt]+[g]を押します
:::

Ubuntuの場合、インストール中にsshのインストールの可否を聞かれます。
インストールするとTeraTerm等でアクセスが出来るようになり便利です。

インストールが完了したら、以下の手順で再起動します。

QEMUで [Ctrl]+[Alt]+[2]を押してQEMUモニタに入ります。
下記コマンドを実行して、isoをマウントしているデバイスを確認します。

```bash
(qemu) info block
```

確認したデバイスをアンマウントします。

```bash
(qemu) eject ide1-cd0
(qemu) info block  # マウントされていないことを確認する
```

QEMUで [Ctrl]+[Alt]+[1]を押て、元の画面に戻ります。
再起動します。

このままだと、仮想マシンへの22ポートが空いていないため、sshでアクセスできません。
ポートを開けた状態で起動するために、再起動後、ログインしたら一旦終了します。
```bash
sudo poweroff
```

起動用バッチファイルを作成します。
各ツールが使用するポートを開放しているため長くなっています。

```bash:qemu_start.bat
.\qemu-system-x86_64.exe -k ja -display sdl -rtc clock=vm,base=utc -smp 2 -m 2048 -netdev user,id=n1,hostfwd=tcp::22-:22,hostfwd=tcp::1883-:1883,hostfwd=tcp::8883-:8883,hostfwd=tcp::8083-:8083,hostfwd=tcp::8084-:8084,hostfwd=tcp::18083-:18083,hostfwd=tcp::8086-:8086,hostfwd=udp::8092-:8092,hostfwd=udp::8125-:8125,hostfwd=tcp::8094-:8094,hostfwd=tcp::3000-:3000 -device virtio-net-pci,netdev=n1 -hda ubuntu24.img
```

バッチファイルを実行して起動します。
起動後、sshで接続が可能になります。
接続情報は、下記になります。
<table>
  <tr>
    <td> 接続先 </td> <td> localhost </td>
  </tr>
  <tr>
    <td> ポート </td> <td> TCP 22 </td>
  </tr>
</table>

# Dockerのインストール

emqx、telegraf、InfluxDB、Grafanaは、Dockerコンテナで動作させます。
まずは、Dockerをインストールします。

## UbuntuにDockerをインストール

Ubuntuにアクセスして、下記のコマンドを実行し、Dockerをインストールします。
```bash
sudo apt update
sudo apt install ca-certificates curl vim
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo gpasswd -a `logname` docker
```

ここで、一度ログインし直す必要があります。

## Dockerのテスト

hello-worldを表示するコンテナを起動します。
```bash
docker run hello-world
```
下記のhello-worldが表示されたらOKです。
```
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
c1ec31eb5944: Pull complete
Digest: sha256:d211f485f2dd1dee407a80973c8f129f00d54604d2c90732e8e320e5038a0348
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/
```

## Docker ネットワークの作成

```bash
docker network create --subnet=172.16.1.0/24 mqtt-network
```
出来ていることを確認します。
```bash
docker network ls
```

## 準備

作成するディレクトリ構成は下記になります。

```
mqtt/
│   docker-compose.yml
│   telegraf.conf    
│
└── emqx/
│      data/
│      log/
│   
└── grafana/
```

必要なディレクトリを作成します。

```bash
mkdir mqtt
cd mqtt
mkdir -p emqx/{data,log}
mkdir grafana
```

telegraf用のコンフィグファイルを作成します。
```bash:telegraf.conf
[global_tags]
[agent]
  interval = "10s"
  round_interval = true
  metric_batch_size = 1000
  metric_buffer_limit = 10000
  collection_jitter = "0s"
  flush_interval = "10s"
  flush_jitter = "0s"
  precision = "0s"
  hostname = "telegraf_001"
  omit_hostname = false
[[outputs.influxdb_v2]]
   urls = ["http://influxdb:8086"]
   token = "influx-admin-token"
   organization = "influxdb-org"
   bucket = "influxdb-bucket"
[[inputs.mqtt_consumer]]
   servers = ["tcp://emqx:1883"]
   topics = [
     "mqtt-topics/#",
   ]
    client_id = "telegraf_001"
    username = "emqx-user"
    password = "emqx-user-password"
```

Docker-composeの設定ファイルを作成します。

```bash:docker-compose.yml
services:
  influxdb:
    image: influxdb:2.7.10
    container_name: influxdb
    hostname: influxdb
    ports:
      - "8086:8086"
    environment:
      DOCKER_INFLUXDB_INIT_MODE: setup
      DOCKER_INFLUXDB_INIT_USERNAME: influx-user
      DOCKER_INFLUXDB_INIT_PASSWORD: influx-user-password
      DOCKER_INFLUXDB_INIT_ADMIN_TOKEN: influx-admin-token
      DOCKER_INFLUXDB_INIT_ORG: influxdb-org
      DOCKER_INFLUXDB_INIT_BUCKET: influxdb-bucket
    volumes:
      - type: volume
        source: influxdb-data
        target: /var/lib/influxdb2
      - type: volume
        source: influxdb-config
        target: /etc/influxdb2
    networks:
      mqtt-network:
        ipv4_address: 172.16.1.30
        aliases:
          - influxdb.local.com

  grafana:
    image: grafana/grafana-oss:11.1.4
    container_name: grafana
    hostname: grafana
    ports:
      - "3000:3000"
    volumes:
      - ./grafana:/var/lib/grafana
    user: "0:0"
    networks:
      mqtt-network:
        ipv4_address: 172.16.1.40
        aliases:
          - grafana.local.com
    depends_on:
      - influxdb

  emqx:
    image: emqx:5.7.2
    container_name: emqx
    ports:
      - "1883:1883"
      - "8083:8083"
      - "8084:8084"
      - "8883:8883"
      - "18083:18083"
    volumes:
      - ./emqx/data:/opt/emqx/data
      - ./emqx/log:/opt/emqx/log
    networks:
      mqtt-network:
        ipv4_address: 172.16.1.10
        aliases:
          - emqx.local.com
    depends_on:
      - influxdb

  telegraf:
    image: telegraf:1.31.3
    container_name: telegraf
    ports:
      - "8092:8092/udp"
      - "8125:8125/udp"
      - "8094:8094/tcp"
    volumes:
      - ./telegraf.conf:/etc/telegraf/telegraf.conf
    networks:
      mqtt-network:
        ipv4_address: 172.16.1.20
        aliases:
          -  telegraf.local.com
    depends_on:
      - emqx

volumes:
  influxdb-data:
  influxdb-config:

networks:
  mqtt-network:
    name: mqtt-network
    external: true
    ipam:
      driver: default
      config:
        - subnet: 172.16.1.0/24
```

## コンテナの起動

下記コマンドを実行します。
```bash
docker compose up -d
```

下記コマンドで起動を確認します。
```bash
$ docker compose ps
```

telegrafコンテナだけはログを見て接続を確認してください。
コンテナが起動していても、うまくアクセス出来ていないときがあります。
```bash
docker compose logs telegraf
```
正しくアクセスしているときは、下記のように表示されます。
```bash
[inputs.mqtt_consumer] Connected [tcp://emqx:1883]
```

アクセス出来ていないときは、下記のように表示されます。
```bash
[inputs.mqtt_consumer] Connected [tcp://emqx:1883]
[inputs.mqtt_consumer] Error in plugin: connection lost: EOF
```
アクセス出来ていないときは、コンテナを再起動します。
```bash
docker compose restart telegraf
```

# EMQXの設定

EMQXのダッシュボードから設定を行います。

ブラウザから下記へアクセスします。
http://localhost:18083/

デフォルトのログイン情報です。

<table>
  <tr>
    <td> ユーザー名 </td> <td> admin </td>
  </tr>
  <tr>
    <td> パスワード </td> <td> public </td>
  </tr>
</table>

:::note info
初回ログイン時はパスワードの変更が必要です
:::

左のメニューから「Access Control」-「Authentication」を選択します。
「Create」ボタンをクリックします。
「Password-Based」を選択した状態で、「Next」ボタンをクリックします。
「Built-in Database」を選択した状態で、「Next」ボタンをクリックします。
UserID Type、Password Hash、Salt Positionはデフォルトのまま、「Create」をクリックします。

「Paasword-Based」というバックエンドが作成されています。
作成されたバックエンドの「Users」をクリックします。
「+」ボタンをクリックします。

下記を設定します。

| 項目      | 設定値               |
|:----------|:--------------------|
| Username  | emqx-user           |
| Password  | emqx-user-password  |

「Save」ボタンをクリックします。
「emqx-user」が作成されていることを確認します。

# InfluxDBの設定

InfluxDBのダッシュボードから設定を行います。

ブラウザから下記へアクセスします。
http://localhost:8086/

デフォルトのログイン情報です。
<table>
  <tr>
    <td> ユーザー名 </td> <td> influx-user </td>
  </tr>
  <tr>
    <td> パスワード </td> <td> influx-user-password </td>
  </tr>
</table>

左のメニューから「Load Data」-「Bucket」を選択します。
「CREATE BUCKET」をクリックします。
下記を設定します。

| 項目  | 設定値               |
|:------|:--------------------|
| Name  | influxdb-bucket     |

「CREATE」をクリックします。

左のメニューから「Load Data」-「Telegraf」を選択します。
「CREATE CONFIGURATION」をクリックします。
下記を設定します。

| 項目    | 設定値             |
|:--------|:------------------|
| Bucket  | influxdb-bucket   |
| source  | Telegraf internal |

:::note info
検索に「teleg」まで入力すると、アイコンが1つだけになります
:::

「CONTINUE CONFIGURING」をクリックします。

下記を設定します。

| 項目               | 設定値      |
|:-------------------|:-----------|
| Configuration Name | influx-001 |

「SAVE AND TEST」をクリックします。

「FINISH」をクリックします。

# Grafanaの設定

Grafanaのダッシュボードから設定を行います。

ブラウザから下記へアクセスします。
http://localhost:3000/

デフォルトのログイン情報です。
<table>
  <tr>
    <td> ユーザー名 </td> <td> admin </td>
  </tr>
  <tr>
    <td> パスワード </td> <td> admin </td>
  </tr>
</table>

:::note info
初回ログイン時はパスワードの変更が必要です
:::


左のメニューから「Connections」-「Data sources」を選択します。

:::note info
メニューが表示されていない場合は、左上の「Open menu」ボタンをクリックします
:::

「Add data sources」をクリックします。
「InfluxDB」をクリックします。
下記を設定します。

| 項目               | 設定値                |
|:-------------------|:---------------------|
| Query language     | Flux                 |
| URL                | http://influxdb:8086 |
| User               | influx-001           |
| Password           | influx-user-password |
| Organization       | influxdb-org         |
| Token              | influx-admin-token   |
| Default Bucket     | influxdb-bucket      |

「Save & test」をクリックします。
成功すると、「datasource is working」と表示されます。

左のメニューから「Dashboards 」を選択します。
「New」-「New Dashboard」をクリックします。
「Add visualization」をクリックします。
Select data sourceは、「influxdb」をクリックします。

画面下部のQuery欄に、下記のスクリプトを貼り付けます。

```
from(bucket: "influxdb-bucket")
  |> range(start: v.timeRangeStart, stop: v.timeRangeStop)
  |> filter(fn: (r) => r["_measurement"] == "temp")
  |> filter(fn: (r) => r["_field"] == "cpu")
  |> filter(fn: (r) => r["host"] == "telegraf_001")
  |> filter(fn: (r) => r["topic"] == "mqtt-topics/cpu")
  |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
  |> yield(name: "mean")
```

「Save」をクリックします。

Titleに任意の名称を入力します。
「Save」をクリックします。
グラフが表示されます。

# MQTTXのインストール

MQTTのデータ送信にMQTTXを使用します。
下記URLからQEMUのインストールファイルをダウンロードします。
[MQTTX: オール・イン・ワンのMQTTクライアント](https://mqttx.app/ja)
今回は「v1.10.1.win64.exe」をダウンロードしています。

## MQTTXの設定
MQTTXを起動します。
「New Connection」をクリックします。

EMQXに接続するため下記を設定します。

| 項目       | 設定値               |
|:-----------|:--------------------|
| Name       | mqttx001            |
| Client ID  | mqttx_001           |
| host       | mqtt://localhost    |
| Username   | emqx-user           |
| Password   | emqx-user-password  |

「Connect」をクリックします。


:::note info
Connetに失敗する場合は、ブラウザでemqxのUsernameとPasswordを再度設定してみてください。
その際は、コピペではなく手入力してください。
:::



「New Subscription」をクリックします。
下記を設定します。

| 項目  | 設定値        |
|:------|:-------------|
| Topic | mqtt-topic/# |

「Confirm」をクリックします。

下記を入力します。

| 設定値           |
|:----------------|
| mqtt-topics/cpu |
| temp cpu=40     |

<br>
<img width="70%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236088/fb97e43c-146a-8eda-96e3-a7957eef4c39.png">

右下の矢印ボタンをクリックします。

しばらくすると、Grafanaでデータがプロットされます。

<img width="70%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236088/6cf3d6a4-5ee0-e6ff-00f0-d816083139cb.png">

mqttxで、値を変えて矢印ボタンをクリックすることで、グラフが表示されていきます。


## 接続確認

うまくプロットされない場合の確認についてです。

### EMQXの確認

ブラウザから下記へアクセスします。
http://localhost:18083/

左のメニューから「Monitoring」-「Client」を選択します。
下記の用に表示されていることを確認します。

<img width="70%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236088/cdb86b97-e0f5-0985-07c4-6256cf9c7c65.png">

mqttx_001の表示がない・正しくない場合は、mqttxの設定を確認します。
telegraf_001の表示がない・正しくない場合は、telegraf.confとコンテナの設定を確認します。

### Influxdbの確認

ブラウザから下記へアクセスします。
http://localhost:8086/

左のメニューから「Data Explorer」を選択します。

下図のように、「influxdb-bucket」-「temp」-「cpu」-「telegraf_001」-[mqtt-topics/cpu」にチェックを入れて、「SUBMIT」をクリックします。
<img width="80%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236088/c6927b95-568c-3326-d5f5-c05f9b9b6b93.png">

mqttx入力した値がプロットされていることを確認します。
プロットされていない場合は、influxdbまでデータが届いていないことになります。

# 最後に
MQTTのPublisher・Broker・Subscriberの構成を利用したデータプロットの仕組みを構築できました。
Publisher(MQTTクライアント)を増やしても、EMQXで受け取れば即可視化が出来るようになっています。
今後は、Raspberry PiやM5Stackといった小型デバイスからのデータを送信して可視化を試したいです。



# 参考
- [emqx - Official Image | Docker Hub](https://hub.docker.com/_/emqx)
- [telegraf - Official Image | Docker Hub](https://hub.docker.com/_/telegraf)
- [influxdb - Official Image | Docker Hub](https://hub.docker.com/_/influxdb/)
- [Line protocol | InfluxDB Cloud (TSM) Documentation](https://docs.influxdata.com/influxdb/cloud/reference/syntax/line-protocol/)
- [Grafana: The open observability platform | Grafana Labs](https://grafana.com/)
- [Run Grafana Docker image | Grafana documentation](https://grafana.com/docs/grafana/latest/setup-grafana/installation/docker/)
- [Introduction - MQTTX Documentation](https://mqttx.app/docs)