In This Post...
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.
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
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:
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.