55ab18f814
9 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 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> |
|||
| 01877f0ae1 |
fix: stop recording how often or how long a session was paused
Pausing is for life happening - a baby needing attention - not a metric worth
keeping, and the numbers were meaningless anyway: the last recorded attempt read
12 pauses / 90,755 seconds, almost all of it sessions left paused overnight.
Removed:
- the st["pauses"] counter in set_paused()
- "pauses" and "paused_seconds" from the history entry in record_attempt()
- "pauses" from the web API session payload
- the "· paused 3× (12:04)" segment on the results screen
Kept, because both are load-bearing rather than telemetry:
- paused_total, which remaining() adds back to the deadline so paused time is
not silently deducted from the clock, and which the web clock reads to do the
same countdown math
- paused_at, which is just "is the session paused right now" and drives the ⏸
indicator
Neither is written to history or surfaced in any report; they exist only for the
lifetime of a live session.
Note for anyone reading a long time_used later: it is computed as
ended_at - started_at - paused_total, and paused_total only accrues while
actually paused, so a session resumed and then left running overnight inflates
it. Attempt #11 logs 28:59:01 for that reason, not a timing bug.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NziZdvBvy7CpEAAbZNgp8i
|
|||
| 49fcb76166 |
fix: taint cleanup removed nothing, Q23 leaked a broken metrics-server, CKS lost 40% of its blueprint
Audit of the bank and harness after a 23% mock result. Two of these were
silently corrupting every practice session on this cluster.
restore_baseline:
- The w1-taint cleanup removed nothing at all. `kubectl taint node N a- b- c-`
aborts on the first key that is not present and applies none of the removals,
so with only `workload` set the command reported five "not found" lines,
exited 1, and left the taint in place - the precise failure the block's own
comment says it exists to prevent. W1 then rejected pods in every later
session. Now one call per key. Verified by re-seeding workload=gpu:NoSchedule
and confirming a single restore_baseline run clears it.
- metrics-server was never repaired. Q23 seeds its scenario by stripping
--kubelet-insecure-tls, but carried no "breaks" key, so nothing tracked it
and nothing undid it. On kind the kubelet serving certs have no IP SANs, so
without that flag metrics-server never goes Ready, v1beta1.metrics.k8s.io
has no endpoints, aggregated discovery goes stale, and EVERY namespace
deletion in the cluster hangs on NamespaceDeletionDiscoveryFailure - which
is where the ~40 stuck namespaces came from, plus a dead `kubectl top`.
Q23 now declares breaks: metrics-server and the baseline restores the flag.
Baseline runs before seeding, so a session containing Q23 re-breaks it a
moment later as intended. Verified by stripping the flag and confirming one
restore_baseline run puts it back.
Session composition:
- pick() dropped any domain whose tasks are all "hard" from a normal session.
CKS SUPPLY and RUNTIME hold one hard task each, so `./exam start` returned
6 of 9 tasks with 40% of the blueprint silently absent. It now tops up from
the hard pool when a domain's normal pool is short.
- compose_exam() iterated QUOTA in declaration order, so a one-task domain
lost every "breaks" tie to a bigger domain that claimed the shared slot
first: CKS RUNTIME (only Q9, apiserver-flag) was never drawn in ANY mode
because SETUP went first and took that slot with Q2. Now walks domains
scarcest-first, and warns on a shortfall instead of truncating in silence.
CKS now yields 8/9 covering all six domains, warning SETUP 1/2.
- session_questions() rebuilt the list by filtering QUESTIONS (sorted by id),
discarding the session's own order - so compose_exam's rng.shuffle never
reached the candidate and every exam arrived grouped by domain and
difficulty, base tasks then hard then sim sets. Fixed to preserve the stored
order, and cmd_drill now shuffles explicit-id drills after conflict
resolution, so which task gets dropped stays deterministic while the order
does not.
Bank correctness (verified by grading each reference solution):
- CKAD Q2 was unpassable, 5/8. Its init container waited on `until nslookup
backend`, but busybox nslookup exits 1 even on a successful lookup because
it walks the whole search list and the trailing NXDOMAINs set the status -
the loop never terminates. The premise was wrong too: a ClusterIP Service
resolves from creation, endpoints or not, so DNS could never have gated it.
Now waits on a real connection; 8/8, and the pod genuinely holds at Init:0/1
until the backend Deployment exists. Same bug class as
|
|||
| 111a6c63a0 |
fix: repair apiserver/staticPodPath leaks, 3 drifted solutions, workspace tracking
Remaining findings from the repo audit (1, 2 and 4 landed in
|
|||
| df04c56533 |
fix: create exam contexts when needed, widen baseline reset, bind ttyd to loopback
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 |
|||
| d755c4017c |
fix: reset control-plane/node state to baseline before every session
Disruptive tasks corrupt shared state (static pod manifests, kubelet config, CoreDNS) keyed by a "breaks" target, but a session's setup only touches the questions it actually contains. If a task isn't re-picked in a later session, its damage lingers indefinitely - e.g. Q39 wrecking W1's kubelet config persisted across two unrelated TSHOOT drills that never included Q39 again. Add restore_baseline(), run unconditionally in begin_session() before seeding, that repairs every known "breaks" target (scheduler, controller-manager, CoreDNS, all three worker kubelets, W1's demo taints) regardless of which questions are in the new session. Hard-mode- only breaks (etcd, certs, apiserver) aren't covered yet. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
|||
| 23a9774a9f |
fix: install etcdctl/etcdutl on the control-plane node for the etcd tasks
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> |
|||
| 40dca1dab1 |
fix: output single checkmark line when seeding scenarios
Co-authored-by: Junie <junie@jetbrains.com> |
|||
| dece60fd8a |
feat: add CKA/CKAD/CKS exam simulator and question banks
Co-authored-by: Junie <junie@jetbrains.com> |