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
97 lines
5.4 KiB
Bash
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
|