127 lines
4.8 KiB
Bash
Executable File
127 lines
4.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
|
|
run_check() {
|
|
local name="$1"
|
|
shift
|
|
echo "## ${name}"
|
|
set +e
|
|
"$@" 2>&1
|
|
local status=$?
|
|
set -e
|
|
echo "exit_status=${status}"
|
|
}
|
|
|
|
tmp_file="$(mktemp "${TMPDIR:-/tmp}/jeannie-capacity-advisor.XXXXXX")"
|
|
trap 'rm -f "$tmp_file"' EXIT
|
|
|
|
{
|
|
run_check "capacity" "${REPO_ROOT}/scripts/capacity-report" --only problems
|
|
run_check "resource-budget" "${REPO_ROOT}/scripts/resource-budget"
|
|
run_check "control-plane" "${REPO_ROOT}/scripts/control-plane" status
|
|
} >"$tmp_file"
|
|
|
|
python3 - "$tmp_file" <<'PY'
|
|
import re
|
|
import sys
|
|
|
|
text = open(sys.argv[1], encoding="utf-8").read()
|
|
lowered = text.lower()
|
|
|
|
missing_match = re.search(r"(\d+)\s+pods missing requests/limits", text)
|
|
missing_count = int(missing_match.group(1)) if missing_match else 0
|
|
|
|
pressure = any(token in lowered for token in ["diskpressure", "memorypressure", "node pressure"])
|
|
worker_storage_warn = "worker storage" in lowered and "warn" in lowered
|
|
control_plane_section = text.split("## control-plane", 1)[-1]
|
|
control_plane_tainted = "node-role.kubernetes.io/control-plane" in control_plane_section and ":NoSchedule" in control_plane_section
|
|
app_control_plane_pods = []
|
|
legacy_control_plane_pods = []
|
|
|
|
for line in control_plane_section.splitlines():
|
|
match = re.match(r"\s*([a-z0-9-]+)\s+([^\s]+)\s+([A-Za-z]+)\s*$", line)
|
|
if not match:
|
|
continue
|
|
namespace, pod_name, status = match.groups()
|
|
if namespace == "arr-stack":
|
|
legacy_control_plane_pods.append((namespace, pod_name, status))
|
|
elif namespace in {"argocd", "website-production", "demos-static", "n8n", "security-lab", "monitoring"}:
|
|
app_control_plane_pods.append((namespace, pod_name, status))
|
|
|
|
print("Jeannie Capacity Advisor")
|
|
print("========================")
|
|
|
|
recommendations = []
|
|
if missing_count:
|
|
recommendations.append(
|
|
(
|
|
"Fix resource requests/limits first",
|
|
f"{missing_count} pods are missing requests/limits, so scheduling and capacity math are not trustworthy yet.",
|
|
"./jeannie resource-budget --details",
|
|
"code/config: add resources to app manifests or Helm values",
|
|
)
|
|
)
|
|
if legacy_control_plane_pods:
|
|
count = len(legacy_control_plane_pods)
|
|
recommendations.append(
|
|
(
|
|
"Remove stale Kubernetes ARR workloads",
|
|
f"{count} legacy arr-stack pod(s) still exist in Kubernetes even though ARR now lives in Docker Compose on Debian.",
|
|
"kubectl delete namespace arr-stack",
|
|
"cluster cleanup: active ARR state is infra/arr-stack, not archive/apps/arr-stack",
|
|
)
|
|
)
|
|
if app_control_plane_pods and control_plane_tainted:
|
|
count = len(app_control_plane_pods)
|
|
namespaces = ", ".join(sorted({namespace for namespace, _, _ in app_control_plane_pods}))
|
|
recommendations.append(
|
|
(
|
|
"Reschedule remaining app/platform pods off Debian",
|
|
f"Debian is already tainted, but {count} pod(s) still run there in: {namespaces}. Existing pods are not evicted by a NoSchedule taint.",
|
|
"./jeannie control-plane pods",
|
|
"scheduling: restart or drain selected workloads; if they return to Debian, check tolerations, node selectors, affinity, and worker readiness",
|
|
)
|
|
)
|
|
elif app_control_plane_pods:
|
|
recommendations.append(
|
|
(
|
|
"Keep app/data-plane pods off Debian",
|
|
"Non-system workloads are still scheduled on the Debian control-plane host.",
|
|
"./jeannie control-plane taint",
|
|
"scheduling: taint Debian after Pimox workers are Ready",
|
|
)
|
|
)
|
|
if pressure or worker_storage_warn:
|
|
recommendations.append(
|
|
(
|
|
"Consider adding or recreating Pimox worker capacity",
|
|
"Cluster pressure or worker storage pressure is visible after current placement checks.",
|
|
"./jeannie workers list",
|
|
"capacity: increase LAB_PIMOX_WORKER_COUNT or recreate the pressured worker",
|
|
)
|
|
)
|
|
|
|
if not recommendations:
|
|
recommendations.append(
|
|
(
|
|
"No new Pimox VM needed from current signals",
|
|
"No capacity warning, node pressure, or control-plane placement issue was detected in the sampled reports.",
|
|
"./jeannie capacity",
|
|
"watch: rerun after adding workloads",
|
|
)
|
|
)
|
|
|
|
for index, (title, why, command, fix) in enumerate(recommendations, start=1):
|
|
print(f"\n{index}. {title}")
|
|
print(f" Why: {why}")
|
|
print(f" Next command: {command}")
|
|
print(f" Fix path: {fix}")
|
|
|
|
print("\nRule of thumb")
|
|
print("- Add requests before deciding you need more VMs.")
|
|
print("- Taint Debian once Pimox workers are stable.")
|
|
print("- Add a Pimox VM only when real pressure remains after placement and requests are cleaned up.")
|
|
PY
|