MCUs meet Containers. Containers meet MCUs.
What it means to bring greater agility to the tiniest of edge devices.
Last week, I laid out our plan and our progress toward bringing agility to the farthest edge – to microcontrollers, which account for two-thirds of the devices used in IoT. In the past several months, many of our conversations with customers and partners have been around the complexity and challenges of managing and updating those devices. More often than not, this conversation quickly shifts to the real issue at hand: friction at the edge is constraining innovation. These challenges present a major adoption blocker for companies – and their customers – which leaves them unable to introduce new functionality, push agile compute to the edge, and develop new business models.
Building device management is hard. Managing fleets at scale is even harder. People using “roll-your-own” update systems often find managing the heterogeneity at the device edge near impossible to keep up with. As a result, they may choose not to add new services or capabilities to their ecosystem and live with ‘dumb’ devices because the alternative is too complex. We spoke to a partner this week who was using an older low-cost, low-power microcontroller for all their products. And it wasn’t because that device was awesome and cheap; it was because supporting multiple versions of firmware creates too much overhead for their engineering team. As such, they have artificially constrained their feature set to conform to a single version of the firmware. The cost of supporting that second board and that third board is just too high. This takes companies off their path of delivering value to their customers.
We believe that organizations shouldn’t have to make such a choice. Let’s take a look at the traditional approach to how microcontroller-based devices are currently updated and managed:
- Monolithic and Static – Typically, the traditional firmware is written as a monolithic, single image. Sometimes that is broken down into some sort of recovery code or bootloader at the beginning, but then the rest of the application is one big block of code. As an analogy, this is very similar to how server applications were written ten or fifteen years ago. Even in cases where they were deployed to virtual machines, they were still large monolithic entities.
- Risky – When you have monolithic firmware and need to update that device, you often have to update the code in situ. You have to write over the current firmware’s bits in storage. This is risky because many devices lack sufficient storage to hold both the current as well as the new images at the same time. It's not like your laptop or your PC that has gigs of storage. These devices have kilobytes of storage, or maybe a megabyte. As such, if the new firmware update fails for any reason (e.g., stops transmitting halfway through) you are very likely to have a corrupt firmware image and a reboot may brick the target device.
- High Overhead, High Complexity – There is typically a lot of overhead and complexity in this environment that isn't necessarily on the device but in how the image gets built. Especially true in industrial IoT, there is a very complicated pipeline to build that image, get it over to the test rigs, and then qualify that image. More often than not, there is a high level of testing needed to make sure that the image is going to work properly. This leads to a lot of cycles spent on “boilerplate” tasks. Imagine a scenario where there is a one-line security update or a new version of the library that has a few small changes. You now have to rebuild the entire firmware, make sure that it is built properly, run through complete end-to-end validation, and then distribute that entire image to all the devices. That’s a lot of effort for even the smallest of changes.
We see a better way to do this: MCUs meet containers; Containers meet MCUs.
At Nubix, we believe the modern development paradigm that evolved with the cloud can be brought to the edge. In the cloud, the industry moved from physical servers to virtualization and, ultimately, to containers. It’s this container-based approach that allows for high levels of agility through practices such as continuous deployment and delivery, microservices architectures, and declarative deployments. In this world, it doesn’t matter what you are running on. You just want an environment where your code can run reliably. This is modern DevOps and we believe that MCUs can live in this modern world as well.
Here is the approach we are taking: you are still going to have a firmware image that runs with a bootloader, but now that firmware is a lot more constrained. It's just the fabric that needs to be there for everything else to run; not the entirety of your application. Now, applications or things that run on these devices connected through the fleet infrastructure, are run inside of containers. The advantages of this approach include:
- True isolation – Today if you run two threads on a microcontroller, there's no protection between them. Code generally can do anything – it can reset the device, it can rewrite a register, or it can go change things around. There are no protections around that. By moving to a container approach, you can provide isolation between that code and code running in a different container. For example, you could have a device that performs three functions: reading data from a sensor periodically, providing some logic to determine if there is an event, and then sending those events to the cloud. Each capability could be broken up into containers, built in different languages by different teams, and maintained separately. So having that isolation allows customers to break up development and reuse code within their organization.
- Safe updates – If you are updating a monolithic “blob” of firmware, you have to do it all at once. However, if you are breaking that code into multiple containers, you can update just the containers that change. So go back to the example with a one-line security update. If that's just in one library in one container, then all you have to do is rebuild, retest, and then update that one container. You don't have to re-do everything. Of course, you still have to validate that the device works with the new container update, but you're not rebuilding everything and don't have to retest all of the things you haven’t touched.
- Re-usability, Agility – Imagine you have a board with a number of sensors on it. In order to interface and access these sensors, you are likely going to go to GitHub to download some sample code that needs to be integrated into your codebase. If all you want to do is read or use one of the sensors on the board, you should just be able to fire up a container, configure it to read from the device at some specified interval or such. That code should be coming from the vendor of the sensor, fully tested, and production-ready. All it requires is a container to drop it in, freeing up developers from lower-level details to focus on the value they're delivering.
- Portability and Hardware abstraction – Today if you have two similar but different boards, moving from one to the other can be a nightmare because all sorts of things are different. We believe that you should be able to move your containerized code from one device to another running without a recompile. The only thing that might change is the configuration such mappings to where devices are located on each board. This is a key virtue of containerization for MCUs.
We look forward to extending this agile paradigm to the farthest edge. Nubix’s edge-first design allows us to think about containers in this environment in a new way. If you are interested in learning more, I'd love to hear from you.