Node-RED Docker+Kubernetes

Node-RED Docker+Kubernetes

node-red docker kubernetes

Node-RED allows Raspberry Pis to act as gateways for specific home-automation features, playing an essential role in both the van and the cabin.

This is one of several posts on containerization of common, useful software.

If you’ve not used it before, Node-RED is a visual code editor — of the sorts often used to introduce computer programming to beginners, or otherwise simplify the creation of basic scripts.

node red in kubernetes docker home assistant panel
Node RED can even run in an iframe inside of Home Assistant using a custom panel.

Docker Considerations

The official getting started guide has lots of good information about running in Docker. It covers the basics, like persistence and security. However, there are many possible ways to use node RED.

The above screenshot shows the code for my DIY “Nest-equivalent” smart thermostat. It connects to an Arduino via a USB port and then feeds sensor data to Home Assistant. I frequently follow this pattern of using Node RED on a Raspberry Pi device to control peripherals.

Because everything runs in Kubernetes, Node-RED is communicating with Home Assistant within a secure virtual network. It’s even easy to monitor each of the different nodes from the Grafana Kubernetes panel.

Serial Ports (USB), GPIO & LIRC

As noted in the docs, the preferred way to access the gpio/serial in versions of Node-RED >= 1.0 is to use node-red-node-pi-gpiod on the host machine and ensure that the Docker user is in the dialout group.

Alternatively, it is still possible to achieve host access with privileged: true security context in Kubernetes. And an alternative to the dialout group is to make the necessary devices more permissive. The most heavy-handed approach is to do so for all devices by editing /etc/udev/rules.d/50-myusb.rules to include:

KERNEL=="ttyUSB[0-9]*",MODE="0666"
KERNEL=="ttyACM[0-9]*",MODE="0666"

Running on Multiple Devices

If each of your nodes are meant to have exactly the same configuration, then you can scale up the deployment as usual. However, in many cases each node is meant to run different code. It may even have different hardware attached, as in the example above.

The first trick is to use the node’s name to automatically mount a sub-directory of the persistent volume, so each node’s config lives in its own folder:

env:
- name: NODE_NAME
  valueFrom:
    fieldRef:
      fieldPath: spec.nodeName

volumeMounts:
- name: node-red-data
  subPathExpr: $(NODE_NAME)
  mountPath: /data

If each node needs to be addressed uniquely (almost certainly the case), then it becomes necessary to create a service. Using Switchboard, you can simply set the egress to the name of the service (i.e., node-red.svc.default.cluster.local). When combined with Pi-Hole DNS and free OpenDNS servers, you’re then able to access the node via a secure URL (i.e., https://node-red.my-domain.com) both inside and outside the local network.

My Deployment File(s)

Here is an example of deploying Node-RED as a daemon set with a service:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-red
  labels:
    app: node-red
spec:
  selector:
    matchLabels:
      app: node-red
  template:
    metadata:
      labels:
        app: node-red
    spec:
      containers:
      - name: node-red
        image: nodered/node-red:latest
        ports:
        - containerPort: 1880
          name: node-red-ui
        securityContext:
          privileged: true
        volumeMounts:
        - name: node-red-data
          subPathExpr: $(NODE_NAME)
          mountPath: /data
        env:
        - name: NODE_NAME
          valueFrom:
            fieldRef:
              fieldPath: spec.nodeName
        - name: TZ
          value: America/Los_Angeles
      volumes:
      - name: node-red-data
        persistentVolumeClaim:
          claimName: node-red-claim
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: node-red-claim
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
---
apiVersion: v1
kind: Service
metadata:
  name: rode-red
spec:
  selector:
    app: node-red
  type: LoadBalancer
  loadBalancerIP: 192.168.0.103
  ports:
    - name: node-red-ui
      port: 1880
      protocol: TCP
      targetPort: node-red-ui

I use metallb to achieve the static IPs seen above in a baremetal cluster. However, it is not necessary to do so when addressing the service via the internal DNS to Kubernetes services.

Build Guides

Looking for even more detail?

Drop your email in the form below and you'll receive links to the individual build-guides and projects on this site, as well as updates with the newest projects.

... but this site has no paywalls. If you do choose to sign up for this mailing list I promise I'll keep the content worth your time.

Written by
(zane) / Technically Wizardry
Join the discussion

1 comment

Menu