Skip to content

fix: include user-defined ports in driver Service#2895

Open
YassinNouh21 wants to merge 2 commits intokubeflow:masterfrom
YassinNouh21:fix/driver-service-user-ports
Open

fix: include user-defined ports in driver Service#2895
YassinNouh21 wants to merge 2 commits intokubeflow:masterfrom
YassinNouh21:fix/driver-service-user-ports

Conversation

@YassinNouh21
Copy link
Copy Markdown
Contributor

Summary

  • User-defined ports in spec.driver.ports are correctly added to driver pod containers via the webhook, but the generated driver Service only includes the Spark UI port (4040)
  • This makes custom ports (metrics endpoints, debug ports) unreachable via the Service, breaking service mesh strict mode and custom monitoring setups
  • Extracted port list construction into buildDriverServicePorts() which appends spec.driver.ports entries alongside the primary Spark UI port

Fixes #2891

Reproduction & Verification

Tested on a Kind cluster with spark-operator built from this branch:

Before (bug):

$ kubectl get svc port-test-ui-svc -o jsonpath='{.spec.ports[*].name}'
spark-driver-ui-port                          # only 4040

After (fix):

$ kubectl get svc port-test-ui-svc -o jsonpath='{.spec.ports[*].name}'
spark-driver-ui-port custom-metrics debug-port   # 4040 + 9090 + 5005

Test plan

  • Verified on Kind cluster: custom driver ports now appear in generated Service
  • go build ./... passes
  • Existing unit tests pass
  • CI checks pass

When ports are specified in spec.driver.ports, they are correctly added
to the driver pod's container spec via the webhook, but the generated
driver Service only includes the Spark UI port. This makes user-defined
ports unreachable via the Service, breaking service mesh strict mode
and custom metrics endpoints.

Extract port list construction into buildDriverServicePorts() which
appends spec.driver.ports entries alongside the primary Spark UI port.

Fixes kubeflow#2891

Signed-off-by: yassinnouh21 <yassinnouh21@gmail.com>
Copilot AI review requested due to automatic review settings March 29, 2026 15:02
@github-actions
Copy link
Copy Markdown

🎉 Welcome to the Kubeflow Spark Operator! 🎉

Thanks for opening your first PR! We're happy to have you as part of our community 🚀

Here's what happens next:

Join the community:

Feel free to ask questions in the comments if you need any help or clarification!
Thanks again for contributing to Kubeflow! 🙏

@google-oss-prow
Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign mwielgus for approval. For more information see the Kubernetes Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a gap in the SparkApplication controller where user-defined driver ports (spec.driver.ports) were added to the driver pod but were not exposed on the generated driver/UI Service, making those ports unreachable via the Service.

Changes:

  • Extracted driver Service port construction into a helper (buildDriverServicePorts()).
  • Updated driver Service creation to include both the primary Spark UI port and any spec.driver.ports entries.

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

Comment on lines +468 to +474
for _, p := range app.Spec.Driver.Ports {
ports = append(ports, corev1.ServicePort{
Name: p.Name,
Port: p.ContainerPort,
TargetPort: intstr.FromInt32(p.ContainerPort),
Protocol: corev1.Protocol(p.Protocol),
})
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

spec.driver.ports entries are appended verbatim, which can make the Service invalid if a user-defined port collides with the primary port (same port+protocol) or if two user-defined ports reuse the same .name. Kubernetes rejects Services with duplicate spec.ports[].name and (for the same protocol) duplicate spec.ports[].port, so reconciliation would fail for such apps. Consider de-duplicating/validating while building the list (e.g., skip or surface a clear error when a name or port+protocol is already used, including collisions with the primary UI port).

Copilot uses AI. Check for mistakes.
Comment on lines +467 to +474

for _, p := range app.Spec.Driver.Ports {
ports = append(ports, corev1.ServicePort{
Name: p.Name,
Port: p.ContainerPort,
TargetPort: intstr.FromInt32(p.ContainerPort),
Protocol: corev1.Protocol(p.Protocol),
})
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

This change introduces new behavior (propagating spec.driver.ports onto the generated driver/UI Service), but there are no unit tests asserting the additional ServicePorts are present. Adding a test case that sets app.Spec.Driver.Ports and verifies the created Service contains both the primary port and the custom ports (and ideally a collision case) would prevent regressions.

Suggested change
for _, p := range app.Spec.Driver.Ports {
ports = append(ports, corev1.ServicePort{
Name: p.Name,
Port: p.ContainerPort,
TargetPort: intstr.FromInt32(p.ContainerPort),
Protocol: corev1.Protocol(p.Protocol),
})
// Track used port names and numbers to avoid collisions between the primary
// ServicePort and any user-defined ports from app.Spec.Driver.Ports.
usedNames := map[string]struct{}{
portName: {},
}
usedPorts := map[int32]struct{}{
port: {},
}
for _, p := range app.Spec.Driver.Ports {
// Skip ports that would collide with an existing ServicePort by name or port.
if _, exists := usedNames[p.Name]; exists {
continue
}
if _, exists := usedPorts[p.ContainerPort]; exists {
continue
}
ports = append(ports, corev1.ServicePort{
Name: p.Name,
Port: p.ContainerPort,
TargetPort: intstr.FromInt32(p.ContainerPort),
Protocol: corev1.Protocol(p.Protocol),
})
usedNames[p.Name] = struct{}{}
usedPorts[p.ContainerPort] = struct{}{}

Copilot uses AI. Check for mistakes.
Address Copilot review feedback:
- Skip user-defined ports that collide with the primary port by name
  or port number to prevent Kubernetes Service rejection
- Add unit tests for buildDriverServicePorts covering: no user ports,
  appended user ports, duplicate port number, and duplicate port name

Signed-off-by: yassinnouh21 <yassinnouh21@gmail.com>
@google-oss-prow google-oss-prow bot added size/L and removed size/M labels Mar 29, 2026
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.

Ports defined in executor/driver config are not exposed on generated Services

2 participants