Recently, our customer Senic asked us to integrate an Over-The-Air (OTA) mechanism in their embedded Linux system, and after some discussion, they ended up chosing Mender. This article will detail an example of Mender’s integration and how to use it.
What is Mender?
Mender is an open source remote updater for embedded devices. It is composed of a client installed on the embedded device, and a management server installed on a remote server. However, the server is not mandatory as Mender can be used standalone, with updates triggered directly on the embedded device.
In order to offer a fallback in case of failure, Mender uses the double partition layout: the device will have at least 2 rootfs partitions, one active and one inactive. Mender will deploy an update on the inactive partition, so that in case of an error during the update process, it will still have the active partition intact. If the update succeeds, it will switch to the updated partition: the active partition becomes inactive and the inactive one becomes the new active. As the kernel and the device tree are stored in the /boot
folder of the root filesystem, it is possible to easily update an entire system. Note that Mender needs at least 4 partitions:
- bootloader partition
- data persistent partition
- rootfs + kernel active partition
- rootfs + kernel inactive partition
It is, of course, customizable if you need more partitions.
Two reference devices are supported: the BeagleBone Black and a virtual device. In our case, the board was a Nanopi-Neo, which is based on an Allwinner H3.
Mender provides a Yocto Project layer containing all the necessary classes and recipes to make it work. The most important thing to know is that it will produce an image ready to be written to an SD card to flash empty boards. It will also produce “artifacts” (files with .mender
extension) that will be used to update an existing system.
Installation and setup
In this section, we will see how to setup the Mender client and server for your project. Most of the instructions are taken from the Mender documentation that we found well detailed and really pleasant to read. We’ll simply summarize the most important steps.
Server side
The Mender server will allow you to remotely update devices. The server can be installed in two modes:
- demo mode: Used to test a demo server. It can be nice to test it if you just want to quickly deploy a Mender solution, for testing purpose only. It includes a demo layer that simplify and configure for you a default Mender server on localhost of your workstation.
- production mode: Used for production. We will focus on this mode as we wanted to use Mender in a production context. This mode allows to customize the server configuration: IP address, certificates, etc. Because of that, some configuration will be necessary (which is not the case in the demo mode).
In order to install the Mender server, you should first install Docker CE and Docker Compose. Have a look at the corresponding Docker instructions.
Setup
- Download the
integration
repository from Mender:
$ git clone https://github.com/mendersoftware/integration mender-server
$ cd mender-server $ git checkout 1.1.0 -b my-production-setup
$ cp -a template production $ cd production $ sed -i -e 's#/template/#/production/#g' prod.yml
$ ./run pull
$ CERT_API_CN=mender.foobar.com CERT_STORAGE_CN=s3.foobar.com ../keygen
$ docker volume create --name=mender-artifacts $ docker volume create --name=mender-deployments-db $ docker volume create --name=mender-useradm-db $ docker volume create --name=mender-inventory-db $ docker volume create --name=mender-deviceadm-db $ docker volume create --name=mender-deviceauth-db
Final configuration
This final configuration will link the generated keys with the Mender server. All the modifications will be in the prod.yml file.
- Locate the storage-proxy service in
prod.yml
and set it to your domain name. In our cases3.foobar.com
under thenetworks.mender.aliases
- Locate the minio service. Set
MINIO_ACCESS_KEY
to“mender-deployments”
and theMINIO_SECRET_KEY
to a generated password (with e.g.:$ apg -n1 -a0 -m32
) - Locate the mender-deployments service. Set
DEPLOYMENTS_AWS_AUTH_KEY
andDEPLOYMENTS_AWS_AUTH_SECRET
to respectively the value ofMINIO_ACCESS_KEY
andMINIO_SECRET_KEY
. SetDEPLOYMENTS_AWS_URI
to point to your domain such ashttps://s3.foobar.com:9000
Start the server
Make sure that the domain names you have defined (mender.foobar.com
and s3.foobar.com
) are accessible, potentially by adding them to /etc/hosts
if you’re just testing.
- Start the server
$ ./run up -d
$ curl -X POST -D - --cacert keys-generated/certs/api-gateway/cert.crt https://mender.foobar.com:443/api/management/v1/useradm/auth/login
$ firefox http://mender.foobar.com:443
Client side – Yocto Project
Mender has a Yocto Project layer to easily interface with your own layer.
We will see how to customize your layer and image components (U-Boot, Linux kernel) to correctly configure it for Mender use.
In this section, we will assume that you have your own U-Boot and your own kernel repositories (and thus, recipes) and that you retrieved the correct branch of this layer.
Machine and distro configurations
- Make sure that the kernel image and Device Tree files are installed in the root filesystem image
RDEPENDS_kernel-base += "kernel-image kernel-devicetree"
mender-full
class and add systemd
as the init manager (we only tested Mender’s integration with systemd)# Enable systemd for Mender DISTRO_FEATURES_append = " systemd" VIRTUAL-RUNTIME_init_manager = "systemd" DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit" VIRTUAL-RUNTIME_initscripts = "" INHERIT += "mender-full"
/dev/mmcblk0
, that mmcblk0p1
is your boot partition (containing the bootloader), that mmcblk0p2
and mmcblk0p3
are your two root filesystem partitions, and that mmcblk0p5
is your data partition. If that’s the case for you, then everything is fine! However, if you need a different layout, you need to update your machine configuration. Mender’s client will retrieve which storage device to use by using the MENDER_STORAGE_DEVICE
variable (which defaults to mmcblk0
). The partitions themselves should be specified using MENDER_BOOT_PART
, MENDER_ROOTFS_PART_A
, MENDER_ROOTFS_PART_B
and ROOTFS_DATA_PART
. If you need to change the default storage or the partitions’ layout, edit in your machine configuration the different variables according to your need. Here is an example for /dev/sda
:MENDER_STORAGE_DEVICE = "/dev/sda" MENDER_STORAGE_DEVICE_BASE = "${MENDER_STORAGE_DEVICE}" MENDER_BOOT_PART = "${MENDER_STORAGE_DEVICE_BASE}1" MENDER_ROOTFS_PART_A = "${MENDER_STORAGE_DEVICE_BASE}2" MENDER_ROOTFS_PART_B = "${MENDER_STORAGE_DEVICE_BASE}3" MENDER_DATA_PART = "${MENDER_STORAGE_DEVICE_BASE}5"
local.conf
, for example:
MENDER_ARTIFACT_NAME = "release-1"
As described in Mender’s documentation, Mender will store the artifact name in its artifact image. It must be unique which is what we expect because an artifact will represent a release tag or a delivery. Note that if you forgot to update it and upload an artifact with the same name as an existing in the web UI, it will not be taken into account.
U-Boot configuration tuning
Some modifications in U-Boot are necessary to be able to perform the rollback (use a different partition after an unsuccessful update)
- Mender needs BOOTCOUNT support in U-Boot. It creates a
bootcount
variable that will be incremented each time a reboot appears (or reset to 1 after a power-on reset). Mender will use this variable in its rollback mechanism.
Make sure to enable it in your U-Boot configuration. This will most likely require a patch to your board.h
configuration file, enabling:
#define CONFIG_BOOTCOUNT_LIMIT #define CONFIG_BOOTCOUNT_ENV
# Mender integration require recipes-bsp/u-boot/u-boot-mender.inc PROVIDES += "u-boot" RPROVIDES_${PN} += "u-boot" BOOTENV_SIZE = "0x20000"
The BOOTENV_SIZE
must be set the same content as the U-Boot CONFIG_ENV_SIZE
variable. It will be used by the u-boot-fw-utils
tool to retrieve the U-Boot environment variables.
Mender is using u-boot-fw-utils
so make sure that you have a recipe for it and that Mender include’s file is included. To do that, you can create a bbappend file on the default recipe or create your own recipe if you need a specific version. Have a look at Mender’s documentation example.
root=
kernel argument to use ${mender_kernel_root}
, set the bootcmd
to load the kernel image and Device Tree from ${mender_uboot_root}
and to run mender_setup
. Make sure that you are loading the Linux kernel image and Device Tree file from the root filesystem /boot
directory.
setenv bootargs 'console=${console} root=${mender_kernel_root} rootwait' setenv mmcboot 'load ${mender_uboot_root} ${fdt_addr_r} boot/my-device-tree.dtb; load ${mender_uboot_root} ${kernel_addr_r} boot/zImage; bootz ${kernel_addr_r} - ${fdt_addr_r}' setenv bootcmd 'run mender_setup; run mmcboot'
Mender’s client recipe
As stated in the introduction, Mender has a client, in the form of a userspace application, that will be used on the target. Mender’s layer has a Yocto recipe for it but it does not have our server certificates. To establish a connection between the client and the server, the certificates have to be installed in the image. For that, a bbappend
recipe will be created. It will also allow to perform additional Mender configuration, such as defining the server URL.
- Create a
bbappend
for the Mender recipe
FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:" SRC_URI_append = " file://server.crt" MENDER_SERVER_URL = "https://mender.senic.com"
bbappend
recipe folderRecompile an image and now, we should have everything we need to be able to update an image. Do not hesitate to run the integration checklist, it is really a convenient way to know if everything is correctly configured (or not).
If you want to be more robust and secure, you can sign your artifacts to be sure that they come from a trusted source. If you want this feature, have a look at this documentation.
Usage
Standalone mode
To update an artifact using the standalone mode (i.e. without server), here are the commands to use. You will need to update them according to your needs.
- On your work station, create a simple HTTP server in your Yocto
deploy
folder:
$ python -m SimpleHTTPServer
mender
in standalone mode$ mender -log-level info -rootfs http://192.168.42.251:8000/foobar.mender
You can also use the mender
command to start an update from a local .mender
file, provided by a USB key or SD card.
$ reboot
After the first reboot, you will be on the the new active partition (if the previous one was /dev/mmcblk0p2
, you should be on /dev/mmcblk0p3
). Check the kernel version, artifact name or command line:
$ uname -a $ cat /etc/mender/artifact_info $ cat /proc/cmdline
If you are okay with this update, you will have to commit the modification otherwise the update will not be persistent and once you will reboot the board, Mender will rollback to the previous partition:
$ mender -commit
Using Mender’s server UI
The Mender server UI provides a management interface to deploy updates on all your devices. It knows about all your devices, their current software version, and you can plan deployments on all or a subset of your devices. Here are the basic steps to trigger a deployment:
- Login (or create an account) into the mender server UI: https://mender.foobar.com:443
- Power-up your device
- The first time, you will have to authorize the device. You will find it in your “
dashboard
” or in the “devices
” section. - After authorizing it, it will retrieve device information such as current software version, MAC address, network interface, and so on
- To update a partition, you will have to create a deployment using an artifact.
- Upload the new artifact in the server UI using the
“Artifacts”
section - Deploy the new artifact using the
“deployment”
or the“devices”
section. You will retrieve the status of the deployment in the“status”
field. It will be in “installing”, “rebooting”, etc. The board will reboot and the partition should be updated.
Troubleshooting
Here are some issues we faced when we integrated Mender for our device. The Mender documentation also has a troubleshooting section so have a look at it if you are facing issues. Otherwise, the community seems to be active even if we did not need to interact with it as it worked like a charm when we tried it.
Update systemd’s service starting
By default, the Mender systemd service will start after the service “resolved” the domain name. On our target device, the network was available only via WiFi. We had to wait for the wlan0
interface to be up and configured to automatically connect a network before starting Mender’s service. Otherwise, it leads to an error due to the network being unreachable. To solve this issue which is specific to our platform, we set the systemd dependencies to “network-online.target” to be sure that a network is available:
-After=systemd-resolved.service +After=network-online.target +Wants=network-online.target
It now matches our use case because the Mender service will start only if the wlan0
connection is available and working.
Certificate expired
The certificates generated and used by Mender have a validity period. In case your board does not have a RTC set, Mender can fail with the error:
systemctl status mender [...] ... level=error msg="authorize failed: transient error: authorization request failed: failed to execute authorization request: Post https:///api/devices/v1/authentication/auth_requests: x509: certificate has expired or is not yet valid" module=state
To solve this issue, update the date on your board and make sure your RTC is correctly set.
Device deletion
While testing Mender’s server (version 1.0), we always used the same board and got into the issue that the board was already registered in the Server UI but had a different Device ID (which is used by Mender to identify devices). Because of that, the server was always rejecting the authentication. The next release of the Mender server offers the possibility to remove a device so we updated the Mender’s server to the last version.
Deployments not taken into account
Note that the Mender’s client is checking by default every 30 minutes if a deployment is available for this device. During testing, you may want to reduce this period, which you can in the Mender’s configuration file using its UpdatePollIntervalSeconds
variable.
Conclusion
Mender is an OTA updater for Embedded devices. It has a great documentation in the form of tutorials which makes the integration easy. While testing it, the only issues we got were related to our custom platform or were already indicated in the documentation. Deploying it on a board was not difficult, only some U-Boot/kernel and Yocto Project modifications were necessary. All in all, Mender worked perfectly fine for our project!