Developing IoTEdge Modules
Azure IoT Hub includes support for IoT Edge, a runtime to run custom workloads as Modules.
There has been different tools to help with this task, extensions for Visual Studio, Visual Studio Code and even a CLI can help with the most common developer tasks.
However, most of these tools are in maintenance mode, with no major investments.
An alternative approach to develop IoTEdge modules
A module is just an application, running as a docker container, that communicates with the a system module called $edgeHub
via HTTP, AMQP or MQTT protocols. To develop a module we need a local instance of $edgeHub
that we can target from the machine we use to develop.
In this post, I’m exploring a different approach to configure your workstation so you can develop and debug modules with your favourite IDE.
Note, this sample is using dotnet, but the same concepts should be applicable in other languages.
How to run $edgeHub locally
$edge hub is available as a docker image in mcr.microsoft.com/azureiotedge-hub:1.4
, and we need to perform some pre-liminary tasks before running locally:
- Initialize the $edgeHub identity in Azure IoT Hub
- Configure $edgeHub module twin
- Provide certificates to configure the TLS connection
Provisioning $edgeHub
When you create an IoT Edge device identity in Azuere IoT Hub, it will include the module identities for $edgeAgent
and $edgeHub
system modules. However, the modules are not initialized and do not include the connection string we will need to configure our local instance of $edgeHub.
In a real environment, this iotedge runtime takes care of initializing the system modules, however it implies to connect, at least one time, the iotedge runtime.
Instead, I’m using the dotnet tool aziotedge-modinit
to initialize the $edgeHub
` module, so we can get the connection string.
.env configuration files
The following az
scripts require
- The
$HUB_ID
variable, with the name of the Azure IoT Hub - The
$EDGE_ID
variable, with the name of the IoTEdge device identity - The
az
extension is configured to use the subscription with access to the Azure IoT Hub
az account set -s $SUB_ID
$HUB_ID=<hubname>
$EDGE_ID=<edge_device_id>
Initialize the $edgeHub
module identity:
source .env
edgeConnStr=$(az iot hub device-identity connection-string show -n $HUB_ID -d $EDGE_ID -o tsv)
dotnet tool install -g aziotedge-modinit
aziotedge-modinit --moduleId='$edgeHub' --ConnectionStrings:IoTEdge="$edgeConnStr"
Configuring $edgeHub
At startup, $edgeHub requires two properties to be configured in the module twin, however as this is a system module, we cannot update the twin, neither using the portal, or the CLI.
"$edgeHub": {
"properties.desired": {
"schemaVersion": "1.1",
"storeAndForwardConfiguration": {
"timeToLiveSecs": 7200
},
"routes": {}
}
}
The only workaround I’ve found, is to invoke the SetModules
operation, using a default deployment manifest, which includes the expected Twin Properties.
source .env
curl https://raw.githubusercontent.com/ridomin/iotedge-modules-demo/main/deployBase.json -o deployBase.json
az iot edge set-modules -n $HUB_ID -d $EDGE_ID -k deployBase.json
Or you can use the portal and follow the SetModules screens with all default values.
Certificates for local development with dotnet dev-certs
The system modules expose endpoints protected by a TLS connection, to configure the TLS we need to provide an X509 certificate. This certificate can be the same certificate aspnet uses to provide a local TLS connection.
The next script creates a certificate for localhost and exports the public and private keys as .PEM and .KEY files, then we can use those files to configure the docker instance.
dotnet dev-certs https -ep _certs/localhost.pem --format PEM --no-password
chmod +r _certs/*
cp _certs/localhost.pem _certs/ca.pem
Running $edgeHub locally with docker
Now that we have the certs, and the $edgeHub connection string, we can run the docker container with:
source .env
connStr=$(az iot hub module-identity connection-string show -n $HUB_ID -d $EDGE_ID -m '$edgeHub' --query connectionString -o tsv)
docker run -it --rm \
-e IotHubConnectionString="$connStr" \
-e EdgeModuleHubServerCertificateFile=/certs/localhost.pem \
-e EdgeModuleHubServerCAChainCertificateFile=/certs/ca.pem \
-e EdgeHubDevServerCertificateFile=/certs/localhost.pem \
-e EdgeHubDevTrustBundleFile=/certs/ca.pem \
-e EdgeHubDevServerPrivateKeyFile=/certs/localhost.key \
-v ${pwd}/_certs:/certs \
-p 8883:8883 \
mcr.microsoft.com/azureiotedge-hub:1.4
If everything goes as expected, you should see this output.
Create a dotnet Module
Now you can create a dotnet module using the dotnet template, to install it:
dotnet new install Microsoft.Azure.IoT.Edge.Module
To create a new project based on this template
MODULE_ID=MyModule
dotnet new aziotedgemodule -o $MODULE_ID
Provision the Module Identity
source ../.env
az iot hub module-identity create -n $HUB_ID -d $EDGE_ID -m $MODULE_ID
modcs=$(az iot hub module-identity connection-string show -n $HUB_ID -d $EDGE_ID -m ModuleA -o tsv)
Run/Debug the custom module
Finally, you can run or debug this project by tweaking the configuration so it will connect to the local $edgeHub
instance by appending the ;GatewayHostName=localhost
to the module connection string, and using the certificate configured for TLS with the EdgeModuleCACertificateFile
variable:
export IotHubConnectionString="$modcs;GatewayHostName=localhost"
export EdgeModuleCACertificateFile="../_certs/ca.pem"
dotnet run
Or you can configure your IDE to set these two environment variables (launch.json
for VSCode, or launchSettings.json
for VS) before starting a debugging session.
The complete sample is available in https://github.com/ridomin/iotedge-modules-demo (CodeSpaces ready)