Skip to content

Commit 55b3a06

Browse files
committed
Add initial project structure for SandboxFleet
- Introduced .gitignore to exclude build artifacts and test files. - Created go.mod and go.sum for dependency management. - Added initial API definitions and types for v1alpha1, including Sandbox and SandboxPool. - Implemented Dockerfiles for building controller and worker images. - Included CNI configuration for networking in worker pods. - Added client implementation for interacting with the SandboxFleet API. - Expanded README with prerequisites and quick start instructions. Signed-off-by: zhoujinyu <2319109590@qq.com>
1 parent 5dae74b commit 55b3a06

58 files changed

Lines changed: 6051 additions & 8 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/bin/
2+
*.test

README.md

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,51 @@
11
# SandboxFleet
22

33
SandboxFleet runs multiple isolated AI agent sandboxes as slots inside shared
4-
Kubernetes Worker Pods.
4+
Kubernetes Worker Pods. Sandboxes are scheduled into fixed-capacity Slots;
5+
occupied Slots run through containerd (for example with a gVisor `runsc`
6+
handler).
57

6-
Kubernetes schedules Worker Pods onto nodes. SandboxFleet schedules Sandboxes
7-
into fixed-capacity Slots inside those Workers. Each occupied Slot runs as an
8-
independent gVisor sandbox through the standard containerd runtime stack.
8+
Design docs: [docs/design.md](docs/design.md), [docs/architecture.md](docs/architecture.md).
99

10-
SandboxFleet focuses on Slot capacity, placement, lifecycle, and cleanup. It
11-
delegates image management and sandbox creation to containerd and gVisor.
10+
## Prerequisites
1211

13-
See the [core concepts](docs/design.md) and
14-
[architecture](docs/architecture.md) for the design.
12+
Install on your machine:
13+
14+
- `docker`
15+
- `kind`
16+
- `kubectl`
17+
- `go` (1.26+)
18+
19+
## Quick start (deploy + verify)
20+
21+
From the repository root:
22+
23+
```bash
24+
# 1. Create a kind cluster, build images, and install SandboxFleet
25+
./hack/deploy-kind.sh
26+
27+
# 2. Run end-to-end tests against that cluster (does not redeploy)
28+
./hack/verify-e2e.sh
29+
```
30+
31+
What this checks: create a Pool, create a Sandbox, run a command via Exec, then
32+
delete the Sandbox.
33+
34+
Optional cleanup:
35+
36+
```bash
37+
./hack/cleanup-kind.sh
38+
```
39+
40+
## Notes
41+
42+
- `deploy-kind.sh` writes kubeconfig to `bin/KUBECONFIG` and runtime selection to
43+
`bin/runtime.env` (used by `verify-e2e.sh`).
44+
- `WORKER_RUNTIME` selects which Worker image to build and load (`gvisor` default,
45+
or `runc`). Example: `WORKER_RUNTIME=runc ./hack/deploy-kind.sh` builds only the
46+
base image and sets `runtimeHandler=runc`.
47+
- `APPLY_SAMPLES=1` (default) also applies demo Pool/Sandbox manifests; e2e uses
48+
its own namespace and does not depend on those samples.
49+
- `APPLY_SAMPLES=0 ./hack/deploy-kind.sh` installs only the control plane.
50+
- Re-run tests later without rebuilding: `./hack/verify-e2e.sh`
51+
- Build Worker images alone: `./hack/build-worker-images.sh runc|gvisor|all`

api/v1alpha1/doc.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// +kubebuilder:object:generate=true
2+
// +groupName=sandboxfleet.io
3+
4+
package v1alpha1

api/v1alpha1/groupversion_info.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package v1alpha1
2+
3+
import (
4+
"k8s.io/apimachinery/pkg/runtime/schema"
5+
"sigs.k8s.io/controller-runtime/pkg/scheme"
6+
)
7+
8+
const GroupName = "sandboxfleet.io"
9+
10+
var (
11+
GroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"}
12+
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
13+
AddToScheme = SchemeBuilder.AddToScheme
14+
)

api/v1alpha1/sandbox_types.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package v1alpha1
2+
3+
import (
4+
corev1 "k8s.io/api/core/v1"
5+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
6+
)
7+
8+
const SandboxFinalizer = "sandboxfleet.io/runtime-cleanup"
9+
10+
type SandboxPhase string
11+
12+
const (
13+
SandboxPhasePending SandboxPhase = "Pending"
14+
SandboxPhaseStarting SandboxPhase = "Starting"
15+
SandboxPhaseRunning SandboxPhase = "Running"
16+
SandboxPhaseStopping SandboxPhase = "Stopping"
17+
SandboxPhaseFailed SandboxPhase = "Failed"
18+
)
19+
20+
const (
21+
ConditionReady = "Ready"
22+
ConditionScheduled = "Scheduled"
23+
ConditionWorkersReady = "WorkersReady"
24+
)
25+
26+
type ContainerSpec struct {
27+
// +kubebuilder:validation:MinLength=1
28+
Image string `json:"image"`
29+
Command []string `json:"command,omitempty"`
30+
Args []string `json:"args,omitempty"`
31+
Env []corev1.EnvVar `json:"env,omitempty"`
32+
}
33+
34+
// SandboxSpec defines one execution environment.
35+
type SandboxSpec struct {
36+
// +kubebuilder:validation:MinLength=1
37+
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="poolRef is immutable"
38+
PoolRef string `json:"poolRef"`
39+
40+
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="container is immutable"
41+
Container ContainerSpec `json:"container"`
42+
}
43+
44+
// Assignment identifies the Worker and Slot assigned to a Sandbox.
45+
type Assignment struct {
46+
Worker string `json:"worker"`
47+
SlotID int32 `json:"slotID"`
48+
}
49+
50+
type SandboxStatus struct {
51+
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
52+
Phase SandboxPhase `json:"phase,omitempty"`
53+
Assignment *Assignment `json:"assignment,omitempty"`
54+
Conditions []metav1.Condition `json:"conditions,omitempty"`
55+
}
56+
57+
// +kubebuilder:object:root=true
58+
// +kubebuilder:subresource:status
59+
// +kubebuilder:resource:scope=Namespaced,shortName=sf
60+
// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase`
61+
// +kubebuilder:printcolumn:name="Worker",type=string,JSONPath=`.status.assignment.worker`
62+
// +kubebuilder:printcolumn:name="Slot",type=integer,JSONPath=`.status.assignment.slotID`
63+
type Sandbox struct {
64+
metav1.TypeMeta `json:",inline"`
65+
metav1.ObjectMeta `json:"metadata,omitempty"`
66+
67+
Spec SandboxSpec `json:"spec"`
68+
Status SandboxStatus `json:"status,omitempty"`
69+
}
70+
71+
// +kubebuilder:object:root=true
72+
type SandboxList struct {
73+
metav1.TypeMeta `json:",inline"`
74+
metav1.ListMeta `json:"metadata,omitempty"`
75+
Items []Sandbox `json:"items"`
76+
}
77+
78+
func init() {
79+
SchemeBuilder.Register(&Sandbox{}, &SandboxList{})
80+
}

api/v1alpha1/sandboxpool_types.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package v1alpha1
2+
3+
import (
4+
corev1 "k8s.io/api/core/v1"
5+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
6+
)
7+
8+
type RuntimeBackend string
9+
10+
const RuntimeBackendCRI RuntimeBackend = "cri"
11+
12+
type CRIRuntimeConfig struct {
13+
// RuntimeHandler is the CRI runtime handler name configured in the Worker's
14+
// containerd. It is an opaque string (for example "runsc" or "runc");
15+
// SandboxFleet does not interpret runtime-specific values.
16+
// +kubebuilder:validation:MinLength=1
17+
RuntimeHandler string `json:"runtimeHandler"`
18+
}
19+
20+
// +kubebuilder:validation:XValidation:rule="self.backend != 'cri' || has(self.cri)",message="cri configuration is required for the cri backend"
21+
type RuntimeConfig struct {
22+
// +kubebuilder:validation:Enum=cri
23+
Backend RuntimeBackend `json:"backend"`
24+
25+
// +optional
26+
CRI *CRIRuntimeConfig `json:"cri,omitempty"`
27+
}
28+
29+
// SandboxPoolSpec defines a homogeneous group of Workers.
30+
type SandboxPoolSpec struct {
31+
// +kubebuilder:validation:Minimum=0
32+
Workers int32 `json:"workers"`
33+
34+
// +kubebuilder:validation:Minimum=1
35+
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="slotsPerWorker is immutable"
36+
SlotsPerWorker int32 `json:"slotsPerWorker"`
37+
38+
// SlotResources is the resource budget for each Slot.
39+
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="slotResources is immutable"
40+
SlotResources corev1.ResourceRequirements `json:"slotResources,omitempty"`
41+
42+
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="runtime is immutable"
43+
Runtime RuntimeConfig `json:"runtime"`
44+
}
45+
46+
type SandboxPoolStatus struct {
47+
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
48+
CurrentWorkers int32 `json:"currentWorkers,omitempty"`
49+
ReadyWorkers int32 `json:"readyWorkers,omitempty"`
50+
UsedSlots int32 `json:"usedSlots,omitempty"`
51+
AvailableSlots int32 `json:"availableSlots,omitempty"`
52+
Conditions []metav1.Condition `json:"conditions,omitempty"`
53+
}
54+
55+
// +kubebuilder:object:root=true
56+
// +kubebuilder:subresource:status
57+
// +kubebuilder:resource:scope=Namespaced,shortName=sfp
58+
// +kubebuilder:printcolumn:name="Workers",type=integer,JSONPath=`.status.currentWorkers`
59+
// +kubebuilder:printcolumn:name="Ready",type=integer,JSONPath=`.status.readyWorkers`
60+
// +kubebuilder:printcolumn:name="Available",type=integer,JSONPath=`.status.availableSlots`
61+
type SandboxPool struct {
62+
metav1.TypeMeta `json:",inline"`
63+
metav1.ObjectMeta `json:"metadata,omitempty"`
64+
65+
Spec SandboxPoolSpec `json:"spec"`
66+
Status SandboxPoolStatus `json:"status,omitempty"`
67+
}
68+
69+
// +kubebuilder:object:root=true
70+
type SandboxPoolList struct {
71+
metav1.TypeMeta `json:",inline"`
72+
metav1.ListMeta `json:"metadata,omitempty"`
73+
Items []SandboxPool `json:"items"`
74+
}
75+
76+
func init() {
77+
SchemeBuilder.Register(&SandboxPool{}, &SandboxPoolList{})
78+
}

0 commit comments

Comments
 (0)