Engineering
Glossvation Engineering
IoT・エッジ

エッジ ⇔ クラウドの通信方式を選ぶ — MQTT / gRPC / WebSocket / mTLS

NAT 配下のエッジデバイスからクラウドへ安全・双方向に通信する方式を比較。Outbound long-lived gRPC bidi stream、MQTT、WebSocket、mTLS 認証、Agent パターンの選定基準と運用ポイント。

Glossvation ·

エッジ通信特有の制約

エッジデバイス (工場 PC / Raspberry Pi / Jetson / 店舗 NVR) からクラウドに通信するとき、以下の制約がほぼ必ずついてきます。

  • NAT 配下 でグローバル IP なし → クラウドからエッジへ直接接続できない
  • ファイアウォール でインバウンドポート開放不可 (情シスが許可しない)
  • 不安定なネットワーク (モバイル回線 / Wi-Fi 切断)
  • 拠点数が多い (数十〜数万) → 1 拠点 1 拠点の設定は非現実的
  • 長時間稼働 (24/7) で、数ヶ月単位の接続を維持する必要

これを踏まえた通信方式の基本原則:

  1. アウトバウンド接続が前提 — エッジから発信、クラウドで待ち受け
  2. 長時間維持 — 都度接続では遅い、再接続コストも大きい
  3. 双方向 — クラウド → エッジ への指示も同じコネクション上で
  4. 認証は mTLS — API Key だけでは不十分、相互認証でなりすまし防止

選択肢の全体マップ

方式典型用途双方向要ブローカー標準認証
HTTPS Polling低頻度のテレメトリ✗✗mTLS or Bearer
HTTPS Long Polling / SSE片方向の指示通知△ (疑似)✗同上
WebSocketWeb アプリ連携✓✗mTLS or Bearer
gRPC (Bidi Stream)マイクロサービス間・制御系✓✗mTLS ネイティブ
MQTTIoT の pub/sub 一択✓✓ (ブローカー必須)mTLS + X.509
カスタム TCPレガシー機器連携✓✗自前実装

1. MQTT — IoT pub/sub の定番

なぜ MQTT か

  • 軽量: ヘッダ 2 バイト、低帯域回線 (LTE-M / NB-IoT) でも実用- pub/sub モデル: トピック購読で N 対 N 通信を自然に表現- QoS 0 / 1 / 2 で配信保証レベルを選べる
  • Last Will Testament (LWT): 切断時に自動で 「オフライン」 を通知- Retained Message: 最新状態を新規購読者に即配布 (設定値などに便利)

典型構成

Edge Pi ──mTLS/MQTT──┐
Edge Jetson ─mTLS/MQTT┼──→ MQTT Broker (EMQX / HiveMQ / AWS IoT Core)
Edge PC ───mTLS/MQTT─┘        │
                              ├─→ Kafka / Kinesis (データ)
                              └─→ Lambda / Function (指令処理)

Python クライアント例

import paho.mqtt.client as mqtt

def on_connect(client, userdata, flags, rc):
    client.subscribe(f"edge/{DEVICE_ID}/command/#")

def on_message(client, userdata, msg):
    print(f"cmd: {msg.topic} = {msg.payload}")

client = mqtt.Client(client_id=DEVICE_ID, protocol=mqtt.MQTTv5)
client.tls_set(ca_certs="ca.pem", certfile="device.pem", keyfile="device.key")
client.will_set(f"edge/{DEVICE_ID}/status", "offline", qos=1, retain=True)
client.on_connect = on_connect
client.on_message = on_message
client.connect("mqtt.example.com", 8883, keepalive=60)
client.publish(f"edge/{DEVICE_ID}/status", "online", qos=1, retain=True)
client.loop_forever()

メリット

  • IoT 向けツールチェーン (AWS IoT / Azure IoT / Google Cloud IoT の後継系) が充実
  • ブローカー OSS (EMQX / Mosquitto / HiveMQ CE) で自前運用も可能
  • ACL でトピック単位の権限制御

デメリット

  • ブローカーが単一障害点 になりがち (HA 構成は運用重い)
  • メッセージ順序保証はパーティション (トピック) 単位 でしかない
  • 大きなメッセージは不向き (数 MB を超えるファイルは別経路で)
  • pub/sub モデルに慣れていないチームでは学習コスト

2. gRPC Bidirectional Stream — 制御系の決定版

なぜ gRPC Bidi Stream か

  • 1 本の長寿命 TCP 接続上で双方向にメッセージをストリーミング
  • Protocol Buffers で 型安全、言語非依存
  • HTTP/2 なので 1 接続で複数ストリーム多重化
  • 認証は mTLS がネイティブサポート

典型構成

Edge Agent ─(outbound long-lived gRPC bidi)─→ Cloud gRPC Server (ALB / Envoy)
    ↕                                             ↕
    │  stream<EdgeMessage> edge→cloud: telemetry  │
    │  stream<CloudMessage> cloud→edge: command   │
    │                                             │

proto 定義例

syntax = "proto3";
service EdgeChannel {
  // 1 コネクションで双方向ストリーム。クラウドがエッジに指令を送れる
  rpc Connect (stream EdgeEvent) returns (stream CloudCommand);
}

message EdgeEvent {
  string device_id = 1;
  int64 timestamp = 2;
  oneof body {
    Telemetry telemetry = 10;
    StatusUpdate status = 11;
    LogEntry log = 12;
  }
}

message CloudCommand {
  string correlation_id = 1;
  oneof body {
    ConfigUpdate config = 10;
    ActionRequest action = 11;
    HealthCheck ping = 12;
  }
}

メリット

  • 双方向・型安全・効率的 — 制御系の決定版
  • バッチ / 単発要求も同じコネクションで混ぜられる
  • 中継プロキシ (Envoy / Istio) のエコシステムが豊富
  • バックプレッシャー (HTTP/2 flow control) がネイティブ

デメリット

  • コネクション維持の管理が複雑 — keep-alive, 再接続バックオフ, half-closed handling
  • ロードバランサー が長寿命接続を適切に扱う必要 (ALB は 60 秒切断するなど)
  • ブラウザから直接呼べない (gRPC-Web / Connect で緩和)
  • proto ファイル管理 (buf 等) が別途必要

実装の落とし穴

  • Keep-alive 設定: 30〜60 秒間隔で grpc.keepalive_time_ms を設定しないと、間の proxy が切断- 再接続戦略: Exponential backoff + jitter (1s → 2s → 4s → 10s + ランダム)- メッセージサイズ上限: デフォルト 4MB、必要なら明示的に拡張- サーバー側の水平スケール: 長寿命接続はセッションアフィニティが必要

3. WebSocket — Web アプリとの親和性

立ち位置

HTTP でアップグレードして双方向通信に移行する方式。 ブラウザ ⇔ エッジ 的な連携 (管理画面がエッジに指示を送る) で自然。

メリット

  • ブラウザサポート完備、WebSocket API 1 行で接続可能
  • 既存の HTTP インフラ (リバプロ / WAF) に乗せやすい

デメリット

  • 型安全性が弱い (JSON / バイナリの手設計)
  • 認証は WebSocket プロトコル外で設計 (初回 Upgrade 時のトークン or mTLS)
  • サーバ側の水平スケールは gRPC と同じ課題

推奨ケース

  • ブラウザ UI から直接エッジを操作したいとき (エッジを WebSocket サーバに)
  • または Web ベースの管理アプリから社内エッジを制御

エッジ ⇔ クラウド の主要経路として選ぶより、管理 UI の都合で WebSocket を併用 するパターンが多い。

4. mTLS — 全方式共通の認証層

なぜ API Key だけでは不十分か

  • API Key は流出リスク (ログ・メモリダンプ・従業員離職)
  • なりすまし を防ぐにはクライアントもサーバーも証明書で相互認証が必要
  • 大量拠点で 鍵ローテーション を回すには証明書管理基盤が前提

mTLS の基本構成

エッジ証明書 (device-xxxx.pem)   ←── ACM Private CA / HashiCorp Vault / 自前 CA
サーバ証明書 (server.pem)         ←── Let's Encrypt or Private CA

接続時:
  エッジ → サーバー: 証明書提示 + 検証
  サーバー → エッジ: 証明書提示 + 検証 (CN からデバイス ID を抽出)

実装の鉄則

  • 短寿命証明書 (7〜30 日)、デバイス側で自動 renew
  • CN からデバイス ID 取得 → ACL と結合
  • 失効管理 (OCSP / CRL / 短寿命でカバー)
  • CI/CD で証明書リリース (静的ファイル配布は危険)

MQTT との統合

MQTT ブローカーでは mTLS 証明書の CN がクライアント ID になる設計が定番:

# EMQX authorization rule
{allow, {username, {re, "^device-[0-9]+$"}}, publish, ["edge/${username}/#"]}
{allow, {username, {re, "^device-[0-9]+$"}}, subscribe, ["edge/${username}/command/#"]}

これで 「デバイス X は自分のトピックしか触れない」 を物理的に強制。

選び方のマトリクス

シナリオ推奨方式理由
大量の IoT センサー (数千〜数万)MQTT + mTLS軽量 + pub/sub の N 対 N
エッジ機器の遠隔制御 (指令 + 状態取得)gRPC Bidi + mTLS型安全 + 双方向 + 低レイテンシ
ブラウザから直接エッジ操作WebSocket (+mTLS)開発簡単、インフラ流用
低頻度テレメトリのみ (1 日数回)HTTPS Polling + mTLSシンプル、インフラコスト最小
ビッグデータ転送 (ログ・動画)別経路 (S3 Multipart Upload) + 制御は上記MQTT/gRPC では不向き

典型的なアーキテクチャ (折衷案)

実務では 複数方式を併用 するのが普通。

Edge Device
├─ MQTT (pub/sub, 常時)    ─→ Broker ─→ Stream Processor
│    テレメトリ・状態
│
├─ gRPC Bidi (常時)         ─→ Control Plane
│    リモート指令・設定配布
│
└─ S3 Multipart (必要時)    ─→ S3
     大きな画像・動画・ログファイル
  • MQTT: 低オーバーヘッドの定常テレメトリ- gRPC: 低頻度だが重要度の高い制御チャンネル- S3: ファイル転送 (MQTT/gRPC で流すとブローカー負荷爆発)

落とし穴

1. 「常時接続」 を甘く見る

数万台が同時再接続 (サーバメンテ後など) で雪崩を起こす。 Exponential backoff + jitter + 徐々に接続受付 (connection rate limit) を初期設計で。

2. TCP Keep-alive と Application Keep-alive の混同

  • TCP Keep-alive (OS 層): 数時間で切断検知、デフォルトは 2 時間で遅い
  • Application Keep-alive (MQTT PING / gRPC keepalive_time_ms): アプリで数十秒間隔

後者がないと中継 FW / ロードバランサーがアイドルセッションを切る。

3. ロードバランサーのタイムアウト

AWS ALB: 60 秒 無通信で切断 (Idle Timeout)。gRPC Bidi は Keep-alive 必須。 ALB より NLB の方が TCP レベルの長寿命接続に向く。

4. 片方向切断 (Half-closed) ハンドリング

エッジ側のネットワーク断で TCP 接続が 「片方向だけ生きている」 状態になることがある。 Application Keep-alive の タイムアウト でちゃんと検知して再接続する。

5. mTLS 証明書の期限切れ

本番拠点で証明書期限が切れて全台切断、という事故は定番。 期限 30 日前にアラート、自動 renew の失敗時にもアラート の二段構え。

6. 時計ズレで mTLS が失敗

デバイスの時刻が極端にずれていると証明書の有効期間判定でコケる。 ChronyD / NTP でエッジの時刻同期を必ず。

まとめ

用途第一選択
IoT 定常テレメトリMQTT + mTLS
リモート制御・指令gRPC Bidi + mTLS
ブラウザ ↔ エッジWebSocket + mTLS
低頻度状態取得HTTPS Polling + mTLS
ファイル転送S3 Multipart Upload (他経路で指令)

実務ではこれらを 併用 するのが普通。認証は常に mTLS を基盤に、通信方式は用途で使い分けるのが現実解です。

関連記事