feat(doctor): surface the machine beneath the nodes (backend#2221) - #541
Draft
shujaatTracebloc wants to merge 3 commits into
Draft
feat(doctor): surface the machine beneath the nodes (backend#2221)#541shujaatTracebloc wants to merge 3 commits into
shujaatTracebloc wants to merge 3 commits into
Conversation
Every other check in this package reads the cluster and believes it. On a local
k3d install that belief is misplaced: the node containers are created with
`NanoCpus=0 CpuQuota=0 Memory=0`, so each one honestly reports the WHOLE Docker
VM and a default server+agent cluster tells Kubernetes the machine is twice its
real size. Measured on k3d v5.9.0 / k3s v1.35.5 / Docker 29.5.2:
host 10 cpu / 16.00 GiB
Docker VM 10 cpu / 7.75 GiB <- the real machine
2 node containers 15.50 GiB <- what Kubernetes believes
The node memory was byte-identical to the VM's MemTotal on BOTH nodes. So
checkNodeFit can truthfully say "a Ready node can schedule this job" for two
jobs that cannot both exist, and its drift nudge advertises half the VM twice.
Adds "Machine capacity": host -> VM -> node capacity -> unrequested, plus the
one invariant that makes the lie legible, sum(node capacity) <= VM capacity.
Uncapped k3d violates it by exactly the node count.
Verified against a LIVE 2-node edge, not just fakes:
WARN Machine capacity -- host 16.00 GiB -> Docker VM 7.75 GiB (10 cpu)
-> 2 nodes claiming 15.50 GiB -> 4.55 GiB unrequested -- Kubernetes
believes 2.00x the memory this machine has [...]
That live run also found a bug in this check that fakes could not: the
"unrequested" level was computed from the inflated SUM, so it reported 12.30 GiB
free on a 7.75 GiB VM -- the fourth level inheriting the third's error, this
check repeating the very lie it exists to expose. Now measured against
min(sum, VM), a no-op on an honest cluster. Regression-tested.
Refuses to assert what it cannot know, in four ways: a non-k3d or mixed cluster
gets StatusUnknown (on EKS the nodes ARE machines and `docker info` on this
laptop describes something unrelated); so does an unreadable VM, no Ready node,
and nodes reporting no capacity at all -- "0 GiB claimed, all good" would be a
green with nothing behind it. StatusUnknown carries no signal, so none of these
move the verdict or the exit code.
The tolerance is measured too. A node capped at 3 GiB (3221225472 B) reported
capacity 3221225Ki, 2.4% ABOVE the limit it came from, so a strict `sum > vm`
would call correct capping an over-commit. 1.05 is far below the 2.00x the real
bug produces and far above that rounding.
Probes are injectable (Options.VMProbe / HostProbe), so the chain is fully
exercisable without Docker. The host level is best-effort and OMITTED rather
than zero-filled when unreadable -- the VM is the constraint, the host is
context.
Mutation-tested: widening the tolerance, dropping the k3d guard, and counting
terminal pods as holding memory each redden their own tests.
Does not touch the installers, so it cannot conflict with the client-side work
on this ticket.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
Author
|
bugbot run |
The tolerance comment said node capacity came from "the cgroup limit with a
rounding that does not exactly invert". That is wrong about the mechanism, and
the mechanism matters for the rest of this ticket. Measured:
k3d --servers-memory 3g -> cgroup memory.max = 3221225472
k3d's fake /proc/meminfo -> MemTotal: 3221225 kB
kubelet capacity -> 3221225Ki == 3298534400 B, +2.4%
k3d caps a node by bind-mounting a SYNTHETIC /proc/meminfo into the node
container (a "fakeowner" mount), not by the cgroup -- kubelet never reads the
cgroup for capacity. k3d writes MemTotal as bytes/1000 labelled kB, but kB
there means 1024 bytes, so advertised capacity overstates the real cgroup limit
by 2.4%. The tolerance was right; the reason recorded beside it was not.
Two consequences worth having written down, both measured:
* capping is a CREATE-TIME operation. `docker update --memory=4g` on a
running node container set memory.max=4294967296 and left /proc/meminfo at
the VM's 8126672 kB, so capacity stayed 8126672Ki even after a restart.
Existing edges cannot be capped in place; they have to be recreated.
* a pod sized to the advertised capacity can exceed the node's real cgroup
limit by 2.4%. The 3 GiB platform overhead absorbs that at any realistic
cap, but it is a reason not to shrink the overhead casually.
Comment-only; no behaviour change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…r (Bugbot #541) Two real defects from Bugbot, both reproduced with a failing test first. 1. The probes could hang the sweep. hostProbe ran `sysctl` through a bare exec.Command with no context at all, and dockerVMProbe took the signal ctx with no deadline of its own -- unlike httpProbeTimeout (8s) on the other injectable probe in this package. The sharp edge is that the remedy for an unreadable VM is `docker info`, which is exactly what hangs against a WEDGED daemon as opposed to a stopped one, so the probe for that case could hang `tracebloc doctor` with no verdict and no exit code. Both are now bounded by machineProbeTimeout, mirroring httpProbeTimeout; the installer's own _docker_answers bounds `docker info` for the same reason. HostProbe takes a context now, which is also the honest signature for something that shells out. 2. k3d node NAMES are not proof the cluster is on this machine. A kubeconfig pointing at another host's k3d cluster -- a LAN address, an SSH tunnel, a copied config -- has the same k3d-* names while `docker info` here describes an unrelated VM. That is the same error the EKS guard already refuses, seen from a direction I missed. Now a PRESENT, non-loopback API endpoint is a disqualifier and the check returns StatusUnknown, reusing the isLoopback this package already had. An EMPTY ServerURL deliberately still measures. It is documented as optional throughout this package, so treating absence as "remote" would let a missing Option silence a check that has node-name evidence to go on. Pinned by its own test, and mutation-testing confirms tightening the guard to `!isLoopback(serverURL)` reddens exactly that test. Re-verified on the live 2-node edge afterwards: served on loopback, so it is still measured and still reports 2.00x. Mutation-tested: dropping the remote guard reddens the remote test and nothing else; tightening it to reject an empty URL reddens the empty-URL test. One new string catalogued in zz-all-strings.golden. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
Author
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 6ebe9ae. Configure here.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds a
Machine capacitycheck totracebloc doctor: the four-level chain host → Docker VM → node capacity → unrequested, plus the one invariant that makes the double-count legible:Uncapped k3d violates it by exactly the node count. This is the
doctorhalf of backend#2221 (RFC-BACKEND-664 §P4) and touches no installer, so it cannot conflict with the client-side work on the same ticket.Why
Every other check in this package reads the cluster and believes it. On a local k3d install that belief is misplaced — the node containers are created with
NanoCpus=0 CpuQuota=0 Memory=0, so each one honestly reports the whole Docker VM. Measured on k3d v5.9.0 / k3s v1.35.5 / Docker 29.5.2, macOS aarch64:8126672Kieach — byte-identical to the VM'sMemTotalSo
checkNodeFitcan truthfully report "a Ready node can schedule a training job" for two jobs that cannot both exist, and its drift nudge (this machine could give a run up to cpu=9,memory=4Gi) advertises half the VM twice. The gap between the levels is the customer's problem, and nothing surfaced it: doctor read level 3 only, the installer's preflight gates on level 1, and nothing looked at level 2 at all.Verified on a live cluster, not just fakes
Run against a real 2-node edge:
The two lines together tell the true story that neither tells alone. As a cross-check,
7.75 − 4.55 = 3.20 GiBrequested matches the ~3276 Mi the control plane is known to request.The live run found a bug in this check that the fakes could not. "Unrequested" was computed from the inflated sum, so it reported 12.30 GiB free on a 7.75 GiB VM — the fourth level inheriting the third's error, this check repeating the very lie it exists to expose. Now measured against
min(sum, VM), which is a no-op on an honest cluster. There is a named regression test for it.Refuses to assert what it cannot know
StatusUnknown(no signal — never moves the verdict or exit code) in four cases:docker infoon this laptop describes something unrelated; asserting from it would be worse than silenceThe host level is best-effort and omitted rather than zero-filled when unreadable: the VM is the constraint, the host is context.
The tolerance is measured, not picked
A node capped at 3 GiB (
3221225472B) reported capacity3221225Ki— 2.4% above the limit it came from. A strictsum > vmwould therefore report correct capping as over-commit.1.05sits far below the 2.00× the real bug produces and far above that rounding. There is a test for each side.Uses capacity, not allocatable, deliberately: the invariant is about what the nodes claim the machine is. Making
allocatablehonest is P3's kubelet-reservation question, and k3s sets no reservations, so on the clusters this applies to the two are byte-identical anyway (confirmed while measuring).Tests
internal/doctor/machine_test.go, 18 cases pinned to the measured numbers. Probes are injectable (Options.VMProbe/HostProbe) so the chain is fully exercisable without Docker.Mutation-tested — each of these reddens its own tests and nothing else:
1.05→100allK3dalways trueFull suite green.
gofmt -s,goimports -local,staticcheck -checks all,-ST1005,ineffassign,misspellall clean locally.TestRun_HealthyCluster's check count moves 9 → 10. Its fixture nodes are notk3d-named, so the new check reportsStatusUnknownthere — which the rollup ignores, so the healthy verdict is unaffected. That is the intended behaviour on a non-local cluster, and the comment says so rather than leaving it looking like a fixture accident.20 new strings are catalogued in
zz-all-strings.golden(regenerated, diff reviewed).Scope
Deliberately only the surfacing half. Capping the node containers and collapsing the topology is the client-installer half — it changes the default topology on every install, so it is flagged for @LukasWodka rather than landing on review alone. The contract arithmetic it will read is tracebloc/client-runtime#363.
Audit of the ticket's claims, including two corrections that changed the fix, is on backend#2221.
🤖 Generated with Claude Code
Note
Medium Risk
Diagnostic-only doctor check that shells out to
docker info/sysctland lists cluster-wide pods/nodes. It can change doctor output and warn on default local installs, but does not alter scheduling, auth, or installer topology.Overview
Adds a Machine capacity check to
tracebloc doctorso local k3d installs show the real host → Docker VM → node capacity → unrequested chain, instead of trusting Kubernetes’ inflated node memory.Uncapped multi-node k3d reports each node as the whole VM, so
checkNodeFitcan say a job fits when two such jobs would OOM the VM. The new check warns whensum(node capacity)exceeds VM size (with a 5% tolerance for k3d’s measured rounding) and remedies with single-node or--servers-memory/--agents-memory. “Unrequested” is bounded bymin(sum, VM)so free memory is not computed from the lie.It stays unknown (no verdict/exit-code impact) for non-k3d, mixed, remote k3d, missing VM/
docker info, or no Ready capacity. Host RAM is best-effort and omitted if unreadable. Probes are injectable and time-bounded so a wedged Docker daemon cannot hang the sweep.Reviewed by Cursor Bugbot for commit 6ebe9ae. Bugbot is set up for automated code reviews on this repo. Configure here.