mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-06 17:46:57 +02:00
feat: resolve LB-type Service hostname to create A/AAAA instead of CNAME
This commit is contained in:
parent
eaabf715fe
commit
1d232c4b86
@ -83,16 +83,17 @@ The following table lists the configurable parameters of the _ExternalDNS_ chart
|
|||||||
| `secretConfiguration.mountPath` | Mount path of secret configuration secret (this can be templated). | `""` |
|
| `secretConfiguration.mountPath` | Mount path of secret configuration secret (this can be templated). | `""` |
|
||||||
| `secretConfiguration.data` | Secret configuration secret data. Could be used to store DNS provider credentials. | `{}` |
|
| `secretConfiguration.data` | Secret configuration secret data. Could be used to store DNS provider credentials. | `{}` |
|
||||||
| `secretConfiguration.subPath` | Sub-path of secret configuration secret (this can be templated). | `""` |
|
| `secretConfiguration.subPath` | Sub-path of secret configuration secret (this can be templated). | `""` |
|
||||||
|
| `resolveLoadBalancerHostname` | Resolve the hostname of LoadBalancer-type Service object to IP addresses in order to create DNS A/AAAA records instead of CNAMEs | `false` |
|
||||||
|
|
||||||
## Namespaced scoped installation
|
## Namespaced scoped installation
|
||||||
|
|
||||||
external-dns supports running on a namespaced only scope, too.
|
external-dns supports running on a namespaced only scope, too.
|
||||||
If `namespaced=true` is defined, the helm chart will setup `Roles` and `RoleBindings` instead `ClusterRoles` and `ClusterRoleBindings`.
|
If `namespaced=true` is defined, the helm chart will setup `Roles` and `RoleBindings` instead `ClusterRoles` and `ClusterRoleBindings`.
|
||||||
|
|
||||||
### Limited supported
|
### Limited supported
|
||||||
Not all sources are supported in namespaced scope, since some sources depends on cluster-wide resources.
|
Not all sources are supported in namespaced scope, since some sources depends on cluster-wide resources.
|
||||||
For example: Source `node` isn't supported, since `kind: Node` has scope `Cluster`.
|
For example: Source `node` isn't supported, since `kind: Node` has scope `Cluster`.
|
||||||
Sources like `istio-virtualservice` only work, if all resources like `Gateway` and `VirtualService` are present in the same
|
Sources like `istio-virtualservice` only work, if all resources like `Gateway` and `VirtualService` are present in the same
|
||||||
namespaces as `external-dns`.
|
namespaces as `external-dns`.
|
||||||
|
|
||||||
The annotation `external-dns.alpha.kubernetes.io/endpoints-type: NodeExternalIP` is not supported.
|
The annotation `external-dns.alpha.kubernetes.io/endpoints-type: NodeExternalIP` is not supported.
|
||||||
|
@ -96,6 +96,9 @@ spec:
|
|||||||
- --domain-filter={{ . }}
|
- --domain-filter={{ . }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
- --provider={{ tpl .Values.provider $ }}
|
- --provider={{ tpl .Values.provider $ }}
|
||||||
|
{{- if .Values.resolveLoadBalancerHostname }}
|
||||||
|
- --resolve-load-balancer-hostname
|
||||||
|
{{- end }}
|
||||||
{{- range .Values.extraArgs }}
|
{{- range .Values.extraArgs }}
|
||||||
- {{ tpl . $ }}
|
- {{ tpl . $ }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
@ -15,6 +15,8 @@ fullnameOverride: ""
|
|||||||
|
|
||||||
commonLabels: {}
|
commonLabels: {}
|
||||||
|
|
||||||
|
resolveLoadBalancerHostname: false
|
||||||
|
|
||||||
serviceAccount:
|
serviceAccount:
|
||||||
# Specifies whether a service account should be created
|
# Specifies whether a service account should be created
|
||||||
create: true
|
create: true
|
||||||
|
1
main.go
1
main.go
@ -141,6 +141,7 @@ func main() {
|
|||||||
DefaultTargets: cfg.DefaultTargets,
|
DefaultTargets: cfg.DefaultTargets,
|
||||||
OCPRouterName: cfg.OCPRouterName,
|
OCPRouterName: cfg.OCPRouterName,
|
||||||
UpdateEvents: cfg.UpdateEvents,
|
UpdateEvents: cfg.UpdateEvents,
|
||||||
|
ResolveLoadBalancerHostname: cfg.ResolveLoadBalancerHostname,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lookup all the selected sources by names and pass them the desired configuration.
|
// Lookup all the selected sources by names and pass them the desired configuration.
|
||||||
|
@ -166,6 +166,7 @@ type Config struct {
|
|||||||
CFAPIEndpoint string
|
CFAPIEndpoint string
|
||||||
CFUsername string
|
CFUsername string
|
||||||
CFPassword string
|
CFPassword string
|
||||||
|
ResolveLoadBalancerHostname bool
|
||||||
RFC2136Host string
|
RFC2136Host string
|
||||||
RFC2136Port int
|
RFC2136Port int
|
||||||
RFC2136Zone string
|
RFC2136Zone string
|
||||||
@ -390,6 +391,7 @@ func (cfg *Config) ParseFlags(args []string) error {
|
|||||||
app.Flag("server", "The Kubernetes API server to connect to (default: auto-detect)").Default(defaultConfig.APIServerURL).StringVar(&cfg.APIServerURL)
|
app.Flag("server", "The Kubernetes API server to connect to (default: auto-detect)").Default(defaultConfig.APIServerURL).StringVar(&cfg.APIServerURL)
|
||||||
app.Flag("kubeconfig", "Retrieve target cluster configuration from a Kubernetes configuration file (default: auto-detect)").Default(defaultConfig.KubeConfig).StringVar(&cfg.KubeConfig)
|
app.Flag("kubeconfig", "Retrieve target cluster configuration from a Kubernetes configuration file (default: auto-detect)").Default(defaultConfig.KubeConfig).StringVar(&cfg.KubeConfig)
|
||||||
app.Flag("request-timeout", "Request timeout when calling Kubernetes APIs. 0s means no timeout").Default(defaultConfig.RequestTimeout.String()).DurationVar(&cfg.RequestTimeout)
|
app.Flag("request-timeout", "Request timeout when calling Kubernetes APIs. 0s means no timeout").Default(defaultConfig.RequestTimeout.String()).DurationVar(&cfg.RequestTimeout)
|
||||||
|
app.Flag("resolve-lb-hostname", "Resolve the hostname of LoadBalancer-type Service object to IP addresses in order to create DNS A/AAAA records instead of CNAMEs").BoolVar(&cfg.ResolveLoadBalancerHostname)
|
||||||
|
|
||||||
// Flags related to cloud foundry
|
// Flags related to cloud foundry
|
||||||
app.Flag("cf-api-endpoint", "The fully-qualified domain name of the cloud foundry instance you are targeting").Default(defaultConfig.CFAPIEndpoint).StringVar(&cfg.CFAPIEndpoint)
|
app.Flag("cf-api-endpoint", "The fully-qualified domain name of the cloud foundry instance you are targeting").Default(defaultConfig.CFAPIEndpoint).StringVar(&cfg.CFAPIEndpoint)
|
||||||
|
@ -19,6 +19,7 @@ package source
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
@ -57,6 +58,7 @@ type serviceSource struct {
|
|||||||
publishInternal bool
|
publishInternal bool
|
||||||
publishHostIP bool
|
publishHostIP bool
|
||||||
alwaysPublishNotReadyAddresses bool
|
alwaysPublishNotReadyAddresses bool
|
||||||
|
resolveLoadBalancerHostname bool
|
||||||
serviceInformer coreinformers.ServiceInformer
|
serviceInformer coreinformers.ServiceInformer
|
||||||
endpointsInformer coreinformers.EndpointsInformer
|
endpointsInformer coreinformers.EndpointsInformer
|
||||||
podInformer coreinformers.PodInformer
|
podInformer coreinformers.PodInformer
|
||||||
@ -66,7 +68,7 @@ type serviceSource struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewServiceSource creates a new serviceSource with the given config.
|
// NewServiceSource creates a new serviceSource with the given config.
|
||||||
func NewServiceSource(ctx context.Context, kubeClient kubernetes.Interface, namespace, annotationFilter string, fqdnTemplate string, combineFqdnAnnotation bool, compatibility string, publishInternal bool, publishHostIP bool, alwaysPublishNotReadyAddresses bool, serviceTypeFilter []string, ignoreHostnameAnnotation bool, labelSelector labels.Selector) (Source, error) {
|
func NewServiceSource(ctx context.Context, kubeClient kubernetes.Interface, namespace, annotationFilter string, fqdnTemplate string, combineFqdnAnnotation bool, compatibility string, publishInternal bool, publishHostIP bool, alwaysPublishNotReadyAddresses bool, serviceTypeFilter []string, ignoreHostnameAnnotation bool, labelSelector labels.Selector, resolveLoadBalancerHostname bool) (Source, error) {
|
||||||
tmpl, err := parseTemplate(fqdnTemplate)
|
tmpl, err := parseTemplate(fqdnTemplate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -137,6 +139,7 @@ func NewServiceSource(ctx context.Context, kubeClient kubernetes.Interface, name
|
|||||||
nodeInformer: nodeInformer,
|
nodeInformer: nodeInformer,
|
||||||
serviceTypeFilter: serviceTypes,
|
serviceTypeFilter: serviceTypes,
|
||||||
labelSelector: labelSelector,
|
labelSelector: labelSelector,
|
||||||
|
resolveLoadBalancerHostname: resolveLoadBalancerHostname,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -480,7 +483,7 @@ func (sc *serviceSource) generateEndpoints(svc *v1.Service, hostname string, pro
|
|||||||
if useClusterIP {
|
if useClusterIP {
|
||||||
targets = append(targets, extractServiceIps(svc)...)
|
targets = append(targets, extractServiceIps(svc)...)
|
||||||
} else {
|
} else {
|
||||||
targets = append(targets, extractLoadBalancerTargets(svc)...)
|
targets = append(targets, extractLoadBalancerTargets(svc, sc.resolveLoadBalancerHostname)...)
|
||||||
}
|
}
|
||||||
case v1.ServiceTypeClusterIP:
|
case v1.ServiceTypeClusterIP:
|
||||||
if sc.publishInternal {
|
if sc.publishInternal {
|
||||||
@ -540,7 +543,7 @@ func extractServiceExternalName(svc *v1.Service) endpoint.Targets {
|
|||||||
return endpoint.Targets{svc.Spec.ExternalName}
|
return endpoint.Targets{svc.Spec.ExternalName}
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractLoadBalancerTargets(svc *v1.Service) endpoint.Targets {
|
func extractLoadBalancerTargets(svc *v1.Service, resolveLoadBalancerHostname bool) endpoint.Targets {
|
||||||
var (
|
var (
|
||||||
targets endpoint.Targets
|
targets endpoint.Targets
|
||||||
externalIPs endpoint.Targets
|
externalIPs endpoint.Targets
|
||||||
@ -552,7 +555,18 @@ func extractLoadBalancerTargets(svc *v1.Service) endpoint.Targets {
|
|||||||
targets = append(targets, lb.IP)
|
targets = append(targets, lb.IP)
|
||||||
}
|
}
|
||||||
if lb.Hostname != "" {
|
if lb.Hostname != "" {
|
||||||
targets = append(targets, lb.Hostname)
|
if resolveLoadBalancerHostname {
|
||||||
|
ips, err := net.LookupIP(lb.Hostname)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Unable to resolve %q: %v", lb.Hostname, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, ip := range ips {
|
||||||
|
targets = append(targets, ip.String())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
targets = append(targets, lb.Hostname)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,6 +78,7 @@ func (suite *ServiceSuite) SetupTest() {
|
|||||||
[]string{},
|
[]string{},
|
||||||
false,
|
false,
|
||||||
labels.Everything(),
|
labels.Everything(),
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
suite.NoError(err, "should initialize service source")
|
suite.NoError(err, "should initialize service source")
|
||||||
}
|
}
|
||||||
@ -158,6 +159,7 @@ func testServiceSourceNewServiceSource(t *testing.T) {
|
|||||||
ti.serviceTypesFilter,
|
ti.serviceTypesFilter,
|
||||||
false,
|
false,
|
||||||
labels.Everything(),
|
labels.Everything(),
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
|
|
||||||
if ti.expectError {
|
if ti.expectError {
|
||||||
@ -174,25 +176,26 @@ func testServiceSourceEndpoints(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
title string
|
title string
|
||||||
targetNamespace string
|
targetNamespace string
|
||||||
annotationFilter string
|
annotationFilter string
|
||||||
svcNamespace string
|
svcNamespace string
|
||||||
svcName string
|
svcName string
|
||||||
svcType v1.ServiceType
|
svcType v1.ServiceType
|
||||||
compatibility string
|
compatibility string
|
||||||
fqdnTemplate string
|
fqdnTemplate string
|
||||||
combineFQDNAndAnnotation bool
|
combineFQDNAndAnnotation bool
|
||||||
ignoreHostnameAnnotation bool
|
ignoreHostnameAnnotation bool
|
||||||
labels map[string]string
|
labels map[string]string
|
||||||
annotations map[string]string
|
annotations map[string]string
|
||||||
clusterIP string
|
clusterIP string
|
||||||
externalIPs []string
|
externalIPs []string
|
||||||
lbs []string
|
lbs []string
|
||||||
serviceTypesFilter []string
|
serviceTypesFilter []string
|
||||||
expected []*endpoint.Endpoint
|
expected []*endpoint.Endpoint
|
||||||
expectError bool
|
expectError bool
|
||||||
serviceLabelSelector string
|
serviceLabelSelector string
|
||||||
|
resolveLoadBalancerHostname bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
title: "no annotated services return no endpoints",
|
title: "no annotated services return no endpoints",
|
||||||
@ -389,6 +392,24 @@ func testServiceSourceEndpoints(t *testing.T) {
|
|||||||
{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeCNAME, Targets: endpoint.Targets{"lb.example.com"}},
|
{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeCNAME, Targets: endpoint.Targets{"lb.example.com"}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "annotated services return an endpoint with hostname then resolve hostname",
|
||||||
|
svcNamespace: "testing",
|
||||||
|
svcName: "foo",
|
||||||
|
svcType: v1.ServiceTypeLoadBalancer,
|
||||||
|
labels: map[string]string{},
|
||||||
|
annotations: map[string]string{
|
||||||
|
hostnameAnnotationKey: "foo.example.org.",
|
||||||
|
},
|
||||||
|
externalIPs: []string{},
|
||||||
|
lbs: []string{"example.com"}, // Use a resolvable hostname for testing.
|
||||||
|
serviceTypesFilter: []string{},
|
||||||
|
resolveLoadBalancerHostname: true,
|
||||||
|
expected: []*endpoint.Endpoint{
|
||||||
|
{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"93.184.216.34"}},
|
||||||
|
{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2606:2800:220:1:248:1893:25c8:1946"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "annotated services can omit trailing dot",
|
title: "annotated services can omit trailing dot",
|
||||||
svcNamespace: "testing",
|
svcNamespace: "testing",
|
||||||
@ -1086,6 +1107,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
|||||||
tc.serviceTypesFilter,
|
tc.serviceTypesFilter,
|
||||||
tc.ignoreHostnameAnnotation,
|
tc.ignoreHostnameAnnotation,
|
||||||
sourceLabel,
|
sourceLabel,
|
||||||
|
tc.resolveLoadBalancerHostname,
|
||||||
)
|
)
|
||||||
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -1275,6 +1297,7 @@ func testMultipleServicesEndpoints(t *testing.T) {
|
|||||||
tc.serviceTypesFilter,
|
tc.serviceTypesFilter,
|
||||||
tc.ignoreHostnameAnnotation,
|
tc.ignoreHostnameAnnotation,
|
||||||
labels.Everything(),
|
labels.Everything(),
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -1440,6 +1463,7 @@ func TestClusterIpServices(t *testing.T) {
|
|||||||
[]string{},
|
[]string{},
|
||||||
tc.ignoreHostnameAnnotation,
|
tc.ignoreHostnameAnnotation,
|
||||||
labelSelector,
|
labelSelector,
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -2010,6 +2034,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
|
|||||||
[]string{},
|
[]string{},
|
||||||
tc.ignoreHostnameAnnotation,
|
tc.ignoreHostnameAnnotation,
|
||||||
labels.Everything(),
|
labels.Everything(),
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -2483,6 +2508,7 @@ func TestHeadlessServices(t *testing.T) {
|
|||||||
[]string{},
|
[]string{},
|
||||||
tc.ignoreHostnameAnnotation,
|
tc.ignoreHostnameAnnotation,
|
||||||
labels.Everything(),
|
labels.Everything(),
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -2840,6 +2866,7 @@ func TestHeadlessServicesHostIP(t *testing.T) {
|
|||||||
[]string{},
|
[]string{},
|
||||||
tc.ignoreHostnameAnnotation,
|
tc.ignoreHostnameAnnotation,
|
||||||
labels.Everything(),
|
labels.Everything(),
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -2952,6 +2979,7 @@ func TestExternalServices(t *testing.T) {
|
|||||||
[]string{},
|
[]string{},
|
||||||
tc.ignoreHostnameAnnotation,
|
tc.ignoreHostnameAnnotation,
|
||||||
labels.Everything(),
|
labels.Everything(),
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -3006,6 +3034,7 @@ func BenchmarkServiceEndpoints(b *testing.B) {
|
|||||||
[]string{},
|
[]string{},
|
||||||
false,
|
false,
|
||||||
labels.Everything(),
|
labels.Everything(),
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
require.NoError(b, err)
|
require.NoError(b, err)
|
||||||
|
|
||||||
|
@ -73,6 +73,7 @@ type Config struct {
|
|||||||
DefaultTargets []string
|
DefaultTargets []string
|
||||||
OCPRouterName string
|
OCPRouterName string
|
||||||
UpdateEvents bool
|
UpdateEvents bool
|
||||||
|
ResolveLoadBalancerHostname bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClientGenerator provides clients
|
// ClientGenerator provides clients
|
||||||
@ -215,7 +216,7 @@ func BuildWithConfig(ctx context.Context, source string, p ClientGenerator, cfg
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return NewServiceSource(ctx, client, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.Compatibility, cfg.PublishInternal, cfg.PublishHostIP, cfg.AlwaysPublishNotReadyAddresses, cfg.ServiceTypeFilter, cfg.IgnoreHostnameAnnotation, cfg.LabelFilter)
|
return NewServiceSource(ctx, client, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.Compatibility, cfg.PublishInternal, cfg.PublishHostIP, cfg.AlwaysPublishNotReadyAddresses, cfg.ServiceTypeFilter, cfg.IgnoreHostnameAnnotation, cfg.LabelFilter, cfg.ResolveLoadBalancerHostname)
|
||||||
case "ingress":
|
case "ingress":
|
||||||
client, err := p.KubeClient()
|
client, err := p.KubeClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Loading…
Reference in New Issue
Block a user