Documentation
¶
Overview ¶
Package scale is the opt-in, host-aware process auto-scaler (plan §8A). It scales container REPLICAS of one edge-fronted HTTP service — never VMs, never the whole project — and is conservative-by-construction: it REFUSES rather than queues, treats a refusal as an alertable signal, and on a small box collapses to a safe no-op (effective_max = 1).
This file is the candidacy gate. The decision core (controller hysteresis) and the load-bearing host-capacity guard are in controller.go / capacity.go. All three are pure so the safety properties are exhaustively testable; the watcher (watcher.go) supplies the inputs, applies the §0 gate + semaphore, and performs the scale.
Index ¶
- func Candidacy(s ServiceSpec) (ok bool, reason string)
- func MaxReplicas(in CapacityInput) (ceiling int, nearOOM bool, reason string)
- func StatefulImage(image string) bool
- type Action
- type Budget
- type CapacityInput
- type Config
- type Decision
- type EdgeReconciler
- type Key
- type Metrics
- type Policy
- type PolicyRow
- type Reserves
- type Scaler
- type ServiceSpec
- type State
- type Store
- func (s *Store) DeleteApp(ctx context.Context, app string) error
- func (s *Store) EnabledPolicies() (map[Key]PolicyRow, error)
- func (s *Store) LoadStates() (map[Key]State, error)
- func (s *Store) PolicyFor(k Key) (PolicyRow, bool, error)
- func (s *Store) SavePolicy(ctx context.Context, k Key, pr PolicyRow) error
- func (s *Store) SaveState(ctx context.Context, k Key, st State, now int64) error
- type Watcher
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Candidacy ¶
func Candidacy(s ServiceSpec) (ok bool, reason string)
Candidacy reports whether a service may be auto-scaled, with the first failing reason. Default is NOT scalable: every condition must hold. A stateful service is rejected with a clear reason — it is a config-file/cert-binding app (§7.4), not a scaling candidate.
func MaxReplicas ¶
func MaxReplicas(in CapacityInput) (ceiling int, nearOOM bool, reason string)
MaxReplicas returns the hard replica ceiling this service may run RIGHT NOW. It is never below 1 (a service always keeps its base replica) and is capped by the policy max and BOTH resource budgets. nearOOM=true means the box is critically low on memory and scaling is a no-op (effective_max = 1) — a wanted scale-up above this is a refusal the caller must surface as scale_refused_no_capacity.
func StatefulImage ¶
StatefulImage reports whether an image reference belongs to a known stateful / clustered family (C4). It strips the registry, tag, and digest, then matches the final repository path component against the denylist (so "ghcr.io/acme/postgres:16" and "postgres" both match, but "my-postgres-helper" does not).
Types ¶
type Budget ¶
type Budget struct {
HostTotal uint64 // total host resource
HostFree uint64 // measured-free right now
Reserved uint64 // everything NOT this service: control plane + edge + safety floor + OTHER apps' desired replicas
FreeFloor uint64 // keep at least this much free (applied to the measured budget)
PerReplica uint64 // this service's per-replica reservation (required, non-zero)
Current int // this service's current replica count
}
Budget is one resource's accounting (memory in bytes, or CPU in milli-units).
type CapacityInput ¶
type CapacityInput struct {
Mem Budget
CPU Budget
PolicyMax int // operator's configured max_replicas
PerReplicaMemFloor uint64 // an implausibly small per-replica mem reservation is rejected
NearOOMFreeBytes uint64 // host mem free below this → effective_max collapses to 1
}
CapacityInput is everything MaxReplicas needs for one service this tick.
type Config ¶
type Config struct {
Store *Store
Alerts *alertstore.Store // nil → refusals are logged only
Snap func() *monitor.Snapshot
Sem *dockerexec.Semaphore
Scaler Scaler
Edge EdgeReconciler // optional
Reserves Reserves
Log *slog.Logger
Interval time.Duration
WritePlaneOK bool
HostCPUMilli uint64 // total host CPU (milli); 0 disables the CPU budget
IsCandidate func(app, service string) (ServiceSpec, bool) // C1–C6 from compose; nil → trust the policy opt-in
Now func() int64
}
Config configures the auto-scaling Watcher.
type Decision ¶
Decision is the pure outcome; the watcher persists Next and, on Up/Down, performs the scale (+ edge-pool reconcile). On Refused it raises scale_refused_no_capacity.
type EdgeReconciler ¶
type EdgeReconciler interface {
ReconcilePool(ctx context.Context, app, service string, replicas int) error
}
EdgeReconciler updates the edge replica pool for a service after a count change (discover live replicas → validated pool → reload). May be nil (then the route's single upstream DNS-round-robins across replicas).
type Metrics ¶
Metrics is the per-service signal: per-replica CPU MEAN and mem MAX aggregated across the running replicas (plan §8A), plus whether every replica is healthy.
type Policy ¶
type Policy struct {
Min, Max int
UpCPUPct float64
UpMemPct float64
DownCPUPct float64
DownMemPct float64
BreachForSecs int64
CooldownUpSecs int64
CooldownDownSecs int64
}
Policy is the operator's scaling policy for one service.
type Reserves ¶
type Reserves struct {
MemReserveBytes uint64 // control plane + edge + safety floor (memory)
CPUReserveMilli uint64 // control plane + edge (cpu)
MemFreeFloor uint64 // keep at least this much memory free (measured budget)
CPUFreeFloor uint64
NearOOMFreeBytes uint64
PerReplicaMemFloor uint64
}
Reserves are the host headroom the capacity guard subtracts before funding this app's replicas (control plane + edge slice + a safety floor), plus the near-OOM and per-replica-floor guards.
type Scaler ¶
Scaler performs the actual replica change for a service (static-argv `docker compose up -d --no-deps --no-recreate --scale <svc>=<n>`). The watcher calls it only after the §0 gate + a non-blocking semaphore acquire, holding the one-docker-child semaphore.
type ServiceSpec ¶
type ServiceSpec struct {
Name string
EdgeUpstream bool // C1: an edge HTTP upstream with a known internal port
L4Upstream bool // C1 (alt): a managed L4 (TCP/UDP) upstream — fronted by an edge l4_route, replicas internal-only
FixedHostPort bool // C2 (disqualifies): publishes a fixed host port the LB does NOT own
RWVolume bool // C3 (disqualifies): has an exclusive read-write volume
Stateful bool // C4 (disqualifies): a DB/broker/coordination store
IdentityPlaceholder bool // C5 (disqualifies): a deploy-time identity (node cookie/name/seed/host-bound port)
StatelessContract bool // C6: honors the stateless restart contract (operator attests)
OptedIn bool // C7: the operator explicitly enabled scaling for this service
}
ServiceSpec is the deploy-time view of one service, derived from its compose definition + the managed edge routes. Candidacy is re-evaluated on every deploy / config change; a service that GAINS a host port or RW volume loses candidacy and is scaled back to 1.
type State ¶
type State struct {
Replicas int
BreachSince int64 // unix sec; when the current up-breach started (0 = not breaching)
LastChange int64 // unix sec; last scale action (for the cooldowns)
}
State is the persisted controller state for one service. Replicas is the DESIRED count the controller is driving toward (the watcher reconciles observed→desired).
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store persists scaling policies (operator opt-in + thresholds) and controller state (desired replicas + hysteresis timers, recovered on restart).
func (*Store) DeleteApp ¶
SavePolicy validates + upserts a policy. A policy must pass Policy.Valid() and carry non-zero per-replica reservations before it can be enabled. DeleteApp removes ALL scaling policies and controller state for every service of an app. Used by the app-delete teardown.
func (*Store) EnabledPolicies ¶
EnabledPolicies returns every enabled policy keyed by (app,service).
func (*Store) LoadStates ¶
LoadStates returns all persisted controller states.
func (*Store) PolicyFor ¶
PolicyFor returns the policy for one service (enabled flag included), or ok=false.