23 KiB
23 KiB
Ananke Recovery Notes - 2026-07-07
Context
- Titan cluster lost power briefly; Ariadne may have started shutdown behavior.
- User goal: let one Ananke recovery process bring the cluster back to health, monitor it, and record any manual help or automation gaps.
- Operator view is from
/home/brad/Development/bstein_dev_home; Kubernetes API is reachable through/home/brad/titan/kubeconfig.
Current Ananke Run
- Coordinator host:
titan-db. ananke.servicedaemon is active.ananke-bootstrap.serviceis active and running:- command:
/usr/local/bin/ananke startup --config /etc/ananke/ananke.yaml --execute --force-flux-branch main --auto-peer-failover --peer-wait-seconds 180 - current run started:
2026-07-07T17:19:50Z - current phase observed:
critical-workloads - checklist observed:
10 passed / 0 failed / 2 running - auto-heals observed: pod recycling for
VaultInitStuck,ContainerRuntimeWedge,CrashLoopBackOff, stale terminating pods, and Longhorn attach blocked on unready nodes. - previous run failed at
2026-07-07T17:19:19Zwaiting onlogging/deployment/oauth2-proxy-logs; systemd restarted the bootstrap service.
- command:
Observed Recovery Progress
- Latest snapshot at approximately
2026-07-07T17:58Z:- Ananke is still running in
convergence-checkswith17/19checks passed,0failed, and2running. - Ananke has recorded
31auto-heal actions, mostly repeated stuck-pod recycling. titan-22andtitan-24both recovered toReady=Truewith fresh kubelet transitions (titan-22at2026-07-07T17:56:29Z,titan-24at2026-07-07T17:55:11Z).- Metrics server is running and
kubectl top nodesworks for recovered nodes;titan-22had just rejoined and metrics may lag briefly. - Remaining convergence blocker is mostly app workload readiness, led by
finance/firefly.
- Ananke is still running in
- Ananke completed the startup workflow successfully at
2026-07-07T17:59:00Z:startup_status=successstartup_phase=completestartup_checklist_total=20 passed=19 failed=0 running=1startup_auto_heals=33intent=normal
- Vault recovered:
vault/vault-0is1/1 Runningontitan-18.- Vault status reports
initialized=true,sealed=false,ha_enabled=true. - Vault became active at
2026-07-07T17:05:46Z. vault-k8s-auth-config-autoheal-*completed.
- Vault injector recovered after
titan-04kubelet recovery:vault-injector-agent-injector-*became1/1 Running.- Vault service endpoints and injector webhook endpoints are populated.
- Vault-backed workloads started moving again:
- Ariadne's Vault init authenticated successfully and rendered
/vault/secrets/ariadne-env.sh. - Ariadne is now
2/2 Runningontitan-08.
- Ariadne's Vault init authenticated successfully and rendered
- Flux is mostly reconciled, but several app kustomizations are still waiting on app readiness or dependencies.
- Longhorn no longer shows the earlier broad faulted volume picture; observed state later was
41 attached,1 attaching,47 detached.
Current Known Issues
titan-22andtitan-24are stillNotReady/Unknownfrom Kubernetes.titan-05isUnknownafter power-event recovery and k3s-agent restart attempts:- Ananke SSH route on port
2277reaches the host. systemctl restart k3s-agenttimed out while stopping the old process.- systemd reported
Processes still around after SIGKILL, then spawned a new/usr/local/bin/k3s agent. k3s-agent.serviceremainedactivating/start; local127.0.0.1:6444was open but kubelet127.0.0.1:10250was closed.- Kubernetes node heartbeat stayed stale at
2026-07-07T17:21:57Zwithnode.kubernetes.io/unreachabletaints. maintenance/k3s-agent-restart-*ontitan-05stayedContainerCreating, which confirms the in-cluster helper cannot repair this class once the node runtime is wedged.
- Ananke SSH route on port
titan-15andtitan-17areReady,SchedulingDisabled; Ananke cordoned them because encrypted Longhorn mounts exposed missingcryptsetup.- Ananke tried to install
cryptsetup-binontitan-15andtitan-17, but the SSH repair failed becausesudorequired a password foratlas. - Several workloads are still unready while Ananke recycles stale
VaultInitStuck,CrashLoopBackOff, andImagePullBackOffpods.
Manual Actions Taken
- Verified cluster health with
kubectland Flux commands. - Verified Vault directly with
vault statusinsidevault-0. - Verified Ananke coordinator service state through
titan-db. - Did not start a second Ananke recovery process after discovering the active
ananke-bootstrap.service. - Used the existing
maintenance/k3s-agent-restartDaemonSet as a targeted host-level nudge by deleting its pods ontitan-04,titan-05,titan-06,titan-08, andtitan-18; this reruns the DaemonSet'sk3s-agentrestart command on those nodes. - The in-cluster helper successfully reran on
titan-08, but helper startup/termination was itself wedged on other affected nodes. Queued directsystemctl --no-block restart k3s-agentover Ananke SSH fortitan-05,titan-06, andtitan-18. A direct restart attempt againsttitan-04hung and was stopped. - After discovering Ananke's SSH profile uses
atlas@node:2277, queuedsystemctl --no-block restart k3s-agentontitan-04andtitan-05through the same route. titan-04recovered toReadyafter the non-blocking restart and itsmaintenance/k3s-agent-restart-*pod becameRunning.titan-05did not recover: systemd timed out killing the oldk3s-agent, left containerd shims behind, and started a new agent process without reopening kubelet port10250or restoring node heartbeats.- A controlled reboot was issued for
titan-05through Ananke's SSH route at approximately2026-07-07T17:32Zbecause the node met the proposed escalation criteria: stale Kubernetes heartbeat, kubelet10250closed,k3s-agent.servicestuck inactivating/start, and the in-cluster helper stuckContainerCreating. - After the reboot command returned,
titan-05temporarily stopped responding to ping and SSH, then recovered to KubernetesReady=Truewith fresh heartbeats and nounreachabletaints by approximately2026-07-07T17:35Z. - Ananke later detected and logged
cordoned container-runtime-wedged node(s): titan-07 pods=4; direct host checks showedtitan-07reachable,k3s-agentactive/running, and kubelet10250open. This should become a false-positive/auto-uncordon test case if Kubernetes readiness remains healthy. - Later
titan-07showed a real runtime/API-state split:oauth2-proxy-logsreported container staterunning, butkubectl execfailed withcannot exec in a deleted stateandkubectl logs --previouscould not retrieve the container logs. Manual assist: queuedsystemctl --no-block restart k3s-agentontitan-07and deleted the stalelogging/oauth2-proxy-logs-*pod. titan-07recovered toReady=Truewithk3s-agentactive and kubelet10250open.logging/oauth2-proxy-logswas recreated ontitan-12and became2/2 Running.- Ananke repeatedly failed and restarted on
logging/deployment/oauth2-proxy-logsbecause the replacement pod ontitan-14hit the samefailed to reserve container name/CreateContainerErrorruntime wedge signature. - Manual assist: queued
systemctl --no-block restart k3s-agentontitan-14through Ananke SSH and deleted the stucklogging/oauth2-proxy-logs-5b4d4f87db-2tdzdpod with--wait=falseso the controller can recreate it after the runtime reset. - Follow-up on
titan-14: the direct SSH command timed out from the operator view, but a later status check showedk3s-agent.servicehad a new MainPID and wasactivating/start; the existingmaintenance/k3s-agent-restartDaemonSet pod fortitan-14was also deleted to rerun the in-cluster helper. titan-14recovered to freshReady=Trueheartbeats and the replacementmaintenance/k3s-agent-restart-*pod becameRunning.- After the
titan-05reboot,logging/opensearch-0recovered to1/1 Running;logging/oauth2-proxy-logswas recreated ontitan-07and was still initializing at the time of note. - Ananke also entered a short retry loop where
kubectl exec vault-0 ... vault statuswas killed even though Vault HTTP health through the Kubernetes pod proxy reportedinitialized=true,sealed=false,standby=false. This should become a separate robustness fix: startup should not fail outright on a transient killedkubectl execwhen service endpoints/HTTP health prove Vault is active. - After Keycloak completed its slow post-attach startup at approximately
2026-07-07T17:45:55Z, OIDC-dependent OAuth proxies began recovering. - Ananke advanced past
critical-workloadsintoconvergence-checksby approximately2026-07-07T17:50:50Z. - Manual assist: force-deleted stale pod
finance/firefly-6669f46874-q8rtqafter Ananke repeatedly issued ordinary deletes but the pod remainedTerminatingwith no finalizers and continued to blockfirefly-storageattachment. The Firefly Longhorn volume moved fromtitan-04totitan-06and reportedattached/healthyon the replacement node. - Post-Ananke-completion manual assist:
titan-04was stillReadywith kubelet10250open, but several newly scheduled pods showedFailedCreatePodSandBox,failed to reserve sandbox name, andcontext deadline exceeded. Queuedsystemctl --no-block restart k3s-agentontitan-04at approximately2026-07-07T18:02:55Z. titan-04did not recover from the non-blockingk3s-agentrestart. It moved to KubernetesReady=Unknownwithnode.kubernetes.io/unreachabletaints; host checks showedk3s-agent.servicestuck indeactivating/stop-sigkill,Result=timeout, and kubelet10250closed. Manual assist: issued a controlled reboot fortitan-04through Ananke's SSH route shortly after2026-07-07T18:06Z.titan-04recovered after reboot by approximately2026-07-07T18:10Z: Kubernetes reportedReady=True, taints cleared,k3s-agentwasactive/running, and kubelet10250was open.- Firefly hit a second stale-owner handoff after the first cleanup. Replacement pod
firefly-6669f46874-pjhg6ontitan-07was blocked while terminating podfirefly-6669f46874-lrz5dontitan-06still had the mainfireflycontainer running and mountedfirefly-storage. No force delete was issued for this second handoff because the application container was live and PVC-writing. The old pod later disappeared on its own; Longhorn then attachedpvc-358b4319-60cb-4322-b948-776c34a414a9totitan-07asattached/healthy. - Attempted to inspect container runtime state on
titan-06withcrictl, butatlasrequired a sudo password. This should be part of the automation fix: Ananke either needs a noninteractive, bounded runtime-inspection primitive or must report that it cannot prove a PVC-writing container is stopped. - Manual assist: uncordoned
titan-15andtitan-17after confirming KubernetesReady=True, LonghornallowScheduling=true, and Longhorn disks ready. The remaining blocker for Longhorn on both nodes was KubernetesSchedulingDisabled; after uncordon, LonghornSchedulable=Trueon both nodes. - Manual assist: resolved the
titan-22Kubernetes/Longhorn readiness discrepancy. Kubernetes reportedReady=True, but Longhorn nodeReady=FalsewithManagerPodMissing; the Longhorn manager DaemonSet selectedlonghorn-host=true, andtitan-22was missing that label. Restoredlonghorn-host=true, which startedlonghorn-manager-jtsjwontitan-22and brought Longhorn nodeReady=True. - Manual assist: deleted pending controller-owned pod
crypto/wallet-monero-temp-844bd949c7-28r4pto force a fresh volume-attach retry after thetitan-22Longhorn manager came up. - Manual assist: deleted stale unattached VolumeAttachment
csi-0bac99e18b687f6a50647dd959c26d6e2a1b2bf1a4f844de4f646cf3408d41c5after it retained the pre-fix "node titan-22 is not ready" Longhorn error. Longhorn then attachedpvc-c6a4bf9b-17c7-4bdf-8a88-ca460ad0da5btotitan-22asattached/healthy.
Automation Growth Opportunities
- Ananke should expose from a remote operator machine whether the coordinator bootstrap is already running, so an operator does not risk starting a duplicate recovery.
- The local Ananke config path assumes
/etc/ananke/ananke.yamland/var/lib/ananke; operator-sidestatusfailed without root. A read-only remote status wrapper would make this smoother. - Ananke should handle the
cryptsetup-binhost repair on managed nodes without requiring interactive sudo, or clearly preflight and report the missing node privilege before a power event. - Ananke should separate
Vault pod/data volume unavailablefromVault sealedin status. It did recover correctly, but early status said the unseal check passed while deferring becausevault-0was pending. - Ananke should avoid repeatedly recycling the same ImagePullBackOff pods if the underlying registry/image problem is unchanged; this creates noisy recovery logs.
- Ananke should surface current blocking workloads in a compact operator summary rather than requiring journal inspection.
- Ananke should detect pods stuck in
PodInitializingafter init completion, especially when events showFailedCreatePodSandbox,failed to reserve sandbox name,FailedKillPod, or image pulls that never transition toImagePullBackOff. - Ananke should be able to rerun the existing
k3s-agent-restartmaintenance primitive on affected nodes when containerd/kubelet sandbox state is wedged after a power event. - Ananke should fall back from the in-cluster restart helper to SSH
systemctl --no-block restart k3s-agentwhen the helper pod cannot start because kubelet/containerd is already wedged. Blockingsystemctl restartover SSH can hang the operator path. - Ananke should check host and CoreDNS upstream DNS health separately from node SSH reachability. During this incident, node IP reachability passed while hostname resolution and image pulls were intermittently failing.
- Ananke should stop repeatedly issuing ordinary pod deletes when the same controller pod remains
Terminatingand continues to own exclusive resources. It should escalate to a stale-owner cleanup flow that proves the old workload is not writing, force-removes the orphaned API object when safe, then verifies Longhorn detach/attach and replacement pod startup. - Ananke should reconcile Kubernetes node labels against Longhorn manager scheduling requirements. During this incident
titan-22was KubernetesReady=True, had CSI/engine Longhorn pods, and had a Longhorn Node CR, but nolonghorn-managerpod becauselonghorn-host=truewas missing. Kubernetes Ready alone was not sufficient for PVC workloads. - Ananke should understand that Kubernetes cordon directly affects Longhorn node schedulability.
titan-15andtitan-17had Longhorn-ready disks andallowScheduling=true, but LonghornSchedulable=Falsesolely because the Kubernetes nodes were cordoned.
Generalized Node Runtime Wedge Case
This incident exposed a reusable failure mode that Ananke should treat as a node-runtime recovery problem, not just an app-pod recovery problem.
Detection signals:
- Node is
Ready=Unknownor has a stale heartbeat while SSH on the managed Ananke route still works. - Host is pingable and local k3s load balancer port
127.0.0.1:6444may be open, but kubelet port127.0.0.1:10250is closed or API proxy calls fail. systemctl restart k3s-agenthangs, orsystemctl show k3s-agentreportsdeactivating,final-sigterm,final-sigkill,activating/start, orResult=timeoutfor longer than a bounded recovery window.journalctl -u k3s-agentincludesProcesses still around after SIGKILL,State 'stop-sigterm' timed out, orState 'final-sigterm' timed out.- Pods on the node show
FailedCreatePodSandBox,failed to reserve sandbox name,CreateContainerError,FailedKillPod, or long-livedContainerCreating/PodInitializing. - The
maintenance/k3s-agent-restartDaemonSet pod for that node cannot start or staysContainerCreating, which means Kubernetes-based repair is unavailable.
Suggested automated escalation ladder:
- Mark the node as in node-runtime recovery and pause noisy repeated pod recycling for pods pinned to that node.
- Cordon the node or confirm it already has
unreachabletaints before host-level repair. - Try a bounded SSH health probe using Ananke's configured SSH user, port, config, and identity.
- Prefer
systemctl --no-block restart k3s-agentover blockingsystemctl restart k3s-agent. - After a short wait, require both Kubernetes heartbeat freshness and local kubelet port
10250to recover. - If the service remains
deactivating,final-sigkill, oractivating/startwith stale heartbeats, escalate to a configured host reboot path. - After reboot, wait for SSH, k3s-agent active/running, kubelet
10250, KubernetesReady=True, and removal ofunreachabletaints before resuming app-pod recycling for that node.
Implementation notes:
- This should be a generic managed-node repair primitive, not hard-coded to
titan-05. - The primitive should emit one compact operator summary: node, phase, trigger signals, action taken, elapsed time, and next escalation.
- Tests should cover a node with SSH available but kubelet closed, a blocking restart timeout, leftover shim evidence, a helper DaemonSet stuck in
ContainerCreating, and successful recovery through reboot. - The post-completion
titan-04recurrence confirms the primitive must continue monitoring after Ananke declares cluster startup success. A Ready node with fresh-enough status can still be unable to create new sandboxes, and a later restart can transition it into the same harddeactivating/stop-sigkillstate seen ontitan-05.
Generalized Stale Pod / Volume Ownership Case
This incident also exposed a reusable app-convergence failure mode that Ananke should treat as stale Kubernetes/runtime ownership rather than simple pod unhealthiness.
Concrete example:
finance/firefly-6669f46874-q8rtqentered deletion at2026-07-07T17:43:55Zand remainedTerminatingfor more than 25 minutes.- The pod had no finalizers but still reported a running
vault-agentsidecar ontitan-04. - The replacement pod
finance/firefly-6669f46874-lrz5dontitan-06stayedInit:0/2. - Events showed
FailedAttachVolume/Multi-Attachfor PVCfirefly-storagebecause volumepvc-358b4319-60cb-4322-b948-776c34a414a9was still attached totitan-04. - Longhorn reported the volume
attached,healthy, current nodetitan-04. - Ananke repeatedly logged ordinary recycling of the same stuck pod, but did not escalate to clearing the stale owner.
- Manual force deletion of the stale pod cleared the stale API owner and allowed Longhorn to attach
firefly-storageto the replacement pod's node.
Detection signals:
- A controller-owned pod has
metadata.deletionTimestampolder than a bounded threshold and no finalizers. - A replacement pod from the same controller is pending or initializing on another node.
- Events include
Multi-Attach,FailedAttachVolume,Volume is already used by pod(s), orVolume is already exclusively attached to one node. - The PVC is
ReadWriteOnceand the old and new pod nodes differ. - Longhorn/CNI/runtime state still shows the old node as the attachment owner.
- Ordinary
kubectl delete pod --wait=falseis repeated without changing the deletion timestamp or clearing the volume attachment.
Suggested automated escalation ladder:
- Group the old pod, replacement pod, PVC, VolumeAttachment, Longhorn volume, controller, and node into one recovery incident.
- Verify whether any application container that mounts the PVC is still running. Sidecars that do not mount the PVC should not block stale-owner cleanup by themselves.
- Check node health for the old owner node. If kubelet/runtime is unhealthy, route through the managed-node recovery primitive first.
- If the old owner node is healthy but the pod remains deleting past the threshold, issue a bounded force deletion for the stale pod.
- Wait for VolumeAttachment/Longhorn ownership to detach from the old node or attach to the replacement node.
- Recheck the replacement pod init sequence and controller readiness before declaring the app recovered.
- Emit one compact operator event instead of repeatedly logging the same pod recycle.
Implementation notes:
- This should be generic across any
ReadWriteOncePVC workload, not hard-coded to Firefly. - The safety check should reason at container/mount level: a sidecar that only mounts service-account, Vault secret, or emptyDir volumes is materially different from an app container actively mounting the PVC.
- Tests should cover a terminating pod without finalizers holding an RWO PVC, an old node with healthy kubelet, an old node with wedged kubelet, a sidecar-only survivor, and a replacement pod blocked by
Multi-Attach. - Tests should also cover the unsafe variant where the terminating pod's application container is still running and mounts the PVC. In that case Ananke should wait, stop the container through a controlled runtime path if allowed, or report that it lacks the privilege to prove storage safety; it should not blindly force-delete the API object.
Generalized Longhorn / Kubernetes Readiness Drift Case
This incident exposed a reusable storage readiness gap: Kubernetes Ready=True is not enough for PVC workloads when Longhorn's node model disagrees.
Detection signals:
- Kubernetes node is
Ready=True, untainted, and schedulable, but Longhorn NodeReady=False. - Longhorn Node reason is
ManagerPodMissing, or the Longhorn manager DaemonSet has no pod on the node. - Longhorn manager DaemonSet selects a node label such as
longhorn-host=true, and the Kubernetes node is missing it. - Longhorn CSI/engine pods or a Longhorn Node CR exist for the node, proving the node participates in Longhorn even though manager scheduling is broken.
- VolumeAttachment errors say Longhorn cannot attach because the node is not ready, despite Kubernetes Ready=True.
Suggested automated escalation ladder:
- Compare Kubernetes node readiness, labels, taints, Longhorn Node readiness/schedulability, Longhorn manager DaemonSet selector, manager pod presence, CSI pod presence, and VolumeAttachment errors.
- If desired-state evidence proves the node should be a Longhorn node, restore the missing manager selector label and wait for the manager pod and Longhorn Node
Ready=True. - If desired-state evidence is missing, report a clear unsafe label-drift blocker rather than guessing.
- After Longhorn Node
Ready=True, retry stale VolumeAttachments by recreating the affected controller-owned pending pod or using a safe VolumeAttachment retry policy. - For Kubernetes-cordoned Longhorn nodes with
allowScheduling=trueand ready disks, either complete the host repair and uncordon or report the explicit blocker; do not leave Longhorn capacity disabled silently.
Implementation notes:
- This must be generic across Longhorn nodes and manager DaemonSet selectors, not hard-coded to
titan-22orlonghorn-host. - Tests should cover missing Longhorn selector labels, manager pod missing, Kubernetes Ready but Longhorn NotReady, Kubernetes cordon causing Longhorn Schedulable=False, and stale attach errors that clear after Longhorn readiness is restored.