diff --git a/README.md b/README.md index 77023ea..76a65ef 100644 --- a/README.md +++ b/README.md @@ -273,6 +273,7 @@ Run a read-only health snapshot from the Debian server with: ./jeannie status ./jeannie capacity ./jeannie capacity-advisor +./jeannie capacity-limits ./jeannie recover-plan ./jeannie recover-power --dry-run ./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 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 checks, from Debian and Gitea through DNS, Pimox, Kubernetes, GitOps apps, and edge verification. diff --git a/README.md.tmpl b/README.md.tmpl index 7b73191..1399100 100644 --- a/README.md.tmpl +++ b/README.md.tmpl @@ -273,6 +273,7 @@ Run a read-only health snapshot from the Debian server with: ./{{ main_script }} status ./{{ main_script }} capacity ./{{ main_script }} capacity-advisor +./{{ main_script }} capacity-limits ./{{ main_script }} recover-plan ./{{ main_script }} recover-power --dry-run ./{{ 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 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 checks, from Debian and Gitea through DNS, Pimox, Kubernetes, GitOps apps, and edge verification. diff --git a/docs/jeannie.1.md b/docs/jeannie.1.md index b1580dd..ec04b5a 100644 --- a/docs/jeannie.1.md +++ b/docs/jeannie.1.md @@ -71,6 +71,11 @@ problems`, or `--verbose` for the older full dump. recommend whether to add Pimox VMs, taint Debian, or fix resource definitions 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` : Print the ordered disaster recovery checklist and lightweight prerequisite checks for Debian, Gitea, RPi DNS, Pimox, Kubernetes, GitOps apps, and edge diff --git a/infra/resource-recommendations.yml b/infra/resource-recommendations.yml new file mode 100644 index 0000000..44d6051 --- /dev/null +++ b/infra/resource-recommendations.yml @@ -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 diff --git a/jeannie b/jeannie index 0beabe0..64a8160 100755 --- a/jeannie +++ b/jeannie @@ -6016,6 +6016,11 @@ capacity_advisor() { "${REPO_ROOT}/scripts/capacity-advisor" "${@:2}" } +capacity_limits() { + require_debian_server "capacity-limits" + "${REPO_ROOT}/scripts/capacity-limits" "${@:2}" +} + control_plane() { require_debian_server "control-plane" "${REPO_ROOT}/scripts/control-plane" "${@:2}" @@ -6133,6 +6138,7 @@ Operator View And Reports scorecard Compact pass/warn/fail operator scorecard. capacity Compact capacity and placement report. 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-power [--dry-run] Run or preview post-outage recovery. impact [--since REF] [PATH...] Explain affected lab areas before changes. @@ -6236,6 +6242,9 @@ case "${1:-}" in capacity-advisor) capacity_advisor "$@" ;; + capacity-limits) + capacity_limits "$@" + ;; recover-plan) recover_plan ;; diff --git a/scripts/capacity-limits b/scripts/capacity-limits new file mode 100755 index 0000000..f0ae2e8 --- /dev/null +++ b/scripts/capacity-limits @@ -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