【IoTデータのグラフ化】 Grafana + InfluxDB + Telegraf + Nginx をDockerで動かしLet’s encryptの証明書を自動更新


はじめに

下記記事にあるようにThingsBoard Community Editionを利用して、IoTデバイスのセンサ情報をグラフ化していました。しかし、Community Editionでは、データのエクスポートができないなど不都合があるため、今回はInfluxDBにデータを格納し、そのデータをGrafanaでグラフ化してみます。

Grafanaのダッシュボードはつぎのような画面になります。

Dockerのインストール

システムの更新と依存関係のインストール

まず、パッケージリストを更新し、HTTPS経由でリポジトリを利用するために必要なパッケージをインストールします。

sudo apt update && sudo apt upgrade -y
sudo apt install apt-transport-https ca-certificates curl software-properties-common -y

Dockerの公式GPGキーを追加

Dockerパッケージの信頼性を検証するために、公式のGPGキーを追加します。

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

Dockerリポジトリを設定

APTパッケージマネージャーにDockerの公式リポジトリを追加します。

echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

Docker Engineをインストール

リポジトリを追加した後、再度パッケージリストを更新し、Docker Community Edition(CE)をインストールします。

sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io -y

sudo権限なしでDockerを実行できるようにする

sudo usermod -aG docker $USER
su - ${USER}

Dockerサービスの起動と有効化

インストール後、Dockerサービスを起動し、システムの起動時に自動で立ち上がるように設定します。

sudo systemctl enable --now docker

インストールの確認

最後に、Dockerが正常に動作しているか確認します。

sudo systemctl status docker

各種設定

ディレクトリ構成

project-root/
├─ docker-compose.yml
├─ .env
├─ nginx/
│   └─ nginx.conf
├─ telegraf
│   └─ telegraf.conf
├─ certbot/

環境変数を定義(.env)

.envの内容は漏洩しないよう注意してください。

INFLUXDB_ADMIN_USER=admin
INFLUXDB_ADMIN_PASSWORD=任意のパスワード
INFLUXDB_ORG=任意のORG
INFLUXDB_BUCKET=任意のBUCKET
INFLUXDB_TOKEN=influxdbから取得したトークン(あとで取得)
GRAFANA_ADMIN_USER=admin
GRAFANA_ADMIN_PASSWORD=任意のパスワード
DOMAIN=任意のドメイン

docker-compose.yml

version: '3.8'

services:
  nginx:
    image: nginx:1.27.5
    container_name: nginx
    depends_on:
      - telegraf
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./certbot/www:/var/www/certbot
      - ./certbot/letsencrypt:/etc/letsencrypt
    restart: unless-stopped

  certbot:
    image: certbot/certbot
    container_name: certbot
    volumes:
      - ./certbot/www:/var/www/certbot
      - ./certbot/letsencrypt:/etc/letsencrypt
    entrypoint: /bin/sh -c

  influxdb:
    image: influxdb:2.7
    container_name: influxdb
    environment:
      - DOCKER_INFLUXDB_INIT_MODE=setup
      - DOCKER_INFLUXDB_INIT_USERNAME=${INFLUXDB_ADMIN_USER}
      - DOCKER_INFLUXDB_INIT_PASSWORD=${INFLUXDB_ADMIN_PASSWORD}
      - DOCKER_INFLUXDB_INIT_ORG=${INFLUXDB_ORG}
      - DOCKER_INFLUXDB_INIT_BUCKET=${INFLUXDB_BUCKET}
    volumes:
      - influxdb-data:/var/lib/influxdb2
    restart: unless-stopped

  grafana:
    image: grafana/grafana:11.4.0
    container_name: grafana
    environment:
      - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USER}
      - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD}
      - GF_SERVER_ROOT_URL=https://${DOMAIN}/grafana/
      - GF_SERVER_SERVE_FROM_SUB_PATH=true
    volumes:
      - grafana-data:/var/lib/grafana
    restart: unless-stopped

  telegraf:
    image: telegraf:1.27
    container_name: telegraf
    depends_on:
      - influxdb
    volumes:
      - ./telegraf/telegraf.conf:/etc/telegraf/telegraf.conf:ro
    environment:
      - INFLUX_TOKEN=${INFLUXDB_TOKEN}
      - INFLUXDB_ORG=${INFLUXDB_ORG}
      - INFLUXDB_BUCKET=${INFLUXDB_BUCKET}
    restart: unless-stopped

volumes:
  influxdb-data:
    external: true
  grafana-data:
    external: true

nginx/nginx.conf

load_module modules/ngx_http_js_module.so;

events {}

http {
    js_path "/etc/nginx/njs/";
    js_import telegraf_filter from telegraf_filter.js;

    client_body_buffer_size 16k;

    server {
        listen 80;
        server_name example.com;

        location /.well-known/acme-challenge/ {
            root /var/www/certbot;
        }

        location / {
            return 301 https://$host$request_uri;
        }
    }

    server {
        listen 443 ssl http2;
        server_name example.com;

        error_log  /var/log/nginx/error.log  info;

        ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers HIGH:!aNULL:!MD5;

        location /grafana/ {
            proxy_pass http://grafana:(grafana用の任意のポート)/grafana/;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
        }
        

        location /influxdb/ {
            proxy_pass http://influxdb:(influxdb用の任意のポート)/;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location /telegraf/ {
            proxy_pass http://telegraf:(telegraf用の任意のポート)/telegraf/;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
}

IoTデバイスからのPOST例

今回はIoTデバイスに1nceのsimを利用しているため、センサーデータも1nceのCloudIntengrationからWebhookでPOSTします。
POSTされるJSONデータは次のようなデータです。

{
  "payload": {
    "type": "JSON",
    "value": {
      "humi": 61.197834,
      "rain": 0,
      "temp": 30.637064,
      "press": 10.351074,
      "deviceid": 1,
      "lux": 0
    }
  },
  "received": "xxxxx",
  "id": "xxxxx",
  "source": "UDP",
  "type": "TELEMETRY_DATA",
  "version": "1.0.0",
  "device": {
    "iccid": "xxxxx",
    "ip": "xxxxx",
    "imsi": "xxxxx"
  }
}

telegraf/telegraf.conf

POSTされたJSONデータを解析し、InfluxDBにデータを格納する処理を行います。
tagについては、不要であれば削除してもよいです。

[agent]
  interval = "10s"
  omit_hostname = true

[[outputs.influxdb_v2]]
urls = ["http://influxdb:(influxdb用の任意のポート)"]
token = "${INFLUX_TOKEN}"
organization = "${INFLUXDB_ORG}"
bucket = "${INFLUXDB_BUCKET}"

[[inputs.http_listener_v2]]
  service_address = ":(telegraf用の任意のポート)"
  paths = ["/telegraf","/telegraf/"]
  methods = ["POST"]
  data_format = "json_v2"

  [[inputs.http_listener_v2.json_v2]]
    measurement_name = "telemetry_data"  # measurement名は任意で
    [[inputs.http_listener_v2.json_v2.tag]]
      path = "payload.value.deviceid"
    [[inputs.http_listener_v2.json_v2.field]]
      path = "payload.value.humi"
    [[inputs.http_listener_v2.json_v2.field]]
      path = "payload.value.rain"
    [[inputs.http_listener_v2.json_v2.field]]
      path = "payload.value.temp"
    [[inputs.http_listener_v2.json_v2.field]]
      path = "payload.value.press"
    [[inputs.http_listener_v2.json_v2.field]]
      path = "payload.value.lux"
    [[inputs.http_listener_v2.json_v2.field]]
      path = "payload.received"
    [[inputs.http_listener_v2.json_v2.tag]]
      path = "payload.device.iccid"
    [[inputs.http_listener_v2.json_v2.tag]]
      path = "payload.device.ip"
    [[inputs.http_listener_v2.json_v2.tag]]
      path = "payload.device.imsi"
    [[inputs.http_listener_v2.json_v2.tag]]
      path = "payload.id"
    [[inputs.http_listener_v2.json_v2.tag]]
      path = "payload.source"
    [[inputs.http_listener_v2.json_v2.tag]]
      path = "payload.type"
    [[inputs.http_listener_v2.json_v2.tag]]
      path = "payload.version"

Dockerボリューム作成

sudo docker volume create grafana-data
sudo docker volume create influxdb-data

Docker compose起動

sudo docker compose down
sudo docker compose up -d

CertbotによるLet’s encrypt証明書取得・自動更新

初回証明書取得

nginxの443設定を一時的にコメントアウトし、80のみで起動

sudo docker compose restart nginx

別ターミナルで次のコマンドを実行

sudo docker compose run --rm --entrypoint '' certbot certbot certonly --webroot -w /var/www/certbot -d example.com

証明書取得後、nginx.confの443設定を有効化し、nginxを再起動

自動更新(cron例)cron例(毎週月曜3:30に実行)

crontab -eで次を編集

0 3 * * 1 cd /path/to/project-root && docker-compose run --rm certbot renew --webroot --webroot-path=/var/www/certbot && docker-compose exec nginx nginx -s reload

InfluxDB のtoken取得

取得したトークンを .envのINFLUXDB_TOKENに設定します。

sudo docker compose exec influxdb influx auth list

Grafanaの設定

  1. Grafanaの管理画面にログイン
    https://example.com/grafana/ にブラウザでアクセスし、.env に設定したユーザー名・パスワードにてログインする。
  2. [Data Sources] → [Add data source] → [InfluxDB]を選択
  3. Query LanguageFlux選択
  4. URLhttp://influxdb:(influxdb用の任意のポート)を指定
  5. InfluxDB Detailsに、Organization(任意のもの), Bucket(任意のもの), Token(上記で取得したトークン)を入力してSave & Test
  6. ダッシュボードやパネルを作成しグラフ表示

データのテスト送信

下記コマンドでデータを送信し、Grafanaからデータが閲覧できることを確認する。

curl -X POST "https://wohlcloud.com/telegraf/" \
  -H "Content-Type: application/json" \
  -d '{
  "payload": {
    "type": "JSON",
    "value": {
      "humi": 61.197834,
      "rain": 0,
      "temp": 30.637064,
      "press": 10.351074,
      "deviceid": 1,
      "lux": 0
    }
  },
  "received": "xxxxx",
  "id": "xxxxx",
  "source": "UDP",
  "type": "TELEMETRY_DATA",
  "version": "1.0.0",
  "device": {
    "iccid": "xxxxx",
    "ip": "xxxxx",
    "imsi": "xxxxx"
  }
}'

データの全削除

テストデータを削除する場合には、influxdbのbucketを削除し、再作成する。

docker exec -it influxdb influx bucket delete --name production --org wohlcloud --token influxdbのトークン
docker exec -it influxdb influx bucket create --name production --org wohlcloud --retention 0 --token influxdbのトークン

FAQ

なぜThingsBoard Community EditionからInfluxDB+Grafana構成に移行したのですか?

ThingsBoard Community Editionではデータのエクスポート機能が制限されているなどの不都合があったため、より柔軟なデータ管理・可視化を求めてInfluxDB+Grafanaへ移行しました。

docker-compose.ymlの主なサービス構成は?

nginx, certbot, influxdb, grafana, telegrafの5サービスで構成されています。

.envファイルにはどのような環境変数を設定しますか?

InfluxDBやGrafanaの管理ユーザー名・パスワード、組織名、バケット名、トークン、ドメイン名などを設定します。

IoTデバイスからのデータはどのように受信・格納されますか?

IoTデバイスからWebhookでPOSTされたJSONデータをTelegrafが受信し、指定のフィールド・タグを抽出してInfluxDBに格納します。

GrafanaでInfluxDBのデータソースを追加する際の設定方法は?

Data Source追加画面でInfluxDBを選択し、Fluxクエリ言語を指定、URL・Organization・Bucket・Token等を入力してSave & Testします。

nginxのリバースプロキシ設定のポイントは?

各サービス(grafana, influxdb, telegraf)ごとにlocationディレクティブでproxy_pass先を指定し、ヘッダー情報も適切に付与します。

Let’s Encrypt証明書の取得・自動更新はどのように行いますか?

初回はnginxの443設定を一時的に外し80番のみで起動後、certbotで証明書取得。その後443設定を戻し、cronで自動更新を設定します。

 InfluxDBのトークンはどのように取得し、どこに設定しますか?

influx auth listコマンドで取得し、.envのINFLUXDB_TOKENに設定します。

GrafanaでIoTデータのグラフ化はどのように行いますか?

ダッシュボード作成画面でInfluxDBデータソースを選択し、必要なクエリを記述してパネルを作成します。

テストデータの送信方法は?

curlコマンドでJSON形式のデータを指定エンドポイント(/telegraf)にPOSTします。

データを全削除したい場合はどうすればよいですか?

influxdbのbucketを削除・再作成することで全データを消去できます。

Dockerボリュームの作成の目的は?

influxdb-data, grafana-dataのボリュームを作成し、データの永続化を確保します。