開発や本番といった「環境」を考慮した、AWS IoT Core のトピック設計と Tips
Author : 松下 享平 (AWS Community HERO)
こんにちは、AWS Community Hero の松下 (ニックネーム: Max) です。IoT プラットフォームを提供している 株式会社ソラコム で、日夜 IoT の普及に尽力しています。AWS のユーザーグループ「JAWS-UG」では IoT 専門支部 に所属しており、AWS ヒーロー です。
センサー等のデータを AWS で活用する際に用いられるサービスが「AWS IoT Core」で、デバイスと AWS の各種サービスをつなげるゲートウェイとして機能します。ゲートウェイと言えば Amazon API Gateway を思い浮かべる方もいらっしゃると思いますが、本質的には同じものです。
IoT Core 利用で基礎となるのが「トピック」です。トピックは、API Gateway の URL に該当します。Web アプリでは URL を切り替えて、開発や本番といった環境を分離していると思いますが、IoT Core でも同様の事が可能です。
本記事では、開発や本番といった「環境」を考慮した IoT Coreのトピック設計の考え方と、実装に役立つ Tips をご紹介します。
※本稿では、MQTTの基礎知識をお持ちの方に向けて書いています。MQTTそのものについては「MQTT とは何ですか ?」をご覧ください。
トピック設計のベストプラクティスと動作環境
IoT Core での MQTT トピック設計には、ベストプラクティスがあります (MQTT design best practices)。テレメトリーと言われる、IoT デバイスからのデータ受信を行うトピック設計は抜粋すると以下の通りです。
dt/<application>/<context>/<thing-name>(/<dt-type>)
例えば生産ラインにおける製造個数をカウントする IoT アプリケーション名を fa-app とし、デバイス (例えば、配置先の製造ライン番号=a109、カウントデバイスID=d1094) からのデータを受信するトピックは、 dt/fa-app/a109/d1094 となります。
開発や本番といった「環境」を、トピックに反映する
Web 開発では dev や prod といった動作環境を環境変数等に入れることで切り替えて、開発や本番に適用していきます。IoT デバイスにおいても環境の切り替えは有用です。トピックを工夫することで、動作環境の切り替えが実現できます。
もっとも簡単な実装は、ベストプラクティスにおける <application> 部分に環境名 <envname> の階層を加える方法です。
dt/<application>/<envname>/<context>/<thingname>
例えば dt/fa-app/prod/d1094 、または dt/fa-app/prod/a109/d1094 のように設計します。ベストプラクティスに手を加えているように見えますが、 <envname> と <application> の2つで <application> を構成しているようになるため、気にすることは無いでしょう。
IoT Core 上での動的なトピック切り替え
環境毎にトピックがあるということは、デバイスからの送信先トピックが複数存在することになります。一般的には、デバイスの設定で送信先トピック切り替えをすることになりますが、デバイス側で行う場合は、現地作業やOTA (Over the Air) での設定変更が不可欠です。
AWS IoT Greengrass や AWS IoT Jobs によるデバイス設定の遠隔更新も考えられますが、より手軽な方法として「IoT Core 上での動的なトピック切り替えの実装」をご紹介します。
ポイントは、MQTT メッセージを別の MQTT トピックに再発行する「Republish」アクションと、get_dynamodb() 関数 の利用です。
- Amazon DynamoDB で thingname をキーに、 "envname = 環境名(例: prod)" というデータを登録しておく
- デバイスからの送信トピック先は1つにする (例: dt/fa-app/incoming/<thingname>)
- IoT Core のルールで、Republish 先トピックの組み立てに get_dynamodb() 関数の返り値を利用する
一つずつ、見ていきましょう。
DynamoDB のテーブル (例: TBL1) は、こちらのようにしておきます。
IoT Core のルールは以下のように設定します。
- SQL ステートメント: SELECT * FROM "dt/fa-app/incoming/#"
- アクション (Repoblish):
- (Republish先の) トピック: dt/fa-app/${get_dynamodb("TBL1", "thingname", ${topic(4)}, "GetItem権限がある IAM ロール").envname}/${topic(4)}
さて、この状態で dt/fa-app/incoming/d1094 へデータ送信 (MQTT Publish) をしてみると、MQTT テストクライアント で以下のように確認できます。
MQTT テストクライアントは、最新メッセージが一番上に表示されます。そのため、時系列としては一番下から見ていきます。
topic(4) は d1094 に、get_dynamodb(...).envname は Key= d1094 に対応する envname である dev と置き換わっています。その結果、 dt/fa-app/dev/d1094 という新たなトピック名が組み立てられて、そこへ Republish されます。
デバイス側からは dt/fa-app/incoming/… という 1 トピックへの送信をするだけです。dev や prod といった環境切り替えは DynamoDB 上で行えるようになります。
実装上の注意点
このアーキテクチャの適用には、注意することが4点あります。
1つ目は get_dynamodb() の実行に失敗した時です。検索結果が無かった場合も含まれます。
この時、 Republish は実行されず、Amazon CloudWatch Logs の IoT Core のログにエラーとして出力されます。メトリクスフィルタ 等によるモニタリングをオススメします。
{
"timestamp": "2024-09-23 07:39:42.329",
"logLevel": "ERROR",
"traceId": "4fe18597-4f72-0c72-b125-b57d54eac9fa",
"accountId": "***",
"status": "Failure",
"eventType": "RuleExecution",
"clientId": "N/A",
"topicName": "dt/fa-app/incoming/d1094",
"ruleName": "lookupRule1",
"principalId": "***",
"reason": "ExternFunctionException",
"details": "Function 'GetDynamodb' failed to execute for rule 'lookupRule1'. Not able to retrieve and/or parse the requested item from dynamo. ErrorMessage: Cannot invoke \"com.amazonaws.services.dynamodbv2.document.Item.toJSON()\" because \"result\" is null"
}
2つ目は Republish で指定できるトピックの最大文字数です。
256文字を超える設定は「256 バイトを超えることはできません。」となります。この制限は、 設定時のテキストエリアに入力した文字数自体が対象です。そのため、DynamoDB のテーブル名や、実行時の IAM ロール名が長いと設定できない可能性があります。
3つ目は Republish に使用する IAM ロールのポリシーです。
Republish アクションを新規作成した際、指定した IAM ロールに aws-iot-rule-** というポリシーが新規作成されます。トピック内に ${get_dynamodb(...)} や ${topic(...)} といった関数が含まれた場合、下図のように "その文字列そのもの" でポリシーが作られてしまい、実質無効なポリシーになります。
この場合、当該ポリシーを調整 (例えば dt/fa-app/* などに) してください。
4つ目はコストです。
このアーキテクチャでは Republish を用いてるため、必ず2つの MQTT メッセージが発生します。つまり、メッセージング料金が倍になります。そこで検討したいのが Basic Injest です。Basic Injest とは、「メッセージブローカー」を介さずにルールへ直接メッセージを送信でき、メッセージング料金を削減できる機能で、Republish 先トピックの指定にも利用できます。
具体的にはトピックに $$aws/rules/RULENAME/dt/... と指定することで、 RULENAME としているルールへメッセージを直接送信できます。Republish 先のトピック指定で、 $ で始まる予約済みトピックに送信する際には、$$ としているのがポイントです。Basic Injest や Republish 時に指定できるトピック名については、基本的な取り込みによるメッセージングコストの削減 や Republish のパラメータ をご覧ください。
加えて、DynamoDB のコストも考慮すべきです。IoT Core でのルール評価毎に DynamoDB の読み込みが発生します。この頻度は「対象デバイス数 × 送信頻度」となるため、この情報を基に DynamoDB のキャパシティモードの選択 といったコスト最適化を検討してください。
おわりに ― 環境分離と IoT Core の Republish
動作環境の分離をすることで、積極的な開発ができます。一方で、デバイス上での設定切り替えは手間です。その切り替えをクラウドにオフロードできる方法として、今回の記事をご紹介しました。get_dynamodb() 関数の部分には aws_lambda() 関数 を同様に利用できます。コストには注意しつつも、運用の手間が減らせる構成が見えてきたのではないでしょうか。
この手法は新規の IoT システムだけでなく、既に IoT Core をご利用の方でも適用できます。今回の例として使っていた dt/fa-app/incoming/... トピックを現在使っているトピックとすれば、ベストプラクティスへの再整理ができるわけです。そういう面でも、IoT Coreの Republish は有用でしょう。
上手なトピック設計で、安定かつ柔軟な IoT システムを構築してください!
筆者プロフィール
松下 享平 (まつした こうへい)
株式会社ソラコム テクノロジー・エバンジェリスト / AWS Community Hero
IoT の活用事例やデモを通じて、IoT を世に広める講演や執筆を行う。登壇回数は延べ 500 以上、共著に『IoT エンジニア養成読本』(技術評論社) 等。1978 年生まれ、静岡育ち。座右の銘は「論よりコード」。JAWS-UG IoT 専門支部所属、AWS ヒーロー (2020 年受賞)
AWS を無料でお試しいただけます