feat: set service ports when containerPort is set#547
feat: set service ports when containerPort is set#547flpanbin wants to merge 1 commit intokubernetes-sigs:mainfrom
Conversation
✅ Deploy Preview for agent-sandbox canceled.
|
eb72d0e to
aa12a0d
Compare
|
/assign @barney-s |
|
/ok-to-test |
Signed-off-by: bin <bin.pan@daocloud.io>
aa12a0d to
1098b30
Compare
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: flpanbin The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
There was a problem hiding this comment.
Pull request overview
This PR adds an opt-in mechanism for the Sandbox controller to populate headless Service.spec.ports based on the containerPort declarations in the Sandbox pod template, addressing Istio’s requirement for Service port declarations.
Changes:
- Adds
agents.x-k8s.io/auto-expose-ports: "true"annotation to enable auto-populating headless Service ports. - Implements port extraction/deduplication (per
<port>/<protocol>) and collision-safe port naming. - Adds unit tests validating Service port generation behavior.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| controllers/sandbox_controller.go | Adds annotation gate + port-building helpers and wires them into Service creation. |
| controllers/sandbox_controller_test.go | Adds tests for Service port population and name collision handling. |
| if shouldAutoExposePorts(sandbox) { | ||
| ports := buildServicePorts(&sandbox.Spec.PodTemplate.Spec) | ||
| if len(ports) > 0 { | ||
| service.Spec.Ports = ports | ||
| } | ||
| } |
There was a problem hiding this comment.
Service port auto-population is only applied on the create path. If the Service already exists (owned by the Sandbox), enabling/removing the agents.x-k8s.io/auto-expose-ports annotation later (or changing container ports) will never update service.Spec.Ports, so the Service can remain stale forever. Consider reconciling Spec.Ports in the resourceOwnedBySandbox path as well (and likely clearing ports when annotation is unset).
| port := corev1.ServicePort{ | ||
| Port: containerPort.ContainerPort, | ||
| Protocol: protocol, | ||
| TargetPort: intstr.FromInt32(containerPort.ContainerPort), | ||
| } | ||
|
|
||
| if containerPort.Name != "" { | ||
| name := containerPort.Name | ||
| if _, exists := seenNames[name]; exists { | ||
| name = uniqueServicePortName(name, containerPort.ContainerPort, protocol) | ||
| } | ||
| port.Name = name | ||
| seenNames[name] = struct{}{} | ||
| } |
There was a problem hiding this comment.
buildServicePorts only sets ServicePort.Name when containerPort.Name is non-empty. If a Pod template declares multiple container ports without names (common), this will produce a Service with multiple unnamed ports, which the API server rejects (port names are required when there is more than one Service port). Suggest generating a deterministic name when containerPort.Name is empty (e.g., based on port/protocol) and still enforcing uniqueness.
| const maxServicePortNameLength = 63 | ||
|
|
||
| func shouldAutoExposePorts(sandbox *sandboxv1alpha1.Sandbox) bool { | ||
| if sandbox == nil { | ||
| return false | ||
| } | ||
| value, ok := sandbox.Annotations[SandboxExposePortsAnnotation] | ||
| if !ok { | ||
| return false | ||
| } | ||
| return strings.EqualFold(value, "true") | ||
| } | ||
|
|
||
| func uniqueServicePortName(base string, port int32, protocol corev1.Protocol) string { | ||
| protocolLower := strings.ToLower(string(protocol)) | ||
| suffix := fmt.Sprintf("-%d-%s", port, protocolLower) | ||
| maxBase := maxServicePortNameLength - len(suffix) | ||
| if len(base) > maxBase { | ||
| base = base[:maxBase] | ||
| } | ||
| return base + suffix | ||
| } |
There was a problem hiding this comment.
maxServicePortNameLength is set to 63, but ServicePort.Name is validated as an IANA service name (max 15 chars). On name collisions, uniqueServicePortName can easily exceed that limit (e.g., a 15-char container port name + "-8080-tcp"), causing Service create/update to be rejected. Consider capping to the Service port name limit (15) and truncating the base accordingly (or using a short hash) while keeping the name unique.
| sandboxLabel = "agents.x-k8s.io/sandbox-name-hash" | ||
| SandboxPodNameAnnotation = "agents.x-k8s.io/pod-name" |
There was a problem hiding this comment.
SandboxPodNameAnnotation is introduced here but the controller code continues to use sandboxv1alpha1.SandboxPodNameAnnotation (see resolvePodName). Having two constants for the same annotation key in different packages is confusing and makes it easy for them to drift. Consider removing this constant or switching to a single source of truth (preferably the API package constant).
| sandboxLabel = "agents.x-k8s.io/sandbox-name-hash" | |
| SandboxPodNameAnnotation = "agents.x-k8s.io/pod-name" | |
| sandboxLabel = "agents.x-k8s.io/sandbox-name-hash" |
| name: "returns existing service without changes", | ||
| initialObjs: []runtime.Object{ |
There was a problem hiding this comment.
Test case name is misleading: this scenario appears to adopt an unowned Service by adding an OwnerReference (and bumps ResourceVersion), so it isn't "without changes". Renaming it to reflect adoption would make failures easier to interpret.
| if shouldAutoExposePorts(sandbox) { | ||
| ports := buildServicePorts(&sandbox.Spec.PodTemplate.Spec) | ||
| if len(ports) > 0 { | ||
| service.Spec.Ports = ports | ||
| } | ||
| } |
There was a problem hiding this comment.
Current tests cover Service creation with ports, but they don't cover key reconcile edge cases introduced by this feature: (1) toggling the annotation after a Service already exists (owned by the Sandbox) and (2) multiple unnamed container ports, which should still result in a valid Service (requires generated names). Adding unit tests for these cases would help prevent regressions.
related to #154
Populate headless Services with port definitions when the sandbox pod template declares container ports.
Deduplicate ports per protocol, default to TCP when unspecified, and reuse container port names in the Service spec.
services like:
Some details:
e.g. <name>-<port>-<protocol>) to avoid collisions.agents.x-k8s.io/auto-expose-ports: "true"). When unset/false, the controller creates the headless Service without ports; when true, it auto-populates ports from the pod template.