Node lost
The scheduler marks a node lost when it has not leased or heartbeated for PUSHLEFT_NODE_LOST_AFTER_SECONDS (default 300, minimum 30) and reclaims its work (fleet.md). This runbook covers what is automatic, what is not, and what to check.
Symptoms
node_heartbeat_stale, thenpushleft-controller fleet listshows the node aslostwith a line such aslost app-3 since …: fenced 4 job(s), 3 instance(s) lost, replacements 2 ready / 1 queued, 0 database instance(s) stranded.- Deployment events
node_lost, thenapplication_readyfor the replacement instances,ingress_relocatingandingress_route_relocated. Ordeployment_degraded. pushleft-controller fleet eventsshowsnode_lost(anddatabase_instance_strandedfor a database node).
What already happened (automatic)
- The node is
lostand unschedulable. Every queued or leased job targeted at it isfailedwithlast_error='target node lost; lease fenced'. A late heartbeat or completion from it gets409. - Its application instances are
lost(rows kept). Each instance of an active deployment has a replacement row (replaces_instance_id, generation + 1) with a normalensure_applicationjob. - Its application and environment-gateway capacity is released. Subnet slots are not reused.
- A live deployment stays
liveon its old route. Once the replacement web instances are ready on a healthy node, a relocation route is published there; the old route drains only after it is active. - Database instances on the node are
errorand keep their allocation. Nothing is moved.
Diagnosis
-- The reclamation record.
SELECT created_at, payload FROM fleet_events WHERE node_id = '<node>' AND kind = 'node_lost';
-- Replacements and where they are.
SELECT o.id AS lost, o.role_name, r.id AS replacement, r.generation, r.status, n.name AS node
FROM application_instances o JOIN application_instances r ON r.replaces_instance_id = o.id
LEFT JOIN fleet_nodes n ON n.id = r.active_node_id
WHERE o.active_node_id = '<node>' ORDER BY o.deployment_id, o.role_name;
-- Why a replacement is not placed yet.
SELECT j.id, j.status, j.target_node_id, j.available_at, j.payload->>'last_error' AS error
FROM jobs j JOIN application_instances r ON r.id::text = j.payload->>'application_instance_id'
JOIN application_instances o ON o.id = r.replaces_instance_id
WHERE o.active_node_id = '<node>' AND j.kind = 'ensure_application';
-- Routes still on the lost node.
SELECT deployment_id, hostname, route_generation, status FROM ingress_analytics_routes
WHERE node_id = '<node>' AND status IN ('publishing','active','draining');
A replacement waits (queued, retried every 15 s) until a fresh, schedulable application node in the deployment’s regions has capacity and fresh network admission evidence for the environment. In an environment with a sealed topology only nodes that are already members qualify. Otherwise the placement is rejected (application_placement_rejected) and the deployment is degraded.
Actions
- Ingress DNS. The wildcard
*.<base>record is operator-managed. If the lost node was the ingress host, point the record at the node that now carries the relocation route (SELECT DISTINCT n.name, n.public_ipv4 FROM ingress_analytics_routes r JOIN fleet_nodes n ON n.id = r.node_id WHERE r.relocation AND r.status = 'active'). - Capacity. If replacements are queued and the deployment is
degraded, add capacity:fleet provision --pool application …,fleet admit. Placement continues by itself, and the deployment returns tolivewhen its long-lived roles are ready. - Degraded with a failed relocation. A failed relocation (
deployment_degraded, reasoningress relocation off a lost node failed) is retried per hostname after five minutes, up to three failures; each hostname drains its lost route only once its own replacement is active. After the third failure automatic relocation stops. Ask the user to runpl upagain, which publishes a fresh generation (ingress host failure). - Database node. Data is on the lost host’s volume. Recover the host and its volume (then decommission and replace following node drain), or restore from the tenant’s own backups. Pushleft does not relocate database data.
- The host comes back. It authenticates but is refused work (
403 node_lost) and receives a cleanup list; its agent removes the containers of every fenced instance (node was marked lost; removed fenced instance …in the agent journal). Check withdocker ps --filter label=com.pushleft.managed=trueon the host. It never leases again. Decommission it:pushleft-controller fleet decommission <node>(accepted forlost; refused while a stranded database allocation is held).
Verification
fleet list: replacementsN ready / 0 queued, the deploymentlive.SELECT status FROM ingress_analytics_routes WHERE id = …for the relocation route isactive, the lost node’s route isdraining.curl -sI https://<app>.<base>answers from the new node.
Do not
- Do not set a lost node back to
activeor edit alostinstance. The status is terminal (a trigger refuses it), and the replacement already owns the role slot. - Do not retarget the fenced jobs or reuse their lease tokens.
- Do not release a stranded database allocation by hand; the data is still on that disk.
- Do not run
fleet ca rotate --completewhile a lost node that may come back still runs containers: completion revokes its certificate, and it then cannot receive its cleanup list.