kubestronaut-sim/images/vm/guest/provision.sh
vumas3r f3d0e536b6 Exam simulator for CKA, CKAD and CKS with per-task hosts and a remote desktop
kubestronaut-sim runs each exam task on a host of its own - a kind (kubeadm)
cluster in rootless podman, or a QEMU/KVM Ubuntu VM for the CKS tasks that
need their own kernel (Falco, AppArmor) - and puts a browser desktop in front
of them, the way killer.sh and sailor.sh do.

- ksim, sim/: the engine (sessions, drills, study mode, grading, coverage,
  audit), host and VM lifecycle, the desktop
- images/: exam host images (common, CKS variant with its tools, VM guest)
- cka/ (131 tasks), ckad/ (105), cks/ (110): task banks, curricula with
  study notes, reference solutions and scripted solutions

Every domain of every track has at least 17 pool tasks. `./ksim audit` proves
a task three ways: it seeds cleanly on its own host, scores nothing before
anyone has worked on it, and reaches full marks when its scripted solution is
run on the host as `candidate`. All 346 tasks pass (CKA audited 2026-10-13,
CKAD and CKS 2026-10-12).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZPd3GGWyxTgAkL6a4gAJz
2026-10-13 16:29:33 -06:00

97 lines
5.4 KiB
Bash

#!/bin/bash
# Build-time provisioning of the VM exam host. Runs once, inside the VM, as the first-boot script of
# the Ubuntu cloud image (cloud-init user-data), and powers the machine off when it is through; what
# is on the disk then is the base disk every VM exam host starts from.
#
# The result: Ubuntu with containerd, kubeadm, kubelet and kubectl as apt packages (held), the
# control-plane images pulled, Falco with the modern eBPF probe, the AppArmor tools, and `candidate`
# with passwordless sudo. No cluster yet: every host makes its own on its first boot
# (guest/firstboot.sh), under its own name.
exec > >(tee -a /var/log/ksim-provision.log /dev/ttyS0) 2>&1
set -euxo pipefail
trap 'echo KSIM-PROVISION-FAILED line $LINENO; sync; poweroff -f' ERR
export DEBIAN_FRONTEND=noninteractive
modprobe 9p; modprobe 9pnet_virtio
mkdir -p /mnt/build && mount -t 9p -o trans=virtio,version=9p2000.L,ro build /mnt/build
. /mnt/build/env # K8S_VERSION=1.35.8 K8S_MINOR=v1.35
# --- nothing that works behind the candidate's back
systemctl disable --now unattended-upgrades.service apt-daily.timer apt-daily-upgrade.timer 2>/dev/null || true
apt-get purge -y snapd unattended-upgrades >/dev/null 2>&1 || true
# --- kernel settings Kubernetes wants, and the modules the host needs at every boot
printf 'overlay\nbr_netfilter\n9p\n9pnet_virtio\n' > /etc/modules-load.d/ksim.conf
modprobe overlay; modprobe br_netfilter
printf 'net.bridge.bridge-nf-call-iptables = 1\nnet.bridge.bridge-nf-call-ip6tables = 1\nnet.ipv4.ip_forward = 1\n' > /etc/sysctl.d/99-kubernetes.conf
sysctl --system >/dev/null
swapoff -a; sed -i '/\sswap\s/d' /etc/fstab
# --- packages: Kubernetes and Falco from their own repositories
mkdir -p /etc/apt/keyrings
curl -fsSL "https://pkgs.k8s.io/core:/stable:/$K8S_MINOR/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:/$K8S_MINOR/deb/ /" > /etc/apt/sources.list.d/kubernetes.list
curl -fsSL https://falco.org/repo/falcosecurity-packages.asc | gpg --dearmor -o /usr/share/keyrings/falco-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/falco-archive-keyring.gpg] https://download.falco.org/packages/deb stable main" > /etc/apt/sources.list.d/falcosecurity.list
apt-get update
apt-get install -y --no-install-recommends containerd apparmor-utils jq vim tmux bash-completion conntrack socat strace \
"kubelet=$K8S_VERSION-*" "kubeadm=$K8S_VERSION-*" "kubectl=$K8S_VERSION-*"
apt-mark hold kubelet kubeadm kubectl
curl -fsSL -o /usr/local/bin/yq "https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64" && chmod 755 /usr/local/bin/yq
# --- containerd: systemd cgroups, and Docker Hub through the simulator's cache
mkdir -p /etc/containerd/certs.d/docker.io
containerd config default > /etc/containerd/config.toml
# (containerd 1.x writes the empty registry path as "", 2.x as '' - the first one is the CRI's)
sed -i "s/SystemdCgroup = false/SystemdCgroup = true/; 0,/config_path = \\(\"\"\\|''\\)/s##config_path = \"\/etc\/containerd\/certs.d\"#" /etc/containerd/config.toml
grep -q 'SystemdCgroup = true' /etc/containerd/config.toml && grep -q 'config_path = "/etc/containerd/certs.d"' /etc/containerd/config.toml
install -m644 /mnt/build/hosts.toml /etc/containerd/certs.d/docker.io/hosts.toml
systemctl restart containerd
printf 'runtime-endpoint: unix:///run/containerd/containerd.sock\nimage-endpoint: unix:///run/containerd/containerd.sock\n' > /etc/crictl.yaml
kubeadm config images pull --kubernetes-version "v$K8S_VERSION"
for i in busybox:1.36 nginx:1.27; do crictl pull docker.io/library/$i || true; done
# --- Falco, with the modern eBPF probe (no kernel module, no compiler)
FALCO_FRONTEND=noninteractive FALCO_DRIVER_CHOICE=modern_ebpf apt-get install -y --no-install-recommends falco
systemctl enable falco-modern-bpf.service
falco --version | head -1
# --- the candidate, as on an exam host
userdel -r ubuntu 2>/dev/null || true
useradd -m -s /bin/bash -u 1000 candidate
echo 'candidate ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/candidate && chmod 440 /etc/sudoers.d/candidate
install -m644 /mnt/build/exam.bashrc /etc/exam.bashrc && echo '. /etc/exam.bashrc' >> /etc/bash.bashrc
for h in /etc/skel /root /home/candidate; do install -m644 /mnt/build/vimrc $h/.vimrc; touch $h/.hushlogin; done
chown candidate:candidate /home/candidate/.vimrc /home/candidate/.hushlogin
printf 'PasswordAuthentication no\nPermitRootLogin prohibit-password\nUseDNS no\nPrintMotd no\n' > /etc/ssh/sshd_config.d/00-exam.conf
chmod -x /etc/update-motd.d/* 2>/dev/null || true
# --- what every boot does: name, keys, /opt/course, and on the first one the cluster
install -m755 /mnt/build/firstboot.sh /usr/local/sbin/ksim-firstboot
cat > /etc/systemd/system/ksim-firstboot.service <<'EOF'
[Unit]
Description=ksim exam host: name, keys, /opt/course and the cluster
After=network-online.target containerd.service
Wants=network-online.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/sbin/ksim-firstboot
TimeoutStartSec=600
[Install]
WantedBy=multi-user.target
EOF
systemctl enable ksim-firstboot.service
mkdir -p /opt/course
# --- the image is done: cloud-init must not run again, and the disk should be small
touch /etc/cloud/cloud-init.disabled
apt-get clean
umount /mnt/build || true
fstrim -av || true
echo KSIM-PROVISION-OK
sync
poweroff