Containing the Chaos

Building and Scaling IoT Node Services on Azure Using PaaS and Containerization

Michael Lanzetta
Open Source Engineer, Microsoft

Internet of Things


It's great!
However, two minor problems...

The Internet

Things

In This Presentation

I solve all of these problems
...well, most of these problems
...well, some of these problems

Nitrogen.js

Our (other) IoT Solution

  • Runs anywhere and everywhere
  • Common protocols supported (e.g. MQTT)
  • Can write to many destinations (Mongo, Azure Tables, EventHub, Qpidd)
  • Interface-based implementation
  • Identity, registration, batching, permissioning
  • Free (as in beer, and 'Murica) - fully OSS, MIT License

Nitrogen Architecture

(from the before times)

Devices
[Not supported by viewer]
Applications
[Not supported by viewer]
Front Door (API Surface Consolidation)
[Not supported by viewer]
Ingestion
[Not supported by viewer]
Device Registry
[Not supported by viewer]
Consumption
[Not supported by viewer]
MQTT
[Not supported by viewer]
REST
[Not supported by viewer]
Provisioning
[Not supported by viewer]
Auth
[Not supported by viewer]
Query
[Not supported by viewer]
MQTT
[Not supported by viewer]
REST
[Not supported by viewer]

Oxide


IoT Portal for the user
Built using Ember-cli

Our Use-Case:
Car Telemetry

Sizing the Deployment

  • We send up roughly 2 messages a second during operation of the vehicle
  • 37% peak concurrent usage of car fleet is typical
  • Design goal of monitoring car fleet of 30,000 vehicles
  • Deployment designed to be capable of 60% usage of fleet
  • Need to be able to support 60k messages/sec load from car fleet
  • Each ingestion instance can handle roughly 1000 messages/second
  • 60 ingestion instances
  • 20 frontdoor instances
  • 10 consumption instances
  • 5 device registry instances
  • So... roughly 100 servers in total

Deploying Nitrogen

Peeling the DevOps Onion

  • Docker
  • CoreOS
  • Deis

Our Nginx Dockerfile


FROM nitrogen/ubuntu:14.04
MAINTAINER Tim Park <tpark@microsoft.com>

# install confd
RUN \
wget https://github.com/kelseyhightower/confd/releases/download/v0.6.3/confd-0.6.3-linux-amd64 && \
sudo mv confd-0.6.3-linux-amd64 /usr/local/bin/confd && \
sudo chmod +x /usr/local/bin/confd

# install and configure nginx.
RUN \
add-apt-repository -y ppa:nginx/stable && \
apt-get update && \
apt-get install -y nginx && \
rm -rf /var/lib/apt/lists/* && \
echo "\ndaemon off;" >> /etc/nginx/nginx.conf && \
chown -R www-data:www-data /var/lib/nginx

# setup and run start script.
ADD start.sh /var/www/start.sh
RUN chmod +x /var/www/start.sh

CMD ./var/www/start.sh

# expose ports.
EXPOSE 80
EXPOSE 443
                    

CoreOS

  • Stripped down Linux distro
  • Optimized to run containers
  • Autoupdating
  • Systemd / Fleetctl / etcd
  • Cores/Containers, not VMs
Deis Router
[Not supported by viewer]
M
[Not supported by viewer]
R
[Not supported by viewer]
M
[Not supported by viewer]
M
[Not supported by viewer]
CoreOS
[Not supported by viewer]
M
[Not supported by viewer]
R
[Not supported by viewer]
M
[Not supported by viewer]
M
[Not supported by viewer]
CoreOS
[Not supported by viewer]
M
[Not supported by viewer]
R
[Not supported by viewer]
R
[Not supported by viewer]
M
[Not supported by viewer]
CoreOS
[Not supported by viewer]
M
[Not supported by viewer]
M
[Not supported by viewer]
M
[Not supported by viewer]
M
[Not supported by viewer]
CoreOS
[Not supported by viewer]
M
[Not supported by viewer]
R
[Not supported by viewer]
M
[Not supported by viewer]
M
[Not supported by viewer]
CoreOS
[Not supported by viewer]
M
[Not supported by viewer]
M
[Not supported by viewer]
R
[Not supported by viewer]
M
[Not supported by viewer]
CoreOS
[Not supported by viewer]
M
[Not supported by viewer]
R
[Not supported by viewer]
M
[Not supported by viewer]
M
[Not supported by viewer]
CoreOS
[Not supported by viewer]
M
[Not supported by viewer]
M
[Not supported by viewer]
R
[Not supported by viewer]
M
[Not supported by viewer]
CoreOS
[Not supported by viewer]

Scaling the Rest

  • Nitrogen is identity, ingest and routing
  • Actually processing all of the incoming data takes more work
  • We pump all of our data into Azure Event Hubs
    • Hubs can scale to orders of magnitude over what we're currently driving
    • Event Hubs can output into Storm/Spark pipelines

Supporting Azure Event Hub on Node

  • Event Hub lets us ignore "downstream"
  • Simple REST-based POST for messages
  • ...unfortunately, too slow
  • ... but it supports AMQP
  • ... but only AMQP 1.0, for which there are no modules

AMQP 1.0

Implementing binary protocols in node is super fun!

  • No 64-bit integers
  • Bit-twiddling is... special
  • Keeping track of buffers (thanks bl!)
  • Promisification in the face of failures

Debugging is even more fun!


amqp10:connection Rx: 0000002002000005005311c01308540152015264526470ffffffff4040c10100 +0ms
amqp10:session processing frame(BeginFrame): {"frameType":0,"channel":5,"remoteChannel":1,"nextOutgoingId":1,
     "incomingWindow":100,"outgoingWindow":100,"handleMax":4294967295,"offeredCapabilities":null,"desiredCapabilities":null,"properties":{}} +0ms
amqp10:session Transitioning from BEGIN_SENT to MAPPED due to beginReceived +0ms
amqp10:session On BEGIN_RCVD, setting params to (1,100,100,4294967295) +1ms
amqp10:link Transitioning from DETACHED to ATTACHING due to sendAttach +0ms
amqp10:link Tx attach CH=1, Handle=0 +0ms
amqp10:framing:amqp Encoding performative: {"descriptor":{"buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,18]},"offset":0}
     ,"value":{"name":"test","handle":{"typeName":"uint","value":0},"role":false,"senderSettleMode":{"typeName":"ubyte","value":2},"receiverSettleMode":{"typeName":"ubyte","value":0},"source":{"descriptor":{"buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,40]},"offset":0},"address":"test-src","durable":0,"expiryPolicy":"session-end","timeout":0,"dynamic":false},"target":{"descriptor":{"buffer":{"type":"Buffer","data":[0,0,0,0,0,0,0,41]},"offset":0},"address":"test-tgt","durable":0,"expiryPolicy":{"contents":"session-end"},"timeout":0,"dynamic":false},"unsettled":{},"incompleteUnsettled":false,"initialDeliveryCount":{"typeName":"uint","value":1},"maxMessageSize":{"typeName":"ulong","value":0},"offeredCapabilities":null,"desiredCapabilities":null,"properties":{},"encodeOrdering":["name","handle","role","senderSettleMode","receiverSettleMode","source","target","unsettled","incompleteUnsettled","initialDeliveryCount","maxMessageSize","offeredCapabilities","desiredCapabilities","properties"]}} +1ms
amqp10:framing Sending frame: AttachFrame: {"type":"Buffer","data":[0,0,0,116,2,0,0,1,0,83,18,192,103,14,161,4,116,101,115,116,67,66,80,2,80,0,0,83,40,192,37,11,161,8,116,101,115,116,45,115,114,99,67,163,11,115,101,115,115,105,111,110,45,101,110,100,67,66,193,1,0,64,193,1,0,64,64,64,0,83,41,192,31,7,161,8,116,101,115,116,45,116,103,116,67,163,11,115,101,115,115,105,111,110,45,101,110,100,67,66,193,1,0,64,193,1,0,66,82,1,68,64,64,193,1,0]}:
     0000007402000001005312c0670ea10474657374434250025000005328c
     0250ba108746573742d73726343a30b73657373696f6e2d656e644342c1
     010040c10100404040005329c01f07a108746573742d74677443a30b736
     57373696f6e2d656e644342c1010040c10100425201444040c10100 +2ms
                    

Why We're Great

  • No native code dependencies
  • Fully promisified API
  • Policy-based configuration
  • Use it "dumb", or "go deep" and specify AMQP internals

Coders/Users Needed

  • Supports Qpidd, ActiveMQ, RabbitMQ
  • Still more work to do
  • We love user feedback

For More Information

Nitrogen.io Main Site
NitrogenJS Github
AMQP 1.0 Node Github (Feel free to help!)
These Slides

Thanks For Listening!

What's a Microsoft OSS Engineer?
My Blog: http://mikelanzetta.com
My Github: https://github.com/noodlefrenzy
My Twitter: @noodlefrenzy

http://aka.ms/cascadia