你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

教程:在嵌套的 IoT Edge 层次结构上部署连接注册表

在本教程中,你将使用 Azure CLI 命令创建 Azure IoT Edge 设备的两层层次结构,并在每个层将连接注册表部署为模块。 在此方案中,上层中有一个设备与云注册表通信。 下层中有一个设备与其上层中的连接注册表父级通信。

有关将连接的注册表与 IoT Edge 结合使用的概述,请参阅将连接的注册表与 Azure IoT Edge 结合使用

先决条件

  • Azure IoT 中心。 有关部署步骤,请参阅使用 Azure 门户创建 IoT 中心

  • Azure 中的两项连接注册表资源。 有关部署步骤,请使用 Azure CLIAzure 门户查看快速入门。

    • 对于上层,连接注册表可能处于 ReadWrite 或 ReadOnly 模式。 本文假定它处于 ReadWrite 模式,且连接注册表名称存储在环境变量 $CONNECTED_REGISTRY_RW 中。
    • 对于下层,连接注册表必须处于 ReadOnly 模式。 本文假定连接注册表名称存储在环境变量 $CONNECTED_REGISTRY_RO 中。

将映像导入到云注册表

使用 az acr import 命令将以下容器映像导入到云注册表。 如果已导入这些映像,请跳过此步骤。

连接注册表映像

为了支持嵌套的 IoT Edge 方案,连接注册表运行时的容器映像必须在专用 Azure 容器注册表中可用。 使用 az acr import 命令将连接注册表映像导入专用注册表。

# Use the REGISTRY_NAME variable in the following Azure CLI commands to identify the registry
REGISTRY_NAME=<container-registry-name>

az acr import \
  --name $REGISTRY_NAME \
  --source mcr.microsoft.com/acr/connected-registry:0.8.0

IoT Edge 和 API 代理映像

若要支持嵌套 IoT Edge 上连接的注册表,需要为 IoT Edge 和 API 代理部署模块。 将这些映像导入专用注册表。

IoT Edge API 代理模块允许 IoT Edge 设备在同一端口(例如 443)上使用 HTTPS 协议公开多个服务。

az acr import \
  --name $REGISTRY_NAME \
  --source mcr.microsoft.com/azureiotedge-agent:1.2.4

az acr import \
  --name $REGISTRY_NAME \
  --source mcr.microsoft.com/azureiotedge-hub:1.2.4

az acr import \
  --name $REGISTRY_NAME \
  --source mcr.microsoft.com/azureiotedge-api-proxy:1.1.2

az acr import \
  --name $REGISTRY_NAME \
  --source mcr.microsoft.com/azureiotedge-diagnostics:1.2.4

Hello-world 映像

若要测试连接注册表,请导入 hello-world 映像。 此存储库将与连接注册表同步,并由连接注册表客户端拉取。

az acr import \
  --name $REGISTRY_NAME \
  --source mcr.microsoft.com/hello-world:1.1.2

检索连接注册表配置

若要将每个连接注册表部署到层次结构中的 IoT Edge 设备,需要从 Azure 中的已连接注册表资源检索配置设置。 如果需要,请为每个连接注册表运行 az acr connected-registry get-settings 命令,以检索配置。

默认情况下,设置信息不包括同步令牌密码,部署连接注册表时也需要该密码。 (可选)通过传递 --generate-password 1--generate-password 2 参数来生成密码之一。 将生成的密码保存到安全位置。 无法再次检索该密码。

警告

重新生成密码将轮换同步令牌凭据。 如果使用旧密码配置了设备,则需要更新配置。

# Use the REGISTRY_NAME variable in the following Azure CLI commands to identify the registry
REGISTRY_NAME=<container-registry-name>

# Run the command for each registry resource in the hierarchy

az acr connected-registry get-settings \
  --registry $REGISTRY_NAME \
  --name $CONNECTED_REGISTRY_RW \
  --parent-protocol https

az acr connected-registry get-settings \
  --registry $REGISTRY_NAME \
  --name $CONNECTED_REGISTRY_RO \
  --parent-protocol https

命令输出包括注册表连接字符串和相关设置。 下例输出显示了与父级注册表 contosoregistry 连接的名为 myconnectedregistry 的注册表的连接字符串 :

{
  "ACR_REGISTRY_CONNECTION_STRING": "ConnectedRegistryName=myconnectedregistry;SyncTokenName=myconnectedregistry-sync-token;SyncTokenPassword=xxxxxxxxxxxxxxxx;ParentGatewayEndpoint=contosoregistry.eastus.data.azurecr.io;ParentEndpointProtocol=https"
}

配置部署清单

部署清单是一个 JSON 文档,用于描述要部署到 IoT Edge 设备的模块。 有关详细信息,请参阅了解如何使用、配置并重复使用 IoT Edge 模块

若要使用 Azure CLI 在每个 IoT Edge 设备上部署连接注册表模块,请将以下部署清单本地保存为 JSON 文件。 使用之前部分中的信息更新每个清单中的相关 JSON 值。 在下一部分运行命令以将配置应用到设备时,会用到这些文件路径。

顶层的部署清单

对于位于顶层的设备,请创建包含以下内容的部署清单文件 deploymentTopLayer.json。 此清单与快速入门:将连接注册表部署到 IoT Edge 设备中使用的类似。

注意

如果已使用快速入门将连接注册表部署到上层 IoT Edge 设备,则可将其用于嵌套层次结构的上层。 修改本教程中的部署步骤,以在层次结构(未显示)中配置它。

连接注册表模块设置

  • 使用前面部分中的令牌凭据和连接字符串更新 env 节点中的相关 JSON 值。

  • 以下环境变量在 env 节点中是可选的:

    变量 说明
    ACR_REGISTRY_LOGIN_SERVER 指定唯一的主机名或 FQDN。 如果使用,则连接注册表仅接受对此登录服务器值的请求。

    如果未提供值,则可以通过任何登录服务器值访问连接注册表。
    ACR_REGISTRY_CERTIFICATE_VOLUME 如果可以通过 HTTPS 访问连接注册表,则指向存储 HTTPS 证书的卷。

    如果未设置,则默认位置为 /var/acr/certs
    ACR_REGISTRY_DATA_VOLUME 覆盖连接注册表将存储映像的默认位置 /var/acr/data

    此位置必须与容器的卷绑定匹配。

    重要

    如果连接注册表侦听 80 和 443 以外的端口,则 ACR_REGISTRY_LOGIN_SERVER 值(若指定)必须包含该端口。 示例:192.168.0.100:8080

  • 如果未使用 API 代理模块,应设置连接注册表的 HostPort 绑定。 示例:

     "createOptions": "{\"HostConfig\":{\"Binds\":[\"/home/azureuser/connected-registry:/var/acr/data\"],\"PortBindings\":{\"8080/tcp\":[{\"HostPort\":\"8080\"}]}}}"
    

API 代理模块设置

  • API 代理将侦听配置为 NGINX_DEFAULT_PORT 的端口 8000。 有关 API 代理设置的更多信息,请参阅 IoT Edge GitHub 存储库
{
    "modulesContent": {
        "$edgeAgent": {
            "properties.desired": {
                "modules": {
                    "connected-registry": {
                        "settings": {
                            "image": "<REPLACE_WITH_CLOUD_REGISTRY_NAME>.azurecr.io/acr/connected-registry:0.8.0",
                            "createOptions": "{\"HostConfig\":{\"Binds\":[\"/home/azureuser/connected-registry:/var/acr/data\"]}}"
                        },
                        "type": "docker",
                        "env": {
                            "ACR_REGISTRY_CONNECTION_STRING": {
                                "value": "ConnectedRegistryName=<REPLACE_WITH_CONNECTED_REGISTRY_NAME>;SyncTokenName=<REPLACE_WITH_SYNC_TOKEN_NAME>;SyncTokenPassword=REPLACE_WITH_SYNC_TOKEN_PASSWORD;ParentGatewayEndpoint=<REPLACE_WITH_CLOUD_REGISTRY_NAME>.<REPLACE_WITH_CLOUD_REGISTRY_REGION>.data.azurecr.io;ParentEndpointProtocol=https"
                            }
                        },
                        "status": "running",
                        "restartPolicy": "always",
                        "version": "1.0"
                    },
                    "IoTEdgeAPIProxy": {
                        "settings": {
                            "image": "<REPLACE_WITH_CLOUD_REGISTRY_NAME>.azurecr.io/azureiotedge-api-proxy:1.1.2",
                            "createOptions": "{\"HostConfig\":{\"PortBindings\":{\"8000/tcp\":[{\"HostPort\":\"8000\"}]}}}"
                        },
                        "type": "docker",
                        "env": {
                            "NGINX_DEFAULT_PORT": {
                                "value": "8000"
                            },
                            "CONNECTED_ACR_ROUTE_ADDRESS": {
                                "value": "connected-registry:8080"
                            },
                            "BLOB_UPLOAD_ROUTE_ADDRESS": {
                                "value": "AzureBlobStorageonIoTEdge:11002"
                            }
                        },
                        "status": "running",
                        "restartPolicy": "always",
                        "version": "1.0"
                    }
                },
                "runtime": {
                    "settings": {
                        "minDockerVersion": "v1.25",
                        "registryCredentials": {
                            "cloudregistry": {
                                "address": "<REPLACE_WITH_CLOUD_REGISTRY_NAME>.azurecr.io",
                                "password": "<REPLACE_WITH_SYNC_TOKEN_PASSWORD>",
                                "username": "<REPLACE_WITH_SYNC_TOKEN_NAME>"
                            }
                        }
                    },
                    "type": "docker"
                },
                "schemaVersion": "1.1",
                "systemModules": {
                    "edgeAgent": {
                        "settings": {
                            "image": "<REPLACE_WITH_CLOUD_REGISTRY_NAME>.azurecr.io/azureiotedge-agent:1.2.4",
                            "createOptions": ""
                        },
                        "type": "docker",
                        "env": {
                            "SendRuntimeQualityTelemetry": {
                                "value": "false"
                            }
                        }
                    },
                    "edgeHub": {
                        "settings": {
                            "image": "<REPLACE_WITH_CLOUD_REGISTRY_NAME>.azurecr.io/azureiotedge-hub:1.2.4",
                            "createOptions": "{\"HostConfig\":{\"PortBindings\":{\"443/tcp\":[{\"HostPort\":\"443\"}],\"5671/tcp\":[{\"HostPort\":\"5671\"}],\"8883/tcp\":[{\"HostPort\":\"8883\"}]}}}"
                        },
                        "type": "docker",
                        "status": "running",
                        "restartPolicy": "always"
                    }
                }
            }
        },
        "$edgeHub": {
            "properties.desired": {
                "routes": {
                    "route": "FROM /messages/* INTO $upstream"
                },
                "schemaVersion": "1.1",
                "storeAndForwardConfiguration": {
                    "timeToLiveSecs": 7200
                }
            }
        }
    }
}

下层的部署清单

对于位于下层的设备,请创建包含以下内容的部署清单文件 deploymentLowerLayer.json。

总体而言,下层部署文件类似于顶层部署文件。 区别在于:

  • 它从顶层连接注册表而不是云注册表拉取所需的映像。

    设置上层连接注册表时,请确保它在本地同步所有所需的映像(包括 azureiotedge-agentazureiotedge-hubazureiotedge-api-proxyacr/connected-registry)。 下层 IoT 设备需要从顶层连接注册表拉取这些映像。

  • 它使用在下层配置的同步令牌向顶层连接注册表进行身份验证。

  • 它将父网关终结点配置为顶层连接注册表的 IP 地址或 FQDN,而不是云注册表的 FQDN。

重要

在下面的部署清单中,$upstream 用作托管父连接注册表的设备的 IP 地址或 FQDN。 但是,环境变量中不支持 $upstream。 连接的注册表需要读取环境变量 ACR_PARENT_GATEWAY_ENDPOINT 以获取父网关终结点。 连接注册表支持从另一个环境变量动态解析 IP 地址或 FQDN,而不是使用 $upstream

在嵌套 IoT Edge 上,下层有一个环境变量 $IOTEDGE_PARENTHOSTNAME 等于父设备的 IP 地址或 FQDN。 将环境变量手动替换为连接字符串中的 ParentGatewayEndpoint 值,以避免对父 IP 地址或 FQDN 进行硬编码。 由于此示例中的父设备在端口 8000 上运行 nginx,因此请传递 $IOTEDGE_PARENTHOSTNAME:8000。 还需要在 ParentEndpointProtocol 中选择正确的协议。

{
    "modulesContent": {
        "$edgeAgent": {
            "properties.desired": {
                "modules": {
                    "connected-registry": {
                        "settings": {
                            "image": "$upstream:8000/acr/connected-registry:0.8.0",
                            "createOptions": "{\"HostConfig\":{\"Binds\":[\"/home/azureuser/connected-registry:/var/acr/data\"]}}"
                        },
                        "type": "docker",
                        "env": {
                            "ACR_REGISTRY_CONNECTION_STRING": {
                                "value": "ConnectedRegistryName=<REPLACE_WITH_CONNECTED_REGISTRY_NAME>;SyncTokenName=<REPLACE_WITH_SYNC_TOKEN_NAME>;SyncTokenPassword=<REPLACE_WITH_SYNC_TOKEN_PASSWORD>;ParentGatewayEndpoint=$IOTEDGE_PARENTHOSTNAME:8000;ParentEndpointProtocol=https"
                            }
                        },
                        "status": "running",
                        "restartPolicy": "always",
                        "version": "1.0"
                    },
                    "IoTEdgeApiProxy": {
                        "settings": {
                            "image": "$upstream:8000/azureiotedge-api-proxy:1.1.2",
                            "createOptions": "{\"HostConfig\": {\"PortBindings\": {\"8000/tcp\": [{\"HostPort\": \"8000\"}]}}}"
                        },
                        "type": "docker",
                        "version": "1.0",
                        "env": {
                            "NGINX_DEFAULT_PORT": {
                                "value": "8000"
                            },
                            "CONNECTED_ACR_ROUTE_ADDRESS": {
                                "value": "connected-registry:8080"
                            },
                            "NGINX_CONFIG_ENV_VAR_LIST": {
                                    "value": "NGINX_DEFAULT_PORT,BLOB_UPLOAD_ROUTE_ADDRESS,CONNECTED_ACR_ROUTE_ADDRESS,IOTEDGE_PARENTHOSTNAME,DOCKER_REQUEST_ROUTE_ADDRESS"
                            },
                            "BLOB_UPLOAD_ROUTE_ADDRESS": {
                                "value": "AzureBlobStorageonIoTEdge:11002"
                            }
                        },
                        "status": "running",
                        "restartPolicy": "always",
                        "startupOrder": 3
                    }
                },
                "runtime": {
                    "settings": {
                        "minDockerVersion": "v1.25",
                        "registryCredentials": {
                            "connectedregistry": {
                                "address": "$upstream:8000",
                                "password": "<REPLACE_WITH_SYNC_TOKEN_PASSWORD>",
                                "username": "<REPLACE_WITH_SYNC_TOKEN_NAME>"
                            }
                        }
                    },
                    "type": "docker"
                },
                "schemaVersion": "1.1",
                "systemModules": {
                    "edgeAgent": {
                        "settings": {
                            "image": "$upstream:8000/azureiotedge-agent:1.2.4",
                            "createOptions": ""
                        },
                        "type": "docker",
                        "env": {
                            "SendRuntimeQualityTelemetry": {
                                "value": "false"
                            }
                        }
                    },
                    "edgeHub": {
                        "settings": {
                            "image": "$upstream:8000/azureiotedge-hub:1.2.4",
                            "createOptions": "{\"HostConfig\":{\"PortBindings\":{\"443/tcp\":[{\"HostPort\":\"443\"}],\"5671/tcp\":[{\"HostPort\":\"5671\"}],\"8883/tcp\":[{\"HostPort\":\"8883\"}]}}}"
                        },
                        "type": "docker",
                        "status": "running",
                        "restartPolicy": "always"
                    }
                }
            }
        },
        "$edgeHub": {
            "properties.desired": {
                "routes": {
                    "route": "FROM /messages/* INTO $upstream"
                },
                "schemaVersion": "1.1",
                "storeAndForwardConfiguration": {
                    "timeToLiveSecs": 7200
                }
            }
        }
    }
}

设置和部署连接注册表模块

以下步骤改编自教程:创建 IoT Edge 设备的层次结构,专用于在 IoT Edge 层次结构中部署连接注册表模块。 请参阅该教程以了解有关各个步骤的详细信息。

创建顶层和下层设备

使用现有 ARM 模板创建上层和下层虚拟机。 该模板还会安装 IoT Edge 代理。 如果要改为从自己的设备进行部署,请参阅教程:安装或卸载适用于 Linux 的 Azure IoT Edge,了解如何手动设置设备。

重要

为便于以后访问部署在顶层设备上的模块,请确保打开以下端口入站:8000、443、5671、8883。 有关配置步骤,请参阅如何使用 Azure 门户向虚拟机开放端口

创建和配置层次结构

在 Azure CLI 或 Azure Cloud Shell 中按照以下步骤操作,使用 iotedge-config 工具创建和配置层次结构:

  1. 下载配置工具。

     mkdir nested_iot_edge_tutorial
     cd ~/nested_iot_edge_tutorial
     wget -O iotedge_config.tar "https://github.com/Azure-Samples/iotedge_config_cli/releases/download/latest/iotedge_config_cli.tar.gz"
     tar -xvf iotedge_config.tar
    

    这一步会在教程目录中创建 iotedge_config_cli_release 文件夹。 用于创建设备层次结构的模板文件是在 iotedge_config.yaml 文件,可在 ~/nested_iot_edge_tutorial/iotedge_config_cli_release/templates/tutorial 中找到。 在同一个目录中,有顶层和下层的两个部署清单:deploymentTopLayer.jsondeploymentLowerLayer.json 文件。

  2. 用你的信息编辑 iotedge_config.yaml。 编辑 iothub_hostnameiot_name、上层和下层的部署清单文件名,以及你创建用于从每个层拉取上游映像的客户端令牌凭据。 下面是一个示例配置文件:

    config_version: "1.0"
    
    iothub:
        iothub_hostname: <REPLACE_WITH_HUB_NAME>.azure-devices.net
        iothub_name: <REPLACE_WITH_HUB_NAME>
        ## Authentication method used by IoT Edge devices: symmetric_key or x509_certificate
        authentication_method: symmetric_key 
    
        ## Root certificate used to generate device CA certificates. Optional. If not provided a self-signed CA will be generated
        # certificates:
        #   root_ca_cert_path: ""
        #   root_ca_cert_key_path: ""
    
        ## IoT Edge configuration template to use
    configuration:
        template_config_path: "./templates/tutorial/device_config.toml"
        default_edge_agent: "$upstream:8000/azureiotedge-agent:1.2.4"
    
        ## Hierarchy of IoT Edge devices to create
    edgedevices:
        device_id: top-layer
        edge_agent: "<REPLACE_WITH_REGISTRY_NAME>.azurecr.io/azureiotedge-agent:1.2.4" ## Optional. If not provided, default_edge_agent will be used
        deployment: "./templates/tutorial/deploymentTopLayer.json" ## Optional. If provided, the given deployment file will be applied to the newly created device
            # hostname: "FQDN or IP" ## Optional. If provided, install.sh will not prompt user for this value nor the parent_hostname value
        container_auth: ## The token used to pull the image from cloud registry
            serveraddress: "<REPLACE_WITH_REGISTRY_NAME>.azurecr.io"
            username: "<REPLACE_WITH_SYNC_TOKEN_NAME_FOR_TOP_LAYER>"
            password: "<REPLACE_WITH_SYNC_TOKEN_PASSWORD_FOR_TOP_LAYER>"
        child:
            - device_id: lower-layer
              deployment: "./templates/tutorial/deploymentLowerLayer.json" ## Optional. If provided, the given deployment file will be applied to the newly created device
               # hostname: "FQDN or IP" ## Optional. If provided, install.sh will not prompt user for this value nor the parent_hostname value
              container_auth: ## The token used to pull the image from parent connected registry
                serveraddress: "$upstream:8000"
                username: "<REPLACE_WITH_SYNC_TOKEN_NAME_FOR_LOWER_LAYER>"
                password: "<REPLACE_WITH_SYNC_TOKEN_PASSWORD_FOR_LOWER_LAYER>"
    
  3. 准备上层和下层部署文件:deploymentTopLayer.json 和 deploymentLowerLayer.json。 将你在本文前面部分创建的部署清单文件复制到以下文件夹:~/nestedIotEdgeTutorial/iotedge_config_cli_release/templates/tutorial

  4. 导航到 iotedge_config_cli_release 目录,然后运行该工具以创建 IoT Edge 设备的层次结构。

    cd ~/nestedIotEdgeTutorial/iotedge_config_cli_release
    ./iotedge_config --config ~/nestedIotEdgeTutorial/iotedge_config_cli_release/templates/tutorial/iotedge_config.yaml --output ~/nestedIotEdgeTutorial/iotedge_config_cli_release/outputs -f
    

    如果使用 --output 参数,该工具会在所选目录中创建设备证书、证书捆绑包和日志文件。 如果使用 -f 参数,该工具会自动查找 IoT 中心内现有的 IoT Edge 设备并将其删除,以避免错误并使中心干净整洁。

    该工具可能会运行几分钟。

  5. 使用 scp 将在上一步骤中生成的 top-layer.zip 和 lower-layer.zip 文件复制到相应的上层和下层虚拟机:

    scp <PATH_TO_CONFIGURATION_BUNDLE>   <USER>@<VM_IP_OR_FQDN>:~
    
  6. 连接到顶层设备以安装配置捆绑包。

    1. 解压缩配置捆绑包。 首先需要安装 zip。

      sudo apt install zip
      unzip ~/<PATH_TO_CONFIGURATION_BUNDLE>/<CONFIGURATION_BUNDLE>.zip #unzip top-layer.zip
      
    2. 运行 sudo ./install.sh。 输入 IP 地址或主机名。 建议使用 IP 地址。

    3. 运行 sudo iotedge list 以确认所有模块正在运行。

  7. 连接到下层设备以安装配置捆绑包。

    1. 解压缩配置捆绑包。 首先需要安装 zip。

      sudo apt install zip
      unzip ~/<PATH_TO_CONFIGURATION_BUNDLE>/<CONFIGURATION_BUNDLE>.zip #unzip lower-layer.zip
      
    2. 运行 sudo ./install.sh。 输入设备和父 IP 地址或主机名。 建议使用 IP 地址。

    3. 运行 sudo iotedge list 以确认所有模块正在运行。

如果没有为设备配置指定部署文件,或者在上层或下层设备上出现部署问题(如部署清单无效),请手动部署模块。 请参阅以下部分。

手动部署连接注册表模块

使用以下命令在 IoT Edge 设备上手动部署连接注册表模块:

az iot edge set-modules \
  --device-id <device-id> \
  --hub-name <hub-name> \
  --content <deployment-manifest-filename>

有关详细信息,请参阅使用 Azure CLI 部署 Azure IoT Edge 模块

成功部署后,连接注册表显示 Online 状态。

要检查连接注册表的状态,请使用以下 az acr connected-registry show 命令:

az acr connected-registry show \
  --registry $REGISTRY_NAME \
  --name $CONNECTED_REGISTRY_RO \
  --output table

可能需要等待几分钟,直到连接注册表部署完成。

成功部署后,连接注册表显示 Online 状态。

若要排查部署问题,请在受影响的设备上运行 iotedge check。 有关详细信息,请参阅疑难解答

后续步骤

本快速入门介绍了如何将连接注册表部署到嵌套的 IoT Edge 设备。 继续查看下一指南,了解如何从新部署的连接注册表拉取映像。