# LXD on Ubuntu 26.04 – Kubernetes 1.33 From Scratch

_Preseed • ZFS • k8sbr0 • k8s-base profile • k8master + k8worker1_

## 1. Architecture Overview

Network topology (matches reference diagram):
Internet → VirtualBox NAT (10.0.2.15) → Ubuntu 26 Host → LXD bridge k8sbr0 (192.168.100.1/24, DHCP+NAT) → k8master (192.168.100.10) + k8worker1 (192.168.100.11).
Single reusable profile (k8s-base), single bridge (k8sbr0), single ZFS storage pool (default).
One common node-install script, one rebuild script, one validation script. Snapshots after each milestone.

## 2. Host Prerequisites (Ubuntu 26.04)

```bash
# Update host
sudo apt update && sudo apt -y full-upgrade
sudo apt -y install zfsutils-linux bridge-utils curl jq snapd

# Kernel modules required by Kubernetes + LXD
sudo tee /etc/modules-load.d/k8s.conf <<'EOF'
overlay
br_netfilter
ip_tables
ip6_tables
nf_nat
netlink_diag
EOF
sudo modprobe overlay br_netfilter ip_tables ip6_tables nf_nat netlink_diag

# Sysctls required by kubeadm
sudo tee /etc/sysctl.d/99-k8s.conf <<'EOF'
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
EOF
sudo sysctl --system

# Disable swap (kubelet requirement)
sudo swapoff -a
sudo sed -i.bak '/ swap / s/^/#/' /etc/fstab
```

## 3. Install LXD 5.21 LTS (Snap)

```bash
sudo snap install lxd --channel=5.21/stable
sudo usermod -aG lxd $USER
newgrp lxd
lxd --version   # expect 5.21.x
```

## 4. Preseed: lxd-preseed.yaml

```yaml
config:
  core.https_address: 192.168.50.40:8443
  core.trust_password: true

networks:
  - name: k8sbr0
    type: bridge
    description: Kubernetes bridge
    config:
      ipv4.address: 192.168.100.1/24
      ipv4.nat: "true"
      ipv4.dhcp: "true"
      ipv6.address: none

storage_pools:
  - name: default
    driver: zfs
    config:
      source: /dev/sdc1     # dedicated disk/partition for ZFS

profiles:
  - name: default
    description: Default LXD profile
    devices:
      root:
        type: disk
        pool: default
        path: /
      eth0:
        type: nic
        network: k8sbr0

  - name: k8s-base
    description: Reusable Kubernetes node profile
    config:
      limits.memory: 4GB
      limits.cpu: "2"
      limits.memory.swap: "false"
      security.privileged: "true"
      security.nesting: "true"
      linux.kernel_modules: ip_tables,ip6_tables,nf_nat,overlay,br_netfilter,netlink_diag
      raw.lxc: |
        lxc.apparmor.profile=unconfined
        lxc.cgroup2.devices.allow=a
        lxc.cap.drop=
        lxc.mount.auto=proc:rw sys:rw cgroup:rw
    devices:
      root:
        type: disk
        pool: default
        path: /
      eth0:
        type: nic
        network: k8sbr0
      kmsg:
        type: unix-char
        source: /dev/kmsg
        path: /dev/kmsg

projects: []
cluster: null
```

## 5. Initialize LXD

```bash
cat lxd-preseed.yaml | sudo lxd init --preseed

# Verify
lxc storage list        # default (zfs)
lxc network list        # k8sbr0
lxc profile list        # default, k8s-base
```

## 6. DHCP Reservations (static IPs)

```bash
# Reserve IPs on k8sbr0 so containers always come up on the same address
lxc network set k8sbr0 ipv4.dhcp.ranges 192.168.100.100-192.168.100.200

# Pin master + worker via dnsmasq hosts file (LXD-managed)
sudo tee /var/snap/lxd/common/lxd/networks/k8sbr0/dnsmasq.hosts/k8master <<'EOF'
k8master,192.168.100.10
EOF
sudo tee /var/snap/lxd/common/lxd/networks/k8sbr0/dnsmasq.hosts/k8worker1 <<'EOF'
k8worker1,192.168.100.11
EOF
sudo systemctl reload snap.lxd.daemon || sudo snap restart lxd
```

## 7. Launch Containers

```bash
lxc launch ubuntu:24.04 k8master  --profile default --profile k8s-base
lxc launch ubuntu:24.04 k8worker1 --profile default --profile k8s-base

# Force static IPs on eth0 as an override
lxc config device override k8master  eth0 ipv4.address=192.168.100.10
lxc config device override k8worker1 eth0 ipv4.address=192.168.100.11

lxc restart k8master k8worker1
lxc list
```

## 8. Common Node Install Script (scripts/install-node.sh)

```bash
#!/usr/bin/env bash
set -euo pipefail

K8S_VER="1.33"

apt-get update
apt-get -y install apt-transport-https ca-certificates curl gpg

# containerd
apt-get -y install containerd
mkdir -p /etc/containerd
containerd config default | tee /etc/containerd/config.toml >/dev/null
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
systemctl restart containerd && systemctl enable containerd

# Kubernetes 1.33 apt repo
curl -fsSL https://pkgs.k8s.io/core:/stable:/v${K8S_VER}/deb/Release.key \
  | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] \
https://pkgs.k8s.io/core:/stable:/v${K8S_VER}/deb/ /" \
  > /etc/apt/sources.list.d/kubernetes.list

apt-get update
apt-get -y install kubelet kubeadm kubectl
apt-mark hold kubelet kubeadm kubectl

swapoff -a
modprobe br_netfilter overlay
echo '1' > /proc/sys/net/ipv4/ip_forward

systemctl enable --now kubelet
```

## 9. Push & Run on Each Container

```bash
for c in k8master k8worker1; do
  lxc file push scripts/install-node.sh $c/root/install-node.sh --mode=0755
  lxc exec $c -- bash /root/install-node.sh
  lxc snapshot $c post-node-install
done
```

## 10. Bootstrap Kubernetes 1.33

```bash
# On k8master
lxc exec k8master -- kubeadm init \
  --apiserver-advertise-address=192.168.100.10 \
  --pod-network-cidr=10.244.0.0/16 \
  --kubernetes-version=1.33.0 \
  --ignore-preflight-errors=all

lxc exec k8master -- bash -c 'mkdir -p /root/.kube && cp /etc/kubernetes/admin.conf /root/.kube/config'

# CNI (Flannel – simplest in LXD)
lxc exec k8master -- kubectl apply -f \
  https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml

# Join worker
JOIN=$(lxc exec k8master -- kubeadm token create --print-join-command)
lxc exec k8worker1 -- bash -c "$JOIN --ignore-preflight-errors=all"

lxc snapshot k8master post-kubeadm-init
lxc snapshot k8worker1 post-kubeadm-join
```

## 11. Rebuild Script (scripts/rebuild.sh)

```bash
#!/usr/bin/env bash
set -euo pipefail
for c in k8worker1 k8master; do
  lxc delete -f $c || true
done
lxc launch ubuntu:24.04 k8master  --profile default --profile k8s-base
lxc launch ubuntu:24.04 k8worker1 --profile default --profile k8s-base
lxc config device override k8master  eth0 ipv4.address=192.168.100.10
lxc config device override k8worker1 eth0 ipv4.address=192.168.100.11
lxc restart k8master k8worker1
echo 'Now run scripts/install-node.sh + kubeadm bootstrap.'
```

## 12. Validation Script (scripts/validate.sh)

```bash
#!/usr/bin/env bash
set -euo pipefail
echo '--- LXD ---'         ; lxd --version
echo '--- Storage ---'     ; lxc storage list
echo '--- Network ---'     ; lxc network list ; lxc network show k8sbr0
echo '--- Profiles ---'    ; lxc profile list
echo '--- Containers ---'  ; lxc list
echo '--- Nodes ---'       ; lxc exec k8master -- kubectl get nodes -o wide
echo '--- System pods ---' ; lxc exec k8master -- kubectl get pods -A
```

## 13. Snapshots (per milestone)

```bash
lxc snapshot k8master  base-image
lxc snapshot k8worker1 base-image
# after install-node.sh  -> post-node-install
# after kubeadm init/join -> post-kubeadm-init / post-kubeadm-join
# after CNI ready         -> post-cni-ready
lxc snapshot k8master  post-cni-ready

# Restore any time:
# lxc restore k8master post-node-install
```

## 14. Complete Undo / Uninstall Procedure

```bash
# 1. Stop & delete containers
for c in k8master k8worker1; do
  lxc stop $c --force 2>/dev/null || true
  lxc delete $c --force 2>/dev/null || true
done

# 2. Delete profile, network, storage pool
lxc profile delete k8s-base 2>/dev/null || true
lxc network delete k8sbr0 2>/dev/null || true
lxc storage delete default 2>/dev/null || true

# 3. Remove LXD snap (also removes all remaining data)
sudo snap remove --purge lxd
sudo snap remove --purge lxd-installer 2>/dev/null || true

# 4. Purge residual dirs
sudo rm -rf /var/snap/lxd /var/lib/lxd /etc/subuid /etc/subgid
sudo deluser --remove-home lxd 2>/dev/null || true
sudo delgroup lxd 2>/dev/null || true

# 5. Destroy the ZFS pool that backed LXD (DATA LOSS)
sudo zpool list
sudo zpool destroy default 2>/dev/null || true
sudo wipefs -a /dev/sdc1

# 6. Remove the bridge if leftover
sudo ip link delete k8sbr0 2>/dev/null || true

# 7. Remove kernel-module & sysctl drop-ins
sudo rm -f /etc/modules-load.d/k8s.conf /etc/sysctl.d/99-k8s.conf
sudo sysctl --system

# 8. Optional: remove ZFS + bridge utils
sudo apt -y purge zfsutils-linux bridge-utils
sudo apt -y autoremove --purge

# 9. Verify clean state
which lxc lxd || echo 'LXD fully removed'
ip a | grep k8sbr0 || echo 'Bridge gone'
zpool list || true
```

## 15. Reference Materials

PDF / Docs:
  • Canonical LXD 5.21 LTS docs (PDF export): https://documentation.ubuntu.com/lxd/en/latest-5.21/
  • Kubernetes 1.33 official docs: https://kubernetes.io/docs/
  • kubeadm cluster guide (PDF via browser Print → PDF): https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/
  • Ubuntu 26.04 release notes: https://discourse.ubuntu.com/c/release/

PPT / Slide decks:
  • CNCF Kubernetes 101 slides: https://github.com/cncf/presentations/tree/main/kubernetes
  • Canonical LXD deck (SlideShare): https://www.slideshare.net/Canonical/lxd

GitHub repos:
  • canonical/lxd — https://github.com/canonical/lxd
  • kubernetes/kubeadm — https://github.com/kubernetes/kubeadm
  • flannel-io/flannel — https://github.com/flannel-io/flannel
  • kelseyhightower/kubernetes-the-hard-way — https://github.com/kelseyhightower/kubernetes-the-hard-way
  • justmeandopensource/kubernetes (LXD labs) — https://github.com/justmeandopensource/kubernetes

YouTube:
  • "Kubernetes cluster using LXD containers" – Venkat Nagappan / justmeandopensource: https://www.youtube.com/watch?v=iSisDN9WVw0
  • "LXD Crash Course" – Stgraber: https://www.youtube.com/@stgraber
  • TechWorld with Nana – Kubernetes full course: https://www.youtube.com/watch?v=X48VuDVv0do
  • KodeKloud – Kubernetes for Beginners: https://www.youtube.com/watch?v=l_lWfipUimk
