mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-06 17:46:57 +02:00
feat(source/node): fqdn support combineFQDNAnnotation (#5526)
Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>
This commit is contained in:
parent
8ce0b3d609
commit
0aababa741
@ -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-gateway` | Queries Istio Gateway resources for endpoints. | ✅ | ✅ |
|
||||||
| `istio-virtualservice` | Queries Istio VirtualService resources for endpoints. | ✅ | ✅ |
|
| `istio-virtualservice` | Queries Istio VirtualService resources for endpoints. | ✅ | ✅ |
|
||||||
| `kong-tcpingress` | Queries Kong TCPIngress 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. | ✅ | ✅ |
|
| `openshift-route` | Queries OpenShift Route resources for endpoints. | ✅ | ✅ |
|
||||||
| `pod` | Queries Kubernetes Pod resources for endpoints. | ✅ | ✅ |
|
| `pod` | Queries Kubernetes Pod resources for endpoints. | ✅ | ✅ |
|
||||||
| `service` | Queries Kubernetes Service resources for endpoints. | ✅ | ✅ |
|
| `service` | Queries Kubernetes Service resources for endpoints. | ✅ | ✅ |
|
||||||
|
@ -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."
|
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 {
|
type nodeSource struct {
|
||||||
client kubernetes.Interface
|
client kubernetes.Interface
|
||||||
annotationFilter string
|
annotationFilter string
|
||||||
fqdnTemplate *template.Template
|
fqdnTemplate *template.Template
|
||||||
|
combineFQDNAnnotation bool
|
||||||
|
|
||||||
nodeInformer coreinformers.NodeInformer
|
nodeInformer coreinformers.NodeInformer
|
||||||
labelSelector labels.Selector
|
labelSelector labels.Selector
|
||||||
excludeUnschedulable bool
|
excludeUnschedulable bool
|
||||||
@ -48,7 +50,14 @@ type nodeSource struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewNodeSource creates a new nodeSource with the given config.
|
// 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)
|
tmpl, err := fqdn.ParseTemplate(fqdnTemplate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -76,18 +85,19 @@ func NewNodeSource(ctx context.Context, kubeClient kubernetes.Interface, annotat
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &nodeSource{
|
return &nodeSource{
|
||||||
client: kubeClient,
|
client: kubeClient,
|
||||||
annotationFilter: annotationFilter,
|
annotationFilter: annotationFilter,
|
||||||
fqdnTemplate: tmpl,
|
fqdnTemplate: tmpl,
|
||||||
nodeInformer: nodeInformer,
|
combineFQDNAnnotation: combineFQDNAnnotation,
|
||||||
labelSelector: labelSelector,
|
nodeInformer: nodeInformer,
|
||||||
excludeUnschedulable: excludeUnschedulable,
|
labelSelector: labelSelector,
|
||||||
exposeInternalIPv6: exposeInternalIPv6,
|
excludeUnschedulable: excludeUnschedulable,
|
||||||
|
exposeInternalIPv6: exposeInternalIPv6,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Endpoints returns endpoint objects for each service that should be processed.
|
// 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)
|
nodes, err := ns.nodeInformer.Lister().List(ns.labelSelector)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -127,21 +137,9 @@ func (ns *nodeSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, erro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dnsNames := make(map[string]bool)
|
dnsNames, err := ns.collectDNSNames(node)
|
||||||
|
if err != nil {
|
||||||
if ns.fqdnTemplate != nil {
|
return nil, err
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for dns := range dnsNames {
|
for dns := range dnsNames {
|
||||||
@ -233,3 +231,36 @@ func (ns *nodeSource) filterByAnnotations(nodes []*v1.Node) ([]*v1.Node, error)
|
|||||||
|
|
||||||
return filteredList, nil
|
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
|
||||||
|
}
|
||||||
|
@ -64,6 +64,7 @@ func TestNodeSourceNewNodeSourceWithFqdn(t *testing.T) {
|
|||||||
labels.Everything(),
|
labels.Everything(),
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
if tt.expectError {
|
if tt.expectError {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
@ -80,6 +81,7 @@ func TestNodeSourceFqdnTemplatingExamples(t *testing.T) {
|
|||||||
nodes []*v1.Node
|
nodes []*v1.Node
|
||||||
fqdnTemplate string
|
fqdnTemplate string
|
||||||
expected []*endpoint.Endpoint
|
expected []*endpoint.Endpoint
|
||||||
|
combineFQDN bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
title: "templating expansion with multiple domains",
|
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"}},
|
{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) {
|
t.Run(tt.title, func(t *testing.T) {
|
||||||
kubeClient := fake.NewClientset()
|
kubeClient := fake.NewClientset()
|
||||||
@ -310,6 +338,7 @@ func TestNodeSourceFqdnTemplatingExamples(t *testing.T) {
|
|||||||
labels.Everything(),
|
labels.Everything(),
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
|
tt.combineFQDN,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -95,6 +95,7 @@ func testNodeSourceNewNodeSource(t *testing.T) {
|
|||||||
labels.Everything(),
|
labels.Everything(),
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
|
|
||||||
if ti.expectError {
|
if ti.expectError {
|
||||||
@ -440,6 +441,7 @@ func testNodeSourceEndpoints(t *testing.T) {
|
|||||||
labelSelector,
|
labelSelector,
|
||||||
tc.exposeInternalIPv6,
|
tc.exposeInternalIPv6,
|
||||||
tc.excludeUnschedulable,
|
tc.excludeUnschedulable,
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -557,6 +559,7 @@ func testNodeEndpointsWithIPv6(t *testing.T) {
|
|||||||
labelSelector,
|
labelSelector,
|
||||||
tc.exposeInternalIPv6,
|
tc.exposeInternalIPv6,
|
||||||
tc.excludeUnschedulable,
|
tc.excludeUnschedulable,
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -604,6 +607,7 @@ func TestResourceLabelIsSetForEachNodeEndpoint(t *testing.T) {
|
|||||||
labels.Everything(),
|
labels.Everything(),
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -267,7 +267,7 @@ func BuildWithConfig(ctx context.Context, source string, p ClientGenerator, cfg
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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":
|
case "service":
|
||||||
client, err := p.KubeClient()
|
client, err := p.KubeClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Loading…
Reference in New Issue
Block a user