Documentation
¶
Overview ¶
Package sandbox — images.go manages base images for sandboxes.
Images are stored as rootfs.ext4 + vmlinux kernel pairs. Each image has a name, and sandboxes reference images by name.
Directory layout:
{ImagesDir}/
├── default/
│ ├── rootfs.ext4
│ └── vmlinux
├── python312/
│ ├── rootfs.ext4
│ └── vmlinux
└── node22/
├── rootfs.ext4
└── vmlinux
Package sandbox manages Firecracker microVM lifecycles.
Architecture:
Manager ├── CreateSandbox() → spawn jailer + firecracker, wait for vsock agent ├── ExecInSandbox() → connect vsock, send ExecRequest, return ExecResponse ├── DestroySandbox() → SIGKILL process, cleanup state dir + tap + DB ├── Reconcile() → startup recovery of orphaned VMs └── cidAllocator → atomic counter for unique vsock CIDs
Package sandbox — network.go implements per-sandbox network isolation.
Default policy: DENY ALL. Each sandbox gets a tap device on a bridge, but iptables rules block all traffic by default.
Available policies:
- "none" — no network access (default)
- "outbound" — outbound internet via NAT, no inbound, no inter-sandbox
- "full" — full network access (dangerous, for trusted workloads)
Implementation:
Host iptables chain: FCLK-{sandbox-id}
├── ACCEPT established,related (stateful return traffic)
├── DROP inter-sandbox (block tap-to-tap)
└── policy-specific rules
NAT (outbound policy):
POSTROUTING -s {vm-ip}/32 -o {wan-iface} -j MASQUERADE
Package sandbox — pool.go implements pre-warmed snapshot pools.
The pool maintains N ready-to-use VM snapshots per image. When a sandbox is requested, it restores from snapshot (~50ms) instead of cold-booting (~1-3s). The pool auto-replenishes in the background.
Pool lifecycle:
┌───────────┐ boot+snapshot ┌──────────────┐ restore ┌─────────┐
│ WARMING │──────────────────▶│ POOL (ready) │────────────▶│ CLAIMED │
│ (cold boot│ │ N snapshots │ │ (in use)│
│ + snap) │ │ per image │ │ │
└───────────┘ └──────────────┘ └─────────┘
▲ │ count < target
└───────────────────────────────┘ (auto-replenish)
Index ¶
- Variables
- func ApplyNetworkPolicy(tapDevice, vmIP string, policy NetworkPolicy, cfg NetworkConfig) error
- func RemoveNetworkPolicy(tapDevice, vmIP string, cfg NetworkConfig)
- type Config
- type ImageConfig
- type ImageInfo
- type ImageManager
- func (im *ImageManager) CreateFromDockerfile(ctx context.Context, name, dockerfilePath string) (*ImageInfo, error)
- func (im *ImageManager) CreateFromRegistry(ctx context.Context, name, source string, force bool, puller *registry.Puller) (*ImageInfo, error)
- func (im *ImageManager) Get(name string) (*ImageInfo, error)
- func (im *ImageManager) Ledger() *imagestate.Ledger
- func (im *ImageManager) List() ([]*ImageInfo, error)
- func (im *ImageManager) ListKernels() ([]*KernelInfo, error)
- func (im *ImageManager) ResolveKernel(version string) (string, error)
- func (im *ImageManager) SetEmitter(e imagestate.Emitter)
- func (im *ImageManager) SetInvalidator(p PoolInvalidator)
- func (im *ImageManager) Status(name string) *ImageInfo
- type KernelInfo
- type Manager
- func (m *Manager) ActiveCount() int
- func (m *Manager) CreateSandbox(ctx context.Context, apiKeyID, image string, ttl time.Duration, ...) (*store.Sandbox, error)
- func (m *Manager) DestroySandbox(ctx context.Context, id string) error
- func (m *Manager) ExecInSandbox(ctx context.Context, id string, req *protocol.ExecRequest) (*protocol.ExecResponse, error)
- func (m *Manager) ReadFileFromSandbox(ctx context.Context, id string, req *protocol.FileReadRequest) (*protocol.FileReadResponse, error)
- func (m *Manager) Reconcile(ctx context.Context) error
- func (m *Manager) SetPool(p *Pool)
- func (m *Manager) Shutdown(ctx context.Context)
- func (m *Manager) WaitForAgentAt(stateDir string, timeout time.Duration) error
- func (m *Manager) WriteFileInSandbox(ctx context.Context, id string, req *protocol.FileWriteRequest) (*protocol.FileWriteResponse, error)
- type NetworkConfig
- type NetworkPolicy
- type Pool
- type PoolConfig
- type PoolInvalidator
- type Reaper
- type VMResources
Constants ¶
This section is empty.
Variables ¶
var ErrAtCapacity = errors.New("at capacity")
ErrAtCapacity is returned when the sandbox manager can't create more VMs.
Functions ¶
func ApplyNetworkPolicy ¶
func ApplyNetworkPolicy(tapDevice, vmIP string, policy NetworkPolicy, cfg NetworkConfig) error
ApplyNetworkPolicy configures iptables rules for a sandbox.
func RemoveNetworkPolicy ¶
func RemoveNetworkPolicy(tapDevice, vmIP string, cfg NetworkConfig)
RemoveNetworkPolicy cleans up iptables rules for a sandbox.
Types ¶
type Config ¶
type Config struct {
// StateDir is the base directory for VM state (sockets, metadata).
// Each VM gets a subdirectory: {StateDir}/{sandbox-id}/
StateDir string
// FirecrackerBin is the path to the firecracker binary.
FirecrackerBin string
// JailerBin is the path to the jailer binary.
JailerBin string
// KernelPath is the path to the guest kernel image.
KernelPath string
// DefaultRootfs is the path to the default rootfs ext4 image.
DefaultRootfs string
// ImagesDir is the directory containing image subdirectories.
// Each image has {ImagesDir}/{name}/rootfs.ext4.
ImagesDir string
// BridgeName is the network bridge for VM tap devices.
BridgeName string
// VsockAgentPort is the port the in-VM agent listens on inside vsock.
VsockAgentPort uint32
// ExecTimeout is the default timeout for command execution.
ExecTimeout time.Duration
// MaxSandboxes is the maximum number of concurrent sandboxes.
MaxSandboxes int
// MaxVCPU is the max vCPUs per sandbox (0 = no limit).
MaxVCPU int
// MaxMemMiB is the max memory per sandbox in MiB (0 = no limit).
MaxMemMiB int
// DefaultVCPU is the default vCPU count when not specified.
DefaultVCPU int
// DefaultMemMiB is the default memory in MiB when not specified.
DefaultMemMiB int
// Metrics for recording phase timings (optional).
Metrics interface {
RecordCreatePhase(ctx context.Context, image, phase string, duration time.Duration)
}
}
Config holds sandbox manager configuration.
type ImageConfig ¶
type ImageConfig struct {
// ImagesDir is the base directory for all images.
ImagesDir string
// AgentBinaryPath is the path to the compiled pyro-agent binary.
// It gets injected into new rootfs images at /usr/bin/pyro-agent.
AgentBinaryPath string
// MaxImageSizeMB caps the estimated decompressed rootfs size for
// registry pulls. Σ(layer_size) × 1.3 must not exceed this. Zero
// applies defaultMaxImageSizeMB; explicit -1 disables (operators
// who want to opt out can set MaxImageSizeMB=-1).
MaxImageSizeMB int
}
ImageConfig configures image management.
type ImageInfo ¶
type ImageInfo struct {
Name string `json:"name"`
Status imagestate.Status `json:"status,omitzero"`
Source string `json:"source,omitzero"`
Digest string `json:"digest,omitzero"`
Error string `json:"error,omitzero"`
RootfsPath string `json:"rootfs_path,omitzero"`
KernelPath string `json:"kernel_path,omitzero"`
Size int64 `json:"size,omitzero"` // rootfs size in bytes
Labels map[string]string `json:"labels,omitzero"`
CreatedAt time.Time `json:"created_at,omitzero"`
}
ImageInfo describes a stored base image.
`omitzero` JSON tags keep the response payload terse for in-flight or disk-only views (e.g. a pulling entry has no rootfs path; a ready disk image without a meta sidecar has no digest).
type ImageManager ¶
type ImageManager struct {
// contains filtered or unexported fields
}
ImageManager handles base image lifecycle.
func NewImageManager ¶
func NewImageManager(cfg ImageConfig, log *slog.Logger) (*ImageManager, error)
NewImageManager creates an image manager.
func (*ImageManager) CreateFromDockerfile ¶
func (im *ImageManager) CreateFromDockerfile(ctx context.Context, name, dockerfilePath string) (*ImageInfo, error)
CreateFromDockerfile builds a rootfs from a Dockerfile. It uses docker/buildah to build the image, then exports the filesystem to an ext4 image with the pyro-agent binary injected.
func (*ImageManager) CreateFromRegistry ¶ added in v0.1.11
func (im *ImageManager) CreateFromRegistry(ctx context.Context, name, source string, force bool, puller *registry.Puller) (*ImageInfo, error)
CreateFromRegistry registers an image pull from a remote OCI registry.
Behavior depends on force and existing state:
- name absent → start async pull, return pulling info (handler 202).
- name present + ready on disk + !force → idempotent no-op, return existing ImageInfo with Status=ready (handler 200). No pull, no ledger touch.
- name present + force → invalidate warm snapshots, start a fresh pull, atomically swap the on-disk rootfs when extraction completes (handler 202).
- in-flight pull (pulling/extracting) + force → ErrForceDuringPull (handler 409). User must wait for the in-flight pull to settle.
The async pull drives the ledger through pulling → extracting → ready (or failed) in a background goroutine. Callers poll ImageManager.Status(name) or subscribe to the SSE stream.
On failure: non-force removes the partial image directory; force removes only the staged .new files so the prior ready image stays intact. The ledger records the error and the failed entry stays queryable for ~1h.
puller may be nil; in that case a default Puller is created.
func (*ImageManager) Get ¶
func (im *ImageManager) Get(name string) (*ImageInfo, error)
Get returns info for a specific image.
func (*ImageManager) Ledger ¶ added in v0.1.11
func (im *ImageManager) Ledger() *imagestate.Ledger
Ledger exposes the in-memory pull ledger so the API layer can surface in-flight and recently-failed pull state.
func (*ImageManager) List ¶
func (im *ImageManager) List() ([]*ImageInfo, error)
List returns all available images.
func (*ImageManager) ListKernels ¶
func (im *ImageManager) ListKernels() ([]*KernelInfo, error)
ListKernels returns all vmlinux-* kernels in the images dir, sorted by version descending.
func (*ImageManager) ResolveKernel ¶
func (im *ImageManager) ResolveKernel(version string) (string, error)
ResolveKernel returns the path for a kernel version, or the latest if version is empty.
func (*ImageManager) SetEmitter ¶ added in v0.1.11
func (im *ImageManager) SetEmitter(e imagestate.Emitter)
SetEmitter wires an event emitter for image lifecycle SSE events. The same emitter is installed on the underlying ledger.
func (*ImageManager) SetInvalidator ¶ added in v0.1.11
func (im *ImageManager) SetInvalidator(p PoolInvalidator)
SetInvalidator installs the snapshot-pool invalidator used by force-replace pulls. nil disables invalidation (force pulls still swap the rootfs but warm snapshots survive — only safe in tests).
func (*ImageManager) Status ¶ added in v0.1.11
func (im *ImageManager) Status(name string) *ImageInfo
Status returns the live view of an image: ledger entry if one exists (in-flight or recently-failed), otherwise the disk record. Returns nil if the image is unknown to both the ledger and the disk.
type KernelInfo ¶
type KernelInfo struct {
Version string `json:"version"`
Path string `json:"path"`
Size int64 `json:"size"`
}
KernelInfo describes an available guest kernel.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager handles Firecracker VM lifecycle operations.
func (*Manager) ActiveCount ¶
ActiveCount returns the number of running sandboxes.
func (*Manager) CreateSandbox ¶
func (m *Manager) CreateSandbox(ctx context.Context, apiKeyID, image string, ttl time.Duration, res VMResources) (*store.Sandbox, error)
CreateSandbox provisions a new Firecracker microVM.
func (*Manager) DestroySandbox ¶
DestroySandbox kills a VM and cleans up all resources.
func (*Manager) ExecInSandbox ¶
func (m *Manager) ExecInSandbox(ctx context.Context, id string, req *protocol.ExecRequest) (*protocol.ExecResponse, error)
ExecInSandbox runs a command inside a sandbox via vsock.
func (*Manager) ReadFileFromSandbox ¶
func (m *Manager) ReadFileFromSandbox(ctx context.Context, id string, req *protocol.FileReadRequest) (*protocol.FileReadResponse, error)
ReadFileFromSandbox reads a file from the in-VM agent.
func (*Manager) WaitForAgentAt ¶
WaitForAgentAt polls the vsock agent at a given UDS path until it responds.
func (*Manager) WriteFileInSandbox ¶
func (m *Manager) WriteFileInSandbox(ctx context.Context, id string, req *protocol.FileWriteRequest) (*protocol.FileWriteResponse, error)
WriteFileInSandbox sends a file to the in-VM agent for writing.
type NetworkConfig ¶
type NetworkConfig struct {
// WANInterface is the host's outbound network interface (e.g., "eth0").
WANInterface string
// SubnetCIDR is the subnet for sandbox VMs (e.g., "172.16.0.0/24").
SubnetCIDR string
}
NetworkConfig holds network isolation settings.
type NetworkPolicy ¶
type NetworkPolicy string
NetworkPolicy defines what network access a sandbox has.
const ( NetworkNone NetworkPolicy = "none" NetworkOutbound NetworkPolicy = "outbound" NetworkFull NetworkPolicy = "full" )
type Pool ¶
type Pool struct {
// contains filtered or unexported fields
}
Pool manages pre-warmed Firecracker snapshots for fast sandbox creation.
func (*Pool) Claim ¶
Claim takes a ready snapshot from the pool matching image and kernel. Returns nil if none available (caller should fall back to cold boot).
func (*Pool) Invalidate ¶ added in v0.1.11
Invalidate drains the ready slice for image and removes the corresponding snapshot dirs from disk. The replenishment loop refills at its own cadence. Idempotent — safe for unknown image names and repeat calls.
Used by force-replace pulls (slice 06): warm snapshots reference the old rootfs's blocks, so reusing them after the rootfs is swapped would corrupt new sandboxes. Running sandboxes already hold their own copy from manager.CreateSandbox and are unaffected.
type PoolConfig ¶
type PoolConfig struct {
// TargetSize is the number of warm snapshots to maintain per image.
TargetSize int
// SnapshotDir is where snapshot files are stored.
SnapshotDir string
// ReplenishInterval is how often to check and refill the pool.
ReplenishInterval time.Duration
// Images is the list of images to maintain warm pools for.
// If empty, discovers from ImagesDir.
Images []string
}
PoolConfig configures the snapshot pool.
type PoolInvalidator ¶ added in v0.1.11
type PoolInvalidator interface {
Invalidate(image string)
}
PoolInvalidator drops warm snapshots tied to an image. The snapshot pool implements this. Decoupled via interface so the image manager can be tested without spinning up a real pool.
type Reaper ¶
type Reaper struct {
// contains filtered or unexported fields
}
Reaper periodically destroys sandboxes that have exceeded their TTL.
┌──────────┐ tick ┌──────────────┐ expired? ┌───────────┐
│ Sleep │───────────▶│ Query expired │──── yes ─────▶│ Destroy │
│ interval │ │ sandboxes │ │ sandbox │
└──────────┘ └──────────────┘ └───────────┘
▲ │ no │
└────────────────────────┘ │
└───────────────────────────────────────────────────────┘
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package imageconfig defines the OCI image runtime config shared between host-side image registration (registry pull / Dockerfile build) and the in-VM agent's exec handler.
|
Package imageconfig defines the OCI image runtime config shared between host-side image registration (registry pull / Dockerfile build) and the in-VM agent's exec handler. |
|
Package imageops builds rootfs ext4 filesystems from OCI image layers.
|
Package imageops builds rootfs ext4 filesystems from OCI image layers. |
|
Package imagestate owns the in-memory ledger of in-flight and recently failed image pulls.
|
Package imagestate owns the in-memory ledger of in-flight and recently failed image pulls. |
|
Package registry pulls OCI/Docker images from a remote registry without a Docker daemon.
|
Package registry pulls OCI/Docker images from a remote registry without a Docker daemon. |