feat(scheduling): replica spread + resource-aware auto placement (#56)

services.spread distributes replicas across every online, schedulable
agent (replica i → agents[i mod n], deterministic). auto placement now
orders by real headroom — memory utilisation, cpu usage, then container
count — instead of count alone.

Lifecycle (start/stop/restart/delete) and GET /runtime are now driven
by container_instances inventory, so remote replicas are visible and
tear down wherever they run — covers spread, pins, and stale pin
leftovers. Spread and explicit pins are mutually exclusive.

Generated with [Devin](https://devin.ai)

Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomáš DvořákandDevin authored and GitHub committed 2026-10-09 17:03:26 +02:00
1 parent bc93c83cf9
commit dd5ce80ce2
21 files changed
+469 -100

No files matched your search

@@ -57,6 +57,9 @@ type RuntimeSpec struct {
BasicAuthUsers string // htpasswd-format user:hash pairs, comma-separated
// NodeID pins the service to a node agent; "" runs on the local host.
NodeID string
// Spread distributes replicas across all online, schedulable agents
// instead of a single node. Mutually exclusive with NodeID.
Spread bool
}
// RuntimeContainer describes one live replica.
@@ -156,6 +159,12 @@ func (de *DeploymentEngine) ListServiceContainers(ctx context.Context, serviceID
// otherwise only the replica delta is applied. A pinned NodeID routes the
// whole reconcile to that agent's command queue instead of local Docker.
func (de *DeploymentEngine) ReconcileService(ctx context.Context, spec RuntimeSpec) (*RuntimeState, error) {
if spec.Spread && spec.NodeID == "" {
if de.nodeRunner == nil {
return nil, fmt.Errorf("service has spread placement but remote dispatch is unavailable")
}
return de.nodeRunner.ReconcileSpread(ctx, spec)
}
if spec.NodeID != "" {
if de.nodeRunner == nil {
return nil, fmt.Errorf("service is pinned to node %s but remote dispatch is unavailable", spec.NodeID)
@@ -526,6 +535,15 @@ func (de *DeploymentEngine) RemoveServiceContainersOnNode(ctx context.Context, s
return de.nodeRunner.RemoveService(ctx, serviceID, nodeID)
}
// RemoteRuntimeState exposes inventory-backed remote container state; nil
// runner means no node support was wired at boot.
func (de *DeploymentEngine) RemoteRuntimeState(ctx context.Context, serviceID string) (*RuntimeState, error) {
if de.nodeRunner == nil {
return nil, nil
}
return de.nodeRunner.RemoteRuntimeState(ctx, serviceID)
}
// RemoveProjectContainers deletes every managed container in a project.
func (de *DeploymentEngine) RemoveProjectContainers(ctx context.Context, projectID string) error {
containers, err := de.dockerClient.ListContainersFiltered(ctx, filters.NewArgs(