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
@@ -11,10 +11,24 @@ import (
|
||||
"containr/internal/types"
|
||||
)
|
||||
|
||||
// NodeRunner deploys a service's containers onto a remote node agent.
|
||||
// Wired at boot; nil means every service runs on the local Docker host.
|
||||
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)
|
||||
// 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
|
||||
}
|
||||
|
||||
type DeploymentEngine struct {
|
||||
buildManager *build.BuildManager
|
||||
dockerClient *docker.Client
|
||||
scheduler *Scheduler
|
||||
nodeRunner NodeRunner
|
||||
deployments map[string]*Deployment
|
||||
deploymentLog chan *DeploymentEvent
|
||||
}
|
||||
@@ -67,6 +81,10 @@ type ServiceConfig struct {
|
||||
// PublishedPort is the host port bound by the previous deployment; replica 0
|
||||
// reuses it so public URLs survive restarts. 0 = pick ephemeral.
|
||||
PublishedPort int32 `json:"published_port,omitempty"`
|
||||
// NodeID pins the service to a registered node agent; "" = local Docker
|
||||
// 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"`
|
||||
}
|
||||
|
||||
type PortMapping struct {
|
||||
@@ -177,6 +195,11 @@ func (de *DeploymentEngine) DockerClient() *docker.Client {
|
||||
return de.dockerClient
|
||||
}
|
||||
|
||||
// SetNodeRunner wires remote-node dispatch for services pinned to an agent.
|
||||
func (de *DeploymentEngine) SetNodeRunner(r NodeRunner) {
|
||||
de.nodeRunner = r
|
||||
}
|
||||
|
||||
func NewDeploymentEngine(buildManager *build.BuildManager, dockerClient *docker.Client) *DeploymentEngine {
|
||||
return &DeploymentEngine{
|
||||
buildManager: buildManager,
|
||||
@@ -352,6 +375,7 @@ func (de *DeploymentEngine) deployService(ctx context.Context, deployment *Deplo
|
||||
Maintenance: cfg.Maintenance,
|
||||
MaintenanceURL: cfg.MaintenanceURL,
|
||||
BasicAuthUsers: cfg.BasicAuthUsers,
|
||||
NodeID: cfg.NodeID,
|
||||
}
|
||||
for _, pm := range cfg.PortMappings {
|
||||
if spec.Port == 0 {
|
||||
|
||||
Reference in new issue
Block a user