Cloud Intelligence™Cloud Intelligence™

Cloud Intelligence™

本番規模IoTのベストプラクティス:AWS実装編(前編)

By Matthew PorterSep 21, 202115 min read

このページはEnglishDeutschEspañolFrançaisItalianoPortuguêsでもご覧いただけます。

数百万台規模のIoTデバイスをクラウド環境に安全かつ手軽に登録し、高スループットのデータストリームを適切に保存して、可視化まで一気通貫で実現する——そんな仕組みを実装可能なコード付きで学びたい方は、ぜひ本記事を読み進めてください。本記事ではIoTデバイスの認可からデータストリーミングまでの一連のワークフローを取り上げ、保存と可視化は続編で解説します。クラウドにはAWSを、IoTデバイスには温度センサーを接続したRaspberry Piを使用します。

本番規模IoTのベストプラクティス:AWS実装編

概要

本記事は次の構成でお届けします。

  1. Raspberry Piのソフトウェア・ハードウェアセットアップ
  2. デバイス固有の認証情報をプロビジョニングする方法の概要
  3. プロビジョニングテンプレートとブートストラップ証明書の作成(実践編)
  4. デバイス接続テストと温度データのストリーミング
  5. ストリーミングデータの保存(後編で解説)
  6. ストリーミングデータの可視化(後編で解説)

ボリュームのある内容ですが、早速始めましょう。本記事を読み進めるのに必要なのは、BashとPythonの基礎、AWSウェブコンソールの基本操作、そして長丁場を乗り切るための大きめのコーヒー1杯くらいです。

Raspberry Piのソフトウェア・ハードウェアセットアップ

まずはRaspberry Piを複数台用意して起動します(本記事ではPi 3を使用)。microSDカードへのOSインストールを自動化できるRaspbian OS Imagerの利用がおすすめです。デスクトップが立ち上がりデバイスのアップデートが完了したら、次のコマンドでAWS IoT用のSDKをインストールします。

pip3 install -U awsiotsdk

先に進む前に、このパッケージについて押さえておきましょう。

1. なぜIoT SDKは汎用のboto3 SDKと別になっているのか? boto3はHTTPベースで、ほとんどのAWS操作を素早く実行するのに便利なプロトコルです。しかし、接続が断続的になりやすく、データが主にデバイス側から送出されるような長時間接続のメッセージ送信には、HTTPは適していません。IoT SDKが採用しているMQTTはまさにIoT用途のために設計されたプロトコルで、接続が不安定な状況下でも、デバイスが頻繁にメッセージを発行し、必要に応じて受信する処理を大幅に簡略化します。

2. 「awsiotsdk」パッケージはAWS IoT SDKのv2にあたります。名前の似たv1の「AWSIoTPythonSDK」とは別物です。これまでに公開されているAWS IoTのウォークスルー記事のほとんどはv1を使用していますが、残念ながらv1では本番規模のデバイスレジストリ実装が難しくなります。v2はメッセージの発行・受信もシンプルになっているため、本記事では最新版を使っていきます。

必要なAWS SDKがRaspberry Piにインストールできたら、次はデジタル温度センサーを接続します。本記事と一緒に手を動かしたい方は、DS18B20センサーがおすすめです。

センサーをRaspberry Piに接続するには、以下も必要です。

ミニブレッドボード

ブレッドボード用ジャンパーワイヤー

抵抗器セット(4.7kΩの抵抗器が必要です)

これらの部品の到着を数日待つ場合でも、記事はそのまま読み進めて構いません。温度値をシミュレートしてストリーミングするスクリプトも用意しています。すでに部品が手元にある方は、以下のチュートリアル動画の冒頭5分間で、センサーをRaspberry Piに接続し、温度値が読み取れることを確認する手順を確認できます。

Raspberry Pi DS18B20温度センサーチュートリアル

このチュートリアルの手順に加え、再起動のたびにmodprobeを実行せずに済むよう、/etc/modulesに以下を追記して、起動時にonewireモジュールが自動でロードされるようにすることをおすすめします。

w1-gpio
w1-therm

デバイス固有の認証情報をプロビジョニングする方法の概要

世に出回っているAWS IoTの解説記事の多くは、IoT SDKのv1を使い、単一のデバイスをクラウドアカウントに登録するだけの簡易な例を扱っています。典型的な流れは、AWS IoT権限を付与した証明書を作成し、その証明書ファイルをデバイスに配置して、クラウドへデータをストリーミングするIoT API呼び出しを可能にする、というものです。

しかし、現実のIoTユースケースでは数千〜数百万台のデバイスがクラウドにストリーミングするため、こうした例はあまり実用的ではありません。フリート内の各デバイスには固有の認証情報を割り当てておくべきで、そうすればデバイスやAWS認証情報が侵害され不正利用された場合(例:偽データを自社プラットフォームにストリーミングするなど)でも、フリート内の他デバイスに影響を与えずにその認証情報だけを無効化できます。

では、デバイス固有の認証情報を作成するプロセスを、どうすれば最もシンプルに実現できるでしょうか。数百万件もの証明書をあらかじめ作成し、製造時に各デバイスへ個別に配置するような調整作業を避けることはできるでしょうか。あるいは、デバイスが製造ラインを流れてソフトウェアと認証情報をブートストラップされる際に、製造業者が都度クラウド環境にAPIを叩いて新しい証明書を作成する必要のない方法はあるでしょうか。これらのアプローチは複雑でミスが起きやすく、製造業者に余計な負担を強いるため、避けるべきです。

幸い、IoT SDKのv2でやや実装しやすくなった、シンプルな解決策があります。デバイス固有の認証情報を大規模に割り当てるには、AWS IoTコンソールで次の2つを作成します。(1)フリートプロビジョニングテンプレート、(2)すべてのデバイスに配置するブートストラップ証明書、の2つです。流れは以下のとおりです。

  1. すべてのIoTデバイスに配置する単一のIoT証明書を作成します。「ブートストラップ」証明書と呼ばれるこの証明書には、(a)デバイス固有の証明書を作成しその認証情報を取得するリクエストの発行、(b)IoTレジストリへの自身の追加、のみを許可するパーミッションポリシーが紐付けられています。リクエストにはオプションでシリアル番号などのデバイス固有の識別子を含められます。
  2. AWS IoTプラットフォームは証明書作成リクエストを受け取ると、新しい証明書を発行し、関連ファイルをデバイスに配信します。新規証明書に紐付くIoT権限や、新規登録デバイスに付与されるさまざまな属性は、AWSが「フリートプロビジョニングテンプレート」と呼ぶ、ユーザーが作成するテンプレートに基づいて決まります。

このテンプレートは新規登録デバイスに属性(例:{"DeviceType": "RaspberryPi"})を付与すると同時に、すべてのデバイス固有証明書を同じパーミッションポリシーに紐付けつつも、デバイスごとに異なる権限を定義することを可能にします。

例えば、登録デバイス用テンプレートの単一のIoTポリシーを、デバイス名「sensor123」はIoTトピックsensors/temp/sensor123にのみ発行可能、デバイス名「sensor456」はIoTトピックsensors/temp/sensor456にのみ発行可能、といった形で設計できます。言い換えれば、フリートプロビジョニングテンプレートのパーミッションポリシーがあれば、デバイスと証明書を登録するたびに新しいパーミッションポリシーを作る必要がなくなるのです。このアプローチなら、ポリシーを1回更新するだけでIoTフリート全体に権限変更を適用することもできます。 3. デバイス証明書の作成プロセスは、AWSが「pre-provisioning hook」と呼ぶ仕組みによって任意でゲートできます。これはユーザーが実装するLambda関数で、証明書リクエスト時に固有識別子の提供を必須にし、ホワイトリスト(例:製造済みシリアル番号一覧)やブラックリスト(例:侵害・悪用された端末のシリアル番号一覧)と照合できます。 4. リクエストが承認されると、データストリーミング用のデバイス証明書ファイルがデバイスに配信され、テンプレートで定義した属性とともにIoTレジストリへ登録されます。これ以降、ブートストラップ証明書はデバイスにとって不要となります。

このワークフローなら、IoTデバイスの製造業者は各デバイスにブートストラップ証明書を提供するだけで済みます。データストリーミング用のデバイス固有証明書は、必要に応じて製造工場内ですぐに作成・取得することも、エンドユーザーの手に渡った後に取得することもできます。いずれの場合も、製造業者はユーザー側が用意するソフトウェアをデバイスに組み込み、起動時かつインターネット接続が利用可能で、かつデバイス証明書が見つからないときに、フリートプロビジョニングテンプレートを利用した証明書作成プロセスを毎回走らせるように設定します。

フリートプロビジョニングテンプレートとブートストラップ証明書の実践例

ここまでで情報量が多くなりましたが、実際に手を動かして実装してみれば、より腹落ちするはずです。

以下に、フリートプロビジョニングテンプレートとブートストラップ証明書を介してデバイスを追加し、IoTレジストリをセットアップする一連の実装例を示します。続いて、作成したデバイス証明書を使って温度データをAWS IoTプラットフォームへストリーミングします。

まずAWS IoT Coreのサービス画面に移動します。このサービスを使うのが初めての場合は、「Onboard a device」または「Onboard many devices」を促すウィザード画面が表示されます。本来はここからフリートプロビジョニングテンプレートのプロセスを進められるのですが、このウィザードの仕様上、いったん画面を離れて先にブートストラップ証明書を作成する必要があります。

画面左側のメニューからSecure → Certificatesに進み、「Create」をクリックして「One-click certificate creation」を選択します。これでブートストラップ証明書として使う新しい証明書がすぐに作成されます。証明書(cert.pem)、秘密鍵(private.key)、ルート認証局(DownloadリンクからたどってAmazonRootCA1.pemを保存)を必ずダウンロードし、「Activate」をクリックして有効化してください。

証明書を有効化し、鍵をダウンロードできたら「Attach a policy」をクリックします。この証明書をブートストラップ用として機能させるためのポリシーを作成します。「Add authorization to certificate」画面で「Create New Policy」をクリックし、「IoT_Bootstrapping_Certificate」という名前のポリシーを作成します。このポリシーには、証明書作成およびフリートプロビジョニングテンプレートのプロセスで使用される2つのAWS予約トピックに対する発行・購読・受信の権限を付与します。

region、account ID、templateNameは必ずご自身のアカウントの値に置き換えてください。

Action: iot:Connect
Resource ARN: *Action: iot:Publish
Action: iot:Receive
Resource ARN: arn:aws:iot:us-west-2:123456789012:topic/$aws/certificates/create/*Action: iot:Publish
Action: iot:Receive
Resource ARN: arn:aws:iot:us-west-2:123456789012:topic/$aws/provisioning-templates/SensorTemplate/provision/*Action: iot:Subscribe
Resource ARN: arn:aws:iot:us-west-2:123456789012:topicfilter/$aws/certificates/create/*Action: iot:Subscribe
Resource ARN: arn:aws:iot:us-west-2:123456789012:topicfilter/$aws/provisioning-templates/SensorTemplate/provision/*

「Create」をクリックします。証明書ポリシーが作成できたら、先ほど作った証明書の画面に戻ってこのポリシーを紐付けます。

これでデバイス用のブートストラップ証明書が用意できたので、フリートプロビジョニングテンプレートのウィザードに戻りましょう。Onboard → Get startedをクリックして移動します。

ウィザードで「Onboard many devices」を選び、以下を入力します。

  • Template name:「SensorTemplate」
  • Provisioning role:「IoTProvisioningRole」というロールを作成し、AWS管理ポリシー「AWSIoTThingsRegistration」を紐付けます。このロールにより、AWS IoTプラットフォームは新しいデバイスが接続した際に登録できるようになります。
  • 本番環境ではpre-provisioning hookのLambda関数を実装し、デバイス証明書を作成する前にシリアル番号を検証するのが定石ですが、ここでは簡潔さのためこのオプションは扱いません。例えば、ブラックリストや出荷済みシリアル番号一覧を保持するDynamoDBテーブルとシリアル番号を突き合わせる用途に活用できます。簡単な例を以下に示します。
def lambda_handler(event, context):
    serial = event["parameters"]["SerialNumber"]    # シリアル番号の検証関数を実装する
    if is_valid_serial(serial) and not is_blacklisted(serial):
        return {"allowProvisioning": True}    return {"allowProvisioning": False}
  • オプション設定の「Use the AWS IoT registry to manage your device fleet」にチェックを入れます。これによりIoTウェブコンソールから関連デバイスを確認したり、デバイスの状態やソフトウェアの更新を発行したりできるようになります。

次のページでは、テンプレートに紐付けるIoTポリシーを定義し、デバイスごとに固有のIoTトピックに対するメッセージの発行・受信権限を付与します。IoTポリシーはthing policy variableを用いて作成します。thing policy variableを使うと、デバイスごとに固有のIoTトピックへ発行・購読できるようになります。本例では、BuildingName、Location(建物内の場所)、ThingNameに基づくトピックに対して発行・購読を行います。

Action: iot:Connect
Resource ARN: arn:aws:iot:us-west-2:123456789012:client/${iot:Connection.Thing.ThingName}

Action: iot:Publish
Action: iot:Receive
Resource ARN: arn:aws:iot:us-west-2:123456789012:topic/temperature/${iot:Connection.Thing.Attributes[BuildingName]}/${iot:Connection.Thing.Attributes[Location]}/${iot:Connection.Thing.ThingName}

Action: iot:Subscribe
Resource ARN: arn:aws:iot:us-west-2:123456789012:topicfilter/temperature/${iot:Connection.Thing.Attributes[BuildingName]}/${iot:Connection.Thing.Attributes[Location]}/${iot:Connection.Thing.ThingName}

次のページではAWS IoTレジストリの設定を行います。これらは新規プロビジョニングされる各デバイスに割り当てられる属性です。

  • Thing name prefix:sensor_
  • 新しいThing Type「TemperatureSensor」を作成し、Searchable Thing Attributeとして「Location」(例:「loft」「downstairs」)と「BuildingName」(例:「home417」)を設定
  • 検索不可のthing attributeとして「DeviceType」(例:「RaspberryPi」)と「ModelNumber」(例:「3」)を作成

「Create template」をクリックした後の最終画面で、先ほど作成したブートストラップ証明書を選択し、「Attach policy」をクリック、続けて「Enable template」を選びます。これで、ブートストラップ証明書を持つデバイスが本テンプレートを使い、デバイス名固有のIoTトピックへメッセージを発行するのに必要な権限が付与されたデバイス証明書を取得できるようになります。証明書取得に加え、デバイスはフリートプロビジョニングテンプレートを介してIoTレジストリに自身を登録し、定義した属性が割り当てられます。

ここまでの作業を整理します。

  • ブートストラップ証明書を作成し、その証明書を使うデバイスがフリートプロビジョニングテンプレート経由で(1)新しいデバイス固有証明書をリクエストし、(2)デフォルト属性とともにIoTレジストリに追加されることのみを許可するポリシーに紐付けました。
  • 新規登録されたデバイスに対して発行されるデバイス証明書は、そのIoTデバイス固有のIoTトピックへのメッセージ発行・受信を許可するIoTポリシーに紐付けられています。

IoTサービスの準備作業はあと少しです。残るは、フリートプロビジョニングテンプレートを更新し、プロビジョニングリクエスト時に検索可能属性の提供を必須にすることです。「SensorTemplate」に戻り、テンプレートのJSONに以下を追記します。

# "Parameters"の中に "BuildingName" と "Location" を追加
{
  "Parameters": {
    "SerialNumber": {
      "Type": "String"
    },
    "BuildingName": {
      "Type": "String"
    },
    "Location": {
      "Type": "String"
    },
    "AWS::IoT::Certificate::Id": {
      "Type": "String"
    }
  }
}# これらの属性パラメータを "thing": "Properties" 内で参照する
      "Properties": {
        "AttributePayload": {
          "DeviceType": "RaspberryPi",
          "ModelNumber": "3",
          "BuildingName": {
            "Ref": "BuildingName"
          },
          "Location": {
            "Ref": "Location"
          }
        },
        "ThingGroups": [],

IoTコンソールで多くの設定を行ってきました。それでは、ブートストラップ証明書とフリートプロビジョニングテンプレートが正しく機能しているかを、実際のコード例で確認していきましょう。

デバイス接続テストと温度データのストリーミング

ダウンロードした証明書ファイルをRPiデバイス上のフォルダにコピーし、以下のconfig.iniファイルを適切な値で作成します。証明書ファイルのパスを更新するほか、AWS IoTコンソールのSettingsページからアカウントのIoT Endpointを取得しておく必要があります。

# ブートストラップ証明書(root、private、claim certificate)を配置したパスを指定する
SECURE_CERT_PATH = /home/pi/iot_certs/

# ルート証明書、プロビジョニング用claim証明書、秘密鍵のファイル名を指定する。
ROOT_CERT = AmazonRootCA1.pem
CLAIM_CERT = <YOURCERT>-certificate.pem.crt
SECURE_KEY = <YOURCERT>-private.pem.key

# IoT Endpointを指定する
IOT_ENDPOINT = <YOUR_ENDPOINT>-ats.iot.us-west-2.amazonaws.com

# IoTトピック名を指定する
IOT_TOPIC = temperature/${iot:Connection.Thing.Attributes[BuildingName]}/${iot:Connection.Thing.Attributes[Location]}/${iot:Connection.Thing.ThingName}

# IoTプロビジョニングテンプレート名を指定する
PROVISIONING_TEMPLATE_NAME = SensorTemplate

次に、ブートストラップ証明書を使ってデバイス証明書を取得し(iot_certs/permanent_cert/に保存)、デバイスをIoTレジストリに追加する以下のスクリプトを実行します。-lパラメータには、デバイスを設置する自宅内の場所を表すLocation属性を指定します。例えば次のようにします。

./connect_rpi_to_iot_core.py -c config.ini -b home417 -l loft

処理の中身が多いので、IoT SDKのAPI呼び出しの流れを把握するためにも、コードにじっくり目を通しておくことを強くおすすめします。すべて正しく設定できていれば、画面に「Success」と出力されてスクリプトが終了し、permanent_cert/内に3つの証明書ファイルと、デバイス証明書ファイル用の新しいperm_config.iniファイルが生成されているはずです。

このスクリプトでRPiデバイスを追加していくと、IoTコンソールに新しい証明書が次々と表示され、いずれもフリートプロビジョニングテンプレートで定義した単一のIoTポリシー(BuildingName、Location、ThingNameに基づくトピック名へのメッセージ発行・購読を許可するもの)に紐付いていることが確認できます。

このスクリプトで追加された各デバイスは、ThingName sensor_{UUID}として登録されます。UUIDはデバイス上の/etc/machine-idから取得される固有識別子です。これにより、各デバイスはそれぞれ固有のIoTトピックにメッセージを発行することになります。実運用のIoTシナリオでは、デバイスのシリアル番号を固有のThingNameとして組み込む方法も考えられます。

では、シミュレートした温度値でデバイス証明書をテストしてみましょう。以下のスクリプトを実行すると、(1)デバイスのIoTトピックにランダムな数値を発行し、(2)同じトピックを購読して受信した値を表示します。

./pubsub_simulated_temps.py -c /home/pi/iot_certs/permanent_cert/perm_config.ini

以下のような出力が得られるはずです。

おめでとうございます。安全かつ大規模に登録プロセスを実施するために設計されたAWSの方法論に沿って、小規模なIoTデバイスフリートのオンボーディングに成功しました。

実データで接続をテストするには、以下のスクリプトを実行します。先ほどのスクリプトとほぼ同じですが、(1)発行先のトピックを購読しない、(2)5秒ごとに実際の温度値を送信する、という違いがあります。

./publish_temps.py -c /home/pi/iot_certs/permanent_cert/perm_config.ini

出力は次のようになります。

このスクリプトを再起動時にcrontabジョブで実行するようにしておけば(onewireモジュールが起動時にロードされるよう、30秒のスリープを前置)、想定外の再起動が起きてもIoTデバイスはストリーミングを再開します。

$ crontab -e
@reboot sleep 30 && /home/pi/publish_temps.py -c /home/pi/iot_certs/permanent_cert/perm_config.ini

自宅内に設置するすべてのRaspberry Piデバイスに対して、オンボーディングとストリーミングのプロセスを繰り返してください。これでIoTデータを大規模に扱う準備が整いつつあります。

次回:保存と可視化

大規模ストリーミングIoTデータの適切な保存と可視化について解説する後編もぜひお楽しみに。

補足

ここで採用したpub/sub方式のメッセージ発行アプローチは、ストリーミングされるデータをバックエンドの分析アプリだけでなく、エンドユーザーも直接利用するデバイステレメトリのユースケース(例:自宅の温度値を確認できるアプリ経由で利用するケースなど)に最も適しています。バックエンドアプリだけでデータを利用する場合は、AWS IoT Core Best Practices for Designing MQTT Topicsで示されている「IoT Basic Ingest」方式でメッセージを発行する方がコスト効率に優れます。汎用的なIoT Coreトピックではなく、IoT Ruleに紐付くトピックにメッセージを発行することで、トピックの購読機能は使えなくなる代わりに、IoT料金のメッセージング部分を支払う必要がなくなります。