Add capacity limit recommendations

This commit is contained in:
juvdiaz 2026-06-29 20:48:17 -06:00
parent eabc5b049b
commit 1954a2e975
6 changed files with 187 additions and 0 deletions

View File

@ -273,6 +273,7 @@ Run a read-only health snapshot from the Debian server with:
./jeannie status ./jeannie status
./jeannie capacity ./jeannie capacity
./jeannie capacity-advisor ./jeannie capacity-advisor
./jeannie capacity-limits
./jeannie recover-plan ./jeannie recover-plan
./jeannie recover-power --dry-run ./jeannie recover-power --dry-run
./jeannie impact --since HEAD~1 ./jeannie impact --since HEAD~1
@ -328,6 +329,11 @@ Kubernetes, Pimox, RPi, and placement signals without dumping full details. Use
placement signals to recommend whether to add Pimox VMs, taint Debian, or fix placement signals to recommend whether to add Pimox VMs, taint Debian, or fix
resource definitions first. resource definitions first.
`capacity-limits` prints suggested Kubernetes `resources.requests` and
`resources.limits` YAML snippets for containers missing them. It uses
`infra/resource-recommendations.yml` as a conservative starting profile and does
not edit manifests.
`recover-plan` prints the disaster recovery order and lightweight prerequisite `recover-plan` prints the disaster recovery order and lightweight prerequisite
checks, from Debian and Gitea through DNS, Pimox, Kubernetes, GitOps apps, and checks, from Debian and Gitea through DNS, Pimox, Kubernetes, GitOps apps, and
edge verification. edge verification.

View File

@ -273,6 +273,7 @@ Run a read-only health snapshot from the Debian server with:
./{{ main_script }} status ./{{ main_script }} status
./{{ main_script }} capacity ./{{ main_script }} capacity
./{{ main_script }} capacity-advisor ./{{ main_script }} capacity-advisor
./{{ main_script }} capacity-limits
./{{ main_script }} recover-plan ./{{ main_script }} recover-plan
./{{ main_script }} recover-power --dry-run ./{{ main_script }} recover-power --dry-run
./{{ main_script }} impact --since HEAD~1 ./{{ main_script }} impact --since HEAD~1
@ -328,6 +329,11 @@ Kubernetes, Pimox, RPi, and placement signals without dumping full details. Use
placement signals to recommend whether to add Pimox VMs, taint Debian, or fix placement signals to recommend whether to add Pimox VMs, taint Debian, or fix
resource definitions first. resource definitions first.
`capacity-limits` prints suggested Kubernetes `resources.requests` and
`resources.limits` YAML snippets for containers missing them. It uses
`infra/resource-recommendations.yml` as a conservative starting profile and does
not edit manifests.
`recover-plan` prints the disaster recovery order and lightweight prerequisite `recover-plan` prints the disaster recovery order and lightweight prerequisite
checks, from Debian and Gitea through DNS, Pimox, Kubernetes, GitOps apps, and checks, from Debian and Gitea through DNS, Pimox, Kubernetes, GitOps apps, and
edge verification. edge verification.

View File

@ -71,6 +71,11 @@ problems`, or `--verbose` for the older full dump.
recommend whether to add Pimox VMs, taint Debian, or fix resource definitions recommend whether to add Pimox VMs, taint Debian, or fix resource definitions
first. first.
`capacity-limits [namespace]`
: Recommend Kubernetes `resources.requests` and `resources.limits` YAML snippets
for containers missing them. Uses `infra/resource-recommendations.yml` and does
not edit manifests.
`recover-plan` `recover-plan`
: Print the ordered disaster recovery checklist and lightweight prerequisite : Print the ordered disaster recovery checklist and lightweight prerequisite
checks for Debian, Gitea, RPi DNS, Pimox, Kubernetes, GitOps apps, and edge checks for Debian, Gitea, RPi DNS, Pimox, Kubernetes, GitOps apps, and edge

View File

@ -0,0 +1,32 @@
defaults:
request_cpu_m: 50
request_memory_mib: 128
limit_cpu_multiplier: 4
limit_memory_multiplier: 2
namespaces:
website-production:
request_cpu_m: 75
request_memory_mib: 128
limit_cpu_multiplier: 4
limit_memory_multiplier: 2
demos-static:
request_cpu_m: 25
request_memory_mib: 64
limit_cpu_multiplier: 4
limit_memory_multiplier: 2
heimdall:
request_cpu_m: 50
request_memory_mib: 128
limit_cpu_multiplier: 4
limit_memory_multiplier: 2
arr-stack:
request_cpu_m: 150
request_memory_mib: 512
limit_cpu_multiplier: 3
limit_memory_multiplier: 2
argocd:
request_cpu_m: 100
request_memory_mib: 256
limit_cpu_multiplier: 3
limit_memory_multiplier: 2

View File

@ -6016,6 +6016,11 @@ capacity_advisor() {
"${REPO_ROOT}/scripts/capacity-advisor" "${@:2}" "${REPO_ROOT}/scripts/capacity-advisor" "${@:2}"
} }
capacity_limits() {
require_debian_server "capacity-limits"
"${REPO_ROOT}/scripts/capacity-limits" "${@:2}"
}
control_plane() { control_plane() {
require_debian_server "control-plane" require_debian_server "control-plane"
"${REPO_ROOT}/scripts/control-plane" "${@:2}" "${REPO_ROOT}/scripts/control-plane" "${@:2}"
@ -6133,6 +6138,7 @@ Operator View And Reports
scorecard Compact pass/warn/fail operator scorecard. scorecard Compact pass/warn/fail operator scorecard.
capacity Compact capacity and placement report. capacity Compact capacity and placement report.
capacity-advisor Decide whether to add VMs or fix placement/resources. capacity-advisor Decide whether to add VMs or fix placement/resources.
capacity-limits [namespace] Recommend requests/limits YAML snippets.
recover-plan Print disaster recovery order and prerequisites. recover-plan Print disaster recovery order and prerequisites.
recover-power [--dry-run] Run or preview post-outage recovery. recover-power [--dry-run] Run or preview post-outage recovery.
impact [--since REF] [PATH...] Explain affected lab areas before changes. impact [--since REF] [PATH...] Explain affected lab areas before changes.
@ -6236,6 +6242,9 @@ case "${1:-}" in
capacity-advisor) capacity-advisor)
capacity_advisor "$@" capacity_advisor "$@"
;; ;;
capacity-limits)
capacity_limits "$@"
;;
recover-plan) recover-plan)
recover_plan recover_plan
;; ;;

129
scripts/capacity-limits Executable file
View File

@ -0,0 +1,129 @@
#!/usr/bin/env bash
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
KUBECONFIG_PATH="${KUBECONFIG:-${LAB_KUBECONFIG_PATH:-/home/jv/.kube/config}}"
PROFILE_FILE="${HOMELAB_RESOURCE_RECOMMENDATIONS:-${REPO_ROOT}/infra/resource-recommendations.yml}"
NAMESPACE="${1:-}"
PODS_JSON=""
cleanup() {
if [ -n "$PODS_JSON" ] && [ -f "$PODS_JSON" ]; then
rm -f "$PODS_JSON"
fi
}
trap cleanup EXIT
usage() {
cat <<'EOF'
Usage: ./jeannie capacity-limits [namespace]
Recommend Kubernetes requests/limits for containers missing them. This command
prints YAML snippets only; it does not edit manifests.
EOF
}
case "${1:-}" in
-h|--help|help)
usage
exit 0
;;
esac
if ! command -v kubectl >/dev/null 2>&1; then
echo "kubectl is required." >&2
exit 1
fi
if [ ! -s "$KUBECONFIG_PATH" ]; then
echo "kubeconfig is missing: $KUBECONFIG_PATH" >&2
exit 1
fi
PODS_JSON="$(mktemp "${TMPDIR:-/tmp}/jeannie-capacity-limits.XXXXXX")"
if [ -n "$NAMESPACE" ]; then
kubectl --kubeconfig "$KUBECONFIG_PATH" -n "$NAMESPACE" get pods -o json >"$PODS_JSON"
else
kubectl --kubeconfig "$KUBECONFIG_PATH" get pods -A -o json >"$PODS_JSON"
fi
python3 - "$PROFILE_FILE" "$PODS_JSON" <<'PY'
import json
import sys
profile_file, pods_json = sys.argv[1:3]
try:
import yaml
except ImportError:
yaml = None
profiles = {
"defaults": {
"request_cpu_m": 50,
"request_memory_mib": 128,
"limit_cpu_multiplier": 4,
"limit_memory_multiplier": 2,
},
"namespaces": {},
}
if yaml:
with open(profile_file, encoding="utf-8") as handle:
loaded = yaml.safe_load(handle) or {}
profiles["defaults"].update(loaded.get("defaults", {}))
profiles["namespaces"].update(loaded.get("namespaces", {}))
with open(pods_json, encoding="utf-8") as handle:
pods = json.load(handle).get("items", [])
seen = set()
def profile_for(namespace):
profile = dict(profiles["defaults"])
profile.update(profiles["namespaces"].get(namespace, {}))
return profile
print("Jeannie Capacity Limits")
print("=======================")
print("mode: recommendations only")
print(f"profile_file: {profile_file}")
count = 0
for pod in pods:
namespace = pod["metadata"].get("namespace", "default")
owner = namespace
for ref in pod["metadata"].get("ownerReferences", []):
if ref.get("kind") in {"ReplicaSet", "StatefulSet", "DaemonSet", "Job"}:
owner = ref.get("name", owner)
break
for container in pod.get("spec", {}).get("containers", []):
resources = container.get("resources", {})
requests = resources.get("requests", {})
limits = resources.get("limits", {})
if requests.get("cpu") and requests.get("memory") and limits.get("cpu") and limits.get("memory"):
continue
key = (namespace, owner, container["name"])
if key in seen:
continue
seen.add(key)
profile = profile_for(namespace)
request_cpu = int(profile["request_cpu_m"])
request_memory = int(profile["request_memory_mib"])
limit_cpu = request_cpu * int(profile["limit_cpu_multiplier"])
limit_memory = request_memory * int(profile["limit_memory_multiplier"])
count += 1
print(f"\n{count}. {namespace}/{owner}:{container['name']}")
print(" Add or tune this resources block in the owning manifest/chart:")
print(" resources:")
print(" requests:")
print(f" cpu: {request_cpu}m")
print(f" memory: {request_memory}Mi")
print(" limits:")
print(f" cpu: {limit_cpu}m")
print(f" memory: {limit_memory}Mi")
if count == 0:
print("\nNo containers missing requests/limits were found.")
else:
print("\nNotes:")
print("- Treat these as starting values, not truth.")
print("- Prefer app namespaces first; be careful with kube-system/control-plane pods.")
print("- After changes, run ./jeannie resource-budget and ./jeannie capacity.")
PY