NetBSD as a Kubernetes Pod

I had to do it.
So here’s how to run a NetBSD micro-vm as… a Kubernetes pod.

First thing is to modify the start script from the previous article in order to add Docker-style networking, i.e. port forwarding from the host to the micro-vm. This is done using the hostfwd flag in qemu’s -netdev parameter

#!/bin/sh

kernel=$1

img=${2:-"root.img"}

[ -n "$3" ] && drive2="-drive file=${3},if=virtio"

qemu-system-x86_64 -enable-kvm -m 256 \
        -kernel $kernel -append "console=com root=ld0a" \
        -serial mon:stdio -display none \
        -drive file=${img},if=virtio $drive2 \
        -netdev user,id=net0,hostfwd=tcp::8080-:80 -device virtio-net,netdev=net0

In the previous experience we mapped the kernel and the root image from the host using Docker’s -v parameter, and while it’s possible to map files from the host using a Kubernetes volume, we will bundle NetBSD these files into the Docker image to make things easier.
Please refer to mksmolnb documentation to learn how to produce a minimal nginx micro-vm.

FROM alpine:latest

RUN apk add --quiet --no-cache qemu-system-x86_64 iproute2 bridge-utils

COPY netbsd-SMOL nginx.img startnb.sh ./
COPY qemu/qemu-ifup qemu/qemu-ifdown /etc/

CMD /startnb.sh /netbsd-SMOL nginx.img

Once the image is created, export it to a tar archive in order to add it to the “cluster”; I won’t upload smolBSD images to public repo just yet, I have a bigger plan, so for now, I’ll just make the images available to containerd as cached ones.

Warning, it took me way too long to understand that an image tagged with latest won’t be cached and then will be fetched no matter what, so tag your image accordingly.

builder$ docker save imil/smolbsd:0.1 > smolbsd.tar

There’s a lot of homelab Kubernetes all-in-one clusters out there, I picked one that works out of the box on the GNU/Linux Mint system I’m using, microk8s.
Let’s import the smolbsd image

cluster$ microk8s ctr image import smolbsd.tar

To spare some time I did the classic alias

cluster$ alias k='microk8s kubectl'

And now the real deal, here’s the pod manifest, basically this does the same as the Docker command line. Note the imagePullPolicy: Never key / value as we don’t want the container runtime to try and fetch the image “outside”

apiVersion: v1
kind: Pod
metadata:
  name: smolbsd-nginx
  labels:
    app.kubernetes.io/name: smolbsd-nginx
spec:
  containers:
  - name: nginx
    image: imil/smolbsd:0.1
    imagePullPolicy: Never
    ports:
    - containerPort: 8080
    volumeMounts:
    - mountPath: /dev/kvm
      name: dev-kvm
    securityContext:
      privileged: true
      capabilities:
        add:
          - NET_ADMIN
  volumes:
  - name: dev-kvm
    hostPath:
      path: /dev/kvm

We could have written a deployment, and I actually tried it, it works, but for the sake of simplicity of this blog post, we’ll keep it as simple as a single pod.
We expose the port 8080 from the container, which in turn is forwarded by qemu with the hostfwd parameter.

In order to make this pod service available, here the associated service, here again we expose the port 8080

apiVersion: v1
kind: Service
metadata:
  name: smolbsd-svc
spec:
  selector:
    app.kubernetes.io/name: smolbsd-nginx
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 8080

We can join the two as a single yaml file, by separating both definitions with a --- and they invoke k apply

cluster$ k apply -f smolbsd.yaml

And here we go

cluster$ k get pods
NAME            READY   STATUS    RESTARTS   AGE
smolbsd-nginx   1/1     Running   0          13h
cluster$ k get services
NAME          TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
kubernetes    ClusterIP   10.152.183.1    <none>        443/TCP    22h
smolbsd-svc   ClusterIP   10.152.183.18   <none>        8080/TCP   19h
cluster$ k logs smolbsd-nginx 2>&1|tail -20
vioif1: leased 10.0.2.15 for 86400 seconds
vioif1: adding route to 10.0.2.0/24
vioif1: adding default route via 10.0.2.2
script_runreason: /libexec/dhcpcd-run-hooks: No such file or directory
forked to background, child pid 122

starting nginx.. done

Testing web server:
HTTP/1.1 200 OK
Server: nginx/1.24.0
Date: Mon, 23 Oct 2023 15:02:01 GMT
Content-Type: text/html
Content-Length: 38
Last-Modified: Mon, 23 Oct 2023 04:13:29 GMT
Connection: close
ETag: "6535f2e9-26"
Accept-Ranges: bytes

The moment of truth

$ curl http://10.152.183.18:8080
Welcome to nginx on NetBSD on Docker on Kubernetes!

NetBSD pod on Kubernetes [✔]