Working with I²C peripherals via Kubernetes on Raspberry Pi

Photo by author showing k8s cluster and I²C devices

Inter-integrated circuit (I²C) protocol is one of the supported communication mechanisms to connect peripheral devices on Raspberry Pi. In this post we will look at working with BME280 sensor via Kubernetes pod. This will allow us to schedule a k8s workload to capture temperature, humidity and pressure values. The image above shows the setup consisting of two Raspberry Pi’s, which form a Kubernetes cluster, and the sensor is attached to one of them (seen on the far right).

Once everything is setup and running properly the logs show the sensor data streaming in:

└─ $ ▶ kubectl --namespace=bme280 logs -f bme280
{"level":"info","time":"2021-08-13T15:12:54.785Z","name":"bme280","msg":"detected bme280 with device id: 0x60"}
{"level":"info","time":"2021-08-13T15:12:59.796Z","name":"bme280.data","msg":"data","temperature":27.29,"pressure":1007.56,"humidity":43.02,"altitude":47}
{"level":"info","time":"2021-08-13T15:13:04.801Z","name":"bme280.data","msg":"data","temperature":27.27,"pressure":1007.54,"humidity":43.07,"altitude":47}

As you can see, the logs from the pod are streaming the sensor data we are interested in. I’ll skip the details on setting up Kubernetes on Raspberry Pi in this post but will briefly mention the configuration as follows:

  • Hardware consisted of a Raspberry Pi 4 and a Raspberry Pi 3 both running Debian 64-bit OS
  • BME280 sensor was attached to one of the nodes via I²C
  • k0s was used to deploy Kubernetes cluster on the nodes
  • I²C bus was enabled on the nodes via raspi-config
└─ $ ▶ kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
rpi4-* Ready <none> 25h v1.21.3+k0s ***.***.*.** <none> Debian GNU/Linux 10 (buster) 5.10.52-v8+ containerd://1.4.8
rpi3-* Ready <none> 25h v1.21.3+k0s ***.***.*.** <none> Debian GNU/Linux 10 (buster) 5.10.52-v8+ containerd://1.4.8

Furthermore, I put together a Go based binary to interface with I²C bus and capture the sensor data, which can be used directly on the node to confirm that the data capture is working well:

pi@raspberry:~ $ ./bme280 run --interval-seconds=2 --total-count=100 | jq '.'
{
"level": "info",
"time": "2021-05-21T17:53:10.210-0700",
"name": "bme280",
"msg": "detected bme280 with device id: 0x60"
}
{
"level": "info",
"time": "2021-05-21T17:53:12.228-0700",
"name": "bme280.data",
"msg": "data",
"temperature": 23.87,
"pressure": 1003.94,
"humidity": 32.5,
"altitude": 77
}
{
"level": "info",
"time": "2021-05-21T17:53:14.251-0700",
"name": "bme280.data",
"msg": "data",
"temperature": 23.87,
"pressure": 1003.93,
"humidity": 32.39,
"altitude": 77
}
...<redacted>

At this point we can now build a container image with this binary so it can be deployed as a Kubernetes pod. The Dockerfile below shows steps to build arm64 binary and encapsulate it within a so-called distroless base container image.

# Build the manager binary
FROM docker.io/library/golang:1.16.7 as builder
WORKDIR /workspace
# Copy the Go Modules manifests
COPY go.mod go.mod
COPY go.sum go.sum
# Copy the go source
COPY main.go main.go
COPY cmd/ cmd/
COPY flags/ flags/
COPY run/ run/
COPY vendor/ vendor/
# Build binaries for both arm64 arch
RUN CGO_ENABLED=0 GOOS=linux GOARCH=arm64 GO111MODULE=on go build -mod=vendor -a -o manager.arm64 main.go
# Use distroless as minimal base image to package the manager binary
# Refer to https://github.com/GoogleContainerTools/distroless for more details
FROM gcr.io/distroless/static:nonroot
WORKDIR /
COPY --from=builder /workspace/manager.arm64 /manager
USER 65532:65532

I won’t go over the details on how to put together the binary to capture sensor data in this post. You can read more about it in the link below:

Pod Spec

Assuming that binary works well and the container is ready, we can now begin to put together a pod spec:

apiVersion: v1
kind: Pod
metadata:
name: bme280
spec:
securityContext:
runAsNonRoot: true
runAsGroup: 998
runAsUser: 65532
fsGroup: 998
nodeSelector:
kubernetes.io/hostname: "node-where-sensor-is-attached"
imagePullSecrets:
- name: registry-key
volumes:
- name: dev
hostPath:
path: /dev
containers:
- name: bme280
image: your-registry/your-account/bme280:1.2.3
imagePullPolicy: IfNotPresent
command:
- "/manager"
args:
- "run"
- "--total-count"
- "0"
- "--total-duration-minutes"
- "0"
securityContext:
privileged: true
volumeMounts:
- mountPath: /dev
name: dev

The important point to note in the pod spec is the securityContext which indicates user id and group id that should be used to access I²C bus. Group id can be fetched by listing groups id’s belonging to the pi user on the node

pi@rpi:~ $ whoami
pi
pi@rpi:~ $ id
uid=1000(pi) gid=1000(pi) groups=1000(pi),4(adm),20(dialout),24(cdrom),27(sudo),29(audio),44(video),46(plugdev),60(games),100(users),105(input),109(netdev),997(gpio),998(i2c),999(spi)

As you can see the id of 998 is assigned to the group i2c, which is what we can use in the pod spec.

Another critical aspect of making this work via a containerized binary is to provide access to /dev mount paths. The I²C devices show up as follows and we also need to confirm that the ownership on these files match the group level permission we have assigned to the pod.

pi@rpi3-0:~ $ ls -la /dev/i2c-*
crw-rw---- 1 root i2c 89, 1 Aug 13 04:24 /dev/i2c-1
crw-rw---- 1 root i2c 89, 2 Aug 13 04:24 /dev/i2c-2

Finally, we can deploy the pod spec and it should communicate with the underlying hardware!

└─ $ ▶ kubectl --namespace=bme280 get pods 
NAME READY STATUS RESTARTS AGE
bme280 1/1 Running 0 176m
└─ $ ▶ kubectl --namespace=bme280 logs -f bme280
{"level":"info","time":"2021-08-13T15:12:54.785Z","name":"bme280","msg":"detected bme280 with device id: 0x60"}
{"level":"info","time":"2021-08-13T15:12:59.796Z","name":"bme280.data","msg":"data","temperature":27.29,"pressure":1007.56,"humidity":43.02,"altitude":47}
{"level":"info","time":"2021-08-13T15:13:04.801Z","name":"bme280.data","msg":"data","temperature":27.27,"pressure":1007.54,"humidity":43.07,"altitude":47}

--

--

--

Software engineer and entrepreneur currently building Kubernetes infrastructure and cloud native stack for edge/IoT and ML workflows.

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Testing C++ signal-slot libraries

Hack the Box — Knife Write up

🐺Wolf-Pack Space Airdrop Announcement

DIY goal light using a Raspberry Pi

5 Tips to help get you through your first month at Bloc.

Keep an eye on the uptime checks using Google Monitoring service

AMAZON WEB SERVICES:A CHEAT SHEET

Mongo DB: In and Out

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Saurabh Deoras

Saurabh Deoras

Software engineer and entrepreneur currently building Kubernetes infrastructure and cloud native stack for edge/IoT and ML workflows.

More from Medium

Build Oracle Fusion Middleware (FMW) Weblogic 12c Admin Server Image for Kubernetes or Docker

An update on the Stock Trader cloud-native application

Getting started with K8s in 2022

Listing Symbolic Links in Apache