kube: wait for longhorn instance-manager before declaring storage ready - #6259
kube: wait for longhorn instance-manager before declaring storage ready#6259eriknordmark wants to merge 2 commits into
Conversation
An EVE-k node reports cluster storage as ready while it still cannot attach any volume, so the first app deployed after a conversion or a fresh install sits in volume creation for minutes while its CDI upload pod repeats "volume ... is not ready for workloads". Readiness gated only on the Longhorn DaemonSets, but a volume's engine and replica processes run inside the per-node instance-manager pod, which is owned by an InstanceManager CR and so was never examined. That pod is typically still pulling its ~440 MB image when the DaemonSets go ready: 9m51s on one device here, and over twenty minutes on slower storage. Require a running InstanceManager for this node in both the pillar and the shell readiness paths. Longhorn creates the CR during node setup rather than on first volume request, so waiting on it cannot deadlock against a volume whose own creation is gated on storage readiness. Signed-off-by: eriknordmark <erik@zededa.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
e90abe6 to
80a52e4
Compare
An EVE-k node reports cluster storage as ready while it still cannot attach any volume, so the first app deployed after a conversion or a fresh install sits in volume creation for minutes while its CDI upload pod repeats "volume ... is not ready for workloads". Readiness gated only on the Longhorn DaemonSets, but a volume's engine and replica processes run inside the per-node instance-manager pod, which is owned by an InstanceManager CR and so was never examined. That pod is typically still pulling its ~440 MB image when the DaemonSets go ready: 9m51s on one device here, and over twenty minutes on slower storage. Require a running InstanceManager for this node in pillar's readiness path. Longhorn creates the CR during node setup rather than on first volume request, so waiting on it cannot deadlock against a volume whose own creation is gated on storage readiness. Two branch-local adjustments to the upstream change. The shell hunk is dropped: this branch carries the kube-init Go rewrite, which deletes longhorn-utils.sh and already gates on the instance-manager in its own readiness check. The two positive daemonset tests now stub the gate, since a fake clientset cannot supply the kubeconfig the real gate builds its Longhorn client from. Cherry-picked from lf-edge#6259. Signed-off-by: eriknordmark <erik@zededa.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An EVE-k node reports cluster storage as ready while it still cannot attach any volume, so the first app deployed after a conversion or a fresh install sits in volume creation for minutes while its CDI upload pod repeats "volume ... is not ready for workloads". Readiness gated only on the Longhorn DaemonSets, but a volume's engine and replica processes run inside the per-node instance-manager pod, which is owned by an InstanceManager CR and so was never examined. That pod is typically still pulling its ~440 MB image when the DaemonSets go ready: 9m51s on one device here, and over twenty minutes on slower storage. Require a running InstanceManager for this node in pillar's readiness path. Longhorn creates the CR during node setup rather than on first volume request, so waiting on it cannot deadlock against a volume whose own creation is gated on storage readiness. Two branch-local adjustments to the upstream change. The shell hunk is dropped: this branch carries the kube-init Go rewrite, which deletes longhorn-utils.sh and already gates on the instance-manager in its own readiness check. The two positive daemonset tests now stub the gate, since a fake clientset cannot supply the kubeconfig the real gate builds its Longhorn client from. Cherry-picked from lf-edge#6259. Signed-off-by: eriknordmark <erik@zededa.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #6259 +/- ##
==========================================
+ Coverage 24.13% 24.52% +0.38%
==========================================
Files 512 523 +11
Lines 93537 95281 +1744
==========================================
+ Hits 22575 23363 +788
- Misses 69177 69942 +765
- Partials 1785 1976 +191 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Marking this draft: as written it regresses the ReproductionSame host, same eden escripts, same topology; the only difference is this change.
No host suspend during either failing run, so the wall-clock step budgets were real. The other six legs ( The leg fails in the escript's "wait for volumemgr ready (incl Mechanism
nodeReadyErr = wait.PollImmediate(time.Second, time.Minute*20, func() (bool, error) {
...
if opts.WaitForLonghorn {
if err := checkLonghornReady(client, nodeName); err != nil {This change adds "a running Measured on the failing leg: A diagnostic trap worth flagging: on expiry the reported condition is whichever sub-check happened to be failing at that instant. The failing run recorded Intended fixRaise the ceiling, not the predicate. Reverting the instance-manager condition would restore the premature-ready bug of #6258, where storage reports ready while the pod that serves volumes is still downloading and every volume attach fails with I will push the timeout change and re-test |
Waiting for the instance-manager made an EVE-k node with two-disk ZFS storage fail to bring up cluster storage at all: the readiness poll bounds the node, kubevirt and Longhorn checks with one 20-minute deadline, and the instance-manager pod alone pulls a ~440 MB image that takes over twenty minutes on that topology. The poll expired, volumemgr never initialised, and every app volume stayed uncreatable -- worse than the premature-ready behaviour the instance-manager check exists to fix. Give the poll a longer deadline when the caller waits on Longhorn, and keep the original budget otherwise. The single-disk topologies pull the same image in under nine minutes and were never close to the limit; only the slowest storage layout crossed it. Signed-off-by: eriknordmark <erik@zededa.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Regression resolved — undrafting. What was wrongThe first commit added "a running MeasurementsSame host, same eden escripts, same
Per-iteration detail on the fixed build: the instance-manager reached For scale, the host used for these runs was slower than the one that produced the passing baseline ( Why widen the budget rather than drop the predicateReverting the instance-manager condition would restore the bug it exists to fix: storage reports ready while the pod that actually serves volumes is still downloading, so the first app's volume attach fails repeatedly with The 20-minute ceiling was already tight before this change; adding the slowest component to the predicate is what pushed it over. The widened budget applies only when the caller waits on Longhorn, so non-Longhorn callers keep the original bound. Diagnostic note for anyone reading a failure of this shapeOn expiry the recorded condition is whichever sub-check happened to be failing at that instant, which is misleading. One failing run reported Testing
|
An EVE-k node reports cluster storage as ready while it still cannot attach any volume, so the first app deployed after a conversion or a fresh install sits in volume creation for minutes while its CDI upload pod repeats "volume ... is not ready for workloads". Readiness gated only on the Longhorn DaemonSets, but a volume's engine and replica processes run inside the per-node instance-manager pod, which is owned by an InstanceManager CR and so was never examined. That pod is typically still pulling its ~440 MB image when the DaemonSets go ready: 9m51s on one device here, and over twenty minutes on slower storage. Require a running InstanceManager for this node in both the pillar and the shell readiness paths. Longhorn creates the CR during node setup rather than on first volume request, so waiting on it cannot deadlock against a volume whose own creation is gated on storage readiness. Cherry-picked from lf-edge#6259, with one branch-local adjustment: the two positive daemonset tests stub the instance-manager gate, since a fake clientset cannot supply the kubeconfig the real gate builds its Longhorn client from. Those tests arrive with lf-edge#6240, which this branch also carries, so the interaction only shows up here. Signed-off-by: eriknordmark <erik@zededa.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An EVE-k node reports cluster storage as ready while it still cannot attach any volume, so the first app deployed after a conversion or a fresh install sits in volume creation for minutes while its CDI upload pod repeats "volume ... is not ready for workloads". Readiness gated only on the Longhorn DaemonSets, but a volume's engine and replica processes run inside the per-node instance-manager pod, which is owned by an InstanceManager CR and so was never examined. That pod is typically still pulling its ~440 MB image when the DaemonSets go ready: 9m51s on one device here, and over twenty minutes on slower storage. Require a running InstanceManager for this node in both the pillar and the shell readiness paths. Longhorn creates the CR during node setup rather than on first volume request, so waiting on it cannot deadlock against a volume whose own creation is gated on storage readiness. Cherry-picked from lf-edge#6259, with one branch-local adjustment: the two positive daemonset tests stub the instance-manager gate, since a fake clientset cannot supply the kubeconfig the real gate builds its Longhorn client from. Those tests arrive with lf-edge#6240, which this branch also carries, so the interaction only shows up here. Signed-off-by: eriknordmark <erik@zededa.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Heads-up on an interaction with 6240 that neither PR sees on its own. #6240 adds No merge order is required — both are based on master and touch disjoint // stubInstanceManagerGate satisfies the instance-manager check for tests that
// exercise the daemonset logic: the real gate builds a Longhorn client from the
// on-device kubeconfig, which a fake clientset cannot supply.
func stubInstanceManagerGate(t *testing.T) {
saved := instanceManagerReady
t.Cleanup(func() { instanceManagerReady = saved })
instanceManagerReady = func(context.Context, string) error { return nil }
}called at the top of both tests (plus a Found on an integration branch carrying both PRs; with the stub applied, |
Record lf-edge#6259 and the two source PRs the table never listed (lf-edge#6240, lf-edge#6242), note that lf-edge#6063 is now 17 commits, and mark the CDI upload-pod teardown logging as upstream in master rather than branch-local. Add the lf-edge#6240/lf-edge#6259 test interaction and the rebase-due note: master is 63 commits ahead of the shared merge-base. Signed-off-by: eriknordmark <erik@zededa.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Record lf-edge#6259 and the two source PRs the table never listed (lf-edge#6240, lf-edge#6242), note that lf-edge#6063 is now 17 commits, and mark the CDI upload-pod teardown logging as a pre-merge copy of an upstream commit. Add the lf-edge#6240/lf-edge#6259 test interaction and the rebase-due note: master is 63 commits ahead of the shared merge-base. Signed-off-by: eriknordmark <erik@zededa.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Record lf-edge#6259, note that lf-edge#6063 is now 17 commits, refresh lf-edge#6240's head, and mark the CDI upload-pod teardown logging as a pre-merge copy of an upstream commit. Spell out why lf-edge#6242 is not replayed here and that its NAD-CRD race is still open in the Go ApplyMultusCNI. Add the lf-edge#6240/lf-edge#6259 test interaction and the rebase-due note: master is 63 commits ahead of the shared merge-base. Signed-off-by: eriknordmark <erik@zededa.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Record lf-edge#6259, note that lf-edge#6063 is now 17 commits, refresh lf-edge#6240's head, and mark the CDI upload-pod teardown logging as a pre-merge copy of an upstream commit. Spell out why lf-edge#6242 is not replayed here and that its NAD-CRD race is still open in the Go ApplyMultusCNI. Add the lf-edge#6240/lf-edge#6259 test interaction and the rebase-due note: master is 63 commits ahead of the shared merge-base. Signed-off-by: eriknordmark <erik@zededa.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Claude finished @rene's task in 1m 52s —— View job ReviewRead the two readiness paths (Go Two points posted inline:
Neither blocks merge. The Go state matrix is well covered; the |
| # running. It is owned by an InstanceManager CR rather than a DaemonSet, | ||
| # so the daemonset sweep above cannot observe it. | ||
| imState=$(kubectl -n longhorn-system get instancemanagers.longhorn.io -o json | jq -r --arg n "$node" '[.items[] | select(.spec.nodeID==$n) | .status.currentState] | index("running")') | ||
| if [ "$imState" = "null" ]; then |
There was a problem hiding this comment.
This gate fails open on a transient error. The check only rejects the literal string null; if the kubectl get instancemanagers call fails (API blip, apiserver restart), jq receives empty input and imState becomes the empty string "", which is != "null", so execution falls through and the node is declared ready.
Contrast with the DaemonSet check above ("$lhStatus" != "truetruetrue"), which fails closed — any non-exact value keeps waiting. For a readiness gate the safe default is to keep waiting unless a running instance-manager is positively observed. Consider requiring a numeric index instead:
if ! printf '%s' "$imState" | grep -qE '^[0-9]+$'; thenconfirmed: true
| // request, so waiting on it cannot deadlock against a volume whose own creation | ||
| // is gated on storage readiness. | ||
| func checkLonghornInstanceManagerReady(ctx context.Context, nodeName string) error { | ||
| config, err := GetKubeConfig() |
There was a problem hiding this comment.
checkLonghornReady already receives a built kubernetes.Interface client (and WaitForKubernetes already holds a *rest.Config), yet this gate re-reads the kubeconfig from disk and rebuilds a versioned clientset on every poll iteration. WaitForKubernetes polls at a 1s interval up to the now-45-minute deadline, so on a slow instance-manager pull this does the GetKubeConfig() disk stat + NewForConfig() construction thousands of times before the CR reports running.
Building the Longhorn client once (outside the poll loop, or memoized) and passing the lister in would avoid the repeated work and let the gate be tested through the real checkLonghornReady path rather than requiring the instanceManagerReady var stub. Not a correctness bug, but worth tightening on a device-management path.
|
Full-matrix result on the shipped form of this change, superseding the "the other six legs passed on the gate-only build" note above: all seven kvm→EVE-k conversion legs pass with the instance-manager gate and the widened budget in place. Image
Two limits worth stating plainly:
|
Description
Fixes #6258.
An EVE-k node reports cluster storage as ready while it still cannot attach any volume. The first app deployed after a conversion or a fresh install then sits in volume creation for minutes, while its CDI upload pod repeats:
Readiness gated only on the Longhorn DaemonSets. A volume's engine and replica processes run inside the per-node instance-manager pod, which is owned by an
InstanceManagerCR rather than a DaemonSet, so the existing sweep could not observe it. That pod is normally still pullinglonghornio/longhorn-instance-manager— 441,724,959 bytes, the largest image in the Longhorn set — at the moment the DaemonSets report ready. Measured at 9m51s on one device, and over twenty minutes on slower storage.Both readiness paths now require a running
InstanceManagerwhosespec.nodeIDis this node:checkLonghornReady()in pillar, andLonghorn_is_ready()inpkg/kube/longhorn-utils.sh.Longhorn creates the CR during node setup rather than on first volume request, so waiting on it cannot deadlock against a volume whose own creation is itself gated on storage readiness.
Everything needed was already vendored — no dependency change.
PR dependencies
None. Note this overlaps in area with #6240, which also touches
checkLonghornReady()but inside the DaemonSet loop, whereas this adds a check after it. Whichever lands second needs only a trivial rebase.How to test and validate this PR
Automated:
make -C pkg/pillar testcovers it.TestInstanceManagerRunningOnNodehas 8 subtests — running / starting / error / stopped states, this-node vs another-node, several InstanceManagers with one running, none present — plusTestInstanceManagerRunningOnNodeListErrorfor the API-error path.On a device, the behavior to confirm is that storage is no longer declared ready early:
kubectl -n longhorn-system get pod -l longhorn.io/component=instance-manager— it will sit inContainerCreatingfor several minutes pulling ~440 MB.kubectl -n longhorn-system get instancemanagers.longhorn.io -o json | jq '.items[] | {node: .spec.nodeID, state: .status.currentState}'shows a non-runningstate, and volumes stay unready rather than being offered and failing to attach.not ready for workloadswhile the pull is in flight; after it, the deployment simply waits until storage is genuinely ready and then attaches on the first try.The premature-ready window was characterized across a 7-leg kvm→EVE-k conversion matrix on amd64 (all 7 legs passed — it cost wall-clock time, not correctness), which is where the 9m51s and 20-minute figures come from.
Changelog notes
Fixed an issue where a Kubernetes-enabled (EVE-k) node could report cluster storage as ready before it was able to attach volumes, causing the first application deployed on a newly installed or converted node to fail its volume attach repeatedly for several minutes before recovering on its own.
PR Backports
checkLonghornReadyis present there.(Verified by checking for
func checkLonghornReadyinpkg/pillar/kubeapi/kubeapi.goon each branch.)Checklist
And the last but not least:
Reasons for the unchecked boxes: no documentation change — this corrects an existing readiness check rather than adding a knob or a user-visible feature. The pillar half of this patch has now run on an amd64 device (QEMU under Eden) across the full seven-leg kvm→EVE-k conversion matrix, 7/7; arm64 is still untested, and the
pkg/kube/longhorn-utils.shhalf has not run anywhere, because the branch used for that matrix carries #5971 and #5971 deletes that file. Labels:stableshould be added for the 17.0-stable backport.