Files
mcp/internal/runtime/runtime.go
Kyle Isom d56f224359 Add unikernel runtime: run services as Nanos VMs under QEMU/KVM
Implements the hypervisor design's Phase 1: a second runtime.Runtime
backend (QEMU) that runs each service component as a Nanos unikernel VM
instead of a podman container, selected per-component via a new
runtime = "unikernel" service-def field.

- internal/runtime/qemu.go: QEMURuntime. Pull extracts the ELF from the
  OCI image; Run does `ops build` + boots qemu-system-x86_64 with KVM,
  user-mode net port-forwards, QMP control socket and serial console log;
  Stop/Remove/Inspect/List/Logs map onto VM lifecycle + state dir.
- proto/registry/servicedef: add runtime, memory_mb, vcpus fields
  (registry migration 5).
- agent: holds both runtimes; runtimeFor() selects per component;
  listAllContainers() merges containers + VMs so drift/status see both.
  Unikernel runtime auto-enables on nodes with /dev/kvm + ops.

Validated end-to-end on straylight: a test service deploys via
`mcp deploy --direct`, boots as a Nanos unikernel, serves HTTP through
the agent port-forward, and reports running via `mcp status`/`mcp logs`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 00:54:49 -07:00

76 lines
2.4 KiB
Go

package runtime
import (
"context"
"strings"
"time"
)
// ContainerSpec describes a container to create and run.
type ContainerSpec struct {
Name string // container name, format: <service>-<component>
Image string // full image reference
Network string // docker network name
User string // container user (e.g., "0:0")
Restart string // restart policy (e.g., "unless-stopped")
Ports []string // "host:container" port mappings
Volumes []string // "host:container" volume mounts
Cmd []string // command and arguments
Env []string // environment variables (KEY=VALUE)
// Unikernel-only fields (ignored by the container runtime).
MemoryMB int // guest memory in MB (default 256)
VCPUs int // guest vCPUs (default 1)
}
// ContainerInfo describes the observed state of a running or stopped container.
type ContainerInfo struct {
Name string
Image string
State string // "running", "stopped", "exited", etc.
Network string
User string
Restart string
Ports []string
Volumes []string
Cmd []string
Version string // extracted from image tag
Started time.Time // when the container started (zero if not running)
}
// Runtime is the container runtime abstraction. The first six methods are
// used by the agent for container lifecycle. The last three are used by the
// CLI for building and pushing images.
type Runtime interface {
Pull(ctx context.Context, image string) error
Run(ctx context.Context, spec ContainerSpec) error
Stop(ctx context.Context, name string) error
Remove(ctx context.Context, name string) error
Inspect(ctx context.Context, name string) (ContainerInfo, error)
List(ctx context.Context) ([]ContainerInfo, error)
Build(ctx context.Context, image, contextDir, dockerfile string) error
Push(ctx context.Context, image string) error
ImageExists(ctx context.Context, image string) (bool, error)
}
// ExtractVersion parses the tag from an image reference.
// Examples:
//
// "registry/img:v1.2.0" -> "v1.2.0"
// "registry/img:latest" -> "latest"
// "registry/img" -> ""
// "registry:5000/img:v1" -> "v1"
func ExtractVersion(image string) string {
// Strip registry/path prefix so that a port like "registry:5000" isn't
// mistaken for a tag separator.
name := image
if i := strings.LastIndex(image, "/"); i >= 0 {
name = image[i+1:]
}
if i := strings.LastIndex(name, ":"); i >= 0 {
return name[i+1:]
}
return ""
}