Skip to content

Commit 5dae74b

Browse files
committed
Document core concepts and architecture
1 parent 516ec87 commit 5dae74b

2 files changed

Lines changed: 307 additions & 1 deletion

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ independent gVisor sandbox through the standard containerd runtime stack.
1010
SandboxFleet focuses on Slot capacity, placement, lifecycle, and cleanup. It
1111
delegates image management and sandbox creation to containerd and gVisor.
1212

13-
See [the core architecture](docs/design.md) for the design.
13+
See the [core concepts](docs/design.md) and
14+
[architecture](docs/architecture.md) for the design.

docs/architecture.md

Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
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

Comments
 (0)