Skip to content

feat(mpi): add --disable-mpi-rbac-management flag - #1

Merged
cheyang merged 9 commits into
AliyunContainerService:developfrom
vicoooo26:fix/mpijob-role-rbac
Jul 28, 2026
Merged

feat(mpi): add --disable-mpi-rbac-management flag#1
cheyang merged 9 commits into
AliyunContainerService:developfrom
vicoooo26:fix/mpijob-role-rbac

Conversation

@vicoooo26

@vicoooo26 vicoooo26 commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

What this PR does / why we need it:

Adds a new --disable-mpi-rbac-management CLI flag (default: false) to the MPIJob controller. When set to true, the controller will:

  • Not create ServiceAccount, Role, or RoleBinding for MPIJob launchers
  • Not inject ServiceAccountName into the launcher pod template
  • Not watch RBAC resources (Role, RoleBinding, ServiceAccount)

This allows operators to run with reduced privileges and supports multi-tenant environments where users pre-provide their own ServiceAccount with the necessary permissions (pods get/list/watch, pods/exec create) via spec.serviceAccountName in the launcher pod template.

Which issue(s) this PR fixes (optional, in Fixes #<issue number>, #<issue number>, ... format, will close the issue(s) when PR gets merged):
Fixes #

Checklist:

- [ ] Docs included if any changes are user facing

@vicoooo26
vicoooo26 changed the base branch from master to release-1.9 July 14, 2026 16:23
@vicoooo26

Copy link
Copy Markdown
Collaborator Author

@cheyang, could you please review this PR? Thanks in advance.

@vicoooo26
vicoooo26 force-pushed the fix/mpijob-role-rbac branch 2 times, most recently from a5ad06a to b847f6b Compare July 22, 2026 09:22

@cheyang cheyang 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.

Reviewed the feature end to end and ran the MPI envtest suite against this PR head — the added “disabled RBAC management” specs pass. The gating is consistent across the three sites (watch setup, ReconcilePods, launcher SA injection), and since podSpec is a DeepCopy of the launcher template, a user-supplied serviceAccountName is correctly preserved when the flag is on. Two things worth addressing before merge, plus a docs gap (inline).

Comment thread pkg/controller.v1/mpi/mpijob_controller.go
Comment thread cmd/training-operator.v1/main.go

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds a new --disable-mpi-rbac-management flag to allow the MPIJob controller to run without creating/watching launcher RBAC resources, enabling reduced-privilege / multi-tenant deployments where users provide their own ServiceAccount.

Changes:

  • Gate RBAC watches (Role/RoleBinding/ServiceAccount) behind DisableMPIRBACManagement.
  • Skip launcher ServiceAccount/Role/RoleBinding reconciliation and ServiceAccountName injection when RBAC management is disabled.
  • Add CLI/config plumbing and controller tests covering the disabled-RBAC behavior.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
pkg/controller.v1/mpi/mpijob_controller.go Conditionally disables RBAC watches, RBAC resource creation, and SA name injection based on the new config flag.
pkg/controller.v1/mpi/mpijob_controller_test.go Adds envtest coverage verifying no RBAC resources are created and controller still reconciles correctly when disabled.
pkg/config/config.go Adds DisableMPIRBACManagement to shared controller config.
cmd/training-operator.v1/main.go Wires up the --disable-mpi-rbac-management CLI flag.
Comments suppressed due to low confidence (1)

pkg/controller.v1/mpi/mpijob_controller_test.go:711

  • This test resets DisableMPIRBACManagement to false unconditionally. Restoring the previous value reduces coupling to global state and avoids surprising behavior if the suite default ever changes.
			ctlrconfig.Config.DisableMPIRBACManagement = true
			defer func() {
				ctlrconfig.Config.DisableMPIRBACManagement = false
			}()

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/controller.v1/mpi/mpijob_controller_test.go
Comment thread pkg/controller.v1/mpi/mpijob_controller_test.go Outdated
@vicoooo26
vicoooo26 force-pushed the fix/mpijob-role-rbac branch 2 times, most recently from d11ef50 to 8f8a791 Compare July 23, 2026 15:56
@vicoooo26
vicoooo26 changed the base branch from release-1.9 to develop July 24, 2026 05:40
kubernetes 36.0.0 regenerated the sync client with Pydantic models,
breaking the FakeResponse deserialization hack used in SDK unit tests.
Pin kubernetes<36 in all CI workflows that install the Python SDK
(test-python, integration-tests, e2e-test-train-api, test-example-notebooks)
to avoid impacting SDK release. Add a TODO for future Pydantic work.

Signed-off-by: Vico Chu <vico24826@gmail.com>
JAX worker pods were OOMKilled (exit 137) with 3Gi memory limit.
Increase to 5Gi to accommodate XLA compilation and training memory usage.
GitHub-hosted runners have 16GB RAM, so 2 replicas × 5Gi is feasible.

Signed-off-by: Vico Chu <vico24826@gmail.com>
@vicoooo26
vicoooo26 force-pushed the fix/mpijob-role-rbac branch from 8f8a791 to 079673d Compare July 25, 2026 14:02
fastjsonschema 2.22.0 introduced PEP 604 union type syntax (dict | bool)
in type annotations, which requires Python 3.10+. This breaks papermill
imports on Python 3.9 in the notebook test workflow. Pin to <2.22 until
Python 3.9 support is dropped from the test matrix.

Signed-off-by: Vico Chu <vico24826@gmail.com>
When the flag is set to true, the MPIJob controller skips creating
ServiceAccount, Role, and RoleBinding, skips injecting
ServiceAccountName into the launcher pod, and skips watching RBAC
resources. Users must pre-provide a ServiceAccount with sufficient
permissions via the launcher pod template's spec.serviceAccountName.
ConfigMap creation and kubectl delivery remain unconditional.

Signed-off-by: Vico Chu <vico24826@gmail.com>
Add integration test that starts a second envtest instance with
DisableMPIRBACManagement=true and verifies:
- No Role/RoleBinding/ServiceAccount created by the operator
- ConfigMap and kubectl delivery init container still created
- Role changes do not trigger reconcile (watch suppressed)
- ConfigMap changes still trigger reconcile (positive control)
- Launcher pod recreated only after ConfigMap-triggered reconcile

Also adds kubectl delivery init container assertion to pin the
spec invariant that kubectl delivery remains unconditional.

Signed-off-by: Vico Chu <vico24826@gmail.com>
Add kubectl delivery init container check to the first disabled RBAC
test context, mirroring the assertion in the SetupWithManager test.
Both ConfigMap creation and kubectl delivery are unconditional (not
gated by the flag), so both should be verified when the flag is true.

Signed-off-by: Vico Chu <vico24826@gmail.com>
…cceeds

Signed-off-by: Vico Chu <vico24826@gmail.com>
…ment help text

Specify that the user-provided ServiceAccount needs a Role granting
get, list, watch on pods and create on pods/exec, matching the
permissions that newLauncherRole would have granted.

Signed-off-by: Vico Chu <vico24826@gmail.com>
…ncher template

Signed-off-by: Vico Chu <vicochu@gmail.com>
@vicoooo26
vicoooo26 force-pushed the fix/mpijob-role-rbac branch from 079673d to 1200c71 Compare July 27, 2026 03:09
cheyang added a commit to cheyang/trainer that referenced this pull request Jul 27, 2026
PR #1 was force-pushed and retargeted (release-1.9 -> develop), so this round
compared diff-of-diffs from the shared base 8e41c03 instead of a linear delta.

- PR1-F1 still present: the flag + no user SA still leaves the launcher SA empty.
  Round 2 adds a NoServiceAccountName warning Event => mitigation, not a fix.
- PR1-F1b (new, live read-only): the namespace default SA has none of the four
  permissions the launcher needs on ACK v1.36.1-aliyun.1.
- PR1-F3 (new): that warning is one-shot (newLauncher is the single call site).
  Mutation D vs D2 shows re-emitting the same Event would be deduped by the
  client-go correlator, so the fix must be a status condition or fail-fast.
- PR1-F4 (new): Should not watch RBAC resources when flag is true stays green
  with the SetupWithManager guard removed => that guard has no coverage.
- PR1-F5 (new): 4 CI pins + a jaxjob memory bump are unrelated scope creep.
- Dropped: suspected vacuous assertions (disproven by mutation A) and suspected
  order-dependent flakiness (not reproduced across 3 randomized seeds).

Harness now uses the canonical review-finding-verifier re-verify.sh with a
machine-readable manifest. Production code untouched.
podSpec.Spec.ServiceAccountName = launcherName
}
} else if len(mpiJob.Spec.MPIReplicaSpecs[kubeflowv1.MPIJobReplicaTypeLauncher].Template.Spec.ServiceAccountName) == 0 {
jc.Recorder.Eventf(mpiJob, corev1.EventTypeWarning, NoServiceAccountNameReason,

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.

With the flag on and no serviceAccountName on the launcher, this branch emits a warning but leaves podSpec.Spec.ServiceAccountName empty. The launcher then runs under the namespace default SA, which on a stock cluster cannot do what it needs. I checked on ACK v1.36.1-aliyun.1 with kubectl auth can-i --as=system:serviceaccount:default:default: get, list and watch on pods all return no, and so does create on pods/exec. So the MPIJob is admitted happily and then dies at runtime when the launcher tries to exec into the workers.

Two things make the warning weaker than it first looks.

First, newLauncher is the only caller (line 400, when the launcher Pod is created), so the event fires exactly once. After it ages out of the API server, or if the operator is upgraded while a launcher already exists, nothing points at the misconfiguration any more. There is no status condition, and the Pod spec looks unremarkable.

Second, and this is the part that surprised me: re-emitting the same warning on every reconcile would not fix it either. client-go's event correlator keys on (involvedObject, reason, message) and patches or suppresses instead of creating a new object. I patched the controller to re-emit on every reconcile to check. With an identical message no new event ever appeared; with a unique message per reconcile it did.

So whatever the durable signal ends up being, it has to be a status condition on the MPIJob or a fail-fast (reject in a validating webhook, or return a reconcile error). A repeated event will just get deduped. Either way, it would help to spell out the permissions a user-supplied SA needs: get, list and watch on pods, plus create on pods/exec.

Test logs and the mutation matrix behind this: https://github.com/cheyang/trainer/tree/verify/aliyun-trainer-prs/docs/verification/aliyun-trainer-prs

})

Context("SetupWithManager with disabled RBAC management", func() {
It("Should not watch RBAC resources when flag is true", func() {

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.

I removed the if !ctlrconfig.Config.DisableMPIRBACManagement guard in SetupWithManager (line 218), so the Role, RoleBinding and ServiceAccount watches get registered even with the flag on. This spec stayed green.

What it actually asserts is that no RBAC objects are created, and that the ConfigMap and Pod watches still work. Both are worth having. Neither says anything about watch registration. The comment inside the spec is candid that this is structural rather than behavioral; the name is not, and a refactor that quietly drops the guard would sail through CI.

Either rename it to match what it checks, or make it behavioral by asserting on the manager's watch set or informer cache contents.

Credit where it is due: the rest of the added coverage does bite. When I removed the ReconcilePods guard instead, two of the three specs failed, exactly as they should.

# this pin, either adapt FakeResponse in
# sdk/python/kubeflow/training/utils/utils.py for Pydantic or mock
# ApiClient.deserialize directly in the test suite.
pip install pytest python-dateutil urllib3 "kubernetes<36"

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.

This pin, and the matching ones in integration-tests.yaml, e2e-test-train-api.yaml and test-example-notebooks.yaml, fixes a real breakage, and the TODO explaining the Pydantic switch is the kind of comment I wish more pins had. It just has nothing to do with MPI RBAC. Bundled like this, reverting the feature also reverts the CI fix. Worth its own PR so the two can land and be rolled back independently.

name=CONTAINER_NAME,
image=os.getenv("JAX_JOB_IMAGE", "docker.io/kubeflow/jaxjob-dist-spmd-mnist:latest"),
resources=V1ResourceRequirements(limits={"memory": "3Gi", "cpu": "1.2"}),
resources=V1ResourceRequirements(limits={"memory": "5Gi", "cpu": "1.2"}),

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.

Two thirds more memory for an e2e job, in a PR about RBAC. If this is working around OOMKills, a sentence in the commit message would save the next person the archaeology. It probably belongs with the CI changes rather than the feature.

handler.TypedEnqueueRequestForOwner[*corev1.ServiceAccount](mgr.GetScheme(), mgr.GetRESTMapper(), &kubeflowv1.MPIJob{}, handler.OnlyControllerOwner()),
util.OnDependentFuncs[*corev1.ServiceAccount](jc.Scheme, jc.Expectations, &jc.JobController))); err != nil {
return err
if !ctlrconfig.Config.DisableMPIRBACManagement {

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.

No test fails when this guard is removed, as noted on the watch spec. It is also the one part of the feature whose effect never shows up in the API: a stray RBAC watch just means the operator still needs list and watch on Roles and RoleBindings cluster-wide, which is the thing someone turning this flag on is trying to get rid of. That makes it the piece most likely to regress unnoticed.

}, testutil.Timeout, testutil.Interval).Should(Equal(launcherSaName))
})

It("Should warn when no ServiceAccount is provided on the launcher", func() {

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.

Asserting that a warning is recorded locks in today's behavior, which is the behavior I would like to see change. Once there is a status condition or a fail-fast, the test worth keeping is one that pins the launcher never starting silently with an empty ServiceAccountName.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (3)

.github/workflows/test-python.yaml:35

  • This workflow pinning (e.g., "kubernetes<36") and the related TODO about SDK deserialization are significant, but they’re not mentioned in the PR description/title (which focus on the MPI RBAC flag). Please either update the PR description to call out these CI/dependency changes (and why they’re needed) or split them into a separate PR to keep review scope clear.
          # TODO: kubernetes 36.0.0 switched to Pydantic models, breaking the
          # FakeResponse deserialization hack in the SDK unit tests. To remove
          # this pin, either adapt FakeResponse in
          # sdk/python/kubeflow/training/utils/utils.py for Pydantic or mock
          # ApiClient.deserialize directly in the test suite.

.github/workflows/test-example-notebooks.yaml:31

  • The new fastjsonschema pin is introduced without any rationale. Unexplained dependency pins tend to get removed later and regress CI; please add a short TODO/comment with a link to an issue describing the breakage this pin addresses.
      - name: Install Python Dependencies
        run: |
          pip install papermill==2.6.0 jupyter==1.1.1 ipykernel==6.29.5 "kubernetes<36" "fastjsonschema<2.22"

sdk/python/test/e2e/test_e2e_jaxjob.py:160

  • Hard-coding the JAXJob memory limit to 5Gi can make the e2e test brittle across different CI cluster sizes. Consider making the memory limit configurable via an env var (similar to JAX_JOB_IMAGE) so CI can tune it without editing the test.
    return V1Container(
        name=CONTAINER_NAME,
        image=os.getenv("JAX_JOB_IMAGE", "docker.io/kubeflow/jaxjob-dist-spmd-mnist:latest"),
        resources=V1ResourceRequirements(limits={"memory": "5Gi", "cpu": "1.2"}),
    )

@cheyang cheyang 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.

/lgtm
/approve

@cheyang
cheyang merged commit 15cc1de into AliyunContainerService:develop Jul 28, 2026
62 of 64 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants