Common issues and solutions for 5-Spot.
# Operator pods
kubectl get pods -n 5spot-system
# Operator logs (JSON — pipe through jq for readability)
kubectl logs -n 5spot-system -l app=5spot-controller --tail=100 | jq .
# Plain-text logs (for quick reads without jq)
RUST_LOG_FORMAT=text kubectl logs -n 5spot-system -l app=5spot-controller --tail=100
# Detailed pod info
kubectl describe pod -n 5spot-system -l app=5spot-controllerEvery reconciliation carries a unique reconcile_id field. Use it to isolate all log lines for a single reconciliation attempt:
# Stream logs and filter by resource name, showing reconcile_id
kubectl logs -n 5spot-system -l app=5spot-controller -f | \
jq -c 'select(.fields.resource == "<machine-name>")'
# Trace a specific reconciliation end-to-end
kubectl logs -n 5spot-system -l app=5spot-controller | \
jq -c 'select(.fields.reconcile_id == "<id-from-a-previous-log-line>")'
# Find all Error-phase transitions
kubectl logs -n 5spot-system -l app=5spot-controller | \
jq -c 'select(.fields.to_phase == "Error")'# List all ScheduledMachines
kubectl get scheduledmachines -A
# Detailed status
kubectl describe scheduledmachine <name>
# Get status as JSON
kubectl get scheduledmachine <name> -o jsonpath='{.status}'# List CAPI machines
kubectl get machines -A
# Describe machine
kubectl describe machine <name>Symptoms:
- Machine stays in
Pendingphase - No Machine resource created
Possible Causes:
-
Schedule not matching current time
# Check current time vs schedule kubectl get scheduledmachine <name> -o jsonpath='{.spec.schedule}' date -u # Compare with UTC
-
Operator not running
kubectl get pods -n 5spot-system
-
RBAC permissions
kubectl auth can-i create machines --as=system:serviceaccount:5spot-system:5spot-controller
Solution:
- Verify schedule matches current time and timezone
- Check controller logs for errors
- Ensure RBAC is correctly configured
Symptoms:
- Machine stays in
Activeafter schedule window - Grace period seems to never complete
Possible Causes:
-
Pods not draining
kubectl get pods -o wide | grep <machine-name>
-
PodDisruptionBudget blocking eviction
PDB-blocked evictions (HTTP 429) now surface as a
CapiErrorin the reconciler and will cause the machine to enter theErrorphase. Check for blocking PDBs:kubectl get pdb -A # Look for PDBs with maxUnavailable: 0 or minAvailable matching current replicas kubectl get pdb -A -o json | jq '.items[] | {name:.metadata.name, ns:.metadata.namespace, disruptions:.status.disruptionsAllowed}'
-
Long grace period
kubectl get scheduledmachine <name> -o jsonpath='{.spec.gracefulShutdownTimeout}'
Solution:
- Check for pods that can't be evicted; look for
warnlog lines with"Pod eviction blocked by PDB (HTTP 429)" - Review PDB settings — temporarily scale up or relax
minAvailableto allow drain - Consider using
killSwitch: truefor immediate removal (bypasses drain)
Symptoms:
- Machine doesn't activate during schedule window
- No status changes
Possible Causes:
-
Schedule disabled
kubectl get scheduledmachine <name> -o jsonpath='{.spec.schedule.enabled}'
-
Timezone mismatch
kubectl get scheduledmachine <name> -o jsonpath='{.spec.schedule.timezone}' TZ=<timezone> date # Check time in that timezone
-
Multi-instance: wrong instance handling resource
# Check which instance should handle this resource kubectl logs -n 5spot-system -l app=5spot-controller | grep <resource-name>
Solution:
- Ensure
enabled: true - Verify timezone is correct
- Check controller instance distribution
Symptoms:
- Error events on ScheduledMachine
- CAPI Machine not being created
Possible Causes:
-
Invalid bootstrapRef or infrastructureRef
kubectl get scheduledmachine <name> -o jsonpath='{.spec.bootstrapRef}' kubectl get <kind> <name> -n <namespace> # Verify reference exists
-
CAPI provider not ready
kubectl get pods -n capi-system kubectl get pods -n capi-kubeadm-bootstrap-system
Solution:
- Verify references point to existing resources
- Check CAPI provider health
- Review CAPI controller logs
Symptoms:
- Repeated error events on a
ScheduledMachine - Logs show
retry_countclimbing andbackoff_secsgrowing (30 → 60 → 120 → 240 → 300)
Cause: The controller uses bounded exponential back-off. Each consecutive failure doubles the retry delay up to 300 s (5 min). The counter resets after a successful reconciliation.
# Watch the retry_count and backoff_secs fields
kubectl logs -n 5spot-system -l app=5spot-controller -f | \
jq -c 'select(.fields.resource == "<machine-name>") | {retry: .fields.retry_count, backoff: .fields.backoff_secs, error: .fields.error}'Solution:
- Check the underlying error causing repeated failures (CAPI, schedule, validation)
- Once the root cause is fixed, the next successful reconciliation resets the counter
- If the resource is stuck at max backoff (300 s), fix the underlying issue and patch the resource to trigger an immediate reconcile:
kubectl annotate scheduledmachine <name> 5spot.finos.org/force-reconcile="$(date -u +%s)" --overwrite
See Emergency Reclaim for the full lifecycle. This section covers the diagnostic angles most operators hit in the field.
Symptoms:
kubectl get scheduledmachineshowsPHASE=EmergencyRemoveand does not move toDisabled.- The node still appears in the cluster.
Diagnosis:
# Is the reclaim annotation still on the Node? (expected during eject, cleared at end)
kubectl get node <node-name> -o jsonpath='{.metadata.annotations}' | jq \
'with_entries(select(.key | startswith("5spot.finos.org/reclaim")))'
# Controller logs for the emergency-remove handler
kubectl logs -n 5spot-system -l app=5spot-controller --tail=200 | \
jq -c 'select(.fields.phase == "EmergencyRemove")'
# Events on the ScheduledMachine
kubectl describe scheduledmachine/<name> | grep -A 5 EventsCommon causes:
- Drain is blocked by non-evictable pods. The handler uses
--force --disable-eviction, so this should be rare — if it happens, a pod is probably stuck inTerminatingwaiting on a finalizer of its own. - CAPI Machine deletion is blocked. Check
kubectl describe machine/<machine-name>for a finalizer that has not been cleared. - Controller crashed mid-handler. On restart the annotation is still there (cleared last), so the handler will retry from the top — the operation is idempotent.
Symptom: The ScheduledMachine cycles Disabled → Pending → Active → EmergencyRemove → Disabled → ... at every schedule boundary.
Cause: The matched process is still running, the user re-enabled the schedule without quitting it first, and the agent correctly re-fired on the next poll.
Confirm:
# Check what the agent matched on
kubectl logs -n 5spot-system -l app=5spot-reclaim-agent --tail=50 | jq -c 'select(.fields.matched_pattern)'
# Check the condition reason on the ScheduledMachine
kubectl get scheduledmachine/<name> -o jsonpath='{.status.conditions}' | jq \
'.[] | select(.reason == "EmergencyReclaimDisabledSchedule")'Solution: Quit the matched process on the node, then re-enable:
kubectl patch scheduledmachine/<name> --type merge \
-p '{"spec":{"schedule":{"enabled":true}}}'If the user does not want this node in the reclaim path at all, clear killIfCommands:
kubectl patch scheduledmachine/<name> --type merge \
-p '{"spec":{"killIfCommands":null}}'Symptoms: User has a matching process running, but the Node never gets annotated.
Checklist:
-
Is the agent pod actually running on the node?
kubectl get pods -n 5spot-system -l app=5spot-reclaim-agent -o wide
If no pod lands on the target node, the
5spot.finos.org/reclaim-agent=enabledlabel is probably missing. Check the node labels:kubectl get node <node-name> --show-labels | grep reclaim-agent
-
Is the per-node ConfigMap present and readable? The agent no longer mounts its config from a file — it watches the per-node
ConfigMapnamedreclaim-agent-<NODE_NAME>in5spot-systemvia the kube API and hot-reloads on every change. Check the ConfigMap directly:kubectl get cm -n 5spot-system reclaim-agent-<node-name> -o jsonpath='{.data.reclaim\.toml}'
Missing ConfigMap → agent idles (no proc scanning) until one appears. Empty
match_commands+ emptymatch_argv_substrings= agent is armed but inert (never matches) by design. The agent logsconfigmap applied — rearming scannerat INFO on every observed change; tail the pod logs to confirm it sees yours:kubectl logs -n 5spot-system <agent-pod> | grep configmap
-
Is the agent reading real
/proc?kubectl exec -n 5spot-system <agent-pod> -- ls /host/proc | head
Expect many numeric directory names. If you only see
1andself, the pod'shostPID: truemount is broken — re-check the DaemonSet template. -
Match is case-sensitive.
match_commands = ["Java"]does not match ajavaprocess. Lowercase the pattern to match the typical JVM binary name. -
The agent only reads
/proc/<pid>/comm(exact basename) and/proc/<pid>/cmdline(substring). A process whosecommisjava-wrapperbut argv starts with/opt/jdk/bin/java ...matches oncmdline(substring), not oncomm(exact).
Symptom: The EmergencyReclaim event is on the ScheduledMachine, but spec.schedule.enabled is still true.
This indicates the controller crashed between the drain/delete steps and the enabled=false PATCH. The Node annotation is cleared after the PATCH, so the controller will see the annotation on the next reconcile and retry. If it does not, check:
# Is the EmergencyReclaimDisabledSchedule event present?
kubectl get events --field-selector reason=EmergencyReclaimDisabledSchedule \
--sort-by='.lastTimestamp'
# If yes, but spec.schedule.enabled is still true, the PATCH may have lost a race
# with a user edit. Check the generation on the ScheduledMachine:
kubectl get scheduledmachine/<name> -o jsonpath='{.metadata.generation} {.status.observedGeneration}'spec.nodeTaints is declared on a ScheduledMachine, the machine is Active,
but the Node does not have the expected taints. Walk the NodeTainted
condition on the CR first — it tells you exactly which layer is failing.
kubectl get scheduledmachine <name> \
-o jsonpath='{.status.conditions[?(@.type=="NodeTainted")]}{"\n"}'Three failure reasons, each with its own fix:
reason=NoNodeYet (status=Unknown)
CAPI populated status.nodeRef but the Node object is not yet in the API
server. This is usually a few seconds after Machine creation. The Node watch
will re-enqueue us automatically — no action needed. If stuck for > 1 min,
check that CAPI's Machine actually materialised the Node:
kubectl get machine <name>-machine -o jsonpath='{.status.nodeRef}{"\n"}'
kubectl get nodes <node-name>reason=NodeNotReady (status=False)
Node exists but Ready != True. Kubelet hasn't registered, networking is
degraded, or CNI is failing. Look at the Node's own conditions first:
kubectl describe node <node-name> | sed -n '/Conditions:/,/Addresses:/p'Fix the underlying Node problem; the controller will re-reconcile on the next
Node Ready transition.
reason=TaintOwnershipConflict (status=False)
An admin taint exists with the same (key, effect) tuple as a declared
spec.nodeTaints entry. The controller refuses to overwrite admin-owned
taints. Inspect the current state:
kubectl get node <node-name> -o jsonpath='{.spec.taints}{"\n"}'
kubectl get node <node-name> \
-o jsonpath='{.metadata.annotations.5spot\.finos\.org/applied-taints}{"\n"}'Resolve by either removing the admin taint (kubectl taint nodes <node> key:effect-)
or changing the spec.nodeTaints entry so the (key, effect) no longer
collides. Note: the annotation 5spot.finos.org/applied-taints lists the
keys we own; any taint not in that list belongs to the admin.
reason=PatchFailed (status=False)
A non-404, non-conflict API error on the Node PATCH. Check controller logs for
the exact kube error (RBAC rejection, API server unreachable, etc.):
kubectl logs -n 5spot-system -l app=5spot-controller \
| grep -E "node_taint|appliedNodeTaints"The controller retries with exponential backoff on transient failures. For
RBAC issues, confirm the controller ClusterRole grants patch on nodes.
Cause: Multi-instance deployment where this resource is assigned to a different instance.
Solution: This is expected behavior. Each instance handles a subset of resources.
Cause: Invalid schedule configuration.
Solution: Check schedule syntax:
- Days:
mon-fri, notmonday-friday - Hours:
9-17, not9:00-17:00 - Timezone: Valid IANA name like
America/New_York
Cause: CAPI couldn't create the machine.
Solution:
- Check CAPI logs:
kubectl logs -n capi-system -l control-plane=controller-manager - Verify infrastructure provider is configured
- Check bootstrap template validity
# Operator version
kubectl get deployment -n 5spot-system 5spot-controller -o jsonpath='{.spec.template.spec.containers[0].image}'
# Full controller logs
kubectl logs -n 5spot-system -l app=5spot-controller --all-containers > controller-logs.txt
# ScheduledMachine YAML
kubectl get scheduledmachine <name> -o yaml > scheduledmachine.yaml
# Events
kubectl get events -A --sort-by='.lastTimestamp' > events.txtWhen filing a GitHub issue, include:
- 5-Spot version
- Kubernetes version
- CAPI version
- Operator logs (sensitive data redacted)
- ScheduledMachine YAML
- Expected vs actual behavior
- Configuration - Operator configuration
- Monitoring - Metrics and health checks
- Machine Lifecycle - Understanding phases