-
Notifications
You must be signed in to change notification settings - Fork 433
[NodePortLocal] Add IPv6 and Dual-Stack support #7594
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
base: main
Are you sure you want to change the base?
Conversation
|
/test-ipv6-e2e |
a0273a3 to
e7298ac
Compare
|
/test-kind-ipv6-e2e |
e7298ac to
9569d84
Compare
|
/test-kind-ipv6-e2e |
1 similar comment
|
/test-kind-ipv6-e2e |
503b873 to
4577fb6
Compare
|
/test-kind-ipv6-e2e |
1 similar comment
|
/test-kind-ipv6-e2e |
Add IPv6 and dual-stack support to NodePortLocal on Linux Nodes using
iptables/ip6tables. NPL previously only supported IPv4.
Key changes:
1. **Separate NPL mappings per IP family**: For dual-stack Services, NPL
now creates independent mappings for IPv4 and IPv6, each with its own
Node IP and Node port. The required IP families are determined from
the Service's `.spec.ipFamilies` field.
2. **IPv4 and IPv6 port space**: Node ports for IPv4 and IPv6 are
allocated independently from the same port range, as iptables and
ip6tables rules operate independently and do not interfere.
3. **Node IP address selection**: The NPL controller now watches the
Node object to obtain Node IP addresses for both IP families,
prioritizing external IPs over internal IPs. When Node IPs change,
all local Pods are automatically reconciled with updated NPL
annotations. Note that prior to this change, internal Node IPs had
precedence, but external IPs make more sense for a feature such as
this one.
4. **IP family tracking**: Introduced an efficient `ipFamilies` bitmask
type to track sets of IP families with no memory allocations.
5. **Annotation format**: NPL annotations now include an `ipFamily`
field to distinguish between IPv4 and IPv6 mappings. For example:
```json
[
{"podPort":8080,"nodeIP":"10.10.10.10","nodePort":61002,
"protocol":"tcp","ipFamily":"IPv4"},
{"podPort":8080,"nodeIP":"fd12:3456:789a:1::1",
"nodePort":61003,"protocol":"tcp","ipFamily":"IPv6"}
]
```
6. **PortTable and iptables rule management**: The NPL controller gets
one PortTable per IP family and all rules are managed
independently. The `AddAllRules` function now performs separate
restore operations for each IP family.
7. **Port allocation**: Modified `LocalPortOpener.OpenLocalPort` to
accept an `isIPv6` parameter and bind to the appropriate network type
(tcp4/tcp6, udp4/udp6) based on the Pod IP family.
Implementation notes:
- Assumes Kubernetes version >= 1.23, which guarantees the availability
of `.spec.ipFamilies` on Services and `.status.podIPs` on Pods.
- Windows support remains IPv4-only and is not affected by this change.
- The implementation maintains backward compatibility by treating
existing annotations without the `ipFamily` field as IPv4.
- All existing unit tests have been updated, and new tests have been
added to verify Node IP updates and dual-stack functionality.
- E2E tests have been updated to support IPv6 and dual-stack
testing. All NPL Services are created with the PreferDualStack policy.
Fixes antrea-io#7513
Signed-off-by: Antonin Bas <[email protected]>
Signed-off-by: Antonin Bas <[email protected]>
59dcfc5 to
9bc251e
Compare
|
/test-kind-ipv6-e2e |
9bc251e to
9e5bc6c
Compare
|
/test-kind-ipv6-e2e |
Signed-off-by: Antonin Bas <[email protected]>
9e5bc6c to
49df002
Compare
|
/test-kind-ipv6-e2e |
|
/test-kind-ipv6-e2e |
| // This will be handled gracefully by the NPL controller: if there is an | ||
| // annotation using this port, it will be removed and replaced with a new | ||
| // one with a valid port mapping. | ||
| klog.ErrorS(err, "Cannot bind to local port, skipping it", "port", nplPort.NodePort) |
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.
| klog.ErrorS(err, "Cannot bind to local port, skipping it", "port", nplPort.NodePort) | |
| klog.ErrorS(err, "Cannot bind to local port, skipping it", "port", nplPort.NodePort, "ipv6", pt.IsIPv6) |
| ) | ||
|
|
||
| // Bubble time will automatically advance when goroutines are blocked. | ||
| portTable.RestoreRules(t.Context(), allNPLPorts) |
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 suppose we can have some ipv6 NPL ports in allNPLPorts to verify the ipv6?
| expectedAnnotation := a.find(nplAnnotation.PodPort, nplAnnotation.Protocol) | ||
| if !assert.NotNilf(t, expectedAnnotation, "Unexpected annotation with PodPort %d", nplAnnotation.PodPort) { | ||
| expectedAnnotation := a.find(nplAnnotation.PodPort, nplAnnotation.Protocol, nplAnnotation.IPFamily) | ||
| if !assert.NotNilf(t, expectedAnnotation, "Unexpected annotation with PodPort %d, Protocol %s, IPFamily %s", nplAnnotation.PodPort, nplAnnotation.Protocol, nplAnnotation.IPFamily) { |
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.
| if !assert.NotNilf(t, expectedAnnotation, "Unexpected annotation with PodPort %d, Protocol %s, IPFamily %s", nplAnnotation.PodPort, nplAnnotation.Protocol, nplAnnotation.IPFamily) { | |
| if !assert.NotNilf(t, expectedAnnotation, "Unexpected annotation with Pod port %d, Protocol %s, IPFamily %s", nplAnnotation.PodPort, nplAnnotation.Protocol, nplAnnotation.IPFamily) { |
| if portData != nil && portData.Defunct() { | ||
| klog.InfoS("Deleting defunct NodePortLocal rule for Pod to prevent re-use", "pod", klog.KObj(pod), "podIP", podIP, "port", port, "protocol", protocol) | ||
| if err := portTable.DeleteRule(key, port, protocol); err != nil { | ||
| return fmt.Errorf("failed to delete defunct rule for Pod %s, Pod Port %d, Protocol %s: %w", key, port, protocol, err) |
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.
| return fmt.Errorf("failed to delete defunct rule for Pod %s, Pod Port %d, Protocol %s: %w", key, port, protocol, err) | |
| return fmt.Errorf("failed to delete defunct rule for Pod %s, Pod port %d, Protocol %s: %w", key, port, protocol, err) |
| if portData != nil && portData.PodIP != podIP { | ||
| klog.InfoS("Deleting NodePortLocal rule for Pod because of IP change", "pod", klog.KObj(pod), "podIP", podIP, "prevPodIP", portData.PodIP) | ||
| if err := portTable.DeleteRule(key, port, protocol); err != nil { | ||
| return fmt.Errorf("failed to delete rule for Pod %s, Pod Port %d, Protocol %s: %w", key, port, protocol, err) |
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.
ditto
| const ( | ||
| IPFamilyIPv4 IPFamilyType = "IPv4" | ||
| IPFamilyIPv6 IPFamilyType = "IPv6" | ||
| IPFamilyUnknown IPFamilyType = "" |
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 am not seeing it's used anywhere.
| } | ||
|
|
||
| return nplk8s.NewNPLController(kubeClient, podInformer, serviceInformer.Informer(), portTable, nodeName), nil | ||
| if ipv6Enabled { |
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.
ipv6 is not supported on Windows, I suppose we should skip this on Windows platform?
| NodeIP string `json:"nodeIP"` | ||
| NodePort int `json:"nodePort"` | ||
| Protocol string `json:"protocol"` | ||
| IPFamily IPFamilyType `json:"ipFamily"` |
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.
Should we update annotation examples in the document?
| return fmt.Errorf("failed to add rule for Pod %s: %v", key, err) | ||
|
|
||
| portTable := c.getPortTableForFamily(ipFamily) | ||
| portData := portTable.GetEntry(key, port, protocol) |
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.
Is it safe to call portTable.GetEntry without portTable nil check?
| } | ||
|
|
||
| if c.updateNodeIPs(newNode) { | ||
| klog.InfoS("Node IPs changed, reconciling all Pods", "node", klog.KObj(newNode), "IPv4", c.nodeIPv4, "IPv6", c.nodeIPv6) |
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.
Is it expected to print unprotected c.nodeIPv4 and c.nodeIPv6 here?
Add IPv6 and dual-stack support to NodePortLocal on Linux Nodes using iptables/ip6tables. NPL previously only supported IPv4.
Key changes:
Separate NPL mappings per IP family: For dual-stack Services, NPL now creates independent mappings for IPv4 and IPv6, each with its own Node IP and Node port. The required IP families are determined from the Service's
.spec.ipFamiliesfield.IPv4 and IPv6 port space: Node ports for IPv4 and IPv6 are allocated independently from the same port range, as iptables and ip6tables rules operate independently and do not interfere.
Node IP address selection: The NPL controller now watches the Node object to obtain Node IP addresses for both IP families, prioritizing external IPs over internal IPs. When Node IPs change, all local Pods are automatically reconciled with updated NPL annotations. Note that prior to this change, internal Node IPs had precedence, but external IPs make more sense for a feature such as this one.
IP family tracking: Introduced an efficient
ipFamiliesbitmask type to track sets of IP families with no memory allocations.Annotation format: NPL annotations now include an
ipFamilyfield to distinguish between IPv4 and IPv6 mappings. For example:[ {"podPort":8080,"nodeIP":"10.10.10.10","nodePort":61002, "protocol":"tcp","ipFamily":"IPv4"}, {"podPort":8080,"nodeIP":"fd12:3456:789a:1::1", "nodePort":61003,"protocol":"tcp","ipFamily":"IPv6"} ]PortTable and iptables rule management: The NPL controller gets one PortTable per IP family and all rules are managed independently. The
AddAllRulesfunction now performs separate restore operations for each IP family.Port allocation: Modified
LocalPortOpener.OpenLocalPortto accept anisIPv6parameter and bind to the appropriate network type (tcp4/tcp6, udp4/udp6) based on the Pod IP family.Implementation notes:
.spec.ipFamilieson Services and.status.podIPson Pods.ipFamilyfield as IPv4.Fixes #7513