Infrastructure as a Service

 View Only

MicroShift – Part 18: Raspberry Pi 4 with Manjaro

By Alexei Karve posted Thu June 02, 2022 05:41 AM


MicroShift and KubeVirt on Raspberry Pi 4 with Manjaro 22.04


MicroShift is a research project that is exploring how OpenShift OKD Kubernetes distribution can be optimized for small form factor devices and edge computing. In Part 1 we looked at multiple ways to run MicroShift on a MacBook Pro. In Part 4, we ran MicroShift on the Raspberry Pi 4 with the Raspberry Pi OS (64 bit) and further in Part 9, we looked at Virtualization with MicroShift on Raspberry Pi 4 with Raspberry Pi OS (64 bit). In Part 5, we saw multiple options to build and run MicroShift on the Raspberry Pi 4 with the CentOS 8 Stream. In Part 6, we deployed MicroShift on the Raspberry Pi 4 with Ubuntu 20.04. In Part 8, we looked at the All-In-One install of MicroShift on balenaOS. In Part 10, Part 11, and Part 12, we deployed MicroShift and KubeVirt on Fedora IoT, Fedora Server and Fedora CoreOS respectively, Part 13 with Ubuntu 22.04, Part 14 on Rocky Linux, Part 15 on openSUSE, Part 16 on Oracle Linux and Part 17 on AlmaLinux. In this Part 18, we will work with MicroShift on Manjaro (based on ArchLinux). We will run an object detection sample and send messages to Node Red installed on MicroShift. Further, we will setup KubeVirt and the OKD Web Console and run Virtual Machine Instances in MicroShift. We will also run sample notebooks for object detection and license plate detection.

Manjaro inherits a lot of the features of Arch Linux and implements many unique features. It subscribes to the rolling release development model; updates are continuous and it’s never necessary to download a new release of Manjaro. You keep your system up to date through the Pacman package manager. Manjaro is only available for 64 bit CPU architectures. Manjaro has several editions, including “official” and “community”, which both contain several different supported desktop environments. Support for AppArmor, SELinux and Tomoyo was removed in the Arch kernel, and this also applies to the Manjaro kernels. Manjaro also supports ARM and has a download tailored to Raspberry Pi systems. We use the Minimal image without the desktop environment.

Manjaro Download

Setting up the Raspberry Pi 4 with Manjaro

Run the following steps to download the Manjaro image and setup the Raspberry Pi 4.

1. Download the Manjaro Raspberry Pi 4 Minimal from

2. Write to Microsdxc card using balenaEtcher or the Raspberry Pi Imager

3. Have a Keyboard and Monitor connected to the Raspberry Pi 4

4. Insert Microsdxc into Raspberry Pi 4 and poweron

5. Set the user rpi, hostname rpi and select the other inputs as required using the Keyboard and Monitor attached to the Raspberry Pi. It will automatically resize the partition and reboot.

Input settings

6. Find the ethernet dhcp ipaddress of your Raspberry Pi 4 by running the nmap on your Macbook with your subnet

$ sudo nmap -sn
Nmap scan report for
Host is up (0.080s latency).
MAC Address: E4:5F:01:2E:D8:95 (Raspberry Pi Trading)

7. Login using rpi and password (you set above) to ipaddress of your Raspberry Pi.

$ ssh rpi@
rpi@'s password:
Welcome to Manjaro-ARM
[rpi@rpi ~]$ sudo su -

We trust you have received the usual lecture from the local System
Administrator. It usually boils down to these three things:

    #1) Respect the privacy of others.
    #2) Think before you type.
    #3) With great power comes great responsibility.

[sudo] password for rpi:
[root@rpi ~]#

8. Check the partition is using the full size

[root@rpi ~]# df -h
Filesystem      Size  Used Avail Use% Mounted on
dev             3.7G     0  3.7G   0% /dev
run             3.9G   13M  3.9G   1% /run
/dev/mmcblk0p2   58G  1.5G   54G   3% /
tmpfs           3.9G     0  3.9G   0% /dev/shm
tmpfs           3.9G     0  3.9G   0% /tmp
/dev/mmcblk0p1  214M   56M  159M  26% /boot
tmpfs           782M     0  782M   0% /run/user/1000

9. Update and add the ipv4 address to /etc/hosts

yes Y | sudo pacman -Syyu

hostnamectl set-hostname
echo "$ipaddress rpi" >> /etc/hosts

10. Optionally, enable wifi using nmcli

yes Y | pacman -S networkmanager 
systemctl enable --now NetworkManager
sleep 10
nmcli device wifi list # Note your ssid
nmcli device wifi connect $ssid --ask

11. Check the release

cat /etc/os-release
[root@rpi ~]# cat /etc/os-release
ID_LIKE="manjaro arch"

12. Update the kernel parameters

We want the memory to be enabled, it is not enabled by default

[root@rpi ~]# mount | grep cgroup # Check that memory and cpuset are present
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot)
[root@rpi ~]# cat /proc/cgroups | column -t # Check that memory and cpuset are present
#subsys_name  hierarchy  num_cgroups  enabled
cpuset        0          32           1
cpu           0          32           1
cpuacct       0          32           1
blkio         0          32           1
memory        0          32           0
devices       0          32           1
freezer       0          32           1
net_cls       0          32           1
perf_event    0          32           1
net_prio      0          32           1
pids          0          32           1
rdma          0          32           1

Edit the cmdline.txt and reboot

yes Y | pacman -S vim vi
vim /boot/cmdline.txt

Concatenate the following onto the end of the existing line (do not add a new line) in /boot/cmdline.txt

 cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory

A control group (cgroup) is a Linux kernel feature that limits, accounts for, and isolates the resource usage (CPU, memory, disk I/O, network, and so on) of a collection of processes. Cgroups are a key component of containers because there are often multiple processes running in a container that you need to control together. In Microshift, cgroups are used to implement resource requests and limits and corresponding QoS classes at the pod level.



ssh rpi@$ipaddress
sudo su -
cat /proc/cmdline
mount | grep cgroup # Check that memory and cpuset are present
cat /proc/cgroups | column -t # Check that memory and cpuset are present


[root@rpi ~]# cat /proc/cmdline
coherent_pool=1M 8250.nr_uarts=0 snd_bcm2835.enable_compat_alsa=0 snd_bcm2835.enable_hdmi=1 video=HDMI-A-1:1920x1200M@60 smsc95xx.macaddr=E4:5F:01:2E:D8:95 vc_mem.mem_base=0x3ec00000 vc_mem.mem_size=0x40000000  root=PARTUUID=1e7027b0-02 rw rootwait console=ttyS0,115200 console=tty3 selinux=0 quiet splash plymouth.ignore-serial-consoles smsc95xx.turbo_mode=N dwc_otg.lpm_enable=0 kgdboc=ttyS0,115200 usbhid.mousepoll=8 audit=0 cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory
[root@rpi ~]# mount | grep cgroup # Check that memory and cpuset are present
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot)
[root@rpi ~]# cat /proc/cgroups | column -t # Check that memory and cpuset are present
#subsys_name  hierarchy  num_cgroups  enabled
cpuset        0          60           1
cpu           0          60           1
cpuacct       0          60           1
blkio         0          60           1
memory        0          60           1
devices       0          60           1
freezer       0          60           1
net_cls       0          60           1
perf_event    0          60           1
net_prio      0          60           1
pids          0          60           1
rdma          0          60           1

Install sense_hat and RTIMULib on ArchLinux

The Sense HAT is an add-on board for the Raspberry Pi. The Sense HAT has an 8 × 8 RGB LED matrix, a five – button joystick and includes the following sensors: Inertial Measurement Unit (Accelerometer, Gyroscope, Magnetometer), Temperature, Barometric pressure, Humidity. If you have the Sense HAT attached, install the libraries.

Install sensehat

yes Y | pacman -S i2c-tools make cmake gcc python-pip

pip3 install Cython Pillow numpy sense_hat

Check the Sense Hat with i2cdetect

modprobe i2c-dev
modprobe i2c-bcm2708
echo "i2c-dev" > /etc/modules-load.d/i2c-dev.conf
echo "i2c-bcm2708" > /etc/modules-load.d/i2c-bcm2708.conf
i2cdetect -y 1


[root@rpi ~]# modprobe i2c-dev
[root@rpi ~]# modprobe i2c-bcm2708
[root@rpi ~]# i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- 1c -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- UU -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- 5c -- -- 5f
60: -- -- -- -- -- -- -- -- -- -- 6a -- -- -- -- --
70: -- -- -- -- -- -- -- --

Install RTIMULib

yes Y | pacman -S git
cd ~
git clone
cd RTIMULib/
cd Linux/python
python3 build
python3 install
cd ../..
mkdir build
cd build
cmake ..
make -j4
make install

# Optional test the sensors
cd /root/RTIMULib/Linux/RTIMULibDrive11
make -j4
make install
RTIMULibDrive11 # Ctrl-C to break

cd /root/RTIMULib/Linux/RTIMULibDrive10
make -j4
make install
RTIMULibDrive10 # Ctrl-C to break

# Optional
yes Y | pacman -S qt5-base
cd /root/RTIMULib/Linux/RTIMULibDemoGL
make -j4
make install

Test the SenseHat samples for the Sense Hat's LED matrix and sensors.

cd ~
git clone
cd ~/microshift/raspberry-pi/sensehat-fedora-iot

# Enable random LEDs
python3 # Ctrl-C to interrupt

# Show multiple screens to test LEDs
python3 # Ctrl-C to interrupt

# First time you run the, you may see “Temperature: 0 C”. Just run it again.

# Show the Temperature, Pressure and Humidity
python3 # Ctrl-C to interrupt

# Show two digits for multiple numbers
sed -i "s/32,32,32/255,255,255/"

# When a magnet gets close to SenseHAT, the LEDs will all turn red for 1/5 of a second

# Find Magnetic North

Install MicroShift on the Raspberry Pi 4 Manjaro host

Install the dependencies and copy the latest microshift prebuilt binary. You will also need to set the crio.conf.

yes Y | pacman -S firewalld cri-o crictl

# Check the registries /etc/crio/crio.conf 
#echo 'registries=[""]' >> /etc/crio/crio.conf
echo 'unqualified-search-registries=[""]' >> /etc/containers/registries.conf

VERSION=$(curl -sL | grep tag_name | head -n 1 | cut -d '"' -f 4)
curl -LO$VERSION/microshift-linux-$ARCH
curl -LO$VERSION/release.sha256
BIN_SHA="$(sha256sum microshift-linux-$ARCH | awk '{print $1}')"
KNOWN_SHA="$(grep "microshift-linux-$ARCH" release.sha256 | awk '{print $1}')"
if [[ "$BIN_SHA" != "$KNOWN_SHA" ]]; then
    echo "SHA256 checksum failed" && exit 1
sudo chmod +x microshift-linux-$ARCH
sudo mv microshift-linux-$ARCH /usr/bin/microshift

yes Y | pacman -S --needed wget
wget -O /usr/lib/systemd/system/microshift.service
systemctl daemon-reload

Install KVM on the host and validate the Host Virtualization Setup.

yes Y | pacman -S jack2 virt-manager virt-viewer virt-install libvirt qemu ebtables dnsmasq bridge-utils

If the above command gives error, then we need to download some packages from the Arch x86_64 repo and install them using pacman as mentioned at

mkdir packages;cd packages
wget -O edk2-ovmf-202202-2-any.pkg.tar.zst
wget -O seabios-1.16.0-1-any.pkg.tar.zst
wget -O edk2-armvirt-202202-2-any.pkg.tar.zst
pacman -U --overwrite \* --noconfirm *

pacman -S jack2 virt-manager virt-viewer virt-install libvirt qemu ebtables dnsmasq bridge-utils qemu-system-aarch64

This will install the qemu-base and remove the iptables as shown below:

[root@rpi packages]# pacman -S jack2 virt-manager virt-viewer virt-install libvirt qemu ebtables dnsmasq bridge-utils qemu-system-aarch64
:: There are 3 providers available for qemu:
:: Repository extra
   1) qemu-base  2) qemu-desktop  3) qemu-full

Enter a number (default=1): 1
resolving dependencies...
looking for conflicting packages...
:: iptables-nft and iptables are in conflict. Remove iptables? [y/N] y
warning: dependency cycle detected:
warning: libglvnd will be installed before its mesa dependency

The virt-host-validate command validates that the host is configured in a suitable way to run libvirt hypervisor driver qemu.

# Works with nftables on Fedora Server and Fedora IoT, Oracle Linux, AlmaLinux, ArchLinux
# vim /etc/firewalld/firewalld.conf

systemctl enable --now libvirtd virt-host-validate qemu


[root@rpi sensehat-fedora-iot]# systemctl enable --now libvirtd
Created symlink /etc/systemd/system/ → /usr/lib/systemd/system/libvirtd.service.
Created symlink /etc/systemd/system/ → /usr/lib/systemd/system/virtlockd.socket.
Created symlink /etc/systemd/system/ → /usr/lib/systemd/system/virtlogd.socket.
Created symlink /etc/systemd/system/ → /usr/lib/systemd/system/libvirtd.socket.
Created symlink /etc/systemd/system/ → /usr/lib/systemd/system/libvirtd-ro.socket.
[root@rpi sensehat-fedora-iot]# virt-host-validate qemu
  QEMU: Checking if device /dev/kvm exists                                   : PASS
  QEMU: Checking if device /dev/kvm is accessible                            : PASS
  QEMU: Checking if device /dev/vhost-net exists                             : PASS
  QEMU: Checking if device /dev/net/tun exists                               : PASS
  QEMU: Checking for cgroup 'cpu' controller support                         : PASS
  QEMU: Checking for cgroup 'cpuacct' controller support                     : PASS
  QEMU: Checking for cgroup 'cpuset' controller support                      : PASS
  QEMU: Checking for cgroup 'memory' controller support                      : PASS
  QEMU: Checking for cgroup 'devices' controller support                     : PASS
  QEMU: Checking for cgroup 'blkio' controller support                       : PASS
  QEMU: Checking for device assignment IOMMU support                         : WARN (Unknown if this platform has IOMMU support)
  QEMU: Checking for secure guest support                                    : WARN (Unknown if this platform has Secure Guest support)

Check that cni plugins are present

ls /opt/cni/bin/ # cni plugins
ls /usr/libexec/cni # No such file or directory


[root@rpi microshift]# ls /opt/cni/bin/ # cni plugins
bandwidth  bridge  dhcp  firewall  host-device	host-local  ipvlan  loopback  macvlan  portmap	ptp  sbr  static  tuning  vlan	vrf
[root@rpi microshift]# ls /usr/libexec/cni # empty
ls: cannot access '/usr/libexec/cni': No such file or directory

We will have systemd start and manage MicroShift. Refer to the microshift service for the three approaches.

systemctl enable --now crio microshift

You may read about selecting zones for your interfaces.

systemctl enable firewalld --now
firewall-cmd --zone=trusted --add-source= --permanent
firewall-cmd --zone=trusted --add-source= --permanent
firewall-cmd --zone=public --add-port=80/tcp --permanent
firewall-cmd --zone=public --add-port=443/tcp --permanent
firewall-cmd --zone=public --add-port=5353/udp --permanent
firewall-cmd --reload

Additional ports may need to be opened. For external access to run kubectl or oc commands against MicroShift, add the 6443 port:

firewall-cmd --zone=public --permanent --add-port=6443/tcp

For access to services through NodePort, add the port range 30000-32767:

firewall-cmd --zone=public --permanent --add-port=30000-32767/tcp

firewall-cmd --reload
firewall-cmd --list-all --zone=public
firewall-cmd --get-default-zone
#firewall-cmd --set-default-zone=public
#firewall-cmd --get-active-zones
firewall-cmd --list-all

Check the microshift and crio logs

journalctl -u microshift -f
journalctl -u crio -f

The microshift service references the microshift binary in the /usr/bin directory

[root@rpi ~]# cat /usr/lib/systemd/system/microshift.service
Description=MicroShift crio.service crio.service

ExecStart=microshift run


Install the kubectl and the openshift oc client

cd /tmp
dnf -y install tar
export OCP_VERSION=4.9.11 && \
    curl -o oc.tar.gz$ARCH/clients/ocp/$OCP_VERSION/openshift-client-linux-$OCP_VERSION.tar.gz && \
    tar -xzvf oc.tar.gz && \
    rm -f oc.tar.gz && \
    install -t /usr/local/bin {kubectl,oc} && \
    rm -f {,kubectl,oc}

It will take around 3 minutes for all pods to start. Check the status of node and pods using kubectl or oc client.

export KUBECONFIG=/var/lib/microshift/resources/kubeadmin/kubeconfig
#watch "kubectl get nodes;kubectl get pods -A;crictl pods;crictl images"
watch "oc get nodes;oc get pods -A;crictl pods;crictl images"

Output of top after MicroShift is started, see the memory usage

top - 04:41:57 up 13 min,  1 user,  load average: 1.37, 2.20, 1.34
Tasks: 159 total,   1 running, 158 sleeping,   0 stopped,   0 zombie
%Cpu(s):  6.3 us,  2.0 sy,  0.0 ni, 91.4 id,  0.1 wa,  0.0 hi,  0.2 si,  0.0 st
MiB Mem :   7810.5 total,   5626.7 free,    955.5 used,   1228.3 buff/cache
MiB Swap:  11715.7 total,  11715.7 free,      0.0 used.   6743.2 avail Mem

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
    731 root      20   0   13.2g 767016 110208 S  30.3   9.6   5:12.09 microshift
    688 root      20   0 1940960  70836  40208 S   1.0   0.9   1:23.65 crio
   2069 root      20   0  747776  41956  31776 S   1.0   0.5   0:01.95 coredns
   3193 root      20   0   16864   5692   4576 R   1.0   0.1   0:00.66 top
   1759 root      20   0  755076  57380  37904 S   0.7   0.7   0:29.37 service-ca-oper
     77 root       0 -20       0      0      0 I   0.3   0.0   0:03.08 kworker/u9:0-brcmf_wq/mmc1:0001:1
     94 root      rt   0       0      0      0 S   0.3   0.0   0:01.16 sugov:0
    187 root      20   0   49544  17480  16388 S   0.3   0.2   0:02.11 systemd-journal
   1043 root      20   0       0      0      0 D   0.3   0.0   0:00.79 kworker/0:4+events
   1421 root      20   0  748840  36080  26300 S   0.3   0.5   0:01.20 flanneld

Install podman - We will use podman for containerized deployment of MicroShift and building images for the samples.

yes Y | pacman -S podman buildah skopeo

Samples to run on MicroShift

1. InfluxDB/Telegraf/Grafana

The source code is available for this influxdb sample in github.

cd ~
git clone
cd ~/microshift/raspberry-pi/influxdb

If you want to run all the steps in a single command, get the nodename.

oc get nodes


[root@rpi influxdb]# oc get nodes
NAME              STATUS   ROLES    AGE     VERSION   Ready    

Replace the annotation with the above nodename and execute the Note that the node name is different when running MicroShift with the all-in-one containerized approach. So, you will use the instead of the

sed -i "s|coreos||" influxdb-data-dynamic.yaml
sed -i "s|coreos||" grafana/grafana-data-dynamic.yaml


We create and push the “measure:latest” image using the Dockerfile in the sensor directory. The script will create a new project influxdb for this sample, install InfluxDB, install the pod for SenseHat measurements, install Telegraf and check the measurements for the telegraf database in InfluxDB. Finally, it will install Grafana.

This script will allocate dynamic persistent volumes using influxdb-data-dynamic.yaml and grafana-data-dynamic.yaml. The annotation provisionOnNode and the storageClassName are required for dynamic PV.

  storageClassName: kubevirt-hostpath-provisioner

Persistent Volumes and Claims Output:

[root@rpi influxdb]# oc get pv,pvc
NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                    STORAGECLASS                    REASON   AGE
persistentvolume/pvc-d120a1c7-ab39-4101-bcf4-66e2930aed52   57Gi       RWO            Delete           Bound    influxdb/grafana-data    kubevirt-hostpath-provisioner            5m5s
persistentvolume/pvc-e4c56850-0739-410d-840e-c45c602da375   57Gi       RWO            Delete           Bound    influxdb/influxdb-data   kubevirt-hostpath-provisioner            8m9s

NAME                                  STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS                    AGE
persistentvolumeclaim/grafana-data    Bound    pvc-d120a1c7-ab39-4101-bcf4-66e2930aed52   57Gi       RWO            kubevirt-hostpath-provisioner   5m6s
persistentvolumeclaim/influxdb-data   Bound    pvc-e4c56850-0739-410d-840e-c45c602da375   57Gi       RWO            kubevirt-hostpath-provisioner   8m9s

Add the "<RaspberryPiIPAddress> grafana-service-influxdb.cluster.local" to /etc/hosts on your laptop and login to http://grafana-service-influxdb.cluster.local/login using admin/admin. You will need to change the password on first login. Go to the Dashboards list (left menu > Dashboards > Manage). Open the Analysis Server dashboard to display monitoring information for MicroShift. Open the Balena Sense dashboard to show the temperature, pressure, and humidity from SenseHat.

Finally, after you are done working with this sample, you can run the


Deleting the persistent volume claims automatically deletes the persistent volumes.

2. Node Red live data dashboard with SenseHat sensor charts

We will install Node Red on the ARM device as a deployment within MicroShift, add the dashboard and view the gauges for temperature/pressure/humidity data from SenseHat on the dashboard.

cd ~
git clone
cd ~/microshift/raspberry-pi/nodered

Build and push the arm64v8 image “karve/nodered:arm64”

cd docker-custom/
# Replace docker with podman in and run it
podman push karve/nodered:arm64
cd ..

Deploy Node Red with persistent volume for /data within the node red container

mkdir /var/hpvolumes/nodered
restorecon -R -v "/var/hpvolumes/*"
rm -rf /var/hpvolumes/nodered/*;cp -r nodered-volume/* /var/hpvolumes/nodered/.
oc new-project nodered
oc apply -f noderedpv.yaml -f noderedpvc.yaml -f nodered3.yaml -f noderedroute.yaml
oc get routes
oc -n nodered wait deployment nodered-deployment --for condition=Available --timeout=300s
oc logs deployment/nodered-deployment -f

Add the ipaddress of the Raspberry Pi 4 device for nodered-svc-nodered.cluster.local to /etc/hosts on your Laptop and browse to http://nodered-svc-nodered.cluster.local/

The following modules required for the dashboard have been preinstalled node-red-dashboard, node-red-node-smooth, node-red-node-pi-sense-hat. These can be seen under “Manage Palette - Install”. The Flow 1 or Flow 2 have already been imported from the nodered sample. This import to the Node Red can be done manually under “Import Nodes” and then click “Deploy”.

Double click the Sense HAT input node and make sure that all the events are checked. Select the Dashboard. Click on the outward arrow in the tabs to view the sensor charts. You will see the Home by Default. You can see the state of the Joystick Up, Down, Left, Right or Pressed. Click on the Hamburger Menu (3 lines) and select PiSenseHAT. If you selected the Flow 1, you could click on the Input for the Timestamp under “Dot Matrix” to see the “Alarm” message scroll on the SenseHat LED. You can see the screenshots for these dashboards in previous blogs.

We can continue running the next sample that will reuse this Node Red deployment. If the Node Red Deployment is no longer required, we can delete it as follows:

cd ~/microshift/raspberry-pi/nodered
oc delete -f noderedpv.yaml -f noderedpvc.yaml -f nodered3.yaml -f noderedroute.yaml -n nodered
oc project default
oc delete project nodered

3. TensorFlow Lite Python object detection example in MicroShift with SenseHat and Node Red

This example requires the same Node Red setup as in the previous Sample 2.

cd ~
git clone
cd ~/microshift/raspberry-pi/object-detection

We will build the image for object detection send pictures and web socket chat messages to Node Red when a person is detected using a pod in microshift.

# Use buildah or podman to build the image for object detection
podman build -t .
#buildah bud -t .
podman push

Update the env WebSocketURL and ImageUploadURL as shown below. Also update the hostAliases in object-detection.yaml to point to your raspberry pi 4 ip address ( shown below).

          - name: WebSocketURL
            value: "ws://nodered-svc-nodered.cluster.local/ws/chat"
          - name: ImageUploadURL
            value: http://nodered-svc-nodered.cluster.local/upload

      - hostnames:
        - nodered-svc-nodered.cluster.local

Create the deployment

oc project default
oc apply -f object-detection.yaml
oc -n default wait deployment object-detection-deployment --for condition=Available --timeout=300s

We will see pictures being sent to Node Red when a person is detected at http://nodered-svc-nodered.cluster.local/#flow/3e30dc50ae28f61f and chat messages at http://nodered-svc-nodered.cluster.local/chat. When we are done testing, we can delete the deployment

oc delete -f object-detection.yaml

4. Running a Virtual Machine Instance on MicroShift

Find the latest version of the KubeVirt Operator.

LATEST=$(curl -L
echo $LATEST

I used the following version:

LATEST=20220523 # If the latest version does not work

oc apply -f${LATEST}/kubevirt-operator-arm64.yaml
oc apply -f${LATEST}/kubevirt-cr-arm64.yaml
oc adm policy add-scc-to-user privileged -n kubevirt -z kubevirt-operator

# The .status.phase will show Deploying multiple times and finally Deployed
oc get -n kubevirt -o=jsonpath="{.status.phase}" -w # Ctrl-C to break
oc -n kubevirt wait kv kubevirt --for condition=Available --timeout=300s
oc get pods -n kubevirt

We can build the OKD Web Console (Codename: “bridge”) from the source as mentioned in Part 9. We will run the “bridge” as a container image that we run within MicroShift.

cd /root/microshift/raspberry-pi/console
oc create serviceaccount console -n kube-system
oc create clusterrolebinding console --clusterrole=cluster-admin --serviceaccount=kube-system:console -n kube-system
sleep 5
oc get serviceaccount console --namespace=kube-system -o jsonpath='{.secrets[0].name}'
oc get serviceaccount console --namespace=kube-system -o jsonpath='{.secrets[1].name}'

Replace BRIDGE_K8S_MODE_OFF_CLUSTER_ENDPOINT value with your raspberry pi 4's ip address, and secretRef token with the console-token-* from above two secret names for BRIDGE_K8S_AUTH_BEARER_TOKEN in okd-web-console-install.yaml. Then apply/create the okd-web-console-install.yaml.

oc apply -f okd-web-console-install.yaml
oc expose svc console-np-service -n kube-system
oc get routes -n kube-system
oc -n kube-system wait deployment console-deployment --for condition=Available --timeout=300s
oc logs deployment/console-deployment -f -n kube-system

Add the Raspberry Pi IP address to /etc/hosts on your Macbook Pro to resolve console-np-service-kube-system.cluster.local. Now you can access the OKD Web Console from your Laptop http://console-np-service-kube-system.cluster.local/. If you see a blank page, you probably have the value of BRIDGE_K8S_MODE_OFF_CLUSTER_ENDPOINT set incorrectly.

We can optionally preload the fedora image into crio (if using the all-in-one containerized approach, this needs to be run within the microshift pod running in podman)

crictl pull

Now let’s create a Fedora Virtual Machine Instance using the vmi-fedora.yaml.

cd /root/microshift/raspberry-pi/vmi
oc apply -f vmi-fedora.yaml
watch oc get vmi,pods

The output for the virtualmachineinstance PHASE goes from “Scheduling” to “Scheduled” to “Running” after the virt-launcher-vmi-fedora pod STATUS goes from “Init” to “Running”. Note down the ip address of the vmi-fedora Virtual Machine Instance. Directly connect to the VMI from the Raspberry Pi 4 with fedora as the password. Note that it will take another minute after the VMI goes to Running state to ssh to the instance.


[root@rpi vmi]# oc get vmi
NAME         AGE     PHASE     IP           NODENAME          READY
vmi-fedora   6m20s   Running   True
[root@rpi vmi]# ssh -o StrictHostKeyChecking=no fedora@ ping -c 2
Warning: Permanently added '' (ED25519) to the list of known hosts.
fedora@'s password:
PING ( 56(84) bytes of data.
64 bytes from ( icmp_seq=1 ttl=116 time=5.49 ms
64 bytes from ( icmp_seq=2 ttl=116 time=6.25 ms

--- ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 5.489/5.869/6.250/0.380 ms

Alternatively, a second way is to create a Pod to run the ssh client and connect to the Fedora VM from this pod. Let’s create that openssh-client pod:

oc run alpine --privileged --rm -ti --image=alpine -- /bin/sh
apk update && apk add --no-cache openssh-client


oc run sshclient --privileged --rm -ti --image=karve/alpine-sshclient:arm64 -- /bin/sh
#oc attach sshclient -c sshclient -i -t

Then, ssh to the Fedora VMI from this openssh-client container.


[root@rpi vmi]# oc run sshclient --privileged --rm -ti --image=karve/alpine-sshclient:arm64 -- /bin/sh
If you don't see a command prompt, try pressing enter.
/ # ssh -o StrictHostKeyChecking=no fedora@ "bash -c \"ping -c 2\""
Warning: Permanently added '' (ED25519) to the list of known hosts.
fedora@'s password:
PING ( 56(84) bytes of data.
64 bytes from ( icmp_seq=1 ttl=116 time=5.70 ms
64 bytes from ( icmp_seq=2 ttl=116 time=6.44 ms

--- ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 5.700/6.068/6.436/0.368 ms
/ # exit
Session ended, resume using 'oc attach sshclient -c sshclient -i -t' command when the pod is running
pod "sshclient" deleted

A third way to connect to the VM is to use the virtctl console. You can compile your own virtctl as was described in Part 9. To simplify, we copy virtctl arm64 binary from prebuilt container image to /usr/local/bin on the Raspberry Pi 4 and connect to the VMI using “virtctl console” command.

id=$(podman create
podman cp $id:_out/cmd/virtctl/virtctl /usr/local/bin
podman rm -v $id
virtctl console vmi-fedora


[root@rpi vmi]# id=$(podman create
Trying to pull
Getting image source signatures
Copying blob 7065f6098427 done
Copying config 1c7a5aa443 done
Writing manifest to image destination
Storing signatures
[root@rpi vmi]# podman cp $id:_out/cmd/virtctl/virtctl /usr/local/bin
[root@rpi vmi]# podman rm -v $id
[root@rpi vmi]# virtctl console vmi-fedora
Successfully connected to vmi-fedora console. The escape sequence is ^]

vmi-fedora login: fedora
Failed Units: 1
[fedora@vmi-fedora ~]$ ping -c 2
PING ( 56(84) bytes of data.
64 bytes from ( icmp_seq=1 ttl=116 time=5.41 ms
64 bytes from ( icmp_seq=2 ttl=116 time=3.98 ms

--- ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 3.979/4.695/5.411/0.716 ms
[fedora@vmi-fedora ~]$ # Ctrl-] to detach
[root@rpi vmi]#

When done, we can delete the VMI

oc delete -f vmi-fedora.yaml

We can run other VM and VMI samples for alpine, cirros and fedora images as in Part 9. When done, you may delete kubevirt operator

oc delete -f${LATEST}/kubevirt-cr-arm64.yaml
oc delete -f${LATEST}/kubevirt-operator-arm64.yaml

5. Run a jupyter notebook sample for license plate recognition (RPi with 8GB RAM)

We will run the sample described at the Red Hat OpenShift Data Science Workshop License plate recognition. The Dockerfile uses the arm64 Jupyter Notebook base image: scipy-notebook. Since we do not have a tensorflow arm64 image, we install it as described at Qengineering. The notebook.yaml downloads the licence-plate-workshop sample in an initContainer.

cd ~
git clone
cd ~/microshift/raspberry-pi/tensorflow-notebook
oc apply -f notebook.yaml 
oc -n default wait pod notebook --for condition=Ready --timeout=600s
oc get routes


[root@rpi tensorflow-notebook]# oc get routes
NAME             HOST/PORT                              PATH   SERVICES       PORT   TERMINATION   WILDCARD
flask-route      flask-route-default.cluster.local             notebook-svc   5000                 None
notebook-route   notebook-route-default.cluster.local          notebook-svc   5001                 None

The image is large, it may take a while for image to be downloaded:

[root@rpi tensorflow-notebook]# crictl images | grep tensorflow-notebook             arm64                           c8da62870fec2       4.73GB

If running in the all-in-one microshift container, you need to run the command within the container

[root@rpi tensorflow-notebook]# # podman exec -it microshift crictl images | grep tensorflow-notebook # All in one

Add the ipaddress of the Raspberry Pi 4 device for notebook-route-default.cluster.local to /etc/hosts on your Laptop and browse to http://notebook-route-default.cluster.local/tree?. Login with the default password mysecretpassword. Go the work folder and select and run the License-plate-recognition notebook at http://notebook-route-default.cluster.local/notebooks/work/02_Licence-plate-recognition.ipynb

We can also run it as an application and test it using the corresponding notebooks. Run the http://notebook-route-default.cluster.local/notebooks/work/03_LPR_run_application.ipynb

Wait for the following to appear.

Instructions for updating:
non-resource variables are not supported in the long term
Model Loaded successfully...
Model Loaded successfully...
[INFO] Model loaded successfully...
[INFO] Labels loaded successfully...

Then run http://notebook-route-default.cluster.local/notebooks/work/04_LPR_test_application.ipynb

We can experiment with a custom image. Let’s download the image to the pod and run the cells again with the new image and check the prediction.

oc exec -it notebook -- bash -c "wget \"\" -O /tmp/3183KND.jpg"

Then, run the http://notebook-route-default.cluster.local/notebooks/work/05_Send_image.ipynb

Add the cell with the following code:

my_image = ''
from PIL import Image
import requests
from io import BytesIO

response = requests.get(my_image)
img = BytesIO(response.content).read()
import base64
import requests
from json import dumps
encoded_image = base64.b64encode(img).decode('utf-8')
content = {"image": encoded_image}
json_data = dumps(content)
headers = {"Content-Type" : "application/json"}
r = + '/predictions', data=json_data, headers=headers)
from IPython.display import Image
from IPython.core.display import HTML 

When we are done working with the license plate recognition sample notebook, we can delete it as follows:

oc delete -f notebook.yaml

6. Run a jupyter notebook sample for object detection

We will run the sample described at the Red Hat OpenShift Data Science Workshop Object Detection. We use the same container image as in previous Sample 5, the only change is to download the object detection sample in object-detection-rest.yaml from object-detection-rest.git.

cd ~
git clone
cd ~/microshift/raspberry-pi/tensorflow-notebook
oc apply -f object-detection-rest.yaml 
oc -n default wait pod notebook --for condition=Ready --timeout=300s
oc get routes

Output will look the same as in Sample 5; we use the same service and route names.

[root@rpi tensorflow-notebook]# oc apply -f object-detection-rest.yaml
pod/notebook created
service/flask-svc created
service/notebook-svc created created created
[root@rpi tensorflow-notebook]# oc get routes
NAME             HOST/PORT                              PATH   SERVICES       PORT   TERMINATION   WILDCARD
flask-route      flask-route-default.cluster.local             notebook-svc   5000                 None
notebook-route   notebook-route-default.cluster.local          notebook-svc   5001                 None

Login at http://notebook-route-default.cluster.local/tree/work with the default password mysecretpassword. We can run the 1_explore.ipynb that will download twodogs.jpg and use a pre-trained model to identify objects in images. In the next notebooks (2_predict.ipynb, 3_run_flask.ipynb, and 4_test_flask.ipynb), this model is wrapped in a flask app that can be used as part of a larger application.

In 4_test_flask.ipynb, replace the my_route as follows:

my_route = 'http://flask-svc:5000'

We can also test by downloading custom images, for example from Dogs Best Life.

oc exec -it notebook -- bash -c "wget -O /home/jovyan/work/two-dogs-same-litter-min.jpeg"

In 4_test_flask.ipynb, replace the my_image and run the notebook.

my_image = 'two-dogs-same-litter-min.jpeg'
Two dogs object detection

When we are done working with the object detection sample notebook, we can delete it as follows:

oc delete -f object-detection-rest.yaml

7. Tutorial Notebooks from

We can run the tutorials from using the tutorials.yaml. We use the same container image as in previous Sample 5, the only change is that it pulls notebooks from You will need to make a few minor changes to use the /tmp to download temporary file and cache folders used in the notebooks to avoid permission denied errors because the local directory is not writable.

[root@rpi vmi]# cd ~/microshift/raspberry-pi/tensorflow-notebook
[root@rpi tensorflow-notebook]# oc apply -f tutorials.yaml
pod/notebook created
service/flask-svc created
service/notebook-svc created created created
[root@rpi tensorflow-notebook]# oc get routes notebook-route
NAME             HOST/PORT                              PATH   SERVICES       PORT   TERMINATION   WILDCARD
notebook-route   notebook-route-default.cluster.local          notebook-svc   5001                 None 

1. Tensorflow 2 quickstart beginner http://notebook-route-default.cluster.local/notebooks/work/site/en/tutorials/quickstart/beginner.ipynb

2. TensorFlow 2 quickstart for experts http://notebook-route-default.cluster.local/notebooks/work/site/en/tutorials/quickstart/advanced.ipynb

3. Segmentation http://notebook-route-default.cluster.local/notebooks/work/site/en/tutorials/images/segmentation.ipynb


4. Classification of flowers that shows overfitting, data augmentation for generating additional training data http://notebook-route-default.cluster.local/notebooks/work/site/en/tutorials/images/classification.ipynb


5. Audio recognition: Recognizing keywords http://notebook-route-default.cluster.local/notebooks/work/site/en/tutorials/audio/simple_audio.ipynb

Audio Recognition
Recognizing Keywords

6. Time series forecasting - It builds a few different styles of models including Convolutional and Recurrent Neural Networks (CNNs and RNNs) http://notebook-route-default.cluster.local/notebooks/work/site/en/tutorials/structured_data/time_series.ipynb

Cleanup MicroShift

We can use the script available on github to cleanup the pods and images. If you already cloned the microshift repo from github, you have the script in the ~/microshift/hack directory.

cd ~/microshift/hack

Containerized MicroShift on Manjaro (64 bit)

We can run MicroShift within containers in two ways:

  1. MicroShift Containerized – The MicroShift binary runs in a Podman container, CRI-O Systemd service runs directly on the host and data is stored in a podman volume
  2. MicroShift Containerized All-In-One – The MicroShift binary and CRI-O service run within a container and data is stored in a podman volume, microshift-data. This should be used for “Testing and Development” only

Microshift Containerized

If you did not already install podman, you can do it now.

pacman -S podman

We will use a new microshift.service that runs microshift in a pod using the prebuilt image and uses a podman volume. Rest of the pods run using crio on the host.

cat << EOF > /usr/lib/systemd/system/microshift.service
Description=MicroShift Containerized
Documentation=man:podman-generate-systemd(1) crio.service crio.service

ExecStartPre=/usr/bin/mkdir -p /var/lib/kubelet ; /usr/bin/mkdir -p /var/hpvolumes
ExecStartPre=/bin/rm -f %t/%n.ctr-id
ExecStart=/bin/podman run \
  --cidfile=%t/%n.ctr-id \
  --cgroups=no-conmon \
  --rm \
  --replace \
  --sdnotify=container \
  --label io.containers.autoupdate=registry \
  --network=host \
  --privileged \
  -d \
  --name microshift \
  -v /var/hpvolumes:/var/hpvolumes:z,rw,rshared \
  -v /var/run/crio/crio.sock:/var/run/crio/crio.sock:rw,rshared \
  -v microshift-data:/var/lib/microshift:rw,rshared \
  -v /var/lib/kubelet:/var/lib/kubelet:z,rw,rshared \
  -v /var/log:/var/log \
  -v /etc:/etc
ExecStop=/bin/podman stop --ignore --cidfile=%t/%n.ctr-id
ExecStopPost=/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id


systemctl daemon-reload
systemctl enable --now crio microshift
podman ps -a
podman volume inspect microshift-data # Get the Mountpoint where kubeconfig is located
export KUBECONFIG=/var/lib/containers/storage/volumes/microshift-data/_data/resources/kubeadmin/kubeconfig
watch "oc get nodes;oc get pods -A;crictl pods;crictl images;podman ps"


NAME              STATUS   ROLES    AGE     VERSION   Ready    <none>   2m45s   v1.21.0
NAMESPACE                       NAME                                  READY   STATUS    RESTARTS   AGE
kube-system                     kube-flannel-ds-pgkmc                 1/1     Running   0          2m45s
kubevirt-hostpath-provisioner   kubevirt-hostpath-provisioner-vf9ch   1/1     Running   0          2m35s
openshift-dns                   dns-default-hzqff                     2/2     Running   0          2m45s
openshift-dns                   node-resolver-gct24                   1/1     Running   0          2m45s
openshift-ingress               router-default-85bcfdd948-bdb7p       1/1     Running   0          2m48s
openshift-service-ca            service-ca-7764c85869-ss4jx           1/1     Running   0          2m50s
POD ID              CREATED              STATE               NAME                                  NAMESPACE                       ATTEMPT
cfb2ca0152657       28 seconds ago       Ready               dns-default-hzqff                     openshift-dns                   0
70a054df7ac5f       About a minute ago   Ready               router-default-85bcfdd948-bdb7p       openshift-ingress               0
2fa32698761df       2 minutes ago        Ready               service-ca-7764c85869-ss4jx           openshift-service-ca            0
4d5ec69042155       2 minutes ago        Ready               kubevirt-hostpath-provisioner-vf9ch   kubevirt-hostpath-provisioner   0
3a3dfcafc14df       2 minutes ago        Ready               kube-flannel-ds-pgkmc                 kube-system                     0
be5fcdba15e33       2 minutes ago        Ready               node-resolver-gct24                   openshift-dns                   0
IMAGE                                     TAG                             IMAGE ID            SIZE                          3.6                             7d46a07936af9       492kB                    4.8.0-0.okd-2021-10-10-030117   33a276ba2a973       205MB                4.8.0-0.okd-2021-10-10-030117   67a95c8f15902       265MB            4.8.0-0.okd-2021-10-10-030117   0e66d6f50c694       8.78MB                4.8.0-0.okd-2021-10-10-030117   85fc911ceba5a       68.1MB         4.8.0-0.okd-2021-10-10-030117   37292c44812e7       225MB   4.8.0-0.okd-2021-10-10-030117   fdef3dc1264ad       39.3MB        4.8.0-0.okd-2021-10-10-030117   7f149e453e908       41.5MB             latest                          bdccb7de6c282       406MB    4.8.0-0.okd-2021-10-10-030117   0d3ab44356260       276MB
CONTAINER ID  IMAGE                                 COMMAND     CREATED        STATUS            PORTS       NAMES
37391110a83f  run         4 minutes ago  Up 4 minutes ago              microshift

Now that microshift is started, we can run the samples shown earlier.

After we are done, we can stop microshift

systemctl stop microshift
podman volume rm microshift-data

Alternatively, delete the microshift container. The --rm we used in the podman run will delete the container when we stop it.

podman stop microshift && podman volume rm microshift-data

After it is stopped, we can run the to delete the pods and images from crio.

MicroShift Containerized All-In-One

Let’s stop the crio on the host, we will be creating an all-in-one container in podman that will run crio within the container.

systemctl stop crio
systemctl disable crio
mkdir /var/hpvolumes

We will run the all-in-one microshift in podman using prebuilt images (replace the image in the podman run command below with the latest image).

podman volume rm microshift-data;podman volume create microshift-data
podman run -d --rm --name microshift -h --privileged -v /lib/modules:/lib/modules -v microshift-data:/var/lib -v /var/hpvolumes:/var/hpvolumes -p 6443:6443 -p 8080:8080 -p 80:80

Now that you know the podman command to start the microshift all-in-one, you may alternatively use the following microshift service.

wget -O /usr/lib/systemd/system/microshift.service
# Add the “-p 80:80” after the “-p 6443:6443” so we can expose the applications
# Add the “-h”


cat << EOF > /usr/lib/systemd/system/microshift.service
Description=MicroShift all-in-one

ExecStartPre=/bin/rm -f %t/%n.ctr-id
ExecStart=/usr/bin/podman run --cidfile=%t/%n.ctr-id --sdnotify=conmon --cgroups=no-conmon --rm --replace -d --name microshift -h --privileged -v /sys/fs/cgroup:/sys/fs/cgroup:ro -v microshift-data:/var/lib -v /var/hpvolumes:/var/hpvolumes -v /lib/modules:/lib/modules --label io.containers.autoupdate=registry -p 6443:6443 -p 80:80
ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id
ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id


Then run:

systemctl daemon-reload
systemctl start microshift

On the host Raspberry Pi 4, we set KUBECONFIG to point to the kubeconfig on the data volume at the Mountpoint from above.

export KUBECONFIG=/var/lib/containers/storage/volumes/microshift-data/_data/microshift/resources/kubeadmin/kubeconfig
# crio on host is stopped, so we do not run crictl commands on host
watch "oc get nodes;oc get pods -A;podman exec -it microshift crictl ps -a"


1a0e4de49f0c   Ready    <none>   3m24s   v1.21.0
NAMESPACE                       NAME                                  READY   STATUS    RESTARTS   AGE
kube-system                     kube-flannel-ds-x2gnq                 1/1     Running   0          3m23s
kubevirt-hostpath-provisioner   kubevirt-hostpath-provisioner-72bj5   1/1     Running   0          2m33s
openshift-dns                   dns-default-rnprr                     2/2     Running   0          3m24s
openshift-dns                   node-resolver-cmjqw                   1/1     Running   0          3m24s
openshift-ingress               router-default-85bcfdd948-d2fkn       1/1     Running   0          3m27s
openshift-service-ca            service-ca-7764c85869-vcv55           1/1     Running   0          3m29s
CONTAINER           IMAGE                                                                                                             CREATED
    STATE               NAME                            ATTEMPT             POD IDD
66e45f02e2a40        48 seconds ago
    Running             kube-rbac-proxy                 0                   188183a0488679
6c0dcd12de6a0                55 seconds ago
    Running             dns                             0                   188183a0488675
bf2677b89d554         56 seconds ago
    Running             router                          0                   e18109aeeb3edb
28ab2b3973ad3   About a minute ag
o   Running             kubevirt-hostpath-provisioner   0                   25c8df5729dbd7
fac5960bf4739    2 minutes ago
    Running             service-ca-controller           0                   4b3c3fab45afcb
dac23f94cfd67       85fc911ceba5a5a5e43a7c613738b2d6c0a14dad541b1577cdc6f921c16f5b75                                                  2 minutes ago
    Running             kube-flannel                    0                   67b1085d9bec9b
8f5dc2a9657b5                    2 minutes ago
    Running             dns-node-resolver               0                   f3da680ae7071
5795218a71896                2 minutes ago
    Exited              install-cni                     0                   67b1085d9bec9
098607fa50de6            3 minutes ago
    Exited              install-cni-bin                 0                   67b1085d9bec9

The crio service is stopped on the Raspberry Pi, so crictl command will not work directly on the Pi. The crictl commands will work within the microshift container in podman as shown in the watch command above.

Now, we can run the samples shown earlier. To run the Virtual Machine examples in the all-in-one MicroShift, we need to execute the mount with --make-shared as follows in the microshift container to prevent the “Error: path "/var/run/kubevirt" is mounted on "/" but it is not a shared mount” event from virt-handler.

podman exec -it microshift mount --make-shared /

We may also preload the virtual machine images using "crictl pull".

podman exec -it microshift crictl pull

For the Virtual Machine Instance Sample 4, we can connect to the vmi-fedora by exposing the ssh port for the Virtual Machine Instance as a NodePort Service after the instance is started. This NodePort is within the all-in-one pod that is running in podman. The ip address of the all-in-one microshift podman container is We expose the target port 22 on the VM as a service on port 22 that is in turn exposed on the microshift container with allocated port 30339 as seen below. We run and exec into a new pod called ssh-proxy, install the openssh-client on the ssh-proxy and ssh to the port 30339 on the all-in-one microshift container. This takes us to the VMI port 22 as shown below:

[root@rpi vmi]# oc get vmi,pods
NAME                                            AGE     PHASE     IP           NODENAME       READY   3m17s   Running   1a0e4de49f0c   True

NAME                                 READY   STATUS    RESTARTS   AGE
pod/virt-launcher-vmi-fedora-kp8zz   2/2     Running   0          3m16s
[root@rpi vmi]# virtctl expose vmi vmi-fedora --port=22 --target-port=22 --name=vmi-fedora-ssh --type=NodePort
Service vmi-fedora-ssh successfully exposed for vmi vmi-fedora
[root@rpi vmi]# oc get svc vmi-fedora-ssh
NAME             TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
vmi-fedora-ssh   NodePort   <none>        22:32462/TCP   12s
[root@rpi vmi]# podman inspect --format "{{.NetworkSettings.IPAddress}}" microshift
[root@rpi vmi]# oc run -i --tty ssh-proxy --rm --image=karve/alpine-sshclient:arm64 --restart=Never -- /bin/sh -c "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null fedora@ -p 32462"
If you don't see a command prompt, try pressing enter.

[fedora@vmi-fedora ~]$ sudo dnf install -y qemu-guest-agent >/dev/null
[fedora@vmi-fedora ~]$ sudo systemctl enable --now qemu-guest-agent
[fedora@vmi-fedora ~]$ ping -c 2
PING ( 56(84) bytes of data.
64 bytes from ( icmp_seq=1 ttl=115 time=6.14 ms
64 bytes from ( icmp_seq=2 ttl=115 time=6.08 ms

--- ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 6.083/6.111/6.139/0.028 ms
[fedora@vmi-fedora ~]$ exit
Connection to closed.
pod "ssh-proxy" deleted

The QEMU guest agent that we installed is a daemon that runs on the virtual machine and passes information to the host about the virtual machine, users, file systems, and secondary networks.

After we are done, we can delete the all-in-one microshift container.

podman rm -f microshift && podman volume rm microshift-data

or if started using systemd, then

systemctl stop microshift

Error Messages in MicroShift Logs

Message: failed to fetch hugetlb info

The following journalctl command will continuously show warning messages “failed to fetch hugetlb info”. The default kernel for the Raspberry Pi OS does not support HugeTLB hugepages.

journalctl -u microshift -f


May 23 11:49:20 microshift[3112]: W0523 11:49:20.347604    3112 container.go:586] Failed to update stats for container "/system.slice/crio-00c0b63eeee509979e7652cca8b91a1e9e900c1989b7fe54f6a05f6591de0108.scope": error while statting cgroup v2: [open /sys/kernel/mm/hugepages: no such file or directory
May 23 11:49:20 microshift[3112]: failed to fetch hugetlb info

To remove these messages, we can recompile the microshift binary using the changes from hugetlb.go.

pacman -S pkg-config

# Install golang
rm -rf /usr/local/go && tar -C /usr/local -xzf go1.17.2.linux-arm64.tar.gz
rm -f go1.17.2.linux-arm64.tar.gz
export PATH=$PATH:/usr/local/go/bin
export GOPATH=/root/go
cat << EOF >> /root/.bashrc
export PATH=$PATH:/usr/local/go/bin
export GOPATH=/root/go
mkdir $GOPATH

git clone
cd microshift

Edit the file vendor/ and remove the return on err.

func statHugeTlb(dirPath string, stats *cgroups.Stats) error {
        hugePageSizes, _ := cgroups.GetHugePageSize()
        //hugePageSizes, err := cgroups.GetHugePageSize()
        //if err != nil {
        //        return errors.Wrap(err, "failed to fetch hugetlb info")
        hugetlbStats := cgroups.HugetlbStats{}

Build and replace the microshift binary. Restart MicroShift.

mv microshift /usr/local/bin/.
systemctl restart microshift


In this Part 18, we saw multiple options to run MicroShift on the Raspberry Pi 4 with the Manjaro (64 bit). We used dynamic persistent volumes to install InfluxDB/Telegraf/Grafana with a dashboard to show SenseHat sensor data. We ran samples that used the Sense Hat/USB camera and worked with a sample that sent the pictures and web socket messages to Node Red when a person was detected. We installed the OKD Web Console and saw how to connect to a Virtual Machine Instance using KubeVirt on MicroShift with Manjaro. Finally, we saw how to run jupyter notebooks with the license plate recognition, object detection, image segmentation, image classification and audio keyword recognition. We will return to Manjaro in Part 24 where we will build and install the Kata Containers Runtime. In the next Part 19, we will work on Kali Linux and in Part 20 with ArchLinux.

Hope you have enjoyed the article. Share your thoughts in the comments or engage in the conversation with me on Twitter @aakarve. I look forward to hearing about your use of MicroShift on ARM devices and if you would like to see something covered in more detail.