-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Add support for TCP_UDP to NLB TargetGroups and Listeners #2275
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,12 +15,35 @@ import ( | |
|
||
func (t *defaultModelBuildTask) buildListeners(ctx context.Context, scheme elbv2model.LoadBalancerScheme) error { | ||
cfg := t.buildListenerConfig(ctx) | ||
|
||
// group by listener port number | ||
portMap := make(map[int32][]corev1.ServicePort) | ||
for _, port := range t.service.Spec.Ports { | ||
_, err := t.buildListener(ctx, port, cfg, scheme) | ||
if err != nil { | ||
return err | ||
key := port.Port | ||
if vals, exists := portMap[key]; exists { | ||
portMap[key] = append(vals, port) | ||
} else { | ||
portMap[key] = []corev1.ServicePort{port} | ||
} | ||
} | ||
|
||
// execute build listener | ||
for _, port := range t.service.Spec.Ports { | ||
key := port.Port | ||
if vals, exists := portMap[key]; exists { | ||
if len(vals) > 1 { | ||
port = mergeServicePortsForListener(vals) | ||
} else { | ||
port = vals[0] | ||
} | ||
_, err := t.buildListener(ctx, port, cfg, scheme) | ||
if err != nil { | ||
return err | ||
} | ||
delete(portMap, key) | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
|
@@ -171,3 +194,24 @@ func (t *defaultModelBuildTask) buildListenerConfig(ctx context.Context) listene | |
func (t *defaultModelBuildTask) buildListenerTags(ctx context.Context) (map[string]string, error) { | ||
return t.buildAdditionalResourceTags(ctx) | ||
} | ||
|
||
func mergeServicePortsForListener(ports []corev1.ServicePort) corev1.ServicePort { | ||
port0 := ports[0] | ||
mergeableProtocols := map[corev1.Protocol]bool{ | ||
corev1.ProtocolTCP: true, | ||
corev1.ProtocolUDP: true, | ||
} | ||
if _, ok := mergeableProtocols[port0.Protocol]; !ok { | ||
return port0 | ||
} | ||
for _, port := range ports[1:] { | ||
if _, ok := mergeableProtocols[port.Protocol]; !ok { | ||
continue | ||
} | ||
if port.NodePort == port0.NodePort && port.Protocol != port0.Protocol { | ||
port0.Protocol = corev1.Protocol("TCP_UDP") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can't add a comment there, but will this require flipping the test In fact, it might be better to throw an error if that case happens, as the merge is between a TCP service that should be converted to a TLS service, and a UDP service that cannot be so-converted, and I don't see anything in the docs that suggests a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TLS and UDP are on different levels. So that seems like a non-problem. |
||
break | ||
} | ||
} | ||
return port0 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that if this is an nlb-ip LB,
NodePort
is the wrong test, as the code in model_build_target_group.go will be using the TargetPort as its destination, e.g. https://github.com/kubernetes-sigs/aws-load-balancer-controller/blob/main/pkg/ingress/model_build_target_group.go#L59-L62The code that processes the annotations and determines the target type is happening much later (the call to
buildTargetGroup
viabuildListener
immediately after this method is called, so addressing this might require pulling the port-merging to later, or the annotation-reading earlier.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IP-target mode is going to introduce a difficult edge-case:
hence, it's possible to produce the situation that some of the pods have their TCP and UDP listeners on the same port, and some of the pods have their TCP and UDP listeners on different ports.
At this level, a target port equality test would never pass (Edit: I had the logic backwards earlier), as it'd be seeing the names, numeric lookup for named ports is done later (
(m *defaultNetworkingManager) computeNumericalPorts
for bindings, and implicitly by theEndpoints
and consumed by(m *defaultResourceManager) registerPodEndpoints
I think).So I don't see a way to identify that any pair of named target ports in ip-target mode can be merged without some further hint from the user that, e.g.
dns-udp
anddns-tcp
targetPorts will be the same port when resolved on all Pods in the Service; the same service port might be a good hint, but maybe not reliable?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be possible for the logic in
(m *defaultResourceManager) registerPodEndpoints
and thedefaultEndpointResolver
APIs it calls, to validate that when given a numeric port, that for TCP_UDP two ports exist on the service, and that for non-TCP_UDP, the right one is seen (which means adding the protocol to the ServiceRef in TargetGroupBinding as right now it's not specified if the numeric port is TCP or UDP).When given a named port for TCP_UDP (if a way can be found to reliably match/merge them), it really should be a pair of names (as the TCP and UDP ports will have different names) and validate that both exist on the service.
That suggests that ServiceRef in TargetGroupBinding would need to be able to list multiple ports.
And then for TCP_UDP, exclude any resulting endpoints where the resulting port for TCP and UDP are different. Right now that won't be checked as for a numeric port, it'll take the first port name that has the right port number, irrespective of TCP or UDP) and for a named port, only one name is currently preserved through the pipeline.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The simplest way forward of-course is to initially limit TCP_UDP support to instance-target NLBs pending further design work. That still suffers from my first comment here, that we don't know at this point that it's an instance-target NLB.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to throw another small spanner in the works,
spec.allocateLoadBalancerNodePorts
may allow the user to make this value (port.NodePort
andport0.NodePort
) be 0.That only makes sense for ip-target NLBs though, as instance-target NLBs rely on a NodePort. I'm not sure if the AWS Load Balancer Controller is going to be able to override that setting for instance-target NLBs, but a NodePort equality test should probably never consider two
NodePort==0
ports as equal, to avoid making an invalid situation worse.