mirror of
https://github.com/outbackdingo/talos-cloud-controller-manager.git
synced 2026-01-27 18:20:23 +00:00
feat: support CloudDualStackNodeIPs
Talos CCM now supports the `CloudDualStackNodeIPs` feature gate. This feature allows the user(cloud) to specify a list of IPv4 and IPv6 addresses for each node in the cluster. https://github.com/kubernetes/kubernetes/pull/120275 Signed-off-by: Serge Logvinov <serge.logvinov@sinextra.dev>
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
utilsnet "github.com/siderolabs/talos-cloud-controller-manager/pkg/utils/net"
|
utilsnet "github.com/siderolabs/talos-cloud-controller-manager/pkg/utils/net"
|
||||||
"github.com/siderolabs/talos/pkg/machinery/resources/network"
|
"github.com/siderolabs/talos/pkg/machinery/resources/network"
|
||||||
@@ -17,11 +18,11 @@ import (
|
|||||||
"k8s.io/utils/strings/slices"
|
"k8s.io/utils/strings/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getNodeAddresses(config *cloudConfig, platform, nodeIP string, ifaces []network.AddressStatusSpec) []v1.NodeAddress {
|
func getNodeAddresses(config *cloudConfig, platform string, nodeIPs []string, ifaces []network.AddressStatusSpec) []v1.NodeAddress {
|
||||||
var publicIPv4s, publicIPv6s, publicIPs []string
|
var publicIPv4s, publicIPv6s, publicIPs []string
|
||||||
|
|
||||||
switch platform {
|
switch platform {
|
||||||
case "nocloud", "metal":
|
case "nocloud", "metal", "openstack":
|
||||||
for _, iface := range ifaces {
|
for _, iface := range ifaces {
|
||||||
if iface.LinkName == "kubespan" {
|
if iface.LinkName == "kubespan" {
|
||||||
continue
|
continue
|
||||||
@@ -29,6 +30,10 @@ func getNodeAddresses(config *cloudConfig, platform, nodeIP string, ifaces []net
|
|||||||
|
|
||||||
ip := iface.Address.Addr()
|
ip := iface.Address.Addr()
|
||||||
if ip.IsGlobalUnicast() && !ip.IsPrivate() {
|
if ip.IsGlobalUnicast() && !ip.IsPrivate() {
|
||||||
|
if slices.Contains(nodeIPs, ip.String()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if ip.Is6() {
|
if ip.Is6() {
|
||||||
publicIPv6s = append(publicIPv6s, ip.String())
|
publicIPv6s = append(publicIPv6s, ip.String())
|
||||||
} else {
|
} else {
|
||||||
@@ -41,6 +46,10 @@ func getNodeAddresses(config *cloudConfig, platform, nodeIP string, ifaces []net
|
|||||||
if iface.LinkName == "external" {
|
if iface.LinkName == "external" {
|
||||||
ip := iface.Address.Addr()
|
ip := iface.Address.Addr()
|
||||||
|
|
||||||
|
if slices.Contains(nodeIPs, ip.String()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if ip.Is6() {
|
if ip.Is6() {
|
||||||
publicIPv6s = append(publicIPv6s, ip.String())
|
publicIPv6s = append(publicIPv6s, ip.String())
|
||||||
} else {
|
} else {
|
||||||
@@ -50,14 +59,12 @@ func getNodeAddresses(config *cloudConfig, platform, nodeIP string, ifaces []net
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addresses := []v1.NodeAddress{{Type: v1.NodeInternalIP, Address: nodeIP}}
|
addresses := []v1.NodeAddress{}
|
||||||
|
for _, ip := range utilsnet.PreferedDualStackNodeIPs(config.Global.PreferIPv6, nodeIPs) {
|
||||||
if config.Global.PreferIPv6 {
|
addresses = append(addresses, v1.NodeAddress{Type: v1.NodeInternalIP, Address: ip})
|
||||||
publicIPs = utilsnet.SortedNodeIPs(nodeIP, publicIPv6s, publicIPv4s)
|
|
||||||
} else {
|
|
||||||
publicIPs = utilsnet.SortedNodeIPs(nodeIP, publicIPv4s, publicIPv6s)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
publicIPs = utilsnet.PreferedDualStackNodeIPs(config.Global.PreferIPv6, append(publicIPv4s, publicIPv6s...))
|
||||||
for _, ip := range publicIPs {
|
for _, ip := range publicIPs {
|
||||||
addresses = append(addresses, v1.NodeAddress{Type: v1.NodeExternalIP, Address: ip})
|
addresses = append(addresses, v1.NodeAddress{Type: v1.NodeExternalIP, Address: ip})
|
||||||
}
|
}
|
||||||
@@ -105,7 +112,7 @@ func csrNodeChecks(ctx context.Context, kclient clientkubernetes.Interface, x509
|
|||||||
|
|
||||||
if node != nil {
|
if node != nil {
|
||||||
if providedIP, ok := node.ObjectMeta.Annotations[cloudproviderapi.AnnotationAlphaProvidedIPAddr]; ok {
|
if providedIP, ok := node.ObjectMeta.Annotations[cloudproviderapi.AnnotationAlphaProvidedIPAddr]; ok {
|
||||||
nodeAddrs = append(nodeAddrs, providedIP)
|
nodeAddrs = append(nodeAddrs, strings.Split(providedIP, ",")...)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ip := range node.Status.Addresses {
|
for _, ip := range node.Status.Addresses {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -45,6 +46,21 @@ func TestGetNodeAddresses(t *testing.T) {
|
|||||||
{Type: v1.NodeInternalIP, Address: "192.168.0.1"},
|
{Type: v1.NodeInternalIP, Address: "192.168.0.1"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "nocloud has dualstack",
|
||||||
|
cfg: cfg,
|
||||||
|
platform: "nocloud",
|
||||||
|
providedIP: "192.168.0.1,fd00:192:168:0::1",
|
||||||
|
ifaces: []network.AddressStatusSpec{
|
||||||
|
{Address: netip.MustParsePrefix("192.168.0.1/24")},
|
||||||
|
{Address: netip.MustParsePrefix("fe80::e0b5:71ff:fe24:7e60/64")},
|
||||||
|
{Address: netip.MustParsePrefix("fd00:192:168:0::1/64")},
|
||||||
|
},
|
||||||
|
expected: []v1.NodeAddress{
|
||||||
|
{Type: v1.NodeInternalIP, Address: "192.168.0.1"},
|
||||||
|
{Type: v1.NodeInternalIP, Address: "fd00:192:168::1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "nocloud has many PublicIPs",
|
name: "nocloud has many PublicIPs",
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
@@ -62,14 +78,14 @@ func TestGetNodeAddresses(t *testing.T) {
|
|||||||
expected: []v1.NodeAddress{
|
expected: []v1.NodeAddress{
|
||||||
{Type: v1.NodeInternalIP, Address: "192.168.0.1"},
|
{Type: v1.NodeInternalIP, Address: "192.168.0.1"},
|
||||||
{Type: v1.NodeExternalIP, Address: "1.2.3.4"},
|
{Type: v1.NodeExternalIP, Address: "1.2.3.4"},
|
||||||
{Type: v1.NodeExternalIP, Address: "2001:1234:4321::32"},
|
{Type: v1.NodeExternalIP, Address: "2001:1234::1"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "nocloud has many PublicIPs (IPv6 preferred)",
|
name: "nocloud has many PublicIPs (IPv6 preferred)",
|
||||||
cfg: cloudConfig{Global: cloudConfigGlobal{PreferIPv6: true}},
|
cfg: cloudConfig{Global: cloudConfigGlobal{PreferIPv6: true}},
|
||||||
platform: "nocloud",
|
platform: "nocloud",
|
||||||
providedIP: "192.168.0.1",
|
providedIP: "192.168.0.1,fd15:1:2::192:168:0:1",
|
||||||
ifaces: []network.AddressStatusSpec{
|
ifaces: []network.AddressStatusSpec{
|
||||||
{Address: netip.MustParsePrefix("192.168.0.1/24")},
|
{Address: netip.MustParsePrefix("192.168.0.1/24")},
|
||||||
{Address: netip.MustParsePrefix("fe80::e0b5:71ff:fe24:7e60/64")},
|
{Address: netip.MustParsePrefix("fe80::e0b5:71ff:fe24:7e60/64")},
|
||||||
@@ -80,8 +96,9 @@ func TestGetNodeAddresses(t *testing.T) {
|
|||||||
{Address: netip.MustParsePrefix("2001:1234:4321::32/64")},
|
{Address: netip.MustParsePrefix("2001:1234:4321::32/64")},
|
||||||
},
|
},
|
||||||
expected: []v1.NodeAddress{
|
expected: []v1.NodeAddress{
|
||||||
|
{Type: v1.NodeInternalIP, Address: "fd15:1:2:0:192:168:0:1"},
|
||||||
{Type: v1.NodeInternalIP, Address: "192.168.0.1"},
|
{Type: v1.NodeInternalIP, Address: "192.168.0.1"},
|
||||||
{Type: v1.NodeExternalIP, Address: "2001:1234:4321::32"},
|
{Type: v1.NodeExternalIP, Address: "2001:1234::1"},
|
||||||
{Type: v1.NodeExternalIP, Address: "1.2.3.4"},
|
{Type: v1.NodeExternalIP, Address: "1.2.3.4"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -124,7 +141,7 @@ func TestGetNodeAddresses(t *testing.T) {
|
|||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
addresses := getNodeAddresses(&tt.cfg, tt.platform, tt.providedIP, tt.ifaces)
|
addresses := getNodeAddresses(&tt.cfg, tt.platform, strings.Split(tt.providedIP, ","), tt.ifaces)
|
||||||
|
|
||||||
assert.Equal(t, tt.expected, addresses)
|
assert.Equal(t, tt.expected, addresses)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/siderolabs/talos-cloud-controller-manager/pkg/utils/net"
|
||||||
"github.com/siderolabs/talos-cloud-controller-manager/pkg/utils/platform"
|
"github.com/siderolabs/talos-cloud-controller-manager/pkg/utils/platform"
|
||||||
|
"github.com/siderolabs/talos/pkg/machinery/resources/runtime"
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
cloudprovider "k8s.io/cloud-provider"
|
cloudprovider "k8s.io/cloud-provider"
|
||||||
@@ -46,16 +48,34 @@ func (i *instances) InstanceMetadata(ctx context.Context, node *v1.Node) (*cloud
|
|||||||
klog.V(4).Info("instances.InstanceMetadata() called, node: ", node.Name)
|
klog.V(4).Info("instances.InstanceMetadata() called, node: ", node.Name)
|
||||||
|
|
||||||
if providedIP, ok := node.ObjectMeta.Annotations[cloudproviderapi.AnnotationAlphaProvidedIPAddr]; ok {
|
if providedIP, ok := node.ObjectMeta.Annotations[cloudproviderapi.AnnotationAlphaProvidedIPAddr]; ok {
|
||||||
meta, err := i.c.getNodeMetadata(ctx, providedIP)
|
nodeIPs := net.PreferedDualStackNodeIPs(i.c.config.Global.PreferIPv6, strings.Split(providedIP, ","))
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error getting metadata from the node %s: %w", node.Name, err)
|
var (
|
||||||
|
meta *runtime.PlatformMetadataSpec
|
||||||
|
err error
|
||||||
|
nodeIP string
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, ip := range nodeIPs {
|
||||||
|
meta, err = i.c.getNodeMetadata(ctx, ip)
|
||||||
|
if err == nil {
|
||||||
|
nodeIP = ip
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
klog.Errorf("error getting metadata from the node %s: %v", node.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if meta == nil {
|
||||||
|
return nil, fmt.Errorf("error getting metadata from the node %s", node.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
klog.V(5).Infof("instances.InstanceMetadata() resource: %+v", meta)
|
klog.V(5).Infof("instances.InstanceMetadata() resource: %+v", meta)
|
||||||
|
|
||||||
providerID := meta.ProviderID
|
providerID := meta.ProviderID
|
||||||
if providerID == "" {
|
if providerID == "" {
|
||||||
providerID = fmt.Sprintf("%s://%s/%s", ProviderName, meta.Platform, providedIP)
|
providerID = fmt.Sprintf("%s://%s/%s", ProviderName, meta.Platform, nodeIP)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fix for Azure, resource group name must be lower case.
|
// Fix for Azure, resource group name must be lower case.
|
||||||
@@ -66,12 +86,12 @@ func (i *instances) InstanceMetadata(ctx context.Context, node *v1.Node) (*cloud
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ifaces, err := i.c.getNodeIfaces(ctx, providedIP)
|
ifaces, err := i.c.getNodeIfaces(ctx, nodeIP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error getting interfaces list from the node %s: %w", node.Name, err)
|
return nil, fmt.Errorf("error getting interfaces list from the node %s: %w", node.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
addresses := getNodeAddresses(i.c.config, meta.Platform, providedIP, ifaces)
|
addresses := getNodeAddresses(i.c.config, meta.Platform, nodeIPs, ifaces)
|
||||||
|
|
||||||
addresses = append(addresses, v1.NodeAddress{Type: v1.NodeHostName, Address: node.Name})
|
addresses = append(addresses, v1.NodeAddress{Type: v1.NodeHostName, Address: node.Name})
|
||||||
|
|
||||||
|
|||||||
@@ -38,3 +38,44 @@ func SortedNodeIPs(nodeIP string, first, second []string) (res []string) {
|
|||||||
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PreferedDualStackNodeIPs returns the first IPv4 and IPv6 addresses from the list of IPs.
|
||||||
|
func PreferedDualStackNodeIPs(preferIPv6 bool, ips []string) []string {
|
||||||
|
var ipv6, ipv4 string
|
||||||
|
|
||||||
|
for _, ip := range ips {
|
||||||
|
if nip, err := netip.ParseAddr(ip); err == nil {
|
||||||
|
if nip.Is6() {
|
||||||
|
if ipv6 == "" {
|
||||||
|
ipv6 = nip.String()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ipv4 == "" {
|
||||||
|
ipv4 = nip.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res := []string{}
|
||||||
|
|
||||||
|
if preferIPv6 {
|
||||||
|
if ipv6 != "" {
|
||||||
|
res = append(res, ipv6)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ipv4 != "" {
|
||||||
|
res = append(res, ipv4)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ipv4 != "" {
|
||||||
|
res = append(res, ipv4)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ipv6 != "" {
|
||||||
|
res = append(res, ipv6)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|||||||
@@ -106,3 +106,49 @@ func TestSortedNodeIPs(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPreferedDualStackNodeIPs(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
for _, tt := range []struct { //nolint:govet
|
||||||
|
name string
|
||||||
|
preferIPv6 bool
|
||||||
|
nodeIPs []string
|
||||||
|
expected []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "one nodeIP",
|
||||||
|
preferIPv6: false,
|
||||||
|
nodeIPs: []string{"192.168.0.1"},
|
||||||
|
expected: []string{"192.168.0.1"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dualstack nodeIP",
|
||||||
|
preferIPv6: false,
|
||||||
|
nodeIPs: []string{"192.168.0.1", "fd00::1"},
|
||||||
|
expected: []string{"192.168.0.1", "fd00::1"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dualstack nodeIP preferIPv6",
|
||||||
|
preferIPv6: true,
|
||||||
|
nodeIPs: []string{"192.168.0.1", "fd00::1"},
|
||||||
|
expected: []string{"fd00::1", "192.168.0.1"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dualstack nodeIP preferIPv6 with external IPs",
|
||||||
|
preferIPv6: true,
|
||||||
|
nodeIPs: []string{"192.168.0.1", "fd00::1", "1.1.1.1", "1111:2222:3333::1", "2000:123:123::9b87:57a7:38bf:6c71"},
|
||||||
|
expected: []string{"fd00::1", "192.168.0.1"},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
tt := tt
|
||||||
|
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
result := utilnet.PreferedDualStackNodeIPs(tt.preferIPv6, tt.nodeIPs)
|
||||||
|
|
||||||
|
assert.Equal(t, fmt.Sprintf("%v", tt.expected), fmt.Sprintf("%v", result))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user