kubestronaut-sim/images/node/Containerfile
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

41 lines
2.5 KiB
Docker

# Exam host: a kind node (kubeadm, kubelet, containerd, static-pod control plane) that you reach
# with `ssh <host>` as `candidate`, carrying the tools the real exam hosts have. Built once per
# Kubernetes minor by `./ksim build`; every question gets its own cluster from this image.
ARG BASE
ARG ETCD_IMAGE
FROM ${ETCD_IMAGE} AS etcd
FROM ${BASE}
# etcdctl/etcdutl at exactly the version of the cluster's etcd (the etcd image itself is distroless)
COPY --from=etcd /usr/local/bin/etcdctl /usr/local/bin/etcdutl /usr/local/bin/
RUN apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
openssh-server dbus sudo vim nano less jq tmux bash-completion curl wget ca-certificates \
iputils-ping dnsutils netcat-openbsd psmisc tree openssl git \
podman netavark aardvark-dns \
&& rm -rf /var/lib/apt/lists/* \
# Debian's podman ships a CNI config. The kubelet would take it for the cluster's pod network
# until kindnet has written its own, and the first Pods would land on podman's bridge.
&& rm -f /etc/cni/net.d/87-podman-bridge.conflist \
&& touch /etc/containers/nodocker
ARG HELM_VERSION
RUN arch="$(dpkg --print-architecture)" \
&& curl -fsSL "https://get.helm.sh/helm-${HELM_VERSION}-linux-${arch}.tar.gz" \
| tar -xz -C /usr/local/bin --strip-components=1 "linux-${arch}/helm" \
&& curl -fsSL -o /usr/local/bin/yq "https://github.com/mikefarah/yq/releases/latest/download/yq_linux_${arch}" \
&& chmod +x /usr/local/bin/yq
COPY rootfs/ /
# uid 1000, passwordless sudo: the exam gives you `candidate` and expects `sudo -i` for node work
RUN useradd -m -s /bin/bash -u 1000 candidate \
&& echo 'candidate ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/candidate && chmod 440 /etc/sudoers.d/candidate \
&& cp /etc/skel/.vimrc /root/.vimrc \
&& echo '. /etc/exam.bashrc' >> /etc/bash.bashrc \
&& systemctl enable ssh \
# registry mirrors are read from /etc/containerd/certs.d (see docker.io/hosts.toml there)
&& printf '\n[plugins."io.containerd.grpc.v1.cri".registry]\n config_path = "/etc/containerd/certs.d"\n' >> /etc/containerd/config.toml \
# Installing dbus stamps the image with a D-Bus machine id, and systemd-machine-id-setup (run by
# kind's entrypoint on every boot) copies that into /etc/machine-id instead of making a new one:
# every node would share one machine id, derive the same eth0 MAC from it, and be unreachable from
# its neighbours. Point it at /etc/machine-id, as a stock Debian does, so each node gets its own.
&& rm -f /var/lib/dbus/machine-id && ln -s /etc/machine-id /var/lib/dbus/machine-id