Documentation
¶
Overview ¶
Package docker is the container capability: the Docker Engine reached over its API socket, granted to the code that runs containers.
It wraps the upstream Docker Engine SDK client (github.com/moby/moby/client) rather than the docker CLI run through ipc/proc. The SDK is already the client CSF's local simulations consume, it is typed end to end (no parsing of CLI output), and it gives the owner of a container its whole lifecycle — create, wait, start, kill, logs, remove — as separate calls, which is what cleanup on cancellation needs. Talking to the Engine socket is a kernel I/O crossing, so the client is constructed here, under ipc, and nowhere else.
A binary constructs one ContainerHost and passes it to whatever runs containers. Consumers that need only part of the Engine API declare their own narrow interface, which ContainerHost satisfies; ContainerHost.RunSandboxed is the bounded, isolated run-to-completion that sandboxed evaluation and the node executor are built on; ContainerHost.EnsureService keeps one long-lived named container up, such as the database CSF owns, and ContainerHost.Exec runs a command inside it.
Index ¶
- Constants
- Variables
- type ContainerHost
- func (host *ContainerHost) ContainerUsage(started StartedContainer) (sandbox.Usage, error)
- func (host *ContainerHost) EnsureService(ctx context.Context, spec ServiceSpec) (ServiceState, error)
- func (host *ContainerHost) Exec(ctx context.Context, name string, spec ExecSpec) (ExecResult, error)
- func (host *ContainerHost) InspectService(ctx context.Context, name string) (ServiceState, error)
- func (host *ContainerHost) RemoveContainer(ctx context.Context, id string) error
- func (host *ContainerHost) RunSandboxed(ctx context.Context, spec SandboxSpec) (result SandboxResult, err error)
- func (host *ContainerHost) SampleContainer(ctx context.Context, id string) (ContainerSample, error)
- func (host *ContainerHost) SignalService(ctx context.Context, name string, signal string) error
- func (host *ContainerHost) StartDetached(ctx context.Context, spec SandboxSpec) (StartedContainer, error)
- func (host *ContainerHost) StopService(ctx context.Context, name string) error
- type ContainerHostOption
- type ContainerSample
- type ExecResult
- type ExecSpec
- type ExitError
- type IContainerAPI
- type IServices
- type Mount
- type OwnedLocation
- type OwnedRecord
- type OwnedService
- func (owned *OwnedService[Settings]) Ensure(ctx context.Context) (OwnedRecord[Settings], error)
- func (owned *OwnedService[Settings]) Record() (OwnedRecord[Settings], error)
- func (owned *OwnedService[Settings]) Status(ctx context.Context) (OwnedStatus, error)
- func (owned *OwnedService[Settings]) Stop(ctx context.Context) error
- type OwnedServiceDefinition
- type OwnedServiceOption
- type OwnedStatus
- type RecordError
- type SandboxResult
- type SandboxSpec
- type ServiceSpec
- type ServiceState
- type StartedContainer
Constants ¶
const ( // DefaultOutputBytes bounds each captured output stream of a sandboxed run. DefaultOutputBytes = 1 << 20 // DefaultPidsLimit bounds the processes a sandboxed container may create. DefaultPidsLimit = 256 // RemoveTimeout bounds the cleanup that removes a finished or canceled // sandbox, which runs even after the caller's context has ended. RemoveTimeout = 30 * time.Second )
const ModelServerLabel = "dev.csf.model-server"
ModelServerLabel marks a container that serves a model on request and is idle otherwise, such as a reranker: holding the GPU is not using it, so a GPU consumer that yields to others does not yield to it.
const ( // OwnedHealthLimit is how long Ensure waits for the health check. OwnedHealthLimit = 2 * time.Minute )
Variables ¶
var ( // ErrImageRequired is returned for a sandbox spec without an image. ErrImageRequired = errors.New("ipc/docker: sandbox image is required") // ErrInvalidOption is returned by NewContainerHost for a nil, empty or // conflicting option. ErrInvalidOption = errors.New("ipc/docker: invalid container host option") )
var ( // ErrServiceRecordLost reports a container with no record: what the record // held (a password) is gone, so the owner refuses to guess rather than // create a second one. ErrServiceRecordLost = errors.New("ipc/docker: the container exists but its record is missing") // ErrServiceNotHealthy reports a container whose health check did not pass // within [OwnedHealthLimit]. ErrServiceNotHealthy = errors.New("ipc/docker: the health check did not pass") // ErrServiceNotOwned reports a state directory that owns no such // container. ErrServiceNotOwned = errors.New("ipc/docker: this state directory owns no such container") )
var ErrNoContainer = errors.New("ipc/docker: no such container")
ErrNoContainer reports a named container the Engine does not have.
var ErrRecordInvalid = errors.New("ipc/docker: state record cannot be used")
ErrRecordInvalid is what every RecordError wraps.
Functions ¶
This section is empty.
Types ¶
type ContainerHost ¶
type ContainerHost struct {
IContainerAPI
// contains filtered or unexported fields
}
ContainerHost is the Docker Engine granted as a capability. Its Engine API methods are promoted from the client it wraps, so it satisfies a consumer's narrow interface directly.
func NewContainerHost ¶
func NewContainerHost(options ...ContainerHostOption) (*ContainerHost, error)
NewContainerHost connects the container capability. Construction does not contact the Engine; the first call does.
func (*ContainerHost) ContainerUsage ¶
func (host *ContainerHost) ContainerUsage(started StartedContainer) (sandbox.Usage, error)
ContainerUsage reads the running container's cpu and memory receipt from its cgroup, through its init process.
func (*ContainerHost) EnsureService ¶
func (host *ContainerHost) EnsureService(ctx context.Context, spec ServiceSpec) (ServiceState, error)
EnsureService brings the named container up: it pulls the image when the Engine lacks it, creates the container when none has the name, and starts it when it is not running. An existing container is never recreated, so its configuration is the one it was created with.
func (*ContainerHost) Exec ¶
func (host *ContainerHost) Exec(ctx context.Context, name string, spec ExecSpec) (ExecResult, error)
Exec runs one command inside the running container and waits for it.
func (*ContainerHost) InspectService ¶
func (host *ContainerHost) InspectService(ctx context.Context, name string) (ServiceState, error)
InspectService reports the named container, or ErrNoContainer.
func (*ContainerHost) RemoveContainer ¶
func (host *ContainerHost) RemoveContainer(ctx context.Context, id string) error
RemoveContainer force-removes the container, killing every process in it. It outlives the caller's context, bounded by RemoveTimeout.
func (*ContainerHost) RunSandboxed ¶
func (host *ContainerHost) RunSandboxed(ctx context.Context, spec SandboxSpec) (result SandboxResult, err error)
RunSandboxed creates the container, starts it, waits for it to exit and collects its output, then removes it. The container is removed on every path — success, failure, and cancellation, which kills it first — so a sandbox never outlives the call. A nonzero exit returns the result together with an *ExitError.
func (*ContainerHost) SampleContainer ¶
func (host *ContainerHost) SampleContainer(ctx context.Context, id string) (ContainerSample, error)
SampleContainer takes one usage reading of a running container, at once rather than after the Engine's one-second prior sample.
func (*ContainerHost) SignalService ¶
SignalService sends signal, such as "HUP", to the named container's main process: how a long-lived service is told to reread its configuration.
func (*ContainerHost) StartDetached ¶
func (host *ContainerHost) StartDetached(ctx context.Context, spec SandboxSpec) (StartedContainer, error)
StartDetached creates and starts a long-lived container under the sandbox defaults and returns once it runs; the caller owns it and ends it with ContainerHost.RemoveContainer. A container that fails to start is removed.
func (*ContainerHost) StopService ¶
func (host *ContainerHost) StopService(ctx context.Context, name string) error
StopService stops the named container and leaves it, and its volumes, in place for a later ContainerHost.EnsureService.
type ContainerHostOption ¶
type ContainerHostOption func(settings *containerHostSettings) error
ContainerHostOption configures a ContainerHost.
func WithContainerAPI ¶
func WithContainerAPI(api IContainerAPI) ContainerHostOption
WithContainerAPI supplies an already-constructed Engine client, which the host then owns and closes.
func WithDockerHost ¶
func WithDockerHost(host string) ContainerHostOption
WithDockerHost names the Engine endpoint, such as unix:///var/run/docker.sock. The default is the SDK's platform default; the environment is never read.
func WithOutputBytes ¶
func WithOutputBytes(limit int64) ContainerHostOption
WithOutputBytes bounds each captured output stream of a sandboxed run.
type ContainerSample ¶
type ContainerSample struct {
// CPUNanoseconds is the container's cumulative cpu time.
CPUNanoseconds uint64
// SystemNanoseconds is the host's cumulative cpu time across every cpu.
SystemNanoseconds uint64
// OnlineCPUs is how many cpus the host had online at the reading.
OnlineCPUs uint32
// MemoryBytes is the memory the container's cgroup charges.
MemoryBytes uint64
}
ContainerSample is one reading of a running container's usage: cumulative cpu time beside the host's, so two readings give a cpu share, and the memory it holds now.
type ExecResult ¶
ExecResult is what a finished command left behind, each output bounded by the host's output limit.
type ExecSpec ¶
type ExecSpec struct {
Command []string
// User is the user[:group] the command runs as; empty means the
// container's.
User string
}
ExecSpec is one command run inside a running container.
type IContainerAPI ¶
type IContainerAPI interface {
ContainerCreate(ctx context.Context, options client.ContainerCreateOptions) (client.ContainerCreateResult, error)
ContainerInspect(ctx context.Context, id string, options client.ContainerInspectOptions) (client.ContainerInspectResult, error)
ContainerStart(ctx context.Context, id string, options client.ContainerStartOptions) (client.ContainerStartResult, error)
ContainerStop(ctx context.Context, id string, options client.ContainerStopOptions) (client.ContainerStopResult, error)
ContainerKill(ctx context.Context, id string, options client.ContainerKillOptions) (client.ContainerKillResult, error)
ContainerPause(ctx context.Context, id string, options client.ContainerPauseOptions) (client.ContainerPauseResult, error)
ContainerUnpause(ctx context.Context, id string, options client.ContainerUnpauseOptions) (client.ContainerUnpauseResult, error)
ContainerStats(ctx context.Context, id string, options client.ContainerStatsOptions) (client.ContainerStatsResult, error)
ContainerWait(ctx context.Context, id string, options client.ContainerWaitOptions) client.ContainerWaitResult
ContainerRemove(ctx context.Context, id string, options client.ContainerRemoveOptions) (client.ContainerRemoveResult, error)
ContainerLogs(ctx context.Context, id string, options client.ContainerLogsOptions) (client.ContainerLogsResult, error)
ContainerList(ctx context.Context, options client.ContainerListOptions) (client.ContainerListResult, error)
ImageList(ctx context.Context, options client.ImageListOptions) (client.ImageListResult, error)
ImagePrune(ctx context.Context, options client.ImagePruneOptions) (client.ImagePruneResult, error)
BuildCachePrune(ctx context.Context, options client.BuildCachePruneOptions) (client.BuildCachePruneResult, error)
DiskUsage(ctx context.Context, options client.DiskUsageOptions) (client.DiskUsageResult, error)
ImagePull(ctx context.Context, reference string, options client.ImagePullOptions) (client.ImagePullResponse, error)
ExecCreate(ctx context.Context, containerID string, options client.ExecCreateOptions) (client.ExecCreateResult, error)
ExecAttach(ctx context.Context, execID string, options client.ExecAttachOptions) (client.ExecAttachResult, error)
ExecInspect(ctx context.Context, execID string, options client.ExecInspectOptions) (client.ExecInspectResult, error)
Close() error
}
IContainerAPI is the part of the Docker Engine API client this capability uses and hands on. *client.Client satisfies it; a test substitutes a double.
type IServices ¶
type IServices interface {
EnsureService(ctx context.Context, spec ServiceSpec) (ServiceState, error)
InspectService(ctx context.Context, name string) (ServiceState, error)
StopService(ctx context.Context, name string) error
}
IServices is the part of the container capability an owned service uses. ContainerHost satisfies it.
type OwnedLocation ¶
type OwnedLocation struct {
Container string `json:"container"`
Volume string `json:"volume"`
Image string `json:"image"`
Host string `json:"host"`
Port uint16 `json:"port"`
}
OwnedLocation is where an owned container is, recorded on its first start and reused on every later one.
type OwnedRecord ¶
type OwnedRecord[Settings any] struct { OwnedLocation Settings Settings `json:"settings"` }
OwnedRecord is the record file: the location and the owner's own settings, such as a password, which only the record file holds.
func DecodeOwnedRecord ¶
func DecodeOwnedRecord[Settings any](path string, content []byte, validate func(settings Settings) *RecordError) (record OwnedRecord[Settings], previous bool, err error)
DecodeOwnedRecord decodes an owned record in the current shape or the previous flat one, strictly: an unknown field is an error, not ignored, and a required field that is missing is an error, not a zero value the host then acts on. previous reports a record in the previous shape, which the caller rewrites. validate checks the owner's settings; nil checks none.
type OwnedService ¶
type OwnedService[Settings any] struct { // contains filtered or unexported fields }
OwnedService is one long-lived container a state directory owns. Ensure creates it on the first start (a free port probed once and then bound for good, the owner's settings filled once, all recorded mode 0600) and reuses it on every later one; stopping the process that owns it leaves it running, because it holds state.
func NewOwnedService ¶
func NewOwnedService[Settings any](definition OwnedServiceDefinition[Settings], options ...OwnedServiceOption) (*OwnedService[Settings], error)
NewOwnedService validates the definition and the capabilities.
func (*OwnedService[Settings]) Ensure ¶
func (owned *OwnedService[Settings]) Ensure(ctx context.Context) (OwnedRecord[Settings], error)
Ensure brings the container up and returns its record, once its health check passes. The host paths it mounts are created first, so the Engine never creates them root-owned.
func (*OwnedService[Settings]) Record ¶
func (owned *OwnedService[Settings]) Record() (OwnedRecord[Settings], error)
Record reads the record, in the current or the previous shape, or reports ErrServiceNotOwned or a RecordError naming the file, the field and the fix.
func (*OwnedService[Settings]) Status ¶
func (owned *OwnedService[Settings]) Status(ctx context.Context) (OwnedStatus, error)
Status reports the location and the container, without the settings.
type OwnedServiceDefinition ¶
type OwnedServiceDefinition[Settings any] struct { // StateDirectory holds RecordFile, mode 0600; one container per state // directory. StateDirectory string RecordFile string // NamePrefix and a digest of StateDirectory name the container; its // volume adds "-data" and is mounted at DataDirectory. NamePrefix string DataDirectory string // Image is pinned by digest. Image string // Host is the address the port is published on: loopback, or a tailnet // address. Host netip.Addr // NewSettings fills the owner's settings on the first start only; nil // leaves them zero. NewSettings func() (Settings, error) // Spec is the container a record describes: environment, mounts, health // check and anything else of the owner's. Name, Image, HostAddress, // HostPort and the data volume are set from the record. Spec func(record OwnedRecord[Settings]) ServiceSpec // Validate checks the owner's settings each time the record is loaded, // naming a required field that is missing; nil checks none. Validate func(settings Settings) *RecordError }
OwnedServiceDefinition is what an owner decides; OwnedService does the rest.
type OwnedServiceOption ¶
type OwnedServiceOption func(capabilities *ownedCapabilities) error
OwnedServiceOption grants an OwnedService its capabilities.
func WithClock ¶
func WithClock(source clock.IClock) OwnedServiceOption
WithClock replaces the host's clock, which paces the health wait.
func WithListener ¶
func WithListener(listener ionet.IListener) OwnedServiceOption
WithListener grants the network capability the first start probes a free port with. Required.
func WithServices ¶
func WithServices(services IServices) OwnedServiceOption
WithServices grants the container capability. Required.
type OwnedStatus ¶
type OwnedStatus struct {
Location OwnedLocation `json:"location"`
State ServiceState `json:"state"`
}
OwnedStatus is what a lifecycle verb prints: the location and the container as the Engine sees it, never the owner's settings.
type RecordError ¶
RecordError names a state record a host refuses to act on: the file, the field, what is wrong with it and how to fix it.
func RequiredField ¶
func RequiredField(field string, fix string) *RecordError
RequiredField is a RecordError for a field a record must carry, for an owner's Validate: the decoder fills the path.
func (*RecordError) Error ¶
func (recordError *RecordError) Error() string
Error names the file, the field, the problem and the fix.
func (*RecordError) Unwrap ¶
func (recordError *RecordError) Unwrap() error
Unwrap is ErrRecordInvalid.
type SandboxResult ¶
type SandboxResult struct {
ContainerID string
ExitCode int64
Stdout []byte
Stderr []byte
StdoutTruncated bool
StderrTruncated bool
}
SandboxResult is what a finished sandbox left behind.
type SandboxSpec ¶
type SandboxSpec struct {
Image string
Command []string
Environment []string
WorkingDirectory string
User string
Mounts []Mount
Labels map[string]string
// Network is a Docker network mode; empty means no network at all.
Network string
// MemoryBytes and NanoCPUs are resource limits; zero means unlimited.
MemoryBytes int64
NanoCPUs int64
// PidsLimit bounds process creation; zero means [DefaultPidsLimit].
PidsLimit int64
// WritableRoot leaves the root filesystem writable.
WritableRoot bool
}
SandboxSpec is one isolated run to completion. Unset limits fall back to the sandbox defaults: no network, every capability dropped, no privilege escalation, a read-only root filesystem with a small /tmp, an init process, and DefaultPidsLimit.
type ServiceSpec ¶
type ServiceSpec struct {
Name string
Image string
Environment []string
Labels map[string]string
// Volumes maps a named volume to the path it is mounted at; the Engine
// creates a missing volume.
Volumes map[string]string
// Mounts are host paths visible inside the container. A missing source is
// an error rather than a root-owned directory the Engine creates.
Mounts []Mount
// Port is the container port ("5432/tcp") published on HostAddress at
// HostPort, which [ServiceState.HostPort] reports. A fixed HostPort keeps
// the same port across restarts; zero lets the Engine choose one on every
// start.
Port string
HostAddress netip.Addr
HostPort uint16
// ExtraHostAddresses publish Port at the same HostPort on each further
// address, such as a tailnet address beside the loopback one; they need a
// fixed HostPort, so every address serves the one recorded port.
ExtraHostAddresses []netip.Addr
// Command replaces the image's default arguments; empty keeps them.
Command []string
// HealthCheck is the command the Engine runs every HealthInterval;
// [ServiceState.Health] reports its verdict.
HealthCheck []string
HealthInterval time.Duration
// GPU grants the container the host's GPUs through the nvidia driver.
GPU bool
}
ServiceSpec is one long-lived named container: created once, kept across restarts of the caller, and restarted by the Engine unless stopped. Its data lives in named volumes, which removing the container never removes.
type ServiceState ¶
type ServiceState struct {
ID string `json:"id"`
Status string `json:"status"`
Running bool `json:"running"`
// Health is the health check's verdict: starting, healthy or unhealthy.
Health string `json:"health"`
HostPort uint16 `json:"host_port"`
}
ServiceState is what the Engine reports of a named container.
type StartedContainer ¶
type StartedContainer struct {
ID string
// Pid is the host PID of the container's init process.
Pid int
}
StartedContainer is a container ContainerHost.StartDetached left running.