cka-sim/setup.sh
vumast3r 55ab18f814 fix: repair the CKA bank and harness after a live audit; add Simulator Set D
Every changed task was seeded, graded untouched, solved and re-graded on a
separate kind cluster. The four sim sets were rehearsed as full sittings (real
baseline, seed everything, solve in order, grade at the end): A 150/150,
B 176/176, C 163/163, D 122/122. Every random-pool task was either passed in a
real session or cycled the same way.

Graders that could never pass, or passed for free:
  Q306 (local-path cannot resize; now graded as an in-place resize), Q314 (GNU
  grep prefixed file names), Q305 (plain `get events` has no SOURCE column and
  events expire; now managedFields), Q410 (kindnet DOES enforce NetworkPolicy;
  the expected answer is now derived from the CNI), Q35, Q212 (missing `]`),
  Q101 (`can-i create pods/exec` asks about a pod NAMED exec; multi-type get),
  Q109 (an emptyDir over nginx's /var/log crash-loops it), Q40 (TTL was
  measured at grading time), Q121, Q106, Q104, Q41, Q26, Q403, Q409, Q417,
  Q113, Q22, Q24/Q108 (flaky right after a rollout), and free points on
  untouched Q24/Q102/Q108/Q119/Q122.

Reference solutions: S[43], S[107], S[121], S[106] rewritten; S[116] (a
Completed Job pod still pins its PVC), S[119] (a pods quota equal to replicas
deadlocks the rollout); DNS checks use FQDNs; the explainer and exam guide
are corrected (etcd restore is etcdutl's job on 3.6+).

Cross-session leaks: the pre-session baseline now also repairs Q114 (the
kube-system NetworkPolicy and CoreDNS replicas), Q117/Q39 kubelet CA paths,
Q317, kubelet drop-in binary paths, a moved scheduler manifest, leftover
static pods and the default StorageClass. Q115 releases its fixed PVs from
claims left by earlier sessions.

Conflicts: `breaks` may be a list and `solo` keeps Q102's etcd restore out of
mixed sessions. Q118, Q5/Q103, Q26 vs the W1 taints, set A 203/204/208/212 and
set B 305/309 no longer sabotage each other. Q317 now disables only CSR
signing (the old break killed the whole controller-manager). Q110/Q311 no
longer compare against the whole cluster at submit time. Two delete/recreate
races in setups are fixed.

Harness: the weak list clears after two clean passes in a row, weak entries
show the latest attempt's failures, domain scores use the exam's own tasks,
history entries can be marked synthetic (entry 11 is), the web UI lists
every sim set, `./exam grade` refuses mid-exam (--force), preflight checks W1.

Set D (`./exam sim d`): 17 original killer.sh-style tasks, ids 501-517,
122 points - control-plane inventory, event forensics with crictl, a broken
kubelet unit, kubelet certificates, manual scheduling with the scheduler
stopped, Helm housekeeping with a stuck release, an enforced AND-vs-OR
NetworkPolicy, dnsPolicy and access-mode troubleshooting, and more.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-22 21:59:35 -06:00

102 lines
4.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# Bootstraps the cka-sim kind cluster (1 control-plane + 3 workers) on podman and preloads images.
# Override with CONTAINER_RUNTIME=docker if you ever want docker instead.
set -euo pipefail
cd "$(dirname "$0")"
RT="${CONTAINER_RUNTIME:-podman}"
need() {
command -v "$1" >/dev/null 2>&1 || { echo "ERROR: '$1' is not installed. $2"; exit 1; }
}
need "$RT" "https://podman.io/docs/installation"
need kind "https://kind.sigs.k8s.io/docs/user/quick-start/#installation"
need kubectl "https://kubernetes.io/docs/tasks/tools/"
need python3 ""
if [ "$RT" = podman ]; then
export KIND_EXPERIMENTAL_PROVIDER=podman
podman info >/dev/null 2>&1 || { echo "ERROR: podman is not working (try: podman info)."; exit 1; }
# kind needs cgroup v2 with controller delegation when podman is rootless.
if [ "$(id -u)" != 0 ] && [ "$(podman info --format '{{.Host.CgroupsVersion}}' 2>/dev/null)" != "v2" ]; then
echo "ERROR: rootless podman needs cgroup v2 for kind. Either enable cgroup v2 or run setup as root (sudo -E ./setup.sh)."
exit 1
fi
# inotify limits are the #1 cause of pods stuck in ContainerCreating on multi-node kind
for k in fs.inotify.max_user_watches:524288 fs.inotify.max_user_instances:512; do
key=${k%%:*}; want=${k##*:}; have=$(sysctl -n "$key" 2>/dev/null || echo 0)
[ "$have" -ge "$want" ] || echo "warn: $key=$have (recommend >= $want: sudo sysctl $key=$want)"
done
else
docker info >/dev/null 2>&1 || { echo "ERROR: docker daemon is not running/reachable."; exit 1; }
fi
if kind get clusters 2>/dev/null | grep -qx cka-sim; then
echo "==> kind cluster 'cka-sim' already exists, reusing it."
else
echo "==> Creating kind cluster 'cka-sim' on $RT (1 control-plane + 3 workers)..."
kind create cluster --config kind-config.yaml ${KIND_NODE_IMAGE:+--image "$KIND_NODE_IMAGE"} --wait 180s
fi
kubectl config use-context kind-cka-sim >/dev/null
echo "==> Waiting for nodes..."
kubectl wait --for=condition=Ready nodes --all --timeout=180s
echo "==> Preloading images into the cluster (non-fatal if a pull fails)..."
# `kind load docker-image` shells out to the docker CLI internally no matter which
# provider the cluster uses, so it can't see images that only exist in podman's
# store. Save to a tar and use `kind load image-archive` instead, which is
# runtime-agnostic.
for img in nginx:1.25 nginx:1.27 busybox:1.36 redis:7-alpine; do
if "$RT" pull -q "docker.io/library/$img" >/dev/null 2>&1; then
tarfile="$(mktemp)"
if "$RT" save -o "$tarfile" "docker.io/library/$img" >/dev/null 2>&1 \
&& kind load image-archive --name cka-sim "$tarfile" >/dev/null 2>&1; then
echo " loaded $img"
else
echo " warn: could not load $img"
fi
rm -f "$tarfile"
else
echo " warn: could not pull $img (tasks will pull at runtime)"
fi
done
echo "==> Making sure the exam workdir exists on the control-plane node..."
"$RT" exec cka-sim-control-plane mkdir -p /opt/cka
# The real exam gives you vim (and less/jq) on every node; the kind node image is minimal, so
# install them once here. Skip with CKA_SKIP_NODE_TOOLS=1.
if [ -z "${CKA_SKIP_NODE_TOOLS:-}" ]; then
echo "==> Installing editors and tools on the nodes (vim, nano, less, jq)..."
for n in $(kind get nodes --name cka-sim); do
if "$RT" exec "$n" sh -c 'command -v vim >/dev/null'; then
echo " $n already has vim"
continue
fi
if "$RT" exec "$n" sh -c 'DEBIAN_FRONTEND=noninteractive apt-get update -qq >/dev/null 2>&1 && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq vim nano less jq >/dev/null 2>&1'; then
"$RT" exec "$n" sh -c 'printf "set tabstop=2\nset expandtab\nset shiftwidth=2\n" > /root/.vimrc'
echo " $n ok"
else
echo " warn: could not install tools on $n (no outbound network?). Fall back to:"
echo " $RT cp $n:/path/file /tmp/f && \$EDITOR /tmp/f && $RT cp /tmp/f $n:/path/file"
fi
done
fi
# The exam expects etcdctl/etcdutl on the control-plane node, like the real one. They cannot be
# copied out of the running etcd pod (that image is distroless), so install them properly.
echo "==> Installing etcdctl/etcdutl on the control-plane node..."
printf ' '
CONTAINER_RUNTIME="$RT" bash ./etcd-tools.sh || echo " warn: no etcd client on the control-plane node; Q02/Q102 will say so when they seed"
# remember the runtime so the CLI uses the matching exec command
mkdir -p .state && echo "$RT" > .state/runtime
chmod +x exam
echo
echo "Cluster ready. Nodes:"
kubectl get nodes -o wide
echo
echo "Next: ./exam web (browser UI) or ./exam drill (5 tasks from your least-drilled domain)"