kind ships no Gateway API CRDs. Set C's Q74 creates a GatewayClass at seed
time and assumed they were already there; the only code that installed them
was Q56's setup and the retired Set A Gateway task. On a cluster rebuilt by
./setup.sh, `./exam sim c` therefore failed to seed Q74 ("no matches for
kind GatewayClass") and the task could not be done as written.
Both setups now call ensure_gateway_crds, a PRELUDE helper next to
ensure_etcd_tools. Verified on a throwaway cluster with the CRDs deleted:
Q74 and Q56 each seed cleanly, grade 0 unsolved and full when solved.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mhxm4rsUfuE5HoytYbu2YW
Task ids used to jump 1-43 / 101-124 / 401-418 / 501-517. They now run
contiguously in the same order:
1-43 unchanged (base and extra)
44-67 hard pool (was 101-124)
68-85 Simulator Set C (was 401-418)
86-102 Simulator Set D (was 501-517)
Every reference moved with its task: ids, solution keys, workspace paths
({WS}/qNN and the graders' hidden .qNN-* files), temp paths, the one
hardcoded id in code (Q56's derived canary weight), and Q-number
cross-references in the harness, README and the explainer. Each task's
paths were checked to carry only its own new number. History entries and
the local session state were remapped so past attempts stay attached to the
same tasks; retired set A/B ids keep their old numbers and match nothing.
weak-spots is regenerated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mhxm4rsUfuE5HoytYbu2YW
Removes tracks/cka/bank_sim_a.py and bank_sim_b.py (34 tasks, ids 201-217 and
301-317). The previous commit holds their last versions, with every fix from
the audit applied and both sets verified end to end - restore with
`git show HEAD~1:tracks/cka/bank_sim_a.py` if one is ever wanted back.
The baseline keeps repairing what those sets could leave on a cluster that
ran them (the maintenance taint, cordons, Q317's controller flag, Q314's static
pod) - only its comments changed. `breaks` keys that existed solely to keep
pool tasks apart from set A/B tasks are gone. History keeps its raw entries;
weak-spots is regenerated without the retired tasks. A mistyped drill argument
now gets a usage message instead of a traceback.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
Remaining findings from the repo audit (1, 2 and 4 landed in df04c56).
restore_baseline now undoes two more leaks, both of which cost real debugging
time this week:
- apiserver (Q123 renames the command to "kube-apiserverr"). This runs FIRST
and over podman/ssh rather than kubectl, because it is exactly the failure
that makes kubectl unusable - every later step in the block depends on the
API being up. It waits for /readyz before continuing.
- staticPodPath (Q103 relocates it to /etc/kubernetes/static on the tier=edge
node). The existing kubelet repair only fired when
/var/lib/kubelet/config.yaml.orig existed, and nothing ever creates that
backup, so a relocated path survived every later session and silently broke
Q05, whose check hardcodes /etc/kubernetes/manifests. Normalised
unconditionally, restarting the kubelet only when the file actually changed.
Verified against the live cluster: with both breaks applied the API was
unreachable, and one restore_baseline run brought all four nodes Ready, restored
the manifest, reset staticPodPath and let the kubelet re-adopt the Q05 mirror
pod. Re-running takes 1.1s versus 14.6s when repairing, confirming it is
idempotent and does not restart anything it has not changed.
etcd (Q102) and certs (Q122) are deliberately left alone, now documented in the
block: neither setup damages anything, so any damage comes from a half-finished
candidate attempt, and "mid-attempt" is indistinguishable from "done correctly"
without guessing - guessing wrong rewrites etcd's data-dir or re-issues certs on
a healthy cluster. proxy-rules (Q302) needs no repair at all; its "breaks" key
exists for no_conflicts() bookkeeping, not because it touches shared state.
Also:
- S[30], S[42], S[108] referenced params that do not exist (%{svc}/%{app},
%{np}/%{port}, %{rev}), so they rendered placeholders verbatim. Rewritten
against the real task text - S[42] was missing the DNS egress rule entirely
and S[108] stopped before the scale/pause/set-image steps, so both would
have failed 2 of 3 checks if followed literally. A param-reference scan over
the whole bank is now clean.
- Q124's check and S[124] used the deprecated v1 Endpoints API; switched to
EndpointSlice, verified both forms return 10.89.1.3:6443 on this cluster.
- q_by_id printed "Valid: 1-101" for non-contiguous ids; it now derives the
real ranges (1-43, 101-124, 201-217, 301-317).
- Untracked workspace/q03. Both q03 and q21 setups start with rm -rf and
rewrite every file from heredocs, so they are generated output, not seed
templates: tracking them only produced phantom diffs, since a re-seed
reverted edits to base/ and deleted anything left in overlays/. With nothing
tracked there, teardown.sh can go back to wiping workspace/ wholesale.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fqcio9TXkEXH35UHebTgoL
Both checks ran `nslookup kubernetes.default` inside a busybox:1.36 pod and
required it to succeed. busybox's nslookup does not apply the pod's resolv.conf
`search` list - it queries the name literally - so that lookup returns NXDOMAIN
no matter how correctly CoreDNS is configured. Verified in-cluster: from a pod
with a correct `search default.svc.cluster.local svc.cluster.local cluster.local`
and a healthy Corefile, `kubernetes`, `kubernetes.default` and
`kubernetes.default.svc` all return NXDOMAIN while the full
`kubernetes.default.svc.cluster.local` resolves to 10.96.0.1.
The result was a task that could never score more than 3/6: the local drill
history shows this check failing on 4 of 4 attempts with the Corefile fully
repaired.
Use the FQDN in both checks, and say so in the task text so the candidate
verifies the same way the grader does. Q216 in bank_sim_a already used an FQDN;
these two were the outliers.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fqcio9TXkEXH35UHebTgoL
Three fixes from an audit of the repo:
1. setup_contexts() deleted all three k8s-c?-? contexts and recreated none
unless the session was started with --hard. But './exam drill <ids>' and
'./exam drill weak' don't filter by difficulty, so hard tasks land in
hard=False sessions - and 24 of them open with "kubectl config use-context
...", which then fails outright. Q121 was unpassable that way: it says to
deploy into the context's namespace without -n, and its check requires that
namespace, so the context was the only way to learn the name. Contexts are
now created whenever a task in the session references one.
Also fixes how k8s-c2-AC's namespace was chosen: it took the first ns in the
whole session, which is arbitrary when several tasks share a context. A
context has one default namespace, so it belongs to the task that actually
reads it - Q121, now marked "ctx_ns": True.
2. restore_baseline() cleared taint keys dedicated/workload/team, but Q107
draws from pool/lane and Q307 pins maintenance=inprogress, and nothing
uncordoned a node. A leaked cordon or taint silently fails every later task
that counts pods per node (Q26/Q211 DaemonSet coverage, Q212's spread) -
W1 had been cordoned and tainted across sessions for two days. Clears all
six keys now and uncordons all three workers.
3. ttyd was launched with -W (writable) and no interface binding, so it listened
on 0.0.0.0 while the API server correctly bound localhost. That exposed an
unauthenticated root-equivalent shell with the cluster kubeconfig to the whole
network. Pinned to loopback, overridable via CKA_TTYD_INTERFACE.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fqcio9TXkEXH35UHebTgoL
Close two of the four CKA-relevant gaps identified against Killercoda's
scenario catalog:
- Q123 (TSHOOT, hard): kube-apiserver itself crash-loops on a static-pod
manifest typo, same pattern as the existing scheduler/controller-manager
failures (Q20/Q118) but with the added twist that kubectl is completely
unusable until it's fixed, forcing node-level diagnosis (crictl,
journalctl).
- Q124 (ARCH, hard): produce a real node-join handoff file by
independently computing the CA discovery hash kubeadm join verifies
against, and finding the API server's actual in-cluster address rather
than the local kubeconfig's port-forwarded one.
Cluster-upgrade and operator-authoring remain unimplemented and are
documented as genuine infrastructure gaps in CKA_SOLUTIONS_EXPLAINED.md
(kind's node images have no apt-installable multi-version kubelet/kubeadm,
and operator authoring needs a real built/pushed image) rather than faked.
Also updates the Killercoda cross-reference table with the gaps closed,
and logs an ended drill session to history-cka.jsonl/weak-spots-cka.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The Q02/Q102 seeds tried to copy etcdctl out of the running etcd pod with
`kubectl exec -- cat`, which cannot work: registry.k8s.io/etcd is distroless
(no cat, no sh, no tar). The copy failed, stderr went to /dev/null and seeding
still printed a green tick, so the snapshot task had no etcdctl on the node.
Q102 was worse: it also ran `exec -- sh -c 'etcdctl ... snapshot save'` inside
that container, so its /opt/cka/backup.db was never created at all.
- etcd-tools.sh installs both binaries on the control-plane node, taking them
from the etcd image already in the node's containerd (offline, exactly the
server version), falling back to the upstream release tarball, then apt.
- setup.sh runs it at bootstrap; ensure_etcd_tools in the exam prelude installs
them through CKA_NODE_ACCESS when a task is seeded, so ssh/vagrant clusters
get them too, and both etcd setups now fail with a reason instead of hiding it.
- Q102 takes its snapshot with the node's etcdctl; Q02 no longer documents a
kubectl cp fallback that cannot work on a distroless image.
Co-authored-by: Junie <junie@jetbrains.com>