Azure IoT Edge is just Docker containers all the way

Azure IoT Edge gotchas – and how to overcome them!

This post was most recently updated on April 29th, 2022.

5 min read.

This article describes the gotchas and weird one-offs that I’ve encountered with Azure IoT Edge so far. Consider them not-too-thoroughly tested quick fixes that aren’t significant enough to warrant an actual blog post themselves :)

Okay – let’s get to them gotchas, then!

Random learnings from Azure IoT Edge projects

How do you define a Target Condition for your layered deployments?

See the sample below:
az iot edge deployment create -d 'unique-deployment-name' -n 'iot-hub-name' --content 'path-to-your-layered-deployment-manifest-file.json' --layered --target-condition "tags.location='stockholm'" --priority 10

How do you UPDATE (“set”) a Target Condition for your layered deployments?

You can’t modify the content, but you can tweak labels and the target condition.

See the sample below:
az iot edge deployment update -d 'unique-deployment-name' -n 'iot-hub-name' --set targetCondition="tags.location='stockholm' AND tags.environment='dev'" --priority 10

Sample of a layered deployment using az cli in Azure DevOps pipeline YAML file

I can’t be the only one googling for this, right? :)

So here it goes (in case WordPress messes up the structure, see at the end of the page for a better copy-pasteable version of the az cli task for Azure IoT Edge deployment using Azure DevOps):
task: [email protected]
displayName: Create Edge Deployment using AZ CLI
azureSubscription: $(serviceConnection)
scriptType: 'pscore'
scriptLocation: 'inlineScript'
inlineScript: |
az config set extension.use_dynamic_install=yes_without_prompt
az account set --subscription "subName"
$TargetCondition = "(tags.environment='dev' AND tags.location='stockholm')"
az iot edge deployment create -d 'unique-deployment-name' -n 'iot-hub-name' --content 'path-to-your-layered-deployment-manifest-file.json' --layered --target-condition "$TargetCondition" --priority 10

How do I remove a module/container that is specified in the base/single-device deployment?

You don’t. Unless you define a new base/single-device deployment, that is.

You can, however, create a new layered deployment that’ll shut the module down, but I don’t think you can remove it.

I accidentally deployed a single-device/base manifest using az cli with –layered switch and now my Edge device is borked. What do?

Been there, done that. It doesn’t look like IoT Edge validates your manifest beyond some very rudimentary schema checks before trying to apply it.

This will render your Edge device looking somewhat like this in Azure Portal, and configuring it becomes impossible:

Long story short – you’ve got a couple of options:
1. Remove your borked deployment (az iot edge deployment delete -d ‘<deployment-id>’)
2. Fix your environment using one of the available options:

2.1 Deploy a WORKING base/single-device manifest. This’ll overwrite everything you had before.
2.2 Go to “Set Modules” -> “Runtime” and fix your $edgeAgent and $edgeHub configuration (compare with a working device)
2.3 Theoretically, a single-device deployment from VS Code should also overwrite the broken configuration with a functional one, but I haven’t actually verified this.

I’m getting errors about a malformed manifest file for my layered deployment even though I properly copy-pasted my modules to create the file! What’s up?

One of the biggest differences between the schemas of the manifest file for “full” deployment (single-device or base) is the fact the createOptions needs to be an escaped string, not JSON.

So instead of having:
createOptions: {}

You need to have:
createOptions: "{}"

And obviously adjust any contents to be a properly escaped, stringified JSON. Yeah, that’s going to be a lot of wacks.

Which quote or tick or backtick or whatever do I need to use with JMESPath?

Okay, so this one is a bit complicated, but bear with me. What I’ve found out so far – things change depending on whether you’re using PowerShell, bash, or perhaps the az cli task in Azure DevOps, but the basic idea is this:

1) JMESPath usually shows all samples using backticks – that’s this guy:
2) Values inside backticks are evaluated properly, so comparison operators like this work:
--target-condition "tags.priority > `5`"

Without the backticks – or sometimes double backticks – numeric comparison doesn’t seem to work in PowerShell or any Azure DevOps tasks I’ve tried.

3) Doubleticks to the rescue
THAT SAID, backticks don’t always work and your JMESPath queries might fail to parse or produce unexpected results. In that case, try double-backticks. Something like this:
--target-condition "tags.priority > ``5``"

4) Single quotes usually work too
All THAT said, you can actually just use single quotes like 99% of the time. String comparisons and such work just fine.

5) Last weird difference – property names with dashes
This is the last weird detail I’ve found – if you have a property name, that has a dash (this one: – ) in it, you’ll need to surround that particular property name with double quotes – and since that probably happens inside a target condition or similar, it’ll be enclosed in quotes already – so your quotes need to be escaped.

So something like this:
--target-condition "\"tags.item-priority\" > ``5``"

How to send a JSON object instead of a string with Azure IoT Hub Edge message payload?

This is very unintuitive, but to get Message Routing rules to work for any $body-based routing rules, you’ll need to get the Payload to be rendered as JSON, not a string. But if you just follow instructions or tutorials, it’ll be handled as a string.

See – doing this:
var TableName = "table01";
var PartitionKey = "key01";
string messagePayload = $"{{\"tablename\":\"{TableName}\",\"partitionkey\":\"{PartitionKey}\"}}";

using var eventMessage = new Microsoft.Azure.Devices.Client.Message(Encoding.UTF8.GetBytes(messagePayload));

await moduleClient.SendEventAsync(eventMessage);

This will result in this:
"event": {
"origin": "Simulator",
"module": "ContosoModule",
"interface": "",
"component": "",
"payload": "{\"tablename\":\"table01\",\"partitionkey\":\"key01\"}"

Routing rules based on the payload – for example $body.tablename == “table01” will not work.

Worse yet, even trying to do it properly and sending an object like this:
JsonMessage newMsg = new JsonMessage()
tablename = "table01",
partitionkey = "key01",
string payload = JsonConvert.SerializeObject(newMsg);
using var eventMessage = new Microsoft.Azure.Devices.Client.Message(Encoding.UTF8.GetBytes(payload));
await moduleClient.SendEventAsync(eventMessage);

Might not work (well, it didn’t for us). It’s still a string, not JSON.

However, adding this should help:
eventMessage.ContentEncoding = Encoding.UTF8.ToString(),
eventMessage.ContentType = "application/json"

When the content type of the message is properly configured as application/json, it should render successfully.

And with that, it should look like this:

How to listen to Azure IoT Hub messages (for example, from Edge modules)?

Fire up your terminal of choice, make sure you have your Azure CLI installed, and run this:
az iot hub monitor-events --hub-name yourhubname d *simulator

In the example, we’re filtering for messages coming from devices with names ending in “simulator”.

Or if you don’t want to log in, select your Azure IoT Hub, browse to Shared Access Policies, and select a connection string for a policy that can listen:
az iot hub monitor-events --login "HostName=the-rest-of-your-iot-hub-shared-access-policy-connection-string-here" -d *simulator

Note, however, that this monitors the default endpoint, so any successfully routed messages won’t show up here!

If this FAQ didn’t answer your question, see my other Azure IoT Edge-related posts below!

  • How to get your Azure IoT Edge runtime to use port 443 instead of using 5671 and 8883?

  • Azure IoT Edge is just Docker containers all the way

    How to fix “Cannot start service edgeHubDev: Ports are not available: listen tcp”

  • Docker - sure, it works great!

    How to fix “exec user process caused: no such file or directory” in Docker on Windows?

References and Appendices

Appendix 1 – Azure DevOps az cli deployment task

The sample below will should you an example of an Azure IoT Edge deployment using Azure CLI.

task: [email protected]
displayName: Create Edge Deployment using AZ CLI
 azureSubscription: $(serviceConnection)
 scriptType: 'pscore'
 scriptLocation: 'inlineScript'
 inlineScript: |
  az config set extension.use_dynamic_install=yes_without_prompt
  az account set --subscription "subName"
  $TargetCondition = "(tags.environment='dev' AND tags.location='stockholm')"
  az iot edge deployment create -d 'unique-deployment-name' -n 'iot-hub-name' --content 'path-to-your-layered-deployment-manifest-file.json' --layered --target-condition "$TargetCondition" --priority 10


0 0 votes
Article Rating
Notify of
Inline Feedbacks
View all comments