Skip to content

microvm: serve durable-dir volumes through the one kataShared virtiofsd - #1034

Open
Lucky Abolorunke (Oneimu) wants to merge 1 commit into
agent-substrate:mainfrom
Oneimu:durdir-single-share
Open

microvm: serve durable-dir volumes through the one kataShared virtiofsd#1034
Lucky Abolorunke (Oneimu) wants to merge 1 commit into
agent-substrate:mainfrom
Oneimu:durdir-single-share

Conversation

@Oneimu

Copy link
Copy Markdown
Contributor

What Changed and Why

Durable-dir volumes were previously served to the guest by a second per-actor virtiofsd. That arrangement predates the writable kataShared share: when the rootfs share was a read-only lower, a writable durable share had to be its own device. Since #846, the kataShared tree is writable and served with --announce-submounts, making the second daemon redundant—it cost an extra process and vhost socket per actor, an extra fs device in every snapshot config, and a restore-time revival of all three.

This PR folds the durable-dir volumes into the single existing share:

  • Subtree Bind Mounting (_durable):
    kata.BindIntoShare bind-mounts the atelet-owned volumes directory into the served tree as its _durable subtree. The leading underscore keeps it out of the container-ID namespace (container names are RFC 1123 labels and cannot begin with an underscore). The guest sees it as a submount of the kataShared mount, and containers bind their volumes from <shared>/_durable/<volume> exactly as they previously did from the second share.
  • Streamlined VM Configuration:
    Cold boot no longer spawns the durable virtiofsd, and the VM configuration carries exactly one virtio-fs device.
  • Unchanged Ownership Semantics:
    atelet still owns the directory (creates it before boot, wipes it on actor reset). The bind is ateom-owned mount state, detached by CleanupSandboxState before any removal, ensuring atelet's data is never touched through it.
  • Unchanged Snapshot Content:
    Checkpoints tar the host directory directly under every scope, exactly as before.

Note

The bind uses the same mechanism the per-container merged rootfs mounts already use through this virtiofsd (submounts inside the served tree, re-opened by find-paths on restore), introducing no new guest-side behavior. BindIntoShare's doc comment establishes this pattern for future per-actor shares: mount a reserved subtree rather than adding a device (relevant to in-flight work such as #803; #923 already follows this subtree approach).


Compatibility with Existing Snapshots

Restore is self-describing in both directions:

  • Two-Share Era Snapshots:
    A snapshot whose config.json carries an ateDurable fs device originates from the two-share era. The resumed guest still expects that device, so restore revives the second virtiofsd exactly as before (stageLegacyDurableShare). Such a lineage remains two-share across its own re-checkpoints since cloud-hypervisor re-emits the device.
  • Single-Share Snapshots:
    A snapshot without the device gets the volumes re-bound into the shared tree before virtiofsd starts, allowing find-paths to re-open the guest's open durable files at their _durable/... paths, which the restored tar reproduces exactly.

Detection logic lives in rewriteSnapshotSocketPaths, which already inspects the configuration's fs devices. The configuration acts as the authority because the device is what cloud-hypervisor re-opens, regardless of the actor's spec.


How This Was Tested

  • Unit Tests:
    • Added assertions verifying that rewriteSnapshotSocketPaths classifies single-device and two-device snapshot configs correctly (routing restore to the bind vs. the legacy share).
    • Added a new kata-package test pinning _durable subtree invariants: the underscore namespace reservation, the guest path residing inside the single kataShared mount, and host/guest agreement on the relative path re-opened by find-paths.
    • Verified that existing durable-volume unit tests (tar/untar round-trip, container mount construction, spec isolation) continue to pass unchanged.
  • End-to-End Verification:
    • Ran the counter demo on a GKE cluster with nested virtualization: verified cold boot, suspend, and resume of a durable-volume actor on this branch (volume contents survived across worker pods).
    • Verified restore of a legacy snapshot taken on main before this change to exercise the legacy two-share fallback path.

@Oneimu

Copy link
Copy Markdown
Contributor Author

/assign Benjamin Elder (@BenTheElder)

// three — per actor, forever. A bind costs one mount, and teardown already
// covers it: CleanupSandboxState lazily detaches every mount under the sandbox
// dir before removing it, so the source directory (which may belong to atelet,
// as the durable-dir volumes do) is never touched. Callers must stage binds

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 should-fix 🟡 – CleanupSandboxState doesn't enforce this, so a failed detach deletes the actor's durable data.

It logs each unix.Unmount failure and continues, then runs os.RemoveAll(d) unconditionally. Worse, the whole unmount loop sits inside if b, err := os.ReadFile("/proc/self/mountinfo"); err == nil with no else — if that read fails, nothing is unmounted and the RemoveAll still runs, silently.

Either way RemoveAll walks into a live _durable bind and deletes through it, and what it deletes is ateompath.DurableDirVolumeMountsDir — the data durable volumes exist to preserve. Nothing under SharedDir had that property before: the merged rootfs overlays would take their whiteouts to an ateom-owned upper that gets wiped anyway, and their lower is reconstructible from the image.

Both triggers are unlikely, which is why I'd not block on it — but the guarantee is asserted here and in durable.go ("nothing of atelet's is ever deleted through it") rather than enforced. Skipping the RemoveAll for a directory whose unmounts didn't all succeed turns silent data loss into a leaked directory, which is the right trade.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — removal is now gated on the unmounts: an unmount failure (or unreadable mountinfo) leaves the dir in place instead of RemoveAll through a live bind. The stagers tolerate leftover dirs, so the degraded case is a retry, not data loss.

Comment thread cmd/ateom-microvm/durable.go Outdated
// kataShared tree — so teardown has nothing extra to unmount. Unlike the RO
// lower's virtiofsd this one runs with cache=auto: the host contents change
// underneath the guest whenever a snapshot is restored into them.
// stageLegacyDurableShare starts the second virtiofsd that snapshots from the

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 question 🟢 – Is this worth carrying? We don't support in-place upgrade, and the position on snapshot compatibility elsewhere this week has been that we don't care about it yet.

Unlike a stub, this one genuinely works — you've kept the device detection, the second daemon, its teardown, and the tests, and you verified a legacy restore end to end. That's the cost: a branch through restoreFullScope, a legacyDurable return threaded out of rewriteSnapshotSocketPaths, DurableFsTag/DurableVirtiofsdSocketPath/durableVirtiofsdLogPath kept alive, and a lineage that stays permanently two-share once restored.

If pre-change snapshots can just be discarded, dropping all of it lets rewriteSnapshotSocketPaths keep its single return value and lets default: reject ateDurable the way it already rejects the retired ateUpper tag.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Human: Yes, don't try to maintain compatibility yet. Just clean up the legacy code.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: dropped entirely, and the PR is re-scoped after #840 landed (which already folded durable/CSI into the single share).

  Hoist the durable-dir and CSI volume staging (two inlined copies of the
  same umount/mkdir/bind sequence) into kata.BindIntoShare, the one pattern
  for exposing a host directory through the single kataShared share. Drop
  the dead restore compat for the retired ateDurable/ateCSI fs devices —
  nothing spawns those virtiofsds anymore, so repointing their sockets only
  deferred the failure to cloud-hypervisor's vhost connect — and gate
  CleanupSandboxState's dir removal on every mount beneath being detached,
  so RemoveAll can never delete the actor's volume data through a live bind.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants