|
| 1 | +# SandboxFleet Architecture |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +SandboxFleet has five internal modules: |
| 6 | + |
| 7 | +1. API |
| 8 | +2. Controller Manager |
| 9 | +3. Slot Scheduler |
| 10 | +4. Worker Agent |
| 11 | +5. Runtime Adapter |
| 12 | + |
| 13 | +The first four modules manage Sandbox and Slot state. The Runtime Adapter |
| 14 | +delegates execution to an existing container runtime. |
| 15 | + |
| 16 | +## API |
| 17 | + |
| 18 | +The API module defines two Kubernetes resources. |
| 19 | + |
| 20 | +### SandboxPool |
| 21 | + |
| 22 | +`SandboxPool` defines Worker capacity: |
| 23 | + |
| 24 | +```yaml |
| 25 | +apiVersion: sandboxfleet.io/v1alpha1 |
| 26 | +kind: SandboxPool |
| 27 | +spec: |
| 28 | + workers: 3 |
| 29 | + slotsPerWorker: 4 |
| 30 | + runtime: |
| 31 | + backend: cri |
| 32 | + handler: runsc |
| 33 | +``` |
| 34 | +
|
| 35 | +It records: |
| 36 | +
|
| 37 | +- The desired number of Workers. |
| 38 | +- The number of Slots per Worker. |
| 39 | +- Slot resource limits. |
| 40 | +- The runtime backend and handler. |
| 41 | +- Total, used, and available capacity. |
| 42 | +
|
| 43 | +### Sandbox |
| 44 | +
|
| 45 | +`Sandbox` defines one execution environment: |
| 46 | + |
| 47 | +```yaml |
| 48 | +apiVersion: sandboxfleet.io/v1alpha1 |
| 49 | +kind: Sandbox |
| 50 | +spec: |
| 51 | + poolRef: gvisor-pool |
| 52 | + image: python:3.12 |
| 53 | + command: ["sleep", "infinity"] |
| 54 | +``` |
| 55 | + |
| 56 | +Its status records: |
| 57 | + |
| 58 | +- Lifecycle phase. |
| 59 | +- Assigned Worker. |
| 60 | +- Assigned Slot. |
| 61 | +- Runtime handle. |
| 62 | +- Last reported error. |
| 63 | + |
| 64 | +The initial phases are `Pending`, `Assigned`, `Starting`, `Running`, `Stopping`, |
| 65 | +and `Failed`. |
| 66 | + |
| 67 | +## Controller Manager |
| 68 | + |
| 69 | +The Controller Manager contains the Pool Controller and Sandbox Controller. |
| 70 | + |
| 71 | +### Pool Controller |
| 72 | + |
| 73 | +The Pool Controller: |
| 74 | + |
| 75 | +- Creates and scales Worker Pods for each SandboxPool. |
| 76 | +- Passes Slot and runtime configuration to each Worker. |
| 77 | +- Tracks Worker health and aggregate Slot capacity. |
| 78 | +- Removes Workers during pool scale-down. |
| 79 | + |
| 80 | +Workers should use stable names so that assignments and recovery can identify |
| 81 | +the correct Worker after a control-plane restart. |
| 82 | + |
| 83 | +### Sandbox Controller |
| 84 | + |
| 85 | +The Sandbox Controller: |
| 86 | + |
| 87 | +- Watches Sandbox creation, updates, and deletion. |
| 88 | +- Requests a Slot assignment from the Slot Scheduler. |
| 89 | +- Persists the assignment in Sandbox status. |
| 90 | +- Requests the assigned Worker to start or stop the Sandbox. |
| 91 | +- Reconciles requested state with actual runtime state. |
| 92 | +- Releases the Slot after runtime cleanup succeeds. |
| 93 | + |
| 94 | +The Controller Manager uses Kubernetes leader election. A single active leader |
| 95 | +performs Slot assignment. |
| 96 | + |
| 97 | +## Slot Scheduler |
| 98 | + |
| 99 | +The Slot Scheduler owns placement decisions. |
| 100 | + |
| 101 | +It maintains: |
| 102 | + |
| 103 | +- Registered Workers. |
| 104 | +- Worker heartbeat state. |
| 105 | +- Total and available Slots. |
| 106 | +- Current Sandbox-to-Slot assignments. |
| 107 | +- Runtime and resource compatibility. |
| 108 | + |
| 109 | +The initial scheduling policy selects a healthy Worker in the requested Pool |
| 110 | +with a compatible runtime and an available Slot. |
| 111 | + |
| 112 | +An assignment contains: |
| 113 | + |
| 114 | +```text |
| 115 | +Sandbox UID |
| 116 | +Worker name |
| 117 | +Slot ID |
| 118 | +``` |
| 119 | + |
| 120 | +The assignment is persisted in Sandbox status. Slots are internal scheduling |
| 121 | +units and are not Kubernetes resources. |
| 122 | + |
| 123 | +The Scheduler rebuilds its state from Sandbox resources and Worker reports after |
| 124 | +a restart. |
| 125 | + |
| 126 | +## Worker Agent |
| 127 | + |
| 128 | +Each Worker Pod runs one Worker Agent. |
| 129 | + |
| 130 | +The Worker Agent: |
| 131 | + |
| 132 | +- Registers the Worker with the control plane. |
| 133 | +- Reports Slot capacity and health. |
| 134 | +- Maintains the local Slot-to-Sandbox mapping. |
| 135 | +- Reserves and releases Slots. |
| 136 | +- Starts and stops Sandboxes through the Runtime Adapter. |
| 137 | +- Removes runtime resources before marking a Slot as free. |
| 138 | +- Rebuilds local state from runtime labels after a restart. |
| 139 | + |
| 140 | +The Worker Agent exposes idempotent operations: |
| 141 | + |
| 142 | +```text |
| 143 | +ReserveSlot |
| 144 | +StartSandbox |
| 145 | +StopSandbox |
| 146 | +ReleaseSlot |
| 147 | +GetSandbox |
| 148 | +ListSlots |
| 149 | +Exec |
| 150 | +``` |
| 151 | + |
| 152 | +Each runtime object is labeled with its Sandbox UID, Worker name, and Slot ID. |
| 153 | +These labels allow state recovery without a separate local database. |
| 154 | + |
| 155 | +## Runtime Adapter |
| 156 | + |
| 157 | +The Runtime Adapter is an internal Worker Agent package. It is not a separate |
| 158 | +service and does not implement a container runtime. |
| 159 | + |
| 160 | +It exposes a runtime-neutral interface: |
| 161 | + |
| 162 | +```go |
| 163 | +type Runtime interface { |
| 164 | + Create(ctx context.Context, spec SandboxSpec) (Handle, error) |
| 165 | + Start(ctx context.Context, handle Handle) error |
| 166 | + Stop(ctx context.Context, handle Handle) error |
| 167 | + Delete(ctx context.Context, handle Handle) error |
| 168 | + Status(ctx context.Context, handle Handle) (Status, error) |
| 169 | + Exec(ctx context.Context, handle Handle, command []string) error |
| 170 | +} |
| 171 | +``` |
| 172 | + |
| 173 | +`Handle` is opaque to the Slot Scheduler: |
| 174 | + |
| 175 | +```go |
| 176 | +type Handle struct { |
| 177 | + ID string |
| 178 | +} |
| 179 | +``` |
| 180 | + |
| 181 | +The Scheduler stores the Handle but does not interpret runtime-specific IDs. |
| 182 | + |
| 183 | +## CRI Backend |
| 184 | + |
| 185 | +The first Runtime implementation is `CRIRuntime`. |
| 186 | + |
| 187 | +`CRIRuntime` uses the Kubernetes CRI v1 client to call a local containerd |
| 188 | +instance. It performs: |
| 189 | + |
| 190 | +- Image pull. |
| 191 | +- PodSandbox creation. |
| 192 | +- Container creation and start. |
| 193 | +- Container stop and removal. |
| 194 | +- PodSandbox removal. |
| 195 | +- Status lookup. |
| 196 | +- Command execution. |
| 197 | + |
| 198 | +The mapping is: |
| 199 | + |
| 200 | +| SandboxFleet object | CRI object | |
| 201 | +| --- | --- | |
| 202 | +| Slot | PodSandbox | |
| 203 | +| Sandbox | Container | |
| 204 | +| Runtime Handle | PodSandbox ID | |
| 205 | + |
| 206 | +For gVisor, the Pool uses the `runsc` runtime handler. Containerd delegates |
| 207 | +execution to `containerd-shim-runsc-v1`, which invokes `runsc`. |
| 208 | + |
| 209 | +SandboxFleet does not import gVisor internals or invoke `runsc` directly. |
| 210 | + |
| 211 | +## Runtime Extensibility |
| 212 | + |
| 213 | +Runtime selection belongs to SandboxPool. A Worker Pool uses one runtime |
| 214 | +configuration. |
| 215 | + |
| 216 | +Examples: |
| 217 | + |
| 218 | +```yaml |
| 219 | +runtime: |
| 220 | + backend: cri |
| 221 | + handler: runsc |
| 222 | +``` |
| 223 | + |
| 224 | +```yaml |
| 225 | +runtime: |
| 226 | + backend: cri |
| 227 | + handler: kata |
| 228 | +``` |
| 229 | + |
| 230 | +Any runtime with a containerd CRI handler can use `CRIRuntime`. Supporting such |
| 231 | +a runtime requires configuration, not a new Slot Scheduler implementation. |
| 232 | + |
| 233 | +A runtime without CRI support requires another Runtime implementation: |
| 234 | + |
| 235 | +```text |
| 236 | +internal/runtime/ |
| 237 | +├── runtime.go |
| 238 | +├── cri/ |
| 239 | +│ └── runtime.go |
| 240 | +└── firecracker/ |
| 241 | + └── runtime.go |
| 242 | +``` |
| 243 | + |
| 244 | +The new implementation must satisfy the same Runtime interface. The API, |
| 245 | +controllers, Scheduler, and Worker Slot logic remain unchanged. |
| 246 | + |
| 247 | +The initial version supports only `CRIRuntime` with the `runsc` handler. |
| 248 | + |
| 249 | +## State Ownership |
| 250 | + |
| 251 | +| State | Owner | |
| 252 | +| --- | --- | |
| 253 | +| Desired Pool configuration | `SandboxPool.spec` | |
| 254 | +| Aggregate Pool capacity | `SandboxPool.status` | |
| 255 | +| Desired Sandbox configuration | `Sandbox.spec` | |
| 256 | +| Sandbox phase and assignment | `Sandbox.status` | |
| 257 | +| Placement decisions | Slot Scheduler | |
| 258 | +| Local Slot state | Worker Agent | |
| 259 | +| Container and process state | containerd | |
| 260 | +| gVisor execution state | runsc | |
| 261 | + |
| 262 | +Kubernetes resources are the persistent source of desired state and assignment. |
| 263 | +Worker and Scheduler state must be reconstructable. |
| 264 | + |
| 265 | +## Package Layout |
| 266 | + |
| 267 | +```text |
| 268 | +api/ |
| 269 | +└── v1alpha1/ |
| 270 | +
|
| 271 | +cmd/ |
| 272 | +├── controller-manager/ |
| 273 | +└── worker/ |
| 274 | +
|
| 275 | +internal/ |
| 276 | +├── controller/ |
| 277 | +├── scheduler/ |
| 278 | +├── worker/ |
| 279 | +└── runtime/ |
| 280 | + ├── runtime.go |
| 281 | + └── cri/ |
| 282 | +
|
| 283 | +config/ |
| 284 | +├── crd/ |
| 285 | +├── rbac/ |
| 286 | +└── manager/ |
| 287 | +
|
| 288 | +docs/ |
| 289 | +├── architecture.md |
| 290 | +└── design.md |
| 291 | +``` |
| 292 | + |
| 293 | +## Initial Scope |
| 294 | + |
| 295 | +The initial implementation includes: |
| 296 | + |
| 297 | +- SandboxPool and Sandbox APIs. |
| 298 | +- Fixed Slot capacity per Worker. |
| 299 | +- Sandbox-to-Slot placement. |
| 300 | +- Worker registration and health reporting. |
| 301 | +- CRI-based gVisor Sandbox lifecycle. |
| 302 | +- Slot cleanup and state recovery. |
| 303 | + |
| 304 | +Checkpointing, migration, mixed runtimes within one Pool, and custom VM |
| 305 | +backends are outside the initial scope. |
0 commit comments