大規模な IoT デバイス デプロイのベスト プラクティス

IoT ソリューションを数百万台のデバイスにスケーリングすることは困難な場合があります。 多くの場合、大規模なソリューションは、サービスとサブスクリプションの制限に従って設計する必要があります。 お客様が Azure IoT Device Provisioning Service を使用する場合、IoT Hub や Azure IoT device SDK など、他の Azure IoT プラットフォーム サービスやコンポーネントと組み合わせて使用します。 この記事では、これらのサービスを利用し、デプロイをスケールアウトできるようにするためのベスト プラクティス、パターン、設計に組み込めるサンプル コードについて説明します。プロジェクトの設計フェーズから始まるこれらのパターンとプラクティスに従えば、IoT デバイスのパフォーマンスを最大化できます。

新しいデバイスのプロビジョニング

初回プロビジョニングは、IoT ソリューションの一部として初めてデバイスをオンボードするプロセスです。 大規模なデプロイに取り組む場合、すべてのデバイスが同時に接続を試みることによって発生するオーバーロード状況を回避するために、プロビジョニング プロセスをスケジュールすることが重要です。

時間差プロビジョニング スケジュールの使用

数百万台の規模でデバイスをデプロイする場合、すべてのデバイスを一度に登録すると、スロットリング (HTTP 応答コード 429, Too Many Requests) が原因で DPS インスタンスが過負荷になり、デバイスの登録エラーが発生する可能性があります。 このようなスロットリングを防ぐために、デバイスの時間差登録スケジュールを使用します。 DPS のクォータと制限に従って、デバイス登録バッチ サイズを構成します。 たとえば、登録率が 1 分あたり 200 デバイスの場合、オンボードのバッチ サイズはバッチあたり 200 デバイスになります。

再試行操作

サービスのビジー状態が原因で一時的な障害が発生した場合、再試行ロジックにより、デバイスは IoT クラウドに正常に接続できます。 ただし、再試行の数が多いと、その能力近く、またはその能力いっぱいで実行されている、ビジー状態のサービスのパフォーマンスが低下する場合があります。 他の Azure サービスと同様に、エクスポネンシャル バックオフを使用してインテリジェントな再試行メカニズムを実装する必要があります。 さまざまな再試行パターンの詳細については、再試行の設計パターンに関するページと一時的な障害の処理に関するページを参照してください。

スロットル時にただちにデプロイを再試行するのではなく、retry-after ヘッダーに指定されている時間まで待機します。 サービスから使用できる再試行ヘッダーがない場合、このアルゴリズムは、よりスムーズなデバイス オンボード エクスペリエンスを実現するのに役立ちます。

min_retry_delay_msec = 1000
max_retry_delay_msec = (1.0 / <load>) * <T> * 1000
max_random_jitter_msec = max_retry_delay_msec

このロジックでは、デバイスは、min_retry_delay_msecmax_retry_delay_msec の間のランダムな時間の再接続を遅延させます。 再試行の最大遅延は、次の変数で計算されます:

  • <load> は、値が > 0 の構成可能な係数です。これは、読み込み時間の平均に 1 秒あたりの接続数を乗算して負荷が実行されることを示します
  • <T> は、デバイスをコールドブートするための絶対的な最小時間です (T = N / cps として計算され、N はデバイスの総数、cps は 1 秒あたりの接続数のサービス制限)。

再試行操作のタイミングについて詳しくは、再試行のタイミングに関するページを参照してください。

デバイスの再プロビジョニング

再プロビジョニングは、デバイスが以前に正常に接続された後に、そのデバイスを IoT Hub にプロビジョニングする必要がある場合のプロセスです。 デバイスを IoT Hub に再接続することが必要になる理由は多数あります。次に例を示します:

  • 停電、ネットワーク接続の損失、geo 再配置、ファームウェアの更新、出荷時の設定へのリセット、または証明書キーのローテーションにより、デバイスを再起動する。
  • 計画外の IoT Hub の停止により、IoT Hub インスタンスを使用できない。

デバイスが再起動するたびにプロビジョニング プロセスを実行する必要はありません。 再プロビジョニングされたほとんどのデバイスは、最終的に同じ IoT ハブに接続されます。 代わりに、デバイスで、以前の正常な接続からキャッシュされた情報を使用して、その IoT ハブへ直接接続を試みる必要があります。

接続文字列を保存できるデバイス

初期プロビジョニング後に接続文字列を格納できるデバイスは、それを行い、再起動後に IoT Hub に直接再接続を試みる必要があります。 このパターンにより、適切な IoT Hub に正常に接続するときの待機時間が短縮されます。 ここでは、次の 2 つのケースが考えられます。

  • デバイスの再起動時に接続する IoT Hub が、以前に接続した IoT Hub と同じ。

    キャッシュから取得された接続文字列は正常に動作し、デバイスは同じエンドポイントに再接続できます。 プロビジョニング プロセスを新たに開始する必要はありません。

  • デバイスの再起動時に接続する IoT Hub が、以前に接続した IoT Hub とは異なる。

    メモリに保存されている接続文字列が正しくありません。 同じエンドポイントに接続しようとしても成功しないため、IoT Hub 接続の再試行メカニズムがトリガーされます。 IoT Hub 接続エラーのしきい値に達すると、再試行メカニズムによって、プロビジョニング プロセスの新たな開始が自動的にトリガーされます。

接続文字列を保存できないデバイス

一部のデバイスには、過去に成功した IoT Hub 接続からの接続文字列のキャッシュに対応できる十分な占有領域またはメモリがありません。 これらのデバイスは、再起動後に DPS を使用して再プロビジョニングする必要があります。 再登録するには、DPS 登録 API を使用します。 1 分あたりの再登録の数は、DPS の デバイス登録の制限に基づいて制限されていることに注意してください。

再プロビジョニングのサンプル

このセクションのコード例では、デバイス キャッシュとの間で読み取りと書き込みを行うクラスと、接続文字列が見つかった場合にデバイスを IoT Hub に再接続し、そうでない場合は DPS 経由で再プロビジョニングするコードを示しています。

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace ProvisioningCache
{
  public class ProvisioningDetailsFileStorage : IProvisioningDetailCache
  {
    private string dataDirectory = null;

    public ProvisioningDetailsFileStorage()
    {
      dataDirectory = Environment.GetEnvironmentVariable("ProvisioningDetailsDataDirectory");
    }

    public ProvisioningResponse GetProvisioningDetailResponseFromCache(string registrationId)
    {
      try
        {
          var provisioningResponseFile = File.ReadAllText(Path.Combine(dataDirectory, registrationId));

          ProvisioningResponse response = JsonConvert.DeserializeObject<ProvisioningResponse>(provisioningResponseFile);

          return response;
        }
      catch (Exception ex)
      {
        return null;
      }
    }

    public void SetProvisioningDetailResponse(string registrationId, ProvisioningResponse provisioningDetails)
    {
      var provisioningDetailsJson = JsonConvert.SerializeObject(provisioningDetails);

      File.WriteAllText(Path.Combine(dataDirectory, registrationId), provisioningDetailsJson);
    }
  }
}

次のようなコードを使用して、キャッシュに接続情報があるかどうかを判断した後、デバイスの再接続をどのように進めるかを決定できます。

IProvisioningDetailCache provisioningDetailCache = new ProvisioningDetailsFileStorage();

var provisioningDetails = provisioningDetailCache.GetProvisioningDetailResponseFromCache(registrationId);

// If no info is available in cache, go through DPS for provisioning
if(provisioningDetails == null)
{
  logger.LogInformation($"Initializing the device provisioning client...");
  using var transport = new ProvisioningTransportHandlerAmqp();
  ProvisioningDeviceClient provClient = ProvisioningDeviceClient.Create(dpsEndpoint, dpsScopeId, security, transport);
  logger.LogInformation($"Initialized for registration Id {security.GetRegistrationID()}.");
  logger.LogInformation("Registering with the device provisioning service... ");

  // This method will attempt to retry in case of a transient fault
  DeviceRegistrationResult result = await registerDevice(provClient);
  provisioningDetails = new ProvisioningResponse() { iotHubHostName = result.AssignedHub, deviceId = result.DeviceId };
  provisioningDetailCache.SetProvisioningDetailResponse(registrationId, provisioningDetails);
}

// If there was IoT Hub info from previous provisioning in the cache, try connecting to the IoT Hub directly
// If trying to connect to the IoT Hub returns status 429, make sure to retry operation honoring
//   the retry-after header
// If trying to connect to the IoT Hub returns a 500-series server error, have an exponential backoff with
//   at least 5 seconds of wait-time
// For all response codes 429 and 5xx, reprovision through DPS
// Ideally, you should also support a method to manually trigger provisioning on demand
if (provisioningDetails != null)
{
  logger.LogInformation($"Device {provisioningDetails.deviceId} registered to {provisioningDetails.iotHubHostName}.");
  logger.LogInformation("Creating TPM authentication for IoT Hub...");
  IAuthenticationMethod auth = new DeviceAuthenticationWithTpm(provisioningDetails.deviceId, security);
  logger.LogInformation($"Testing the provisioned device with IoT Hub...");
  DeviceClient iotClient = DeviceClient.Create(provisioningDetails.iotHubHostName, auth, TransportType.Amqp);
  logger.LogInformation($"Registering the Method Call back for Reprovisioning...");
  await iotClient.SetMethodHandlerAsync("Reprovision",reprovisionDirectMethodCallback, iotClient);

  // Now you should start a thread into this method and do your business while the DeviceClient is still connected
  await startBackgroundWork(iotClient);
  logger.LogInformation("Wait until closed...");

  // Wait until the app unloads or is cancelled
  var cts = new CancellationTokenSource();
  AssemblyLoadContext.Default.Unloading += (ctx) => cts.Cancel();
  Console.CancelKeyPress += (sender, cpe) => cts.Cancel();

  await WhenCancelled(cts.Token);
  await iotClient.CloseAsync();
  Console.WriteLine("Finished.");
}

IoT Hub 接続に関する考慮事項

1 つの IoT ハブは、100 万台のデバイスとモジュールに制限されます。 100 万を超えるデバイスを使用する場合は、デプロイの規模を拡大するときに、ハブあたりのデバイスの数を 100 万に制限し、必要に応じてハブを追加します。 詳細については、IoT Hub のクォータに関するページを参照してください。 100 万台を超えるデバイスを計画していて、特定のリージョン (データ所在地の要件により EU リージョンなど) でそれらのデバイスをサポートする必要がある場合、サポートに問い合わせて、デプロイ先のリージョンに、現在および今後のスケールをサポートできるキャパシティがあるかどうかを確認できます。

DPS 経由で IoT Hub に接続する場合、デバイスは接続時のエラー コードに応じて次のロジックを使用する必要があります:

  • 500 番台のサーバー エラー応答を受け取ったら、キャッシュされた資格情報またはデバイスの登録状態の検索 API 呼び出しの結果を使用して、接続を再試行します。
  • 401, Unauthorized403, Forbidden、または 404, Not Found を受け取った場合、DPS 登録 API を呼び出して完全な再登録を実行します。

デバイスはいつでも、ユーザーが開始した再プロビジョニング コマンドに応答できる必要があります。

デバイスが IoT Hub から切断された場合、デバイスは DPS に戻る前に、同じ IoT Hub に 15 から 30 分間、直接接続する必要があります。

DPS を使用する場合のその他の IoT Hub シナリオ:

  • IoT Hub のフェールオーバー: 接続情報の変更は許可されないため、デバイスを継続して動作させる必要があり、Hub が再び使用可能になったら接続を再試行するロジックを用意する。
  • IoT Hub の変更: カスタム割り当てポリシーを使用して、別の IoT Hub にデバイスを割り当てる必要がある。
  • IoT Hub 接続の再試行: 積極的な再試行戦略を使用しないでください。 代わりに、再試行の少なくとも 1 分前にギャップを許容してください。
  • IoT Hub のパーティション: デバイス戦略がテレメトリに大きく依存する場合に、Device-to-cloud パーティションの数を増やす必要がある。

デバイスを監視する

デプロイ全体で重要な部分になるのが、ソリューションをエンドツーエンドで監視して、システムが適切に実行されているか確認することです。 IoT デバイスの大規模デプロイのサービスの正常性を監視する方法は複数あります。 サービスの監視では、次のパターンが効果的であることが実証されています。

  • DPS インスタンスの各登録グループに対してクエリを実行するアプリケーションを作成し、そのグループに登録されているデバイスの合計数を取得し、さまざまな登録グループからの数値を集計します。 この数値により、DPS を介して現在登録されていて、かつサービスの状態を監視するために使用できるデバイスの正確な数が判明します。
  • 特定の期間、デバイスの登録を監視します。 たとえば、前の 5 日間の DPS インスタンスの登録率を監視します。 この方法では、おおよその数値のみが提供され、期間も制限されることに注意してください。

次のステップ