Crash-Consistent Multi-Generation CoW Fork and Stateless Multi-Replica Gateway HA
此内容尚不支持你的语言。
Source:
docs/enhancements/0022-cow-fork-and-stateless-ha-gateway/README.md· Status:implemented· Area:controller· Milestone:v0.4.0| Field | Value | | :--- | :--- | | CEP Number |0022| | Status |implementable(alpha) | | Owning Area |controller| | Participating Areas |gateway,storage,runtime| | Authors | @cubepod-maintainers | | Created | 2026-11-13 | | Last Updated | 2026-11-13 |
Release Signoff Checklist
Section titled “Release Signoff Checklist”- Enhancement scope, Goals, and Non-Goals are agreed upon (
provisional->implementable) - API / CRD (
pod.cubeplex.ai/v1alpha1) and Controller-Gateway sync contracts are specified - Dual-runtime behavior (Kubernetes Kata + CSI Block AND Local Docker Loopback) is defined
- Security boundary and filesystem consistency (
sync+fsfreezearound live block clone) are reviewed - Test plan includes unit tests and multi-replica Gateway routing E2E tests
- Production Readiness Review (PRR) section is complete
Summary
Section titled “Summary”Two architectural gaps currently prevent horizontal scaling and production sub-agent cloning:
- Multi-Generation & Crash-Consistent CoW Fork:
BuildBlockPVCsinpkg/controller/manifests.gohardcodesDataSourcePVC = "<parent>-rootfs-pvc", which clones a stale or deleted volume when the parent sandbox has incrementedrootfsGeneration > 1(<parent>-rootfs-gen<N>-pvc). Furthermore, cloning a mountedext4block device without briefly quiescing writes (prepare-stop/fsfreeze) leaves dirty uncommitted page cache in the parent guest kernel, and local Docker runtime (pkg/runtime) lacks sparse loopback image cloning (cp --reflink=auto --sparse=always). - Stateless Multi-Replica Gateway HA:
cubepod-gatewaystoress.sandboxes,s.commands, ands.endpointsin process memory. In a 2+ replica Kubernetes Deployment behind a Service load balancer, aGET /sandboxes/{id}or/panel/{token}/request landing on Replica B afterPOST /sandboxesran on Replica A returns404 Not Foundunless Replica B dynamically resolves and caches sandbox state from KubernetesPodBoxCRDs / Controller.
This CEP fixes both problems so POST /sandboxes/{id}/fork produces filesystem-consistent clones across any rootfsGeneration in both Kubernetes CSI Block and Docker Loopback modes, and cubepod-gateway operates cleanly at replicas >= 2 with zero sticky-session requirement.
Motivation
Section titled “Motivation”- Resolve exact parent
RootfsPVCNameandWorkspacePVCNamefromPodBoxStatus(supporting arbitraryrootfsGeneration >= 1) duringForkFrom, supporting both direct CSI PVC-to-PVC clone (dataSource.kind: PersistentVolumeClaim) and CSIVolumeSnapshotclone (snapshot.storage.k8s.io/v1) depending on CSI driver capability. - Trigger brief in-guest
sync+fsfreeze(with automatic 2s unfreeze safety timeout inpodd) when forking aRunningparent sandbox so the clonedext4block volumes contain all flushed writes cleanly. - Implement Copy-on-Write (
cp --reflink=auto --sparse=always) loopback image cloning inpkg/runtime/docker.gofor local single-nodefork. - Enable stateless multi-replica
cubepod-gatewayHA: on cache miss (or periodic watch/poll), any gateway replica reconstructsSandboxRecordand upstream<PodIP>:<port>targets directly fromPodBoxCRD status + annotations (cubepod.io/metadata-*,cubepod.io/template-id,cubepod.io/timeout-seconds), and proxies command logs/status directly to<PodIP>:49983rather than relying on gateway-locals.commandsbuffers.
Non-Goals
Section titled “Non-Goals”- Cross-cluster or cross-region volume replication.
- Distributed Raft/etcd state inside
cubepod-gateway—KubernetesPodBoxCRDs remain the single source of truth.
Proposal
Section titled “Proposal”User Stories
Section titled “User Stories”Story 1: Forking a Sandbox After RootFS Reset
Section titled “Story 1: Forking a Sandbox After RootFS Reset”As an agent orchestrator, after resetting sbx-parent to rootfsGeneration: 2 and installing ripgrep, calling POST /sandboxes/sbx-parent/fork clones sbx-parent-rootfs-gen2-pvc and sbx-parent-workspace-pvc so the child sandbox immediately has both ripgrep and /workspace files.
Story 2: Rolling Restart or Horizontal Scale-Out of cubepod-gateway
Section titled “Story 2: Rolling Restart or Horizontal Scale-Out of cubepod-gateway”As a cluster SRE running 3 replicas of cubepod-gateway behind a round-robin Kubernetes Service, I can kill any gateway Pod mid-session while agents continue calling Sandbox.connect, commands.run, and /panel/{token}/ without 404 errors or dropped background processes.
Notes / Constraints / Caveats
Section titled “Notes / Constraints / Caveats”fsfreezeDeadlock Safety: Ifcubepod-gatewaycrashes while a parent sandbox is frozen duringfork, an indefinitefsfreeze -fwould hang the parent VM. ThereforepoddimplementsPOST /v1/lifecycle/quiescewith a mandatory bounded kernel/thread timer (maxFreezeMs, default1500ms) that unconditionally callsFIFITHAW(ioctl) even if the caller disconnects before sendingthaw.- XFS / Btrfs / APFS Reflink in Local Mode: On Linux hosts backed by XFS (
reflink=1) or Btrfs,cp --reflink=auto --sparse=alwaysclones a10Gisparse loopback file in< 5mswith zero physical block duplication; onext4hosts without reflink,--sparse=alwayscopies only allocated non-zero extents (~50–200msfor typical100MBdeltas).
Risks and Mitigations
Section titled “Risks and Mitigations”| Risk | Impact | Likelihood | Mitigation |
|---|---|---|---|
| Parent VM stays frozen if Controller crashes mid-fork | High | Low | podd auto-thaws after maxFreezeMs (1500ms) on an independent monotonic timer thread. |
Duplicate ext4 filesystem UUID on cloned block devices on the same host kernel |
Med | Med | Inside Kata MicroVMs, each sandbox has its own isolated Guest Linux kernel so identical ext4 UUIDs never collide; in runc privileged container Block mode (runtimeClassName=none), podd-init passes -o nouuid / tune2fs -U random when CUBEPOD_FORKED=1 on first boot. |
Concurrent Auto-Resume from two Gateway replicas creates duplicate Pods |
Med | Med | EnsurePod and PodBox CRD updates are idempotent (409 AlreadyExists adopts the active Pod IP and waits for Running). |
Design Details
Section titled “Design Details”API / CRD / Wire Changes
Section titled “API / CRD / Wire Changes”PodBoxAnnotations for Stateless Gateway Reconstruction:cubepod.io/template-id: Original E2BtemplateIDcubepod.io/timeout-seconds: Requested TTLcubepod.io/volume-key: Optional deterministic volume keycubepod.io/forked-from: Parent sandbox ID (when forked)
poddBounded Quiesce Endpoint:POST /v1/lifecycle/quiesce({"maxFreezeMs": 1500}) ->syncfs+ioctl(FIFREEZE)+ background watchdog thread ->POST /v1/lifecycle/thaw(ioctl(FITHAW)).
- Gateway Miss-Through Lookup (
resolveSandbox(ctx, sandboxID)):- All Gateway handlers replace raw
s.sandboxes[sandboxID]map reads withs.getOrLoadSandbox(r.Context(), sandboxID), which falls back tos.reconciler.SyncBoxFromKube(ctx, sandboxID)on cache miss whenlivePodRouting/KubeClientis active. - Panel proxy (
handlePanelProxy) computeshttp://<PodIP>:<claims.Port>directly fromPodBoxStatus.PodIPwhenevers.endpoints[sandboxID][port]is empty, making/panel/{token}/100% stateless across replicas!
- All Gateway handlers replace raw
Architecture & State Machine
Section titled “Architecture & State Machine”flowchart TD
Client["SDK / Browser"] -->|"Round-Robin"| GW2["Gateway Replica #2 (Cold Cache)"]
GW2 -->|"Cache Miss: GetPodBox"| API["K8s APIServer (PodBox CRD)"]
API -->|"Phase=Running, PodIP=10.244.1.19"| GW2
GW2 -->|"Direct HTTP/WS :49983 / :8080"| Pod["Kata MicroVM Pod (10.244.1.19)"]Boundary Conditions & Failure Recovery
Section titled “Boundary Conditions & Failure Recovery”- Deleted Sandbox Across Replicas: If Replica A deletes
sbx-1, Replica B’s next request verifies liveness on upstream error or periodicSyncFromKube(5sinterval) and evicts terminated entries from memory. - Forking a
PausedSandbox: Since no Pod is running andprepare-stopalready flushed the filesystem before pause,forkskipsquiesceand immediately clones the quiescent PVCs.
Dual-Runtime Behavior (K8s Kata vs. Local Docker)
Section titled “Dual-Runtime Behavior (K8s Kata vs. Local Docker)”- Kubernetes Production (
Kata + CSI Raw Block): Quiesces livepodd(1500msbounded), creates child PVCs withdataSourcereferencing the parent’s activestatus.rootfsPVCName(<parent>-rootfs-gen<N>-pvc) andstatus.workspacePVCName, thaws parent once CSI binds/snapshots, and boots the child Kata MicroVM. - Local Development (
Docker + Loopback ext4): Quiesces parent container, runscp --reflink=auto --sparse=alwayson<parent>/rootfs.ext4and<parent>/workspace.ext4, thaws parent, and boots the child Docker container mounted onto the cloned loopback files.
Test Plan
Section titled “Test Plan”- Unit Tests:
TestForkSandboxWithRootfsGeneration2: VerifiesBuildBlockPVCsusesstatus.RootfsPVCName(*-gen2-pvc) instead of*-rootfs-pvc.TestStatelessGatewayMultiReplicaFailover: Instantiates two independentgateway.Serverinstances (gw1,gw2) backed by a sharedKubeClient: creates sandbox, runs background command, and mints/panel/{token}/ongw1, then queries sandbox, streams logs with?cursor=, and proxies/panel/{token}/throughgw2without prior state sharing!
- Integration / E2E Tests:
- Docker loopback
forkE2E verifying isolated mutations between parent and child after fork.
- Docker loopback
Graduation Criteria
Section titled “Graduation Criteria”- Alpha (
v0.5.0): Multi-genForkFrom, Docker sparse reflink fork, boundedquiesce/thaw, and statelessgw1->gw2failover merged and tested. - Beta (
v0.6.0): Helm chart defaultsgateway.replicas: 2with PodDisruptionBudget; CSIVolumeSnapshotmode verified on Longhorn V2 SPDK. - Stable (
v1.0.0): Zero-downtime Gateway rolling updates verified under continuous load in CubePlex.
Upgrade / Downgrade Strategy
Section titled “Upgrade / Downgrade Strategy”- Existing
PodBoxCRDs without the new optional annotations fall back gracefully to default metadata (templateID: "base"). No PVC data migration required.
Production Readiness Review Questionnaire (PRR)
Section titled “Production Readiness Review Questionnaire (PRR)”Feature Enablement and Rollback
Section titled “Feature Enablement and Rollback”- How can this feature be enabled / disabled in a live cluster? Scale
gateway.replicasback to1or toggleStatelessGatewayHA. - Does disabling or rolling back corrupt
/workspaceorRootFSblock volumes? No.
Rollout, Upgrade and Rollback Planning
Section titled “Rollout, Upgrade and Rollback Planning”- Rolling update of
cubepod-gatewayseamlessly hands active sandboxes to new Pods viaPodBoxCRD reconstruction.
Monitoring Requirements
Section titled “Monitoring Requirements”cubepod_sandbox_forks_total{mode="pvc_clone|snapshot|loopback",result="ok|error"},cubepod_sandbox_fork_duration_seconds, andcubepod_gateway_crd_resyncs_total.
Dependencies
Section titled “Dependencies”- CSI driver with
CLONE_VOLUMEorCREATE_DELETE_SNAPSHOTcapability (Longhorn V2 SPDK, OpenEBS LVM-LocalPV / Mayastor, AWS EBS CSI).
Scalability
Section titled “Scalability”- Periodic
ListPodBoxesresync (5s) plus on-missGetPodBoxadds minimal APIServer load (< 2 QPSfor 1,000 sandboxes).
Troubleshooting
Section titled “Troubleshooting”- Check
PodBox.status.reason(ForkingVolumes,Running) andkubectl describe pvc <child>-rootfs-pvcfor CSIProvisioningSucceededevents.
Implementation History
Section titled “Implementation History”2026-11-13: Initial CEP drafted and markedimplementableforv0.5.0.
Drawbacks
Section titled “Drawbacks”- Slightly higher APIServer read rate on cold cache misses compared to single-process in-memory routing, easily bounded via short TTL negative caching (
1s) for non-existent sandbox IDs.
Alternatives
Section titled “Alternatives”Alternative 1: Redis / PostgreSQL External Session Store for cubepod-gateway
Section titled “Alternative 1: Redis / PostgreSQL External Session Store for cubepod-gateway”- Description: Introduce Redis or Postgres to store
SandboxRecordand port mappings across Gateway replicas. - Reason for Rejection: Adds an extra stateful database dependency to operate and back up, whereas Kubernetes CRDs (
PodBox) already persist sandbox state inetcd.
Alternative 2: Sticky Ingress Sessions (ip_hash / Cookie Affinity)
Section titled “Alternative 2: Sticky Ingress Sessions (ip_hash / Cookie Affinity)”- Description: Pin clients to a single Gateway replica at the load balancer.
- Reason for Rejection: Fails on Gateway Pod restarts/scale-down and breaks server-to-server SDK calls from multi-worker backends like CubePlex.