LXD Playground for Kubernetes

Use LXD system containers to build a playground for Kubernetes

Published on updated on

This article describes a way to build with LXD system containers usable to create Kubernetes servers and workers on a single host. I think that understanding the main steps is important not only for this task but also for future usages of LXD.

For a practical, scripted way, see the k-playground.sh script.

Test k0s and K3s using LXD shows a way to use this playground.

Using cloud-init allows for configuring users, IP addresses and SSH keys for those containers.

Here are the steps:

For tests and troubleshooting I have used the k0s and K3s lightweight Kubernetes distributions.

Also, check the Notes and Info and links sections.

Create a project

lxc project create "K8sPlay" \
  -c features.images=false \
  -c features.profiles=true

Create a dedicated network

lxc network create "K8sPlayNet" --type=bridge \
    ipv4.address="10.11.12.1/24" \
    ipv4.dhcp.ranges="10.11.12.64-10.11.12.127" \
    ipv4.nat=true \
    ipv6.address=none

Add devices

# the root disk
lxc profile device add default root disk \
    path=/ pool=default --project "K8sPlay"

# a network interface
lxc profile device add default eth0 nic \
    name=eth0 \
    network="K8sPlayNet" --project "K8sPlay"

# /dev/kmsg is needed for Kubelet from K8s and derivatives
lxc profile device add default kmsg unix-char \
    source="/dev/kmsg" path="/dev/kmsg" --project "K8sPlay"

Add the common cloud-init configuration

# br_netfilter is needed by K8s network components
lxc profile set default --project "K8sPlay" linux.kernel_modules=br_netfilter

# for K8s the containers must be privileged
lxc profile set default --project "K8sPlay" security.privileged true

# drop the security, the containers need more rights then usual
cat << EOF | lxc profile set default --project "K8sPlay" raw.lxc -
lxc.apparmor.profile = unconfined
lxc.cgroup.devices.allow = a
lxc.cap.drop =
lxc.mount.auto = cgroup:mixed proc:rw sys:mixed
lxc.mount.entry = /dev/kmsg dev/kmsg none defaults,bind,create=file
EOF

# these are basic, "standard", settings
cat << EOF | lxc profile set default --project "K8sPlay" cloud-init.user-data -
#cloud-config
package_upgrade: true
packages:
  - openssh-server
ssh_pwauth: false
users:
- name: default
  gecos: System administrator
  groups: adm,netdev,sudo
  sudo: ALL=(ALL) NOPASSWD:ALL
  shell: /bin/bash
  lock_passwd: true
  ssh_authorized_keys:
  - "ssh-ed25519 AAAA___replace_this_with_ypur_public_key___"
EOF

Profiles to set static IP addresses using cloud-init

for (( idx=0; idx<5; idx++ ))
do
    lxc profile create "ip$idx" --project "K8sPlay"

    cat << EOF | lxc profile set "ip$idx" --project "K8sPlay" cloud-init.network-config -
version: 1
config:
  - type: physical
    name: eth0
    subnets:
      - type: static
        ipv4: true
        address: "10.11.12.$(( 10 + idx ))"
        netmask: 255.255.255.0
        gateway: "10.11.12.1"
        control: auto
  - type: nameserver
    address: "10.11.12.1"
EOF
done

Create the containers

for (( idx=0; idx<5; idx++ ))
do
    lxc launch ubuntu-minimal:22.04 "sc$idx" \
        --project "K8sPlay" \
        --profile default \
        --profile "ip$idx"
done

Notes

/dev/kmsg

The whole /dev/kmsg trouble may be avoided by creating a link to /dev/console:

[[ -e /dev/kmsg ]] || ln -s /dev/console /dev/kmsg

preferably with cloud-init.

lxc.mount.auto

I’ve made a test with this setting and worked:

lxc.mount.auto = cgroup:mixed proc:rw sys:mixed

Of course, LXD documentation should be bookmarked.

I have sorted most of the settings and steps needed after many hours and tests … wish I have found, and read, these sooner: