Restart NotReady Pimox worker during heal
This commit is contained in:
parent
91df63af96
commit
8e19df1c71
|
|
@ -284,7 +284,9 @@ Normal `status` remains read-only. The heal planner maps known findings to
|
||||||
guarded actions, chooses one highest-impact auto-eligible finding, checks the
|
guarded actions, chooses one highest-impact auto-eligible finding, checks the
|
||||||
proposed command against `infra/agent-sandbox/policy.tsv`, and runs only that
|
proposed command against `infra/agent-sandbox/policy.tsv`, and runs only that
|
||||||
one action. Re-run `./jeannie status` after every heal cycle so Jeannie
|
one action. Re-run `./jeannie status` after every heal cycle so Jeannie
|
||||||
can reassess before touching the next issue. Destructive actions remain blocked.
|
can reassess before touching the next issue. If a specific Pimox worker is
|
||||||
|
NotReady, heal targets that worker with `./jeannie workers restart N`
|
||||||
|
instead of repeating a broad cluster start. Destructive actions remain blocked.
|
||||||
|
|
||||||
## Model Behavior Observatory
|
## Model Behavior Observatory
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -284,7 +284,9 @@ Normal `status` remains read-only. The heal planner maps known findings to
|
||||||
guarded actions, chooses one highest-impact auto-eligible finding, checks the
|
guarded actions, chooses one highest-impact auto-eligible finding, checks the
|
||||||
proposed command against `infra/agent-sandbox/policy.tsv`, and runs only that
|
proposed command against `infra/agent-sandbox/policy.tsv`, and runs only that
|
||||||
one action. Re-run `./{{ main_script }} status` after every heal cycle so Jeannie
|
one action. Re-run `./{{ main_script }} status` after every heal cycle so Jeannie
|
||||||
can reassess before touching the next issue. Destructive actions remain blocked.
|
can reassess before touching the next issue. If a specific Pimox worker is
|
||||||
|
NotReady, heal targets that worker with `./{{ main_script }} workers restart N`
|
||||||
|
instead of repeating a broad cluster start. Destructive actions remain blocked.
|
||||||
|
|
||||||
## Model Behavior Observatory
|
## Model Behavior Observatory
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -212,7 +212,7 @@ visible Uptime Kuma monitor coverage.
|
||||||
|
|
||||||
`workers <command>`
|
`workers <command>`
|
||||||
: Manage Kubernetes/Pimox worker lifecycle operations. Commands include `list`,
|
: Manage Kubernetes/Pimox worker lifecycle operations. Commands include `list`,
|
||||||
`tailnet`, `start`, `stop`, `drain`, `uncordon`, `recreate-plan`, and
|
`tailnet`, `start`, `stop`, `restart`, `drain`, `uncordon`, `recreate-plan`, and
|
||||||
`rebalance`.
|
`rebalance`.
|
||||||
|
|
||||||
`change-journal {list|path}`
|
`change-journal {list|path}`
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ pattern decision reason
|
||||||
./jeannie ai-evals allow Read-only eval harness.
|
./jeannie ai-evals allow Read-only eval harness.
|
||||||
./jeannie prompt-injection-lab allow Read-only security lab harness.
|
./jeannie prompt-injection-lab allow Read-only security lab harness.
|
||||||
./jeannie start-cluster allow Idempotent recovery path that starts saved Kubernetes services and worker VMs without destroying state.
|
./jeannie start-cluster allow Idempotent recovery path that starts saved Kubernetes services and worker VMs without destroying state.
|
||||||
|
./jeannie workers restart allow Targeted Pimox worker VM restart for one-at-a-time self-healing.
|
||||||
./jeannie deploy-gitea allow Idempotent Debian-hosted Gitea Compose repair path for this homelab.
|
./jeannie deploy-gitea allow Idempotent Debian-hosted Gitea Compose repair path for this homelab.
|
||||||
./jeannie rpi-services allow Idempotent RPi Pi-hole/Unbound/Uptime Kuma Compose repair path for this homelab.
|
./jeannie rpi-services allow Idempotent RPi Pi-hole/Unbound/Uptime Kuma Compose repair path for this homelab.
|
||||||
./jeannie up approval High-blast-radius deployment.
|
./jeannie up approval High-blast-radius deployment.
|
||||||
|
|
|
||||||
|
2
jeannie
2
jeannie
|
|
@ -6579,7 +6579,7 @@ Cluster Lifecycle
|
||||||
rebuild-cluster Recreate the cluster through the guarded path.
|
rebuild-cluster Recreate the cluster through the guarded path.
|
||||||
stop-cluster Stop Kubernetes and worker VMs without destroy.
|
stop-cluster Stop Kubernetes and worker VMs without destroy.
|
||||||
start-cluster Start Kubernetes and desired worker VMs.
|
start-cluster Start Kubernetes and desired worker VMs.
|
||||||
workers <list|tailnet|start|stop|drain|uncordon|recreate-plan|rebalance>
|
workers <list|tailnet|start|stop|restart|drain|uncordon|recreate-plan|rebalance>
|
||||||
Manage Pimox/Kubernetes workers.
|
Manage Pimox/Kubernetes workers.
|
||||||
move-prometheus-stack-workers Move monitoring workloads to worker nodes.
|
move-prometheus-stack-workers Move monitoring workloads to worker nodes.
|
||||||
doctor-versions [--all|--details|--verbose|--json]
|
doctor-versions [--all|--details|--verbose|--json]
|
||||||
|
|
|
||||||
32
scripts/heal
32
scripts/heal
|
|
@ -6,6 +6,7 @@ from __future__ import annotations
|
||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
@ -79,6 +80,26 @@ def rule_for(row: dict[str, str]) -> HealRule | None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def pimox_worker_index(row: dict[str, str]) -> int | None:
|
||||||
|
text = " ".join(str(row.get(key, "")) for key in ("summary", "detail", "check"))
|
||||||
|
match = re.search(r"\bpimox-worker-(\d{1,2})\b", text)
|
||||||
|
if not match:
|
||||||
|
return None
|
||||||
|
return int(match.group(1))
|
||||||
|
|
||||||
|
|
||||||
|
def command_for(row: dict[str, str], rule: HealRule) -> tuple[str, str, int]:
|
||||||
|
if rule.check == "Kubernetes nodes Ready":
|
||||||
|
index = pimox_worker_index(row)
|
||||||
|
if index is not None:
|
||||||
|
return (
|
||||||
|
f"./jeannie workers restart {index}",
|
||||||
|
f"Restart Pimox VM for pimox-worker-{index:02d}; a running VM with a NotReady kubelet can be wedged.",
|
||||||
|
98,
|
||||||
|
)
|
||||||
|
return rule.command, rule.reason, rule.priority
|
||||||
|
|
||||||
|
|
||||||
def build_plan(status: dict[str, object]) -> list[dict[str, object]]:
|
def build_plan(status: dict[str, object]) -> list[dict[str, object]]:
|
||||||
planned: list[dict[str, object]] = []
|
planned: list[dict[str, object]] = []
|
||||||
seen_commands: set[str] = set()
|
seen_commands: set[str] = set()
|
||||||
|
|
@ -99,19 +120,20 @@ def build_plan(status: dict[str, object]) -> list[dict[str, object]]:
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
if rule.command in seen_commands and rule.auto:
|
command, reason, priority = command_for(row, rule)
|
||||||
|
if command in seen_commands and rule.auto:
|
||||||
continue
|
continue
|
||||||
seen_commands.add(rule.command)
|
seen_commands.add(command)
|
||||||
planned.append(
|
planned.append(
|
||||||
{
|
{
|
||||||
"check": row.get("check", ""),
|
"check": row.get("check", ""),
|
||||||
"area": row.get("area", ""),
|
"area": row.get("area", ""),
|
||||||
"status": row.get("status", ""),
|
"status": row.get("status", ""),
|
||||||
"command": rule.command,
|
"command": command,
|
||||||
"risk": rule.risk,
|
"risk": rule.risk,
|
||||||
"auto": rule.auto,
|
"auto": rule.auto,
|
||||||
"priority": rule.priority,
|
"priority": priority,
|
||||||
"reason": rule.reason,
|
"reason": reason,
|
||||||
"summary": row.get("summary", ""),
|
"summary": row.get("summary", ""),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ Commands:
|
||||||
tailnet Install/join Tailscale on generated Pimox workers
|
tailnet Install/join Tailscale on generated Pimox workers
|
||||||
start [index|all] Start Pimox worker VM(s)
|
start [index|all] Start Pimox worker VM(s)
|
||||||
stop [index|all] Gracefully stop Pimox worker VM(s)
|
stop [index|all] Gracefully stop Pimox worker VM(s)
|
||||||
|
restart [index|all] Gracefully restart Pimox worker VM(s)
|
||||||
drain <node> Drain a Kubernetes worker node
|
drain <node> Drain a Kubernetes worker node
|
||||||
uncordon <node> Mark a Kubernetes worker node schedulable
|
uncordon <node> Mark a Kubernetes worker node schedulable
|
||||||
recreate-plan <index> Print the safe recreate sequence for one Pimox worker
|
recreate-plan <index> Print the safe recreate sequence for one Pimox worker
|
||||||
|
|
@ -138,6 +139,29 @@ stop_workers() {
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
restart_workers() {
|
||||||
|
local index
|
||||||
|
local vmid
|
||||||
|
local timeout="${LAB_WORKER_SHUTDOWN_TIMEOUT:-90}"
|
||||||
|
|
||||||
|
for index in $(target_indexes "${1:-all}"); do
|
||||||
|
vmid="$(vmid_for_index "$index")"
|
||||||
|
echo "Restarting Pimox worker index $index VM $vmid..."
|
||||||
|
pimox_ssh "set -eu
|
||||||
|
if ! sudo '$PIMOX_QM_BIN' status '$vmid' >/dev/null 2>&1; then
|
||||||
|
echo 'Pimox worker VM $vmid does not exist.' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if sudo '$PIMOX_QM_BIN' status '$vmid' | grep -q 'status: running'; then
|
||||||
|
if ! sudo '$PIMOX_QM_BIN' shutdown '$vmid' --timeout '$timeout'; then
|
||||||
|
echo 'Graceful shutdown timed out; forcing stop for Pimox worker VM $vmid.'
|
||||||
|
sudo '$PIMOX_QM_BIN' stop '$vmid'
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
sudo '$PIMOX_QM_BIN' start '$vmid'"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
drain_node() {
|
drain_node() {
|
||||||
local node="${1:-}"
|
local node="${1:-}"
|
||||||
|
|
||||||
|
|
@ -277,6 +301,9 @@ case "${1:-}" in
|
||||||
stop)
|
stop)
|
||||||
stop_workers "${2:-all}"
|
stop_workers "${2:-all}"
|
||||||
;;
|
;;
|
||||||
|
restart)
|
||||||
|
restart_workers "${2:-all}"
|
||||||
|
;;
|
||||||
drain)
|
drain)
|
||||||
drain_node "${2:-}"
|
drain_node "${2:-}"
|
||||||
;;
|
;;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue