Code change:
<!-- Please use this template while reporting a bug and provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner. Thanks! If the matter is security related, please disclose it privately via --What happened: I created several Workloads which require up to ten ports for both UDP and TCP protocol. Now my logs get spammed with . What you expected to happen: should consider -combination instead of only . How to reproduce it (as minimally and precisely as possible): Deploy any service with a node-port with both protocols, e.g.: Anything else we need to know?: This was already acknowledged by here: However, as this thread addresses a different bug and seems kind of stuck, i'd like to suggest that this might be addressed in a new context. Environment: Kubernetes version (use ): Cloud provider or hardware configuration: OS (e.g: ): Kernel (e.g. ): Install tools: Deployed via Rancher v2.4.3
/sig api-machinery
/remove-sig api-machinery /sig network
/triage unresolved Comment when the issue is assessed and confirmed. I am a bot run by vllry. ‍
How did you get into this situation? The allocator should not have granted the same node port twice?
Oh right, that issue. The only real way out is to de-allocate the NodePorts and then re-allocate them, I think
As this happens for all of my ports in the said Production Application (the one where my log gets spammed) as well as complete fresh installation (where i verified the bug), i think there's no "way out" except fixing the bug. As said, for me it's necessary to assign both protocols to the same port. This might be a niche-use-case, but checking the it's actually very likely for ports to allow tcp AND udp. I'm not sure about the history of this, but it might be that issue was the origin, which was adressed in PR ("Allow services which use same port, different protocol to use the same nodePort for both"). But this PR doesn't contain the adjustment of the said , so still considers the behaviour as invalid, while it's actually allowed and supported.
/assign
Ahh, I thought you meant that you had accidentally been allocated the same nodePorts and I was very confused when you said "all of them". I have reproduced this issue, and I can see it by inspection. The obvious fix would be to collect ports split by protocol, but I think that's not right since we don't store it split. An even simpler fix would be for to build a . Then for each protocol, for each port, build that contains all the port numbers, but remove any duplicates ONLY IF they are duplicated across protocols. Duplicates in the same protocol should be retained (to report an error later). I don't have time to tackle this tonight (and write a test), but it seems like a good bug.
perhaps I can fix this problem /assign
things may be a little more complicated. Right now the is not aware of the protocol of the port so I guess simply rewriting function to return is not enough, the port allocator will still return on same node port even with different protocol. perhaps we need to refactor the to make it protocol-aware.
I don't think we need to do make the allocator be protocol aware. IIRC we allow you to use the same nodeport on different protocols ONLY within a single service. This means (I think) that we can do hyper-local deduplication as we collect the nodeports for each service. If we find the same nodeport number with different protocols, just remove the duplicates because it is allowed. I.e. a service that looks like: return - deduped. if ANY OTHER service tried to use it would be an error at the "rebuilt" allocator Likewise if a single Service claims to use the same nodeport number more than once on the same protocol, it should be an error: return - NOT deduped.
obviously, we want a great unit test here :P
ah got it! thanks for your detailed explanation!
Hi! I have filed a PR to resolve this issue, could you take a look?

Related discussion:
text: t.Errorf("unexpected portallocator state: %d free", free) } } <ins> func TestCollectServiceNodePorts(t *testing.T) { tests := []struct { name        string serviceSpec corev1.ServiceSpec expected    []int }{ { name: "no duplicated nodePorts", serviceSpec: corev1.ServiceSpec{ Ports: []corev1.ServicePort{ {NodePort: 111, Protocol: corev1.ProtocolTCP}, {NodePort: 112, Protocol: corev1.ProtocolUDP}, {NodePort: 113, Protocol: corev1.ProtocolUDP}, }, }, expected: []int{111, 112, 113}, }, { name: "duplicated nodePort with TCP protocol", serviceSpec: corev1.ServiceSpec{ Ports: []corev1.ServicePort{ {NodePort: 111, Protocol: corev1.ProtocolTCP}, {NodePort: 111, Protocol: corev1.ProtocolTCP}, {NodePort: 112, Protocol: corev1.ProtocolUDP}, }, }, expected: []int{111, 111, 112}, }, { name: "duplicated nodePort with UDP protocol", serviceSpec: corev1.ServiceSpec{ Ports: []corev1.ServicePort{ {NodePort: 111, Protocol: corev1.ProtocolUDP}, {NodePort: 111, Protocol: corev1.ProtocolUDP}, {NodePort: 112, Protocol: corev1.ProtocolTCP}, }, }, expected: []int{111, 111, 112}, }, { name: "duplicated nodePort with different protocol", serviceSpec: corev1.ServiceSpec{ Ports: []corev1.ServicePort{ {NodePort: 111, Protocol: corev1.ProtocolTCP}, {NodePort: 112, Protocol: corev1.ProtocolTCP}, {NodePort: 111, Protocol: corev1.ProtocolUDP}, }, }, expected: []int{111, 112}, }, { name: "no duplicated port(with health check port)", serviceSpec: corev1.ServiceSpec{ Ports: []corev1.ServicePort{ {NodePort: 111, Protocol: corev1.ProtocolTCP}, {NodePort: 112, Protocol: corev1.ProtocolUDP}, }, HealthCheckNodePort: 113, }, expected: []int{111, 112, 113}, }, { name: "nodePort has different protocol with duplicated health check port", serviceSpec: corev1.ServiceSpec{ Ports: []corev1.ServicePort{ {NodePort: 111, Protocol: corev1.ProtocolUDP}, {NodePort: 112, Protocol: corev1.ProtocolTCP}, }, HealthCheckNodePort: 111, }, expected: []int{111, 112}, }, { name: "nodePort has same protocol as duplicated health check port", serviceSpec: corev1.ServiceSpec{ Ports: []corev1.ServicePort{ {NodePort: 111, Protocol: corev1.ProtocolUDP}, {NodePort: 112, Protocol: corev1.ProtocolTCP}, }, HealthCheckNodePort: 112, }, expected: []int{111, 112, 112}, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { ports := collectServiceNodePorts(&corev1.Service{ ObjectMeta: metav1.ObjectMeta{Namespace: "one", Name: "one"}, Spec:       tc.serviceSpec, }) sort.Ints(ports) if !reflect.DeepEqual(tc.expected, ports) { t.Fatalf("Invalid resultnexpected: %vngot: %v", tc.expected, ports) } }) } } </ins>