feat(source/node): fqdn support combineFQDNAnnotation (#5526)

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>
This commit is contained in:
Ivan Ka 2025-06-15 23:36:59 +01:00 committed by GitHub
parent 8ce0b3d609
commit 0aababa741
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 93 additions and 29 deletions

View File

@ -54,7 +54,7 @@ The template uses the following data from the source object (e.g., a `Service` o
| `istio-gateway` | Queries Istio Gateway resources for endpoints. | ✅ | ✅ |
| `istio-virtualservice` | Queries Istio VirtualService resources for endpoints. | ✅ | ✅ |
| `kong-tcpingress` | Queries Kong TCPIngress resources for endpoints. | ❌ | ❌ |
| `node` | Queries Kubernetes Node resources for endpoints. | ✅ | |
| `node` | Queries Kubernetes Node resources for endpoints. | ✅ | |
| `openshift-route` | Queries OpenShift Route resources for endpoints. | ✅ | ✅ |
| `pod` | Queries Kubernetes Pod resources for endpoints. | ✅ | ✅ |
| `service` | Queries Kubernetes Service resources for endpoints. | ✅ | ✅ |

View File

@ -38,9 +38,11 @@ import (
const warningMsg = "The default behavior of exposing internal IPv6 addresses will change in the next minor version. Use --no-expose-internal-ipv6 flag to opt-in to the new behavior."
type nodeSource struct {
client kubernetes.Interface
annotationFilter string
fqdnTemplate *template.Template
client kubernetes.Interface
annotationFilter string
fqdnTemplate *template.Template
combineFQDNAnnotation bool
nodeInformer coreinformers.NodeInformer
labelSelector labels.Selector
excludeUnschedulable bool
@ -48,7 +50,14 @@ type nodeSource struct {
}
// NewNodeSource creates a new nodeSource with the given config.
func NewNodeSource(ctx context.Context, kubeClient kubernetes.Interface, annotationFilter, fqdnTemplate string, labelSelector labels.Selector, exposeInternalIPv6, excludeUnschedulable bool) (Source, error) {
func NewNodeSource(
ctx context.Context,
kubeClient kubernetes.Interface,
annotationFilter, fqdnTemplate string,
labelSelector labels.Selector,
exposeInternalIPv6,
excludeUnschedulable bool,
combineFQDNAnnotation bool) (Source, error) {
tmpl, err := fqdn.ParseTemplate(fqdnTemplate)
if err != nil {
return nil, err
@ -76,18 +85,19 @@ func NewNodeSource(ctx context.Context, kubeClient kubernetes.Interface, annotat
}
return &nodeSource{
client: kubeClient,
annotationFilter: annotationFilter,
fqdnTemplate: tmpl,
nodeInformer: nodeInformer,
labelSelector: labelSelector,
excludeUnschedulable: excludeUnschedulable,
exposeInternalIPv6: exposeInternalIPv6,
client: kubeClient,
annotationFilter: annotationFilter,
fqdnTemplate: tmpl,
combineFQDNAnnotation: combineFQDNAnnotation,
nodeInformer: nodeInformer,
labelSelector: labelSelector,
excludeUnschedulable: excludeUnschedulable,
exposeInternalIPv6: exposeInternalIPv6,
}, nil
}
// Endpoints returns endpoint objects for each service that should be processed.
func (ns *nodeSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error) {
func (ns *nodeSource) Endpoints(_ context.Context) ([]*endpoint.Endpoint, error) {
nodes, err := ns.nodeInformer.Lister().List(ns.labelSelector)
if err != nil {
return nil, err
@ -127,21 +137,9 @@ func (ns *nodeSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, erro
}
}
dnsNames := make(map[string]bool)
if ns.fqdnTemplate != nil {
hostnames, err := fqdn.ExecTemplate(ns.fqdnTemplate, node)
if err != nil {
return nil, err
}
for _, name := range hostnames {
dnsNames[name] = true
log.Debugf("applied template for %s, converting to %s", node.Name, name)
}
} else {
dnsNames[node.Name] = true
log.Debugf("not applying template for %s", node.Name)
dnsNames, err := ns.collectDNSNames(node)
if err != nil {
return nil, err
}
for dns := range dnsNames {
@ -233,3 +231,36 @@ func (ns *nodeSource) filterByAnnotations(nodes []*v1.Node) ([]*v1.Node, error)
return filteredList, nil
}
// collectDNSNames returns a set of DNS names associated with the given Kubernetes Node.
// If an FQDN template is configured, it renders the template using the Node object
// to generate one or more DNS names.
// If combineFQDNAnnotation is enabled, the Node's name is also included alongside
// the templated names. If no FQDN template is provided, the result will include only
// the Node's name.
//
// Returns an error if template rendering fails.
func (ns *nodeSource) collectDNSNames(node *v1.Node) (map[string]bool, error) {
dnsNames := make(map[string]bool)
// If no FQDN template is configured, fallback to the node name
if ns.fqdnTemplate == nil {
dnsNames[node.Name] = true
return dnsNames, nil
}
names, err := fqdn.ExecTemplate(ns.fqdnTemplate, node)
if err != nil {
return nil, err
}
for _, name := range names {
dnsNames[name] = true
log.Debugf("applied template for %s, converting to %s", node.Name, name)
}
if ns.combineFQDNAnnotation {
dnsNames[node.Name] = true
}
return dnsNames, nil
}

View File

@ -64,6 +64,7 @@ func TestNodeSourceNewNodeSourceWithFqdn(t *testing.T) {
labels.Everything(),
true,
true,
false,
)
if tt.expectError {
assert.Error(t, err)
@ -80,6 +81,7 @@ func TestNodeSourceFqdnTemplatingExamples(t *testing.T) {
nodes []*v1.Node
fqdnTemplate string
expected []*endpoint.Endpoint
combineFQDN bool
}{
{
title: "templating expansion with multiple domains",
@ -293,6 +295,32 @@ func TestNodeSourceFqdnTemplatingExamples(t *testing.T) {
{DNSName: "node-name-2.domain.tld", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"243.186.136.178"}},
},
},
{
title: "templating with shared all domain and fqdn combination annotation",
combineFQDN: true,
nodes: []*v1.Node{
{
ObjectMeta: metav1.ObjectMeta{Name: "node-name-1"},
Status: v1.NodeStatus{
Addresses: []v1.NodeAddress{{Type: v1.NodeExternalIP, Address: "243.186.136.160"}},
},
},
{
ObjectMeta: metav1.ObjectMeta{Name: "node-name-2"},
Status: v1.NodeStatus{
Addresses: []v1.NodeAddress{{Type: v1.NodeExternalIP, Address: "243.186.136.178"}},
},
},
},
fqdnTemplate: "{{ .Name }}.domain.tld,all.example.com",
expected: []*endpoint.Endpoint{
{DNSName: "all.example.com", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"243.186.136.160", "243.186.136.178"}},
{DNSName: "node-name-1.domain.tld", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"243.186.136.160"}},
{DNSName: "node-name-2.domain.tld", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"243.186.136.178"}},
{DNSName: "node-name-1", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"243.186.136.160"}},
{DNSName: "node-name-2", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"243.186.136.178"}},
},
},
} {
t.Run(tt.title, func(t *testing.T) {
kubeClient := fake.NewClientset()
@ -310,6 +338,7 @@ func TestNodeSourceFqdnTemplatingExamples(t *testing.T) {
labels.Everything(),
true,
true,
tt.combineFQDN,
)
require.NoError(t, err)

View File

@ -95,6 +95,7 @@ func testNodeSourceNewNodeSource(t *testing.T) {
labels.Everything(),
true,
true,
false,
)
if ti.expectError {
@ -440,6 +441,7 @@ func testNodeSourceEndpoints(t *testing.T) {
labelSelector,
tc.exposeInternalIPv6,
tc.excludeUnschedulable,
false,
)
require.NoError(t, err)
@ -557,6 +559,7 @@ func testNodeEndpointsWithIPv6(t *testing.T) {
labelSelector,
tc.exposeInternalIPv6,
tc.excludeUnschedulable,
false,
)
require.NoError(t, err)
@ -604,6 +607,7 @@ func TestResourceLabelIsSetForEachNodeEndpoint(t *testing.T) {
labels.Everything(),
false,
true,
false,
)
require.NoError(t, err)

View File

@ -267,7 +267,7 @@ func BuildWithConfig(ctx context.Context, source string, p ClientGenerator, cfg
if err != nil {
return nil, err
}
return NewNodeSource(ctx, client, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.LabelFilter, cfg.ExposeInternalIPv6, cfg.ExcludeUnschedulable)
return NewNodeSource(ctx, client, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.LabelFilter, cfg.ExposeInternalIPv6, cfg.ExcludeUnschedulable, cfg.CombineFQDNAndAnnotation)
case "service":
client, err := p.KubeClient()
if err != nil {