教程:使用 Visual Studio Code 开发 IoT Edge 模块

适用于:IoT Edge 1.4 checkmark IoT Edge 1.4

本教程逐步介绍如何开发自己的代码并将其部署到 IoT Edge 设备。 可以使用 Azure IoT Edge 模块部署代码,直接将业务逻辑实现到 IoT Edge 设备。 在将代码部署到 Linux 设备快速入门中,你已创建一个 IoT Edge 设备并已从 Azure 市场部署了一个模块。

本文包含适用于两种 IoT Edge 开发工具的步骤。

  • Azure IoT Edge 开发工具命令行 (CLI)。 此工具是开发的首选工具。
  • 适用于 Visual Studio Code 的 Azure IoT Edge 工具扩展。 该扩展处于维护模式

使用本文开头的工具选择器按钮选择工具版本。

在本教程中,你将了解如何执行以下操作:

  • 设置开发计算机。
  • 使用 IoT Edge Tools 创建新项目。
  • 将项目生成为 Docker 容器,并将其存储在 Azure 容器注册表中。
  • 将代码部署到 IoT Edge 设备。

在本教程中创建的 IoT Edge 模块可以筛选设备生成的温度数据。 它只在温度高于指定阈值的情况下,向上游发送消息。 在边缘进行的此类分析适用于减少传递到云中和存储在云中的数据量。

先决条件

开发计算机:

  • 使用自己的计算机或虚拟机。
  • 开发计算机必须支持嵌套虚拟化才能运行容器引擎。
  • 大多数可以运行容器引擎的操作系统都可用于为 Linux 设备开发 IoT Edge 模块。 本教程使用 Windows 计算机,但指出 macOS 或 Linux 上的已知差异。
  • 安装 Visual Studio Code
  • 安装 Azure CLI

Azure IoT Edge 设备:

云资源:

如果没有 Azure 试用版订阅,请在开始前创建一个试用版订阅

提示

有关 Visual Studio Code 或 Visual Studio 2022 中交互式调试的指导:

本教程讲解适用于 Visual Studio Code 的开发步骤。

关键概念

本教程详细介绍如何开发 IoT Edge 模块。 IoT Edge 模块是包含可执行代码的容器。 可以将一个或多个模块部署到 IoT Edge 设备。 模块执行特定的任务,例如,从传感器引入数据、清理和分析数据,或者将消息发送到 IoT 中心。 有关详细信息,请参阅了解 Azure IoT Edge 模块

开发 IoT Edge 模块时,了解开发计算机和部署模块的目标 IoT Edge 设备之间的差异非常重要。 为保存模块代码而生成的容器必须与目标设备的操作系统 (OS) 匹配。 例如,最常见的方案是在 Windows 计算机上开发面向运行 IoT Edge 的 Linux 设备的模块。 在这种情况下,容器操作系统将为 Linux。 在学习本教程的过程中,请记住开发计算机 OS容器 OS 之间的差异。

提示

如果使用的是 IoT Edge for Linux on Windows,则方案中的“目标设备”是 Linux 虚拟机,而不是 Windows 主机。

本教程针对使用 Linux 容器运行 IoT Edge 的设备。 只要开发计算机可以运行 Linux 容器,就可以使用首选操作系统。 我们建议通过 Linux 容器使用 Visual Studio Code 进行开发,它就是本教程使用的工具。 还可以使用 Visual Studio,但这两个工具提供的支持存在差异。

下表列出了 Visual Studio Code 和 Visual Studio 支持的 Linux 容器开发方案。

Visual Studio Code Visual Studio 2019/2022
Linux 设备体系结构 Linux AMD64
Linux ARM32v7
Linux ARM64
Linux AMD64
Linux ARM32
Linux ARM64
Azure 服务 Azure Functions
Azure 流分析
Azure 机器学习
语言 C
C#
Java
Node.js
Python
C
C#
详细信息 适用于 Visual Studio Code 的 Azure IoT Edge 适用于 Visual Studio 2019 的 Azure IoT Edge 工具
Azure IoT Edge Tools for Visual Studio 2022

安装容器引擎

IoT Edge 模块被打包为容器,因此,需要在开发计算机上安装与 Docker 兼容的容器管理系统来生成和管理容器。 Docker Desktop 提供广泛的功能支持且非常流行,因此我们建议使用它进行开发。 使用 Windows 上的 Docker Desktop 可在 Linux 容器和 Windows 容器之间切换,以便为不同类型的 IoT Edge 设备开发模块。

按照 Docker 文档的说明在开发计算机上安装:

设置工具

安装基于 Python 的 Azure IoT Edge 开发工具以创建 IoT Edge 解决方案。 有两个选项:

重要

适用于 Visual Studio Code 的 Azure IoT Edge 工具扩展处于维护模式。 首选开发工具为命令行 (CLI) Azure IoT Edge 开发工具

使用适用于 Visual Studio Code 的 IoT 扩展开发 IoT Edge 模块。 这些扩展提供项目模板、自动化部署清单的创建,并允许监视和管理 IoT Edge 设备。 本部分将安装 Visual Studio Code 和 IoT 扩展,然后设置 Azure 帐户用于在 Visual Studio Code 中管理 IoT 中心资源。

  1. 安装 Azure IoT Edge 扩展。

  2. 安装 Azure IoT 中心扩展。

  3. 安装扩展后,选择“查看”>“命令面板”打开命令面板。

  4. 再次在命令面板中搜索并选择 Azure IoT Hub:Select IoT Hub 命令。 按照提示选择 Azure 订阅和 IoT 中心。

  5. 通过选择左侧活动栏中的图标,或选择“查看”>“资源管理器”,打开 Visual Studio Code 的“资源管理器”部分 。

  6. 在“资源管理器”部分的底部,展开折叠的“Azure IoT 中心/设备”菜单。 应该看到与通过命令面板选择的 IoT 中心关联的设备和 IoT Edge 设备。

安装特定于语言的工具

安装特定于你正在开发的语言的工具:

创建容器注册表

本教程将使用 Azure IoT Tools 扩展来生成模块并从文件创建容器映像。 然后将该映像推送到用于存储和管理映像的注册表。 最后,从注册表部署在 IoT Edge 设备上运行的映像。

可以使用任意兼容 Docker 的注册表来保存容器映像。 两个常见 Docker 注册表服务分别是 Azure 容器注册表Docker 中心。 本教程使用 Azure 容器注册表。

如果还没有容器注册表,请执行以下步骤,以便在 Azure 中创建一个新的:

  1. Azure 门户中,选择“创建资源” > “容器”> “容器注册表”。

  2. 提供以下所需值以创建容器注册表:

    字段
    订阅 从下拉列表中选择“订阅”。
    资源组 对在 IoT Edge 快速入门和教程中创建的所有测试资源使用同一资源组。 例如,IoTEdgeResources
    注册表名称 提供唯一名称。
    位置 选择靠近你的位置。
    SKU 选择“基本”。
  3. 依次选择“查看 + 创建”、“创建”。

  4. 从 Azure 门户主页的“资源”节选择新的容器注册表以将其打开。

  5. 在容器注册表的左窗格中,从位于“设置”下的菜单中选择“访问密钥”。

    Screenshot of the Access Keys menu location.

  6. 用切换按钮启用“管理员用户”,查看容器注册表的“用户名”和“密码”。

  7. 复制“登录服务器”、“用户名”和“密码”的值,并将其保存在方便的位置。 本教程将使用这些值来访问容器注册表。

创建新的模块项目

Azure IoT Edge 扩展为 Visual Studio Code 中支持的所有 IoT Edge 模块语言提供项目模板。 这些模板包含将工作模块部署到测试 IoT Edge 所需的所有文件和代码,或为你提供使用自己的业务逻辑自定义模板的起点。

创建项目模板

IoT Edge 开发工具将 Azure IoT Edge 开发简化为由环境变量驱动的命令。 它可帮助你开始使用 IoT Edge 开发容器和 IoT Edge 解决方案基架进行 IoT Edge 开发,其中包含默认模块和所有必需的配置文件。

  1. 使用你选择的路径为解决方案创建一个目录。 切换到 iotedgesolution 目录。

    mkdir c:\dev\iotedgesolution
    cd c:\dev\iotedgesolution
    
  2. 使用 iotedgedev solution init 命令创建解决方案并以选择的开发语言设置 Azure IoT 中心。

    iotedgedev solution init --template csharp
    

iotedgedev solution init 脚本会提示你完成以下几个步骤:

  • 向 Azure 进行身份验证
  • 选择一个 Azure 订阅
  • 选择或创建资源组
  • 选择或创建 Azure IoT 中心
  • 选择或创建 Azure IoT Edge 设备

使用 Visual Studio Code 和 Azure IoT Edge 扩展。 首先创建一个解决方案,然后生成该解决方案中的第一个模块。 每个解决方案可以包含多个模块。

  1. 选择“查看”>“命令面板” 。
  2. 在“命令面板”中,输入并运行 Azure IoT Edge: New IoT Edge Solution 命令。
  3. 浏览到要在其中创建新解决方案的文件夹,然后选择“选择文件夹” 。
  4. 输入解决方案的名称。
  5. 选择首选开发语言的模块模板作为解决方案中的第一个模块。
  6. 输入模块的名称。 选择容器注册表中唯一的名称。
  7. 提供模块的映像存储库的名称。 Visual Studio Code 使用“localhost:5000/<你的模块名称>”自动填充模块名。 将其替换为你自己的注册表信息。 如果使用本地 Docker 注册表进行测试,请使用 localhost。 如果使用 Azure 容器注册表,那么请从注册表的设置中使用登录服务器。 登录服务器如下所示:<注册表名称>.azurecr.cn。 仅替换字符串的 localhost:5000 部分,使最终结果类似于 <注册表名称>.azurecr.cn/<模块名称>

Visual Studio Code 采用你提供的信息,创建一个 IoT Edge 解决方案,然后在新窗口中加载它。

创建解决方案后,解决方案将包含以下主文件:

  • .vscode 文件夹包含配置文件 launch.json。

  • 一个 modules 文件夹,其中包含每个模块的子文件夹。 在每个模块的子文件夹中,module.json 文件控制模块的构建和部署方式。

  • 一个 .env 文件,列出环境变量 。 默认情况下,容器注册表的环境变量为 localhost:5000。

  • 名为 deployment.template.json 和 deployment.debug.template.json 的两个模块部署文件列出了要部署到设备的模块。 默认情况下,该列表包含 IoT Edge 系统模块(edgeAgent 和 edgeHub)和示例模块,例如:

    注意

    确切地安装哪些模块取决于选择的语言。

设置 IoT Edge 运行时版本

最新的稳定 IoT Edge 系统模块版本为 1.4。 将系统模块设置为版本 1.4。

  1. 在 Visual Studio Code 中,打开 deployment.debug.template.json 部署清单文件。 部署清单是一个 JSON 文档,用于描述要在目标 IoT Edge 设备上配置的模块。

  2. 更改系统运行时模块映像 edgeAgent 和 edgeHub 的运行时版本。 例如,如果要使用 IoT Edge 运行时版本 1.4,请更改部署清单文件中的以下行:

    "systemModules": {
        "edgeAgent": {
    
            "image": "mcr.microsoft.com/azureiotedge-agent:1.4",
    
        "edgeHub": {
    
            "image": "mcr.microsoft.com/azureiotedge-hub:1.4",
    

向 IoT Edge 代理提供注册表凭据

环境文件存储容器注册表的凭据,并将其与 IoT Edge 运行时共享。 运行时需要这些凭据才能将容器映像拉取到 IoT Edge 设备中。

IoT Edge 扩展尝试从 Azure 中拉取容器注册表凭据并将其填充到环境文件中。

注意

仅当为模块提供了映像存储库时,才会创建环境文件。 如果接受 localhost 默认值在本地进行测试和调试,则不需要声明环境变量。

检查你的凭据是否存在。 如果没有,请立即添加:

  1. 如果 Azure 容器注册表是你的注册表,请设置 Azure 容器注册表用户名和密码。 通过 Azure 门户中容器注册表的“设置”>“访问密钥”菜单获取这些值。

  2. 打开模块解决方案中的 .env 文件。

  3. 添加从 Azure 容器注册表中复制的 usernamepassword 值。 例如:

    CONTAINER_REGISTRY_SERVER="myacr.azurecr.cn"
    CONTAINER_REGISTRY_USERNAME="myacr"
    CONTAINER_REGISTRY_PASSWORD="<registry_password>"
    
  4. 保存对 .env 文件所做的更改。

备注

本教程使用 Azure 容器注册表的管理员登录凭据,这对于开发和测试方案非常方便。 为生产方案做好准备后,建议使用最低权限身份验证选项(如服务主体或存储库范围的令牌)。 有关详细信息,请参阅管理容器注册表的访问权限

目标体系结构

需要选择每种解决方案的目标体系结构,因为这会影响容器的生成和运行方式。 默认值为 Linux AMD64。 在本教程中,我们使用 Ubuntu 虚拟机作为 IoT Edge 设备,并将保留默认的“amd64”。

如果需要更改解决方案的目标体系结构,请遵循以下步骤。

  1. 打开命令面板并搜索 Azure IoT Edge:Set Default Target Platform for Edge Solution,或选择窗口底部侧栏中的快捷方式图标。

  2. 在命令面板中,从选项列表中选择目标体系结构。

在后面的步骤中创建容器映像时,将设置目标体系结构。

使用自定义代码更新模块

每个模板都包含示例代码,该代码从 SimulatedTemperatureSensor 模块提取模拟传感器数据并将其路由到 IoT 中心。 此示例模块接收消息,然后传递消息。 管道功能演示 IoT Edge 中的一个重要概念,即模块之间相互通信的方式。

每个模块可以在其代码中声明多个输入输出队列。 在设备上运行的 IoT Edge 中心将来自一个模块的输出的消息路由到一个或多个模块的输入。 用于声明输入和输出的特定代码因语言而异,但所有模块的概念都相同。 有关在模块之间路由的详细信息,请参阅声明路由

项目模板附带的示例 C# 代码使用适用于 .NET 的 IoT 中心 SDK 中的 ModuleClient 类

  1. 在 Visual Studio Code 资源管理器中,打开“模块”>“filtermodule”>“ModuleBackgroundService.cs”。

  2. filtermodule 命名空间之前,为稍后要使用的类型添加三个 using 语句:

    using System.Collections.Generic;     // For KeyValuePair<>
    using Microsoft.Azure.Devices.Shared; // For TwinCollection
    using Newtonsoft.Json;                // For JsonConvert
    
  3. temperatureThreshold 变量添加到 ModuleBackgroundService 类。 此变量设置一个值,若要向 IoT 中心发送数据,测量的温度必须超出该值。

    static int temperatureThreshold { get; set; } = 25;
    
  4. 添加 MessageBody、Machine 和 Ambient 类。 这些类将为传入消息的正文定义所需的架构。

    class MessageBody
    {
        public Machine machine {get;set;}
        public Ambient ambient {get; set;}
        public string timeCreated {get; set;}
    }
    class Machine
    {
        public double temperature {get; set;}
        public double pressure {get; set;}
    }
    class Ambient
    {
        public double temperature {get; set;}
        public int humidity {get; set;}
    }
    
  5. 找到 ExecuteAsync 函数。 此函数可创建并配置 ModuleClient 对象,使模块能够连接到本地 Azure IoT Edge 运行时以发送和接收消息。 创建 ModuleClient 后,代码将从模块孪生的所需属性中读取 temperatureThreshold 值。 代码注册一个回调,以通过名为 input1 的终结点从 IoT Edge 中心接收消息。

    将对 ProcessMessageAsync 方法的调用替换为一种新方法,以更新终结点名称以及在输入到达时调用的方法。 此外添加 SetDesiredPropertyUpdateCallbackAsync 方法用于更新所需属性。 若要进行此更改,请使用以下代码替换 ExecuteAsync 方法的最后一行:

    // Register a callback for messages that are received by the module.
    // await _moduleClient.SetInputMessageHandlerAsync("input1", PipeMessage, cancellationToken);
    
    // Read the TemperatureThreshold value from the module twin's desired properties
    var moduleTwin = await _moduleClient.GetTwinAsync();
    await OnDesiredPropertiesUpdate(moduleTwin.Properties.Desired, _moduleClient);
    
    // Attach a callback for updates to the module twin's desired properties.
    await _moduleClient.SetDesiredPropertyUpdateCallbackAsync(OnDesiredPropertiesUpdate, null);
    
    // Register a callback for messages that are received by the module. Messages received on the inputFromSensor endpoint are sent to the FilterMessages method.
    await _moduleClient.SetInputMessageHandlerAsync("inputFromSensor", FilterMessages, _moduleClient);
    
  6. onDesiredPropertiesUpdate 方法添加到 ModuleBackgroundService 类。 此方法从孪生模块接收所需属性的更新,然后更新 temperatureThreshold 变量,使之匹配。 所有模块都有自己的孪生模块,因此可以直接从云配置在模块中运行的代码。

    static Task OnDesiredPropertiesUpdate(TwinCollection desiredProperties, object userContext)
    {
        try
        {
            Console.WriteLine("Desired property change:");
            Console.WriteLine(JsonConvert.SerializeObject(desiredProperties));
    
            if (desiredProperties["TemperatureThreshold"]!=null)
                temperatureThreshold = desiredProperties["TemperatureThreshold"];
    
        }
        catch (AggregateException ex)
        {
            foreach (Exception exception in ex.InnerExceptions)
            {
                Console.WriteLine();
                Console.WriteLine("Error when receiving desired property: {0}", exception);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine();
            Console.WriteLine("Error when receiving desired property: {0}", ex.Message);
        }
        return Task.CompletedTask;
    }
    
  7. 添加 FilterMessages 方法。 每当模块从 IoT Edge 中心接收消息,就会调用此方法。 此方法筛选掉那些所报告温度低于温度阈值(通过孪生模块进行设置)的消息。 它还将 MessageType 属性添加到消息,其值设置为“警报”。

    async Task<MessageResponse> FilterMessages(Message message, object userContext)
    {
        var counterValue = Interlocked.Increment(ref _counter);
        try
        {
            ModuleClient moduleClient = (ModuleClient)userContext;
            var messageBytes = message.GetBytes();
            var messageString = Encoding.UTF8.GetString(messageBytes);
            Console.WriteLine($"Received message {counterValue}: [{messageString}]");
    
            // Get the message body.
            var messageBody = JsonConvert.DeserializeObject<MessageBody>(messageString);
    
            if (messageBody != null && messageBody.machine.temperature > temperatureThreshold)
            {
                Console.WriteLine($"Machine temperature {messageBody.machine.temperature} " +
                    $"exceeds threshold {temperatureThreshold}");
                using (var filteredMessage = new Message(messageBytes))
                {
                    foreach (KeyValuePair<string, string> prop in message.Properties)
                    {
                        filteredMessage.Properties.Add(prop.Key, prop.Value);
                    }
    
                    filteredMessage.Properties.Add("MessageType", "Alert");
                    await moduleClient.SendEventAsync("output1", filteredMessage);
                }
            }
    
            // Indicate that the message treatment is completed.
            return MessageResponse.Completed;
        }
        catch (AggregateException ex)
        {
            foreach (Exception exception in ex.InnerExceptions)
            {
                Console.WriteLine();
                Console.WriteLine("Error in sample: {0}", exception);
            }
            // Indicate that the message treatment is not completed.
            var moduleClient = (ModuleClient)userContext;
            return MessageResponse.Abandoned;
        }
        catch (Exception ex)
        {
            Console.WriteLine();
            Console.WriteLine("Error in sample: {0}", ex.Message);
            // Indicate that the message treatment is not completed.
            ModuleClient moduleClient = (ModuleClient)userContext;
            return MessageResponse.Abandoned;
        }
    }
    
  8. 保存 ModuleBackgroundService.cs 文件。

  9. 在 Visual Studio Code 资源管理器的 IoT Edge 解决方案工作区中打开 deployment.template.json 文件。

  10. 由于我们更改了模块侦听的终结点的名称,因此还需要更新部署清单中的路由,以便 edgeHub 将消息发送到新终结点。

    在 $edgeHub 模块孪生中找到 routes 节 。 更新 sensorTofiltermodule 路由以将 input1 替换为 inputFromSensor

    "sensorTofiltermodule": "FROM /messages/modules/tempSensor/outputs/temperatureOutput INTO BrokeredEndpoint(\"/modules/filtermodule/inputs/inputFromSensor\")"
    
  11. filtermodule 模块孪生添加到部署清单。 在 modulesContent 节底部的 $edgeHub 模块孪生后面插入以下 JSON 内容:

       "filtermodule": {
           "properties.desired":{
               "TemperatureThreshold":25
           }
       }
    
  12. 保存 deployment.template.json 文件。

生成并推送解决方案

你已更新模块代码和部署模板来帮助了解一些关键部署概念。 现在,已准备好生成模块容器映像并将其推送到容器注册表。

在 Visual Studio Code 中,打开 deployment.template.json 部署清单文件。 部署清单描述要在目标 IoT Edge 设备上配置的模块。 在部署之前,需要使用正确的 createOptions 值更新 Azure 容器注册表凭据和模块映像。 若要了解 createOption 值的详细信息,请参阅如何配置 IoT Edge 模块的容器创建选项

如果使用 Azure 容器注册表来存储模块映像,请将凭据添加到 deployment.template.json 中的 modulesContent>edgeAgent>settings>registryCredentials 部分。 将 myacr 替换为你自己的注册表名称,并提供密码和登录服务器地址。 例如:

"registryCredentials": {
    "myacr": {
        "username": "myacr",
        "password": "<your_acr_password>",
        "address": "myacr.azurecr.cn"
    }
}

将以下字符串化内容添加到列出的每个系统(edgeHub 和 edgeAgent)和自定义模块(filtermodule 和 tempSensor)的 createOptions 值或替换此值。 请根据需要更改这些值。

"createOptions": "{\"HostConfig\":{\"PortBindings\":{\"5671/tcp\":[{\"HostPort\":\"5671\"}],\"8883/tcp\":[{\"HostPort\":\"8883\"}],\"443/tcp\":[{\"HostPort\":\"443\"}]}}}"

例如,filtermodule 配置应类似于:

"filtermodule": {
"version": "1.0",
"type": "docker",
"status": "running",
"restartPolicy": "always",
"settings": {
   "image": "myacr.azurecr.cn/filtermodule:0.0.1-amd64",
   "createOptions": "{\"HostConfig\":{\"PortBindings\":{\"5671/tcp\":[{\"HostPort\":\"5671\"}],\"8883/tcp\":[{\"HostPort\":\"8883\"}],\"443/tcp\":[{\"HostPort\":\"443\"}]}}}"
}

生成模块 Docker 映像

通过选择“终端”>“新建终端”或打开 Visual Studio Code 集成终端。

使用 dotnet publish 命令生成适用于 Linux 和 amd64 体系结构的容器映像。 将目录更改为项目中的 filtermodule 目录,并运行 dotnet publish 命令。

dotnet publish --os linux --arch x64 /t:PublishContainer

目前,iotedgedev 工具模板的目标为 .NET 7.0。 如果要以不同版本的 .NET 为目标,可以编辑 filtermodule.csproj 文件并更改 TargetFrameworkPackageReference 值。 例如,若要以 .NET 8.0 为目标,filtermodule.csproj 文件应如下所示:

<Project Sdk="Microsoft.NET.Sdk.Worker">
    <PropertyGroup>
        <TargetFramework>net8.0</TargetFramework>
        <Nullable>enable</Nullable>
        <ImplicitUsings>enable</ImplicitUsings>
    </PropertyGroup>
    <ItemGroup>
        <PackageReference Include="Microsoft.Azure.Devices.Client" Version="1.42.0" />
        <PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
        <PackageReference Include="Microsoft.NET.Build.Containers" Version="8.0.101" />
    </ItemGroup>
</Project>

使用容器注册表信息、版本和体系结构标记 docker 映像。 将 myacr 替换为自己的注册表名称。

docker tag filtermodule myacr.azurecr.cn/filtermodule:0.0.1-amd64

推送模块 Docker 映像

向 Docker 提供容器注册表凭据,以便它可以推送要存储在注册表中的容器映像。

  1. 使用 Azure 容器注册表 (ACR) 凭据登录到 Docker。

    docker login -u <ACR username> -p <ACR password> <ACR login server>
    

    可能会收到一条安全警告,推荐使用 --password-stdin。 虽然它是为生产方案推荐的最佳做法,但它超出了本教程的范畴。 有关详细信息,请参阅 docker login 参考。

  2. 登录到 Azure 容器注册表。 可能需要安装 Azure CLI 才能使用 az 命令。 此命令要求提供在“设置”>“访问密钥”中的容器注册表中的用户名和密码。

    az acr login -n <ACR registry name>
    

    提示

    如果你在学习本教程过程中的任何时候退出登录,请重复 Docker 和 Azure 容器注册表登录步骤以继续。

  3. 将模块映像推送到本地注册表或容器注册表。

    docker push <ImageName>
    

    例如:

    # Push the Docker image to the local registry
    
    docker push localhost:5000/filtermodule:0.0.1-amd64
    
    # Or push the Docker image to an Azure Container Registry. Replace myacr with your Azure Container Registry name.
    
    az acr login --name myacr
    docker push myacr.azurecr.cn/filtermodule:0.0.1-amd64
    

更新部署模板

使用容器注册表映像位置更新部署模板 deployment.template.json。 例如,如果使用 Azure 容器注册表 myacr.azurecr.cn 并且图像为 filtermodule:0.0.1-amd64,请将 filtermodule 配置更新为:

"filtermodule": {
    "version": "1.0",
    "type": "docker",
    "status": "running",
    "restartPolicy": "always",
    "settings": {
        "image": "myacr.azurecr.cn/filtermodule:0.0.1-amd64",
        "createOptions": "{\"HostConfig\":{\"PortBindings\":{\"5671/tcp\":[{\"HostPort\":\"5671\"}],\"8883/tcp\":[{\"HostPort\":\"8883\"}],\"443/tcp\":[{\"HostPort\":\"443\"}]}}}"
    }
}

在 Visual Studio Code 资源管理器中右键单击 deployment.template.json 文件,然后选择“生成并推送 IoT Edge 解决方案” 。

“生成并推送”命令会启动三项操作。 首先,它在解决方案中创建名为 config 的新文件夹,用于保存基于部署模板和其他解决方案文件中的信息生成的完整部署清单。 其次,它会运行 docker build,以基于目标体系结构的相应 dockerfile 生成容器映像。 然后,它会运行 docker push,以将映像存储库推送到容器注册表。

首次执行此过程可能需要几分钟时间,但下次运行命令时速度会变快。

可选:更新模块和映像

如果对模块代码进行更改,则需要重新生成模块映像并将其推送到容器注册表。 使用本部分中的步骤更新生成和容器映像。 如果未对模块代码进行任何更改,则可以跳过此部分。

打开新建的 config 文件夹中的 deployment.amd64.json 文件。 文件名会反映目标体系结构,因此,选择其他体系结构时会有所不同。

请注意,具有占位符的两个参数现在都包含了适当的值。 registryCredentials 部分具有从 .env 文件中拉取的注册表用户名和密码。 filtermodule 具有完整的映像存储库,以及来自 module.json 文件的名称、版本和体系结构标记。

  1. 打开 filtermodule 文件夹中的 module.json 文件。

  2. 更改模块映像的版本号。 例如,将补丁版本号递增为 "version": "0.0.2",就像对模块代码进行了小修补一样。

    提示

    模块版本支持版本控制,允许在将更新部署到生产环境之前在少量设备上测试更改。 如果在生成并推送之前未递增模块版本,则会替代容器注册表中的存储库。

  3. 将更改保存到 module.json 文件。

使用 0.0.2 版本标记生成并推送更新的映像。 例如,若要为本地注册表或 Azure 容器注册表生成并推送映像,请使用以下命令:


# Build the container image for Linux and amd64 architecture.

dotnet publish --os linux --arch x64

# For local registry:
# Tag the image with version 0.0.2, x64 architecture, and the local registry.

docker tag filtermodule localhost:5000/filtermodule:0.0.2-amd64

# For Azure Container Registry:
# Tag the image with version 0.0.2, x64 architecture, and your container registry information. Replace **myacr** with your own registry name.

docker tag filtermodule myacr.azurecr.cn/filtermodule:0.0.2-amd64

再次右键单击 deployment.template.json 文件,然后再次选择“生成并推送 IoT Edge 解决方案” 。

再次打开 deployment.amd64.json 文件。 请注意,再次运行生成并推送命令时,生成系统不会创建新文件。 反而,同一文件会更新以反映更改。 filtermodule 映像现在指向容器的 0.0.2 版。

若要进一步验证“生成并推送”命令执行的操作,请转到 Azure 门户并导航到容器注册表。

在容器注册表中,选择“存储库”,然后选择“filtermodule”。 验证映像的两个版本是否都已推送到注册表。

Screenshot of where to view both image versions in your container registry.

疑难解答

如果在生成并推送模块映像时遇到错误,这通常与开发计算机上的 Docker 配置有关。 使用以下检查来检查配置:

  • 是否使用从容器注册表复制的凭据运行 docker login azure --cloud-name AzureChinaCloud 命令? 这些凭据与用于登录 Azure 的凭据不同。
  • 你的容器存储库是否正确? 它是否拥有正确的容器注册表名称和正确的模块名称? 打开 filtermodule 文件夹中要检查的 module.json 文件。 存储库值应类似于 <注册表名称>.azurecr.cn/filtermodule。
  • 如果为模块使用的名称不是 filtermodule,那么使用的名称在整个解决方案中是否保持一致?
  • 计算机运行的容器类型与要生成的容器类型是否相同? 本教程适用于 Linux IoT Edge 设备,因此,Visual Studio Code 应在侧栏指明 amd64arm32v7,且 Docker Desktop 应运行 Linux 容器。

将模块部署到设备

你已确认生成的容器映像存储在容器注册表中,现在可将它们部署到设备。 确保 IoT Edge 设备已启动并运行。

使用 IoT Edge Azure CLI set-modules 命令将模块部署到 Azure IoT 中心。 例如,若要将 deployment.template.json 文件中定义的模块部署到 IoT Edge 设备 my-device 的 IoT 中心 my-iot-hub,请使用以下命令。 将 hub-name、device-id 和 login IoT 中心连接字符串的值替换为自己的值。

az iot edge set-modules --hub-name my-iot-hub --device-id my-device --content ./deployment.template.json --login "HostName=my-iot-hub.azure-devices.cn;SharedAccessKeyName=iothubowner;SharedAccessKey=<SharedAccessKey>"

提示

可以在 Azure 门户中找到包含共享访问密钥的 IoT 中心连接字符串。 转到 IoT 中心>“安全设置”>“共享访问策略”>“iothubowner”。

  1. 在 Visual Studio Code 资源管理器中的“Azure IoT 中心”部分下,展开“设备”可查看 IoT 设备的列表。

  2. 右键单击想要在其中部署的 IoT Edge 设备,然后选择“为单个设备创建部署” 。

  3. 在文件资源管理器中,导航到 config 文件夹,然后选择 deployment.amd64.json 文件。

    请勿使用 deployment.template.json 文件,它不包含容器注册表凭据或模块映像值。 如果以 Linux ARM32 设备为目标,则部署清单的名称为 deployment.arm32v7.json

  4. 在设备下,展开“模块”可查看已部署的正在运行的模块的列表。 选择“刷新”按钮。 应会看到新的 tempSensor 和 filtermodule 模块在设备上运行。

    启动模块可能需要数分钟时间。 IoT Edge 运行时需要接收其新的部署清单,从容器运行时下拉模块映像,然后启动每个新模块。

查看设备消息

示例模块代码通过其输入队列接收消息,并通过其输出队列传递消息。 部署清单声明了从 tempSensor 将消息传递到 filtermodule,再将消息从 filtermodule 转发到 IoT 中心的路由。 使用 Azure IoT Edge 和 Azure IoT 中心扩展可以查看单个设备发出的已抵达 IoT 中心的消息。

  1. 在 Visual Studio Code 资源管理器中,右键单击想要监视的 IoT Edge 设备,然后选择“开始监视内置事件终结点” 。

  2. 观察 Visual Studio Code 的输出窗口,查看到达 IoT 中心的消息。

    Screenshot showing where to view incoming device to cloud messages.

查看设备上的更改

如果想要查看设备本身发生的情况,请使用本部分中的命令检查设备上运行的 IoT Edge 运行时和模块。

本部分中的命令适用于 IoT Edge 设备,而不适用于开发计算机。 如果为 IoT Edge 设备使用虚拟机,请立即连接到虚拟机。 在 Azure 中,转到虚拟机的“概述”页,然后选择“连接”以访问安全 shell 连接 。

  • 查看部署到设备的所有模块,并检查其状态:

    iotedge list
    

    应该看到四个模块:两个 IoT Edge 运行时模块、tempSensor 和 filtermodule。 你应该会看到四个全都列为正在运行。

  • 检查特定模块的日志:

    iotedge logs <module name>
    

    IoT Edge 模块区分大小写。

    tempSensor 和 filtermodule 日志应显示其正在处理的消息。 edgeAgent 模块负责启动其他模块,因此其日志包含有关实施部署清单的信息。 如果你发现模块未列出或未运行,则 edgeAgent 日志可能有误。 edgeHub 模块负责模块和 IoT 中心之间的通信。 如果模块已启动并运行,但消息未到达 IoT 中心,则 edgeHub 日志可能有错误信息。

清理资源

如果打算继续学习下一篇建议的文章,可以保留已创建的资源和配置,以便重复使用。 还可以继续使用相同的 IoT Edge 设备作为测试设备。

否则,可以删除本文中使用的本地配置和 Azure 资源,以免产生费用。

删除 Azure 资源

删除 Azure 资源和资源组的操作不可逆。 请确保不要意外删除错误的资源组或资源。 如果在现有的包含要保留资源的资源组中创建了 IoT 中心,请只删除 IoT 中心资源本身,而不要删除资源组。

若要删除资源,请执行以下操作:

  1. 登录到 Azure 门户,然后选择“资源组”。

  2. 选择包含 IoT Edge 测试资源的资源组的名称。

  3. 查看资源组中所包含资源的列表。 若要删除这一切,可以选择“删除资源组”。 如果只需删除部分内容,可以单击要单独删除的每个资源。

后续步骤

在本教程中,你将在开发计算机上设置 Visual Studio Code,并部署了第一个 IoT Edge 模块,该模块包含用于筛选 IoT Edge 设备生成的原始数据的代码。

可以继续学习后续教程,了解如何借助 Azure IoT Edge 部署 Azure 云服务,在边缘位置处理和分析数据。