I tried to follow the documentation to run MicroK8s on LXD. However, it didn’t work because the microk8s nodes (really node since I was just trying on one system) wouldn’t start. I’m not sure of the root cause of that but it was vexing.

After a little bit of searching, I found an article on the Ubuntu blog that described running Apache Spark on MicroK8s on Ubuntu Core in the cloud. The use case is different, but the initial part of the configuration was to get MicroK8s running on LXD.

I adapted the instructions to map to my situation and got it working on my Ubuntu 20.04 desktop. First thing is to create a new profile for microk8s. I’m using ZFS on my system which is also what is used in the article, so that made it straightforward.

lxc profile create microk8s
cat > microk8s.profile <<EOF
config:
  boot.autostart: "true"
  linux.kernel_modules: ip_vs,ip_vs_rr,ip_vs_wrr,ip_vs_sh,ip_tables,ip6_tables,netlink_diag,nf_nat,overlay,br_netfilter
  raw.lxc: |
    lxc.apparmor.profile=unconfined
    lxc.mount.auto=proc:rw sys:rw cgroup:rw
    lxc.cgroup.devices.allow=a
    lxc.cap.drop=
  security.nesting: "true"
  security.privileged: "true"
  security.syscalls.intercept.bpf: "true"
  security.syscalls.intercept.bpf.devices: "true"
  security.syscalls.intercept.mknod: "true"
  security.syscalls.intercept.setxattr: "true"
description: ""
devices:
  aadisable:
    path: /sys/module/nf_conntrack/parameters/hashsize
    source: /sys/module/nf_conntrack/parameters/hashsize
    type: disk
  aadisable1:
    path: /sys/module/apparmor/parameters/enabled
    source: /dev/null
    type: disk
  aadisable2:
    path: /dev/zfs
    source: /dev/zfs
    type: disk
  aadisable3:
    path: /dev/kmsg
    source: /dev/kmsg
    type: disk
  aadisable4:
    path: /sys/fs/bpf
    source: /sys/fs/bpf
    type: disk
name: microk8s
used_by: []
EOF
cat microk8s.profile | lxc profile edit microk8s
rm microk8s.profile

Using that profile, I created a new container and installed microk8s:

lxc launch -p default -p microk8s ubuntu:20.04 microk8s
lxc exec microk8s -- sudo snap install microk8s --classic

There are a few additional configuration steps that need to take place on the container. Basically, this fixes a problem with apparmor and sets a couple of configuration variables.

lxc shell microk8s

cat > /etc/rc.local <<EOF
#!/bin/bash

apparmor_parser --replace /var/lib/snapd/apparmor/profiles/snap.microk8s.*
exit 0
EOF

chmod +x /etc/rc.local
systemctl restart rc-local

echo 'L /dev/kmsg - - - - /dev/null' > /etc/tmpfiles.d/kmsg.conf
echo '--conntrack-max-per-core=0' >> /var/snap/microk8s/current/args/kube-proxy
exit

After that, stop and restart the container. You can try restart, but I had it timeout.

lxc stop microk8s
lxc start microk8s

The last config was to turn the swap off in the container. I’m not sure if this is critical, but it makes sense to not swap in this circumstance.

lxc exec microk8s -- sudo swapoff -a

Now we are ready to test by deploying a small app to microk8s.

lxc exec microk8s -- sudo microk8s.kubectl create deployment microbot --image=dontrebootme/microbot:v1

To see the app outside the container, we expose the service.

lxc exec microk8s -- sudo microk8s.kubectl expose deployment microbot --type=NodePort --name=microbot-service --port=80

Finally, we can test it using curl.

MICROBOT_PORT=$(lxc exec microk8s -- sudo microk8s.kubectl get all | grep service/microbot | awk '{ print $5 }' | awk -F':' '{ print $2 }' | awk -F'/' '{ print $1 }')
MK8S_IP=$(lxc list microk8s | grep microk8s | awk -F'|' '{ print $4 }' | awk -F' ' '{ print $1 }')
curl http://$MK8S_IP:$MICROBOT_PORT/

If you get some results out of that, it all worked.