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
58 lines
2.9 KiB
Bash
58 lines
2.9 KiB
Bash
#!/bin/bash
|
|
# Every boot of a VM exam host (ksim-firstboot.service). The hypervisor hands over two things
|
|
# in a small shared directory: the host's name and the simulator's public ssh key. On the first boot the node
|
|
# then becomes a single-node kubeadm cluster of its own, under that name - which is why the cluster
|
|
# is not baked into the base disk: its node name and certificates would be the same on every host.
|
|
exec >> /var/log/ksim-firstboot.log 2>&1
|
|
set -x
|
|
F=/run/ksim-host
|
|
mkdir -p $F
|
|
mountpoint -q $F || mount -t 9p -o trans=virtio,version=9p2000.L,ro ksim $F
|
|
|
|
name=$(tr -d ' \n' < $F/hostname 2>/dev/null)
|
|
if [ -n "$name" ] && [ "$(hostname)" != "$name" ]; then
|
|
hostnamectl set-hostname "$name"
|
|
grep -q "^127.0.1.1 " /etc/hosts && sed -i "s/^127.0.1.1 .*/127.0.1.1 $name/" /etc/hosts || echo "127.0.1.1 $name" >> /etc/hosts
|
|
fi
|
|
if [ -s $F/authorized_keys ]; then
|
|
for h in /root /home/candidate; do
|
|
install -d -m700 $h/.ssh && cat $F/authorized_keys > $h/.ssh/authorized_keys && chmod 600 $h/.ssh/authorized_keys
|
|
done
|
|
chown -R candidate:candidate /home/candidate/.ssh
|
|
fi
|
|
|
|
# the task's files: a directory of the simulator, shared into the VM
|
|
mkdir -p /opt/course
|
|
mountpoint -q /opt/course || mount -t 9p -o trans=virtio,version=9p2000.L,msize=262144 course /opt/course
|
|
|
|
[ -f /var/lib/ksim-ready ] && exit 0
|
|
|
|
kubeadm init --node-name "$(hostname)" --pod-network-cidr=10.244.0.0/16 --kubernetes-version "$(kubeadm version -o short)" \
|
|
--apiserver-cert-extra-sans=127.0.0.1,localhost,"$(hostname)" || { echo "kubeadm init failed"; exit 1; }
|
|
|
|
# Pod network: one node, so a bridge is all it takes - no CNI daemon to pull or wait for.
|
|
# (Nothing here enforces NetworkPolicy; tasks that need that run on the container hosts.)
|
|
mkdir -p /etc/cni/net.d
|
|
cat > /etc/cni/net.d/10-ksim.conflist <<'EOF'
|
|
{
|
|
"cniVersion": "1.0.0",
|
|
"name": "ksim",
|
|
"plugins": [
|
|
{"type": "bridge", "bridge": "cni0", "isGateway": true, "ipMasq": true, "hairpinMode": true,
|
|
"ipam": {"type": "host-local", "ranges": [[{"subnet": "10.244.0.0/24"}]], "routes": [{"dst": "0.0.0.0/0"}]}},
|
|
{"type": "portmap", "capabilities": {"portMappings": true}}
|
|
]
|
|
}
|
|
EOF
|
|
export KUBECONFIG=/etc/kubernetes/admin.conf
|
|
for i in $(seq 1 60); do kubectl get --raw=/readyz >/dev/null 2>&1 && break; sleep 2; done
|
|
kubectl taint nodes --all node-role.kubernetes.io/control-plane- || true
|
|
install -d -m700 /root/.kube && install -m600 /etc/kubernetes/admin.conf /root/.kube/config
|
|
install -d -m700 -o candidate -g candidate /home/candidate/.kube && install -m600 -o candidate -g candidate /etc/kubernetes/admin.conf /home/candidate/.kube/config
|
|
for i in $(seq 1 90); do
|
|
[ "$(kubectl get node "$(hostname)" -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null)" = True ] && break; sleep 2
|
|
done
|
|
# a task's Pods resolve names from their first second on: the host is ready when cluster DNS is
|
|
kubectl -n kube-system rollout status deploy coredns --timeout=180s || true
|
|
touch /var/lib/ksim-ready
|