mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-05 17:16:59 +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.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). | `""` |
|
||||
| `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
|
||||
|
||||
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`.
|
||||
|
||||
### Limited supported
|
||||
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`.
|
||||
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`.
|
||||
|
||||
The annotation `external-dns.alpha.kubernetes.io/endpoints-type: NodeExternalIP` is not supported.
|
||||
|
@ -96,6 +96,9 @@ spec:
|
||||
- --domain-filter={{ . }}
|
||||
{{- end }}
|
||||
- --provider={{ tpl .Values.provider $ }}
|
||||
{{- if .Values.resolveLoadBalancerHostname }}
|
||||
- --resolve-load-balancer-hostname
|
||||
{{- end }}
|
||||
{{- range .Values.extraArgs }}
|
||||
- {{ tpl . $ }}
|
||||
{{- end }}
|
||||
|
@ -15,6 +15,8 @@ fullnameOverride: ""
|
||||
|
||||
commonLabels: {}
|
||||
|
||||
resolveLoadBalancerHostname: false
|
||||
|
||||
serviceAccount:
|
||||
# Specifies whether a service account should be created
|
||||
create: true
|
||||
|
1
main.go
1
main.go
@ -141,6 +141,7 @@ func main() {
|
||||
DefaultTargets: cfg.DefaultTargets,
|
||||
OCPRouterName: cfg.OCPRouterName,
|
||||
UpdateEvents: cfg.UpdateEvents,
|
||||
ResolveLoadBalancerHostname: cfg.ResolveLoadBalancerHostname,
|
||||
}
|
||||
|
||||
// Lookup all the selected sources by names and pass them the desired configuration.
|
||||
|
@ -166,6 +166,7 @@ type Config struct {
|
||||
CFAPIEndpoint string
|
||||
CFUsername string
|
||||
CFPassword string
|
||||
ResolveLoadBalancerHostname bool
|
||||
RFC2136Host string
|
||||
RFC2136Port int
|
||||
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("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("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
|
||||
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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/template"
|
||||
@ -57,6 +58,7 @@ type serviceSource struct {
|
||||
publishInternal bool
|
||||
publishHostIP bool
|
||||
alwaysPublishNotReadyAddresses bool
|
||||
resolveLoadBalancerHostname bool
|
||||
serviceInformer coreinformers.ServiceInformer
|
||||
endpointsInformer coreinformers.EndpointsInformer
|
||||
podInformer coreinformers.PodInformer
|
||||
@ -66,7 +68,7 @@ type serviceSource struct {
|
||||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -137,6 +139,7 @@ func NewServiceSource(ctx context.Context, kubeClient kubernetes.Interface, name
|
||||
nodeInformer: nodeInformer,
|
||||
serviceTypeFilter: serviceTypes,
|
||||
labelSelector: labelSelector,
|
||||
resolveLoadBalancerHostname: resolveLoadBalancerHostname,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -480,7 +483,7 @@ func (sc *serviceSource) generateEndpoints(svc *v1.Service, hostname string, pro
|
||||
if useClusterIP {
|
||||
targets = append(targets, extractServiceIps(svc)...)
|
||||
} else {
|
||||
targets = append(targets, extractLoadBalancerTargets(svc)...)
|
||||
targets = append(targets, extractLoadBalancerTargets(svc, sc.resolveLoadBalancerHostname)...)
|
||||
}
|
||||
case v1.ServiceTypeClusterIP:
|
||||
if sc.publishInternal {
|
||||
@ -540,7 +543,7 @@ func extractServiceExternalName(svc *v1.Service) endpoint.Targets {
|
||||
return endpoint.Targets{svc.Spec.ExternalName}
|
||||
}
|
||||
|
||||
func extractLoadBalancerTargets(svc *v1.Service) endpoint.Targets {
|
||||
func extractLoadBalancerTargets(svc *v1.Service, resolveLoadBalancerHostname bool) endpoint.Targets {
|
||||
var (
|
||||
targets endpoint.Targets
|
||||
externalIPs endpoint.Targets
|
||||
@ -552,7 +555,18 @@ func extractLoadBalancerTargets(svc *v1.Service) endpoint.Targets {
|
||||
targets = append(targets, lb.IP)
|
||||
}
|
||||
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{},
|
||||
false,
|
||||
labels.Everything(),
|
||||
false,
|
||||
)
|
||||
suite.NoError(err, "should initialize service source")
|
||||
}
|
||||
@ -158,6 +159,7 @@ func testServiceSourceNewServiceSource(t *testing.T) {
|
||||
ti.serviceTypesFilter,
|
||||
false,
|
||||
labels.Everything(),
|
||||
false,
|
||||
)
|
||||
|
||||
if ti.expectError {
|
||||
@ -174,25 +176,26 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tc := range []struct {
|
||||
title string
|
||||
targetNamespace string
|
||||
annotationFilter string
|
||||
svcNamespace string
|
||||
svcName string
|
||||
svcType v1.ServiceType
|
||||
compatibility string
|
||||
fqdnTemplate string
|
||||
combineFQDNAndAnnotation bool
|
||||
ignoreHostnameAnnotation bool
|
||||
labels map[string]string
|
||||
annotations map[string]string
|
||||
clusterIP string
|
||||
externalIPs []string
|
||||
lbs []string
|
||||
serviceTypesFilter []string
|
||||
expected []*endpoint.Endpoint
|
||||
expectError bool
|
||||
serviceLabelSelector string
|
||||
title string
|
||||
targetNamespace string
|
||||
annotationFilter string
|
||||
svcNamespace string
|
||||
svcName string
|
||||
svcType v1.ServiceType
|
||||
compatibility string
|
||||
fqdnTemplate string
|
||||
combineFQDNAndAnnotation bool
|
||||
ignoreHostnameAnnotation bool
|
||||
labels map[string]string
|
||||
annotations map[string]string
|
||||
clusterIP string
|
||||
externalIPs []string
|
||||
lbs []string
|
||||
serviceTypesFilter []string
|
||||
expected []*endpoint.Endpoint
|
||||
expectError bool
|
||||
serviceLabelSelector string
|
||||
resolveLoadBalancerHostname bool
|
||||
}{
|
||||
{
|
||||
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"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
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",
|
||||
svcNamespace: "testing",
|
||||
@ -1086,6 +1107,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
tc.serviceTypesFilter,
|
||||
tc.ignoreHostnameAnnotation,
|
||||
sourceLabel,
|
||||
tc.resolveLoadBalancerHostname,
|
||||
)
|
||||
|
||||
require.NoError(t, err)
|
||||
@ -1275,6 +1297,7 @@ func testMultipleServicesEndpoints(t *testing.T) {
|
||||
tc.serviceTypesFilter,
|
||||
tc.ignoreHostnameAnnotation,
|
||||
labels.Everything(),
|
||||
false,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
@ -1440,6 +1463,7 @@ func TestClusterIpServices(t *testing.T) {
|
||||
[]string{},
|
||||
tc.ignoreHostnameAnnotation,
|
||||
labelSelector,
|
||||
false,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
@ -2010,6 +2034,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
|
||||
[]string{},
|
||||
tc.ignoreHostnameAnnotation,
|
||||
labels.Everything(),
|
||||
false,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
@ -2483,6 +2508,7 @@ func TestHeadlessServices(t *testing.T) {
|
||||
[]string{},
|
||||
tc.ignoreHostnameAnnotation,
|
||||
labels.Everything(),
|
||||
false,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
@ -2840,6 +2866,7 @@ func TestHeadlessServicesHostIP(t *testing.T) {
|
||||
[]string{},
|
||||
tc.ignoreHostnameAnnotation,
|
||||
labels.Everything(),
|
||||
false,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
@ -2952,6 +2979,7 @@ func TestExternalServices(t *testing.T) {
|
||||
[]string{},
|
||||
tc.ignoreHostnameAnnotation,
|
||||
labels.Everything(),
|
||||
false,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
@ -3006,6 +3034,7 @@ func BenchmarkServiceEndpoints(b *testing.B) {
|
||||
[]string{},
|
||||
false,
|
||||
labels.Everything(),
|
||||
false,
|
||||
)
|
||||
require.NoError(b, err)
|
||||
|
||||
|
@ -73,6 +73,7 @@ type Config struct {
|
||||
DefaultTargets []string
|
||||
OCPRouterName string
|
||||
UpdateEvents bool
|
||||
ResolveLoadBalancerHostname bool
|
||||
}
|
||||
|
||||
// ClientGenerator provides clients
|
||||
@ -215,7 +216,7 @@ func BuildWithConfig(ctx context.Context, source string, p ClientGenerator, cfg
|
||||
if err != nil {
|
||||
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":
|
||||
client, err := p.KubeClient()
|
||||
if err != nil {
|
||||
|
Loading…
Reference in New Issue
Block a user