Skip to content

feat(deployments): grant k8s backend RBAC in deploy chart (AIRCORE-757 phase 6) - #575

Merged
tylersbray merged 4 commits into
mainfrom
757-k8s-phase6-rbac-chart/tbray
Jul 7, 2026
Merged

feat(deployments): grant k8s backend RBAC in deploy chart (AIRCORE-757 phase 6)#575
tylersbray merged 4 commits into
mainfrom
757-k8s-phase6-rbac-chart/tbray

Conversation

@tylersbray

@tylersbray tylersbray commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Summary

AIRCORE-757 Phase 6 — grants the k8s deployments backend (Phase 5, #553) the RBAC it needs, wired into the deploy chart.

  • Extends the existing nmp-core controller Role (k8s/helm/templates/core/controller-role.yaml) with the two verbs the k8s DeploymentBackend needs but didn't have: create/delete on apps/v1.Deployment, and a new rule for services (get/list/create/delete).
  • Documents the k8s executor config and RBAC scope in plugins/nemo-deployments/README.md.
  • Adds plugins/nemo-deployments/tests/unit/backends/k8s/test_rbac_manifest.py asserting the chart's RBAC rules, plus a regression guard over the pre-existing base rules (pods, pods/log, jobs, PVCs, configmaps).

Design decisions

Extended the existing nmp-core controller Role instead of a dedicated ServiceAccount/Role for the deployments plugin. DeploymentsController is registered via the nemo.controllers entry point and runs inside the existing nmp-core controller pod (--controller-group=all), under controllerServiceAccountName. A parallel Role bound to that same pod's ServiceAccount would be redundant, so this PR extends the Role already granting that pod's RBAC (pods, pods/log, jobs, PVCs, configmaps) rather than introducing a second one.

RBAC audit was exhaustive against every k8s API call in the Phase 1-5 backend code, not just the ticket's suggested verb list: volumes.py, jobs.py, deployments.py, compiler.py. Result: exactly two gaps — apps/deployments was get/list/watch only (backend also creates/deletes), and services had no rule at all (backend gets/creates/deletes). Both additions are minimal: no update/patch on Deployments since the backend recreates rather than patches.

RBAC additions are ungated (no new rbac.* values flag), consistent with the existing jobs/PVC/configmap base rules — keeps values.yaml and helm-docs-generated README untouched, since this isn't an optional third-party integration like Volcano or the NIM Operator.

RBAC test uses raw-template regex parsing, not a helm template render — this diverged from the original plan. k8s/helm/Chart.yaml declares an unconditional k8s-nim-operator chart dependency (only the installed resources are gated by .Values.rbac.k8sNimOperatorEnabled, not the dependency-presence check), so helm template refuses to run at all unless that subchart .tgz is fetched from an NGC repo into charts/ first — verified locally that this fails even with k8s-nim-operator.enabled=false and --show-only. There's no existing helm lint/template CI job in this repo and no committed Chart.lock/charts/ dir, so a skip-gated render would silently skip in CI 100% of the time. The test instead parses the static YAML rule entries directly from the Go-template source, which is safe because the base RBAC rules (unlike the Volcano/NIM-Operator blocks) are plain YAML with no Helm expressions inside them. Runs deterministically with no helm binary or network dependency.

No functional carryover from Phase 5 review — confirmed against #553's merge history; mckornfield's review comments were style-only and already addressed pre-merge.

Forward-looking note: AIRCORE-759 interaction

AIRCORE-759 (models adopts this plugin, drops k8s-nim-operator) is backlogged but directly bears on the decisions above:

  • It validates the Role-reuse decision: once models stops driving k8s directly and routes NIM/vLLM through this plugin's SDK, that traffic executes through the exact same K8sDeploymentBackend/DeploymentsController/nmp-core controller pod this PR grants RBAC to. A dedicated per-plugin ServiceAccount would have needed reconciling with models' path later; extending the shared Role avoids that.
  • It explains and time-bounds the helm template blocker above: AIRCORE-759 requires removing the k8s-nim-operator subchart dependency, its CRDs, and its RBAC (nimservices/nimcaches rules) from this same chart. Once that lands, helm template k8s/helm should render standalone and test_rbac_manifest.py could be upgraded from raw-text parsing to a real chart render.
  • The RBAC surface this PR grants (Deployment/Service/Job/PVC/ConfigMap/Pods/pods-log) should already be sufficient for AIRCORE-759's NIM/vLLM traffic, since both compile to the same generic (Volume, puller, server) DeploymentConfig primitives that already flow through the backend code audited here — no new verbs anticipated purely from that migration.
  • One real gap for whoever picks up AIRCORE-759: its scope note mentions image-pull secrets moving to backend_config.k8s.image_pull_secrets, but that field doesn't exist yet on K8sDeploymentConfig/compiler.py today (verified) — will need a small compiler addition, and possibly broader secrets verbs (currently only create/delete) if the plugin needs to read/compare an existing pull secret.
  • The dev-blue tbray-dev pod's missing nemo-models-vllm Role/RoleBinding (noted separately during manual-smoke-test setup) is models' current, pre-759 standalone k8s-emission identity from AIRCORE-694. It becomes obsolete once AIRCORE-759 lands and models delegates to this plugin's nmp-core Role — a natural line item for that ticket's own RBAC-cleanup requirement, not addressed here.

Test plan

  • uv run --frozen pytest plugins/nemo-deployments/tests/unit -q — 258 passed
  • uv run ruff check / uv run ruff format --check / uv run --frozen ty check — clean
  • Manually verified with helm template (locally cached k8s-nim-operator subchart) that the rendered Role contains the expected verbs
  • uv run pre-commit run -a — all hooks pass except studio-lint-staged, a pre-existing Node-version environment gap scoped to web/ (untouched by this PR) that self-skips in CI
  • Bugbot subagent review (clean context): no bugs found
  • Phase 7 (follow-up PR) adds kind/kubeconfig integration tests and cluster-validation docs; Tier C manual smoke on dev-blue runs after Phase 6 + 7 merge

Summary by CodeRabbit

  • Bug Fixes
    • Expanded controller RBAC permissions to allow creating and deleting Kubernetes deployments and to grant required permissions for core services.
  • Documentation
    • Updated Kubernetes executors backend documentation, including created resources, namespace override behavior, RBAC requirements, and native sidecar compatibility notes.
  • Tests
    • Added unit tests that statically validate Helm chart RBAC rules and enforce required verb sets for key resources.

@tylersbray
tylersbray requested review from a team as code owners July 6, 2026 17:43
@github-actions github-actions Bot added the feat label Jul 6, 2026
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor
Suite Lines Covered Line Rate Branch Rate
Unit Tests 23272/30437 76.5% 61.2%
Integration Tests 13616/29117 46.8% 20.0%

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 8d530538-46c3-43d0-b7b5-2a35fdf533d5

📥 Commits

Reviewing files that changed from the base of the PR and between 5265cea and 57fb965.

📒 Files selected for processing (3)
  • k8s/helm/templates/core/controller-role.yaml
  • plugins/nemo-deployments/README.md
  • plugins/nemo-deployments/tests/unit/backends/k8s/test_rbac_manifest.py
✅ Files skipped from review due to trivial changes (1)
  • plugins/nemo-deployments/README.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • k8s/helm/templates/core/controller-role.yaml

📝 Walkthrough

Walkthrough

Extends controller RBAC for deployments and services, documents Kubernetes executor RBAC and sidecar behavior, and adds static tests that parse the Helm Role template.

Changes

RBAC rule and validation

Layer / File(s) Summary
RBAC role manifest changes
k8s/helm/templates/core/controller-role.yaml
Adds create/delete to deployments and a new services rule with get/list/create/delete.
README documentation of k8s executors and RBAC
plugins/nemo-deployments/README.md
Documents Kubernetes executor resource creation, config options, RBAC scope/reuse of nmp-core, and native sidecar fallback behavior for Kubernetes < 1.29.
RBAC manifest unit tests
plugins/nemo-deployments/tests/unit/backends/k8s/test_rbac_manifest.py
Adds static parsing helpers and assertions for deployments, services, and base RBAC rules.

Possibly related PRs

Suggested reviewers: mckornfield, benmccown

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: granting Kubernetes backend RBAC in the deploy chart.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 757-k8s-phase6-rbac-chart/tbray

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

@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: 1

🤖 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 `@plugins/nemo-deployments/README.md`:
- Around line 89-92: The README permission summary is inaccurate for `pods/log`:
it currently says `get`/`list`/`watch`, but the actual role only grants
`get`/`list`. Update the permissions description in the deployment docs so the
`pods/log` verbs match the real RBAC definition, and keep the rest of the
Kubernetes verb summary unchanged.
🪄 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: 5c8a65d5-96d6-4631-a719-6ada462c62e3

📥 Commits

Reviewing files that changed from the base of the PR and between fb50b86 and 0d3d545.

📒 Files selected for processing (3)
  • k8s/helm/templates/core/controller-role.yaml
  • plugins/nemo-deployments/README.md
  • plugins/nemo-deployments/tests/unit/backends/k8s/test_rbac_manifest.py

Comment thread plugins/nemo-deployments/README.md
Comment thread plugins/nemo-deployments/tests/unit/backends/k8s/test_rbac_manifest.py Outdated
Comment thread plugins/nemo-deployments/README.md Outdated
…7 phase 6)

Extends the nmp-core controller Role with the two permissions the Phase 5
k8s DeploymentBackend needs but didn't have: create/delete on apps/v1
Deployments, and a new services rule (get/list/create/delete). The
DeploymentsController runs inside the existing nmp-core controller pod, so
this reuses that pod's ServiceAccount/Role rather than adding a parallel one.

Also documents the k8s executor config and RBAC scope in the plugin README,
and adds a unit test asserting the chart's rendered RBAC rules.

Signed-off-by: Tyler Bray <tbray@nvidia.com>
The example used my personal dev-blue namespace (tbray-dev) as the
"default" default_namespace value. Replace with the literal default
from K8sExecutorConfig plus a comment explaining what belongs there,
so the doc doesn't imply a personal namespace is the expected default.

Signed-off-by: Tyler Bray <tbray@nvidia.com>
CodeRabbit caught that the RBAC summary grouped pods and pods/log under
the same get/list/watch verb set, but controller-role.yaml only grants
get/list on pods/log (no watch). Split the two out.

Signed-off-by: Tyler Bray <tbray@nvidia.com>
- test_rbac_manifest.py: extract the repeated controller-role.yaml path
  segments (previously duplicated in _repo_root() and
  _controller_role_source()) into a single _CONTROLLER_ROLE_RELATIVE_PATH
  constant.
- README: drop the "(a RoleBinding in that namespace, or a
  ClusterRole/ClusterRoleBinding)" parenthetical from the RBAC section —
  self-evident to anyone hitting the "additional RBAC" gap it describes,
  and trims a sentence that was otherwise dense with cross-references.

Signed-off-by: Tyler Bray <tbray@nvidia.com>
@tylersbray
tylersbray force-pushed the 757-k8s-phase6-rbac-chart/tbray branch from 5265cea to 57fb965 Compare July 7, 2026 17:30
@tylersbray
tylersbray added this pull request to the merge queue Jul 7, 2026
Merged via the queue into main with commit 658a02f Jul 7, 2026
98 of 100 checks passed
@tylersbray
tylersbray deleted the 757-k8s-phase6-rbac-chart/tbray branch July 7, 2026 18:23
tylersbray added a commit that referenced this pull request Jul 7, 2026
…end (AIRCORE-757 phase 7)

Mirror the existing Docker integration-test layout for the k8s
deployments backend: PVC lifecycle, Job success, ConfigMap mount
round-trip, Deployment+Service readiness, and label-gated delete
rejection, plus a reconciler-level prerequisite-chain test. All tests
skip cleanly when no kubeconfig/cluster is reachable, matching the
Docker suite's CI behavior.

Branched from origin/main ahead of Phase 6 (#575) landing, since these
tests instantiate K8sDeploymentBackend directly with the developer's
own kubeconfig rather than the restricted nmp-core ServiceAccount that
Phase 6's RBAC changes target -- no functional dependency between the
two. Will rebase onto main once #575 merges.

Signed-off-by: Tyler Bray <tbray@nvidia.com>
arpitsardhana pushed a commit that referenced this pull request Jul 9, 2026
…7 phase 6) (#575)

* feat(deployments): grant k8s backend RBAC in deploy chart (AIRCORE-757 phase 6)

Extends the nmp-core controller Role with the two permissions the Phase 5
k8s DeploymentBackend needs but didn't have: create/delete on apps/v1
Deployments, and a new services rule (get/list/create/delete). The
DeploymentsController runs inside the existing nmp-core controller pod, so
this reuses that pod's ServiceAccount/Role rather than adding a parallel one.

Also documents the k8s executor config and RBAC scope in the plugin README,
and adds a unit test asserting the chart's rendered RBAC rules.

Signed-off-by: Tyler Bray <tbray@nvidia.com>

* fix(deployments): use generic namespace in k8s executor README example

The example used my personal dev-blue namespace (tbray-dev) as the
"default" default_namespace value. Replace with the literal default
from K8sExecutorConfig plus a comment explaining what belongs there,
so the doc doesn't imply a personal namespace is the expected default.

Signed-off-by: Tyler Bray <tbray@nvidia.com>

* fix(deployments): correct pods/log verb claim in README RBAC summary

CodeRabbit caught that the RBAC summary grouped pods and pods/log under
the same get/list/watch verb set, but controller-role.yaml only grants
get/list on pods/log (no watch). Split the two out.

Signed-off-by: Tyler Bray <tbray@nvidia.com>

* fix(deployments): address mckornfield review feedback on RBAC chart PR

- test_rbac_manifest.py: extract the repeated controller-role.yaml path
  segments (previously duplicated in _repo_root() and
  _controller_role_source()) into a single _CONTROLLER_ROLE_RELATIVE_PATH
  constant.
- README: drop the "(a RoleBinding in that namespace, or a
  ClusterRole/ClusterRoleBinding)" parenthetical from the RBAC section —
  self-evident to anyone hitting the "additional RBAC" gap it describes,
  and trims a sentence that was otherwise dense with cross-references.

Signed-off-by: Tyler Bray <tbray@nvidia.com>

---------

Signed-off-by: Tyler Bray <tbray@nvidia.com>
arpitsardhana pushed a commit that referenced this pull request Jul 9, 2026
…end (AIRCORE-757 phase 7) (#581)

* test(deployments): add kind/kubeconfig integration tests for k8s backend (AIRCORE-757 phase 7)

Mirror the existing Docker integration-test layout for the k8s
deployments backend: PVC lifecycle, Job success, ConfigMap mount
round-trip, Deployment+Service readiness, and label-gated delete
rejection, plus a reconciler-level prerequisite-chain test. All tests
skip cleanly when no kubeconfig/cluster is reachable, matching the
Docker suite's CI behavior.

Branched from origin/main ahead of Phase 6 (#575) landing, since these
tests instantiate K8sDeploymentBackend directly with the developer's
own kubeconfig rather than the restricted nmp-core ServiceAccount that
Phase 6's RBAC changes target -- no functional dependency between the
two. Will rebase onto main once #575 merges.

Signed-off-by: Tyler Bray <tbray@nvidia.com>

* test(deployments): address CodeRabbit feedback on k8s integration tests

Fixes 3 actionable review comments on #581:
- _wait_for_status now fails explicitly with an attempt count instead of
  silently returning on timeout, so callers get a clear message instead of
  a later ambiguous assertion failure.
- test_pvc_lifecycle no longer asserts inside its finally block, so a
  cleanup-path failure can't mask the real assertion failure from the
  try block.
- test_reconcile_k8s's teardown deletes puller and server concurrently via
  asyncio.gather(return_exceptions=True) so a failure deleting one doesn't
  leak the other.

Also addresses 2 nitpicks:
- README: added a prerequisites block ahead of the kind/dev-blue run
  commands in the new Cluster validation section.
- test_reconcile_k8s's get_side_effect now raises KeyError for any
  Deployment name other than "puller"/"server" instead of silently
  falling back to server_dep, so a typo fails loudly.

Left 2 nitpicks as-is (noted in the PR): the `from __future__ import
annotations` import and the import-time cluster/daemon availability probe
both match the pre-existing docker_availability.py /
test_reconcile_docker.py convention already used throughout this test
suite; fixing only the new k8s files would create inconsistency, and a
broader refactor of the shared pattern is out of scope here.

Signed-off-by: Tyler Bray <tbray@nvidia.com>

* docs(deployments): clarify ambient kubeconfig wording in README

Addresses mckornfield's PR #581 nitpick that "ambient" reads oddly out
of context.

Signed-off-by: tbray <tbray@nvidia.com>

---------

Signed-off-by: Tyler Bray <tbray@nvidia.com>
Signed-off-by: tbray <tbray@nvidia.com>
Zenodia pushed a commit to Zenodia/nemo-platform that referenced this pull request Jul 10, 2026
…end (AIRCORE-757 phase 7) (NVIDIA-NeMo#581)

* test(deployments): add kind/kubeconfig integration tests for k8s backend (AIRCORE-757 phase 7)

Mirror the existing Docker integration-test layout for the k8s
deployments backend: PVC lifecycle, Job success, ConfigMap mount
round-trip, Deployment+Service readiness, and label-gated delete
rejection, plus a reconciler-level prerequisite-chain test. All tests
skip cleanly when no kubeconfig/cluster is reachable, matching the
Docker suite's CI behavior.

Branched from origin/main ahead of Phase 6 (NVIDIA-NeMo#575) landing, since these
tests instantiate K8sDeploymentBackend directly with the developer's
own kubeconfig rather than the restricted nmp-core ServiceAccount that
Phase 6's RBAC changes target -- no functional dependency between the
two. Will rebase onto main once NVIDIA-NeMo#575 merges.

Signed-off-by: Tyler Bray <tbray@nvidia.com>

* test(deployments): address CodeRabbit feedback on k8s integration tests

Fixes 3 actionable review comments on NVIDIA-NeMo#581:
- _wait_for_status now fails explicitly with an attempt count instead of
  silently returning on timeout, so callers get a clear message instead of
  a later ambiguous assertion failure.
- test_pvc_lifecycle no longer asserts inside its finally block, so a
  cleanup-path failure can't mask the real assertion failure from the
  try block.
- test_reconcile_k8s's teardown deletes puller and server concurrently via
  asyncio.gather(return_exceptions=True) so a failure deleting one doesn't
  leak the other.

Also addresses 2 nitpicks:
- README: added a prerequisites block ahead of the kind/dev-blue run
  commands in the new Cluster validation section.
- test_reconcile_k8s's get_side_effect now raises KeyError for any
  Deployment name other than "puller"/"server" instead of silently
  falling back to server_dep, so a typo fails loudly.

Left 2 nitpicks as-is (noted in the PR): the `from __future__ import
annotations` import and the import-time cluster/daemon availability probe
both match the pre-existing docker_availability.py /
test_reconcile_docker.py convention already used throughout this test
suite; fixing only the new k8s files would create inconsistency, and a
broader refactor of the shared pattern is out of scope here.

Signed-off-by: Tyler Bray <tbray@nvidia.com>

* docs(deployments): clarify ambient kubeconfig wording in README

Addresses mckornfield's PR NVIDIA-NeMo#581 nitpick that "ambient" reads oddly out
of context.

Signed-off-by: tbray <tbray@nvidia.com>

---------

Signed-off-by: Tyler Bray <tbray@nvidia.com>
Signed-off-by: tbray <tbray@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants