Skip to content

Commit f023bec

Browse files
committed
Add GPU HPA deployment chart and split Kubernetes deploy docs.
Introduce nemoclaw-gpu (Ollama + agent, DCGM gpu_utilization_percent HPA) with load-test and ops scripts tuned for 4× L40S. Split deploy guides into README-gpu/README-cpu and refresh the CPU chart helpers.
1 parent a1ebcf9 commit f023bec

59 files changed

Lines changed: 4419 additions & 866 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

deploy/README-cpu.md

Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
# NemoClaw Kubernetes deployment — CPU (HPA, optional)
2+
3+
**Optional pre-GPU chart** — use only if you do not have cluster GPUs yet and want to test agents/HPA against **NVIDIA Inference Hub** (Nemotron Ultra). For the real deployment, use the **[GPU chart](README-gpu.md)**.
4+
5+
Helm chart and scripts for **CPU agent pods** with **HPA**. Each pod uses **1 CPU** and proxies remote inference (no `nvidia.com/gpu`).
6+
7+
**Index:** [deploy/README.md](README.md) · **Models (CPU vs GPU):** [helm/README.md](helm/README.md)
8+
9+
This document is the **CPU deployment guide** — install, operate, and load-test the optional CPU chart. GPU deployment: [README-gpu.md](README-gpu.md).
10+
11+
---
12+
13+
## Install (CPU only)
14+
15+
You do **not** install the chart, then HPA, then load test as three separate steps. One script installs **metrics-server + Helm chart + CPU HPA** together.
16+
17+
```bash
18+
cd ~/NemoClaw/deploy/helm/nemoclaw-cpu
19+
source ~/.nemoclaw/secrets.env # NVIDIA_INFERENCE_HUB_API_KEY=sk-...
20+
21+
./scripts/install-hpa.sh
22+
```
23+
24+
Confirm idle state (no workload required):
25+
26+
```bash
27+
kubectl get hpa -n nemoclaw
28+
kubectl get pods -n nemoclaw
29+
```
30+
31+
Expect **1 Running pod** and HPA **REPLICAS 1** (CPU well below the ~65% target). **Autoscaling only happens under load** — see [Idle vs load test](#idle-vs-load-test).
32+
33+
---
34+
35+
## Port-forward
36+
37+
CPU agent listens on **8080** (Service, pod, and local port-forward):
38+
39+
```bash
40+
kubectl port-forward -n nemoclaw svc/nemoclaw-nemoclaw-cpu-agent 8080:8080
41+
```
42+
43+
**Verify** Inference Hub (needed for chat, not for rollout):
44+
45+
```bash
46+
curl -s http://127.0.0.1:8080/healthz # always ok if pod is up
47+
curl -s http://127.0.0.1:8080/readyz # 200 = Hub key OK; 503 = fix secrets
48+
```
49+
50+
Override host port: `LOCAL_PORT=8080 ./scripts/install-hpa.sh` (default is 8080).
51+
52+
Optional demo of scale up/down:
53+
54+
```bash
55+
./scripts/hpa-load-test.sh
56+
./scripts/hpa-reset.sh # return to idle baseline after the test
57+
```
58+
59+
60+
<img width="1832" height="1302" alt="Screenshot 2026-05-28 at 12 49 11 PM" src="https://github.com/user-attachments/assets/a91dd398-059a-4e3f-afec-07b2b66b12b0" />
61+
62+
63+
64+
65+
<img width="1926" height="550" alt="Screenshot 2026-05-28 at 12 42 34 PM" src="https://github.com/user-attachments/assets/25b876d5-19a8-4758-8880-5702b38d5f51" />
66+
67+
68+
69+
70+
If install keeps failing with **rollout failed** or Deployment **`0 up-to-date`**:
71+
72+
```bash
73+
./scripts/cluster-recover.sh
74+
```
75+
76+
Chart details: [helm/nemoclaw-cpu/README.md](helm/nemoclaw-cpu/README.md) · [CPU vs GPU comparison](helm/README.md)
77+
78+
---
79+
80+
## Scripts
81+
82+
All scripts live under `deploy/helm/nemoclaw-cpu/scripts/`. Run from the chart directory after sourcing `~/.nemoclaw/secrets.env`.
83+
84+
| Script | What it does | When to use |
85+
|--------|----------------|-------------|
86+
| **`install-hpa.sh`** | Enables **metrics-server**; `helm upgrade --install` with **CPU HPA** (`values-step2-hpa.yaml`, min **1** / max **7**); readiness on **`/healthz`** so rollout succeeds without Hub traffic. | **First install** or healthy cluster refresh. |
87+
| **`hpa-reset.sh`** | Deletes load-test Jobs; force-deletes **pods** and stale ReplicaSets; **keeps HPA + Deployment** by default; `helm upgrade` to idle baseline. | After load test, stuck load-test pods, return to idle. |
88+
| **`cluster-recover.sh`** | Removes stray resources, **`helm uninstall`**, wipes namespace leftovers, **restarts MicroK8s**, runs **`install-hpa.sh`**. | Repeated **rollout failed**, Deployment **`0 updated \| 0 total`**, ghost HPA. |
89+
| **`hpa-load-test.sh`** | Switches to **saturate** values (400m CPU/pod); runs in-cluster load Job (~12 min); watches scale up then down. | Prove HPA only — not required for setup. |
90+
| **`install-performance-hpa.sh`** | Optional Prometheus + adapter + inflight-metric HPA. Heavy; may timeout on small VMs. | Advanced only. |
91+
| **`hpa-common.sh`** | Shared helpers (not run directly). ||
92+
93+
### Workflow
94+
95+
```text
96+
First time: install-hpa.sh
97+
After load test / tidy: hpa-reset.sh
98+
Rollout keeps failing: cluster-recover.sh
99+
Prove autoscaling: hpa-load-test.sh → watch → hpa-reset.sh
100+
```
101+
102+
Do **not** run `hpa-reset.sh && install-hpa.sh` unless you used `SKIP_HELM=1` on reset. Reset already runs `helm upgrade`.
103+
104+
### Environment variables
105+
106+
| Variable | Scripts | Effect |
107+
|----------|---------|--------|
108+
| `DELETE_DEPLOYMENT=1` | `hpa-reset.sh` | Delete Deployment before reinstall (stuck rollout) |
109+
| `DELETE_HPA=1` | `hpa-reset.sh` | Delete HPA; two-phase reinstall (`desiredReplicas=0`) |
110+
| `SKIP_HELM=1` | `hpa-reset.sh` | kubectl cleanup only; then run `install-hpa.sh` |
111+
| `RUN_LOAD_TEST=1` | `hpa-reset.sh` | Run `hpa-load-test.sh` after reset |
112+
| `RESTART_MICROK8S=0` | `cluster-recover.sh` | Skip MicroK8s restart (cleanup only) |
113+
| `RUN_INSTALL=0` | `cluster-recover.sh` | Cleanup without reinstall |
114+
| `MIN_REPLICAS` / `MAX_REPLICAS` | install, reset | Override HPA bounds (default 1 / 7) |
115+
| `ROLLOUT_TIMEOUT` | install, reset | Seconds to wait for rollout (default 300) |
116+
| `LOCAL_PORT` | install | Host port for port-forward hints (default **8080**) |
117+
118+
---
119+
120+
## Architecture
121+
122+
```text
123+
┌─────────────────────────────────────────────────────────────────────────┐
124+
│ Your VM (Brev) │
125+
│ ┌──────────────────────┐ ┌──────────────────────────────────┐ │
126+
│ │ NemoClaw (fork) │ │ MicroK8s — namespace: nemoclaw │ │
127+
│ │ Telegram / OpenShell │ │ agent pods: 1 CPU each (idle) │ │
128+
│ │ (not in this chart) │ │ HPA min 1 … max 7 (CPU metrics) │ │
129+
│ └──────────┬───────────┘ └──────────────┬───────────────────┘ │
130+
│ │ same API key │ metrics-server │
131+
└─────────────┼───────────────────────────────────┼────────────────────────┘
132+
│ HTTPS │
133+
▼ ▼
134+
┌────────────────────────────────────────────────┐
135+
│ NVIDIA Inference Hub (Nemotron Ultra) │
136+
└────────────────────────────────────────────────┘
137+
```
138+
139+
HPA scales on **CPU of agent pods** in `nemoclaw`, not on VM Telegram/OpenShell traffic.
140+
141+
---
142+
143+
## Load balancer
144+
145+
This chart uses a **ClusterIP** Service (`nemoclaw-nemoclaw-cpu-agent`) — **no AWS ALB/NLB** and no Ingress by default.
146+
147+
| Resource | Type | Role |
148+
|----------|------|------|
149+
| Agent Service | **ClusterIP** | In-cluster VIP; kube-proxy balances **new connections** across Ready CPU agent pods |
150+
| `kubectl port-forward` | Local tunnel | Debug on **8080**; not production routing |
151+
| Load-test Job | In-cluster client | Hits Service DNS → all replicas as HPA scales |
152+
153+
HPA changes replica count; the Service picks up new pods automatically. That is **not** the same as an external load balancer or GPU-aware routing.
154+
155+
**Next step (not implemented):** add **NGINX Ingress** (or AWS Load Balancer Controller on EKS) for external clients and smarter HTTP routing (least-connections, timeouts, rate limits). See [README-gpu.md — Load balancer](README-gpu.md#load-balancer) for the same pattern on GPU (including example **`g6e.12xlarge`** / 4× L40S reference node).
156+
157+
---
158+
159+
## Prerequisites
160+
161+
| Item | Notes |
162+
|------|--------|
163+
| MicroK8s or K8s 1.25+ | `microk8s status --wait-ready` |
164+
| `helm` 3, `kubectl` | |
165+
| Inference Hub key | `~/.nemoclaw/secrets.env` |
166+
167+
```bash
168+
# ~/.nemoclaw/secrets.env
169+
export NVIDIA_INFERENCE_HUB_API_KEY='sk-...'
170+
```
171+
172+
`install-hpa.sh` enables metrics-server on MicroK8s when present. Confirm:
173+
174+
```bash
175+
kubectl get apiservice v1beta1.metrics.k8s.io
176+
# AVAILABLE should be True
177+
```
178+
179+
---
180+
181+
## Readiness and Inference Hub
182+
183+
| Probe | Path | Used for |
184+
|-------|------|----------|
185+
| Liveness | `/healthz` | Pod restart if process dead |
186+
| Readiness (default) | `/healthz` | Rollout success (`probes.readinessChecksInferenceHub: false`) |
187+
| Manual check | `/readyz` | Inference Hub reachable (chat / load test) |
188+
189+
Install succeeds when the agent process is up. **`/readyz` can still return 503** if the API key is wrong — fix secrets and `helm upgrade`, or use:
190+
191+
```bash
192+
helm upgrade nemoclaw . -n nemoclaw -f values-step2-hpa.yaml \
193+
--reuse-values --set inference.apiKey="${NVIDIA_INFERENCE_HUB_API_KEY}"
194+
```
195+
196+
To require Hub for readiness (stricter, install may fail without valid key):
197+
198+
```bash
199+
helm upgrade nemoclaw . -n nemoclaw --reuse-values \
200+
--set probes.readinessChecksInferenceHub=true
201+
```
202+
203+
---
204+
205+
## Values overlays
206+
207+
| File | Used by | Purpose |
208+
|------|---------|---------|
209+
| `values-step2-hpa.yaml` | `install-hpa.sh`, `hpa-reset.sh` | Idle baseline: 1 CPU/pod, CPU % HPA, `/healthz` readiness |
210+
| `values-step2-hpa-saturate.yaml` | `hpa-load-test.sh` | 400m CPU request/pod so up to 7 replicas fit on 8 vCPU |
211+
| `values-step2-hpa-performance.yaml` | `install-performance-hpa.sh` | Scale on `nemoclaw_http_inflight_requests` (Prometheus) |
212+
213+
---
214+
215+
## Idle vs load test
216+
217+
### Idle (no traffic)
218+
219+
| Resource | Expected |
220+
|----------|----------|
221+
| Pods | **1** agent, `Running`, `READY 1/1` |
222+
| HPA | `MINPODS 1`, `MAXPODS 7`, **`REPLICAS 1`** |
223+
| TARGETS | `cpu: <low>%/65%` (may show `<unknown>` briefly after install) |
224+
225+
**No scale-up or scale-down** without workload — HPA stays at min replicas.
226+
227+
### Under load (`hpa-load-test.sh`)
228+
229+
Replicas rise toward **7** while CPU is high; after the Job ends, count drifts back to **1** over ~2–8 minutes (scale-down stabilization ~120s).
230+
231+
Watch (use separate commands — some kubectl versions reject `hpa,pods` together):
232+
233+
```bash
234+
kubectl get hpa -n nemoclaw -w
235+
kubectl get pods -n nemoclaw -w
236+
kubectl top pods -n nemoclaw
237+
less /tmp/nemoclaw-hpa-watch.log
238+
```
239+
240+
---
241+
242+
## Troubleshooting
243+
244+
| Symptom | What to try |
245+
|---------|-------------|
246+
| `install-hpa.sh`**rollout failed** | `./scripts/cluster-recover.sh` |
247+
| Deployment **`0 up-to-date`**, no ReplicaSet | `./scripts/cluster-recover.sh` (restarts MicroK8s) |
248+
| HPA **`REPLICAS 0`** / `desiredReplicas=0` | `DELETE_HPA=1 ./scripts/hpa-reset.sh` — never `kubectl scale … --replicas=0` |
249+
| `/readyz` **503** after install | Fix `NVIDIA_INFERENCE_HUB_API_KEY`; pod can still be Running |
250+
| Port-forward fails | Check nothing else is bound to local port **8080** |
251+
| Load-test pods stuck `Terminating` | `./scripts/hpa-reset.sh` |
252+
| HPA slow to scale down | Normal after load stops |
253+
| Prometheus install timeout | Use CPU path only (`install-hpa.sh`) |
254+
255+
---
256+
257+
## Optional: Prometheus performance HPA
258+
259+
Scales on `nemoclaw_http_inflight_requests` instead of CPU. Requires kube-prometheus-stack + prometheus-adapter; **heavy** on small VMs.
260+
261+
```bash
262+
PROM_HELM_TIMEOUT=35m ./scripts/install-performance-hpa.sh
263+
```
264+
265+
See [helm/nemoclaw-cpu/observability.md](helm/nemoclaw-cpu/observability.md).
266+
267+
---
268+
269+
## Directory layout
270+
271+
```text
272+
deploy/
273+
├── README-cpu.md ← this file
274+
├── README-gpu.md ← GPU deployment
275+
└── helm/
276+
├── README.md ← CPU vs GPU comparison (models, endpoints)
277+
├── nemoclaw-cpu/
278+
│ ├── README.md
279+
│ ├── values.yaml
280+
│ ├── values-step2-hpa.yaml
281+
│ ├── values-step2-hpa-saturate.yaml
282+
│ ├── values-step2-hpa-performance.yaml
283+
│ ├── scripts/
284+
│ │ ├── install-hpa.sh ← start here
285+
│ │ ├── cluster-recover.sh ← rollout / controller stuck
286+
│ │ ├── hpa-reset.sh
287+
│ │ ├── hpa-load-test.sh
288+
│ │ ├── install-performance-hpa.sh
289+
│ │ └── hpa-common.sh
290+
│ └── files/
291+
└── nemoclaw-gpu/
292+
```
293+
294+
Cursor skill: `.cursor/skills/nemoclaw-k8s-hpa/`
295+
296+
---
297+
298+
## Uninstall
299+
300+
```bash
301+
helm uninstall nemoclaw -n nemoclaw
302+
helm uninstall prometheus-adapter kube-prometheus -n monitoring 2>/dev/null || true
303+
kubectl delete namespace nemoclaw monitoring --ignore-not-found
304+
```
305+
306+
---
307+
308+
## Further reading
309+
310+
- [README-gpu.md](README-gpu.md) — GPU + Ollama deployment
311+
- [helm/nemoclaw-cpu/README.md](helm/nemoclaw-cpu/README.md) — chart values, manual helm, load-test tuning
312+
- [NVIDIA Inference Hub](https://inference-api.nvidia.com)
313+
- [Kubernetes HPA](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/)

0 commit comments

Comments
 (0)