Add lessons: untimed guided labs, one per curriculum objective, on a host of their own, in the browser and in the terminal. Each step has a check, a hint and its commands; a lesson writes no history or scores, only a done mark on its objective. CKAD 2.1-2.4 (24 steps) and all 27 CKA objectives (135 steps) have one, and `./ksim lesson audit` proves that every step fails, is done by its own commands, then passes. Add CKA tasks 132-147 (cka/bank_course.py) on what a full CKA course teaches beyond the bank: a real upgrade from v1.36, a drain under a PodDisruptionBudget, QoS and eviction, in-place resize, kube-proxy's chains, encryption at rest, an operator, and CSI expansion, snapshots and clones (ensure_csi_hostpath). Move CKA and CKS to Kubernetes v1.37 and rebuild the hosts on Ubuntu 24.04, with kubelet, kubeadm and kubectl as held packages from pkgs.k8s.io; the desktop runs Ubuntu too. Hosts gain man pages, hexdump and xxd. Fix CKS Q4, which gave 3 of 12 points untouched, and CKA Q4 and Q49, whose solutions raced their new CRD. Audited on v1.37: CKA 147/147, CKAD 105/105, CKS 109/110 (Q103, a Falco task, failed to seed once and awaits a re-audit); lessons: 135 of 135 CKA steps and 24 of 24 CKAD steps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
122 lines
7.7 KiB
Docker
122 lines
7.7 KiB
Docker
# Exam host: a kind node (kubeadm, kubelet, containerd, static-pod control plane) on Ubuntu 24.04,
|
|
# as the exam's hosts are, 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} AS kind
|
|
|
|
# kind's node image is Debian: kind's images/base/Dockerfile (v0.33.0) on debian:trixie-slim, plus
|
|
# Kubernetes. This is the same node on Ubuntu. kind's packages and systemd trimming are done here;
|
|
# what does not depend on the distribution is copied from kind's image: its scripts and units,
|
|
# containerd, runc, crictl, the CNI plugins and the preloaded images. Kubernetes itself comes from
|
|
# pkgs.k8s.io below, as on a node installed with kubeadm - and with it the kubernetes-cni package,
|
|
# whose plugins (the same release) take the place of kind's.
|
|
FROM docker.io/library/ubuntu:24.04
|
|
# Ubuntu's container image is minimized: no man pages, and `man` a stub. The exam hosts have them
|
|
# ("man and man pages for further documentation"), so they come back the way `unminimize` does it;
|
|
# docs and translations stay out. Its user `ubuntu` holds uid 1000, which is the candidate's.
|
|
RUN printf 'path-exclude=/usr/share/doc/*\npath-include=/usr/share/doc/*/copyright\npath-exclude=/usr/share/locale/*/LC_MESSAGES/*.mo\n' \
|
|
> /etc/dpkg/dpkg.cfg.d/excludes \
|
|
&& rm -f /usr/bin/man && dpkg-divert --quiet --remove --rename /usr/bin/man \
|
|
&& userdel -r ubuntu \
|
|
&& apt-get update \
|
|
&& dpkg -S /usr/share/man/ | sed 's|, |\n|g;s|: [^:]*$||' | DEBIAN_FRONTEND=noninteractive xargs apt-get install --reinstall -y -qq \
|
|
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
|
systemd \
|
|
conntrack iptables nftables iproute2 ethtool util-linux mount kmod \
|
|
libseccomp2 pigz fuse-overlayfs \
|
|
nfs-common open-iscsi \
|
|
e2fsprogs \
|
|
bash ca-certificates curl jq procps \
|
|
man-db manpages \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
COPY --from=kind /kind/ /kind/
|
|
COPY --from=kind /LICENSES/ /LICENSES/
|
|
COPY --from=kind /usr/local/bin/ /usr/local/bin/
|
|
COPY --from=kind /usr/local/sbin/runc /usr/local/sbin/
|
|
COPY --from=kind /opt/cni/ /opt/cni/
|
|
COPY --from=kind /etc/containerd/ /etc/containerd/
|
|
COPY --from=kind /etc/crictl.yaml /etc/
|
|
COPY --from=kind /etc/default/kubelet /etc/default/
|
|
COPY --from=kind /etc/sysctl.d/10-network-magic.conf /etc/sysctl.d/10-network-security.conf /etc/sysctl.d/
|
|
COPY --from=kind /etc/systemd/system/containerd.service /etc/systemd/system/containerd-fuse-overlayfs.service \
|
|
/etc/systemd/system/kubelet.service /etc/systemd/system/kubelet.slice /etc/systemd/system/undo-mount-hacks.service \
|
|
/etc/systemd/system/
|
|
COPY --from=kind /etc/systemd/system/kubelet.service.d/ /etc/systemd/system/kubelet.service.d/
|
|
COPY --from=kind /var/lib/containerd/ /var/lib/containerd/
|
|
RUN find /lib/systemd/system/sysinit.target.wants/ -name "systemd-tmpfiles-setup.service" -delete \
|
|
&& rm -f /lib/systemd/system/multi-user.target.wants/* \
|
|
&& rm -f /etc/systemd/system/*.wants/* \
|
|
&& rm -f /lib/systemd/system/local-fs.target.wants/* \
|
|
&& rm -f /lib/systemd/system/sockets.target.wants/*udev* \
|
|
&& rm -f /lib/systemd/system/sockets.target.wants/*initctl* \
|
|
&& rm -f /lib/systemd/system/basic.target.wants/* \
|
|
&& echo "ReadKMsg=no" >> /etc/systemd/journald.conf \
|
|
&& ln -sf /usr/lib/systemd/systemd /sbin/init \
|
|
&& systemctl enable kubelet.service containerd.service undo-mount-hacks.service \
|
|
&& systemctl mask systemd-binfmt.service \
|
|
&& mkdir -p /etc/kubernetes/manifests
|
|
ENV container=docker
|
|
STOPSIGNAL SIGRTMIN+3
|
|
ENTRYPOINT ["/usr/local/bin/entrypoint", "/sbin/init"]
|
|
|
|
# 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 gnupg \
|
|
iputils-ping dnsutils netcat-openbsd psmisc tree openssl git bsdextrautils xxd \
|
|
podman netavark aardvark-dns \
|
|
&& rm -rf /var/lib/apt/lists/* \
|
|
# A podman package may ship 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
|
|
|
|
# --- Kubernetes as packages -------------------------------------------------------------------
|
|
# kubelet, kubeadm and kubectl of the cluster's version from pkgs.k8s.io, held, with the repository
|
|
# of their minor - as on an exam node. An upgrade is then `apt-get install` and `kubeadm upgrade`,
|
|
# as on the day, and a task can hold a node back on an older release. kind's kubelet unit and
|
|
# drop-ins live in /etc/systemd and keep taking precedence over the package's.
|
|
ARG K8S_VERSION
|
|
ARG K8S_MINOR
|
|
RUN 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 \
|
|
# kubeadm refuses to upgrade a node without the cpuset cgroup controller, which a rootless
|
|
# container does not get. The packaged binary is diverted and a wrapper (rootfs/usr/bin/kubeadm)
|
|
# passes --ignore-preflight-errors=SystemVerification to `kubeadm upgrade`, so the candidate
|
|
# types the same commands as on a real node. The diversion survives package upgrades.
|
|
&& dpkg-divert --add --rename --divert /usr/bin/kubeadm.real /usr/bin/kubeadm \
|
|
&& apt-get update \
|
|
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends -o Dpkg::Options::=--force-confold \
|
|
"kubelet=${K8S_VERSION}-*" "kubeadm=${K8S_VERSION}-*" "kubectl=${K8S_VERSION}-*" \
|
|
&& apt-mark hold kubelet kubeadm kubectl \
|
|
&& rm -rf /var/lib/apt/lists/* \
|
|
&& test -x /usr/bin/kubeadm.real
|
|
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 chmod 755 /usr/bin/kubeadm \
|
|
&& 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 \
|
|
# sshd as a service of its own: Ubuntu 24.04 would start it from ssh.socket on the first
|
|
# connection, and a host that is up should be listening
|
|
&& (systemctl disable ssh.socket 2>/dev/null || true) && systemctl enable ssh.service \
|
|
# 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 Ubuntu does, so each node gets its own.
|
|
&& rm -f /var/lib/dbus/machine-id && ln -s /etc/machine-id /var/lib/dbus/machine-id
|