# IoT Edge installation on Linux VM
## Preparation steps
You will need the Azure CLI for this. Installation instructions: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli. Note that I run all commands in this document on WSL2 (Windows Subsystem for Linux) on Ubuntu 18.04.
You will need an Azure subscription.
Run **az account show**: are you in the correct subscription? If noy, run **az login**.
Install the Azure CLI extension for IoT:
```
az extension add –name azure-iot
```
**Note:** in what follows, I presume you already deployed a free IoT Hub instance. If not, please see https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-create-through-portal and make sure you select the free tier. The free tier supports IoT Edge.
Create an edge device in IoT Hub. Like a regular IoT device, an IoT Edge device needs to be registered:
```
az iot hub device-identity create --device-id myEdgeDevice --edge-enabled --hub-name {hub_name}
```
Replace {hub_name} with the name of your IoT Hub (e.g. myHub). Do not add azuredevices.net at the end.
Get the connection string for the edge device:
```
az iot hub device-identity connection-string show --device-id myEdgeDevice --hub-name {hub_name}
```
Save the connection string for later use.
## Deploy the Linux VM
Use the Azure CLI to deploy the VM. Use the following command:
```
az vm create \
--resource-group rg-training \
--name edgeVM \
--image UbuntuLTS \
--authentication-type password \
--admin-username azureuser \
--admin-password PASSWORD \
--size Standard_B2ms
```
Replace **rg-training** with your resource group name if different.
Replace PASSWORD with your password. Use uppercase/lowercase/letters. Do not use special characters like $ etc...
This will create a virtual machine with port 22 (SSH) opened on its public IP address. This is not a best practice but ok for testing. This also enables password authentication to the virtual machine to make it a bit easier. In most cases, you should use a private key to log on.
When the VM is created, the public IP will be in the output. SSH into the VM with:
```
ssh azureuser@PUBLICIP
```
**Note:** if you are running on Linux, **ssh** is available to you. On Windows, you can use something like Putty: https://www.putty.org/. Another option is to start **git bash** which likely is installed because you have Git for Windows: https://git-scm.com/downloads
## Prepare the VM
In your SSH session, so on the VM, run the following commands (4 lines):
```
curl https://packages.microsoft.com/config/ubuntu/18.04/multiarch/prod.list > ./microsoft-prod.list
sudo cp ./microsoft-prod.list /etc/apt/sources.list.d/
curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg
sudo cp ./microsoft.gpg /etc/apt/trusted.gpg.d/
```
## Install the moby container engine
IoT Edge modules are containers, so we need a container engine. Run the following commands (2 lines):
```
sudo apt-get update
sudo apt-get install moby-engine
```
## Install the IoT Edge security daemon
Run the following commands (2 lines):
```
apt list -a iotedge
sudo apt-get install iotedge
```
## Link to IoT Hub with connection string
It is also possibile to authenticate with certificates. We will use the symmetric key which uses a connection string. Open the IoT Edge config file:
```
sudo nano /etc/iotedge/config.yaml
```
In the file, find `<ADD DEVICE CONNECTION STRING HERE> ` and replace it with the IoT Edge device connection string. Close nano and make sure you save the file when asked (CTRL X).
Restart IoT Edge:
```
sudo systemctl restart iotedge
```
## Verify status
Each of the below commands, checks a part of the installation:
1. check the status of the iotedge service: is it running?
2. check the logs of the iotedge service
3. run checks on the iotedge installation: this will result in quite some output with several warnings and one or more errors; most warnings and errors will be expected after installation
4. check the running modules
```
systemctl status iotedge
journalctl -u iotedge --no-pager --no-full
sudo iotedge check
iotedge list
```
Note that several issues will be found when you run iotedge check:
* edgeHub not found: expected; it still needs to be deployed
* certificates: use of production certs in production scenarios; do not worry about this in test & development phase
* storage: do not use container storage; use host storage instead and map it to the container; see https://docs.microsoft.com/en-us/azure/iot-edge/how-to-access-host-storage-from-module
# Deploying a marketplace module
To deploy a module, you would select the edge device in IoT Hub and click Set Modules:
![](https://i.imgur.com/jWbvP5b.png)
You then get the option to deploy a custom module or a marketplace module from the **Add Module** button. However, you will not find the marketplace module we want to use here, which is the simulated temperature sensor.
Instead, navigate here:
https://azuremarketplace.microsoft.com/en-us/marketplace/apps/azure-iot.simulated-temperature-sensor?tab=Overview
What to do?
* Log in to Azure Marketplace with the same account you use for the Azure Portal
* Click **Get it now**
* Continue the process... you will be redirected to the Azure Portal and should see the screen below. Deploy the module to the device
![](https://i.imgur.com/VQeXBcV.png)
In the screen above, click **Create**.
You are now in the **Set Modules on device** wizard, the first step. The temperature module should be listed with a **desired status** of running. Nothing has been deployed yet. You are merely creating a deployment manifest now.
![](https://i.imgur.com/O51RBQl.png)
Click **Next: Routes >** to go to the routes screen. Routes define how messages are routed by IoT Edge. There are two routes here:
1. Route to send all messages to $upstream which is IoT Hub
2. Route to send the temperature module messages to $upstream
The first route is redundant and should be deleted.
You now have one route that looks like this:
```
FROM /messages/modules/SimulatedTemperatureSensor/* INTO $upstream
```
The route sends **all** outputs from the SimulatedTemperatureSensor module to $upstream, which is IoT Hub. More information about routing can be found here: https://docs.microsoft.com/en-us/azure/iot-edge/module-composition#declare-routes
The screen looks like below:
![](https://i.imgur.com/1n6ChXS.png)
Click **Next: Review + create >**. You will see the deployment manifest in JSON.
![](https://i.imgur.com/Ji4IRLi.png)
Click **Create** and give it some time. In the edge device screen, you should see the modules running as below (use Refresh):
![](https://i.imgur.com/tAq4pwm.png)
After a while, you should see telemetry from the module on your IoT Hub:
![](https://i.imgur.com/jiSLxJq.png)
The above output is from VS Code's **Start monitoring built-in event endpoint**.
# Create a custom Python module
See https://github.com/gbaeke/azure-iot/tree/master/edgesol/edgesol for the code. This code is sample code and is for illustration purposes only.
## The code: main.py
In modules/PythonModule there is a main.py: https://github.com/gbaeke/azure-iot/blob/master/edgesol/edgesol/modules/PythonModule/main.py
The code uses the Azure IoT device SDK for Python, similar to device code. There are some notable differences:
* use of IoTHubModuleClient vs IoTHubDeviceClient
* use of a message handler via module_client.on_message_received: use to pickup messages sent to specific inputs via IoT Edge routing rules; this module checks for incoming messages on an input called **input1**
* use of a desired properties handler via module_client.on_twin_desired_properties_patch_received: use to pick up a change in a desired propery of the module twin called **TemperatureThreshold**; the global TEMPERATURE_THRESHOLD variable is used to determine if a message needs to be sent to an output; as such, this module is a **filter module**
* use of module_client.send_message_to_output to send a message to an output called output1; an IoT Edge routing rule can pick up messages here and forward them to IoT Hub or another module
How this module interacts with the SimulatedTemperatureSensor we deployed earlier and IoT Hub is shown below:
![](https://i.imgur.com/EtIpscU.png)
**Note:** although we label this module as a filter module, the code in this module forwards ALL messages it receives and alters the message when the temperature is over a threshold; it adds a custom property to the message that can be picked up by downstream systems
The routing rules in IoT Edge are the following:
```
SimulatedTemperatureSensorToIoTHub: FROM /messages/modules/SimulatedTemperatureSensor/* INTO BrokeredEndpoint("/modules/PythonModule/inputs/input1")
PythonToIoTHub: FROM /messages/modules/PythonModule/* INTO $upstream
```
Note that we are using * to catch ALL outputs instead of specifying the outputs. That's a choice, not a requirement.
## Creating the container image
The code needs to be packaged in a container image. That image is then downloaded (pulled) to the IoT Edge device. A new container based on that image is then started.
To create the image, you need a Dockerfile. For the amd64 platform (so Intel/AMD hardware), the following Dockerfile is used:
```
FROM amd64/python:3.7-slim-buster
WORKDIR /app
COPY requirements.txt ./
RUN pip install -r requirements.txt
COPY . .
CMD [ "python3", "-u", "./main.py" ]
```
To build the image, you need Docker installed on your machine. You can also use Docker on the IoT Edge device beccause you installed the **moby engine**. Run the following command in the folder that contains main.py and the Dockerfile:
```
docker build -t pythonmodule:0.0.1 .
```
**Note:** use only lowercase; you might need to add **sudo** in front of the command if your user is not a member of the **docker** group
The container image will now be built with an image name of **PythonModule** and a **tag** of 0.0.1. The image will be saved on your local machine. To see a list of images on your local machine, type the following:
```
docker images
```
You should see the image you just created in the list.
## Uploading the image to a registry
A container registry is a server that stores container images in repositories. The image name is usually the repository name. In the repository, multiple versions of the image are kept based on the tags.
There are many container repositories. Below are two well-known ones but there are many more. You can also host a registry yourself on your local machine or a server in the cloud:
* Docker Hub: a registry service provided by Docker; you need to go to https://hub.docker.io and register. You can then upload images for free but they can be downloaded (pulled) by anyone
* Azure Container Registry (ACR): a service provided by Azure to host private repositories; to download an image, you need to authenticate; it works the same as Docker Hub and is fully compatible with the docker tooling
It is easy to create a registry in Azure:
* Use this link: https://portal.azure.com/#create/Microsoft.ContainerRegistry
* Select resource group
* Enter a name for the registry: name must generate a unique domain name ending in **.azurecr.io**
* Select location
* Select Basic (we only need basic functionality)
* Skip ahead to **Next: Review + create** and follow the wizard to the end
ACR comes at a cost. See below for more information:
![](https://i.imgur.com/XnxuQhO.png)
After creation, turn on **Admin user** in **Access Keys**:
![](https://i.imgur.com/0Fowo6O.png)
The username and password on this page can be used when you need to specify logins to this registry. Many tools that work with container registries will need this login and password.
## Upload image to registry
When you want to upload a container image to a registry, you need to include the registry URL. For example:
```
gebareg.azurecr.io/pythonmodule:0.0.1
```
So instead of using the previous **docker build** command, use the following one:
```
docker build -t YourRegistryURL/pythonmodule:0.0.1 .
```
You can also use the **docker tag** command to change the tag of an existing image. See https://docs.docker.com/engine/reference/commandline/tag/
The image on your local machine now includes the registry name. To upload it to Azure Container Registry, first authenticate to ACR. That can be easily done with the Azure CLI:
```
az acr login -r YourRegistryName
```
**Note:** use the short name, without .azure.cr at the end
**Note:** you can also use **docker login YourRegistryName.azurecr.io** and specify the user name and password you enabled during the creation of Azure Container Registry. If you use the IoT Edge device to build and push your container image, use docker login.
Now push the image:
```
docker push YourRegistryURL/pythonmodule:0.0.1
```
Because Azure Container Registry is compatible with the **docker** tool and you authenticated to the registry, the image can be pushed. This will result in a repository with the same name as the image:
![](https://i.imgur.com/mgr39aO.png)
Above, you see the repository with different versions of the image, identified by the tag. Note that the image tag is a bit different here. It also includes -amd64. Tags do not have to use just numbers and dots.
## Deploying the module to our IoT Edge device
Now that the image has been uploaded to the Azure Container Registry (ACR), we can deploy the module to our IoT Edge device as a container.
We will use the portal to better illustrate the steps. In production though, the portal will be rarely used.
In your IoT Hub, navigate to **IoT Egde** and select your device:
![](https://i.imgur.com/4WWSauw.png)
The screenshot above shows a part of myEdgeDevice.
The list of modules will show:
* $edgeAgent
* $edgeHub
* SimulatedTemperatureSensor
**Note:** if you followed the previous steps 😉
We will add the PythonModule. Click the **Set Modules** link:
![](https://i.imgur.com/t09V9nI.png)
A wizard will start. Click **+ Add** and select **+ IoT Edge Module**.
Fill in the module name and image URI. The image URI is the name of the Docker image including the registry name. If you were not able to upload the image earlier, you can use **gbaeke/pythonmodule:0.0.1**, which is publicly available (at least for now). That image is on Docker Hub: https://hub.docker.com/repository/docker/gbaeke/pythonmodule
On the same page, you can specify a temperature threshold, used by the code in the module to filter the messages and to add a custom property. Add the following in **Module Twin Settings**:
```
{
"TemperatureThreshold": 25
}
```
You can now click **Add** to add the module. The modules section should look like below:
![](https://i.imgur.com/aqgx7n0.png)
**Note:** if you used a Docker image in Azure Container registry, add its name, URL and the user name and password you enabled earlier. The user name is actually the registry name.
Now, we can specify the **routes**. Click **Next: Routes >**.
Two routes should be created:
* SimulatedTemperatureSensorToIoTHub
```
FROM /messages/modules/SimulatedTemperatureSensor/* INTO BrokeredEndpoint("/modules/PythonModule/inputs/input1")
```
Above, you decide that the input to PythonModule should be input1. You basically define the input names via the routes. Your code is then written to check for messages of these inputs.
* PythonToIoTHub:
```
FROM /messages/modules/PythonModule/* INTO $upstream
```
On screen, it looks like below:
![](https://i.imgur.com/IHsKwAl.png)
The first route takes all outputs from SimulatedTemperatureSensor and sends them to input1 of PythonModule. PythonModule, in its code, checks for messages arriving on that input.
The second route takes all outputs from PythonModule and forwards them to IoT Hub which is $upstream. The code in PythonModule modifies the input message and sends it to output1 when a field in the JSON it receives on its input1 is higher than a threshold value defined in the module. You can set the threshold with a desired property at the module twin level as discussed above.
You can now click **Next: Review + create >** to go to the last screen.
![](https://i.imgur.com/U0m3PRB.png)
Our settings passed validation steps. When you click create, the deployment is submitted. Our IoT Edge device (VM) will pick up this deployment and configure itself accordingly.
## Checking if the module works
SSH into the IoT Edge device (our VM) and run the following command:
```
iotedge logs PythonModule -f
```
If you do not see any received inputs, restart the SimulatedTemperatureSensor module:
```
iotedge restart SimulatedTemperatureSensor
```
Now check the logs of PythonModule again. You should see logs similar to below:
![](https://i.imgur.com/GJsOshk.png)
Congratulations, you installed and configured an edge device with IoT Edge!
![](https://i.imgur.com/jMgTDEE.png)