대량으로 IoT Hub 디바이스 ID 가져오기 및 내보내기

각 IoT Hub에는 서비스에서 디바이스 리소스를 만드는 데 사용할 수 있는 ID 레지스트리가 있습니다. 또한 ID 레지스트리를 통해 디바이스 지향 엔드포인트에 대한 액세스를 제어할 수 있습니다. 이 문서에서는 .NET용 Microsoft Azure IoT SDK에 포함된 ImportExportDeviceSample 샘플을 사용하여 ID 레지스트리에서 디바이스 ID를 대량으로 가져오고 내보내는 방법을 설명합니다. IoT Hub를 다른 지역으로 마이그레이션할 때 이 기능을 사용하는 방법에 대한 자세한 내용은 Azure Resource Manager 템플릿을 사용하여 Azure IoT Hub를 수동으로 마이그레이션하는 방법을 참조하세요.

참고 항목

IoT Hub는 제한된 수의 지역에서 최근에 가상 네트워크 지원을 추가했습니다. 이 기능은 가져오기 및 내보내기 작업을 보호하고 인증을 위해 키를 전달할 필요성을 없앱니다. 현재 가상 네트워크 지원은 WestUS2, EastUSSouthCentralUS 지역에서만 사용할 수 있습니다. 가상 네트워크 지원 및 이를 구현하는 API 호출에 대한 자세한 내용은 가상 네트워크에 대한 IoT Hub 지원을 참조하세요.

가져오기 및 내보내기 작업은 사용자가 IoT Hub에 대해 대량 서비스 작업을 실행할 수 있는 작업 상황에서 이루어집니다.

SDK의 RegistryManager 클래스는 Job 프레임워크를 사용하는 ExportDevicesAsyncImportDevicesAsync 메서드를 포함합니다. 이러한 메서드를 사용하면 전체 IoT Hub ID 레지스트리를 내보내고, 가져오고, 동기화할 수 있습니다.

이 문서에서는 RegistryManager 클래스 및 작업 시스템을 사용하여 IoT Hub의 ID 레지스트리로 디바이스를 대량으로 내보내거나 이러한 레지스트리에서 디바이스를 대량으로 가져오는 작업에 대해 설명합니다. 또한 Azure IoT Hub Device Provisioning Service를 사용하여 하나 이상의 IoT Hub에 대해 무인 Just-In-Time 프로비저닝을 수행할 수도 있습니다. 자세한 내용은 프로비저닝 서비스 설명서를 참조하세요.

참고 항목

이 문서의 코드 조각 중 일부는 .NET용 Microsoft Azure IoT SDK와 함께 제공되는 ImportExportDevicesSample 서비스 샘플에 포함되어 있습니다. 샘플은 SDK의 /iothub/service/samples/how to guides/ImportExportDevicesSample 폴더에 있으며 지정된 경우 해당 SDK 샘플에 대한 ImportExportDevicesSample.cs 파일에서 코드 조각이 포함됩니다. .NET용 Azure IoT SDK에 포함된 ImportExportDevicesSample 샘플 및 기타 서비스 샘플에 대한 자세한 내용은 C#용 Azure IoT Hub 서비스 샘플을 참조하세요.

작업이란?

ID 레지스트리 작업은 다음 작업을 수행할 때 작업 시스템을 사용합니다.

  • 표준 런타임 작업에 비해 잠재적으로 실행 시간이 깁니다.

  • 사용자에게 많은 양의 데이터를 반환합니다.

단일 API 호출을 기다리거나 작업 결과를 차단하는 대신에 작업은 비동기적으로 해당 IoT Hub에 대한 작업을 만듭니다. 그런 다음 즉시 JobProperties 개체를 반환합니다.

다음 C# 코드 조각은 작업을 만드는 방법을 보여 줍니다.

// Call an export job on the IoT hub to retrieve all devices
JobProperties exportJob = await 
  registryManager.ExportDevicesAsync(containerSasUri, false);

참고 항목

C# 코드에서 RegistryManager 클래스를 사용하려면 프로젝트에 Microsoft.Azure.Devices NuGet 패키지를 추가합니다. RegistryManager 클래스는 Microsoft.Azure.Devices 네임스페이스에 있습니다.

RegistryManager 클래스를 사용하면 반환된 JobProperties 메타데이터를 사용하는 작업의 상태를 쿼리할 수 있습니다. RegistryManager 클래스의 인스턴스를 만들려면 CreateFromConnectionString 메서드를 사용합니다.

RegistryManager registryManager =
  RegistryManager.CreateFromConnectionString("{your IoT Hub connection string}");

IoT Hub에 대한 연결 문자열을 찾으려면 Azure Portal에서 다음을 수행합니다.

  1. IoT Hub로 이동

  2. 공유 액세스 정책을 선택합니다.

  3. 필요한 사용 권한을 고려하여 정책을 선택합니다.

  4. 해당 정책의 연결 문자열을 복사합니다.

다음 C# 코드 조각은 SDK 샘플의 WaitForJobAsync 메서드에서 5초마다 폴링하여 작업이 실행을 완료했는지 확인하는 방법을 보여줍니다.

// Wait until job is finished
while (true)
{
    job = await registryManager.GetJobAsync(job.JobId);
    if (job.Status == JobStatus.Completed
        || job.Status == JobStatus.Failed
        || job.Status == JobStatus.Cancelled)
    {
        // Job has finished executing
        break;
    }
    Console.WriteLine($"\tJob status is {job.Status}...");

    await Task.Delay(TimeSpan.FromSeconds(5));
}

참고 항목

스토리지 계정에 IoT Hub의 연결을 제한하는 방화벽 구성이 있는 경우 Microsoft 신뢰할 수 있는 자사 예외를 사용하는 것이 좋습니다(관리되는 서비스 ID로 IoT Hub의 선택 지역에서 사용 가능).

디바이스 가져오기/내보내기 작업 제한

모든 IoT Hub 계층에 대해 한 번에 하나의 활성 디바이스 가져오기 또는 내보내기 작업만 허용됩니다. 또한 IoT Hub에는 작업 비율에 대한 제한이 있습니다. 자세한 내용은 IoT Hub 할당량 및 제한을 참조하세요.

내보내기 디바이스

ExportDevicesAsync 메서드를 사용하여 SAS(공유 액세스 서명)를 사용하는 Azure Storage Blob 컨테이너에 전체 IoT Hub ID 레지스트리를 내보냅니다. 이 메서드를 사용하면 사용자가 제어하는 blob 컨테이너에 디바이스 정보의 신뢰할 수 있는 백업을 만들 수 있습니다.

ExportDevicesAsync 메서드에 매개변수 두 개를 지정해야 합니다.

  • Blob 컨테이너의 URI가 포함된 문자열 . 이 URI는 컨테이너에 대한 쓰기 액세스 권한을 부여하는 SAS 토큰을 포함해야 합니다. 작업은 직렬화된 내보내기 디바이스 데이터를 저장하기 위해 이 컨테이너에 블록 blob를 만듭니다. SAS 토큰은 이러한 사용 권한을 포함해야 합니다.

    SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.Read 
       | SharedAccessBlobPermissions.Delete
    
  • 내보내기 데이터에서 인증 키를 제외하려는지 여부를 나타내는 부울 값입니다. false인 경우 인증 키가 내보내기 출력에 포함됩니다. 그렇지 않으면 키는 null로 내보내집니다.

다음 C# 코드 조각은 내보내기 데이터에 디바이스 인증 키를 포함하고 있는 내보내기 작업을 시작한 다음 완료를 폴링하는 방법을 보여 줍니다.

// Call an export job on the IoT Hub to retrieve all devices
JobProperties exportJob = 
  await registryManager.ExportDevicesAsync(containerSasUri, false);

// Wait until job is finished
while(true)
{
    exportJob = await registryManager.GetJobAsync(exportJob.JobId);
    if (exportJob.Status == JobStatus.Completed || 
        exportJob.Status == JobStatus.Failed ||
        exportJob.Status == JobStatus.Cancelled)
    {
    // Job has finished executing
    break;
    }

    await Task.Delay(TimeSpan.FromSeconds(5));
}

SDK 샘플의 ExportDevicesAsync 메서드에서 유사한 코드를 찾을 수 있습니다. 작업은 출력을 제공된 blob 컨테이너에 이름 devices.txt의 블록 blob로 저장합니다. 출력 데이터는 JSON 직렬화된 디바이스 데이터로 구성되며 한 디바이스가 한 줄에 표시됩니다.

다음 예제에서는 출력 데이터를 보여줍니다.

{"id":"Device1","eTag":"MA==","status":"enabled","authentication":{"symmetricKey":{"primaryKey":"abc=","secondaryKey":"def="}}}
{"id":"Device2","eTag":"MA==","status":"enabled","authentication":{"symmetricKey":{"primaryKey":"abc=","secondaryKey":"def="}}}
{"id":"Device3","eTag":"MA==","status":"disabled","authentication":{"symmetricKey":{"primaryKey":"abc=","secondaryKey":"def="}}}
{"id":"Device4","eTag":"MA==","status":"disabled","authentication":{"symmetricKey":{"primaryKey":"abc=","secondaryKey":"def="}}}
{"id":"Device5","eTag":"MA==","status":"enabled","authentication":{"symmetricKey":{"primaryKey":"abc=","secondaryKey":"def="}}}

디바이스에 쌍으로 된 데이터가 있는 경우 해당 쌍 데이터를 디바이스 데이터와 함께 내보냅니다. 다음 예제는 이러한 형식을 보여줍니다. "twinETag" 줄의 모든 데이터는 끝까지 쌍으로 된 데이터입니다.

{
   "id":"export-6d84f075-0",
   "eTag":"MQ==",
   "status":"enabled",
   "authentication":null,
   "twinETag":"AAAAAAAAAAI=",
   "tags":{
      "Location":"LivingRoom"
   },
   "properties":{
      "desired":{
         "Thermostat":{
            "Temperature":75.1,
            "Unit":"F"
         },
      },
      "reported":{}
   }
}

코드에서 이 데이터에 액세스해야 하는 경우 ExportImportDevice 클래스를 사용하여 이 데이터를 역직렬화할 수 있습니다. SDK 샘플의 ReadFromBlobAsync 메서드에서 다음 C# 코드 조각은 이전에 ExportImportDevice에서 BlobClient 인스턴스로 내보낸 디바이스 정보를 읽는 방법을 보여 줍니다.

private static async Task<List<string>> ReadFromBlobAsync(BlobClient blobClient)
{
    // Read the blob file of devices, import each row into a list.
    var contents = new List<string>();

    using Stream blobStream = await blobClient.OpenReadAsync();
    using var streamReader = new StreamReader(blobStream, Encoding.UTF8);
    while (streamReader.Peek() != -1)
    {
        string line = await streamReader.ReadLineAsync();
        contents.Add(line);
    }

    return contents;
}

디바이스 가져오기

RegistryManager 클래스의 ImportDevicesAsync 메서드를 사용하여 IoT Hub ID 레지스트리에서 대량 가져오기 및 동기화 작업을 수행할 수 있습니다. ExportDevicesAsync 메서드와 마찬가지로 ImportDevicesAsync 메서드도 작업 프레임워크를 사용합니다.

ID 레지스트리에 새 디바이스를 프로비전할 뿐만 아니라 기존 디바이스를 업데이트 및 삭제할 수도 있으므로 ImportDevicesAsync 메서드를 사용할 때 주의합니다.

Warning

가져오기 작업은 실행 취소할 수 없습니다. ID 레지스트리를 대량 변경하기 전에 항상 ExportDevicesAsync 메서드를 사용하여 기존 데이터를 다른 Blob 컨테이너에 백업합니다.

ImportDevicesAsync 메서드에 매개 변수 두 개를 선택합니다.

  • 작업에 대한 입력으로 Azure Storage Blob 컨테이너의 URI가 포함된 문자열. 이 URI는 컨테이너에 대한 읽기 액세스 권한을 부여하는 SAS 토큰을 포함해야 합니다. 이 컨테이너에는 ID 레지스트리에 가져올 직렬화된 디바이스 데이터가 포함된 devices.txt 이름의 Blob이 있어야 합니다. 가져오기 데이터는 ExportImportDevice 작업이 devices.txt Blob을 생성할 때 사용하는 것과 같은 JSON 형식의 디바이스 정보를 포함해야 합니다. SAS 토큰은 이러한 사용 권한을 포함해야 합니다.

    SharedAccessBlobPermissions.Read
    
  • 작업에서 출력으로 사용할 Azure Storage blob 컨테이너의 URI가 포함된 문자열. 작업은 이 컨테이너에 완료된 가져오기 작업에서 나온 오류 정보를 저장하기 위한 블록 blob를 생성합니다. SAS 토큰은 이러한 사용 권한을 포함해야 합니다.

    SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.Read 
       | SharedAccessBlobPermissions.Delete
    

참고 항목

두 매개 변수가 같은 blob 컨테이너를 가리킬 수 있습니다. 단순히 별도 매개 변수를 사용하여 출력 컨테이너에 추가 사용 권한이 필요할 때 데이터를 더 강력하게 제어할 수 있습니다.

다음 C# 코드 조각은 가져오기 작업을 시작하는 방법을 보여 줍니다.

JobProperties importJob = 
   await registryManager.ImportDevicesAsync(containerSasUri, containerSasUri);

디바이스 쌍에 데이터를 가져오는 데도 이 메서드를 사용할 수 있습니다. 데이터 입력의 형식은 ExportDevicesAsync 섹션에 나온 형식과 같습니다. 이러한 방식으로 내보낸 데이터를 다시 가져올 수 있습니다.

가져오기 동작

ImportDevicesAsync 메서드를 사용하여 ID 레지스트리에서 다음 대량 작업을 수행할 수 있습니다.

  • 새 디바이스 대량 등록
  • 기존 디바이스의 대량 삭제
  • 대량으로 상태 변경(디바이스를 사용 또는 사용하지 않도록 설정)
  • 새 디바이스 인증 키의 대량 할당
  • 디바이스 인증 키의 대량 자동 다시 생성
  • 쌍으로 된 데이터의 대량 업데이트

단일 ImportDevicesAsync 호출 내에서 이전 작업의 조합을 수행할 수 있습니다. 예를 들어 새 디바이스를 등록하는 동시에 기존 디바이스를 삭제 또는 업데이트할 수 있습니다. ExportDevicesAsync 메서드와 함께 사용하는 경우 모든 디바이스를 한 IoT Hub에서 다른 IoT Hub로 완전히 마이그레이션할 수 있습니다.

각 디바이스에 대한 가져오기 직렬화 데이터에 선택적 importMode 속성을 사용하여 가져오기 프로세스를 디바이스별로 제어합니다. importMode 속성에 다음과 같은 옵션이 있습니다.

  • 만들기
  • CreateOrUpdate(기본값)
  • CreateOrUpdateIfMatchETag
  • Delete
  • DeleteIfMatchETag
  • Update
  • UpdateIfMatchETag
  • UpdateTwin
  • UpdateTwinIfMatchETag

이러한 각 가져오기 모드 옵션에 대한 자세한 내용은 ImportMode를 참조하세요.

가져오기 작업 문제 해결

가져오기 작업을 사용하여 디바이스를 만들면 IoT Hub의 디바이스 수 제한에 가까울 때 할당량 문제와 함께 실패할 수 있습니다. 이러한 실패는 총 디바이스 수가 여전히 할당량 한도보다 적은 경우에도 발생할 수 있습니다. IotHubQuotaExceeded(403002) 오류가 다음 오류 메시지와 함께 반환됩니다. "IotHub의 총 디바이스 수가 할당된 할당량을 초과했습니다."

이 오류가 발생하면 다음 쿼리를 사용하여 IoT Hub에 등록된 총 디바이스 수를 반환할 수 있습니다.

SELECT COUNT() as totalNumberOfDevices FROM devices

IoT Hub에 등록할 수 있는 총 디바이스 수에 대한 자세한 내용은 IoT Hub 제한을 참조하세요.

여전히 남은 할당량이 있으면 IotHubQuotaExceeded(403002) 오류로 실패한 디바이스의 작업 출력 blob을 검사할 수 있습니다. 그런 다음 이러한 디바이스를 IoT Hub에 개별적으로 추가할 수 있습니다. 예를 들어 AddDeviceAsync 또는 AddDeviceWithTwinAsync 메서드를 사용할 수 있습니다. 동일한 오류가 발생할 수 있으므로 다른 작업을 사용하여 디바이스를 추가하지 마세요.

디바이스 가져오기 예제 – 대량 디바이스 프로비저닝

SDK 샘플의 GenerateDevicesAsync 메서드에서 다음 C# 코드 조각은 다음과 같은 여러 디바이스 ID를 생성하는 방법을 보여 줍니다.

  • 인증 키를 포함합니다.
  • 블록 Blob에 해당 디바이스 정보를 씁니다.
  • 디바이스를 ID 레지스트리로 가져옵니다.
private async Task GenerateDevicesAsync(RegistryManager registryManager, int numToAdd)
{
    var stopwatch = Stopwatch.StartNew();

    Console.WriteLine($"Creating {numToAdd} devices for the source IoT hub.");
    int interimProgressCount = 0;
    int displayProgressCount = 1000;
    int totalProgressCount = 0;

    // generate reference for list of new devices we're going to add, will write list to this blob
    BlobClient generateDevicesBlob = _blobContainerClient.GetBlobClient(_generateDevicesBlobName);

    // define serializedDevices as a generic list<string>
    var serializedDevices = new List<string>(numToAdd);

    for (int i = 1; i <= numToAdd; i++)
    {
        // Create device name with this format: Hub_00000000 + a new guid.
        // This should be large enough to display the largest number (1 million).
        string deviceName = $"Hub_{i:D8}_{Guid.NewGuid()}";
        Debug.Print($"Adding device '{deviceName}'");

        // Create a new ExportImportDevice.
        var deviceToAdd = new ExportImportDevice
        {
            Id = deviceName,
            Status = DeviceStatus.Enabled,
            Authentication = new AuthenticationMechanism
            {
                SymmetricKey = new SymmetricKey
                {
                    PrimaryKey = GenerateKey(32),
                    SecondaryKey = GenerateKey(32),
                }
            },
            // This indicates that the entry should be added as a new device.
            ImportMode = ImportMode.Create,
        };

        // Add device to the list as a serialized object.
        serializedDevices.Add(JsonConvert.SerializeObject(deviceToAdd));

        // Not real progress as you write the new devices, but will at least show *some* progress.
        interimProgressCount++;
        totalProgressCount++;
        if (interimProgressCount >= displayProgressCount)
        {
            Console.WriteLine($"Added {totalProgressCount}/{numToAdd} devices.");
            interimProgressCount = 0;
        }
    }

    // Now have a list of devices to be added, each one has been serialized.
    // Write the list to the blob.
    var sb = new StringBuilder();
    serializedDevices.ForEach(serializedDevice => sb.AppendLine(serializedDevice));

    // Write list of serialized objects to the blob.
    using Stream stream = await generateDevicesBlob.OpenWriteAsync(overwrite: true);
    byte[] bytes = Encoding.UTF8.GetBytes(sb.ToString());
    for (int i = 0; i < bytes.Length; i += BlobWriteBytes)
    {
        int length = Math.Min(bytes.Length - i, BlobWriteBytes);
        await stream.WriteAsync(bytes.AsMemory(i, length));
    }
    await stream.FlushAsync();

    Console.WriteLine("Running a registry manager job to add the devices.");

    // Should now have a file with all the new devices in it as serialized objects in blob storage.
    // generatedListBlob has the list of devices to be added as serialized objects.
    // Call import using the blob to add the new devices.
    // Log information related to the job is written to the same container.
    // This normally takes 1 minute per 100 devices (according to the docs).

    // First, initiate an import job.
    // This reads in the rows from the text file and writes them to IoT Devices.
    // If you want to add devices from a file, you can create a file and use this to import it.
    //   They have to be in the exact right format.
    try
    {
        // The first URI is the container to import from; the file defaults to devices.txt, but may be specified.
        // The second URI points to the container to write errors to as a blob.
        // This lets you import the devices from any file name. Since we wrote the new
        // devices to [devicesToAdd], need to read the list from there as well.
        var importGeneratedDevicesJob = JobProperties.CreateForImportJob(
            _containerUri,
            _containerUri,
            _generateDevicesBlobName);
        importGeneratedDevicesJob = await registryManager.ImportDevicesAsync(importGeneratedDevicesJob);
        await WaitForJobAsync(registryManager, importGeneratedDevicesJob);
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Adding devices failed due to {ex.Message}");
    }

    stopwatch.Stop();
    Console.WriteLine($"GenerateDevices, time elapsed = {stopwatch.Elapsed}.");
}

디바이스 가져오기 예제 – 대량 삭제

SDK 샘플의 DeleteFromHubAsync 메서드에서 다음 C# 코드 조각은 IoT Hub에서 모든 디바이스를 삭제하는 방법을 보여 줍니다.

private async Task DeleteFromHubAsync(RegistryManager registryManager, bool includeConfigurations)
{
    var stopwatch = Stopwatch.StartNew();

    Console.WriteLine("Deleting all devices from an IoT hub.");

    Console.WriteLine("Exporting a list of devices from IoT hub to blob storage.");

    // Read from storage, which contains serialized objects.
    // Write each line to the serializedDevices list.
    BlobClient devicesBlobClient = _blobContainerClient.GetBlobClient(_destHubDevicesImportBlobName);

    Console.WriteLine("Reading the list of devices in from blob storage.");
    List<string> serializedDevices = await ReadFromBlobAsync(devicesBlobClient);

    // Step 1: Update each device's ImportMode to be Delete
    Console.WriteLine("Updating ImportMode to be 'Delete' for each device and writing back to the blob.");
    var sb = new StringBuilder();
    serializedDevices.ForEach(serializedEntity =>
    {
        // Deserialize back to an ExportImportDevice and change import mode.
        ExportImportDevice device = JsonConvert.DeserializeObject<ExportImportDevice>(serializedEntity);
        device.ImportMode = ImportMode.Delete;

        // Reserialize the object now that we've updated the property.
        sb.AppendLine(JsonConvert.SerializeObject(device));
    });

    // Step 2: Write the list in memory to the blob.
    BlobClient deleteDevicesBlobClient = _blobContainerClient.GetBlobClient(_hubDevicesCleanupBlobName);
    await WriteToBlobAsync(deleteDevicesBlobClient, sb.ToString());

    // Step 3: Call import using the same blob to delete all devices.
    Console.WriteLine("Running a registry manager job to delete the devices from the IoT hub.");
    var importJob = JobProperties.CreateForImportJob(
        _containerUri,
        _containerUri,
        _hubDevicesCleanupBlobName);
    importJob = await registryManager.ImportDevicesAsync(importJob);
    await WaitForJobAsync(registryManager, importJob);

    // Step 4: delete configurations
    if (includeConfigurations)
    {
        BlobClient configsBlobClient = _blobContainerClient.GetBlobClient(_srcHubConfigsExportBlobName);
        List<string> serializedConfigs = await ReadFromBlobAsync(configsBlobClient);
        foreach (string serializedConfig in serializedConfigs)
        {
            try
            {
                Configuration config = JsonConvert.DeserializeObject<Configuration>(serializedConfig);
                await registryManager.RemoveConfigurationAsync(config.Id);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Failed to deserialize or remove a config.\n\t{serializedConfig}\n\n{ex.Message}");
            }
        }
    }

    stopwatch.Stop();
    Console.WriteLine($"Deleted IoT hub devices and configs: time elapsed = {stopwatch.Elapsed}");
}

컨테이너 SAS URI 가져오기

다음 코드 샘플은 blob 컨테이너에 대한 읽기, 쓰기 및 삭제 사용 권한을 가진 SAS URI를 생성하는 방법을 보여 줍니다.

static string GetContainerSasUri(CloudBlobContainer container)
{
  // Set the expiry time and permissions for the container.
  // In this case no start time is specified, so the
  // shared access signature becomes valid immediately.
  var sasConstraints = new SharedAccessBlobPolicy();
  sasConstraints.SharedAccessExpiryTime = DateTime.UtcNow.AddHours(24);
  sasConstraints.Permissions = 
    SharedAccessBlobPermissions.Write | 
    SharedAccessBlobPermissions.Read | 
    SharedAccessBlobPermissions.Delete;

  // Generate the shared access signature on the container,
  // setting the constraints directly on the signature.
  string sasContainerToken = container.GetSharedAccessSignature(sasConstraints);

  // Return the URI string for the container,
  // including the SAS token.
  return container.Uri + sasContainerToken;
}

다음 단계

이 문서에서는 IoT Hub의 ID 레지스트리에 대한 대량 작업을 수행하는 방법을 살펴보았습니다. 한 허브에서 다른 허브로 디바이스를 이동하는 방법을 포함하여 이러한 작업의 대부분은 Azure Resource Manager 템플릿을 사용하여 Azure IoT Hub를 수동으로 마이그레이션하는 방법IoT Hub에 등록된 디바이스 관리 섹션에서 사용됩니다.