CEP-0001: In-Guest Persistent OverlayFS Pivot and Loopback Verification (`podd-init`)
Source:
docs/enhancements/0001-in-guest-overlayfs-pivot/README.md· Status:implemented· Area:runtime· Milestone:v0.1| Field | Value | | :--- | :--- | | CEP Number |0001| | Status |implemented(alpha) | | Owning Area |runtime| | Authors | @xfgong | | Created | 2026-11-10 | | Last Updated | 2026-11-10 |
Release Signoff Checklist
Section titled “Release Signoff Checklist”- Enhancement scope, Goals, and Non-Goals are agreed upon (
provisional->implemented) - API / CRD (
pod.cubeplex.ai/v1alpha1) and E2B-superset OpenAPI changes are specified - Dual-runtime behavior (Kubernetes Kata + CSI Block AND Local Docker Loopback) is defined
- Security boundary (MicroVM isolation, UID/GID privilege separation, Egress secret safety) is reviewed
- Test plan includes unit tests (
cargo test/go test) and real invariant E2E tests - Production Readiness Review (PRR) section is complete
Summary
Section titled “Summary”podd-init is the single-threaded, zero-GC Rust PID 1 entrypoint injected at runtime into /opt/cubepod/bin/podd-init inside every CubePod sandbox container. Before handing execution over to /opt/cubepod/bin/podd, podd-init inspects the attached RootFS Upper block device (/dev/cubepod-rootfs) and Workspace block device (/dev/cubepod-workspace), auto-heals (e2fsck -p) or formats (mkfs.ext4) them, constructs a persistent OverlayFS merging the read-only Base Image (/) with the persistent upperdir/workdir, mounts /workspace with unprivileged ownership (1000:1000), bind-mounts /opt/cubepod so runtime binaries do not pollute the persistent upper layer, and performs pivot_root(2).
Motivation
Section titled “Motivation”Standard Kubernetes and Kata Containers prepare the container’s / on the host via containerd snapshotters, while CSI volumeMode: Block PVCs (volumeDevices) are attached at /dev/cubepod-* after the initial rootfs is already mounted. Without an in-guest root pivot mechanism, preserving system-wide package installations (apt, pip, /etc) across Pod deletion requires either custom host containerd plugins or heavy OCI image snapshot exports.
- Provide a production-grade, single-threaded Rust
podd-initbinary (crates/podd-init) with zero external crate dependencies beyondlibc. - Support configurable paths (
CUBEPOD_ROOTFS_DEV,CUBEPOD_WORKSPACE_DEV,CUBEPOD_LOWER_DIR,CUBEPOD_UPPER_MOUNT,CUBEPOD_MERGED_ROOT,CUBEPOD_UID,CUBEPOD_GID,CUBEPOD_DRY_RUN) so every step of the mount/pivot pipeline can be unit-tested and integration-tested both with real ext4 loopback images and in unprivileged CI environments. - Write a state marker file (
/run/cubepod/init-state.json) recording whetherrootfsandworkspacedevices were freshly formatted, auto-healed, or skipped, and the elapsed boot duration in milliseconds. - Ensure
/opt/cubepodis bind-mounted acrosspivot_rootso injected binaries remain accessible without writing to the user’s persistentupperdir.
Non-Goals
Section titled “Non-Goals”- Modifying host
containerdor Kubelet CRI code. - Managing network firewall (
nftables) rules insidepodd-init(handled by the Egress sidecar).
Proposal
Section titled “Proposal”User Stories
Section titled “User Stories”Story 1: Persistent apt and pip Packages Across Suspend/Resume
Section titled “Story 1: Persistent apt and pip Packages Across Suspend/Resume”As a CubePlex agent user, when my agent runs apt-get install -y jq and /workspace edits, and my sandbox is paused (Pod deleted) and later resumed on a different node, podd-init mounts the existing rootfs-pvc and workspace-pvc in < 200ms so jq and /workspace files are immediately present.
Story 2: Independent RootFS Reset Preserving /workspace
Section titled “Story 2: Independent RootFS Reset Preserving /workspace”As a CubePlex user, when my system environment breaks and I click “Reset System Environment”, the controller attaches a fresh rootfs-pvc while keeping my existing workspace-pvc. podd-init formats only the new rootfs-pvc with mkfs.ext4 and mounts my existing workspace-pvc untouched.
Notes / Constraints / Caveats
Section titled “Notes / Constraints / Caveats”podd-initMUST remain single-threaded prior topivot_root(2)because Linux returnsEINVALifpivot_rootis called from a multi-threaded process or if the mount propagation of/is shared (MS_SHARED). Therefore,podd-initMUST first set/mount propagation toMS_PRIVATE | MS_RECbefore mounting and pivoting.
Risks and Mitigations
Section titled “Risks and Mitigations”| Risk | Impact | Likelihood | Mitigation |
|---|---|---|---|
| Unclean VM termination leaves ext4 journal dirty | High | Med | ensure_ext4 runs e2fsck -p on every boot and treats exit codes 0, 1, and 2 as clean/healed. |
pivot_root fails with EINVAL due to MS_SHARED propagation on / |
High | High | Explicitly call libc::mount(null, "/", null, MS_PRIVATE | MS_REC, null) before assembling mounts. |
podd binary lost after pivot_root because /opt/cubepod was mounted on old root |
High | High | Bind-mount /opt/cubepod into <merged_root>/opt/cubepod before pivot_root. |
Design Details
Section titled “Design Details”API / CRD / Wire Changes
Section titled “API / CRD / Wire Changes”podd-init writes /run/cubepod/init-state.json (or CUBEPOD_INIT_STATE_PATH) after completing setup:
{ "pivoted": true, "rootfs_device": "/dev/cubepod-rootfs", "rootfs_action": "mounted", "workspace_device": "/dev/cubepod-workspace", "workspace_action": "mounted", "duration_ms": 42}Architecture & State Machine
Section titled “Architecture & State Machine”flowchart TD
Start["podd-init (PID 1)"] --> CheckSkip{"CUBEPOD_SKIP_PIVOT=1 or<br/>rootfs_device missing?"}
CheckSkip -- Yes --> WriteStateSkip["Write init-state.json (pivoted=false)"] --> Exec["execvp(/opt/cubepod/bin/podd)"]
CheckSkip -- No --> MakePrivate["mount('/', MS_PRIVATE | MS_REC)"]
MakePrivate --> EnsureRoot["ensure_ext4(rootfs_device)<br/>blkid -> mkfs.ext4 or e2fsck -p"]
EnsureRoot --> MountUpper["Mount ext4 -> /mnt/cubepod-upper<br/>mkdir upper/, work/"]
MountUpper --> MountOverlay["Mount overlayfs -> /mnt/cubepod-merged<br/>(lowerdir=/, upperdir=upper, workdir=work)"]
MountOverlay --> EnsureWS["If workspace_device exists:<br/>ensure_ext4 & mount -> <merged>/workspace<br/>chown 1000:1000"]
EnsureWS --> BindSys["Bind-mount /proc, /sys, /dev, /run, /opt/cubepod<br/>& /etc/{resolv.conf,hosts,hostname}"]
BindSys --> Pivot["SYS_pivot_root(/mnt/cubepod-merged, .old_root)<br/>umount2(/.old_root, MNT_DETACH)"]
Pivot --> WriteState["Write /run/cubepod/init-state.json (pivoted=true)"]
WriteState --> ExecDual-Runtime Behavior (K8s Kata vs. Local Docker)
Section titled “Dual-Runtime Behavior (K8s Kata vs. Local Docker)”- Kubernetes Production (
Kata + CSI Raw Block): K8s attaches twovolumeMode: BlockPVCs at/dev/cubepod-rootfsand/dev/cubepod-workspace.podd-initformats/heals and pivots inside the Kata Guest VM kernel. - Local Development (
Docker + Loopback ext4):cubepod-gateway/ local runner creates sparse.imgfiles formatted withmkfs.ext4(or loop devices / bind mounts) and passes them into the container. When no block device is attached,podd-initgracefully recordspivoted: falseand execspodd.
Test Plan
Section titled “Test Plan”- Unit Tests (
cargo test -p podd-init):- Config parsing from environment variables (
CUBEPOD_ROOTFS_DEV,CUBEPOD_WORKSPACE_DEV,CUBEPOD_UID,CUBEPOD_GID,CUBEPOD_INIT_STATE_PATH). - Graceful fallback when block devices are absent or
CUBEPOD_SKIP_PIVOT=1is set, verifyinginit-state.jsonis written accurately. - Overlay option string formatting and directory layout preparation (
upper,work,merged,workspace) in a temporary directory using dry-run / mockable command runner. - Real
mkfs.ext4+blkid+e2fsck -ptest against a temporary sparse file (truncate/set_len(64 * 1024 * 1024)) verifyingensure_ext4formats an unformatted image file and subsequently detects and validates theext4signature on the second run!
- Config parsing from environment variables (
Graduation Criteria
Section titled “Graduation Criteria”- Alpha (
v0.1): All unit tests and real sparse-fileext4formatting/healing tests pass viacargo test -p podd-initandcargo clippy --workspace -- -D warnings.
Upgrade / Downgrade Strategy
Section titled “Upgrade / Downgrade Strategy”podd-init is injected via cubepod-podd initContainer at Pod creation time. Upgrading cubepod-podd takes effect on the next Pod resume/start without touching data on rootfs-pvc or workspace-pvc.
Production Readiness Review Questionnaire (PRR)
Section titled “Production Readiness Review Questionnaire (PRR)”Feature Enablement and Rollback
Section titled “Feature Enablement and Rollback”- Set
CUBEPOD_SKIP_PIVOT=1on the container environment to bypasspivot_rootimmediately without deleting PVCs.
Monitoring Requirements
Section titled “Monitoring Requirements”/run/cubepod/init-state.jsonexposes boot duration (duration_ms) and volume actions (formatted|healed|mounted|skipped), whichpoddserves onGET /health.
Dependencies
Section titled “Dependencies”- Requires
e2fsprogs(blkid,mkfs.ext4,e2fsck) andutil-linux(mount) in the guest environment.
Troubleshooting
Section titled “Troubleshooting”- If
podd-initfails, container logs on stderr show[podd-init] fatal error: ...with the exact command and exit status.
Implementation History
Section titled “Implementation History”2026-11-10: CEP-0001 drafted and markedimplementedforv0.1.
Drawbacks
Section titled “Drawbacks”- Requires the guest container inside the Kata VM to have
CAP_SYS_ADMIN(orprivileged_without_host_devices = truein Kata) to executemountandpivot_root.
Alternatives
Section titled “Alternatives”Alternative 1: Custom Host containerd Block Snapshotter
Section titled “Alternative 1: Custom Host containerd Block Snapshotter”- Description: Write a custom
containerdsnapshotter plugin installed on every Kubernetes worker node. - Reason for Rejection: High operational friction; breaks standard managed Kubernetes installations and cannot be deployed via a simple Helm chart.