agent

package
v0.0.0-...-5b32b38 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 3, 2026 License: Apache-2.0 Imports: 27 Imported by: 0

Documentation

Overview

Agent loop (ADR-040 phase 1): the helper pushes outbound observations — Docker state transitions of managed containers, scale-to-zero wakes, a heartbeat — to the control plane over HTTPS, authenticated by a per-server token injected at container creation. The loop is an accelerator, never a dependency: it is bounded, best-effort, isolated from the wake path, and its silence degrades to the control plane's SSH scans.

Executor (ADR-052): the agent side of the typed command channel. Each command names one dockerruntime.Runtime method and carries its SDK-typed params; the executor unmarshals, calls the local daemon, and answers with a result or stream chunks. It decides nothing — policy, ordering and retries stay on the control plane (ADR-001) — and it executes nothing outside the enumerated vocabulary.

Package waker implements the scale-to-zero helper (ADR-036, proxy-contract §8): a reverse-proxy run as a mode of the AkerDock binary, in front of every scale_to_zero resource on a server. It wakes the target container on the first request (docker start, await healthy, hold-and-forward) and dates activity into a per-resource file the control plane reads over SSH.

Docker and the filesystem are behind interfaces so the wake decision, the limits (§8.3) and the activity accounting are unit-testable without a daemon.

Index

Constants

View Source
const DefaultDir = "/var/lib/akerdock/waker"

DefaultDir is where the control plane deposits the routing table and the waker writes activity files (§8.1). Both sit under the server's AkerDock root.

View Source
const DefaultListenAddr = ":8080"

DefaultListenAddr is the port the waker listens on. It MUST match proxy.AgentPort — the dynamic file routes scale-to-zero traffic to http://akerdock-agent:8080 (ADR-036 §2, renamed by ADR-056).

View Source
const (
	// MaxHoldBody is the largest request body held across a cold start (§8.3):
	// beyond it the waker returns 503 rather than buffer a big upload while the
	// target boots.
	MaxHoldBody = 1 << 20 // 1 MiB

)
View Source
const RoutesFile = "routes.json"

RoutesFile is the routing table filename inside the waker directory.

View Source
const UptimeProbeHeader = "X-AkerDock-Uptime"

UptimeProbeHeader marks an AkerDock uptime check (ADR-037): the waker wakes and forwards it (so the check measures the app truly up), but does NOT record it as activity — otherwise monitoring would keep a scale-to-zero app awake forever. The app wakes briefly per check, then sleeps again.

Variables

This section is empty.

Functions

func ActivityPath

func ActivityPath(dir, uuid string) string

ActivityPath is the per-resource activity file the control plane reads over SSH. The value is decimal Unix seconds — trivial to `cat` and parse remotely.

func MarshalConfig

func MarshalConfig(cfg Config) ([]byte, error)

MarshalConfig renders a routing table for the control plane to deposit.

func ParseActivity

func ParseActivity(content string) (time.Time, error)

ParseActivity parses the content of an activity file (decimal Unix seconds) into a time. The control plane uses it after reading the file over SSH.

func Serve

func Serve(ctx context.Context, dir, addr string, rt dockerruntime.Runtime, agentCfg Enrollment, logger *slog.Logger) error

Serve runs the waker HTTP server on addr, forwarding for the routing table in dir/routes.json and waking targets on demand. The routing file is reloaded when its modification time changes, so the control plane can add or remove scale-to-zero resources without restarting the container. agent, when enabled (ADR-040 enrollment injected at container creation), pushes outbound observations alongside — its failure modes never touch the wake path.

Types

type Activity

type Activity interface {
	Record(uuid string, at time.Time) error
}

Activity records the last request time of a resource so the control plane's sleep pass can read it over SSH (ADR-036 §2).

type Agent

type Agent struct {

	// Settle is how long the agent waits after a container event before
	// reading the real state: a replacement (stop old → rm → rename
	// candidate) must have landed, or the reading describes a container that
	// no longer exists. Resync is the safety net that repairs any state a
	// missed event left stale.
	Settle time.Duration
	Resync time.Duration

	Heartbeat time.Duration
	Flush     time.Duration
	Backoff   time.Duration
	// WSCooldown is how long the agent stays on the POST fallback after a
	// WebSocket failure before re-dialing (ADR-041 §4).
	WSCooldown time.Duration
	// DisableWS forces the POST fallback (tests, or an egress known to break
	// WebSockets).
	DisableWS bool
	// Executor serves the ADR-052 typed commands when the control plane
	// speaks v2; nil keeps the channel observation-only (v1).
	Executor *Executor
	// contains filtered or unexported fields
}

Agent buffers observations and flushes them in batches. Overflow drops the OLDEST entries: observations are hints the SSH scans re-derive anyway, and the traffic path must never block on the control plane (ADR-040 §7).

func NewAgent

func NewAgent(cfg Enrollment, events eventStreamer, logger *slog.Logger) *Agent

NewAgent builds an agent; events may be nil (no Docker stream). When the source also reads container state and lists managed containers (the socket client does), the agent verifies every event and resyncs periodically.

func (*Agent) Push

func (a *Agent) Push(o Observation)

Push queues an observation, dropping the oldest when full. Never blocks.

func (*Agent) Run

func (a *Agent) Run(ctx context.Context)

Run drives the stream, the heartbeat and the flusher until ctx ends. Each loop is panic-contained: a bug here must never take the wake path down.

type Config

type Config struct {
	Routes    []Route    `json:"routes"`
	Resources []Resource `json:"resources"`
}

Config is the routing table the control plane deposits for the waker (/var/lib/akerdock/waker/routes.json). The waker never generates it.

func LoadConfig

func LoadConfig(dir string) (Config, error)

LoadConfig reads the routing table the control plane deposited.

type ContainerEvent

type ContainerEvent struct {
	Container string // container name
	Action    string // start, die, stop, oom, health_status: healthy, …
	At        time.Time
}

ContainerEvent is the slice of a Docker event the agent pushes (ADR-040): a state transition of an akerdock.managed container.

type ContainerState

type ContainerState struct {
	Running bool
	// Health is the healthcheck status: "healthy", "unhealthy", "starting", or
	// "none" when the image declares no healthcheck (§8.2, running-stable path).
	Health string
}

ContainerState is the subset of `docker inspect` the waker needs.

type Docker

type Docker interface {
	Start(ctx context.Context, container string) error
	Inspect(ctx context.Context, container string) (ContainerState, error)
	Stop(ctx context.Context, container string) error
}

Docker is the container control the waker is code-limited to (§8.1): it only starts, inspects and stops akerdock.managed containers — never create/remove/build. Stop exists solely to roll back a failed wake: it is the same operation the control plane's sleep performs.

type Enrollment

type Enrollment struct {
	InstanceURL string
	Token       string
}

Enrollment is the enrollment injected by the control plane (ADR-040 §3).

func (Enrollment) Enabled

func (c Enrollment) Enabled() bool

Enabled reports whether the enrollment is complete; otherwise the helper runs waker-only.

type Executor

type Executor struct {
	// contains filtered or unexported fields
}

Executor runs typed commands against the local runtime.

func NewExecutor

func NewExecutor(rt dockerruntime.Runtime, host *hostops.Local, logger *slog.Logger) *Executor

NewExecutor builds an executor over the local runtime and host tree.

func (*Executor) Cancel

func (e *Executor) Cancel(id int64)

Cancel aborts the command with this id, if still in flight — the control plane's ctx ended, or a stream consumer closed.

func (*Executor) DeliverInput

func (e *Executor) DeliverInput(chunk *agentwire.StreamChunk)

DeliverInput routes one input chunk to its attach session: data goes to the exec's stdin, EOF closes it (output keeps flowing). Writes land on the local daemon socket, so the channel's read loop is never meaningfully stalled here.

func (*Executor) Execute

func (e *Executor) Execute(ctx context.Context, cmd agentwire.Command, send func(agentwire.Frame) error)

Execute runs one command to completion and delivers its result — and, for streaming methods, its chunks — through send. The channel runs each command on its own goroutine and serializes send; a send failure means the channel died, and the command's ctx dies with it.

type FileActivity

type FileActivity struct {
	Dir string
}

FileActivity records last-activity timestamps as one file per resource, written atomically (write-temp + rename) so the control plane's SSH read never sees a half-written value.

func (FileActivity) Record

func (f FileActivity) Record(uuid string, at time.Time) error

Record writes the resource's last-activity timestamp atomically.

type Observation

type Observation = agentwire.Observation

Observation is one pushed fact — the wire type lives in agentwire, shared with the control plane's ingestion. Types: "container_state" (a managed container changed state), "stz_woken" (a wake started containers), "heartbeat" (the agent is alive).

type Resource

type Resource struct {
	UUID       string          `json:"uuid"`
	Containers []string        `json:"containers"`
	WakeSet    []WakeContainer `json:"wake_set,omitempty"`
}

Resource is the wake unit: the containers started together (a whole compose stack, or a single container for a plain app). Containers is the full set in topological start order (§2.6) — used for the readiness fast-path and the waiting page. WakeSet carries the compose depends_on graph: each container starts as soon as ITS dependencies are satisfied, like `docker compose up` — independent services wake in parallel, never hostage to an unrelated sibling's healthcheck. Absent WakeSet (older configs, plain apps), the containers are dependency-free.

type Route

type Route struct {
	Host         string `json:"host"`
	ResourceUUID string `json:"resource_uuid"`
	Container    string `json:"container"`
	Port         int    `json:"port"`
}

Route maps one public host to the container that serves it. Several routes may share a ResourceUUID (a compose preview with one host per service).

type RuntimeDocker

type RuntimeDocker struct {
	// contains filtered or unexported fields
}

RuntimeDocker serves the waker's code-limited Docker interface (§8.1) and the agent's observation sources from the shared runtime adapter (ADR-051). The restriction to start/inspect/stop plus read-only listing and events is enforced here, by what this type exposes — the runtime underneath can do more, this helper cannot.

func NewRuntimeDocker

func NewRuntimeDocker(rt dockerruntime.Runtime) *RuntimeDocker

NewRuntimeDocker wraps rt — for the waker, the local-socket runtime.

func (*RuntimeDocker) Inspect

func (d *RuntimeDocker) Inspect(ctx context.Context, name string) (ContainerState, error)

Inspect reports the container's running/health state.

func (*RuntimeDocker) ListManaged

func (d *RuntimeDocker) ListManaged(ctx context.Context) ([]string, error)

ListManaged returns the names of the akerdock.managed containers on this host, running or not — the agent's periodic resync (ADR-040): a missed or misread event must never leave the control plane with a stale observed state forever.

func (*RuntimeDocker) Start

func (d *RuntimeDocker) Start(ctx context.Context, name string) error

Start starts a container by name or id; already running is a success.

func (*RuntimeDocker) Stop

func (d *RuntimeDocker) Stop(ctx context.Context, name string) error

Stop stops a container — the rollback of a failed wake, the same operation the control plane's sleep performs, so a half-woken stack does not crash-loop while the control plane believes it asleep.

func (*RuntimeDocker) StreamEvents

func (d *RuntimeDocker) StreamEvents(ctx context.Context, handler func(ContainerEvent)) error

StreamEvents follows the daemon's event stream, filtered to container events of akerdock.managed containers, and calls handler for each until ctx ends or the stream breaks (the caller reconnects with backoff).

type WakeContainer

type WakeContainer struct {
	Container string `json:"container"`
	// Needs lists the containers this one waits for before starting — the
	// compose depends_on edges, container-name resolved. A dependency this
	// wake started must be READY (healthy, or running-stable); one already
	// running before the wake satisfies its edge as-is.
	Needs []string `json:"needs,omitempty"`
}

WakeContainer is one member of the wake set with its start dependencies.

type Waker

type Waker struct {
	WakeTimeout time.Duration
	Poll        time.Duration
	StableFor   time.Duration
	// Logger records wake failures with their real cause; nil uses slog.Default.
	Logger *slog.Logger

	// OnWake, when set, is told about every wake that actually started
	// containers — the agent pushes it as an "stz_woken" observation
	// (ADR-040) so the control plane can flip the resource's status without
	// waiting for its next SSH scan. Called outside any lock; must not block.
	OnWake func(resourceUUID string)
	// contains filtered or unexported fields
}

Waker is the http.Handler front of the scale-to-zero resources.

func New

func New(cfg Config, docker Docker, activity Activity, now func() time.Time) *Waker

New builds a Waker from a routing config. now defaults to time.Now.

func (*Waker) ServeHTTP

func (w *Waker) ServeHTTP(rw http.ResponseWriter, req *http.Request)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL