Skip to content

feat(deploy): add K8s autoscaling and NGINX load balancing for NemoClaw - #7459

Closed
maggiezha wants to merge 44 commits into
NVIDIA:mainfrom
maggiezha:k8s-autoscaling
Closed

feat(deploy): add K8s autoscaling and NGINX load balancing for NemoClaw#7459
maggiezha wants to merge 44 commits into
NVIDIA:mainfrom
maggiezha:k8s-autoscaling

Conversation

@maggiezha

@maggiezha maggiezha commented Jul 24, 2026

Copy link
Copy Markdown

Summary

Adds a Helm chart (deploy/helm/gpu_autoscaling_k8s) that autoscales NemoClaw on Kubernetes using the per-pod gpu_utilization_percent HPA metric, with an NGINX Ingress in front of the agent pods.
The current revision also scopes recovery to chart-owned resources, requires TLS by default, validates the configured readiness model, and rejects shared PVC persistence that is not ReadWriteMany.

Related Issue

Changes

  • Add a Helm chart with Deployment, Service, HPA, and Ingress templates; each replica requests one nvidia.com/gpu.
  • Add install, load-test, reset, monitoring, and recovery scripts for the GPU autoscaling workflow.
  • Restrict recovery to the named load-test Job and chart-owned pod labels.
  • Require TLS during the normal workflow, enforce ssl-redirect: "true" for TLS Ingress, and keep cleartext HTTP behind an explicit isolation preflight.
  • Require the configured Ollama model during readiness checks.
  • Require an explicit ReadWriteMany storage class for shared PVC persistence while preserving emptyDir and single-node hostPath modes.
  • Constrain autoscaling to the fixed gpu_utilization_percent Pods metric and remove unsupported performance, latency, CPU, and memory HPA modes.
  • Add render and script regression tests for resource selectors, HTTPS redirects, persistence validation, cleartext isolation, and the GPU-only HPA contract.
  • Document installation, verification, storage behavior, load testing, traffic distribution, and Grafana queries.

Type of Change

  • Code change (feature, bug fix, or refactor)
  • Code change with doc updates
  • Doc only (prose changes, no code sample modifications)
  • Doc only (includes code sample changes)

Quality Gates

  • Tests added or updated for changed behavior
  • Existing tests cover changed behavior — justification:
  • Tests not applicable — justification:
  • Docs updated for user-facing behavior changes
  • Docs not applicable — justification:
  • Sensitive paths changed (security, policy, credentials, preflight, onboarding, inference, runner, sandbox, or messaging)
  • Sensitive-path review completed or maintainer-approved waiver recorded — reviewer/approval link/justification: pending maintainer review
  • Non-success, skipped, or missing CI check accepted by maintainer — check name, approval link, and follow-up issue:

Documentation Writer Review

  • Documentation writer subagent reviewed the completed changes
  • Result: docs-updated
  • Evidence: deploy/helm/gpu_autoscaling_k8s/README.md, deploy/helm/gpu_autoscaling_k8s/templates/NOTES.txt, and script help; exact-head review passed for RWX persistence, HTTPS redirect, working-directory-independent Helm NOTES, and the GPU-only HPA support boundary, including observability-only request and latency metrics; no documentation change was required for the internal malformed replica-count guard, test-only TLS-policy assertion, YAML SPDX correction, or internal Node ESM metadata.
  • Agent: Codex Desktop

DGX Station Hardware Evidence

Verification

  • PR description includes a Signed-off-by: line and every new commit appears as Verified in GitHub
  • Normal pre-commit, commit-msg, and pre-push hooks passed, or npm run check:diff passed when hooks were skipped or unavailable — normal hooks passed for the signed commits and fast-forward pushes.
  • Targeted behavior tests pass for the current change set, or tests are marked not applicable above — test-render-contract.sh and test-script-security-contract.sh pass on ab009dcb; Bash syntax and git diff --check also pass.
  • Applicable broad gate passed — npm test for broad runtime/test-harness changes; npm run check for repo-wide validation/coverage changes — not applicable to the chart-scoped change; npm run checks:repository passes.
  • Quality Gates section completed with required justifications or waivers
  • No secrets, API keys, or credentials committed
  • npm run docs builds without warnings (doc changes only) — the command passes; Fern reports two warnings.
  • Doc pages follow the style guide (doc changes only)
  • New doc pages include SPDX header and frontmatter (new pages only)

Signed-off-by: maggiezha maggiez@nvidia.com

Summary by CodeRabbit

New Features

  • Added a GPU-enabled Kubernetes Helm chart for inference with configurable resources, storage, scheduling, ingress, authentication, and replicas.
  • Added autoscaling based on GPU utilization, request load, and latency metrics.
  • Added health checks, Prometheus metrics, inference proxying, TLS safeguards, and persistent model storage.
  • Added installation, monitoring, recovery, reset, pod-status, and load-testing utilities.

Documentation

  • Added comprehensive installation, configuration, verification, troubleshooting, and monitoring guidance.

Tests

  • Added Helm rendering and security contract checks for ingress, persistence, cleanup, and autoscaling.

Chores

  • Updated YAML linting exclusions and chart ignore patterns.

Provide a tested Kubernetes deployment with GPU-utilization HPA, monitoring, load validation, and optional NGINX routing.

Signed-off-by: maggiezha <maggiez@nvidia.com>
…cript re-runs

metrics.serviceMonitor.enabled defaulted to false, so every plain `helm
upgrade` from install-hpa.sh/hpa-load-test.sh/hpa-reset.sh (none of which
use --reuse-values) silently reset it and deleted the ServiceMonitor,
breaking per-pod request-count metrics in Grafana even though GPU
utilization (scraped by a separate always-on ServiceMonitor) kept working.

Also trims the README: drop the now-redundant manual "enable scraping"
helm upgrade snippet, add a short troubleshooting note for this failure
mode, and fix stale script/path references.
- Export OLLAMA_HOST (was computed but never applied) before `ollama
  serve`, and drop other genuinely unused shellcheck-flagged locals in
  hpa-common.sh; annotate the one false positive (nameref var) in
  hpa-load-test.sh.
- chmod +x on files with a shebang that weren't marked executable
  (ollama-start.sh, agent-server.mjs, load-generator.mjs).
- Fix markdownlint (extra blank lines) in README.md.
- Exclude Helm templates/ from the check-yaml hook: Go templating
  ({{ ... }}) in .yaml files isn't valid standalone YAML.

Signed-off-by: maggiezha <maggiez@nvidia.com>
@copy-pr-bot

copy-pr-bot Bot commented Jul 24, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a GPU autoscaling Helm chart with configurable HPA metrics, Prometheus integration, inference serving, ingress, operational recovery tools, load generation, documentation, and static contract validation.

Changes

GPU autoscaling deployment

Layer / File(s) Summary
Chart contracts and Kubernetes resources
deploy/helm/gpu_autoscaling_k8s/Chart.yaml, values*.yaml, templates/*, monitoring/*, README.md
Defines chart metadata, deployment resources, persistence, ingress authentication, HPA modes, monitoring configuration, packaging rules, and installation documentation.
Inference agent runtime
deploy/helm/gpu_autoscaling_k8s/files/{agent-server.ts,agent-metrics.ts,ollama-start.sh}
Adds the Ollama startup process, Node inference proxy, readiness endpoints, Prometheus request metrics, and rolling latency metrics.
Installation and HPA operations
deploy/helm/gpu_autoscaling_k8s/scripts/{install-hpa.sh,hpa-common.sh,hpa-reset.sh,cluster-recover.sh,get-hpa.sh,get-agent-pods.sh,hpa-watch.sh}
Adds shared HPA formatting, GPU and custom-metric checks, Helm installation, rollout recovery, cleanup, reset, status, watch, and cluster recovery workflows.
Scale load testing
deploy/helm/gpu_autoscaling_k8s/files/{load-generator.ts,questions-sample.txt}, deploy/helm/gpu_autoscaling_k8s/scripts/hpa-load-test.sh
Discovers ready agent pods, generates controlled chat traffic, manages per-pod concurrency, and verifies HPA scale-up and scale-down behavior.
Chart and script contract validation
deploy/helm/gpu_autoscaling_k8s/scripts/{test-render-contract.sh,test-script-security-contract.sh}
Validates rendered resources, ingress and persistence rules, cleanup selectors, and recovery safety defaults.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Installer
  participant Helm
  participant Prometheus
  participant Adapter
  participant KubernetesHPA
  participant AgentPods
  Installer->>Helm: Install monitoring and GPU chart
  Prometheus->>AgentPods: Scrape GPU and agent metrics
  Adapter->>Prometheus: Query custom metric series
  KubernetesHPA->>Adapter: Read GPU or agent custom metrics
  KubernetesHPA->>AgentPods: Adjust replica count
  Installer->>KubernetesHPA: Verify rollout and HPA bounds
Loading

Suggested labels: area: e2e, area: inference, area: local-models, area: security

Suggested reviewers: cv, apurvvkumaria

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.32% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the PR's main changes: Kubernetes autoscaling and NGINX load balancing for NemoClaw.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

Comment thread deploy/helm/gpu_autoscaling_k8s/scripts/hpa-common.sh Fixed
Comment thread deploy/helm/gpu_autoscaling_k8s/scripts/hpa-common.sh Fixed
Comment thread deploy/helm/gpu_autoscaling_k8s/scripts/hpa-common.sh Fixed
Comment thread deploy/helm/gpu_autoscaling_k8s/scripts/install-hpa.sh Fixed
Comment thread deploy/helm/gpu_autoscaling_k8s/files/agent-server.mjs Fixed
Comment thread deploy/helm/gpu_autoscaling_k8s/files/load-generator.ts Fixed
Comment thread deploy/helm/gpu_autoscaling_k8s/files/load-generator.ts Fixed
Comment thread deploy/helm/gpu_autoscaling_k8s/files/load-generator.mjs Fixed
@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

PR Review Advisor — Blocking findings reported

Advisor assessment: Blockers require maintainer review
Next action: Review the blockers below.
Findings: 1 blocker · 3 warnings · 0 suggestions

Model lanes

  • GPT-5.6 Terra (primary): Completed · high confidence · 1 blocker · 3 warnings · 0 suggestions
  • Nemotron 3 Ultra (second opinion): Failed after a partial review · low confidence · 0 blockers · 2 warnings · 1 suggestion

Second-opinion terminology and E2E selections are advisory. Live E2E does not run automatically for pull requests.

2 semantic terminology decisions

Terminology decisions are advisory. They affect the assessment only when a separate finding identifies concrete semantic impact.

  • define — cleartext ingress isolation at deploy/helm/gpu_autoscaling_k8s/scripts/hpa-common.sh:348: Define this term at its first explanatory use as Kubernetes-reported exposure checks; state that it does not prove complete network isolation.
  • justified — shared PVC persistence at deploy/helm/gpu_autoscaling_k8s/scripts/test-render-contract.sh:280: Retain the modifier where the ReadWriteMany and storage-class requirements apply.

E2E guidance

Advisory only. A maintainer can dispatch the default E2E suite against this exact revision.

Recommended E2E: cloud-inference, security-posture

2 optional E2E recommendations
  • gpu-e2e
  • ollama-auth-proxy

Blockers

PRA-1 Blocker — Enforce authentication for direct Service access

  • Location: deploy/helm/gpu_autoscaling_k8s/templates/service.yaml:10
  • Category: security
  • Problem: Basic authentication is configured only on the Ingress, while the agent Service exposes the unauthenticated completion proxy to other cluster workloads. A pod that can resolve the ClusterIP Service can bypass the Ingress and call the proxy without Basic authentication.
  • Impact: Any workload with network reachability to the Service can use the GPU inference proxy without the generated or configured Basic-auth credential, bypassing the chart's intended authorization control.
  • Fix: Apply the authorization control at every reachable boundary: restrict Service-to-pod traffic to the ingress controller with a NetworkPolicy, or authenticate requests in the proxy. Keep any required operational access explicitly scoped.
  • Verification: Render the chart and inspect the Service, pod labels, and any NetworkPolicy; then verify a non-ingress pod cannot reach /v1/chat/completions while an authorized Ingress request can.
  • Test coverage: Add a rendered-policy contract test that permits ingress-controller traffic to the agent port and rejects direct traffic from an unrelated pod; include an authorized Ingress completion request in boundary-level coverage.
  • Evidence: deploy/helm/gpu_autoscaling_k8s/templates/ingress.yaml:14-18 configures Basic auth only as Ingress annotations. deploy/helm/gpu_autoscaling_k8s/templates/service.yaml:10-20 exposes port 8081 through a ClusterIP Service. deploy/helm/gpu_autoscaling_k8s/files/agent-server.ts:205-211 proxies POST completion requests without an authentication check.
3 warnings · 0 suggestions

Warnings

Warnings do not block.

PRA-2 Warning — Verify downloaded Helm chart provenance

  • Location: deploy/helm/gpu_autoscaling_k8s/scripts/install-hpa.sh:101
  • Category: security
  • Problem: The installer updates remote Helm repositories and installs chart names at pinned versions, but it does not verify chart provenance or another immutable artifact-integrity control.
  • Impact: A compromised or substituted repository artifact for a selected version can introduce cluster-level resources and code during installation.
  • Recommendation: Use the repository-approved Helm chart provenance verification or vendor immutable reviewed chart artifacts, and fail installation when integrity verification fails.
  • Verification: Inspect the installer invocation and the repository dependency policy to confirm that every remote chart package has a verified provenance or immutable integrity reference before Helm installs it.
  • Test coverage: Add a script contract test that rejects installer chart installation when the required provenance or integrity-verification argument is absent.
  • Evidence: deploy/helm/gpu_autoscaling_k8s/scripts/install-hpa.sh:101-113 updates prometheus-community and installs kube-prometheus-stack by remote chart reference. deploy/helm/gpu_autoscaling_k8s/scripts/install-hpa.sh:166-184 updates ingress-nginx and installs ingress-nginx by remote chart reference. The version variables constrain selection but do not verify the retrieved chart package provenance.

PRA-3 Warning — Test ingress credential lifecycle across recovery

  • Location: deploy/helm/gpu_autoscaling_k8s/templates/ingress-auth-secret.yaml:6
  • Category: tests
  • Problem: The chart generates or looks up an ingress Basic-auth Secret and recovery preserves it, but no checked-in test proves the install, uninstall or recovery, and reinstall lifecycle reuses an existing credential without restoring a deliberately deleted Secret.
  • Impact: A change to lookup, retention, or recovery behavior can silently rotate credentials or restore a removed credential without a deterministic regression failure.
  • Recommendation: Add a Helm lifecycle regression test with a mocked or disposable Kubernetes API that verifies retained Secret reuse after recovery or reinstall and fresh generation after explicit deletion.
  • Verification: Inspect a new lifecycle test that renders or installs against pre-existing and absent ingress-auth Secret states, then compares the resulting password behavior.
  • Test coverage: A lifecycle test that installs with no Secret, records the generated password, simulates retained Secret reinstall and verifies reuse, then deletes the Secret and verifies the next install creates a different password.
  • Evidence: deploy/helm/gpu_autoscaling_k8s/templates/ingress-auth-secret.yaml:6-14 reads an existing Secret password or generates a new value. deploy/helm/gpu_autoscaling_k8s/templates/ingress-auth-secret.yaml:20-23 applies helm.sh/resource-policy: keep. deploy/helm/gpu_autoscaling_k8s/scripts/test-script-security-contract.sh:1-214 contains no Helm Secret lifecycle test.

PRA-4 Warning — Add focused tests for proxy request limits and upstream failures

  • Location: deploy/helm/gpu_autoscaling_k8s/files/agent-server.ts:30
  • Category: tests
  • Problem: The new Node.js proxy implements request-body size and timeout controls plus streaming and upstream-error paths, but the PR has no checked-in source test for these behaviors.
  • Impact: Future changes can remove request exhaustion protection or expose internal upstream failures without deterministic test evidence.
  • Recommendation: Add focused Node.js HTTP tests that exercise the proxy server with an oversized body, a stalled body, a successful streamed upstream response, and an upstream failure.
  • Verification: Run the focused source test with a local mocked upstream and assert 413, 408, streamed response propagation, and a generic 502 response.
  • Test coverage: A source-level HTTP test suite for agent-server.ts that asserts oversized bodies return 413, stalled bodies return 408, successful upstream streams are forwarded, and upstream errors return generic 502 responses without internal endpoint details.
  • Evidence: deploy/helm/gpu_autoscaling_k8s/files/agent-server.ts:30-62 enforces body size and timeout limits. deploy/helm/gpu_autoscaling_k8s/files/agent-server.ts:69-89 maps those limit failures to HTTP 413 and 408. deploy/helm/gpu_autoscaling_k8s/files/agent-server.ts:117-129 handles upstream failures. pr_review_tests_regressions_context reports no changed test files and no nearby test names.

Workflow run details

This automated review informs maintainers. Warnings and suggestions do not require a response. A maintainer decides whether to merge.

…s on NVIDIA#7459

- hpa-common.sh: hpa_common_release_fullname took $1/$2 overrides that no
  caller ever passed (ShellCheck: "references arguments, but none are ever
  passed"). Both callers rely entirely on RELEASE/CHART_NAME env vars, so
  drop the dead positional-arg fallback instead of just forwarding "$@".
- install-hpa.sh: group `kick_deployment && helm_install` before `|| true`
  so the no-op fallback unambiguously covers the whole compound command,
  not just helm_install (ShellCheck: "A && B || C is not if-then-else").
- agent-server.mjs: stop echoing raw fetch errors (String(err)) back to
  HTTP clients (CodeQL: information exposure through a stack trace/error
  message); log server-side, return a generic message instead.
- load-generator.mjs: remove SERVICE_FALLBACK, an unused leftover from an
  earlier single-URL design now fully superseded by per-pod-IP discovery
  (CodeQL: unused variable). Add comments on the two "file data in
  outbound network request" findings (in-cluster SA token/CA, and the
  bundled sample-questions payload) clarifying both are the intended,
  non-attacker-controlled data flow for this load generator.

Signed-off-by: maggiezha <maggiez@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Nitpick comments (1)
deploy/helm/gpu_autoscaling_k8s/files/load-generator.mjs (1)

14-17: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Dead SERVICE_FALLBACK / TARGET_URL env contract.

SERVICE_FALLBACK is computed but never referenced — the generator only ever targets per-pod IPs and exits fatally in requirePodTargets when discovery yields nothing. As a result the TARGET_URL env that hpa-load-test.sh sets (lines 250-251) is silently ignored. Either wire it up as an actual fallback target when pod discovery fails, or drop both the constant and the env to avoid a misleading config knob.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deploy/helm/gpu_autoscaling_k8s/files/load-generator.mjs` around lines 14 -
17, Resolve the unused SERVICE_FALLBACK contract in the load generator: either
update requirePodTargets to use SERVICE_FALLBACK as the target when pod
discovery returns no pods, preserving the existing fatal path otherwise, or
remove SERVICE_FALLBACK and the corresponding TARGET_URL configuration from
hpa-load-test.sh. Keep the chosen behavior consistent with the generator’s
target selection flow.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@deploy/helm/gpu_autoscaling_k8s/files/agent-server.mjs`:
- Around line 24-30: Update readBody to enforce a configurable maximum
request-body size while accumulating chunks, stopping or terminating the request
when the limit is exceeded and surfacing a payload-too-large result so the
handler returns HTTP 413. Ensure oversized requests are drained or destroyed to
prevent continued buffering, while preserving normal UTF-8 body parsing and
existing error propagation for bodies within the limit.
- Around line 64-66: Update the catch handler around the backend request in
agent-server.mjs to log the diagnostic error server-side, then return a fixed
generic JSON 502 response instead of exposing String(err) to callers. Preserve
the existing 502 status and JSON content type while removing raw backend details
from the response.
- Around line 61-63: Update the upstream response handling around hubRes and the
proxy response to preserve streaming: pipe hubRes.body directly into res instead
of buffering with hubRes.text(), and forward hubRes.headers.get("content-type")
rather than forcing application/json. Keep the upstream status code unchanged.

In `@deploy/helm/gpu_autoscaling_k8s/monitoring/dcgm-servicemonitor.yaml`:
- Line 1: Prepend the standard two-line SPDX header, using YAML comment syntax,
to deploy/helm/gpu_autoscaling_k8s/monitoring/dcgm-servicemonitor.yaml (lines
1-1), deploy/helm/gpu_autoscaling_k8s/monitoring/kube-prometheus-microk8s.yaml
(lines 1-1), and
deploy/helm/gpu_autoscaling_k8s/monitoring/prometheus-adapter-gpu-values.yaml
(lines 1-1), with the Apache-2.0 identifier and applicable copyright text.

In `@deploy/helm/gpu_autoscaling_k8s/scripts/install-hpa.sh`:
- Line 175: Update the recovery command following hpa_common_kick_deployment to
use the same kick-or-reinstall pattern as hpa-reset.sh: invoke helm_install only
when hpa_common_kick_deployment returns non-zero, while preserving successful
kick behavior and avoiding a trailing unconditional success mask.

In `@deploy/helm/gpu_autoscaling_k8s/templates/_helpers.tpl`:
- Around line 72-80: Update the nemoclaw-gpu.hpaMaxReplicas helper so that when
gpuScaling.oneReplicaPerGpu is enabled, it uses the lower positive limit between
autoscaling.maxReplicas and autoscaling.maxGpus; retain the existing fallback
behavior when those values are not applicable, ensuring the resulting HPA
replica cap never exceeds maxGpus in GPU mode.

In `@deploy/helm/gpu_autoscaling_k8s/templates/deployment.yaml`:
- Around line 42-113: Harden the ollama and agent container definitions with
non-root user/group identities, allowPrivilegeEscalation disabled, and all Linux
capabilities dropped. Enable read-only root filesystems for images that support
them, then add explicit emptyDir mounts for any required writable paths while
preserving the existing ollama-data hostPath mount. Ensure the selected
identities can read mounted application/scripts data and write only to the
intended storage.

In `@deploy/helm/gpu_autoscaling_k8s/templates/service.yaml`:
- Around line 1-4: Add the applicable SPDX copyright and license header to
deploy/helm/gpu_autoscaling_k8s/templates/service.yaml lines 1-4 and
deploy/helm/gpu_autoscaling_k8s/templates/servicemonitor.yaml lines 1-3 using
YAML # comments; add the same header to
deploy/helm/gpu_autoscaling_k8s/templates/NOTES.txt lines 1-4 inside a Helm
template comment so it is not rendered in installation notes.

In `@deploy/helm/gpu_autoscaling_k8s/values-step2-hpa.yaml`:
- Line 1: Add the applicable SPDX copyright and license headers to
deploy/helm/gpu_autoscaling_k8s/values-step2-hpa.yaml,
values-step2-hpa-performance.yaml, values-step2-hpa-latency.yaml, and
values-load-test-hpa.yaml using YAML comments; add equivalent Helm-template
comment headers to templates/configmap.yaml, templates/deployment.yaml,
templates/hpa.yaml, templates/namespace.yaml, and templates/pvc.yaml.

In `@deploy/helm/gpu_autoscaling_k8s/values.yaml`:
- Around line 24-27: Update the image.tag value in the values.yaml image
configuration to an immutable Ollama version tag or digest instead of latest,
while preserving the existing repository and pullPolicy settings.

---

Nitpick comments:
In `@deploy/helm/gpu_autoscaling_k8s/files/load-generator.mjs`:
- Around line 14-17: Resolve the unused SERVICE_FALLBACK contract in the load
generator: either update requirePodTargets to use SERVICE_FALLBACK as the target
when pod discovery returns no pods, preserving the existing fatal path
otherwise, or remove SERVICE_FALLBACK and the corresponding TARGET_URL
configuration from hpa-load-test.sh. Keep the chosen behavior consistent with
the generator’s target selection flow.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 153d6e56-5c8a-4ec7-8d48-2234cf572746

📥 Commits

Reviewing files that changed from the base of the PR and between f1161a5 and 409aef0.

📒 Files selected for processing (35)
  • .pre-commit-config.yaml
  • deploy/helm/gpu_autoscaling_k8s/.helmignore
  • deploy/helm/gpu_autoscaling_k8s/Chart.yaml
  • deploy/helm/gpu_autoscaling_k8s/README.md
  • deploy/helm/gpu_autoscaling_k8s/files/agent-metrics.mjs
  • deploy/helm/gpu_autoscaling_k8s/files/agent-server.mjs
  • deploy/helm/gpu_autoscaling_k8s/files/load-generator.mjs
  • deploy/helm/gpu_autoscaling_k8s/files/ollama-start.sh
  • deploy/helm/gpu_autoscaling_k8s/files/questions-sample.txt
  • deploy/helm/gpu_autoscaling_k8s/monitoring/dcgm-servicemonitor.yaml
  • deploy/helm/gpu_autoscaling_k8s/monitoring/kube-prometheus-microk8s.yaml
  • deploy/helm/gpu_autoscaling_k8s/monitoring/prometheus-adapter-gpu-values.yaml
  • deploy/helm/gpu_autoscaling_k8s/scripts/cluster-recover.sh
  • deploy/helm/gpu_autoscaling_k8s/scripts/get-agent-pods.sh
  • deploy/helm/gpu_autoscaling_k8s/scripts/get-hpa.sh
  • deploy/helm/gpu_autoscaling_k8s/scripts/hpa-common.sh
  • deploy/helm/gpu_autoscaling_k8s/scripts/hpa-load-test.sh
  • deploy/helm/gpu_autoscaling_k8s/scripts/hpa-reset.sh
  • deploy/helm/gpu_autoscaling_k8s/scripts/hpa-watch.sh
  • deploy/helm/gpu_autoscaling_k8s/scripts/install-hpa.sh
  • deploy/helm/gpu_autoscaling_k8s/templates/NOTES.txt
  • deploy/helm/gpu_autoscaling_k8s/templates/_helpers.tpl
  • deploy/helm/gpu_autoscaling_k8s/templates/configmap.yaml
  • deploy/helm/gpu_autoscaling_k8s/templates/deployment.yaml
  • deploy/helm/gpu_autoscaling_k8s/templates/hpa.yaml
  • deploy/helm/gpu_autoscaling_k8s/templates/ingress.yaml
  • deploy/helm/gpu_autoscaling_k8s/templates/namespace.yaml
  • deploy/helm/gpu_autoscaling_k8s/templates/pvc.yaml
  • deploy/helm/gpu_autoscaling_k8s/templates/service.yaml
  • deploy/helm/gpu_autoscaling_k8s/templates/servicemonitor.yaml
  • deploy/helm/gpu_autoscaling_k8s/values-load-test-hpa.yaml
  • deploy/helm/gpu_autoscaling_k8s/values-step2-hpa-latency.yaml
  • deploy/helm/gpu_autoscaling_k8s/values-step2-hpa-performance.yaml
  • deploy/helm/gpu_autoscaling_k8s/values-step2-hpa.yaml
  • deploy/helm/gpu_autoscaling_k8s/values.yaml

Comment thread deploy/helm/gpu_autoscaling_k8s/files/agent-server.ts
Comment thread deploy/helm/gpu_autoscaling_k8s/files/agent-server.ts Outdated
Comment thread deploy/helm/gpu_autoscaling_k8s/files/agent-server.mjs Outdated
Comment thread deploy/helm/gpu_autoscaling_k8s/monitoring/dcgm-servicemonitor.yaml
Comment thread deploy/helm/gpu_autoscaling_k8s/scripts/install-hpa.sh Outdated
Comment thread deploy/helm/gpu_autoscaling_k8s/templates/_helpers.tpl
Comment thread deploy/helm/gpu_autoscaling_k8s/templates/deployment.yaml
Comment thread deploy/helm/gpu_autoscaling_k8s/templates/service.yaml
Comment thread deploy/helm/gpu_autoscaling_k8s/values-step2-hpa.yaml
Comment thread deploy/helm/gpu_autoscaling_k8s/values.yaml
…VIDIA#7459

Blockers:
- hpa-reset.sh: forward INGRESS_HOST to hpa_common_gpu_helm_upgrade like
  install-hpa.sh does, so reset no longer silently resets a custom Ingress
  host back to values.yaml's nemoclaw.local default.
- agent-server.mjs: cap request body size (MAX_BODY_BYTES, 413) and add a
  body-read timeout (REQUEST_BODY_TIMEOUT_MS, 408) plus server-level
  requestTimeout/headersTimeout, so an unauthenticated client sending a
  large or never-ending body can no longer exhaust pod memory or hold a
  connection open indefinitely.
- Ingress: refuse to render at all unless ingress.tls is configured or
  ingress.allowInsecureHttp is explicitly set (new templates/ingress.yaml
  guard) — the install/load-test/reset scripts set that flag themselves as
  a documented, explicit acknowledgment for their private/dev-cluster use
  case. Mandatory basic auth (new templates/ingress-auth-secret.yaml) is
  on by default, auto-generating a password on first install and reusing
  it across `helm upgrade` re-runs via `lookup`, since none of this
  chart's scripts pass --reuse-values.
- Pin ollama/ollama, node:22-bookworm-slim (agent + load-test images) to
  digests, and prometheus-community/kube-prometheus-stack,
  prometheus-community/prometheus-adapter, ingress-nginx/ingress-nginx to
  explicit chart --version pins in install-hpa.sh, so a later install or
  reset can no longer silently pull an unreviewed newer artifact.

Warnings:
- cluster-recover.sh/hpa-reset.sh/hpa_common_clear_stuck_pods: scope
  destructive pod/Deployment/ReplicaSet/HPA deletes to the chart's
  selector label or job-name, never a blanket `--all`, so a namespace
  accidentally shared with unrelated workloads is left alone; documented
  the recovery boundary (NAMESPACE is assumed dedicated to this chart).
- deployment.yaml: add pod/container securityContext — seccomp
  RuntimeDefault for both containers, dropped capabilities and no
  privilege escalation for both, and full non-root/read-only-rootfs for
  the agent sidecar (the ollama container's GPU device-plugin access is
  left otherwise unconstrained since non-root there is untested here).
- New scripts/test-render-contract.sh: static (no cluster) helm template
  check that HPA scaleTargetRef/metric and Service/ServiceMonitor
  selectors still agree with the Deployment.
- hpa-load-test.sh/hpa-reset.sh: also delete the load test's
  ServiceAccount/Role/RoleBinding on exit/reset, not just the Job and
  ConfigMap, so repeated runs don't accumulate unused RBAC objects.

Signed-off-by: maggiezha <maggiez@nvidia.com>
@maggiezha maggiezha changed the title Add K8s autoscaling and NGINX load balancing for NemoClaw feat(deploy): add K8s autoscaling and NGINX load balancing for NemoClaw Jul 24, 2026
Comment thread deploy/helm/gpu_autoscaling_k8s/values.yaml Fixed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
deploy/helm/gpu_autoscaling_k8s/templates/NOTES.txt (1)

1-1: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add the applicable SPDX license header.

This Helm template has no SPDX header. Add the repository-standard identifier using a Helm template comment so it does not appear in rendered notes.

As per coding guidelines, every source file must include the applicable SPDX license header.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deploy/helm/gpu_autoscaling_k8s/templates/NOTES.txt` at line 1, Add the
repository-standard SPDX license identifier at the top of the Helm NOTES
template using a Helm template comment, ensuring it is omitted from rendered
output while preserving the existing HPA command.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@deploy/helm/gpu_autoscaling_k8s/scripts/cluster-recover.sh`:
- Around line 5-14: Update cluster recovery cleanup to avoid unconditional
namespace-wide Job deletion: label load-test Jobs with the chart selector and
delete only matching Jobs, or require an explicit safety acknowledgment before
allowing namespace-wide cleanup. Apply this consistently to every kubectl delete
job invocation in the recovery script, while preserving cleanup of chart-owned
resources.
- Line 30: Update the cleanup logic in cluster-recover.sh to wait synchronously,
or explicitly poll until all selected Deployments, Services, HPAs, and
ReplicaSets are gone before reinstalling. Remove the blanket error suppression
so Kubernetes API deletion failures cause recovery to fail, and ensure the
second deletion pass includes Services alongside the other resource kinds.

In `@deploy/helm/gpu_autoscaling_k8s/scripts/test-render-contract.sh`:
- Around line 30-31: Update the preflight checks near require_cmd helm and
require_cmd python3 to validate that Python can import the yaml module before
running the embedded script. Add a quick python3 import check and fail clearly
when PyYAML is unavailable.

In `@deploy/helm/gpu_autoscaling_k8s/templates/NOTES.txt`:
- Line 2: Update the command shown in the Helm NOTES template to use a path or
equivalent kubectl invocation that remains runnable from the installation
context, rather than relying on the user being inside the chart directory.
Preserve the namespace interpolation from the existing get-agent-pods.sh
command.

---

Outside diff comments:
In `@deploy/helm/gpu_autoscaling_k8s/templates/NOTES.txt`:
- Line 1: Add the repository-standard SPDX license identifier at the top of the
Helm NOTES template using a Helm template comment, ensuring it is omitted from
rendered output while preserving the existing HPA command.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 965d8368-b185-4a0a-963c-6b975d8d883d

📥 Commits

Reviewing files that changed from the base of the PR and between 920333d and ce0d726.

📒 Files selected for processing (14)
  • deploy/helm/gpu_autoscaling_k8s/README.md
  • deploy/helm/gpu_autoscaling_k8s/files/agent-server.mjs
  • deploy/helm/gpu_autoscaling_k8s/scripts/cluster-recover.sh
  • deploy/helm/gpu_autoscaling_k8s/scripts/hpa-common.sh
  • deploy/helm/gpu_autoscaling_k8s/scripts/hpa-load-test.sh
  • deploy/helm/gpu_autoscaling_k8s/scripts/hpa-reset.sh
  • deploy/helm/gpu_autoscaling_k8s/scripts/install-hpa.sh
  • deploy/helm/gpu_autoscaling_k8s/scripts/test-render-contract.sh
  • deploy/helm/gpu_autoscaling_k8s/templates/NOTES.txt
  • deploy/helm/gpu_autoscaling_k8s/templates/_helpers.tpl
  • deploy/helm/gpu_autoscaling_k8s/templates/deployment.yaml
  • deploy/helm/gpu_autoscaling_k8s/templates/ingress-auth-secret.yaml
  • deploy/helm/gpu_autoscaling_k8s/templates/ingress.yaml
  • deploy/helm/gpu_autoscaling_k8s/values.yaml
🚧 Files skipped from review as they are similar to previous changes (10)
  • deploy/helm/gpu_autoscaling_k8s/templates/ingress.yaml
  • deploy/helm/gpu_autoscaling_k8s/templates/deployment.yaml
  • deploy/helm/gpu_autoscaling_k8s/templates/_helpers.tpl
  • deploy/helm/gpu_autoscaling_k8s/README.md
  • deploy/helm/gpu_autoscaling_k8s/values.yaml
  • deploy/helm/gpu_autoscaling_k8s/scripts/hpa-reset.sh
  • deploy/helm/gpu_autoscaling_k8s/scripts/install-hpa.sh
  • deploy/helm/gpu_autoscaling_k8s/files/agent-server.mjs
  • deploy/helm/gpu_autoscaling_k8s/scripts/hpa-load-test.sh
  • deploy/helm/gpu_autoscaling_k8s/scripts/hpa-common.sh

Comment thread deploy/helm/gpu_autoscaling_k8s/scripts/cluster-recover.sh Outdated
Comment thread deploy/helm/gpu_autoscaling_k8s/scripts/cluster-recover.sh Outdated
Comment thread deploy/helm/gpu_autoscaling_k8s/scripts/test-render-contract.sh
Comment thread deploy/helm/gpu_autoscaling_k8s/templates/NOTES.txt Outdated
Rename agent-server, agent-metrics, and load-generator from .mjs to
.ts to satisfy the repo's codebase-growth-guardrails check, which
blocks newly added .js/.cjs/.mjs source files in favor of TypeScript.
The pinned node:22-bookworm-slim image runs .ts files with no build
step or flag needed (Node's built-in type-stripping), so behavior is
unchanged; only the extension and internal import path changed.

Signed-off-by: maggiezha <maggiez@nvidia.com>
Comment on lines +97 to +105
{
hostname: process.env.KUBERNETES_SERVICE_HOST,
port: process.env.KUBERNETES_SERVICE_PORT || 443,
path,
method: "GET",
headers: { Authorization: `Bearer ${token}` },
ca,
rejectUnauthorized: true,
},
Comment on lines +471 to +475
body: JSON.stringify({
messages: [{ role: "user", content: q }],
max_tokens: MAX_TOKENS,
stream: false,
}),

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@deploy/helm/gpu_autoscaling_k8s/files/agent-metrics.ts`:
- Line 6: Update the LLM_LATENCY_WINDOW initialization to parse
LLM_LATENCY_WINDOW_SIZE as a positive finite integer, reject invalid or
non-positive values, and cap accepted values at a safe maximum before
agent-metrics uses it for the llmDurationsMs trim condition.
- Around line 16-23: Update the duration handling in the request metrics flow to
normalize the input once by clamping invalid or negative duration values, then
reuse that normalized duration for both cumulative metrics and the
llmDurationsMs rolling window. Ensure p50, p95, and average calculations consume
the same finite normalized value as llmDurationSumSec and llmDurationCount.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 51d5f078-d837-46e0-a35a-81a4489adf37

📥 Commits

Reviewing files that changed from the base of the PR and between ce0d726 and a7d4954.

📒 Files selected for processing (7)
  • deploy/helm/gpu_autoscaling_k8s/README.md
  • deploy/helm/gpu_autoscaling_k8s/files/agent-metrics.ts
  • deploy/helm/gpu_autoscaling_k8s/files/agent-server.ts
  • deploy/helm/gpu_autoscaling_k8s/files/load-generator.ts
  • deploy/helm/gpu_autoscaling_k8s/scripts/hpa-load-test.sh
  • deploy/helm/gpu_autoscaling_k8s/templates/configmap.yaml
  • deploy/helm/gpu_autoscaling_k8s/templates/deployment.yaml
🚧 Files skipped from review as they are similar to previous changes (4)
  • deploy/helm/gpu_autoscaling_k8s/templates/configmap.yaml
  • deploy/helm/gpu_autoscaling_k8s/templates/deployment.yaml
  • deploy/helm/gpu_autoscaling_k8s/README.md
  • deploy/helm/gpu_autoscaling_k8s/scripts/hpa-load-test.sh

Comment thread deploy/helm/gpu_autoscaling_k8s/files/agent-metrics.ts Outdated
Comment thread deploy/helm/gpu_autoscaling_k8s/files/agent-metrics.ts Outdated
Change values.yaml's default ingress.auth.password from an empty
string to YAML null: the empty string tripped CodeQL's
js/empty-password-in-configuration-file check even though it's an
intentional "auto-generate on install" sentinel, not a real blank
credential. Template behavior (`if not $password`) is unchanged for
both null and "".

Add codeql[js/file-access-to-http] suppression comments (with
justification) in load-generator.ts for the two flagged spots: the
in-cluster K8s API token/CA send and the bundled sample-question
payload. Both are the load generator's intended, non-attacker-
controlled data flow; the .mjs->.ts rename reset CodeQL's alert
fingerprints so these reappeared as "new" on the new path.

Signed-off-by: maggiezha <maggiez@nvidia.com>
…#7459

- agent-server.ts: stream the upstream chat-completions response via
  pipe instead of buffering with hubRes.text(), and forward its real
  content-type instead of forcing application/json, so "stream": true
  requests reach clients incrementally (e.g. text/event-stream).
- install-hpa.sh: fix inverted kick/reinstall recovery — helm_install
  must run when hpa_common_kick_deployment fails (it tore down an
  unrecoverable Deployment), not when it succeeds. Matches the
  correct kick-or-reinstall pattern already used in hpa-reset.sh.
- _helpers.tpl: cap HPA maxReplicas by maxGpus when
  gpuScaling.oneReplicaPerGpu is set, so a maxReplicas override can't
  exceed the GPU count and create unschedulable Pending pods.
- hpa-load-test.sh: drop the unused TARGET_URL env var — the load
  generator only ever targets per-pod IPs via k8s API discovery.
- Add missing SPDX headers to 15 chart YAML/tpl/txt files (the
  spdx-headers pre-commit hook only covers .ts/.py/.sh, not these).

Signed-off-by: maggiezha <maggiez@nvidia.com>
agent-metrics.ts: parse LLM_LATENCY_WINDOW_SIZE as a positive finite
integer capped at 10000, falling back to the 128 default on invalid
input (NaN/Infinity previously disabled the llmDurationsMs trim,
letting it grow unbounded). Also normalize durationMs once (finite,
non-negative) and reuse that value for both the rolling window and
the cumulative sum/count/histogram, so p50/p95/avg and the counters
always agree on the same value instead of diverging on bad input.

Signed-off-by: maggiezha <maggiez@nvidia.com>
@maggiezha

Copy link
Copy Markdown
Author

CI fixes since initial review

All automated review findings (GitHub Advanced Security/CodeQL, CodeRabbit, and the PR review advisor) have been addressed. Summary of what failed and how it was fixed:

Security (CodeQL)

  • values.yaml: ingress.auth.password: "" flagged as an empty-password config → changed to password: null (Helm template still auto-generates a credential; null avoids the literal-empty-string CodeQL signature).
  • load-generator.ts: two "file data in outbound network request" alerts on the in-cluster K8s API call and the synthetic load-test payload → added inline codeql[js/file-access-to-http] suppressions with justification (token/CA come from the projected service-account files by design; the payload is a bundled sample question, not attacker-controlled input).
  • agent-server.ts: "information exposure through a stack trace" on the 502 error response → replaced String(err) in the response body with a fixed generic message; raw error is now only logged server-side.

Code quality / robustness (CodeRabbit)

  • New Node.js files (agent-server.mjs, agent-metrics.mjs, load-generator.mjs) violated the repo's TypeScript-only policy for new code → renamed to .ts (Node 22 natively type-strips .ts, no build step needed).
  • agent-server.ts buffered the entire LLM response before replying → refactored to stream the upstream response body directly to the client via stream/promises pipeline, forwarding the real content-type.
  • install-hpa.sh had inverted recovery logic (kick && reinstall || true always looked like success) → fixed to kick || reinstall, so a failed kick correctly triggers reinstall.
  • _helpers.tpl: hpaMaxReplicas didn't cap replicas by available GPUs when oneReplicaPerGpu is enabled → now takes min(maxReplicas, maxGpus), preventing unschedulable pods.
  • agent-metrics.ts: LLM_LATENCY_WINDOW_SIZE wasn't validated, so NaN/Infinity could make the latency window grow unbounded → now parsed as a positive finite integer, capped at 10,000, falling back to 128.
  • agent-metrics.ts: latency duration was clamped inconsistently between the rolling window and the cumulative counters → normalized once and reused for both, so p50/p95/avg always agree with the cumulative metrics.
  • Removed the dead TARGET_URL/SERVICE_FALLBACK env contract from hpa-load-test.sh and load-generator.ts since it was never wired up.
  • Added missing SPDX headers to 15 Helm template/values/monitoring files.

Result: all cli-test-shards, cli-tests, build-typecheck, static-checks, CodeQL, CodeRabbit, commit-lint, dco-check, and related quality gates now pass on the current HEAD.

Remaining (not code issues, need maintainer action):

  • E2E / PR Gate Coordination — requires a maintainer to approve the credentialed E2E skip/run for this fork PR (standard gate for external contributions).
  • PR review advisor (Nemotron 3 Ultra) — fails due to an internal bug in that advisor's own harness (it calls grep mid atomic-commit), unrelated to this diff.

@maggiezha

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (8)
deploy/helm/gpu_autoscaling_k8s/scripts/install-hpa.sh (2)

151-151: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the duplicate INFERENCE_MODEL default.

Line 43 already applies the same default. Line 151 has no effect.

♻️ Proposed change
-INFERENCE_MODEL="${INFERENCE_MODEL:-llama3.2:3b}"
-
 ensure_ingress_nginx() {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deploy/helm/gpu_autoscaling_k8s/scripts/install-hpa.sh` at line 151, Remove
the duplicate INFERENCE_MODEL default assignment near the later configuration
block; retain the existing default assignment earlier in install-hpa.sh so the
variable continues using llama3.2:3b when unset.

107-123: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Surface the Prometheus install failure.

helm upgrade --install redirects stdout and stderr to /dev/null and ends with || true. If the chart install fails, the operator sees only the later message Prometheus not found. Capture stderr and print it when the command fails.

♻️ Proposed change
-      --wait >/dev/null 2>&1 || true
+      --wait >/dev/null 2>"${prom_err}" || {
+        echo "kube-prometheus-stack install did not complete:" >&2
+        cat "${prom_err}" >&2
+      }

Declare prom_err="$(mktemp)" before the call and remove it afterwards.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deploy/helm/gpu_autoscaling_k8s/scripts/install-hpa.sh` around lines 107 -
123, Update the Prometheus installation block around helm upgrade --install to
capture stderr in a temporary file, report the captured error when the command
fails, and remove the temporary file afterward. Preserve the existing successful
flow while ensuring installation failures are surfaced instead of silently
ignored.
deploy/helm/gpu_autoscaling_k8s/scripts/hpa-common.sh (2)

126-137: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The script-style header assumes GPU mode.

The header always prints GPU utilization rate (avg per pod) and the GPU UTIL % column. templates/hpa.yaml supports gpu, performance, latency, and CPU modes. In the non-GPU modes the header mislabels the values. Derive the column label from the first spec metric, or use a neutral TARGETS label.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deploy/helm/gpu_autoscaling_k8s/scripts/hpa-common.sh` around lines 126 -
137, The non-kubectl header in the header-printing logic assumes GPU metrics and
mislabels performance, latency, and CPU modes. Update the relevant script-style
output to derive its metric description and column label from the first spec
metric, or use the neutral TARGETS label, while preserving the existing GPU
output.

652-676: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The max parameter is unused.

hpa_common_verify_hpa_bounds accepts max at line 657 but never reads it. install-hpa.sh line 228 passes MAX_REPLICAS, so the caller expects an upper-bound check. Either verify the HPA spec.maxReplicas against max and report a mismatch, or drop the parameter and update the callers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deploy/helm/gpu_autoscaling_k8s/scripts/hpa-common.sh` around lines 652 -
676, The max parameter in hpa_common_verify_hpa_bounds is unused despite callers
passing MAX_REPLICAS. Implement an upper-bound validation by reading the HPA
spec.maxReplicas, comparing it with max, and reporting a mismatch with a nonzero
return; preserve the existing lower-bound enforcement and successful behavior
when the configured maximum matches.
deploy/helm/gpu_autoscaling_k8s/templates/configmap.yaml (1)

11-16: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

.Files.Get fails silently for a missing or renamed file.

If a path under files/ changes, .Files.Get returns an empty string. The ConfigMap then renders an empty key, and the container starts with an empty script instead of failing the install. Wrap each read with required so a rename breaks the render.

♻️ Proposed fail-fast guard
   agent-server.ts: |
-{{ .Files.Get "files/agent-server.ts" | indent 4 }}
+{{ required "files/agent-server.ts is missing from the chart" (.Files.Get "files/agent-server.ts") | indent 4 }}
   agent-metrics.ts: |
-{{ .Files.Get "files/agent-metrics.ts" | indent 4 }}
+{{ required "files/agent-metrics.ts is missing from the chart" (.Files.Get "files/agent-metrics.ts") | indent 4 }}
   ollama-start.sh: |
-{{ .Files.Get "files/ollama-start.sh" | indent 4 }}
+{{ required "files/ollama-start.sh is missing from the chart" (.Files.Get "files/ollama-start.sh") | indent 4 }}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deploy/helm/gpu_autoscaling_k8s/templates/configmap.yaml` around lines 11 -
16, Update the ConfigMap entries for agent-server.ts, agent-metrics.ts, and
ollama-start.sh to wrap each .Files.Get result with Helm’s required function,
using a clear non-empty error message so missing or renamed files fail template
rendering instead of producing empty scripts.
deploy/helm/gpu_autoscaling_k8s/files/load-generator.ts (1)

500-539: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Retired target workers spin until the test ends.

syncTargetWorkers sets worker.limit = 0 for targets that disappear, and workerPromises keeps the promise. runTargetWorker then loops with sleep(20) until endAt. After a scale-down, each removed pod leaves an idle 50 Hz loop for the rest of the run. Add an exit condition when the limit is 0 and the target is no longer active, and delete the entry from workerPromises.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deploy/helm/gpu_autoscaling_k8s/files/load-generator.ts` around lines 500 -
539, Update runTargetWorker and syncTargetWorkers so a worker whose limit is 0
and target is no longer active exits immediately instead of sleeping until
endAt. When retiring such a worker, also remove its promise from workerPromises
while preserving normal completion and active-worker behavior.
deploy/helm/gpu_autoscaling_k8s/templates/ingress-auth-secret.yaml (1)

29-32: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Each render produces a new bcrypt hash.

bcrypt generates a random salt, so auth changes on every helm upgrade even when the password is unchanged. The Secret is patched each time, and ingress-nginx reloads the auth file. Authentication still succeeds. If you want a stable Secret, reuse the auth value from $existing when the password did not change.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deploy/helm/gpu_autoscaling_k8s/templates/ingress-auth-secret.yaml` around
lines 29 - 32, The ingress auth template currently regenerates the bcrypt hash
on every render. Update the auth value logic around the existing `$existing`
Secret to reuse its current auth content when the configured username and
password are unchanged, and only generate a new bcrypt hash when the password
changes or no existing value is available.
deploy/helm/gpu_autoscaling_k8s/files/agent-server.ts (1)

96-128: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Latency metric includes client stream time.

recordLlmLatency runs after the response finishes streaming to the client. For "stream": true requests, the recorded value mixes upstream inference latency and client read time. The HPA latency mode targets nemoclaw_llm_latency_p95_milliseconds, so slow clients can inflate the scaling signal. Consider recording the time to the first upstream byte, or the time to hubRes headers.

♻️ Proposed change
     llmOk = hubRes.ok;
+    // Record inference latency at upstream response headers, before client streaming.
+    recordLlmLatency(performance.now() - llmStart, llmOk);
+    llmRecorded = true;

Then guard the finally block with if (!llmRecorded).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deploy/helm/gpu_autoscaling_k8s/files/agent-server.ts` around lines 96 - 128,
Update the chat completion timing in the request handler around llmStart and
recordLlmLatency so it captures upstream response-header or first-byte latency
instead of waiting for pipeline to finish streaming to the client. Add an
llmRecorded guard and ensure the finally block records latency only when it has
not already been recorded, while preserving metric recording for errors and
non-streaming responses.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@deploy/helm/gpu_autoscaling_k8s/scripts/hpa-common.sh`:
- Around line 52-83: Update targets() so non-GPU Pods metrics include their
metric name in the formatted output, matching the Resource branch, while
preserving GPU-specific formatting for gpu_utilization_percent. Also update
hpa_common_format_hpa at deploy/helm/gpu_autoscaling_k8s/scripts/hpa-common.sh
lines 126-137 to derive the column label and subtitle from the first spec metric
instead of hard-coding GPU UTIL %.
- Around line 640-650: Update hpa_common_enforce_replica_floor to validate that
spec is a numeric integer before comparing it with min; treat empty or
non-numeric values as requiring the existing replica-floor patch, while
preserving the current comparison and patch behavior for valid values.

In `@deploy/helm/gpu_autoscaling_k8s/scripts/test-render-contract.sh`:
- Around line 37-41: Update the helm template assertion in
test-render-contract.sh to capture the command’s output and verify it contains
the specific no-TLS error emitted by templates/ingress.yaml, rather than
accepting any nonzero exit status. Keep the failure path for successful
rendering and unrelated render errors distinct so only the expected TLS-policy
rejection passes.

In `@deploy/helm/gpu_autoscaling_k8s/templates/deployment.yaml`:
- Around line 1-2: Replace the Helm template comments at
deploy/helm/gpu_autoscaling_k8s/templates/deployment.yaml lines 1-2 with
equivalent YAML comments using # while preserving both SPDX header texts; make
the same replacement at deploy/helm/gpu_autoscaling_k8s/templates/pvc.yaml lines
1-2.
- Line 102: Update the deployment configuration around the node command and
mounted TypeScript files so Node recognizes the agent entry point as ESM. Add a
package.json under /app with "type": "module", or consistently rename the entry
point and related imports to .mts, or compile the files to JavaScript before
execution; preserve the existing agent-server startup behavior.

---

Nitpick comments:
In `@deploy/helm/gpu_autoscaling_k8s/files/agent-server.ts`:
- Around line 96-128: Update the chat completion timing in the request handler
around llmStart and recordLlmLatency so it captures upstream response-header or
first-byte latency instead of waiting for pipeline to finish streaming to the
client. Add an llmRecorded guard and ensure the finally block records latency
only when it has not already been recorded, while preserving metric recording
for errors and non-streaming responses.

In `@deploy/helm/gpu_autoscaling_k8s/files/load-generator.ts`:
- Around line 500-539: Update runTargetWorker and syncTargetWorkers so a worker
whose limit is 0 and target is no longer active exits immediately instead of
sleeping until endAt. When retiring such a worker, also remove its promise from
workerPromises while preserving normal completion and active-worker behavior.

In `@deploy/helm/gpu_autoscaling_k8s/scripts/hpa-common.sh`:
- Around line 126-137: The non-kubectl header in the header-printing logic
assumes GPU metrics and mislabels performance, latency, and CPU modes. Update
the relevant script-style output to derive its metric description and column
label from the first spec metric, or use the neutral TARGETS label, while
preserving the existing GPU output.
- Around line 652-676: The max parameter in hpa_common_verify_hpa_bounds is
unused despite callers passing MAX_REPLICAS. Implement an upper-bound validation
by reading the HPA spec.maxReplicas, comparing it with max, and reporting a
mismatch with a nonzero return; preserve the existing lower-bound enforcement
and successful behavior when the configured maximum matches.

In `@deploy/helm/gpu_autoscaling_k8s/scripts/install-hpa.sh`:
- Line 151: Remove the duplicate INFERENCE_MODEL default assignment near the
later configuration block; retain the existing default assignment earlier in
install-hpa.sh so the variable continues using llama3.2:3b when unset.
- Around line 107-123: Update the Prometheus installation block around helm
upgrade --install to capture stderr in a temporary file, report the captured
error when the command fails, and remove the temporary file afterward. Preserve
the existing successful flow while ensuring installation failures are surfaced
instead of silently ignored.

In `@deploy/helm/gpu_autoscaling_k8s/templates/configmap.yaml`:
- Around line 11-16: Update the ConfigMap entries for agent-server.ts,
agent-metrics.ts, and ollama-start.sh to wrap each .Files.Get result with Helm’s
required function, using a clear non-empty error message so missing or renamed
files fail template rendering instead of producing empty scripts.

In `@deploy/helm/gpu_autoscaling_k8s/templates/ingress-auth-secret.yaml`:
- Around line 29-32: The ingress auth template currently regenerates the bcrypt
hash on every render. Update the auth value logic around the existing
`$existing` Secret to reuse its current auth content when the configured
username and password are unchanged, and only generate a new bcrypt hash when
the password changes or no existing value is available.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 675d8dd4-78fd-46da-81d3-5f6151682d96

📥 Commits

Reviewing files that changed from the base of the PR and between affe641 and d002933.

📒 Files selected for processing (38)
  • .pre-commit-config.yaml
  • deploy/helm/gpu_autoscaling_k8s/.helmignore
  • deploy/helm/gpu_autoscaling_k8s/Chart.yaml
  • deploy/helm/gpu_autoscaling_k8s/README.md
  • deploy/helm/gpu_autoscaling_k8s/files/agent-metrics.ts
  • deploy/helm/gpu_autoscaling_k8s/files/agent-server.ts
  • deploy/helm/gpu_autoscaling_k8s/files/load-generator.ts
  • deploy/helm/gpu_autoscaling_k8s/files/ollama-start.sh
  • deploy/helm/gpu_autoscaling_k8s/files/questions-sample.txt
  • deploy/helm/gpu_autoscaling_k8s/monitoring/dcgm-servicemonitor.yaml
  • deploy/helm/gpu_autoscaling_k8s/monitoring/kube-prometheus-microk8s.yaml
  • deploy/helm/gpu_autoscaling_k8s/monitoring/prometheus-adapter-gpu-values.yaml
  • deploy/helm/gpu_autoscaling_k8s/scripts/cluster-recover.sh
  • deploy/helm/gpu_autoscaling_k8s/scripts/get-agent-pods.sh
  • deploy/helm/gpu_autoscaling_k8s/scripts/get-hpa.sh
  • deploy/helm/gpu_autoscaling_k8s/scripts/hpa-common.sh
  • deploy/helm/gpu_autoscaling_k8s/scripts/hpa-load-test.sh
  • deploy/helm/gpu_autoscaling_k8s/scripts/hpa-reset.sh
  • deploy/helm/gpu_autoscaling_k8s/scripts/hpa-watch.sh
  • deploy/helm/gpu_autoscaling_k8s/scripts/install-hpa.sh
  • deploy/helm/gpu_autoscaling_k8s/scripts/test-render-contract.sh
  • deploy/helm/gpu_autoscaling_k8s/scripts/test-script-security-contract.sh
  • deploy/helm/gpu_autoscaling_k8s/templates/NOTES.txt
  • deploy/helm/gpu_autoscaling_k8s/templates/_helpers.tpl
  • deploy/helm/gpu_autoscaling_k8s/templates/configmap.yaml
  • deploy/helm/gpu_autoscaling_k8s/templates/deployment.yaml
  • deploy/helm/gpu_autoscaling_k8s/templates/hpa.yaml
  • deploy/helm/gpu_autoscaling_k8s/templates/ingress-auth-secret.yaml
  • deploy/helm/gpu_autoscaling_k8s/templates/ingress.yaml
  • deploy/helm/gpu_autoscaling_k8s/templates/namespace.yaml
  • deploy/helm/gpu_autoscaling_k8s/templates/pvc.yaml
  • deploy/helm/gpu_autoscaling_k8s/templates/service.yaml
  • deploy/helm/gpu_autoscaling_k8s/templates/servicemonitor.yaml
  • deploy/helm/gpu_autoscaling_k8s/values-load-test-hpa.yaml
  • deploy/helm/gpu_autoscaling_k8s/values-step2-hpa-latency.yaml
  • deploy/helm/gpu_autoscaling_k8s/values-step2-hpa-performance.yaml
  • deploy/helm/gpu_autoscaling_k8s/values-step2-hpa.yaml
  • deploy/helm/gpu_autoscaling_k8s/values.yaml
🚧 Files skipped from review as they are similar to previous changes (20)
  • deploy/helm/gpu_autoscaling_k8s/.helmignore
  • deploy/helm/gpu_autoscaling_k8s/scripts/hpa-watch.sh
  • deploy/helm/gpu_autoscaling_k8s/values-step2-hpa-performance.yaml
  • deploy/helm/gpu_autoscaling_k8s/files/questions-sample.txt
  • deploy/helm/gpu_autoscaling_k8s/scripts/get-hpa.sh
  • deploy/helm/gpu_autoscaling_k8s/values-step2-hpa-latency.yaml
  • deploy/helm/gpu_autoscaling_k8s/files/ollama-start.sh
  • deploy/helm/gpu_autoscaling_k8s/monitoring/kube-prometheus-microk8s.yaml
  • deploy/helm/gpu_autoscaling_k8s/monitoring/prometheus-adapter-gpu-values.yaml
  • .pre-commit-config.yaml
  • deploy/helm/gpu_autoscaling_k8s/files/agent-metrics.ts
  • deploy/helm/gpu_autoscaling_k8s/values-step2-hpa.yaml
  • deploy/helm/gpu_autoscaling_k8s/Chart.yaml
  • deploy/helm/gpu_autoscaling_k8s/scripts/hpa-reset.sh
  • deploy/helm/gpu_autoscaling_k8s/monitoring/dcgm-servicemonitor.yaml
  • deploy/helm/gpu_autoscaling_k8s/scripts/hpa-load-test.sh
  • deploy/helm/gpu_autoscaling_k8s/values.yaml
  • deploy/helm/gpu_autoscaling_k8s/values-load-test-hpa.yaml
  • deploy/helm/gpu_autoscaling_k8s/templates/_helpers.tpl
  • deploy/helm/gpu_autoscaling_k8s/scripts/get-agent-pods.sh

Comment thread deploy/helm/gpu_autoscaling_k8s/scripts/hpa-common.sh
Comment thread deploy/helm/gpu_autoscaling_k8s/scripts/hpa-common.sh
Comment thread deploy/helm/gpu_autoscaling_k8s/scripts/test-render-contract.sh Outdated
Comment thread deploy/helm/gpu_autoscaling_k8s/templates/deployment.yaml Outdated
Comment thread deploy/helm/gpu_autoscaling_k8s/templates/deployment.yaml
@maggiezha

maggiezha commented Aug 6, 2026

Copy link
Copy Markdown
Author

@apurvvkumaria Confirmed resolved on newer head 2aa7eafe.

  1. Recovery is scoped to the Helm release and named load-test Job.

    • cluster-recover.sh deletes Deployments, Services, HPAs, and ReplicaSets with app.kubernetes.io/name=nemoclaw-gpu,app.kubernetes.io/instance=${RELEASE}.
    • Both Job deletions target only ${JOB_NAME}. No --all Job deletion remains.
    • hpa_common_clear_stuck_pods patches and deletes only pods with the Helm release selector or the exact job-name=${JOB_NAME} selector. It does not use the existential job-name selector.
  2. TLS is the default, and cleartext HTTP requires an explicit, verified exception.

    • ALLOW_INSECURE_HTTP defaults to 0. The installer rejects a TLS-less configuration in this mode.
    • hpa_common_gpu_helm_upgrade, hpa_common_ensure_agent_ready, and hpa-load-test.sh derive ingress.allowInsecureHttp from hpa_common_ingress_allow_insecure_value instead of setting it to true.
    • ALLOW_INSECURE_HTTP=1 runs the isolation preflight before returning true. The preflight rejects node ExternalIP addresses, non-private node InternalIP addresses, non-ClusterIP ingress Services, externalIPs, load-balancer addresses, hostNetwork, and hostPort.
    • When TLS is configured, the chart enforces nginx.ingress.kubernetes.io/ssl-redirect: "true" unless cleartext HTTP was explicitly enabled.

Verification on exact head 2aa7eafe

  • test-script-security-contract.sh: passes recovery-selector and cleartext-isolation cases.
  • test-render-contract.sh: passes the TLS guard and explicit HTTPS redirect cases.
  • The corresponding recovery, cleartext, storage, and HTTPS CodeRabbit findings are addressed in the current history.

Please re-review exact head 2aa7eafe or the newest head, thank you!

@cv
cv dismissed apurvvkumaria’s stale review August 7, 2026 08:00

Commit d4ce708 scopes recovery to chart-owned resources and the named load-test Job, defaults ingress to TLS, and requires an isolation preflight for explicit cleartext use. Later commits add TLS enforcement tests.

@cv cv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Product scope blocks this PR. It has no linked accepted issue or design decision, while NemoClaw's canonical platform documentation explicitly classifies native Kubernetes and OpenShift deployments as unsupported. Merging this Helm chart, autoscaling contract, NGINX ingress, persistence modes, operational scripts, and documentation would create a supported deployment surface without defined ownership, lifecycle, compatibility, security, or validation expectations. Route this independent solution through NemoClaw Community, or first obtain an accepted product decision that defines those requirements. Passing chart tests and partial security hardening do not establish product approval.

@maggiezha

Copy link
Copy Markdown
Author

Product scope blocks this PR. It has no linked accepted issue or design decision, while NemoClaw's canonical platform documentation explicitly classifies native Kubernetes and OpenShift deployments as unsupported. Merging this Helm chart, autoscaling contract, NGINX ingress, persistence modes, operational scripts, and documentation would create a supported deployment surface without defined ownership, lifecycle, compatibility, security, or validation expectations. Route this independent solution through NemoClaw Community, or first obtain an accepted product decision that defines those requirements. Passing chart tests and partial security hardening do not establish product approval.

Thank you! Moved it to: NVIDIA/nemoclaw-community#111

@cv

cv commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Closing this as NVIDIA/nemoclaw-community#111 is its new home. Thanks @maggiezha!

@cv cv closed this Aug 12, 2026
apurvvkumaria pushed a commit to NVIDIA/nemoclaw-community that referenced this pull request Aug 13, 2026
## Related Issue

This community recipe continues and re-homes the public work from
[NVIDIA/NemoClaw#7459](NVIDIA/NemoClaw#7459)
into `examples/recipes/nvidia/kubernetes-gpu-autoscaling/`.

Architecture follow-ups in this PR (beyond the imported baseline):

1. **OpenShell CPU sandbox** — NemoClaw/OpenClaw runs as a CPU-only
sandbox under OpenShell + Agent Sandbox. Only Ollama inference pods
request GPUs. The sandbox policy removes NVIDIA-hosted inference before
agent start.
2. **Optional Envoy Gateway LeastRequest** — users may enable Envoy
(default) for LeastRequest balancing across HPA replicas, or disable it
(`ENABLE_ENVOY_LB=0`) and use the agent Service only. Replaces
ingress-nginx when enabled. Envoy dataplane stays ClusterIP while the
OpenShell cleartext HTTP listener is present.
3. **Inference API key** — Chart-generated local Secret for Bearer auth
on model/chat; OpenShell injects it (users need not supply one). Not for
Ollama model pulls; not OpenAI/`NVIDIA_API_KEY`.
4. **HPA metrics (Pods `AverageValue`)** — two documented examples:
average per-pod **GPU utilization > 40%** and average per-pod **LLM chat
proxy latency > 3000 ms**. These are examples; operators can choose
other metrics or define their own customized Prometheus → Adapter → HPA
metrics.

## Description

Add an experimental NVIDIA-authored Kubernetes recipe that runs a
CPU-only NemoClaw/OpenClaw agent in an OpenShell sandbox and sends
inference through **Envoy Gateway (LeastRequest)** to authenticated
Ollama replicas on NVIDIA GPUs in the same cluster. A Kubernetes
Horizontal Pod Autoscaler scales only the Ollama inference pods using
Pods **`AverageValue`** custom metrics — documented examples are per-pod
**GPU utilization** (`gpu_utilization_percent` via DCGM → Prometheus →
Adapter) and per-pod **LLM latency**
(`nemoclaw_llm_latency_avg_milliseconds` via metrics-proxy `/metrics` →
Prometheus → Adapter). Latency is the metrics-proxy chat/completions
proxy duration (from just before the in-pod inference call until the
full response is written to the client).

The runtime path is entirely on-premises. HPA scales to **N** Ollama
pods (1 GPU each); Envoy only changes load balancing (LeastRequest vs
the agent ClusterIP Service when `ENABLE_ENVOY_LB=0`):

```text
OpenShell CPU sandbox
        ↓
Envoy Gateway — LeastRequest  (or metrics-proxy Service when ENABLE_ENVOY_LB=0)
        ↓
Authenticated inference endpoints
├─ Ollama pod → GPU 1
├─ Ollama pod → GPU 2
├─ …
└─ Ollama pod → GPU N
        ↑
HPA (examples: GPU util >40% or latency >3000 ms)
```

Operators can toggle Envoy with `ENABLE_ENVOY_LB` /
`ingress.gateway.enabled`, verify the sandbox path with
`./scripts/verify-nemoclaw-sandbox.sh`, and keep the Envoy dataplane as
ClusterIP while the OpenShell cleartext HTTP listener exists.

**Inference API key.** The chart generates a local Secret for Bearer
auth on model/chat; users do not supply a cloud key. OpenShell injects
it for the sandbox path — not for Ollama model pulls, and not an
OpenAI/`NVIDIA_API_KEY` credential. The sandbox policy removes and
verifies removal of the inherited `integrate.api.nvidia.com` endpoint
before the agent starts.

### Source migration

| Source | Destination |
| --- | --- |
| [`NVIDIA/NemoClaw` `deploy/helm/gpu_autoscaling_k8s/` @
`77334cc`](NVIDIA/NemoClaw@77334cc)
([#7459](NVIDIA/NemoClaw#7459)) |
`NVIDIA/nemoclaw-community:
examples/recipes/nvidia/kubernetes-gpu-autoscaling/` |

- Imported chart files from exact source head
[`77334ccbbadba2c5079fe9e99fe80a3cddc846b5`](NVIDIA/NemoClaw@77334cc).
- Preserve compatibility-sensitive Helm release, namespace, Service,
metric, and label identifiers.
- Make the community directory the canonical source after migration.
Future compatibility changes should be reviewed and applied here rather
than mirrored from the old pull-request branch.
- Pin the experimental integration to NemoClaw `v0.0.104` at
`f389c9d872775006ae069473f58250fa8f3ad40f`, OpenShell `0.0.85`, and
Agent Sandbox `v0.5.0`.
- Follow-up on this branch replaces ingress-nginx with Envoy Gateway and
pins LeastRequest on the OpenShell and external inference routes.

### NemoClaw and OpenShell path

- Build and push a deployment-specific NemoClaw image without embedding
credentials.
- Install an internal-only OpenShell Kubernetes gateway after the
operator explicitly installs the pinned Agent Sandbox controller.
- Require OIDC by default. The unauthenticated mode requires a separate
dedicated-cluster, port-forward-only acknowledgement.
- Configure the OpenShell inference provider to the in-cluster **Envoy
Gateway** dataplane (`LeastRequest`), which forwards
Bearer-authenticated requests to the Ollama inference pods.
- Create the NemoClaw sandbox without a GPU request, remove the
hosted-inference endpoint, and run NemoClaw in a foreground OpenShell
exec session.
- Document the OpenShell `0.0.85` idle-sandbox lifecycle, capabilities,
privilege-separation limitation, cleanup, and coordinated pin-update
procedure.

### Security and lifecycle boundaries

- **Inference API key:** chart-generated local Secret; OpenShell injects
Bearer for sandbox traffic (users need not supply a key). Not for Ollama
pulls; not OpenAI/`NVIDIA_API_KEY`. Timing-safe check on
model/completion traffic (`Authorization: Bearer` on the OpenShell/Envoy
path, or `X-Api-Key` for external clients when Gateway Basic auth owns
`Authorization`). Health, readiness, and metrics remain unauthenticated.
- Generate and retain a random inference API key, or preserve an
operator-managed Secret name and key end to end. Operational scripts
discover the effective Secret contract from the installed Helm release.
- Keep TLS required by default. Enable Gateway Basic authentication on
the **external** HTTPS HTTPRoute; the OpenShell HTTPRoute does not use
Gateway Basic auth so Bearer can pass through Envoy.
- Keep the Envoy dataplane Service as **ClusterIP** while the
hostname-unrestricted OpenShell cleartext HTTP listener is present;
reject `NodePort`/`LoadBalancer` in the chart and installer so that
listener cannot bypass hostname-scoped HTTPS redirect and Basic auth
when externally exposed.
- State that direct Service access bypasses Gateway TLS and Basic
authentication but not the application API-key check, and that the chart
creates no NetworkPolicy.
- Document Prometheus Adapter, **Envoy Gateway**, DCGM ServiceMonitor,
Agent Sandbox, OpenShell, and MicroK8s mutations and retained resources.
- Default `MAX_REPLICAS` / `TARGET_PODS` to allocatable GPU count **N**
(override only for an intentional lower ceiling).
- Synchronize the HPA replica maximum and GPU safety ceiling so explicit
targets are not capped by an outdated lower `maxGpus` default.
- Make Helm uninstall the default cleanup. Namespace deletion requires
an explicit exclusive-ownership check.

This remains an unsupported, non-production community experiment; it
does not establish native Kubernetes as a supported NemoClaw product
surface.

## Verification

- [x] `python3 scripts/check_license_headers.py --check`
- [x] `python3 scripts/check_label_taxonomy.py`
- [x] `python3 -m unittest scripts.tests.test_governance_taxonomy`
- [x] `python3 scripts/check_pr_title.py --advisory 'feat(examples): add
Kubernetes GPU autoscaling recipe'`
- [x] `git diff --check`
- [x] Bash syntax validation for the recipe scripts
- [x] `bash scripts/test-script-security-contract.sh`
- [x] `bash scripts/test-nemoclaw-k8s-contract.sh`
- [x] `node scripts/test-inference-auth-contract.mjs`
- [x] `bash scripts/test-render-contract.sh` with Helm 3.20.2 on `PATH`
- [x] `helm lint . --set ingress.allowInsecureHttp=true --set-string
ingress.auth.htpasswd='demo:{SHA}<base64-sha1>'` (Apache `{SHA}`
htpasswd required by Envoy Gateway Basic auth)
- [x] Render coverage for mandatory inference authentication, TLS
redirect, operator-managed Secrets, long Secret names, scalar-like and
dotted Secret keys, RWX persistence, and single-node `hostPath`
- [x] Current branch is rebased onto the latest fetched NVIDIA `main`;
it is zero commits behind
- [x] Current head includes OpenShell sandbox verification and Envoy
LeastRequest/ClusterIP hardening commits on this branch
- [x] Latency HPA idle-expire: `metrics.llmLatencyIdleExpireMs` /
`LLM_LATENCY_IDLE_EXPIRE_MS` (default 60s) resets
`nemoclaw_llm_latency_avg_milliseconds` after idle so HPA can scale
down; covered by `scripts/test-metrics-proxy-metrics-contract.mjs`
- [x] Built-in HPA modes restricted to live-validated `gpu_utilization`
and `latency_avg` (retired `latency_p50` / `latency_p95` /
`request_rate`; render contract rejects them). Mode-aware
`hpa-load-test.sh` via `HPA_METRIC=...`
- [x] Legacy `*-agent` → `*-metrics-proxy` migration:
`hpa_common_migrate_pre_metrics_proxy_resources` detects leftovers by
basename and label, deletes them before ensure/Helm
(install/reset/load-test); contract-tested; live-validated on 4× L40S
(injected competing `nemoclaw-gpu-agent`, removed, then install + auth
inference + HPA 1→2→1 + Envoy LeastRequest on
`nemoclaw-gpu-metrics-proxy` only)
- [x] GPU front-door renamed from confusing “agent” to `metrics-proxy`
(Ollama + metrics-proxy containers); OpenShell/NemoClaw AI agent remains
CPU-only in the sandbox
- [x] Live Kubernetes/GPU validation on
[**Brev**](https://brev.nvidia.com) (AWS), single-node MicroK8s, **4×
NVIDIA L40S** (48 GB GDDR6 each): chart deploy, Envoy Gateway
LeastRequest, authenticated inference (external Basic + `X-Api-Key`;
OpenShell-path Bearer through Envoy), HPA on **GPU utilization**
(`gpu_utilization_percent`, average per-pod util > 40%) and **LLM
latency** (`nemoclaw_llm_latency_avg_milliseconds`, average per-pod
latency > 3000 ms) via `hpa-load-test.sh`, Envoy LeastRequest
distribution check (concurrent chat completions across Ready GPU pods),
OpenShell sandbox creation (`nemoclaw-onprem`), and authenticated model
+ chat/completions requests from the sandbox through
`https://inference.local/v1` (OpenShell → Envoy LeastRequest → Ollama).
- [ ] **H100 validation (non-blocking follow-up)** — optional later work
after time-slicing is removed; 4× L40S evidence is sufficient for this
experimental recipe.

## Documentation Writer Review

- [x] Documentation writer review completed for the final changes
- Result: `docs-updated`
- Evidence or justification: Independent review covered the README,
catalog, notices, Helm notes, scripts, templates, and tests. It
confirmed the pinned lifecycle and capability claims, CPU-only sandbox
boundary, authenticated on-premises Envoy route, hosted-endpoint
removal, operator-managed Secret behavior, retained-resource cleanup,
synchronized GPU ceiling, dual HPA examples (GPU util >40% and latency
>3000 ms) with custom-metric guidance, latency proxy timing, and L40S
validation guidance. Bash syntax, Helm render, native Kubernetes,
recovery/security, inference authentication, SPDX, and diff checks
passed.
- Reviewer: Independent Codex documentation-writer review
- Reviewed head: `970c1db890dc5fde2fcfb9a4dac4489ae396d72d` (restores
sanitized README screenshots; migration before ensure/helm in
load-test/reset; security-contract cleanup exit 0; metrics-proxy naming
+ pre-metrics-proxy migration)
- [x] Changed user-facing text follows the [writing
guide](https://github.com/NVIDIA/nemoclaw-community/blob/main/WRITING.md)
and [controlled-word
list](https://github.com/NVIDIA/nemoclaw-community/blob/main/.agents/skills/_shared/controlled-words.md).
- [x] A public contributor can understand the changed text without
internal company context.
- [x] I reviewed any agent-generated text before submission. Contributor
review is required before merge.

## Release And Compliance

- [x] No secrets or credentials are included, including API keys, access
tokens, passwords, local `.env` files, private certificates, or token
caches.
- [x] No nonpublic project names, environment names, hostnames, URLs,
ticket identifiers, workspace paths, logs, screenshots, or configuration
values are included.
- [x] Third-party dependency changes are reflected in
`THIRD-PARTY-NOTICES`.
- [x] Public content uses sanitized examples and placeholders instead of
private values.
- [x] I added my DCO sign-off declaration to this pull request
description.

Signed-off-by: maggiezha <maggiez@nvidia.com>

---------

Signed-off-by: maggiezha <42832776+maggiezha@users.noreply.github.com>
Signed-off-by: maggiezha <maggiez@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: ci CI workflows, checks, release automation, or GitHub Actions area: networking DNS, proxy, TLS, ports, host aliases, or connectivity area: packaging Packages, images, registries, installers, or distribution feature PR adds or expands user-visible functionality platform: k8s Affects Kubernetes deployments

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants