mirror of
https://github.com/Dvorinka/Containr.git
synced 2026-10-28 07:41:27 +00:00
feat(scheduling): multi-node service placement + remote lifecycle (#54)
* feat(scheduling): multi-node service placement + remote container lifecycle services.node_id pins a service to a node agent: explicit id, "auto" (least-loaded online node), or "local"/empty for the local Docker host. - DeploymentEngine gains a NodeRunner seam; ReconcileService routes pinned services to the agent's command queue (create_container with image/env/command/ports/volumes/labels/resource limits), tracked in container_instances with deterministic per-replica ids. - Stop/start/restart/delete fan out to the pinned node — no more leaked remote containers on service delete. - Agent create_container is idempotent (rm -f first) and honors restart policy, labels, memory/cpus, published+ephemeral ports, volumes, env. - GET /nodes/options exposes id/name/status to authed users; full agent management stays admin-only. - CLI services create/update --node, MCP node_id docs, OpenAPI node_id/node_name + /nodes/options, service-detail Placement section. Ceilings: remote nodes run registry-pulled images only — git builds, Traefik domains, and remote sleep detection stay local until build-on-node and per-node ingress land. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * feat(scheduling): node cordon/drain + schedulable gate - node_agents.schedulable column; "auto" placement and explicit pins skip cordoned nodes; already-pinned services keep running (reconcile is unaffected — cordon is not eviction). - POST /agents/:id/cordon|uncordon|drain (admin). Drain cordons, removes every service container via the agent queue, and clears the services' pins so they redeploy on the local host. Requires the node online — an offline node can't acknowledge removals. - CLI nodes cordon|uncordon|drain (drain confirm-gated), MCP containr_nodes_cordon|uncordon|drain, OpenAPI paths + schedulable field, node-detail page cordon badge + drain/cordon controls. - Fix: re-registering an agent no longer resets auto_prune. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * feat(scheduling): remote registry pulls + post-run health check - Dispatch payload carries the project owner's registry credentials for the image host; the agent docker-logins for the pull and logs out after, so private images deploy to remote nodes. - Completed command payloads get their registry password scrubbed — secrets don't linger in agent_commands rows. - Agent verifies the container is still running ~1.5s after docker run; crash-loops now fail the deployment with the container's last log lines instead of reporting "running" optimistically. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
401daf69aa
commit
bc93c83cf9
28 files changed
+1565
-48
No files matched your search
@@ -55,6 +55,8 @@ type RuntimeSpec struct {
|
||||
Maintenance bool // redirect traffic to MaintenanceURL
|
||||
MaintenanceURL string // absolute URL; empty = serve empty response
|
||||
BasicAuthUsers string // htpasswd-format user:hash pairs, comma-separated
|
||||
// NodeID pins the service to a node agent; "" runs on the local host.
|
||||
NodeID string
|
||||
}
|
||||
|
||||
// RuntimeContainer describes one live replica.
|
||||
@@ -151,8 +153,15 @@ func (de *DeploymentEngine) ListServiceContainers(ctx context.Context, serviceID
|
||||
|
||||
// ReconcileService brings live containers in line with the spec. When the
|
||||
// spec hash changed (image/env/ports/etc.) all replicas are recreated;
|
||||
// otherwise only the replica delta is applied.
|
||||
// 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.NodeID != "" {
|
||||
if de.nodeRunner == nil {
|
||||
return nil, fmt.Errorf("service is pinned to node %s but remote dispatch is unavailable", spec.NodeID)
|
||||
}
|
||||
return de.nodeRunner.ReconcileOnNode(ctx, spec)
|
||||
}
|
||||
if spec.Replicas < 1 {
|
||||
spec.Replicas = 1
|
||||
}
|
||||
@@ -498,6 +507,25 @@ func (de *DeploymentEngine) RemoveServiceContainers(ctx context.Context, service
|
||||
return nil
|
||||
}
|
||||
|
||||
// ControlServiceOnNode fans a lifecycle action out to the service's replicas
|
||||
// on a remote node agent. Callers resolve the pin; nil runner means the
|
||||
// platform was booted without node support.
|
||||
func (de *DeploymentEngine) ControlServiceOnNode(ctx context.Context, serviceID, nodeID, action string) error {
|
||||
if de.nodeRunner == nil {
|
||||
return fmt.Errorf("remote node control is not configured")
|
||||
}
|
||||
return de.nodeRunner.ControlService(ctx, serviceID, nodeID, action)
|
||||
}
|
||||
|
||||
// RemoveServiceContainersOnNode tears down the service's replicas on a remote
|
||||
// node agent.
|
||||
func (de *DeploymentEngine) RemoveServiceContainersOnNode(ctx context.Context, serviceID, nodeID string) error {
|
||||
if de.nodeRunner == nil {
|
||||
return fmt.Errorf("remote node control is not configured")
|
||||
}
|
||||
return de.nodeRunner.RemoveService(ctx, serviceID, nodeID)
|
||||
}
|
||||
|
||||
// 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