mirror of
https://github.com/Dvorinka/Containr.git
synced 2026-10-28 07:41:27 +00:00
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:
1 parent
bc93c83cf9
commit
dd5ce80ce2
21 files changed
+469
-100
No files matched your search
@@ -17,11 +17,17 @@ type NodeRunner interface {
|
||||
// ReconcileOnNode brings the node's containers in line with the spec and
|
||||
// returns the resulting runtime state.
|
||||
ReconcileOnNode(ctx context.Context, spec RuntimeSpec) (*RuntimeState, error)
|
||||
// ReconcileSpread distributes the service's replicas across every online,
|
||||
// schedulable agent — replica i lands on agents[i % len].
|
||||
ReconcileSpread(ctx context.Context, spec RuntimeSpec) (*RuntimeState, error)
|
||||
// ControlService fans a lifecycle action ("start"|"stop"|"restart"|"remove")
|
||||
// out to every replica of the service on the agent.
|
||||
ControlService(ctx context.Context, serviceID, agentID, action string) error
|
||||
// RemoveService tears down every replica of the service on the agent.
|
||||
RemoveService(ctx context.Context, serviceID, agentID string) error
|
||||
// RemoteRuntimeState reads the service's remote replicas from inventory
|
||||
// across all agents. nil/empty means nothing runs remotely.
|
||||
RemoteRuntimeState(ctx context.Context, serviceID string) (*RuntimeState, error)
|
||||
}
|
||||
|
||||
type DeploymentEngine struct {
|
||||
@@ -85,6 +91,9 @@ type ServiceConfig struct {
|
||||
// host. Remote placement requires a registry-pullable image — builds stay
|
||||
// on the local host (build-on-node is not yet supported).
|
||||
NodeID string `json:"node_id,omitempty"`
|
||||
// Spread distributes replicas across every online, schedulable agent.
|
||||
// Same registry-image constraint as NodeID.
|
||||
Spread bool `json:"spread,omitempty"`
|
||||
}
|
||||
|
||||
type PortMapping struct {
|
||||
@@ -376,6 +385,7 @@ func (de *DeploymentEngine) deployService(ctx context.Context, deployment *Deplo
|
||||
MaintenanceURL: cfg.MaintenanceURL,
|
||||
BasicAuthUsers: cfg.BasicAuthUsers,
|
||||
NodeID: cfg.NodeID,
|
||||
Spread: cfg.Spread,
|
||||
}
|
||||
for _, pm := range cfg.PortMappings {
|
||||
if spec.Port == 0 {
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in new issue
Block a user