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
- func ActivityPath(dir, uuid string) string
- func MarshalConfig(cfg Config) ([]byte, error)
- func ParseActivity(content string) (time.Time, error)
- func Serve(ctx context.Context, dir, addr string, rt dockerruntime.Runtime, ...) error
- type Activity
- type Agent
- type Config
- type ContainerEvent
- type ContainerState
- type Docker
- type Enrollment
- type Executor
- type FileActivity
- type Observation
- type Resource
- type Route
- type RuntimeDocker
- func (d *RuntimeDocker) Inspect(ctx context.Context, name string) (ContainerState, error)
- func (d *RuntimeDocker) ListManaged(ctx context.Context) ([]string, error)
- func (d *RuntimeDocker) Start(ctx context.Context, name string) error
- func (d *RuntimeDocker) Stop(ctx context.Context, name string) error
- func (d *RuntimeDocker) StreamEvents(ctx context.Context, handler func(ContainerEvent)) error
- type WakeContainer
- type Waker
Constants ¶
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.
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).
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 )
const RoutesFile = "routes.json"
RoutesFile is the routing table filename inside the waker directory.
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 ¶
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 ¶
MarshalConfig renders a routing table for the control plane to deposit.
func ParseActivity ¶
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 ¶
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.
type Config ¶
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 ¶
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 ¶
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 ¶
NewExecutor builds an executor over the local runtime and host tree.
func (*Executor) Cancel ¶
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.
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.